@williamthorsen/release-kit 15.0.3 → 16.1.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 +93 -0
- package/.readyup/kits/default.js +14 -14
- package/.readyup/kits/npm-auto-publish.js +4 -4
- package/.readyup/manifest.json +13 -13
- package/CHANGELOG.md +27 -0
- package/README.md +12 -3
- package/dist/esm/applyChangelogOverrides.js +6 -6
- package/dist/esm/assertReleasesOnNpm.js +11 -11
- package/dist/esm/assertTaggedBaseline.js +5 -5
- package/dist/esm/assertTarballsComplete.d.ts +9 -0
- package/dist/esm/assertTarballsComplete.js +52 -0
- package/dist/esm/bin/release-kit.js +16 -16
- package/dist/esm/buildChangelogEntries.d.ts +19 -19
- package/dist/esm/buildChangelogEntries.js +40 -36
- package/dist/esm/buildChangelogItem.js +24 -24
- package/dist/esm/buildDependencyGraph.js +23 -23
- package/dist/esm/buildReleasedResult.js +21 -19
- package/dist/esm/buildSyntheticChangelogEntry.js +2 -2
- package/dist/esm/bumpVersion.js +8 -8
- package/dist/esm/changelogArchive.d.ts +1 -1
- package/dist/esm/changelogArchive.js +2 -2
- package/dist/esm/changelogJsonFile.js +5 -5
- package/dist/esm/changelogJsonUtils.js +3 -3
- package/dist/esm/changelogOverrides.d.ts +5 -5
- package/dist/esm/changelogOverrides.js +23 -23
- package/dist/esm/classifyChangelogCommit.js +4 -4
- package/dist/esm/commitCommand.js +3 -3
- package/dist/esm/compareVersions.js +13 -13
- package/dist/esm/configSchema.d.ts +3 -2
- package/dist/esm/configSchema.js +2 -1
- package/dist/esm/createGithubRelease.d.ts +4 -4
- package/dist/esm/createGithubRelease.js +10 -10
- package/dist/esm/createGithubReleaseCommand.js +9 -9
- package/dist/esm/createTags.d.ts +2 -2
- package/dist/esm/createTags.js +8 -8
- package/dist/esm/decideDirectReleases.d.ts +5 -5
- package/dist/esm/decideDirectReleases.js +25 -25
- package/dist/esm/decideRelease.d.ts +1 -1
- package/dist/esm/decideRelease.js +2 -2
- package/dist/esm/deriveDirectoryConfig.d.ts +1 -1
- package/dist/esm/deriveDirectoryConfig.js +18 -18
- package/dist/esm/deriveWorkspaceConfig.js +4 -4
- package/dist/esm/detectPackageManager.d.ts +2 -0
- package/dist/esm/detectPackageManager.js +8 -5
- package/dist/esm/detectUndeclaredTagPrefixes.js +6 -6
- package/dist/esm/enumerateReleaseWindows.d.ts +1 -1
- package/dist/esm/enumerateReleaseWindows.js +7 -7
- package/dist/esm/findMissingManifestPaths.d.ts +14 -0
- package/dist/esm/findMissingManifestPaths.js +90 -0
- package/dist/esm/findTransitiveDependents.js +5 -5
- package/dist/esm/findUnroutedEntryScopes.js +11 -11
- package/dist/esm/format.d.ts +1 -1
- package/dist/esm/format.js +1 -1
- package/dist/esm/formatPrepareFiles.d.ts +3 -3
- package/dist/esm/formatPrepareFiles.js +10 -10
- package/dist/esm/formatPrepareWarnings.js +6 -6
- package/dist/esm/hasPrettierConfig.js +2 -2
- package/dist/esm/init/checks.js +5 -3
- package/dist/esm/init/detectRepoType.js +3 -3
- package/dist/esm/init/initCommand.d.ts +4 -4
- package/dist/esm/init/initCommand.js +7 -7
- package/dist/esm/init/parseJsonRecord.d.ts +1 -1
- package/dist/esm/init/parseJsonRecord.js +4 -4
- package/dist/esm/init/scaffold.d.ts +4 -4
- package/dist/esm/init/scaffold.js +15 -15
- package/dist/esm/init/templates.d.ts +10 -10
- package/dist/esm/init/templates.js +5 -5
- package/dist/esm/init/updateTemplatesCommand.d.ts +2 -2
- package/dist/esm/init/updateTemplatesCommand.js +14 -12
- package/dist/esm/injectReleaseNotesIntoReadme.d.ts +1 -1
- package/dist/esm/injectReleaseNotesIntoReadme.js +6 -6
- package/dist/esm/injectSection.js +8 -8
- package/dist/esm/loadChangelogOverrides.d.ts +1 -1
- package/dist/esm/loadChangelogOverrides.js +9 -9
- package/dist/esm/loadConfig.d.ts +3 -3
- package/dist/esm/loadConfig.js +10 -10
- package/dist/esm/loadValidatedConfig.js +4 -4
- package/dist/esm/mergeConfig.d.ts +1 -1
- package/dist/esm/mergeConfig.js +27 -27
- package/dist/esm/monorepoConfigAssertions.js +15 -15
- package/dist/esm/npmRegistry.js +4 -4
- package/dist/esm/packFromCleanCheckout.d.ts +11 -0
- package/dist/esm/packFromCleanCheckout.js +81 -0
- package/dist/esm/packsPath.js +3 -3
- package/dist/esm/parseChangeRecordBlock.js +33 -33
- package/dist/esm/parseCommitMessage.js +5 -5
- package/dist/esm/planChangelogFiles.js +8 -8
- package/dist/esm/planReleaseNotesPreviews.js +10 -10
- package/dist/esm/planVersionBump.js +24 -16
- package/dist/esm/prepareArgs.d.ts +9 -9
- package/dist/esm/prepareArgs.js +11 -11
- package/dist/esm/prepareCommand.js +27 -24
- package/dist/esm/prepareComposite.d.ts +4 -4
- package/dist/esm/prepareComposite.js +16 -16
- package/dist/esm/{releasePrepareTargets.d.ts → prepareReleaseTargets.d.ts} +9 -9
- package/dist/esm/{releasePrepareTargets.js → prepareReleaseTargets.js} +40 -40
- package/dist/esm/prepareRunContext.d.ts +1 -1
- package/dist/esm/prepareRunContext.js +5 -5
- package/dist/esm/prepareTarget.d.ts +3 -3
- package/dist/esm/prepareTarget.js +41 -39
- package/dist/esm/previewTagPrefixes.js +19 -19
- package/dist/esm/propagateBumps.js +8 -8
- package/dist/esm/publish.d.ts +3 -3
- package/dist/esm/publish.js +6 -11
- package/dist/esm/publishCommand.d.ts +3 -3
- package/dist/esm/publishCommand.js +28 -20
- package/dist/esm/pushCommand.js +8 -8
- package/dist/esm/pushRelease.d.ts +1 -1
- package/dist/esm/pushRelease.js +2 -2
- package/dist/esm/readChangelogSections.d.ts +1 -1
- package/dist/esm/readChangelogSections.js +16 -16
- package/dist/esm/releasePlan.js +25 -25
- package/dist/esm/renderChangelogMarkdown.d.ts +4 -4
- package/dist/esm/renderChangelogMarkdown.js +18 -15
- package/dist/esm/renderReleaseNotes.d.ts +1 -1
- package/dist/esm/renderReleaseNotes.js +16 -16
- package/dist/esm/reportPrepare.js +34 -34
- package/dist/esm/resolveCommandTags.js +9 -9
- package/dist/esm/resolveReleaseDeclaration.d.ts +2 -2
- package/dist/esm/resolveReleaseDeclaration.js +22 -22
- package/dist/esm/resolveReleaseTags.js +17 -15
- package/dist/esm/resolveReleaseTargets.js +2 -2
- package/dist/esm/showTagPrefixesCommand.js +4 -4
- package/dist/esm/sync-labels/generateCommand.d.ts +3 -3
- package/dist/esm/sync-labels/generateCommand.js +10 -10
- package/dist/esm/sync-labels/initCommand.d.ts +3 -3
- package/dist/esm/sync-labels/initCommand.js +15 -15
- package/dist/esm/sync-labels/presets.js +3 -3
- package/dist/esm/sync-labels/resolveLabels.js +13 -13
- package/dist/esm/sync-labels/templates.d.ts +2 -2
- package/dist/esm/sync-labels/templates.js +1 -1
- package/dist/esm/tagCommand.js +4 -4
- package/dist/esm/topologicalSort.d.ts +2 -2
- package/dist/esm/topologicalSort.js +7 -7
- package/dist/esm/types.d.ts +3 -3
- package/dist/esm/validateAllChangelogOverrides.js +9 -9
- package/dist/esm/validateConfig.d.ts +1 -1
- package/dist/esm/validateConfig.js +60 -50
- package/dist/esm/validateOverridesCommand.js +2 -2
- package/docs/changelogs.md +5 -5
- package/docs/configuration.md +16 -15
- package/docs/publishing.md +4 -0
- package/docs/releasing.md +6 -0
- package/docs/work-types.md +1 -1
- package/package.json +2 -2
- package/presets/labels/common.yaml +3 -0
|
@@ -4,3 +4,5 @@ export type PackageManager = 'npm' | 'pnpm' | 'yarn' | 'yarn-berry';
|
|
|
4
4
|
* and otherwise returns `npm`. Yarn v2+ is `'yarn-berry'`, to distinguish it from Yarn Classic.
|
|
5
5
|
*/
|
|
6
6
|
export declare function detectPackageManager(): PackageManager;
|
|
7
|
+
/** Maps a `PackageManager` value to its CLI executable name. */
|
|
8
|
+
export declare function resolveExecutable(packageManager: PackageManager): string;
|
|
@@ -5,9 +5,9 @@ export function detectPackageManager() {
|
|
|
5
5
|
const packageJsonPath = join(process.cwd(), 'package.json');
|
|
6
6
|
try {
|
|
7
7
|
const content = readFileSync(packageJsonPath, 'utf8');
|
|
8
|
-
const
|
|
9
|
-
if (isRecord(
|
|
10
|
-
const [name, version] =
|
|
8
|
+
const parsedJson = JSON.parse(content);
|
|
9
|
+
if (isRecord(parsedJson) && typeof parsedJson['packageManager'] === 'string') {
|
|
10
|
+
const [name, version] = parsedJson['packageManager'].split('@', 2);
|
|
11
11
|
if (name === 'pnpm' || name === 'npm') {
|
|
12
12
|
return name;
|
|
13
13
|
}
|
|
@@ -20,12 +20,15 @@ export function detectPackageManager() {
|
|
|
20
20
|
}
|
|
21
21
|
return detectFromLockfile();
|
|
22
22
|
}
|
|
23
|
+
export function resolveExecutable(packageManager) {
|
|
24
|
+
return packageManager === 'yarn-berry' ? 'yarn' : packageManager;
|
|
25
|
+
}
|
|
23
26
|
function isYarnBerry(version) {
|
|
24
27
|
if (version === undefined) {
|
|
25
28
|
return false;
|
|
26
29
|
}
|
|
27
|
-
const
|
|
28
|
-
return !Number.isNaN(
|
|
30
|
+
const majorVersion = Number.parseInt(version, 10);
|
|
31
|
+
return !Number.isNaN(majorVersion) && majorVersion >= 2;
|
|
29
32
|
}
|
|
30
33
|
function detectFromLockfile() {
|
|
31
34
|
const cwd = process.cwd();
|
|
@@ -3,7 +3,7 @@ import { GIT_OUTPUT_LIMIT_BYTES } from '@williamthorsen/nmr-core';
|
|
|
3
3
|
const EXAMPLE_TAG_LIMIT = 3;
|
|
4
4
|
const CANDIDATE_TAG_PATTERN = /^(?<prefix>[a-z][a-z0-9-]*-v)\d+\.\d+\.\d+(?:-[A-Za-z0-9.-]+)?$/;
|
|
5
5
|
export function detectUndeclaredTagPrefixes(knownPrefixes) {
|
|
6
|
-
const
|
|
6
|
+
const knownPrefixSet = new Set(knownPrefixes);
|
|
7
7
|
let rawOutput;
|
|
8
8
|
try {
|
|
9
9
|
rawOutput = execFileSync('git', ['tag', '--list'], {
|
|
@@ -15,7 +15,7 @@ export function detectUndeclaredTagPrefixes(knownPrefixes) {
|
|
|
15
15
|
catch {
|
|
16
16
|
return [];
|
|
17
17
|
}
|
|
18
|
-
const
|
|
18
|
+
const tagsByPrefix = new Map();
|
|
19
19
|
for (const line of rawOutput.split('\n')) {
|
|
20
20
|
const tag = line.trim();
|
|
21
21
|
if (tag === '')
|
|
@@ -24,17 +24,17 @@ export function detectUndeclaredTagPrefixes(knownPrefixes) {
|
|
|
24
24
|
if (match === null)
|
|
25
25
|
continue;
|
|
26
26
|
const prefix = match.groups?.['prefix'] ?? '';
|
|
27
|
-
if (prefix === '' ||
|
|
27
|
+
if (prefix === '' || knownPrefixSet.has(prefix))
|
|
28
28
|
continue;
|
|
29
|
-
let tags =
|
|
29
|
+
let tags = tagsByPrefix.get(prefix);
|
|
30
30
|
if (tags === undefined) {
|
|
31
31
|
tags = [];
|
|
32
|
-
|
|
32
|
+
tagsByPrefix.set(prefix, tags);
|
|
33
33
|
}
|
|
34
34
|
tags.push(tag);
|
|
35
35
|
}
|
|
36
36
|
const results = [];
|
|
37
|
-
for (const [prefix, tags] of
|
|
37
|
+
for (const [prefix, tags] of tagsByPrefix) {
|
|
38
38
|
results.push({
|
|
39
39
|
prefix,
|
|
40
40
|
tagCount: tags.length,
|
|
@@ -14,7 +14,7 @@ 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;
|
|
16
16
|
/** Unix seconds: the tag's creation date for a released window, the clock for the unreleased one. */
|
|
17
|
-
|
|
17
|
+
timestampSec: number;
|
|
18
18
|
/** Oldest first. */
|
|
19
19
|
commits: RawCommit[];
|
|
20
20
|
}
|
|
@@ -11,7 +11,7 @@ export function enumerateReleaseWindows(options) {
|
|
|
11
11
|
}
|
|
12
12
|
const unreleasedWindow = {
|
|
13
13
|
version: unreleasedTag,
|
|
14
|
-
|
|
14
|
+
timestampSec: Math.floor(now() / 1000),
|
|
15
15
|
commits: [],
|
|
16
16
|
};
|
|
17
17
|
const ancestry = readAncestry();
|
|
@@ -21,7 +21,7 @@ export function enumerateReleaseWindows(options) {
|
|
|
21
21
|
const boundaries = readTagBoundaries(tagPrefixes, ancestry.positionByHash);
|
|
22
22
|
const releasedWindows = boundaries.map((boundary) => ({
|
|
23
23
|
version: boundary.tag,
|
|
24
|
-
|
|
24
|
+
timestampSec: boundary.timestampSec,
|
|
25
25
|
commits: [],
|
|
26
26
|
}));
|
|
27
27
|
const windowIndexByHash = claimAncestors(boundaries, ancestry.parentsByHash);
|
|
@@ -35,14 +35,14 @@ export function enumerateReleaseWindows(options) {
|
|
|
35
35
|
function claimAncestors(boundaries, parentsByHash) {
|
|
36
36
|
const windowIndexByHash = new Map();
|
|
37
37
|
for (const [windowIndex, boundary] of boundaries.entries()) {
|
|
38
|
-
const
|
|
39
|
-
while (
|
|
40
|
-
const hash =
|
|
38
|
+
const pendingHashes = [boundary.hash];
|
|
39
|
+
while (pendingHashes.length > 0) {
|
|
40
|
+
const hash = pendingHashes.pop();
|
|
41
41
|
if (hash === undefined || windowIndexByHash.has(hash)) {
|
|
42
42
|
continue;
|
|
43
43
|
}
|
|
44
44
|
windowIndexByHash.set(hash, windowIndex);
|
|
45
|
-
|
|
45
|
+
pendingHashes.push(...(parentsByHash.get(hash) ?? []));
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
48
|
return windowIndexByHash;
|
|
@@ -103,7 +103,7 @@ function readTagBoundaries(tagPrefixes, positionByHash) {
|
|
|
103
103
|
const hash = dereferencedName || objectName;
|
|
104
104
|
const position = positionByHash.get(hash);
|
|
105
105
|
if (position !== undefined) {
|
|
106
|
-
boundaries.push({ hash, position, tag,
|
|
106
|
+
boundaries.push({ hash, position, tag, timestampSec: Number(creatorDate) });
|
|
107
107
|
}
|
|
108
108
|
}
|
|
109
109
|
return boundaries.toSorted((left, right) => right.position - left.position);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** A path that a manifest names but the tarball lacks, with the manifest location that names it. */
|
|
2
|
+
export interface MissingManifestPath {
|
|
3
|
+
location: string;
|
|
4
|
+
path: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Lists each path named by the manifest's `main`, `types`, `bin`, and `exports` that `packedFiles` lacks.
|
|
8
|
+
*
|
|
9
|
+
* `packedFiles` holds tarball entries relative to the package root. `main` resolves as Node resolves it: the path
|
|
10
|
+
* itself, then with `.js` or `.json` appended, then as a directory containing `index.js`. An `exports` target
|
|
11
|
+
* containing `*` passes when any entry matches it, with `*` spanning `/` as in Node's subpath patterns. A target under
|
|
12
|
+
* `source` or under one of `devExportConditions` is exempt, at any depth.
|
|
13
|
+
*/
|
|
14
|
+
export declare function findMissingManifestPaths(manifest: Record<string, unknown>, packedFiles: readonly string[], devExportConditions: readonly string[]): MissingManifestPath[];
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { isRecord } from "./typeGuards.js";
|
|
2
|
+
const BUILT_IN_DEV_CONDITION = 'source';
|
|
3
|
+
export function findMissingManifestPaths(manifest, packedFiles, devExportConditions) {
|
|
4
|
+
const packedSet = new Set(packedFiles);
|
|
5
|
+
const exemptConditions = new Set([BUILT_IN_DEV_CONDITION, ...devExportConditions]);
|
|
6
|
+
const missingPaths = [];
|
|
7
|
+
const main = manifest['main'];
|
|
8
|
+
if (typeof main === 'string' && listMainCandidates(main).every((candidate) => !packedSet.has(candidate))) {
|
|
9
|
+
missingPaths.push({ location: 'main', path: main });
|
|
10
|
+
}
|
|
11
|
+
const types = manifest['types'];
|
|
12
|
+
if (typeof types === 'string' && !packedSet.has(normalizePath(types))) {
|
|
13
|
+
missingPaths.push({ location: 'types', path: types });
|
|
14
|
+
}
|
|
15
|
+
const binTargets = listBinTargets(manifest['bin']);
|
|
16
|
+
for (const { location, path } of binTargets) {
|
|
17
|
+
if (!packedSet.has(normalizePath(path))) {
|
|
18
|
+
missingPaths.push({ location, path });
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
const exportTargets = [];
|
|
22
|
+
collectExportTargets(manifest['exports'], 'exports', exemptConditions, exportTargets);
|
|
23
|
+
for (const target of exportTargets) {
|
|
24
|
+
if (!isTargetPacked(target.path, packedFiles, packedSet)) {
|
|
25
|
+
missingPaths.push(target);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return missingPaths;
|
|
29
|
+
}
|
|
30
|
+
function collectExportTargets(value, location, exemptConditions, targets) {
|
|
31
|
+
if (typeof value === 'string') {
|
|
32
|
+
targets.push({ location, path: value });
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (Array.isArray(value)) {
|
|
36
|
+
for (const [index, element] of value.entries()) {
|
|
37
|
+
collectExportTargets(element, `${location}[${index}]`, exemptConditions, targets);
|
|
38
|
+
}
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
if (!isRecord(value))
|
|
42
|
+
return;
|
|
43
|
+
for (const [key, nestedValue] of Object.entries(value)) {
|
|
44
|
+
if (key.startsWith('.')) {
|
|
45
|
+
collectExportTargets(nestedValue, `${location}[${JSON.stringify(key)}]`, exemptConditions, targets);
|
|
46
|
+
}
|
|
47
|
+
else if (!exemptConditions.has(key)) {
|
|
48
|
+
collectExportTargets(nestedValue, `${location}.${key}`, exemptConditions, targets);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function isTargetPacked(target, packedFiles, packedSet) {
|
|
53
|
+
const normalizedTarget = normalizePath(target);
|
|
54
|
+
if (!normalizedTarget.includes('*')) {
|
|
55
|
+
return packedSet.has(normalizedTarget);
|
|
56
|
+
}
|
|
57
|
+
const pattern = new RegExp(`^${normalizedTarget
|
|
58
|
+
.split('*')
|
|
59
|
+
.map((part) => RegExp.escape(part))
|
|
60
|
+
.join('.*')}$`);
|
|
61
|
+
return packedFiles.some((file) => pattern.test(file));
|
|
62
|
+
}
|
|
63
|
+
function listBinTargets(bin) {
|
|
64
|
+
if (typeof bin === 'string') {
|
|
65
|
+
return [{ location: 'bin', path: bin }];
|
|
66
|
+
}
|
|
67
|
+
if (!isRecord(bin))
|
|
68
|
+
return [];
|
|
69
|
+
const targets = [];
|
|
70
|
+
for (const [command, path] of Object.entries(bin)) {
|
|
71
|
+
if (typeof path === 'string') {
|
|
72
|
+
targets.push({ location: `bin.${command}`, path });
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return targets;
|
|
76
|
+
}
|
|
77
|
+
function listMainCandidates(main) {
|
|
78
|
+
const normalizedMain = normalizePath(main);
|
|
79
|
+
const directoryPrefix = normalizedMain === '' ? '' : `${normalizedMain}/`;
|
|
80
|
+
const indexCandidates = [`${directoryPrefix}index.js`, `${directoryPrefix}index.json`];
|
|
81
|
+
if (normalizedMain === '')
|
|
82
|
+
return indexCandidates;
|
|
83
|
+
return [normalizedMain, `${normalizedMain}.js`, `${normalizedMain}.json`, ...indexCandidates];
|
|
84
|
+
}
|
|
85
|
+
function normalizePath(path) {
|
|
86
|
+
return path
|
|
87
|
+
.replace(/^\.?\/+/, '')
|
|
88
|
+
.replace(/^\.$/, '')
|
|
89
|
+
.replace(/\/+$/, '');
|
|
90
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export function findTransitiveDependents(graph, startDirs) {
|
|
2
|
-
const
|
|
3
|
-
const queue = [...
|
|
2
|
+
const foundDirs = new Set(startDirs);
|
|
3
|
+
const queue = [...foundDirs];
|
|
4
4
|
while (queue.length > 0) {
|
|
5
5
|
const dir = queue.shift();
|
|
6
6
|
if (dir === undefined)
|
|
@@ -10,11 +10,11 @@ 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 (
|
|
13
|
+
if (foundDirs.has(dependent.id))
|
|
14
14
|
continue;
|
|
15
|
-
|
|
15
|
+
foundDirs.add(dependent.id);
|
|
16
16
|
queue.push(dependent.id);
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
|
-
return
|
|
19
|
+
return foundDirs;
|
|
20
20
|
}
|
|
@@ -28,11 +28,11 @@ function findCommitUnroutedScopes(commit, windowDirs, unreadDirs, isRootReserved
|
|
|
28
28
|
for (const [index, entry] of reading.entries.entries()) {
|
|
29
29
|
const scopes = new Set(entry.scopes);
|
|
30
30
|
for (const scope of scopes) {
|
|
31
|
-
const
|
|
32
|
-
const isRouted =
|
|
33
|
-
windowDirs.has(
|
|
34
|
-
unreadDirs.has(
|
|
35
|
-
(
|
|
31
|
+
const resolvedScope = scopeAliases[scope] ?? scope;
|
|
32
|
+
const isRouted = resolvedScope === '*' ||
|
|
33
|
+
windowDirs.has(resolvedScope) ||
|
|
34
|
+
unreadDirs.has(resolvedScope) ||
|
|
35
|
+
(resolvedScope === 'root' && isRootReserved);
|
|
36
36
|
if (!isRouted) {
|
|
37
37
|
findings.push({ commitHash: commit.hash, commitSubject: commit.subject, entryPosition: index + 1, scope });
|
|
38
38
|
}
|
|
@@ -41,17 +41,17 @@ function findCommitUnroutedScopes(commit, windowDirs, unreadDirs, isRootReserved
|
|
|
41
41
|
return findings;
|
|
42
42
|
}
|
|
43
43
|
function indexCommits(windows) {
|
|
44
|
-
const
|
|
44
|
+
const commitsByHash = new Map();
|
|
45
45
|
for (const window of windows) {
|
|
46
46
|
for (const commit of window.commits) {
|
|
47
|
-
const
|
|
48
|
-
if (
|
|
49
|
-
|
|
47
|
+
const indexedEntry = commitsByHash.get(commit.hash);
|
|
48
|
+
if (indexedEntry === undefined) {
|
|
49
|
+
commitsByHash.set(commit.hash, { commit, windowDirs: new Set([window.dir]) });
|
|
50
50
|
}
|
|
51
51
|
else {
|
|
52
|
-
|
|
52
|
+
indexedEntry.windowDirs.add(window.dir);
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
|
-
return
|
|
56
|
+
return commitsByHash;
|
|
57
57
|
}
|
package/dist/esm/format.d.ts
CHANGED
|
@@ -3,4 +3,4 @@ export declare function bold(text: string, stream?: NodeJS.WritableStream): stri
|
|
|
3
3
|
/** Styles text dim, returning it unstyled for a stream that cannot render color. */
|
|
4
4
|
export declare function dim(text: string, stream?: NodeJS.WritableStream): string;
|
|
5
5
|
/** Renders a section header for separating workspaces in CLI output. */
|
|
6
|
-
export declare function
|
|
6
|
+
export declare function renderSectionHeader(name: string): string;
|
package/dist/esm/format.js
CHANGED
|
@@ -5,6 +5,6 @@ export function bold(text, stream = process.stdout) {
|
|
|
5
5
|
export function dim(text, stream = process.stdout) {
|
|
6
6
|
return styleText('dim', text, { stream });
|
|
7
7
|
}
|
|
8
|
-
export function
|
|
8
|
+
export function renderSectionHeader(name) {
|
|
9
9
|
return `━━━ ${name} ━━━`;
|
|
10
10
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { type OutputStyle } from '@williamthorsen/nmr-core';
|
|
2
2
|
import type { ReleasedPackageResult } from './types.js';
|
|
3
3
|
/** Appends bump file detail lines. */
|
|
4
|
-
export declare function formatBumpFiles(lines: string[], workspace: Pick<ReleasedPackageResult, 'bumpedFiles'>,
|
|
4
|
+
export declare function formatBumpFiles(lines: string[], workspace: Pick<ReleasedPackageResult, 'bumpedFiles'>, isDryRun: boolean, indent?: string): void;
|
|
5
5
|
/**
|
|
6
6
|
* Appends a line per generated changelog file, each followed by the versions kept from the existing changelog and a
|
|
7
7
|
* warning naming the sections dropped for lacking a version, then a line per removed changelog archive file.
|
|
8
8
|
*/
|
|
9
|
-
export declare function formatChangelogFiles(lines: string[], release: Pick<ReleasedPackageResult, 'changelogFiles' | 'changelogPreservation' | 'removedChangelogFiles'>,
|
|
9
|
+
export declare function formatChangelogFiles(lines: string[], release: Pick<ReleasedPackageResult, 'changelogFiles' | 'changelogPreservation' | 'removedChangelogFiles'>, isDryRun: boolean, style: OutputStyle, indent?: string): void;
|
|
10
10
|
/** Appends release-notes preview file lines, which are present only under `--with-release-notes`. */
|
|
11
|
-
export declare function formatPreviewFiles(lines: string[], release: Pick<ReleasedPackageResult, 'previewFiles'>,
|
|
11
|
+
export declare function formatPreviewFiles(lines: string[], release: Pick<ReleasedPackageResult, 'previewFiles'>, isDryRun: boolean, indent?: string): void;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { formatStatusLine } from '@williamthorsen/nmr-core';
|
|
2
2
|
import { dim } from "./format.js";
|
|
3
|
-
export function formatBumpFiles(lines, workspace,
|
|
3
|
+
export function formatBumpFiles(lines, workspace, isDryRun, indent = '') {
|
|
4
4
|
for (const file of workspace.bumpedFiles) {
|
|
5
|
-
if (
|
|
5
|
+
if (isDryRun) {
|
|
6
6
|
lines.push(dim(`${indent} [dry-run] Would bump ${file}`));
|
|
7
7
|
}
|
|
8
8
|
else {
|
|
@@ -10,9 +10,9 @@ export function formatBumpFiles(lines, workspace, dryRun, indent = '') {
|
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
12
|
}
|
|
13
|
-
export function formatChangelogFiles(lines, release,
|
|
13
|
+
export function formatChangelogFiles(lines, release, isDryRun, style, indent = '') {
|
|
14
14
|
for (const file of release.changelogFiles) {
|
|
15
|
-
if (
|
|
15
|
+
if (isDryRun) {
|
|
16
16
|
lines.push(dim(`${indent} [dry-run] Would generate changelog: ${file}`));
|
|
17
17
|
}
|
|
18
18
|
else {
|
|
@@ -25,16 +25,16 @@ export function formatChangelogFiles(lines, release, dryRun, style, indent = '')
|
|
|
25
25
|
if (preservation.preservedVersions.length > 0) {
|
|
26
26
|
lines.push(dim(`${indent} Kept from the existing changelog: ${preservation.preservedVersions.join(', ')}`));
|
|
27
27
|
}
|
|
28
|
-
const
|
|
29
|
-
if (
|
|
28
|
+
const droppedHeadings = preservation.droppedUnversionedHeadings;
|
|
29
|
+
if (droppedHeadings.length === 0) {
|
|
30
30
|
continue;
|
|
31
31
|
}
|
|
32
|
-
const message = `Dropped ${
|
|
32
|
+
const message = `Dropped ${droppedHeadings.length} section${droppedHeadings.length === 1 ? '' : 's'} without a version: ${droppedHeadings.join(', ')}`;
|
|
33
33
|
lines.push(`${indent} ${formatStatusLine(style, 'warning', message)}`);
|
|
34
34
|
}
|
|
35
35
|
const removedFiles = release.removedChangelogFiles ?? [];
|
|
36
36
|
for (const file of removedFiles) {
|
|
37
|
-
if (
|
|
37
|
+
if (isDryRun) {
|
|
38
38
|
lines.push(dim(`${indent} [dry-run] Would remove changelog: ${file}`));
|
|
39
39
|
}
|
|
40
40
|
else {
|
|
@@ -42,10 +42,10 @@ export function formatChangelogFiles(lines, release, dryRun, style, indent = '')
|
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
|
-
export function formatPreviewFiles(lines, release,
|
|
45
|
+
export function formatPreviewFiles(lines, release, isDryRun, indent = '') {
|
|
46
46
|
const previewFiles = release.previewFiles ?? [];
|
|
47
47
|
for (const file of previewFiles) {
|
|
48
|
-
if (
|
|
48
|
+
if (isDryRun) {
|
|
49
49
|
lines.push(dim(`${indent} [dry-run] Would write ${file}`));
|
|
50
50
|
}
|
|
51
51
|
else {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { formatStatusLine, truncateToWidth } from '@williamthorsen/nmr-core';
|
|
2
|
-
const
|
|
2
|
+
const SUBJECT_BUDGET_COLUMNS = 72;
|
|
3
3
|
export function formatChangeRecordWarnings(lines, release, style, indent = '') {
|
|
4
4
|
const { malformedBlocks = [], undeclaredEntryTypes = [], unroutedEntryScopes = [] } = release;
|
|
5
5
|
if (malformedBlocks.length > 0) {
|
|
@@ -45,13 +45,13 @@ export function formatPolicyViolations(lines, violations, style, indent = '') {
|
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
export function formatUnparseableWarning(lines, release, style, indent = '') {
|
|
48
|
-
const
|
|
49
|
-
if (
|
|
48
|
+
const unparseableCommits = release.unparseableCommits;
|
|
49
|
+
if (unparseableCommits === undefined || unparseableCommits.length === 0) {
|
|
50
50
|
return;
|
|
51
51
|
}
|
|
52
|
-
const count =
|
|
52
|
+
const count = unparseableCommits.length;
|
|
53
53
|
lines.push(`${indent} ${formatStatusLine(style, 'warning', `${count} commit${count === 1 ? '' : 's'} could not be parsed`)}`);
|
|
54
|
-
for (const commit of
|
|
54
|
+
for (const commit of unparseableCommits) {
|
|
55
55
|
const shortHash = commit.hash.slice(0, 7);
|
|
56
56
|
const truncatedSubject = truncateSubject(commit.subject);
|
|
57
57
|
lines.push(`${indent} · ${shortHash} ${truncatedSubject}`);
|
|
@@ -68,5 +68,5 @@ export function formatWarnings(lines, result, style) {
|
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
function truncateSubject(subject) {
|
|
71
|
-
return truncateToWidth(subject, { width:
|
|
71
|
+
return truncateToWidth(subject, { width: SUBJECT_BUDGET_COLUMNS });
|
|
72
72
|
}
|
|
@@ -15,8 +15,8 @@ function hasPrettierKeyInPackageJson(packageJsonPath) {
|
|
|
15
15
|
return false;
|
|
16
16
|
}
|
|
17
17
|
try {
|
|
18
|
-
const
|
|
19
|
-
return isRecord(
|
|
18
|
+
const parsedJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
|
|
19
|
+
return isRecord(parsedJson) && 'prettier' in parsedJson;
|
|
20
20
|
}
|
|
21
21
|
catch {
|
|
22
22
|
return false;
|
package/dist/esm/init/checks.js
CHANGED
|
@@ -24,9 +24,11 @@ export function usesPnpm() {
|
|
|
24
24
|
if (existsSync('pnpm-lock.yaml')) {
|
|
25
25
|
return { ok: true };
|
|
26
26
|
}
|
|
27
|
-
const
|
|
28
|
-
const
|
|
29
|
-
if (
|
|
27
|
+
const manifestJson = readFileSync('package.json', 'utf8');
|
|
28
|
+
const packageJson = parseJsonRecord(manifestJson);
|
|
29
|
+
if (packageJson !== undefined &&
|
|
30
|
+
typeof packageJson['packageManager'] === 'string' &&
|
|
31
|
+
packageJson['packageManager'].startsWith('pnpm')) {
|
|
30
32
|
return { ok: true };
|
|
31
33
|
}
|
|
32
34
|
return {
|
|
@@ -5,9 +5,9 @@ export function detectRepoType() {
|
|
|
5
5
|
return 'monorepo';
|
|
6
6
|
}
|
|
7
7
|
if (existsSync('package.json')) {
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
if (
|
|
8
|
+
const manifestJson = readFileSync('package.json', 'utf8');
|
|
9
|
+
const packageJson = parseJsonRecord(manifestJson);
|
|
10
|
+
if (packageJson !== undefined && Array.isArray(packageJson['workspaces'])) {
|
|
11
11
|
return 'monorepo';
|
|
12
12
|
}
|
|
13
13
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { type StreamStyles } from '@williamthorsen/nmr-core';
|
|
2
2
|
interface InitOptions {
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
isDryRun: boolean;
|
|
4
|
+
shouldForce: boolean;
|
|
5
5
|
styles: StreamStyles;
|
|
6
|
-
|
|
6
|
+
shouldScaffoldConfig: boolean;
|
|
7
7
|
}
|
|
8
8
|
/** Runs the eligibility checks, stopping at the first failure, and reports whether all passed. */
|
|
9
9
|
export declare function checkEligibility(styles: StreamStyles): boolean;
|
|
10
10
|
/** Runs the `release-kit init` command and returns the process exit code. */
|
|
11
|
-
export declare function initCommand({
|
|
11
|
+
export declare function initCommand({ isDryRun, shouldForce, styles, shouldScaffoldConfig }: InitOptions): number;
|
|
12
12
|
export {};
|
|
@@ -10,8 +10,8 @@ export function checkEligibility(styles) {
|
|
|
10
10
|
runRequiredCheck('package.json found', hasPackageJson(), styles) &&
|
|
11
11
|
runRequiredCheck('pnpm detected', usesPnpm(), styles));
|
|
12
12
|
}
|
|
13
|
-
export function initCommand({
|
|
14
|
-
if (
|
|
13
|
+
export function initCommand({ isDryRun, shouldForce, styles, shouldScaffoldConfig }) {
|
|
14
|
+
if (isDryRun) {
|
|
15
15
|
console.info('[dry-run mode]');
|
|
16
16
|
}
|
|
17
17
|
let eligible;
|
|
@@ -43,25 +43,25 @@ export function initCommand({ dryRun, force, styles, withConfig }) {
|
|
|
43
43
|
}
|
|
44
44
|
let results;
|
|
45
45
|
try {
|
|
46
|
-
results = scaffoldFiles({ repoType,
|
|
46
|
+
results = scaffoldFiles({ repoType, isDryRun, shouldOverwrite: shouldForce, publishable, shouldScaffoldConfig });
|
|
47
47
|
}
|
|
48
48
|
catch (error) {
|
|
49
49
|
printError(`Failed to scaffold files: ${describeError(error)}`, styles.stderr);
|
|
50
50
|
return 1;
|
|
51
51
|
}
|
|
52
52
|
for (const result of results) {
|
|
53
|
-
reportWriteResult(result,
|
|
53
|
+
reportWriteResult(result, isDryRun, styles);
|
|
54
54
|
}
|
|
55
55
|
if (results.some((r) => r.outcome === 'failed')) {
|
|
56
56
|
return 1;
|
|
57
57
|
}
|
|
58
58
|
printStep('Next steps');
|
|
59
|
-
console.info(`\n${formatNextSteps({ publishable,
|
|
59
|
+
console.info(`\n${formatNextSteps({ publishable, shouldScaffoldConfig })}\n`);
|
|
60
60
|
return 0;
|
|
61
61
|
}
|
|
62
|
-
function formatNextSteps({ publishable,
|
|
62
|
+
function formatNextSteps({ publishable, shouldScaffoldConfig, }) {
|
|
63
63
|
const steps = [
|
|
64
|
-
|
|
64
|
+
shouldScaffoldConfig
|
|
65
65
|
? '(Optional) Customize .config/release-kit.config.ts.'
|
|
66
66
|
: '(Optional) Run again with --with-config to scaffold config files.',
|
|
67
67
|
...(publishable ? ['If this is a private repo, remove provenance: true from .github/workflows/publish.yaml.'] : []),
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** Parses a JSON string, returning the result if it is a plain object and `undefined` otherwise. */
|
|
2
|
-
export declare function parseJsonRecord(
|
|
2
|
+
export declare function parseJsonRecord(jsonText: string): Record<string, unknown> | undefined;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { isRecord } from "../typeGuards.js";
|
|
2
|
-
export function parseJsonRecord(
|
|
3
|
-
let
|
|
2
|
+
export function parseJsonRecord(jsonText) {
|
|
3
|
+
let parsedJson;
|
|
4
4
|
try {
|
|
5
|
-
|
|
5
|
+
parsedJson = JSON.parse(jsonText);
|
|
6
6
|
}
|
|
7
7
|
catch {
|
|
8
8
|
return undefined;
|
|
9
9
|
}
|
|
10
|
-
return isRecord(
|
|
10
|
+
return isRecord(parsedJson) ? parsedJson : undefined;
|
|
11
11
|
}
|
|
@@ -2,15 +2,15 @@ import { type WriteResult } from '@williamthorsen/nmr-core';
|
|
|
2
2
|
import type { RepoType } from './detectRepoType.js';
|
|
3
3
|
interface ScaffoldOptions {
|
|
4
4
|
repoType: RepoType;
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
isDryRun: boolean;
|
|
6
|
+
shouldOverwrite: boolean;
|
|
7
7
|
publishable: boolean;
|
|
8
|
-
|
|
8
|
+
shouldScaffoldConfig: boolean;
|
|
9
9
|
}
|
|
10
10
|
export declare const CREATE_GITHUB_RELEASE_WORKFLOW_PATH = ".github/workflows/create-github-release.yaml";
|
|
11
11
|
export declare const PUBLISH_WORKFLOW_PATH = ".github/workflows/publish.yaml";
|
|
12
12
|
/** The workflows that act only on publishable packages, which a repo whose packages are all private does not get. */
|
|
13
13
|
export declare const PUBLISHING_WORKFLOW_PATHS: readonly [".github/workflows/create-github-release.yaml", ".github/workflows/publish.yaml"];
|
|
14
14
|
/** Scaffolds release-kit files for the target repo, returning a result for each file attempted. */
|
|
15
|
-
export declare function scaffoldFiles({ repoType,
|
|
15
|
+
export declare function scaffoldFiles({ repoType, isDryRun, shouldOverwrite, publishable, shouldScaffoldConfig, }: ScaffoldOptions): WriteResult[];
|
|
16
16
|
export {};
|
|
@@ -1,27 +1,27 @@
|
|
|
1
1
|
import { writeFileWithCheck } from '@williamthorsen/nmr-core';
|
|
2
|
-
import {
|
|
2
|
+
import { renderCreateGithubReleaseWorkflow, renderPublishWorkflow, renderReleaseConfigScript, renderReleaseWorkflow, } from "./templates.js";
|
|
3
3
|
export const CREATE_GITHUB_RELEASE_WORKFLOW_PATH = '.github/workflows/create-github-release.yaml';
|
|
4
4
|
export const PUBLISH_WORKFLOW_PATH = '.github/workflows/publish.yaml';
|
|
5
5
|
export const PUBLISHING_WORKFLOW_PATHS = [CREATE_GITHUB_RELEASE_WORKFLOW_PATH, PUBLISH_WORKFLOW_PATH];
|
|
6
|
-
export function scaffoldFiles({ repoType,
|
|
6
|
+
export function scaffoldFiles({ repoType, isDryRun, shouldOverwrite, publishable, shouldScaffoldConfig, }) {
|
|
7
7
|
const results = [];
|
|
8
8
|
if (publishable) {
|
|
9
|
-
results.push(writeFileWithCheck(CREATE_GITHUB_RELEASE_WORKFLOW_PATH,
|
|
10
|
-
isDryRun
|
|
11
|
-
shouldOverwrite
|
|
12
|
-
}), writeFileWithCheck(PUBLISH_WORKFLOW_PATH,
|
|
13
|
-
isDryRun
|
|
14
|
-
shouldOverwrite
|
|
9
|
+
results.push(writeFileWithCheck(CREATE_GITHUB_RELEASE_WORKFLOW_PATH, renderCreateGithubReleaseWorkflow(repoType), {
|
|
10
|
+
isDryRun,
|
|
11
|
+
shouldOverwrite,
|
|
12
|
+
}), writeFileWithCheck(PUBLISH_WORKFLOW_PATH, renderPublishWorkflow(repoType), {
|
|
13
|
+
isDryRun,
|
|
14
|
+
shouldOverwrite,
|
|
15
15
|
}));
|
|
16
16
|
}
|
|
17
|
-
results.push(writeFileWithCheck('.github/workflows/release.yaml',
|
|
18
|
-
isDryRun
|
|
19
|
-
shouldOverwrite
|
|
17
|
+
results.push(writeFileWithCheck('.github/workflows/release.yaml', renderReleaseWorkflow(repoType), {
|
|
18
|
+
isDryRun,
|
|
19
|
+
shouldOverwrite,
|
|
20
20
|
}));
|
|
21
|
-
if (
|
|
22
|
-
results.push(writeFileWithCheck('.config/release-kit.config.ts',
|
|
23
|
-
isDryRun
|
|
24
|
-
shouldOverwrite
|
|
21
|
+
if (shouldScaffoldConfig) {
|
|
22
|
+
results.push(writeFileWithCheck('.config/release-kit.config.ts', renderReleaseConfigScript(repoType), {
|
|
23
|
+
isDryRun,
|
|
24
|
+
shouldOverwrite,
|
|
25
25
|
}));
|
|
26
26
|
}
|
|
27
27
|
return results;
|