@williamthorsen/release-kit 4.8.0 → 5.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.
Files changed (100) hide show
  1. package/CHANGELOG.md +134 -4
  2. package/README.md +404 -40
  3. package/cliff.toml.template +2 -1
  4. package/dist/esm/.cache +1 -1
  5. package/dist/esm/assertCleanWorkingTree.js +1 -1
  6. package/dist/esm/bin/release-kit.js +45 -14
  7. package/dist/esm/buildChangelogEntries.d.ts +3 -0
  8. package/dist/esm/{generateChangelogJson.js → buildChangelogEntries.js} +40 -77
  9. package/dist/esm/buildDependencyGraph.d.ts +4 -3
  10. package/dist/esm/buildDependencyGraph.js +18 -11
  11. package/dist/esm/buildReleaseSummary.js +12 -4
  12. package/dist/esm/buildSyntheticChangelogEntry.d.ts +5 -0
  13. package/dist/esm/buildSyntheticChangelogEntry.js +13 -0
  14. package/dist/esm/bumpAllVersions.d.ts +1 -0
  15. package/dist/esm/bumpAllVersions.js +16 -2
  16. package/dist/esm/bumpVersion.js +3 -0
  17. package/dist/esm/changelogJsonFile.d.ts +4 -0
  18. package/dist/esm/changelogJsonFile.js +68 -0
  19. package/dist/esm/commitCommand.js +1 -1
  20. package/dist/esm/compareVersions.d.ts +1 -0
  21. package/dist/esm/compareVersions.js +27 -0
  22. package/dist/esm/createGithubRelease.d.ts +6 -2
  23. package/dist/esm/createGithubRelease.js +17 -17
  24. package/dist/esm/createGithubReleaseCommand.d.ts +1 -0
  25. package/dist/esm/createGithubReleaseCommand.js +41 -0
  26. package/dist/esm/decideRelease.d.ts +25 -0
  27. package/dist/esm/decideRelease.js +28 -0
  28. package/dist/esm/defaults.d.ts +1 -0
  29. package/dist/esm/defaults.js +7 -3
  30. package/dist/esm/deriveWorkspaceConfig.d.ts +2 -0
  31. package/dist/esm/deriveWorkspaceConfig.js +37 -0
  32. package/dist/esm/detectUndeclaredTagPrefixes.d.ts +7 -0
  33. package/dist/esm/detectUndeclaredTagPrefixes.js +46 -0
  34. package/dist/esm/generateChangelogs.d.ts +1 -1
  35. package/dist/esm/generateChangelogs.js +14 -3
  36. package/dist/esm/getCommitsSinceTarget.d.ts +1 -1
  37. package/dist/esm/getCommitsSinceTarget.js +8 -4
  38. package/dist/esm/index.d.ts +2 -39
  39. package/dist/esm/index.js +0 -75
  40. package/dist/esm/init/initCommand.js +1 -1
  41. package/dist/esm/init/scaffold.d.ts +1 -1
  42. package/dist/esm/init/scaffold.js +8 -5
  43. package/dist/esm/init/templates.d.ts +1 -0
  44. package/dist/esm/init/templates.js +35 -5
  45. package/dist/esm/injectReleaseNotesIntoReadme.d.ts +6 -1
  46. package/dist/esm/injectReleaseNotesIntoReadme.js +20 -7
  47. package/dist/esm/loadConfig.d.ts +12 -2
  48. package/dist/esm/loadConfig.js +161 -14
  49. package/dist/esm/parseRequestedTags.d.ts +1 -0
  50. package/dist/esm/parseRequestedTags.js +10 -0
  51. package/dist/esm/prepareCommand.d.ts +3 -1
  52. package/dist/esm/prepareCommand.js +121 -31
  53. package/dist/esm/previewTagPrefixes.d.ts +30 -0
  54. package/dist/esm/previewTagPrefixes.js +120 -0
  55. package/dist/esm/propagateBumps.d.ts +1 -0
  56. package/dist/esm/propagateBumps.js +1 -1
  57. package/dist/esm/publish.d.ts +0 -1
  58. package/dist/esm/publish.js +3 -3
  59. package/dist/esm/publishCommand.js +18 -14
  60. package/dist/esm/pushCommand.js +5 -4
  61. package/dist/esm/readCurrentVersion.d.ts +1 -0
  62. package/dist/esm/readCurrentVersion.js +21 -0
  63. package/dist/esm/releasePrepare.d.ts +2 -0
  64. package/dist/esm/releasePrepare.js +140 -54
  65. package/dist/esm/releasePrepareMono.js +312 -143
  66. package/dist/esm/releasePrepareProject.d.ts +9 -0
  67. package/dist/esm/releasePrepareProject.js +109 -0
  68. package/dist/esm/renderReleaseNotes.d.ts +1 -0
  69. package/dist/esm/renderReleaseNotes.js +29 -2
  70. package/dist/esm/reportPrepare.js +146 -73
  71. package/dist/esm/resolveCliffConfigPath.js +1 -1
  72. package/dist/esm/resolveCommandTags.d.ts +1 -1
  73. package/dist/esm/resolveCommandTags.js +17 -13
  74. package/dist/esm/resolveReleaseNotesConfig.d.ts +8 -1
  75. package/dist/esm/resolveReleaseNotesConfig.js +17 -7
  76. package/dist/esm/resolveReleaseTags.d.ts +2 -1
  77. package/dist/esm/resolveReleaseTags.js +19 -14
  78. package/dist/esm/showTagPrefixesCommand.d.ts +1 -0
  79. package/dist/esm/showTagPrefixesCommand.js +84 -0
  80. package/dist/esm/sync-labels/initCommand.js +1 -1
  81. package/dist/esm/sync-labels/presets.js +1 -1
  82. package/dist/esm/tagCommand.js +1 -1
  83. package/dist/esm/types.d.ts +77 -19
  84. package/dist/esm/validateConfig.js +205 -36
  85. package/dist/esm/validateOnlyExcludesStrandedDependents.d.ts +14 -0
  86. package/dist/esm/validateOnlyExcludesStrandedDependents.js +109 -0
  87. package/dist/esm/version.d.ts +1 -1
  88. package/dist/esm/version.js +1 -1
  89. package/dist/esm/writeReleaseNotesPreviews.d.ts +18 -0
  90. package/dist/esm/writeReleaseNotesPreviews.js +65 -0
  91. package/package.json +5 -2
  92. package/presets/labels/common.yaml +9 -6
  93. package/schemas/label-map.json +24 -0
  94. package/dist/esm/component.d.ts +0 -2
  95. package/dist/esm/component.js +0 -14
  96. package/dist/esm/findPackageRoot.d.ts +0 -1
  97. package/dist/esm/findPackageRoot.js +0 -17
  98. package/dist/esm/generateChangelogJson.d.ts +0 -7
  99. package/dist/esm/githubReleaseCommand.d.ts +0 -1
  100. package/dist/esm/githubReleaseCommand.js +0 -35
