@williamthorsen/release-kit 15.0.3 → 16.1.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/.meta/changelog.json +93 -0
- package/.readyup/kits/default.js +14 -14
- package/.readyup/kits/npm-auto-publish.js +4 -4
- package/.readyup/manifest.json +13 -13
- package/CHANGELOG.md +27 -0
- package/README.md +12 -3
- package/dist/esm/applyChangelogOverrides.js +6 -6
- package/dist/esm/assertReleasesOnNpm.js +11 -11
- package/dist/esm/assertTaggedBaseline.js +5 -5
- package/dist/esm/assertTarballsComplete.d.ts +9 -0
- package/dist/esm/assertTarballsComplete.js +52 -0
- package/dist/esm/bin/release-kit.js +16 -16
- package/dist/esm/buildChangelogEntries.d.ts +19 -19
- package/dist/esm/buildChangelogEntries.js +40 -36
- package/dist/esm/buildChangelogItem.js +24 -24
- package/dist/esm/buildDependencyGraph.js +23 -23
- package/dist/esm/buildReleasedResult.js +21 -19
- package/dist/esm/buildSyntheticChangelogEntry.js +2 -2
- package/dist/esm/bumpVersion.js +8 -8
- package/dist/esm/changelogArchive.d.ts +1 -1
- package/dist/esm/changelogArchive.js +2 -2
- package/dist/esm/changelogJsonFile.js +5 -5
- package/dist/esm/changelogJsonUtils.js +3 -3
- package/dist/esm/changelogOverrides.d.ts +5 -5
- package/dist/esm/changelogOverrides.js +23 -23
- package/dist/esm/classifyChangelogCommit.js +4 -4
- package/dist/esm/commitCommand.js +3 -3
- package/dist/esm/compareVersions.js +13 -13
- package/dist/esm/configSchema.d.ts +3 -2
- package/dist/esm/configSchema.js +2 -1
- package/dist/esm/createGithubRelease.d.ts +4 -4
- package/dist/esm/createGithubRelease.js +10 -10
- package/dist/esm/createGithubReleaseCommand.js +9 -9
- package/dist/esm/createTags.d.ts +2 -2
- package/dist/esm/createTags.js +8 -8
- package/dist/esm/decideDirectReleases.d.ts +5 -5
- package/dist/esm/decideDirectReleases.js +25 -25
- package/dist/esm/decideRelease.d.ts +1 -1
- package/dist/esm/decideRelease.js +2 -2
- package/dist/esm/deriveDirectoryConfig.d.ts +1 -1
- package/dist/esm/deriveDirectoryConfig.js +18 -18
- package/dist/esm/deriveWorkspaceConfig.js +4 -4
- package/dist/esm/detectPackageManager.d.ts +2 -0
- package/dist/esm/detectPackageManager.js +8 -5
- package/dist/esm/detectUndeclaredTagPrefixes.js +6 -6
- package/dist/esm/enumerateReleaseWindows.d.ts +1 -1
- package/dist/esm/enumerateReleaseWindows.js +7 -7
- package/dist/esm/findMissingManifestPaths.d.ts +14 -0
- package/dist/esm/findMissingManifestPaths.js +90 -0
- package/dist/esm/findTransitiveDependents.js +5 -5
- package/dist/esm/findUnroutedEntryScopes.js +11 -11
- package/dist/esm/format.d.ts +1 -1
- package/dist/esm/format.js +1 -1
- package/dist/esm/formatPrepareFiles.d.ts +3 -3
- package/dist/esm/formatPrepareFiles.js +10 -10
- package/dist/esm/formatPrepareWarnings.js +6 -6
- package/dist/esm/hasPrettierConfig.js +2 -2
- package/dist/esm/init/checks.js +5 -3
- package/dist/esm/init/detectRepoType.js +3 -3
- package/dist/esm/init/initCommand.d.ts +4 -4
- package/dist/esm/init/initCommand.js +7 -7
- package/dist/esm/init/parseJsonRecord.d.ts +1 -1
- package/dist/esm/init/parseJsonRecord.js +4 -4
- package/dist/esm/init/scaffold.d.ts +4 -4
- package/dist/esm/init/scaffold.js +15 -15
- package/dist/esm/init/templates.d.ts +10 -10
- package/dist/esm/init/templates.js +5 -5
- package/dist/esm/init/updateTemplatesCommand.d.ts +2 -2
- package/dist/esm/init/updateTemplatesCommand.js +14 -12
- package/dist/esm/injectReleaseNotesIntoReadme.d.ts +1 -1
- package/dist/esm/injectReleaseNotesIntoReadme.js +6 -6
- package/dist/esm/injectSection.js +8 -8
- package/dist/esm/loadChangelogOverrides.d.ts +1 -1
- package/dist/esm/loadChangelogOverrides.js +9 -9
- package/dist/esm/loadConfig.d.ts +3 -3
- package/dist/esm/loadConfig.js +10 -10
- package/dist/esm/loadValidatedConfig.js +4 -4
- package/dist/esm/mergeConfig.d.ts +1 -1
- package/dist/esm/mergeConfig.js +27 -27
- package/dist/esm/monorepoConfigAssertions.js +15 -15
- package/dist/esm/npmRegistry.js +4 -4
- package/dist/esm/packFromCleanCheckout.d.ts +11 -0
- package/dist/esm/packFromCleanCheckout.js +81 -0
- package/dist/esm/packsPath.js +3 -3
- package/dist/esm/parseChangeRecordBlock.js +33 -33
- package/dist/esm/parseCommitMessage.js +5 -5
- package/dist/esm/planChangelogFiles.js +8 -8
- package/dist/esm/planReleaseNotesPreviews.js +10 -10
- package/dist/esm/planVersionBump.js +24 -16
- package/dist/esm/prepareArgs.d.ts +9 -9
- package/dist/esm/prepareArgs.js +11 -11
- package/dist/esm/prepareCommand.js +27 -24
- package/dist/esm/prepareComposite.d.ts +4 -4
- package/dist/esm/prepareComposite.js +16 -16
- package/dist/esm/{releasePrepareTargets.d.ts → prepareReleaseTargets.d.ts} +9 -9
- package/dist/esm/{releasePrepareTargets.js → prepareReleaseTargets.js} +40 -40
- package/dist/esm/prepareRunContext.d.ts +1 -1
- package/dist/esm/prepareRunContext.js +5 -5
- package/dist/esm/prepareTarget.d.ts +3 -3
- package/dist/esm/prepareTarget.js +41 -39
- package/dist/esm/previewTagPrefixes.js +19 -19
- package/dist/esm/propagateBumps.js +8 -8
- package/dist/esm/publish.d.ts +3 -3
- package/dist/esm/publish.js +6 -11
- package/dist/esm/publishCommand.d.ts +3 -3
- package/dist/esm/publishCommand.js +28 -20
- package/dist/esm/pushCommand.js +8 -8
- package/dist/esm/pushRelease.d.ts +1 -1
- package/dist/esm/pushRelease.js +2 -2
- package/dist/esm/readChangelogSections.d.ts +1 -1
- package/dist/esm/readChangelogSections.js +16 -16
- package/dist/esm/releasePlan.js +25 -25
- package/dist/esm/renderChangelogMarkdown.d.ts +4 -4
- package/dist/esm/renderChangelogMarkdown.js +18 -15
- package/dist/esm/renderReleaseNotes.d.ts +1 -1
- package/dist/esm/renderReleaseNotes.js +16 -16
- package/dist/esm/reportPrepare.js +34 -34
- package/dist/esm/resolveCommandTags.js +9 -9
- package/dist/esm/resolveReleaseDeclaration.d.ts +2 -2
- package/dist/esm/resolveReleaseDeclaration.js +22 -22
- package/dist/esm/resolveReleaseTags.js +17 -15
- package/dist/esm/resolveReleaseTargets.js +2 -2
- package/dist/esm/showTagPrefixesCommand.js +4 -4
- package/dist/esm/sync-labels/generateCommand.d.ts +3 -3
- package/dist/esm/sync-labels/generateCommand.js +10 -10
- package/dist/esm/sync-labels/initCommand.d.ts +3 -3
- package/dist/esm/sync-labels/initCommand.js +15 -15
- package/dist/esm/sync-labels/presets.js +3 -3
- package/dist/esm/sync-labels/resolveLabels.js +13 -13
- package/dist/esm/sync-labels/templates.d.ts +2 -2
- package/dist/esm/sync-labels/templates.js +1 -1
- package/dist/esm/tagCommand.js +4 -4
- package/dist/esm/topologicalSort.d.ts +2 -2
- package/dist/esm/topologicalSort.js +7 -7
- package/dist/esm/types.d.ts +3 -3
- package/dist/esm/validateAllChangelogOverrides.js +9 -9
- package/dist/esm/validateConfig.d.ts +1 -1
- package/dist/esm/validateConfig.js +60 -50
- package/dist/esm/validateOverridesCommand.js +2 -2
- package/docs/changelogs.md +5 -5
- package/docs/configuration.md +16 -15
- package/docs/publishing.md +4 -0
- package/docs/releasing.md +6 -0
- package/docs/work-types.md +1 -1
- package/package.json +2 -2
- package/presets/labels/common.yaml +3 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync } from 'node:fs';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { resolveExecutable } from "./detectPackageManager.js";
|
|
6
|
+
import { isRecord } from "./typeGuards.js";
|
|
7
|
+
export function packFromCleanCheckout(workspacePaths, packageManager) {
|
|
8
|
+
const repoRoot = runStep('find the repository root', 'git', ['rev-parse', '--show-toplevel'], process.cwd()).trim();
|
|
9
|
+
const headSha = runStep('resolve HEAD', 'git', ['rev-parse', 'HEAD'], repoRoot).trim();
|
|
10
|
+
const scratchDir = mkdtempSync(join(tmpdir(), 'release-kit-pack-'));
|
|
11
|
+
try {
|
|
12
|
+
const cloneDir = join(scratchDir, 'checkout');
|
|
13
|
+
runStep('clone HEAD', 'git', ['clone', '--quiet', '--shared', '--no-checkout', repoRoot, cloneDir], scratchDir);
|
|
14
|
+
runStep('check out HEAD', 'git', ['checkout', '--quiet', '--detach', headSha], cloneDir);
|
|
15
|
+
const executable = resolveExecutable(packageManager);
|
|
16
|
+
const commands = PACKAGE_MANAGER_COMMANDS[packageManager];
|
|
17
|
+
runStep('install dependencies', executable, commands.installArgs, cloneDir);
|
|
18
|
+
const filesByWorkspace = new Map();
|
|
19
|
+
for (const [index, workspacePath] of workspacePaths.entries()) {
|
|
20
|
+
const packageDir = join(cloneDir, workspacePath);
|
|
21
|
+
if (declaresPrepublishOnly(packageDir)) {
|
|
22
|
+
runStep(`run prepublishOnly in ${workspacePath}`, executable, ['run', 'prepublishOnly'], packageDir);
|
|
23
|
+
}
|
|
24
|
+
const packDir = join(scratchDir, 'packs', String(index));
|
|
25
|
+
mkdirSync(packDir, { recursive: true });
|
|
26
|
+
runStep(`pack ${workspacePath}`, executable, commands.buildPackArgs(packDir), packageDir);
|
|
27
|
+
filesByWorkspace.set(workspacePath, listTarballEntries(findTarball(packDir, workspacePath)));
|
|
28
|
+
}
|
|
29
|
+
return filesByWorkspace;
|
|
30
|
+
}
|
|
31
|
+
finally {
|
|
32
|
+
rmSync(scratchDir, { force: true, recursive: true });
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const PACKAGE_MANAGER_COMMANDS = {
|
|
36
|
+
npm: {
|
|
37
|
+
installArgs: ['ci', '--prefer-offline'],
|
|
38
|
+
buildPackArgs: (packDir) => ['pack', '--pack-destination', packDir],
|
|
39
|
+
},
|
|
40
|
+
pnpm: {
|
|
41
|
+
installArgs: ['install', '--frozen-lockfile', '--prefer-offline'],
|
|
42
|
+
buildPackArgs: (packDir) => ['pack', '--pack-destination', packDir],
|
|
43
|
+
},
|
|
44
|
+
yarn: {
|
|
45
|
+
installArgs: ['install', '--frozen-lockfile'],
|
|
46
|
+
buildPackArgs: (packDir) => ['pack', '--filename', join(packDir, 'package.tgz')],
|
|
47
|
+
},
|
|
48
|
+
'yarn-berry': {
|
|
49
|
+
installArgs: ['install', '--immutable'],
|
|
50
|
+
buildPackArgs: (packDir) => ['pack', '--out', join(packDir, 'package.tgz')],
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
function declaresPrepublishOnly(packageDir) {
|
|
54
|
+
const manifest = JSON.parse(readFileSync(join(packageDir, 'package.json'), 'utf8'));
|
|
55
|
+
const scripts = isRecord(manifest) ? manifest['scripts'] : undefined;
|
|
56
|
+
return isRecord(scripts) && typeof scripts['prepublishOnly'] === 'string';
|
|
57
|
+
}
|
|
58
|
+
function findTarball(packDir, workspacePath) {
|
|
59
|
+
const tarballs = readdirSync(packDir).filter((name) => name.endsWith('.tgz'));
|
|
60
|
+
const [tarball] = tarballs;
|
|
61
|
+
if (tarballs.length !== 1 || tarball === undefined) {
|
|
62
|
+
throw new Error(`Could not pack ${workspacePath}: expected one tarball, found ${tarballs.length}.`);
|
|
63
|
+
}
|
|
64
|
+
return join(packDir, tarball);
|
|
65
|
+
}
|
|
66
|
+
function listTarballEntries(tarballPath) {
|
|
67
|
+
const listing = runStep('list the tarball', 'tar', ['--list', '--gzip', '--file', tarballPath], process.cwd());
|
|
68
|
+
return listing
|
|
69
|
+
.split('\n')
|
|
70
|
+
.filter((entry) => entry !== '' && !entry.endsWith('/'))
|
|
71
|
+
.map((entry) => entry.replace(/^package\//, ''));
|
|
72
|
+
}
|
|
73
|
+
function runStep(step, command, args, cwd) {
|
|
74
|
+
const result = spawnSync(command, args, { cwd, encoding: 'utf8', maxBuffer: 2 ** 26 });
|
|
75
|
+
if (result.error === undefined && result.status === 0) {
|
|
76
|
+
return result.stdout;
|
|
77
|
+
}
|
|
78
|
+
const outcome = result.error === undefined ? `exited with status ${result.status}` : result.error.message;
|
|
79
|
+
const output = [result.stdout, result.stderr].filter((text) => text !== '').join('\n');
|
|
80
|
+
throw new Error(`Could not ${step}: \`${[command, ...args].join(' ')}\` ${outcome}.${output === '' ? '' : `\n${output}`}`);
|
|
81
|
+
}
|
package/dist/esm/packsPath.js
CHANGED
|
@@ -11,10 +11,10 @@ const GLOB_TOKEN_PATTERN = /\*\*\/|\*\*|\*|\?|[^*?]+/g;
|
|
|
11
11
|
function compileEntry(entry) {
|
|
12
12
|
if (entry.startsWith('!'))
|
|
13
13
|
return undefined;
|
|
14
|
-
const
|
|
15
|
-
if (
|
|
14
|
+
const normalizedEntry = entry.replace(/^\.?\//, '').replace(/\/+$/, '');
|
|
15
|
+
if (normalizedEntry === '')
|
|
16
16
|
return undefined;
|
|
17
|
-
return new RegExp(`^${translateGlob(
|
|
17
|
+
return new RegExp(`^${translateGlob(normalizedEntry)}$`);
|
|
18
18
|
}
|
|
19
19
|
function isStringArray(value) {
|
|
20
20
|
return Array.isArray(value) && value.every((entry) => typeof entry === 'string');
|
|
@@ -6,10 +6,10 @@ export function parseChangeRecordBlock(message) {
|
|
|
6
6
|
if (fence === undefined) {
|
|
7
7
|
return { kind: 'absent' };
|
|
8
8
|
}
|
|
9
|
-
if (fence.
|
|
9
|
+
if (fence.closeIndex === undefined) {
|
|
10
10
|
return { kind: 'malformed', reason: 'the block opens but never closes' };
|
|
11
11
|
}
|
|
12
|
-
const document = parseDocument(lines.slice(fence.
|
|
12
|
+
const document = parseDocument(lines.slice(fence.openIndex + 1, fence.closeIndex).join('\n'));
|
|
13
13
|
const [error] = document.errors;
|
|
14
14
|
if (error !== undefined) {
|
|
15
15
|
const detail = (error.message.split('\n', 1)[0] ?? '').replace(/:$/, '');
|
|
@@ -21,60 +21,60 @@ export function stripChangeRecordBlocks(message) {
|
|
|
21
21
|
const lines = message.split(/\r?\n/);
|
|
22
22
|
const fences = findFences(lines);
|
|
23
23
|
return lines
|
|
24
|
-
.filter((_line, index) => fences.every((fence) => index < fence.
|
|
24
|
+
.filter((_line, index) => fences.every((fence) => index < fence.openIndex || index > (fence.closeIndex ?? lines.length)))
|
|
25
25
|
.join('\n');
|
|
26
26
|
}
|
|
27
27
|
const FENCE = '```';
|
|
28
28
|
function findFences(lines) {
|
|
29
29
|
const fences = [];
|
|
30
|
-
let
|
|
30
|
+
let openIndex;
|
|
31
31
|
for (const [index, line] of lines.entries()) {
|
|
32
|
-
if (
|
|
32
|
+
if (openIndex === undefined) {
|
|
33
33
|
if (line.trim() === `${FENCE}${INFO_STRING}`) {
|
|
34
|
-
|
|
34
|
+
openIndex = index;
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
else if (line.trim() === FENCE) {
|
|
38
|
-
fences.push({
|
|
39
|
-
|
|
38
|
+
fences.push({ closeIndex: index, openIndex });
|
|
39
|
+
openIndex = undefined;
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
|
-
if (
|
|
43
|
-
fences.push({
|
|
42
|
+
if (openIndex !== undefined) {
|
|
43
|
+
fences.push({ openIndex });
|
|
44
44
|
}
|
|
45
45
|
return fences;
|
|
46
46
|
}
|
|
47
47
|
const INFO_STRING = 'change-record';
|
|
48
48
|
function readEntry(item, index) {
|
|
49
|
-
const
|
|
49
|
+
const entryPath = `entries[${index}]`;
|
|
50
50
|
if (!isRecord(item)) {
|
|
51
|
-
return { reason: `\`${
|
|
51
|
+
return { reason: `\`${entryPath}\` is not a mapping` };
|
|
52
52
|
}
|
|
53
53
|
const { breaking, migration, scopes } = item;
|
|
54
54
|
if (breaking !== undefined && breaking !== null && typeof breaking !== 'boolean') {
|
|
55
|
-
return { reason: `\`${
|
|
55
|
+
return { reason: `\`${entryPath}.breaking\` is not a boolean` };
|
|
56
56
|
}
|
|
57
57
|
if (migration !== undefined && migration !== null && typeof migration !== 'string') {
|
|
58
|
-
return { reason: `\`${
|
|
58
|
+
return { reason: `\`${entryPath}.migration\` is not a string` };
|
|
59
59
|
}
|
|
60
60
|
if (scopes !== undefined && scopes !== null && !Array.isArray(scopes)) {
|
|
61
|
-
return { reason: `\`${
|
|
61
|
+
return { reason: `\`${entryPath}.scopes\` is not a list` };
|
|
62
62
|
}
|
|
63
63
|
const declaredScopes = scopes ?? [];
|
|
64
64
|
const readScopes = [];
|
|
65
65
|
for (const [position, scope] of declaredScopes.entries()) {
|
|
66
66
|
if (typeof scope !== 'string') {
|
|
67
|
-
return { reason: `\`${
|
|
67
|
+
return { reason: `\`${entryPath}.scopes[${position}]\` is not a string` };
|
|
68
68
|
}
|
|
69
69
|
readScopes.push(scope.trim());
|
|
70
70
|
}
|
|
71
|
-
const
|
|
72
|
-
if ('reason' in
|
|
73
|
-
return
|
|
71
|
+
const typeResult = readRequiredString(item, entryPath, 'type');
|
|
72
|
+
if ('reason' in typeResult) {
|
|
73
|
+
return typeResult;
|
|
74
74
|
}
|
|
75
|
-
const
|
|
76
|
-
if ('reason' in
|
|
77
|
-
return
|
|
75
|
+
const textResult = readRequiredString(item, entryPath, 'text');
|
|
76
|
+
if ('reason' in textResult) {
|
|
77
|
+
return textResult;
|
|
78
78
|
}
|
|
79
79
|
const trimmedMigration = migration?.trim();
|
|
80
80
|
return {
|
|
@@ -82,8 +82,8 @@ function readEntry(item, index) {
|
|
|
82
82
|
breaking: breaking === true,
|
|
83
83
|
...(trimmedMigration !== undefined && trimmedMigration !== '' && { migration: trimmedMigration }),
|
|
84
84
|
scopes: readScopes,
|
|
85
|
-
text:
|
|
86
|
-
type:
|
|
85
|
+
text: textResult.value,
|
|
86
|
+
type: typeResult.value,
|
|
87
87
|
},
|
|
88
88
|
};
|
|
89
89
|
}
|
|
@@ -106,11 +106,11 @@ function readPayload(payload) {
|
|
|
106
106
|
return { kind: 'malformed', reason: '`entries` is not a list' };
|
|
107
107
|
}
|
|
108
108
|
for (const [index, item] of entries.entries()) {
|
|
109
|
-
const
|
|
110
|
-
if ('reason' in
|
|
111
|
-
return { kind: 'malformed', reason:
|
|
109
|
+
const readResult = readEntry(item, index);
|
|
110
|
+
if ('reason' in readResult) {
|
|
111
|
+
return { kind: 'malformed', reason: readResult.reason };
|
|
112
112
|
}
|
|
113
|
-
readEntries.push(
|
|
113
|
+
readEntries.push(readResult.entry);
|
|
114
114
|
}
|
|
115
115
|
}
|
|
116
116
|
const trimmedTicketRef = ticketRef?.trim();
|
|
@@ -121,14 +121,14 @@ function readPayload(payload) {
|
|
|
121
121
|
...(trimmedTicketRef !== undefined && trimmedTicketRef !== '' && { ticketRef: trimmedTicketRef }),
|
|
122
122
|
};
|
|
123
123
|
}
|
|
124
|
-
function readRequiredString(item,
|
|
124
|
+
function readRequiredString(item, entryPath, name) {
|
|
125
125
|
const value = item[name];
|
|
126
126
|
if (value === undefined || value === null) {
|
|
127
|
-
return { reason: `\`${
|
|
127
|
+
return { reason: `\`${entryPath}.${name}\` is missing` };
|
|
128
128
|
}
|
|
129
129
|
if (typeof value !== 'string') {
|
|
130
|
-
return { reason: `\`${
|
|
130
|
+
return { reason: `\`${entryPath}.${name}\` is not a string` };
|
|
131
131
|
}
|
|
132
|
-
const
|
|
133
|
-
return
|
|
132
|
+
const trimmedValue = value.trim();
|
|
133
|
+
return trimmedValue === '' ? { reason: `\`${entryPath}.${name}\` is blank` } : { value: trimmedValue };
|
|
134
134
|
}
|
|
@@ -30,7 +30,7 @@ export function parseCommitMessage(message, hash, workTypes, scopeAliases, optio
|
|
|
30
30
|
};
|
|
31
31
|
}
|
|
32
32
|
export function parseSubject(subject, workTypes) {
|
|
33
|
-
const taxonomy =
|
|
33
|
+
const taxonomy = adaptToTaxonomy(workTypes);
|
|
34
34
|
for (const nodes of SUBJECT_TEMPLATES) {
|
|
35
35
|
const record = parse(nodes, subject, taxonomy);
|
|
36
36
|
if (record !== undefined) {
|
|
@@ -54,14 +54,14 @@ export function evaluateBreakingPolicy(inputs) {
|
|
|
54
54
|
return hasPrefixBreaking || hasFooterBreaking;
|
|
55
55
|
}
|
|
56
56
|
export function resolveType(rawType, workTypes) {
|
|
57
|
-
const
|
|
57
|
+
const loweredType = rawType.toLowerCase();
|
|
58
58
|
for (const [key, config] of Object.entries(workTypes)) {
|
|
59
|
-
if (key ===
|
|
59
|
+
if (key === loweredType) {
|
|
60
60
|
return key;
|
|
61
61
|
}
|
|
62
62
|
if (config.aliases !== undefined) {
|
|
63
63
|
for (const alias of config.aliases) {
|
|
64
|
-
if (alias ===
|
|
64
|
+
if (alias === loweredType) {
|
|
65
65
|
return key;
|
|
66
66
|
}
|
|
67
67
|
}
|
|
@@ -69,7 +69,7 @@ export function resolveType(rawType, workTypes) {
|
|
|
69
69
|
}
|
|
70
70
|
return undefined;
|
|
71
71
|
}
|
|
72
|
-
function
|
|
72
|
+
function adaptToTaxonomy(workTypes) {
|
|
73
73
|
return {
|
|
74
74
|
tiers: [],
|
|
75
75
|
types: Object.entries(workTypes).map(([key, config]) => ({ aliases: config.aliases ?? [], key, tier: '' })),
|
|
@@ -13,16 +13,16 @@ export function planChangelogFiles(changelogDir, newEntries, settings, sectionOr
|
|
|
13
13
|
changelogJsonFiles.push(jsonPath);
|
|
14
14
|
}
|
|
15
15
|
const changelogFile = joinPath(changelogDir, 'CHANGELOG.md');
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
const
|
|
16
|
+
const preservedPlan = planPreservedSections(changelogDir, entries);
|
|
17
|
+
const keptVersionCount = settings.changelogMarkdown?.keptVersionCount;
|
|
18
|
+
const renderedFiles = renderChangelogFiles(entries, {
|
|
19
19
|
sectionOrder,
|
|
20
|
-
preservedSections:
|
|
21
|
-
...(
|
|
20
|
+
preservedSections: preservedPlan.sections,
|
|
21
|
+
...(keptVersionCount !== undefined && { keptVersionCount }),
|
|
22
22
|
});
|
|
23
|
-
writes.push({ path: changelogFile, content:
|
|
23
|
+
writes.push({ path: changelogFile, content: renderedFiles.main });
|
|
24
24
|
const archiveFiles = [];
|
|
25
|
-
for (const { major, content } of
|
|
25
|
+
for (const { major, content } of renderedFiles.archives) {
|
|
26
26
|
const path = joinPath(changelogDir, describeArchivePath(major));
|
|
27
27
|
writes.push({ path, content });
|
|
28
28
|
archiveFiles.push(path);
|
|
@@ -34,7 +34,7 @@ export function planChangelogFiles(changelogDir, newEntries, settings, sectionOr
|
|
|
34
34
|
changelogFiles: [changelogFile, ...archiveFiles],
|
|
35
35
|
changelogJsonFiles,
|
|
36
36
|
entries,
|
|
37
|
-
preservation:
|
|
37
|
+
preservation: preservedPlan.preservation,
|
|
38
38
|
removals,
|
|
39
39
|
writes,
|
|
40
40
|
};
|
|
@@ -13,21 +13,21 @@ export function planReleaseNotesPreviews(options) {
|
|
|
13
13
|
warnings.push(describePreviewSkip(workspacePath, result.reason, result.version));
|
|
14
14
|
return { writes: [], warnings };
|
|
15
15
|
}
|
|
16
|
-
const {
|
|
17
|
-
if (!readme.
|
|
16
|
+
const { renderedReadme } = result;
|
|
17
|
+
if (!readme.doesExist && !readme.unreadable) {
|
|
18
18
|
warnings.push(`${readmePath} not found; skipping injected-README preview but still writing standalone release notes`);
|
|
19
19
|
}
|
|
20
20
|
const version = extractVersion(tag);
|
|
21
21
|
const docsDir = path.join(workspacePath, 'docs');
|
|
22
22
|
const writes = [];
|
|
23
|
-
if (readme.
|
|
24
|
-
writes.push({ path: path.join(docsDir, `README.v${version}.md`), content:
|
|
23
|
+
if (readme.doesExist) {
|
|
24
|
+
writes.push({ path: path.join(docsDir, `README.v${version}.md`), content: renderedReadme.injectedReadme });
|
|
25
25
|
}
|
|
26
26
|
writes.push({
|
|
27
27
|
path: path.join(docsDir, `RELEASE_NOTES.v${version}.md`),
|
|
28
|
-
content:
|
|
29
|
-
?
|
|
30
|
-
: `${
|
|
28
|
+
content: renderedReadme.releaseNotesMarkdown.endsWith('\n')
|
|
29
|
+
? renderedReadme.releaseNotesMarkdown
|
|
30
|
+
: `${renderedReadme.releaseNotesMarkdown}\n`,
|
|
31
31
|
});
|
|
32
32
|
return { writes, warnings };
|
|
33
33
|
}
|
|
@@ -39,14 +39,14 @@ function describePreviewSkip(workspacePath, reason, version) {
|
|
|
39
39
|
}
|
|
40
40
|
function readWorkspaceReadme(readmePath, warnings) {
|
|
41
41
|
if (!existsSync(readmePath)) {
|
|
42
|
-
return { content: '',
|
|
42
|
+
return { content: '', doesExist: false, unreadable: false };
|
|
43
43
|
}
|
|
44
44
|
try {
|
|
45
|
-
return { content: readFileSync(readmePath, 'utf8'),
|
|
45
|
+
return { content: readFileSync(readmePath, 'utf8'), doesExist: true, unreadable: false };
|
|
46
46
|
}
|
|
47
47
|
catch (error) {
|
|
48
48
|
const message = describeError(error);
|
|
49
49
|
warnings.push(`failed to read ${readmePath}: ${message}; skipping injected-README preview`);
|
|
50
|
-
return { content: '',
|
|
50
|
+
return { content: '', doesExist: false, unreadable: true };
|
|
51
51
|
}
|
|
52
52
|
}
|
|
@@ -6,34 +6,42 @@ function isPackageJson(value) {
|
|
|
6
6
|
return typeof value === 'object' && value !== null && 'version' in value && typeof value.version === 'string';
|
|
7
7
|
}
|
|
8
8
|
export function planVersionBump(packageFiles, releaseType) {
|
|
9
|
-
const { firstFile,
|
|
10
|
-
const currentVersion =
|
|
9
|
+
const { firstFile, firstPackageJson } = readPrimaryPackage(packageFiles);
|
|
10
|
+
const currentVersion = firstPackageJson.version;
|
|
11
11
|
const newVersion = bumpVersion(currentVersion, releaseType);
|
|
12
|
-
return {
|
|
12
|
+
return {
|
|
13
|
+
currentVersion,
|
|
14
|
+
newVersion,
|
|
15
|
+
writes: renderVersionWrites(packageFiles, firstFile, firstPackageJson, newVersion),
|
|
16
|
+
};
|
|
13
17
|
}
|
|
14
18
|
export function planVersionSet(packageFiles, newVersion) {
|
|
15
|
-
const { firstFile,
|
|
16
|
-
const currentVersion =
|
|
19
|
+
const { firstFile, firstPackageJson } = readPrimaryPackage(packageFiles);
|
|
20
|
+
const currentVersion = firstPackageJson.version;
|
|
17
21
|
if (!isForwardVersion(currentVersion, newVersion)) {
|
|
18
22
|
throw new Error(`--set-version ${newVersion} is not greater than current version ${currentVersion}`);
|
|
19
23
|
}
|
|
20
|
-
return {
|
|
24
|
+
return {
|
|
25
|
+
currentVersion,
|
|
26
|
+
newVersion,
|
|
27
|
+
writes: renderVersionWrites(packageFiles, firstFile, firstPackageJson, newVersion),
|
|
28
|
+
};
|
|
21
29
|
}
|
|
22
30
|
export function readCurrentVersion(packageFiles) {
|
|
23
|
-
return readPrimaryPackage(packageFiles).
|
|
31
|
+
return readPrimaryPackage(packageFiles).firstPackageJson.version;
|
|
24
32
|
}
|
|
25
33
|
function readPrimaryPackage(packageFiles) {
|
|
26
34
|
const firstFile = packageFiles[0];
|
|
27
35
|
if (firstFile === undefined) {
|
|
28
36
|
throw new Error('No package files specified');
|
|
29
37
|
}
|
|
30
|
-
return { firstFile,
|
|
38
|
+
return { firstFile, firstPackageJson: readPackageJson(firstFile) };
|
|
31
39
|
}
|
|
32
|
-
function renderVersionWrites(packageFiles, firstFile,
|
|
40
|
+
function renderVersionWrites(packageFiles, firstFile, firstPackageJson, newVersion) {
|
|
33
41
|
return packageFiles.map((filePath) => {
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
return { path: filePath, content: JSON.stringify(
|
|
42
|
+
const packageJson = filePath === firstFile ? firstPackageJson : readPackageJson(filePath);
|
|
43
|
+
packageJson.version = newVersion;
|
|
44
|
+
return { path: filePath, content: JSON.stringify(packageJson, null, 2) + '\n' };
|
|
37
45
|
});
|
|
38
46
|
}
|
|
39
47
|
function readPackageJson(filePath) {
|
|
@@ -44,15 +52,15 @@ function readPackageJson(filePath) {
|
|
|
44
52
|
catch (error) {
|
|
45
53
|
throw chainError(`Failed to read ${filePath}`, error);
|
|
46
54
|
}
|
|
47
|
-
let
|
|
55
|
+
let parsedJson;
|
|
48
56
|
try {
|
|
49
|
-
|
|
57
|
+
parsedJson = JSON.parse(content);
|
|
50
58
|
}
|
|
51
59
|
catch (error) {
|
|
52
60
|
throw chainError(`Failed to parse JSON in ${filePath}`, error);
|
|
53
61
|
}
|
|
54
|
-
if (!isPackageJson(
|
|
62
|
+
if (!isPackageJson(parsedJson)) {
|
|
55
63
|
throw new Error(`No valid 'version' field found in ${filePath}`);
|
|
56
64
|
}
|
|
57
|
-
return
|
|
65
|
+
return parsedJson;
|
|
58
66
|
}
|
|
@@ -2,15 +2,15 @@ import type { ReleaseType } from './types.js';
|
|
|
2
2
|
/** Canonical `N.N.N` semver pattern for validating `--set-version` input. */
|
|
3
3
|
export declare const CANONICAL_SEMVER_PATTERN: RegExp;
|
|
4
4
|
export declare const prepareFlagSchema: {
|
|
5
|
-
|
|
5
|
+
isDryRun: {
|
|
6
6
|
long: string;
|
|
7
7
|
type: "boolean";
|
|
8
8
|
};
|
|
9
|
-
|
|
9
|
+
shouldForce: {
|
|
10
10
|
long: string;
|
|
11
11
|
type: "boolean";
|
|
12
12
|
};
|
|
13
|
-
|
|
13
|
+
shouldSkipGitChecks: {
|
|
14
14
|
long: string;
|
|
15
15
|
type: "boolean";
|
|
16
16
|
short: string;
|
|
@@ -27,7 +27,7 @@ export declare const prepareFlagSchema: {
|
|
|
27
27
|
long: string;
|
|
28
28
|
type: "string";
|
|
29
29
|
};
|
|
30
|
-
|
|
30
|
+
shouldIncludeReleaseNotes: {
|
|
31
31
|
long: string;
|
|
32
32
|
type: "boolean";
|
|
33
33
|
};
|
|
@@ -39,14 +39,14 @@ export declare const prepareFlagSchema: {
|
|
|
39
39
|
/** Parses CLI arguments into structured options. Prints a usage error and exits on invalid input. */
|
|
40
40
|
export declare function parseArgs(argv: string[]): {
|
|
41
41
|
configPath: string | undefined;
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
isDryRun: boolean;
|
|
43
|
+
shouldForce: boolean;
|
|
44
|
+
shouldSkipGitChecks: boolean;
|
|
45
45
|
bumpOverride: ReleaseType | undefined;
|
|
46
|
-
|
|
46
|
+
onlyNames: string[] | undefined;
|
|
47
47
|
/** The raw `--set-version` value, whose accepted form depends on the mode. */
|
|
48
48
|
setVersion: string | undefined;
|
|
49
|
-
|
|
49
|
+
shouldIncludeReleaseNotes: boolean;
|
|
50
50
|
};
|
|
51
51
|
/**
|
|
52
52
|
* Parses a monorepo `--set-version` value, a comma-separated list of `<target>@N.N.N` entries, into versions keyed by
|
package/dist/esm/prepareArgs.js
CHANGED
|
@@ -4,9 +4,9 @@ const VALID_BUMP_TYPES = ['major', 'minor', 'patch'];
|
|
|
4
4
|
export const CANONICAL_SEMVER_PATTERN = /^\d+\.\d+\.\d+$/;
|
|
5
5
|
export const prepareFlagSchema = {
|
|
6
6
|
...configFlagSchema,
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
isDryRun: { long: '--dry-run', type: 'boolean' },
|
|
8
|
+
shouldForce: { long: '--force', type: 'boolean' },
|
|
9
|
+
shouldSkipGitChecks: {
|
|
10
10
|
long: '--no-git-checks',
|
|
11
11
|
type: 'boolean',
|
|
12
12
|
short: '-n',
|
|
@@ -14,7 +14,7 @@ export const prepareFlagSchema = {
|
|
|
14
14
|
bump: { long: '--bump', type: 'string' },
|
|
15
15
|
setVersion: { long: '--set-version', type: 'string' },
|
|
16
16
|
only: { long: '--only', type: 'string' },
|
|
17
|
-
|
|
17
|
+
shouldIncludeReleaseNotes: { long: '--with-release-notes', type: 'boolean' },
|
|
18
18
|
};
|
|
19
19
|
export function parseArgs(argv) {
|
|
20
20
|
const { flags } = parseArgsOrExit(argv, prepareFlagSchema);
|
|
@@ -26,19 +26,19 @@ export function parseArgs(argv) {
|
|
|
26
26
|
}
|
|
27
27
|
bumpOverride = flags.bump;
|
|
28
28
|
}
|
|
29
|
-
let
|
|
29
|
+
let onlyNames;
|
|
30
30
|
if (flags.only !== undefined) {
|
|
31
|
-
|
|
31
|
+
onlyNames = flags.only.split(',');
|
|
32
32
|
}
|
|
33
33
|
return {
|
|
34
34
|
configPath: flags.config,
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
isDryRun: flags.isDryRun,
|
|
36
|
+
shouldForce: flags.shouldForce,
|
|
37
|
+
shouldSkipGitChecks: flags.shouldSkipGitChecks,
|
|
38
38
|
bumpOverride,
|
|
39
|
-
|
|
39
|
+
onlyNames,
|
|
40
40
|
setVersion: flags.setVersion,
|
|
41
|
-
|
|
41
|
+
shouldIncludeReleaseNotes: flags.shouldIncludeReleaseNotes,
|
|
42
42
|
};
|
|
43
43
|
}
|
|
44
44
|
export function parseSetVersions(value, knownNames) {
|
|
@@ -3,28 +3,29 @@ import { formatGlyphLine, reportError } from '@williamthorsen/nmr-core';
|
|
|
3
3
|
import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
4
4
|
import { assertCleanWorkingTree } from "./assertCleanWorkingTree.js";
|
|
5
5
|
import { assertReleasesOnNpm } from "./assertReleasesOnNpm.js";
|
|
6
|
+
import { assertTarballsComplete } from "./assertTarballsComplete.js";
|
|
6
7
|
import { describeEmptyWorkspace, discoverWorkspaces } from "./discoverWorkspaces.js";
|
|
7
8
|
import { dim } from "./format.js";
|
|
8
9
|
import { RELEASE_GLYPHS } from "./glyphs.js";
|
|
9
10
|
import { loadValidatedConfig, reportConfigProblem, reportConfigWarnings } from "./loadValidatedConfig.js";
|
|
10
11
|
import { CANONICAL_SEMVER_PATTERN, parseArgs, parseSetVersions } from "./prepareArgs.js";
|
|
12
|
+
import { prepareReleaseTargets } from "./prepareReleaseTargets.js";
|
|
11
13
|
import { RELEASE_SUMMARY_FILE, RELEASE_TAGS_FILE } from "./releaseFiles.js";
|
|
12
14
|
import { applyReleasePlan } from "./releasePlan.js";
|
|
13
|
-
import { releasePrepareTargets } from "./releasePrepareTargets.js";
|
|
14
15
|
import { reportPrepare } from "./reportPrepare.js";
|
|
15
16
|
import { resolveConfigFlag } from "./resolveConfigFlag.js";
|
|
16
17
|
import { isSinglePackageTargets, PROJECT_TARGET_ID, resolveReleaseTargets, SINGLE_PACKAGE_TARGET_ID, } from "./resolveReleaseTargets.js";
|
|
17
18
|
export async function prepareCommand(argv, styles, invocationDir) {
|
|
18
|
-
const { configPath: configFlag,
|
|
19
|
+
const { configPath: configFlag, isDryRun, shouldForce, shouldSkipGitChecks, bumpOverride, onlyNames, setVersion, shouldIncludeReleaseNotes, } = parseArgs(argv);
|
|
19
20
|
const options = {
|
|
20
|
-
|
|
21
|
+
shouldForce,
|
|
21
22
|
...(bumpOverride !== undefined && { bumpOverride }),
|
|
22
|
-
...(
|
|
23
|
+
...(shouldIncludeReleaseNotes && { shouldIncludeReleaseNotes: true }),
|
|
23
24
|
};
|
|
24
|
-
if (
|
|
25
|
+
if (isDryRun) {
|
|
25
26
|
console.info(`\n${formatGlyphLine(RELEASE_GLYPHS, styles.stdout, 'dryRun', 'DRY RUN: release-kit will not modify any files')}\n`);
|
|
26
27
|
}
|
|
27
|
-
if (!
|
|
28
|
+
if (!isDryRun && !shouldSkipGitChecks) {
|
|
28
29
|
try {
|
|
29
30
|
assertCleanWorkingTree();
|
|
30
31
|
}
|
|
@@ -43,37 +44,39 @@ export async function prepareCommand(argv, styles, invocationDir) {
|
|
|
43
44
|
reportConfigWarnings(configResult.warnings, styles.stderr);
|
|
44
45
|
userConfig = configResult.config;
|
|
45
46
|
}
|
|
46
|
-
let
|
|
47
|
+
let workspaceDiscovery;
|
|
47
48
|
try {
|
|
48
|
-
|
|
49
|
+
workspaceDiscovery = discoverWorkspaces();
|
|
49
50
|
}
|
|
50
51
|
catch (error) {
|
|
51
52
|
reportError(`Failed to discover workspaces: ${describeError(error)}`);
|
|
52
53
|
process.exit(1);
|
|
53
54
|
}
|
|
54
|
-
if (
|
|
55
|
-
reportError(`No workspace package to release. ${describeEmptyWorkspace(
|
|
55
|
+
if (workspaceDiscovery.kind === 'empty') {
|
|
56
|
+
reportError(`No workspace package to release. ${describeEmptyWorkspace(workspaceDiscovery)}`);
|
|
56
57
|
process.exit(1);
|
|
57
58
|
}
|
|
58
|
-
const config = resolveTargetsOrExit(userConfig,
|
|
59
|
+
const config = resolveTargetsOrExit(userConfig, workspaceDiscovery, onlyNames, setVersion);
|
|
59
60
|
const targetOptions = {
|
|
60
61
|
...options,
|
|
61
|
-
...(
|
|
62
|
-
...(setVersion !== undefined && {
|
|
62
|
+
...(onlyNames !== undefined && { onlyTargetIds: onlyNames }),
|
|
63
|
+
...(setVersion !== undefined && { versionOverridesById: resolveSetVersions(setVersion, config.targets) }),
|
|
63
64
|
};
|
|
64
65
|
runAndReport(() => {
|
|
65
|
-
const plan =
|
|
66
|
-
|
|
66
|
+
const plan = prepareReleaseTargets(config, targetOptions);
|
|
67
|
+
const releasedPackagePaths = findReleasedPackagePaths(plan, config.targets);
|
|
68
|
+
assertReleasesOnNpm(releasedPackagePaths);
|
|
69
|
+
assertTarballsComplete(releasedPackagePaths, userConfig?.devExportConditions ?? []);
|
|
67
70
|
return plan;
|
|
68
|
-
},
|
|
71
|
+
}, isDryRun, styles.stdout);
|
|
69
72
|
}
|
|
70
73
|
function findReleasedPackagePaths(plan, targets) {
|
|
71
74
|
const releasedIds = new Set(plan.targets.flatMap((result) => result.status === 'released' && result.kind === 'package' ? result.targetId : []));
|
|
72
75
|
return targets.flatMap((target) => target.kind === 'package' && releasedIds.has(target.id) ? target.packagePath : []);
|
|
73
76
|
}
|
|
74
|
-
function resolveTargetsOrExit(userConfig, discovery,
|
|
77
|
+
function resolveTargetsOrExit(userConfig, discovery, onlyNames, setVersion) {
|
|
75
78
|
if (discovery.kind === 'single-package') {
|
|
76
|
-
if (
|
|
79
|
+
if (onlyNames !== undefined) {
|
|
77
80
|
reportError('--only is only supported for monorepo configurations');
|
|
78
81
|
process.exit(1);
|
|
79
82
|
}
|
|
@@ -98,8 +101,8 @@ function resolveTargetsOrExit(userConfig, discovery, only, setVersion) {
|
|
|
98
101
|
process.exit(1);
|
|
99
102
|
}
|
|
100
103
|
const knownNames = listSelectableIds(config.targets);
|
|
101
|
-
const
|
|
102
|
-
for (const name of
|
|
104
|
+
const requestedNames = onlyNames ?? [];
|
|
105
|
+
for (const name of requestedNames) {
|
|
103
106
|
if (knownNames.includes(name)) {
|
|
104
107
|
continue;
|
|
105
108
|
}
|
|
@@ -120,7 +123,7 @@ function listSelectableIds(targets) {
|
|
|
120
123
|
function listMemberIds(targets) {
|
|
121
124
|
return targets.flatMap((target) => (target.kind === 'composite' ? [] : target.id));
|
|
122
125
|
}
|
|
123
|
-
function runAndReport(computePlan,
|
|
126
|
+
function runAndReport(computePlan, isDryRun, style) {
|
|
124
127
|
let plan;
|
|
125
128
|
try {
|
|
126
129
|
plan = computePlan();
|
|
@@ -130,7 +133,7 @@ function runAndReport(computePlan, dryRun, style) {
|
|
|
130
133
|
process.stderr.write('The command did not write any files; the working tree is unchanged.\n');
|
|
131
134
|
process.exit(1);
|
|
132
135
|
}
|
|
133
|
-
if (
|
|
136
|
+
if (isDryRun) {
|
|
134
137
|
process.stdout.write(reportPrepare(plan, { applied: false, style }) + '\n');
|
|
135
138
|
reportPlanFiles(plan, true);
|
|
136
139
|
return;
|
|
@@ -164,11 +167,11 @@ function runFormatCommand(formatCommand) {
|
|
|
164
167
|
return describeError(error);
|
|
165
168
|
}
|
|
166
169
|
}
|
|
167
|
-
function reportPlanFiles(plan,
|
|
170
|
+
function reportPlanFiles(plan, isDryRun) {
|
|
168
171
|
if (plan.tags.length === 0) {
|
|
169
172
|
return;
|
|
170
173
|
}
|
|
171
|
-
if (
|
|
174
|
+
if (isDryRun) {
|
|
172
175
|
console.info(dim(` [dry-run] Would write ${RELEASE_TAGS_FILE}: ${plan.tags.join(' ')}`));
|
|
173
176
|
if (plan.summary.length > 0) {
|
|
174
177
|
console.info(dim(` [dry-run] Would write ${RELEASE_SUMMARY_FILE}`));
|