@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.
Files changed (134) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +3 -3
  3. package/dist/esm/.cache +1 -1
  4. package/dist/esm/assertCleanWorkingTree.js +9 -14
  5. package/dist/esm/bin/release-kit.js +200 -199
  6. package/dist/esm/buildChangelogEntries.d.ts +2 -2
  7. package/dist/esm/buildChangelogEntries.js +142 -149
  8. package/dist/esm/buildDependencyGraph.d.ts +1 -1
  9. package/dist/esm/buildDependencyGraph.js +60 -59
  10. package/dist/esm/buildEmptyReleaseEntry.d.ts +1 -1
  11. package/dist/esm/buildEmptyReleaseEntry.js +12 -15
  12. package/dist/esm/buildReleaseSummary.d.ts +1 -1
  13. package/dist/esm/buildReleaseSummary.js +23 -26
  14. package/dist/esm/buildSyntheticChangelogEntry.d.ts +1 -1
  15. package/dist/esm/buildSyntheticChangelogEntry.js +9 -12
  16. package/dist/esm/bumpAllVersions.d.ts +1 -1
  17. package/dist/esm/bumpAllVersions.js +51 -52
  18. package/dist/esm/bumpVersion.d.ts +1 -1
  19. package/dist/esm/bumpVersion.js +25 -28
  20. package/dist/esm/changelogJsonFile.d.ts +1 -1
  21. package/dist/esm/changelogJsonFile.js +59 -62
  22. package/dist/esm/changelogJsonUtils.d.ts +1 -1
  23. package/dist/esm/changelogJsonUtils.js +35 -32
  24. package/dist/esm/changelogOverrides.d.ts +1 -1
  25. package/dist/esm/changelogOverrides.js +374 -377
  26. package/dist/esm/checkWorkTypesDrift.js +104 -102
  27. package/dist/esm/collectPolicyViolations.d.ts +2 -2
  28. package/dist/esm/collectPolicyViolations.js +11 -14
  29. package/dist/esm/commitCommand.js +48 -46
  30. package/dist/esm/compareVersions.js +20 -23
  31. package/dist/esm/createGithubRelease.js +56 -59
  32. package/dist/esm/createGithubReleaseCommand.js +21 -23
  33. package/dist/esm/createTags.js +57 -56
  34. package/dist/esm/decideRelease.d.ts +2 -2
  35. package/dist/esm/decideRelease.js +27 -41
  36. package/dist/esm/defaults.d.ts +3 -3
  37. package/dist/esm/defaults.js +36 -48
  38. package/dist/esm/deleteFileIfExists.js +10 -12
  39. package/dist/esm/deriveWorkspaceConfig.d.ts +1 -1
  40. package/dist/esm/deriveWorkspaceConfig.js +34 -36
  41. package/dist/esm/detectPackageManager.js +35 -37
  42. package/dist/esm/detectUndeclaredTagPrefixes.js +41 -40
  43. package/dist/esm/determineBumpFromCommits.d.ts +2 -2
  44. package/dist/esm/determineBumpFromCommits.js +25 -29
  45. package/dist/esm/determineBumpType.d.ts +1 -1
  46. package/dist/esm/determineBumpType.js +31 -32
  47. package/dist/esm/discoverWorkspaces.js +38 -40
  48. package/dist/esm/format.js +6 -11
  49. package/dist/esm/generateChangelogs.d.ts +1 -1
  50. package/dist/esm/generateChangelogs.js +13 -17
  51. package/dist/esm/getCommitsSinceTarget.d.ts +1 -1
  52. package/dist/esm/getCommitsSinceTarget.js +54 -55
  53. package/dist/esm/hasPrettierConfig.js +33 -35
  54. package/dist/esm/help/prepareHelp.js +4 -8
  55. package/dist/esm/index.d.ts +4 -4
  56. package/dist/esm/index.js +2 -8
  57. package/dist/esm/init/checks.js +28 -32
  58. package/dist/esm/init/detectRepoType.js +12 -15
  59. package/dist/esm/init/initCommand.js +60 -54
  60. package/dist/esm/init/parseJsonRecord.js +9 -11
  61. package/dist/esm/init/scaffold.d.ts +1 -1
  62. package/dist/esm/init/scaffold.js +32 -38
  63. package/dist/esm/init/templates.d.ts +1 -1
  64. package/dist/esm/init/templates.js +16 -22
  65. package/dist/esm/injectReleaseNotesIntoReadme.js +42 -49
  66. package/dist/esm/injectSection.js +17 -27
  67. package/dist/esm/loadConfig.d.ts +1 -1
  68. package/dist/esm/loadConfig.js +206 -225
  69. package/dist/esm/parseCommitMessage.d.ts +1 -1
  70. package/dist/esm/parseCommitMessage.js +72 -76
  71. package/dist/esm/parseRequestedTags.js +6 -9
  72. package/dist/esm/prepareCommand.d.ts +1 -1
  73. package/dist/esm/prepareCommand.js +225 -235
  74. package/dist/esm/previewTagPrefixes.d.ts +1 -1
  75. package/dist/esm/previewTagPrefixes.js +101 -96
  76. package/dist/esm/propagateBumps.d.ts +2 -2
  77. package/dist/esm/propagateBumps.js +48 -50
  78. package/dist/esm/publish.d.ts +2 -2
  79. package/dist/esm/publish.js +22 -28
  80. package/dist/esm/publishCommand.js +75 -80
  81. package/dist/esm/pushCommand.js +33 -34
  82. package/dist/esm/pushRelease.d.ts +1 -1
  83. package/dist/esm/pushRelease.js +21 -24
  84. package/dist/esm/readCurrentVersion.js +13 -17
  85. package/dist/esm/releasePrepare.d.ts +1 -1
  86. package/dist/esm/releasePrepare.js +213 -231
  87. package/dist/esm/releasePrepareMono.d.ts +2 -2
  88. package/dist/esm/releasePrepareMono.js +434 -494
  89. package/dist/esm/releasePrepareProject.d.ts +2 -2
  90. package/dist/esm/releasePrepareProject.js +143 -153
  91. package/dist/esm/renderChangelogMarkdown.d.ts +1 -1
  92. package/dist/esm/renderChangelogMarkdown.js +20 -24
  93. package/dist/esm/renderReleaseNotes.d.ts +1 -1
  94. package/dist/esm/renderReleaseNotes.js +50 -55
  95. package/dist/esm/reportPrepare.d.ts +1 -1
  96. package/dist/esm/reportPrepare.js +230 -231
  97. package/dist/esm/resolveCliffConfigPath.js +18 -23
  98. package/dist/esm/resolveCommandTags.d.ts +1 -1
  99. package/dist/esm/resolveCommandTags.js +47 -45
  100. package/dist/esm/resolveReleaseNotesConfig.d.ts +1 -1
  101. package/dist/esm/resolveReleaseNotesConfig.js +39 -43
  102. package/dist/esm/resolveReleaseTags.d.ts +1 -1
  103. package/dist/esm/resolveReleaseTags.js +39 -40
  104. package/dist/esm/runGitCliff.js +27 -30
  105. package/dist/esm/showTagPrefixesCommand.js +48 -70
  106. package/dist/esm/stripEmojiPrefix.js +2 -5
  107. package/dist/esm/stripScope.js +18 -21
  108. package/dist/esm/sync-labels/generateCommand.d.ts +1 -1
  109. package/dist/esm/sync-labels/generateCommand.js +51 -53
  110. package/dist/esm/sync-labels/initCommand.js +46 -48
  111. package/dist/esm/sync-labels/loadSyncLabelsConfig.d.ts +1 -1
  112. package/dist/esm/sync-labels/loadSyncLabelsConfig.js +46 -52
  113. package/dist/esm/sync-labels/presets.d.ts +1 -1
  114. package/dist/esm/sync-labels/presets.js +40 -43
  115. package/dist/esm/sync-labels/resolveLabels.d.ts +1 -1
  116. package/dist/esm/sync-labels/resolveLabels.js +28 -35
  117. package/dist/esm/sync-labels/syncCommand.js +30 -31
  118. package/dist/esm/sync-labels/templates.d.ts +1 -1
  119. package/dist/esm/sync-labels/templates.js +23 -26
  120. package/dist/esm/sync-labels/types.js +1 -0
  121. package/dist/esm/syncWorkTypes.js +83 -84
  122. package/dist/esm/tagCommand.js +12 -14
  123. package/dist/esm/typeGuards.js +4 -8
  124. package/dist/esm/types.js +66 -60
  125. package/dist/esm/validateConfig.d.ts +1 -1
  126. package/dist/esm/validateConfig.js +109 -115
  127. package/dist/esm/validateOnlyExcludesStrandedDependents.d.ts +2 -2
  128. package/dist/esm/validateOnlyExcludesStrandedDependents.js +97 -91
  129. package/dist/esm/validateOverridesCommand.d.ts +2 -2
  130. package/dist/esm/validateOverridesCommand.js +110 -111
  131. package/dist/esm/workTypesData.js +57 -60
  132. package/dist/esm/workTypesUtils.js +16 -21
  133. package/dist/esm/writeReleaseNotesPreviews.js +55 -57
  134. package/package.json +2 -2