@@ -0,0 +1,109 @@
1
+ function validateOnlyExcludesStrandedDependents(workspaces, only, graph, hasCommits) {
2
+ const probeCommits = memoizeCommitsProbe(hasCommits);
3
+ const workspaceByDir = new Map(workspaces.map((w) => [w.dir, w]));
4
+ const released = computeReleasedSet(only, workspaceByDir, graph, probeCommits);
5
+ const violations = collectStrandedViolations(released, new Set(only), graph, probeCommits);
6
+ if (violations.length === 0) return void 0;
7
+ violations.sort((a, b) => a.dir.localeCompare(b.dir));
8
+ return violations;
9
+ }
10
+ function memoizeCommitsProbe(probe) {
11
+ const cache = /* @__PURE__ */ new Map();
12
+ return (workspace) => {
13
+ const cached = cache.get(workspace.dir);
14
+ if (cached !== void 0) return cached;
15
+ const result = probe(workspace);
16
+ cache.set(workspace.dir, result);
17
+ return result;
18
+ };
19
+ }
20
+ function computeReleasedSet(only, workspaceByDir, graph, probeCommits) {
21
+ const released = /* @__PURE__ */ new Set();
22
+ for (const dir of only) {
23
+ const workspace = workspaceByDir.get(dir);
24
+ if (workspace !== void 0 && probeCommits(workspace).has) {
25
+ released.add(dir);
26
+ }
27
+ }
28
+ let changed = true;
29
+ while (changed) {
30
+ changed = false;
31
+ for (const dir of only) {
32
+ if (released.has(dir)) continue;
33
+ if (hasDependencyIn(dir, graph, released)) {
34
+ released.add(dir);
35
+ changed = true;
36
+ }
37
+ }
38
+ }
39
+ return released;
40
+ }
41
+ function hasDependencyIn(dir, graph, released) {
42
+ const forwardDeps = graph.dependenciesOf.get(dir);
43
+ if (forwardDeps === void 0) return false;
44
+ for (const depPackageName of forwardDeps) {
45
+ const depDir = graph.packageNameToDir.get(depPackageName);
46
+ if (depDir !== void 0 && released.has(depDir)) return true;
47
+ }
48
+ return false;
49
+ }
50
+ function collectStrandedViolations(released, onlySet, graph, probeCommits) {
51
+ const violations = [];
52
+ const violationDirs = /* @__PURE__ */ new Set();
53
+ const visited = /* @__PURE__ */ new Set();
54
+ const queue = [];
55
+ for (const dir of released) {
56
+ const packageName = graph.dirToPackageName.get(dir);
57
+ if (packageName !== void 0) {
58
+ queue.push({ packageName, attributionRoot: dir });
59
+ }
60
+ }
61
+ while (queue.length > 0) {
62
+ const item = queue.shift();
63
+ if (item === void 0) break;
64
+ if (visited.has(item.packageName)) continue;
65
+ visited.add(item.packageName);
66
+ const dependents = graph.dependentsOf.get(item.packageName);
67
+ if (dependents === void 0) continue;
68
+ for (const dependent of dependents) {
69
+ visitDependent(dependent, item.attributionRoot, {
70
+ released,
71
+ onlySet,
72
+ graph,
73
+ probeCommits,
74
+ queue,
75
+ violations,
76
+ violationDirs
77
+ });
78
+ }
79
+ }
80
+ return violations;
81
+ }
82
+ function visitDependent(dependent, attributionRoot, ctx) {
83
+ if (ctx.onlySet.has(dependent.dir)) {
84
+ if (ctx.released.has(dependent.dir)) {
85
+ enqueueDependent(dependent, ctx);
86
+ }
87
+ return;
88
+ }
89
+ const probe = ctx.probeCommits(dependent);
90
+ if (!probe.has) return;
91
+ if (!ctx.violationDirs.has(dependent.dir)) {
92
+ ctx.violationDirs.add(dependent.dir);
93
+ ctx.violations.push({
94
+ dir: dependent.dir,
95
+ downstreamOf: attributionRoot,
96
+ tag: probe.tag
97
+ });
98
+ }
99
+ enqueueDependent(dependent, ctx);
100
+ }
101
+ function enqueueDependent(dependent, ctx) {
102
+ const packageName = ctx.graph.dirToPackageName.get(dependent.dir);
103
+ if (packageName !== void 0) {
104
+ ctx.queue.push({ packageName, attributionRoot: dependent.dir });
105
+ }
106
+ }
107
+ export {
108
+ validateOnlyExcludesStrandedDependents
109
+ };
@@ -1 +1 @@
1
- export declare const VERSION = "4.8.0";
1
+ export declare const VERSION = "5.1.0";
@@ -1,4 +1,4 @@
1
- const VERSION = "4.8.0";
1
+ const VERSION = "5.1.0";
2
2
  export {
3
3
  VERSION
4
4
  };
