@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.
- package/CHANGELOG.md +134 -4
- package/README.md +404 -40
- package/cliff.toml.template +2 -1
- package/dist/esm/.cache +1 -1
- package/dist/esm/assertCleanWorkingTree.js +1 -1
- package/dist/esm/bin/release-kit.js +45 -14
- package/dist/esm/buildChangelogEntries.d.ts +3 -0
- package/dist/esm/{generateChangelogJson.js → buildChangelogEntries.js} +40 -77
- package/dist/esm/buildDependencyGraph.d.ts +4 -3
- package/dist/esm/buildDependencyGraph.js +18 -11
- package/dist/esm/buildReleaseSummary.js +12 -4
- package/dist/esm/buildSyntheticChangelogEntry.d.ts +5 -0
- package/dist/esm/buildSyntheticChangelogEntry.js +13 -0
- package/dist/esm/bumpAllVersions.d.ts +1 -0
- package/dist/esm/bumpAllVersions.js +16 -2
- package/dist/esm/bumpVersion.js +3 -0
- package/dist/esm/changelogJsonFile.d.ts +4 -0
- package/dist/esm/changelogJsonFile.js +68 -0
- package/dist/esm/commitCommand.js +1 -1
- package/dist/esm/compareVersions.d.ts +1 -0
- package/dist/esm/compareVersions.js +27 -0
- package/dist/esm/createGithubRelease.d.ts +6 -2
- package/dist/esm/createGithubRelease.js +17 -17
- package/dist/esm/createGithubReleaseCommand.d.ts +1 -0
- package/dist/esm/createGithubReleaseCommand.js +41 -0
- package/dist/esm/decideRelease.d.ts +25 -0
- package/dist/esm/decideRelease.js +28 -0
- package/dist/esm/defaults.d.ts +1 -0
- package/dist/esm/defaults.js +7 -3
- package/dist/esm/deriveWorkspaceConfig.d.ts +2 -0
- package/dist/esm/deriveWorkspaceConfig.js +37 -0
- package/dist/esm/detectUndeclaredTagPrefixes.d.ts +7 -0
- package/dist/esm/detectUndeclaredTagPrefixes.js +46 -0
- package/dist/esm/generateChangelogs.d.ts +1 -1
- package/dist/esm/generateChangelogs.js +14 -3
- package/dist/esm/getCommitsSinceTarget.d.ts +1 -1
- package/dist/esm/getCommitsSinceTarget.js +8 -4
- package/dist/esm/index.d.ts +2 -39
- package/dist/esm/index.js +0 -75
- package/dist/esm/init/initCommand.js +1 -1
- package/dist/esm/init/scaffold.d.ts +1 -1
- package/dist/esm/init/scaffold.js +8 -5
- package/dist/esm/init/templates.d.ts +1 -0
- package/dist/esm/init/templates.js +35 -5
- package/dist/esm/injectReleaseNotesIntoReadme.d.ts +6 -1
- package/dist/esm/injectReleaseNotesIntoReadme.js +20 -7
- package/dist/esm/loadConfig.d.ts +12 -2
- package/dist/esm/loadConfig.js +161 -14
- package/dist/esm/parseRequestedTags.d.ts +1 -0
- package/dist/esm/parseRequestedTags.js +10 -0
- package/dist/esm/prepareCommand.d.ts +3 -1
- package/dist/esm/prepareCommand.js +121 -31
- package/dist/esm/previewTagPrefixes.d.ts +30 -0
- package/dist/esm/previewTagPrefixes.js +120 -0
- package/dist/esm/propagateBumps.d.ts +1 -0
- package/dist/esm/propagateBumps.js +1 -1
- package/dist/esm/publish.d.ts +0 -1
- package/dist/esm/publish.js +3 -3
- package/dist/esm/publishCommand.js +18 -14
- package/dist/esm/pushCommand.js +5 -4
- package/dist/esm/readCurrentVersion.d.ts +1 -0
- package/dist/esm/readCurrentVersion.js +21 -0
- package/dist/esm/releasePrepare.d.ts +2 -0
- package/dist/esm/releasePrepare.js +140 -54
- package/dist/esm/releasePrepareMono.js +312 -143
- package/dist/esm/releasePrepareProject.d.ts +9 -0
- package/dist/esm/releasePrepareProject.js +109 -0
- package/dist/esm/renderReleaseNotes.d.ts +1 -0
- package/dist/esm/renderReleaseNotes.js +29 -2
- package/dist/esm/reportPrepare.js +146 -73
- package/dist/esm/resolveCliffConfigPath.js +1 -1
- package/dist/esm/resolveCommandTags.d.ts +1 -1
- package/dist/esm/resolveCommandTags.js +17 -13
- package/dist/esm/resolveReleaseNotesConfig.d.ts +8 -1
- package/dist/esm/resolveReleaseNotesConfig.js +17 -7
- package/dist/esm/resolveReleaseTags.d.ts +2 -1
- package/dist/esm/resolveReleaseTags.js +19 -14
- package/dist/esm/showTagPrefixesCommand.d.ts +1 -0
- package/dist/esm/showTagPrefixesCommand.js +84 -0
- package/dist/esm/sync-labels/initCommand.js +1 -1
- package/dist/esm/sync-labels/presets.js +1 -1
- package/dist/esm/tagCommand.js +1 -1
- package/dist/esm/types.d.ts +77 -19
- package/dist/esm/validateConfig.js +205 -36
- package/dist/esm/validateOnlyExcludesStrandedDependents.d.ts +14 -0
- package/dist/esm/validateOnlyExcludesStrandedDependents.js +109 -0
- package/dist/esm/version.d.ts +1 -1
- package/dist/esm/version.js +1 -1
- package/dist/esm/writeReleaseNotesPreviews.d.ts +18 -0
- package/dist/esm/writeReleaseNotesPreviews.js +65 -0
- package/package.json +5 -2
- package/presets/labels/common.yaml +9 -6
- package/schemas/label-map.json +24 -0
- package/dist/esm/component.d.ts +0 -2
- package/dist/esm/component.js +0 -14
- package/dist/esm/findPackageRoot.d.ts +0 -1
- package/dist/esm/findPackageRoot.js +0 -17
- package/dist/esm/generateChangelogJson.d.ts +0 -7
- package/dist/esm/githubReleaseCommand.d.ts +0 -1
- package/dist/esm/githubReleaseCommand.js +0 -35
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { detectRepoType } from "./init/detectRepoType.js";
|
|
2
|
+
import { previewTagPrefixes } from "./previewTagPrefixes.js";
|
|
3
|
+
async function showTagPrefixesCommand() {
|
|
4
|
+
if (detectRepoType() === "single-package") {
|
|
5
|
+
process.stdout.write(renderSinglePackage());
|
|
6
|
+
return 0;
|
|
7
|
+
}
|
|
8
|
+
const preview = await previewTagPrefixes();
|
|
9
|
+
process.stdout.write(renderMonorepo(preview));
|
|
10
|
+
return computeExitCode(preview);
|
|
11
|
+
}
|
|
12
|
+
function renderSinglePackage() {
|
|
13
|
+
const lines = [
|
|
14
|
+
"Workspace Derived prefix Status",
|
|
15
|
+
". v (single-package mode)",
|
|
16
|
+
""
|
|
17
|
+
];
|
|
18
|
+
return lines.join("\n");
|
|
19
|
+
}
|
|
20
|
+
function renderMonorepo(preview) {
|
|
21
|
+
const lines = ["Workspace tag prefixes:", ""];
|
|
22
|
+
for (const row of preview.workspaces) {
|
|
23
|
+
lines.push(...renderWorkspaceRow(row));
|
|
24
|
+
}
|
|
25
|
+
if (preview.collisions.length > 0) {
|
|
26
|
+
lines.push(
|
|
27
|
+
"",
|
|
28
|
+
...preview.collisions.map(
|
|
29
|
+
(collision) => `\u26D4 tag prefix collision: '${collision.tagPrefix}' used by ${collision.workspacePaths.join(", ")}`
|
|
30
|
+
)
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
if (preview.undeclaredCandidates.length > 0) {
|
|
34
|
+
lines.push(
|
|
35
|
+
"",
|
|
36
|
+
"Undeclared tag prefixes:",
|
|
37
|
+
"",
|
|
38
|
+
...preview.undeclaredCandidates.map(
|
|
39
|
+
(candidate) => ` '${candidate.prefix}' \u2014 ${candidate.tagCount} tags (e.g., ${candidate.exampleTags.join(", ")})`
|
|
40
|
+
),
|
|
41
|
+
"",
|
|
42
|
+
"Suggested config snippet (adjust `dir` to match your workspace if the guess is wrong, and replace the `name` placeholder with the legacy npm name):",
|
|
43
|
+
"",
|
|
44
|
+
renderSuggestedSnippet(preview.undeclaredCandidates),
|
|
45
|
+
"",
|
|
46
|
+
"If the suggested `dir` does not match your workspace, adjust before pasting. Each legacy identity requires a `name` \u2014 replace the `TODO-fill-in-legacy-npm-name` placeholder with the package's prior npm name."
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
lines.push("");
|
|
50
|
+
return lines.join("\n");
|
|
51
|
+
}
|
|
52
|
+
function renderWorkspaceRow(row) {
|
|
53
|
+
const lines = [];
|
|
54
|
+
if (row.derivedPrefix === null) {
|
|
55
|
+
lines.push(` ${row.workspacePath} \u2014 \u26D4 derivation failed: ${row.derivationError ?? "unknown error"}`);
|
|
56
|
+
return lines;
|
|
57
|
+
}
|
|
58
|
+
const statusMarker = row.derivedTagCount > 0 ? `\u2705 ${row.derivedTagCount} tags` : "\u26A0\uFE0F no existing tags";
|
|
59
|
+
lines.push(` ${row.workspacePath} \u2014 derived prefix '${row.derivedPrefix}', ${statusMarker}`);
|
|
60
|
+
for (const entry of row.legacyEntries) {
|
|
61
|
+
if (entry.tagCount > 0) {
|
|
62
|
+
lines.push(` \u2705 ${entry.tagCount} legacy tags with '${entry.prefix}' prefix (recognized)`);
|
|
63
|
+
} else {
|
|
64
|
+
lines.push(` \u26A0\uFE0F recorded legacy prefix '${entry.prefix}' has no tags`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return lines;
|
|
68
|
+
}
|
|
69
|
+
function renderSuggestedSnippet(candidates) {
|
|
70
|
+
const entries = candidates.map(
|
|
71
|
+
(candidate) => ` { dir: '${candidate.suggestedDir}', legacyIdentities: [{ name: 'TODO-fill-in-legacy-npm-name', tagPrefix: '${candidate.prefix}' }] },`
|
|
72
|
+
).join("\n");
|
|
73
|
+
return ` workspaces: [
|
|
74
|
+
${entries}
|
|
75
|
+
],`;
|
|
76
|
+
}
|
|
77
|
+
function computeExitCode(preview) {
|
|
78
|
+
const hasDerivationFailure = preview.workspaces.some((row) => row.derivedPrefix === null);
|
|
79
|
+
const hasCollision = preview.collisions.length > 0;
|
|
80
|
+
return hasDerivationFailure || hasCollision ? 1 : 0;
|
|
81
|
+
}
|
|
82
|
+
export {
|
|
83
|
+
showTagPrefixesCommand
|
|
84
|
+
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { reportWriteResult, writeFileWithCheck } from "@williamthorsen/
|
|
1
|
+
import { reportWriteResult, writeFileWithCheck } from "@williamthorsen/nmr-core";
|
|
2
2
|
import { discoverWorkspaces } from "../discoverWorkspaces.js";
|
|
3
3
|
import { generateCommand, LABELS_OUTPUT_PATH } from "./generateCommand.js";
|
|
4
4
|
import { SYNC_LABELS_CONFIG_PATH } from "./loadSyncLabelsConfig.js";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { existsSync, readFileSync } from "node:fs";
|
|
3
3
|
import { resolve } from "node:path";
|
|
4
|
+
import { findPackageRoot } from "@williamthorsen/nmr-core";
|
|
4
5
|
import { load } from "js-yaml";
|
|
5
|
-
import { findPackageRoot } from "../findPackageRoot.js";
|
|
6
6
|
import { isRecord } from "../typeGuards.js";
|
|
7
7
|
function resolvePresetPath(presetName) {
|
|
8
8
|
const root = findPackageRoot(import.meta.url);
|
package/dist/esm/tagCommand.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { parseArgs, translateParseError } from "@williamthorsen/
|
|
1
|
+
import { parseArgs, translateParseError } from "@williamthorsen/nmr-core";
|
|
2
2
|
import { createTags } from "./createTags.js";
|
|
3
3
|
const tagFlagSchema = {
|
|
4
4
|
dryRun: { long: "--dry-run", type: "boolean" },
|
package/dist/esm/types.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ export type ReleaseType = 'major' | 'minor' | 'patch';
|
|
|
2
2
|
export type ChangelogAudience = 'all' | 'dev';
|
|
3
3
|
export interface ChangelogItem {
|
|
4
4
|
description: string;
|
|
5
|
+
body?: string;
|
|
5
6
|
}
|
|
6
7
|
export interface ChangelogSection {
|
|
7
8
|
title: string;
|
|
@@ -20,7 +21,12 @@ export interface ChangelogJsonConfig {
|
|
|
20
21
|
}
|
|
21
22
|
export interface ReleaseNotesConfig {
|
|
22
23
|
shouldInjectIntoReadme: boolean;
|
|
23
|
-
|
|
24
|
+
}
|
|
25
|
+
export interface ProjectConfig {
|
|
26
|
+
tagPrefix?: string;
|
|
27
|
+
}
|
|
28
|
+
export interface ResolvedProjectConfig {
|
|
29
|
+
tagPrefix: string;
|
|
24
30
|
}
|
|
25
31
|
export interface PropagationSource {
|
|
26
32
|
packageName: string;
|
|
@@ -31,25 +37,60 @@ export interface BumpResult {
|
|
|
31
37
|
newVersion: string;
|
|
32
38
|
files: string[];
|
|
33
39
|
}
|
|
34
|
-
export interface
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
previousTag?: string
|
|
40
|
+
export interface ReleasedWorkspaceResult {
|
|
41
|
+
status: 'released';
|
|
42
|
+
name?: string;
|
|
43
|
+
previousTag?: string;
|
|
38
44
|
commitCount: number;
|
|
39
|
-
parsedCommitCount?: number
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
45
|
+
parsedCommitCount?: number;
|
|
46
|
+
unparseableCommits?: Commit[];
|
|
47
|
+
releaseType?: ReleaseType;
|
|
48
|
+
currentVersion: string;
|
|
49
|
+
newVersion: string;
|
|
50
|
+
tag: string;
|
|
44
51
|
bumpedFiles: string[];
|
|
45
52
|
changelogFiles: string[];
|
|
46
|
-
commits?: Commit[]
|
|
47
|
-
|
|
48
|
-
propagatedFrom?: PropagationSource[]
|
|
49
|
-
|
|
53
|
+
commits?: Commit[];
|
|
54
|
+
bumpOverride?: ReleaseType;
|
|
55
|
+
propagatedFrom?: PropagationSource[];
|
|
56
|
+
setVersion?: string;
|
|
57
|
+
}
|
|
58
|
+
export interface SkippedWorkspaceResult {
|
|
59
|
+
status: 'skipped';
|
|
60
|
+
name?: string;
|
|
61
|
+
previousTag?: string;
|
|
62
|
+
commitCount: number;
|
|
63
|
+
parsedCommitCount?: number;
|
|
64
|
+
unparseableCommits?: Commit[];
|
|
65
|
+
skipReason: string;
|
|
66
|
+
}
|
|
67
|
+
export type WorkspacePrepareResult = ReleasedWorkspaceResult | SkippedWorkspaceResult;
|
|
68
|
+
export interface ReleasedProjectResult {
|
|
69
|
+
status: 'released';
|
|
70
|
+
previousTag?: string;
|
|
71
|
+
commitCount: number;
|
|
72
|
+
parsedCommitCount: number;
|
|
73
|
+
unparseableCommits?: Commit[];
|
|
74
|
+
releaseType: ReleaseType;
|
|
75
|
+
currentVersion: string;
|
|
76
|
+
newVersion: string;
|
|
77
|
+
tag: string;
|
|
78
|
+
bumpedFiles: string[];
|
|
79
|
+
changelogFiles: string[];
|
|
80
|
+
commits: Commit[];
|
|
81
|
+
bumpOverride?: ReleaseType;
|
|
82
|
+
}
|
|
83
|
+
export interface SkippedProjectResult {
|
|
84
|
+
status: 'skipped';
|
|
85
|
+
previousTag?: string;
|
|
86
|
+
commitCount: number;
|
|
87
|
+
parsedCommitCount: number;
|
|
88
|
+
unparseableCommits?: Commit[];
|
|
89
|
+
skipReason: string;
|
|
50
90
|
}
|
|
91
|
+
export type ProjectPrepareResult = ReleasedProjectResult | SkippedProjectResult;
|
|
51
92
|
export interface PrepareResult {
|
|
52
|
-
|
|
93
|
+
workspaces: WorkspacePrepareResult[];
|
|
53
94
|
tags: string[];
|
|
54
95
|
formatCommand?: {
|
|
55
96
|
command: string;
|
|
@@ -58,6 +99,7 @@ export interface PrepareResult {
|
|
|
58
99
|
} | undefined;
|
|
59
100
|
dryRun: boolean;
|
|
60
101
|
warnings?: string[] | undefined;
|
|
102
|
+
project?: ProjectPrepareResult | undefined;
|
|
61
103
|
}
|
|
62
104
|
export interface WorkTypeConfig {
|
|
63
105
|
header: string;
|
|
@@ -68,7 +110,7 @@ export interface VersionPatterns {
|
|
|
68
110
|
minor: string[];
|
|
69
111
|
}
|
|
70
112
|
export interface ReleaseKitConfig {
|
|
71
|
-
|
|
113
|
+
workspaces?: WorkspaceOverride[];
|
|
72
114
|
versionPatterns?: VersionPatterns;
|
|
73
115
|
workTypes?: Record<string, WorkTypeConfig>;
|
|
74
116
|
formatCommand?: string;
|
|
@@ -76,10 +118,22 @@ export interface ReleaseKitConfig {
|
|
|
76
118
|
scopeAliases?: Record<string, string>;
|
|
77
119
|
changelogJson?: Partial<ChangelogJsonConfig>;
|
|
78
120
|
releaseNotes?: Partial<ReleaseNotesConfig>;
|
|
121
|
+
retiredPackages?: RetiredPackage[];
|
|
122
|
+
project?: ProjectConfig;
|
|
123
|
+
}
|
|
124
|
+
export interface LegacyIdentity {
|
|
125
|
+
name: string;
|
|
126
|
+
tagPrefix: string;
|
|
127
|
+
}
|
|
128
|
+
export interface RetiredPackage {
|
|
129
|
+
name: string;
|
|
130
|
+
tagPrefix: string;
|
|
131
|
+
successor?: string;
|
|
79
132
|
}
|
|
80
|
-
export interface
|
|
133
|
+
export interface WorkspaceOverride {
|
|
81
134
|
dir: string;
|
|
82
135
|
shouldExclude?: boolean;
|
|
136
|
+
legacyIdentities?: LegacyIdentity[];
|
|
83
137
|
}
|
|
84
138
|
export interface Commit {
|
|
85
139
|
message: string;
|
|
@@ -93,15 +147,18 @@ export interface ParsedCommit {
|
|
|
93
147
|
scope?: string;
|
|
94
148
|
breaking: boolean;
|
|
95
149
|
}
|
|
96
|
-
export interface
|
|
150
|
+
export interface WorkspaceConfig {
|
|
97
151
|
dir: string;
|
|
152
|
+
name: string;
|
|
98
153
|
tagPrefix: string;
|
|
154
|
+
workspacePath: string;
|
|
99
155
|
packageFiles: string[];
|
|
100
156
|
changelogPaths: string[];
|
|
101
157
|
paths: string[];
|
|
158
|
+
legacyIdentities?: LegacyIdentity[];
|
|
102
159
|
}
|
|
103
160
|
export interface MonorepoReleaseConfig {
|
|
104
|
-
|
|
161
|
+
workspaces: WorkspaceConfig[];
|
|
105
162
|
workTypes?: Record<string, WorkTypeConfig>;
|
|
106
163
|
versionPatterns?: VersionPatterns;
|
|
107
164
|
formatCommand?: string;
|
|
@@ -109,6 +166,7 @@ export interface MonorepoReleaseConfig {
|
|
|
109
166
|
scopeAliases?: Record<string, string>;
|
|
110
167
|
changelogJson: ChangelogJsonConfig;
|
|
111
168
|
releaseNotes: ReleaseNotesConfig;
|
|
169
|
+
project?: ResolvedProjectConfig;
|
|
112
170
|
}
|
|
113
171
|
export interface ReleaseConfig {
|
|
114
172
|
tagPrefix: string;
|
|
@@ -8,11 +8,13 @@ function validateConfig(raw) {
|
|
|
8
8
|
const knownFields = /* @__PURE__ */ new Set([
|
|
9
9
|
"changelogJson",
|
|
10
10
|
"cliffConfigPath",
|
|
11
|
-
"components",
|
|
12
11
|
"formatCommand",
|
|
12
|
+
"project",
|
|
13
13
|
"releaseNotes",
|
|
14
|
+
"retiredPackages",
|
|
14
15
|
"scopeAliases",
|
|
15
16
|
"versionPatterns",
|
|
17
|
+
"workspaces",
|
|
16
18
|
"workTypes"
|
|
17
19
|
]);
|
|
18
20
|
for (const key of Object.keys(raw)) {
|
|
@@ -21,26 +23,21 @@ function validateConfig(raw) {
|
|
|
21
23
|
}
|
|
22
24
|
}
|
|
23
25
|
validateChangelogJson(raw.changelogJson, config, errors);
|
|
24
|
-
|
|
26
|
+
validateWorkspaces(raw.workspaces, config, errors);
|
|
25
27
|
validateReleaseNotes(raw.releaseNotes, config, errors);
|
|
26
28
|
validateVersionPatterns(raw.versionPatterns, config, errors);
|
|
27
29
|
validateWorkTypes(raw.workTypes, config, errors);
|
|
28
30
|
validateStringField("formatCommand", raw.formatCommand, config, errors);
|
|
29
31
|
validateStringField("cliffConfigPath", raw.cliffConfigPath, config, errors);
|
|
30
32
|
validateScopeAliases(raw.scopeAliases, config, errors);
|
|
33
|
+
validateRetiredPackages(raw.retiredPackages, config, errors);
|
|
34
|
+
validateProjectConfig(raw.project, config, errors);
|
|
31
35
|
const warnings = [];
|
|
32
36
|
const changelogJsonEnabled = config.changelogJson?.enabled ?? true;
|
|
33
|
-
if (!changelogJsonEnabled) {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
}
|
|
37
|
+
if (!changelogJsonEnabled && config.releaseNotes?.shouldInjectIntoReadme) {
|
|
38
|
+
warnings.push(
|
|
39
|
+
"releaseNotes.shouldInjectIntoReadme is enabled but changelogJson.enabled is false; README injection will be skipped at runtime"
|
|
40
|
+
);
|
|
44
41
|
}
|
|
45
42
|
return { config, errors, warnings };
|
|
46
43
|
}
|
|
@@ -80,16 +77,46 @@ function validateChangelogJson(value, config, errors) {
|
|
|
80
77
|
}
|
|
81
78
|
config.changelogJson = result;
|
|
82
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
|
+
}
|
|
83
104
|
function validateReleaseNotes(value, config, errors) {
|
|
84
105
|
if (value === void 0) return;
|
|
85
106
|
if (!isRecord(value)) {
|
|
86
107
|
errors.push("'releaseNotes' must be an object");
|
|
87
108
|
return;
|
|
88
109
|
}
|
|
89
|
-
const knownReleaseNotesFields = /* @__PURE__ */ new Set(["shouldInjectIntoReadme"
|
|
110
|
+
const knownReleaseNotesFields = /* @__PURE__ */ new Set(["shouldInjectIntoReadme"]);
|
|
90
111
|
for (const key of Object.keys(value)) {
|
|
91
112
|
if (!knownReleaseNotesFields.has(key)) {
|
|
92
|
-
|
|
113
|
+
if (key === "shouldCreateGithubRelease") {
|
|
114
|
+
errors.push(
|
|
115
|
+
"releaseNotes.shouldCreateGithubRelease is no longer supported. Adoption is now signaled by installing the create-github-release workflow. Remove this field from your config; see README for the updated workflow."
|
|
116
|
+
);
|
|
117
|
+
} else {
|
|
118
|
+
errors.push(`releaseNotes: unknown field '${key}'`);
|
|
119
|
+
}
|
|
93
120
|
}
|
|
94
121
|
}
|
|
95
122
|
const result = {};
|
|
@@ -100,57 +127,199 @@ function validateReleaseNotes(value, config, errors) {
|
|
|
100
127
|
errors.push("releaseNotes.shouldInjectIntoReadme: must be a boolean");
|
|
101
128
|
}
|
|
102
129
|
}
|
|
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
130
|
config.releaseNotes = result;
|
|
111
131
|
}
|
|
112
132
|
function isStringArray(value) {
|
|
113
133
|
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
114
134
|
}
|
|
115
|
-
function
|
|
135
|
+
function validateWorkspaces(value, config, errors) {
|
|
116
136
|
if (value === void 0) return;
|
|
117
137
|
if (!Array.isArray(value)) {
|
|
118
|
-
errors.push("'
|
|
138
|
+
errors.push("'workspaces' must be an array");
|
|
119
139
|
return;
|
|
120
140
|
}
|
|
121
|
-
const
|
|
122
|
-
const
|
|
141
|
+
const workspaces = [];
|
|
142
|
+
const knownWorkspaceFields = /* @__PURE__ */ new Set(["dir", "shouldExclude", "legacyIdentities"]);
|
|
123
143
|
for (const [i, entry] of value.entries()) {
|
|
124
144
|
if (!isRecord(entry)) {
|
|
125
|
-
errors.push(`
|
|
145
|
+
errors.push(`workspaces[${i}]: must be an object`);
|
|
126
146
|
continue;
|
|
127
147
|
}
|
|
128
148
|
if (typeof entry.dir !== "string" || entry.dir === "") {
|
|
129
|
-
errors.push(`
|
|
149
|
+
errors.push(`workspaces[${i}]: 'dir' is required`);
|
|
130
150
|
continue;
|
|
131
151
|
}
|
|
132
152
|
for (const key of Object.keys(entry)) {
|
|
133
|
-
if (!
|
|
153
|
+
if (!knownWorkspaceFields.has(key)) {
|
|
134
154
|
if (key === "tagPrefix") {
|
|
135
155
|
errors.push(
|
|
136
|
-
`
|
|
156
|
+
`workspaces[${i}]: 'tagPrefix' is no longer supported; remove it to use the default '${entry.dir}-v'`
|
|
157
|
+
);
|
|
158
|
+
} else if (key === "legacyTagPrefixes") {
|
|
159
|
+
errors.push(
|
|
160
|
+
`workspaces[${i}]: 'legacyTagPrefixes' is no longer supported; use 'legacyIdentities: [{ name, tagPrefix }, ...]' instead`
|
|
137
161
|
);
|
|
138
162
|
} else {
|
|
139
|
-
errors.push(`
|
|
163
|
+
errors.push(`workspaces[${i}]: unknown field '${key}'`);
|
|
140
164
|
}
|
|
141
165
|
}
|
|
142
166
|
}
|
|
143
|
-
const
|
|
167
|
+
const workspace = { dir: entry.dir };
|
|
144
168
|
if (entry.shouldExclude !== void 0) {
|
|
145
169
|
if (typeof entry.shouldExclude === "boolean") {
|
|
146
|
-
|
|
170
|
+
workspace.shouldExclude = entry.shouldExclude;
|
|
147
171
|
} else {
|
|
148
|
-
errors.push(`
|
|
172
|
+
errors.push(`workspaces[${i}]: 'shouldExclude' must be a boolean`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
if (entry.legacyIdentities !== void 0) {
|
|
176
|
+
const identities = validateLegacyIdentities(entry.legacyIdentities, i, errors);
|
|
177
|
+
if (identities !== void 0) {
|
|
178
|
+
workspace.legacyIdentities = identities;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
workspaces.push(workspace);
|
|
182
|
+
}
|
|
183
|
+
config.workspaces = workspaces;
|
|
184
|
+
}
|
|
185
|
+
function validateLegacyIdentities(value, workspaceIndex, errors) {
|
|
186
|
+
if (!Array.isArray(value)) {
|
|
187
|
+
errors.push(`workspaces[${workspaceIndex}]: 'legacyIdentities' must be an array`);
|
|
188
|
+
return void 0;
|
|
189
|
+
}
|
|
190
|
+
const knownIdentityFields = /* @__PURE__ */ new Set(["name", "tagPrefix"]);
|
|
191
|
+
const identities = [];
|
|
192
|
+
const seenTuples = /* @__PURE__ */ new Set();
|
|
193
|
+
for (const [entryIndex, entry] of value.entries()) {
|
|
194
|
+
if (!isRecord(entry)) {
|
|
195
|
+
errors.push(`workspaces[${workspaceIndex}].legacyIdentities[${entryIndex}]: must be an object`);
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
let entryValid = true;
|
|
199
|
+
for (const key2 of Object.keys(entry)) {
|
|
200
|
+
if (!knownIdentityFields.has(key2)) {
|
|
201
|
+
errors.push(`workspaces[${workspaceIndex}].legacyIdentities[${entryIndex}]: unknown field '${key2}'`);
|
|
202
|
+
entryValid = false;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
const { name, tagPrefix } = entry;
|
|
206
|
+
if (typeof name !== "string") {
|
|
207
|
+
errors.push(`workspaces[${workspaceIndex}].legacyIdentities[${entryIndex}].name: must be a string`);
|
|
208
|
+
entryValid = false;
|
|
209
|
+
} else if (name === "") {
|
|
210
|
+
errors.push(`workspaces[${workspaceIndex}].legacyIdentities[${entryIndex}].name: must be a non-empty string`);
|
|
211
|
+
entryValid = false;
|
|
212
|
+
}
|
|
213
|
+
if (typeof tagPrefix !== "string") {
|
|
214
|
+
errors.push(`workspaces[${workspaceIndex}].legacyIdentities[${entryIndex}].tagPrefix: must be a string`);
|
|
215
|
+
entryValid = false;
|
|
216
|
+
} else if (tagPrefix === "") {
|
|
217
|
+
errors.push(
|
|
218
|
+
`workspaces[${workspaceIndex}].legacyIdentities[${entryIndex}].tagPrefix: must be a non-empty string`
|
|
219
|
+
);
|
|
220
|
+
entryValid = false;
|
|
221
|
+
}
|
|
222
|
+
if (!entryValid || typeof name !== "string" || typeof tagPrefix !== "string") {
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
const key = `${name}\0${tagPrefix}`;
|
|
226
|
+
if (seenTuples.has(key)) {
|
|
227
|
+
errors.push(
|
|
228
|
+
`workspaces[${workspaceIndex}].legacyIdentities[${entryIndex}]: duplicate identity (name='${name}', tagPrefix='${tagPrefix}')`
|
|
229
|
+
);
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
seenTuples.add(key);
|
|
233
|
+
identities.push({ name, tagPrefix });
|
|
234
|
+
}
|
|
235
|
+
return identities;
|
|
236
|
+
}
|
|
237
|
+
function validateRetiredPackages(value, config, errors) {
|
|
238
|
+
if (value === void 0) return;
|
|
239
|
+
if (!Array.isArray(value)) {
|
|
240
|
+
errors.push("'retiredPackages' must be an array");
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
const validEntries = [];
|
|
244
|
+
const seenTuples = /* @__PURE__ */ new Set();
|
|
245
|
+
for (const [i, entry] of value.entries()) {
|
|
246
|
+
const retired = validateRetiredPackageEntry(entry, i, errors);
|
|
247
|
+
if (retired === void 0) continue;
|
|
248
|
+
const key = `${retired.name}\0${retired.tagPrefix}`;
|
|
249
|
+
if (seenTuples.has(key)) {
|
|
250
|
+
errors.push(
|
|
251
|
+
`retiredPackages[${i}]: duplicate package (name='${retired.name}', tagPrefix='${retired.tagPrefix}')`
|
|
252
|
+
);
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
seenTuples.add(key);
|
|
256
|
+
validEntries.push({ entry: retired, rawIndex: i });
|
|
257
|
+
}
|
|
258
|
+
detectRetiredVsLegacyCollisions(validEntries, config, errors);
|
|
259
|
+
config.retiredPackages = validEntries.map(({ entry }) => entry);
|
|
260
|
+
}
|
|
261
|
+
function validateRetiredPackageEntry(entry, i, errors) {
|
|
262
|
+
if (!isRecord(entry)) {
|
|
263
|
+
errors.push(`retiredPackages[${i}]: must be an object`);
|
|
264
|
+
return void 0;
|
|
265
|
+
}
|
|
266
|
+
const knownRetiredFields = /* @__PURE__ */ new Set(["name", "tagPrefix", "successor"]);
|
|
267
|
+
let entryValid = true;
|
|
268
|
+
for (const key of Object.keys(entry)) {
|
|
269
|
+
if (!knownRetiredFields.has(key)) {
|
|
270
|
+
errors.push(`retiredPackages[${i}]: unknown field '${key}'`);
|
|
271
|
+
entryValid = false;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
const { name, tagPrefix, successor } = entry;
|
|
275
|
+
if (!validateNonEmptyString(name, `retiredPackages[${i}].name`, errors)) {
|
|
276
|
+
entryValid = false;
|
|
277
|
+
}
|
|
278
|
+
if (!validateNonEmptyString(tagPrefix, `retiredPackages[${i}].tagPrefix`, errors)) {
|
|
279
|
+
entryValid = false;
|
|
280
|
+
}
|
|
281
|
+
if (successor !== void 0 && !validateNonEmptyString(successor, `retiredPackages[${i}].successor`, errors)) {
|
|
282
|
+
entryValid = false;
|
|
283
|
+
}
|
|
284
|
+
if (!entryValid || typeof name !== "string" || typeof tagPrefix !== "string") {
|
|
285
|
+
return void 0;
|
|
286
|
+
}
|
|
287
|
+
const retired = { name, tagPrefix };
|
|
288
|
+
if (typeof successor === "string" && successor !== "") {
|
|
289
|
+
retired.successor = successor;
|
|
290
|
+
}
|
|
291
|
+
return retired;
|
|
292
|
+
}
|
|
293
|
+
function detectRetiredVsLegacyCollisions(retiredPackages, config, errors) {
|
|
294
|
+
if (config.workspaces === void 0) return;
|
|
295
|
+
const legacyPrefixToWorkspace = /* @__PURE__ */ new Map();
|
|
296
|
+
for (const workspace of config.workspaces) {
|
|
297
|
+
if (workspace.legacyIdentities === void 0) continue;
|
|
298
|
+
for (const identity of workspace.legacyIdentities) {
|
|
299
|
+
if (!legacyPrefixToWorkspace.has(identity.tagPrefix)) {
|
|
300
|
+
legacyPrefixToWorkspace.set(identity.tagPrefix, workspace.dir);
|
|
149
301
|
}
|
|
150
302
|
}
|
|
151
|
-
components.push(component);
|
|
152
303
|
}
|
|
153
|
-
|
|
304
|
+
for (const { entry: retired, rawIndex } of retiredPackages) {
|
|
305
|
+
const collidingDir = legacyPrefixToWorkspace.get(retired.tagPrefix);
|
|
306
|
+
if (collidingDir !== void 0) {
|
|
307
|
+
errors.push(
|
|
308
|
+
`retiredPackages[${rawIndex}]: tagPrefix '${retired.tagPrefix}' collides with a declared legacyIdentities[].tagPrefix on workspace '${collidingDir}'`
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
function validateNonEmptyString(value, fieldPath, errors) {
|
|
314
|
+
if (typeof value !== "string") {
|
|
315
|
+
errors.push(`${fieldPath}: must be a string`);
|
|
316
|
+
return false;
|
|
317
|
+
}
|
|
318
|
+
if (value === "") {
|
|
319
|
+
errors.push(`${fieldPath}: must be a non-empty string`);
|
|
320
|
+
return false;
|
|
321
|
+
}
|
|
322
|
+
return true;
|
|
154
323
|
}
|
|
155
324
|
function validateVersionPatterns(value, config, errors) {
|
|
156
325
|
if (value === void 0) return;
|
|
@@ -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 {};
|