@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,388 @@
|
|
|
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
|
+
*
|
|
16
|
+
* @param {string|null} latestTag Last released tag from branch or null if non-existent.
|
|
17
|
+
* @param {string} lastVersion Last version released.
|
|
18
|
+
* @param {string} packagePreRelease Prerelease tag from package to-be-released.
|
|
19
|
+
* @returns {string} Next pkg version.
|
|
20
|
+
* @internal
|
|
21
|
+
*/
|
|
22
|
+
const _nextPreHighestVersion = (latestTag, lastVersion, packagePreRelease) => {
|
|
23
|
+
const bumpFromTags = latestTag ? semver.inc(latestTag, "prerelease", packagePreRelease) : null;
|
|
24
|
+
const bumpFromLast = semver.inc(lastVersion, "prerelease", packagePreRelease);
|
|
25
|
+
|
|
26
|
+
return bumpFromTags ? getHighestVersion(bumpFromLast, bumpFromTags) : bumpFromLast;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Resolve next prerelease special cases: highest version from tags or major/minor/patch.#
|
|
31
|
+
*
|
|
32
|
+
* @param {Array<string>} tags - if non-empty, we will use these tags as part fo the comparison
|
|
33
|
+
* @param {string} lastVersionForCurrentMultiRelease Last package version released from multi-semantic-release
|
|
34
|
+
* @param {string} packageNextType Next type evaluated for the next package type.
|
|
35
|
+
* @param {string} packagePreRelease Package prerelease suffix.
|
|
36
|
+
* @returns {string|undefined} Next pkg version.
|
|
37
|
+
* @internal
|
|
38
|
+
*/
|
|
39
|
+
const _nextPreVersionCases = (tags, lastVersionForCurrentMultiRelease, packageNextType, packagePreRelease) => {
|
|
40
|
+
// Case 1: Normal release on last version and is now converted to a prerelease
|
|
41
|
+
if (!semver.prerelease(lastVersionForCurrentMultiRelease)) {
|
|
42
|
+
const { major, minor, patch } = semver.parse(lastVersionForCurrentMultiRelease);
|
|
43
|
+
|
|
44
|
+
return `${semver.inc(`${major}.${minor}.${patch}`, packageNextType || "patch")}-${packagePreRelease}.1`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Case 2: Validates version with tags
|
|
48
|
+
const latestTag = getLatestVersion(tags, true);
|
|
49
|
+
|
|
50
|
+
return _nextPreHighestVersion(latestTag, lastVersionForCurrentMultiRelease, packagePreRelease);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Get dependent release type by recursive scanning and updating pkg deps.
|
|
55
|
+
*
|
|
56
|
+
* @param {Package} package_ The package with local deps to check.
|
|
57
|
+
* @param {string} bumpStrategy Dependency resolution strategy: override, satisfy, inherit.
|
|
58
|
+
* @param {string} releaseStrategy Release type triggered by deps updating: patch, minor, major, inherit.
|
|
59
|
+
* @param {Package[]} ignore Packages to ignore (to prevent infinite loops).
|
|
60
|
+
* @param {string} prefix Dependency version prefix to be attached if `bumpStrategy='override'`. ^ | ~ | '' (defaults to empty string)
|
|
61
|
+
* @returns {string|undefined} Returns the highest release type if found, undefined otherwise
|
|
62
|
+
* @internal
|
|
63
|
+
*/
|
|
64
|
+
const getDependentRelease = (package_, bumpStrategy, releaseStrategy, ignore, prefix) => {
|
|
65
|
+
const severityOrder = ["patch", "minor", "major"];
|
|
66
|
+
const { localDeps, manifest = {} } = package_;
|
|
67
|
+
const lastVersion = package_._lastRelease && package_._lastRelease.version;
|
|
68
|
+
const { dependencies = {}, devDependencies = {}, optionalDependencies = {}, peerDependencies = {} } = manifest;
|
|
69
|
+
const scopes = [dependencies, devDependencies, peerDependencies, optionalDependencies];
|
|
70
|
+
const bumpDependency = (scope, name, nextVersion) => {
|
|
71
|
+
// eslint-disable-next-line security/detect-object-injection
|
|
72
|
+
const currentVersion = scope[name];
|
|
73
|
+
|
|
74
|
+
if (!nextVersion || !currentVersion) {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// eslint-disable-next-line no-use-before-define
|
|
79
|
+
const resolvedVersion = resolveNextVersion(currentVersion, nextVersion, bumpStrategy, prefix);
|
|
80
|
+
|
|
81
|
+
if (currentVersion !== resolvedVersion) {
|
|
82
|
+
// eslint-disable-next-line no-param-reassign,security/detect-object-injection
|
|
83
|
+
scope[name] = resolvedVersion;
|
|
84
|
+
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return false;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
localDeps
|
|
93
|
+
.filter((p) => !ignore.includes(p))
|
|
94
|
+
// eslint-disable-next-line unicorn/no-array-reduce
|
|
95
|
+
.reduce((releaseType, p) => {
|
|
96
|
+
// Has changed if...
|
|
97
|
+
// 1. Any local dep package itself has changed
|
|
98
|
+
// 2. Any local dep package has local deps that have changed.
|
|
99
|
+
// eslint-disable-next-line no-use-before-define
|
|
100
|
+
const nextType = resolveReleaseType(p, bumpStrategy, releaseStrategy, [...ignore, package_], prefix);
|
|
101
|
+
const nextVersion = nextType
|
|
102
|
+
? // Update the nextVersion only if there is a next type to be bumped
|
|
103
|
+
|
|
104
|
+
p._preRelease
|
|
105
|
+
? // eslint-disable-next-line no-use-before-define
|
|
106
|
+
getNextPreVersion(p)
|
|
107
|
+
: // eslint-disable-next-line no-use-before-define
|
|
108
|
+
getNextVersion(p)
|
|
109
|
+
: // Set the nextVersion fallback to the last local dependency package last version
|
|
110
|
+
p._lastRelease && p._lastRelease.version;
|
|
111
|
+
|
|
112
|
+
// 3. And this change should correspond to the manifest updating rule.
|
|
113
|
+
const requireRelease = scopes
|
|
114
|
+
// eslint-disable-next-line unicorn/no-array-reduce
|
|
115
|
+
.reduce((result, scope) => bumpDependency(scope, p.name, nextVersion) || result, !lastVersion);
|
|
116
|
+
|
|
117
|
+
return requireRelease && severityOrder.indexOf(nextType) > severityOrder.indexOf(releaseType) ? nextType : releaseType;
|
|
118
|
+
}, undefined)
|
|
119
|
+
);
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Substitute "workspace:" in currentVersion
|
|
124
|
+
* See:
|
|
125
|
+
* {@link https://yarnpkg.com/features/workspaces#publishing-workspaces}
|
|
126
|
+
* {@link https://pnpm.io/workspaces#publishing-workspace-packages}
|
|
127
|
+
*
|
|
128
|
+
* @param {string} currentVersion Current version, may start with "workspace:"
|
|
129
|
+
* @param {string} nextVersion Next version
|
|
130
|
+
* @returns {string} current version without "workspace:"
|
|
131
|
+
*/
|
|
132
|
+
const substituteWorkspaceVersion = (currentVersion, nextVersion) => {
|
|
133
|
+
if (currentVersion.startsWith("workspace:")) {
|
|
134
|
+
// eslint-disable-next-line regexp/optimal-quantifier-concatenation
|
|
135
|
+
const [, range, caret] = /^workspace:(([\^~*])?.*)$/u.exec(currentVersion);
|
|
136
|
+
|
|
137
|
+
return caret === range ? (caret === "*" ? nextVersion : caret + nextVersion) : range;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return currentVersion;
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
// eslint-disable-next-line no-secrets/no-secrets
|
|
144
|
+
// https://gist.github.com/Yimiprod/7ee176597fef230d1451
|
|
145
|
+
const difference = (object, base) =>
|
|
146
|
+
transform(object, (result, value, key) => {
|
|
147
|
+
// eslint-disable-next-line security/detect-object-injection
|
|
148
|
+
if (!isEqual(value, base[key])) {
|
|
149
|
+
// eslint-disable-next-line security/detect-object-injection,no-param-reassign
|
|
150
|
+
result[key] = isObject(value) && isObject(base[key]) ? difference(value, base[key]) : `${base[key]} → ${value}`;
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Clarify what exactly was changed in manifest file.
|
|
156
|
+
* @param {object} actualManifest manifest object
|
|
157
|
+
* @param {string} path manifest path
|
|
158
|
+
* @returns {boolean} has changed or not
|
|
159
|
+
* @internal
|
|
160
|
+
*/
|
|
161
|
+
const auditManifestChanges = (actualManifest, path) => {
|
|
162
|
+
const debugPrefix = `[${actualManifest.name}]`;
|
|
163
|
+
const oldManifest = getManifest(path);
|
|
164
|
+
const depScopes = ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"];
|
|
165
|
+
// eslint-disable-next-line unicorn/no-array-reduce
|
|
166
|
+
const changes = depScopes.reduce((result, scope) => {
|
|
167
|
+
// eslint-disable-next-line security/detect-object-injection
|
|
168
|
+
const diff = difference(actualManifest[scope], oldManifest[scope]);
|
|
169
|
+
|
|
170
|
+
if (Object.keys(diff).length > 0) {
|
|
171
|
+
// eslint-disable-next-line security/detect-object-injection,no-param-reassign
|
|
172
|
+
result[scope] = diff;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return result;
|
|
176
|
+
}, {});
|
|
177
|
+
|
|
178
|
+
debug(debugPrefix, "package.json path=", path);
|
|
179
|
+
|
|
180
|
+
if (Object.keys(changes).length > 0) {
|
|
181
|
+
debug(debugPrefix, "changes=", changes);
|
|
182
|
+
|
|
183
|
+
return true;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
debug(debugPrefix, "no deps changes");
|
|
187
|
+
|
|
188
|
+
return false;
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Resolve next package version.
|
|
193
|
+
*
|
|
194
|
+
* @param {Package} package_ Package object.
|
|
195
|
+
* @returns {string|undefined} Next pkg version.
|
|
196
|
+
* @internal
|
|
197
|
+
*/
|
|
198
|
+
export const getNextVersion = (package_) => {
|
|
199
|
+
const lastVersion = package_._lastRelease && package_._lastRelease.version;
|
|
200
|
+
|
|
201
|
+
return lastVersion && typeof package_._nextType === "string" ? semver.inc(lastVersion, package_._nextType) : lastVersion || "1.0.0";
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Resolve the package version from a tag
|
|
206
|
+
*
|
|
207
|
+
* @param {Package} package_ Package object.
|
|
208
|
+
* @param {string} tag The tag containing the version to resolve
|
|
209
|
+
* @returns {string|null} The version of the package or null if no tag was passed
|
|
210
|
+
* @internal
|
|
211
|
+
*/
|
|
212
|
+
export const getVersionFromTag = (package_, tag) => {
|
|
213
|
+
if (!package_.name) {
|
|
214
|
+
return tag || null;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (!tag) {
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// TODO inherit semantic-release/lib/branches/get-tags.js
|
|
222
|
+
const stringMatch = tag.match(/\d.\d.\d[^+]*/u);
|
|
223
|
+
return stringMatch && stringMatch[0] && semver.valid(stringMatch[0]) ? stringMatch[0] : null;
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Parse the prerelease tag from a semver version.
|
|
228
|
+
*
|
|
229
|
+
* @param {string} version Semver version in a string format.
|
|
230
|
+
* @returns {string|null} preReleaseTag Version prerelease tag or null.
|
|
231
|
+
* @internal
|
|
232
|
+
*/
|
|
233
|
+
export const getPreReleaseTag = (version) => {
|
|
234
|
+
const parsed = semver.parse(version);
|
|
235
|
+
|
|
236
|
+
if (!parsed) {
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return parsed.prerelease[0] || null;
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Resolve next package version on prereleases.
|
|
245
|
+
*
|
|
246
|
+
* Will resolve highest next version of either:
|
|
247
|
+
*
|
|
248
|
+
* 1. The last release for the package during this multi-release cycle
|
|
249
|
+
* 2. (if tag options provided):
|
|
250
|
+
* a. the highest increment of the tags array provided
|
|
251
|
+
* b. the highest increment of the gitTags for the prerelease
|
|
252
|
+
*
|
|
253
|
+
*
|
|
254
|
+
* @param {Package} package_ Package object.
|
|
255
|
+
* @returns {string|undefined} Next pkg version.
|
|
256
|
+
* @internal
|
|
257
|
+
*/
|
|
258
|
+
export const getNextPreVersion = (package_) => {
|
|
259
|
+
// Note: this is only set is a current multi-semantic-release released
|
|
260
|
+
const lastVersionForCurrentRelease = package_._lastRelease && package_._lastRelease.version;
|
|
261
|
+
|
|
262
|
+
const lastPreReleaseTag = getPreReleaseTag(lastVersionForCurrentRelease);
|
|
263
|
+
const isNewPreReleaseTag = lastPreReleaseTag && lastPreReleaseTag !== package_._preRelease;
|
|
264
|
+
|
|
265
|
+
return isNewPreReleaseTag || !lastVersionForCurrentRelease
|
|
266
|
+
? `1.0.0-${package_._preRelease}.1`
|
|
267
|
+
: _nextPreVersionCases([], lastVersionForCurrentRelease, package_._nextType, package_._preRelease);
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Resolve package release type taking into account the cascading dependency update.
|
|
272
|
+
*
|
|
273
|
+
* @param {Package} package_ Package object.
|
|
274
|
+
* @param {string|undefined} bumpStrategy Dependency resolution strategy: override, satisfy, inherit.
|
|
275
|
+
* @param {string|undefined} releaseStrategy Release type triggered by deps updating: patch, minor, major, inherit.
|
|
276
|
+
* @param {Package[]} ignore Packages to ignore (to prevent infinite loops).
|
|
277
|
+
* @param {string} prefix Dependency version prefix to be attached if `bumpStrategy='override'`. ^ | ~ | '' (defaults to empty string)
|
|
278
|
+
* @returns {string|undefined} Resolved release type.
|
|
279
|
+
* @internal
|
|
280
|
+
*/
|
|
281
|
+
export const resolveReleaseType = (package_, bumpStrategy = "override", releaseStrategy = "patch", ignore = [], prefix = "") => {
|
|
282
|
+
// NOTE This fn also updates pkg deps, so it must be invoked anyway.
|
|
283
|
+
const dependentReleaseType = getDependentRelease(package_, bumpStrategy, releaseStrategy, ignore, prefix);
|
|
284
|
+
|
|
285
|
+
// Release type found by commitAnalyzer.
|
|
286
|
+
if (package_._nextType) {
|
|
287
|
+
return package_._nextType;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (!dependentReleaseType) {
|
|
291
|
+
return undefined;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Define release type for dependent package if any of its deps changes.
|
|
295
|
+
// `patch`, `minor`, `major` — strictly declare the release type that occurs when any dependency is updated.
|
|
296
|
+
// `inherit` — applies the "highest" release of updated deps to the package.
|
|
297
|
+
// For example, if any dep has a breaking change, `major` release will be applied to the all dependants up the chain.
|
|
298
|
+
|
|
299
|
+
// eslint-disable-next-line no-param-reassign
|
|
300
|
+
package_._nextType = releaseStrategy === "inherit" ? dependentReleaseType : releaseStrategy;
|
|
301
|
+
|
|
302
|
+
return package_._nextType;
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Resolve next version of dependency.
|
|
307
|
+
*
|
|
308
|
+
* @param {string} currentVersion Current dep version
|
|
309
|
+
* @param {string} nextVersion Next release type: patch, minor, major
|
|
310
|
+
* @param {string|undefined} bumpStrategy Resolution strategy: inherit, override, satisfy
|
|
311
|
+
* @param {string} prefix Dependency version prefix to be attached if `bumpStrategy='override'`. ^ | ~ | '' (defaults to empty string)
|
|
312
|
+
* @returns {string} Next dependency version
|
|
313
|
+
* @internal
|
|
314
|
+
*/
|
|
315
|
+
export const resolveNextVersion = (currentVersion, nextVersion, bumpStrategy = "override", prefix = "") => {
|
|
316
|
+
// handle cases of "workspace protocol" defined in yarn and pnpm workspace, whose version starts with "workspace:"
|
|
317
|
+
// eslint-disable-next-line no-param-reassign
|
|
318
|
+
currentVersion = substituteWorkspaceVersion(currentVersion, nextVersion);
|
|
319
|
+
|
|
320
|
+
// if strategy is ignore, return the current version
|
|
321
|
+
if (bumpStrategy === "ignore") {
|
|
322
|
+
return currentVersion;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// no change...
|
|
326
|
+
if (currentVersion === nextVersion) {
|
|
327
|
+
return currentVersion;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Check the next pkg version against its current references.
|
|
331
|
+
// If it matches (`*` matches to any, `1.1.0` matches `1.1.x`, `1.5.0` matches to `^1.0.0` and so on)
|
|
332
|
+
// release will not be triggered, if not `override` strategy will be applied instead.
|
|
333
|
+
if ((bumpStrategy === "satisfy" || bumpStrategy === "inherit") && semver.satisfies(nextVersion, currentVersion)) {
|
|
334
|
+
return currentVersion;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// `inherit` will try to follow the current declaration version/range.
|
|
338
|
+
// `~1.0.0` + `minor` turns into `~1.1.0`, `1.x` + `major` gives `2.x`,
|
|
339
|
+
// but `1.x` + `minor` gives `1.x` so there will be no release, etc.
|
|
340
|
+
if (bumpStrategy === "inherit") {
|
|
341
|
+
const separator = ".";
|
|
342
|
+
const nextChunks = nextVersion.split(separator);
|
|
343
|
+
const currentChunks = currentVersion.split(separator);
|
|
344
|
+
// eslint-disable-next-line security/detect-object-injection
|
|
345
|
+
const resolvedChunks = currentChunks.map((chunk, index) => (nextChunks[index] ? chunk.replace(/\d+/u, nextChunks[index]) : chunk));
|
|
346
|
+
|
|
347
|
+
return resolvedChunks.join(separator);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// "override"
|
|
351
|
+
// By default next package version would be set as is for the all dependants.
|
|
352
|
+
return prefix + nextVersion;
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Update pkg deps.
|
|
357
|
+
*
|
|
358
|
+
* @param {Package} package_ The package this function is being called on.
|
|
359
|
+
* @returns {undefined}
|
|
360
|
+
* @internal
|
|
361
|
+
*/
|
|
362
|
+
export const updateManifestDeps = (package_) => {
|
|
363
|
+
const { manifest, path } = package_;
|
|
364
|
+
const { indent, trailingWhitespace } = recognizeFormat(manifest.__contents__);
|
|
365
|
+
|
|
366
|
+
// We need to bump pkg.version for correct yarn.lock update
|
|
367
|
+
// https://github.com/qiwi/multi-semantic-release/issues/58
|
|
368
|
+
manifest.version = package_._nextRelease.version || manifest.version;
|
|
369
|
+
|
|
370
|
+
// Loop through localDeps to verify release consistency.
|
|
371
|
+
package_.localDeps.forEach((d) => {
|
|
372
|
+
// Get version of dependency.
|
|
373
|
+
const release = d._nextRelease || d._lastRelease;
|
|
374
|
+
|
|
375
|
+
// Cannot establish version.
|
|
376
|
+
if (!release || !release.version) {
|
|
377
|
+
throw new Error(`Cannot release ${package_.name} because dependency ${d.name} has not been released yet`);
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
if (!auditManifestChanges(manifest, path)) {
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Write package.json back out.
|
|
386
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
|
387
|
+
writeFileSync(path, JSON.stringify(manifest, null, indent) + trailingWhitespace);
|
|
388
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { existsSync, lstatSync } from "node:fs";
|
|
2
|
+
import { Writable } from "node:stream";
|
|
3
|
+
|
|
4
|
+
import { add, checker } from "blork";
|
|
5
|
+
import { WritableStreamBuffer } from "stream-buffers";
|
|
6
|
+
|
|
7
|
+
// Get some checkers.
|
|
8
|
+
const isAbsolute = checker("absolute");
|
|
9
|
+
|
|
10
|
+
// Add a directory checker.
|
|
11
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
|
12
|
+
add("directory", (v) => isAbsolute(v) && existsSync(v) && lstatSync(v).isDirectory(), "directory that exists in the filesystem");
|
|
13
|
+
|
|
14
|
+
// Add a writable stream checker.
|
|
15
|
+
add(
|
|
16
|
+
"stream",
|
|
17
|
+
// istanbul ignore next (not important)
|
|
18
|
+
(v) => v instanceof Writable || v instanceof WritableStreamBuffer,
|
|
19
|
+
"instance of stream.Writable or WritableStreamBuffer",
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
// eslint-disable-next-line simple-import-sort/exports
|
|
23
|
+
export { ValueError, check } from "blork";
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { isAbsolute, join, normalize } from "node:path";
|
|
2
|
+
|
|
3
|
+
import { check } from "./blork.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Normalize and make a path absolute, optionally using a custom CWD.
|
|
7
|
+
* Trims any trailing slashes from the path.
|
|
8
|
+
*
|
|
9
|
+
* @param {string} path The path to normalize and make absolute.
|
|
10
|
+
* @param {string} cwd=process.cwd() The CWD to prepend to the path to make it absolute.
|
|
11
|
+
* @returns {string} The absolute and normalized path.
|
|
12
|
+
*
|
|
13
|
+
* @internal
|
|
14
|
+
*/
|
|
15
|
+
function cleanPath(path, cwd = process.cwd()) {
|
|
16
|
+
check(path, "path: path");
|
|
17
|
+
check(cwd, "cwd: absolute");
|
|
18
|
+
|
|
19
|
+
// Normalize, absolutify, and trim trailing slashes from the path.
|
|
20
|
+
return normalize(isAbsolute(path) ? path : join(cwd, path)).replace(/[/\\]+$/u, "");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Exports.
|
|
24
|
+
export default cleanPath;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lifted and tweaked from semantic-release because we follow how they bump their packages/dependencies.
|
|
3
|
+
* https://github.com/semantic-release/semantic-release/blob/master/lib/utils.js
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { gt, prerelease, rcompare } from "semver";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* HOC that applies highest/lowest semver function.
|
|
10
|
+
* @param {Function} predicate High order function to be called.
|
|
11
|
+
* @param {string|undefined} version1 Version 1 to be compared with.
|
|
12
|
+
* @param {string|undefined} version2 Version 2 to be compared with.
|
|
13
|
+
* @returns {string|undefined} Highest or lowest version.
|
|
14
|
+
* @internal
|
|
15
|
+
*/
|
|
16
|
+
const _selectVersionBy = (predicate, version1, version2) => {
|
|
17
|
+
if (predicate && version1 && version2) {
|
|
18
|
+
return predicate(version1, version2) ? version1 : version2;
|
|
19
|
+
}
|
|
20
|
+
return version1 || version2;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Gets highest semver function binding gt to the HOC selectVersionBy.
|
|
25
|
+
*/
|
|
26
|
+
export const getHighestVersion = _selectVersionBy.bind(null, gt);
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Retrieve the latest version from a list of versions.
|
|
30
|
+
* @param {array} versions Versions as string list.
|
|
31
|
+
* @param {boolean|undefined} withPrerelease Prerelease flag.
|
|
32
|
+
* @returns {string|undefined} Latest version.
|
|
33
|
+
* @internal
|
|
34
|
+
*/
|
|
35
|
+
export function getLatestVersion(versions, withPrerelease) {
|
|
36
|
+
return versions.filter((version) => withPrerelease || !prerelease(version)).sort(rcompare)[0];
|
|
37
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// eslint-disable-next-line you-dont-need-lodash-underscore/cast-array
|
|
2
|
+
import { castArray, pickBy } from "lodash-es";
|
|
3
|
+
|
|
4
|
+
const isNil = (value) => value == null;
|
|
5
|
+
|
|
6
|
+
const mergeConfig = (a = {}, b = {}) => {
|
|
7
|
+
return {
|
|
8
|
+
...a,
|
|
9
|
+
// Remove `null` and `undefined` options so they can be replaced with default ones
|
|
10
|
+
...pickBy(b, (option) => !isNil(option)),
|
|
11
|
+
// Treat nested objects differently as otherwise we'll loose undefined keys
|
|
12
|
+
deps: {
|
|
13
|
+
...a.deps,
|
|
14
|
+
...pickBy(b.deps, (option) => !isNil(option)),
|
|
15
|
+
},
|
|
16
|
+
// Treat arrays differently by merging them
|
|
17
|
+
ignorePackages: [...new Set([...castArray(a.ignorePackages || []), ...castArray(b.ignorePackages || [])])],
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export default mergeConfig;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import detectIndent from "detect-indent";
|
|
2
|
+
import { detectNewline } from "detect-newline";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Information about the format of a file.
|
|
6
|
+
* @typedef FileFormat
|
|
7
|
+
* @property {string|number} indent Indentation characters
|
|
8
|
+
* @property {string} trailingWhitespace Trailing whitespace at the end of the file
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Detects the indentation and trailing whitespace of a file.
|
|
13
|
+
*
|
|
14
|
+
* @param {string} contents contents of the file
|
|
15
|
+
* @returns {FileFormat} Formatting of the file
|
|
16
|
+
*/
|
|
17
|
+
function recognizeFormat(contents) {
|
|
18
|
+
return {
|
|
19
|
+
indent: detectIndent(contents).indent,
|
|
20
|
+
trailingWhitespace: detectNewline(contents) || "",
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Exports.
|
|
25
|
+
export default recognizeFormat;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Converts a stream to an array
|
|
3
|
+
*
|
|
4
|
+
* @param {ReadStream} stream
|
|
5
|
+
* @returns {Promise<array>}
|
|
6
|
+
*/
|
|
7
|
+
export default function streamToArray(stream) {
|
|
8
|
+
if (!stream.readable) {
|
|
9
|
+
return Promise.resolve([]);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// eslint-disable-next-line compat/compat
|
|
13
|
+
return new Promise((resolve, reject) => {
|
|
14
|
+
// stream is already ended
|
|
15
|
+
if (!stream.readable) {
|
|
16
|
+
resolve([]);
|
|
17
|
+
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let array = [];
|
|
22
|
+
|
|
23
|
+
function cleanup() {
|
|
24
|
+
array = null;
|
|
25
|
+
|
|
26
|
+
// eslint-disable-next-line no-use-before-define
|
|
27
|
+
stream.removeListener("data", onData);
|
|
28
|
+
// eslint-disable-next-line no-use-before-define
|
|
29
|
+
stream.removeListener("end", onEnd);
|
|
30
|
+
// eslint-disable-next-line no-use-before-define
|
|
31
|
+
stream.removeListener("error", onError);
|
|
32
|
+
// eslint-disable-next-line no-use-before-define
|
|
33
|
+
stream.removeListener("close", onClose);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function onData(document_) {
|
|
37
|
+
array.push(document_);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function onEnd() {
|
|
41
|
+
resolve(array);
|
|
42
|
+
cleanup();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function onError(error) {
|
|
46
|
+
reject(error);
|
|
47
|
+
cleanup();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function onClose() {
|
|
51
|
+
resolve(array);
|
|
52
|
+
cleanup();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
stream.on("data", onData);
|
|
56
|
+
stream.on("end", onEnd);
|
|
57
|
+
stream.on("error", onEnd);
|
|
58
|
+
stream.on("close", onClose);
|
|
59
|
+
});
|
|
60
|
+
}
|