@wpelevator/wp-release 0.6.1 → 0.8.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/README.md +81 -7
- package/dist/changeset-config.d.ts +31 -0
- package/dist/changeset-config.js +8 -0
- package/dist/changeset-release.d.ts +25 -5
- package/dist/changeset-release.js +335 -21
- package/dist/changesets.d.ts +19 -0
- package/dist/changesets.js +21 -0
- package/dist/cli.js +2 -0
- package/dist/commands/changeset-args.d.ts +14 -0
- package/dist/commands/changeset-args.js +19 -0
- package/dist/commands/changeset-tag.d.ts +7 -0
- package/dist/commands/changeset-tag.js +22 -5
- package/dist/commands/changeset-version.d.ts +7 -0
- package/dist/commands/changeset-version.js +8 -2
- package/dist/commands/deploy-git.d.ts +45 -0
- package/dist/commands/deploy-git.js +81 -0
- package/dist/git-deploy.d.ts +218 -0
- package/dist/git-deploy.js +420 -0
- package/dist/git.d.ts +49 -0
- package/dist/git.js +65 -0
- package/dist/github.d.ts +24 -0
- package/dist/github.js +30 -0
- package/dist/index.d.ts +12 -5
- package/dist/index.js +6 -2
- package/dist/summary.d.ts +51 -0
- package/dist/summary.js +78 -0
- package/package.json +1 -1
|
@@ -6,7 +6,7 @@ import { promisify } from 'node:util';
|
|
|
6
6
|
import { loadChangesetConfig } from "./changeset-config.js";
|
|
7
7
|
import { findMainPluginFile } from "./package-meta.js";
|
|
8
8
|
import { findConstantValues } from "./version-check.js";
|
|
9
|
-
import { getChangesetBump, getNextVersion, parseChangeset, } from "./changesets.js";
|
|
9
|
+
import { formatChangelogList, formatChangeset, getChangesetBump, getNextVersion, parseChangeset, } from "./changesets.js";
|
|
10
10
|
import { ValidationError } from "./errors.js";
|
|
11
11
|
const execFileAsync = promisify(execFile);
|
|
12
12
|
/**
|
|
@@ -19,6 +19,18 @@ export async function getChangesetReleasePlan(input) {
|
|
|
19
19
|
const context = await getReleaseContext(input);
|
|
20
20
|
return context?.plan;
|
|
21
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Discover the packages of the repository configuration.
|
|
24
|
+
*
|
|
25
|
+
* @param input Repository root and optional explicit config file.
|
|
26
|
+
* @return Packages in the configured order with their current versions.
|
|
27
|
+
* @throws {ValidationError} Without a config, or for an invalid one.
|
|
28
|
+
*/
|
|
29
|
+
export async function getChangesetPackages(input) {
|
|
30
|
+
const loaded = await requireConfig(input);
|
|
31
|
+
const packages = await discoverPackages(input.cwd, loaded.config);
|
|
32
|
+
return packages.map(toChangesetPackage);
|
|
33
|
+
}
|
|
22
34
|
async function getReleaseContext(input) {
|
|
23
35
|
const loaded = await loadChangesetConfig(input);
|
|
24
36
|
if (!loaded) {
|
|
@@ -110,12 +122,17 @@ export async function versionChangesets(input) {
|
|
|
110
122
|
if (!context) {
|
|
111
123
|
throw new ValidationError('No wp-release config found. Add wp-release.config.js or pass --config <path>.');
|
|
112
124
|
}
|
|
113
|
-
const { config, packages
|
|
125
|
+
const { config, packages } = context;
|
|
126
|
+
const releasePlan = input.packages
|
|
127
|
+
? selectReleasePlan(context.plan, packages, input.packages)
|
|
128
|
+
: context.plan;
|
|
114
129
|
const date = input.date ?? new Date().toISOString().slice(0, 10);
|
|
130
|
+
const changesetDirectory = path.resolve(input.cwd, config.changesets ?? '.changeset');
|
|
131
|
+
const releases = await getVersionedReleases(input.cwd, config, releasePlan.releases, changesetDirectory);
|
|
115
132
|
if (!input.dryRun) {
|
|
116
133
|
const journal = new Map();
|
|
117
134
|
try {
|
|
118
|
-
for (const release of
|
|
135
|
+
for (const release of releases) {
|
|
119
136
|
const configured = packages.find((pkg) => pkg.name === release.package.name);
|
|
120
137
|
if (!configured) {
|
|
121
138
|
throw new ValidationError(`Unknown package "${release.package.name}".`);
|
|
@@ -126,11 +143,8 @@ export async function versionChangesets(input) {
|
|
|
126
143
|
await verifyVersionTargets(input.cwd, configured, release.nextVersion);
|
|
127
144
|
await prependChangelog(configured.directory, release, date, config.readmeChangelog ?? false, config.changelogInsertAfter, journal);
|
|
128
145
|
}
|
|
129
|
-
const changesetDirectory = path.resolve(input.cwd, config.changesets ?? '.changeset');
|
|
130
146
|
for (const changeset of releasePlan.changesets) {
|
|
131
|
-
|
|
132
|
-
await rememberFile(journal, file);
|
|
133
|
-
await rm(file);
|
|
147
|
+
await consumeChangeset(path.join(changesetDirectory, changeset.fileName), changeset, input.packages, journal);
|
|
134
148
|
}
|
|
135
149
|
}
|
|
136
150
|
catch (error) {
|
|
@@ -142,11 +156,118 @@ export async function versionChangesets(input) {
|
|
|
142
156
|
}
|
|
143
157
|
}
|
|
144
158
|
return {
|
|
145
|
-
|
|
159
|
+
changesets: releasePlan.changesets,
|
|
160
|
+
releases,
|
|
146
161
|
date,
|
|
147
162
|
changed: !input.dryRun && releasePlan.changesets.length > 0,
|
|
148
163
|
};
|
|
149
164
|
}
|
|
165
|
+
/**
|
|
166
|
+
* Format the changelog entries of each release, with the configured
|
|
167
|
+
* formatter and the commit that added each changeset.
|
|
168
|
+
*
|
|
169
|
+
* @param cwd Repository root.
|
|
170
|
+
* @param config Repository release configuration.
|
|
171
|
+
* @param releases Package releases.
|
|
172
|
+
* @param changesetDirectory Directory of the changeset files.
|
|
173
|
+
* @return Releases with their changelog entries.
|
|
174
|
+
*/
|
|
175
|
+
async function getVersionedReleases(cwd, config, releases, changesetDirectory) {
|
|
176
|
+
const commits = new Map();
|
|
177
|
+
const getCommit = (changeset) => {
|
|
178
|
+
const cached = commits.get(changeset.fileName) ??
|
|
179
|
+
getChangesetCommit(cwd, path.join(changesetDirectory, changeset.fileName));
|
|
180
|
+
commits.set(changeset.fileName, cached);
|
|
181
|
+
return cached;
|
|
182
|
+
};
|
|
183
|
+
return Promise.all(releases.map(async (release) => {
|
|
184
|
+
const entries = [];
|
|
185
|
+
// Format one at a time, so that formatters calling an API aren't rate limited.
|
|
186
|
+
for (const changeset of release.changesets) {
|
|
187
|
+
entries.push(await getChangelogEntry(config, release.package, changeset, getCommit));
|
|
188
|
+
}
|
|
189
|
+
return { ...release, changelog: formatChangelogList(entries) };
|
|
190
|
+
}));
|
|
191
|
+
}
|
|
192
|
+
async function getChangelogEntry(config, pkg, changeset, getCommit) {
|
|
193
|
+
const summary = changeset.summary.trim();
|
|
194
|
+
if (!summary || !config.changelogEntry) {
|
|
195
|
+
return summary;
|
|
196
|
+
}
|
|
197
|
+
const commit = await getCommit(changeset);
|
|
198
|
+
const entry = await config.changelogEntry({
|
|
199
|
+
package: pkg,
|
|
200
|
+
changeset,
|
|
201
|
+
...(commit ? { commit } : {}),
|
|
202
|
+
});
|
|
203
|
+
if ('string' !== typeof entry) {
|
|
204
|
+
throw new ValidationError(`The changelogEntry formatter didn't return a string for changeset ${changeset.fileName}.`);
|
|
205
|
+
}
|
|
206
|
+
return entry.trim();
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Find the commit that added a changeset file.
|
|
210
|
+
*
|
|
211
|
+
* @param cwd Repository root.
|
|
212
|
+
* @param file Changeset file.
|
|
213
|
+
* @return Commit, or undefined when the file isn't committed.
|
|
214
|
+
*/
|
|
215
|
+
async function getChangesetCommit(cwd, file) {
|
|
216
|
+
const output = await git(cwd, [
|
|
217
|
+
'log',
|
|
218
|
+
'--diff-filter=A',
|
|
219
|
+
'--format=%H%x1f%h%x1f%s',
|
|
220
|
+
'-1',
|
|
221
|
+
'--',
|
|
222
|
+
path.relative(cwd, file).split(path.sep).join('/'),
|
|
223
|
+
]);
|
|
224
|
+
const [hash, shortHash = '', subject = ''] = output
|
|
225
|
+
.trim()
|
|
226
|
+
.split('\x1f');
|
|
227
|
+
return hash ? { hash, shortHash, subject } : undefined;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Limit a release plan to some packages and the changesets that name them.
|
|
231
|
+
*
|
|
232
|
+
* @param plan Release plan of all pending changesets.
|
|
233
|
+
* @param packages Configured packages.
|
|
234
|
+
* @param names Packages to version.
|
|
235
|
+
* @return Release plan of the named packages.
|
|
236
|
+
* @throws {ValidationError} For unknown packages.
|
|
237
|
+
*/
|
|
238
|
+
function selectReleasePlan(plan, packages, names) {
|
|
239
|
+
names.forEach((name) => {
|
|
240
|
+
if (!packages.some((pkg) => pkg.name === name)) {
|
|
241
|
+
throw new ValidationError(`Unknown package "${name}".`);
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
return {
|
|
245
|
+
releases: plan.releases.filter((release) => names.includes(release.package.name)),
|
|
246
|
+
changesets: plan.changesets.filter((changeset) => names.some((name) => Object.hasOwn(changeset.packages, name))),
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Remove an applied changeset, or only the applied packages from a changeset
|
|
251
|
+
* that names other packages too, so that their releases keep its summary.
|
|
252
|
+
*
|
|
253
|
+
* @param file Changeset file.
|
|
254
|
+
* @param changeset Parsed changeset.
|
|
255
|
+
* @param applied Versioned packages, or undefined when all were versioned.
|
|
256
|
+
* @param journal File journal for rolling back.
|
|
257
|
+
*/
|
|
258
|
+
async function consumeChangeset(file, changeset, applied, journal) {
|
|
259
|
+
const remaining = Object.entries(changeset.packages).filter(([name]) => undefined !== applied && !applied.includes(name));
|
|
260
|
+
await rememberFile(journal, file);
|
|
261
|
+
if (remaining.length) {
|
|
262
|
+
await writeFile(file, formatChangeset({
|
|
263
|
+
packages: Object.fromEntries(remaining),
|
|
264
|
+
summary: changeset.summary,
|
|
265
|
+
}));
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
await rm(file);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
150
271
|
/**
|
|
151
272
|
* Create missing release tags, optionally pushing them to the configured remote.
|
|
152
273
|
*
|
|
@@ -164,19 +285,26 @@ export async function tagChangesets(input) {
|
|
|
164
285
|
if (!context) {
|
|
165
286
|
throw new ValidationError('No wp-release config found. Add wp-release.config.js or pass --config <path>.');
|
|
166
287
|
}
|
|
288
|
+
const selected = selectPackages(context.packages, input);
|
|
167
289
|
const packages = input.since
|
|
168
|
-
? (await changedPackagesSince(input.cwd,
|
|
169
|
-
|
|
290
|
+
? (await changedPackagesSince(input.cwd, selected, input.since))
|
|
291
|
+
.packages
|
|
292
|
+
: selected;
|
|
170
293
|
const results = [];
|
|
171
294
|
const dryRun = input.dryRun || input.untagged;
|
|
172
295
|
const tagFormat = context.config.tagFormat ?? '{path}/{version}';
|
|
173
296
|
for (const pkg of packages) {
|
|
174
|
-
const version =
|
|
175
|
-
|
|
176
|
-
|
|
297
|
+
const version = await getTagVersion(pkg, input.version);
|
|
298
|
+
if (undefined !== input.version && input.version !== version) {
|
|
299
|
+
throw new ValidationError(`Package "${pkg.name}" is at version ${version}, not ${input.version}.`);
|
|
300
|
+
}
|
|
177
301
|
const tag = formatTag(tagFormat, pkg, version);
|
|
178
302
|
const exists = await gitTagExists(input.cwd, tag);
|
|
179
|
-
|
|
303
|
+
if (!exists) {
|
|
304
|
+
await verifyVersionTargets(input.cwd, pkg, version);
|
|
305
|
+
}
|
|
306
|
+
const previousTag = await getPreviousTag(input.cwd, formatTag(tagFormat, pkg, ''), version);
|
|
307
|
+
const notes = await getReleaseNotes(input.cwd, context.config, pkg, { version, tag, previousTag }, exists ? tag : 'HEAD');
|
|
180
308
|
if (!exists && !dryRun) {
|
|
181
309
|
await git(input.cwd, ['tag', tag]);
|
|
182
310
|
}
|
|
@@ -184,6 +312,8 @@ export async function tagChangesets(input) {
|
|
|
184
312
|
package: pkg.name,
|
|
185
313
|
version,
|
|
186
314
|
tag,
|
|
315
|
+
...(previousTag ? { previousTag } : {}),
|
|
316
|
+
prerelease: isPrerelease(version),
|
|
187
317
|
exists,
|
|
188
318
|
created: !exists && !dryRun,
|
|
189
319
|
notes,
|
|
@@ -223,6 +353,7 @@ async function createGitHubRelease(cwd, release) {
|
|
|
223
353
|
`${release.package}@${release.version}`,
|
|
224
354
|
'--notes',
|
|
225
355
|
release.notes,
|
|
356
|
+
...(release.prerelease ? ['--prerelease'] : []),
|
|
226
357
|
], { cwd });
|
|
227
358
|
}
|
|
228
359
|
catch (error) {
|
|
@@ -231,6 +362,141 @@ async function createGitHubRelease(cwd, release) {
|
|
|
231
362
|
}
|
|
232
363
|
}
|
|
233
364
|
}
|
|
365
|
+
/**
|
|
366
|
+
* Get the version to tag. Packages versioned by their git tags have no
|
|
367
|
+
* version file, so the expected version is the version to tag, or else the
|
|
368
|
+
* latest CHANGELOG.md version.
|
|
369
|
+
*
|
|
370
|
+
* @param pkg Configured package.
|
|
371
|
+
* @param expected Expected version from --version, if any.
|
|
372
|
+
* @return Version to tag.
|
|
373
|
+
*/
|
|
374
|
+
async function getTagVersion(pkg, expected) {
|
|
375
|
+
if ('git-tag' !== pkg.versionConfig.from.kind) {
|
|
376
|
+
return pkg.currentVersion;
|
|
377
|
+
}
|
|
378
|
+
return (expected ??
|
|
379
|
+
(await getLatestChangelogVersion(pkg.directory, pkg.currentVersion)));
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Pick the packages passed with --package, or all packages without it.
|
|
383
|
+
*
|
|
384
|
+
* @param packages Configured packages.
|
|
385
|
+
* @param input Tag options.
|
|
386
|
+
* @return Selected packages in the configured order.
|
|
387
|
+
* @throws {ValidationError} For unknown packages, or --version without a single package.
|
|
388
|
+
*/
|
|
389
|
+
function selectPackages(packages, input) {
|
|
390
|
+
const names = input.packages ?? [];
|
|
391
|
+
if (undefined !== input.version && 1 !== names.length) {
|
|
392
|
+
throw new ValidationError('An expected version requires exactly one package.');
|
|
393
|
+
}
|
|
394
|
+
names.forEach((name) => {
|
|
395
|
+
if (!packages.some((pkg) => pkg.name === name)) {
|
|
396
|
+
throw new ValidationError(`Unknown package "${name}".`);
|
|
397
|
+
}
|
|
398
|
+
});
|
|
399
|
+
return names.length
|
|
400
|
+
? packages.filter((pkg) => names.includes(pkg.name))
|
|
401
|
+
: packages;
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Find the tag of the newest release before a version.
|
|
405
|
+
*
|
|
406
|
+
* @param cwd Repository root.
|
|
407
|
+
* @param prefix Tag prefix of the package, like `plugins/alpha/`.
|
|
408
|
+
* @param version Version being released.
|
|
409
|
+
* @return Previous tag, or undefined for the first release.
|
|
410
|
+
*/
|
|
411
|
+
async function getPreviousTag(cwd, prefix, version) {
|
|
412
|
+
const output = await git(cwd, ['tag', '--list', `${prefix}*`]);
|
|
413
|
+
return output
|
|
414
|
+
.trim()
|
|
415
|
+
.split(/\r?\n/)
|
|
416
|
+
.filter((tag) => tag.startsWith(prefix))
|
|
417
|
+
.map((tag) => ({ tag, version: tag.slice(prefix.length) }))
|
|
418
|
+
.filter((item) => isVersion(item.version) &&
|
|
419
|
+
compareVersions(item.version, version) < 0)
|
|
420
|
+
.sort((a, b) => compareVersions(b.version, a.version))[0]?.tag;
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Build the release notes of a tag with the configured formatter.
|
|
424
|
+
*
|
|
425
|
+
* @param cwd Repository root.
|
|
426
|
+
* @param config Repository release configuration.
|
|
427
|
+
* @param pkg Released package.
|
|
428
|
+
* @param release Version, tag and previous tag of the release.
|
|
429
|
+
* @param release.version Released version.
|
|
430
|
+
* @param release.tag Release tag.
|
|
431
|
+
* @param release.previousTag Tag of the previous release, if any.
|
|
432
|
+
* @param end Commit of the release: the tag, or HEAD before tagging.
|
|
433
|
+
* @return Release notes.
|
|
434
|
+
*/
|
|
435
|
+
async function getReleaseNotes(cwd, config, pkg, release, end) {
|
|
436
|
+
const changelog = await readReleaseNotes(pkg.directory, release.version);
|
|
437
|
+
const commits = await getReleaseCommits(cwd, pkg, end, release.previousTag);
|
|
438
|
+
const notes = changelog ||
|
|
439
|
+
commits
|
|
440
|
+
.map((commit) => `- ${commit.subject} (${commit.shortHash})`)
|
|
441
|
+
.join('\n');
|
|
442
|
+
if (!config.releaseNotes) {
|
|
443
|
+
return notes;
|
|
444
|
+
}
|
|
445
|
+
const formatted = await config.releaseNotes({
|
|
446
|
+
package: toChangesetPackage(pkg),
|
|
447
|
+
...release,
|
|
448
|
+
changelog,
|
|
449
|
+
commits,
|
|
450
|
+
notes,
|
|
451
|
+
});
|
|
452
|
+
if ('string' !== typeof formatted) {
|
|
453
|
+
throw new ValidationError(`The releaseNotes formatter didn't return a string for package "${pkg.name}".`);
|
|
454
|
+
}
|
|
455
|
+
return formatted.trim();
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* List the commits that changed a package since its previous release.
|
|
459
|
+
*
|
|
460
|
+
* @param cwd Repository root.
|
|
461
|
+
* @param pkg Released package.
|
|
462
|
+
* @param end Commit of the release.
|
|
463
|
+
* @param previousTag Tag of the previous release, if any.
|
|
464
|
+
* @return Commits, newest first.
|
|
465
|
+
*/
|
|
466
|
+
async function getReleaseCommits(cwd, pkg, end, previousTag) {
|
|
467
|
+
const packageFromRepo = path.relative(cwd, pkg.directory).split(path.sep).join('/') ||
|
|
468
|
+
'.';
|
|
469
|
+
const output = await git(cwd, [
|
|
470
|
+
'log',
|
|
471
|
+
'--format=%H%x1f%h%x1f%s',
|
|
472
|
+
previousTag ? `${previousTag}..${end}` : end,
|
|
473
|
+
'--',
|
|
474
|
+
packageFromRepo,
|
|
475
|
+
]);
|
|
476
|
+
return output
|
|
477
|
+
.trim()
|
|
478
|
+
.split(/\r?\n/)
|
|
479
|
+
.filter(Boolean)
|
|
480
|
+
.map((line) => {
|
|
481
|
+
const [hash = '', shortHash = '', subject = ''] = line.split('\x1f');
|
|
482
|
+
return { hash, shortHash, subject };
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
/**
|
|
486
|
+
* Remove the release configuration from a package passed to config callbacks.
|
|
487
|
+
*
|
|
488
|
+
* @param pkg Configured package.
|
|
489
|
+
* @return Package without its group configuration.
|
|
490
|
+
*/
|
|
491
|
+
function toChangesetPackage(pkg) {
|
|
492
|
+
return {
|
|
493
|
+
name: pkg.name,
|
|
494
|
+
directory: pkg.directory,
|
|
495
|
+
path: pkg.path,
|
|
496
|
+
type: pkg.type,
|
|
497
|
+
currentVersion: pkg.currentVersion,
|
|
498
|
+
};
|
|
499
|
+
}
|
|
234
500
|
/**
|
|
235
501
|
* Check whether changed files belong to packages without a changeset.
|
|
236
502
|
*
|
|
@@ -549,9 +815,7 @@ async function prependChangelog(directory, release, date, readmeChangelog, chang
|
|
|
549
815
|
const summaries = release.changesets
|
|
550
816
|
.map((item) => item.summary.trim())
|
|
551
817
|
.filter(Boolean);
|
|
552
|
-
const section = `## ${release.nextVersion} (${date})\n\n${
|
|
553
|
-
.map((item) => `- ${item.replaceAll(/\r?\n/g, '\n ')}`)
|
|
554
|
-
.join('\n')}\n\n`;
|
|
818
|
+
const section = `## ${release.nextVersion} (${date})\n\n${release.changelog}\n\n`;
|
|
555
819
|
const changelog = path.join(directory, 'CHANGELOG.md');
|
|
556
820
|
let previous = '';
|
|
557
821
|
try {
|
|
@@ -826,10 +1090,60 @@ function packageType(group) {
|
|
|
826
1090
|
}
|
|
827
1091
|
return 'other';
|
|
828
1092
|
}
|
|
1093
|
+
const VERSION_PATTERN = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z.-]+))?$/;
|
|
1094
|
+
function isVersion(version) {
|
|
1095
|
+
return VERSION_PATTERN.test(version);
|
|
1096
|
+
}
|
|
1097
|
+
function isPrerelease(version) {
|
|
1098
|
+
return Boolean(VERSION_PATTERN.exec(version)?.[4]);
|
|
1099
|
+
}
|
|
1100
|
+
/**
|
|
1101
|
+
* Compare two versions with SemVer precedence, where a pre-release like
|
|
1102
|
+
* 1.0.0-rc.1 comes before 1.0.0.
|
|
1103
|
+
*
|
|
1104
|
+
* @param left Version.
|
|
1105
|
+
* @param right Version to compare with.
|
|
1106
|
+
* @return Negative when left is lower, positive when higher, otherwise 0.
|
|
1107
|
+
*/
|
|
829
1108
|
function compareVersions(left, right) {
|
|
830
|
-
const
|
|
831
|
-
const
|
|
832
|
-
|
|
1109
|
+
const [leftCore = '', leftPre] = splitPrerelease(left);
|
|
1110
|
+
const [rightCore = '', rightPre] = splitPrerelease(right);
|
|
1111
|
+
const a = leftCore.split('.').map(Number);
|
|
1112
|
+
const b = rightCore.split('.').map(Number);
|
|
1113
|
+
const core = (a[0] ?? 0) - (b[0] ?? 0) ||
|
|
833
1114
|
(a[1] ?? 0) - (b[1] ?? 0) ||
|
|
834
|
-
(a[2] ?? 0) - (b[2] ?? 0)
|
|
1115
|
+
(a[2] ?? 0) - (b[2] ?? 0);
|
|
1116
|
+
if (core || leftPre === rightPre) {
|
|
1117
|
+
return core;
|
|
1118
|
+
}
|
|
1119
|
+
if (undefined === leftPre || undefined === rightPre) {
|
|
1120
|
+
return undefined === leftPre ? 1 : -1;
|
|
1121
|
+
}
|
|
1122
|
+
return comparePrerelease(leftPre.split('.'), rightPre.split('.'));
|
|
1123
|
+
}
|
|
1124
|
+
function splitPrerelease(version) {
|
|
1125
|
+
const index = version.indexOf('-');
|
|
1126
|
+
return -1 === index
|
|
1127
|
+
? [version]
|
|
1128
|
+
: [version.slice(0, index), version.slice(index + 1)];
|
|
1129
|
+
}
|
|
1130
|
+
function comparePrerelease(left, right) {
|
|
1131
|
+
const [a, ...leftRest] = left;
|
|
1132
|
+
const [b, ...rightRest] = right;
|
|
1133
|
+
if (undefined === a || undefined === b) {
|
|
1134
|
+
return (undefined === a ? 0 : 1) - (undefined === b ? 0 : 1);
|
|
1135
|
+
}
|
|
1136
|
+
const order = compareIdentifier(a, b);
|
|
1137
|
+
return order || comparePrerelease(leftRest, rightRest);
|
|
1138
|
+
}
|
|
1139
|
+
function compareIdentifier(a, b) {
|
|
1140
|
+
const aNumeric = /^\d+$/.test(a);
|
|
1141
|
+
const bNumeric = /^\d+$/.test(b);
|
|
1142
|
+
if (aNumeric && bNumeric) {
|
|
1143
|
+
return Number(a) - Number(b);
|
|
1144
|
+
}
|
|
1145
|
+
if (aNumeric || bNumeric) {
|
|
1146
|
+
return aNumeric ? -1 : 1;
|
|
1147
|
+
}
|
|
1148
|
+
return Number(a > b) - Number(a < b);
|
|
835
1149
|
}
|
package/dist/changesets.d.ts
CHANGED
|
@@ -32,6 +32,11 @@ export interface PackageRelease {
|
|
|
32
32
|
nextVersion: string;
|
|
33
33
|
changesets: Changeset[];
|
|
34
34
|
}
|
|
35
|
+
/** A package release with its formatted changelog entries, once versioned. */
|
|
36
|
+
export interface VersionedRelease extends PackageRelease {
|
|
37
|
+
/** Changelog entries as a Markdown list, as added to CHANGELOG.md. */
|
|
38
|
+
changelog: string;
|
|
39
|
+
}
|
|
35
40
|
/** Deterministic release plan assembled from pending changesets. */
|
|
36
41
|
export interface ChangesetReleasePlan {
|
|
37
42
|
changesets: Changeset[];
|
|
@@ -53,6 +58,20 @@ export declare function parseChangeset(contents: string, fileName?: string): Cha
|
|
|
53
58
|
* @return Markdown contents with a trailing newline.
|
|
54
59
|
*/
|
|
55
60
|
export declare function formatChangeset(changeset: Omit<Changeset, 'fileName'>): string;
|
|
61
|
+
/**
|
|
62
|
+
* Format the summaries of changesets as a Markdown list of changelog entries.
|
|
63
|
+
*
|
|
64
|
+
* @param changesets Changesets of a release.
|
|
65
|
+
* @return Markdown list, empty without summaries.
|
|
66
|
+
*/
|
|
67
|
+
export declare function formatChangelogEntries(changesets: Changeset[]): string;
|
|
68
|
+
/**
|
|
69
|
+
* Format changelog entries as a Markdown list, indenting their extra lines.
|
|
70
|
+
*
|
|
71
|
+
* @param entries Changelog entries; empty ones are left out.
|
|
72
|
+
* @return Markdown list, empty without entries.
|
|
73
|
+
*/
|
|
74
|
+
export declare function formatChangelogList(entries: string[]): string;
|
|
56
75
|
/**
|
|
57
76
|
* Return the strongest bump requested for a package across changesets.
|
|
58
77
|
*
|
package/dist/changesets.js
CHANGED
|
@@ -64,6 +64,27 @@ export function formatChangeset(changeset) {
|
|
|
64
64
|
.join('\n');
|
|
65
65
|
return `---\n${frontmatter}\n---\n\n${changeset.summary.trim()}\n`;
|
|
66
66
|
}
|
|
67
|
+
/**
|
|
68
|
+
* Format the summaries of changesets as a Markdown list of changelog entries.
|
|
69
|
+
*
|
|
70
|
+
* @param changesets Changesets of a release.
|
|
71
|
+
* @return Markdown list, empty without summaries.
|
|
72
|
+
*/
|
|
73
|
+
export function formatChangelogEntries(changesets) {
|
|
74
|
+
return formatChangelogList(changesets.map((item) => item.summary.trim()));
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Format changelog entries as a Markdown list, indenting their extra lines.
|
|
78
|
+
*
|
|
79
|
+
* @param entries Changelog entries; empty ones are left out.
|
|
80
|
+
* @return Markdown list, empty without entries.
|
|
81
|
+
*/
|
|
82
|
+
export function formatChangelogList(entries) {
|
|
83
|
+
return entries
|
|
84
|
+
.filter(Boolean)
|
|
85
|
+
.map((item) => `- ${item.replaceAll(/\r?\n/g, '\n ')}`)
|
|
86
|
+
.join('\n');
|
|
87
|
+
}
|
|
67
88
|
/**
|
|
68
89
|
* Return the strongest bump requested for a package across changesets.
|
|
69
90
|
*
|
package/dist/cli.js
CHANGED
|
@@ -6,6 +6,7 @@ import sign from "./commands/sign.js";
|
|
|
6
6
|
import verify from "./commands/verify.js";
|
|
7
7
|
import versionCheck from "./commands/version-check.js";
|
|
8
8
|
import deploySvn from "./commands/deploy-svn.js";
|
|
9
|
+
import deployGit from "./commands/deploy-git.js";
|
|
9
10
|
import { Logger, TextWriter } from "./logger.js";
|
|
10
11
|
import { getBoolean, getString } from "./commands/command.js";
|
|
11
12
|
import { ValidationError } from "./errors.js";
|
|
@@ -25,6 +26,7 @@ export const commands = [
|
|
|
25
26
|
verify,
|
|
26
27
|
versionCheck,
|
|
27
28
|
deploySvn,
|
|
29
|
+
deployGit,
|
|
28
30
|
changesetAdd,
|
|
29
31
|
changesetStatus,
|
|
30
32
|
changesetVersion,
|
|
@@ -61,11 +61,16 @@ export declare const changesetVersionArgs: {
|
|
|
61
61
|
readonly date: {
|
|
62
62
|
readonly type: "string";
|
|
63
63
|
};
|
|
64
|
+
readonly package: {
|
|
65
|
+
readonly type: "string";
|
|
66
|
+
readonly multiple: true;
|
|
67
|
+
};
|
|
64
68
|
};
|
|
65
69
|
/** Parsed input for `changeset-version`. */
|
|
66
70
|
export interface ChangesetVersionArgs {
|
|
67
71
|
dryRun: boolean;
|
|
68
72
|
date?: string;
|
|
73
|
+
packages?: string[];
|
|
69
74
|
}
|
|
70
75
|
/**
|
|
71
76
|
* Validate and normalize a release date option.
|
|
@@ -100,6 +105,13 @@ export declare const changesetTagArgs: {
|
|
|
100
105
|
readonly since: {
|
|
101
106
|
readonly type: "string";
|
|
102
107
|
};
|
|
108
|
+
readonly package: {
|
|
109
|
+
readonly type: "string";
|
|
110
|
+
readonly multiple: true;
|
|
111
|
+
};
|
|
112
|
+
readonly version: {
|
|
113
|
+
readonly type: "string";
|
|
114
|
+
};
|
|
103
115
|
};
|
|
104
116
|
/** Parsed input for `changeset-tag`. */
|
|
105
117
|
export interface ChangesetTagArgs {
|
|
@@ -108,6 +120,8 @@ export interface ChangesetTagArgs {
|
|
|
108
120
|
untagged: boolean;
|
|
109
121
|
githubRelease: boolean;
|
|
110
122
|
since?: string;
|
|
123
|
+
packages: string[];
|
|
124
|
+
version?: string;
|
|
111
125
|
}
|
|
112
126
|
/**
|
|
113
127
|
* Parse the `changeset-tag` options.
|
|
@@ -74,6 +74,7 @@ export function parseChangesetStatusArgs(values) {
|
|
|
74
74
|
export const changesetVersionArgs = {
|
|
75
75
|
'dry-run': { type: 'boolean' },
|
|
76
76
|
date: { type: 'string' },
|
|
77
|
+
package: { type: 'string', multiple: true },
|
|
77
78
|
};
|
|
78
79
|
/**
|
|
79
80
|
* Validate and normalize a release date option.
|
|
@@ -104,9 +105,14 @@ export function parseChangesetDate(value) {
|
|
|
104
105
|
*/
|
|
105
106
|
export function parseChangesetVersionArgs(values) {
|
|
106
107
|
const date = parseChangesetDate(getString(values, 'date'));
|
|
108
|
+
const packages = getStrings(values, 'package');
|
|
109
|
+
if (packages.some((name) => !name.trim())) {
|
|
110
|
+
throw new ValidationError('--package <name> must not be empty.');
|
|
111
|
+
}
|
|
107
112
|
return {
|
|
108
113
|
dryRun: getBoolean(values, 'dry-run'),
|
|
109
114
|
...(date ? { date } : {}),
|
|
115
|
+
...(packages.length ? { packages } : {}),
|
|
110
116
|
};
|
|
111
117
|
}
|
|
112
118
|
/** Parse-args options for `changeset-tag`. */
|
|
@@ -116,6 +122,8 @@ export const changesetTagArgs = {
|
|
|
116
122
|
untagged: { type: 'boolean' },
|
|
117
123
|
'github-release': { type: 'boolean' },
|
|
118
124
|
since: { type: 'string' },
|
|
125
|
+
package: { type: 'string', multiple: true },
|
|
126
|
+
version: { type: 'string' },
|
|
119
127
|
};
|
|
120
128
|
/**
|
|
121
129
|
* Parse the `changeset-tag` options.
|
|
@@ -130,10 +138,21 @@ export function parseChangesetTagArgs(values) {
|
|
|
130
138
|
untagged: getBoolean(values, 'untagged'),
|
|
131
139
|
githubRelease: getBoolean(values, 'github-release'),
|
|
132
140
|
since: getString(values, 'since'),
|
|
141
|
+
packages: getStrings(values, 'package'),
|
|
142
|
+
version: getString(values, 'version')?.trim(),
|
|
133
143
|
};
|
|
134
144
|
if ('' === args.since?.trim()) {
|
|
135
145
|
throw new ValidationError('--since <ref> must not be empty.');
|
|
136
146
|
}
|
|
147
|
+
if (args.packages.some((name) => !name.trim())) {
|
|
148
|
+
throw new ValidationError('--package <name> must not be empty.');
|
|
149
|
+
}
|
|
150
|
+
if ('' === args.version) {
|
|
151
|
+
throw new ValidationError('--version <version> must not be empty.');
|
|
152
|
+
}
|
|
153
|
+
if (undefined !== args.version && 1 !== args.packages.length) {
|
|
154
|
+
throw new ValidationError('--version <version> requires exactly one --package <name>.');
|
|
155
|
+
}
|
|
137
156
|
if (args.githubRelease && !args.push) {
|
|
138
157
|
throw new ValidationError('--github-release requires --push.');
|
|
139
158
|
}
|
|
@@ -19,6 +19,13 @@ declare const _default: {
|
|
|
19
19
|
readonly since: {
|
|
20
20
|
readonly type: "string";
|
|
21
21
|
};
|
|
22
|
+
readonly package: {
|
|
23
|
+
readonly type: "string";
|
|
24
|
+
readonly multiple: true;
|
|
25
|
+
};
|
|
26
|
+
readonly version: {
|
|
27
|
+
readonly type: "string";
|
|
28
|
+
};
|
|
22
29
|
};
|
|
23
30
|
run({ values, cwd, configFile, logger }: import("./command.ts").CommandContext): Promise<{
|
|
24
31
|
exitCode: number;
|
|
@@ -1,18 +1,25 @@
|
|
|
1
1
|
import { tagChangesets } from "../changeset-release.js";
|
|
2
2
|
import { parseChangesetTagArgs, changesetTagArgs } from "./changeset-args.js";
|
|
3
|
+
import { getChangesetTagSummary, getTagStatus } from "../summary.js";
|
|
3
4
|
export default {
|
|
4
5
|
name: 'changeset-tag',
|
|
5
|
-
usage: 'changeset-tag [--dry-run] [--untagged] [--since <ref>] [--push] [--github-release]',
|
|
6
|
+
usage: 'changeset-tag [--dry-run] [--untagged] [--since <ref>] [--package <name>] [--version <version>] [--push] [--github-release]',
|
|
6
7
|
summary: 'Create missing release tags for configured package versions.',
|
|
7
8
|
help: `Options:
|
|
8
|
-
--dry-run List missing tags
|
|
9
|
+
--dry-run List missing tags and their release notes without
|
|
10
|
+
creating them
|
|
9
11
|
--untagged List only releases without a tag, without creating tags
|
|
10
12
|
--since <ref> Tag packages changed since this git ref
|
|
13
|
+
--package <name> Tag only this package (repeatable)
|
|
14
|
+
--version <version> Fail unless the single --package is at this version;
|
|
15
|
+
the version to tag for packages versioned by git tags
|
|
11
16
|
--push Push newly created tags to origin
|
|
12
|
-
--github-release Create a GitHub Release for each tag
|
|
17
|
+
--github-release Create a GitHub Release for each tag, marked as a
|
|
18
|
+
pre-release for versions like 1.0.0-rc.1
|
|
13
19
|
|
|
14
20
|
Tags are created only when absent, and GitHub Releases are created only when
|
|
15
|
-
missing, so rerunning after a partial failure is safe.
|
|
21
|
+
missing, so rerunning after a partial failure is safe. Before a tag is created,
|
|
22
|
+
every configured version target must match the version. --github-release also
|
|
16
23
|
requires --push. With --json or --github-output, --untagged lists the releases
|
|
17
24
|
that still need a tag, for example to release each of them with a separate
|
|
18
25
|
workflow.`,
|
|
@@ -24,7 +31,17 @@ workflow.`,
|
|
|
24
31
|
...(configFile ? { configFile } : {}),
|
|
25
32
|
...args,
|
|
26
33
|
});
|
|
27
|
-
result.tags.forEach((item) =>
|
|
34
|
+
result.tags.forEach((item) => {
|
|
35
|
+
logger.info(`${getTagStatus(item)} ${item.tag}`);
|
|
36
|
+
// A dry run is for previewing the release notes.
|
|
37
|
+
if (args.dryRun) {
|
|
38
|
+
logger.info(item.notes || 'No release notes.');
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
logger.debug(item.notes);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
logger.summary(() => getChangesetTagSummary(result));
|
|
28
45
|
return { exitCode: 0, result };
|
|
29
46
|
},
|
|
30
47
|
};
|