@williamthorsen/release-kit 4.5.1 → 4.6.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 (52) hide show
  1. package/CHANGELOG.md +115 -100
  2. package/LICENSE +4 -4
  3. package/cliff.toml.template +66 -54
  4. package/dist/esm/.cache +1 -1
  5. package/dist/esm/assertCleanWorkingTree.d.ts +1 -0
  6. package/dist/esm/assertCleanWorkingTree.js +15 -0
  7. package/dist/esm/bin/release-kit.js +29 -6
  8. package/dist/esm/changelogJsonUtils.d.ts +4 -0
  9. package/dist/esm/changelogJsonUtils.js +29 -0
  10. package/dist/esm/createGithubRelease.d.ts +11 -0
  11. package/dist/esm/createGithubRelease.js +54 -0
  12. package/dist/esm/defaults.d.ts +3 -1
  13. package/dist/esm/defaults.js +14 -0
  14. package/dist/esm/generateChangelogJson.d.ts +7 -0
  15. package/dist/esm/generateChangelogJson.js +196 -0
  16. package/dist/esm/generateChangelogs.js +25 -9
  17. package/dist/esm/githubReleaseCommand.d.ts +1 -0
  18. package/dist/esm/githubReleaseCommand.js +35 -0
  19. package/dist/esm/index.d.ts +11 -3
  20. package/dist/esm/index.js +27 -3
  21. package/dist/esm/injectReleaseNotesIntoReadme.d.ts +2 -0
  22. package/dist/esm/injectReleaseNotesIntoReadme.js +42 -0
  23. package/dist/esm/injectSection.d.ts +1 -0
  24. package/dist/esm/injectSection.js +32 -0
  25. package/dist/esm/loadConfig.js +35 -3
  26. package/dist/esm/parseCommitMessage.js +1 -1
  27. package/dist/esm/prepareCommand.d.ts +1 -0
  28. package/dist/esm/prepareCommand.js +54 -20
  29. package/dist/esm/publish.d.ts +1 -1
  30. package/dist/esm/publish.js +7 -26
  31. package/dist/esm/publishCommand.js +49 -29
  32. package/dist/esm/releasePrepare.js +12 -1
  33. package/dist/esm/releasePrepareMono.js +23 -0
  34. package/dist/esm/renderReleaseNotes.d.ts +8 -0
  35. package/dist/esm/renderReleaseNotes.js +40 -0
  36. package/dist/esm/resolveCommandTags.d.ts +2 -0
  37. package/dist/esm/resolveCommandTags.js +36 -0
  38. package/dist/esm/resolveReleaseNotesConfig.d.ts +6 -0
  39. package/dist/esm/resolveReleaseNotesConfig.js +37 -0
  40. package/dist/esm/sync-labels/generateCommand.d.ts +1 -1
  41. package/dist/esm/sync-labels/generateCommand.js +12 -6
  42. package/dist/esm/sync-labels/presets.d.ts +1 -0
  43. package/dist/esm/sync-labels/presets.js +10 -0
  44. package/dist/esm/typeGuards.d.ts +1 -0
  45. package/dist/esm/typeGuards.js +5 -1
  46. package/dist/esm/types.d.ts +29 -0
  47. package/dist/esm/validateConfig.d.ts +1 -0
  48. package/dist/esm/validateConfig.js +89 -6
  49. package/dist/esm/version.d.ts +1 -1
  50. package/dist/esm/version.js +1 -1
  51. package/package.json +4 -3
  52. package/presets/labels/common.yaml +43 -0
@@ -1,3 +1,4 @@
1
+ import { createHash } from "node:crypto";
1
2
  import { existsSync, readFileSync } from "node:fs";
2
3
  import { resolve } from "node:path";
3
4
  import { load } from "js-yaml";
@@ -7,6 +8,14 @@ function resolvePresetPath(presetName) {
7
8
  const root = findPackageRoot(import.meta.url);
8
9
  return resolve(root, "presets", "labels", `${presetName}.yaml`);
9
10
  }
