semantic-release 20.0.0-beta.3 → 20.0.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -5
- package/bin/semantic-release.js +9 -9
- package/cli.js +31 -31
- package/docs/developer-guide/js-api.md +46 -37
- package/docs/developer-guide/plugin.md +97 -93
- package/docs/extending/plugins-list.md +8 -7
- package/docs/extending/shareable-configurations-list.md +2 -0
- package/docs/recipes/ci-configurations/README.md +1 -0
- package/docs/recipes/ci-configurations/github-actions.md +6 -3
- package/docs/recipes/ci-configurations/gitlab-ci.md +2 -3
- package/docs/recipes/git-hosted-services/README.md +1 -0
- package/docs/recipes/git-hosted-services/git-auth-ssh-keys.md +4 -1
- package/docs/recipes/release-workflow/README.md +1 -0
- package/docs/recipes/release-workflow/distribution-channels.md +1 -0
- package/docs/recipes/release-workflow/maintenance-releases.md +1 -0
- package/docs/recipes/release-workflow/pre-releases.md +1 -0
- package/docs/support/FAQ.md +25 -14
- package/docs/support/troubleshooting.md +1 -0
- package/docs/usage/ci-configuration.md +4 -3
- package/docs/usage/configuration.md +8 -2
- package/docs/usage/plugins.md +11 -5
- package/docs/usage/workflow-configuration.md +29 -17
- package/index.js +75 -72
- package/lib/get-commits.js +15 -9
- package/lib/get-config.js +38 -38
- package/lib/get-error.js +4 -4
- package/lib/get-git-auth-url.js +32 -32
- package/lib/get-last-release.js +8 -8
- package/lib/get-logger.js +10 -10
- package/lib/get-next-version.js +11 -11
- package/lib/get-release-to-add.js +17 -17
- package/lib/git.js +41 -41
- package/lib/hide-sensitive.js +6 -6
- package/lib/utils.js +12 -12
- package/lib/verify.js +14 -14
- package/package.json +6 -13
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# Workflow configuration
|
|
2
2
|
|
|
3
3
|
**semantic-release** allow to manage and automate complex release workflow, based on multiple Git branches and distribution channels. This allow to:
|
|
4
|
+
|
|
4
5
|
- Distribute certain releases to a particular group of users via distribution channels
|
|
5
6
|
- Manage the availability of releases on distribution channels via branches merge
|
|
6
7
|
- Maintain multiple lines of releases in parallel
|
|
@@ -12,6 +13,7 @@ The release workflow is configured via the [branches option](./configuration.md#
|
|
|
12
13
|
Each branch can be defined either as a string, a [glob](https://github.com/micromatch/micromatch#matching-features) or an object. For string and glob definitions each [property](#branches-properties) will be defaulted.
|
|
13
14
|
|
|
14
15
|
A branch can defined as one of three types:
|
|
16
|
+
|
|
15
17
|
- [release](#release-branches): to make releases on top of the last version released
|
|
16
18
|
- [maintenance](#maintenance-branches): to make releases on top of an old release
|
|
17
19
|
- [pre-release](#pre-release-branches): to make pre-releases
|
|
@@ -21,7 +23,7 @@ The type of the branch is automatically determined based on naming convention an
|
|
|
21
23
|
## Branches properties
|
|
22
24
|
|
|
23
25
|
| Property | Branch type | Description | Default |
|
|
24
|
-
|
|
26
|
+
| ------------ | ----------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
|
|
25
27
|
| `name` | All | **Required.** The Git branch holding the commits to analyze and the code to release. See [name](#name). | - The value itself if defined as a `String` or the matching branches name if defined as a glob. |
|
|
26
28
|
| `channel` | All | The distribution channel on which to publish releases from this branch. Set to `false` to force the default distribution channel instead of using the default. See [channel](#channel). | `undefined` for the first release branch, the value of `name` for subsequent ones. |
|
|
27
29
|
| `range` | [maintenance](#maintenance-branches) only | **Required unless `name` is formatted like `N.N.x` or `N.x` (`N` is a number).** The range of [semantic versions](https://semver.org) to support on this branch. See [range](#range). | The value of `name`. |
|
|
@@ -35,14 +37,15 @@ It can be defined as a [glob](https://github.com/micromatch/micromatch#matching-
|
|
|
35
37
|
If `name` doesn't match to any branch existing in the repository, the definition will be ignored. For example the default configuration includes the definition `next` and `next-major` which will become active only when the branches `next` and/or `next-major` are created in the repository. This allow to define your workflow once with all potential branches you might use and have the effective configuration evolving as you create new branches.
|
|
36
38
|
|
|
37
39
|
For example the configuration `['+([0-9])?(.{+([0-9]),x}).x', 'master', 'next']` will be expanded as:
|
|
40
|
+
|
|
38
41
|
```js
|
|
39
42
|
{
|
|
40
43
|
branches: [
|
|
41
|
-
{name:
|
|
42
|
-
{name:
|
|
43
|
-
{name:
|
|
44
|
-
{name:
|
|
45
|
-
]
|
|
44
|
+
{ name: "1.x", range: "1.x", channel: "1.x" }, // Only after the `1.x` is created in the repo
|
|
45
|
+
{ name: "2.x", range: "2.x", channel: "2.x" }, // Only after the `2.x` is created in the repo
|
|
46
|
+
{ name: "master" },
|
|
47
|
+
{ name: "next", channel: "next" }, // Only after the `next` is created in the repo
|
|
48
|
+
];
|
|
46
49
|
}
|
|
47
50
|
```
|
|
48
51
|
|
|
@@ -54,12 +57,13 @@ If the `channel` property is set to `false` the default channel will be used.
|
|
|
54
57
|
The value of `channel`, if defined as a string, is generated with [Lodash template](https://lodash.com/docs#template) with the variable `name` available.
|
|
55
58
|
|
|
56
59
|
For example the configuration `['master', {name: 'next', channel: 'channel-${name}'}]` will be expanded as:
|
|
60
|
+
|
|
57
61
|
```js
|
|
58
62
|
{
|
|
59
63
|
branches: [
|
|
60
|
-
{name:
|
|
61
|
-
{name:
|
|
62
|
-
]
|
|
64
|
+
{ name: "master" }, // `channel` is undefined so the default distribution channel will be used
|
|
65
|
+
{ name: "next", channel: "channel-next" }, // `channel` is built with the template `channel-${name}`
|
|
66
|
+
];
|
|
63
67
|
}
|
|
64
68
|
```
|
|
65
69
|
|
|
@@ -68,13 +72,14 @@ For example the configuration `['master', {name: 'next', channel: 'channel-${nam
|
|
|
68
72
|
A `range` only applies to maintenance branches, is required and must be formatted like `N.N.x` or `N.x` (`N` is a number). In case the `name` is formatted as a range (for example `1.x` or `1.5.x`) the branch will be considered a maintenance branch and the `name` value will be used for the `range`.
|
|
69
73
|
|
|
70
74
|
For example the configuration `['1.1.x', '1.2.x', 'master']` will be expanded as:
|
|
75
|
+
|
|
71
76
|
```js
|
|
72
77
|
{
|
|
73
78
|
branches: [
|
|
74
|
-
{name:
|
|
75
|
-
{name:
|
|
76
|
-
{name:
|
|
77
|
-
]
|
|
79
|
+
{ name: "1.1.x", range: "1.1.x", channel: "1.1.x" },
|
|
80
|
+
{ name: "1.2.x", range: "1.2.x", channel: "1.2.x" },
|
|
81
|
+
{ name: "master" },
|
|
82
|
+
];
|
|
78
83
|
}
|
|
79
84
|
```
|
|
80
85
|
|
|
@@ -86,13 +91,14 @@ If the `prerelease` property is set to `true` the `name` value will be used.
|
|
|
86
91
|
The value of `prerelease`, if defined as a string, is generated with [Lodash template](https://lodash.com/docs#template) with the variable `name` available.
|
|
87
92
|
|
|
88
93
|
For example the configuration `['master', {name: 'pre/rc', prerelease: '${name.replace(/^pre\\//g, "")}'}, {name: 'beta', prerelease: true}]` will be expanded as:
|
|
94
|
+
|
|
89
95
|
```js
|
|
90
96
|
{
|
|
91
97
|
branches: [
|
|
92
|
-
{name:
|
|
93
|
-
{name:
|
|
94
|
-
{name:
|
|
95
|
-
]
|
|
98
|
+
{ name: "master" },
|
|
99
|
+
{ name: "pre/rc", channel: "pre/rc", prerelease: "rc" }, // `prerelease` is built with the template `${name.replace(/^pre\\//g, "")}`
|
|
100
|
+
{ name: "beta", channel: "beta", prerelease: true }, // `prerelease` is set to `beta` as it is the value of `name`
|
|
101
|
+
];
|
|
96
102
|
}
|
|
97
103
|
```
|
|
98
104
|
|
|
@@ -113,10 +119,12 @@ See [publishing on distribution channels recipe](../recipes/release-workflow/dis
|
|
|
113
119
|
#### Pushing to a release branch
|
|
114
120
|
|
|
115
121
|
With the configuration `"branches": ["master", "next"]`, if the last release published from `master` is `1.0.0` and the last one from `next` is `2.0.0` then:
|
|
122
|
+
|
|
116
123
|
- Only versions in range `1.x.x` can be published from `master`, so only `fix` and `feat` commits can be pushed to `master`
|
|
117
124
|
- Once `next` get merged into `master` the release `2.0.0` will be made available on the channel associated with `master` and both `master` and `next` will accept any commit type
|
|
118
125
|
|
|
119
126
|
This verification prevent scenario such as:
|
|
127
|
+
|
|
120
128
|
1. Create a `feat` commit on `next` which triggers the release of version `1.0.0` on the `next` channel
|
|
121
129
|
2. Merge `next` into `master` which adds `1.0.0` on the default channel
|
|
122
130
|
3. Create a `feat` commit on `next` which triggers the release of version `1.1.0` on the `next` channel
|
|
@@ -147,6 +155,7 @@ See [publishing maintenance releases recipe](../recipes/release-workflow/mainten
|
|
|
147
155
|
#### Pushing to a maintenance branch
|
|
148
156
|
|
|
149
157
|
With the configuration `"branches": ["1.0.x", "1.x", "master"]`, if the last release published from `master` is `1.5.0` then:
|
|
158
|
+
|
|
150
159
|
- Only versions in range `>=1.0.0 <1.1.0` can be published from `1.0.x`, so only `fix` commits can be pushed to `1.0.x`
|
|
151
160
|
- Only versions in range `>=1.1.0 <1.5.0` can be published from `1.x`, so only `fix` and `feat` commits can be pushed to `1.x` as long the resulting release is lower than `1.5.0`
|
|
152
161
|
- Once `2.0.0` is released from `master`, versions in range `>=1.1.0 <2.0.0` can be published from `1.x`, so any number of `fix` and `feat` commits can be pushed to `1.x`
|
|
@@ -154,6 +163,7 @@ With the configuration `"branches": ["1.0.x", "1.x", "master"]`, if the last rel
|
|
|
154
163
|
#### Merging into a maintenance branch
|
|
155
164
|
|
|
156
165
|
With the configuration `"branches": ["1.0.x", "1.x", "master"]`, if the last release published from `master` is `1.0.0` then:
|
|
166
|
+
|
|
157
167
|
- Creating the branch `1.0.x` from `master` will make the `1.0.0` release available on the `1.0.x` distribution channel
|
|
158
168
|
- Pushing a `fix` commit on the `1.0.x` branch will release the version `1.0.1` on the `1.0.x` distribution channel
|
|
159
169
|
- Creating the branch `1.x` from `master` will make the `1.0.0` release available on the `1.x` distribution channel
|
|
@@ -176,11 +186,13 @@ See [publishing pre-releases recipe](../recipes/release-workflow/pre-releases.md
|
|
|
176
186
|
#### Pushing to a pre-release branch
|
|
177
187
|
|
|
178
188
|
With the configuration `"branches": ["master", {"name": "beta", "prerelease": true}]`, if the last release published from `master` is `1.0.0` then:
|
|
189
|
+
|
|
179
190
|
- Pushing a `BREAKING CHANGE` commit on the `beta` branch will release the version `2.0.0-beta.1` on the `beta` distribution channel
|
|
180
191
|
- Pushing either a `fix`, `feat` or a `BREAKING CHANGE` commit on the `beta` branch will release the version `2.0.0-beta.2` (then `2.0.0-beta.3`, `2.0.0-beta.4`, etc...) on the `beta` distribution channel
|
|
181
192
|
|
|
182
193
|
#### Merging into a pre-release branch
|
|
183
194
|
|
|
184
195
|
With the configuration `"branches": ["master", {"name": "beta", "prerelease": true}]`, if the last release published from `master` is `1.0.0` and the last one published from `beta` is `2.0.0-beta.1` then:
|
|
196
|
+
|
|
185
197
|
- Pushing a `fix` commit on the `master` branch will release the version `1.0.1` on the default distribution channel
|
|
186
198
|
- Merging the branch `master` into `beta` will release the version `2.0.0-beta.2` on the `beta` distribution channel
|
package/index.js
CHANGED
|
@@ -1,33 +1,33 @@
|
|
|
1
|
-
import {createRequire} from
|
|
2
|
-
import {pick} from
|
|
3
|
-
import * as marked from
|
|
4
|
-
import envCi from
|
|
5
|
-
import {hookStd} from
|
|
6
|
-
import semver from
|
|
7
|
-
import AggregateError from
|
|
8
|
-
import hideSensitive from
|
|
9
|
-
import getConfig from
|
|
10
|
-
import verify from
|
|
11
|
-
import getNextVersion from
|
|
12
|
-
import getCommits from
|
|
13
|
-
import getLastRelease from
|
|
14
|
-
import getReleaseToAdd from
|
|
15
|
-
import {extractErrors, makeTag} from
|
|
16
|
-
import getGitAuthUrl from
|
|
17
|
-
import getBranches from
|
|
18
|
-
import getLogger from
|
|
19
|
-
import {addNote, getGitHead, getTagHead, isBranchUpToDate, push, pushNotes, tag, verifyAuth} from
|
|
20
|
-
import getError from
|
|
21
|
-
import {COMMIT_EMAIL, COMMIT_NAME} from
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import { pick } from "lodash-es";
|
|
3
|
+
import * as marked from "marked";
|
|
4
|
+
import envCi from "env-ci";
|
|
5
|
+
import { hookStd } from "hook-std";
|
|
6
|
+
import semver from "semver";
|
|
7
|
+
import AggregateError from "aggregate-error";
|
|
8
|
+
import hideSensitive from "./lib/hide-sensitive.js";
|
|
9
|
+
import getConfig from "./lib/get-config.js";
|
|
10
|
+
import verify from "./lib/verify.js";
|
|
11
|
+
import getNextVersion from "./lib/get-next-version.js";
|
|
12
|
+
import getCommits from "./lib/get-commits.js";
|
|
13
|
+
import getLastRelease from "./lib/get-last-release.js";
|
|
14
|
+
import getReleaseToAdd from "./lib/get-release-to-add.js";
|
|
15
|
+
import { extractErrors, makeTag } from "./lib/utils.js";
|
|
16
|
+
import getGitAuthUrl from "./lib/get-git-auth-url.js";
|
|
17
|
+
import getBranches from "./lib/branches/index.js";
|
|
18
|
+
import getLogger from "./lib/get-logger.js";
|
|
19
|
+
import { addNote, getGitHead, getTagHead, isBranchUpToDate, push, pushNotes, tag, verifyAuth } from "./lib/git.js";
|
|
20
|
+
import getError from "./lib/get-error.js";
|
|
21
|
+
import { COMMIT_EMAIL, COMMIT_NAME } from "./lib/definitions/constants.js";
|
|
22
22
|
|
|
23
23
|
const require = createRequire(import.meta.url);
|
|
24
|
-
const pkg = require(
|
|
24
|
+
const pkg = require("./package.json");
|
|
25
25
|
|
|
26
26
|
let markedOptionsSet = false;
|
|
27
27
|
async function terminalOutput(text) {
|
|
28
28
|
if (!markedOptionsSet) {
|
|
29
|
-
const {default: TerminalRenderer} = await import(
|
|
30
|
-
marked.setOptions({renderer: new TerminalRenderer()});
|
|
29
|
+
const { default: TerminalRenderer } = await import("marked-terminal"); // eslint-disable-line node/no-unsupported-features/es-syntax
|
|
30
|
+
marked.setOptions({ renderer: new TerminalRenderer() });
|
|
31
31
|
markedOptionsSet = true;
|
|
32
32
|
}
|
|
33
33
|
|
|
@@ -36,12 +36,12 @@ async function terminalOutput(text) {
|
|
|
36
36
|
|
|
37
37
|
/* eslint complexity: off */
|
|
38
38
|
async function run(context, plugins) {
|
|
39
|
-
const {cwd, env, options, logger, envCi} = context;
|
|
40
|
-
const {isCi, branch, prBranch, isPr} = envCi;
|
|
39
|
+
const { cwd, env, options, logger, envCi } = context;
|
|
40
|
+
const { isCi, branch, prBranch, isPr } = envCi;
|
|
41
41
|
const ciBranch = isPr ? prBranch : branch;
|
|
42
42
|
|
|
43
43
|
if (!isCi && !options.dryRun && !options.noCi) {
|
|
44
|
-
logger.warn(
|
|
44
|
+
logger.warn("This run was not triggered in a known CI environment, running in dry-run mode.");
|
|
45
45
|
options.dryRun = true;
|
|
46
46
|
} else {
|
|
47
47
|
// When running on CI, set the commits author and committer info and prevent the `git` CLI to prompt for username/password. See #703.
|
|
@@ -51,7 +51,7 @@ async function run(context, plugins) {
|
|
|
51
51
|
GIT_COMMITTER_NAME: COMMIT_NAME,
|
|
52
52
|
GIT_COMMITTER_EMAIL: COMMIT_EMAIL,
|
|
53
53
|
...env,
|
|
54
|
-
GIT_ASKPASS:
|
|
54
|
+
GIT_ASKPASS: "echo",
|
|
55
55
|
GIT_TERMINAL_PROMPT: 0,
|
|
56
56
|
});
|
|
57
57
|
}
|
|
@@ -64,30 +64,30 @@ async function run(context, plugins) {
|
|
|
64
64
|
// Verify config
|
|
65
65
|
await verify(context);
|
|
66
66
|
|
|
67
|
-
options.repositoryUrl = await getGitAuthUrl({...context, branch: {name: ciBranch}});
|
|
67
|
+
options.repositoryUrl = await getGitAuthUrl({ ...context, branch: { name: ciBranch } });
|
|
68
68
|
context.branches = await getBranches(options.repositoryUrl, ciBranch, context);
|
|
69
|
-
context.branch = context.branches.find(({name}) => name === ciBranch);
|
|
69
|
+
context.branch = context.branches.find(({ name }) => name === ciBranch);
|
|
70
70
|
|
|
71
71
|
if (!context.branch) {
|
|
72
72
|
logger.log(
|
|
73
73
|
`This test run was triggered on the branch ${ciBranch}, while semantic-release is configured to only publish from ${context.branches
|
|
74
|
-
.map(({name}) => name)
|
|
75
|
-
.join(
|
|
74
|
+
.map(({ name }) => name)
|
|
75
|
+
.join(", ")}, therefore a new version won’t be published.`
|
|
76
76
|
);
|
|
77
77
|
return false;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
logger[options.dryRun ?
|
|
80
|
+
logger[options.dryRun ? "warn" : "success"](
|
|
81
81
|
`Run automated release from branch ${ciBranch} on repository ${options.originalRepositoryURL}${
|
|
82
|
-
options.dryRun ?
|
|
82
|
+
options.dryRun ? " in dry-run mode" : ""
|
|
83
83
|
}`
|
|
84
84
|
);
|
|
85
85
|
|
|
86
86
|
try {
|
|
87
87
|
try {
|
|
88
|
-
await verifyAuth(options.repositoryUrl, context.branch.name, {cwd, env});
|
|
88
|
+
await verifyAuth(options.repositoryUrl, context.branch.name, { cwd, env });
|
|
89
89
|
} catch (error) {
|
|
90
|
-
if (!(await isBranchUpToDate(options.repositoryUrl, context.branch.name, {cwd, env}))) {
|
|
90
|
+
if (!(await isBranchUpToDate(options.repositoryUrl, context.branch.name, { cwd, env }))) {
|
|
91
91
|
logger.log(
|
|
92
92
|
`The local branch ${context.branch.name} is behind the remote one, therefore a new version won't be published.`
|
|
93
93
|
);
|
|
@@ -98,7 +98,7 @@ async function run(context, plugins) {
|
|
|
98
98
|
}
|
|
99
99
|
} catch (error) {
|
|
100
100
|
logger.error(`The command "${error.command}" failed with the error message ${error.stderr}.`);
|
|
101
|
-
throw getError(
|
|
101
|
+
throw getError("EGITNOPERMISSION", context);
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
logger.success(`Allowed to push to the Git repository`);
|
|
@@ -110,24 +110,27 @@ async function run(context, plugins) {
|
|
|
110
110
|
const releaseToAdd = getReleaseToAdd(context);
|
|
111
111
|
|
|
112
112
|
if (releaseToAdd) {
|
|
113
|
-
const {lastRelease, currentRelease, nextRelease} = releaseToAdd;
|
|
113
|
+
const { lastRelease, currentRelease, nextRelease } = releaseToAdd;
|
|
114
114
|
|
|
115
|
-
nextRelease.gitHead = await getTagHead(nextRelease.gitHead, {cwd, env});
|
|
116
|
-
currentRelease.gitHead = await getTagHead(currentRelease.gitHead, {cwd, env});
|
|
115
|
+
nextRelease.gitHead = await getTagHead(nextRelease.gitHead, { cwd, env });
|
|
116
|
+
currentRelease.gitHead = await getTagHead(currentRelease.gitHead, { cwd, env });
|
|
117
117
|
if (context.branch.mergeRange && !semver.satisfies(nextRelease.version, context.branch.mergeRange)) {
|
|
118
|
-
errors.push(getError(
|
|
118
|
+
errors.push(getError("EINVALIDMAINTENANCEMERGE", { ...context, nextRelease }));
|
|
119
119
|
} else {
|
|
120
|
-
const commits = await getCommits({...context, lastRelease, nextRelease});
|
|
121
|
-
nextRelease.notes = await plugins.generateNotes({...context, commits, lastRelease, nextRelease});
|
|
120
|
+
const commits = await getCommits({ ...context, lastRelease, nextRelease });
|
|
121
|
+
nextRelease.notes = await plugins.generateNotes({ ...context, commits, lastRelease, nextRelease });
|
|
122
122
|
|
|
123
123
|
if (options.dryRun) {
|
|
124
124
|
logger.warn(`Skip ${nextRelease.gitTag} tag creation in dry-run mode`);
|
|
125
125
|
} else {
|
|
126
|
-
await addNote({channels: [...currentRelease.channels, nextRelease.channel]}, nextRelease.gitHead, {
|
|
127
|
-
|
|
128
|
-
|
|
126
|
+
await addNote({ channels: [...currentRelease.channels, nextRelease.channel] }, nextRelease.gitHead, {
|
|
127
|
+
cwd,
|
|
128
|
+
env,
|
|
129
|
+
});
|
|
130
|
+
await push(options.repositoryUrl, { cwd, env });
|
|
131
|
+
await pushNotes(options.repositoryUrl, { cwd, env });
|
|
129
132
|
logger.success(
|
|
130
|
-
`Add ${nextRelease.channel ? `channel ${nextRelease.channel}` :
|
|
133
|
+
`Add ${nextRelease.channel ? `channel ${nextRelease.channel}` : "default channel"} to tag ${
|
|
131
134
|
nextRelease.gitTag
|
|
132
135
|
}`
|
|
133
136
|
);
|
|
@@ -140,9 +143,9 @@ async function run(context, plugins) {
|
|
|
140
143
|
gitHead: nextRelease.gitHead,
|
|
141
144
|
});
|
|
142
145
|
|
|
143
|
-
const releases = await plugins.addChannel({...context, commits, lastRelease, currentRelease, nextRelease});
|
|
146
|
+
const releases = await plugins.addChannel({ ...context, commits, lastRelease, currentRelease, nextRelease });
|
|
144
147
|
context.releases.push(...releases);
|
|
145
|
-
await plugins.success({...context, lastRelease, commits, nextRelease, releases});
|
|
148
|
+
await plugins.success({ ...context, lastRelease, commits, nextRelease, releases });
|
|
146
149
|
}
|
|
147
150
|
}
|
|
148
151
|
|
|
@@ -152,7 +155,7 @@ async function run(context, plugins) {
|
|
|
152
155
|
|
|
153
156
|
context.lastRelease = getLastRelease(context);
|
|
154
157
|
if (context.lastRelease.gitHead) {
|
|
155
|
-
context.lastRelease.gitHead = await getTagHead(context.lastRelease.gitHead, {cwd, env});
|
|
158
|
+
context.lastRelease.gitHead = await getTagHead(context.lastRelease.gitHead, { cwd, env });
|
|
156
159
|
}
|
|
157
160
|
|
|
158
161
|
if (context.lastRelease.gitTag) {
|
|
@@ -168,11 +171,11 @@ async function run(context, plugins) {
|
|
|
168
171
|
const nextRelease = {
|
|
169
172
|
type: await plugins.analyzeCommits(context),
|
|
170
173
|
channel: context.branch.channel || null,
|
|
171
|
-
gitHead: await getGitHead({cwd, env}),
|
|
174
|
+
gitHead: await getGitHead({ cwd, env }),
|
|
172
175
|
};
|
|
173
176
|
if (!nextRelease.type) {
|
|
174
|
-
logger.log(
|
|
175
|
-
return context.releases.length > 0 ? {releases: context.releases} : false;
|
|
177
|
+
logger.log("There are no relevant changes, so no new version is released.");
|
|
178
|
+
return context.releases.length > 0 ? { releases: context.releases } : false;
|
|
176
179
|
}
|
|
177
180
|
|
|
178
181
|
context.nextRelease = nextRelease;
|
|
@@ -180,11 +183,11 @@ async function run(context, plugins) {
|
|
|
180
183
|
nextRelease.gitTag = makeTag(options.tagFormat, nextRelease.version);
|
|
181
184
|
nextRelease.name = nextRelease.gitTag;
|
|
182
185
|
|
|
183
|
-
if (context.branch.type !==
|
|
184
|
-
throw getError(
|
|
186
|
+
if (context.branch.type !== "prerelease" && !semver.satisfies(nextRelease.version, context.branch.range)) {
|
|
187
|
+
throw getError("EINVALIDNEXTVERSION", {
|
|
185
188
|
...context,
|
|
186
189
|
validBranches: context.branches.filter(
|
|
187
|
-
({type, accept}) => type !==
|
|
190
|
+
({ type, accept }) => type !== "prerelease" && accept.includes(nextRelease.type)
|
|
188
191
|
),
|
|
189
192
|
});
|
|
190
193
|
}
|
|
@@ -199,20 +202,20 @@ async function run(context, plugins) {
|
|
|
199
202
|
logger.warn(`Skip ${nextRelease.gitTag} tag creation in dry-run mode`);
|
|
200
203
|
} else {
|
|
201
204
|
// Create the tag before calling the publish plugins as some require the tag to exists
|
|
202
|
-
await tag(nextRelease.gitTag, nextRelease.gitHead, {cwd, env});
|
|
203
|
-
await addNote({channels: [nextRelease.channel]}, nextRelease.gitHead, {cwd, env});
|
|
204
|
-
await push(options.repositoryUrl, {cwd, env});
|
|
205
|
-
await pushNotes(options.repositoryUrl, {cwd, env});
|
|
205
|
+
await tag(nextRelease.gitTag, nextRelease.gitHead, { cwd, env });
|
|
206
|
+
await addNote({ channels: [nextRelease.channel] }, nextRelease.gitHead, { cwd, env });
|
|
207
|
+
await push(options.repositoryUrl, { cwd, env });
|
|
208
|
+
await pushNotes(options.repositoryUrl, { cwd, env });
|
|
206
209
|
logger.success(`Created tag ${nextRelease.gitTag}`);
|
|
207
210
|
}
|
|
208
211
|
|
|
209
212
|
const releases = await plugins.publish(context);
|
|
210
213
|
context.releases.push(...releases);
|
|
211
214
|
|
|
212
|
-
await plugins.success({...context, releases});
|
|
215
|
+
await plugins.success({ ...context, releases });
|
|
213
216
|
|
|
214
217
|
logger.success(
|
|
215
|
-
`Published release ${nextRelease.version} on ${nextRelease.channel ? nextRelease.channel :
|
|
218
|
+
`Published release ${nextRelease.version} on ${nextRelease.channel ? nextRelease.channel : "default"} channel`
|
|
216
219
|
);
|
|
217
220
|
|
|
218
221
|
if (options.dryRun) {
|
|
@@ -222,10 +225,10 @@ async function run(context, plugins) {
|
|
|
222
225
|
}
|
|
223
226
|
}
|
|
224
227
|
|
|
225
|
-
return pick(context, [
|
|
228
|
+
return pick(context, ["lastRelease", "commits", "nextRelease", "releases"]);
|
|
226
229
|
}
|
|
227
230
|
|
|
228
|
-
async function logErrors({logger, stderr}, err) {
|
|
231
|
+
async function logErrors({ logger, stderr }, err) {
|
|
229
232
|
const errors = extractErrors(err).sort((error) => (error.semanticRelease ? -1 : 0));
|
|
230
233
|
for (const error of errors) {
|
|
231
234
|
if (error.semanticRelease) {
|
|
@@ -234,7 +237,7 @@ async function logErrors({logger, stderr}, err) {
|
|
|
234
237
|
stderr.write(await terminalOutput(error.details)); // eslint-disable-line no-await-in-loop
|
|
235
238
|
}
|
|
236
239
|
} else {
|
|
237
|
-
logger.error(
|
|
240
|
+
logger.error("An error occurred while running semantic-release: %O", error);
|
|
238
241
|
}
|
|
239
242
|
}
|
|
240
243
|
}
|
|
@@ -243,16 +246,16 @@ async function callFail(context, plugins, err) {
|
|
|
243
246
|
const errors = extractErrors(err).filter((err) => err.semanticRelease);
|
|
244
247
|
if (errors.length > 0) {
|
|
245
248
|
try {
|
|
246
|
-
await plugins.fail({...context, errors});
|
|
249
|
+
await plugins.fail({ ...context, errors });
|
|
247
250
|
} catch (error) {
|
|
248
251
|
await logErrors(context, error);
|
|
249
252
|
}
|
|
250
253
|
}
|
|
251
254
|
}
|
|
252
255
|
|
|
253
|
-
export default async (cliOptions = {}, {cwd = process.cwd(), env = process.env, stdout, stderr} = {}) => {
|
|
254
|
-
const {unhook} = hookStd(
|
|
255
|
-
{silent: false, streams: [process.stdout, process.stderr, stdout, stderr].filter(Boolean)},
|
|
256
|
+
export default async (cliOptions = {}, { cwd = process.cwd(), env = process.env, stdout, stderr } = {}) => {
|
|
257
|
+
const { unhook } = hookStd(
|
|
258
|
+
{ silent: false, streams: [process.stdout, process.stderr, stdout, stderr].filter(Boolean) },
|
|
256
259
|
hideSensitive(env)
|
|
257
260
|
);
|
|
258
261
|
const context = {
|
|
@@ -260,12 +263,12 @@ export default async (cliOptions = {}, {cwd = process.cwd(), env = process.env,
|
|
|
260
263
|
env,
|
|
261
264
|
stdout: stdout || process.stdout,
|
|
262
265
|
stderr: stderr || process.stderr,
|
|
263
|
-
envCi: envCi({env, cwd}),
|
|
266
|
+
envCi: envCi({ env, cwd }),
|
|
264
267
|
};
|
|
265
268
|
context.logger = getLogger(context);
|
|
266
269
|
context.logger.log(`Running ${pkg.name} version ${pkg.version}`);
|
|
267
270
|
try {
|
|
268
|
-
const {plugins, options} = await getConfig(context, cliOptions);
|
|
271
|
+
const { plugins, options } = await getConfig(context, cliOptions);
|
|
269
272
|
options.originalRepositoryURL = options.repositoryUrl;
|
|
270
273
|
context.options = options;
|
|
271
274
|
try {
|
|
@@ -281,4 +284,4 @@ export default async (cliOptions = {}, {cwd = process.cwd(), env = process.env,
|
|
|
281
284
|
unhook();
|
|
282
285
|
throw error;
|
|
283
286
|
}
|
|
284
|
-
}
|
|
287
|
+
};
|
package/lib/get-commits.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import debugCommits from
|
|
2
|
-
import {getCommits} from
|
|
1
|
+
import debugCommits from "debug";
|
|
2
|
+
import { getCommits } from "./git.js";
|
|
3
3
|
|
|
4
|
-
const debug = debugCommits(
|
|
4
|
+
const debug = debugCommits("semantic-release:get-commits");
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Retrieve the list of commits on the current branch since the commit sha associated with the last release, or all the commits of the current branch if there is no last released version.
|
|
@@ -10,16 +10,22 @@ const debug = debugCommits('semantic-release:get-commits');
|
|
|
10
10
|
*
|
|
11
11
|
* @return {Promise<Array<Object>>} The list of commits on the branch `branch` since the last release.
|
|
12
12
|
*/
|
|
13
|
-
export default async ({
|
|
13
|
+
export default async ({
|
|
14
|
+
cwd,
|
|
15
|
+
env,
|
|
16
|
+
lastRelease: { gitHead: from },
|
|
17
|
+
nextRelease: { gitHead: to = "HEAD" } = {},
|
|
18
|
+
logger,
|
|
19
|
+
}) => {
|
|
14
20
|
if (from) {
|
|
15
|
-
debug(
|
|
21
|
+
debug("Use from: %s", from);
|
|
16
22
|
} else {
|
|
17
|
-
logger.log(
|
|
23
|
+
logger.log("No previous release found, retrieving all commits");
|
|
18
24
|
}
|
|
19
25
|
|
|
20
|
-
const commits = await getCommits(from, to, {cwd, env});
|
|
26
|
+
const commits = await getCommits(from, to, { cwd, env });
|
|
21
27
|
|
|
22
28
|
logger.log(`Found ${commits.length} commits since last release`);
|
|
23
|
-
debug(
|
|
29
|
+
debug("Parsed commits: %o", commits);
|
|
24
30
|
return commits;
|
|
25
|
-
}
|
|
31
|
+
};
|
package/lib/get-config.js
CHANGED
|
@@ -1,39 +1,39 @@
|
|
|
1
|
-
import {dirname, resolve} from
|
|
2
|
-
import {fileURLToPath} from
|
|
3
|
-
import {createRequire} from
|
|
1
|
+
import { dirname, resolve } from "node:path";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
4
|
|
|
5
|
-
import {castArray, isNil, isPlainObject, isString, pickBy} from
|
|
6
|
-
import {readPackageUp} from
|
|
7
|
-
import {cosmiconfig} from
|
|
8
|
-
import resolveFrom from
|
|
9
|
-
import debugConfig from
|
|
10
|
-
import {repoUrl} from
|
|
11
|
-
import PLUGINS_DEFINITIONS from
|
|
12
|
-
import plugins from
|
|
13
|
-
import {parseConfig, validatePlugin} from
|
|
5
|
+
import { castArray, isNil, isPlainObject, isString, pickBy } from "lodash-es";
|
|
6
|
+
import { readPackageUp } from "read-pkg-up";
|
|
7
|
+
import { cosmiconfig } from "cosmiconfig";
|
|
8
|
+
import resolveFrom from "resolve-from";
|
|
9
|
+
import debugConfig from "debug";
|
|
10
|
+
import { repoUrl } from "./git.js";
|
|
11
|
+
import PLUGINS_DEFINITIONS from "./definitions/plugins.js";
|
|
12
|
+
import plugins from "./plugins/index.js";
|
|
13
|
+
import { parseConfig, validatePlugin } from "./plugins/utils.js";
|
|
14
14
|
|
|
15
|
-
const debug = debugConfig(
|
|
15
|
+
const debug = debugConfig("semantic-release:config");
|
|
16
16
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
17
17
|
const require = createRequire(import.meta.url);
|
|
18
18
|
|
|
19
|
-
const CONFIG_NAME =
|
|
19
|
+
const CONFIG_NAME = "release";
|
|
20
20
|
|
|
21
21
|
export default async (context, cliOptions) => {
|
|
22
|
-
const {cwd, env} = context;
|
|
23
|
-
const {config, filepath} = (await cosmiconfig(CONFIG_NAME).search(cwd)) || {};
|
|
22
|
+
const { cwd, env } = context;
|
|
23
|
+
const { config, filepath } = (await cosmiconfig(CONFIG_NAME).search(cwd)) || {};
|
|
24
24
|
|
|
25
|
-
debug(
|
|
25
|
+
debug("load config from: %s", filepath);
|
|
26
26
|
|
|
27
27
|
// Merge config file options and CLI/API options
|
|
28
|
-
let options = {...config, ...cliOptions};
|
|
28
|
+
let options = { ...config, ...cliOptions };
|
|
29
29
|
|
|
30
30
|
const pluginsPath = {};
|
|
31
31
|
let extendPaths;
|
|
32
|
-
({extends: extendPaths, ...options} = options);
|
|
32
|
+
({ extends: extendPaths, ...options } = options);
|
|
33
33
|
if (extendPaths) {
|
|
34
34
|
// If `extends` is defined, load and merge each shareable config with `options`
|
|
35
35
|
options = {
|
|
36
|
-
...await
|
|
36
|
+
...(await castArray(extendPaths).reduce(async (eventualResult, extendPath) => {
|
|
37
37
|
const result = await eventualResult;
|
|
38
38
|
const extendsOptions = require(resolveFrom.silent(__dirname, extendPath) || resolveFrom(cwd, extendPath));
|
|
39
39
|
|
|
@@ -43,7 +43,7 @@ export default async (context, cliOptions) => {
|
|
|
43
43
|
.filter(([, value]) => Boolean(value))
|
|
44
44
|
.reduce((pluginsPath, [option, value]) => {
|
|
45
45
|
castArray(value).forEach((plugin) => {
|
|
46
|
-
if (option ===
|
|
46
|
+
if (option === "plugins" && validatePlugin(plugin)) {
|
|
47
47
|
pluginsPath[parseConfig(plugin)[0]] = extendPath;
|
|
48
48
|
} else if (
|
|
49
49
|
PLUGINS_DEFINITIONS[option] &&
|
|
@@ -55,7 +55,7 @@ export default async (context, cliOptions) => {
|
|
|
55
55
|
return pluginsPath;
|
|
56
56
|
}, pluginsPath);
|
|
57
57
|
|
|
58
|
-
return {...result, ...extendsOptions};
|
|
58
|
+
return { ...result, ...extendsOptions };
|
|
59
59
|
}, {})),
|
|
60
60
|
...options,
|
|
61
61
|
};
|
|
@@ -64,36 +64,36 @@ export default async (context, cliOptions) => {
|
|
|
64
64
|
// Set default options values if not defined yet
|
|
65
65
|
options = {
|
|
66
66
|
branches: [
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
{name:
|
|
72
|
-
{name:
|
|
67
|
+
"+([0-9])?(.{+([0-9]),x}).x",
|
|
68
|
+
"master",
|
|
69
|
+
"next",
|
|
70
|
+
"next-major",
|
|
71
|
+
{ name: "beta", prerelease: true },
|
|
72
|
+
{ name: "alpha", prerelease: true },
|
|
73
73
|
],
|
|
74
|
-
repositoryUrl: (await pkgRepoUrl({normalize: false, cwd})) || (await repoUrl({cwd, env})),
|
|
74
|
+
repositoryUrl: (await pkgRepoUrl({ normalize: false, cwd })) || (await repoUrl({ cwd, env })),
|
|
75
75
|
tagFormat: `v\${version}`,
|
|
76
76
|
plugins: [
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
77
|
+
"@semantic-release/commit-analyzer",
|
|
78
|
+
"@semantic-release/release-notes-generator",
|
|
79
|
+
"@semantic-release/npm",
|
|
80
|
+
"@semantic-release/github",
|
|
81
81
|
],
|
|
82
82
|
// Remove `null` and `undefined` options, so they can be replaced with default ones
|
|
83
83
|
...pickBy(options, (option) => !isNil(option)),
|
|
84
|
-
...(options.branches ? {branches: castArray(options.branches)} : {}),
|
|
84
|
+
...(options.branches ? { branches: castArray(options.branches) } : {}),
|
|
85
85
|
};
|
|
86
86
|
|
|
87
87
|
if (options.ci === false) {
|
|
88
88
|
options.noCi = true;
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
-
debug(
|
|
91
|
+
debug("options values: %O", options);
|
|
92
92
|
|
|
93
|
-
return {options, plugins: await plugins({...context, options}, pluginsPath)};
|
|
94
|
-
}
|
|
93
|
+
return { options, plugins: await plugins({ ...context, options }, pluginsPath) };
|
|
94
|
+
};
|
|
95
95
|
|
|
96
96
|
async function pkgRepoUrl(options) {
|
|
97
|
-
const {packageJson} = (await readPackageUp(options)) || {};
|
|
97
|
+
const { packageJson } = (await readPackageUp(options)) || {};
|
|
98
98
|
return packageJson && (isPlainObject(packageJson.repository) ? packageJson.repository.url : packageJson.repository);
|
|
99
99
|
}
|
package/lib/get-error.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import SemanticReleaseError from
|
|
2
|
-
import * as ERROR_DEFINITIONS from
|
|
1
|
+
import SemanticReleaseError from "@semantic-release/error";
|
|
2
|
+
import * as ERROR_DEFINITIONS from "./definitions/errors.js";
|
|
3
3
|
|
|
4
4
|
export default (code, ctx = {}) => {
|
|
5
|
-
const {message, details} = ERROR_DEFINITIONS[code](ctx);
|
|
5
|
+
const { message, details } = ERROR_DEFINITIONS[code](ctx);
|
|
6
6
|
return new SemanticReleaseError(message, code, details);
|
|
7
|
-
}
|
|
7
|
+
};
|