@@ -1,114 +1,116 @@
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";
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 = "https://raw.githubusercontent.com/williamthorsen/codeassembly/main/packages/agents/content/skills/_data/work-types.json";
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
- const moduleDir = dirname(fileURLToPath(import.meta.url));
9
- return resolve(moduleDir, "work-types.json");
8
+ const moduleDir = dirname(fileURLToPath(import.meta.url));
9
+ return resolve(moduleDir, 'work-types.json');
10
10
  }
11
- 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
- } catch (error) {
20
- return {
21
- exitCode: 3,
22
- message: `Local work-types.json is not valid JSON: ${errorMessage(error)}`
23
- };
24
- }
25
- const fetchInit = buildFetchInit();
26
- let response;
27
- try {
28
- response = fetchInit === void 0 ? await fetcher(url) : await fetcher(url, fetchInit);
29
- } catch (error) {
30
- return {
31
- exitCode: 2,
32
- message: formatErrorLine(`Failed to fetch upstream work-types.json: ${errorMessage(error)}`)
33
- };
34
- }
35
- if (response.status === 404) {
36
- return {
37
- exitCode: 0,
38
- message: `Upstream work-types.json not yet published at ${url}; skipping drift check.`
39
- };
40
- }
41
- if (!response.ok) {
42
- return {
43
- exitCode: 2,
44
- message: formatErrorLine(
45
- `Failed to fetch upstream work-types.json: HTTP ${response.status} ${response.statusText}`
46
- )
47
- };
48
- }
49
- const upstreamText = await response.text();
50
- let upstreamJson;
51
- try {
52
- upstreamJson = JSON.parse(upstreamText);
53
- } catch (error) {
54
- return {
55
- exitCode: 3,
56
- message: `Upstream work-types.json is not valid JSON: ${errorMessage(error)}`
57
- };
58
- }
59
- if (!hasExpectedTopLevelShape(upstreamJson)) {
60
- return {
61
- exitCode: 3,
62
- message: "Upstream work-types.json does not match the expected schema shape (missing `tiers` or `types`)."
63
- };
64
- }
65
- const normalisedLocal = stripLocalOnlyFields(localJson);
66
- if (deepEqual(normalisedLocal, upstreamJson)) {
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
- exitCode: 0,
69
- message: "Local work-types.json matches upstream."
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
- if (typeof value !== "object" || value === null || Array.isArray(value)) {
81
- return value;
82
- }
83
- const record = { ...value };
84
- delete record.$schema;
85
- return record;
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
- if (a === b) return true;
89
- if (typeof a !== typeof b) return false;
90
- if (a === null || b === null) return a === b;
91
- if (Array.isArray(a) && Array.isArray(b)) {
92
- if (a.length !== b.length) return false;
93
- for (let i = 0; i < a.length; i++) {
94
- if (!deepEqual(a[i], b[i])) return false;
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
- return true;
97
- }
98
- if (typeof a === "object" && typeof b === "object" && !Array.isArray(a) && !Array.isArray(b)) {
99
- const aRecord = { ...a };
100
- const bRecord = { ...b };
101
- const aKeys = Object.keys(aRecord);
102
- const bKeys = Object.keys(bRecord);
103
- if (aKeys.length !== bKeys.length) return false;
104
- for (const key of aKeys) {
105
- if (!deepEqual(aRecord[key], bRecord[key])) return false;
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 true;
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.ts';
2
- import type { PolicyViolation } from './types.ts';
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
- 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 };
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 "node:child_process";
2
- import { readFileSync } from "node:fs";
3
- import { parseArgsOrExit } from "@williamthorsen/nmr-core";
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
- dryRun: { long: "--dry-run", type: "boolean" }
6
+ dryRun: { long: '--dry-run', type: 'boolean' },
7
7
  };
8
- function commitCommand(argv) {
9
- const dryRun = parseArgsOrExit(argv, commitFlagSchema).flags.dryRun;
10
- let tagsContent;
11
- try {
12
- tagsContent = readFileSync(RELEASE_TAGS_FILE, "utf8");
13
- } catch {
14
- throw new Error("No tags file found. Run `release-kit prepare` first.");
15
- }
16
- const tags = tagsContent.split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
17
- if (tags.length === 0) {
18
- throw new Error("Tags file is empty. Run `release-kit prepare` first.");
19
- }
20
- let summary = "";
21
- try {
22
- summary = readFileSync(RELEASE_SUMMARY_FILE, "utf8").trim();
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
- const status = execFileSync("git", ["status", "--porcelain"], { encoding: "utf8" });
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
- return;
46
- }
47
- execFileSync("git", ["add", "-A"]);
48
- execFileSync("git", ["commit", "-m", message]);
49
- console.info(`Created release commit: ${title}`);
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
- 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
- };
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
- 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;
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 "node:child_process";
2
- import { existsSync } from "node:fs";
3
- import { join } from "node:path";
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
- 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 === void 0) {
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 === void 0) {
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 === void 0 ? {} : { sectionOrder }
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 (result.status === "created") {
54
- created.push(tag);
55
- } else {
56
- skipped.push({ tag, reason: result.reason });
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 "@williamthorsen/nmr-core";
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
- dryRun: { long: "--dry-run", type: "boolean" },
8
- tags: { long: "--tags", type: "string" }
7
+ dryRun: { long: '--dry-run', type: 'boolean' },
8
+ tags: { long: '--tags', type: 'string' },
9
9
  };
10
- 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
- } catch (error) {
20
- reportError(`Failed to create GitHub Releases: ${error instanceof Error ? error.message : String(error)}`);
21
- process.exit(1);
22
- }
23
- if (outcome.skipped.length > 0) {
24
- const formatted = outcome.skipped.map((s) => `${s.tag} (${s.reason})`).join(", ");
25
- console.info(`Skipped ${outcome.skipped.length} tag(s) with no releasable content: ${formatted}.`);
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
- };