@williamthorsen/release-kit 5.0.0 → 5.2.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 (73) hide show
  1. package/CHANGELOG.md +149 -49
  2. package/README.md +275 -78
  3. package/cliff.toml.template +26 -17
  4. package/dist/esm/.cache +1 -1
  5. package/dist/esm/assertCleanWorkingTree.js +1 -1
  6. package/dist/esm/bin/release-kit.js +97 -4
  7. package/dist/esm/buildChangelogEntries.d.ts +4 -0
  8. package/dist/esm/buildChangelogEntries.js +173 -0
  9. package/dist/esm/buildDependencyGraph.d.ts +1 -0
  10. package/dist/esm/buildDependencyGraph.js +8 -1
  11. package/dist/esm/buildReleaseSummary.js +9 -1
  12. package/dist/esm/buildSyntheticChangelogEntry.d.ts +5 -0
  13. package/dist/esm/buildSyntheticChangelogEntry.js +13 -0
  14. package/dist/esm/changelogJsonFile.d.ts +4 -0
  15. package/dist/esm/changelogJsonFile.js +68 -0
  16. package/dist/esm/checkWorkTypesDrift.d.ts +11 -0
  17. package/dist/esm/checkWorkTypesDrift.js +110 -0
  18. package/dist/esm/collectPolicyViolations.d.ts +6 -0
  19. package/dist/esm/collectPolicyViolations.js +15 -0
  20. package/dist/esm/createGithubRelease.d.ts +12 -2
  21. package/dist/esm/createGithubRelease.js +12 -8
  22. package/dist/esm/createGithubReleaseCommand.js +10 -6
  23. package/dist/esm/decideRelease.d.ts +28 -0
  24. package/dist/esm/decideRelease.js +44 -0
  25. package/dist/esm/defaults.d.ts +8 -0
  26. package/dist/esm/defaults.js +43 -20
  27. package/dist/esm/deriveWorkspaceConfig.js +3 -0
  28. package/dist/esm/determineBumpFromCommits.d.ts +6 -1
  29. package/dist/esm/determineBumpFromCommits.js +9 -3
  30. package/dist/esm/generateChangelogs.js +14 -29
  31. package/dist/esm/index.d.ts +2 -43
  32. package/dist/esm/index.js +0 -82
  33. package/dist/esm/init/templates.js +2 -2
  34. package/dist/esm/loadConfig.d.ts +10 -1
  35. package/dist/esm/loadConfig.js +110 -24
  36. package/dist/esm/parseCommitMessage.d.ts +8 -2
  37. package/dist/esm/parseCommitMessage.js +32 -3
  38. package/dist/esm/prepareCommand.js +51 -9
  39. package/dist/esm/publish.d.ts +0 -1
  40. package/dist/esm/publish.js +3 -3
  41. package/dist/esm/publishCommand.js +31 -3
  42. package/dist/esm/releasePrepare.js +109 -41
  43. package/dist/esm/releasePrepareMono.js +156 -87
  44. package/dist/esm/releasePrepareProject.d.ts +9 -0
  45. package/dist/esm/releasePrepareProject.js +121 -0
  46. package/dist/esm/renderReleaseNotes.js +2 -1
  47. package/dist/esm/reportPrepare.js +88 -24
  48. package/dist/esm/resolveCommandTags.js +16 -6
  49. package/dist/esm/resolveReleaseTags.d.ts +8 -1
  50. package/dist/esm/resolveReleaseTags.js +11 -7
  51. package/dist/esm/runGitCliff.d.ts +2 -0
  52. package/dist/esm/runGitCliff.js +27 -0
  53. package/dist/esm/stripEmojiPrefix.d.ts +1 -0
  54. package/dist/esm/stripEmojiPrefix.js +7 -0
  55. package/dist/esm/syncWorkTypes.d.ts +10 -0
  56. package/dist/esm/syncWorkTypes.js +90 -0
  57. package/dist/esm/types.d.ts +72 -14
  58. package/dist/esm/validateConfig.js +26 -0
  59. package/dist/esm/validateOnlyExcludesStrandedDependents.d.ts +14 -0
  60. package/dist/esm/validateOnlyExcludesStrandedDependents.js +109 -0
  61. package/dist/esm/work-types.json +127 -0
  62. package/dist/esm/work-types.schema.json +73 -0
  63. package/dist/esm/workTypesData.d.ts +14 -0
  64. package/dist/esm/workTypesData.js +59 -0
  65. package/dist/esm/workTypesUtils.d.ts +5 -0
  66. package/dist/esm/workTypesUtils.js +16 -0
  67. package/package.json +9 -3
  68. package/presets/labels/common.yaml +9 -6
  69. package/schemas/label-map.json +24 -0
  70. package/dist/esm/generateChangelogJson.d.ts +0 -7
  71. package/dist/esm/generateChangelogJson.js +0 -232
  72. package/dist/esm/version.d.ts +0 -1
  73. package/dist/esm/version.js +0 -4
