@williamthorsen/release-kit 14.1.0 → 14.2.1
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 +136 -0
- package/.readyup/kits/default.js +63 -6
- package/.readyup/kits/npm-auto-publish.js +47 -35
- package/.readyup/manifest.json +12 -7
- package/CHANGELOG.md +39 -0
- package/README.md +2 -16
- package/dist/esm/assertTaggedBaseline.d.ts +2 -2
- package/dist/esm/attachChangelogDiagnostics.d.ts +2 -2
- package/dist/esm/bin/release-kit.js +5 -4
- package/dist/esm/buildChangelogEntries.d.ts +3 -3
- package/dist/esm/buildChangelogEntries.js +3 -3
- package/dist/esm/buildDependencyGraph.d.ts +11 -11
- package/dist/esm/buildDependencyGraph.js +13 -13
- package/dist/esm/buildReleaseSummary.d.ts +3 -3
- package/dist/esm/buildReleaseSummary.js +4 -12
- package/dist/esm/changelogArchive.d.ts +14 -0
- package/dist/esm/changelogArchive.js +21 -0
- package/dist/esm/changelogJsonFile.d.ts +2 -2
- package/dist/esm/changelogOverrides.d.ts +9 -8
- package/dist/esm/changelogOverrides.js +3 -6
- package/dist/esm/createGithubReleaseCommand.d.ts +2 -1
- package/dist/esm/createGithubReleaseCommand.js +7 -4
- package/dist/esm/deriveDirectoryConfig.d.ts +13 -0
- package/dist/esm/deriveDirectoryConfig.js +59 -0
- package/dist/esm/deriveWorkspaceConfig.js +0 -1
- package/dist/esm/discoverWorkspaces.d.ts +3 -3
- package/dist/esm/findTransitiveDependents.js +3 -3
- package/dist/esm/findUnroutedEntryScopes.d.ts +2 -2
- package/dist/esm/formatDirectoryPublishSkip.d.ts +3 -0
- package/dist/esm/formatDirectoryPublishSkip.js +3 -0
- package/dist/esm/formatExcludedSkip.d.ts +5 -2
- package/dist/esm/formatExcludedSkip.js +2 -2
- package/dist/esm/formatProjectPublishSkip.d.ts +3 -0
- package/dist/esm/formatProjectPublishSkip.js +3 -0
- package/dist/esm/generateChangelogs.d.ts +7 -7
- package/dist/esm/generateChangelogs.js +2 -2
- package/dist/esm/help/prepareHelp.d.ts +1 -1
- package/dist/esm/help/prepareHelp.js +7 -5
- package/dist/esm/init/templates.js +1 -1
- package/dist/esm/loadConfig.d.ts +12 -12
- package/dist/esm/loadConfig.js +79 -30
- package/dist/esm/prepareCommand.d.ts +1 -1
- package/dist/esm/prepareCommand.js +47 -46
- package/dist/esm/prepareTarget.d.ts +103 -0
- package/dist/esm/prepareTarget.js +274 -0
- package/dist/esm/previewTagPrefixes.d.ts +5 -3
- package/dist/esm/previewTagPrefixes.js +58 -13
- package/dist/esm/propagateBumps.js +1 -1
- package/dist/esm/publishCommand.js +13 -1
- package/dist/esm/readChangelogSections.d.ts +6 -4
- package/dist/esm/readChangelogSections.js +18 -4
- package/dist/esm/releasePlan.d.ts +4 -2
- package/dist/esm/releasePlan.js +34 -12
- package/dist/esm/releasePrepareTargets.d.ts +47 -0
- package/dist/esm/releasePrepareTargets.js +335 -0
- package/dist/esm/renderChangelogMarkdown.d.ts +23 -0
- package/dist/esm/renderChangelogMarkdown.js +47 -11
- package/dist/esm/reportPrepare.d.ts +2 -3
- package/dist/esm/reportPrepare.js +24 -12
- package/dist/esm/resolveCommandTags.d.ts +3 -1
- package/dist/esm/resolveCommandTags.js +15 -22
- package/dist/esm/resolveReleaseDeclaration.d.ts +45 -0
- package/dist/esm/resolveReleaseDeclaration.js +153 -0
- package/dist/esm/resolveReleaseTags.d.ts +14 -3
- package/dist/esm/resolveReleaseTags.js +36 -15
- package/dist/esm/resolveReleaseTargets.d.ts +29 -0
- package/dist/esm/resolveReleaseTargets.js +131 -0
- package/dist/esm/sync-labels/generateCommand.js +2 -1
- package/dist/esm/sync-labels/resolveLabels.d.ts +4 -3
- package/dist/esm/sync-labels/resolveLabels.js +30 -1
- package/dist/esm/sync-labels/types.d.ts +2 -0
- package/dist/esm/types.d.ts +251 -167
- package/dist/esm/types.js +22 -0
- package/dist/esm/validateConfig.js +80 -21
- package/dist/esm/validateOverridesCommand.d.ts +1 -1
- package/dist/esm/validateOverridesCommand.js +17 -28
- package/docs/api.md +25 -58
- package/docs/changelogs.md +26 -6
- package/docs/configuration.md +79 -16
- package/docs/labels.md +25 -2
- package/docs/project-releases.md +42 -2
- package/docs/publishing.md +3 -3
- package/docs/releasing.md +4 -4
- package/docs/work-types.md +1 -1
- package/package.json +3 -3
- package/presets/labels/common.yaml +11 -0
- package/dist/esm/releasePrepare.d.ts +0 -36
- package/dist/esm/releasePrepare.js +0 -230
- package/dist/esm/releasePrepareMono.d.ts +0 -29
- package/dist/esm/releasePrepareMono.js +0 -503
- package/dist/esm/releasePrepareProject.d.ts +0 -47
- package/dist/esm/releasePrepareProject.js +0 -164
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type RawCommit } from './enumerateReleaseWindows.js';
|
|
2
2
|
import type { GenerateChangelogOptions } from './generateChangelogs.js';
|
|
3
|
-
import type { ChangelogEntry, ChangelogSection, MalformedChangeRecordBlock, PolicyViolation,
|
|
3
|
+
import type { ChangelogEntry, ChangelogSection, MalformedChangeRecordBlock, PolicyViolation, ReleaseSettings, ReleaseType, UndeclaredEntryType, UnroutedEntryScope } from './types.js';
|
|
4
4
|
/**
|
|
5
5
|
* Strips a section title down to its bare name.
|
|
6
6
|
*
|
|
@@ -35,13 +35,13 @@ export interface ChangelogDiagnostics {
|
|
|
35
35
|
* window decides: its commits, its bump, and its diagnostics.
|
|
36
36
|
*
|
|
37
37
|
* A commit whose last `change-record` block records entries yields one item per entry routed to
|
|
38
|
-
* `options.
|
|
38
|
+
* `options.scope`; any other commit is classified by its title through `classifyChangelogCommit`. A `release:`
|
|
39
39
|
* or merge subject yields nothing either way. The read records diagnostics only for the unreleased window, since the
|
|
40
40
|
* released windows were reported when they were prepared.
|
|
41
41
|
*/
|
|
42
42
|
export declare function readReleaseHistory(config: ReleaseHistoryConfig, options: GenerateChangelogOptions): ReleaseHistory;
|
|
43
43
|
/** The configuration that `readReleaseHistory` reads. */
|
|
44
|
-
export type ReleaseHistoryConfig = Pick<
|
|
44
|
+
export type ReleaseHistoryConfig = Pick<ReleaseSettings, 'breakingPolicies' | 'changelogJson' | 'scopeAliases' | 'versionPatterns' | 'workTypes'>;
|
|
45
45
|
/** One read of a scope's release windows. */
|
|
46
46
|
export interface ReleaseHistory {
|
|
47
47
|
/** The newest matching tag that HEAD reaches; undefined when none does. */
|
|
@@ -32,8 +32,8 @@ export function readReleaseHistory(config, options) {
|
|
|
32
32
|
const context = {
|
|
33
33
|
breakingPolicies: config.breakingPolicies ?? DEFAULT_BREAKING_POLICIES,
|
|
34
34
|
devOnlySections: new Set(config.changelogJson.devOnlySections.map(stripGroupDecorations)),
|
|
35
|
-
...(options.
|
|
36
|
-
routing: {
|
|
35
|
+
...(options.scope !== undefined && {
|
|
36
|
+
routing: { scope: options.scope, scopeAliases: config.scopeAliases ?? {} },
|
|
37
37
|
}),
|
|
38
38
|
versionPatterns: config.versionPatterns ?? DEFAULT_VERSION_PATTERNS,
|
|
39
39
|
workTypes: config.workTypes ?? DEFAULT_WORK_TYPES,
|
|
@@ -170,7 +170,7 @@ function isRoutedTo(scopes, routing) {
|
|
|
170
170
|
}
|
|
171
171
|
return scopes.some((scope) => {
|
|
172
172
|
const resolved = routing.scopeAliases[scope] ?? scope;
|
|
173
|
-
return resolved === '*' || resolved === routing.
|
|
173
|
+
return resolved === '*' || resolved === routing.scope;
|
|
174
174
|
});
|
|
175
175
|
}
|
|
176
176
|
function formatDate(timestamp) {
|
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
/** Identities, current versions, and `workspace:` dependency edges of the
|
|
1
|
+
import type { PackageTarget } from './types.js';
|
|
2
|
+
/** Identities, current versions, and `workspace:` dependency edges of the package targets. */
|
|
3
3
|
export interface DependencyGraph {
|
|
4
|
-
/** Maps a package name to its workspace `dir`. */
|
|
4
|
+
/** Maps a package name to its target id, the workspace `dir`. */
|
|
5
5
|
packageNameToDir: Map<string, string>;
|
|
6
|
-
/** Maps a workspace `dir` to its package name (inverse of `packageNameToDir`). */
|
|
6
|
+
/** Maps a target id (a workspace `dir`) to its package name (inverse of `packageNameToDir`). */
|
|
7
7
|
dirToPackageName: Map<string, string>;
|
|
8
|
-
/** Maps a
|
|
8
|
+
/** Maps a target id to its current version; absent when its `package.json` doesn't have a string `version`. */
|
|
9
9
|
dirToVersion: Map<string, string>;
|
|
10
|
-
/** Maps a package name to the
|
|
11
|
-
dependentsOf: Map<string,
|
|
10
|
+
/** Maps a package name to the targets that depend on it. */
|
|
11
|
+
dependentsOf: Map<string, PackageTarget[]>;
|
|
12
12
|
/**
|
|
13
|
-
* Maps a
|
|
13
|
+
* Maps a target id to the `workspace:`-protocol package names that it declares in `dependencies` or
|
|
14
14
|
* `peerDependencies` (the forward counterpart of `dependentsOf`).
|
|
15
15
|
*/
|
|
16
16
|
dependenciesOf: Map<string, Set<string>>;
|
|
17
17
|
}
|
|
18
18
|
/**
|
|
19
|
-
* Builds the dependency graph from the
|
|
19
|
+
* Builds the dependency graph from the package targets' `package.json` files.
|
|
20
20
|
*
|
|
21
|
-
* Reads each
|
|
21
|
+
* Reads each target's primary `package.json` (first entry in `versionFiles`) to discover
|
|
22
22
|
* `workspace:` references in `dependencies` and `peerDependencies`, and to record its current
|
|
23
23
|
* version.
|
|
24
24
|
*/
|
|
25
|
-
export declare function buildDependencyGraph(
|
|
25
|
+
export declare function buildDependencyGraph(targets: readonly PackageTarget[]): DependencyGraph;
|
|
@@ -3,30 +3,30 @@ import { chainError } from '@williamthorsen/toolbelt.errors/candidate';
|
|
|
3
3
|
function isPackageJsonSubset(value) {
|
|
4
4
|
return typeof value === 'object' && value !== null;
|
|
5
5
|
}
|
|
6
|
-
export function buildDependencyGraph(
|
|
6
|
+
export function buildDependencyGraph(targets) {
|
|
7
7
|
const packageNameToDir = new Map();
|
|
8
8
|
const dirToPackageName = new Map();
|
|
9
9
|
const dirToVersion = new Map();
|
|
10
10
|
const dependentsOf = new Map();
|
|
11
11
|
const dependenciesOf = new Map();
|
|
12
|
-
const
|
|
13
|
-
for (const
|
|
14
|
-
const primaryPackageFile =
|
|
12
|
+
const targetPackages = new Map();
|
|
13
|
+
for (const target of targets) {
|
|
14
|
+
const primaryPackageFile = target.versionFiles[0];
|
|
15
15
|
if (primaryPackageFile === undefined) {
|
|
16
16
|
continue;
|
|
17
17
|
}
|
|
18
18
|
const pkg = readPackageJsonSubset(primaryPackageFile);
|
|
19
|
-
|
|
19
|
+
targetPackages.set(target, pkg);
|
|
20
20
|
if (typeof pkg.version === 'string') {
|
|
21
|
-
dirToVersion.set(
|
|
21
|
+
dirToVersion.set(target.id, pkg.version);
|
|
22
22
|
}
|
|
23
23
|
if (pkg.name === undefined) {
|
|
24
24
|
continue;
|
|
25
25
|
}
|
|
26
|
-
packageNameToDir.set(pkg.name,
|
|
27
|
-
dirToPackageName.set(
|
|
26
|
+
packageNameToDir.set(pkg.name, target.id);
|
|
27
|
+
dirToPackageName.set(target.id, pkg.name);
|
|
28
28
|
}
|
|
29
|
-
for (const [
|
|
29
|
+
for (const [target, pkg] of targetPackages) {
|
|
30
30
|
const allDeps = { ...pkg.dependencies, ...pkg.peerDependencies };
|
|
31
31
|
for (const [depName, depVersion] of Object.entries(allDeps)) {
|
|
32
32
|
if (typeof depVersion !== 'string' || !depVersion.startsWith('workspace:')) {
|
|
@@ -34,14 +34,14 @@ export function buildDependencyGraph(workspaces) {
|
|
|
34
34
|
}
|
|
35
35
|
const existing = dependentsOf.get(depName);
|
|
36
36
|
if (existing === undefined) {
|
|
37
|
-
dependentsOf.set(depName, [
|
|
37
|
+
dependentsOf.set(depName, [target]);
|
|
38
38
|
}
|
|
39
39
|
else {
|
|
40
|
-
existing.push(
|
|
40
|
+
existing.push(target);
|
|
41
41
|
}
|
|
42
|
-
const forward = dependenciesOf.get(
|
|
42
|
+
const forward = dependenciesOf.get(target.id);
|
|
43
43
|
if (forward === undefined) {
|
|
44
|
-
dependenciesOf.set(
|
|
44
|
+
dependenciesOf.set(target.id, new Set([depName]));
|
|
45
45
|
}
|
|
46
46
|
else {
|
|
47
47
|
forward.add(depName);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { PrepareResult } from './types.js';
|
|
2
2
|
/**
|
|
3
|
-
* Builds a release commit body from a prepare result: one section per released
|
|
4
|
-
*
|
|
3
|
+
* Builds a release commit body from a prepare result: one section per released target that has commits, in result
|
|
4
|
+
* order, each headed by its tag and listing its scope-stripped commit subjects.
|
|
5
5
|
*/
|
|
6
|
-
export declare function buildReleaseSummary(result: Pick<PrepareResult, '
|
|
6
|
+
export declare function buildReleaseSummary(result: Pick<PrepareResult, 'targets'>): string;
|
|
@@ -1,27 +1,19 @@
|
|
|
1
1
|
import { stripScope } from "./stripScope.js";
|
|
2
2
|
export function buildReleaseSummary(result) {
|
|
3
3
|
const sections = [];
|
|
4
|
-
for (const
|
|
5
|
-
if (
|
|
4
|
+
for (const target of result.targets) {
|
|
5
|
+
if (target.status !== 'released') {
|
|
6
6
|
continue;
|
|
7
7
|
}
|
|
8
|
-
const commits =
|
|
8
|
+
const commits = target.commits;
|
|
9
9
|
if (commits === undefined || commits.length === 0) {
|
|
10
10
|
continue;
|
|
11
11
|
}
|
|
12
|
-
const lines = [
|
|
12
|
+
const lines = [target.tag];
|
|
13
13
|
for (const commit of commits) {
|
|
14
14
|
lines.push(`- ${stripScope(commit.subject)}`);
|
|
15
15
|
}
|
|
16
16
|
sections.push(lines.join('\n'));
|
|
17
17
|
}
|
|
18
|
-
const project = result.project;
|
|
19
|
-
if (project !== undefined && project.status === 'released' && project.commits.length > 0) {
|
|
20
|
-
const lines = [project.tag];
|
|
21
|
-
for (const commit of project.commits) {
|
|
22
|
-
lines.push(`- ${stripScope(commit.subject)}`);
|
|
23
|
-
}
|
|
24
|
-
sections.push(lines.join('\n'));
|
|
25
|
-
}
|
|
26
18
|
return sections.join('\n\n');
|
|
27
19
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Directory, relative to a changelog directory, that contains the per-major archive files of its `CHANGELOG.md`. */
|
|
2
|
+
export declare const CHANGELOG_ARCHIVE_DIRECTORY = "changelogs";
|
|
3
|
+
/** An archive file that exists on disk. */
|
|
4
|
+
export interface ChangelogArchivePath {
|
|
5
|
+
major: number;
|
|
6
|
+
path: string;
|
|
7
|
+
}
|
|
8
|
+
/** Returns the path, relative to the changelog directory, of the archive file for a major version. */
|
|
9
|
+
export declare function describeArchivePath(major: number): string;
|
|
10
|
+
/**
|
|
11
|
+
* Lists the archive files under the changelog directory, newest major first. A missing archive directory has none;
|
|
12
|
+
* files whose names do not match the archive pattern are ignored.
|
|
13
|
+
*/
|
|
14
|
+
export declare function listArchiveFiles(changelogDir: string): ChangelogArchivePath[];
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { existsSync, readdirSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
export const CHANGELOG_ARCHIVE_DIRECTORY = 'changelogs';
|
|
4
|
+
export function describeArchivePath(major) {
|
|
5
|
+
return `${CHANGELOG_ARCHIVE_DIRECTORY}/CHANGELOG-v${major}.md`;
|
|
6
|
+
}
|
|
7
|
+
export function listArchiveFiles(changelogDir) {
|
|
8
|
+
const archiveDir = join(changelogDir, CHANGELOG_ARCHIVE_DIRECTORY);
|
|
9
|
+
if (!existsSync(archiveDir)) {
|
|
10
|
+
return [];
|
|
11
|
+
}
|
|
12
|
+
const archives = [];
|
|
13
|
+
for (const name of readdirSync(archiveDir)) {
|
|
14
|
+
const match = ARCHIVE_FILE_PATTERN.exec(name);
|
|
15
|
+
if (match?.[1] !== undefined) {
|
|
16
|
+
archives.push({ major: Number(match[1]), path: join(archiveDir, name) });
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return archives.toSorted((a, b) => b.major - a.major);
|
|
20
|
+
}
|
|
21
|
+
const ARCHIVE_FILE_PATTERN = /^CHANGELOG-v(\d+)\.md$/;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { ChangelogEntry,
|
|
1
|
+
import type { ChangelogEntry, ReleaseSettings } from './types.js';
|
|
2
2
|
/** Resolves the path of the `changelog.json` file under a workspace's changelog directory. */
|
|
3
|
-
export declare function resolveChangelogJsonPath(config: Pick<
|
|
3
|
+
export declare function resolveChangelogJsonPath(config: Pick<ReleaseSettings, 'changelogJson'>, changelogPath: string): string;
|
|
4
4
|
/** Renders changelog entries as a `changelog.json` file's complete content, sorted newest-first. */
|
|
5
5
|
export declare function renderChangelogJson(entries: ChangelogEntry[]): string;
|
|
6
6
|
/**
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ChangelogEntry, ChangelogOverride
|
|
1
|
+
import type { ChangelogEntry, ChangelogOverride } from './types.js';
|
|
2
2
|
/** Known fields on a single override entry; presence of any other field is a validation error. */
|
|
3
3
|
export declare const KNOWN_OVERRIDE_FIELDS: Set<string>;
|
|
4
4
|
/** Form of an override key: a lowercase hex commit hash or prefix, optionally followed by `:<n>`, a 1-based entry position. */
|
|
@@ -102,11 +102,11 @@ export interface OverrideContext {
|
|
|
102
102
|
globalMatchedRootKeys: Set<string>;
|
|
103
103
|
}
|
|
104
104
|
/**
|
|
105
|
-
* Loads and validates the override files of the root and every workspace
|
|
106
|
-
* aggregators. Throws if any file fails to load, so that a malformed checked-in
|
|
107
|
-
* any workspace writes.
|
|
105
|
+
* Loads and validates the override files of the root and of every workspace at the given repo-relative paths, and
|
|
106
|
+
* bundles them with empty warning aggregators. Throws if any file fails to load, so that a malformed checked-in
|
|
107
|
+
* override file stops `prepare` before any workspace writes.
|
|
108
108
|
*/
|
|
109
|
-
export declare function createOverrideContext(
|
|
109
|
+
export declare function createOverrideContext(workspacePaths: readonly string[]): OverrideContext;
|
|
110
110
|
/** A changelog item as override matching sees it. `entry` is absent for a title-derived item. */
|
|
111
111
|
export interface OverrideTargetItem {
|
|
112
112
|
hash: string;
|
|
@@ -164,12 +164,13 @@ export interface ValidateAllChangelogOverridesResult {
|
|
|
164
164
|
*/
|
|
165
165
|
export declare function validateAllChangelogOverrides(inputs: ValidateAllChangelogOverridesInputs): ValidateAllChangelogOverridesResult;
|
|
166
166
|
/**
|
|
167
|
-
* Applies the composed root and workspace override map to a
|
|
168
|
-
* tier.
|
|
167
|
+
* Applies the composed root and workspace override map to a target's changelog entries, and records stale keys by
|
|
168
|
+
* tier. A target without a workspace override file, such as the project, passes `undefined` for `workspacePath` and
|
|
169
|
+
* applies the root tier alone.
|
|
169
170
|
*
|
|
170
171
|
* A workspace key that matched nothing, whether or not the root map has the same key, is stale, and it is warned about
|
|
171
172
|
* at once. The function adds each root key that matched without being shadowed to `globalMatchedRootKeys`, from which
|
|
172
173
|
* the caller warns, at the end of the run, about the root keys that matched nowhere. Throws if applying reports any
|
|
173
174
|
* error, such as an ambiguous prefix.
|
|
174
175
|
*/
|
|
175
|
-
export declare function applyWorkspaceOverrides(newEntries: ChangelogEntry[], workspacePath: string, overrideContext: OverrideContext): ReturnType<typeof applyChangelogOverrides>;
|
|
176
|
+
export declare function applyWorkspaceOverrides(newEntries: ChangelogEntry[], workspacePath: string | undefined, overrideContext: OverrideContext): ReturnType<typeof applyChangelogOverrides>;
|
|
@@ -336,11 +336,8 @@ export function composeOverrides(rootEntries, workspaceEntries) {
|
|
|
336
336
|
}
|
|
337
337
|
return composed;
|
|
338
338
|
}
|
|
339
|
-
export function createOverrideContext(
|
|
340
|
-
const result = loadOverridesForScopes({
|
|
341
|
-
project: '.',
|
|
342
|
-
workspaces: workspaces.map((workspace) => workspace.workspacePath),
|
|
343
|
-
});
|
|
339
|
+
export function createOverrideContext(workspacePaths) {
|
|
340
|
+
const result = loadOverridesForScopes({ project: '.', workspaces: [...workspacePaths] });
|
|
344
341
|
if (result.errors.length > 0) {
|
|
345
342
|
throw new Error(`Failed to load changelog overrides:\n - ${result.errors.join('\n - ')}`);
|
|
346
343
|
}
|
|
@@ -488,7 +485,7 @@ function formatRootStaleWarning(filePath, key) {
|
|
|
488
485
|
}
|
|
489
486
|
export function applyWorkspaceOverrides(newEntries, workspacePath, overrideContext) {
|
|
490
487
|
const { project, perWorkspace, overrideWarnings, globalMatchedRootKeys } = overrideContext;
|
|
491
|
-
const workspaceOverrides = perWorkspace.get(workspacePath);
|
|
488
|
+
const workspaceOverrides = workspacePath === undefined ? undefined : perWorkspace.get(workspacePath);
|
|
492
489
|
const composed = composeOverrides(project, workspaceOverrides);
|
|
493
490
|
const applied = applyChangelogOverrides(newEntries, composed);
|
|
494
491
|
if (applied.errors.length > 0) {
|
|
@@ -4,6 +4,7 @@ import { type StreamStyles } from '@williamthorsen/nmr-core';
|
|
|
4
4
|
* a comma-separated `--tags` subset, without publishing to npm.
|
|
5
5
|
*
|
|
6
6
|
* A private workspace's tag is skipped with a warning and doesn't get a Release; an all-private tag set is a clean
|
|
7
|
-
* no-op. A relative `--config` resolves
|
|
7
|
+
* no-op. A project tag gets a Release whatever the root `package.json#private` says. A relative `--config` resolves
|
|
8
|
+
* against `invocationDir`.
|
|
8
9
|
*/
|
|
9
10
|
export declare function createGithubReleaseCommand(argv: string[], styles: StreamStyles, invocationDir: string): Promise<void>;
|
|
@@ -20,19 +20,19 @@ export async function createGithubReleaseCommand(argv, styles, invocationDir) {
|
|
|
20
20
|
const userConfig = await loadUsableConfig(styles.stderr, configPath);
|
|
21
21
|
const requestedTags = parseRequestedTags(parsed.flags.tags);
|
|
22
22
|
const resolvedTags = resolveCommandTags(requestedTags, userConfig);
|
|
23
|
-
const
|
|
23
|
+
const releasableTags = resolvedTags.filter(isReleasable);
|
|
24
24
|
for (const resolvedTag of resolvedTags) {
|
|
25
|
-
if (!resolvedTag
|
|
25
|
+
if (!isReleasable(resolvedTag)) {
|
|
26
26
|
console.warn(formatPrivateSkip(resolvedTag));
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
|
-
if (
|
|
29
|
+
if (releasableTags.length === 0) {
|
|
30
30
|
return;
|
|
31
31
|
}
|
|
32
32
|
const { changelogJsonOutputPath, sectionOrder } = deriveReleaseNotesConfig(userConfig);
|
|
33
33
|
let outcome;
|
|
34
34
|
try {
|
|
35
|
-
outcome = createGithubReleases(
|
|
35
|
+
outcome = createGithubReleases(releasableTags, changelogJsonOutputPath, dryRun, sectionOrder);
|
|
36
36
|
}
|
|
37
37
|
catch (error) {
|
|
38
38
|
reportError(`Failed to create GitHub Releases: ${describeError(error)}`);
|
|
@@ -43,3 +43,6 @@ export async function createGithubReleaseCommand(argv, styles, invocationDir) {
|
|
|
43
43
|
console.info(`Skipped ${outcome.skipped.length} tag(s) without releasable content: ${formatted}.`);
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
|
+
function isReleasable(resolvedTag) {
|
|
47
|
+
return resolvedTag.kind !== 'package' || resolvedTag.isPublishable;
|
|
48
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { DeclaredDirectory } from './resolveReleaseDeclaration.js';
|
|
2
|
+
import type { DirectoryConfig } from './types.js';
|
|
3
|
+
/** Name of the version file of a directory that doesn't contain a `package.json`. */
|
|
4
|
+
export declare const DIRECTORY_VERSION_FILE = "version.json";
|
|
5
|
+
/**
|
|
6
|
+
* Derives a directory target's configuration from its declaration, reading the directory under `root`.
|
|
7
|
+
*
|
|
8
|
+
* The version file is the directory's `package.json` when it has one, and its `version.json` otherwise; either must
|
|
9
|
+
* contain a string `version`. The tag prefix defaults to the directory's basename followed by `-v`, and to `v` for the
|
|
10
|
+
* repo root. Throws when the path isn't a directory, when the version file is missing or lacks a `version`, and when
|
|
11
|
+
* the basename would not make a valid tag prefix.
|
|
12
|
+
*/
|
|
13
|
+
export declare function deriveDirectoryConfig(declared: DeclaredDirectory, root?: string): DirectoryConfig;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { chainError } from '@williamthorsen/toolbelt.errors/candidate';
|
|
4
|
+
import { isRecord } from "./typeGuards.js";
|
|
5
|
+
export const DIRECTORY_VERSION_FILE = 'version.json';
|
|
6
|
+
const TAG_PREFIX_PATTERN = /^[\w.-]+$/;
|
|
7
|
+
export function deriveDirectoryConfig(declared, root = process.cwd()) {
|
|
8
|
+
const label = `releases.${declared.name}`;
|
|
9
|
+
if (statSync(path.resolve(root, declared.path), { throwIfNoEntry: false })?.isDirectory() !== true) {
|
|
10
|
+
throw new Error(`${label}: path '${declared.path}' is not a directory; declare a directory that exists in the repo`);
|
|
11
|
+
}
|
|
12
|
+
const versionFile = resolveVersionFile(declared.path, root);
|
|
13
|
+
assertVersionField(label, versionFile, root);
|
|
14
|
+
const config = {
|
|
15
|
+
name: declared.name,
|
|
16
|
+
path: declared.path,
|
|
17
|
+
versionFile,
|
|
18
|
+
tagPrefix: declared.tagPrefix ?? deriveTagPrefix(label, declared.path),
|
|
19
|
+
};
|
|
20
|
+
if (declared.paths !== undefined) {
|
|
21
|
+
config.paths = [...declared.paths];
|
|
22
|
+
}
|
|
23
|
+
if (declared.legacyIdentities !== undefined) {
|
|
24
|
+
config.legacyIdentities = declared.legacyIdentities;
|
|
25
|
+
}
|
|
26
|
+
return config;
|
|
27
|
+
}
|
|
28
|
+
function assertVersionField(label, versionFile, root) {
|
|
29
|
+
const absolutePath = path.resolve(root, versionFile);
|
|
30
|
+
if (!existsSync(absolutePath)) {
|
|
31
|
+
throw new Error(`${label}: ${versionFile} not found; create it with the directory's current version, such as { "version": "0.0.0" }`);
|
|
32
|
+
}
|
|
33
|
+
let parsed;
|
|
34
|
+
try {
|
|
35
|
+
parsed = JSON.parse(readFileSync(absolutePath, 'utf8'));
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
throw chainError(`${label}: failed to read ${versionFile}`, error);
|
|
39
|
+
}
|
|
40
|
+
if (!isRecord(parsed) || typeof parsed['version'] !== 'string') {
|
|
41
|
+
throw new Error(`${label}: ${versionFile} does not contain a string 'version' field; add one`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function deriveTagPrefix(label, directoryPath) {
|
|
45
|
+
if (directoryPath === '.') {
|
|
46
|
+
return 'v';
|
|
47
|
+
}
|
|
48
|
+
const basename = path.posix.basename(directoryPath);
|
|
49
|
+
if (!TAG_PREFIX_PATTERN.test(basename)) {
|
|
50
|
+
throw new Error(`${label}: the directory name '${basename}' cannot form a tag prefix; set tagPrefix, such as '${basename.replaceAll(/[^\w.-]+/g, '-')}-v'`);
|
|
51
|
+
}
|
|
52
|
+
return `${basename}-v`;
|
|
53
|
+
}
|
|
54
|
+
function resolveVersionFile(directoryPath, root) {
|
|
55
|
+
const packageJson = path.posix.join(directoryPath, 'package.json');
|
|
56
|
+
return existsSync(path.resolve(root, packageJson))
|
|
57
|
+
? packageJson
|
|
58
|
+
: path.posix.join(directoryPath, DIRECTORY_VERSION_FILE);
|
|
59
|
+
}
|
|
@@ -50,8 +50,8 @@ export declare function describeEmptyWorkspace(workspace: EmptyWorkspace): strin
|
|
|
50
50
|
* as patterns at all.
|
|
51
51
|
*
|
|
52
52
|
* `packageDirs` come back relative to `monorepoRoot` as POSIX paths, because that is what every consumer
|
|
53
|
-
* needs: `WorkspaceConfig.paths` feeds `git log -- <paths>`, and `packageFiles` and
|
|
54
|
-
* from disk relative to the working directory. The workspace root itself relativizes to
|
|
55
|
-
* which `deriveWorkspaceConfig` reads a root package's manifest.
|
|
53
|
+
* needs: `WorkspaceConfig.paths` feeds `git log -- <paths>`, and `packageFiles` and the changelogs under
|
|
54
|
+
* `workspacePath` are read from disk relative to the working directory. The workspace root itself relativizes to
|
|
55
|
+
* `.`, the path through which `deriveWorkspaceConfig` reads a root package's manifest.
|
|
56
56
|
*/
|
|
57
57
|
export declare function discoverWorkspaces(monorepoRoot?: string): WorkspaceDiscovery;
|
|
@@ -10,10 +10,10 @@ export function findTransitiveDependents(graph, startDirs) {
|
|
|
10
10
|
continue;
|
|
11
11
|
const dependents = graph.dependentsOf.get(packageName) ?? [];
|
|
12
12
|
for (const dependent of dependents) {
|
|
13
|
-
if (found.has(dependent.
|
|
13
|
+
if (found.has(dependent.id))
|
|
14
14
|
continue;
|
|
15
|
-
found.add(dependent.
|
|
16
|
-
queue.push(dependent.
|
|
15
|
+
found.add(dependent.id);
|
|
16
|
+
queue.push(dependent.id);
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
19
|
return found;
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import type { RawCommit } from './enumerateReleaseWindows.js';
|
|
2
2
|
import type { UnroutedEntryScope } from './types.js';
|
|
3
|
-
/** A workspace's
|
|
3
|
+
/** A workspace target's name, the scope that routes to it, with the commits of its unreleased window. */
|
|
4
4
|
export interface WorkspaceWindow {
|
|
5
5
|
commits: readonly RawCommit[];
|
|
6
6
|
dir: string;
|
|
7
7
|
}
|
|
8
8
|
/**
|
|
9
9
|
* Finds the change-record entry scopes of the unreleased windows that do not route their entry to any workspace whose
|
|
10
|
-
* window contains the commit, and returns the findings keyed by the
|
|
10
|
+
* window contains the commit, and returns the findings keyed by the name of every workspace whose window contains it.
|
|
11
11
|
*
|
|
12
12
|
* A scope resolves through `scopeAliases` first. It is not reported when it resolves to `*`, to a workspace whose
|
|
13
13
|
* window contains the commit, or to a `configuredDirs` workspace absent from `windows`, which the run did not read.
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { ResolvedTag } from './resolveReleaseTags.js';
|
|
2
|
+
/** Formats the warning emitted when `publish` is asked for a directory target's tag, which is never published. */
|
|
3
|
+
export declare function formatDirectoryPublishSkip(resolvedTag: Pick<ResolvedTag, 'dir' | 'tag'>): string;
|
|
@@ -1,3 +1,6 @@
|
|
|
1
1
|
import type { ResolvedTag } from './resolveReleaseTags.js';
|
|
2
|
-
/**
|
|
3
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Formats the warning emitted when a release tag's workspace is excluded by config and therefore skipped, naming the
|
|
4
|
+
* `setting` that excluded it, which defaults to the `workspaces` override form.
|
|
5
|
+
*/
|
|
6
|
+
export declare function formatExcludedSkip(resolvedTag: Pick<ResolvedTag, 'tag' | 'workspacePath'>, setting?: string): string;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export function formatExcludedSkip(resolvedTag) {
|
|
2
|
-
return `Skipping ${resolvedTag.tag} (${resolvedTag.workspacePath}): excluded by config (
|
|
1
|
+
export function formatExcludedSkip(resolvedTag, setting = 'shouldExclude: true') {
|
|
2
|
+
return `Skipping ${resolvedTag.tag} (${resolvedTag.workspacePath}): excluded by config (${setting}).`;
|
|
3
3
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
/** Returns the
|
|
3
|
-
export declare function getAllTagPrefixes(
|
|
1
|
+
import type { ReleaseTarget } from './types.js';
|
|
2
|
+
/** Returns the derived tag prefix followed by each declared legacy-identity tag prefix. */
|
|
3
|
+
export declare function getAllTagPrefixes(owner: Pick<ReleaseTarget, 'legacyIdentities' | 'tagPrefix'>): string[];
|
|
4
4
|
/**
|
|
5
5
|
* Options for `readReleaseHistory`: the tag prefixes and pathspecs that bound its release windows, and the workspace
|
|
6
6
|
* to which it routes change-record entries.
|
|
@@ -11,9 +11,9 @@ export interface GenerateChangelogOptions {
|
|
|
11
11
|
/** Git pathspecs restricting the history to commits that touch them; all paths when omitted. */
|
|
12
12
|
paths?: readonly string[];
|
|
13
13
|
/**
|
|
14
|
-
* The
|
|
15
|
-
* after `scopeAliases` resolution, name this
|
|
16
|
-
* entry.
|
|
14
|
+
* The scope of the workspace being read, its target name. When set, the read includes a change-record entry only
|
|
15
|
+
* when its scopes, after `scopeAliases` resolution, name this scope, are empty, or contain `*`; when omitted, the
|
|
16
|
+
* read includes every entry.
|
|
17
17
|
*/
|
|
18
|
-
|
|
18
|
+
scope?: string;
|
|
19
19
|
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export function getAllTagPrefixes(
|
|
2
|
-
return [
|
|
1
|
+
export function getAllTagPrefixes(owner) {
|
|
2
|
+
return [owner.tagPrefix, ...(owner.legacyIdentities?.map((identity) => identity.tagPrefix) ?? [])];
|
|
3
3
|
}
|
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
* Help text for `release-kit prepare`. The module does not have any import-time side effects, so that a test can
|
|
3
3
|
* import it without running the CLI.
|
|
4
4
|
*/
|
|
5
|
-
export declare const prepareHelpText = "\nUsage: release-kit prepare [options]\n\nRun release preparation with automatic workspace discovery.\n\nOptions:\n --config <path> Config file, relative to the current directory\n (default: .config/release-kit.config.ts at the repo root)\n --dry-run Run without modifying any files\n --bump=major|minor|patch Set the level of every release that happens; does not trigger one\n --set-version=X.Y.Z Set an explicit version; bypasses the changelog-derived bump. In monorepo\n mode, name each workspace
|
|
5
|
+
export declare const prepareHelpText = "\nUsage: release-kit prepare [options]\n\nRun release preparation with automatic workspace discovery.\n\nOptions:\n --config <path> Config file, relative to the current directory\n (default: .config/release-kit.config.ts at the repo root)\n --dry-run Run without modifying any files\n --bump=major|minor|patch Set the level of every release that happens; does not trigger one\n --set-version=X.Y.Z Set an explicit version; bypasses the changelog-derived bump. In monorepo\n mode, name each workspace or directory target:\n --set-version=arrays@1.0.0[,strings@2.0.0].\n Rejected when a composite (a 'project' block or a releases composite)\n is configured.\n --force Release even when the last tag is not followed by any commits,\n or by any bump-worthy ones. Defaults to patch when --bump is not given;\n use --bump=X for a different level.\n --no-git-checks, -n Skip the clean-working-tree check\n --only=name1,name2 Release only the named release targets and their dependents\n (comma-separated, monorepo only). A composite declared under 'releases'\n releases only when named; a 'project' block's release is skipped.\n --with-release-notes Also write per-workspace release-notes previews under <workspacePath>/docs/\n (docs/README.v<version>.md and docs/RELEASE_NOTES.v<version>.md).\n Recommended .gitignore entry: packages/*/docs/*.v*.md (or docs/*.v*.md).\n --help, -h Show this help message\n";
|
|
6
6
|
/** Prints the prepare command's help text. */
|
|
7
7
|
export declare function showPrepareHelp(): void;
|
|
@@ -9,15 +9,17 @@ Options:
|
|
|
9
9
|
--dry-run Run without modifying any files
|
|
10
10
|
--bump=major|minor|patch Set the level of every release that happens; does not trigger one
|
|
11
11
|
--set-version=X.Y.Z Set an explicit version; bypasses the changelog-derived bump. In monorepo
|
|
12
|
-
mode, name each workspace:
|
|
13
|
-
|
|
12
|
+
mode, name each workspace or directory target:
|
|
13
|
+
--set-version=arrays@1.0.0[,strings@2.0.0].
|
|
14
|
+
Rejected when a composite (a 'project' block or a releases composite)
|
|
15
|
+
is configured.
|
|
14
16
|
--force Release even when the last tag is not followed by any commits,
|
|
15
17
|
or by any bump-worthy ones. Defaults to patch when --bump is not given;
|
|
16
18
|
use --bump=X for a different level.
|
|
17
19
|
--no-git-checks, -n Skip the clean-working-tree check
|
|
18
|
-
--only=name1,name2 Release only the named
|
|
19
|
-
monorepo only).
|
|
20
|
-
|
|
20
|
+
--only=name1,name2 Release only the named release targets and their dependents
|
|
21
|
+
(comma-separated, monorepo only). A composite declared under 'releases'
|
|
22
|
+
releases only when named; a 'project' block's release is skipped.
|
|
21
23
|
--with-release-notes Also write per-workspace release-notes previews under <workspacePath>/docs/
|
|
22
24
|
(docs/README.v<version>.md and docs/RELEASE_NOTES.v<version>.md).
|
|
23
25
|
Recommended .gitignore entry: packages/*/docs/*.v*.md (or docs/*.v*.md).
|
|
@@ -89,7 +89,7 @@ on:
|
|
|
89
89
|
workflow_dispatch:
|
|
90
90
|
inputs:
|
|
91
91
|
only:
|
|
92
|
-
description: '
|
|
92
|
+
description: 'Release targets to release, with their dependents (comma-separated, leave empty for all)'
|
|
93
93
|
required: false
|
|
94
94
|
type: string
|
|
95
95
|
bump:
|