@@ -0,0 +1,18 @@
1
+ export interface WriteReleaseNotesPreviewsOptions {
2
+ workspacePath: string;
3
+ tag: string;
4
+ changelogJsonPath: string;
5
+ sectionOrder: string[];
6
+ dryRun: boolean;
7
+ }
8
+ export interface PreviewFileResult {
9
+ filePath: string;
10
+ outcome: 'created' | 'overwritten' | 'skipped-no-readme' | 'failed' | 'dry-run';
11
+ error?: string;
12
+ }
13
+ export interface WriteReleaseNotesPreviewsResult {
14
+ injectedReadme?: PreviewFileResult;
15
+ releaseNotes?: PreviewFileResult;
16
+ renderSkipped: boolean;
17
+ }
18
+ export declare function writeReleaseNotesPreviews(options: WriteReleaseNotesPreviewsOptions): WriteReleaseNotesPreviewsResult;
@@ -0,0 +1,65 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { writeFileWithCheck } from "@williamthorsen/nmr-core";
4
+ import { extractVersion } from "./changelogJsonUtils.js";
5
+ import { dim } from "./format.js";
6
+ import { renderInjectedReadme } from "./injectReleaseNotesIntoReadme.js";
7
+ function writeReleaseNotesPreviews(options) {
8
+ const { workspacePath, tag, changelogJsonPath, sectionOrder, dryRun } = options;
9
+ const version = extractVersion(tag);
10
+ const readmePath = path.join(workspacePath, "README.md");
11
+ let readmeExists = existsSync(readmePath);
12
+ let readmeUnreadable = false;
13
+ let readmeContent = "";
14
+ if (readmeExists) {
15
+ try {
16
+ readmeContent = readFileSync(readmePath, "utf8");
17
+ } catch (error) {
18
+ const message = error instanceof Error ? error.message : String(error);
19
+ console.warn(`Warning: failed to read ${readmePath}: ${message}; skipping injected-README preview`);
20
+ readmeExists = false;
21
+ readmeUnreadable = true;
22
+ }
23
+ }
24
+ const rendered = renderInjectedReadme(readmeContent, changelogJsonPath, tag, sectionOrder);
25
+ if (rendered === void 0) {
26
+ return { renderSkipped: true };
27
+ }
28
+ const docsDir = path.join(workspacePath, "docs");
29
+ const readmePreviewPath = path.join(docsDir, `README.v${version}.md`);
30
+ const releaseNotesPreviewPath = path.join(docsDir, `RELEASE_NOTES.v${version}.md`);
31
+ let injectedReadme;
32
+ if (readmeExists) {
33
+ injectedReadme = writePreviewFile(readmePreviewPath, rendered.injectedReadme, dryRun);
34
+ } else {
35
+ if (!readmeUnreadable) {
36
+ console.warn(
37
+ `Warning: ${readmePath} not found; skipping injected-README preview but still writing standalone release notes`
38
+ );
39
+ }
40
+ injectedReadme = { filePath: readmePreviewPath, outcome: "skipped-no-readme" };
41
+ }
42
+ const releaseNotesContent = rendered.releaseNotesMarkdown.endsWith("\n") ? rendered.releaseNotesMarkdown : `${rendered.releaseNotesMarkdown}
43
+ `;
44
+ const releaseNotes = writePreviewFile(releaseNotesPreviewPath, releaseNotesContent, dryRun);
45
+ return { injectedReadme, releaseNotes, renderSkipped: false };
46
+ }
47
+ function writePreviewFile(filePath, content, dryRun) {
48
+ if (dryRun) {
49
+ console.info(dim(` [dry-run] Would write ${filePath}`));
50
+ return { filePath, outcome: "dry-run" };
51
+ }
52
+ const result = writeFileWithCheck(filePath, content, { dryRun: false, overwrite: true });
53
+ if (result.outcome === "failed") {
54
+ console.error(`Error writing ${filePath}: ${result.error ?? "unknown error"}`);
55
+ return { filePath, outcome: "failed", ...result.error === void 0 ? {} : { error: result.error } };
56
+ }
57
+ if (result.outcome === "created" || result.outcome === "overwritten") {
58
+ console.info(dim(` Wrote ${filePath}`));
59
+ return { filePath, outcome: result.outcome };
60
+ }
61
+ return { filePath, outcome: "failed", error: `unexpected outcome: ${result.outcome}` };
62
+ }
63
+ export {
64
+ writeReleaseNotesPreviews
65
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@williamthorsen/release-kit",
3
- "version": "4.8.0",
3
+ "version": "5.1.0",
4
4
  "description": "Version-bumping and changelog-generation toolkit for release workflows",
5
5
  "keywords": [],
6
6
  "homepage": "https://github.com/williamthorsen/node-monorepo-tools/tree/main/packages/release-kit#readme",
@@ -28,6 +28,7 @@
28
28
  "dist/*",
29
29
  "cliff.toml.template",
30
30
  "presets/**",
31
+ "schemas/**",
31
32
  "CHANGELOG.md"
32
33
  ],
