@williamthorsen/release-kit 14.0.0 → 14.2.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 +173 -0
- package/.readyup/kits/default.js +64 -7
- package/.readyup/kits/npm-auto-publish.js +127 -75
- package/.readyup/manifest.json +22 -7
- package/CHANGELOG.md +47 -0
- package/README.md +18 -4
- package/dist/esm/assertReleasesOnNpm.d.ts +7 -0
- package/dist/esm/assertReleasesOnNpm.js +56 -0
- package/dist/esm/assertTaggedBaseline.d.ts +2 -2
- package/dist/esm/attachChangelogDiagnostics.d.ts +2 -2
- package/dist/esm/bin/release-kit.js +10 -6
- 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 +2 -3
- package/dist/esm/discoverWorkspaces.d.ts +3 -3
- package/dist/esm/enumerateReleaseWindows.d.ts +1 -1
- package/dist/esm/enumerateReleaseWindows.js +1 -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/hasPublishablePackage.d.ts +10 -0
- package/dist/esm/init/hasPublishablePackage.js +28 -0
- package/dist/esm/init/initCommand.js +23 -13
- package/dist/esm/init/scaffold.d.ts +6 -1
- package/dist/esm/init/scaffold.js +10 -7
- package/dist/esm/init/templates.js +1 -1
- package/dist/esm/init/updateTemplatesCommand.d.ts +2 -1
- package/dist/esm/init/updateTemplatesCommand.js +12 -6
- package/dist/esm/isPublishableManifest.d.ts +2 -0
- package/dist/esm/isPublishableManifest.js +5 -0
- package/dist/esm/loadConfig.d.ts +12 -12
- package/dist/esm/loadConfig.js +79 -30
- package/dist/esm/matchesTagPrefix.d.ts +2 -0
- package/dist/esm/matchesTagPrefix.js +4 -0
- package/dist/esm/npmRegistry.d.ts +35 -0
- package/dist/esm/npmRegistry.js +58 -0
- package/dist/esm/prepareCommand.d.ts +1 -1
- package/dist/esm/prepareCommand.js +50 -36
- 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 +60 -14
- 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/templates.js +1 -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 +80 -17
- package/docs/labels.md +30 -5
- package/docs/project-releases.md +42 -2
- package/docs/publishing.md +3 -3
- package/docs/releasing.md +12 -4
- package/docs/work-types.md +1 -1
- package/package.json +6 -6
- 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
|
@@ -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
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
2
|
import { basename } from 'node:path';
|
|
3
3
|
import { chainError } from '@williamthorsen/toolbelt.errors/candidate';
|
|
4
|
+
import { isPublishableManifest } from "./isPublishableManifest.js";
|
|
4
5
|
import { isRecord } from "./typeGuards.js";
|
|
5
6
|
export function deriveWorkspaceConfig(workspacePath) {
|
|
6
7
|
const dir = basename(workspacePath);
|
|
@@ -17,8 +18,7 @@ export function deriveWorkspaceConfig(workspacePath) {
|
|
|
17
18
|
throw new Error(`${packageJsonPath} is missing a 'name' field (required for tag derivation).`);
|
|
18
19
|
}
|
|
19
20
|
const unscopedName = stripNpmScope(name);
|
|
20
|
-
const
|
|
21
|
-
const isPublishable = privateField === undefined || privateField === false;
|
|
21
|
+
const isPublishable = isPublishableManifest(parsed);
|
|
22
22
|
return {
|
|
23
23
|
dir,
|
|
24
24
|
name,
|
|
@@ -26,7 +26,6 @@ export function deriveWorkspaceConfig(workspacePath) {
|
|
|
26
26
|
workspacePath,
|
|
27
27
|
isPublishable,
|
|
28
28
|
packageFiles: [packageJsonPath],
|
|
29
|
-
changelogPaths: [workspacePath],
|
|
30
29
|
paths: [`${workspacePath}/**`],
|
|
31
30
|
};
|
|
32
31
|
}
|
|
@@ -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;
|
|
@@ -22,7 +22,7 @@ export interface ReleaseWindow {
|
|
|
22
22
|
export interface EnumerateReleaseWindowsOptions {
|
|
23
23
|
/** Git pathspecs restricting the enumeration to commits that touch them. */
|
|
24
24
|
paths?: readonly string[];
|
|
25
|
-
/** Tag prefixes to match as a union; a matching tag name continues with a
|
|
25
|
+
/** Tag prefixes to match as a union; a matching tag name continues with a complete SemVer version. */
|
|
26
26
|
tagPrefixes: readonly string[];
|
|
27
27
|
/** The version that the unreleased window reports, such as the tag that the release being prepared will write. */
|
|
28
28
|
unreleasedTag: string;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { execFileSync } from 'node:child_process';
|
|
2
2
|
import { GIT_OUTPUT_LIMIT } from '@williamthorsen/nmr-core';
|
|
3
3
|
import { chainError } from '@williamthorsen/toolbelt.errors/candidate';
|
|
4
|
+
import { matchesTagPrefix } from "./matchesTagPrefix.js";
|
|
4
5
|
const FIELD_SEPARATOR = '\u{1F}';
|
|
5
6
|
const RECORD_SEPARATOR = '\u{1E}';
|
|
6
7
|
export function enumerateReleaseWindows(options) {
|
|
@@ -46,9 +47,6 @@ function claimAncestors(boundaries, parentsByHash) {
|
|
|
46
47
|
}
|
|
47
48
|
return windowIndexByHash;
|
|
48
49
|
}
|
|
49
|
-
function matchesTagPrefix(tagName, tagPrefixes) {
|
|
50
|
-
return tagPrefixes.some((prefix) => tagName.startsWith(prefix) && /^\d/.test(tagName.slice(prefix.length)));
|
|
51
|
-
}
|
|
52
50
|
function parseCommitRecord(record) {
|
|
53
51
|
const [hash, subject, message] = record.replace(/^\n+/, '').split(FIELD_SEPARATOR);
|
|
54
52
|
if (hash === undefined || subject === undefined || message === undefined || hash === '') {
|
|
@@ -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).
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reports whether the repo at `root` contains a package that can be published: a discovered workspace, or the root
|
|
3
|
+
* package of a single-package repo, whose `package.json` is not private.
|
|
4
|
+
*
|
|
5
|
+
* Returns `true` whenever the answer cannot be determined (an empty workspace, a failed discovery, or an unreadable
|
|
6
|
+
* manifest), so that the scaffolding commands keep the publish workflows in every repo that might need them.
|
|
7
|
+
*/
|
|
8
|
+
export declare function hasPublishablePackage(root?: string): boolean;
|
|
9
|
+
/** Formats the line that reports a workflow skipped because the repo does not contain any publishable package. */
|
|
10
|
+
export declare function formatPrivateWorkflowSkip(filePath: string): string;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { discoverWorkspaces } from "../discoverWorkspaces.js";
|
|
4
|
+
import { isPublishableManifest } from "../isPublishableManifest.js";
|
|
5
|
+
export function hasPublishablePackage(root = process.cwd()) {
|
|
6
|
+
let packageDirs;
|
|
7
|
+
try {
|
|
8
|
+
const workspace = discoverWorkspaces(root);
|
|
9
|
+
if (workspace.kind === 'empty')
|
|
10
|
+
return true;
|
|
11
|
+
packageDirs = workspace.kind === 'single-package' ? ['.'] : workspace.packageDirs;
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
return packageDirs.some((packageDir) => isPublishableAt(path.join(root, packageDir, 'package.json')));
|
|
17
|
+
}
|
|
18
|
+
export function formatPrivateWorkflowSkip(filePath) {
|
|
19
|
+
return `Skipping ${filePath}: every package is private`;
|
|
20
|
+
}
|
|
21
|
+
function isPublishableAt(manifestPath) {
|
|
22
|
+
try {
|
|
23
|
+
return isPublishableManifest(JSON.parse(readFileSync(manifestPath, 'utf8')));
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { printError, printStep, printSuccess, reportWriteResult, } from '@williamthorsen/nmr-core';
|
|
1
|
+
import { printError, printSkip, printStep, printSuccess, reportWriteResult, } from '@williamthorsen/nmr-core';
|
|
2
2
|
import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
3
3
|
import { hasPackageJson, isGitRepo, usesPnpm } from "./checks.js";
|
|
4
4
|
import { detectRepoType } from "./detectRepoType.js";
|
|
5
|
-
import {
|
|
5
|
+
import { formatPrivateWorkflowSkip, hasPublishablePackage } from "./hasPublishablePackage.js";
|
|
6
|
+
import { PUBLISHING_WORKFLOW_PATHS, scaffoldFiles } from "./scaffold.js";
|
|
6
7
|
export function checkEligibility(styles) {
|
|
7
8
|
printStep('Checking eligibility');
|
|
8
9
|
if (!runRequiredCheck('Git repository detected', isGitRepo(), styles))
|
|
@@ -37,10 +38,16 @@ export function initCommand({ dryRun, force, styles, withConfig }) {
|
|
|
37
38
|
return 1;
|
|
38
39
|
}
|
|
39
40
|
printSuccess(`Detected: ${repoType}`, styles.stdout);
|
|
41
|
+
const publishable = hasPublishablePackage();
|
|
40
42
|
printStep('Scaffolding files');
|
|
43
|
+
if (!publishable) {
|
|
44
|
+
for (const filePath of PUBLISHING_WORKFLOW_PATHS) {
|
|
45
|
+
printSkip(formatPrivateWorkflowSkip(filePath), styles.stdout);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
41
48
|
let results;
|
|
42
49
|
try {
|
|
43
|
-
results = scaffoldFiles({ repoType, dryRun, overwrite: force, withConfig });
|
|
50
|
+
results = scaffoldFiles({ repoType, dryRun, overwrite: force, publishable, withConfig });
|
|
44
51
|
}
|
|
45
52
|
catch (error) {
|
|
46
53
|
printError(`Failed to scaffold files: ${describeError(error)}`, styles.stderr);
|
|
@@ -53,18 +60,21 @@ export function initCommand({ dryRun, force, styles, withConfig }) {
|
|
|
53
60
|
return 1;
|
|
54
61
|
}
|
|
55
62
|
printStep('Next steps');
|
|
56
|
-
|
|
57
|
-
? '1. (Optional) Customize .config/release-kit.config.ts.'
|
|
58
|
-
: '1. (Optional) Run again with --with-config to scaffold config files.';
|
|
59
|
-
console.info(`
|
|
60
|
-
${configHint}
|
|
61
|
-
2. If this is a private repo, remove provenance: true from .github/workflows/publish.yaml.
|
|
62
|
-
3. Test by running: npx @williamthorsen/release-kit prepare --dry-run
|
|
63
|
-
4. Commit the generated files.
|
|
64
|
-
5. Register each package as a trusted publisher on npmjs.com.
|
|
65
|
-
`);
|
|
63
|
+
console.info(`\n${formatNextSteps({ publishable, withConfig })}\n`);
|
|
66
64
|
return 0;
|
|
67
65
|
}
|
|
66
|
+
function formatNextSteps({ publishable, withConfig }) {
|
|
67
|
+
const steps = [
|
|
68
|
+
withConfig
|
|
69
|
+
? '(Optional) Customize .config/release-kit.config.ts.'
|
|
70
|
+
: '(Optional) Run again with --with-config to scaffold config files.',
|
|
71
|
+
...(publishable ? ['If this is a private repo, remove provenance: true from .github/workflows/publish.yaml.'] : []),
|
|
72
|
+
'Test by running: npx @williamthorsen/release-kit prepare --dry-run',
|
|
73
|
+
'Commit the generated files.',
|
|
74
|
+
...(publishable ? ['Register each package as a trusted publisher on npmjs.com.'] : []),
|
|
75
|
+
];
|
|
76
|
+
return steps.map((step, index) => ` ${String(index + 1)}. ${step}`).join('\n');
|
|
77
|
+
}
|
|
68
78
|
function runRequiredCheck(label, result, styles) {
|
|
69
79
|
if (result.ok) {
|
|
70
80
|
printSuccess(label, styles.stdout);
|
|
@@ -4,8 +4,13 @@ interface ScaffoldOptions {
|
|
|
4
4
|
repoType: RepoType;
|
|
5
5
|
dryRun: boolean;
|
|
6
6
|
overwrite: boolean;
|
|
7
|
+
publishable: boolean;
|
|
7
8
|
withConfig: boolean;
|
|
8
9
|
}
|
|
10
|
+
export declare const CREATE_GITHUB_RELEASE_WORKFLOW_PATH = ".github/workflows/create-github-release.yaml";
|
|
11
|
+
export declare const PUBLISH_WORKFLOW_PATH = ".github/workflows/publish.yaml";
|
|
12
|
+
/** The workflows that act only on publishable packages, which a repo whose packages are all private does not get. */
|
|
13
|
+
export declare const PUBLISHING_WORKFLOW_PATHS: readonly [".github/workflows/create-github-release.yaml", ".github/workflows/publish.yaml"];
|
|
9
14
|
/** Scaffolds release-kit files for the target repo, returning a result for each file attempted. */
|
|
10
|
-
export declare function scaffoldFiles({ repoType, dryRun, overwrite, withConfig }: ScaffoldOptions): WriteResult[];
|
|
15
|
+
export declare function scaffoldFiles({ repoType, dryRun, overwrite, publishable, withConfig, }: ScaffoldOptions): WriteResult[];
|
|
11
16
|
export {};
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import { writeFileWithCheck } from '@williamthorsen/nmr-core';
|
|
2
2
|
import { createGithubReleaseWorkflow, publishWorkflow, releaseConfigScript, releaseWorkflow } from "./templates.js";
|
|
3
|
-
export
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
export const CREATE_GITHUB_RELEASE_WORKFLOW_PATH = '.github/workflows/create-github-release.yaml';
|
|
4
|
+
export const PUBLISH_WORKFLOW_PATH = '.github/workflows/publish.yaml';
|
|
5
|
+
export const PUBLISHING_WORKFLOW_PATHS = [CREATE_GITHUB_RELEASE_WORKFLOW_PATH, PUBLISH_WORKFLOW_PATH];
|
|
6
|
+
export function scaffoldFiles({ repoType, dryRun, overwrite, publishable, withConfig, }) {
|
|
7
|
+
const results = [];
|
|
8
|
+
if (publishable) {
|
|
9
|
+
results.push(writeFileWithCheck(CREATE_GITHUB_RELEASE_WORKFLOW_PATH, createGithubReleaseWorkflow(repoType), {
|
|
6
10
|
dryRun,
|
|
7
11
|
overwrite,
|
|
8
|
-
}),
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
];
|
|
12
|
+
}), writeFileWithCheck(PUBLISH_WORKFLOW_PATH, publishWorkflow(repoType), { dryRun, overwrite }));
|
|
13
|
+
}
|
|
14
|
+
results.push(writeFileWithCheck('.github/workflows/release.yaml', releaseWorkflow(repoType), { dryRun, overwrite }));
|
|
12
15
|
if (withConfig) {
|
|
13
16
|
results.push(writeFileWithCheck('.config/release-kit.config.ts', releaseConfigScript(repoType), { dryRun, overwrite }));
|
|
14
17
|
}
|
|
@@ -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:
|
|
@@ -7,7 +7,8 @@ interface UpdateTemplatesOptions {
|
|
|
7
7
|
* Runs the `release-kit update-templates` command and returns the process exit code.
|
|
8
8
|
*
|
|
9
9
|
* Brings each workflow that release-kit scaffolds up to date with the installed templates. The opt-in
|
|
10
|
-
* `sync-labels.yaml` is refreshed only when it exists,
|
|
10
|
+
* `sync-labels.yaml` is refreshed only when it exists, as are the publishing workflows in a repo whose packages are
|
|
11
|
+
* all private. Config files are never read or written.
|
|
11
12
|
*/
|
|
12
13
|
export declare function updateTemplatesCommand({ dryRun, styles }: UpdateTemplatesOptions): number;
|
|
13
14
|
export {};
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
-
import { printError, printStep, printSuccess, reportTemplateUpdate, updateManagedFile, } from '@williamthorsen/nmr-core';
|
|
2
|
+
import { printError, printSkip, printStep, printSuccess, reportTemplateUpdate, updateManagedFile, } from '@williamthorsen/nmr-core';
|
|
3
3
|
import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
4
4
|
import { syncLabelsWorkflow } from "../sync-labels/templates.js";
|
|
5
5
|
import { detectRepoType } from "./detectRepoType.js";
|
|
6
6
|
import { hasProvenance } from "./hasProvenance.js";
|
|
7
|
+
import { formatPrivateWorkflowSkip, hasPublishablePackage } from "./hasPublishablePackage.js";
|
|
7
8
|
import { checkEligibility } from "./initCommand.js";
|
|
9
|
+
import { CREATE_GITHUB_RELEASE_WORKFLOW_PATH, PUBLISH_WORKFLOW_PATH, PUBLISHING_WORKFLOW_PATHS } from "./scaffold.js";
|
|
8
10
|
import { createGithubReleaseWorkflow, publishWorkflow, releaseWorkflow } from "./templates.js";
|
|
9
|
-
const PUBLISH_WORKFLOW_PATH = '.github/workflows/publish.yaml';
|
|
10
11
|
const SYNC_LABELS_WORKFLOW_PATH = '.github/workflows/sync-labels.yaml';
|
|
11
12
|
export function updateTemplatesCommand({ dryRun, styles }) {
|
|
12
13
|
if (dryRun) {
|
|
@@ -35,7 +36,11 @@ export function updateTemplatesCommand({ dryRun, styles }) {
|
|
|
35
36
|
printStep('Updating workflows');
|
|
36
37
|
let results;
|
|
37
38
|
try {
|
|
38
|
-
|
|
39
|
+
const { files, skipped } = buildManagedFiles(repoType, hasPublishablePackage());
|
|
40
|
+
for (const filePath of skipped) {
|
|
41
|
+
printSkip(formatPrivateWorkflowSkip(filePath), styles.stdout);
|
|
42
|
+
}
|
|
43
|
+
results = files.map(([filePath, content]) => updateManagedFile(filePath, content, { dryRun }));
|
|
39
44
|
}
|
|
40
45
|
catch (error) {
|
|
41
46
|
printError(`Failed to render workflows: ${describeError(error)}`, styles.stderr);
|
|
@@ -46,16 +51,17 @@ export function updateTemplatesCommand({ dryRun, styles }) {
|
|
|
46
51
|
}
|
|
47
52
|
return results.some((result) => result.outcome === 'failed') ? 1 : 0;
|
|
48
53
|
}
|
|
49
|
-
function buildManagedFiles(repoType) {
|
|
54
|
+
function buildManagedFiles(repoType, publishable) {
|
|
55
|
+
const skipped = publishable ? [] : PUBLISHING_WORKFLOW_PATHS.filter((filePath) => !existsSync(filePath));
|
|
50
56
|
const files = [
|
|
51
|
-
[
|
|
57
|
+
[CREATE_GITHUB_RELEASE_WORKFLOW_PATH, createGithubReleaseWorkflow(repoType)],
|
|
52
58
|
[PUBLISH_WORKFLOW_PATH, publishWorkflow(repoType, { provenance: readProvenanceSetting() })],
|
|
53
59
|
['.github/workflows/release.yaml', releaseWorkflow(repoType)],
|
|
54
60
|
];
|
|
55
61
|
if (existsSync(SYNC_LABELS_WORKFLOW_PATH)) {
|
|
56
62
|
files.push([SYNC_LABELS_WORKFLOW_PATH, syncLabelsWorkflow()]);
|
|
57
63
|
}
|
|
58
|
-
return files;
|
|
64
|
+
return { files: files.filter(([filePath]) => !skipped.includes(filePath)), skipped };
|
|
59
65
|
}
|
|
60
66
|
function readProvenanceSetting() {
|
|
61
67
|
if (!existsSync(PUBLISH_WORKFLOW_PATH))
|
package/dist/esm/loadConfig.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { MonorepoReleaseConfig,
|
|
1
|
+
import type { MonorepoReleaseConfig, ReleaseKitConfig, ReleaseSettings, WorkTypeConfig } from './types.js';
|
|
2
2
|
/** Path of the root `package.json` consulted when validating a `project` block. */
|
|
3
3
|
export declare const ROOT_PACKAGE_JSON_PATH = "package.json";
|
|
4
4
|
/**
|
|
@@ -38,26 +38,26 @@ export interface RootPackageInfo {
|
|
|
38
38
|
* Resolves a final monorepo config from discovered workspaces and an optional user config overlay.
|
|
39
39
|
*
|
|
40
40
|
* Merging rules:
|
|
41
|
-
* -
|
|
42
|
-
*
|
|
41
|
+
* - Release targets: `resolveReleaseDeclaration` projects the `releases` block, or the `workspaces` overrides and the
|
|
42
|
+
* `project` block, onto the discovered workspaces. Excluded workspaces move to `excludedWorkspaces`, and the
|
|
43
|
+
* directories that `releases` declares resolve through `deriveDirectoryConfig` into `directories`, which must not
|
|
44
|
+
* share a version file with any other target.
|
|
43
45
|
* - `workTypes`: Shallow merge; consumer entries override or add to defaults by key.
|
|
44
46
|
* - `versionPatterns`: Consumer value replaces defaults entirely.
|
|
45
47
|
* - Pass-through fields (see `applyOptionalPassthroughFields`): Consumer value wins.
|
|
46
|
-
* - `project`: Present iff
|
|
47
|
-
* `
|
|
48
|
-
*
|
|
49
|
-
* throws otherwise. The resolved prefix is included in the strict-prefix collision check
|
|
48
|
+
* - `project`: Present iff the config declares a composite. Resolves `tagPrefix` to `DEFAULT_PROJECT_TAG_PREFIX` and
|
|
49
|
+
* `paths` to the union of its members' `paths` when omitted. Requires `rootPackage` to be passed and to contain a
|
|
50
|
+
* valid `version` field; throws otherwise. The resolved prefix is included in the strict-prefix collision check
|
|
50
51
|
* across all workspace and retired-package prefixes.
|
|
51
52
|
*/
|
|
52
53
|
export declare function mergeMonorepoConfig(discoveredPaths: string[], userConfig: ReleaseKitConfig | undefined, rootPackage?: RootPackageInfo): MonorepoReleaseConfig;
|
|
53
54
|
/**
|
|
54
|
-
* Resolves a
|
|
55
|
+
* Resolves the release settings of a single-package repo from an optional user config overlay.
|
|
55
56
|
*
|
|
56
|
-
* Rejects a configured `project` block:
|
|
57
|
-
*
|
|
58
|
-
* single-package repo.
|
|
57
|
+
* Rejects a configured `project` or `releases` block: Both declare the targets of a monorepo, and a single-package
|
|
58
|
+
* repo has one target, its root package.
|
|
59
59
|
*/
|
|
60
|
-
export declare function mergeSinglePackageConfig(userConfig: ReleaseKitConfig | undefined):
|
|
60
|
+
export declare function mergeSinglePackageConfig(userConfig: ReleaseKitConfig | undefined): ReleaseSettings;
|
|
61
61
|
/**
|
|
62
62
|
* Merges consumer work-type overrides onto `DEFAULT_WORK_TYPES`.
|
|
63
63
|
*
|