-
Notifications
You must be signed in to change notification settings - Fork 36
/
createInlinePluginCreator.js
285 lines (234 loc) · 9.18 KB
/
createInlinePluginCreator.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
const debug = require("debug")("msr:inlinePlugin");
const getCommitsFiltered = require("./getCommitsFiltered");
const { getRootPkgs } = require("./getRootPkgs");
const { getTagHead } = require("./git");
const { updateManifestDeps, resolveReleaseType } = require("./updateDeps");
const { uniqBy } = require("lodash");
/**
* Create an inline plugin creator for a multirelease.
* This is caused once per multirelease and returns a function which should be called once per package within the release.
*
* @param {Package[]} packages The multi-semantic-release context.
* @param {MultiContext} multiContext The multi-semantic-release context.
* @param {Synchronizer} synchronizer Shared synchronization assets
* @param {Object} flags argv options
* @returns {Function} A function that creates an inline package.
*
* @internal
*/
function createInlinePluginCreator(packages, multiContext, synchronizer, flags) {
// Vars.
const { cwd } = multiContext;
const { todo, waitFor, waitForAll, emit, getLucky } = synchronizer;
/**
* Create an inline plugin for an individual package in a multirelease.
* This is called once per package and returns the inline plugin used for semanticRelease()
*
* @param {Package} pkg The package this function is being called on.
* @returns {Object} A semantic-release inline plugin containing plugin step functions.
*
* @internal
*/
function createInlinePlugin(pkg) {
// Vars.
const { plugins, dir, name } = pkg;
const next = () => {
pkg._tagged = true;
emit(
"_readyForTagging",
todo().find((p) => p._nextType && !p._tagged)
);
};
/**
* @param {object} pluginOptions Options to configure this plugin.
* @param {object} context The semantic-release context.
* @returns {Promise<void>} void
* @internal
*/
const verifyConditions = async (pluginOptions, context) => {
// Restore context for plugins that does not rely on parsed opts.
Object.assign(context.options, context.options._pkgOptions);
// And bind actual logger.
Object.assign(pkg.loggerRef, context.logger);
pkg._ready = true;
emit(
"_readyForRelease",
todo().find((p) => !p._ready)
);
const res = await plugins.verifyConditions(context);
debug("verified conditions: %s", pkg.name);
return res;
};
/**
* Analyze commits step.
* 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.
*
* 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.
* Also updates the `context.commits` setting with one returned from `getCommitsFiltered()` (which is filtered by package directory).
*
* @param {object} pluginOptions Options to configure this plugin.
* @param {object} context The semantic-release context.
* @returns {Promise<void>} Promise that resolves when done.
*
* @internal
*/
const analyzeCommits = async (pluginOptions, context) => {
pkg._preRelease = context.branch.prerelease || null;
pkg._branch = context.branch.name;
// Filter commits by directory.
const firstParentBranch = flags.firstParent ? context.branch.name : undefined;
const commits = await getCommitsFiltered(
cwd,
dir,
context.lastRelease ? context.lastRelease.gitHead : undefined,
context.nextRelease ? context.nextRelease.gitHead : undefined,
firstParentBranch
);
// Set context.commits so analyzeCommits does correct analysis.
context.commits = commits;
// Set lastRelease for package from context.
pkg._lastRelease = context.lastRelease;
// Set nextType for package from plugins.
pkg._nextType = await plugins.analyzeCommits(context);
pkg._rawNextType = pkg._nextType;
// Wait until all todo packages have been analyzed.
pkg._analyzed = true;
await waitForAll("_analyzed");
//go in rounds to check for dependency changes that impact the release type until no changes
//are found in any of the packages. Doing this in rounds will have the changes "bubble-up" in
//the dependency graph until all have been processed.
let round = 0;
let stable = false;
while (!stable) {
const signal = "_depCheck" + round++;
//estimate the type of update for the package
const nextType = resolveReleaseType(pkg, flags.deps.bump, flags.deps.release, flags.deps.prefix);
//indicate if it changed
pkg[signal] = pkg._nextType === nextType ? "stable" : "changed";
pkg._nextType = nextType;
await waitForAll(signal);
stable = packages.every((p) => p[signal] === "stable");
}
debug("commits analyzed: %s", pkg.name);
debug("release type (semrel): %s", pkg._rawNextType);
debug("release type (msr): %s", pkg._nextType);
// Return type.
return pkg._nextType;
};
/**
* Generate notes step (after).
* 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.
*
* In multirelease: Edit the H2 to insert the package name and add an upgrades section to the note.
* We want this at the _end_ of the release note which is why it's stored in steps-after.
*
* Should look like:
*
* ## my-amazing-package [9.2.1](github.com/etc) 2018-12-01
*
* ### Features
*
* * etc
*
* ### Dependencies
*
* * **my-amazing-plugin:** upgraded to 1.2.3
* * **my-other-plugin:** upgraded to 4.9.6
*
* @param {object} pluginOptions Options to configure this plugin.
* @param {object} context The semantic-release context.
* @returns {Promise<void>} Promise that resolves to the string
*
* @internal
*/
const generateNotes = async (pluginOptions, context) => {
// Set nextRelease for package.
pkg._nextRelease = context.nextRelease;
// Wait until all todo packages are ready to generate notes.
await waitForAll("_nextRelease", (p) => p._nextType);
// Vars.
const notes = [];
//get SHA of lastRelease if not already there (should have been done by Semantic Release...)
if (context.lastRelease && context.lastRelease.gitTag) {
if (!context.lastRelease.gitHead || context.lastRelease.gitHead === context.lastRelease.gitTag) {
context.lastRelease.gitHead = await getTagHead(context.lastRelease.gitTag, {
cwd: context.cwd,
env: context.env,
});
}
}
// Filter commits by directory (and release range)
const firstParentBranch = flags.firstParent ? context.branch.name : undefined;
const commits = await getCommitsFiltered(
cwd,
dir,
context.lastRelease ? context.lastRelease.gitHead : undefined,
context.nextRelease ? context.nextRelease.gitHead : undefined,
firstParentBranch
);
// Set context.commits so generateNotes does correct analysis.
context.commits = commits;
// Get subnotes and add to list.
// Inject pkg name into title if it matches e.g. `# 1.0.0` or `## [1.0.1]` (as generate-release-notes does).
const subs = await plugins.generateNotes(context);
// istanbul ignore else (unnecessary to test)
if (subs) notes.push(subs.replace(/^(#+) (\[?\d+\.\d+\.\d+\]?)/, `$1 ${name} $2`));
// If it has upgrades add an upgrades section.
const upgrades = pkg._depsChanged.filter((d) => d._nextRelease && d._nextRelease.version);
if (upgrades && upgrades.length > 0) {
notes.push(`### Dependencies`);
const bullets = upgrades.map((d) => `* **${d.name}:** upgraded to ${d._nextRelease.version}`);
notes.push(bullets.join("\n"));
}
debug("notes generated: %s", pkg.name);
if (pkg.options.dryRun) {
next();
}
// Return the notes.
return notes.join("\n\n");
};
const prepare = async (pluginOptions, context) => {
// Wait until the current pkg is ready to be tagged
getLucky("_readyForTagging", pkg);
await waitFor("_readyForTagging", pkg);
// Get all manifests that potentially needs to be updated
const pkgs = uniqBy([pkg, ...getRootPkgs(context, { _depsChanged: pkg._depsChanged })], "path");
// Loop through each manifest and update its dependencies if needed
pkgs.forEach((item) => updateManifestDeps(item, true, flags.deps.bump, flags.deps.prefix));
pkg._depsUpdated = true;
const res = await plugins.prepare(context);
pkg._prepared = true;
debug("prepared: %s", pkg.name);
return res;
};
const publish = async (pluginOptions, context) => {
next();
const res = await plugins.publish(context);
pkg._published = true;
debug("published: %s", pkg.name);
// istanbul ignore next
return res.length ? res[0] : {};
};
const inlinePlugin = {
verifyConditions,
analyzeCommits,
generateNotes,
prepare,
publish,
};
// Add labels for logs.
Object.keys(inlinePlugin).forEach((type) =>
Reflect.defineProperty(inlinePlugin[type], "pluginName", {
value: "Inline plugin",
writable: false,
enumerable: true,
})
);
debug("inlinePlugin created: %s", pkg.name);
return inlinePlugin;
}
// Return creator function.
return createInlinePlugin;
}
// Exports.
module.exports = createInlinePluginCreator;