From a6fd1bd4597de773e8baf426831b6835cf219707 Mon Sep 17 00:00:00 2001 From: "Rasamoelina, Haja Onjatiana" <26148770+rhahao@users.noreply.github.com> Date: Thu, 21 Sep 2023 11:52:18 +0000 Subject: [PATCH 1/2] feat: add discussionCategoryName option --- README.md | 29 ++++---- index.js | 6 +- lib/definitions/errors.js | 13 ++++ lib/publish.js | 6 ++ lib/resolve-config.js | 4 ++ lib/verify.js | 1 + test/integration.test.js | 24 +++++-- test/publish.test.js | 131 +++++++++++++++++++++++++++++++++++- test/verify.test.js | 136 +++++++++++++++++++++++++++++++++++++- 9 files changed, 323 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 51b4eecb..0ae17b17 100644 --- a/README.md +++ b/README.md @@ -79,20 +79,21 @@ When using the _GITHUB_TOKEN_, the **minimum required permissions** are: ### Options -| Option | Description | Default | -| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- | -| `githubUrl` | The GitHub Enterprise endpoint. | `GH_URL` or `GITHUB_URL` environment variable. | -| `githubApiPathPrefix` | The GitHub Enterprise API prefix. | `GH_PREFIX` or `GITHUB_PREFIX` environment variable. | -| `proxy` | The proxy to use to access the GitHub API. Set to `false` to disable usage of proxy. See [proxy](#proxy). | `HTTP_PROXY` environment variable. | -| `assets` | An array of files to upload to the release. See [assets](#assets). | - | -| `successComment` | The comment to add to each issue and pull request resolved by the release. Set to `false` to disable commenting on issues and pull requests. See [successComment](#successcomment). | `:tada: This issue has been resolved in version ${nextRelease.version} :tada:\n\nThe release is available on [GitHub release]()` | -| `failComment` | The content of the issue created when a release fails. Set to `false` to disable opening an issue when a release fails. See [failComment](#failcomment). | Friendly message with links to **semantic-release** documentation and support, with the list of errors that caused the release to fail. | -| `failTitle` | The title of the issue created when a release fails. Set to `false` to disable opening an issue when a release fails. | `The automated release is failing 🚨` | -| `labels` | The [labels](https://help.github.com/articles/about-labels) to add to the issue created when a release fails. Set to `false` to not add any label. | `['semantic-release']` | -| `assignees` | The [assignees](https://help.github.com/articles/assigning-issues-and-pull-requests-to-other-github-users) to add to the issue created when a release fails. | - | -| `releasedLabels` | The [labels](https://help.github.com/articles/about-labels) to add to each issue and pull request resolved by the release. Set to `false` to not add any label. See [releasedLabels](#releasedlabels). | `['released<%= nextRelease.channel ? \` on @\${nextRelease.channel}\` : "" %>']- | -| `addReleases` | Will add release links to the GitHub Release. Can be `false`, `"bottom"` or `"top"`. See [addReleases](#addReleases). | `false` | -| `draftRelease` | A boolean indicating if a GitHub Draft Release should be created instead of publishing an actual GitHub Release. | `false` | +| Option | Description | Default | +| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- | +| `githubUrl` | The GitHub Enterprise endpoint. | `GH_URL` or `GITHUB_URL` environment variable. | +| `githubApiPathPrefix` | The GitHub Enterprise API prefix. | `GH_PREFIX` or `GITHUB_PREFIX` environment variable. | +| `proxy` | The proxy to use to access the GitHub API. Set to `false` to disable usage of proxy. See [proxy](#proxy). | `HTTP_PROXY` environment variable. | +| `assets` | An array of files to upload to the release. See [assets](#assets). | - | +| `successComment` | The comment to add to each issue and pull request resolved by the release. Set to `false` to disable commenting on issues and pull requests. See [successComment](#successcomment). | `:tada: This issue has been resolved in version ${nextRelease.version} :tada:\n\nThe release is available on [GitHub release]()` | +| `failComment` | The content of the issue created when a release fails. Set to `false` to disable opening an issue when a release fails. See [failComment](#failcomment). | Friendly message with links to **semantic-release** documentation and support, with the list of errors that caused the release to fail. | +| `failTitle` | The title of the issue created when a release fails. Set to `false` to disable opening an issue when a release fails. | `The automated release is failing 🚨` | +| `labels` | The [labels](https://help.github.com/articles/about-labels) to add to the issue created when a release fails. Set to `false` to not add any label. | `['semantic-release']` | +| `assignees` | The [assignees](https://help.github.com/articles/assigning-issues-and-pull-requests-to-other-github-users) to add to the issue created when a release fails. | - | +| `releasedLabels` | The [labels](https://help.github.com/articles/about-labels) to add to each issue and pull request resolved by the release. Set to `false` to not add any label. See [releasedLabels](#releasedlabels). | `['released<%= nextRelease.channel ? \` on @\${nextRelease.channel}\` : "" %>']- | +| `addReleases` | Will add release links to the GitHub Release. Can be `false`, `"bottom"` or `"top"`. See [addReleases](#addReleases). | `false` | +| `draftRelease` | A boolean indicating if a GitHub Draft Release should be created instead of publishing an actual GitHub Release. | `false` | +| `discussionCategoryName` | The category name in which to create a linked discussion to the release. Set to `false` to disable creating discussion for a release. | `false` | #### proxy diff --git a/index.js b/index.js index 9bd885b5..2d1aa8b8 100644 --- a/index.js +++ b/index.js @@ -17,7 +17,7 @@ export async function verifyConditions( { Octokit = SemanticReleaseOctokit } = {}, ) { const { options } = context; - // If the GitHub publish plugin is used and has `assets`, `successComment`, `failComment`, `failTitle`, `labels` or `assignees` configured, validate it now in order to prevent any release if the configuration is wrong + // If the GitHub publish plugin is used and has `assets`, `successComment`, `failComment`, `failTitle`, `labels`, `discussionCategoryName` or `assignees` configured, validate it now in order to prevent any release if the configuration is wrong if (options.publish) { const publishPlugin = castArray(options.publish).find( @@ -42,6 +42,10 @@ export async function verifyConditions( pluginConfig.assignees, publishPlugin.assignees, ); + pluginConfig.discussionCategoryName = defaultTo( + pluginConfig.discussionCategoryName, + publishPlugin.discussionCategoryName, + ); } await verifyGitHub(pluginConfig, context, { Octokit }); diff --git a/lib/definitions/errors.js b/lib/definitions/errors.js index 23683422..452bf8f5 100644 --- a/lib/definitions/errors.js +++ b/lib/definitions/errors.js @@ -195,3 +195,16 @@ export function ENOGHTOKEN({ owner, repo }) { Please make sure to create a [GitHub personal token](https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line) and to set it in the \`GH_TOKEN\` or \`GITHUB_TOKEN\` environment variable on your CI environment. The token must allow to push to the repository ${owner}/${repo}.`, }; } + +export function EINVALIDDISCUSSIONCATEGORYNAME({ discussionCategoryName }) { + return { + message: "Invalid `discussionCategoryName` option.", + details: `The [discussionCategoryName option](${linkify( + "README.md#discussionCategoryName", + )}) if defined, must be a non empty \`String\`. + +Your configuration for the \`discussionCategoryName\` option is \`${stringify( + discussionCategoryName, + )}\`.`, + }; +} diff --git a/lib/publish.js b/lib/publish.js index 07a6659e..9f2666b5 100644 --- a/lib/publish.js +++ b/lib/publish.js @@ -29,6 +29,7 @@ export default async function publish(pluginConfig, context, { Octokit }) { proxy, assets, draftRelease, + discussionCategoryName, } = resolveConfig(pluginConfig, context); const { owner, repo } = parseGithubUrl(repositoryUrl); const octokit = new Octokit( @@ -51,6 +52,11 @@ export default async function publish(pluginConfig, context, { Octokit }) { debug("release object: %O", release); + // If discussionCategoryName is not undefined or false + if (discussionCategoryName) { + release.discussion_category_name = discussionCategoryName; + } + const draftReleaseOptions = { ...release, draft: true }; // When there are no assets, we publish a release directly. diff --git a/lib/resolve-config.js b/lib/resolve-config.js index aa823dd8..f0109a11 100644 --- a/lib/resolve-config.js +++ b/lib/resolve-config.js @@ -14,6 +14,7 @@ export default function resolveConfig( releasedLabels, addReleases, draftRelease, + discussionCategoryName, }, { env }, ) { @@ -44,5 +45,8 @@ export default function resolveConfig( : castArray(releasedLabels), addReleases: isNil(addReleases) ? false : addReleases, draftRelease: isNil(draftRelease) ? false : draftRelease, + discussionCategoryName: isNil(discussionCategoryName) + ? false + : discussionCategoryName, }; } diff --git a/lib/verify.js b/lib/verify.js index d171f1b1..625b45d2 100644 --- a/lib/verify.js +++ b/lib/verify.js @@ -45,6 +45,7 @@ const VALIDATORS = { releasedLabels: canBeDisabled(isArrayOf(isNonEmptyString)), addReleases: canBeDisabled(oneOf(["bottom", "top"])), draftRelease: isBoolean, + discussionCategoryName: canBeDisabled(isNonEmptyString), }; export default async function verify(pluginConfig, context, { Octokit }) { diff --git a/test/integration.test.js b/test/integration.test.js index 2b5a7707..38877091 100644 --- a/test/integration.test.js +++ b/test/integration.test.js @@ -121,6 +121,7 @@ test("Throw SemanticReleaseError if invalid config", async (t) => { const failTitle = 42; const labels = 42; const assignees = 42; + const discussionCategoryName = 42; const options = { publish: [ { path: "@semantic-release/npm" }, @@ -132,6 +133,7 @@ test("Throw SemanticReleaseError if invalid config", async (t) => { failTitle, labels, assignees, + discussionCategoryName, }, ], repositoryUrl: "invalid_url", @@ -163,9 +165,11 @@ test("Throw SemanticReleaseError if invalid config", async (t) => { t.is(errors[5].name, "SemanticReleaseError"); t.is(errors[5].code, "EINVALIDASSIGNEES"); t.is(errors[6].name, "SemanticReleaseError"); - t.is(errors[6].code, "EINVALIDGITHUBURL"); + t.is(errors[6].code, "EINVALIDDISCUSSIONCATEGORYNAME"); t.is(errors[7].name, "SemanticReleaseError"); - t.is(errors[7].code, "ENOGHTOKEN"); + t.is(errors[7].code, "EINVALIDGITHUBURL"); + t.is(errors[8].name, "SemanticReleaseError"); + t.is(errors[8].code, "ENOGHTOKEN"); }); test("Publish a release with an array of assets", async (t) => { @@ -219,7 +223,9 @@ test("Publish a release with an array of assets", async (t) => { `${uploadOrigin}${uploadUri}?name=${encodeURIComponent( "upload_file_name.txt", )}&`, - { browser_download_url: assetUrl }, + { + browser_download_url: assetUrl, + }, ) .postOnce( `${uploadOrigin}${uploadUri}?name=${encodeURIComponent( @@ -676,12 +682,16 @@ test("Verify, release and notify success", async (t) => { `${uploadOrigin}${uploadUri}?name=other_file.txt&label=${encodeURIComponent( "Other File", )}`, - { browser_download_url: otherAssetUrl }, + { + browser_download_url: otherAssetUrl, + }, ) .postOnce( `https://api.github.local/repos/${owner}/${repo}/issues/1/comments`, - { html_url: "https://github.com/successcomment-1" }, + { + html_url: "https://github.com/successcomment-1", + }, ); await t.notThrowsAsync( @@ -804,7 +814,9 @@ test("Verify, update release and notify success", async (t) => { ) .postOnce( `https://api.github.local/repos/${owner}/${repo}/issues/1/comments`, - { html_url: "https://github.com/successcomment-1" }, + { + html_url: "https://github.com/successcomment-1", + }, ) .postOnce( `https://api.github.local/repos/${owner}/${repo}/issues/1/labels`, diff --git a/test/publish.test.js b/test/publish.test.js index 9f948d01..74cf0fde 100644 --- a/test/publish.test.js +++ b/test/publish.test.js @@ -18,7 +18,7 @@ test.beforeEach((t) => { t.context.logger = { log: t.context.log, error: t.context.error }; }); -test("Publish a release", async (t) => { +test("Publish a release without creating discussion", async (t) => { const owner = "test_user"; const repo = "test_repo"; const env = { GITHUB_TOKEN: "github_token" }; @@ -78,6 +78,68 @@ test("Publish a release", async (t) => { t.true(fetch.done()); }); +test("Publish a release and create discussion", async (t) => { + const owner = "test_user"; + const repo = "test_repo"; + const env = { GITHUB_TOKEN: "github_token" }; + const pluginConfig = { discussionCategoryName: "Announcements" }; + const nextRelease = { + gitTag: "v1.0.0", + name: "v1.0.0", + notes: "Test release note body", + version: "v1.0.0", + }; + const options = { repositoryUrl: `https://github.com/${owner}/${repo}.git` }; + const releaseUrl = `https://github.com/${owner}/${repo}/releases/${nextRelease.version}`; + const releaseId = 1; + const uploadUri = `/api/uploads/repos/${owner}/${repo}/releases/${releaseId}/assets`; + const uploadUrl = `https://github.com${uploadUri}{?name,label}`; + const branch = "test_branch"; + + const fetch = fetchMock.sandbox().postOnce( + `https://api.github.local/repos/${owner}/${repo}/releases`, + { + upload_url: uploadUrl, + html_url: releaseUrl, + }, + { + body: { + tag_name: nextRelease.gitTag, + target_commitish: branch, + name: nextRelease.name, + body: nextRelease.notes, + prerelease: false, + discussion_category_name: pluginConfig.discussionCategoryName, + }, + }, + ); + + const result = await publish( + pluginConfig, + { + cwd, + env, + options, + branch: { name: branch, type: "release", main: true }, + nextRelease, + logger: t.context.logger, + }, + { + Octokit: TestOctokit.defaults((options) => ({ + ...options, + request: { ...options.request, fetch }, + })), + }, + ); + + t.is(result.url, releaseUrl); + t.deepEqual(t.context.log.args[0], [ + "Published GitHub release: %s", + releaseUrl, + ]); + t.true(fetch.done()); +}); + test("Publish a release on a channel", async (t) => { const owner = "test_user"; const repo = "test_repo"; @@ -138,7 +200,7 @@ test("Publish a release on a channel", async (t) => { t.true(fetch.done()); }); -test("Publish a prerelease", async (t) => { +test("Publish a prerelease wihtout creating discussion", async (t) => { const owner = "test_user"; const repo = "test_repo"; const env = { GITHUB_TOKEN: "github_token" }; @@ -198,6 +260,67 @@ test("Publish a prerelease", async (t) => { t.true(fetch.done()); }); +test("Publish a prerelease and create discussion", async (t) => { + const owner = "test_user"; + const repo = "test_repo"; + const env = { GITHUB_TOKEN: "github_token" }; + const pluginConfig = { discussionCategoryName: "Announcements" }; + const nextRelease = { + gitTag: "v1.0.0", + name: "v1.0.0", + notes: "Test release note body", + }; + const options = { repositoryUrl: `https://github.com/${owner}/${repo}.git` }; + const releaseUrl = `https://github.com/${owner}/${repo}/releases/${nextRelease.version}`; + const releaseId = 1; + const uploadUri = `/api/uploads/repos/${owner}/${repo}/releases/${releaseId}/assets`; + const uploadUrl = `https://github.com${uploadUri}{?name,label}`; + const branch = "test_branch"; + + const fetch = fetchMock.sandbox().postOnce( + `https://api.github.local/repos/${owner}/${repo}/releases`, + { + upload_url: uploadUrl, + html_url: releaseUrl, + }, + { + body: { + tag_name: nextRelease.gitTag, + target_commitish: branch, + name: nextRelease.name, + body: nextRelease.notes, + prerelease: true, + discussion_category_name: pluginConfig.discussionCategoryName, + }, + }, + ); + + const result = await publish( + pluginConfig, + { + cwd, + env, + options, + branch: { name: branch, type: "prerelease", channel: "beta" }, + nextRelease, + logger: t.context.logger, + }, + { + Octokit: TestOctokit.defaults((options) => ({ + ...options, + request: { ...options.request, fetch }, + })), + }, + ); + + t.is(result.url, releaseUrl); + t.deepEqual(t.context.log.args[0], [ + "Published GitHub release: %s", + releaseUrl, + ]); + t.true(fetch.done()); +}); + test("Publish a maintenance release", async (t) => { const owner = "test_user"; const repo = "test_repo"; @@ -405,7 +528,9 @@ test("Publish a release with one asset and custom github url", async (t) => { `${env.GH_URL}${uploadUri}?name=${encodeURIComponent( "upload.txt", )}&label=${encodeURIComponent("A text file")}`, - { browser_download_url: assetUrl }, + { + browser_download_url: assetUrl, + }, ); const result = await publish( diff --git a/test/verify.test.js b/test/verify.test.js index 4ecc04b4..a646a785 100644 --- a/test/verify.test.js +++ b/test/verify.test.js @@ -25,6 +25,7 @@ test("Verify package, token and repository access", async (t) => { const failTitle = "Test title"; const failComment = "Test comment"; const labels = ["semantic-release"]; + const discussionCategoryName = "Announcements"; const fetch = fetchMock .sandbox() @@ -34,7 +35,15 @@ test("Verify package, token and repository access", async (t) => { await t.notThrowsAsync( verify( - { proxy, assets, successComment, failTitle, failComment, labels }, + { + proxy, + assets, + successComment, + failTitle, + failComment, + labels, + discussionCategoryName, + }, { env, options: { @@ -53,7 +62,7 @@ test("Verify package, token and repository access", async (t) => { t.true(fetch.done()); }); -test('Verify package, token and repository access with "proxy", "asset", "successComment", "failTitle", "failComment" and "label" set to "null"', async (t) => { +test('Verify package, token and repository access with "proxy", "asset", "discussionCategoryName", "successComment", "failTitle", "failComment" and "label" set to "null"', async (t) => { const owner = "test_user"; const repo = "test_repo"; const env = { GH_TOKEN: "github_token" }; @@ -63,6 +72,7 @@ test('Verify package, token and repository access with "proxy", "asset", "succes const failTitle = null; const failComment = null; const labels = null; + const discussionCategoryName = null; const fetch = fetchMock .sandbox() @@ -72,7 +82,15 @@ test('Verify package, token and repository access with "proxy", "asset", "succes await t.notThrowsAsync( verify( - { proxy, assets, successComment, failTitle, failComment, labels }, + { + proxy, + assets, + successComment, + failTitle, + failComment, + labels, + discussionCategoryName, + }, { env, options: { @@ -781,6 +799,7 @@ test("Verify if run in GitHub Action", async (t) => { const failTitle = "Test title"; const failComment = "Test comment"; const labels = ["semantic-release"]; + const discussionCategoryName = "Announcements"; await t.notThrowsAsync( verify( @@ -1459,6 +1478,117 @@ test('Throw SemanticReleaseError if "failTitle" option is a whitespace String', t.true(fetch.done()); }); +test('Throw SemanticReleaseError if "discussionCategoryName" option is not a String', async (t) => { + const owner = "test_user"; + const repo = "test_repo"; + const env = { GH_TOKEN: "github_token" }; + const discussionCategoryName = 42; + + const fetch = fetchMock + .sandbox() + .getOnce(`https://api.github.local/repos/${owner}/${repo}`, { + permissions: { push: true }, + }); + + const { + errors: [error, ...errors], + } = await t.throwsAsync( + verify( + { discussionCategoryName }, + { + env, + options: { repositoryUrl: `https://github.com/${owner}/${repo}.git` }, + logger: t.context.logger, + }, + { + Octokit: TestOctokit.defaults((options) => ({ + ...options, + request: { ...options.request, fetch }, + })), + }, + ), + ); + + t.is(errors.length, 0); + t.is(error.name, "SemanticReleaseError"); + t.is(error.code, "EINVALIDDISCUSSIONCATEGORYNAME"); + t.true(fetch.done()); +}); + +test('Throw SemanticReleaseError if "discussionCategoryName" option is an empty String', async (t) => { + const owner = "test_user"; + const repo = "test_repo"; + const env = { GH_TOKEN: "github_token" }; + const discussionCategoryName = ""; + + const fetch = fetchMock + .sandbox() + .getOnce(`https://api.github.local/repos/${owner}/${repo}`, { + permissions: { push: true }, + }); + + const { + errors: [error, ...errors], + } = await t.throwsAsync( + verify( + { discussionCategoryName }, + { + env, + options: { repositoryUrl: `https://github.com/${owner}/${repo}.git` }, + logger: t.context.logger, + }, + { + Octokit: TestOctokit.defaults((options) => ({ + ...options, + request: { ...options.request, fetch }, + })), + }, + ), + ); + + t.is(errors.length, 0); + t.is(error.name, "SemanticReleaseError"); + t.is(error.code, "EINVALIDDISCUSSIONCATEGORYNAME"); + t.true(fetch.done()); +}); + +test('Throw SemanticReleaseError if "discussionCategoryName" option is a whitespace String', async (t) => { + const owner = "test_user"; + const repo = "test_repo"; + const env = { GH_TOKEN: "github_token" }; + const discussionCategoryName = " \n \r "; + + const fetch = fetchMock + .sandbox() + .getOnce(`https://api.github.local/repos/${owner}/${repo}`, { + permissions: { push: true }, + }); + + const { + errors: [error, ...errors], + } = await t.throwsAsync( + verify( + { discussionCategoryName }, + { + env, + options: { repositoryUrl: `https://github.com/${owner}/${repo}.git` }, + logger: t.context.logger, + }, + { + Octokit: TestOctokit.defaults((options) => ({ + ...options, + request: { ...options.request, fetch }, + })), + }, + ), + ); + + t.is(errors.length, 0); + t.is(error.name, "SemanticReleaseError"); + t.is(error.code, "EINVALIDDISCUSSIONCATEGORYNAME"); + t.true(fetch.done()); +}); + test('Throw SemanticReleaseError if "failComment" option is not a String', async (t) => { const owner = "test_user"; const repo = "test_repo"; From ca8652490103ee8d049a65593de6a31b83ac80ba Mon Sep 17 00:00:00 2001 From: "Rasamoelina, Haja Onjatiana" <26148770+rhahao@users.noreply.github.com> Date: Tue, 26 Sep 2023 11:07:27 +0000 Subject: [PATCH 2/2] merge master --- .github/workflows/release.yml | 2 +- .github/workflows/test.yml | 4 +- README.md | 2 + lib/definitions/errors.js | 26 +++ lib/publish.js | 8 +- lib/resolve-config.js | 8 + lib/verify.js | 2 + package-lock.json | 295 ++++++++++++++++++---------------- package.json | 6 +- test/publish.test.js | 126 +++++++++++++++ test/verify.test.js | 72 +++++++++ 11 files changed, 404 insertions(+), 147 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a140d34d..3b631ff7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,7 +18,7 @@ jobs: name: release runs-on: ubuntu-latest steps: - - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4 + - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4 - uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3 with: cache: npm diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d851957e..d74b713d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -20,7 +20,7 @@ jobs: - ubuntu-latest runs-on: "${{ matrix.os }}" steps: - - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4 + - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4 - name: "Use Node.js ${{ matrix.node-version }}" uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3 with: @@ -32,7 +32,7 @@ jobs: runs-on: ubuntu-latest needs: test_matrix steps: - - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4 + - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4 - uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3 with: node-version: "lts/*" diff --git a/README.md b/README.md index 0ae17b17..69428aaf 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,8 @@ When using the _GITHUB_TOKEN_, the **minimum required permissions** are: | `releasedLabels` | The [labels](https://help.github.com/articles/about-labels) to add to each issue and pull request resolved by the release. Set to `false` to not add any label. See [releasedLabels](#releasedlabels). | `['released<%= nextRelease.channel ? \` on @\${nextRelease.channel}\` : "" %>']- | | `addReleases` | Will add release links to the GitHub Release. Can be `false`, `"bottom"` or `"top"`. See [addReleases](#addReleases). | `false` | | `draftRelease` | A boolean indicating if a GitHub Draft Release should be created instead of publishing an actual GitHub Release. | `false` | +| `releaseNameTemplate` | A [Lodash template](https://lodash.com/docs#template) to customize the github release's name | `<%= nextverison.name %>` | +| `releaseBodyTemplate` | A [Lodash template](https://lodash.com/docs#template) to customize the github release's body | `<%= nextverison.notes %>` | | `discussionCategoryName` | The category name in which to create a linked discussion to the release. Set to `false` to disable creating discussion for a release. | `false` | #### proxy diff --git a/lib/definitions/errors.js b/lib/definitions/errors.js index 452bf8f5..6dda6a62 100644 --- a/lib/definitions/errors.js +++ b/lib/definitions/errors.js @@ -196,6 +196,32 @@ Please make sure to create a [GitHub personal token](https://help.github.com/art }; } +export function EINVALIDRELEASEBODYTEMPLATE({ releaseBodyTemplate }) { + return { + message: "Invalid `releaseBodyTemplate` option.", + details: `The [releaseBodyTemplate option](${linkify( + "README.md#releaseBodyTemplate", + )}) must be a non empty \`String\`. + +Your configuration for the \`releaseBodyTemplate\` option is \`${stringify( + releaseBodyTemplate, + )}\`.`, + }; +} + +export function EINVALIDRELEASENAMETEMPLATE({ releaseNameTemplate }) { + return { + message: "Invalid `releaseNameTemplate` option.", + details: `The [releaseNameTemplate option](${linkify( + "README.md#releaseNameTemplate", + )}) must be a non empty \`String\`. + +Your configuration for the \`releaseNameTemplate\` option is \`${stringify( + releaseNameTemplate, + )}\`.`, + }; +} + export function EINVALIDDISCUSSIONCATEGORYNAME({ discussionCategoryName }) { return { message: "Invalid `discussionCategoryName` option.", diff --git a/lib/publish.js b/lib/publish.js index 9f2666b5..7a0ca563 100644 --- a/lib/publish.js +++ b/lib/publish.js @@ -19,7 +19,7 @@ export default async function publish(pluginConfig, context, { Octokit }) { cwd, options: { repositoryUrl }, branch, - nextRelease: { name, gitTag, notes }, + nextRelease: { gitTag }, logger, } = context; const { @@ -29,6 +29,8 @@ export default async function publish(pluginConfig, context, { Octokit }) { proxy, assets, draftRelease, + releaseNameTemplate, + releaseBodyTemplate, discussionCategoryName, } = resolveConfig(pluginConfig, context); const { owner, repo } = parseGithubUrl(repositoryUrl); @@ -45,8 +47,8 @@ export default async function publish(pluginConfig, context, { Octokit }) { repo, tag_name: gitTag, target_commitish: branch.name, - name, - body: notes, + name: template(releaseNameTemplate)(context), + body: template(releaseBodyTemplate)(context), prerelease: isPrerelease(branch), }; diff --git a/lib/resolve-config.js b/lib/resolve-config.js index f0109a11..a5e5c47e 100644 --- a/lib/resolve-config.js +++ b/lib/resolve-config.js @@ -14,6 +14,8 @@ export default function resolveConfig( releasedLabels, addReleases, draftRelease, + releaseNameTemplate, + releaseBodyTemplate, discussionCategoryName, }, { env }, @@ -45,6 +47,12 @@ export default function resolveConfig( : castArray(releasedLabels), addReleases: isNil(addReleases) ? false : addReleases, draftRelease: isNil(draftRelease) ? false : draftRelease, + releaseBodyTemplate: !isNil(releaseBodyTemplate) + ? releaseBodyTemplate + : "<%= nextRelease.notes %>", + releaseNameTemplate: !isNil(releaseNameTemplate) + ? releaseNameTemplate + : "<%= nextRelease.name %>", discussionCategoryName: isNil(discussionCategoryName) ? false : discussionCategoryName, diff --git a/lib/verify.js b/lib/verify.js index 625b45d2..12a186ce 100644 --- a/lib/verify.js +++ b/lib/verify.js @@ -45,6 +45,8 @@ const VALIDATORS = { releasedLabels: canBeDisabled(isArrayOf(isNonEmptyString)), addReleases: canBeDisabled(oneOf(["bottom", "top"])), draftRelease: isBoolean, + releaseBodyTemplate: isNonEmptyString, + releaseNameTemplate: isNonEmptyString, discussionCategoryName: canBeDisabled(isNonEmptyString), }; diff --git a/package-lock.json b/package-lock.json index 6ac30625..9b399db2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,9 +10,9 @@ "license": "MIT", "dependencies": { "@octokit/core": "^5.0.0", - "@octokit/plugin-paginate-rest": "^8.0.0", + "@octokit/plugin-paginate-rest": "^9.0.0", "@octokit/plugin-retry": "^6.0.0", - "@octokit/plugin-throttling": "^7.0.0", + "@octokit/plugin-throttling": "^8.0.0", "@semantic-release/error": "^4.0.0", "aggregate-error": "^5.0.0", "debug": "^4.3.4", @@ -32,7 +32,7 @@ "cpy": "10.1.0", "fetch-mock": "npm:@gr2m/fetch-mock@9.11.0-pull-request-644.1", "prettier": "3.0.3", - "semantic-release": "22.0.0", + "semantic-release": "22.0.5", "sinon": "16.0.0", "tempy": "3.1.0" }, @@ -600,15 +600,15 @@ } }, "node_modules/@octokit/core": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.0.0.tgz", - "integrity": "sha512-YbAtMWIrbZ9FCXbLwT9wWB8TyLjq9mxpKdgB3dUNxQcIVTf9hJ70gRPwAcqGZdY6WdJPZ0I7jLaaNDCiloGN2A==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.0.1.tgz", + "integrity": "sha512-lyeeeZyESFo+ffI801SaBKmCfsvarO+dgV8/0gD8u1d87clbEdWsP5yC+dSj3zLhb2eIf5SJrn6vDz9AheETHw==", "dependencies": { "@octokit/auth-token": "^4.0.0", "@octokit/graphql": "^7.0.0", "@octokit/request": "^8.0.2", "@octokit/request-error": "^5.0.0", - "@octokit/types": "^11.0.0", + "@octokit/types": "^12.0.0", "before-after-hook": "^2.2.0", "universal-user-agent": "^6.0.0" }, @@ -617,11 +617,11 @@ } }, "node_modules/@octokit/endpoint": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.0.tgz", - "integrity": "sha512-szrQhiqJ88gghWY2Htt8MqUDO6++E/EIXqJ2ZEp5ma3uGS46o7LZAzSLt49myB7rT+Hfw5Y6gO3LmOxGzHijAQ==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.1.tgz", + "integrity": "sha512-hRlOKAovtINHQPYHZlfyFwaM8OyetxeoC81lAkBy34uLb8exrZB50SQdeW3EROqiY9G9yxQTpp5OHTV54QD+vA==", "dependencies": { - "@octokit/types": "^11.0.0", + "@octokit/types": "^12.0.0", "is-plain-object": "^5.0.0", "universal-user-agent": "^6.0.0" }, @@ -630,12 +630,12 @@ } }, "node_modules/@octokit/graphql": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-7.0.1.tgz", - "integrity": "sha512-T5S3oZ1JOE58gom6MIcrgwZXzTaxRnxBso58xhozxHpOqSTgDS6YNeEUvZ/kRvXgPrRz/KHnZhtb7jUMRi9E6w==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-7.0.2.tgz", + "integrity": "sha512-OJ2iGMtj5Tg3s6RaXH22cJcxXRi7Y3EBqbHTBRq+PQAqfaS8f/236fUrWhfSn8P4jovyzqucxme7/vWSSZBX2Q==", "dependencies": { "@octokit/request": "^8.0.1", - "@octokit/types": "^11.0.0", + "@octokit/types": "^12.0.0", "universal-user-agent": "^6.0.0" }, "engines": { @@ -643,16 +643,16 @@ } }, "node_modules/@octokit/openapi-types": { - "version": "18.0.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-18.0.0.tgz", - "integrity": "sha512-V8GImKs3TeQRxRtXFpG2wl19V7444NIOTDF24AWuIbmNaNYOQMWRbjcGDXV5B+0n887fgDcuMNOmlul+k+oJtw==" + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-19.0.0.tgz", + "integrity": "sha512-PclQ6JGMTE9iUStpzMkwLCISFn/wDeRjkZFIKALpvJQNBGwDoYYi2fFvuHwssoQ1rXI5mfh6jgTgWuddeUzfWw==" }, "node_modules/@octokit/plugin-paginate-rest": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-8.0.0.tgz", - "integrity": "sha512-2xZ+baZWUg+qudVXnnvXz7qfrTmDeYPCzangBVq/1gXxii/OiS//4shJp9dnCCvj1x+JAm9ji1Egwm1BA47lPQ==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-9.0.0.tgz", + "integrity": "sha512-oIJzCpttmBTlEhBmRvb+b9rlnGpmFgDtZ0bB6nq39qIod6A5DP+7RkVLMOixIgRCYSHDTeayWqmiJ2SZ6xgfdw==", "dependencies": { - "@octokit/types": "^11.0.0" + "@octokit/types": "^12.0.0" }, "engines": { "node": ">= 18" @@ -662,12 +662,12 @@ } }, "node_modules/@octokit/plugin-retry": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@octokit/plugin-retry/-/plugin-retry-6.0.0.tgz", - "integrity": "sha512-a1/A4A+PB1QoAHQfLJxGHhLfSAT03bR1jJz3GgQJZvty2ozawFWs93MiBQXO7SL2YbO7CIq0Goj4qLOBj8JeMQ==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-retry/-/plugin-retry-6.0.1.tgz", + "integrity": "sha512-SKs+Tz9oj0g4p28qkZwl/topGcb0k0qPNX/i7vBKmDsjoeqnVfFUquqrE/O9oJY7+oLzdCtkiWSXLpLjvl6uog==", "dependencies": { "@octokit/request-error": "^5.0.0", - "@octokit/types": "^11.0.0", + "@octokit/types": "^12.0.0", "bottleneck": "^2.15.3" }, "engines": { @@ -678,11 +678,11 @@ } }, "node_modules/@octokit/plugin-throttling": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@octokit/plugin-throttling/-/plugin-throttling-7.0.0.tgz", - "integrity": "sha512-KL2k/d0uANc8XqP5S64YcNFCudR3F5AaKO39XWdUtlJIjT9Ni79ekWJ6Kj5xvAw87udkOMEPcVf9xEge2+ahew==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-throttling/-/plugin-throttling-8.0.0.tgz", + "integrity": "sha512-OkMbHYUidj81q92YRkPzWmwXkEtsI3KOcSkNm763aqUOh9IEplyX05XjKAdZFANAvaYH0Q4JBZwu4h2VnPVXZA==", "dependencies": { - "@octokit/types": "^11.0.0", + "@octokit/types": "^12.0.0", "bottleneck": "^2.15.3" }, "engines": { @@ -693,13 +693,13 @@ } }, "node_modules/@octokit/request": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.1.1.tgz", - "integrity": "sha512-8N+tdUz4aCqQmXl8FpHYfKG9GelDFd7XGVzyN8rc6WxVlYcfpHECnuRkgquzz+WzvHTK62co5di8gSXnzASZPQ==", + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.1.2.tgz", + "integrity": "sha512-A0RJJfzjlZQwb+39eDm5UM23dkxbp28WEG4p2ueH+Q2yY4p349aRK/vcUlEuIB//ggcrHJceoYYkBP/LYCoXEg==", "dependencies": { "@octokit/endpoint": "^9.0.0", "@octokit/request-error": "^5.0.0", - "@octokit/types": "^11.1.0", + "@octokit/types": "^12.0.0", "is-plain-object": "^5.0.0", "universal-user-agent": "^6.0.0" }, @@ -708,11 +708,11 @@ } }, "node_modules/@octokit/request-error": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.0.0.tgz", - "integrity": "sha512-1ue0DH0Lif5iEqT52+Rf/hf0RmGO9NWFjrzmrkArpG9trFfDM/efx00BJHdLGuro4BR/gECxCU2Twf5OKrRFsQ==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.0.1.tgz", + "integrity": "sha512-X7pnyTMV7MgtGmiXBwmO6M5kIPrntOXdyKZLigNfQWSEQzVxR4a4vo49vJjTWX70mPndj8KhfT4Dx+2Ng3vnBQ==", "dependencies": { - "@octokit/types": "^11.0.0", + "@octokit/types": "^12.0.0", "deprecation": "^2.0.0", "once": "^1.4.0" }, @@ -721,11 +721,11 @@ } }, "node_modules/@octokit/types": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-11.1.0.tgz", - "integrity": "sha512-Fz0+7GyLm/bHt8fwEqgvRBWwIV1S6wRRyq+V6exRKLVWaKGsuy6H9QFYeBVDV7rK6fO3XwHgQOPxv+cLj2zpXQ==", + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.0.0.tgz", + "integrity": "sha512-EzD434aHTFifGudYAygnFlS1Tl6KhbTynEWELQXIbTY8Msvb5nEqTZIm7sbPEt4mQYLZwu3zPKVdeIrw0g7ovg==", "dependencies": { - "@octokit/openapi-types": "^18.0.0" + "@octokit/openapi-types": "^19.0.0" } }, "node_modules/@pnpm/config.env-replace": { @@ -770,9 +770,9 @@ } }, "node_modules/@semantic-release/commit-analyzer": { - "version": "11.0.0-beta.4", - "resolved": "https://registry.npmjs.org/@semantic-release/commit-analyzer/-/commit-analyzer-11.0.0-beta.4.tgz", - "integrity": "sha512-K6HkgFadJ+imAc0n2P9sLjrEw+xAPqigsu6RS+wCUqc6et2J7ig8uJ4hqujXsXHZLtsKQt/5yjw0t3FjcvseIA==", + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/@semantic-release/commit-analyzer/-/commit-analyzer-11.0.0.tgz", + "integrity": "sha512-uEXyf4Z0AWJuxI9TbSQP5kkIYqus1/E1NcmE7pIv6d6/m/5EJcNWAGR4FOo34vrV26FhEaRVkxFfYzp/M7BKIg==", "dev": true, "dependencies": { "conventional-changelog-angular": "^7.0.0", @@ -799,15 +799,15 @@ } }, "node_modules/@semantic-release/github": { - "version": "9.0.6", - "resolved": "https://registry.npmjs.org/@semantic-release/github/-/github-9.0.6.tgz", - "integrity": "sha512-GBGt9c3c2UdSvso4jcyQQSUpZA9hbfHqGQerZKN9WvVzCIkaBy8xkhOyiFVX08LjRHHT/H221SJNBLtuihX5iw==", + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/@semantic-release/github/-/github-9.0.7.tgz", + "integrity": "sha512-SU3ayJ4/0TeIVyfCMLmuKoa4KvLclarPCmwY/zippm7sK95SwgWoFd8aFfAJIPGCRYnP3rfHRdYzphsrrNI3Cg==", "dev": true, "dependencies": { "@octokit/core": "^5.0.0", - "@octokit/plugin-paginate-rest": "^8.0.0", + "@octokit/plugin-paginate-rest": "^9.0.0", "@octokit/plugin-retry": "^6.0.0", - "@octokit/plugin-throttling": "^7.0.0", + "@octokit/plugin-throttling": "^8.0.0", "@semantic-release/error": "^4.0.0", "aggregate-error": "^5.0.0", "debug": "^4.3.4", @@ -856,9 +856,9 @@ } }, "node_modules/@semantic-release/release-notes-generator": { - "version": "12.0.0-beta.3", - "resolved": "https://registry.npmjs.org/@semantic-release/release-notes-generator/-/release-notes-generator-12.0.0-beta.3.tgz", - "integrity": "sha512-87tr1FqBTCDIyfiqPTJrRU/aANzaekmhtgBV/nm2D3WCDp6Wr9PydsFOTSTL+H7LZZKDZANsRv9UGRSBEPvtRQ==", + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@semantic-release/release-notes-generator/-/release-notes-generator-12.0.0.tgz", + "integrity": "sha512-m7Ds8ComP1KJgA2Lke2xMwE1TOOU40U7AzP4lT8hJ2tUAeicziPz/1GeDFmRkTOkMFlfHvE6kuvMkvU+mIzIDQ==", "dev": true, "dependencies": { "conventional-changelog-angular": "^7.0.0", @@ -891,6 +891,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@sindresorhus/is": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-3.1.2.tgz", + "integrity": "sha512-JiX9vxoKMmu8Y3Zr2RVathBL1Cdu4Nt4MuNWemt1Nc06A0RAin9c5FArkhGsyMBWfCu4zj+9b+GxtjAnE4qqLQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, "node_modules/@sinonjs/commons": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", @@ -942,9 +954,9 @@ "dev": true }, "node_modules/@types/normalize-package-data": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz", - "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.2.tgz", + "integrity": "sha512-lqa4UEhhv/2sjjIQgjX8B+RBjj47eo0mzGasklVJ78UKGQY1r0VpB9XHDaZZO9qzEFDdy4MrXLuEaSmPrPSe/A==", "dev": true }, "node_modules/acorn": { @@ -1218,9 +1230,9 @@ } }, "node_modules/browserslist": { - "version": "4.21.10", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.10.tgz", - "integrity": "sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ==", + "version": "4.21.11", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.11.tgz", + "integrity": "sha512-xn1UXOKUz7DjdGlg9RrUr0GGiWzI97UQJnugHtH0OLDfJB7jMgoIkYvRIEO1l9EeEERVqeqLYOcFBW9ldjypbQ==", "dev": true, "funding": [ { @@ -1237,10 +1249,10 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001517", - "electron-to-chromium": "^1.4.477", + "caniuse-lite": "^1.0.30001538", + "electron-to-chromium": "^1.4.526", "node-releases": "^2.0.13", - "update-browserslist-db": "^1.0.11" + "update-browserslist-db": "^1.0.13" }, "bin": { "browserslist": "cli.js" @@ -1288,9 +1300,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001534", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001534.tgz", - "integrity": "sha512-vlPVrhsCS7XaSh2VvWluIQEzVhefrUQcEsQWSS5A5V+dM07uv1qHeQzAOTGIMy9i3e9bH15+muvI/UHojVgS/Q==", + "version": "1.0.30001538", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001538.tgz", + "integrity": "sha512-HWJnhnID+0YMtGlzcp3T9drmBJUVDchPJ08tpUGFLs9CYlwWPH2uLgpHn8fND5pCgXVtnGS3H4QR9XLMHVNkHw==", "dev": true, "funding": [ { @@ -1344,6 +1356,15 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/chokidar": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", @@ -1960,9 +1981,9 @@ "dev": true }, "node_modules/electron-to-chromium": { - "version": "1.4.523", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.523.tgz", - "integrity": "sha512-9AreocSUWnzNtvLcbpng6N+GkXnCcBR80IQkxRC9Dfdyg4gaWNUPBujAHUpKkiUkoSoR9UlhA4zD/IgBklmhzg==", + "version": "1.4.528", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.528.tgz", + "integrity": "sha512-UdREXMXzLkREF4jA8t89FQjA8WHI6ssP38PMY4/4KhXFQbtImnghh4GkCgrtiZwLKUKVD2iTVXvDVQjfomEQuA==", "dev": true }, "node_modules/emittery": { @@ -1983,49 +2004,23 @@ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true }, + "node_modules/emojilib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/emojilib/-/emojilib-2.4.0.tgz", + "integrity": "sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==", + "dev": true + }, "node_modules/env-ci": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/env-ci/-/env-ci-9.1.1.tgz", - "integrity": "sha512-Im2yEWeF4b2RAMAaWvGioXk6m0UNaIjD8hj28j2ij5ldnIFrDQT0+pzDvpbRkcjurhXhf/AsBKv8P2rtmGi9Aw==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/env-ci/-/env-ci-10.0.0.tgz", + "integrity": "sha512-U4xcd/utDYFgMh0yWj07R1H6L5fwhVbmxBCpnL0DbVSDZVnsC82HONw0wxtxNkIAcua3KtbomQvIk5xFZGAQJw==", "dev": true, "dependencies": { - "execa": "^7.0.0", + "execa": "^8.0.0", "java-properties": "^1.0.2" }, "engines": { - "node": "^16.14 || >=18" - } - }, - "node_modules/env-ci/node_modules/execa": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-7.2.0.tgz", - "integrity": "sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.1", - "human-signals": "^4.3.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^3.0.7", - "strip-final-newline": "^3.0.0" - }, - "engines": { - "node": "^14.18.0 || ^16.14.0 || >=18.0.0" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/env-ci/node_modules/human-signals": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz", - "integrity": "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==", - "dev": true, - "engines": { - "node": ">=14.18.0" + "node": "^18.17 || >=20.6.1" } }, "node_modules/error-ex": { @@ -3128,9 +3123,9 @@ } }, "node_modules/marked": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/marked/-/marked-5.1.2.tgz", - "integrity": "sha512-ahRPGXJpjMjwSOlBoTMZAK7ATXkli5qCPxZ21TG44rx1KEo44bii4ekgTDQPNRQ4Kh7JMb9Ub1PVk1NxRSsorg==", + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/marked/-/marked-9.0.3.tgz", + "integrity": "sha512-pI/k4nzBG1PEq1J3XFEHxVvjicfjl8rgaMaqclouGSMPhk7Q3Ejb2ZRxx/ZQOcQ1909HzVoWCFYq6oLgtL4BpQ==", "dev": true, "bin": { "marked": "bin/marked.js" @@ -3140,23 +3135,23 @@ } }, "node_modules/marked-terminal": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-5.2.0.tgz", - "integrity": "sha512-Piv6yNwAQXGFjZSaiNljyNFw7jKDdGrw70FSbtxEyldLsyeuV5ZHm/1wW++kWbrOF1VPnUgYOhB2oLL0ZpnekA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-6.0.0.tgz", + "integrity": "sha512-6rruICvqRfA4N+Mvdc0UyDbLA0A0nI5omtARIlin3P2F+aNc3EbW91Rd9HTuD0v9qWyHmNIu8Bt40gAnPfldsg==", "dev": true, "dependencies": { "ansi-escapes": "^6.2.0", "cardinal": "^2.1.1", - "chalk": "^5.2.0", + "chalk": "^5.3.0", "cli-table3": "^0.6.3", - "node-emoji": "^1.11.0", - "supports-hyperlinks": "^2.3.0" + "node-emoji": "^2.1.0", + "supports-hyperlinks": "^3.0.0" }, "engines": { - "node": ">=14.13.1 || >=16.0.0" + "node": ">=16.0.0" }, "peerDependencies": { - "marked": "^1.0.0 || ^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0" + "marked": ">=1 <10" } }, "node_modules/matcher": { @@ -3346,12 +3341,15 @@ } }, "node_modules/node-emoji": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz", - "integrity": "sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-2.1.0.tgz", + "integrity": "sha512-tcsBm9C6FmPN5Wo7OjFi9lgMyJjvkAeirmjR/ax8Ttfqy4N8PoFic26uqFTIgayHPNI5FH4ltUvfh9kHzwcK9A==", "dev": true, "dependencies": { - "lodash": "^4.17.21" + "@sindresorhus/is": "^3.1.2", + "char-regex": "^1.0.2", + "emojilib": "^2.4.0", + "skin-tone": "^2.0.0" } }, "node_modules/node-releases": { @@ -7596,20 +7594,20 @@ "dev": true }, "node_modules/semantic-release": { - "version": "22.0.0", - "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-22.0.0.tgz", - "integrity": "sha512-WTD8zPxh+pyc/DrTALRHJ47p1XbKqi2AJljn5WkXgLFFIMSax4uu15u4ZEZaa7ftBo8cSajh16VeafgUu9DX8g==", + "version": "22.0.5", + "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-22.0.5.tgz", + "integrity": "sha512-ESCEQsZlBj1DWMA84RthaJzQHHnihoGk49s9nUxHfRNUNZelLE9JZrE94bHO2Y00EWb7iwrzr1OYhv5QNVmf8A==", "dev": true, "dependencies": { - "@semantic-release/commit-analyzer": "^11.0.0-beta.3", + "@semantic-release/commit-analyzer": "^11.0.0", "@semantic-release/error": "^4.0.0", "@semantic-release/github": "^9.0.0", "@semantic-release/npm": "^11.0.0", - "@semantic-release/release-notes-generator": "^12.0.0-beta.2", + "@semantic-release/release-notes-generator": "^12.0.0", "aggregate-error": "^5.0.0", "cosmiconfig": "^8.0.0", "debug": "^4.0.0", - "env-ci": "^9.0.0", + "env-ci": "^10.0.0", "execa": "^8.0.0", "figures": "^5.0.0", "find-versions": "^5.1.0", @@ -7618,8 +7616,8 @@ "hook-std": "^3.0.0", "hosted-git-info": "^7.0.0", "lodash-es": "^4.17.21", - "marked": "^5.0.0", - "marked-terminal": "^5.1.1", + "marked": "^9.0.0", + "marked-terminal": "^6.0.0", "micromatch": "^4.0.2", "p-each-series": "^3.0.0", "p-reduce": "^3.0.0", @@ -7950,6 +7948,18 @@ "url": "https://opencollective.com/sinon" } }, + "node_modules/skin-tone": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/skin-tone/-/skin-tone-2.0.0.tgz", + "integrity": "sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==", + "dev": true, + "dependencies": { + "unicode-emoji-modifier-base": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/slash": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", @@ -8019,9 +8029,9 @@ } }, "node_modules/spdx-license-ids": { - "version": "3.0.13", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.13.tgz", - "integrity": "sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w==", + "version": "3.0.15", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.15.tgz", + "integrity": "sha512-lpT8hSQp9jAKp9mhtBU4Xjon8LPGBvLIuBiSVhMEtmLecTh2mO0tlqrAMp47tBXzMr13NJMQ2lf7RpQGLJ3HsQ==", "dev": true }, "node_modules/split2": { @@ -8191,16 +8201,16 @@ } }, "node_modules/supports-hyperlinks": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", - "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.0.0.tgz", + "integrity": "sha512-QBDPHyPQDRTy9ku4URNGY5Lah8PAaXs6tAAwp55sL5WCsSW7GIfdf6W5ixfziW+t7wh3GVvHyHHyQ1ESsoRvaA==", "dev": true, "dependencies": { "has-flag": "^4.0.0", "supports-color": "^7.0.0" }, "engines": { - "node": ">=8" + "node": ">=14.18" } }, "node_modules/temp-dir": { @@ -8365,6 +8375,15 @@ "node": ">=0.8.0" } }, + "node_modules/unicode-emoji-modifier-base": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unicode-emoji-modifier-base/-/unicode-emoji-modifier-base-1.0.0.tgz", + "integrity": "sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/unique-string": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-3.0.0.tgz", @@ -8395,9 +8414,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", - "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==", + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", "dev": true, "funding": [ { diff --git a/package.json b/package.json index d5adbc86..13cc65f8 100644 --- a/package.json +++ b/package.json @@ -21,9 +21,9 @@ ], "dependencies": { "@octokit/core": "^5.0.0", - "@octokit/plugin-paginate-rest": "^8.0.0", + "@octokit/plugin-paginate-rest": "^9.0.0", "@octokit/plugin-retry": "^6.0.0", - "@octokit/plugin-throttling": "^7.0.0", + "@octokit/plugin-throttling": "^8.0.0", "@semantic-release/error": "^4.0.0", "aggregate-error": "^5.0.0", "debug": "^4.3.4", @@ -43,7 +43,7 @@ "cpy": "10.1.0", "fetch-mock": "npm:@gr2m/fetch-mock@9.11.0-pull-request-644.1", "prettier": "3.0.3", - "semantic-release": "22.0.0", + "semantic-release": "22.0.5", "sinon": "16.0.0", "tempy": "3.1.0" }, diff --git a/test/publish.test.js b/test/publish.test.js index 74cf0fde..c5701099 100644 --- a/test/publish.test.js +++ b/test/publish.test.js @@ -845,3 +845,129 @@ test("Publish a release when env.GITHUB_URL is set to https://github.com (Defaul ]); t.true(fetch.done()); }); + +test("Publish a custom release body", async (t) => { + const owner = "test_user"; + const repo = "test_repo"; + const env = { GITHUB_TOKEN: "github_token" }; + const pluginConfig = { + releaseBodyTemplate: + "To install this run npm install package@<%= nextRelease.name %>\n\n<%= nextRelease.notes %>", + }; + const nextRelease = { + gitTag: "v1.0.0", + name: "v1.0.0", + notes: "Test release note body", + }; + const options = { repositoryUrl: `https://github.com/${owner}/${repo}.git` }; + const releaseUrl = `https://github.com/${owner}/${repo}/releases/${nextRelease.version}`; + const releaseId = 1; + const uploadUri = `/api/uploads/repos/${owner}/${repo}/releases/${releaseId}/assets`; + const uploadUrl = `https://github.com${uploadUri}{?name,label}`; + const branch = "test_branch"; + + const fetch = fetchMock.sandbox().postOnce( + `https://api.github.local/repos/${owner}/${repo}/releases`, + { + upload_url: uploadUrl, + html_url: releaseUrl, + }, + { + body: { + tag_name: nextRelease.gitTag, + target_commitish: branch, + name: nextRelease.name, + body: `To install this run npm install package@${nextRelease.name}\n\n${nextRelease.notes}`, + prerelease: false, + }, + }, + ); + + const result = await publish( + pluginConfig, + { + cwd, + env, + options, + branch: { name: branch, type: "release", main: true }, + nextRelease, + logger: t.context.logger, + }, + { + Octokit: TestOctokit.defaults((options) => ({ + ...options, + request: { ...options.request, fetch }, + })), + }, + ); + + t.is(result.url, releaseUrl); + t.deepEqual(t.context.log.args[0], [ + "Published GitHub release: %s", + releaseUrl, + ]); + t.true(fetch.done()); +}); + +test("Publish a custom release name", async (t) => { + const owner = "test_user"; + const repo = "test_repo"; + const env = { GITHUB_TOKEN: "github_token" }; + const pluginConfig = { + releaseNameTemplate: + "omg its the best release: <%= nextRelease.name %> 🌈🌈", + }; + const nextRelease = { + gitTag: "v1.0.0", + name: "v1.0.0", + notes: "Test release note body", + }; + const options = { repositoryUrl: `https://github.com/${owner}/${repo}.git` }; + const releaseUrl = `https://github.com/${owner}/${repo}/releases/${nextRelease.version}`; + const releaseId = 1; + const uploadUri = `/api/uploads/repos/${owner}/${repo}/releases/${releaseId}/assets`; + const uploadUrl = `https://github.com${uploadUri}{?name,label}`; + const branch = "test_branch"; + + const fetch = fetchMock.sandbox().postOnce( + `https://api.github.local/repos/${owner}/${repo}/releases`, + { + upload_url: uploadUrl, + html_url: releaseUrl, + }, + { + body: { + tag_name: nextRelease.gitTag, + target_commitish: branch, + name: `omg its the best release: ${nextRelease.name} 🌈🌈`, + body: nextRelease.notes, + prerelease: false, + }, + }, + ); + + const result = await publish( + pluginConfig, + { + cwd, + env, + options, + branch: { name: branch, type: "release", main: true }, + nextRelease, + logger: t.context.logger, + }, + { + Octokit: TestOctokit.defaults((options) => ({ + ...options, + request: { ...options.request, fetch }, + })), + }, + ); + + t.is(result.url, releaseUrl); + t.deepEqual(t.context.log.args[0], [ + "Published GitHub release: %s", + releaseUrl, + ]); + t.true(fetch.done()); +}); diff --git a/test/verify.test.js b/test/verify.test.js index a646a785..5d97ad91 100644 --- a/test/verify.test.js +++ b/test/verify.test.js @@ -2180,3 +2180,75 @@ test('Throw SemanticReleaseError if "draftRelease" option is not a valid boolean t.is(error.code, "EINVALIDDRAFTRELEASE"); t.true(fetch.done()); }); + +test('Throw SemanticReleaseError if "releaseBodyTemplate" option is an empty string', async (t) => { + const owner = "test_user"; + const repo = "test_repo"; + const env = { GH_TOKEN: "github_token" }; + + const fetch = fetchMock + .sandbox() + .getOnce(`https://api.github.local/repos/${owner}/${repo}`, { + permissions: { push: true }, + }); + + const { + errors: [error, ...errors], + } = await t.throwsAsync( + verify( + { releaseBodyTemplate: "" }, + { + env, + options: { repositoryUrl: `https://github.com/${owner}/${repo}.git` }, + logger: t.context.logger, + }, + { + Octokit: TestOctokit.defaults((options) => ({ + ...options, + request: { ...options.request, fetch }, + })), + }, + ), + ); + + t.is(errors.length, 0); + t.is(error.name, "SemanticReleaseError"); + t.is(error.code, "EINVALIDRELEASEBODYTEMPLATE"); + t.true(fetch.done()); +}); + +test('Throw SemanticReleaseError if "releaseNameTemplate" option is an empty string', async (t) => { + const owner = "test_user"; + const repo = "test_repo"; + const env = { GH_TOKEN: "github_token" }; + + const fetch = fetchMock + .sandbox() + .getOnce(`https://api.github.local/repos/${owner}/${repo}`, { + permissions: { push: true }, + }); + + const { + errors: [error, ...errors], + } = await t.throwsAsync( + verify( + { releaseNameTemplate: "" }, + { + env, + options: { repositoryUrl: `https://github.com/${owner}/${repo}.git` }, + logger: t.context.logger, + }, + { + Octokit: TestOctokit.defaults((options) => ({ + ...options, + request: { ...options.request, fetch }, + })), + }, + ), + ); + + t.is(errors.length, 0); + t.is(error.name, "SemanticReleaseError"); + t.is(error.code, "EINVALIDRELEASENAMETEMPLATE"); + t.true(fetch.done()); +});