@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
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import type { RepoType } from './detectRepoType.js';
|
|
2
|
-
/**
|
|
3
|
-
export declare function
|
|
2
|
+
/** Renders the `.config/release-kit.config.ts` starter config, with commented-out options to customize. */
|
|
3
|
+
export declare function renderReleaseConfigScript(repoType: RepoType): string;
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* Renders the publish.yaml GitHub Actions entry-point workflow.
|
|
6
6
|
*
|
|
7
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. `
|
|
8
|
+
* that OIDC publishing needs. `shouldGenerateProvenance: false` omits the provenance input, which a private repo cannot use.
|
|
9
9
|
*/
|
|
10
|
-
export declare function
|
|
11
|
-
|
|
10
|
+
export declare function renderPublishWorkflow(repoType: RepoType, { shouldGenerateProvenance }?: {
|
|
11
|
+
shouldGenerateProvenance?: boolean;
|
|
12
12
|
}): string;
|
|
13
|
-
/**
|
|
14
|
-
export declare function
|
|
15
|
-
/**
|
|
16
|
-
export declare function
|
|
13
|
+
/** Renders the create-github-release.yaml GitHub Actions caller workflow. */
|
|
14
|
+
export declare function renderCreateGithubReleaseWorkflow(repoType: RepoType): string;
|
|
15
|
+
/** Renders the release.yaml GitHub Actions workflow. */
|
|
16
|
+
export declare function renderReleaseWorkflow(repoType: RepoType): string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export function
|
|
1
|
+
export function renderReleaseConfigScript(repoType) {
|
|
2
2
|
if (repoType === 'monorepo') {
|
|
3
3
|
return `import { defineConfig } from '@williamthorsen/release-kit/config';
|
|
4
4
|
|
|
@@ -39,7 +39,7 @@ export default defineConfig({
|
|
|
39
39
|
});
|
|
40
40
|
`;
|
|
41
41
|
}
|
|
42
|
-
export function
|
|
42
|
+
export function renderPublishWorkflow(repoType, { shouldGenerateProvenance = true } = {}) {
|
|
43
43
|
const tagPattern = repoType === 'monorepo' ? "'*-v[0-9]*.[0-9]*.[0-9]*'" : "'v[0-9]*.[0-9]*.[0-9]*'";
|
|
44
44
|
return `# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
|
|
45
45
|
name: Publish
|
|
@@ -57,10 +57,10 @@ jobs:
|
|
|
57
57
|
publish:
|
|
58
58
|
uses: williamthorsen/node-monorepo-tools/.github/workflows/publish.reusable.yaml@workflow/publish-v1
|
|
59
59
|
with:
|
|
60
|
-
${
|
|
60
|
+
${shouldGenerateProvenance ? ' provenance: true\n' : ''} tags: \${{ github.ref_name }}
|
|
61
61
|
`;
|
|
62
62
|
}
|
|
63
|
-
export function
|
|
63
|
+
export function renderCreateGithubReleaseWorkflow(repoType) {
|
|
64
64
|
const tagPattern = repoType === 'monorepo' ? "'*-v[0-9]*.[0-9]*.[0-9]*'" : "'v[0-9]*.[0-9]*.[0-9]*'";
|
|
65
65
|
return `# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
|
|
66
66
|
name: Create GitHub release
|
|
@@ -80,7 +80,7 @@ jobs:
|
|
|
80
80
|
tag: \${{ github.ref_name }}
|
|
81
81
|
`;
|
|
82
82
|
}
|
|
83
|
-
export function
|
|
83
|
+
export function renderReleaseWorkflow(repoType) {
|
|
84
84
|
if (repoType === 'monorepo') {
|
|
85
85
|
return `# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
|
|
86
86
|
name: Release
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type StreamStyles } from '@williamthorsen/nmr-core';
|
|
2
2
|
interface UpdateTemplatesOptions {
|
|
3
|
-
|
|
3
|
+
isDryRun: boolean;
|
|
4
4
|
styles: StreamStyles;
|
|
5
5
|
}
|
|
6
6
|
/**
|
|
@@ -10,5 +10,5 @@ interface UpdateTemplatesOptions {
|
|
|
10
10
|
* `sync-labels.yaml` is refreshed only when it exists, as are the publishing workflows in a repo whose packages are
|
|
11
11
|
* all private. Config files are never read or written.
|
|
12
12
|
*/
|
|
13
|
-
export declare function updateTemplatesCommand({
|
|
13
|
+
export declare function updateTemplatesCommand({ isDryRun, styles }: UpdateTemplatesOptions): number;
|
|
14
14
|
export {};
|
|
@@ -7,10 +7,10 @@ import { hasProvenance } from "./hasProvenance.js";
|
|
|
7
7
|
import { formatPrivateWorkflowSkip, hasPublishablePackage } from "./hasPublishablePackage.js";
|
|
8
8
|
import { checkEligibility } from "./initCommand.js";
|
|
9
9
|
import { CREATE_GITHUB_RELEASE_WORKFLOW_PATH, PUBLISH_WORKFLOW_PATH, PUBLISHING_WORKFLOW_PATHS } from "./scaffold.js";
|
|
10
|
-
import {
|
|
10
|
+
import { renderCreateGithubReleaseWorkflow, renderPublishWorkflow, renderReleaseWorkflow } from "./templates.js";
|
|
11
11
|
const SYNC_LABELS_WORKFLOW_PATH = '.github/workflows/sync-labels.yaml';
|
|
12
|
-
export function updateTemplatesCommand({
|
|
13
|
-
if (
|
|
12
|
+
export function updateTemplatesCommand({ isDryRun, styles }) {
|
|
13
|
+
if (isDryRun) {
|
|
14
14
|
console.info('[dry-run mode]');
|
|
15
15
|
}
|
|
16
16
|
let eligible;
|
|
@@ -36,32 +36,34 @@ export function updateTemplatesCommand({ dryRun, styles }) {
|
|
|
36
36
|
printStep('Updating workflows');
|
|
37
37
|
let results;
|
|
38
38
|
try {
|
|
39
|
-
const { files,
|
|
40
|
-
for (const filePath of
|
|
39
|
+
const { files, skippedPaths } = buildManagedFiles(repoType, hasPublishablePackage());
|
|
40
|
+
for (const filePath of skippedPaths) {
|
|
41
41
|
printSkip(formatPrivateWorkflowSkip(filePath), styles.stdout);
|
|
42
42
|
}
|
|
43
|
-
results = files.map(([filePath, content]) => updateManagedFile(filePath, content, { isDryRun
|
|
43
|
+
results = files.map(([filePath, content]) => updateManagedFile(filePath, content, { isDryRun }));
|
|
44
44
|
}
|
|
45
45
|
catch (error) {
|
|
46
46
|
printError(`Failed to render workflows: ${describeError(error)}`, styles.stderr);
|
|
47
47
|
return 1;
|
|
48
48
|
}
|
|
49
49
|
for (const result of results) {
|
|
50
|
-
reportTemplateUpdate(result,
|
|
50
|
+
reportTemplateUpdate(result, isDryRun, styles);
|
|
51
51
|
}
|
|
52
52
|
return results.some((result) => result.outcome === 'failed') ? 1 : 0;
|
|
53
53
|
}
|
|
54
54
|
function buildManagedFiles(repoType, publishable) {
|
|
55
|
-
const
|
|
55
|
+
const skippedPaths = publishable
|
|
56
|
+
? []
|
|
57
|
+
: PUBLISHING_WORKFLOW_PATHS.filter((filePath) => !existsSync(filePath));
|
|
56
58
|
const files = [
|
|
57
|
-
[CREATE_GITHUB_RELEASE_WORKFLOW_PATH,
|
|
58
|
-
[PUBLISH_WORKFLOW_PATH,
|
|
59
|
-
['.github/workflows/release.yaml',
|
|
59
|
+
[CREATE_GITHUB_RELEASE_WORKFLOW_PATH, renderCreateGithubReleaseWorkflow(repoType)],
|
|
60
|
+
[PUBLISH_WORKFLOW_PATH, renderPublishWorkflow(repoType, { shouldGenerateProvenance: readProvenanceSetting() })],
|
|
61
|
+
['.github/workflows/release.yaml', renderReleaseWorkflow(repoType)],
|
|
60
62
|
];
|
|
61
63
|
if (existsSync(SYNC_LABELS_WORKFLOW_PATH)) {
|
|
62
64
|
files.push([SYNC_LABELS_WORKFLOW_PATH, readSyncLabelsWorkflowTemplate()]);
|
|
63
65
|
}
|
|
64
|
-
return { files: files.filter(([filePath]) => !
|
|
66
|
+
return { files: files.filter(([filePath]) => !skippedPaths.includes(filePath)), skippedPaths };
|
|
65
67
|
}
|
|
66
68
|
function readProvenanceSetting() {
|
|
67
69
|
if (!existsSync(PUBLISH_WORKFLOW_PATH))
|
|
@@ -16,7 +16,7 @@ export type RenderInjectedReadmeSkipReason = 'no-entry' | 'empty-body';
|
|
|
16
16
|
/** Discriminated outcome of rendering release notes for a tag. */
|
|
17
17
|
export type RenderInjectedReadmeResult = {
|
|
18
18
|
status: 'rendered';
|
|
19
|
-
|
|
19
|
+
renderedReadme: RenderedInjectedReadme;
|
|
20
20
|
} | {
|
|
21
21
|
status: 'skipped';
|
|
22
22
|
reason: RenderInjectedReadmeSkipReason;
|
|
@@ -11,7 +11,7 @@ export function renderInjectedReadmeFromEntries(readme, entries, tag, sectionOrd
|
|
|
11
11
|
}
|
|
12
12
|
const renderedSections = renderReleaseNotesSingle(entry, {
|
|
13
13
|
filter: matchesAudience('all'),
|
|
14
|
-
|
|
14
|
+
shouldIncludeHeading: false,
|
|
15
15
|
...(sectionOrder !== undefined && { sectionOrder }),
|
|
16
16
|
});
|
|
17
17
|
if (renderedSections.trimEnd().length === 0) {
|
|
@@ -20,7 +20,7 @@ export function renderInjectedReadmeFromEntries(readme, entries, tag, sectionOrd
|
|
|
20
20
|
const labeledHeading = `## Release notes — v${version} (${entry.date})`;
|
|
21
21
|
const releaseNotesMarkdown = `${labeledHeading}\n\n${renderedSections.trimEnd()}`;
|
|
22
22
|
const injectedReadme = injectSection(readme, 'release-notes', releaseNotesMarkdown);
|
|
23
|
-
return { status: 'rendered',
|
|
23
|
+
return { status: 'rendered', renderedReadme: { injectedReadme, releaseNotesMarkdown } };
|
|
24
24
|
}
|
|
25
25
|
export function renderInjectedReadme(readme, changelogJsonPath, tag, sectionOrder) {
|
|
26
26
|
if (!existsSync(changelogJsonPath)) {
|
|
@@ -37,15 +37,15 @@ export function renderInjectedReadme(readme, changelogJsonPath, tag, sectionOrde
|
|
|
37
37
|
console.warn(`Warning: ${describeRenderSkip(result.reason, result.version)}; skipping README injection`);
|
|
38
38
|
return undefined;
|
|
39
39
|
}
|
|
40
|
-
return result.
|
|
40
|
+
return result.renderedReadme;
|
|
41
41
|
}
|
|
42
42
|
export function injectReleaseNotesIntoReadme(readmePath, changelogJsonPath, tag, sectionOrder) {
|
|
43
43
|
const originalReadme = readFileSync(readmePath, 'utf8');
|
|
44
|
-
const
|
|
45
|
-
if (
|
|
44
|
+
const renderResult = renderInjectedReadme(originalReadme, changelogJsonPath, tag, sectionOrder);
|
|
45
|
+
if (renderResult === undefined) {
|
|
46
46
|
return undefined;
|
|
47
47
|
}
|
|
48
|
-
writeFileSync(readmePath,
|
|
48
|
+
writeFileSync(readmePath, renderResult.injectedReadme, 'utf8');
|
|
49
49
|
return originalReadme;
|
|
50
50
|
}
|
|
51
51
|
export function resolveReadmePath(workspacePath) {
|
|
@@ -5,16 +5,16 @@ function closeMarker(key) {
|
|
|
5
5
|
return `<!-- /section:${key} -->`;
|
|
6
6
|
}
|
|
7
7
|
export function injectSection(content, key, injection) {
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
const openIndex = content.indexOf(
|
|
11
|
-
const closeIndex = content.indexOf(
|
|
8
|
+
const openComment = openMarker(key);
|
|
9
|
+
const closeComment = closeMarker(key);
|
|
10
|
+
const openIndex = content.indexOf(openComment);
|
|
11
|
+
const closeIndex = content.indexOf(closeComment);
|
|
12
12
|
if (openIndex !== -1 && closeIndex !== -1 && closeIndex > openIndex) {
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
return `${
|
|
13
|
+
const contentBefore = content.slice(0, openIndex + openComment.length);
|
|
14
|
+
const contentAfter = content.slice(closeIndex);
|
|
15
|
+
return `${contentBefore}\n${injection}\n${contentAfter}`;
|
|
16
16
|
}
|
|
17
|
-
const section = `${
|
|
17
|
+
const section = `${openComment}\n${injection}\n${closeComment}`;
|
|
18
18
|
if (content.length === 0) {
|
|
19
19
|
return `${section}\n`;
|
|
20
20
|
}
|
|
@@ -25,7 +25,7 @@ export declare function parseOverrideKey(key: string): {
|
|
|
25
25
|
* Each error names the offending key (e.g. `overrides['abc']: 'audience' must be one of …`) so that the user can find
|
|
26
26
|
* it in the override file.
|
|
27
27
|
*/
|
|
28
|
-
export declare function validateChangelogOverrides(
|
|
28
|
+
export declare function validateChangelogOverrides(rawValue: unknown): {
|
|
29
29
|
overrides: Map<string, ChangelogOverride>;
|
|
30
30
|
errors: string[];
|
|
31
31
|
};
|
|
@@ -18,16 +18,16 @@ export function loadChangelogOverrides(path) {
|
|
|
18
18
|
errors: [`Failed to read override file ${path}: ${describeError(error)}`],
|
|
19
19
|
};
|
|
20
20
|
}
|
|
21
|
-
let
|
|
21
|
+
let parsedJson;
|
|
22
22
|
try {
|
|
23
|
-
|
|
23
|
+
parsedJson = JSON.parse(content);
|
|
24
24
|
}
|
|
25
25
|
catch (error) {
|
|
26
26
|
return {
|
|
27
27
|
errors: [`Failed to parse override file ${path}: ${describeError(error)}`],
|
|
28
28
|
};
|
|
29
29
|
}
|
|
30
|
-
const result = validateChangelogOverrides(
|
|
30
|
+
const result = validateChangelogOverrides(parsedJson);
|
|
31
31
|
if (result.errors.length > 0) {
|
|
32
32
|
return { errors: result.errors };
|
|
33
33
|
}
|
|
@@ -41,14 +41,14 @@ export function parseOverrideKey(key) {
|
|
|
41
41
|
}
|
|
42
42
|
return { hashPrefix: key.slice(0, separatorIndex), entry: Number(key.slice(separatorIndex + 1)) };
|
|
43
43
|
}
|
|
44
|
-
export function validateChangelogOverrides(
|
|
44
|
+
export function validateChangelogOverrides(rawValue) {
|
|
45
45
|
const overrides = new Map();
|
|
46
46
|
const errors = [];
|
|
47
|
-
if (!isRecord(
|
|
47
|
+
if (!isRecord(rawValue)) {
|
|
48
48
|
errors.push('Override file: top-level value must be an object keyed by commit hash');
|
|
49
49
|
return { overrides, errors };
|
|
50
50
|
}
|
|
51
|
-
for (const [key, rawEntry] of Object.entries(
|
|
51
|
+
for (const [key, rawEntry] of Object.entries(rawValue)) {
|
|
52
52
|
if (key === SCHEMA_KEY) {
|
|
53
53
|
if (typeof rawEntry !== 'string') {
|
|
54
54
|
errors.push(`Override file: '${SCHEMA_KEY}' must be a string`);
|
|
@@ -59,9 +59,9 @@ export function validateChangelogOverrides(raw) {
|
|
|
59
59
|
errors.push(`overrides['${key}']: key must be a lowercase hex commit hash or prefix, optionally followed by ':<n>'`);
|
|
60
60
|
continue;
|
|
61
61
|
}
|
|
62
|
-
const
|
|
63
|
-
if (
|
|
64
|
-
overrides.set(key,
|
|
62
|
+
const validatedOverride = validateSingleOverride(key, rawEntry, errors);
|
|
63
|
+
if (validatedOverride !== undefined) {
|
|
64
|
+
overrides.set(key, validatedOverride);
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
67
|
return { overrides, errors };
|
package/dist/esm/loadConfig.d.ts
CHANGED
|
@@ -3,13 +3,13 @@ export declare const ROOT_PACKAGE_JSON_PATH = "package.json";
|
|
|
3
3
|
/**
|
|
4
4
|
* Reads the root `package.json` and returns its `version` field.
|
|
5
5
|
*
|
|
6
|
-
* Returns `{
|
|
7
|
-
* when the file exists but does not have a `version` field, and `{
|
|
6
|
+
* Returns `{ doesExist: false }` when the file is missing, `{ doesExist: true, version: undefined }`
|
|
7
|
+
* when the file exists but does not have a `version` field, and `{ doesExist: true, version }`
|
|
8
8
|
* when both are present; `mergeMonorepoConfig` decides whether the situation is an error.
|
|
9
9
|
* Throws when the file cannot be read or parsed.
|
|
10
10
|
*/
|
|
11
11
|
export declare function readRootPackageVersion(): {
|
|
12
|
-
|
|
12
|
+
doesExist: boolean;
|
|
13
13
|
version: string | undefined;
|
|
14
14
|
};
|
|
15
15
|
/** The path where the consumer-facing config file is expected. */
|
package/dist/esm/loadConfig.js
CHANGED
|
@@ -15,7 +15,7 @@ export const ROOT_PACKAGE_JSON_PATH = 'package.json';
|
|
|
15
15
|
export function readRootPackageVersion() {
|
|
16
16
|
const absolutePath = path.resolve(process.cwd(), ROOT_PACKAGE_JSON_PATH);
|
|
17
17
|
if (!existsSync(absolutePath)) {
|
|
18
|
-
return {
|
|
18
|
+
return { doesExist: false, version: undefined };
|
|
19
19
|
}
|
|
20
20
|
let contents;
|
|
21
21
|
try {
|
|
@@ -24,17 +24,17 @@ export function readRootPackageVersion() {
|
|
|
24
24
|
catch (error) {
|
|
25
25
|
throw chainError(`Failed to read root ${ROOT_PACKAGE_JSON_PATH}`, error);
|
|
26
26
|
}
|
|
27
|
-
let
|
|
27
|
+
let parsedJson;
|
|
28
28
|
try {
|
|
29
|
-
|
|
29
|
+
parsedJson = JSON.parse(contents);
|
|
30
30
|
}
|
|
31
31
|
catch (error) {
|
|
32
32
|
throw chainError(`Failed to parse root ${ROOT_PACKAGE_JSON_PATH}`, error);
|
|
33
33
|
}
|
|
34
|
-
if (!isRecord(
|
|
35
|
-
return {
|
|
34
|
+
if (!isRecord(parsedJson)) {
|
|
35
|
+
return { doesExist: true, version: undefined };
|
|
36
36
|
}
|
|
37
|
-
return {
|
|
37
|
+
return { doesExist: true, version: typeof parsedJson['version'] === 'string' ? parsedJson['version'] : undefined };
|
|
38
38
|
}
|
|
39
39
|
export const CONFIG_FILE_PATH = '.config/release-kit.config.ts';
|
|
40
40
|
export async function loadConfig(configPath) {
|
|
@@ -45,10 +45,10 @@ export async function loadConfig(configPath) {
|
|
|
45
45
|
}
|
|
46
46
|
throw new Error(`Config file not found: ${absoluteConfigPath}`);
|
|
47
47
|
}
|
|
48
|
-
const
|
|
49
|
-
const
|
|
50
|
-
if (
|
|
48
|
+
const importedModule = await import(__rewriteRelativeImportExtension(pathToFileURL(absoluteConfigPath).href));
|
|
49
|
+
const resolvedConfig = isRecord(importedModule) ? (importedModule['default'] ?? importedModule['config']) : undefined;
|
|
50
|
+
if (resolvedConfig === undefined) {
|
|
51
51
|
throw new Error('Config file must have a default export or a named `config` export (e.g., `export default { ... }` or `export const config = { ... }`)');
|
|
52
52
|
}
|
|
53
|
-
return
|
|
53
|
+
return resolvedConfig;
|
|
54
54
|
}
|
|
@@ -4,17 +4,17 @@ import { CONFIG_FILE_PATH, loadConfig } from "./loadConfig.js";
|
|
|
4
4
|
import { validateConfig } from "./validateConfig.js";
|
|
5
5
|
export async function loadValidatedConfig(configPath) {
|
|
6
6
|
const configFilePath = configPath ?? CONFIG_FILE_PATH;
|
|
7
|
-
let
|
|
7
|
+
let rawConfig;
|
|
8
8
|
try {
|
|
9
|
-
|
|
9
|
+
rawConfig = await loadConfig(configPath);
|
|
10
10
|
}
|
|
11
11
|
catch (error) {
|
|
12
12
|
return { status: 'invalid', configFilePath, problem: { kind: 'load', message: describeError(error) } };
|
|
13
13
|
}
|
|
14
|
-
if (
|
|
14
|
+
if (rawConfig === undefined) {
|
|
15
15
|
return { status: 'missing', configFilePath };
|
|
16
16
|
}
|
|
17
|
-
const { config, errors, warnings } = validateConfig(
|
|
17
|
+
const { config, errors, warnings } = validateConfig(rawConfig);
|
|
18
18
|
if (errors.length > 0) {
|
|
19
19
|
return { status: 'invalid', configFilePath, problem: { kind: 'validation', errors } };
|
|
20
20
|
}
|
package/dist/esm/mergeConfig.js
CHANGED
|
@@ -5,10 +5,10 @@ import { ROOT_PACKAGE_JSON_PATH } from "./loadConfig.js";
|
|
|
5
5
|
import { assertLegacyIdentityDoesNotMatchCurrent, assertNoSharedVersionFiles, assertNoTagPrefixCollisions, assertRetiredPackagesDoNotCollideWithActive, assertUniqueTagPrefixes, } from "./monorepoConfigAssertions.js";
|
|
6
6
|
import { resolveReleaseDeclaration } from "./resolveReleaseDeclaration.js";
|
|
7
7
|
export function mergeMonorepoConfig(discoveredPaths, userConfig, rootPackage) {
|
|
8
|
-
const
|
|
9
|
-
assertUniqueTagPrefixes(
|
|
10
|
-
const {
|
|
11
|
-
const directories = declaredDirectories.map((
|
|
8
|
+
const discoveredWorkspaces = discoveredPaths.map((workspacePath) => deriveWorkspaceConfig(workspacePath));
|
|
9
|
+
assertUniqueTagPrefixes(discoveredWorkspaces);
|
|
10
|
+
const { retainedWorkspaces: workspaces, excludedWorkspaces, directories: declaredDirectories, composite: declaredComposite, } = resolveReleaseDeclaration(discoveredWorkspaces, userConfig);
|
|
11
|
+
const directories = declaredDirectories.map((declaredDirectory) => deriveDirectoryConfig(declaredDirectory));
|
|
12
12
|
for (const workspace of workspaces) {
|
|
13
13
|
if (workspace.legacyIdentities !== undefined) {
|
|
14
14
|
assertLegacyIdentityDoesNotMatchCurrent(workspace.dir, workspace.name, workspace.tagPrefix, workspace.legacyIdentities);
|
|
@@ -17,13 +17,13 @@ export function mergeMonorepoConfig(discoveredPaths, userConfig, rootPackage) {
|
|
|
17
17
|
if (userConfig?.retiredPackages !== undefined) {
|
|
18
18
|
assertRetiredPackagesDoNotCollideWithActive(workspaces, userConfig.retiredPackages);
|
|
19
19
|
}
|
|
20
|
-
const
|
|
20
|
+
const projectConfig = resolveProjectConfig(declaredComposite, rootPackage, workspaces);
|
|
21
21
|
const workTypes = resolveWorkTypes(userConfig?.workTypes);
|
|
22
22
|
const versionPatterns = userConfig?.versionPatterns === undefined ? { ...DEFAULT_VERSION_PATTERNS } : { ...userConfig.versionPatterns };
|
|
23
23
|
const changelogJson = mergeChangelogJsonConfig(userConfig?.changelogJson);
|
|
24
24
|
const releaseNotes = mergeReleaseNotesConfig(userConfig?.releaseNotes);
|
|
25
|
-
assertNoSharedVersionFiles(workspaces, directories,
|
|
26
|
-
assertNoTagPrefixCollisions(workspaces, directories, userConfig?.retiredPackages,
|
|
25
|
+
assertNoSharedVersionFiles(workspaces, directories, projectConfig);
|
|
26
|
+
assertNoTagPrefixCollisions(workspaces, directories, userConfig?.retiredPackages, projectConfig);
|
|
27
27
|
const result = {
|
|
28
28
|
workspaces,
|
|
29
29
|
workTypes,
|
|
@@ -31,14 +31,14 @@ export function mergeMonorepoConfig(discoveredPaths, userConfig, rootPackage) {
|
|
|
31
31
|
changelogJson,
|
|
32
32
|
releaseNotes,
|
|
33
33
|
};
|
|
34
|
-
if (
|
|
35
|
-
result.excludedWorkspaces =
|
|
34
|
+
if (excludedWorkspaces.length > 0) {
|
|
35
|
+
result.excludedWorkspaces = excludedWorkspaces;
|
|
36
36
|
}
|
|
37
37
|
if (directories.length > 0) {
|
|
38
38
|
result.directories = directories;
|
|
39
39
|
}
|
|
40
|
-
if (
|
|
41
|
-
result.project =
|
|
40
|
+
if (projectConfig !== undefined) {
|
|
41
|
+
result.project = projectConfig;
|
|
42
42
|
}
|
|
43
43
|
applyOptionalPassthroughFields(result, userConfig);
|
|
44
44
|
return result;
|
|
@@ -80,39 +80,39 @@ function applyOptionalPassthroughFields(result, userConfig) {
|
|
|
80
80
|
result.breakingPolicies = userConfig.breakingPolicies;
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
|
-
function mergeChangelogJsonConfig(
|
|
84
|
-
if (
|
|
83
|
+
function mergeChangelogJsonConfig(partialConfig) {
|
|
84
|
+
if (partialConfig === undefined) {
|
|
85
85
|
return { ...DEFAULT_CHANGELOG_JSON_CONFIG };
|
|
86
86
|
}
|
|
87
87
|
return {
|
|
88
|
-
enabled:
|
|
89
|
-
outputPath:
|
|
90
|
-
devOnlySections:
|
|
88
|
+
enabled: partialConfig.enabled ?? DEFAULT_CHANGELOG_JSON_CONFIG.enabled,
|
|
89
|
+
outputPath: partialConfig.outputPath ?? DEFAULT_CHANGELOG_JSON_CONFIG.outputPath,
|
|
90
|
+
devOnlySections: partialConfig.devOnlySections ?? [...DEFAULT_CHANGELOG_JSON_CONFIG.devOnlySections],
|
|
91
91
|
};
|
|
92
92
|
}
|
|
93
|
-
function mergeReleaseNotesConfig(
|
|
94
|
-
if (
|
|
93
|
+
function mergeReleaseNotesConfig(partialConfig) {
|
|
94
|
+
if (partialConfig === undefined) {
|
|
95
95
|
return { ...DEFAULT_RELEASE_NOTES_CONFIG };
|
|
96
96
|
}
|
|
97
97
|
return {
|
|
98
|
-
shouldInjectIntoReadme:
|
|
98
|
+
shouldInjectIntoReadme: partialConfig.shouldInjectIntoReadme ?? DEFAULT_RELEASE_NOTES_CONFIG.shouldInjectIntoReadme,
|
|
99
99
|
};
|
|
100
100
|
}
|
|
101
|
-
function resolveProjectConfig(
|
|
102
|
-
if (
|
|
101
|
+
function resolveProjectConfig(declaredComposite, rootPackage, workspaces) {
|
|
102
|
+
if (declaredComposite === undefined) {
|
|
103
103
|
return undefined;
|
|
104
104
|
}
|
|
105
|
-
const subject =
|
|
106
|
-
if (rootPackage === undefined || !rootPackage.
|
|
105
|
+
const subject = declaredComposite.name === undefined ? 'project block' : `composite release '${declaredComposite.name}'`;
|
|
106
|
+
if (rootPackage === undefined || !rootPackage.doesExist) {
|
|
107
107
|
throw new Error(`${subject} requires a root ${ROOT_PACKAGE_JSON_PATH}; create one with a 'version' field at the repo root`);
|
|
108
108
|
}
|
|
109
109
|
if (rootPackage.version === undefined) {
|
|
110
110
|
throw new Error(`${subject} requires root ${ROOT_PACKAGE_JSON_PATH} to have a 'version' field; add a 'version' field to the root package.json`);
|
|
111
111
|
}
|
|
112
112
|
return {
|
|
113
|
-
paths:
|
|
114
|
-
tagPrefix:
|
|
115
|
-
...(
|
|
116
|
-
...(
|
|
113
|
+
paths: declaredComposite.paths ?? workspaces.flatMap((workspace) => workspace.paths),
|
|
114
|
+
tagPrefix: declaredComposite.tagPrefix ?? DEFAULT_PROJECT_TAG_PREFIX,
|
|
115
|
+
...(declaredComposite.name !== undefined && { name: declaredComposite.name }),
|
|
116
|
+
...(declaredComposite.members !== undefined && { members: declaredComposite.members }),
|
|
117
117
|
};
|
|
118
118
|
}
|
|
@@ -63,10 +63,10 @@ export function assertNoTagPrefixCollisions(workspaces, directories, retiredPack
|
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
const retiredEntries = (retiredPackages ?? []).entries();
|
|
66
|
-
for (const [index,
|
|
66
|
+
for (const [index, retiredPackage] of retiredEntries) {
|
|
67
67
|
sources.push({
|
|
68
|
-
prefix:
|
|
69
|
-
label: `retiredPackages entry (name='${
|
|
68
|
+
prefix: retiredPackage.tagPrefix,
|
|
69
|
+
label: `retiredPackages entry (name='${retiredPackage.name}')`,
|
|
70
70
|
owner: `retired:${index}`,
|
|
71
71
|
});
|
|
72
72
|
}
|
|
@@ -77,10 +77,10 @@ export function assertNoTagPrefixCollisions(workspaces, directories, retiredPack
|
|
|
77
77
|
owner: 'project',
|
|
78
78
|
});
|
|
79
79
|
}
|
|
80
|
-
for (let
|
|
81
|
-
for (let
|
|
82
|
-
const a = sources[
|
|
83
|
-
const b = sources[
|
|
80
|
+
for (let aIndex = 0; aIndex < sources.length; aIndex++) {
|
|
81
|
+
for (let bIndex = aIndex + 1; bIndex < sources.length; bIndex++) {
|
|
82
|
+
const a = sources[aIndex];
|
|
83
|
+
const b = sources[bIndex];
|
|
84
84
|
if (a === undefined || b === undefined || a.owner === b.owner)
|
|
85
85
|
continue;
|
|
86
86
|
if (isPrefixCollision(a.prefix, b.prefix)) {
|
|
@@ -96,23 +96,23 @@ export function assertRetiredPackagesDoNotCollideWithActive(workspaces, retiredP
|
|
|
96
96
|
for (const workspace of workspaces) {
|
|
97
97
|
workspaceByDerivedPrefix.set(workspace.tagPrefix, workspace);
|
|
98
98
|
}
|
|
99
|
-
for (const
|
|
100
|
-
const
|
|
101
|
-
if (
|
|
102
|
-
throw new Error(`retiredPackages: tagPrefix '${
|
|
103
|
-
`(derived prefix '${
|
|
99
|
+
for (const retiredPackage of retiredPackages) {
|
|
100
|
+
const activeWorkspace = workspaceByDerivedPrefix.get(retiredPackage.tagPrefix);
|
|
101
|
+
if (activeWorkspace !== undefined) {
|
|
102
|
+
throw new Error(`retiredPackages: tagPrefix '${retiredPackage.tagPrefix}' collides with active workspace '${activeWorkspace.dir}' ` +
|
|
103
|
+
`(derived prefix '${activeWorkspace.tagPrefix}'). A retired package's tagPrefix cannot belong to an active workspace.`);
|
|
104
104
|
}
|
|
105
105
|
}
|
|
106
106
|
}
|
|
107
107
|
export function assertUniqueTagPrefixes(workspaces) {
|
|
108
108
|
const pathsByPrefix = new Map();
|
|
109
109
|
for (const workspace of workspaces) {
|
|
110
|
-
const
|
|
111
|
-
if (
|
|
110
|
+
const existingPaths = pathsByPrefix.get(workspace.tagPrefix);
|
|
111
|
+
if (existingPaths === undefined) {
|
|
112
112
|
pathsByPrefix.set(workspace.tagPrefix, [workspace.workspacePath]);
|
|
113
113
|
}
|
|
114
114
|
else {
|
|
115
|
-
|
|
115
|
+
existingPaths.push(workspace.workspacePath);
|
|
116
116
|
}
|
|
117
117
|
}
|
|
118
118
|
for (const [prefix, paths] of pathsByPrefix) {
|
package/dist/esm/npmRegistry.js
CHANGED
|
@@ -34,17 +34,17 @@ export function lookUpNpmPackage(packageName, registry) {
|
|
|
34
34
|
return classifyNpmPackageLookup(runNpmJson(args));
|
|
35
35
|
}
|
|
36
36
|
export function readNpmError(stdout) {
|
|
37
|
-
let
|
|
37
|
+
let parsedJson;
|
|
38
38
|
try {
|
|
39
|
-
|
|
39
|
+
parsedJson = JSON.parse(stdout);
|
|
40
40
|
}
|
|
41
41
|
catch {
|
|
42
42
|
return undefined;
|
|
43
43
|
}
|
|
44
|
-
if (!isRecord(
|
|
44
|
+
if (!isRecord(parsedJson) || !isRecord(parsedJson['error']) || typeof parsedJson['error']['code'] !== 'string') {
|
|
45
45
|
return undefined;
|
|
46
46
|
}
|
|
47
|
-
const { code, summary } =
|
|
47
|
+
const { code, summary } = parsedJson['error'];
|
|
48
48
|
return { code, summary: typeof summary === 'string' ? summary : '' };
|
|
49
49
|
}
|
|
50
50
|
export function runNpmJson(args) {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type PackageManager } from './detectPackageManager.js';
|
|
2
|
+
/**
|
|
3
|
+
* Packs each workspace as the publish environment would and returns each one's tarball entries, keyed by workspace
|
|
4
|
+
* path and relative to the package root.
|
|
5
|
+
*
|
|
6
|
+
* Clones HEAD of the repo at the current directory into a temporary directory, runs a frozen install there, then for
|
|
7
|
+
* each workspace runs its `prepublishOnly` when it declares one and packs it. Workspace paths are relative to the repo
|
|
8
|
+
* root. Throws an error that names the failing step and contains its output. Leaves the repo and its working tree
|
|
9
|
+
* untouched, and removes the temporary directory.
|
|
10
|
+
*/
|
|
11
|
+
export declare function packFromCleanCheckout(workspacePaths: readonly string[], packageManager: PackageManager): Map<string, string[]>;
|