diff --git a/README.md b/README.md index 52c34296..c9e64094 100644 --- a/README.md +++ b/README.md @@ -102,15 +102,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` | This is only applied if `target` is set to `generic_package`. It defines the package name (`:package_name`) to upload asset file to. More information could be found at [Publish a package](https://docs.gitlab.com/user/packages/generic_packages/#publish-a-package) | `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/publish.js b/lib/publish.js index 4a8f9511..b6fa1493 100644 --- a/lib/publish.js +++ b/lib/publish.js @@ -28,7 +28,6 @@ export default async (pluginConfig, context) => { const { projectPath, projectApiUrl } = getProjectContext(context, gitlabUrl, gitlabApiUrl, repositoryUrl); const encodedGitTag = encodeURIComponent(gitTag); - const encodedVersion = encodeURIComponent(version); const apiOptions = { headers: { "PRIVATE-TOKEN": gitlabToken, @@ -76,6 +75,7 @@ export default async (pluginConfig, context) => { const filepath = asset.filepath ? template(asset.filepath)(context) : undefined; const target = asset.target ? template(asset.target)(context) : undefined; const status = asset.status ? template(asset.status)(context) : undefined; + const packageName = asset.packageName ? template(asset.packageName)(context) : "release"; if (_url) { assetsList.push({ label, rawUrl: _url, type, filepath }); @@ -103,17 +103,20 @@ export default async (pluginConfig, context) => { debug("file filepath: %o", filepath); debug("file target: %o", target); debug("file status: %o", status); + debug("package name: %o", packageName); let uploadEndpoint; let response; if (target === "generic_package") { // Upload generic packages + const encodedVersion = encodeURIComponent(version); + const encodedPackageName = encodeURIComponent(packageName); const encodedLabel = encodeURIComponent(label); // 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/package-lock.json b/package-lock.json index bf2dff37..f6c17717 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1852,6 +1852,40 @@ "node": ">=18" } }, + "node_modules/conventional-changelog-writer/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/conventional-changelog-writer/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/conventional-changelog-writer/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, "node_modules/conventional-commits-filter": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-5.0.0.tgz", @@ -3597,6 +3631,40 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/make-dir/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-dir/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, "node_modules/marked": { "version": "15.0.12", "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.12.tgz", @@ -7681,6 +7749,40 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/semver-diff/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver-diff/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver-diff/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, "node_modules/semver-regex": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-4.0.5.tgz", diff --git a/test/publish.test.js b/test/publish.test.js index 249e8bcf..8d798c5a 100644 --- a/test/publish.test.js +++ b/test/publish.test.js @@ -176,6 +176,117 @@ test.serial("Publish a release with generics", async (t) => { t.true(gitlab.isDone()); }); +test.serial("Publish a release with generics: with asset.packageName (fixed text)", 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: "microk8s", + }; + 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: with asset.packageName (template)", 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-alpha.1", + notes: "Test release note body", + version: "1.0.0-alpha.1", + channel: "alpha", + }; + 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: "${nextRelease.channel}", + }; + const assets = [generic]; + const encodedLabel = encodeURIComponent(generic.label); + const expectedUrl = `https://gitlab.com/api/v4/projects/${encodedProjectPath}/packages/generic/alpha/${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/alpha/${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";