From 3a9fb9a0cde27b2077aacf3b542b8e38f3b69ed5 Mon Sep 17 00:00:00 2001 From: awcjack Date: Mon, 16 May 2022 08:32:47 +0000 Subject: [PATCH] feat: allow adding URLs to release assets (#322) Co-authored-by: Florian Greinacher Fixes #154 --- README.md | 4 ++- lib/publish.js | 85 ++++++++++++++++++++++++++------------------ test/publish.test.js | 38 ++++++++++++++++++++ 3 files changed, 91 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index 564e8ede..38830f9d 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ The plugin can be configured in the [**semantic-release** configuration file](ht {"path": "dist/asset.min.css", "label": "CSS distribution"}, {"path": "dist/asset.min.js", "label": "JS distribution"}, {"path": "dist/asset.min.js", "label": "v${nextRelease.version}.js"} + {"url": "https://gitlab.com/gitlab-org/gitlab/-/blob/master/README.md"} ] }], ] @@ -95,7 +96,8 @@ Can be a [glob](https://github.com/isaacs/node-glob#glob-primer) or and `Array` | Property | Description | Default | | -------- | ----------------------------------------------------------------------------------------------------------- | ------------------------------------ | -| `path` | **Required.** A [glob](https://github.com/isaacs/node-glob#glob-primer) to identify the files to upload. | - | +| `path` | **Required**, unless `url` is set. A [glob](https://github.com/isaacs/node-glob#glob-primer) to identify the files to upload. | - | +| `url` | Alternative to setting `path` this provides the ability to add links to releases, e.g. URLs to container images. | - | | `label` | Short description of the file displayed on the GitLab release. Can be dynamically adjusted with [Lodash template](https://lodash.com/docs#template). Allows same variables as [`successComment`](#successComment). Ignored if `path` matches more than one file. | 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)). | `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. | - | diff --git a/lib/publish.js b/lib/publish.js index c467067c..eb2c408a 100644 --- a/lib/publish.js +++ b/lib/publish.js @@ -31,54 +31,69 @@ module.exports = async (pluginConfig, context) => { debug('milestones: %o', milestones); if (assets && assets.length > 0) { - const globbedAssets = await getAssets(context, assets); + // Skip glob if url is provided + const urlAssets = assets.filter((asset) => asset.url); + debug('url assets: %o', urlAssets); + const globbedAssets = await getAssets( + context, + assets.filter((asset) => !asset.url) + ); debug('globbed assets: %o', globbedAssets); + const allAssets = [...urlAssets, ...globbedAssets]; + debug('all assets: %o', allAssets); await Promise.all( - globbedAssets.map(async (asset) => { + allAssets.map(async (asset) => { const {path, type, filepath} = isPlainObject(asset) ? asset : {path: asset}; - const file = pathlib.resolve(cwd, path); const label = asset.label ? template(asset.label)(context) : undefined; - let fileStat; + const _url = asset.url; + if (_url) { + assetsList.push({label, rawUrl: _url, type, filepath}); + debug('use link from release setting: %s', _url); + } else { + const file = pathlib.resolve(cwd, path); - try { - fileStat = await stat(file); - } catch { - logger.error('The asset %s cannot be read, and will be ignored.', path); - return; - } + let fileStat; - if (!fileStat || !fileStat.isFile()) { - logger.error('The asset %s is not a file, and will be ignored.', path); - return; - } + try { + fileStat = await stat(file); + } catch { + logger.error('The asset %s cannot be read, and will be ignored.', path); + return; + } - debug('file path: %o', path); - debug('file label: %o', label); - debug('file type: %o', type); - debug('file filepath: %o', filepath); + if (!fileStat || !fileStat.isFile()) { + logger.error('The asset %s is not a file, and will be ignored.', path); + return; + } - // Uploaded assets to the project - const form = new FormData(); - form.append('file', createReadStream(file)); + debug('file path: %o', path); + debug('file label: %o', label); + debug('file type: %o', type); + debug('file filepath: %o', filepath); - const uploadEndpoint = urlJoin(gitlabApiUrl, `/projects/${encodedRepoId}/uploads`); + // Uploaded assets to the project + const form = new FormData(); + form.append('file', createReadStream(file)); - debug('POST-ing the file %s to %s', file, uploadEndpoint); + const uploadEndpoint = urlJoin(gitlabApiUrl, `/projects/${encodedRepoId}/uploads`); - 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('POST-ing the file %s to %s', file, uploadEndpoint); + + 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; + } - const {url, alt} = response; + const {url, alt} = response; - assetsList.push({label, alt, url, type, filepath}); + assetsList.push({label, alt, url, type, filepath}); - logger.log('Uploaded file: %s', url); + logger.log('Uploaded file: %s', url); + } }) ); } @@ -93,10 +108,10 @@ module.exports = async (pluginConfig, context) => { description: notes && notes.trim() ? notes : gitTag, milestones, assets: { - links: assetsList.map(({label, alt, url, type, filepath}) => { + links: assetsList.map(({label, alt, url, type, filepath, rawUrl}) => { return { name: label || alt, - url: urlJoin(gitlabUrl, repoId, url), + url: rawUrl || urlJoin(gitlabUrl, repoId, url), link_type: type, filepath, }; diff --git a/test/publish.test.js b/test/publish.test.js index 27a083e8..2fa7d98b 100644 --- a/test/publish.test.js +++ b/test/publish.test.js @@ -294,3 +294,41 @@ test.serial('Publish a release with missing release notes', async (t) => { t.deepEqual(t.context.log.args[0], ['Published GitLab release: %s', nextRelease.gitTag]); t.true(gitlab.isDone()); }); + +test.serial('Publish a release with an asset link', 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 link = { + label: 'README.md', + type: 'other', + url: 'https://gitlab.com/gitlab-org/gitlab/-/blob/master/README.md', + }; + const assets = [link]; + const gitlab = authenticate(env) + .post(`/projects/${encodedRepoId}/releases`, { + tag_name: nextRelease.gitTag, + description: nextRelease.notes, + assets: { + links: [ + { + name: 'README.md', + url: `https://gitlab.com/gitlab-org/gitlab/-/blob/master/README.md`, + link_type: 'other', + }, + ], + }, + }) + .reply(200); + + 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], ['Published GitLab release: %s', nextRelease.gitTag]); + t.true(gitlab.isDone()); +});