@williamthorsen/release-kit 12.0.0 → 13.0.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 +212 -0
- package/.readyup/kits/default.js +18 -17
- package/.readyup/kits/npm-auto-publish.js +24 -23
- package/.readyup/manifest.json +16 -11
- package/CHANGELOG.md +58 -0
- package/README.md +55 -48
- package/bin/release-kit.js +1 -1
- package/dist/esm/assertCleanWorkingTree.d.ts +1 -5
- package/dist/esm/assertTaggedBaseline.d.ts +29 -0
- package/dist/esm/assertTaggedBaseline.js +41 -0
- package/dist/esm/attachChangelogDiagnostics.d.ts +1 -1
- package/dist/esm/attachChangelogDiagnostics.js +3 -0
- package/dist/esm/bin/release-kit.js +85 -22
- package/dist/esm/buildChangelogEntries.d.ts +23 -13
- package/dist/esm/buildChangelogEntries.js +36 -17
- package/dist/esm/buildDependencyGraph.d.ts +6 -6
- package/dist/esm/buildEmptyReleaseEntry.d.ts +1 -8
- package/dist/esm/buildReleaseSummary.d.ts +2 -8
- package/dist/esm/buildSyntheticChangelogEntry.d.ts +3 -5
- package/dist/esm/buildSyntheticChangelogEntry.js +2 -2
- package/dist/esm/bumpVersion.d.ts +1 -8
- package/dist/esm/changelogJsonFile.d.ts +5 -7
- package/dist/esm/changelogJsonFile.js +2 -17
- package/dist/esm/changelogJsonUtils.d.ts +7 -10
- package/dist/esm/changelogOverrides.d.ts +59 -98
- package/dist/esm/classifyChangelogCommit.d.ts +7 -9
- package/dist/esm/classifyChangelogCommit.js +3 -2
- package/dist/esm/commitCommand.d.ts +2 -4
- package/dist/esm/compareVersions.d.ts +10 -4
- package/dist/esm/compareVersions.js +16 -0
- package/dist/esm/configFlagSchema.d.ts +1 -1
- package/dist/esm/createGithubRelease.d.ts +7 -19
- package/dist/esm/createGithubReleaseCommand.d.ts +5 -5
- package/dist/esm/createGithubReleaseCommand.js +9 -9
- package/dist/esm/createTags.d.ts +2 -3
- package/dist/esm/decideRelease.d.ts +2 -6
- package/dist/esm/defaults.d.ts +5 -24
- package/dist/esm/defaults.js +4 -5
- package/dist/esm/defineConfig.d.ts +1 -1
- package/dist/esm/deleteFileIfExists.d.ts +1 -1
- package/dist/esm/deriveReleaseNotesConfig.d.ts +20 -0
- package/dist/esm/deriveReleaseNotesConfig.js +21 -0
- package/dist/esm/deriveWorkspaceConfig.d.ts +3 -5
- package/dist/esm/detectPackageManager.d.ts +2 -4
- package/dist/esm/detectUndeclaredTagPrefixes.d.ts +4 -6
- package/dist/esm/detectUndeclaredTagPrefixes.js +1 -1
- package/dist/esm/determineBumpType.d.ts +1 -1
- package/dist/esm/discoverWorkspaces.d.ts +14 -15
- package/dist/esm/discoverWorkspaces.js +12 -12
- package/dist/esm/enterRepoRoot.d.ts +12 -0
- package/dist/esm/enterRepoRoot.js +16 -0
- package/dist/esm/enumerateReleaseWindows.d.ts +8 -8
- package/dist/esm/extractMigration.d.ts +6 -8
- package/dist/esm/findUnroutedEntryScopes.d.ts +16 -0
- package/dist/esm/findUnroutedEntryScopes.js +57 -0
- package/dist/esm/formatExcludedSkip.d.ts +3 -0
- package/dist/esm/formatExcludedSkip.js +3 -0
- package/dist/esm/formatPrivateSkip.d.ts +1 -6
- package/dist/esm/generateChangelogs.d.ts +10 -5
- package/dist/esm/glyphs.d.ts +1 -1
- package/dist/esm/help/prepareHelp.d.ts +3 -4
- package/dist/esm/help/prepareHelp.js +4 -3
- package/dist/esm/init/checks.d.ts +3 -3
- package/dist/esm/init/checks.js +4 -1
- package/dist/esm/init/detectRepoType.d.ts +2 -3
- package/dist/esm/init/hasProvenance.d.ts +2 -0
- package/dist/esm/init/hasProvenance.js +3 -0
- package/dist/esm/init/initCommand.d.ts +3 -6
- package/dist/esm/init/initCommand.js +10 -10
- package/dist/esm/init/parseJsonRecord.d.ts +1 -4
- package/dist/esm/init/scaffold.d.ts +1 -1
- package/dist/esm/init/templates.d.ts +9 -11
- package/dist/esm/init/templates.js +12 -11
- package/dist/esm/init/updateTemplatesCommand.d.ts +13 -0
- package/dist/esm/init/updateTemplatesCommand.js +64 -0
- package/dist/esm/injectReleaseNotesIntoReadme.d.ts +12 -17
- package/dist/esm/injectSection.d.ts +3 -3
- package/dist/esm/loadConfig.d.ts +16 -14
- package/dist/esm/loadConfig.js +2 -2
- package/dist/esm/loadValidatedConfig.d.ts +16 -19
- package/dist/esm/loadValidatedConfig.js +6 -1
- package/dist/esm/packsPath.d.ts +9 -9
- package/dist/esm/parseChangeRecordBlock.d.ts +2 -2
- package/dist/esm/parseCommitMessage.d.ts +17 -32
- package/dist/esm/parseCommitMessage.js +28 -37
- package/dist/esm/parseRequestedTags.d.ts +3 -3
- package/dist/esm/planReleaseNotesPreviews.d.ts +6 -6
- package/dist/esm/planVersionBump.d.ts +5 -3
- package/dist/esm/planVersionBump.js +3 -0
- package/dist/esm/prepareCommand.d.ts +4 -7
- package/dist/esm/prepareCommand.js +13 -6
- package/dist/esm/previewTagPrefixes.d.ts +8 -10
- package/dist/esm/propagateBumps.d.ts +6 -7
- package/dist/esm/publish.d.ts +1 -1
- package/dist/esm/publishCommand.d.ts +4 -3
- package/dist/esm/publishCommand.js +8 -8
- package/dist/esm/pushCommand.d.ts +4 -3
- package/dist/esm/pushCommand.js +8 -2
- package/dist/esm/pushRelease.d.ts +2 -2
- package/dist/esm/readChangelogSections.d.ts +33 -0
- package/dist/esm/readChangelogSections.js +80 -0
- package/dist/esm/releaseFiles.d.ts +5 -7
- package/dist/esm/releasePlan.d.ts +11 -13
- package/dist/esm/releasePrepare.d.ts +23 -22
- package/dist/esm/releasePrepare.js +31 -11
- package/dist/esm/releasePrepareMono.d.ts +9 -7
- package/dist/esm/releasePrepareMono.js +85 -33
- package/dist/esm/releasePrepareProject.d.ts +19 -29
- package/dist/esm/releasePrepareProject.js +31 -12
- package/dist/esm/renderChangelogMarkdown.d.ts +10 -4
- package/dist/esm/renderChangelogMarkdown.js +27 -2
- package/dist/esm/renderReleaseNotes.d.ts +3 -11
- package/dist/esm/renderReleaseNotes.js +2 -6
- package/dist/esm/reportPrepare.d.ts +4 -5
- package/dist/esm/reportPrepare.js +33 -19
- package/dist/esm/resolveCommandTags.d.ts +15 -11
- package/dist/esm/resolveCommandTags.js +35 -5
- package/dist/esm/resolveConfigFlag.d.ts +7 -0
- package/dist/esm/resolveConfigFlag.js +4 -0
- package/dist/esm/resolveReleaseTags.d.ts +6 -8
- package/dist/esm/resolveReleaseTags.js +1 -1
- package/dist/esm/resolveStylesOrExit.d.ts +2 -2
- package/dist/esm/showTagPrefixesCommand.d.ts +7 -10
- package/dist/esm/showTagPrefixesCommand.js +4 -4
- package/dist/esm/stripEmojiPrefix.d.ts +1 -0
- package/dist/esm/stripScope.d.ts +2 -2
- package/dist/esm/stripScope.js +3 -3
- package/dist/esm/sync-labels/generateCommand.d.ts +11 -10
- package/dist/esm/sync-labels/generateCommand.js +11 -8
- package/dist/esm/sync-labels/initCommand.d.ts +6 -7
- package/dist/esm/sync-labels/initCommand.js +2 -2
- package/dist/esm/sync-labels/presets.d.ts +5 -4
- package/dist/esm/sync-labels/presets.js +9 -9
- package/dist/esm/sync-labels/resolveLabels.d.ts +7 -7
- package/dist/esm/sync-labels/resolveLabels.js +1 -1
- package/dist/esm/sync-labels/retiredConfig.d.ts +4 -5
- package/dist/esm/sync-labels/syncCommand.d.ts +1 -4
- package/dist/esm/sync-labels/templates.d.ts +3 -3
- package/dist/esm/sync-labels/templates.js +3 -3
- package/dist/esm/sync-labels/typeLabels.d.ts +14 -0
- package/dist/esm/sync-labels/typeLabels.js +38 -0
- package/dist/esm/sync-labels/types.d.ts +1 -1
- package/dist/esm/tagCommand.d.ts +1 -1
- package/dist/esm/typeGuards.d.ts +2 -2
- package/dist/esm/types.d.ts +116 -91
- package/dist/esm/types.js +1 -1
- package/dist/esm/validateConfig.d.ts +2 -11
- package/dist/esm/validateConfig.js +2 -2
- package/dist/esm/validateOnlyExcludesStrandedDependents.d.ts +14 -12
- package/dist/esm/validateOverridesCommand.d.ts +17 -20
- package/dist/esm/validateOverridesCommand.js +1 -1
- package/docs/api.md +6 -6
- package/docs/changelogs.md +63 -40
- package/docs/configuration.md +29 -27
- package/docs/editorial-overrides.md +23 -23
- package/docs/labels.md +21 -19
- package/docs/project-releases.md +13 -13
- package/docs/publishing.md +5 -5
- package/docs/readiness-checks.md +3 -3
- package/docs/releasing.md +50 -25
- package/docs/work-types.md +41 -57
- package/package.json +7 -6
- package/presets/labels/common.yaml +1 -41
- package/dist/esm/resolveReleaseNotesConfig.d.ts +0 -26
- package/dist/esm/resolveReleaseNotesConfig.js +0 -29
- package/dist/esm/workTypesData.d.ts +0 -50
- package/dist/esm/workTypesData.js +0 -145
- package/schemas/label-map.json +0 -24
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { DEFAULT_CHANGELOG_JSON_CONFIG, DEFAULT_RELEASE_NOTES_CONFIG } from "./defaults.js";
|
|
2
|
+
import { resolveWorkTypes } from "./loadConfig.js";
|
|
3
|
+
export function deriveDependenciesHeader(workTypes) {
|
|
4
|
+
const header = workTypes['deps']?.header;
|
|
5
|
+
if (header === undefined) {
|
|
6
|
+
throw new Error("Resolved work types do not contain a 'deps' entry");
|
|
7
|
+
}
|
|
8
|
+
return header;
|
|
9
|
+
}
|
|
10
|
+
export function deriveReleaseNotesConfig(config) {
|
|
11
|
+
return {
|
|
12
|
+
releaseNotes: {
|
|
13
|
+
shouldInjectIntoReadme: config?.releaseNotes?.shouldInjectIntoReadme ?? DEFAULT_RELEASE_NOTES_CONFIG.shouldInjectIntoReadme,
|
|
14
|
+
},
|
|
15
|
+
changelogJsonOutputPath: config?.changelogJson?.outputPath ?? DEFAULT_CHANGELOG_JSON_CONFIG.outputPath,
|
|
16
|
+
sectionOrder: deriveSectionOrder(resolveWorkTypes(config?.workTypes)),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export function deriveSectionOrder(workTypes) {
|
|
20
|
+
return Object.values(workTypes).map((entry) => entry.header);
|
|
21
|
+
}
|
|
@@ -2,10 +2,8 @@ import type { WorkspaceConfig } from './types.js';
|
|
|
2
2
|
/**
|
|
3
3
|
* Derives a workspace configuration from a workspace-relative path.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
* package's
|
|
7
|
-
*
|
|
8
|
-
* config overrides, and dependency-graph lookups. The `tagPrefix` is `${unscopedName}-v`,
|
|
9
|
-
* so tags reflect the package identity rather than the directory layout.
|
|
5
|
+
* The tag prefix is `${unscopedName}-v`, from the `name` in the workspace's `package.json`, so that tags follow the
|
|
6
|
+
* package's identity rather than the directory layout. `dir` is the basename of the path: the stable internal
|
|
7
|
+
* identifier for `--only`, config overrides, and dependency-graph lookups.
|
|
10
8
|
*/
|
|
11
9
|
export declare function deriveWorkspaceConfig(workspacePath: string): WorkspaceConfig;
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
export type PackageManager = 'npm' | 'pnpm' | 'yarn' | 'yarn-berry';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* Yarn v2+ is returned as `'yarn-berry'` to distinguish it from Yarn Classic.
|
|
3
|
+
* Detects the repo's package manager from the `packageManager` field of the root `package.json`, then from lockfiles,
|
|
4
|
+
* and otherwise returns `npm`. Yarn v2+ is `'yarn-berry'`, to distinguish it from Yarn Classic.
|
|
7
5
|
*/
|
|
8
6
|
export declare function detectPackageManager(): PackageManager;
|
|
@@ -14,12 +14,10 @@ export interface UndeclaredTagPrefix {
|
|
|
14
14
|
suggestedDir: string;
|
|
15
15
|
}
|
|
16
16
|
/**
|
|
17
|
-
*
|
|
17
|
+
* Scans local git tags for release-shaped tags whose prefix is not in `knownPrefixes`, the union of derived and
|
|
18
|
+
* declared prefixes across all workspaces.
|
|
18
19
|
*
|
|
19
|
-
* Reads only the local tag list
|
|
20
|
-
*
|
|
21
|
-
* or no candidate-shaped tags outside the known set.
|
|
22
|
-
*
|
|
23
|
-
* @param knownPrefixes - Union of derived and declared prefixes across all workspaces.
|
|
20
|
+
* Reads only the local tag list, so a remote tag that has not been fetched is not reported. Returns an empty array
|
|
21
|
+
* when the repo doesn't have any tags, or any candidate-shaped tags outside the known set.
|
|
24
22
|
*/
|
|
25
23
|
export declare function detectUndeclaredTagPrefixes(knownPrefixes: readonly string[]): UndeclaredTagPrefix[];
|
|
@@ -42,7 +42,7 @@ export function detectUndeclaredTagPrefixes(knownPrefixes) {
|
|
|
42
42
|
suggestedDir: stripTrailingTagMarker(prefix),
|
|
43
43
|
});
|
|
44
44
|
}
|
|
45
|
-
return results.
|
|
45
|
+
return results.toSorted((a, b) => a.prefix.localeCompare(b.prefix));
|
|
46
46
|
}
|
|
47
47
|
function stripTrailingTagMarker(prefix) {
|
|
48
48
|
return prefix.endsWith('-v') ? prefix.slice(0, -2) : prefix;
|
|
@@ -5,7 +5,7 @@ export interface BumpSignal {
|
|
|
5
5
|
breaking: boolean;
|
|
6
6
|
}
|
|
7
7
|
/**
|
|
8
|
-
* Determines the overall bump type from a set of changes, returning undefined when none
|
|
8
|
+
* Determines the overall bump type from a set of changes, returning undefined when none triggers a bump.
|
|
9
9
|
*
|
|
10
10
|
* Uses `versionPatterns` to decide which types trigger major/minor bumps.
|
|
11
11
|
* The `'!'` sentinel in `versionPatterns.major` means "any breaking change triggers major".
|
|
@@ -5,8 +5,8 @@ export type { EmptyWorkspaceCause } from '@williamthorsen/nmr-core/workspace';
|
|
|
5
5
|
* set of package directories, or it declares a workspace that resolves to none and the cause says which
|
|
6
6
|
* condition emptied it.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
* relative to the monorepo root. The two
|
|
8
|
+
* Kept separate from nmr-core's `WorkspaceResolution`, whose `packageDirs` are absolute, because these are
|
|
9
|
+
* relative to the monorepo root. The two have incompatible path contracts, and a distinct `kind` for the
|
|
10
10
|
* single-package case keeps either from standing in for the other.
|
|
11
11
|
*/
|
|
12
12
|
export type WorkspaceDiscovery = {
|
|
@@ -20,39 +20,38 @@ export type WorkspaceDiscovery = {
|
|
|
20
20
|
} | {
|
|
21
21
|
kind: 'single-package';
|
|
22
22
|
};
|
|
23
|
-
/** A workspace that
|
|
23
|
+
/** A workspace that did not resolve to any package directory, which every release-kit command treats as a failure. */
|
|
24
24
|
export type EmptyWorkspace = Extract<WorkspaceDiscovery, {
|
|
25
25
|
kind: 'empty';
|
|
26
26
|
}>;
|
|
27
27
|
/**
|
|
28
|
-
* The empty-workspace causes that
|
|
28
|
+
* The empty-workspace causes that every release-kit command treats as a failure.
|
|
29
29
|
*
|
|
30
|
-
* `no-packages-list` is absent because a manifest declaring nothing releases as a single package, so
|
|
31
|
-
*
|
|
30
|
+
* `no-packages-list` is absent because a manifest declaring nothing releases as a single package, so
|
|
31
|
+
* release-kit's commands never compose a message for it.
|
|
32
32
|
*/
|
|
33
33
|
export type FailingWorkspaceCause = Exclude<EmptyWorkspaceCause, 'no-packages-list'>;
|
|
34
34
|
/**
|
|
35
|
-
* Returns the sentence naming which
|
|
36
|
-
*
|
|
37
|
-
* reach the matcher with no list to quote.
|
|
35
|
+
* Returns the sentence naming which condition left a workspace without a package directory, and the remedy for
|
|
36
|
+
* it. A message quotes the manifest's `packages` list whenever the manifest declares one that the resolver read.
|
|
38
37
|
*
|
|
39
38
|
* The `package.json` requirement is stated under `no-package` because it is a divergence from pnpm, which
|
|
40
|
-
* recognizes two further manifests, and the reader of a workspace that pnpm resolves
|
|
39
|
+
* recognizes two further manifests, and the reader of a workspace that pnpm resolves cannot infer it.
|
|
41
40
|
*/
|
|
42
41
|
export declare function describeEmptyWorkspace(workspace: EmptyWorkspace): string;
|
|
43
42
|
/**
|
|
44
43
|
* Reads `pnpm-workspace.yaml` at `monorepoRoot` and resolves its `packages` patterns, applying pnpm's
|
|
45
|
-
* semantics
|
|
44
|
+
* semantics, `!`-prefixed exclusions included.
|
|
46
45
|
*
|
|
47
|
-
* A directory
|
|
46
|
+
* A directory without a manifest, and one whose manifest does not declare a `packages` list, both release as a
|
|
48
47
|
* single package: pnpm resolves each to the root package alone, and a workspace file kept for `catalog:`,
|
|
49
48
|
* `overrides:`, or `onlyBuiltDependencies:` is the second of them. Every other empty resolution is a failure,
|
|
50
|
-
* whether the
|
|
49
|
+
* whether the manifest declares patterns that match nothing or a `packages` value that the resolver cannot read
|
|
51
50
|
* as patterns at all.
|
|
52
51
|
*
|
|
53
52
|
* `packageDirs` come back relative to `monorepoRoot` as POSIX paths, because that is what every consumer
|
|
54
53
|
* needs: `WorkspaceConfig.paths` feeds `git log -- <paths>`, and `packageFiles` and `changelogPaths` are read
|
|
55
|
-
* from disk relative to the working directory. The workspace root itself relativizes to `.`, the path
|
|
56
|
-
* `deriveWorkspaceConfig` reads a root package's manifest
|
|
54
|
+
* from disk relative to the working directory. The workspace root itself relativizes to `.`, the path through
|
|
55
|
+
* which `deriveWorkspaceConfig` reads a root package's manifest.
|
|
57
56
|
*/
|
|
58
57
|
export declare function discoverWorkspaces(monorepoRoot?: string): WorkspaceDiscovery;
|
|
@@ -7,21 +7,21 @@ export function describeEmptyWorkspace(workspace) {
|
|
|
7
7
|
return (`pnpm-workspace.yaml ${declaredClause}, whose \`!\` entries exclude every directory matched by the ` +
|
|
8
8
|
'positive patterns. Drop or narrow the exclusion.');
|
|
9
9
|
case 'no-package':
|
|
10
|
-
return (`pnpm-workspace.yaml ${declaredClause}, and the matcher
|
|
11
|
-
'Add one to the directory that should be a package, or declare a pattern
|
|
12
|
-
'
|
|
10
|
+
return (`pnpm-workspace.yaml ${declaredClause}, and the matcher did not find any directory containing a ` +
|
|
11
|
+
'`package.json`. Add one to the directory that should be a package, or declare a pattern matching a ' +
|
|
12
|
+
'directory that contains one.');
|
|
13
13
|
case 'no-pattern':
|
|
14
|
-
return (`pnpm-workspace.yaml ${declaredClause}, so
|
|
15
|
-
'such as `packages/*`, and quote any `!` entry, which YAML reads as a tag rather than a string
|
|
16
|
-
'it
|
|
14
|
+
return (`pnpm-workspace.yaml ${declaredClause}, so the matcher does not receive any pattern. Declare a positive ` +
|
|
15
|
+
'pattern such as `packages/*`, and quote any `!` entry, which YAML reads as a tag rather than a string ' +
|
|
16
|
+
'when it is unquoted.');
|
|
17
17
|
case 'unreadable-manifest':
|
|
18
|
-
return ('pnpm-workspace.yaml
|
|
19
|
-
'syntax error and run the command again. An unterminated quoted string and a
|
|
20
|
-
'the usual ones.');
|
|
18
|
+
return ('pnpm-workspace.yaml does not contain valid YAML, so the matcher does not receive anything that it ' +
|
|
19
|
+
'declares. Repair the syntax error and run the command again. An unterminated quoted string and a ' +
|
|
20
|
+
'mis-indented entry are the usual ones.');
|
|
21
21
|
case 'unreadable-packages':
|
|
22
|
-
return ('pnpm-workspace.yaml declares a `packages` value that is not a list of strings, so
|
|
23
|
-
'
|
|
24
|
-
'and an entry YAML read as a number or a map are the usual ones.');
|
|
22
|
+
return ('pnpm-workspace.yaml declares a `packages` value that is not a list of strings, so the matcher does ' +
|
|
23
|
+
'not receive anything that it declares. Make it a list whose every entry is a quoted pattern; a bare ' +
|
|
24
|
+
'value and an entry that YAML read as a number or a map are the usual ones.');
|
|
25
25
|
default: {
|
|
26
26
|
const unhandledCause = workspace.cause;
|
|
27
27
|
throw new Error(`Unhandled empty-workspace cause: ${String(unhandledCause)}`);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** The directory from which `release-kit` was invoked, and the repo root in which it now runs. */
|
|
2
|
+
export interface RepoLocation {
|
|
3
|
+
invocationDir: string;
|
|
4
|
+
root: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Locates the repo root from `startDir` and makes it the process working directory.
|
|
8
|
+
*
|
|
9
|
+
* The root is the nearest ancestor containing `pnpm-workspace.yaml`, or else `startDir` itself when it contains a
|
|
10
|
+
* `package.json`. A single-package repo is therefore found only from its own root. Throws when neither applies.
|
|
11
|
+
*/
|
|
12
|
+
export declare function enterRepoRoot(startDir?: string): RepoLocation;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { findMonorepoRoot } from '@williamthorsen/nmr-core/workspace';
|
|
4
|
+
export function enterRepoRoot(startDir = process.cwd()) {
|
|
5
|
+
const invocationDir = path.resolve(startDir);
|
|
6
|
+
const root = findMonorepoRoot(invocationDir) ?? findSinglePackageRoot(invocationDir);
|
|
7
|
+
if (root === undefined) {
|
|
8
|
+
throw new Error(`No repo root found from ${invocationDir}: expected a pnpm-workspace.yaml in it or any parent directory, ` +
|
|
9
|
+
'or a package.json in it. Run release-kit from inside a monorepo or at the root of a single-package repo.');
|
|
10
|
+
}
|
|
11
|
+
process.chdir(root);
|
|
12
|
+
return { invocationDir, root };
|
|
13
|
+
}
|
|
14
|
+
function findSinglePackageRoot(dir) {
|
|
15
|
+
return existsSync(path.join(dir, 'package.json')) ? dir : undefined;
|
|
16
|
+
}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
/** A commit as git records it, before any parsing or filtering. */
|
|
2
2
|
export interface RawCommit {
|
|
3
|
-
/** The full 40-character hash
|
|
3
|
+
/** The full 40-character hash. */
|
|
4
4
|
hash: string;
|
|
5
5
|
/** The first line, with a wrapped subject folded onto one line by git. */
|
|
6
6
|
subject: string;
|
|
7
|
-
/** The message after its first blank line, empty when the commit
|
|
7
|
+
/** The message after its first blank line, empty when the commit does not have a body. */
|
|
8
8
|
body: string;
|
|
9
9
|
/** The raw commit message, subject and body together. */
|
|
10
10
|
message: string;
|
|
11
11
|
}
|
|
12
|
-
/** The commits one release contains, with the version and date under which they
|
|
12
|
+
/** The commits that one release contains, with the version and date under which they were released. */
|
|
13
13
|
export interface ReleaseWindow {
|
|
14
14
|
/** The tag name for a released window, the caller's unreleased-tag name for the newest one. */
|
|
15
15
|
version: string;
|
|
@@ -32,19 +32,19 @@ export interface EnumerateReleaseWindowsOptions {
|
|
|
32
32
|
/**
|
|
33
33
|
* Splits the history reachable from HEAD into release windows, newest first.
|
|
34
34
|
*
|
|
35
|
-
* The newest window is always the unreleased one, empty when HEAD
|
|
35
|
+
* The newest window is always the unreleased one, empty when HEAD is at a matching tag. Released
|
|
36
36
|
* windows follow in descending order, one per reachable tag matching a prefix. A tag that
|
|
37
37
|
* HEAD cannot reach is dropped.
|
|
38
38
|
*
|
|
39
39
|
* A commit belongs to the oldest matching tag that contains it, and to the unreleased window
|
|
40
|
-
* when
|
|
41
|
-
* position in a linear walk, because a branch commit authored before a tag
|
|
42
|
-
* is not in that tag's release.
|
|
40
|
+
* when it is not contained by any matching tag. Containment is read from the ancestry graph
|
|
41
|
+
* rather than from a position in a linear walk, because a branch commit authored before a tag
|
|
42
|
+
* and merged after it is not in that tag's release.
|
|
43
43
|
*
|
|
44
44
|
* Three git invocations serve any number of tags: `git rev-list --topo-order --parents` for the
|
|
45
45
|
* ancestry graph, `git for-each-ref` for the tag names with their creation dates and target
|
|
46
46
|
* commits, and one path-filtered `git log` for the commits themselves.
|
|
47
47
|
*
|
|
48
|
-
*
|
|
48
|
+
* Throws if `tagPrefixes` is empty, or if any git invocation fails.
|
|
49
49
|
*/
|
|
50
50
|
export declare function enumerateReleaseWindows(options: EnumerateReleaseWindowsOptions): ReleaseWindow[];
|
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Extracts the instruction from a commit body's `Migration:` paragraph.
|
|
3
3
|
*
|
|
4
|
-
* The
|
|
5
|
-
*
|
|
6
|
-
* case-sensitive,
|
|
7
|
-
* label literal, and tolerating variants would let the form drift.
|
|
4
|
+
* The commit convention defines one literal `Migration:` label per body, so the first paragraph
|
|
5
|
+
* opening with it is read and a second is ignored. Because the match is anchored and
|
|
6
|
+
* case-sensitive, `migration:` and `**Migration:**` yield nothing.
|
|
8
7
|
*
|
|
9
|
-
* Newlines inside the paragraph are preserved. The first character is capitalized, which
|
|
10
|
-
*
|
|
11
|
-
* paragraph
|
|
12
|
-
* the label is followed by nothing.
|
|
8
|
+
* Newlines inside the paragraph are preserved. The first character is capitalized, which repairs
|
|
9
|
+
* an instruction that continues the label in lowercase. Returns `undefined` when the body does not
|
|
10
|
+
* contain a paragraph opening with the label, or when the label is followed by nothing.
|
|
13
11
|
*/
|
|
14
12
|
export declare function extractMigration(body: string | undefined): string | undefined;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { RawCommit } from './enumerateReleaseWindows.js';
|
|
2
|
+
import type { UnroutedEntryScope } from './types.js';
|
|
3
|
+
/** A workspace's `dir` with the commits of its unreleased window. */
|
|
4
|
+
export interface WorkspaceWindow {
|
|
5
|
+
commits: readonly RawCommit[];
|
|
6
|
+
dir: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
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 `dir` of every workspace whose window contains it.
|
|
11
|
+
*
|
|
12
|
+
* A scope resolves through `scopeAliases` first. It is not reported when it resolves to `*`, to a workspace whose
|
|
13
|
+
* window contains the commit, or to a `configuredDirs` workspace absent from `windows`, which the run did not read.
|
|
14
|
+
* `root` is not reported unless a configured workspace has that `dir`, in which case it matches as that workspace.
|
|
15
|
+
*/
|
|
16
|
+
export declare function findUnroutedEntryScopes(windows: readonly WorkspaceWindow[], configuredDirs: readonly string[], scopeAliases: Readonly<Record<string, string>>): Map<string, UnroutedEntryScope[]>;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { isNonChangeSubject } from "./classifyChangelogCommit.js";
|
|
2
|
+
import { parseChangeRecordBlock } from "./parseChangeRecordBlock.js";
|
|
3
|
+
export function findUnroutedEntryScopes(windows, configuredDirs, scopeAliases) {
|
|
4
|
+
const readDirs = new Set(windows.map((window) => window.dir));
|
|
5
|
+
const unreadDirs = new Set(configuredDirs.filter((dir) => !readDirs.has(dir)));
|
|
6
|
+
const isRootReserved = !configuredDirs.includes('root');
|
|
7
|
+
const findings = new Map();
|
|
8
|
+
for (const { commit, windowDirs } of indexCommits(windows).values()) {
|
|
9
|
+
for (const finding of findCommitUnroutedScopes(commit, windowDirs, unreadDirs, isRootReserved, scopeAliases)) {
|
|
10
|
+
for (const dir of windowDirs) {
|
|
11
|
+
const list = findings.get(dir) ?? [];
|
|
12
|
+
list.push(finding);
|
|
13
|
+
findings.set(dir, list);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return findings;
|
|
18
|
+
}
|
|
19
|
+
function findCommitUnroutedScopes(commit, windowDirs, unreadDirs, isRootReserved, scopeAliases) {
|
|
20
|
+
if (isNonChangeSubject(commit.subject)) {
|
|
21
|
+
return [];
|
|
22
|
+
}
|
|
23
|
+
const reading = parseChangeRecordBlock(commit.message);
|
|
24
|
+
if (reading.kind !== 'read') {
|
|
25
|
+
return [];
|
|
26
|
+
}
|
|
27
|
+
const findings = [];
|
|
28
|
+
for (const [index, entry] of reading.entries.entries()) {
|
|
29
|
+
const scopes = new Set(entry.scopes);
|
|
30
|
+
for (const scope of scopes) {
|
|
31
|
+
const resolved = scopeAliases[scope] ?? scope;
|
|
32
|
+
const isRouted = resolved === '*' ||
|
|
33
|
+
windowDirs.has(resolved) ||
|
|
34
|
+
unreadDirs.has(resolved) ||
|
|
35
|
+
(resolved === 'root' && isRootReserved);
|
|
36
|
+
if (!isRouted) {
|
|
37
|
+
findings.push({ commitHash: commit.hash, commitSubject: commit.subject, entryPosition: index + 1, scope });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return findings;
|
|
42
|
+
}
|
|
43
|
+
function indexCommits(windows) {
|
|
44
|
+
const index = new Map();
|
|
45
|
+
for (const window of windows) {
|
|
46
|
+
for (const commit of window.commits) {
|
|
47
|
+
const indexed = index.get(commit.hash);
|
|
48
|
+
if (indexed === undefined) {
|
|
49
|
+
index.set(commit.hash, { commit, windowDirs: new Set([window.dir]) });
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
indexed.windowDirs.add(window.dir);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return index;
|
|
57
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { ResolvedTag } from './resolveReleaseTags.js';
|
|
2
|
+
/** Formats the warning emitted when a release tag's workspace is excluded by config and therefore skipped. */
|
|
3
|
+
export declare function formatExcludedSkip(resolvedTag: Pick<ResolvedTag, 'tag' | 'workspacePath'>): string;
|
|
@@ -1,8 +1,3 @@
|
|
|
1
1
|
import type { ResolvedTag } from './resolveReleaseTags.js';
|
|
2
|
-
/**
|
|
3
|
-
* Format the warning emitted when a release tag's workspace is private and therefore skipped.
|
|
4
|
-
*
|
|
5
|
-
* Shared by `release-kit publish` and `release-kit create-github-release` so the skip wording
|
|
6
|
-
* is defined once and stays identical across both commands.
|
|
7
|
-
*/
|
|
2
|
+
/** Formats the warning emitted when a release tag's workspace is private and therefore skipped. */
|
|
8
3
|
export declare function formatPrivateSkip(resolvedTag: Pick<ResolvedTag, 'tag' | 'workspacePath'>): string;
|
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
import type { WorkspaceConfig } from './types.js';
|
|
2
|
+
/** Returns the workspace's derived tag prefix followed by each declared legacy-identity tag prefix. */
|
|
3
|
+
export declare function getAllTagPrefixes(workspace: WorkspaceConfig): string[];
|
|
2
4
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* `validateOverridesCommand` so the two compute byte-equal per-workspace tag-prefix unions.
|
|
5
|
+
* Options for `readReleaseHistory`: the tag prefixes and pathspecs that bound its release windows, and the workspace
|
|
6
|
+
* to which it routes change-record entries.
|
|
6
7
|
*/
|
|
7
|
-
export declare function getAllTagPrefixes(workspace: WorkspaceConfig): string[];
|
|
8
|
-
/** Options for `buildChangelogEntries`: the tag prefixes and pathspecs that bound its release windows. */
|
|
9
8
|
export interface GenerateChangelogOptions {
|
|
10
9
|
/** Tag prefixes to match as a union; must contain at least one entry. */
|
|
11
10
|
tagPrefixes: readonly string[];
|
|
12
11
|
/** Git pathspecs restricting the history to commits that touch them; all paths when omitted. */
|
|
13
12
|
paths?: readonly string[];
|
|
13
|
+
/**
|
|
14
|
+
* The `dir` of the workspace being read. When set, the read includes a change-record entry only when its scopes,
|
|
15
|
+
* after `scopeAliases` resolution, name this `dir`, are empty, or contain `*`; when omitted, the read includes every
|
|
16
|
+
* entry.
|
|
17
|
+
*/
|
|
18
|
+
workspaceDir?: string;
|
|
14
19
|
}
|
package/dist/esm/glyphs.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
/** Glyphs that name
|
|
1
|
+
/** Glyphs that do not name a status; a status marker comes from nmr-core's `STATUS_GLYPHS`. */
|
|
2
2
|
export declare const RELEASE_GLYPHS: Readonly<Record<"plain" | "rich", Readonly<Record<"tag" | "bump" | "dryRun", import("@williamthorsen/toolbelt.terminal/candidate").Glyph>>>>;
|
|
3
3
|
export type ReleaseGlyphName = keyof (typeof RELEASE_GLYPHS)['rich'];
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* effects so both can load it.
|
|
2
|
+
* Help text for `release-kit prepare`. The module does not have any import-time side effects, so that a test can
|
|
3
|
+
* import it without running the CLI.
|
|
5
4
|
*/
|
|
6
|
-
export declare const prepareHelpText = "\nUsage: release-kit prepare [options]\n\nRun release preparation with automatic workspace discovery.\n\nOptions:\n --config <path>
|
|
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 it requires --only, and is rejected when a 'project' block 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 Only process the named workspaces (comma-separated, monorepo only).\n When a 'project' block is configured, the project 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";
|
|
7
6
|
/** Prints the prepare command's help text. */
|
|
8
7
|
export declare function showPrepareHelp(): void;
|
|
@@ -4,13 +4,14 @@ Usage: release-kit prepare [options]
|
|
|
4
4
|
Run release preparation with automatic workspace discovery.
|
|
5
5
|
|
|
6
6
|
Options:
|
|
7
|
-
--config <path>
|
|
7
|
+
--config <path> Config file, relative to the current directory
|
|
8
|
+
(default: .config/release-kit.config.ts at the repo root)
|
|
8
9
|
--dry-run Run without modifying any files
|
|
9
10
|
--bump=major|minor|patch Set the level of every release that happens; does not trigger one
|
|
10
11
|
--set-version=X.Y.Z Set an explicit version; bypasses the changelog-derived bump. In monorepo
|
|
11
12
|
mode it requires --only, and is rejected when a 'project' block is configured.
|
|
12
|
-
--force Release even when
|
|
13
|
-
|
|
13
|
+
--force Release even when the last tag is not followed by any commits,
|
|
14
|
+
or by any bump-worthy ones. Defaults to patch when --bump is not given;
|
|
14
15
|
use --bump=X for a different level.
|
|
15
16
|
--no-git-checks, -n Skip the clean-working-tree check
|
|
16
17
|
--only=name1,name2 Only process the named workspaces (comma-separated, monorepo only).
|
|
@@ -3,9 +3,9 @@ export interface CheckResult {
|
|
|
3
3
|
ok: boolean;
|
|
4
4
|
message?: string;
|
|
5
5
|
}
|
|
6
|
-
/**
|
|
6
|
+
/** Verifies that the current directory is inside a git repository. */
|
|
7
7
|
export declare function isGitRepo(): CheckResult;
|
|
8
|
-
/**
|
|
8
|
+
/** Verifies that package.json exists in the current directory. */
|
|
9
9
|
export declare function hasPackageJson(): CheckResult;
|
|
10
|
-
/**
|
|
10
|
+
/** Verifies that the project uses pnpm, signaled by pnpm-lock.yaml or a pnpm `packageManager` field. */
|
|
11
11
|
export declare function usesPnpm(): CheckResult;
|
package/dist/esm/init/checks.js
CHANGED
|
@@ -15,7 +15,10 @@ export function hasPackageJson() {
|
|
|
15
15
|
if (existsSync('package.json')) {
|
|
16
16
|
return { ok: true };
|
|
17
17
|
}
|
|
18
|
-
return {
|
|
18
|
+
return {
|
|
19
|
+
ok: false,
|
|
20
|
+
message: 'The current directory does not contain a package.json. Run `npm init` or `pnpm init` first.',
|
|
21
|
+
};
|
|
19
22
|
}
|
|
20
23
|
export function usesPnpm() {
|
|
21
24
|
if (existsSync('pnpm-lock.yaml')) {
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
/** Repo layout type. */
|
|
2
2
|
export type RepoType = 'monorepo' | 'single-package';
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* Detects whether the current directory is a monorepo or a single-package repo.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
* Otherwise returns `'single-package'`.
|
|
6
|
+
* A monorepo has `pnpm-workspace.yaml` or a `workspaces` array in `package.json`.
|
|
8
7
|
*/
|
|
9
8
|
export declare function detectRepoType(): RepoType;
|
|
@@ -5,11 +5,8 @@ interface InitOptions {
|
|
|
5
5
|
styles: StreamStyles;
|
|
6
6
|
withConfig: boolean;
|
|
7
7
|
}
|
|
8
|
-
/**
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
* Checks eligibility, detects repo type, scaffolds files, and prints next steps.
|
|
12
|
-
* Returns the process exit code (0 for success, 1 for failure).
|
|
13
|
-
*/
|
|
8
|
+
/** Runs the eligibility checks, stopping at the first failure, and reports whether all passed. */
|
|
9
|
+
export declare function checkEligibility(styles: StreamStyles): boolean;
|
|
10
|
+
/** Runs the `release-kit init` command and returns the process exit code. */
|
|
14
11
|
export declare function initCommand({ dryRun, force, styles, withConfig }: InitOptions): number;
|
|
15
12
|
export {};
|
|
@@ -3,15 +3,7 @@ import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
|
3
3
|
import { hasPackageJson, isGitRepo, usesPnpm } from "./checks.js";
|
|
4
4
|
import { detectRepoType } from "./detectRepoType.js";
|
|
5
5
|
import { scaffoldFiles } from "./scaffold.js";
|
|
6
|
-
function
|
|
7
|
-
if (result.ok) {
|
|
8
|
-
printSuccess(label, styles.stdout);
|
|
9
|
-
return true;
|
|
10
|
-
}
|
|
11
|
-
printError(result.message ?? `${label} failed`, styles.stderr);
|
|
12
|
-
return false;
|
|
13
|
-
}
|
|
14
|
-
function checkEligibility(styles) {
|
|
6
|
+
export function checkEligibility(styles) {
|
|
15
7
|
printStep('Checking eligibility');
|
|
16
8
|
if (!runRequiredCheck('Git repository detected', isGitRepo(), styles))
|
|
17
9
|
return false;
|
|
@@ -66,10 +58,18 @@ export function initCommand({ dryRun, force, styles, withConfig }) {
|
|
|
66
58
|
: '1. (Optional) Run again with --with-config to scaffold config files.';
|
|
67
59
|
console.info(`
|
|
68
60
|
${configHint}
|
|
69
|
-
2. If this is a
|
|
61
|
+
2. If this is a private repo, remove provenance: true from .github/workflows/publish.yaml.
|
|
70
62
|
3. Test by running: npx @williamthorsen/release-kit prepare --dry-run
|
|
71
63
|
4. Commit the generated files.
|
|
72
64
|
5. Register each package as a trusted publisher on npmjs.com.
|
|
73
65
|
`);
|
|
74
66
|
return 0;
|
|
75
67
|
}
|
|
68
|
+
function runRequiredCheck(label, result, styles) {
|
|
69
|
+
if (result.ok) {
|
|
70
|
+
printSuccess(label, styles.stdout);
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
printError(result.message ?? `${label} failed`, styles.stderr);
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
@@ -1,5 +1,2 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Parse a JSON string and return the result if it is a plain object (Record<string, unknown>).
|
|
3
|
-
* Returns `undefined` if parsing fails or the result is not a plain object.
|
|
4
|
-
*/
|
|
1
|
+
/** Parses a JSON string, returning the result if it is a plain object and `undefined` otherwise. */
|
|
5
2
|
export declare function parseJsonRecord(raw: string): Record<string, unknown> | undefined;
|
|
@@ -6,6 +6,6 @@ interface ScaffoldOptions {
|
|
|
6
6
|
overwrite: boolean;
|
|
7
7
|
withConfig: boolean;
|
|
8
8
|
}
|
|
9
|
-
/**
|
|
9
|
+
/** Scaffolds release-kit files for the target repo, returning a result for each file attempted. */
|
|
10
10
|
export declare function scaffoldFiles({ repoType, dryRun, overwrite, withConfig }: ScaffoldOptions): WriteResult[];
|
|
11
11
|
export {};
|
|
@@ -1,18 +1,16 @@
|
|
|
1
1
|
import type { RepoType } from './detectRepoType.js';
|
|
2
|
-
/**
|
|
2
|
+
/** Generates the `.config/release-kit.config.ts` starter config, with commented-out options to customize. */
|
|
3
3
|
export declare function releaseConfigScript(repoType: RepoType): string;
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* Generates the publish.yaml GitHub Actions entry-point workflow.
|
|
6
6
|
*
|
|
7
|
-
* The `permissions` block
|
|
8
|
-
*
|
|
9
|
-
*/
|
|
10
|
-
export declare function publishWorkflow(repoType: RepoType): string;
|
|
11
|
-
/**
|
|
12
|
-
* Generate the create-github-release.yaml GitHub Actions caller workflow.
|
|
13
|
-
*
|
|
14
|
-
* Fires on tag push and delegates to the reusable workflow under `contents: write`.
|
|
7
|
+
* The caller's `permissions` block caps what the reusable workflow receives, so it must grant the `id-token: write`
|
|
8
|
+
* that OIDC publishing needs. `provenance: false` omits the provenance input, which a private repo cannot use.
|
|
15
9
|
*/
|
|
10
|
+
export declare function publishWorkflow(repoType: RepoType, { provenance }?: {
|
|
11
|
+
provenance?: boolean;
|
|
12
|
+
}): string;
|
|
13
|
+
/** Generates the create-github-release.yaml GitHub Actions caller workflow. */
|
|
16
14
|
export declare function createGithubReleaseWorkflow(repoType: RepoType): string;
|
|
17
|
-
/**
|
|
15
|
+
/** Generates the release.yaml GitHub Actions workflow. */
|
|
18
16
|
export declare function releaseWorkflow(repoType: RepoType): string;
|