11
+ function hashPresetFile(presetName) {
12
+ const presetPath = resolvePresetPath(presetName);
13
+ if (!existsSync(presetPath)) {
14
+ throw new Error(`Unknown preset "${presetName}". No file found at ${presetPath}`);
15
+ }
16
+ const content = readFileSync(presetPath, "utf8");
17
+ return createHash("sha256").update(content).digest("hex");
18
+ }
10
19
  function loadPreset(presetName) {
11
20
  const presetPath = resolvePresetPath(presetName);
12
21
  if (!existsSync(presetPath)) {
@@ -36,5 +45,6 @@ function loadPreset(presetName) {
36
45
  return labels;
37
46
  }
38
47
  export {
48
+ hashPresetFile,
39
49
  loadPreset
40
50
  };
@@ -1 +1,2 @@
1
1
  export declare function isRecord(value: unknown): value is Record<string, unknown>;
2
+ export declare function isUnknownArray(value: unknown): value is unknown[];
@@ -1,6 +1,10 @@
1
1
  function isRecord(value) {
2
2
  return typeof value === "object" && value !== null && !Array.isArray(value);
3
3
  }
4
+ function isUnknownArray(value) {
5
+ return Array.isArray(value);
6
+ }
4
7
  export {
5
- isRecord
8
+ isRecord,
9
+ isUnknownArray
6
10
  };
@@ -1,4 +1,27 @@
1
1
  export type ReleaseType = 'major' | 'minor' | 'patch';
2
+ export type ChangelogAudience = 'all' | 'dev';
3
+ export interface ChangelogItem {
4
+ description: string;
5
+ }
6
+ export interface ChangelogSection {
7
+ title: string;
8
+ audience: ChangelogAudience;
9
+ items: ChangelogItem[];
10
+ }
11
+ export interface ChangelogEntry {
12
+ version: string;
13
+ date: string;
14
+ sections: ChangelogSection[];
15
+ }
16
+ export interface ChangelogJsonConfig {
17
+ enabled: boolean;
18
+ outputPath: string;
19
+ devOnlySections: string[];
20
+ }
21
+ export interface ReleaseNotesConfig {
22
+ shouldInjectIntoReadme: boolean;
23
+ shouldCreateGithubRelease: boolean;
24
+ }
2
25
  export interface PropagationSource {
3
26
  packageName: string;
4
27
  newVersion: string;
@@ -51,6 +74,8 @@ export interface ReleaseKitConfig {
51
74
  formatCommand?: string;
52
75
  cliffConfigPath?: string;
53
76
  scopeAliases?: Record<string, string>;
77
+ changelogJson?: Partial<ChangelogJsonConfig>;
78
+ releaseNotes?: Partial<ReleaseNotesConfig>;
54
79
  }
55
80
  export interface ComponentOverride {
56
81
  dir: string;
@@ -82,6 +107,8 @@ export interface MonorepoReleaseConfig {
82
107
  formatCommand?: string;
83
108
  cliffConfigPath?: string;
84
109
  scopeAliases?: Record<string, string>;
110
+ changelogJson: ChangelogJsonConfig;
111
+ releaseNotes: ReleaseNotesConfig;
85
112
  }
86
113
  export interface ReleaseConfig {
87
114
  tagPrefix: string;
@@ -92,4 +119,6 @@ export interface ReleaseConfig {
92
119
  formatCommand?: string;
93
120
  cliffConfigPath?: string;
94
121
  scopeAliases?: Record<string, string>;
122
+ changelogJson: ChangelogJsonConfig;
123
+ releaseNotes: ReleaseNotesConfig;
95
124
  }
@@ -2,4 +2,5 @@ import type { ReleaseKitConfig } from './types.ts';
2
2
  export declare function validateConfig(raw: unknown): {
3
3
  config: ReleaseKitConfig;
4
4
  errors: string[];
5
+ warnings: string[];
5
6
  };
@@ -2,29 +2,112 @@ import { isRecord } from "./typeGuards.js";
2
2
  function validateConfig(raw) {
3
3
  const errors = [];
4
4
  if (!isRecord(raw)) {
5
- return { config: {}, errors: ["Config must be an object"] };
5
+ return { config: {}, errors: ["Config must be an object"], warnings: [] };
6
6
  }
7
7
  const config = {};
8
8
  const knownFields = /* @__PURE__ */ new Set([
9
+ "changelogJson",
10
+ "cliffConfigPath",
9
11
  "components",
10
- "versionPatterns",
11
- "workTypes",
12
12
  "formatCommand",
13
- "cliffConfigPath",
14
- "scopeAliases"
13
+ "releaseNotes",
14
+ "scopeAliases",
15
+ "versionPatterns",
16
+ "workTypes"
15
17
  ]);
16
18
  for (const key of Object.keys(raw)) {
17
19
  if (!knownFields.has(key)) {
18
20
  errors.push(`Unknown field: '${key}'`);
19
21
  }
20
22
  }
23
+ validateChangelogJson(raw.changelogJson, config, errors);
21
24
  validateComponents(raw.components, config, errors);
25
+ validateReleaseNotes(raw.releaseNotes, config, errors);
22
26
  validateVersionPatterns(raw.versionPatterns, config, errors);
23
27
  validateWorkTypes(raw.workTypes, config, errors);
24
28
  validateStringField("formatCommand", raw.formatCommand, config, errors);
25
29
  validateStringField("cliffConfigPath", raw.cliffConfigPath, config, errors);
26
30
  validateScopeAliases(raw.scopeAliases, config, errors);
27
- return { config, errors };
31
+ const warnings = [];
32
+ const changelogJsonEnabled = config.changelogJson?.enabled ?? true;
33
+ if (!changelogJsonEnabled) {
34
+ if (config.releaseNotes?.shouldCreateGithubRelease) {
35
+ warnings.push(
36
+ "releaseNotes.shouldCreateGithubRelease is enabled but changelogJson.enabled is false; GitHub Releases will be skipped at runtime"
37
+ );
38
+ }
39
+ if (config.releaseNotes?.shouldInjectIntoReadme) {
40
+ warnings.push(
41
+ "releaseNotes.shouldInjectIntoReadme is enabled but changelogJson.enabled is false; README injection will be skipped at runtime"
42
+ );
43
+ }
44
+ }
45
+ return { config, errors, warnings };
46
+ }
47
+ function validateChangelogJson(value, config, errors) {
48
+ if (value === void 0) return;
49
+ if (!isRecord(value)) {
50
+ errors.push("'changelogJson' must be an object");
51
+ return;
52
+ }
53
+ const knownChangelogJsonFields = /* @__PURE__ */ new Set(["enabled", "outputPath", "devOnlySections"]);
54
+ for (const key of Object.keys(value)) {
55
+ if (!knownChangelogJsonFields.has(key)) {
56
+ errors.push(`changelogJson: unknown field '${key}'`);
57
+ }
58
+ }
59
+ const result = {};
60
+ if (value.enabled !== void 0) {
61
+ if (typeof value.enabled === "boolean") {
62
+ result.enabled = value.enabled;
63
+ } else {
64
+ errors.push("changelogJson.enabled: must be a boolean");
65
+ }
66
+ }
67
+ if (value.outputPath !== void 0) {
68
+ if (typeof value.outputPath === "string") {
69
+ result.outputPath = value.outputPath;
70
+ } else {
71
+ errors.push("changelogJson.outputPath: must be a string");
72
+ }
73
+ }
74
+ if (value.devOnlySections !== void 0) {
75
+ if (isStringArray(value.devOnlySections)) {
76
+ result.devOnlySections = value.devOnlySections;
77
+ } else {
78
+ errors.push("changelogJson.devOnlySections: must be a string array");
79
+ }
80
+ }
81
+ config.changelogJson = result;
82
+ }
83
+ function validateReleaseNotes(value, config, errors) {
84
+ if (value === void 0) return;
85
+ if (!isRecord(value)) {
86
+ errors.push("'releaseNotes' must be an object");
87
+ return;
88
+ }
89
+ const knownReleaseNotesFields = /* @__PURE__ */ new Set(["shouldInjectIntoReadme", "shouldCreateGithubRelease"]);
90
+ for (const key of Object.keys(value)) {
91
+ if (!knownReleaseNotesFields.has(key)) {
92
+ errors.push(`releaseNotes: unknown field '${key}'`);
93
+ }
94
+ }
95
+ const result = {};
96
+ if (value.shouldInjectIntoReadme !== void 0) {
97
+ if (typeof value.shouldInjectIntoReadme === "boolean") {
98
+ result.shouldInjectIntoReadme = value.shouldInjectIntoReadme;
99
+ } else {
100
+ errors.push("releaseNotes.shouldInjectIntoReadme: must be a boolean");
101
+ }
102
+ }
103
+ if (value.shouldCreateGithubRelease !== void 0) {
104
+ if (typeof value.shouldCreateGithubRelease === "boolean") {
105
+ result.shouldCreateGithubRelease = value.shouldCreateGithubRelease;
106
+ } else {
107
+ errors.push("releaseNotes.shouldCreateGithubRelease: must be a boolean");
108
+ }
109
+ }
110
+ config.releaseNotes = result;
28
111
  }
29
112
  function isStringArray(value) {
30
113
  return Array.isArray(value) && value.every((item) => typeof item === "string");
@@ -1 +1 @@
1
- export declare const VERSION = "4.5.1";
1
+ export declare const VERSION = "4.6.0";
@@ -1,4 +1,4 @@
1
- const VERSION = "4.5.1";
1
+ const VERSION = "4.6.0";
2
2
  export {
3
3
  VERSION
4
4
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@williamthorsen/release-kit",
3
- "version": "4.5.1",
3
+ "version": "4.6.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",
@@ -12,7 +12,7 @@
12
12
  "url": "https://github.com/williamthorsen/node-monorepo-tools.git",
13
13
  "directory": "packages/release-kit"
14
14
  },
15
- "license": "UNLICENSED",
15
+ "license": "ISC",
16
16
  "author": "William Thorsen <william@thorsen.dev> (https://github.com/williamthorsen)",
17
17
  "type": "module",
18
18
  "exports": {
@@ -34,7 +34,8 @@
34
34
  "glob": "13.0.6",
35
35
  "jiti": "2.6.1",
36
36
  "js-yaml": "4.1.1",
37
- "@williamthorsen/node-monorepo-core": "0.2.5"
37
+ "json-stringify-pretty-compact": "4.0.0",
38
+ "@williamthorsen/node-monorepo-core": "0.2.6"
38
39
  },
39
40
  "devDependencies": {
40
41
  "@types/js-yaml": "4.0.9",
@@ -11,9 +11,21 @@
11
11
  - name: documentation
12
12
  color: a2eeef
13
13
  description: Improvements or additions to documentation
14
+ - name: deprecation
15
+ color: fbca04
16
+ description: Marks functionality for future removal
14
17
  - name: feature
15
18
  color: '0075ca'
16
19
  description: Added or improved external functionality
20
+ - name: fix
21
+ color: d73a4a
22
+ description: Fixes a bug
23
+ - name: performance
24
+ color: '0075ca'
25
+ description: Improves performance without changing behavior
26
+ - name: security
27
+ color: b60205
28
+ description: Security vulnerability or hardening
17
29
  - name: spike
18
30
  color: '000000'
19
31
  description: Investigation
@@ -30,6 +42,9 @@
30
42
  description: Further information is needed
31
43
 
32
44
  # Refactoring
45
+ - name: ci
46
+ color: edc287
47
+ description: CI/CD tooling & configuration
33
48
  - name: dependencies
34
49
  color: edc287
35
50
  description: Change to dependencies
@@ -46,6 +61,34 @@
46
61
  color: 1d76db
47
62
  description: Changed internal functionality
48
63
 
64
+ # Priority
65
+ - name: 'priority:critical'
66
+ color: b60205
67
+ description: Demands immediate attention
68
+ - name: 'priority:high'
69
+ color: d93f0b
70
+ description: Address soon
71
+ - name: 'priority:medium'
72
+ color: fbca04
73
+ description: No rush but don't forget
74
+ - name: 'priority:low'
75
+ color: cfd3d7
76
+ description: Get to it eventually
77
+
78
+ # Value
79
+ - name: 'value:must-have'
80
+ color: 7057ff
81
+ description: Essential for the application to fulfill its purpose
82
+ - name: 'value:should-have'
83
+ color: 8d83fc
84
+ description: Important but the application is usable without it
85
+ - name: 'value:nice-to-have'
86
+ color: a8b2f8
87
+ description: A nice-to-have enhancement
88
+ - name: 'value:questionable'
89
+ color: c5def5
90
+ description: Decision needed on whether this would add sufficient value
91
+
49
92
  # Triage
50
93
  - name: duplicate
51
94
  color: cfd3d7