@williamthorsen/release-kit 14.2.1 → 14.2.3
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/.meta/changelog.json +77 -0
- package/.readyup/kits/default.js +142 -152
- package/.readyup/kits/npm-auto-publish.js +183 -184
- package/.readyup/manifest.json +83 -9
- package/CHANGELOG.md +24 -0
- package/README.md +7 -1
- package/dist/esm/applyChangelogOverrides.d.ts +50 -0
- package/dist/esm/applyChangelogOverrides.js +162 -0
- package/dist/esm/attachChangelogDiagnostics.d.ts +1 -1
- package/dist/esm/bin/release-kit.js +6 -238
- package/dist/esm/buildChangelogEntries.d.ts +20 -31
- package/dist/esm/buildChangelogEntries.js +71 -188
- package/dist/esm/buildChangelogItem.d.ts +56 -0
- package/dist/esm/buildChangelogItem.js +119 -0
- package/dist/esm/buildReleasedResult.d.ts +20 -0
- package/dist/esm/buildReleasedResult.js +78 -0
- package/dist/esm/changelogJsonUtils.d.ts +1 -1
- package/dist/esm/changelogOverrides.d.ts +29 -143
- package/dist/esm/changelogOverrides.js +35 -467
- package/dist/esm/configSchema.d.ts +225 -0
- package/dist/esm/configSchema.js +103 -0
- package/dist/esm/createGithubReleaseCommand.js +4 -3
- package/dist/esm/decideDirectReleases.d.ts +30 -0
- package/dist/esm/decideDirectReleases.js +66 -0
- package/dist/esm/defaults.js +1 -4
- package/dist/esm/defineConfig.d.ts +2 -2
- package/dist/esm/deriveReleaseNotesConfig.d.ts +2 -1
- package/dist/esm/deriveReleaseNotesConfig.js +1 -1
- package/dist/esm/determineBumpType.js +4 -3
- package/dist/esm/enumerateReleaseWindows.js +4 -4
- package/dist/esm/formatPrepareFiles.d.ts +11 -0
- package/dist/esm/formatPrepareFiles.js +55 -0
- package/dist/esm/formatPrepareWarnings.d.ts +26 -0
- package/dist/esm/formatPrepareWarnings.js +72 -0
- package/dist/esm/help/initHelp.d.ts +4 -0
- package/dist/esm/help/initHelp.js +31 -0
- package/dist/esm/help/overridesHelp.d.ts +4 -0
- package/dist/esm/help/overridesHelp.js +32 -0
- package/dist/esm/help/releaseStepsHelp.d.ts +12 -0
- package/dist/esm/help/releaseStepsHelp.js +91 -0
- package/dist/esm/help/syncLabelsHelp.d.ts +8 -0
- package/dist/esm/help/syncLabelsHelp.js +55 -0
- package/dist/esm/help/usage.d.ts +2 -0
- package/dist/esm/help/usage.js +29 -0
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/init/initCommand.js +3 -7
- package/dist/esm/loadChangelogOverrides.d.ts +31 -0
- package/dist/esm/loadChangelogOverrides.js +138 -0
- package/dist/esm/loadConfig.d.ts +0 -40
- package/dist/esm/loadConfig.js +0 -242
- package/dist/esm/loadValidatedConfig.d.ts +1 -1
- package/dist/esm/mergeConfig.d.ts +41 -0
- package/dist/esm/mergeConfig.js +118 -0
- package/dist/esm/monorepoConfigAssertions.d.ts +49 -0
- package/dist/esm/monorepoConfigAssertions.js +126 -0
- package/dist/esm/planChangelogFiles.d.ts +16 -0
- package/dist/esm/planChangelogFiles.js +41 -0
- package/dist/esm/prepareArgs.d.ts +55 -0
- package/dist/esm/prepareArgs.js +69 -0
- package/dist/esm/prepareCommand.d.ts +0 -48
- package/dist/esm/prepareCommand.js +11 -77
- package/dist/esm/prepareComposite.d.ts +16 -0
- package/dist/esm/prepareComposite.js +39 -0
- package/dist/esm/prepareRunContext.d.ts +38 -0
- package/dist/esm/prepareRunContext.js +39 -0
- package/dist/esm/prepareTarget.d.ts +2 -7
- package/dist/esm/prepareTarget.js +2 -118
- package/dist/esm/previewTagPrefixes.d.ts +1 -1
- package/dist/esm/releasePrepareTargets.js +13 -207
- package/dist/esm/renderReleaseNotes.js +6 -5
- package/dist/esm/reportPrepare.js +3 -123
- package/dist/esm/resolveCommandTags.d.ts +1 -1
- package/dist/esm/resolveCommandTags.js +2 -1
- package/dist/esm/resolveReleaseDeclaration.d.ts +2 -1
- package/dist/esm/resolveReleaseTargets.d.ts +2 -1
- package/dist/esm/resolveReleaseTargets.js +2 -1
- package/dist/esm/stripScope.js +4 -3
- package/dist/esm/sync-labels/generateCommand.d.ts +1 -1
- package/dist/esm/sync-labels/resolveLabels.d.ts +1 -1
- package/dist/esm/topologicalSort.d.ts +12 -0
- package/dist/esm/topologicalSort.js +60 -0
- package/dist/esm/types.d.ts +1 -225
- package/dist/esm/types.js +1 -103
- package/dist/esm/validateAllChangelogOverrides.d.ts +56 -0
- package/dist/esm/validateAllChangelogOverrides.js +138 -0
- package/dist/esm/validateConfig.d.ts +1 -1
- package/dist/esm/validateConfig.js +7 -9
- package/dist/esm/validateOverridesCommand.d.ts +1 -1
- package/dist/esm/validateOverridesCommand.js +2 -1
- package/package.json +4 -4
package/dist/esm/loadConfig.js
CHANGED
|
@@ -10,10 +10,6 @@ import { existsSync, readFileSync } from 'node:fs';
|
|
|
10
10
|
import path from 'node:path';
|
|
11
11
|
import { pathToFileURL } from 'node:url';
|
|
12
12
|
import { chainError } from '@williamthorsen/toolbelt.errors/candidate';
|
|
13
|
-
import { DEFAULT_CHANGELOG_JSON_CONFIG, DEFAULT_PROJECT_TAG_PREFIX, DEFAULT_RELEASE_NOTES_CONFIG, DEFAULT_VERSION_PATTERNS, DEFAULT_WORK_TYPES, } from "./defaults.js";
|
|
14
|
-
import { deriveDirectoryConfig } from "./deriveDirectoryConfig.js";
|
|
15
|
-
import { deriveWorkspaceConfig } from "./deriveWorkspaceConfig.js";
|
|
16
|
-
import { getTargetName, normalizeWorkspacePath, resolveReleaseDeclaration, } from "./resolveReleaseDeclaration.js";
|
|
17
13
|
import { isRecord } from "./typeGuards.js";
|
|
18
14
|
export const ROOT_PACKAGE_JSON_PATH = 'package.json';
|
|
19
15
|
export function readRootPackageVersion() {
|
|
@@ -56,241 +52,3 @@ export async function loadConfig(configPath) {
|
|
|
56
52
|
}
|
|
57
53
|
return resolved;
|
|
58
54
|
}
|
|
59
|
-
export function mergeMonorepoConfig(discoveredPaths, userConfig, rootPackage) {
|
|
60
|
-
const discovered = discoveredPaths.map((workspacePath) => deriveWorkspaceConfig(workspacePath));
|
|
61
|
-
assertUniqueTagPrefixes(discovered);
|
|
62
|
-
const { retained: workspaces, excluded, directories: declaredDirectories, composite, } = resolveReleaseDeclaration(discovered, userConfig);
|
|
63
|
-
const directories = declaredDirectories.map((declared) => deriveDirectoryConfig(declared));
|
|
64
|
-
for (const workspace of workspaces) {
|
|
65
|
-
if (workspace.legacyIdentities !== undefined) {
|
|
66
|
-
assertLegacyIdentityDoesNotMatchCurrent(workspace.dir, workspace.name, workspace.tagPrefix, workspace.legacyIdentities);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
if (userConfig?.retiredPackages !== undefined) {
|
|
70
|
-
assertRetiredPackagesDoNotCollideWithActive(workspaces, userConfig.retiredPackages);
|
|
71
|
-
}
|
|
72
|
-
const project = resolveProjectConfig(composite, rootPackage, workspaces);
|
|
73
|
-
const workTypes = resolveWorkTypes(userConfig?.workTypes);
|
|
74
|
-
const versionPatterns = userConfig?.versionPatterns === undefined ? { ...DEFAULT_VERSION_PATTERNS } : { ...userConfig.versionPatterns };
|
|
75
|
-
const changelogJson = mergeChangelogJsonConfig(userConfig?.changelogJson);
|
|
76
|
-
const releaseNotes = mergeReleaseNotesConfig(userConfig?.releaseNotes);
|
|
77
|
-
assertNoSharedVersionFiles(workspaces, directories, project);
|
|
78
|
-
assertNoTagPrefixCollisions(workspaces, directories, userConfig?.retiredPackages, project);
|
|
79
|
-
const result = {
|
|
80
|
-
workspaces,
|
|
81
|
-
workTypes,
|
|
82
|
-
versionPatterns,
|
|
83
|
-
changelogJson,
|
|
84
|
-
releaseNotes,
|
|
85
|
-
};
|
|
86
|
-
if (excluded.length > 0) {
|
|
87
|
-
result.excludedWorkspaces = excluded;
|
|
88
|
-
}
|
|
89
|
-
if (directories.length > 0) {
|
|
90
|
-
result.directories = directories;
|
|
91
|
-
}
|
|
92
|
-
if (project !== undefined) {
|
|
93
|
-
result.project = project;
|
|
94
|
-
}
|
|
95
|
-
applyOptionalPassthroughFields(result, userConfig);
|
|
96
|
-
return result;
|
|
97
|
-
}
|
|
98
|
-
function applyOptionalPassthroughFields(result, userConfig) {
|
|
99
|
-
if (userConfig?.changelogMarkdown !== undefined) {
|
|
100
|
-
result.changelogMarkdown = userConfig.changelogMarkdown;
|
|
101
|
-
}
|
|
102
|
-
if (userConfig?.formatCommand !== undefined) {
|
|
103
|
-
result.formatCommand = userConfig.formatCommand;
|
|
104
|
-
}
|
|
105
|
-
if (userConfig?.scopeAliases !== undefined) {
|
|
106
|
-
result.scopeAliases = userConfig.scopeAliases;
|
|
107
|
-
}
|
|
108
|
-
if (userConfig?.breakingPolicies !== undefined) {
|
|
109
|
-
result.breakingPolicies = userConfig.breakingPolicies;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
export function mergeSinglePackageConfig(userConfig) {
|
|
113
|
-
if (userConfig?.project !== undefined) {
|
|
114
|
-
throw new Error('project block is not supported in single-package mode');
|
|
115
|
-
}
|
|
116
|
-
if (userConfig?.releases !== undefined) {
|
|
117
|
-
throw new Error('releases block is not supported in single-package mode');
|
|
118
|
-
}
|
|
119
|
-
const workTypes = resolveWorkTypes(userConfig?.workTypes);
|
|
120
|
-
const versionPatterns = userConfig?.versionPatterns === undefined ? { ...DEFAULT_VERSION_PATTERNS } : { ...userConfig.versionPatterns };
|
|
121
|
-
const changelogJson = mergeChangelogJsonConfig(userConfig?.changelogJson);
|
|
122
|
-
const releaseNotes = mergeReleaseNotesConfig(userConfig?.releaseNotes);
|
|
123
|
-
const result = {
|
|
124
|
-
workTypes,
|
|
125
|
-
versionPatterns,
|
|
126
|
-
changelogJson,
|
|
127
|
-
releaseNotes,
|
|
128
|
-
};
|
|
129
|
-
applyOptionalPassthroughFields(result, userConfig);
|
|
130
|
-
return result;
|
|
131
|
-
}
|
|
132
|
-
export function resolveWorkTypes(userWorkTypes) {
|
|
133
|
-
return userWorkTypes === undefined ? { ...DEFAULT_WORK_TYPES } : { ...DEFAULT_WORK_TYPES, ...userWorkTypes };
|
|
134
|
-
}
|
|
135
|
-
function mergeChangelogJsonConfig(partial) {
|
|
136
|
-
if (partial === undefined) {
|
|
137
|
-
return { ...DEFAULT_CHANGELOG_JSON_CONFIG };
|
|
138
|
-
}
|
|
139
|
-
return {
|
|
140
|
-
enabled: partial.enabled ?? DEFAULT_CHANGELOG_JSON_CONFIG.enabled,
|
|
141
|
-
outputPath: partial.outputPath ?? DEFAULT_CHANGELOG_JSON_CONFIG.outputPath,
|
|
142
|
-
devOnlySections: partial.devOnlySections ?? [...DEFAULT_CHANGELOG_JSON_CONFIG.devOnlySections],
|
|
143
|
-
};
|
|
144
|
-
}
|
|
145
|
-
function assertLegacyIdentityDoesNotMatchCurrent(dir, currentName, currentTagPrefix, legacyIdentities) {
|
|
146
|
-
for (const identity of legacyIdentities) {
|
|
147
|
-
if (identity.name === currentName && identity.tagPrefix === currentTagPrefix) {
|
|
148
|
-
throw new Error(`Workspace '${dir}': legacyIdentities must not match the current identity ` +
|
|
149
|
-
`(name='${currentName}', tagPrefix='${currentTagPrefix}'). ` +
|
|
150
|
-
'The current identity is always searched; listing it again is a no-op.');
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
function assertRetiredPackagesDoNotCollideWithActive(workspaces, retiredPackages) {
|
|
155
|
-
const workspaceByDerivedPrefix = new Map();
|
|
156
|
-
for (const workspace of workspaces) {
|
|
157
|
-
workspaceByDerivedPrefix.set(workspace.tagPrefix, workspace);
|
|
158
|
-
}
|
|
159
|
-
for (const retired of retiredPackages) {
|
|
160
|
-
const active = workspaceByDerivedPrefix.get(retired.tagPrefix);
|
|
161
|
-
if (active !== undefined) {
|
|
162
|
-
throw new Error(`retiredPackages: tagPrefix '${retired.tagPrefix}' collides with active workspace '${active.dir}' ` +
|
|
163
|
-
`(derived prefix '${active.tagPrefix}'). A retired package's tagPrefix cannot belong to an active workspace.`);
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
function resolveProjectConfig(composite, rootPackage, workspaces) {
|
|
168
|
-
if (composite === undefined) {
|
|
169
|
-
return undefined;
|
|
170
|
-
}
|
|
171
|
-
const subject = composite.name === undefined ? 'project block' : `composite release '${composite.name}'`;
|
|
172
|
-
if (rootPackage === undefined || !rootPackage.exists) {
|
|
173
|
-
throw new Error(`${subject} requires a root ${ROOT_PACKAGE_JSON_PATH}; create one with a 'version' field at the repo root`);
|
|
174
|
-
}
|
|
175
|
-
if (rootPackage.version === undefined) {
|
|
176
|
-
throw new Error(`${subject} requires root ${ROOT_PACKAGE_JSON_PATH} to have a 'version' field; add a 'version' field to the root package.json`);
|
|
177
|
-
}
|
|
178
|
-
return {
|
|
179
|
-
paths: composite.paths ?? workspaces.flatMap((workspace) => workspace.paths),
|
|
180
|
-
tagPrefix: composite.tagPrefix ?? DEFAULT_PROJECT_TAG_PREFIX,
|
|
181
|
-
...(composite.name !== undefined && { name: composite.name }),
|
|
182
|
-
...(composite.members !== undefined && { members: composite.members }),
|
|
183
|
-
};
|
|
184
|
-
}
|
|
185
|
-
function assertNoSharedVersionFiles(workspaces, directories, project) {
|
|
186
|
-
const owners = [
|
|
187
|
-
...directories.map((directory) => ({
|
|
188
|
-
label: `directory release '${directory.name}'`,
|
|
189
|
-
versionFiles: [directory.versionFile],
|
|
190
|
-
})),
|
|
191
|
-
...workspaces.map((workspace) => ({
|
|
192
|
-
label: `workspace '${getTargetName(workspace)}'`,
|
|
193
|
-
versionFiles: workspace.packageFiles.map(normalizeWorkspacePath),
|
|
194
|
-
})),
|
|
195
|
-
...(project === undefined
|
|
196
|
-
? []
|
|
197
|
-
: [
|
|
198
|
-
{
|
|
199
|
-
label: project.name === undefined ? 'the project release' : `composite release '${project.name}'`,
|
|
200
|
-
versionFiles: [ROOT_PACKAGE_JSON_PATH],
|
|
201
|
-
},
|
|
202
|
-
]),
|
|
203
|
-
];
|
|
204
|
-
for (const [index, directory] of directories.entries()) {
|
|
205
|
-
const peer = owners.slice(index + 1).find((owner) => owner.versionFiles.includes(directory.versionFile));
|
|
206
|
-
if (peer !== undefined) {
|
|
207
|
-
throw new Error(`directory release '${directory.name}' and ${peer.label} share the version file '${directory.versionFile}', ` +
|
|
208
|
-
"so each release would overwrite the other's version and changelog; declare only one of them");
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
function assertNoTagPrefixCollisions(workspaces, directories, retiredPackages, project) {
|
|
213
|
-
const sources = [];
|
|
214
|
-
for (const workspace of workspaces) {
|
|
215
|
-
const owner = `ws:${workspace.dir}`;
|
|
216
|
-
sources.push({ prefix: workspace.tagPrefix, label: `workspace '${workspace.dir}'`, owner });
|
|
217
|
-
const legacyIdentities = workspace.legacyIdentities ?? [];
|
|
218
|
-
for (const identity of legacyIdentities) {
|
|
219
|
-
sources.push({
|
|
220
|
-
prefix: identity.tagPrefix,
|
|
221
|
-
label: `workspace '${workspace.dir}' legacyIdentities entry (name='${identity.name}')`,
|
|
222
|
-
owner,
|
|
223
|
-
});
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
for (const directory of directories) {
|
|
227
|
-
const owner = `dir:${directory.path}`;
|
|
228
|
-
sources.push({ prefix: directory.tagPrefix, label: `directory release '${directory.name}'`, owner });
|
|
229
|
-
const legacyIdentities = directory.legacyIdentities ?? [];
|
|
230
|
-
for (const identity of legacyIdentities) {
|
|
231
|
-
sources.push({
|
|
232
|
-
prefix: identity.tagPrefix,
|
|
233
|
-
label: `directory release '${directory.name}' legacyIdentities entry (name='${identity.name}')`,
|
|
234
|
-
owner,
|
|
235
|
-
});
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
const retiredEntries = (retiredPackages ?? []).entries();
|
|
239
|
-
for (const [index, retired] of retiredEntries) {
|
|
240
|
-
sources.push({
|
|
241
|
-
prefix: retired.tagPrefix,
|
|
242
|
-
label: `retiredPackages entry (name='${retired.name}')`,
|
|
243
|
-
owner: `retired:${index}`,
|
|
244
|
-
});
|
|
245
|
-
}
|
|
246
|
-
if (project !== undefined) {
|
|
247
|
-
sources.push({
|
|
248
|
-
prefix: project.tagPrefix,
|
|
249
|
-
label: project.name === undefined ? 'project' : `composite release '${project.name}'`,
|
|
250
|
-
owner: 'project',
|
|
251
|
-
});
|
|
252
|
-
}
|
|
253
|
-
for (let i = 0; i < sources.length; i++) {
|
|
254
|
-
for (let j = i + 1; j < sources.length; j++) {
|
|
255
|
-
const a = sources[i];
|
|
256
|
-
const b = sources[j];
|
|
257
|
-
if (a === undefined || b === undefined)
|
|
258
|
-
continue;
|
|
259
|
-
if (a.owner === b.owner)
|
|
260
|
-
continue;
|
|
261
|
-
if (isPrefixCollision(a.prefix, b.prefix)) {
|
|
262
|
-
throw new Error(`Tag prefix collision: '${a.prefix}' (${a.label}) and '${b.prefix}' (${b.label}). ` +
|
|
263
|
-
'One prefix is identical to or a strict prefix of the other; ' +
|
|
264
|
-
'a tag under the longer prefix can then match the shorter one and be counted among its releases.');
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
function isPrefixCollision(a, b) {
|
|
270
|
-
return a === b || a.startsWith(b) || b.startsWith(a);
|
|
271
|
-
}
|
|
272
|
-
function assertUniqueTagPrefixes(workspaces) {
|
|
273
|
-
const pathsByPrefix = new Map();
|
|
274
|
-
for (const workspace of workspaces) {
|
|
275
|
-
const existing = pathsByPrefix.get(workspace.tagPrefix);
|
|
276
|
-
if (existing === undefined) {
|
|
277
|
-
pathsByPrefix.set(workspace.tagPrefix, [workspace.workspacePath]);
|
|
278
|
-
}
|
|
279
|
-
else {
|
|
280
|
-
existing.push(workspace.workspacePath);
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
for (const [prefix, paths] of pathsByPrefix) {
|
|
284
|
-
if (paths.length > 1) {
|
|
285
|
-
throw new Error(`Duplicate tag prefix '${prefix}' for workspaces: ${paths.join(', ')}`);
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
function mergeReleaseNotesConfig(partial) {
|
|
290
|
-
if (partial === undefined) {
|
|
291
|
-
return { ...DEFAULT_RELEASE_NOTES_CONFIG };
|
|
292
|
-
}
|
|
293
|
-
return {
|
|
294
|
-
shouldInjectIntoReadme: partial.shouldInjectIntoReadme ?? DEFAULT_RELEASE_NOTES_CONFIG.shouldInjectIntoReadme,
|
|
295
|
-
};
|
|
296
|
-
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type OutputStyle } from '@williamthorsen/nmr-core';
|
|
2
|
-
import type { ReleaseKitConfig } from './
|
|
2
|
+
import type { ReleaseKitConfig } from './configSchema.js';
|
|
3
3
|
/**
|
|
4
4
|
* Why a config file could not be used: The import threw, or the contents failed the schema.
|
|
5
5
|
*
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { ReleaseKitConfig } from './configSchema.js';
|
|
2
|
+
import type { MonorepoReleaseConfig, ReleaseSettings, WorkTypeConfig } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Resolves a final monorepo config from discovered workspaces and an optional user config overlay.
|
|
5
|
+
*
|
|
6
|
+
* Merging rules:
|
|
7
|
+
* - Release targets: `resolveReleaseDeclaration` projects the `releases` block, or the `workspaces` overrides and the
|
|
8
|
+
* `project` block, onto the discovered workspaces. Excluded workspaces move to `excludedWorkspaces`, and the
|
|
9
|
+
* directories that `releases` declares resolve through `deriveDirectoryConfig` into `directories`, which must not
|
|
10
|
+
* share a version file with any other target.
|
|
11
|
+
* - `workTypes`: Shallow merge; consumer entries override or add to defaults by key.
|
|
12
|
+
* - `versionPatterns`: Consumer value replaces defaults entirely.
|
|
13
|
+
* - Pass-through fields (see `applyOptionalPassthroughFields`): Consumer value wins.
|
|
14
|
+
* - `project`: Present iff the config declares a composite. Resolves `tagPrefix` to `DEFAULT_PROJECT_TAG_PREFIX` and
|
|
15
|
+
* `paths` to the union of its members' `paths` when omitted. Requires `rootPackage` to be passed and to contain a
|
|
16
|
+
* valid `version` field; throws otherwise. The resolved prefix is included in the strict-prefix collision check
|
|
17
|
+
* across all workspace and retired-package prefixes.
|
|
18
|
+
*/
|
|
19
|
+
export declare function mergeMonorepoConfig(discoveredPaths: string[], userConfig: ReleaseKitConfig | undefined, rootPackage?: RootPackageInfo): MonorepoReleaseConfig;
|
|
20
|
+
/**
|
|
21
|
+
* Resolves the release settings of a single-package repo from an optional user config overlay.
|
|
22
|
+
*
|
|
23
|
+
* Rejects a configured `project` or `releases` block: Both declare the targets of a monorepo, and a single-package
|
|
24
|
+
* repo has one target, its root package.
|
|
25
|
+
*/
|
|
26
|
+
export declare function mergeSinglePackageConfig(userConfig: ReleaseKitConfig | undefined): ReleaseSettings;
|
|
27
|
+
/**
|
|
28
|
+
* Merges consumer work-type overrides onto `DEFAULT_WORK_TYPES`.
|
|
29
|
+
*
|
|
30
|
+
* Preserves the declaration order of defaults; net-new consumer keys append at the end.
|
|
31
|
+
*/
|
|
32
|
+
export declare function resolveWorkTypes(userWorkTypes?: ReleaseKitConfig['workTypes']): Record<string, WorkTypeConfig>;
|
|
33
|
+
/**
|
|
34
|
+
* Information about the root `package.json` passed into `mergeMonorepoConfig` when a
|
|
35
|
+
* `project` block is configured. `readRootPackageVersion` performs the read, so that
|
|
36
|
+
* `mergeMonorepoConfig` does not perform any I/O.
|
|
37
|
+
*/
|
|
38
|
+
export interface RootPackageInfo {
|
|
39
|
+
exists: boolean;
|
|
40
|
+
version: string | undefined;
|
|
41
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { DEFAULT_CHANGELOG_JSON_CONFIG, DEFAULT_PROJECT_TAG_PREFIX, DEFAULT_RELEASE_NOTES_CONFIG, DEFAULT_VERSION_PATTERNS, DEFAULT_WORK_TYPES, } from "./defaults.js";
|
|
2
|
+
import { deriveDirectoryConfig } from "./deriveDirectoryConfig.js";
|
|
3
|
+
import { deriveWorkspaceConfig } from "./deriveWorkspaceConfig.js";
|
|
4
|
+
import { ROOT_PACKAGE_JSON_PATH } from "./loadConfig.js";
|
|
5
|
+
import { assertLegacyIdentityDoesNotMatchCurrent, assertNoSharedVersionFiles, assertNoTagPrefixCollisions, assertRetiredPackagesDoNotCollideWithActive, assertUniqueTagPrefixes, } from "./monorepoConfigAssertions.js";
|
|
6
|
+
import { resolveReleaseDeclaration } from "./resolveReleaseDeclaration.js";
|
|
7
|
+
export function mergeMonorepoConfig(discoveredPaths, userConfig, rootPackage) {
|
|
8
|
+
const discovered = discoveredPaths.map((workspacePath) => deriveWorkspaceConfig(workspacePath));
|
|
9
|
+
assertUniqueTagPrefixes(discovered);
|
|
10
|
+
const { retained: workspaces, excluded, directories: declaredDirectories, composite, } = resolveReleaseDeclaration(discovered, userConfig);
|
|
11
|
+
const directories = declaredDirectories.map((declared) => deriveDirectoryConfig(declared));
|
|
12
|
+
for (const workspace of workspaces) {
|
|
13
|
+
if (workspace.legacyIdentities !== undefined) {
|
|
14
|
+
assertLegacyIdentityDoesNotMatchCurrent(workspace.dir, workspace.name, workspace.tagPrefix, workspace.legacyIdentities);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
if (userConfig?.retiredPackages !== undefined) {
|
|
18
|
+
assertRetiredPackagesDoNotCollideWithActive(workspaces, userConfig.retiredPackages);
|
|
19
|
+
}
|
|
20
|
+
const project = resolveProjectConfig(composite, rootPackage, workspaces);
|
|
21
|
+
const workTypes = resolveWorkTypes(userConfig?.workTypes);
|
|
22
|
+
const versionPatterns = userConfig?.versionPatterns === undefined ? { ...DEFAULT_VERSION_PATTERNS } : { ...userConfig.versionPatterns };
|
|
23
|
+
const changelogJson = mergeChangelogJsonConfig(userConfig?.changelogJson);
|
|
24
|
+
const releaseNotes = mergeReleaseNotesConfig(userConfig?.releaseNotes);
|
|
25
|
+
assertNoSharedVersionFiles(workspaces, directories, project);
|
|
26
|
+
assertNoTagPrefixCollisions(workspaces, directories, userConfig?.retiredPackages, project);
|
|
27
|
+
const result = {
|
|
28
|
+
workspaces,
|
|
29
|
+
workTypes,
|
|
30
|
+
versionPatterns,
|
|
31
|
+
changelogJson,
|
|
32
|
+
releaseNotes,
|
|
33
|
+
};
|
|
34
|
+
if (excluded.length > 0) {
|
|
35
|
+
result.excludedWorkspaces = excluded;
|
|
36
|
+
}
|
|
37
|
+
if (directories.length > 0) {
|
|
38
|
+
result.directories = directories;
|
|
39
|
+
}
|
|
40
|
+
if (project !== undefined) {
|
|
41
|
+
result.project = project;
|
|
42
|
+
}
|
|
43
|
+
applyOptionalPassthroughFields(result, userConfig);
|
|
44
|
+
return result;
|
|
45
|
+
}
|
|
46
|
+
export function mergeSinglePackageConfig(userConfig) {
|
|
47
|
+
if (userConfig?.project !== undefined) {
|
|
48
|
+
throw new Error('project block is not supported in single-package mode');
|
|
49
|
+
}
|
|
50
|
+
if (userConfig?.releases !== undefined) {
|
|
51
|
+
throw new Error('releases block is not supported in single-package mode');
|
|
52
|
+
}
|
|
53
|
+
const workTypes = resolveWorkTypes(userConfig?.workTypes);
|
|
54
|
+
const versionPatterns = userConfig?.versionPatterns === undefined ? { ...DEFAULT_VERSION_PATTERNS } : { ...userConfig.versionPatterns };
|
|
55
|
+
const changelogJson = mergeChangelogJsonConfig(userConfig?.changelogJson);
|
|
56
|
+
const releaseNotes = mergeReleaseNotesConfig(userConfig?.releaseNotes);
|
|
57
|
+
const result = {
|
|
58
|
+
workTypes,
|
|
59
|
+
versionPatterns,
|
|
60
|
+
changelogJson,
|
|
61
|
+
releaseNotes,
|
|
62
|
+
};
|
|
63
|
+
applyOptionalPassthroughFields(result, userConfig);
|
|
64
|
+
return result;
|
|
65
|
+
}
|
|
66
|
+
export function resolveWorkTypes(userWorkTypes) {
|
|
67
|
+
return userWorkTypes === undefined ? { ...DEFAULT_WORK_TYPES } : { ...DEFAULT_WORK_TYPES, ...userWorkTypes };
|
|
68
|
+
}
|
|
69
|
+
function applyOptionalPassthroughFields(result, userConfig) {
|
|
70
|
+
if (userConfig?.changelogMarkdown !== undefined) {
|
|
71
|
+
result.changelogMarkdown = userConfig.changelogMarkdown;
|
|
72
|
+
}
|
|
73
|
+
if (userConfig?.formatCommand !== undefined) {
|
|
74
|
+
result.formatCommand = userConfig.formatCommand;
|
|
75
|
+
}
|
|
76
|
+
if (userConfig?.scopeAliases !== undefined) {
|
|
77
|
+
result.scopeAliases = userConfig.scopeAliases;
|
|
78
|
+
}
|
|
79
|
+
if (userConfig?.breakingPolicies !== undefined) {
|
|
80
|
+
result.breakingPolicies = userConfig.breakingPolicies;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function mergeChangelogJsonConfig(partial) {
|
|
84
|
+
if (partial === undefined) {
|
|
85
|
+
return { ...DEFAULT_CHANGELOG_JSON_CONFIG };
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
enabled: partial.enabled ?? DEFAULT_CHANGELOG_JSON_CONFIG.enabled,
|
|
89
|
+
outputPath: partial.outputPath ?? DEFAULT_CHANGELOG_JSON_CONFIG.outputPath,
|
|
90
|
+
devOnlySections: partial.devOnlySections ?? [...DEFAULT_CHANGELOG_JSON_CONFIG.devOnlySections],
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
function mergeReleaseNotesConfig(partial) {
|
|
94
|
+
if (partial === undefined) {
|
|
95
|
+
return { ...DEFAULT_RELEASE_NOTES_CONFIG };
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
shouldInjectIntoReadme: partial.shouldInjectIntoReadme ?? DEFAULT_RELEASE_NOTES_CONFIG.shouldInjectIntoReadme,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
function resolveProjectConfig(composite, rootPackage, workspaces) {
|
|
102
|
+
if (composite === undefined) {
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
const subject = composite.name === undefined ? 'project block' : `composite release '${composite.name}'`;
|
|
106
|
+
if (rootPackage === undefined || !rootPackage.exists) {
|
|
107
|
+
throw new Error(`${subject} requires a root ${ROOT_PACKAGE_JSON_PATH}; create one with a 'version' field at the repo root`);
|
|
108
|
+
}
|
|
109
|
+
if (rootPackage.version === undefined) {
|
|
110
|
+
throw new Error(`${subject} requires root ${ROOT_PACKAGE_JSON_PATH} to have a 'version' field; add a 'version' field to the root package.json`);
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
paths: composite.paths ?? workspaces.flatMap((workspace) => workspace.paths),
|
|
114
|
+
tagPrefix: composite.tagPrefix ?? DEFAULT_PROJECT_TAG_PREFIX,
|
|
115
|
+
...(composite.name !== undefined && { name: composite.name }),
|
|
116
|
+
...(composite.members !== undefined && { members: composite.members }),
|
|
117
|
+
};
|
|
118
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { LegacyIdentity, RetiredPackage } from './configSchema.js';
|
|
2
|
+
import type { DirectoryConfig, ResolvedProjectConfig, WorkspaceConfig } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Throws when a workspace's `legacyIdentities` contains its current identity.
|
|
5
|
+
*
|
|
6
|
+
* An identity whose full `(name, tagPrefix)` tuple equals the current workspace's
|
|
7
|
+
* `(name, tagPrefix)` is a guaranteed no-op duplicate, almost always a copy-paste mistake.
|
|
8
|
+
* An entry whose `tagPrefix` matches but whose `name` differs is valid: It documents a prior
|
|
9
|
+
* rename that reused the same tag shape.
|
|
10
|
+
*/
|
|
11
|
+
export declare function assertLegacyIdentityDoesNotMatchCurrent(dir: string, currentName: string, currentTagPrefix: string, legacyIdentities: readonly LegacyIdentity[]): void;
|
|
12
|
+
/**
|
|
13
|
+
* Throws when a directory target shares its version file with another target, whose release would bump the same
|
|
14
|
+
* `version` and write the same `CHANGELOG.md`: the repo root beside a composite, which the root `package.json` also
|
|
15
|
+
* versions. Pairs of other targets keep the behavior that they had before directories existed, so the check covers
|
|
16
|
+
* only pairs that include a directory.
|
|
17
|
+
*/
|
|
18
|
+
export declare function assertNoSharedVersionFiles(workspaces: readonly WorkspaceConfig[], directories: readonly DirectoryConfig[], project: ResolvedProjectConfig | undefined): void;
|
|
19
|
+
/**
|
|
20
|
+
* Throws when any pair of tag prefixes from distinct owners is identical or one is a strict
|
|
21
|
+
* prefix of the other.
|
|
22
|
+
*
|
|
23
|
+
* A tag matches a prefix when the rest of its name is a complete SemVer version, so a prefix
|
|
24
|
+
* `'pkg-'` matches `'pkg-1.0.0-v1.2.3'` through its pre-release suffix. The owners are each
|
|
25
|
+
* workspace, each directory, each retired package, and the project. A workspace or a directory owns
|
|
26
|
+
* both its current prefix and its legacy-identity prefixes, because a prior identity may reuse the
|
|
27
|
+
* current tag shape under a different name.
|
|
28
|
+
*/
|
|
29
|
+
export declare function assertNoTagPrefixCollisions(workspaces: readonly WorkspaceConfig[], directories: readonly DirectoryConfig[], retiredPackages: readonly RetiredPackage[] | undefined, project: ResolvedProjectConfig | undefined): void;
|
|
30
|
+
/**
|
|
31
|
+
* Throws when a retired package's `tagPrefix` matches an active workspace's derived prefix.
|
|
32
|
+
*
|
|
33
|
+
* A retired package is, by definition, no longer hosted by any active workspace in this repo.
|
|
34
|
+
* If its declared `tagPrefix` equals an active workspace's derived prefix, new tags from that
|
|
35
|
+
* workspace would collide with the retired package's historical tags.
|
|
36
|
+
*/
|
|
37
|
+
export declare function assertRetiredPackagesDoNotCollideWithActive(workspaces: readonly WorkspaceConfig[], retiredPackages: readonly RetiredPackage[]): void;
|
|
38
|
+
/**
|
|
39
|
+
* Throws when two or more workspaces share the same `tagPrefix`.
|
|
40
|
+
*
|
|
41
|
+
* A collision means two workspaces would produce indistinguishable tags, breaking both tag
|
|
42
|
+
* creation and tag resolution. The error lists every colliding workspace path so that the author
|
|
43
|
+
* can rename one of the conflicting `package.json` `name` fields.
|
|
44
|
+
*
|
|
45
|
+
* This guards the pre-merge state when only workspaces have been derived. The broader
|
|
46
|
+
* strict-prefix check across legacy, retired, and project prefixes runs later in
|
|
47
|
+
* `assertNoTagPrefixCollisions`.
|
|
48
|
+
*/
|
|
49
|
+
export declare function assertUniqueTagPrefixes(workspaces: readonly WorkspaceConfig[]): void;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { ROOT_PACKAGE_JSON_PATH } from "./loadConfig.js";
|
|
2
|
+
import { getTargetName, normalizeWorkspacePath } from "./resolveReleaseDeclaration.js";
|
|
3
|
+
export function assertLegacyIdentityDoesNotMatchCurrent(dir, currentName, currentTagPrefix, legacyIdentities) {
|
|
4
|
+
for (const identity of legacyIdentities) {
|
|
5
|
+
if (identity.name === currentName && identity.tagPrefix === currentTagPrefix) {
|
|
6
|
+
throw new Error(`Workspace '${dir}': legacyIdentities must not match the current identity ` +
|
|
7
|
+
`(name='${currentName}', tagPrefix='${currentTagPrefix}'). ` +
|
|
8
|
+
'The current identity is always searched; listing it again is a no-op.');
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export function assertNoSharedVersionFiles(workspaces, directories, project) {
|
|
13
|
+
const owners = [
|
|
14
|
+
...directories.map((directory) => ({
|
|
15
|
+
label: `directory release '${directory.name}'`,
|
|
16
|
+
versionFiles: [directory.versionFile],
|
|
17
|
+
})),
|
|
18
|
+
...workspaces.map((workspace) => ({
|
|
19
|
+
label: `workspace '${getTargetName(workspace)}'`,
|
|
20
|
+
versionFiles: workspace.packageFiles.map(normalizeWorkspacePath),
|
|
21
|
+
})),
|
|
22
|
+
...(project === undefined
|
|
23
|
+
? []
|
|
24
|
+
: [
|
|
25
|
+
{
|
|
26
|
+
label: project.name === undefined ? 'the project release' : `composite release '${project.name}'`,
|
|
27
|
+
versionFiles: [ROOT_PACKAGE_JSON_PATH],
|
|
28
|
+
},
|
|
29
|
+
]),
|
|
30
|
+
];
|
|
31
|
+
for (const [index, directory] of directories.entries()) {
|
|
32
|
+
const peer = owners.slice(index + 1).find((owner) => owner.versionFiles.includes(directory.versionFile));
|
|
33
|
+
if (peer !== undefined) {
|
|
34
|
+
throw new Error(`directory release '${directory.name}' and ${peer.label} share the version file '${directory.versionFile}', ` +
|
|
35
|
+
"so each release would overwrite the other's version and changelog; declare only one of them");
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export function assertNoTagPrefixCollisions(workspaces, directories, retiredPackages, project) {
|
|
40
|
+
const sources = [];
|
|
41
|
+
for (const workspace of workspaces) {
|
|
42
|
+
const owner = `ws:${workspace.dir}`;
|
|
43
|
+
sources.push({ prefix: workspace.tagPrefix, label: `workspace '${workspace.dir}'`, owner });
|
|
44
|
+
const legacyIdentities = workspace.legacyIdentities ?? [];
|
|
45
|
+
for (const identity of legacyIdentities) {
|
|
46
|
+
sources.push({
|
|
47
|
+
prefix: identity.tagPrefix,
|
|
48
|
+
label: `workspace '${workspace.dir}' legacyIdentities entry (name='${identity.name}')`,
|
|
49
|
+
owner,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
for (const directory of directories) {
|
|
54
|
+
const owner = `dir:${directory.path}`;
|
|
55
|
+
sources.push({ prefix: directory.tagPrefix, label: `directory release '${directory.name}'`, owner });
|
|
56
|
+
const legacyIdentities = directory.legacyIdentities ?? [];
|
|
57
|
+
for (const identity of legacyIdentities) {
|
|
58
|
+
sources.push({
|
|
59
|
+
prefix: identity.tagPrefix,
|
|
60
|
+
label: `directory release '${directory.name}' legacyIdentities entry (name='${identity.name}')`,
|
|
61
|
+
owner,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const retiredEntries = (retiredPackages ?? []).entries();
|
|
66
|
+
for (const [index, retired] of retiredEntries) {
|
|
67
|
+
sources.push({
|
|
68
|
+
prefix: retired.tagPrefix,
|
|
69
|
+
label: `retiredPackages entry (name='${retired.name}')`,
|
|
70
|
+
owner: `retired:${index}`,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
if (project !== undefined) {
|
|
74
|
+
sources.push({
|
|
75
|
+
prefix: project.tagPrefix,
|
|
76
|
+
label: project.name === undefined ? 'project' : `composite release '${project.name}'`,
|
|
77
|
+
owner: 'project',
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
for (let i = 0; i < sources.length; i++) {
|
|
81
|
+
for (let j = i + 1; j < sources.length; j++) {
|
|
82
|
+
const a = sources[i];
|
|
83
|
+
const b = sources[j];
|
|
84
|
+
if (a === undefined || b === undefined || a.owner === b.owner)
|
|
85
|
+
continue;
|
|
86
|
+
if (isPrefixCollision(a.prefix, b.prefix)) {
|
|
87
|
+
throw new Error(`Tag prefix collision: '${a.prefix}' (${a.label}) and '${b.prefix}' (${b.label}). ` +
|
|
88
|
+
'One prefix is identical to or a strict prefix of the other; ' +
|
|
89
|
+
'a tag under the longer prefix can then match the shorter one and be counted among its releases.');
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
export function assertRetiredPackagesDoNotCollideWithActive(workspaces, retiredPackages) {
|
|
95
|
+
const workspaceByDerivedPrefix = new Map();
|
|
96
|
+
for (const workspace of workspaces) {
|
|
97
|
+
workspaceByDerivedPrefix.set(workspace.tagPrefix, workspace);
|
|
98
|
+
}
|
|
99
|
+
for (const retired of retiredPackages) {
|
|
100
|
+
const active = workspaceByDerivedPrefix.get(retired.tagPrefix);
|
|
101
|
+
if (active !== undefined) {
|
|
102
|
+
throw new Error(`retiredPackages: tagPrefix '${retired.tagPrefix}' collides with active workspace '${active.dir}' ` +
|
|
103
|
+
`(derived prefix '${active.tagPrefix}'). A retired package's tagPrefix cannot belong to an active workspace.`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
export function assertUniqueTagPrefixes(workspaces) {
|
|
108
|
+
const pathsByPrefix = new Map();
|
|
109
|
+
for (const workspace of workspaces) {
|
|
110
|
+
const existing = pathsByPrefix.get(workspace.tagPrefix);
|
|
111
|
+
if (existing === undefined) {
|
|
112
|
+
pathsByPrefix.set(workspace.tagPrefix, [workspace.workspacePath]);
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
existing.push(workspace.workspacePath);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
for (const [prefix, paths] of pathsByPrefix) {
|
|
119
|
+
if (paths.length > 1) {
|
|
120
|
+
throw new Error(`Duplicate tag prefix '${prefix}' for workspaces: ${paths.join(', ')}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
function isPrefixCollision(a, b) {
|
|
125
|
+
return a === b || a.startsWith(b) || b.startsWith(a);
|
|
126
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { PlannedWrite } from './releasePlan.js';
|
|
2
|
+
import type { ChangelogEntry, ChangelogPreservation, ReleaseSettings } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Plans the `changelog.json` (when enabled), `CHANGELOG.md`, and archive files of one changelog directory from the new
|
|
5
|
+
* entries merged with the entries on disk, so that the markdown includes prior releases whether or not the JSON is
|
|
6
|
+
* written. Also plans the removal of every existing archive file that the render does not produce.
|
|
7
|
+
*/
|
|
8
|
+
export declare function planChangelogFiles(changelogDir: string, newEntries: ChangelogEntry[], settings: Pick<ReleaseSettings, 'changelogJson' | 'changelogMarkdown'>, sectionOrder: string[]): {
|
|
9
|
+
/** The `CHANGELOG.md`, then its archive files. */
|
|
10
|
+
changelogFiles: string[];
|
|
11
|
+
changelogJsonFiles: string[];
|
|
12
|
+
entries: ChangelogEntry[];
|
|
13
|
+
preservation: ChangelogPreservation | undefined;
|
|
14
|
+
removals: string[];
|
|
15
|
+
writes: PlannedWrite[];
|
|
16
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { join as joinPath } from 'node:path';
|
|
2
|
+
import { describeArchivePath, listArchiveFiles } from "./changelogArchive.js";
|
|
3
|
+
import { mergeChangelogEntriesWithDisk, renderChangelogJson, resolveChangelogJsonPath } from "./changelogJsonFile.js";
|
|
4
|
+
import { planPreservedSections } from "./readChangelogSections.js";
|
|
5
|
+
import { renderChangelogFiles } from "./renderChangelogMarkdown.js";
|
|
6
|
+
export function planChangelogFiles(changelogDir, newEntries, settings, sectionOrder) {
|
|
7
|
+
const jsonPath = resolveChangelogJsonPath(settings, changelogDir);
|
|
8
|
+
const entries = mergeChangelogEntriesWithDisk(jsonPath, newEntries);
|
|
9
|
+
const writes = [];
|
|
10
|
+
const changelogJsonFiles = [];
|
|
11
|
+
if (settings.changelogJson.enabled) {
|
|
12
|
+
writes.push({ path: jsonPath, content: renderChangelogJson(entries) });
|
|
13
|
+
changelogJsonFiles.push(jsonPath);
|
|
14
|
+
}
|
|
15
|
+
const changelogFile = joinPath(changelogDir, 'CHANGELOG.md');
|
|
16
|
+
const preserved = planPreservedSections(changelogDir, entries);
|
|
17
|
+
const keepVersions = settings.changelogMarkdown?.keepVersions;
|
|
18
|
+
const rendered = renderChangelogFiles(entries, {
|
|
19
|
+
sectionOrder,
|
|
20
|
+
preservedSections: preserved.sections,
|
|
21
|
+
...(keepVersions !== undefined && { keepVersions }),
|
|
22
|
+
});
|
|
23
|
+
writes.push({ path: changelogFile, content: rendered.main });
|
|
24
|
+
const archiveFiles = [];
|
|
25
|
+
for (const { major, content } of rendered.archives) {
|
|
26
|
+
const path = joinPath(changelogDir, describeArchivePath(major));
|
|
27
|
+
writes.push({ path, content });
|
|
28
|
+
archiveFiles.push(path);
|
|
29
|
+
}
|
|
30
|
+
const removals = listArchiveFiles(changelogDir)
|
|
31
|
+
.map((archive) => archive.path)
|
|
32
|
+
.filter((path) => !archiveFiles.includes(path));
|
|
33
|
+
return {
|
|
34
|
+
changelogFiles: [changelogFile, ...archiveFiles],
|
|
35
|
+
changelogJsonFiles,
|
|
36
|
+
entries,
|
|
37
|
+
preservation: preserved.preservation,
|
|
38
|
+
removals,
|
|
39
|
+
writes,
|
|
40
|
+
};
|
|
41
|
+
}
|