33
34
  "dependencies": {
@@ -35,10 +36,12 @@
35
36
  "jiti": "2.6.1",
36
37
  "js-yaml": "4.1.1",
37
38
  "json-stringify-pretty-compact": "4.0.0",
38
- "@williamthorsen/node-monorepo-core": "0.2.7"
39
+ "semver": "7.7.4",
40
+ "@williamthorsen/nmr-core": "0.3.0"
39
41
  },
40
42
  "devDependencies": {
41
43
  "@types/js-yaml": "4.0.9",
44
+ "@types/semver": "7.7.1",
42
45
  "smol-toml": "1.6.1"
43
46
  },
44
47
  "engines": {
@@ -8,18 +8,21 @@
8
8
  - name: bug
9
9
  color: d73a4a
10
10
  description: "Something isn't working"
11
- - name: documentation
12
- color: a2eeef
13
- description: Improvements or additions to documentation
14
11
  - name: deprecation
15
12
  color: fbca04
16
13
  description: Marks functionality for future removal
14
+ - name: documentation
15
+ color: a2eeef
16
+ description: Improvements or additions to documentation
17
17
  - name: feature
18
18
  color: '0075ca'
19
19
  description: Added or improved external functionality
20
20
  - name: fix
21
21
  color: d73a4a
22
22
  description: Fixes a bug
23
+ - name: operational
24
+ color: '000000'
25
+ description: Actions outside the codebase
23
26
  - name: performance
24
27
  color: '0075ca'
25
28
  description: Improves performance without changing behavior
@@ -48,6 +51,9 @@
48
51
  - name: dependencies
49
52
  color: edc287
50
53
  description: Change to dependencies
54
+ - name: internal
55
+ color: 1d76db
56
+ description: Internal change without external impact
51
57
  - name: refactoring
52
58
  color: edc287
53
59
  description: Improvement to code without change in functionality
@@ -57,9 +63,6 @@
57
63
  - name: tooling
58
64
  color: edc287
59
65
  description: Development tools
60
- - name: utility
61
- color: 1d76db
62
- description: Changed internal functionality
63
66
 
64
67
  # Priority
65
68
  - name: 'priority:critical'
@@ -0,0 +1,24 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "title": "label-map",
4
+ "description": "Maps commit-prefix scopes and types to GitHub label names. Consumed by agent skills, CI tooling, and any script that needs to translate commit conventions into label operations.",
5
+ "type": "object",
6
+ "required": ["types", "scopes"],
7
+ "additionalProperties": false,
8
+ "properties": {
9
+ "$schema": {
10
+ "type": "string",
11
+ "description": "JSON Schema reference for editor support."
12
+ },
13
+ "types": {
14
+ "type": "object",
15
+ "description": "Maps commit-type prefix (e.g., `feat`) to GitHub label name (e.g., `feature`).",
16
+ "additionalProperties": { "type": "string" }
17
+ },
18
+ "scopes": {
19
+ "type": "object",
20
+ "description": "Maps commit-scope prefix (e.g., `audit`) to GitHub label name (e.g., `scope:audit`).",
21
+ "additionalProperties": { "type": "string" }
22
+ }
23
+ }
24
+ }
@@ -1,2 +0,0 @@
1
- import type { ComponentConfig } from './types.ts';
2
- export declare function component(workspacePath: string): ComponentConfig;
@@ -1,14 +0,0 @@
1
- import { basename } from "node:path";
2
- function component(workspacePath) {
3
- const dir = basename(workspacePath);
4
- return {
5
- dir,
6
- tagPrefix: `${dir}-v`,
7
- packageFiles: [`${workspacePath}/package.json`],
8
- changelogPaths: [workspacePath],
9
- paths: [`${workspacePath}/**`]
10
- };
11
- }
12
- export {
13
- component
14
- };
@@ -1 +0,0 @@
1
- export declare function findPackageRoot(fromUrl: string): string;
@@ -1,17 +0,0 @@
1
- import { existsSync } from "node:fs";
2
- import { dirname, resolve } from "node:path";
3
- import { fileURLToPath } from "node:url";
4
- function findPackageRoot(fromUrl) {
5
- let dir = dirname(fileURLToPath(fromUrl));
6
- while (!existsSync(resolve(dir, "package.json"))) {
7
- const parent = dirname(dir);
8
- if (parent === dir) {
9
- throw new Error("Could not find package root from " + fromUrl);
10
- }
11
- dir = parent;
12
- }
13
- return dir;
14
- }
15
- export {
16
- findPackageRoot
17
- };
@@ -1,7 +0,0 @@
1
- import type { GenerateChangelogOptions } from './generateChangelogs.ts';
2
- import type { ReleaseConfig } from './types.ts';
3
- export declare function generateChangelogJson(config: Pick<ReleaseConfig, 'cliffConfigPath' | 'changelogJson'>, changelogPath: string, tag: string, dryRun: boolean, options?: GenerateChangelogOptions): string[];
4
- export declare function generateSyntheticChangelogJson(config: Pick<ReleaseConfig, 'changelogJson'>, changelogPath: string, newVersion: string, date: string, propagatedFrom: Array<{
5
- packageName: string;
6
- newVersion: string;
7
- }>, dryRun: boolean): string[];
@@ -1 +0,0 @@
1
- export declare function githubReleaseCommand(argv: string[]): Promise<void>;
@@ -1,35 +0,0 @@
1
- import { parseArgs, translateParseError } from "@williamthorsen/node-monorepo-core";
2
- import { createGithubReleases } from "./createGithubRelease.js";
3
- import { resolveCommandTags } from "./resolveCommandTags.js";
4
- import { resolveReleaseNotesConfig } from "./resolveReleaseNotesConfig.js";
5
- const githubReleaseFlagSchema = {
6
- dryRun: { long: "--dry-run", type: "boolean" },
7
- only: { long: "--only", type: "string" }
8
- };
9
- async function githubReleaseCommand(argv) {
10
- let parsed;
11
- try {
12
- parsed = parseArgs(argv, githubReleaseFlagSchema);
13
- } catch (error) {
14
- console.error(`Error: ${translateParseError(error)}`);
15
- process.exit(1);
16
- }
17
- const { dryRun } = parsed.flags;
18
- const only = parsed.flags.only?.split(",");
19
- const resolvedTags = await resolveCommandTags(only);
20
- const { releaseNotes, changelogJsonOutputPath } = await resolveReleaseNotesConfig();
21
- try {
22
- createGithubReleases(
23
- resolvedTags,
24
- { ...releaseNotes, shouldCreateGithubRelease: true },
25
- changelogJsonOutputPath,
26
- dryRun
27
- );
28
- } catch (error) {
29
- console.error(`Error creating GitHub Releases: ${error instanceof Error ? error.message : String(error)}`);
30
- process.exit(1);
31
- }
32
- }
33
- export {
34
- githubReleaseCommand
35
- };