From cb7ce48abb65212de3c3017614856d72da21b2ea Mon Sep 17 00:00:00 2001 From: Jonas Schubert Date: Sun, 22 May 2022 15:35:30 +0200 Subject: [PATCH] feat(generics): allow upload of build assets as generic packages #174 --- README.md | 4 ++- lib/publish.js | 58 +++++++++++++++++++++++++++++++++++--------- test/publish.test.js | 44 +++++++++++++++++++++++++++++++++ 3 files changed, 93 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 4e93ef44..fa26ae6b 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ The plugin can be configured in the [**semantic-release** configuration file](ht "gitlabUrl": "https://custom.gitlab.com", "assets": [ { "path": "dist/asset.min.css", "label": "CSS distribution" }, - { "path": "dist/asset.min.js", "label": "JS distribution" }, + { "path": "dist/asset.min.js", "label": "JS distribution", "type": "generic_package" }, { "path": "dist/asset.min.js", "label": "v${nextRelease.version}.js" }, { "url": "https://gitlab.com/gitlab-org/gitlab/-/blob/master/README.md" } ] @@ -106,6 +106,8 @@ Can be a [glob](https://github.com/isaacs/node-glob#glob-primer) or and `Array` | `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` | 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 3d917c6d..b081dfa9 100644 --- a/lib/publish.js +++ b/lib/publish.js @@ -50,6 +50,9 @@ export default async (pluginConfig, context) => { const label = asset.label ? template(asset.label)(context) : undefined; 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 status = asset.status ? template(asset.status)(context) : undefined; + if (_url) { assetsList.push({ label, rawUrl: _url, type, filepath }); debug("use link from release setting: %s", _url); @@ -74,28 +77,59 @@ export default async (pluginConfig, context) => { debug("file label: %o", label); debug("file type: %o", type); debug("file filepath: %o", filepath); + debug("file target: %o", target); + debug("file status: %o", status); // Uploaded assets to the project const form = new FormData(); form.append("file", createReadStream(file)); - const uploadEndpoint = urlJoin(gitlabApiUrl, `/projects/${encodedRepoId}/uploads`); + let uploadEndpoint; + let response; - debug("POST-ing the file %s to %s", file, uploadEndpoint); + if (target === "generic_package") { + // Upload generic packages + const encodedLabel = encodeURIComponent(label); + uploadEndpoint = urlJoin( + gitlabApiUrl, + `/projects/${encodedRepoId}/packages/generic/release/${encodedGitTag}/${encodedLabel}?${ + status ? `status=${status}&` : "" + }select=package_file` + ); - let response; - try { - response = await got.post(uploadEndpoint, { ...apiOptions, ...proxy, body: form }).json(); - } catch (error) { - logger.error("An error occurred while uploading %s to the GitLab project uploads API:\n%O", file, error); - throw error; - } + debug("PUT-ing the file %s to %s", file, uploadEndpoint); + + try { + response = await got.put(uploadEndpoint, { ...apiOptions, body: form }).json(); + } catch (error) { + logger.error("An error occurred while uploading %s to the GitLab generics package API:\n%O", file, error); + throw error; + } - const { url, alt } = response; + const { url } = response.file; - assetsList.push({ label, alt, url, type, filepath }); + assetsList.push({ label, alt: "release", url, type: "package", filepath }); - logger.log("Uploaded file: %s", url); + logger.log("Uploaded file: %s", url); + } else { + // Handle normal assets + uploadEndpoint = urlJoin(gitlabApiUrl, `/projects/${encodedRepoId}/uploads`); + + debug("POST-ing the file %s to %s", file, uploadEndpoint); + + try { + response = await got.post(uploadEndpoint, { ...apiOptions, ...proxy, body: form }).json(); + } catch (error) { + logger.error("An error occurred while uploading %s to the GitLab project uploads API:\n%O", file, error); + throw error; + } + + const { url, alt } = response; + + assetsList.push({ label, alt, url, type, filepath }); + + logger.log("Uploaded file: %s", url); + } } }) ); diff --git a/test/publish.test.js b/test/publish.test.js index e0ecd661..4e15ebed 100644 --- a/test/publish.test.js +++ b/test/publish.test.js @@ -83,6 +83,50 @@ test.serial("Publish a release with assets", async (t) => { t.true(gitlab.isDone()); }); +test.serial("Publish a release with generics", 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" }; + const options = { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` }; + const encodedRepoId = encodeURIComponent(`${owner}/${repo}`); + const encodedGitTag = encodeURIComponent(nextRelease.gitTag); + const uploaded = { file: { url: "/uploads/file.css" } }; + const generic = { path: "file.css", label: "Style package", target: "generic_package", status: "hidden" }; + const assets = [generic]; + const encodedLabel = encodeURIComponent(generic.label); + const gitlab = authenticate(env) + .post(`/projects/${encodedRepoId}/releases`, { + tag_name: nextRelease.gitTag, + description: nextRelease.notes, + assets: { + links: [ + { + name: "Style package", + url: `https://gitlab.com/${owner}/${repo}${uploaded.file.url}`, + link_type: "package", + }, + ], + }, + }) + .reply(200); + const gitlabUpload = authenticate(env) + .put( + `/projects/${encodedRepoId}/packages/generic/release/${encodedGitTag}/${encodedLabel}?status=${generic.status}&select=package_file`, + /filename="file.css"/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", 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 asset type and permalink", async (t) => { const cwd = "test/fixtures/files"; const owner = "test_user";