diff --git a/README.md b/README.md index 52c34296..cd489def 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,7 @@ The plugin can be configured in the [**semantic-release** configuration file](ht "assets": [ { "path": "dist/asset.min.css", "label": "CSS distribution" }, { "path": "dist/asset.min.js", "label": "JS distribution", "target": "generic_package" }, + { "path": "file.css", "label": "Style package", "target": "generic_package", "packageName": "styles" }, { "path": "dist/asset.min.js", "label": "v${nextRelease.version}.js" }, { "url": "https://gitlab.com/gitlab-org/gitlab/-/blob/master/README.md", "label": "README.md" } ] @@ -102,15 +103,16 @@ If you need to bypass the proxy for some hosts, configure the `NO_PROXY` environ Can be a [glob](https://github.com/isaacs/node-glob#glob-primer) or and `Array` of [globs](https://github.com/isaacs/node-glob#glob-primer) and `Object`s with the following properties: -| Property | Description | Default | -| ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------ | -| `path` | **Required**, unless `url` is set. A [glob](https://github.com/isaacs/node-glob#glob-primer) to identify the files to upload. Supports [Lodash templating](https://lodash.com/docs#template). | - | -| `url` | Alternative to setting `path` this provides the ability to add links to releases, e.g. URLs to container images. Supports [Lodash templating](https://lodash.com/docs#template). | - | -| `label` | Short description of the file displayed on the GitLab release. Ignored if `path` matches more than one file. Supports [Lodash templating](https://lodash.com/docs#template). | File name extracted from the `path`. | -| `type` | Asset type displayed on the GitLab release. Can be `runbook`, `package`, `image` and `other` (see official documents on [release assets](https://docs.gitlab.com/ee/user/project/releases/#release-assets)). Supports [Lodash templating](https://lodash.com/docs#template). | `other` | -| `filepath` | A filepath for creating a permalink pointing to the asset (requires GitLab 12.9+, see official documents on [permanent links](https://docs.gitlab.com/ee/user/project/releases/#permanent-links-to-release-assets)). Ignored if `path` matches more than one file. Supports [Lodash templating](https://lodash.com/docs#template). | - | -| `target` | Controls where the file is uploaded to. Can be set to `project_upload` for storing the file as [project upload](https://docs.gitlab.com/ee/api/projects.html#upload-a-file) or `generic_package` for storing the file as [generic package](https://docs.gitlab.com/ee/user/packages/generic_packages/). | `project_upload` | -| `status` | This is only applied, if `target` is set to `generic_package`. The generic package status. Can be `default` and `hidden` (see official documents on [generic packages](https://docs.gitlab.com/ee/user/packages/generic_packages/)). | `default` | +| Property | Description | Default | +| ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------ | +| `path` | **Required**, unless `url` is set. A [glob](https://github.com/isaacs/node-glob#glob-primer) to identify the files to upload. Supports [Lodash templating](https://lodash.com/docs#template). | - | +| `url` | Alternative to setting `path` this provides the ability to add links to releases, e.g. URLs to container images. Supports [Lodash templating](https://lodash.com/docs#template). | - | +| `label` | Short description of the file displayed on the GitLab release. Ignored if `path` matches more than one file. Supports [Lodash templating](https://lodash.com/docs#template). | File name extracted from the `path`. | +| `type` | Asset type displayed on the GitLab release. Can be `runbook`, `package`, `image` and `other` (see official documents on [release assets](https://docs.gitlab.com/ee/user/project/releases/#release-assets)). Supports [Lodash templating](https://lodash.com/docs#template). | `other` | +| `filepath` | A filepath for creating a permalink pointing to the asset (requires GitLab 12.9+, see official documents on [permanent links](https://docs.gitlab.com/ee/user/project/releases/#permanent-links-to-release-assets)). Ignored if `path` matches more than one file. Supports [Lodash templating](https://lodash.com/docs#template). | - | +| `target` | Controls where the file is uploaded to. Can be set to `project_upload` for storing the file as [project upload](https://docs.gitlab.com/ee/api/projects.html#upload-a-file) or `generic_package` for storing the file as [generic package](https://docs.gitlab.com/ee/user/packages/generic_packages/). | `project_upload` | +| `packageName` | The name of the [generic package](https://docs.gitlab.com/ee/user/packages/generic_packages/) uploaded to the Gitlab package registry. Supports [Lodash templating](https://lodash.com/docs#template). | `release` | +| `status` | This is only applied, if `target` is set to `generic_package`. The generic package status. Can be `default` and `hidden` (see official documents on [generic packages](https://docs.gitlab.com/ee/user/packages/generic_packages/)). | `default` | Each entry in the `assets` `Array` is globbed individually. A [glob](https://github.com/isaacs/node-glob#glob-primer) can be a `String` (`"dist/**/*.js"` or `"dist/mylib.js"`) or an `Array` of `String`s that will be globbed together diff --git a/lib/definitions/constants.js b/lib/definitions/constants.js index 18f1ecd3..7f04c590 100644 --- a/lib/definitions/constants.js +++ b/lib/definitions/constants.js @@ -1,3 +1,3 @@ -export const HOME_URL = 'https://github.com/semantic-release/semantic-release'; +export const HOME_URL = "https://github.com/semantic-release/semantic-release"; -export const RELEASE_NAME = 'GitLab release'; +export const RELEASE_NAME = "GitLab release"; diff --git a/lib/definitions/errors.js b/lib/definitions/errors.js index 51508538..e8a55cc3 100644 --- a/lib/definitions/errors.js +++ b/lib/definitions/errors.js @@ -1,88 +1,88 @@ -import {inspect} from 'util'; +import { inspect } from "util"; import { createRequire } from "node:module"; const require = createRequire(import.meta.url); const pkg = require("../../package.json"); -const [homepage] = pkg.homepage.split('#'); +const [homepage] = pkg.homepage.split("#"); const linkify = (file) => `${homepage}/blob/master/${file}`; -const stringify = (object) => inspect(object, {breakLength: Number.POSITIVE_INFINITY, depth: 2, maxArrayLength: 5}); +const stringify = (object) => inspect(object, { breakLength: Number.POSITIVE_INFINITY, depth: 2, maxArrayLength: 5 }); export default { - EINVALIDASSETS: ({assets}) => ({ - message: 'Invalid `assets` option.', + EINVALIDASSETS: ({ assets }) => ({ + message: "Invalid `assets` option.", details: `The [assets option](${linkify( - 'README.md#assets' + "README.md#assets" )}) must be an \`Array\` of \`Strings\` or \`Objects\` with a \`path\` property. Your configuration for the \`assets\` option is \`${stringify(assets)}\`.`, }), - EINVALIDFAILTITLE: ({failTitle}) => ({ - message: 'Invalid `failTitle` option.', - details: `The [failTitle option](${linkify('README.md#failtitle')}) if defined, must be a non empty \`String\`. + EINVALIDFAILTITLE: ({ failTitle }) => ({ + message: "Invalid `failTitle` option.", + details: `The [failTitle option](${linkify("README.md#failtitle")}) if defined, must be a non empty \`String\`. Your configuration for the \`failTitle\` option is \`${stringify(failTitle)}\`.`, }), - EINVALIDFAILCOMMENT: ({failComment}) => ({ - message: 'Invalid `failComment` option.', - details: `The [failComment option](${linkify('README.md#failcomment')}) if defined, must be a non empty \`String\`. + EINVALIDFAILCOMMENT: ({ failComment }) => ({ + message: "Invalid `failComment` option.", + details: `The [failComment option](${linkify("README.md#failcomment")}) if defined, must be a non empty \`String\`. Your configuration for the \`failComment\` option is \`${stringify(failComment)}\`.`, }), - EINVALIDLABELS: ({labels}) => ({ - message: 'Invalid `labels` option.', - details: `The [labels option](${linkify('README.md#labels')}) if defined, must be a non empty \`String\`. + EINVALIDLABELS: ({ labels }) => ({ + message: "Invalid `labels` option.", + details: `The [labels option](${linkify("README.md#labels")}) if defined, must be a non empty \`String\`. Your configuration for the \`labels\` option is \`${stringify(labels)}\`.`, }), - EINVALIDASSIGNEE: ({assignee}) => ({ - message: 'Invalid `assignee` option.', - details: `The [assignee option](${linkify('README.md#assignee')}) if defined, must be a non empty \`String\`. + EINVALIDASSIGNEE: ({ assignee }) => ({ + message: "Invalid `assignee` option.", + details: `The [assignee option](${linkify("README.md#assignee")}) if defined, must be a non empty \`String\`. Your configuration for the \`assignee\` option is \`${stringify(assignee)}\`.`, }), EINVALIDGITLABURL: () => ({ - message: 'The git repository URL is not a valid GitLab URL.', + 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\`. By default the \`repositoryUrl\` option is retrieved from the \`repository\` property of your \`package.json\` or the [git origin url](https://git-scm.com/book/en/v2/Git-Basics-Working-with-Remotes) of the repository cloned by your CI environment.`, }), - EINVALIDGLTOKEN: ({projectPath}) => ({ - message: 'Invalid GitLab token.', + EINVALIDGLTOKEN: ({ projectPath }) => ({ + message: "Invalid GitLab token.", details: `The [GitLab token](${linkify( - 'README.md#gitlab-authentication' + "README.md#gitlab-authentication" )}) configured in the \`GL_TOKEN\` or \`GITLAB_TOKEN\` environment variable must be a valid [personal access token](https://docs.gitlab.com/ce/user/profile/personal_access_tokens.html) allowing to push to the repository ${projectPath}. Please make sure to set the \`GL_TOKEN\` or \`GITLAB_TOKEN\` environment variable in your CI with the exact value of the GitLab personal token.`, }), - EMISSINGREPO: ({projectPath}) => ({ + EMISSINGREPO: ({ projectPath }) => ({ message: `The repository ${projectPath} doesn't exist.`, details: `The **semantic-release** \`repositoryUrl\` option must refer to your GitLab repository. The repository must be accessible with the [GitLab API](https://docs.gitlab.com/ce/api/README.html). By default the \`repositoryUrl\` option is retrieved from the \`repository\` property of your \`package.json\` or the [git origin url](https://git-scm.com/book/en/v2/Git-Basics-Working-with-Remotes) of the repository cloned by your CI environment. If you are using [GitLab Enterprise Edition](https://about.gitlab.com/gitlab-ee) please make sure to configure the \`gitlabUrl\` and \`gitlabApiPathPrefix\` [options](${linkify( - 'README.md#options' + "README.md#options" )}).`, }), - EGLNOPUSHPERMISSION: ({projectPath}) => ({ + EGLNOPUSHPERMISSION: ({ projectPath }) => ({ message: `The GitLab token doesn't allow to push on the repository ${projectPath}.`, details: `The user associated with the [GitLab token](${linkify( - 'README.md#gitlab-authentication' + "README.md#gitlab-authentication" )}) configured in the \`GL_TOKEN\` or \`GITLAB_TOKEN\` environment variable must allows to push to the repository ${projectPath}. Please make sure the GitLab user associated with the token has the [permission to push](https://docs.gitlab.com/ee/user/permissions.html#project-members-permissions) to the repository ${projectPath}.`, }), - EGLNOPULLPERMISSION: ({projectPath}) => ({ + EGLNOPULLPERMISSION: ({ projectPath }) => ({ message: `The GitLab token doesn't allow to pull from the repository ${projectPath}.`, details: `The user associated with the [GitLab token](${linkify( - 'README.md#gitlab-authentication' + "README.md#gitlab-authentication" )}) configured in the \`GL_TOKEN\` or \`GITLAB_TOKEN\` environment variable must allow pull from the repository ${projectPath}. Please make sure the GitLab user associated with the token has the [permission to push](https://docs.gitlab.com/ee/user/permissions.html#project-members-permissions) to the repository ${projectPath}.`, }), - ENOGLTOKEN: ({repositoryUrl}) => ({ - message: 'No GitLab token specified.', + ENOGLTOKEN: ({ repositoryUrl }) => ({ + message: "No GitLab token specified.", details: `A [GitLab personal access token](${linkify( - 'README.md#gitlab-authentication' + "README.md#gitlab-authentication" )}) must be created and set in the \`GL_TOKEN\` or \`GITLAB_TOKEN\` environment variable on your CI environment. Please make sure to create a [GitLab personal access token](https://docs.gitlab.com/ce/user/profile/personal_access_tokens.html) and to set it in the \`GL_TOKEN\` or \`GITLAB_TOKEN\` environment variable on your CI environment. The token must allow to push to the repository ${repositoryUrl}.`, diff --git a/lib/publish.js b/lib/publish.js index 4a8f9511..93f3a4d0 100644 --- a/lib/publish.js +++ b/lib/publish.js @@ -75,6 +75,7 @@ export default async (pluginConfig, context) => { const type = asset.type ? template(asset.type)(context) : undefined; const filepath = asset.filepath ? template(asset.filepath)(context) : undefined; const target = asset.target ? template(asset.target)(context) : undefined; + const packageName = asset.packageName ? template(asset.packageName)(context) : "release"; const status = asset.status ? template(asset.status)(context) : undefined; if (_url) { @@ -102,6 +103,7 @@ export default async (pluginConfig, context) => { debug("file type: %o", type); debug("file filepath: %o", filepath); debug("file target: %o", target); + debug("file packageName: %o", packageName); debug("file status: %o", status); let uploadEndpoint; @@ -110,10 +112,11 @@ export default async (pluginConfig, context) => { if (target === "generic_package") { // Upload generic packages const encodedLabel = encodeURIComponent(label); + const encodedPackageName = encodeURIComponent(packageName); // https://docs.gitlab.com/ee/user/packages/generic_packages/#publish-a-package-file uploadEndpoint = urlJoin( projectApiUrl, - `packages/generic/release/${encodedVersion}/${encodedLabel}?${ + `packages/generic/${encodedPackageName}/${encodedVersion}/${encodedLabel}?${ status ? `status=${status}&` : "" }select=package_file` ); @@ -128,9 +131,12 @@ export default async (pluginConfig, context) => { } // https://docs.gitlab.com/ee/user/packages/generic_packages/#download-package-file - const url = urlJoin(projectApiUrl, `packages/generic/release/${encodedVersion}/${encodedLabel}`); + const url = urlJoin( + projectApiUrl, + `packages/generic/${encodedPackageName}/${encodedVersion}/${encodedLabel}` + ); - assetsList.push({ label, alt: "release", url, type: "package", filepath }); + assetsList.push({ label, alt: packageName, url, type: "package", filepath }); logger.log("Uploaded file: %s (%s)", url, response.file.url); } else { diff --git a/test/fixtures/files/file.css b/test/fixtures/files/file.css index 5d22e90c..1f49e0f5 100644 --- a/test/fixtures/files/file.css +++ b/test/fixtures/files/file.css @@ -1 +1,2 @@ -.test {} +.test { +} diff --git a/test/helpers/mock-gitlab.js b/test/helpers/mock-gitlab.js index d9c2eb7f..c05bbf7e 100644 --- a/test/helpers/mock-gitlab.js +++ b/test/helpers/mock-gitlab.js @@ -1,5 +1,5 @@ -import nock from 'nock'; -import urlJoin from 'url-join'; +import nock from "nock"; +import urlJoin from "url-join"; /** * Retun a `nock` object setup to respond to a GitLab authentication request. Other expectation and responses can be chained. @@ -13,14 +13,14 @@ import urlJoin from 'url-join'; export default function ( env = {}, { - gitlabToken = env.GL_TOKEN || env.GITLAB_TOKEN || 'GL_TOKEN', - gitlabUrl = env.GL_URL || env.GITLAB_URL || 'https://gitlab.com', - gitlabApiPathPrefix = typeof env.GL_PREFIX === 'string' + gitlabToken = env.GL_TOKEN || env.GITLAB_TOKEN || "GL_TOKEN", + gitlabUrl = env.GL_URL || env.GITLAB_URL || "https://gitlab.com", + gitlabApiPathPrefix = typeof env.GL_PREFIX === "string" ? env.GL_PREFIX - : null || typeof env.GITLAB_PREFIX === 'string' - ? env.GITLAB_PREFIX - : null || '/api/v4', + : null || typeof env.GITLAB_PREFIX === "string" + ? env.GITLAB_PREFIX + : null || "/api/v4", } = {} ) { - return nock(urlJoin(gitlabUrl, gitlabApiPathPrefix), {reqheaders: {'Private-Token': gitlabToken}}); -}; + return nock(urlJoin(gitlabUrl, gitlabApiPathPrefix), { reqheaders: { "Private-Token": gitlabToken } }); +} diff --git a/test/publish.test.js b/test/publish.test.js index 249e8bcf..bb42c914 100644 --- a/test/publish.test.js +++ b/test/publish.test.js @@ -176,6 +176,59 @@ test.serial("Publish a release with generics", async (t) => { t.true(gitlab.isDone()); }); +test.serial("Publish a release with generics, providing a packageName", async (t) => { + const cwd = "test/fixtures/files"; + const owner = "test_user"; + const repo = "test_repo"; + const env = { GITLAB_TOKEN: "gitlab_token" }; + const nextRelease = { gitHead: "123", gitTag: "v1.0.0", notes: "Test release note body", version: "1.0.0" }; + const options = { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` }; + const encodedProjectPath = encodeURIComponent(`${owner}/${repo}`); + 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", + packageName: "styles", + }; + const assets = [generic]; + const encodedLabel = encodeURIComponent(generic.label); + const encodedPackageName = encodeURIComponent(generic.packageName); + const expectedUrl = `https://gitlab.com/api/v4/projects/${encodedProjectPath}/packages/generic/${encodedPackageName}/${encodedVersion}/${encodedLabel}`; + const gitlab = authenticate(env) + .post(`/projects/${encodedProjectPath}/releases`, { + tag_name: nextRelease.gitTag, + description: nextRelease.notes, + assets: { + links: [ + { + name: "Style package", + url: expectedUrl, + link_type: "package", + }, + ], + }, + }) + .reply(200); + const gitlabUpload = authenticate(env) + .put( + `/projects/${encodedProjectPath}/packages/generic/${encodedPackageName}/${encodedVersion}/${encodedLabel}?status=${generic.status}&select=package_file`, + /\.test\s\{\}/gm + ) + .reply(200, uploaded); + + const result = await publish({ assets }, { env, cwd, options, nextRelease, logger: t.context.logger }); + + t.is(result.url, `https://gitlab.com/${owner}/${repo}/-/releases/${encodedGitTag}`); + t.deepEqual(t.context.log.args[0], ["Uploaded file: %s (%s)", expectedUrl, uploaded.file.url]); + t.deepEqual(t.context.log.args[1], ["Published GitLab release: %s", nextRelease.gitTag]); + t.true(gitlabUpload.isDone()); + t.true(gitlab.isDone()); +}); + test.serial("Publish a release with generics and external storage provider (http)", async (t) => { const cwd = "test/fixtures/files"; const owner = "test_user";