@williamthorsen/release-kit 10.5.0 → 10.7.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/.meta/changelog.json +1570 -0
- package/.readyup/kits/default.js +127 -4
- package/.readyup/kits/npm-auto-publish.js +87 -23
- package/.readyup/manifest.json +14 -9
- package/CHANGELOG.md +83 -0
- package/README.md +68 -963
- package/dist/esm/bin/release-kit.js +4 -3
- package/dist/esm/buildChangelogEntries.js +5 -0
- package/dist/esm/buildDependencyGraph.d.ts +6 -5
- package/dist/esm/buildDependencyGraph.js +5 -1
- package/dist/esm/changelogJsonUtils.d.ts +5 -7
- package/dist/esm/changelogJsonUtils.js +0 -13
- package/dist/esm/changelogOverrides.js +8 -0
- package/dist/esm/extractMigration.d.ts +14 -0
- package/dist/esm/extractMigration.js +18 -0
- package/dist/esm/init/scaffold.js +14 -6
- package/dist/esm/packsPath.d.ts +15 -0
- package/dist/esm/packsPath.js +47 -0
- package/dist/esm/planVersionBump.d.ts +2 -1
- package/dist/esm/planVersionBump.js +4 -0
- package/dist/esm/propagateBumps.d.ts +1 -3
- package/dist/esm/propagateBumps.js +2 -2
- package/dist/esm/releasePrepare.js +0 -16
- package/dist/esm/releasePrepareMono.js +3 -24
- package/dist/esm/reportPrepare.js +4 -2
- package/dist/esm/runGitCliff.d.ts +16 -23
- package/dist/esm/runGitCliff.js +4 -10
- package/dist/esm/sync-labels/resolveLabels.d.ts +5 -3
- package/dist/esm/sync-labels/resolveLabels.js +21 -6
- package/dist/esm/types.d.ts +13 -2
- package/dist/esm/validateConfig.js +1 -1
- package/docs/api.md +73 -0
- package/docs/changelogs.md +70 -0
- package/docs/configuration.md +105 -0
- package/docs/editorial-overrides.md +144 -0
- package/docs/labels.md +83 -0
- package/docs/project-releases.md +74 -0
- package/docs/publishing.md +30 -0
- package/docs/readiness-checks.md +18 -0
- package/docs/releasing.md +108 -0
- package/docs/work-types.md +121 -0
- package/package.json +16 -12
- package/dist/esm/readCurrentVersion.d.ts +0 -2
- package/dist/esm/readCurrentVersion.js +0 -18
|
@@ -35,8 +35,9 @@ Commands:
|
|
|
35
35
|
work-types Check for or sync work-type taxonomy drift against the upstream canonical
|
|
36
36
|
|
|
37
37
|
Options:
|
|
38
|
-
--dry-run
|
|
39
|
-
--help, -h
|
|
38
|
+
--dry-run Preview changes without writing files
|
|
39
|
+
--help, -h Show this help message
|
|
40
|
+
--version, -V Show the version number
|
|
40
41
|
`);
|
|
41
42
|
}
|
|
42
43
|
function showSyncLabelsHelp() {
|
|
@@ -95,7 +96,7 @@ function showInitHelp() {
|
|
|
95
96
|
Usage: release-kit init [options]
|
|
96
97
|
|
|
97
98
|
Initialize release-kit in the current repository.
|
|
98
|
-
By default, scaffolds only the GitHub Actions workflow
|
|
99
|
+
By default, scaffolds only the GitHub Actions workflow files.
|
|
99
100
|
|
|
100
101
|
Options:
|
|
101
102
|
--with-config Also scaffold .config/release-kit.config.ts and .config/git-cliff.toml
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { chainError } from '@williamthorsen/toolbelt.errors/candidate';
|
|
2
2
|
import { extractVersion } from "./changelogJsonUtils.js";
|
|
3
3
|
import { DEFAULT_WORK_TYPES } from "./defaults.js";
|
|
4
|
+
import { extractMigration } from "./extractMigration.js";
|
|
4
5
|
import { COMMIT_PREPROCESSOR_PATTERNS, PIPE_SCOPE_SOURCE } from "./parseCommitMessage.js";
|
|
5
6
|
import { resolveCliffConfigPath } from "./resolveCliffConfigPath.js";
|
|
6
7
|
import { runGitCliff } from "./runGitCliff.js";
|
|
@@ -101,6 +102,10 @@ function transformReleases(releases, devOnlySections) {
|
|
|
101
102
|
if (breaking) {
|
|
102
103
|
item.breaking = true;
|
|
103
104
|
}
|
|
105
|
+
const migration = extractMigration(body);
|
|
106
|
+
if (migration !== undefined) {
|
|
107
|
+
item.migration = migration;
|
|
108
|
+
}
|
|
104
109
|
if (commit.id !== undefined && commit.id !== '') {
|
|
105
110
|
item.hash = commit.id;
|
|
106
111
|
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import type { WorkspaceConfig } from './types.js';
|
|
2
|
-
/**
|
|
2
|
+
/** Identities, current versions, and `workspace:` dependency edges of the workspaces' packages. */
|
|
3
3
|
export interface DependencyGraph {
|
|
4
4
|
/** Resolve a package name to its workspace `dir`. */
|
|
5
5
|
packageNameToDir: Map<string, string>;
|
|
6
6
|
/** Resolve a workspace `dir` to its package name (inverse of `packageNameToDir`). */
|
|
7
7
|
dirToPackageName: Map<string, string>;
|
|
8
|
+
/** Resolve a workspace `dir` to its current version; absent when its `package.json` has no string `version`. */
|
|
9
|
+
dirToVersion: Map<string, string>;
|
|
8
10
|
/** Map a package name to the workspaces that depend on it. */
|
|
9
11
|
dependentsOf: Map<string, WorkspaceConfig[]>;
|
|
10
12
|
/**
|
|
@@ -14,11 +16,10 @@ export interface DependencyGraph {
|
|
|
14
16
|
dependenciesOf: Map<string, Set<string>>;
|
|
15
17
|
}
|
|
16
18
|
/**
|
|
17
|
-
*
|
|
19
|
+
* Builds the dependency graph from the workspaces' `package.json` files.
|
|
18
20
|
*
|
|
19
21
|
* Reads each workspace's primary `package.json` (first entry in `packageFiles`) to discover
|
|
20
|
-
* `workspace:` references in `dependencies` and `peerDependencies
|
|
21
|
-
*
|
|
22
|
-
* package to all its dependents.
|
|
22
|
+
* `workspace:` references in `dependencies` and `peerDependencies`, and to record its current
|
|
23
|
+
* version.
|
|
23
24
|
*/
|
|
24
25
|
export declare function buildDependencyGraph(workspaces: readonly WorkspaceConfig[]): DependencyGraph;
|
|
@@ -6,6 +6,7 @@ function isPackageJsonSubset(value) {
|
|
|
6
6
|
export function buildDependencyGraph(workspaces) {
|
|
7
7
|
const packageNameToDir = new Map();
|
|
8
8
|
const dirToPackageName = new Map();
|
|
9
|
+
const dirToVersion = new Map();
|
|
9
10
|
const dependentsOf = new Map();
|
|
10
11
|
const dependenciesOf = new Map();
|
|
11
12
|
const workspacePackages = new Map();
|
|
@@ -16,6 +17,9 @@ export function buildDependencyGraph(workspaces) {
|
|
|
16
17
|
}
|
|
17
18
|
const pkg = readPackageJsonSubset(primaryPackageFile);
|
|
18
19
|
workspacePackages.set(workspace, pkg);
|
|
20
|
+
if (typeof pkg.version === 'string') {
|
|
21
|
+
dirToVersion.set(workspace.dir, pkg.version);
|
|
22
|
+
}
|
|
19
23
|
if (pkg.name === undefined) {
|
|
20
24
|
continue;
|
|
21
25
|
}
|
|
@@ -44,7 +48,7 @@ export function buildDependencyGraph(workspaces) {
|
|
|
44
48
|
}
|
|
45
49
|
}
|
|
46
50
|
}
|
|
47
|
-
return { packageNameToDir, dirToPackageName, dependentsOf, dependenciesOf };
|
|
51
|
+
return { packageNameToDir, dirToPackageName, dirToVersion, dependentsOf, dependenciesOf };
|
|
48
52
|
}
|
|
49
53
|
function readPackageJsonSubset(filePath) {
|
|
50
54
|
let content;
|
|
@@ -1,13 +1,11 @@
|
|
|
1
|
-
import type { ChangelogEntry
|
|
1
|
+
import type { ChangelogEntry } from './types.js';
|
|
2
2
|
/**
|
|
3
|
-
* Type guard for `
|
|
3
|
+
* Type guard for `ChangelogEntry` values parsed from JSON.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
* `
|
|
7
|
-
*
|
|
5
|
+
* Checks the entry's top level only: `sections` is verified to be an array, and its contents pass
|
|
6
|
+
* through unvalidated. `.meta/changelog.json` is written by release-kit and read tolerantly; strict
|
|
7
|
+
* validation applies to the human-authored `.meta/changelog-overrides.json`, in `changelogOverrides.ts`.
|
|
8
8
|
*/
|
|
9
|
-
export declare function isChangelogItem(value: unknown): value is ChangelogItem;
|
|
10
|
-
/** Type guard for `ChangelogEntry` values parsed from JSON. */
|
|
11
9
|
export declare function isChangelogEntry(value: unknown): value is ChangelogEntry;
|
|
12
10
|
/** Extract the version from a tag by stripping the prefix up to the first digit. */
|
|
13
11
|
export declare function extractVersion(tag: string): string;
|
|
@@ -1,18 +1,5 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from 'node:fs';
|
|
2
2
|
import { isRecord, isUnknownArray } from "./typeGuards.js";
|
|
3
|
-
export function isChangelogItem(value) {
|
|
4
|
-
if (!isRecord(value))
|
|
5
|
-
return false;
|
|
6
|
-
if (typeof value['description'] !== 'string')
|
|
7
|
-
return false;
|
|
8
|
-
if (value['body'] !== undefined && typeof value['body'] !== 'string')
|
|
9
|
-
return false;
|
|
10
|
-
if (value['breaking'] !== undefined && typeof value['breaking'] !== 'boolean')
|
|
11
|
-
return false;
|
|
12
|
-
if (value['hash'] !== undefined && typeof value['hash'] !== 'string')
|
|
13
|
-
return false;
|
|
14
|
-
return true;
|
|
15
|
-
}
|
|
16
3
|
export function isChangelogEntry(value) {
|
|
17
4
|
return (isRecord(value) &&
|
|
18
5
|
typeof value['version'] === 'string' &&
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
4
|
+
import { extractMigration } from "./extractMigration.js";
|
|
4
5
|
import { isRecord } from "./typeGuards.js";
|
|
5
6
|
const OVERRIDES_FILENAME = '.meta/changelog-overrides.json';
|
|
6
7
|
const VALID_AUDIENCE_VALUES = new Set(['all', 'dev', 'skip']);
|
|
@@ -206,6 +207,13 @@ function applyOverrideToItem(item, override) {
|
|
|
206
207
|
}
|
|
207
208
|
if (override.body !== undefined) {
|
|
208
209
|
result.body = override.body;
|
|
210
|
+
const migration = extractMigration(override.body);
|
|
211
|
+
if (migration === undefined) {
|
|
212
|
+
delete result.migration;
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
result.migration = migration;
|
|
216
|
+
}
|
|
209
217
|
}
|
|
210
218
|
if (override.breaking !== undefined) {
|
|
211
219
|
result.breaking = override.breaking;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extracts the instruction from a commit body's `Migration:` paragraph.
|
|
3
|
+
*
|
|
4
|
+
* The first paragraph opening with the label wins: the lede doctrine specifies one labeled
|
|
5
|
+
* paragraph per body, so a second is ignored rather than appended. The match is anchored and
|
|
6
|
+
* case-sensitive, so `migration:` and `**Migration:**` yield nothing; the doctrine calls the
|
|
7
|
+
* label literal, and tolerating variants would let the form drift.
|
|
8
|
+
*
|
|
9
|
+
* Newlines inside the paragraph are preserved. The first character is capitalized, which is
|
|
10
|
+
* inert for the imperative the doctrine requires and repairs the lowercase continuation of a
|
|
11
|
+
* paragraph written before it. Returns `undefined` when no paragraph carries the label, or when
|
|
12
|
+
* the label is followed by nothing.
|
|
13
|
+
*/
|
|
14
|
+
export declare function extractMigration(body: string | undefined): string | undefined;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const PARAGRAPH_SEPARATOR = /\n(?:[ \t]*\n)+/;
|
|
2
|
+
const MIGRATION_LABEL = /^Migration:[ \t]*/;
|
|
3
|
+
export function extractMigration(body) {
|
|
4
|
+
if (body === undefined) {
|
|
5
|
+
return undefined;
|
|
6
|
+
}
|
|
7
|
+
for (const paragraph of body.split(PARAGRAPH_SEPARATOR)) {
|
|
8
|
+
if (!MIGRATION_LABEL.test(paragraph)) {
|
|
9
|
+
continue;
|
|
10
|
+
}
|
|
11
|
+
const instruction = paragraph.replace(MIGRATION_LABEL, '').trim();
|
|
12
|
+
if (instruction.length === 0) {
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
return instruction.charAt(0).toUpperCase() + instruction.slice(1);
|
|
16
|
+
}
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
@@ -1,22 +1,30 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
2
|
import { resolve } from 'node:path';
|
|
3
3
|
import { findPackageRoot, writeFileWithCheck } from '@williamthorsen/nmr-core';
|
|
4
4
|
import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
5
5
|
import { createGithubReleaseWorkflow, publishWorkflow, releaseConfigScript, releaseWorkflow } from "./templates.js";
|
|
6
6
|
export function copyCliffTemplate(dryRun, overwrite) {
|
|
7
7
|
const destPath = '.config/git-cliff.toml';
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
let root;
|
|
9
|
+
try {
|
|
10
|
+
root = findPackageRoot(import.meta.url);
|
|
11
|
+
}
|
|
12
|
+
catch (error) {
|
|
13
|
+
const message = describeError(error);
|
|
14
|
+
return { filePath: destPath, outcome: 'failed', error: `Failed to resolve package root: ${message}` };
|
|
12
15
|
}
|
|
16
|
+
const templatePath = resolve(root, 'cliff.toml.template');
|
|
13
17
|
let content;
|
|
14
18
|
try {
|
|
15
19
|
content = readFileSync(templatePath, 'utf8');
|
|
16
20
|
}
|
|
17
21
|
catch (error) {
|
|
18
22
|
const message = describeError(error);
|
|
19
|
-
return {
|
|
23
|
+
return {
|
|
24
|
+
filePath: destPath,
|
|
25
|
+
outcome: 'failed',
|
|
26
|
+
error: `Failed to read bundled template at ${templatePath}: ${message}`,
|
|
27
|
+
};
|
|
20
28
|
}
|
|
21
29
|
return writeFileWithCheck(destPath, content, { dryRun, overwrite });
|
|
22
30
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reports whether npm would place `path` in the published tarball, given a package.json `files` value.
|
|
3
|
+
*
|
|
4
|
+
* An absent or malformed `files` value packs everything, so it reports `true`; an empty array packs nothing
|
|
5
|
+
* beyond the files npm always includes, so it reports `false`. An entry naming an ancestor directory covers
|
|
6
|
+
* everything beneath it, which is how `files: ["dist/*"]` ships `dist/esm/index.js`, so every ancestor prefix
|
|
7
|
+
* of `path` is tested rather than `path` alone.
|
|
8
|
+
*
|
|
9
|
+
* Two approximations of npm's packing rules remain, both failing toward a silent pass: a negation entry
|
|
10
|
+
* (`!dist/*.map`) never counts toward inclusion, because gitignore-style ordered negation is not modeled; and a
|
|
11
|
+
* repo controlling its tarball through `.npmignore` alone declares no `files` field, so it reports `true`
|
|
12
|
+
* without the ignore file being read. For a check that warns about an omission, a missed report costs less than
|
|
13
|
+
* a false one.
|
|
14
|
+
*/
|
|
15
|
+
export declare function packsPath(filesField: unknown, path: string): boolean;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export function packsPath(filesField, path) {
|
|
2
|
+
if (!isStringArray(filesField))
|
|
3
|
+
return true;
|
|
4
|
+
const prefixes = listAncestorPrefixes(path);
|
|
5
|
+
return filesField.some((entry) => {
|
|
6
|
+
const pattern = compileEntry(entry);
|
|
7
|
+
return pattern !== undefined && prefixes.some((prefix) => pattern.test(prefix));
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
const GLOB_TOKEN_PATTERN = /\*\*\/|\*\*|\*|\?|[^*?]+/g;
|
|
11
|
+
function compileEntry(entry) {
|
|
12
|
+
if (entry.startsWith('!'))
|
|
13
|
+
return undefined;
|
|
14
|
+
const normalized = entry.replace(/^\.?\//, '').replace(/\/+$/, '');
|
|
15
|
+
if (normalized === '')
|
|
16
|
+
return undefined;
|
|
17
|
+
return new RegExp(`^${translateGlob(normalized)}$`);
|
|
18
|
+
}
|
|
19
|
+
function isStringArray(value) {
|
|
20
|
+
return Array.isArray(value) && value.every((entry) => typeof entry === 'string');
|
|
21
|
+
}
|
|
22
|
+
function listAncestorPrefixes(path) {
|
|
23
|
+
const segments = path.split('/');
|
|
24
|
+
return segments.map((_segment, index) => segments.slice(0, index + 1).join('/'));
|
|
25
|
+
}
|
|
26
|
+
function translateGlob(pattern) {
|
|
27
|
+
let source = '';
|
|
28
|
+
for (const [token] of pattern.matchAll(GLOB_TOKEN_PATTERN)) {
|
|
29
|
+
switch (token) {
|
|
30
|
+
case '**/':
|
|
31
|
+
source += String.raw `(?:[^/]*\/)*`;
|
|
32
|
+
break;
|
|
33
|
+
case '**':
|
|
34
|
+
source += '.*';
|
|
35
|
+
break;
|
|
36
|
+
case '*':
|
|
37
|
+
source += '[^/]*';
|
|
38
|
+
break;
|
|
39
|
+
case '?':
|
|
40
|
+
source += '[^/]';
|
|
41
|
+
break;
|
|
42
|
+
default:
|
|
43
|
+
source += RegExp.escape(token);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return source;
|
|
47
|
+
}
|
|
@@ -16,6 +16,7 @@ export interface VersionBumpPlan {
|
|
|
16
16
|
export declare function planVersionBump(packageFiles: readonly string[], releaseType: ReleaseType): VersionBumpPlan;
|
|
17
17
|
/**
|
|
18
18
|
* Computes an explicit version assignment for a workspace's package files without writing them,
|
|
19
|
-
* bypassing commit-derived bump logic. Backs the `--set-version` flag
|
|
19
|
+
* bypassing commit-derived bump logic. Backs the `--set-version` flag, and so rejects a
|
|
20
|
+
* `newVersion` that is not greater than the current version.
|
|
20
21
|
*/
|
|
21
22
|
export declare function planVersionSet(packageFiles: readonly string[], newVersion: string): VersionBumpPlan;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
2
|
import { chainError } from '@williamthorsen/toolbelt.errors/candidate';
|
|
3
3
|
import { bumpVersion } from "./bumpVersion.js";
|
|
4
|
+
import { isForwardVersion } from "./compareVersions.js";
|
|
4
5
|
function isPackageJson(value) {
|
|
5
6
|
return typeof value === 'object' && value !== null && 'version' in value && typeof value.version === 'string';
|
|
6
7
|
}
|
|
@@ -13,6 +14,9 @@ export function planVersionBump(packageFiles, releaseType) {
|
|
|
13
14
|
export function planVersionSet(packageFiles, newVersion) {
|
|
14
15
|
const { firstFile, firstPkg } = readPrimaryPackage(packageFiles);
|
|
15
16
|
const currentVersion = firstPkg.version;
|
|
17
|
+
if (!isForwardVersion(currentVersion, newVersion)) {
|
|
18
|
+
throw new Error(`--set-version ${newVersion} is not greater than current version ${currentVersion}`);
|
|
19
|
+
}
|
|
16
20
|
return { currentVersion, newVersion, writes: renderVersionWrites(packageFiles, firstFile, firstPkg, newVersion) };
|
|
17
21
|
}
|
|
18
22
|
function readPrimaryPackage(packageFiles) {
|
|
@@ -12,8 +12,6 @@ export interface ReleaseEntry {
|
|
|
12
12
|
*/
|
|
13
13
|
newVersionOverride?: string;
|
|
14
14
|
}
|
|
15
|
-
/** Map from workspace `dir` to its current version string (read from package.json). */
|
|
16
|
-
export type CurrentVersions = Map<string, string>;
|
|
17
15
|
/**
|
|
18
16
|
* Walk upward through the dependency graph via BFS, adding `patch` bumps for dependents
|
|
19
17
|
* not already in the release set with a higher bump.
|
|
@@ -21,4 +19,4 @@ export type CurrentVersions = Map<string, string>;
|
|
|
21
19
|
* Returns the full release set (direct + propagated). Direct entries that also have a
|
|
22
20
|
* propagated dependency get `propagatedFrom` metadata without changing their bump type.
|
|
23
21
|
*/
|
|
24
|
-
export declare function propagateBumps(directBumps: Map<string, ReleaseEntry>, graph: DependencyGraph
|
|
22
|
+
export declare function propagateBumps(directBumps: Map<string, ReleaseEntry>, graph: DependencyGraph): Map<string, ReleaseEntry>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { bumpVersion } from "./bumpVersion.js";
|
|
2
|
-
export function propagateBumps(directBumps, graph
|
|
2
|
+
export function propagateBumps(directBumps, graph) {
|
|
3
3
|
const result = new Map();
|
|
4
4
|
for (const [dir, entry] of directBumps) {
|
|
5
5
|
result.set(dir, { ...entry });
|
|
@@ -19,7 +19,7 @@ export function propagateBumps(directBumps, graph, currentVersions) {
|
|
|
19
19
|
if (packageName === undefined) {
|
|
20
20
|
continue;
|
|
21
21
|
}
|
|
22
|
-
const currentVersion =
|
|
22
|
+
const currentVersion = graph.dirToVersion.get(dir);
|
|
23
23
|
const entry = result.get(dir);
|
|
24
24
|
if (currentVersion === undefined || entry === undefined) {
|
|
25
25
|
continue;
|
|
@@ -5,7 +5,6 @@ import { buildReleaseSummary } from "./buildReleaseSummary.js";
|
|
|
5
5
|
import { mergeChangelogEntriesWithDisk, renderChangelogJson, resolveChangelogJsonPath } from "./changelogJsonFile.js";
|
|
6
6
|
import { applyChangelogOverrides, formatStaleOverrideKeyWarning, loadOverridesForScopes, } from "./changelogOverrides.js";
|
|
7
7
|
import { createPolicyViolationCollector } from "./collectPolicyViolations.js";
|
|
8
|
-
import { isForwardVersion } from "./compareVersions.js";
|
|
9
8
|
import { DEFAULT_BREAKING_POLICIES, DEFAULT_VERSION_PATTERNS, DEFAULT_WORK_TYPES } from "./defaults.js";
|
|
10
9
|
import { determineBumpFromCommits } from "./determineBumpFromCommits.js";
|
|
11
10
|
import { getCommitsSinceTarget } from "./getCommitsSinceTarget.js";
|
|
@@ -13,10 +12,8 @@ import { hasPrettierConfig } from "./hasPrettierConfig.js";
|
|
|
13
12
|
import { resolveWorkTypes } from "./loadConfig.js";
|
|
14
13
|
import { planReleaseNotesPreviews } from "./planReleaseNotesPreviews.js";
|
|
15
14
|
import { planVersionBump, planVersionSet } from "./planVersionBump.js";
|
|
16
|
-
import { readCurrentVersion } from "./readCurrentVersion.js";
|
|
17
15
|
import { renderChangelogMarkdown } from "./renderChangelogMarkdown.js";
|
|
18
16
|
import { deriveSectionOrder } from "./resolveReleaseNotesConfig.js";
|
|
19
|
-
import { refreshGitCliffCache } from "./runGitCliff.js";
|
|
20
17
|
export function releasePrepare(config, options) {
|
|
21
18
|
const { bumpOverride, setVersion, withReleaseNotes } = options;
|
|
22
19
|
const writes = [];
|
|
@@ -35,18 +32,6 @@ export function releasePrepare(config, options) {
|
|
|
35
32
|
const collector = createPolicyViolationCollector();
|
|
36
33
|
let bump;
|
|
37
34
|
if (setVersion !== undefined) {
|
|
38
|
-
const primaryPackageFile = config.packageFiles[0];
|
|
39
|
-
if (primaryPackageFile === undefined) {
|
|
40
|
-
throw new Error('No package files specified');
|
|
41
|
-
}
|
|
42
|
-
const currentVersion = readCurrentVersion(primaryPackageFile);
|
|
43
|
-
if (currentVersion === undefined) {
|
|
44
|
-
throw new Error(`Cannot validate --set-version: failed to read current version from ${primaryPackageFile}`);
|
|
45
|
-
}
|
|
46
|
-
if (!isForwardVersion(currentVersion, setVersion)) {
|
|
47
|
-
throw new Error(`--set-version ${setVersion} is not greater than current version ${currentVersion}`);
|
|
48
|
-
}
|
|
49
|
-
refreshGitCliffCache();
|
|
50
35
|
bump = planVersionSet(config.packageFiles, setVersion);
|
|
51
36
|
}
|
|
52
37
|
else {
|
|
@@ -78,7 +63,6 @@ export function releasePrepare(config, options) {
|
|
|
78
63
|
formatCommand: undefined,
|
|
79
64
|
};
|
|
80
65
|
}
|
|
81
|
-
refreshGitCliffCache();
|
|
82
66
|
bump = planVersionBump(config.packageFiles, releaseType);
|
|
83
67
|
}
|
|
84
68
|
writes.push(...bump.writes);
|
|
@@ -8,7 +8,6 @@ import { buildSyntheticChangelogEntry } from "./buildSyntheticChangelogEntry.js"
|
|
|
8
8
|
import { mergeChangelogEntriesWithDisk, renderChangelogJson, resolveChangelogJsonPath } from "./changelogJsonFile.js";
|
|
9
9
|
import { applyWorkspaceOverrides, createOverrideContext, formatStaleOverrideKeyWarning, } from "./changelogOverrides.js";
|
|
10
10
|
import { createPolicyViolationCollector } from "./collectPolicyViolations.js";
|
|
11
|
-
import { isForwardVersion } from "./compareVersions.js";
|
|
12
11
|
import { decideRelease } from "./decideRelease.js";
|
|
13
12
|
import { DEFAULT_BREAKING_POLICIES, DEFAULT_VERSION_PATTERNS, DEFAULT_WORK_TYPES } from "./defaults.js";
|
|
14
13
|
import { detectUndeclaredTagPrefixes } from "./detectUndeclaredTagPrefixes.js";
|
|
@@ -19,11 +18,9 @@ import { resolveWorkTypes } from "./loadConfig.js";
|
|
|
19
18
|
import { planReleaseNotesPreviews } from "./planReleaseNotesPreviews.js";
|
|
20
19
|
import { planVersionBump, planVersionSet } from "./planVersionBump.js";
|
|
21
20
|
import { propagateBumps } from "./propagateBumps.js";
|
|
22
|
-
import { readCurrentVersion } from "./readCurrentVersion.js";
|
|
23
21
|
import { releasePrepareProject } from "./releasePrepareProject.js";
|
|
24
22
|
import { renderChangelogMarkdown } from "./renderChangelogMarkdown.js";
|
|
25
23
|
import { deriveSectionOrder } from "./resolveReleaseNotesConfig.js";
|
|
26
|
-
import { refreshGitCliffCache } from "./runGitCliff.js";
|
|
27
24
|
export function releasePrepareMono(config, options) {
|
|
28
25
|
const { only, withReleaseNotes } = options;
|
|
29
26
|
const writes = [];
|
|
@@ -33,7 +30,7 @@ export function releasePrepareMono(config, options) {
|
|
|
33
30
|
}
|
|
34
31
|
const sectionOrder = deriveSectionOrder(resolveWorkTypes(config.workTypes));
|
|
35
32
|
const overrideContext = createOverrideContext(config.workspaces);
|
|
36
|
-
const { directBumps, directResults, skippedResults
|
|
33
|
+
const { directBumps, directResults, skippedResults } = determineDirectBumps(config, options);
|
|
37
34
|
const previousTags = new Map();
|
|
38
35
|
for (const result of directResults.values()) {
|
|
39
36
|
previousTags.set(result.workspace.dir, result.tag);
|
|
@@ -42,7 +39,7 @@ export function releasePrepareMono(config, options) {
|
|
|
42
39
|
previousTags.set(skipped.workspace.dir, skipped.tag);
|
|
43
40
|
}
|
|
44
41
|
const graph = buildDependencyGraph(config.workspaces);
|
|
45
|
-
const fullReleaseSet = propagateBumps(directBumps, graph
|
|
42
|
+
const fullReleaseSet = propagateBumps(directBumps, graph);
|
|
46
43
|
const { sorted: sortedDirs, cyclicDirs } = topologicalSort(fullReleaseSet, graph);
|
|
47
44
|
if (cyclicDirs.length > 0) {
|
|
48
45
|
warnings.push(`Circular workspace dependencies detected among: ${cyclicDirs.join(', ')}. ` +
|
|
@@ -53,9 +50,6 @@ export function releasePrepareMono(config, options) {
|
|
|
53
50
|
enabled: withReleaseNotes === true && config.changelogJson.enabled,
|
|
54
51
|
sectionOrder,
|
|
55
52
|
};
|
|
56
|
-
if (fullReleaseSet.size > 0) {
|
|
57
|
-
refreshGitCliffCache();
|
|
58
|
-
}
|
|
59
53
|
const { tags, modifiedFiles } = executeReleaseSet({
|
|
60
54
|
sortedDirs,
|
|
61
55
|
fullReleaseSet,
|
|
@@ -123,7 +117,6 @@ function determineDirectBumps(config, options) {
|
|
|
123
117
|
const directBumps = new Map();
|
|
124
118
|
const directResults = new Map();
|
|
125
119
|
const skippedResults = [];
|
|
126
|
-
const currentVersions = new Map();
|
|
127
120
|
const hintState = { emitted: false };
|
|
128
121
|
const knownPrefixes = config.workspaces.flatMap(getAllTagPrefixes);
|
|
129
122
|
for (const workspace of config.workspaces) {
|
|
@@ -134,21 +127,7 @@ function determineDirectBumps(config, options) {
|
|
|
134
127
|
if (tag === undefined) {
|
|
135
128
|
maybeEmitBaselineHint(workspace, knownPrefixes, hintState);
|
|
136
129
|
}
|
|
137
|
-
const primaryPackageFile = workspace.packageFiles[0];
|
|
138
|
-
if (primaryPackageFile !== undefined) {
|
|
139
|
-
const currentVersion = tryStage(stageLabel, () => readCurrentVersion(primaryPackageFile));
|
|
140
|
-
if (currentVersion !== undefined) {
|
|
141
|
-
currentVersions.set(workspace.dir, currentVersion);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
130
|
if (setVersion !== undefined) {
|
|
145
|
-
const currentVersion = currentVersions.get(workspace.dir);
|
|
146
|
-
if (currentVersion === undefined) {
|
|
147
|
-
throw new Error(`Cannot validate --set-version: failed to read current version from ${primaryPackageFile ?? '(no package file)'}`);
|
|
148
|
-
}
|
|
149
|
-
if (!isForwardVersion(currentVersion, setVersion)) {
|
|
150
|
-
throw new Error(`--set-version ${setVersion} is not greater than current version ${currentVersion}`);
|
|
151
|
-
}
|
|
152
131
|
directBumps.set(workspace.dir, { releaseType: 'patch', newVersionOverride: setVersion });
|
|
153
132
|
directResults.set(workspace.dir, {
|
|
154
133
|
workspace,
|
|
@@ -203,7 +182,7 @@ function determineDirectBumps(config, options) {
|
|
|
203
182
|
bumpOverride,
|
|
204
183
|
});
|
|
205
184
|
}
|
|
206
|
-
return { directBumps, directResults, skippedResults
|
|
185
|
+
return { directBumps, directResults, skippedResults };
|
|
207
186
|
}
|
|
208
187
|
function collectSkippedWorkspaces(skippedResults, fullReleaseSet) {
|
|
209
188
|
const workspaces = [];
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { bold, dim, sectionHeader } from "./format.js";
|
|
2
|
+
import { GIT_CLIFF_NPX_ARGS } from "./runGitCliff.js";
|
|
3
|
+
const CLIFF_DRY_RUN_COMMAND = `npx ${GIT_CLIFF_NPX_ARGS.join(' ')} ...`;
|
|
2
4
|
export function reportPrepare(plan, options) {
|
|
3
5
|
const isMultiWorkspace = plan.workspaces.some((w) => w.name !== undefined) || plan.project !== undefined;
|
|
4
6
|
if (isMultiWorkspace) {
|
|
@@ -103,7 +105,7 @@ function formatProjectSection(lines, project, dryRun) {
|
|
|
103
105
|
lines.push(dim(' Generating changelogs...'));
|
|
104
106
|
for (const file of project.changelogFiles) {
|
|
105
107
|
if (dryRun) {
|
|
106
|
-
lines.push(dim(` [dry-run] Would run:
|
|
108
|
+
lines.push(dim(` [dry-run] Would run: ${CLIFF_DRY_RUN_COMMAND} --output ${file}`));
|
|
107
109
|
}
|
|
108
110
|
else {
|
|
109
111
|
lines.push(dim(` Generating changelog: ${file}`));
|
|
@@ -195,7 +197,7 @@ function formatBumpFiles(lines, workspace, dryRun, indent = '') {
|
|
|
195
197
|
function formatChangelogFiles(lines, workspace, dryRun, indent = '') {
|
|
196
198
|
for (const file of workspace.changelogFiles) {
|
|
197
199
|
if (dryRun) {
|
|
198
|
-
lines.push(dim(`${indent} [dry-run] Would run:
|
|
200
|
+
lines.push(dim(`${indent} [dry-run] Would run: ${CLIFF_DRY_RUN_COMMAND} --output ${file}`));
|
|
199
201
|
}
|
|
200
202
|
else {
|
|
201
203
|
lines.push(dim(`${indent} Generating changelog: ${file}`));
|
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The exact `git-cliff` version every invocation resolves.
|
|
3
|
+
*
|
|
4
|
+
* npm's `git-cliff` trails the upstream release, so this pin normally sits behind the newest version cliff
|
|
5
|
+
* itself reports. Bumping it is an edit to this line, once npm publishes the version you want.
|
|
6
|
+
*/
|
|
7
|
+
export declare const GIT_CLIFF_VERSION = "2.13.1";
|
|
8
|
+
/** The npx arguments that precede the cliff args at every invocation, and that the dry-run report renders. */
|
|
9
|
+
export declare const GIT_CLIFF_NPX_ARGS: readonly string[];
|
|
1
10
|
/**
|
|
2
11
|
* Invokes `git-cliff` via `npx` and returns the output it produced.
|
|
3
12
|
*
|
|
@@ -9,15 +18,14 @@
|
|
|
9
18
|
* entire matching tag history on every run, so its size grows without bound with the repo;
|
|
10
19
|
* a file accommodates that, while a pipe is bounded by `maxBuffer`.
|
|
11
20
|
*
|
|
12
|
-
* The
|
|
13
|
-
*
|
|
14
|
-
*
|
|
21
|
+
* The npx spec names an exact version, which leaves npm no staleness to check, so `--prefer-offline` skips the
|
|
22
|
+
* per-call registry revalidation round-trip (~2.5 s per invocation on a warm cache) without stranding the run
|
|
23
|
+
* on whatever the cache happens to hold.
|
|
15
24
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* subsequent `--prefer-offline` calls run against a current binary.
|
|
25
|
+
* Two env entries are set for the child. `npm_config_progress=false` suppresses npx's animated stderr spinner,
|
|
26
|
+
* which otherwise renders as a transient flicker. `RUST_LOG=warn` drops cliff's INFO lines, among them the
|
|
27
|
+
* crates.io update notice it emits on every invocation while npm trails upstream, and leaves warnings and errors
|
|
28
|
+
* in place; an inherited `RUST_LOG` wins, so raising the level for debugging still works.
|
|
21
29
|
*
|
|
22
30
|
* The helper injects `--config <path>` and `--output <path>` itself — callers must NOT include either in `cliffArgs`.
|
|
23
31
|
* `--output` is appended last so a caller that passes one anyway cannot redirect the output the helper then reads.
|
|
@@ -28,18 +36,3 @@
|
|
|
28
36
|
* The temp-dir cleanup runs in a `finally` so it still happens on throw.
|
|
29
37
|
*/
|
|
30
38
|
export declare function runGitCliff(cliffConfigPath: string, cliffArgs: readonly string[]): string;
|
|
31
|
-
/**
|
|
32
|
-
* Revalidate npx's cache for `git-cliff` once per `prepare` run.
|
|
33
|
-
*
|
|
34
|
-
* Spawns `npx --yes git-cliff --version` *without* `--prefer-offline`, so that npm performs its normal
|
|
35
|
-
* registry-staleness check and refreshes the cached binary if a newer version is available.
|
|
36
|
-
* Subsequent `runGitCliff` calls within the same run keep `--prefer-offline` (preserving the per-call perf win)
|
|
37
|
-
* but now run against an up-to-date cache, so git-cliff's own self-update notice no longer fires repeatedly.
|
|
38
|
-
*
|
|
39
|
-
* Stdio: stdin ignored, stdout piped (the version line is suppressed as noise), stderr inherited (npm errors and
|
|
40
|
-
* any rare upgrade notice surface, but only once per run). `maxBuffer` is declared even though the piped output
|
|
41
|
-
* is a single version line, because every call that pipes stdout declares one.
|
|
42
|
-
*
|
|
43
|
-
* Errors propagate unchanged — a failed cache refresh fails the prepare run loudly rather than silently degrading.
|
|
44
|
-
*/
|
|
45
|
-
export declare function refreshGitCliffCache(): void;
|
package/dist/esm/runGitCliff.js
CHANGED
|
@@ -2,7 +2,8 @@ import { execFileSync } from 'node:child_process';
|
|
|
2
2
|
import { copyFileSync, mkdtempSync, readFileSync, rmSync } from 'node:fs';
|
|
3
3
|
import { tmpdir } from 'node:os';
|
|
4
4
|
import { join } from 'node:path';
|
|
5
|
-
|
|
5
|
+
export const GIT_CLIFF_VERSION = '2.13.1';
|
|
6
|
+
export const GIT_CLIFF_NPX_ARGS = ['--prefer-offline', '--yes', `git-cliff@${GIT_CLIFF_VERSION}`];
|
|
6
7
|
export function runGitCliff(cliffConfigPath, cliffArgs) {
|
|
7
8
|
const tempDir = mkdtempSync(join(tmpdir(), 'cliff-'));
|
|
8
9
|
try {
|
|
@@ -12,9 +13,9 @@ export function runGitCliff(cliffConfigPath, cliffArgs) {
|
|
|
12
13
|
copyFileSync(cliffConfigPath, configPath);
|
|
13
14
|
}
|
|
14
15
|
const outputPath = join(tempDir, 'output.json');
|
|
15
|
-
execFileSync('npx', [
|
|
16
|
+
execFileSync('npx', [...GIT_CLIFF_NPX_ARGS, '--config', configPath, ...cliffArgs, '--output', outputPath], {
|
|
16
17
|
stdio: ['ignore', 'ignore', 'inherit'],
|
|
17
|
-
env: { ...process.env, npm_config_progress: 'false' },
|
|
18
|
+
env: { ...process.env, npm_config_progress: 'false', RUST_LOG: process.env['RUST_LOG'] ?? 'warn' },
|
|
18
19
|
});
|
|
19
20
|
return readFileSync(outputPath, 'utf8');
|
|
20
21
|
}
|
|
@@ -22,10 +23,3 @@ export function runGitCliff(cliffConfigPath, cliffArgs) {
|
|
|
22
23
|
rmSync(tempDir, { recursive: true, force: true });
|
|
23
24
|
}
|
|
24
25
|
}
|
|
25
|
-
export function refreshGitCliffCache() {
|
|
26
|
-
execFileSync('npx', ['--yes', 'git-cliff', '--version'], {
|
|
27
|
-
maxBuffer: GIT_OUTPUT_LIMIT,
|
|
28
|
-
stdio: ['ignore', 'pipe', 'inherit'],
|
|
29
|
-
env: { ...process.env, npm_config_progress: 'false' },
|
|
30
|
-
});
|
|
31
|
-
}
|
|
@@ -5,9 +5,11 @@ import type { LabelDefinition } from './types.js';
|
|
|
5
5
|
*
|
|
6
6
|
* Resolution is an ordered fold with last-writer-wins: presets are loaded in `extends` order (a later preset replaces
|
|
7
7
|
* an earlier preset's label of the same name), then the `labels` record is applied: An entry adds a label, replaces
|
|
8
|
-
* one an earlier layer defined, or removes it (`null`).
|
|
8
|
+
* one an earlier layer defined, or removes it (`null`). Names match case-insensitively, as GitHub matches them, and the
|
|
9
|
+
* replacing layer's spelling becomes the label's name. Replacement is wholesale, so an entry omitting `description`
|
|
9
10
|
* drops the one an earlier layer supplied rather than inheriting it.
|
|
10
|
-
* Throws
|
|
11
|
-
* would
|
|
11
|
+
* Throws on two misstatements that would otherwise leave no trace in the output diff: keys in the `labels` record that
|
|
12
|
+
* differ only in case, all but one of which the fold would discard, and a dangling `null`, a removal naming a label that
|
|
13
|
+
* no preset defined.
|
|
12
14
|
*/
|
|
13
15
|
export declare function resolveLabels(config: RepoLabelsConfig): LabelDefinition[];
|