From 0e35c06319949d795fa05799a7ef8192d6c17c80 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 5 Oct 2025 11:14:03 +0000 Subject: [PATCH 1/5] Initial plan From 24023a794e38835fc33b91c68e021b8c11cf3127 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 5 Oct 2025 11:17:26 +0000 Subject: [PATCH 2/5] Initial plan for validating generic package labels Co-authored-by: fgreinacher <1540469+fgreinacher@users.noreply.github.com> --- package-lock.json | 5 ----- 1 file changed, 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7a17ecf2..67190c3f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -270,7 +270,6 @@ "integrity": "sha512-jOT8V1Ba5BdC79sKrRWDdMT5l1R+XNHTPR6CPWzUP2EcfAcvIHZWF0eAbmRcpOOP5gVIwnqNg0C4nvh6Abc3OA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@octokit/auth-token": "^6.0.0", "@octokit/graphql": "^9.0.1", @@ -1016,7 +1015,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -3742,7 +3740,6 @@ "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==", "dev": true, "license": "MIT", - "peer": true, "bin": { "marked": "bin/marked.js" }, @@ -6611,7 +6608,6 @@ "dev": true, "inBundle": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -7726,7 +7722,6 @@ "integrity": "sha512-phCkJ6pjDi9ANdhuF5ElS10GGdAKY6R1Pvt9lT3SFhOwM4T7QZE7MLpBDbNruUx/Q3gFD92/UOFringGipRqZA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@semantic-release/commit-analyzer": "^13.0.0-beta.1", "@semantic-release/error": "^4.0.0", From b90cf0233dcf6b2a2efe4afded7ac39988b47d42 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 5 Oct 2025 11:20:55 +0000 Subject: [PATCH 3/5] feat: validate generic package asset labels for GitLab API restrictions Co-authored-by: fgreinacher <1540469+fgreinacher@users.noreply.github.com> --- lib/definitions/errors.js | 13 +++ lib/verify.js | 27 ++++++ test/publish.test.js | 24 ++--- test/verify.test.js | 190 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 242 insertions(+), 12 deletions(-) diff --git a/lib/definitions/errors.js b/lib/definitions/errors.js index 51508538..a611b28c 100644 --- a/lib/definitions/errors.js +++ b/lib/definitions/errors.js @@ -39,6 +39,19 @@ Your configuration for the \`labels\` option is \`${stringify(labels)}\`.`, Your configuration for the \`assignee\` option is \`${stringify(assignee)}\`.`, }), + EINVALIDGENERICPACKAGELABEL: ({label, asset}) => ({ + message: 'Invalid label for generic package asset.', + details: `The label \`${label}\` for a generic package asset is invalid. GitLab generic package filenames must follow these rules: + +- Can contain: Letters (A-Z, a-z), Numbers (0-9), and special characters: . _ - + ~ @ / +- Cannot start with: ~ or @ +- Cannot end with: ~ or @ +- Cannot contain spaces + +Your asset configuration: \`${stringify(asset)}\` + +Please update the label to comply with [GitLab's package naming restrictions](https://docs.gitlab.com/ee/user/packages/generic_packages/#publish-a-package-file).`, + }), EINVALIDGITLABURL: () => ({ message: 'The git repository URL is not a valid GitLab URL.', details: `The **semantic-release** \`repositoryUrl\` option must a valid GitLab URL with the format \`/.git\`. diff --git a/lib/verify.js b/lib/verify.js index fdfc3b7f..03a5a8e9 100644 --- a/lib/verify.js +++ b/lib/verify.js @@ -45,6 +45,33 @@ export default async (pluginConfig, context) => { .filter(([option, value]) => !isValid(option, value)) .map(([option, value]) => getError(`EINVALID${option.toUpperCase()}`, { [option]: value })); + // Validate generic package labels + if (options.assets && Array.isArray(options.assets)) { + const isValidGenericPackageLabel = (label) => { + // GitLab generic package filename restrictions + // Can contain: A-Z, a-z, 0-9, . _ - + ~ @ / + // Cannot start with: ~ or @ + // Cannot end with: ~ or @ + // Cannot contain spaces + if (!label) return true; // label is optional + if (typeof label !== "string") return false; + if (/\s/.test(label)) return false; // no spaces + if (/^[~@]/.test(label)) return false; // cannot start with ~ or @ + if (/[~@]$/.test(label)) return false; // cannot end with ~ or @ + // Check if it only contains allowed characters + if (!/^[A-Za-z0-9._\-+~@/]+$/.test(label)) return false; + return true; + }; + + options.assets.forEach((asset) => { + if (isPlainObject(asset) && asset.target === "generic_package" && asset.label) { + if (!isValidGenericPackageLabel(asset.label)) { + errors.push(getError("EINVALIDGENERICPACKAGELABEL", { label: asset.label, asset })); + } + } + }); + } + if (!projectPath) { errors.push(getError("EINVALIDGITLABURL")); } diff --git a/test/publish.test.js b/test/publish.test.js index d12443a9..f7c1268f 100644 --- a/test/publish.test.js +++ b/test/publish.test.js @@ -141,7 +141,7 @@ test.serial("Publish a release with generics", async (t) => { const encodedGitTag = encodeURIComponent(nextRelease.gitTag); const encodedVersion = encodeURIComponent(nextRelease.version); const uploaded = { file: { url: "/uploads/file.css" } }; - const generic = { path: "file.css", label: "Style package", target: "generic_package", status: "hidden" }; + const generic = { path: "file.css", label: "style-package", target: "generic_package", status: "hidden" }; const assets = [generic]; const encodedLabel = encodeURIComponent(generic.label); const expectedUrl = `https://gitlab.com/api/v4/projects/${encodedProjectPath}/packages/generic/release/${encodedVersion}/${encodedLabel}`; @@ -152,7 +152,7 @@ test.serial("Publish a release with generics", async (t) => { assets: { links: [ { - name: "Style package", + name: "style-package", url: expectedUrl, link_type: "package", }, @@ -189,7 +189,7 @@ test.serial("Publish a release with generics: with asset.packageName (fixed text const uploaded = { file: { url: "/uploads/file.css" } }; const generic = { path: "file.css", - label: "Style package", + label: "style-package", target: "generic_package", status: "hidden", packageName: "microk8s", @@ -205,7 +205,7 @@ test.serial("Publish a release with generics: with asset.packageName (fixed text assets: { links: [ { - name: "Style package", + name: "style-package", url: expectedUrl, link_type: "package", }, @@ -248,7 +248,7 @@ test.serial("Publish a release with generics: with asset.packageName (template)" const uploaded = { file: { url: "/uploads/file.css" } }; const generic = { path: "file.css", - label: "Style package", + label: "style-package", target: "generic_package", status: "hidden", packageName: "${nextRelease.channel}", @@ -263,7 +263,7 @@ test.serial("Publish a release with generics: with asset.packageName (template)" assets: { links: [ { - name: "Style package", + name: "style-package", url: expectedUrl, link_type: "package", }, @@ -344,7 +344,7 @@ test.serial("Publish a release with generics and external storage provider (http const encodedGitTag = encodeURIComponent(nextRelease.gitTag); const encodedVersion = encodeURIComponent(nextRelease.version); const uploaded = { file: { url: "http://aws.example.com/bucket/gitlab/file.css" } }; - const generic = { path: "file.css", label: "Style package", target: "generic_package", status: "hidden" }; + const generic = { path: "file.css", label: "style-package", target: "generic_package", status: "hidden" }; const assets = [generic]; const encodedLabel = encodeURIComponent(generic.label); const expectedUrl = `https://gitlab.com/api/v4/projects/${encodedProjectPath}/packages/generic/release/${encodedVersion}/${encodedLabel}`; @@ -355,7 +355,7 @@ test.serial("Publish a release with generics and external storage provider (http assets: { links: [ { - name: "Style package", + name: "style-package", url: expectedUrl, link_type: "package", }, @@ -390,7 +390,7 @@ test.serial("Publish a release with generics and external storage provider (http const encodedGitTag = encodeURIComponent(nextRelease.gitTag); const encodedVersion = encodeURIComponent(nextRelease.version); const uploaded = { file: { url: "https://aws.example.com/bucket/gitlab/file.css" } }; - const generic = { path: "file.css", label: "Style package", target: "generic_package", status: "hidden" }; + const generic = { path: "file.css", label: "style-package", target: "generic_package", status: "hidden" }; const assets = [generic]; const encodedLabel = encodeURIComponent(generic.label); const expectedUrl = `https://gitlab.com/api/v4/projects/${encodedProjectPath}/packages/generic/release/${encodedVersion}/${encodedLabel}`; @@ -401,7 +401,7 @@ test.serial("Publish a release with generics and external storage provider (http assets: { links: [ { - name: "Style package", + name: "style-package", url: expectedUrl, link_type: "package", }, @@ -436,7 +436,7 @@ test.serial("Publish a release with generics and external storage provider (ftp) const encodedGitTag = encodeURIComponent(nextRelease.gitTag); const encodedVersion = encodeURIComponent(nextRelease.version); const uploaded = { file: { url: "ftp://drive.example.com/gitlab/file.css" } }; - const generic = { path: "file.css", label: "Style package", target: "generic_package", status: "hidden" }; + const generic = { path: "file.css", label: "style-package", target: "generic_package", status: "hidden" }; const assets = [generic]; const encodedLabel = encodeURIComponent(generic.label); const expectedUrl = `https://gitlab.com/api/v4/projects/${encodedProjectPath}/packages/generic/release/${encodedVersion}/${encodedLabel}`; @@ -447,7 +447,7 @@ test.serial("Publish a release with generics and external storage provider (ftp) assets: { links: [ { - name: "Style package", + name: "style-package", url: expectedUrl, link_type: "package", }, diff --git a/test/verify.test.js b/test/verify.test.js index 48f3004f..0bf31a78 100644 --- a/test/verify.test.js +++ b/test/verify.test.js @@ -988,3 +988,193 @@ test.serial( t.true(gitlab.isDone()); } ); + +test.serial("Throw SemanticReleaseError if generic package asset label contains spaces", async (t) => { + const owner = "test_user"; + const repo = "test_repo"; + const env = { GITLAB_TOKEN: "gitlab_token" }; + const assets = [{ path: "file.css", label: "Style package", target: "generic_package" }]; + const gitlab = authenticate(env) + .get(`/projects/${owner}%2F${repo}`) + .reply(200, { permissions: { project_access: { access_level: 40 } } }); + + const { + errors: [error], + } = await t.throwsAsync( + verify( + { assets }, + { env, options: { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` }, logger: t.context.logger } + ) + ); + t.is(error.name, "SemanticReleaseError"); + t.is(error.code, "EINVALIDGENERICPACKAGELABEL"); + t.true(gitlab.isDone()); +}); + +test.serial("Throw SemanticReleaseError if generic package asset label starts with ~", async (t) => { + const owner = "test_user"; + const repo = "test_repo"; + const env = { GITLAB_TOKEN: "gitlab_token" }; + const assets = [{ path: "file.css", label: "~invalid", target: "generic_package" }]; + const gitlab = authenticate(env) + .get(`/projects/${owner}%2F${repo}`) + .reply(200, { permissions: { project_access: { access_level: 40 } } }); + + const { + errors: [error], + } = await t.throwsAsync( + verify( + { assets }, + { env, options: { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` }, logger: t.context.logger } + ) + ); + t.is(error.name, "SemanticReleaseError"); + t.is(error.code, "EINVALIDGENERICPACKAGELABEL"); + t.true(gitlab.isDone()); +}); + +test.serial("Throw SemanticReleaseError if generic package asset label starts with @", async (t) => { + const owner = "test_user"; + const repo = "test_repo"; + const env = { GITLAB_TOKEN: "gitlab_token" }; + const assets = [{ path: "file.css", label: "@invalid", target: "generic_package" }]; + const gitlab = authenticate(env) + .get(`/projects/${owner}%2F${repo}`) + .reply(200, { permissions: { project_access: { access_level: 40 } } }); + + const { + errors: [error], + } = await t.throwsAsync( + verify( + { assets }, + { env, options: { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` }, logger: t.context.logger } + ) + ); + t.is(error.name, "SemanticReleaseError"); + t.is(error.code, "EINVALIDGENERICPACKAGELABEL"); + t.true(gitlab.isDone()); +}); + +test.serial("Throw SemanticReleaseError if generic package asset label ends with ~", async (t) => { + const owner = "test_user"; + const repo = "test_repo"; + const env = { GITLAB_TOKEN: "gitlab_token" }; + const assets = [{ path: "file.css", label: "invalid~", target: "generic_package" }]; + const gitlab = authenticate(env) + .get(`/projects/${owner}%2F${repo}`) + .reply(200, { permissions: { project_access: { access_level: 40 } } }); + + const { + errors: [error], + } = await t.throwsAsync( + verify( + { assets }, + { env, options: { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` }, logger: t.context.logger } + ) + ); + t.is(error.name, "SemanticReleaseError"); + t.is(error.code, "EINVALIDGENERICPACKAGELABEL"); + t.true(gitlab.isDone()); +}); + +test.serial("Throw SemanticReleaseError if generic package asset label ends with @", async (t) => { + const owner = "test_user"; + const repo = "test_repo"; + const env = { GITLAB_TOKEN: "gitlab_token" }; + const assets = [{ path: "file.css", label: "invalid@", target: "generic_package" }]; + const gitlab = authenticate(env) + .get(`/projects/${owner}%2F${repo}`) + .reply(200, { permissions: { project_access: { access_level: 40 } } }); + + const { + errors: [error], + } = await t.throwsAsync( + verify( + { assets }, + { env, options: { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` }, logger: t.context.logger } + ) + ); + t.is(error.name, "SemanticReleaseError"); + t.is(error.code, "EINVALIDGENERICPACKAGELABEL"); + t.true(gitlab.isDone()); +}); + +test.serial("Throw SemanticReleaseError if generic package asset label contains invalid characters", async (t) => { + const owner = "test_user"; + const repo = "test_repo"; + const env = { GITLAB_TOKEN: "gitlab_token" }; + const assets = [{ path: "file.css", label: "invalid$char", target: "generic_package" }]; + const gitlab = authenticate(env) + .get(`/projects/${owner}%2F${repo}`) + .reply(200, { permissions: { project_access: { access_level: 40 } } }); + + const { + errors: [error], + } = await t.throwsAsync( + verify( + { assets }, + { env, options: { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` }, logger: t.context.logger } + ) + ); + t.is(error.name, "SemanticReleaseError"); + t.is(error.code, "EINVALIDGENERICPACKAGELABEL"); + t.true(gitlab.isDone()); +}); + +test.serial("Does not throw for valid generic package asset labels", async (t) => { + const owner = "test_user"; + const repo = "test_repo"; + const env = { GITLAB_TOKEN: "gitlab_token" }; + const assets = [ + { path: "file.css", label: "valid-label_123.tar.gz", target: "generic_package" }, + { path: "file2.css", label: "another.valid+label~0", target: "generic_package" }, + { path: "file3.css", label: "path/to/file", target: "generic_package" }, + ]; + const gitlab = authenticate(env) + .get(`/projects/${owner}%2F${repo}`) + .reply(200, { permissions: { project_access: { access_level: 40 } } }); + + await t.notThrowsAsync( + verify( + { assets }, + { env, options: { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` }, logger: t.context.logger } + ) + ); + t.true(gitlab.isDone()); +}); + +test.serial("Does not throw for generic package assets without labels", async (t) => { + const owner = "test_user"; + const repo = "test_repo"; + const env = { GITLAB_TOKEN: "gitlab_token" }; + const assets = [{ path: "file.css", target: "generic_package" }]; + const gitlab = authenticate(env) + .get(`/projects/${owner}%2F${repo}`) + .reply(200, { permissions: { project_access: { access_level: 40 } } }); + + await t.notThrowsAsync( + verify( + { assets }, + { env, options: { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` }, logger: t.context.logger } + ) + ); + t.true(gitlab.isDone()); +}); + +test.serial("Does not throw for non-generic package assets with spaces in labels", async (t) => { + const owner = "test_user"; + const repo = "test_repo"; + const env = { GITLAB_TOKEN: "gitlab_token" }; + const assets = [{ path: "file.css", label: "Valid Label With Spaces" }]; + const gitlab = authenticate(env) + .get(`/projects/${owner}%2F${repo}`) + .reply(200, { permissions: { project_access: { access_level: 40 } } }); + + await t.notThrowsAsync( + verify( + { assets }, + { env, options: { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` }, logger: t.context.logger } + ) + ); + t.true(gitlab.isDone()); +}); From 0929b199d26ce2e56b5354f55780461622a7e05c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 5 Oct 2025 11:38:15 +0000 Subject: [PATCH 4/5] refactor: move generic package label validator to top level and consolidate tests Co-authored-by: fgreinacher <1540469+fgreinacher@users.noreply.github.com> --- lib/verify.js | 32 +++++------ test/verify.test.js | 132 +++++--------------------------------------- 2 files changed, 31 insertions(+), 133 deletions(-) diff --git a/lib/verify.js b/lib/verify.js index 03a5a8e9..86c66e59 100644 --- a/lib/verify.js +++ b/lib/verify.js @@ -13,6 +13,22 @@ const isStringOrStringArray = (value) => const isArrayOf = (validator) => (array) => isArray(array) && array.every((value) => validator(value)); const canBeDisabled = (validator) => (value) => value === false || validator(value); +const isValidGenericPackageLabel = (label) => { + // GitLab generic package filename restrictions + // Can contain: A-Z, a-z, 0-9, . _ - + ~ @ / + // Cannot start with: ~ or @ + // Cannot end with: ~ or @ + // Cannot contain spaces + if (!label) return true; // label is optional + if (typeof label !== "string") return false; + if (/\s/.test(label)) return false; // no spaces + if (/^[~@]/.test(label)) return false; // cannot start with ~ or @ + if (/[~@]$/.test(label)) return false; // cannot end with ~ or @ + // Check if it only contains allowed characters + if (!/^[A-Za-z0-9._\-+~@/]+$/.test(label)) return false; + return true; +}; + const VALIDATORS = { assets: isArrayOf( (asset) => @@ -47,22 +63,6 @@ export default async (pluginConfig, context) => { // Validate generic package labels if (options.assets && Array.isArray(options.assets)) { - const isValidGenericPackageLabel = (label) => { - // GitLab generic package filename restrictions - // Can contain: A-Z, a-z, 0-9, . _ - + ~ @ / - // Cannot start with: ~ or @ - // Cannot end with: ~ or @ - // Cannot contain spaces - if (!label) return true; // label is optional - if (typeof label !== "string") return false; - if (/\s/.test(label)) return false; // no spaces - if (/^[~@]/.test(label)) return false; // cannot start with ~ or @ - if (/[~@]$/.test(label)) return false; // cannot end with ~ or @ - // Check if it only contains allowed characters - if (!/^[A-Za-z0-9._\-+~@/]+$/.test(label)) return false; - return true; - }; - options.assets.forEach((asset) => { if (isPlainObject(asset) && asset.target === "generic_package" && asset.label) { if (!isValidGenericPackageLabel(asset.label)) { diff --git a/test/verify.test.js b/test/verify.test.js index 0bf31a78..220f2394 100644 --- a/test/verify.test.js +++ b/test/verify.test.js @@ -989,135 +989,33 @@ test.serial( } ); -test.serial("Throw SemanticReleaseError if generic package asset label contains spaces", async (t) => { +test.serial("Throw SemanticReleaseError if generic package asset labels are invalid", async (t) => { const owner = "test_user"; const repo = "test_repo"; const env = { GITLAB_TOKEN: "gitlab_token" }; - const assets = [{ path: "file.css", label: "Style package", target: "generic_package" }]; - const gitlab = authenticate(env) - .get(`/projects/${owner}%2F${repo}`) - .reply(200, { permissions: { project_access: { access_level: 40 } } }); - - const { - errors: [error], - } = await t.throwsAsync( - verify( - { assets }, - { env, options: { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` }, logger: t.context.logger } - ) - ); - t.is(error.name, "SemanticReleaseError"); - t.is(error.code, "EINVALIDGENERICPACKAGELABEL"); - t.true(gitlab.isDone()); -}); - -test.serial("Throw SemanticReleaseError if generic package asset label starts with ~", async (t) => { - const owner = "test_user"; - const repo = "test_repo"; - const env = { GITLAB_TOKEN: "gitlab_token" }; - const assets = [{ path: "file.css", label: "~invalid", target: "generic_package" }]; - const gitlab = authenticate(env) - .get(`/projects/${owner}%2F${repo}`) - .reply(200, { permissions: { project_access: { access_level: 40 } } }); - - const { - errors: [error], - } = await t.throwsAsync( - verify( - { assets }, - { env, options: { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` }, logger: t.context.logger } - ) - ); - t.is(error.name, "SemanticReleaseError"); - t.is(error.code, "EINVALIDGENERICPACKAGELABEL"); - t.true(gitlab.isDone()); -}); - -test.serial("Throw SemanticReleaseError if generic package asset label starts with @", async (t) => { - const owner = "test_user"; - const repo = "test_repo"; - const env = { GITLAB_TOKEN: "gitlab_token" }; - const assets = [{ path: "file.css", label: "@invalid", target: "generic_package" }]; - const gitlab = authenticate(env) - .get(`/projects/${owner}%2F${repo}`) - .reply(200, { permissions: { project_access: { access_level: 40 } } }); - - const { - errors: [error], - } = await t.throwsAsync( - verify( - { assets }, - { env, options: { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` }, logger: t.context.logger } - ) - ); - t.is(error.name, "SemanticReleaseError"); - t.is(error.code, "EINVALIDGENERICPACKAGELABEL"); - t.true(gitlab.isDone()); -}); - -test.serial("Throw SemanticReleaseError if generic package asset label ends with ~", async (t) => { - const owner = "test_user"; - const repo = "test_repo"; - const env = { GITLAB_TOKEN: "gitlab_token" }; - const assets = [{ path: "file.css", label: "invalid~", target: "generic_package" }]; - const gitlab = authenticate(env) - .get(`/projects/${owner}%2F${repo}`) - .reply(200, { permissions: { project_access: { access_level: 40 } } }); - - const { - errors: [error], - } = await t.throwsAsync( - verify( - { assets }, - { env, options: { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` }, logger: t.context.logger } - ) - ); - t.is(error.name, "SemanticReleaseError"); - t.is(error.code, "EINVALIDGENERICPACKAGELABEL"); - t.true(gitlab.isDone()); -}); - -test.serial("Throw SemanticReleaseError if generic package asset label ends with @", async (t) => { - const owner = "test_user"; - const repo = "test_repo"; - const env = { GITLAB_TOKEN: "gitlab_token" }; - const assets = [{ path: "file.css", label: "invalid@", target: "generic_package" }]; - const gitlab = authenticate(env) - .get(`/projects/${owner}%2F${repo}`) - .reply(200, { permissions: { project_access: { access_level: 40 } } }); - - const { - errors: [error], - } = await t.throwsAsync( - verify( - { assets }, - { env, options: { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` }, logger: t.context.logger } - ) - ); - t.is(error.name, "SemanticReleaseError"); - t.is(error.code, "EINVALIDGENERICPACKAGELABEL"); - t.true(gitlab.isDone()); -}); - -test.serial("Throw SemanticReleaseError if generic package asset label contains invalid characters", async (t) => { - const owner = "test_user"; - const repo = "test_repo"; - const env = { GITLAB_TOKEN: "gitlab_token" }; - const assets = [{ path: "file.css", label: "invalid$char", target: "generic_package" }]; + const assets = [ + { path: "file1.css", label: "Style package", target: "generic_package" }, // contains spaces + { path: "file2.css", label: "~invalid", target: "generic_package" }, // starts with ~ + { path: "file3.css", label: "@invalid", target: "generic_package" }, // starts with @ + { path: "file4.css", label: "invalid~", target: "generic_package" }, // ends with ~ + { path: "file5.css", label: "invalid@", target: "generic_package" }, // ends with @ + { path: "file6.css", label: "invalid$char", target: "generic_package" }, // contains invalid character + ]; const gitlab = authenticate(env) .get(`/projects/${owner}%2F${repo}`) .reply(200, { permissions: { project_access: { access_level: 40 } } }); - const { - errors: [error], - } = await t.throwsAsync( + const { errors } = await t.throwsAsync( verify( { assets }, { env, options: { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` }, logger: t.context.logger } ) ); - t.is(error.name, "SemanticReleaseError"); - t.is(error.code, "EINVALIDGENERICPACKAGELABEL"); + t.is(errors.length, 6); + errors.forEach((error) => { + t.is(error.name, "SemanticReleaseError"); + t.is(error.code, "EINVALIDGENERICPACKAGELABEL"); + }); t.true(gitlab.isDone()); }); From fb9c774b849894ebbf75bbf6a7a5b5a3b9f39e34 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 5 Oct 2025 17:42:36 +0000 Subject: [PATCH 5/5] Addressing PR comments Co-authored-by: fgreinacher <1540469+fgreinacher@users.noreply.github.com> --- lib/verify.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/verify.js b/lib/verify.js index 86c66e59..1cd6a1a8 100644 --- a/lib/verify.js +++ b/lib/verify.js @@ -61,7 +61,7 @@ export default async (pluginConfig, context) => { .filter(([option, value]) => !isValid(option, value)) .map(([option, value]) => getError(`EINVALID${option.toUpperCase()}`, { [option]: value })); - // Validate generic package labels + // Validate generic package labels with specific error codes if (options.assets && Array.isArray(options.assets)) { options.assets.forEach((asset) => { if (isPlainObject(asset) && asset.target === "generic_package" && asset.label) {