@anolilab/multi-semantic-release 1.0.0
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 +276 -0
- package/LICENSE.md +12 -0
- package/README.md +410 -0
- package/bin/cli.js +109 -0
- package/lib/create-inline-plugin-creator.js +270 -0
- package/lib/get-commits-filtered.js +86 -0
- package/lib/get-config-multi-semrel.js +78 -0
- package/lib/get-config-semantic.js +36 -0
- package/lib/get-config.js +33 -0
- package/lib/get-manifest.js +92 -0
- package/lib/git.js +41 -0
- package/lib/logger.js +77 -0
- package/lib/multi-semantic-release.js +274 -0
- package/lib/rescoped-stream.js +33 -0
- package/lib/update-deps.js +388 -0
- package/lib/utils/blork.js +23 -0
- package/lib/utils/clean-path.js +24 -0
- package/lib/utils/get-version.js +37 -0
- package/lib/utils/merge-config.js +21 -0
- package/lib/utils/recognize-format.js +25 -0
- package/lib/utils/stream-to-array.js +60 -0
- package/package.json +142 -0
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import getCommitsFiltered from "./get-commits-filtered.js";
|
|
2
|
+
import { getTagHead } from "./git.js";
|
|
3
|
+
import logger from "./logger.js";
|
|
4
|
+
import { resolveReleaseType, updateManifestDeps } from "./update-deps.js";
|
|
5
|
+
|
|
6
|
+
const { debug } = logger.withScope("msr:inlinePlugin");
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Create an inline plugin creator for a multirelease.
|
|
10
|
+
* This is caused once per multirelease and returns a function which should be called once per package within the release.
|
|
11
|
+
*
|
|
12
|
+
* @param {Package[]} packages The multi-semantic-release context.
|
|
13
|
+
* @param {MultiContext} multiContext The multi-semantic-release context.
|
|
14
|
+
* @param {Object} flags argv options
|
|
15
|
+
* @returns {Function} A function that creates an inline package.
|
|
16
|
+
*
|
|
17
|
+
* @internal
|
|
18
|
+
*/
|
|
19
|
+
function createInlinePluginCreator(packages, multiContext, flags) {
|
|
20
|
+
// Vars.
|
|
21
|
+
const { cwd } = multiContext;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Create an inline plugin for an individual package in a multirelease.
|
|
25
|
+
* This is called once per package and returns the inline plugin used for semanticRelease()
|
|
26
|
+
*
|
|
27
|
+
* @param {Package} package_ The package this function is being called on.
|
|
28
|
+
* @returns {Object} A semantic-release inline plugin containing plugin step functions.
|
|
29
|
+
*
|
|
30
|
+
* @internal
|
|
31
|
+
*/
|
|
32
|
+
// eslint-disable-next-line sonarjs/cognitive-complexity
|
|
33
|
+
function createInlinePlugin(package_) {
|
|
34
|
+
// Vars.
|
|
35
|
+
const { dir, name, plugins } = package_;
|
|
36
|
+
const debugPrefix = `[${name}]`;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @param {object} pluginOptions Options to configure this plugin.
|
|
40
|
+
* @param {object} context The semantic-release context.
|
|
41
|
+
* @returns {Promise<void>} void
|
|
42
|
+
* @internal
|
|
43
|
+
*/
|
|
44
|
+
const verifyConditions = async (pluginOptions, context) => {
|
|
45
|
+
// Restore context for plugins that does not rely on parsed opts.
|
|
46
|
+
Object.assign(context.options, context.options._pkgOptions);
|
|
47
|
+
|
|
48
|
+
// And bind the actual logger.
|
|
49
|
+
Object.assign(package_.fakeLogger, context.logger);
|
|
50
|
+
|
|
51
|
+
const result = await plugins.verifyConditions(context);
|
|
52
|
+
// eslint-disable-next-line no-param-reassign
|
|
53
|
+
package_._ready = true;
|
|
54
|
+
|
|
55
|
+
debug(debugPrefix, "verified conditions");
|
|
56
|
+
|
|
57
|
+
return result;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Analyze commits step.
|
|
62
|
+
* Responsible for determining the type of the next release (major, minor or patch). If multiple plugins with a analyzeCommits step are defined, the release type will be the highest one among plugins output.
|
|
63
|
+
*
|
|
64
|
+
* In multirelease: Returns "patch" if the package contains references to other local packages that have changed, or null if this package references no local packages or they have not changed.
|
|
65
|
+
* Also updates the `context.commits` setting with one returned from `getCommitsFiltered()` (which is filtered by package directory).
|
|
66
|
+
*
|
|
67
|
+
* @param {object} pluginOptions Options to configure this plugin.
|
|
68
|
+
* @param {object} context The semantic-release context.
|
|
69
|
+
* @returns {Promise<void>} Promise that resolves when done.
|
|
70
|
+
*
|
|
71
|
+
* @internal
|
|
72
|
+
*/
|
|
73
|
+
const analyzeCommits = async (pluginOptions, context) => {
|
|
74
|
+
// eslint-disable-next-line no-param-reassign
|
|
75
|
+
package_._preRelease = context.branch.prerelease || null;
|
|
76
|
+
// eslint-disable-next-line no-param-reassign
|
|
77
|
+
package_._branch = context.branch.name;
|
|
78
|
+
|
|
79
|
+
// Filter commits by directory.
|
|
80
|
+
const firstParentBranch = flags.firstParent ? context.branch.name : undefined;
|
|
81
|
+
|
|
82
|
+
// Set context.commits so analyzeCommits does correct analysis.
|
|
83
|
+
|
|
84
|
+
context.commits = await getCommitsFiltered(
|
|
85
|
+
cwd,
|
|
86
|
+
dir,
|
|
87
|
+
context.lastRelease ? context.lastRelease.gitHead : undefined,
|
|
88
|
+
context.nextRelease ? context.nextRelease.gitHead : undefined,
|
|
89
|
+
firstParentBranch,
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
// Set lastRelease for package from context.
|
|
93
|
+
// eslint-disable-next-line no-param-reassign
|
|
94
|
+
package_._lastRelease = context.lastRelease;
|
|
95
|
+
|
|
96
|
+
// Set nextType for package from plugins.
|
|
97
|
+
// eslint-disable-next-line no-param-reassign
|
|
98
|
+
package_._nextType = await plugins.analyzeCommits(context);
|
|
99
|
+
|
|
100
|
+
// eslint-disable-next-line no-param-reassign
|
|
101
|
+
package_._analyzed = true;
|
|
102
|
+
|
|
103
|
+
// Make sure type is "patch" if the package has any deps that have been changed.
|
|
104
|
+
// eslint-disable-next-line no-param-reassign
|
|
105
|
+
package_._nextType = resolveReleaseType(package_, flags.deps.bump, flags.deps.release, [], flags.deps.prefix);
|
|
106
|
+
|
|
107
|
+
debug(debugPrefix, "commits analyzed");
|
|
108
|
+
debug(debugPrefix, `release type: ${package_._nextType}`);
|
|
109
|
+
|
|
110
|
+
// Return type.
|
|
111
|
+
return package_._nextType;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Generate notes step (after).
|
|
116
|
+
* Responsible for generating the content of the release note. If multiple plugins with a generateNotes step are defined, the release notes will be the result of the concatenation of each plugin output.
|
|
117
|
+
*
|
|
118
|
+
* In multirelease: Edit the H2 to insert the package name and add an upgrades section to the note.
|
|
119
|
+
* We want this at the _end_ of the release note which is why it's stored in steps-after.
|
|
120
|
+
*
|
|
121
|
+
* Should look like:
|
|
122
|
+
*
|
|
123
|
+
* ## my-amazing-package [9.2.1](github.com/etc) 2018-12-01
|
|
124
|
+
*
|
|
125
|
+
* ### Features
|
|
126
|
+
*
|
|
127
|
+
* * etc
|
|
128
|
+
*
|
|
129
|
+
* ### Dependencies
|
|
130
|
+
*
|
|
131
|
+
* * **my-amazing-plugin:** upgraded to 1.2.3
|
|
132
|
+
* * **my-other-plugin:** upgraded to 4.9.6
|
|
133
|
+
*
|
|
134
|
+
* @param {object} pluginOptions Options to configure this plugin.
|
|
135
|
+
* @param {object} context The semantic-release context.
|
|
136
|
+
* @returns {Promise<void>} Promise that resolves to the string
|
|
137
|
+
*
|
|
138
|
+
* @internal
|
|
139
|
+
*/
|
|
140
|
+
const generateNotes = async (pluginOptions, context) => {
|
|
141
|
+
// Set nextRelease for package.
|
|
142
|
+
// eslint-disable-next-line no-param-reassign
|
|
143
|
+
package_._nextRelease = context.nextRelease;
|
|
144
|
+
|
|
145
|
+
// Wait until all todo packages are ready to generate notes.
|
|
146
|
+
// await waitForAll("_nextRelease", (p) => p._nextType);
|
|
147
|
+
|
|
148
|
+
// Vars.
|
|
149
|
+
const notes = [];
|
|
150
|
+
|
|
151
|
+
// get SHA of lastRelease if not already there (should have been done by Semantic Release...)
|
|
152
|
+
if (
|
|
153
|
+
context.lastRelease &&
|
|
154
|
+
context.lastRelease.gitTag &&
|
|
155
|
+
(!context.lastRelease.gitHead || context.lastRelease.gitHead === context.lastRelease.gitTag)
|
|
156
|
+
) {
|
|
157
|
+
context.lastRelease.gitHead = getTagHead(context.lastRelease.gitTag, {
|
|
158
|
+
cwd: context.cwd,
|
|
159
|
+
env: context.env,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Filter commits by directory (and release range)
|
|
164
|
+
const firstParentBranch = flags.firstParent ? context.branch.name : undefined;
|
|
165
|
+
|
|
166
|
+
// Set context.commits so generateNotes does correct analysis.
|
|
167
|
+
|
|
168
|
+
context.commits = await getCommitsFiltered(
|
|
169
|
+
cwd,
|
|
170
|
+
dir,
|
|
171
|
+
context.lastRelease ? context.lastRelease.gitHead : undefined,
|
|
172
|
+
context.nextRelease ? context.nextRelease.gitHead : undefined,
|
|
173
|
+
firstParentBranch,
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
// Get subnotes and add to list.
|
|
177
|
+
// Inject pkg name into title if it matches e.g. `# 1.0.0` or `## [1.0.1]` (as generate-release-notes does).
|
|
178
|
+
const subs = await plugins.generateNotes(context);
|
|
179
|
+
// istanbul ignore else (unnecessary to __tests__)
|
|
180
|
+
if (subs) {
|
|
181
|
+
notes.push(subs.replace(/^(#+) (\[?\d+\.\d+\.\d+\]?)/u, `$1 ${name} $2`));
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// If it has upgrades add an upgrades section.
|
|
185
|
+
const upgrades = package_.localDeps.filter((d) => d._nextRelease);
|
|
186
|
+
|
|
187
|
+
if (upgrades.length > 0) {
|
|
188
|
+
notes.push(`### Dependencies`);
|
|
189
|
+
|
|
190
|
+
const bullets = upgrades.map((d) => `* **${d.name}:** upgraded to ${d._nextRelease.version}`);
|
|
191
|
+
|
|
192
|
+
notes.push(bullets.join("\n"));
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
debug(debugPrefix, "notes generated");
|
|
196
|
+
|
|
197
|
+
// Return the notes.
|
|
198
|
+
return notes.join("\n\n");
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
const prepare = async (pluginOptions, context) => {
|
|
202
|
+
updateManifestDeps(package_);
|
|
203
|
+
|
|
204
|
+
// eslint-disable-next-line no-param-reassign
|
|
205
|
+
package_._depsUpdated = true;
|
|
206
|
+
|
|
207
|
+
// Filter commits by directory.
|
|
208
|
+
const firstParentBranch = flags.firstParent ? context.branch.name : undefined;
|
|
209
|
+
|
|
210
|
+
// Set context.commits so analyzeCommits does correct analysis.
|
|
211
|
+
|
|
212
|
+
context.commits = await getCommitsFiltered(
|
|
213
|
+
cwd,
|
|
214
|
+
dir,
|
|
215
|
+
context.lastRelease ? context.lastRelease.gitHead : undefined,
|
|
216
|
+
context.nextRelease ? context.nextRelease.gitHead : undefined,
|
|
217
|
+
firstParentBranch,
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
const result = await plugins.prepare(context);
|
|
221
|
+
|
|
222
|
+
// eslint-disable-next-line no-param-reassign
|
|
223
|
+
package_._prepared = true;
|
|
224
|
+
|
|
225
|
+
debug(debugPrefix, "prepared");
|
|
226
|
+
|
|
227
|
+
return result;
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
const publish = async (pluginOptions, context) => {
|
|
231
|
+
const result = await plugins.publish(context);
|
|
232
|
+
|
|
233
|
+
// eslint-disable-next-line no-param-reassign
|
|
234
|
+
package_._published = true;
|
|
235
|
+
|
|
236
|
+
debug(debugPrefix, "published");
|
|
237
|
+
|
|
238
|
+
// istanbul ignore next
|
|
239
|
+
return result.length > 0 ? result[0] : {};
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
const inlinePlugin = {
|
|
243
|
+
analyzeCommits,
|
|
244
|
+
generateNotes,
|
|
245
|
+
prepare,
|
|
246
|
+
publish,
|
|
247
|
+
verifyConditions,
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
// Add labels for logs.
|
|
251
|
+
Object.keys(inlinePlugin).forEach((type) =>
|
|
252
|
+
// eslint-disable-next-line security/detect-object-injection
|
|
253
|
+
Reflect.defineProperty(inlinePlugin[type], "pluginName", {
|
|
254
|
+
enumerable: true,
|
|
255
|
+
value: "Inline plugin",
|
|
256
|
+
writable: false,
|
|
257
|
+
}),
|
|
258
|
+
);
|
|
259
|
+
|
|
260
|
+
debug(debugPrefix, "inlinePlugin created");
|
|
261
|
+
|
|
262
|
+
return inlinePlugin;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Return creator function.
|
|
266
|
+
return createInlinePlugin;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Exports.
|
|
270
|
+
export default createInlinePluginCreator;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// eslint-disable-next-line simple-import-sort/imports
|
|
2
|
+
import { relative } from "node:path";
|
|
3
|
+
|
|
4
|
+
import { execa } from "execa";
|
|
5
|
+
import gitLogParser from "git-log-parser";
|
|
6
|
+
|
|
7
|
+
import logger from "./logger.js";
|
|
8
|
+
import { ValueError, check } from "./utils/blork.js";
|
|
9
|
+
import cleanPath from "./utils/clean-path.js";
|
|
10
|
+
import streamToArray from "./utils/stream-to-array.js";
|
|
11
|
+
|
|
12
|
+
const { debug } = logger.withScope("msr:commitsFilter");
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 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.
|
|
16
|
+
* Commits are filtered to only return those that corresponding to the package directory.
|
|
17
|
+
*
|
|
18
|
+
* This is achieved by using "-- my/dir/path" with `git log` — passing this into gitLogParser() with
|
|
19
|
+
*
|
|
20
|
+
* @param {string} cwd Absolute path of the working directory the Git repo is in.
|
|
21
|
+
* @param {string} direction Path to the target directory to filter by. Either absolute, or relative to cwd param.
|
|
22
|
+
* @param {string|void} lastRelease The SHA of the previous release (default to start of all commits if undefined)
|
|
23
|
+
* @param {string|void} nextRelease The SHA of the next release (default to HEAD if undefined)
|
|
24
|
+
* @param {string|void} firstParentBranch first-parent to determine which merges went into master
|
|
25
|
+
* @return {Promise<Array<Commit>>} The list of commits on the branch `branch` since the last release.
|
|
26
|
+
*/
|
|
27
|
+
async function getCommitsFiltered(cwd, direction, lastRelease, nextRelease, firstParentBranch) {
|
|
28
|
+
// Clean paths and make sure directories exist.
|
|
29
|
+
check(cwd, "cwd: directory");
|
|
30
|
+
check(direction, "dir: path");
|
|
31
|
+
|
|
32
|
+
// eslint-disable-next-line no-param-reassign
|
|
33
|
+
cwd = cleanPath(cwd);
|
|
34
|
+
// eslint-disable-next-line no-param-reassign
|
|
35
|
+
direction = cleanPath(direction, cwd);
|
|
36
|
+
|
|
37
|
+
check(direction, "dir: directory");
|
|
38
|
+
check(lastRelease, "lastRelease: alphanumeric{40}?");
|
|
39
|
+
check(nextRelease, "nextRelease: alphanumeric{40}?");
|
|
40
|
+
|
|
41
|
+
// target must be inside and different than cwd.
|
|
42
|
+
if (direction.indexOf(cwd) !== 0) {
|
|
43
|
+
throw new ValueError("dir: Must be inside cwd", direction);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (direction === cwd) {
|
|
47
|
+
throw new ValueError("dir: Must not be equal to cwd", direction);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Get top-level Git directory as it might be higher up the tree than cwd.
|
|
51
|
+
const root = await execa("git", ["rev-parse", "--show-toplevel"], { cwd });
|
|
52
|
+
|
|
53
|
+
// Add correct fields to gitLogParser.
|
|
54
|
+
Object.assign(gitLogParser.fields, {
|
|
55
|
+
committerDate: { key: "ci", type: Date },
|
|
56
|
+
gitTags: "d",
|
|
57
|
+
hash: "H",
|
|
58
|
+
message: "B",
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// Use git-log-parser to get the commits.
|
|
62
|
+
const relpath = relative(root.stdout, direction);
|
|
63
|
+
const firstParentBranchFilter = firstParentBranch ? ["--first-parent", firstParentBranch] : [];
|
|
64
|
+
const range = (lastRelease ? `${lastRelease}..` : "") + (nextRelease || "HEAD");
|
|
65
|
+
const gitLogFilterQuery = [...firstParentBranchFilter, range, "--", relpath];
|
|
66
|
+
const stream = gitLogParser.parse({ _: gitLogFilterQuery }, { cwd, env: process.env });
|
|
67
|
+
|
|
68
|
+
const commits = await streamToArray(stream);
|
|
69
|
+
|
|
70
|
+
// Trim message and tags.
|
|
71
|
+
commits.forEach((commit) => {
|
|
72
|
+
// eslint-disable-next-line no-param-reassign
|
|
73
|
+
commit.message = commit.message.trim();
|
|
74
|
+
// eslint-disable-next-line no-param-reassign
|
|
75
|
+
commit.gitTags = commit.gitTags.trim();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
debug("git log filter query: %o", gitLogFilterQuery);
|
|
79
|
+
debug("filtered commits: %O", commits);
|
|
80
|
+
|
|
81
|
+
// Return the commits.
|
|
82
|
+
return commits;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Exports.
|
|
86
|
+
export default getCommitsFiltered;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
|
|
3
|
+
import { cosmiconfig } from "cosmiconfig";
|
|
4
|
+
// eslint-disable-next-line you-dont-need-lodash-underscore/cast-array
|
|
5
|
+
import { castArray } from "lodash-es";
|
|
6
|
+
import resolveFrom from "resolve-from";
|
|
7
|
+
|
|
8
|
+
import mergeConfig from "./utils/merge-config.js";
|
|
9
|
+
|
|
10
|
+
const CONFIG_NAME = "multi-release";
|
|
11
|
+
const CONFIG_FILES = [
|
|
12
|
+
"package.json",
|
|
13
|
+
`.${CONFIG_NAME}rc`,
|
|
14
|
+
`.${CONFIG_NAME}rc.json`,
|
|
15
|
+
`.${CONFIG_NAME}rc.yaml`,
|
|
16
|
+
`.${CONFIG_NAME}rc.yml`,
|
|
17
|
+
`.${CONFIG_NAME}rc.js`,
|
|
18
|
+
`.${CONFIG_NAME}rc.cjs`,
|
|
19
|
+
`${CONFIG_NAME}.config.js`,
|
|
20
|
+
`${CONFIG_NAME}.config.cjs`,
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Get the multi semantic release configuration options for a given directory.
|
|
25
|
+
*
|
|
26
|
+
* @param {string} cwd The directory to search.
|
|
27
|
+
* @param {Object} cliOptions cli supplied options.
|
|
28
|
+
* @returns {Object} The found configuration option
|
|
29
|
+
*
|
|
30
|
+
* @internal
|
|
31
|
+
*/
|
|
32
|
+
export default async function getConfig(cwd, cliOptions) {
|
|
33
|
+
const { config } = (await cosmiconfig(CONFIG_NAME, { searchPlaces: CONFIG_FILES }).search(cwd)) || {};
|
|
34
|
+
const { extends: extendPaths, ...rest } = { ...config };
|
|
35
|
+
|
|
36
|
+
let options = rest;
|
|
37
|
+
|
|
38
|
+
if (extendPaths) {
|
|
39
|
+
const require = createRequire(import.meta.url);
|
|
40
|
+
// If `extends` is defined, load and merge each shareable config
|
|
41
|
+
// eslint-disable-next-line unicorn/no-array-reduce
|
|
42
|
+
const extendedOptions = castArray(extendPaths).reduce((result, extendPath) => {
|
|
43
|
+
// eslint-disable-next-line import/no-dynamic-require,security/detect-non-literal-require
|
|
44
|
+
const extendsOptions = require(resolveFrom(cwd, extendPath));
|
|
45
|
+
|
|
46
|
+
return mergeConfig(result, extendsOptions);
|
|
47
|
+
}, {});
|
|
48
|
+
|
|
49
|
+
options = mergeConfig(options, extendedOptions);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Set default options values if not defined yet
|
|
53
|
+
options = mergeConfig(
|
|
54
|
+
{
|
|
55
|
+
branches: undefined,
|
|
56
|
+
ci: undefined,
|
|
57
|
+
debug: false,
|
|
58
|
+
deps: {
|
|
59
|
+
bump: "override",
|
|
60
|
+
prefix: "",
|
|
61
|
+
release: "patch",
|
|
62
|
+
},
|
|
63
|
+
dryRun: undefined,
|
|
64
|
+
firstParent: false,
|
|
65
|
+
ignorePackages: [],
|
|
66
|
+
ignorePrivate: true,
|
|
67
|
+
sequentialInit: false,
|
|
68
|
+
sequentialPrepare: true,
|
|
69
|
+
silent: false,
|
|
70
|
+
// eslint-disable-next-line no-template-curly-in-string
|
|
71
|
+
tagFormat: "${name}@${version}",
|
|
72
|
+
},
|
|
73
|
+
options,
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
// Finally merge CLI options last so they always win
|
|
77
|
+
return mergeConfig(options, cliOptions);
|
|
78
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import semanticGetConfig from "semantic-release/lib/get-config.js";
|
|
2
|
+
import signale from "signale";
|
|
3
|
+
import { WritableStreamBuffer } from "stream-buffers";
|
|
4
|
+
|
|
5
|
+
import logger from "./logger.js";
|
|
6
|
+
|
|
7
|
+
const { Signale } = signale;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Get the release configuration options for a given directory.
|
|
11
|
+
* Unfortunately we've had to copy this over from semantic-release, creating unnecessary duplication.
|
|
12
|
+
*
|
|
13
|
+
* @param {Object} context Object containing cwd, env, and logger properties that are passed to getConfig()
|
|
14
|
+
* @param {Object} options Options object for the config.
|
|
15
|
+
* @returns {Object} Returns what semantic-release's get config returns (object with options and plugins objects).
|
|
16
|
+
*
|
|
17
|
+
* @internal
|
|
18
|
+
*/
|
|
19
|
+
async function getConfigSemantic({ cwd, env, stderr, stdout }, options) {
|
|
20
|
+
try {
|
|
21
|
+
// Blackhole logger (so we don't clutter output with "loaded plugin" messages).
|
|
22
|
+
const blackhole = new Signale({ stream: new WritableStreamBuffer() });
|
|
23
|
+
|
|
24
|
+
// Return semantic-release's getConfig script.
|
|
25
|
+
return await semanticGetConfig({ cwd, env, logger: blackhole, stderr, stdout }, options);
|
|
26
|
+
} catch (error) {
|
|
27
|
+
// Log error and rethrow it.
|
|
28
|
+
// istanbul ignore next (not important)
|
|
29
|
+
logger.failure(`Error in semantic-release getConfig(): %0`, error);
|
|
30
|
+
// istanbul ignore next (not important)
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Exports.
|
|
36
|
+
export default getConfigSemantic;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { cosmiconfig } from "cosmiconfig";
|
|
2
|
+
|
|
3
|
+
// Copied from get-config.js in semantic-release
|
|
4
|
+
const CONFIG_NAME = "release";
|
|
5
|
+
const CONFIG_FILES = [
|
|
6
|
+
"package.json",
|
|
7
|
+
`.${CONFIG_NAME}rc`,
|
|
8
|
+
`.${CONFIG_NAME}rc.json`,
|
|
9
|
+
`.${CONFIG_NAME}rc.yaml`,
|
|
10
|
+
`.${CONFIG_NAME}rc.yml`,
|
|
11
|
+
`.${CONFIG_NAME}rc.js`,
|
|
12
|
+
`.${CONFIG_NAME}rc.cjs`,
|
|
13
|
+
`${CONFIG_NAME}.config.js`,
|
|
14
|
+
`${CONFIG_NAME}.config.cjs`,
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Get the release configuration options for a given directory.
|
|
19
|
+
* Unfortunately we've had to copy this over from semantic-release, creating unnecessary duplication.
|
|
20
|
+
*
|
|
21
|
+
* @param {string} cwd The directory to search.
|
|
22
|
+
* @returns {Object} The found configuration option
|
|
23
|
+
*
|
|
24
|
+
* @internal
|
|
25
|
+
*/
|
|
26
|
+
export default async function getConfig(cwd) {
|
|
27
|
+
// Call cosmiconfig.
|
|
28
|
+
const config = await cosmiconfig(CONFIG_NAME, { searchPlaces: CONFIG_FILES }).search(cwd);
|
|
29
|
+
|
|
30
|
+
// Return the found config or empty object.
|
|
31
|
+
// istanbul ignore next (not important).
|
|
32
|
+
return config ? config.config : {};
|
|
33
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { existsSync, lstatSync, readFileSync } from "node:fs";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Read the content of target package.json if exists.
|
|
5
|
+
*
|
|
6
|
+
* @param {string} path file path
|
|
7
|
+
* @returns {string} file content
|
|
8
|
+
*
|
|
9
|
+
* @internal
|
|
10
|
+
*/
|
|
11
|
+
function readManifest(path) {
|
|
12
|
+
// Check it exists.
|
|
13
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
|
14
|
+
if (!existsSync(path)) {
|
|
15
|
+
throw new ReferenceError(`package.json file not found: "${path}"`);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Stat the file.
|
|
19
|
+
let stat;
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
|
23
|
+
stat = lstatSync(path);
|
|
24
|
+
} catch {
|
|
25
|
+
// istanbul ignore next (hard to __tests__ — happens if no read access etc).
|
|
26
|
+
throw new ReferenceError(`package.json cannot be read: "${path}"`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Check it's a file!
|
|
30
|
+
if (!stat.isFile()) {
|
|
31
|
+
throw new ReferenceError(`package.json is not a file: "${path}"`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Read the file.
|
|
35
|
+
try {
|
|
36
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
|
37
|
+
return readFileSync(path, "utf8");
|
|
38
|
+
} catch {
|
|
39
|
+
// istanbul ignore next (hard to __tests__ — happens if no read access etc).
|
|
40
|
+
throw new ReferenceError(`package.json cannot be read: "${path}"`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Get the parsed contents of a package.json manifest file.
|
|
46
|
+
*
|
|
47
|
+
* @param {string} path The path to the package.json manifest file.
|
|
48
|
+
* @returns {object} The manifest file's contents.
|
|
49
|
+
*
|
|
50
|
+
* @internal
|
|
51
|
+
*/
|
|
52
|
+
export default function getManifest(path) {
|
|
53
|
+
// Read the file.
|
|
54
|
+
const contents = readManifest(path);
|
|
55
|
+
|
|
56
|
+
// Parse the file.
|
|
57
|
+
let manifest;
|
|
58
|
+
try {
|
|
59
|
+
manifest = JSON.parse(contents);
|
|
60
|
+
} catch {
|
|
61
|
+
throw new SyntaxError(`package.json could not be parsed: "${path}"`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Must be an object.
|
|
65
|
+
if (typeof manifest !== "object") {
|
|
66
|
+
throw new SyntaxError(`package.json was not an object: "${path}"`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Must have a name.
|
|
70
|
+
if (typeof manifest.name !== "string" || manifest.name.length === 0) {
|
|
71
|
+
throw new SyntaxError(`Package name must be non-empty string: "${path}"`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Check dependencies.
|
|
75
|
+
const checkDeps = (scope) => {
|
|
76
|
+
// eslint-disable-next-line no-prototype-builtins,security/detect-object-injection
|
|
77
|
+
if (manifest.hasOwnProperty(scope) && typeof manifest[scope] !== "object") {
|
|
78
|
+
throw new SyntaxError(`Package ${scope} must be object: "${path}"`);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
checkDeps("dependencies");
|
|
83
|
+
checkDeps("devDependencies");
|
|
84
|
+
checkDeps("peerDependencies");
|
|
85
|
+
checkDeps("optionalDependencies");
|
|
86
|
+
|
|
87
|
+
// NOTE non-enumerable prop is skipped by JSON.stringify
|
|
88
|
+
Object.defineProperty(manifest, "__contents__", { enumerable: false, value: contents });
|
|
89
|
+
|
|
90
|
+
// Return contents.
|
|
91
|
+
return manifest;
|
|
92
|
+
}
|
package/lib/git.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { execaSync } from "execa";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Get all the tags for a given branch.
|
|
5
|
+
*
|
|
6
|
+
* @param {String} branch The branch for which to retrieve the tags.
|
|
7
|
+
* @param {Object} [execaOptions] Options to pass to `execa`.
|
|
8
|
+
* @param {Array<String>} filters List of string to be checked inside tags.
|
|
9
|
+
*
|
|
10
|
+
* @return {Array<String>} List of git tags.
|
|
11
|
+
* @throws {Error} If the `git` command fails.
|
|
12
|
+
* @internal
|
|
13
|
+
*/
|
|
14
|
+
export function getTags(branch, execaOptions, filters) {
|
|
15
|
+
const { stdout } = execaSync("git", ["tag", "--merged", branch], execaOptions);
|
|
16
|
+
|
|
17
|
+
const tags = stdout
|
|
18
|
+
.split("\n")
|
|
19
|
+
.map((tag) => tag.trim())
|
|
20
|
+
.filter(Boolean);
|
|
21
|
+
|
|
22
|
+
if (!filters || filters.length === 0) {
|
|
23
|
+
return tags;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const validateSubstr = (t, f) => f.every((v) => t.includes(v));
|
|
27
|
+
|
|
28
|
+
return tags.filter((tag) => validateSubstr(tag, filters));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Get the commit sha for a given tag.
|
|
33
|
+
*
|
|
34
|
+
* @param {String} tagName Tag name for which to retrieve the commit sha.
|
|
35
|
+
* @param {Object} [execaOptions] Options to pass to `execa`.
|
|
36
|
+
*
|
|
37
|
+
* @return {String} The commit sha of the tag in parameter or `null`.
|
|
38
|
+
*/
|
|
39
|
+
export function getTagHead(tagName, execaOptions) {
|
|
40
|
+
return execaSync("git", ["rev-list", "-1", tagName], execaOptions).stdout;
|
|
41
|
+
}
|