Skip to content

Commit

Permalink
feat: releaseNameTemplate and releaseBodyTemplate options for cus…
Browse files Browse the repository at this point in the history
…tomizing release body and name (#704)
  • Loading branch information
halkeye committed Sep 25, 2023
1 parent 2265f07 commit 9e2678c
Show file tree
Hide file tree
Showing 7 changed files with 241 additions and 3 deletions.
2 changes: 2 additions & 0 deletions README.md
Expand Up @@ -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 %>` |

#### proxy

Expand Down
26 changes: 26 additions & 0 deletions lib/definitions/errors.js
Expand Up @@ -195,3 +195,29 @@ 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 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,
)}\`.`,
};
}
8 changes: 5 additions & 3 deletions lib/publish.js
Expand Up @@ -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 {
Expand All @@ -29,6 +29,8 @@ export default async function publish(pluginConfig, context, { Octokit }) {
proxy,
assets,
draftRelease,
releaseNameTemplate,
releaseBodyTemplate,
} = resolveConfig(pluginConfig, context);
const { owner, repo } = parseGithubUrl(repositoryUrl);
const octokit = new Octokit(
Expand All @@ -44,8 +46,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),
};

Expand Down
8 changes: 8 additions & 0 deletions lib/resolve-config.js
Expand Up @@ -14,6 +14,8 @@ export default function resolveConfig(
releasedLabels,
addReleases,
draftRelease,
releaseNameTemplate,
releaseBodyTemplate,
},
{ env },
) {
Expand Down Expand Up @@ -44,5 +46,11 @@ 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 %>",
};
}
2 changes: 2 additions & 0 deletions lib/verify.js
Expand Up @@ -45,6 +45,8 @@ const VALIDATORS = {
releasedLabels: canBeDisabled(isArrayOf(isNonEmptyString)),
addReleases: canBeDisabled(oneOf(["bottom", "top"])),
draftRelease: isBoolean,
releaseBodyTemplate: isNonEmptyString,
releaseNameTemplate: isNonEmptyString,
};

export default async function verify(pluginConfig, context, { Octokit }) {
Expand Down
126 changes: 126 additions & 0 deletions test/publish.test.js
Expand Up @@ -720,3 +720,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());
});
72 changes: 72 additions & 0 deletions test/verify.test.js
Expand Up @@ -2050,3 +2050,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());
});

0 comments on commit 9e2678c

Please sign in to comment.