@williamthorsen/release-kit 6.0.0 → 7.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 +8 -0
- package/README.md +3 -3
- package/dist/esm/.cache +1 -1
- package/dist/esm/assertCleanWorkingTree.js +9 -14
- package/dist/esm/bin/release-kit.js +200 -199
- package/dist/esm/buildChangelogEntries.d.ts +2 -2
- package/dist/esm/buildChangelogEntries.js +142 -149
- package/dist/esm/buildDependencyGraph.d.ts +1 -1
- package/dist/esm/buildDependencyGraph.js +60 -59
- package/dist/esm/buildEmptyReleaseEntry.d.ts +1 -1
- package/dist/esm/buildEmptyReleaseEntry.js +12 -15
- package/dist/esm/buildReleaseSummary.d.ts +1 -1
- package/dist/esm/buildReleaseSummary.js +23 -26
- package/dist/esm/buildSyntheticChangelogEntry.d.ts +1 -1
- package/dist/esm/buildSyntheticChangelogEntry.js +9 -12
- package/dist/esm/bumpAllVersions.d.ts +1 -1
- package/dist/esm/bumpAllVersions.js +51 -52
- package/dist/esm/bumpVersion.d.ts +1 -1
- package/dist/esm/bumpVersion.js +25 -28
- package/dist/esm/changelogJsonFile.d.ts +1 -1
- package/dist/esm/changelogJsonFile.js +59 -62
- package/dist/esm/changelogJsonUtils.d.ts +1 -1
- package/dist/esm/changelogJsonUtils.js +35 -32
- package/dist/esm/changelogOverrides.d.ts +1 -1
- package/dist/esm/changelogOverrides.js +374 -377
- package/dist/esm/checkWorkTypesDrift.js +104 -102
- package/dist/esm/collectPolicyViolations.d.ts +2 -2
- package/dist/esm/collectPolicyViolations.js +11 -14
- package/dist/esm/commitCommand.js +48 -46
- package/dist/esm/compareVersions.js +20 -23
- package/dist/esm/createGithubRelease.js +56 -59
- package/dist/esm/createGithubReleaseCommand.js +21 -23
- package/dist/esm/createTags.js +57 -56
- package/dist/esm/decideRelease.d.ts +2 -2
- package/dist/esm/decideRelease.js +27 -41
- package/dist/esm/defaults.d.ts +3 -3
- package/dist/esm/defaults.js +36 -48
- package/dist/esm/deleteFileIfExists.js +10 -12
- package/dist/esm/deriveWorkspaceConfig.d.ts +1 -1
- package/dist/esm/deriveWorkspaceConfig.js +34 -36
- package/dist/esm/detectPackageManager.js +35 -37
- package/dist/esm/detectUndeclaredTagPrefixes.js +41 -40
- package/dist/esm/determineBumpFromCommits.d.ts +2 -2
- package/dist/esm/determineBumpFromCommits.js +25 -29
- package/dist/esm/determineBumpType.d.ts +1 -1
- package/dist/esm/determineBumpType.js +31 -32
- package/dist/esm/discoverWorkspaces.js +38 -40
- package/dist/esm/format.js +6 -11
- package/dist/esm/generateChangelogs.d.ts +1 -1
- package/dist/esm/generateChangelogs.js +13 -17
- package/dist/esm/getCommitsSinceTarget.d.ts +1 -1
- package/dist/esm/getCommitsSinceTarget.js +54 -55
- package/dist/esm/hasPrettierConfig.js +33 -35
- package/dist/esm/help/prepareHelp.js +4 -8
- package/dist/esm/index.d.ts +4 -4
- package/dist/esm/index.js +2 -8
- package/dist/esm/init/checks.js +28 -32
- package/dist/esm/init/detectRepoType.js +12 -15
- package/dist/esm/init/initCommand.js +60 -54
- package/dist/esm/init/parseJsonRecord.js +9 -11
- package/dist/esm/init/scaffold.d.ts +1 -1
- package/dist/esm/init/scaffold.js +32 -38
- package/dist/esm/init/templates.d.ts +1 -1
- package/dist/esm/init/templates.js +16 -22
- package/dist/esm/injectReleaseNotesIntoReadme.js +42 -49
- package/dist/esm/injectSection.js +17 -27
- package/dist/esm/loadConfig.d.ts +1 -1
- package/dist/esm/loadConfig.js +206 -225
- package/dist/esm/parseCommitMessage.d.ts +1 -1
- package/dist/esm/parseCommitMessage.js +72 -76
- package/dist/esm/parseRequestedTags.js +6 -9
- package/dist/esm/prepareCommand.d.ts +1 -1
- package/dist/esm/prepareCommand.js +225 -235
- package/dist/esm/previewTagPrefixes.d.ts +1 -1
- package/dist/esm/previewTagPrefixes.js +101 -96
- package/dist/esm/propagateBumps.d.ts +2 -2
- package/dist/esm/propagateBumps.js +48 -50
- package/dist/esm/publish.d.ts +2 -2
- package/dist/esm/publish.js +22 -28
- package/dist/esm/publishCommand.js +75 -80
- package/dist/esm/pushCommand.js +33 -34
- package/dist/esm/pushRelease.d.ts +1 -1
- package/dist/esm/pushRelease.js +21 -24
- package/dist/esm/readCurrentVersion.js +13 -17
- package/dist/esm/releasePrepare.d.ts +1 -1
- package/dist/esm/releasePrepare.js +213 -231
- package/dist/esm/releasePrepareMono.d.ts +2 -2
- package/dist/esm/releasePrepareMono.js +434 -494
- package/dist/esm/releasePrepareProject.d.ts +2 -2
- package/dist/esm/releasePrepareProject.js +143 -153
- package/dist/esm/renderChangelogMarkdown.d.ts +1 -1
- package/dist/esm/renderChangelogMarkdown.js +20 -24
- package/dist/esm/renderReleaseNotes.d.ts +1 -1
- package/dist/esm/renderReleaseNotes.js +50 -55
- package/dist/esm/reportPrepare.d.ts +1 -1
- package/dist/esm/reportPrepare.js +230 -231
- package/dist/esm/resolveCliffConfigPath.js +18 -23
- package/dist/esm/resolveCommandTags.d.ts +1 -1
- package/dist/esm/resolveCommandTags.js +47 -45
- package/dist/esm/resolveReleaseNotesConfig.d.ts +1 -1
- package/dist/esm/resolveReleaseNotesConfig.js +39 -43
- package/dist/esm/resolveReleaseTags.d.ts +1 -1
- package/dist/esm/resolveReleaseTags.js +39 -40
- package/dist/esm/runGitCliff.js +27 -30
- package/dist/esm/showTagPrefixesCommand.js +48 -70
- package/dist/esm/stripEmojiPrefix.js +2 -5
- package/dist/esm/stripScope.js +18 -21
- package/dist/esm/sync-labels/generateCommand.d.ts +1 -1
- package/dist/esm/sync-labels/generateCommand.js +51 -53
- package/dist/esm/sync-labels/initCommand.js +46 -48
- package/dist/esm/sync-labels/loadSyncLabelsConfig.d.ts +1 -1
- package/dist/esm/sync-labels/loadSyncLabelsConfig.js +46 -52
- package/dist/esm/sync-labels/presets.d.ts +1 -1
- package/dist/esm/sync-labels/presets.js +40 -43
- package/dist/esm/sync-labels/resolveLabels.d.ts +1 -1
- package/dist/esm/sync-labels/resolveLabels.js +28 -35
- package/dist/esm/sync-labels/syncCommand.js +30 -31
- package/dist/esm/sync-labels/templates.d.ts +1 -1
- package/dist/esm/sync-labels/templates.js +23 -26
- package/dist/esm/sync-labels/types.js +1 -0
- package/dist/esm/syncWorkTypes.js +83 -84
- package/dist/esm/tagCommand.js +12 -14
- package/dist/esm/typeGuards.js +4 -8
- package/dist/esm/types.js +66 -60
- package/dist/esm/validateConfig.d.ts +1 -1
- package/dist/esm/validateConfig.js +109 -115
- package/dist/esm/validateOnlyExcludesStrandedDependents.d.ts +2 -2
- package/dist/esm/validateOnlyExcludesStrandedDependents.js +97 -91
- package/dist/esm/validateOverridesCommand.d.ts +2 -2
- package/dist/esm/validateOverridesCommand.js +110 -111
- package/dist/esm/workTypesData.js +57 -60
- package/dist/esm/workTypesUtils.js +16 -21
- package/dist/esm/writeReleaseNotesPreviews.js +55 -57
- package/package.json +2 -2
|
@@ -1,114 +1,116 @@
|
|
|
1
|
-
import { readFileSync } from
|
|
2
|
-
import { dirname, resolve } from
|
|
3
|
-
import { fileURLToPath } from
|
|
4
|
-
import { formatErrorLine } from
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, resolve } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { formatErrorLine } from '@williamthorsen/nmr-core';
|
|
5
5
|
import { buildFetchInit, errorMessage, hasExpectedTopLevelShape } from "./workTypesUtils.js";
|
|
6
|
-
const UPSTREAM_WORK_TYPES_URL =
|
|
6
|
+
export const UPSTREAM_WORK_TYPES_URL = 'https://raw.githubusercontent.com/williamthorsen/codeassembly/main/packages/agents/content/skills/_data/work-types.json';
|
|
7
7
|
function resolveDefaultLocalPath() {
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
const moduleDir = dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
return resolve(moduleDir, 'work-types.json');
|
|
10
10
|
}
|
|
11
|
-
async function checkWorkTypesDrift(dependencies = {}) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
11
|
+
export async function checkWorkTypesDrift(dependencies = {}) {
|
|
12
|
+
const localPath = dependencies.localPath ?? resolveDefaultLocalPath();
|
|
13
|
+
const fetcher = dependencies.fetch ?? globalThis.fetch;
|
|
14
|
+
const url = dependencies.upstreamUrl ?? UPSTREAM_WORK_TYPES_URL;
|
|
15
|
+
const localContent = readFileSync(localPath, 'utf8');
|
|
16
|
+
let localJson;
|
|
17
|
+
try {
|
|
18
|
+
localJson = JSON.parse(localContent);
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
return {
|
|
22
|
+
exitCode: 3,
|
|
23
|
+
message: `Local work-types.json is not valid JSON: ${errorMessage(error)}`,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
const fetchInit = buildFetchInit();
|
|
27
|
+
let response;
|
|
28
|
+
try {
|
|
29
|
+
response = fetchInit === undefined ? await fetcher(url) : await fetcher(url, fetchInit);
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
return {
|
|
33
|
+
exitCode: 2,
|
|
34
|
+
message: formatErrorLine(`Failed to fetch upstream work-types.json: ${errorMessage(error)}`),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
if (response.status === 404) {
|
|
38
|
+
return {
|
|
39
|
+
exitCode: 0,
|
|
40
|
+
message: `Upstream work-types.json not yet published at ${url}; skipping drift check.`,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
if (!response.ok) {
|
|
44
|
+
return {
|
|
45
|
+
exitCode: 2,
|
|
46
|
+
message: formatErrorLine(`Failed to fetch upstream work-types.json: HTTP ${response.status} ${response.statusText}`),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
const upstreamText = await response.text();
|
|
50
|
+
let upstreamJson;
|
|
51
|
+
try {
|
|
52
|
+
upstreamJson = JSON.parse(upstreamText);
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
return {
|
|
56
|
+
exitCode: 3,
|
|
57
|
+
message: `Upstream work-types.json is not valid JSON: ${errorMessage(error)}`,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
if (!hasExpectedTopLevelShape(upstreamJson)) {
|
|
61
|
+
return {
|
|
62
|
+
exitCode: 3,
|
|
63
|
+
message: 'Upstream work-types.json does not match the expected schema shape (missing `tiers` or `types`).',
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
const normalisedLocal = stripLocalOnlyFields(localJson);
|
|
67
|
+
if (deepEqual(normalisedLocal, upstreamJson)) {
|
|
68
|
+
return {
|
|
69
|
+
exitCode: 0,
|
|
70
|
+
message: 'Local work-types.json matches upstream.',
|
|
71
|
+
};
|
|
72
|
+
}
|
|
67
73
|
return {
|
|
68
|
-
|
|
69
|
-
|
|
74
|
+
exitCode: 1,
|
|
75
|
+
message: `Drift detected. Local and upstream work-types.json differ. Run \`nmr work-types:sync\` to update from upstream.\nLocal: ${localPath}\nUpstream: ${url}`,
|
|
70
76
|
};
|
|
71
|
-
}
|
|
72
|
-
return {
|
|
73
|
-
exitCode: 1,
|
|
74
|
-
message: `Drift detected. Local and upstream work-types.json differ. Run \`nmr work-types:sync\` to update from upstream.
|
|
75
|
-
Local: ${localPath}
|
|
76
|
-
Upstream: ${url}`
|
|
77
|
-
};
|
|
78
77
|
}
|
|
79
78
|
function stripLocalOnlyFields(value) {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
79
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
80
|
+
return value;
|
|
81
|
+
}
|
|
82
|
+
const record = { ...value };
|
|
83
|
+
delete record.$schema;
|
|
84
|
+
return record;
|
|
86
85
|
}
|
|
87
86
|
function deepEqual(a, b) {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
if (a
|
|
93
|
-
|
|
94
|
-
|
|
87
|
+
if (a === b)
|
|
88
|
+
return true;
|
|
89
|
+
if (typeof a !== typeof b)
|
|
90
|
+
return false;
|
|
91
|
+
if (a === null || b === null)
|
|
92
|
+
return a === b;
|
|
93
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
94
|
+
if (a.length !== b.length)
|
|
95
|
+
return false;
|
|
96
|
+
for (let i = 0; i < a.length; i++) {
|
|
97
|
+
if (!deepEqual(a[i], b[i]))
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
return true;
|
|
95
101
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
102
|
+
if (typeof a === 'object' && typeof b === 'object' && !Array.isArray(a) && !Array.isArray(b)) {
|
|
103
|
+
const aRecord = { ...a };
|
|
104
|
+
const bRecord = { ...b };
|
|
105
|
+
const aKeys = Object.keys(aRecord);
|
|
106
|
+
const bKeys = Object.keys(bRecord);
|
|
107
|
+
if (aKeys.length !== bKeys.length)
|
|
108
|
+
return false;
|
|
109
|
+
for (const key of aKeys) {
|
|
110
|
+
if (!deepEqual(aRecord[key], bRecord[key]))
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
return true;
|
|
106
114
|
}
|
|
107
|
-
return
|
|
108
|
-
}
|
|
109
|
-
return false;
|
|
115
|
+
return false;
|
|
110
116
|
}
|
|
111
|
-
export {
|
|
112
|
-
UPSTREAM_WORK_TYPES_URL,
|
|
113
|
-
checkWorkTypesDrift
|
|
114
|
-
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { PolicyViolationHandler } from './parseCommitMessage.
|
|
2
|
-
import type { PolicyViolation } from './types.
|
|
1
|
+
import type { PolicyViolationHandler } from './parseCommitMessage.js';
|
|
2
|
+
import type { PolicyViolation } from './types.js';
|
|
3
3
|
export declare function createPolicyViolationCollector(): {
|
|
4
4
|
violations: PolicyViolation[];
|
|
5
5
|
onPolicyViolation: PolicyViolationHandler;
|
|
@@ -1,15 +1,12 @@
|
|
|
1
|
-
function createPolicyViolationCollector() {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
1
|
+
export function createPolicyViolationCollector() {
|
|
2
|
+
const violations = [];
|
|
3
|
+
const onPolicyViolation = (commit, type, surface) => {
|
|
4
|
+
violations.push({
|
|
5
|
+
commitHash: commit.hash,
|
|
6
|
+
commitSubject: commit.message.split('\n', 1)[0] ?? '',
|
|
7
|
+
type,
|
|
8
|
+
surface,
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
return { violations, onPolicyViolation };
|
|
12
12
|
}
|
|
13
|
-
export {
|
|
14
|
-
createPolicyViolationCollector
|
|
15
|
-
};
|
|
@@ -1,53 +1,55 @@
|
|
|
1
|
-
import { execFileSync } from
|
|
2
|
-
import { readFileSync } from
|
|
3
|
-
import { parseArgsOrExit } from
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { parseArgsOrExit } from '@williamthorsen/nmr-core';
|
|
4
4
|
import { RELEASE_SUMMARY_FILE, RELEASE_TAGS_FILE } from "./prepareCommand.js";
|
|
5
5
|
const commitFlagSchema = {
|
|
6
|
-
|
|
6
|
+
dryRun: { long: '--dry-run', type: 'boolean' },
|
|
7
7
|
};
|
|
8
|
-
function commitCommand(argv) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
} catch (error) {
|
|
24
|
-
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
25
|
-
} else {
|
|
26
|
-
throw error;
|
|
8
|
+
export function commitCommand(argv) {
|
|
9
|
+
const dryRun = parseArgsOrExit(argv, commitFlagSchema).flags.dryRun;
|
|
10
|
+
let tagsContent;
|
|
11
|
+
try {
|
|
12
|
+
tagsContent = readFileSync(RELEASE_TAGS_FILE, 'utf8');
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
throw new Error('No tags file found. Run `release-kit prepare` first.');
|
|
16
|
+
}
|
|
17
|
+
const tags = tagsContent
|
|
18
|
+
.split('\n')
|
|
19
|
+
.map((line) => line.trim())
|
|
20
|
+
.filter((line) => line.length > 0);
|
|
21
|
+
if (tags.length === 0) {
|
|
22
|
+
throw new Error('Tags file is empty. Run `release-kit prepare` first.');
|
|
27
23
|
}
|
|
28
|
-
|
|
29
|
-
const title = `release: ${tags.join(" ")}`;
|
|
30
|
-
const message = summary.length > 0 ? `${title}
|
|
31
|
-
|
|
32
|
-
${summary}` : title;
|
|
33
|
-
if (dryRun) {
|
|
34
|
-
console.info("[dry-run] Would create commit with message:\n");
|
|
35
|
-
console.info(message);
|
|
24
|
+
let summary = '';
|
|
36
25
|
try {
|
|
37
|
-
|
|
38
|
-
if (status.trim().length > 0) {
|
|
39
|
-
console.info("\nUncommitted changes:");
|
|
40
|
-
console.info(status.trimEnd());
|
|
41
|
-
}
|
|
42
|
-
} catch {
|
|
43
|
-
console.info("(Could not determine uncommitted changes)");
|
|
26
|
+
summary = readFileSync(RELEASE_SUMMARY_FILE, 'utf8').trim();
|
|
44
27
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
28
|
+
catch (error) {
|
|
29
|
+
if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
throw error;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const title = `release: ${tags.join(' ')}`;
|
|
36
|
+
const message = summary.length > 0 ? `${title}\n\n${summary}` : title;
|
|
37
|
+
if (dryRun) {
|
|
38
|
+
console.info('[dry-run] Would create commit with message:\n');
|
|
39
|
+
console.info(message);
|
|
40
|
+
try {
|
|
41
|
+
const status = execFileSync('git', ['status', '--porcelain'], { encoding: 'utf8' });
|
|
42
|
+
if (status.trim().length > 0) {
|
|
43
|
+
console.info('\nUncommitted changes:');
|
|
44
|
+
console.info(status.trimEnd());
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
console.info('(Could not determine uncommitted changes)');
|
|
49
|
+
}
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
execFileSync('git', ['add', '-A']);
|
|
53
|
+
execFileSync('git', ['commit', '-m', message]);
|
|
54
|
+
console.info(`Created release commit: ${title}`);
|
|
50
55
|
}
|
|
51
|
-
export {
|
|
52
|
-
commitCommand
|
|
53
|
-
};
|
|
@@ -1,27 +1,24 @@
|
|
|
1
1
|
const CANONICAL_SEMVER_PATTERN = /^(\d+)\.(\d+)\.(\d+)$/;
|
|
2
2
|
function parseCanonicalSemver(version) {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
3
|
+
const match = version.match(CANONICAL_SEMVER_PATTERN);
|
|
4
|
+
if (!match) {
|
|
5
|
+
throw new Error(`Invalid semver version: '${version}'`);
|
|
6
|
+
}
|
|
7
|
+
const [, major = '', minor = '', patch = ''] = match;
|
|
8
|
+
return {
|
|
9
|
+
major: Number.parseInt(major, 10),
|
|
10
|
+
minor: Number.parseInt(minor, 10),
|
|
11
|
+
patch: Number.parseInt(patch, 10),
|
|
12
|
+
};
|
|
13
13
|
}
|
|
14
|
-
function isForwardVersion(current, target) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
14
|
+
export function isForwardVersion(current, target) {
|
|
15
|
+
const currentParsed = parseCanonicalSemver(current);
|
|
16
|
+
const targetParsed = parseCanonicalSemver(target);
|
|
17
|
+
if (targetParsed.major !== currentParsed.major) {
|
|
18
|
+
return targetParsed.major > currentParsed.major;
|
|
19
|
+
}
|
|
20
|
+
if (targetParsed.minor !== currentParsed.minor) {
|
|
21
|
+
return targetParsed.minor > currentParsed.minor;
|
|
22
|
+
}
|
|
23
|
+
return targetParsed.patch > currentParsed.patch;
|
|
24
24
|
}
|
|
25
|
-
export {
|
|
26
|
-
isForwardVersion
|
|
27
|
-
};
|
|
@@ -1,64 +1,61 @@
|
|
|
1
|
-
import { execFileSync } from
|
|
2
|
-
import { existsSync } from
|
|
3
|
-
import { join } from
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
4
|
import { extractVersion, readChangelogEntries } from "./changelogJsonUtils.js";
|
|
5
5
|
import { matchesAudience, renderReleaseNotesSingle } from "./renderReleaseNotes.js";
|
|
6
|
-
function createGithubRelease(options) {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
});
|
|
31
|
-
if (body.trim() === "") {
|
|
32
|
-
return { status: "skipped", reason: "empty-body" };
|
|
33
|
-
}
|
|
34
|
-
const args = ["release", "create", tag, "--title", tag, "--notes", body];
|
|
35
|
-
if (dryRun) {
|
|
36
|
-
console.info(`[dry-run] Would run: gh ${args.join(" ")}`);
|
|
37
|
-
return { status: "created" };
|
|
38
|
-
}
|
|
39
|
-
execFileSync("gh", args, { stdio: "inherit" });
|
|
40
|
-
return { status: "created" };
|
|
41
|
-
}
|
|
42
|
-
function createGithubReleases(tags, changelogJsonOutputPath, dryRun, sectionOrder) {
|
|
43
|
-
const created = [];
|
|
44
|
-
const skipped = [];
|
|
45
|
-
for (const { tag, workspacePath } of tags) {
|
|
46
|
-
const changelogJsonPath = join(workspacePath, changelogJsonOutputPath);
|
|
47
|
-
const result = createGithubRelease({
|
|
48
|
-
tag,
|
|
49
|
-
changelogJsonPath,
|
|
50
|
-
dryRun,
|
|
51
|
-
...sectionOrder === void 0 ? {} : { sectionOrder }
|
|
6
|
+
export function createGithubRelease(options) {
|
|
7
|
+
const { tag, changelogJsonPath, dryRun, sectionOrder } = options;
|
|
8
|
+
if (!existsSync(changelogJsonPath)) {
|
|
9
|
+
console.warn(`Warning: ${changelogJsonPath} not found; skipping GitHub Release creation`);
|
|
10
|
+
return { status: 'skipped', reason: 'no-entry' };
|
|
11
|
+
}
|
|
12
|
+
const version = extractVersion(tag);
|
|
13
|
+
const entries = readChangelogEntries(changelogJsonPath);
|
|
14
|
+
if (entries === undefined) {
|
|
15
|
+
console.warn(`Warning: could not parse ${changelogJsonPath}; skipping GitHub Release creation`);
|
|
16
|
+
return { status: 'skipped', reason: 'no-entry' };
|
|
17
|
+
}
|
|
18
|
+
const entry = entries.find((e) => e.version === version);
|
|
19
|
+
if (entry === undefined) {
|
|
20
|
+
console.warn(`Warning: no changelog entry for version ${version}; skipping GitHub Release creation`);
|
|
21
|
+
return { status: 'skipped', reason: 'no-entry' };
|
|
22
|
+
}
|
|
23
|
+
if (!entry.sections.some(matchesAudience('all'))) {
|
|
24
|
+
return { status: 'skipped', reason: 'no-audience-content' };
|
|
25
|
+
}
|
|
26
|
+
const body = renderReleaseNotesSingle(entry, {
|
|
27
|
+
filter: matchesAudience('all'),
|
|
28
|
+
includeHeading: false,
|
|
29
|
+
...(sectionOrder === undefined ? {} : { sectionOrder }),
|
|
52
30
|
});
|
|
53
|
-
if (
|
|
54
|
-
|
|
55
|
-
}
|
|
56
|
-
|
|
31
|
+
if (body.trim() === '') {
|
|
32
|
+
return { status: 'skipped', reason: 'empty-body' };
|
|
33
|
+
}
|
|
34
|
+
const args = ['release', 'create', tag, '--title', tag, '--notes', body];
|
|
35
|
+
if (dryRun) {
|
|
36
|
+
console.info(`[dry-run] Would run: gh ${args.join(' ')}`);
|
|
37
|
+
return { status: 'created' };
|
|
38
|
+
}
|
|
39
|
+
execFileSync('gh', args, { stdio: 'inherit' });
|
|
40
|
+
return { status: 'created' };
|
|
41
|
+
}
|
|
42
|
+
export function createGithubReleases(tags, changelogJsonOutputPath, dryRun, sectionOrder) {
|
|
43
|
+
const created = [];
|
|
44
|
+
const skipped = [];
|
|
45
|
+
for (const { tag, workspacePath } of tags) {
|
|
46
|
+
const changelogJsonPath = join(workspacePath, changelogJsonOutputPath);
|
|
47
|
+
const result = createGithubRelease({
|
|
48
|
+
tag,
|
|
49
|
+
changelogJsonPath,
|
|
50
|
+
dryRun,
|
|
51
|
+
...(sectionOrder === undefined ? {} : { sectionOrder }),
|
|
52
|
+
});
|
|
53
|
+
if (result.status === 'created') {
|
|
54
|
+
created.push(tag);
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
skipped.push({ tag, reason: result.reason });
|
|
58
|
+
}
|
|
57
59
|
}
|
|
58
|
-
|
|
59
|
-
return { created, skipped };
|
|
60
|
+
return { created, skipped };
|
|
60
61
|
}
|
|
61
|
-
export {
|
|
62
|
-
createGithubRelease,
|
|
63
|
-
createGithubReleases
|
|
64
|
-
};
|
|
@@ -1,30 +1,28 @@
|
|
|
1
|
-
import { parseArgsOrExit, reportError } from
|
|
1
|
+
import { parseArgsOrExit, reportError } from '@williamthorsen/nmr-core';
|
|
2
2
|
import { createGithubReleases } from "./createGithubRelease.js";
|
|
3
3
|
import { parseRequestedTags } from "./parseRequestedTags.js";
|
|
4
4
|
import { resolveCommandTags } from "./resolveCommandTags.js";
|
|
5
5
|
import { resolveReleaseNotesConfig } from "./resolveReleaseNotesConfig.js";
|
|
6
6
|
const createGithubReleaseFlagSchema = {
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
dryRun: { long: '--dry-run', type: 'boolean' },
|
|
8
|
+
tags: { long: '--tags', type: 'string' },
|
|
9
9
|
};
|
|
10
|
-
async function createGithubReleaseCommand(argv) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
10
|
+
export async function createGithubReleaseCommand(argv) {
|
|
11
|
+
const parsed = parseArgsOrExit(argv, createGithubReleaseFlagSchema);
|
|
12
|
+
const { dryRun } = parsed.flags;
|
|
13
|
+
const requestedTags = parseRequestedTags(parsed.flags.tags);
|
|
14
|
+
const resolvedTags = await resolveCommandTags(requestedTags);
|
|
15
|
+
const { changelogJsonOutputPath, sectionOrder } = await resolveReleaseNotesConfig({ strictLoad: true });
|
|
16
|
+
let outcome;
|
|
17
|
+
try {
|
|
18
|
+
outcome = createGithubReleases(resolvedTags, changelogJsonOutputPath, dryRun, sectionOrder);
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
reportError(`Failed to create GitHub Releases: ${error instanceof Error ? error.message : String(error)}`);
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
if (outcome.skipped.length > 0) {
|
|
25
|
+
const formatted = outcome.skipped.map((s) => `${s.tag} (${s.reason})`).join(', ');
|
|
26
|
+
console.info(`Skipped ${outcome.skipped.length} tag(s) with no releasable content: ${formatted}.`);
|
|
27
|
+
}
|
|
27
28
|
}
|
|
28
|
-
export {
|
|
29
|
-
createGithubReleaseCommand
|
|
30
|
-
};
|