-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmultiSemanticRelease.js
169 lines (147 loc) · 7.2 KB
/
multiSemanticRelease.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
const { resolve, dirname } = require("path");
const { check } = require("./blork");
const semanticRelease = require("semantic-release");
const { WritableStreamBuffer } = require("stream-buffers");
const getLogger = require("./getLogger");
const getConfig = require("./getConfig");
const getConfigSemantic = require("./getConfigSemantic");
const getManifest = require("./getManifest");
const cleanPath = require("./cleanPath");
const RescopedStream = require("./RescopedStream");
const createInlinePluginCreator = require("./createInlinePluginCreator");
/**
* The multirelease context.
* @typedef MultiContext
* @param {Package[]} packages Array of all packages in this multirelease.
* @param {Package[]} releasing Array of packages that will release.
* @param {string} cwd The current working directory.
* @param {Object} env The environment variables.
* @param {Logger} logger The logger for the multirelease.
* @param {Stream} stdout The output stream for this multirelease.
* @param {Stream} stderr The error stream for this multirelease.
*/
/**
* Details about an individual package in a multirelease
* @typedef Package
* @param {string} path String path to `package.json` for the package.
* @param {string} dir The working directory for the package.
* @param {string} name The name of the package, e.g. `my-amazing-package`
* @param {string[]} deps Array of all dependency package names for the package (merging dependencies, devDependencies, peerDependencies).
* @param {Package[]} localDeps Array of local dependencies this package relies on.
* @param {context|void} context The semantic-release context for this package's release (filled in once semantic-release runs).
* @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.
*/
/**
* Perform a multirelease.
*
* @param {string[]} paths An array of paths to package.json files.
* @param {Object} inputOptions An object containing semantic-release options.
* @param {Object} settings An object containing: cwd, env, stdout, stderr (mainly for configuring tests).
* @returns {Promise<Package[]>} Promise that resolves to a list of package objects with `result` property describing whether it released or not.
*/
async function multiSemanticRelease(
paths,
inputOptions = {},
{ cwd = process.cwd(), env = process.env, stdout = process.stdout, stderr = process.stderr } = {}
) {
// Check params.
check(paths, "paths: string[]");
check(cwd, "cwd: directory");
check(env, "env: objectlike");
check(stdout, "stdout: stream");
check(stderr, "stderr: stream");
cwd = cleanPath(cwd);
// Start.
const logger = getLogger({ stdout, stderr });
logger.complete(`Started multirelease! Loading ${paths.length} packages...`);
// Vars.
const globalOptions = await getConfig(cwd);
const options = Object.assign({}, globalOptions, inputOptions);
const multiContext = { options, cwd, env, stdout, stderr };
// Load packages from paths.
const packages = await Promise.all(paths.map(path => getPackage(path, multiContext)));
packages.forEach(pkg => logger.success(`Loaded package ${pkg.name}`));
logger.complete(`Queued ${packages.length} packages! Starting release...`);
// Release all packages.
const createInlinePlugin = createInlinePluginCreator(packages, multiContext);
await Promise.all(packages.map(pkg => releasePackage(pkg, createInlinePlugin, multiContext)));
const released = packages.filter(pkg => pkg.result).length;
// Return packages list.
logger.complete(`Released ${released} of ${packages.length} packages, semantically!`);
return packages;
}
// Exports.
module.exports = multiSemanticRelease;
/**
* Load details about a package.
*
* @param {string} path The path to load details about.
* @param {Object} allOptions Options that apply to all packages.
* @param {MultiContext} multiContext Context object for the multirelease.
* @returns {Promise<Package|void>} A package object, or void if the package was skipped.
*
* @internal
*/
async function getPackage(path, { options: globalOptions, env, cwd }) {
// Make path absolute.
path = cleanPath(path, cwd);
const dir = dirname(path);
// Get package.json file contents.
const manifest = getManifest(path);
const name = manifest.name;
// Combine list of all dependency names.
const deps = [
...Object.keys(manifest.dependencies),
...Object.keys(manifest.devDependencies),
...Object.keys(manifest.peerDependencies)
];
// Load the package-specific options.
const { options: pkgOptions } = await getConfig(dir);
// The 'final options' are the global options merged with package-specific options.
// We merge this ourselves because package-specific options can override global options.
const finalOptions = Object.assign({}, globalOptions, pkgOptions);
// Make a fake logger so semantic-release's get-config doesn't fail.
const logger = { error() {}, log() {} };
// 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.
// 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.
const { options, plugins } = await getConfigSemantic({ cwd: dir, env, logger }, finalOptions);
// Return package object.
return { path, dir, name, manifest, deps, options, plugins };
}
/**
* Release an individual package.
*
* @param {Package} pkg The specific package.
* @param {Function} createInlinePlugin A function that creates an inline plugin.
* @param {MultiContext} multiContext Context object for the multirelease.
* @returns {Promise<void>} Promise that resolves when done.
*
* @internal
*/
async function releasePackage(pkg, createInlinePlugin, multiContext) {
// Vars.
const { options: pkgOptions, name, path, dir } = pkg;
const { todo, options: globalOptions, cwd, env, logger, stdout, stderr } = multiContext;
// Set the options that we call semanticRelease() with.
// This consists of:
// - The global options (e.g. from the top level package.json)
// - The package options (e.g. from the specific package's package.json)
const options = Object.assign({}, globalOptions, pkgOptions);
// Add the package name into tagFormat.
// 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.
// It'd also be difficult to merge all the assets into one release without full editing/overriding the plugins.
options.tagFormat = name + "@${version}";
// Make an 'inline plugin' for this package.
// The inline plugin is the only plugin we call semanticRelease() with.
// The inline plugin functions then call e.g. plugins.analyzeCommits() manually and sometimes manipulate the responses.
const inlinePlugin = createInlinePlugin(pkg);
options.plugins = [inlinePlugin];
// Call semanticRelease() on the directory and save result to pkg.
// Don't need to log out errors as semantic-release already does that.
pkg.result = await semanticRelease(options, {
cwd: dir,
env,
stdout: new RescopedStream(stdout, name),
stderr: new RescopedStream(stdout, name)
});
}