@@ -3,6 +3,7 @@ export type ChangelogAudience = 'all' | 'dev';
3
3
  export interface ChangelogItem {
4
4
  description: string;
5
5
  body?: string;
6
+ breaking?: boolean;
6
7
  }
7
8
  export interface ChangelogSection {
8
9
  title: string;
@@ -22,6 +23,12 @@ export interface ChangelogJsonConfig {
22
23
  export interface ReleaseNotesConfig {
23
24
  shouldInjectIntoReadme: boolean;
24
25
  }
26
+ export interface ProjectConfig {
27
+ tagPrefix?: string;
28
+ }
29
+ export interface ResolvedProjectConfig {
30
+ tagPrefix: string;
31
+ }
25
32
  export interface PropagationSource {
26
33
  packageName: string;
27
34
  newVersion: string;
@@ -31,24 +38,68 @@ export interface BumpResult {
31
38
  newVersion: string;
32
39
  files: string[];
33
40
  }
34
- export interface WorkspacePrepareResult {
35
- name?: string | undefined;
36
- status: 'released' | 'skipped';
37
- previousTag?: string | undefined;
41
+ export interface PolicyViolation {
42
+ commitHash: string;
43
+ commitSubject: string;
44
+ type: string;
45
+ surface: 'prefix' | 'body';
46
+ }
47
+ export interface ReleasedWorkspaceResult {
48
+ status: 'released';
49
+ name?: string;
50
+ previousTag?: string;
38
51
  commitCount: number;
39
- parsedCommitCount?: number | undefined;
40
- releaseType?: ReleaseType | undefined;
41
- currentVersion?: string | undefined;
42
- newVersion?: string | undefined;
43
- tag?: string | undefined;
52
+ parsedCommitCount?: number;
53
+ unparseableCommits?: Commit[];
54
+ policyViolations?: PolicyViolation[];
55
+ releaseType?: ReleaseType;
56
+ currentVersion: string;
57
+ newVersion: string;
58
+ tag: string;
44
59
  bumpedFiles: string[];
45
60
  changelogFiles: string[];
46
- commits?: Commit[] | undefined;
47
- unparseableCommits?: Commit[] | undefined;
48
- propagatedFrom?: PropagationSource[] | undefined;
49
- skipReason?: string | undefined;
50
- setVersion?: string | undefined;
61
+ commits?: Commit[];
62
+ bumpOverride?: ReleaseType;
63
+ propagatedFrom?: PropagationSource[];
64
+ setVersion?: string;
65
+ }
66
+ export interface SkippedWorkspaceResult {
67
+ status: 'skipped';
68
+ name?: string;
69
+ previousTag?: string;
70
+ commitCount: number;
71
+ parsedCommitCount?: number;
72
+ unparseableCommits?: Commit[];
73
+ policyViolations?: PolicyViolation[];
74
+ skipReason: string;
75
+ }
76
+ export type WorkspacePrepareResult = ReleasedWorkspaceResult | SkippedWorkspaceResult;
77
+ export interface ReleasedProjectResult {
78
+ status: 'released';
79
+ previousTag?: string;
80
+ commitCount: number;
81
+ parsedCommitCount: number;
82
+ unparseableCommits?: Commit[];
83
+ policyViolations?: PolicyViolation[];
84
+ releaseType: ReleaseType;
85
+ currentVersion: string;
86
+ newVersion: string;
87
+ tag: string;
88
+ bumpedFiles: string[];
89
+ changelogFiles: string[];
90
+ commits: Commit[];
91
+ bumpOverride?: ReleaseType;
92
+ }
93
+ export interface SkippedProjectResult {
94
+ status: 'skipped';
95
+ previousTag?: string;
96
+ commitCount: number;
97
+ parsedCommitCount: number;
98
+ unparseableCommits?: Commit[];
99
+ policyViolations?: PolicyViolation[];
100
+ skipReason: string;
51
101
  }
102
+ export type ProjectPrepareResult = ReleasedProjectResult | SkippedProjectResult;
52
103
  export interface PrepareResult {
53
104
  workspaces: WorkspacePrepareResult[];
54
105
  tags: string[];
@@ -59,6 +110,7 @@ export interface PrepareResult {
59
110
  } | undefined;
60
111
  dryRun: boolean;
61
112
  warnings?: string[] | undefined;
113
+ project?: ProjectPrepareResult | undefined;
62
114
  }
63
115
  export interface WorkTypeConfig {
64
116
  header: string;
@@ -72,12 +124,14 @@ export interface ReleaseKitConfig {
72
124
  workspaces?: WorkspaceOverride[];
73
125
  versionPatterns?: VersionPatterns;
74
126
  workTypes?: Record<string, WorkTypeConfig>;
127
+ breakingPolicies?: Record<string, 'forbidden' | 'optional' | 'required'>;
75
128
  formatCommand?: string;
76
129
  cliffConfigPath?: string;
77
130
  scopeAliases?: Record<string, string>;
78
131
  changelogJson?: Partial<ChangelogJsonConfig>;
79
132
  releaseNotes?: Partial<ReleaseNotesConfig>;
80
133
  retiredPackages?: RetiredPackage[];
134
+ project?: ProjectConfig;
81
135
  }
82
136
  export interface LegacyIdentity {
83
137
  name: string;
@@ -110,6 +164,7 @@ export interface WorkspaceConfig {
110
164
  name: string;
111
165
  tagPrefix: string;
112
166
  workspacePath: string;
167
+ isPublishable: boolean;
113
168
  packageFiles: string[];
114
169
  changelogPaths: string[];
115
170
  paths: string[];
@@ -119,11 +174,13 @@ export interface MonorepoReleaseConfig {
119
174
  workspaces: WorkspaceConfig[];
120
175
  workTypes?: Record<string, WorkTypeConfig>;
121
176
  versionPatterns?: VersionPatterns;
177
+ breakingPolicies?: Record<string, 'forbidden' | 'optional' | 'required'>;
122
178
  formatCommand?: string;
123
179
  cliffConfigPath?: string;
124
180
  scopeAliases?: Record<string, string>;
125
181
  changelogJson: ChangelogJsonConfig;
126
182
  releaseNotes: ReleaseNotesConfig;
183
+ project?: ResolvedProjectConfig;
127
184
  }
128
185
  export interface ReleaseConfig {
129
186
  tagPrefix: string;
@@ -131,6 +188,7 @@ export interface ReleaseConfig {
131
188
  changelogPaths: string[];
132
189
  workTypes?: Record<string, WorkTypeConfig>;
133
190
  versionPatterns?: VersionPatterns;
191
+ breakingPolicies?: Record<string, 'forbidden' | 'optional' | 'required'>;
134
192
  formatCommand?: string;
135
193
  cliffConfigPath?: string;
136
194
  scopeAliases?: Record<string, string>;
@@ -9,6 +9,7 @@ function validateConfig(raw) {
9
9
  "changelogJson",
10
10
  "cliffConfigPath",
11
11
  "formatCommand",
12
+ "project",
12
13
  "releaseNotes",
13
14
  "retiredPackages",
14
15
  "scopeAliases",
@@ -30,6 +31,7 @@ function validateConfig(raw) {
30
31
  validateStringField("cliffConfigPath", raw.cliffConfigPath, config, errors);
31
32
  validateScopeAliases(raw.scopeAliases, config, errors);
32
33
  validateRetiredPackages(raw.retiredPackages, config, errors);
34
+ validateProjectConfig(raw.project, config, errors);
33
35
  const warnings = [];
34
36
  const changelogJsonEnabled = config.changelogJson?.enabled ?? true;
35
37
  if (!changelogJsonEnabled && config.releaseNotes?.shouldInjectIntoReadme) {
@@ -75,6 +77,30 @@ function validateChangelogJson(value, config, errors) {
75
77
  }
76
78
  config.changelogJson = result;
77
79
  }
80
+ function validateProjectConfig(value, config, errors) {
81
+ if (value === void 0) return;
82
+ if (!isRecord(value)) {
83
+ errors.push("'project' must be an object");
84
+ return;
85
+ }
86
+ const knownProjectFields = /* @__PURE__ */ new Set(["tagPrefix"]);
87
+ for (const key of Object.keys(value)) {
88
+ if (!knownProjectFields.has(key)) {
89
+ errors.push(`project: unknown field '${key}'`);
90
+ }
91
+ }
92
+ const result = {};
93
+ if (value.tagPrefix !== void 0) {
94
+ if (typeof value.tagPrefix !== "string") {
95
+ errors.push("project.tagPrefix: must be a string");
96
+ } else if (value.tagPrefix === "") {
97
+ errors.push("project.tagPrefix: must be a non-empty string");
98
+ } else {
99
+ result.tagPrefix = value.tagPrefix;
100
+ }
101
+ }
102
+ config.project = result;
103
+ }
78
104
  function validateReleaseNotes(value, config, errors) {
79
105
  if (value === void 0) return;
80
106
  if (!isRecord(value)) {
@@ -0,0 +1,14 @@
1
+ import type { DependencyGraph } from './buildDependencyGraph.ts';
2
+ import type { WorkspaceConfig } from './types.ts';
3
+ export interface CommitsProbeResult {
4
+ has: boolean;
5
+ tag: string | undefined;
6
+ }
7
+ export interface StrandedDependentViolation {
8
+ dir: string;
9
+ downstreamOf: string;
10
+ tag: string | undefined;
11
+ }
12
+ type CommitsProbe = (workspace: WorkspaceConfig) => CommitsProbeResult;
13
+ export declare function validateOnlyExcludesStrandedDependents(workspaces: readonly WorkspaceConfig[], only: readonly string[], graph: DependencyGraph, hasCommits: CommitsProbe): StrandedDependentViolation[] | undefined;
14
+ export {};
@@ -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
+ };
@@ -0,0 +1,127 @@
1
+ {
2
+ "$schema": "./work-types.schema.json",
3
+ "tiers": ["Public", "Internal", "Process"],
4
+ "types": [
5
+ {
6
+ "tier": "Public",
7
+ "key": "feat",
8
+ "aliases": ["feature"],
9
+ "emoji": "🎉",
10
+ "label": "Features",
11
+ "breakingPolicy": "optional"
12
+ },
13
+ {
14
+ "tier": "Public",
15
+ "key": "drop",
16
+ "aliases": [],
17
+ "emoji": "🪦",
18
+ "label": "Removed",
19
+ "breakingPolicy": "required"
20
+ },
21
+ {
22
+ "tier": "Public",
23
+ "key": "deprecate",
24
+ "aliases": [],
25
+ "emoji": "🗑️",
26
+ "label": "Deprecated",
27
+ "breakingPolicy": "forbidden"
28
+ },
29
+ {
30
+ "tier": "Public",
31
+ "key": "fix",
32
+ "aliases": ["bugfix"],
33
+ "emoji": "🐛",
34
+ "label": "Bug fixes",
35
+ "breakingPolicy": "forbidden"
36
+ },
37
+ {
38
+ "tier": "Public",
39
+ "key": "sec",
40
+ "aliases": ["security"],
41
+ "emoji": "🔒",
42
+ "label": "Security",
43
+ "breakingPolicy": "optional"
44
+ },
45
+ {
46
+ "tier": "Public",
47
+ "key": "perf",
48
+ "aliases": ["performance"],
49
+ "emoji": "⚡",
50
+ "label": "Performance",
51
+ "breakingPolicy": "forbidden"
52
+ },
53
+ {
54
+ "tier": "Internal",
55
+ "key": "internal",
56
+ "aliases": ["utility"],
57
+ "emoji": "🏗️",
58
+ "label": "Internal features",
59
+ "breakingPolicy": "forbidden"
60
+ },
61
+ {
62
+ "tier": "Internal",
63
+ "key": "refactor",
64
+ "aliases": [],
65
+ "emoji": "♻️",
66
+ "label": "Refactoring",
67
+ "breakingPolicy": "forbidden"
68
+ },
69
+ {
70
+ "tier": "Internal",
71
+ "key": "tests",
72
+ "aliases": ["test"],
73
+ "emoji": "🧪",
74
+ "label": "Tests",
75
+ "breakingPolicy": "forbidden"
76
+ },
77
+ {
78
+ "tier": "Process",
79
+ "key": "tooling",
80
+ "aliases": [],
81
+ "emoji": "⚙️",
82
+ "label": "Tooling",
83
+ "breakingPolicy": "forbidden"
84
+ },
85
+ {
86
+ "tier": "Process",
87
+ "key": "ci",
88
+ "aliases": [],
89
+ "emoji": "👷",
90
+ "label": "CI",
91
+ "breakingPolicy": "forbidden"
92
+ },
93
+ {
94
+ "tier": "Process",
95
+ "key": "deps",
96
+ "aliases": ["dep"],
97
+ "emoji": "📦",
98
+ "label": "Dependencies",
99
+ "breakingPolicy": "forbidden"
100
+ },
101
+ {
102
+ "tier": "Process",
103
+ "key": "ai",
104
+ "aliases": [],
105
+ "emoji": "🤖",
106
+ "label": "Agentic support",
107
+ "breakingPolicy": "forbidden"
108
+ },
109
+ {
110
+ "tier": "Process",
111
+ "key": "docs",
112
+ "aliases": ["doc"],
113
+ "emoji": "📚",
114
+ "label": "Documentation",
115
+ "breakingPolicy": "forbidden"
116
+ },
117
+ {
118
+ "tier": "Process",
119
+ "key": "fmt",
120
+ "aliases": [],
121
+ "emoji": "🎨",
122
+ "label": "Formatting",
123
+ "breakingPolicy": "forbidden",
124
+ "excludedFromChangelog": true
125
+ }
126
+ ]
127
+ }
@@ -0,0 +1,73 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "https://github.com/williamthorsen/node-monorepo-tools/raw/main/packages/release-kit/src/work-types.schema.json",
4
+ "title": "Work types",
5
+ "description": "Canonical taxonomy of commit work types used by release-kit. Drives section ordering, audience classification, and the `!` (breaking) policy. The structured single-source-of-truth for everything that previously lived in `DEFAULT_WORK_TYPES` and `cliff.toml.template` group definitions.",
6
+ "type": "object",
7
+ "required": ["tiers", "types"],
8
+ "additionalProperties": false,
9
+ "properties": {
10
+ "$schema": {
11
+ "type": "string",
12
+ "description": "Optional reference to this schema for IDE validation."
13
+ },
14
+ "tiers": {
15
+ "type": "array",
16
+ "description": "Render tiers in canonical order. Each entry's `tier` field must reference one of these names.",
17
+ "items": { "type": "string", "minLength": 1 },
18
+ "minItems": 1,
19
+ "uniqueItems": true
20
+ },
21
+ "types": {
22
+ "type": "array",
23
+ "description": "Work-type entries in canonical render order (tier order, then row order within tier).",
24
+ "items": { "$ref": "#/definitions/workType" },
25
+ "minItems": 1
26
+ }
27
+ },
28
+ "definitions": {
29
+ "workType": {
30
+ "type": "object",
31
+ "required": ["tier", "key", "aliases", "emoji", "label", "breakingPolicy"],
32
+ "additionalProperties": false,
33
+ "properties": {
34
+ "tier": {
35
+ "type": "string",
36
+ "description": "Render tier this entry belongs to. Must match one of the names in the top-level `tiers` array."
37
+ },
38
+ "key": {
39
+ "type": "string",
40
+ "description": "Canonical work-type identifier (e.g. `feat`, `fix`). Must be globally unique across all entries.",
41
+ "minLength": 1,
42
+ "pattern": "^[a-z][a-z0-9]*$"
43
+ },
44
+ "aliases": {
45
+ "type": "array",
46
+ "description": "Alternate identifiers that resolve to this work type. Must be globally unique and not collide with any `key`.",
47
+ "items": { "type": "string", "minLength": 1, "pattern": "^[a-z][a-z0-9]*$" },
48
+ "uniqueItems": true
49
+ },
50
+ "emoji": {
51
+ "type": "string",
52
+ "description": "Decorative emoji rendered as the prefix of the section heading.",
53
+ "minLength": 1
54
+ },
55
+ "label": {
56
+ "type": "string",
57
+ "description": "Human-readable section label (rendered as `${emoji} ${label}` in changelogs and release notes).",
58
+ "minLength": 1
59
+ },
60
+ "breakingPolicy": {
61
+ "type": "string",
62
+ "description": "Policy governing the `!` (breaking) marker for this type. `forbidden`: `!` is a policy violation. `optional`: `!` is allowed. `required`: bare type is a policy violation; only the `!` form is accepted.",
63
+ "enum": ["forbidden", "optional", "required"]
64
+ },
65
+ "excludedFromChangelog": {
66
+ "type": "boolean",
67
+ "description": "When `true`, this type's commits never appear in any changelog or release note. Currently only `fmt` sets this. Default: `false`.",
68
+ "default": false
69
+ }
70
+ }
71
+ }
72
+ }
73
+ }
@@ -0,0 +1,14 @@
1
+ export interface WorkTypeEntry {
2
+ tier: string;
3
+ key: string;
4
+ aliases: string[];
5
+ emoji: string;
6
+ label: string;
7
+ breakingPolicy: 'forbidden' | 'optional' | 'required';
8
+ excludedFromChangelog?: boolean;
9
+ }
10
+ export interface WorkTypesData {
11
+ tiers: string[];
12
+ types: WorkTypeEntry[];
13
+ }
14
+ export declare const WORK_TYPES_DATA: WorkTypesData;
@@ -0,0 +1,59 @@
1
+ const WORK_TYPES_DATA = {
2
+ tiers: ["Public", "Internal", "Process"],
3
+ types: [
4
+ { tier: "Public", key: "feat", aliases: ["feature"], emoji: "\u{1F389}", label: "Features", breakingPolicy: "optional" },
5
+ { tier: "Public", key: "drop", aliases: [], emoji: "\u{1FAA6}", label: "Removed", breakingPolicy: "required" },
6
+ { tier: "Public", key: "deprecate", aliases: [], emoji: "\u{1F5D1}\uFE0F", label: "Deprecated", breakingPolicy: "forbidden" },
7
+ { tier: "Public", key: "fix", aliases: ["bugfix"], emoji: "\u{1F41B}", label: "Bug fixes", breakingPolicy: "forbidden" },
8
+ { tier: "Public", key: "sec", aliases: ["security"], emoji: "\u{1F512}", label: "Security", breakingPolicy: "optional" },
9
+ {
10
+ tier: "Public",
11
+ key: "perf",
12
+ aliases: ["performance"],
13
+ emoji: "\u26A1",
14
+ label: "Performance",
15
+ breakingPolicy: "forbidden"
16
+ },
17
+ {
18
+ tier: "Internal",
19
+ key: "internal",
20
+ aliases: ["utility"],
21
+ emoji: "\u{1F3D7}\uFE0F",
22
+ label: "Internal features",
23
+ breakingPolicy: "forbidden"
24
+ },
25
+ {
26
+ tier: "Internal",
27
+ key: "refactor",
28
+ aliases: [],
29
+ emoji: "\u267B\uFE0F",
30
+ label: "Refactoring",
31
+ breakingPolicy: "forbidden"
32
+ },
33
+ { tier: "Internal", key: "tests", aliases: ["test"], emoji: "\u{1F9EA}", label: "Tests", breakingPolicy: "forbidden" },
34
+ { tier: "Process", key: "tooling", aliases: [], emoji: "\u2699\uFE0F", label: "Tooling", breakingPolicy: "forbidden" },
35
+ { tier: "Process", key: "ci", aliases: [], emoji: "\u{1F477}", label: "CI", breakingPolicy: "forbidden" },
36
+ { tier: "Process", key: "deps", aliases: ["dep"], emoji: "\u{1F4E6}", label: "Dependencies", breakingPolicy: "forbidden" },
37
+ { tier: "Process", key: "ai", aliases: [], emoji: "\u{1F916}", label: "Agentic support", breakingPolicy: "forbidden" },
38
+ {
39
+ tier: "Process",
40
+ key: "docs",
41
+ aliases: ["doc"],
42
+ emoji: "\u{1F4DA}",
43
+ label: "Documentation",
44
+ breakingPolicy: "forbidden"
45
+ },
46
+ {
47
+ tier: "Process",
48
+ key: "fmt",
49
+ aliases: [],
50
+ emoji: "\u{1F3A8}",
51
+ label: "Formatting",
52
+ breakingPolicy: "forbidden",
53
+ excludedFromChangelog: true
54
+ }
55
+ ]
56
+ };
57
+ export {
58
+ WORK_TYPES_DATA
59
+ };
@@ -0,0 +1,5 @@
1
+ export declare function hasExpectedTopLevelShape(value: unknown): value is {
2
+ tiers: unknown[];
3
+ types: unknown[];
4
+ };
5
+ export declare function errorMessage(error: unknown): string;
@@ -0,0 +1,16 @@
1
+ function hasExpectedTopLevelShape(value) {
2
+ if (typeof value !== "object" || value === null) {
3
+ return false;
4
+ }
5
+ if (!("tiers" in value) || !("types" in value)) {
6
+ return false;
7
+ }
8
+ return Array.isArray(value.tiers) && Array.isArray(value.types);
9
+ }
10
+ function errorMessage(error) {
11
+ return error instanceof Error ? error.message : String(error);
12
+ }
13
+ export {
14
+ errorMessage,
15
+ hasExpectedTopLevelShape
16
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@williamthorsen/release-kit",
3
- "version": "5.0.0",
3
+ "version": "5.2.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/nmr-core": "0.3.0"
39
+ "semver": "7.7.4",
40
+ "@williamthorsen/nmr-core": "0.3.1"
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": {
@@ -49,9 +52,12 @@
49
52
  "registry": "https://registry.npmjs.org"
50
53
  },
51
54
  "scripts": {
55
+ "build:post": "node scripts/copy-work-types.js",
52
56
  "test": "pnpm exec vitest --config=vitest.standalone.config.ts",
53
57
  "test:coverage": "pnpm exec vitest --config=vitest.standalone.config.ts --coverage",
54
58
  "test:integration": "pnpm exec vitest --config=vitest.integration.config.ts",
55
- "test:watch": "pnpm exec vitest --config=vitest.standalone.config.ts --watch"
59
+ "test:watch": "pnpm exec vitest --config=vitest.standalone.config.ts --watch",
60
+ "work-types:check": "pnpm exec release-kit work-types check",
61
+ "work-types:sync": "pnpm exec release-kit work-types sync"
56
62
  }
57
63
  }