@anolilab/multi-semantic-release 2.0.6 → 3.0.0-alpha.2
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/CHANGELOG.md +34 -0
- package/README.md +1 -1
- package/dist/bin/cli.js +2 -0
- package/dist/multi-semantic-release.d.ts +122 -0
- package/dist/multi-semantic-release.js +1 -0
- package/dist/packem_shared/multi-semantic-release-CD0fW8rP.js +4 -0
- package/package.json +13 -9
- package/bin/cli.js +0 -108
- package/lib/create-inline-plugin-creator.js +0 -268
- package/lib/get-commits-filtered.js +0 -85
- package/lib/get-config-multi-semrel.js +0 -78
- package/lib/get-config-semantic.js +0 -38
- package/lib/get-config.js +0 -33
- package/lib/get-manifest.js +0 -89
- package/lib/logger.js +0 -80
- package/lib/multi-semantic-release.js +0 -291
- package/lib/rescoped-stream.js +0 -31
- package/lib/update-deps.js +0 -351
- package/lib/utils/blork.js +0 -23
- package/lib/utils/clean-path.js +0 -23
- package/lib/utils/get-version.js +0 -38
- package/lib/utils/merge-config.js +0 -21
- package/lib/utils/recognize-format.js +0 -24
- package/lib/utils/stream-to-array.js +0 -76
|
@@ -1,291 +0,0 @@
|
|
|
1
|
-
import { createRequire } from "node:module";
|
|
2
|
-
import { dirname } from "node:path";
|
|
3
|
-
|
|
4
|
-
import { topo } from "@semrel-extra/topo";
|
|
5
|
-
// eslint-disable-next-line you-dont-need-lodash-underscore/cast-array
|
|
6
|
-
import { castArray, sortBy, template } from "lodash-es";
|
|
7
|
-
import semanticRelease from "semantic-release";
|
|
8
|
-
|
|
9
|
-
import createInlinePluginCreator from "./create-inline-plugin-creator.js";
|
|
10
|
-
import getConfig from "./get-config.js";
|
|
11
|
-
import getConfigMultiSemrel from "./get-config-multi-semrel.js";
|
|
12
|
-
import getConfigSemantic from "./get-config-semantic.js";
|
|
13
|
-
import getManifest from "./get-manifest.js";
|
|
14
|
-
import logger from "./logger.js";
|
|
15
|
-
import RescopedStream from "./rescoped-stream.js";
|
|
16
|
-
import { check } from "./utils/blork.js";
|
|
17
|
-
import cleanPath from "./utils/clean-path.js";
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Load details about a package.
|
|
21
|
-
* @param {string} path The path to load details about.
|
|
22
|
-
* @param {object} allOptions Options that apply to all packages.
|
|
23
|
-
* @param {MultiContext} multiContext Context object for the multirelease.
|
|
24
|
-
* @param allOptions.cwd
|
|
25
|
-
* @param allOptions.env
|
|
26
|
-
* @param allOptions.globalOptions
|
|
27
|
-
* @param allOptions.inputOptions
|
|
28
|
-
* @param allOptions.stderr
|
|
29
|
-
* @param allOptions.stdout
|
|
30
|
-
* @returns {Promise<Package|void>} A package object, or void if the package was skipped.
|
|
31
|
-
* @internal
|
|
32
|
-
*/
|
|
33
|
-
async function getPackage(path, { cwd, env, globalOptions, inputOptions, stderr, stdout }) {
|
|
34
|
-
// Make path absolute.
|
|
35
|
-
// eslint-disable-next-line no-param-reassign
|
|
36
|
-
path = cleanPath(path, cwd);
|
|
37
|
-
|
|
38
|
-
const directory = dirname(path);
|
|
39
|
-
|
|
40
|
-
// Get package.json file contents.
|
|
41
|
-
const manifest = getManifest(path);
|
|
42
|
-
const { name } = manifest;
|
|
43
|
-
|
|
44
|
-
// Combine list of all dependency names.
|
|
45
|
-
const deps = Object.keys({
|
|
46
|
-
...manifest.dependencies,
|
|
47
|
-
...manifest.devDependencies,
|
|
48
|
-
...manifest.peerDependencies,
|
|
49
|
-
...manifest.optionalDependencies,
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
// Load the package-specific options.
|
|
53
|
-
const packageOptions = await getConfig(directory);
|
|
54
|
-
|
|
55
|
-
// The 'final options' are the global options merged with package-specific options.
|
|
56
|
-
// We merge this ourselves because package-specific options can override global options.
|
|
57
|
-
const finalOptions = { ...globalOptions, ...packageOptions, ...inputOptions };
|
|
58
|
-
|
|
59
|
-
// Make a fake logger so semantic-release's get-config doesn't fail.
|
|
60
|
-
const fakeLogger = { error() {}, log() {} };
|
|
61
|
-
|
|
62
|
-
// Use semantic-release's internal config with the final options (now we have the right `options.plugins` setting) to get the plugins object and the options including defaults.
|
|
63
|
-
// We need this so we can call e.g. plugins.analyzeCommit() to be able to affect the input and output of the whole set of plugins.
|
|
64
|
-
const { options, plugins } = await getConfigSemantic({ cwd: directory, env, stderr, stdout }, finalOptions);
|
|
65
|
-
|
|
66
|
-
// Return package object.
|
|
67
|
-
return { deps, dir: directory, fakeLogger, manifest, name, options, path, plugins };
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Release an individual package.
|
|
72
|
-
* @param {Package} pkg The specific package.
|
|
73
|
-
* @param package_
|
|
74
|
-
* @param {Function} createInlinePlugin A function that creates an inline plugin.
|
|
75
|
-
* @param {MultiContext} multiContext Context object for the multirelease.
|
|
76
|
-
* @param {object} flags Argv flags.
|
|
77
|
-
* @returns {Promise<void>} Promise that resolves when done.
|
|
78
|
-
* @internal
|
|
79
|
-
*/
|
|
80
|
-
async function releasePackage(package_, createInlinePlugin, multiContext, flags) {
|
|
81
|
-
// Vars.
|
|
82
|
-
const { dir, name, options: packageOptions } = package_;
|
|
83
|
-
const { env, stderr, stdout } = multiContext;
|
|
84
|
-
|
|
85
|
-
// Make an 'inline plugin' for this package.
|
|
86
|
-
// The inline plugin is the only plugin we call semanticRelease() with.
|
|
87
|
-
// The inline plugin functions then call e.g. plugins.analyzeCommits() manually and sometimes manipulate the responses.
|
|
88
|
-
const inlinePlugin = createInlinePlugin(package_);
|
|
89
|
-
|
|
90
|
-
// Set the options that we call semanticRelease() with.
|
|
91
|
-
// This consists of:
|
|
92
|
-
// - The global options (e.g. from the top level package.json)
|
|
93
|
-
// - The package options (e.g. from the specific package's package.json)
|
|
94
|
-
const options = { ...packageOptions, ...inlinePlugin };
|
|
95
|
-
|
|
96
|
-
// Add the package name into tagFormat.
|
|
97
|
-
// Thought about doing a single release for the tag (merging several packages), but it's impossible to prevent Github releasing while allowing NPM to continue.
|
|
98
|
-
// It'd also be difficult to merge all the assets into one release without full editing/overriding the plugins.
|
|
99
|
-
const tagFormatContext = {
|
|
100
|
-
name,
|
|
101
|
-
// eslint-disable-next-line no-template-curly-in-string
|
|
102
|
-
version: "${version}",
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
// eslint-disable-next-line no-template-curly-in-string
|
|
106
|
-
const tagFormatDefault = "${name}@${version}";
|
|
107
|
-
|
|
108
|
-
options.tagFormat = template(flags.tagFormat || tagFormatDefault)(tagFormatContext);
|
|
109
|
-
|
|
110
|
-
// These are the only two options that MSR shares with semrel
|
|
111
|
-
// Set them manually for now, defaulting to the msr versions
|
|
112
|
-
// This is approach can be reviewed if there's ever more crossover.
|
|
113
|
-
// - debug is only supported in semrel as a CLI arg, always default to MSR
|
|
114
|
-
options.debug = flags.debug;
|
|
115
|
-
// - dryRun should use the msr version if specified, otherwise fallback to semrel
|
|
116
|
-
options.dryRun = flags.dryRun === undefined ? options.dryRun : flags.dryRun;
|
|
117
|
-
options.ci = flags.ci === undefined ? options.ci : flags.ci;
|
|
118
|
-
options.branches = flags.branches ? castArray(flags.branches) : options.branches;
|
|
119
|
-
|
|
120
|
-
// This options are needed for plugins that do not rely on `pluginOptions` and extract them independently.
|
|
121
|
-
options._pkgOptions = packageOptions;
|
|
122
|
-
|
|
123
|
-
// Call semanticRelease() on the directory and save result to pkg.
|
|
124
|
-
// Don't need to log out errors as semantic-release already does that.
|
|
125
|
-
// eslint-disable-next-line no-param-reassign
|
|
126
|
-
package_.result = await semanticRelease(options, {
|
|
127
|
-
cwd: dir,
|
|
128
|
-
env,
|
|
129
|
-
stderr: new RescopedStream(stderr, name),
|
|
130
|
-
stdout: new RescopedStream(stdout, name),
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
return package_;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* The multi-release context.
|
|
138
|
-
* @typedef MultiContext
|
|
139
|
-
* @param {Package[]} packages Array of all packages in this multirelease.
|
|
140
|
-
* @param {Package[]} releasing Array of packages that will release.
|
|
141
|
-
* @param {string} cwd The current working directory.
|
|
142
|
-
* @param {object} env The environment variables.
|
|
143
|
-
* @param {Logger} logger The logger for the multirelease.
|
|
144
|
-
* @param {Stream} stdout The output stream for this multirelease.
|
|
145
|
-
* @param {Stream} stderr The error stream for this multirelease.
|
|
146
|
-
*/
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Details about an individual package in a multirelease
|
|
150
|
-
* @typedef Package
|
|
151
|
-
* @param {string} path String path to `package.json` for the package.
|
|
152
|
-
* @param {string} dir The working directory for the package.
|
|
153
|
-
* @param {string} name The name of the package, e.g. `my-amazing-package`
|
|
154
|
-
* @param {string[]} deps Array of all dependency package names for the package (merging dependencies, devDependencies, peerDependencies).
|
|
155
|
-
* @param {Package[]} localDeps Array of local dependencies this package relies on.
|
|
156
|
-
* @param {context|void} context The semantic-release context for this package's release (filled in once semantic-release runs).
|
|
157
|
-
* @param {undefined|Result|false} result The result of semantic-release (object with lastRelease, nextRelease, commits, releases), false if this package was skipped (no changes or similar), or undefined if the package's release hasn't completed yet.
|
|
158
|
-
* @param {object} _lastRelease The last release object for the package before its current release (set during anaylze-commit)
|
|
159
|
-
* @param {object} _nextRelease The next release object (the release the package is releasing for this cycle) (set during generateNotes)
|
|
160
|
-
*/
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* Perform a multirelease.
|
|
164
|
-
* @param {string[]} paths An array of paths to package.json files.
|
|
165
|
-
* @param {object} inputOptions An object containing semantic-release options.
|
|
166
|
-
* @param {object} settings An object containing: cwd, env, stdout, stderr (mainly for configuring tests).
|
|
167
|
-
* @param settings.cwd
|
|
168
|
-
* @param settings.env
|
|
169
|
-
* @param {object} _flags Argv flags.
|
|
170
|
-
* @param settings.stderr
|
|
171
|
-
* @param settings.stdout
|
|
172
|
-
* @returns {Promise<Package[]>} Promise that resolves to a list of package objects with `result` property describing whether it released or not.
|
|
173
|
-
*/
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
*
|
|
177
|
-
* @param paths
|
|
178
|
-
* @param inputOptions
|
|
179
|
-
* @param root0
|
|
180
|
-
* @param root0.cwd
|
|
181
|
-
* @param root0.env
|
|
182
|
-
* @param root0.stderr
|
|
183
|
-
* @param root0.stdout
|
|
184
|
-
* @param _flags
|
|
185
|
-
*/
|
|
186
|
-
async function multiSemanticRelease(
|
|
187
|
-
paths,
|
|
188
|
-
inputOptions = {},
|
|
189
|
-
{ cwd = process.cwd(), env: environment = process.env, stderr = process.stderr, stdout = process.stdout } = {},
|
|
190
|
-
_flags = {},
|
|
191
|
-
) {
|
|
192
|
-
if (paths) {
|
|
193
|
-
check(paths, "paths: string[]");
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
check(cwd, "cwd: directory");
|
|
197
|
-
check(environment, "env: objectlike");
|
|
198
|
-
check(stdout, "stdout: stream");
|
|
199
|
-
check(stderr, "stderr: stream");
|
|
200
|
-
|
|
201
|
-
// eslint-disable-next-line no-param-reassign
|
|
202
|
-
cwd = cleanPath(cwd);
|
|
203
|
-
|
|
204
|
-
const flags = {
|
|
205
|
-
deps: {},
|
|
206
|
-
...await getConfigMultiSemrel(cwd, _flags),
|
|
207
|
-
};
|
|
208
|
-
|
|
209
|
-
const require = createRequire(import.meta.url);
|
|
210
|
-
const multisemrelPackageJson = require("../package.json");
|
|
211
|
-
const semrelPkgJson = require("semantic-release/package.json");
|
|
212
|
-
|
|
213
|
-
// Setup logger.
|
|
214
|
-
logger.config.stdio = [stderr, stdout];
|
|
215
|
-
logger.config.level = flags.logLevel;
|
|
216
|
-
|
|
217
|
-
if (flags.silent) {
|
|
218
|
-
logger.config.level = "silent";
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
if (flags.debug) {
|
|
222
|
-
logger.config.level = "debug";
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
logger.info(`multi-semantic-release version: ${multisemrelPackageJson.version}`);
|
|
226
|
-
logger.info(`semantic-release version: ${semrelPkgJson.version}`);
|
|
227
|
-
|
|
228
|
-
if (flags.debug) {
|
|
229
|
-
logger.info(`flags: ${JSON.stringify(flags, null, 2)}`);
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// Vars.
|
|
233
|
-
const globalOptions = await getConfig(cwd);
|
|
234
|
-
const multiContext = { cwd, env: environment, globalOptions, inputOptions, stderr, stdout };
|
|
235
|
-
const { packages: _packages, queue } = await topo({
|
|
236
|
-
cwd,
|
|
237
|
-
filter: ({ manifest, manifestAbsPath, manifestRelPath }) =>
|
|
238
|
-
(!flags.ignorePrivate || !manifest.private) && (paths ? paths.includes(manifestAbsPath) || paths.includes(manifestRelPath) : true),
|
|
239
|
-
workspacesExtra: Array.isArray(flags.ignorePackages) ? flags.ignorePackages.map((p) => `!${p}`) : [],
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
// Get list of package.json paths according to workspaces.
|
|
243
|
-
// eslint-disable-next-line no-param-reassign
|
|
244
|
-
paths = paths || Object.values(_packages).map((package_) => package_.manifestPath);
|
|
245
|
-
|
|
246
|
-
// Start.
|
|
247
|
-
logger.complete(`Started multirelease! Loading ${paths.length} packages...`);
|
|
248
|
-
|
|
249
|
-
// Load packages from paths.
|
|
250
|
-
// eslint-disable-next-line compat/compat
|
|
251
|
-
const packages = await Promise.all(paths.map((path) => getPackage(path, multiContext)));
|
|
252
|
-
|
|
253
|
-
packages.forEach((package_) => {
|
|
254
|
-
// Once we load all the packages we can find their cross refs
|
|
255
|
-
// Make a list of local dependencies.
|
|
256
|
-
// Map dependency names (e.g. my-awesome-dep) to their actual package objects in the packages array.
|
|
257
|
-
// eslint-disable-next-line no-param-reassign
|
|
258
|
-
package_.localDeps = [...new Set(package_.deps.map((d) => packages.find((p) => d === p.name)).filter(Boolean))];
|
|
259
|
-
|
|
260
|
-
logger.success(`Loaded package ${package_.name}`);
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
logger.complete(`Queued ${queue.length} packages! Starting release...`);
|
|
264
|
-
|
|
265
|
-
// Release all packages.
|
|
266
|
-
const createInlinePlugin = createInlinePluginCreator(packages, multiContext, flags);
|
|
267
|
-
// eslint-disable-next-line unicorn/no-array-reduce
|
|
268
|
-
const released = await queue.reduce(async (_m, _name) => {
|
|
269
|
-
const m = await _m;
|
|
270
|
-
const package_ = packages.find(({ name }) => name === _name);
|
|
271
|
-
|
|
272
|
-
if (package_) {
|
|
273
|
-
const { result } = await releasePackage(package_, createInlinePlugin, multiContext, flags);
|
|
274
|
-
|
|
275
|
-
if (result) {
|
|
276
|
-
return m + 1;
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
return m;
|
|
281
|
-
// eslint-disable-next-line compat/compat
|
|
282
|
-
}, Promise.resolve(0));
|
|
283
|
-
|
|
284
|
-
// Return packages list.
|
|
285
|
-
logger.complete(`Released ${released} of ${queue.length} packages, semantically!`);
|
|
286
|
-
|
|
287
|
-
return sortBy(packages, ({ name }) => queue.indexOf(name));
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
// Exports.
|
|
291
|
-
export default multiSemanticRelease;
|
package/lib/rescoped-stream.js
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { Writable } from "node:stream";
|
|
2
|
-
|
|
3
|
-
import { check } from "./utils/blork.js";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Create a stream that passes messages through while rewriting scope.
|
|
7
|
-
* Replaces `[semantic-release]` with a custom scope (e.g. `[my-awesome-package]`) so output makes more sense.
|
|
8
|
-
* @param {stream.Writable} stream The actual stream to write messages to.
|
|
9
|
-
* @param {string} scope The string scope for the stream (instances of the text `[semantic-release]` are replaced in the stream).
|
|
10
|
-
* @returns {stream.Writable} Object that's compatible with stream.Writable (implements a `write()` property).
|
|
11
|
-
* @internal
|
|
12
|
-
*/
|
|
13
|
-
class RescopedStream extends Writable {
|
|
14
|
-
// Constructor.
|
|
15
|
-
constructor(stream, scope) {
|
|
16
|
-
super();
|
|
17
|
-
check(scope, "scope: string");
|
|
18
|
-
check(stream, "stream: stream");
|
|
19
|
-
this._stream = stream;
|
|
20
|
-
this._scope = scope;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// Custom write method.
|
|
24
|
-
write(message) {
|
|
25
|
-
check(message, "msg: string");
|
|
26
|
-
this._stream.write(message.replace("[semantic-release]", `[${this._scope}]`));
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// Exports.
|
|
31
|
-
export default RescopedStream;
|
package/lib/update-deps.js
DELETED
|
@@ -1,351 +0,0 @@
|
|
|
1
|
-
import { writeFileSync } from "node:fs";
|
|
2
|
-
|
|
3
|
-
import { isEqual, isObject, transform } from "lodash-es";
|
|
4
|
-
import semver from "semver";
|
|
5
|
-
|
|
6
|
-
import getManifest from "./get-manifest.js";
|
|
7
|
-
import logger from "./logger.js";
|
|
8
|
-
import { getHighestVersion, getLatestVersion } from "./utils/get-version.js";
|
|
9
|
-
import recognizeFormat from "./utils/recognize-format.js";
|
|
10
|
-
|
|
11
|
-
const { debug } = logger.withScope("msr:updateDeps");
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Resolve next prerelease comparing bumped tags versions with last version.
|
|
15
|
-
* @param {string|null} latestTag Last released tag from branch or null if non-existent.
|
|
16
|
-
* @param {string} lastVersion Last version released.
|
|
17
|
-
* @param {string} packagePreRelease Prerelease tag from package to-be-released.
|
|
18
|
-
* @returns {string} Next pkg version.
|
|
19
|
-
* @internal
|
|
20
|
-
*/
|
|
21
|
-
const _nextPreHighestVersion = (latestTag, lastVersion, packagePreRelease) => {
|
|
22
|
-
const bumpFromTags = latestTag ? semver.inc(latestTag, "prerelease", packagePreRelease) : null;
|
|
23
|
-
const bumpFromLast = semver.inc(lastVersion, "prerelease", packagePreRelease);
|
|
24
|
-
|
|
25
|
-
return bumpFromTags ? getHighestVersion(bumpFromLast, bumpFromTags) : bumpFromLast;
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Resolve next prerelease special cases: highest version from tags or major/minor/patch.#
|
|
30
|
-
* @param {Array<string>} tags - if non-empty, we will use these tags as part fo the comparison
|
|
31
|
-
* @param {string} lastVersionForCurrentMultiRelease Last package version released from multi-semantic-release
|
|
32
|
-
* @param {string} packageNextType Next type evaluated for the next package type.
|
|
33
|
-
* @param {string} packagePreRelease Package prerelease suffix.
|
|
34
|
-
* @returns {string|undefined} Next pkg version.
|
|
35
|
-
* @internal
|
|
36
|
-
*/
|
|
37
|
-
const _nextPreVersionCases = (tags, lastVersionForCurrentMultiRelease, packageNextType, packagePreRelease) => {
|
|
38
|
-
// Case 1: Normal release on last version and is now converted to a prerelease
|
|
39
|
-
if (!semver.prerelease(lastVersionForCurrentMultiRelease)) {
|
|
40
|
-
const { major, minor, patch } = semver.parse(lastVersionForCurrentMultiRelease);
|
|
41
|
-
|
|
42
|
-
return `${semver.inc(`${major}.${minor}.${patch}`, packageNextType || "patch")}-${packagePreRelease}.1`;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Case 2: Validates version with tags
|
|
46
|
-
const latestTag = getLatestVersion(tags, true);
|
|
47
|
-
|
|
48
|
-
return _nextPreHighestVersion(latestTag, lastVersionForCurrentMultiRelease, packagePreRelease);
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Get dependent release type by recursive scanning and updating pkg deps.
|
|
53
|
-
* @param {Package} package_ The package with local deps to check.
|
|
54
|
-
* @param {string} bumpStrategy Dependency resolution strategy: override, satisfy, inherit.
|
|
55
|
-
* @param {string} releaseStrategy Release type triggered by deps updating: patch, minor, major, inherit.
|
|
56
|
-
* @param {Package[]} ignore Packages to ignore (to prevent infinite loops).
|
|
57
|
-
* @param {string} prefix Dependency version prefix to be attached if `bumpStrategy='override'`. ^ | ~ | '' (defaults to empty string)
|
|
58
|
-
* @returns {string|undefined} Returns the highest release type if found, undefined otherwise
|
|
59
|
-
* @internal
|
|
60
|
-
*/
|
|
61
|
-
const getDependentRelease = (package_, bumpStrategy, releaseStrategy, ignore, prefix) => {
|
|
62
|
-
const severityOrder = ["patch", "minor", "major"];
|
|
63
|
-
const { localDeps, manifest = {} } = package_;
|
|
64
|
-
const lastVersion = package_._lastRelease && package_._lastRelease.version;
|
|
65
|
-
const { dependencies = {}, devDependencies = {}, optionalDependencies = {}, peerDependencies = {} } = manifest;
|
|
66
|
-
const scopes = [dependencies, devDependencies, peerDependencies, optionalDependencies];
|
|
67
|
-
const bumpDependency = (scope, name, nextVersion) => {
|
|
68
|
-
const currentVersion = scope[name];
|
|
69
|
-
|
|
70
|
-
if (!nextVersion || !currentVersion) {
|
|
71
|
-
return false;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// eslint-disable-next-line no-use-before-define
|
|
75
|
-
const resolvedVersion = resolveNextVersion(currentVersion, nextVersion, bumpStrategy, prefix);
|
|
76
|
-
|
|
77
|
-
if (currentVersion !== resolvedVersion) {
|
|
78
|
-
// eslint-disable-next-line no-param-reassign
|
|
79
|
-
scope[name] = resolvedVersion;
|
|
80
|
-
|
|
81
|
-
return true;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
return false;
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
return (
|
|
88
|
-
localDeps
|
|
89
|
-
.filter((p) => !ignore.includes(p))
|
|
90
|
-
// eslint-disable-next-line unicorn/no-array-reduce
|
|
91
|
-
.reduce((releaseType, p) => {
|
|
92
|
-
// Has changed if...
|
|
93
|
-
// 1. Any local dep package itself has changed
|
|
94
|
-
// 2. Any local dep package has local deps that have changed.
|
|
95
|
-
// eslint-disable-next-line no-use-before-define
|
|
96
|
-
const nextType = resolveReleaseType(p, bumpStrategy, releaseStrategy, [...ignore, package_], prefix);
|
|
97
|
-
const nextVersion = nextType
|
|
98
|
-
? // Update the nextVersion only if there is a next type to be bumped
|
|
99
|
-
|
|
100
|
-
p._preRelease
|
|
101
|
-
? // eslint-disable-next-line no-use-before-define
|
|
102
|
-
getNextPreVersion(p)
|
|
103
|
-
: // eslint-disable-next-line no-use-before-define
|
|
104
|
-
getNextVersion(p)
|
|
105
|
-
: // Set the nextVersion fallback to the last local dependency package last version
|
|
106
|
-
p._lastRelease && p._lastRelease.version;
|
|
107
|
-
|
|
108
|
-
// 3. And this change should correspond to the manifest updating rule.
|
|
109
|
-
const requireRelease = scopes
|
|
110
|
-
// eslint-disable-next-line unicorn/no-array-reduce
|
|
111
|
-
.reduce((result, scope) => bumpDependency(scope, p.name, nextVersion) || result, !lastVersion);
|
|
112
|
-
|
|
113
|
-
return requireRelease && severityOrder.indexOf(nextType) > severityOrder.indexOf(releaseType) ? nextType : releaseType;
|
|
114
|
-
}, undefined)
|
|
115
|
-
);
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Substitute "workspace:" in currentVersion
|
|
120
|
-
* See:
|
|
121
|
-
* {@link https://yarnpkg.com/features/workspaces#publishing-workspaces}
|
|
122
|
-
* {@link https://pnpm.io/workspaces#publishing-workspace-packages}
|
|
123
|
-
* @param {string} currentVersion Current version, may start with "workspace:"
|
|
124
|
-
* @param {string} nextVersion Next version
|
|
125
|
-
* @returns {string} current version without "workspace:"
|
|
126
|
-
*/
|
|
127
|
-
const substituteWorkspaceVersion = (currentVersion, nextVersion) => {
|
|
128
|
-
if (currentVersion.startsWith("workspace:")) {
|
|
129
|
-
// eslint-disable-next-line regexp/optimal-quantifier-concatenation
|
|
130
|
-
const [, range, caret] = /^workspace:(([\^~*])?.*)$/u.exec(currentVersion);
|
|
131
|
-
|
|
132
|
-
return caret === range ? caret === "*" ? nextVersion : caret + nextVersion : range;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
return currentVersion;
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
// eslint-disable-next-line no-secrets/no-secrets
|
|
139
|
-
// https://gist.github.com/Yimiprod/7ee176597fef230d1451
|
|
140
|
-
const difference = (object, base) =>
|
|
141
|
-
transform(object, (result, value, key) => {
|
|
142
|
-
if (!isEqual(value, base[key])) {
|
|
143
|
-
// eslint-disable-next-line no-param-reassign
|
|
144
|
-
result[key] = isObject(value) && isObject(base[key]) ? difference(value, base[key]) : `${base[key]} → ${value}`;
|
|
145
|
-
}
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Clarify what exactly was changed in manifest file.
|
|
150
|
-
* @param {object} actualManifest manifest object
|
|
151
|
-
* @param {string} path manifest path
|
|
152
|
-
* @returns {boolean} has changed or not
|
|
153
|
-
* @internal
|
|
154
|
-
*/
|
|
155
|
-
const auditManifestChanges = (actualManifest, path) => {
|
|
156
|
-
const debugPrefix = `[${actualManifest.name}]`;
|
|
157
|
-
const oldManifest = getManifest(path);
|
|
158
|
-
const depScopes = ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"];
|
|
159
|
-
// eslint-disable-next-line unicorn/no-array-reduce
|
|
160
|
-
const changes = depScopes.reduce((result, scope) => {
|
|
161
|
-
const diff = difference(actualManifest[scope], oldManifest[scope]);
|
|
162
|
-
|
|
163
|
-
if (Object.keys(diff).length > 0) {
|
|
164
|
-
// eslint-disable-next-line no-param-reassign
|
|
165
|
-
result[scope] = diff;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
return result;
|
|
169
|
-
}, {});
|
|
170
|
-
|
|
171
|
-
debug(debugPrefix, "package.json path=", path);
|
|
172
|
-
|
|
173
|
-
if (Object.keys(changes).length > 0) {
|
|
174
|
-
debug(debugPrefix, "changes=", changes);
|
|
175
|
-
|
|
176
|
-
return true;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
debug(debugPrefix, "no deps changes");
|
|
180
|
-
|
|
181
|
-
return false;
|
|
182
|
-
};
|
|
183
|
-
|
|
184
|
-
/**
|
|
185
|
-
* Resolve next package version.
|
|
186
|
-
* @param {Package} package_ Package object.
|
|
187
|
-
* @returns {string|undefined} Next pkg version.
|
|
188
|
-
* @internal
|
|
189
|
-
*/
|
|
190
|
-
export const getNextVersion = (package_) => {
|
|
191
|
-
const lastVersion = package_._lastRelease && package_._lastRelease.version;
|
|
192
|
-
|
|
193
|
-
return lastVersion && typeof package_._nextType === "string" ? semver.inc(lastVersion, package_._nextType) : lastVersion || "1.0.0";
|
|
194
|
-
};
|
|
195
|
-
|
|
196
|
-
/**
|
|
197
|
-
* Parse the prerelease tag from a semver version.
|
|
198
|
-
* @param {string} version Semver version in a string format.
|
|
199
|
-
* @returns {string|null} preReleaseTag Version prerelease tag or null.
|
|
200
|
-
* @internal
|
|
201
|
-
*/
|
|
202
|
-
export const getPreReleaseTag = (version) => {
|
|
203
|
-
const parsed = semver.parse(version);
|
|
204
|
-
|
|
205
|
-
if (!parsed) {
|
|
206
|
-
return null;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
return parsed.prerelease[0] || null;
|
|
210
|
-
};
|
|
211
|
-
|
|
212
|
-
/**
|
|
213
|
-
* Resolve next package version on prereleases.
|
|
214
|
-
*
|
|
215
|
-
* Will resolve highest next version of either:
|
|
216
|
-
*
|
|
217
|
-
* 1. The last release for the package during this multi-release cycle
|
|
218
|
-
* 2. (if tag options provided):
|
|
219
|
-
* a. the highest increment of the tags array provided
|
|
220
|
-
* b. the highest increment of the gitTags for the prerelease
|
|
221
|
-
* @param {Package} package_ Package object.
|
|
222
|
-
* @returns {string|undefined} Next pkg version.
|
|
223
|
-
* @internal
|
|
224
|
-
*/
|
|
225
|
-
export const getNextPreVersion = (package_) => {
|
|
226
|
-
// Note: this is only set is a current multi-semantic-release released
|
|
227
|
-
const lastVersionForCurrentRelease = package_._lastRelease && package_._lastRelease.version;
|
|
228
|
-
|
|
229
|
-
const lastPreReleaseTag = getPreReleaseTag(lastVersionForCurrentRelease);
|
|
230
|
-
const isNewPreReleaseTag = lastPreReleaseTag && lastPreReleaseTag !== package_._preRelease;
|
|
231
|
-
|
|
232
|
-
return isNewPreReleaseTag || !lastVersionForCurrentRelease
|
|
233
|
-
? `1.0.0-${package_._preRelease}.1`
|
|
234
|
-
: _nextPreVersionCases([], lastVersionForCurrentRelease, package_._nextType, package_._preRelease);
|
|
235
|
-
};
|
|
236
|
-
|
|
237
|
-
/**
|
|
238
|
-
* Resolve package release type taking into account the cascading dependency update.
|
|
239
|
-
* @param {Package} package_ Package object.
|
|
240
|
-
* @param {string|undefined} bumpStrategy Dependency resolution strategy: override, satisfy, inherit.
|
|
241
|
-
* @param {string|undefined} releaseStrategy Release type triggered by deps updating: patch, minor, major, inherit.
|
|
242
|
-
* @param {Package[]} ignore Packages to ignore (to prevent infinite loops).
|
|
243
|
-
* @param {string} prefix Dependency version prefix to be attached if `bumpStrategy='override'`. ^ | ~ | '' (defaults to empty string)
|
|
244
|
-
* @returns {string|undefined} Resolved release type.
|
|
245
|
-
* @internal
|
|
246
|
-
*/
|
|
247
|
-
export const resolveReleaseType = (package_, bumpStrategy = "override", releaseStrategy = "patch", ignore = [], prefix = "") => {
|
|
248
|
-
// NOTE This fn also updates pkg deps, so it must be invoked anyway.
|
|
249
|
-
const dependentReleaseType = getDependentRelease(package_, bumpStrategy, releaseStrategy, ignore, prefix);
|
|
250
|
-
|
|
251
|
-
// Release type found by commitAnalyzer.
|
|
252
|
-
if (package_._nextType) {
|
|
253
|
-
return package_._nextType;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
if (!dependentReleaseType) {
|
|
257
|
-
return undefined;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// Define release type for dependent package if any of its deps changes.
|
|
261
|
-
// `patch`, `minor`, `major` — strictly declare the release type that occurs when any dependency is updated.
|
|
262
|
-
// `inherit` — applies the "highest" release of updated deps to the package.
|
|
263
|
-
// For example, if any dep has a breaking change, `major` release will be applied to the all dependants up the chain.
|
|
264
|
-
|
|
265
|
-
// eslint-disable-next-line no-param-reassign
|
|
266
|
-
package_._nextType = releaseStrategy === "inherit" ? dependentReleaseType : releaseStrategy;
|
|
267
|
-
|
|
268
|
-
return package_._nextType;
|
|
269
|
-
};
|
|
270
|
-
|
|
271
|
-
/**
|
|
272
|
-
* Resolve next version of dependency.
|
|
273
|
-
* @param {string} currentVersion Current dep version
|
|
274
|
-
* @param {string} nextVersion Next release type: patch, minor, major
|
|
275
|
-
* @param {string|undefined} bumpStrategy Resolution strategy: inherit, override, satisfy
|
|
276
|
-
* @param {string} prefix Dependency version prefix to be attached if `bumpStrategy='override'`. ^ | ~ | '' (defaults to empty string)
|
|
277
|
-
* @returns {string} Next dependency version
|
|
278
|
-
* @internal
|
|
279
|
-
*/
|
|
280
|
-
export const resolveNextVersion = (currentVersion, nextVersion, bumpStrategy = "override", prefix = "") => {
|
|
281
|
-
// handle cases of "workspace protocol" defined in yarn and pnpm workspace, whose version starts with "workspace:"
|
|
282
|
-
// eslint-disable-next-line no-param-reassign
|
|
283
|
-
currentVersion = substituteWorkspaceVersion(currentVersion, nextVersion);
|
|
284
|
-
|
|
285
|
-
// if strategy is ignore, return the current version
|
|
286
|
-
if (bumpStrategy === "ignore") {
|
|
287
|
-
return currentVersion;
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
// no change...
|
|
291
|
-
if (currentVersion === nextVersion) {
|
|
292
|
-
return currentVersion;
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
// Check the next pkg version against its current references.
|
|
296
|
-
// If it matches (`*` matches to any, `1.1.0` matches `1.1.x`, `1.5.0` matches to `^1.0.0` and so on)
|
|
297
|
-
// release will not be triggered, if not `override` strategy will be applied instead.
|
|
298
|
-
if ((bumpStrategy === "satisfy" || bumpStrategy === "inherit") && semver.satisfies(nextVersion, currentVersion)) {
|
|
299
|
-
return currentVersion;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
// `inherit` will try to follow the current declaration version/range.
|
|
303
|
-
// `~1.0.0` + `minor` turns into `~1.1.0`, `1.x` + `major` gives `2.x`,
|
|
304
|
-
// but `1.x` + `minor` gives `1.x` so there will be no release, etc.
|
|
305
|
-
if (bumpStrategy === "inherit") {
|
|
306
|
-
const separator = ".";
|
|
307
|
-
const nextChunks = nextVersion.split(separator);
|
|
308
|
-
const currentChunks = currentVersion.split(separator);
|
|
309
|
-
const resolvedChunks = currentChunks.map((chunk, index) => (nextChunks[index] ? chunk.replace(/\d+/u, nextChunks[index]) : chunk));
|
|
310
|
-
|
|
311
|
-
return resolvedChunks.join(separator);
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
// "override"
|
|
315
|
-
// By default next package version would be set as is for the all dependants.
|
|
316
|
-
return prefix + nextVersion;
|
|
317
|
-
};
|
|
318
|
-
|
|
319
|
-
/**
|
|
320
|
-
* Update pkg deps.
|
|
321
|
-
* @param {Package} package_ The package this function is being called on.
|
|
322
|
-
* @returns {void}
|
|
323
|
-
* @internal
|
|
324
|
-
*/
|
|
325
|
-
export const updateManifestDeps = (package_) => {
|
|
326
|
-
const { manifest, path } = package_;
|
|
327
|
-
const { indent, trailingWhitespace } = recognizeFormat(manifest.__contents__);
|
|
328
|
-
|
|
329
|
-
// We need to bump pkg.version for correct yarn.lock update
|
|
330
|
-
// https://github.com/qiwi/multi-semantic-release/issues/58
|
|
331
|
-
manifest.version = package_._nextRelease.version || manifest.version;
|
|
332
|
-
|
|
333
|
-
// Loop through localDeps to verify release consistency.
|
|
334
|
-
package_.localDeps.forEach((d) => {
|
|
335
|
-
// Get version of dependency.
|
|
336
|
-
const release = d._nextRelease || d._lastRelease;
|
|
337
|
-
|
|
338
|
-
// Cannot establish version.
|
|
339
|
-
if (!release || !release.version) {
|
|
340
|
-
throw new Error(`Cannot release ${package_.name} because dependency ${d.name} has not been released yet`);
|
|
341
|
-
}
|
|
342
|
-
});
|
|
343
|
-
|
|
344
|
-
if (!auditManifestChanges(manifest, path)) {
|
|
345
|
-
return;
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
// Write package.json back out.
|
|
349
|
-
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
|
350
|
-
writeFileSync(path, JSON.stringify(manifest, null, indent) + trailingWhitespace);
|
|
351
|
-
};
|