@exadev/semantic-release-workspace 1.3.4 → 1.3.6
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/dist/cli.js +107 -57
- package/dist/index.cjs +105 -55
- package/dist/index.d.cts +18 -13
- package/dist/index.d.ts +18 -13
- package/dist/index.js +105 -55
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -26,7 +26,6 @@ let node_path = require("node:path");
|
|
|
26
26
|
let tinyglobby = require("tinyglobby");
|
|
27
27
|
let yaml = require("yaml");
|
|
28
28
|
let node_child_process = require("node:child_process");
|
|
29
|
-
let node_util = require("node:util");
|
|
30
29
|
let validate_npm_package_name = require("validate-npm-package-name");
|
|
31
30
|
validate_npm_package_name = __toESM(validate_npm_package_name, 1);
|
|
32
31
|
let node_module = require("node:module");
|
|
@@ -70,7 +69,7 @@ var ReleaseConfigurationError = class extends WorkspaceReleaseError {};
|
|
|
70
69
|
var GitCommandError = class extends WorkspaceReleaseError {
|
|
71
70
|
exitCode;
|
|
72
71
|
constructor(args, cwd, exitCode, detail) {
|
|
73
|
-
super(`git ${args.join(" ")} failed in ${cwd}${exitCode === void 0 ? "" : ` (exit ${exitCode})`}: ${detail}`);
|
|
72
|
+
super(`git ${args.join(" ")} failed in ${cwd}${exitCode === void 0 ? "" : ` (exit ${String(exitCode)})`}: ${detail}`);
|
|
74
73
|
this.exitCode = exitCode;
|
|
75
74
|
}
|
|
76
75
|
};
|
|
@@ -83,10 +82,30 @@ var PnpmCommandError = class extends WorkspaceReleaseError {
|
|
|
83
82
|
}
|
|
84
83
|
};
|
|
85
84
|
//#endregion
|
|
85
|
+
//#region src/exec-file.ts
|
|
86
|
+
/**
|
|
87
|
+
* A hand-written promise wrapper around `child_process.execFile`, in place of `util.promisify(execFile)`: `execFile` synchronously returns a `ChildProcess` in addition to invoking its callback, which trips `@typescript-eslint/strict-void-return` when the whole function is handed to `promisify` (a value-returning function used where a void-returning one is contextually expected there) -- exactly the void-return contravariance leniency that rule exists to catch, even though `tsc` itself accepts the pattern. Calling `execFile` directly with our own callback, whose own return type really is `void`, sidesteps the mismatch instead of suppressing it.
|
|
88
|
+
*/
|
|
89
|
+
async function execFile(command, args, options) {
|
|
90
|
+
return new Promise((resolve, reject) => {
|
|
91
|
+
(0, node_child_process.execFile)(command, [...args], {
|
|
92
|
+
cwd: options.cwd,
|
|
93
|
+
env: options.env,
|
|
94
|
+
maxBuffer: options.maxBuffer
|
|
95
|
+
}, (error, stdout, stderr) => {
|
|
96
|
+
if (error) {
|
|
97
|
+
reject(error instanceof Error ? error : new Error(error.message));
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
resolve({
|
|
101
|
+
stdout,
|
|
102
|
+
stderr
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
//#endregion
|
|
86
108
|
//#region src/git.ts
|
|
87
|
-
const execFileAsync$1 = (0, node_util.promisify)(node_child_process.execFile);
|
|
88
|
-
/** `git log --name-only` over everything since a package's last release tag can legitimately produce tens of megabytes of path output on a long-lived monorepo, well past execFile's default buffer, failing on exactly the big workspaces this tool exists for. */
|
|
89
|
-
const GIT_MAX_BUFFER_BYTES = 104857600;
|
|
90
109
|
/** Separates one commit's record in `git log --format` output. Chosen from the C0 control range so it can never appear in a hash or a file path. */
|
|
91
110
|
const COMMIT_RECORD_SEPARATOR = "";
|
|
92
111
|
/** The identity semantic-release's own core writes release commits under in CI when nothing else is configured (its COMMIT_NAME/COMMIT_EMAIL constants); dependency-bump commits use the same fallback so every commit a release run produces has a consistent author when the repository declares none. */
|
|
@@ -114,18 +133,22 @@ const GIT_REPOSITORY_DISCOVERY_ENV_KEYS = [
|
|
|
114
133
|
*/
|
|
115
134
|
function sanitizeGitEnv(env) {
|
|
116
135
|
const sanitized = { ...env };
|
|
117
|
-
for (const key of GIT_REPOSITORY_DISCOVERY_ENV_KEYS)
|
|
136
|
+
for (const key of GIT_REPOSITORY_DISCOVERY_ENV_KEYS) Reflect.deleteProperty(sanitized, key);
|
|
118
137
|
return sanitized;
|
|
119
138
|
}
|
|
120
139
|
const SANITIZED_PROCESS_GIT_ENV = sanitizeGitEnv(process.env);
|
|
140
|
+
/** `git log --name-only` over everything since a package's last release tag can legitimately produce tens of megabytes of path output on a long-lived monorepo, well past execFile's default buffer, failing on exactly the big workspaces this tool exists for -- hence the generous 100 MiB default, rather than execFile's own. */
|
|
141
|
+
async function execGit(args, cwd, maxBuffer = 104857600) {
|
|
142
|
+
const { stdout } = await execFile("git", args, {
|
|
143
|
+
cwd,
|
|
144
|
+
maxBuffer,
|
|
145
|
+
env: SANITIZED_PROCESS_GIT_ENV
|
|
146
|
+
});
|
|
147
|
+
return stdout;
|
|
148
|
+
}
|
|
121
149
|
async function git(args, options) {
|
|
122
150
|
try {
|
|
123
|
-
|
|
124
|
-
cwd: options.cwd,
|
|
125
|
-
maxBuffer: GIT_MAX_BUFFER_BYTES,
|
|
126
|
-
env: SANITIZED_PROCESS_GIT_ENV
|
|
127
|
-
});
|
|
128
|
-
return stdout;
|
|
151
|
+
return await execGit(args, options.cwd);
|
|
129
152
|
} catch (cause) {
|
|
130
153
|
throw toGitCommandError(args, options.cwd, cause);
|
|
131
154
|
}
|
|
@@ -239,9 +262,11 @@ async function workingTreeChanges(options) {
|
|
|
239
262
|
const paths = [];
|
|
240
263
|
for (let index = 0; index < tokens.length; index += 1) {
|
|
241
264
|
const entry = tokens[index];
|
|
242
|
-
if (entry === void 0
|
|
265
|
+
if (entry === void 0) continue;
|
|
243
266
|
const statusCode = entry.slice(0, 2);
|
|
244
|
-
|
|
267
|
+
const path = entry.slice(3);
|
|
268
|
+
if (path === "") continue;
|
|
269
|
+
paths.push(path);
|
|
245
270
|
if (statusCode.includes("R") || statusCode.includes("C")) index += 1;
|
|
246
271
|
}
|
|
247
272
|
return paths;
|
|
@@ -306,7 +331,7 @@ async function readManifest(path) {
|
|
|
306
331
|
const { name, version } = parsed;
|
|
307
332
|
if (typeof name !== "string" || name.length === 0) throw new WorkspaceDiscoveryError(`${path} has no "name". Every workspace package needs a name: releases are ordered, tagged, and matched to dependents by it.`);
|
|
308
333
|
const validity = (0, validate_npm_package_name.default)(name);
|
|
309
|
-
if (!validity.validForOldPackages) throw new WorkspaceDiscoveryError(`${path} has an invalid "name" ("${name}"): ${
|
|
334
|
+
if (!validity.validForOldPackages) throw new WorkspaceDiscoveryError(`${path} has an invalid "name" ("${name}"): ${validity.errors.join("; ")}`);
|
|
310
335
|
if (typeof version !== "string" || version.length === 0) throw new WorkspaceDiscoveryError(`${path} has no "version".`);
|
|
311
336
|
const dependencies = /* @__PURE__ */ new Map();
|
|
312
337
|
for (const field of DEPENDENCY_FIELDS) {
|
|
@@ -609,6 +634,10 @@ function matchTrailerLine(message, key) {
|
|
|
609
634
|
}
|
|
610
635
|
//#endregion
|
|
611
636
|
//#region src/plugins.ts
|
|
637
|
+
/** semantic-release's own `getLastRelease` returns `{}` for a package with no prior tag -- not `undefined`, and not a fully-populated `LastRelease` -- contradicting the `gitHead: string` its own type declares. Narrows structurally rather than trusting that declared type, so a first-release context's `lastRelease` (correctly, at runtime) never claims a `gitHead` it does not have. */
|
|
638
|
+
function hasGitHead(lastRelease) {
|
|
639
|
+
return typeof lastRelease === "object" && lastRelease !== null && "gitHead" in lastRelease && typeof lastRelease.gitHead === "string";
|
|
640
|
+
}
|
|
612
641
|
/** The standard publish pipeline this orchestrator coordinates when a workspace configures none of its own. Every entry reuses the corresponding official plugin -- the orchestrator scopes and sequences them per package, it does not reimplement npm publishing, GitHub release creation, or changelog writing. */
|
|
613
642
|
const DEFAULT_PUBLISH_PLUGINS = [
|
|
614
643
|
"@semantic-release/changelog",
|
|
@@ -619,7 +648,7 @@ const DEFAULT_PUBLISH_PLUGINS = [
|
|
|
619
648
|
message: "chore(release): ${nextRelease.gitTag} [skip ci]"
|
|
620
649
|
}]
|
|
621
650
|
];
|
|
622
|
-
/** The standard publish pipeline for `commitStrategy: 'single'`: the same as `DEFAULT_PUBLISH_PLUGINS` minus
|
|
651
|
+
/** The standard publish pipeline for `commitStrategy: 'single'`: the same as `DEFAULT_PUBLISH_PLUGINS` minus `@semantic-release/git`, which that mode never runs -- see `resolvePublishPlugins`'s `forbidGitPlugin` option for why it is rejected outright rather than merely unused. Single-commit mode does its own committing (one combined commit for every released package), so a `prepare`-step git plugin here would create the very per-package commits that mode exists to avoid. */
|
|
623
652
|
const SINGLE_COMMIT_DEFAULT_PUBLISH_PLUGINS = [
|
|
624
653
|
"@semantic-release/changelog",
|
|
625
654
|
"@semantic-release/npm",
|
|
@@ -629,14 +658,14 @@ const STEP_PLUGINS_THE_ORCHESTRATOR_OWNS = /* @__PURE__ */ new Set(["@semantic-r
|
|
|
629
658
|
/**
|
|
630
659
|
* Builds the per-package `analyzeCommits` and `generateNotes` functions handed to semantic-release as inline plugins.
|
|
631
660
|
*
|
|
632
|
-
* Both apply the same path scoping before delegating to the real
|
|
661
|
+
* Both apply the same path scoping before delegating to the real `@semantic-release/commit-analyzer` and `@semantic-release/release-notes-generator`: the commit list semantic-release already fetched for the release range is filtered down to commits whose `git log --name-only` file list intersects the package's own directory, and only the filtered list reaches the standard plugin. Conventional-commit parsing and changelog formatting stay entirely inside the standard plugins.
|
|
633
662
|
*
|
|
634
663
|
* The `analyzeCommits` wrapper carries one addition beyond filtering: when the standard analyzer finds no releasable commits but a workspace dependency range of the package's has changed, it returns 'patch' anyway. A dependent whose only change is a dependency bump still needs a release for that range to reach the registry. "Has changed" is read from two sources, merged: bumps recorded in memory earlier in the current run (`scope.bumps`), and bumps recorded in the package's own filtered commit history via the trailer `dependency-bump-commit.ts` writes and reads -- the latter is what lets a run that starts after a previous run already committed and pushed the bump (a crash recovery, or simply a later run) reach the same decision, rather than depending on state that existed only inside the process that made the commit.
|
|
635
664
|
*/
|
|
636
665
|
function createScopedPlugins(scope) {
|
|
637
666
|
let cached;
|
|
638
667
|
async function commitsForPackage(context) {
|
|
639
|
-
const from = context.lastRelease
|
|
668
|
+
const from = hasGitHead(context.lastRelease) ? context.lastRelease.gitHead : void 0;
|
|
640
669
|
if (cached === void 0) cached = {
|
|
641
670
|
from,
|
|
642
671
|
paths: changedPathsSince(from, { cwd: context.cwd })
|
|
@@ -656,10 +685,10 @@ function createScopedPlugins(scope) {
|
|
|
656
685
|
...context,
|
|
657
686
|
commits
|
|
658
687
|
});
|
|
659
|
-
if (type) return type;
|
|
688
|
+
if (typeof type === "string") return type;
|
|
660
689
|
const bumps = mergeDependencyBumps(scope.bumps.bumpsFor(scope.pkg.name), commits);
|
|
661
690
|
if (bumps.length === 0) return false;
|
|
662
|
-
context.logger.log(`No releasable commits under ${scope.pkg.relativeDirectory}, but ${bumps.length === 1 ? "a workspace dependency range changed" : `${bumps.length} workspace dependency ranges changed`}; forcing a patch release.`);
|
|
691
|
+
context.logger.log(`No releasable commits under ${scope.pkg.relativeDirectory}, but ${bumps.length === 1 ? "a workspace dependency range changed" : `${String(bumps.length)} workspace dependency ranges changed`}; forcing a patch release.`);
|
|
663
692
|
return "patch";
|
|
664
693
|
},
|
|
665
694
|
async generateNotes(_pluginConfig, context) {
|
|
@@ -675,7 +704,7 @@ function createScopedPlugins(scope) {
|
|
|
675
704
|
"",
|
|
676
705
|
...bumps.map((bump) => describeDependencyBump(bump))
|
|
677
706
|
].join("\n");
|
|
678
|
-
return notes ? `${notes}\n\n${section}` : section;
|
|
707
|
+
return typeof notes === "string" ? `${notes}\n\n${section}` : section;
|
|
679
708
|
}
|
|
680
709
|
};
|
|
681
710
|
}
|
|
@@ -747,7 +776,6 @@ function parsePublishPluginSpec(spec) {
|
|
|
747
776
|
}
|
|
748
777
|
//#endregion
|
|
749
778
|
//#region src/pnpm.ts
|
|
750
|
-
const execFileAsync = (0, node_util.promisify)(node_child_process.execFile);
|
|
751
779
|
/**
|
|
752
780
|
* Regenerates `pnpm-lock.yaml` for the whole workspace from the manifests currently on disk, without touching `node_modules` or installing anything -- the same lockfile-refresh step a contributor runs by hand after editing a `package.json` dependency range.
|
|
753
781
|
*
|
|
@@ -755,7 +783,7 @@ const execFileAsync = (0, node_util.promisify)(node_child_process.execFile);
|
|
|
755
783
|
*/
|
|
756
784
|
async function regenerateLockfile(options) {
|
|
757
785
|
try {
|
|
758
|
-
await
|
|
786
|
+
await execFile("pnpm", ["install", "--lockfile-only"], { cwd: options.cwd });
|
|
759
787
|
} catch (cause) {
|
|
760
788
|
const stderr = cause instanceof Error && "stderr" in cause && typeof cause.stderr === "string" ? cause.stderr.trim() : "";
|
|
761
789
|
const detail = stderr !== "" ? stderr : cause instanceof Error ? cause.message : String(cause);
|
|
@@ -771,7 +799,7 @@ async function regenerateLockfile(options) {
|
|
|
771
799
|
*
|
|
772
800
|
* 1. **Analyse** (this file's `analysePackage`): for every package, in topological order, run semantic-release with `dryRun: true` forced (regardless of the caller's own `dryRun` option) using the same path-scoped `analyzeCommits`/`generateNotes` wrapper `commitStrategy: 'per-package'` uses -- computing each package's next version and notes without writing, committing, tagging, or publishing anything. Cross-package dependency bumps are tracked purely in memory during this phase (`pendingBumps`), exactly as the per-package strategy tracks them for the span of one run; nothing is committed yet for a later run to recover from, because this strategy never leaves a partial commit for a crash to recover from in the first place -- either the whole combined commit lands, or nothing does.
|
|
773
801
|
* 2. **Verify** every released package's configured publish plugins' `verifyConditions` step (npm registry auth, GitHub token/repo access), before any file is written -- the same fail-fast-before-anything-releases discipline `validateDependencyRangeShapes` already applies to dependency ranges.
|
|
774
|
-
* 3. **Prepare**: for every released package, in topological order, apply any dependency-range bump its own manifest received (writing `package.json` directly, the same `writeDependencyRange` the per-package strategy uses), then run every configured publish plugin's own `prepare` step generically (whichever it defines --
|
|
802
|
+
* 3. **Prepare**: for every released package, in topological order, apply any dependency-range bump its own manifest received (writing `package.json` directly, the same `writeDependencyRange` the per-package strategy uses), then run every configured publish plugin's own `prepare` step generically (whichever it defines -- `@semantic-release/npm` bumps `package.json`'s version, `@semantic-release/changelog` writes `CHANGELOG.md`). `@semantic-release/git` is rejected outright from this mode's plugin list (see `resolvePublishPlugins`'s `forbidGitPlugin`), since its own `prepare` step would create exactly the per-package commit this mode exists to avoid. The lockfile is regenerated once at the end, not once per bump, since `pnpm install --lockfile-only` recomputes it from whatever is on disk regardless of how many manifests changed.
|
|
775
803
|
* 4. **Commit**: discover every file phase 3 touched via `git status` (rather than predicting filenames per plugin), make one commit, tag it once per released package (`name@version`, lightweight, matching semantic-release's own tag form), and push the commit and every tag together.
|
|
776
804
|
* 5. **Publish**: for every released package, in topological order, call each configured plugin's own `publish` step directly (not through semantic-release's top-level orchestrator -- see the note below), then `success`.
|
|
777
805
|
*
|
|
@@ -788,7 +816,7 @@ async function releaseWorkspaceSingleCommit(options) {
|
|
|
788
816
|
const graph = buildDependencyGraph(workspace.packages);
|
|
789
817
|
validateDependencyRangeShapes(graph);
|
|
790
818
|
const order = topologicalOrder(graph);
|
|
791
|
-
log(`${packageName}: ${order.length} packages in release order: ${order.join(" -> ")} (commitStrategy: single)`);
|
|
819
|
+
log(`${packageName}: ${String(order.length)} packages in release order: ${order.join(" -> ")} (commitStrategy: single)`);
|
|
792
820
|
const resolvedPlugins = resolvePublishPlugins(options.plugins ?? SINGLE_COMMIT_DEFAULT_PUBLISH_PLUGINS, workspace.root, {
|
|
793
821
|
requireGitPlugin: false,
|
|
794
822
|
forbidGitPlugin: true
|
|
@@ -814,19 +842,26 @@ async function releaseWorkspaceSingleCommit(options) {
|
|
|
814
842
|
bumpsForThisPackage,
|
|
815
843
|
env,
|
|
816
844
|
branches: options.branches,
|
|
817
|
-
onCommitsResolved: (commits) =>
|
|
845
|
+
onCommitsResolved: (commits) => {
|
|
846
|
+
capturedCommits.set(name, commits);
|
|
847
|
+
},
|
|
818
848
|
onContextCaptured: (context) => {
|
|
819
849
|
captured.branch = context.branch;
|
|
820
850
|
captured.repositoryUrl = context.repositoryUrl;
|
|
821
851
|
}
|
|
822
852
|
});
|
|
823
|
-
outcomes.push({
|
|
853
|
+
outcomes.push(nextRelease === void 0 ? {
|
|
824
854
|
name,
|
|
825
855
|
directory: pkg.directory,
|
|
826
|
-
released:
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
856
|
+
released: false,
|
|
857
|
+
dependencyBumps: bumpsForThisPackage
|
|
858
|
+
} : {
|
|
859
|
+
name,
|
|
860
|
+
directory: pkg.directory,
|
|
861
|
+
released: true,
|
|
862
|
+
version: nextRelease.version,
|
|
863
|
+
gitTag: nextRelease.gitTag,
|
|
864
|
+
type: nextRelease.type,
|
|
830
865
|
dependencyBumps: bumpsForThisPackage
|
|
831
866
|
});
|
|
832
867
|
if (nextRelease === void 0) {
|
|
@@ -854,7 +889,7 @@ async function releaseWorkspaceSingleCommit(options) {
|
|
|
854
889
|
};
|
|
855
890
|
const branch = captured.branch;
|
|
856
891
|
const repositoryUrl = captured.repositoryUrl;
|
|
857
|
-
if (branch === void 0 || repositoryUrl === void 0) throw new ReleaseConfigurationError(`Internal error: ${packageName} analysed ${planned.length} package release(s) but never captured a branch/repositoryUrl from semantic-release's own context. This should be impossible when at least one package releases.`);
|
|
892
|
+
if (branch === void 0 || repositoryUrl === void 0) throw new ReleaseConfigurationError(`Internal error: ${packageName} analysed ${String(planned.length)} package release(s) but never captured a branch/repositoryUrl from semantic-release's own context. This should be impossible when at least one package releases.`);
|
|
858
893
|
const shared = {
|
|
859
894
|
env,
|
|
860
895
|
branch,
|
|
@@ -880,7 +915,7 @@ async function releaseWorkspaceSingleCommit(options) {
|
|
|
880
915
|
}
|
|
881
916
|
if (anyRangeRewritten) await regenerateLockfile({ cwd: workspace.root });
|
|
882
917
|
const touchedPaths = await workingTreeChanges({ cwd: repoRoot });
|
|
883
|
-
if (touchedPaths.length === 0) throw new ReleaseConfigurationError(`${packageName}: analysis planned ${planned.length} release(s), but no files changed while preparing them. Every configured publish plugin's own "prepare" step (bumping package.json, writing CHANGELOG.md) produced nothing to commit -- check the plugin list includes something that writes the version, e.g. @semantic-release/npm.`);
|
|
918
|
+
if (touchedPaths.length === 0) throw new ReleaseConfigurationError(`${packageName}: analysis planned ${String(planned.length)} release(s), but no files changed while preparing them. Every configured publish plugin's own "prepare" step (bumping package.json, writing CHANGELOG.md) produced nothing to commit -- check the plugin list includes something that writes the version, e.g. @semantic-release/npm.`);
|
|
884
919
|
const identity = await resolveCommitIdentity({ cwd: repoRoot });
|
|
885
920
|
await commitFiles(touchedPaths, describeCombinedCommit(planned), {
|
|
886
921
|
cwd: repoRoot,
|
|
@@ -890,7 +925,7 @@ async function releaseWorkspaceSingleCommit(options) {
|
|
|
890
925
|
const tagNames = planned.map((release) => release.gitTag);
|
|
891
926
|
for (const tagName of tagNames) await createTag(tagName, commitSha, { cwd: repoRoot });
|
|
892
927
|
await pushHeadAndTags(tagNames, { cwd: repoRoot });
|
|
893
|
-
log(`${packageName}: committed ${commitSha} and pushed ${tagNames.length} tag(s): ${tagNames.join(", ")}`);
|
|
928
|
+
log(`${packageName}: committed ${commitSha} and pushed ${String(tagNames.length)} tag(s): ${tagNames.join(", ")}`);
|
|
894
929
|
for (const release of planned) {
|
|
895
930
|
const releases = [];
|
|
896
931
|
for (const [modulePath, pluginConfig] of resolvedPlugins) {
|
|
@@ -982,10 +1017,18 @@ function describeCombinedCommit(planned) {
|
|
|
982
1017
|
}
|
|
983
1018
|
function buildPluginContext(release, shared, capturedCommits, releases) {
|
|
984
1019
|
const logger = {
|
|
985
|
-
log: (...args) =>
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
1020
|
+
log: (...args) => {
|
|
1021
|
+
shared.log(`[${release.pkg.name}] ${args.map(String).join(" ")}`);
|
|
1022
|
+
},
|
|
1023
|
+
warn: (...args) => {
|
|
1024
|
+
shared.log(`[${release.pkg.name}] warn: ${args.map(String).join(" ")}`);
|
|
1025
|
+
},
|
|
1026
|
+
error: (...args) => {
|
|
1027
|
+
shared.log(`[${release.pkg.name}] error: ${args.map(String).join(" ")}`);
|
|
1028
|
+
},
|
|
1029
|
+
success: (...args) => {
|
|
1030
|
+
shared.log(`[${release.pkg.name}] ${args.map(String).join(" ")}`);
|
|
1031
|
+
}
|
|
989
1032
|
};
|
|
990
1033
|
return {
|
|
991
1034
|
cwd: release.pkg.directory,
|
|
@@ -1045,7 +1088,7 @@ async function detachWorkspaceRelease(options) {
|
|
|
1045
1088
|
const graph = buildDependencyGraph(workspace.packages);
|
|
1046
1089
|
validateDependencyRangeShapes(graph);
|
|
1047
1090
|
const order = topologicalOrder(graph);
|
|
1048
|
-
log(`${packageName}: ${order.length} packages in release order (gated -- tag only, publish deferred): ${order.join(" -> ")}`);
|
|
1091
|
+
log(`${packageName}: ${String(order.length)} packages in release order (gated -- tag only, publish deferred): ${order.join(" -> ")}`);
|
|
1049
1092
|
const publishPlugins = resolvePublishPlugins(options.plugins ?? DEFAULT_PUBLISH_PLUGINS, workspace.root, { requireGitPlugin: !dryRun });
|
|
1050
1093
|
const analyzeCommitsConfig = options.analyzeCommits ?? {};
|
|
1051
1094
|
const generateNotesConfig = options.generateNotes ?? {};
|
|
@@ -1067,15 +1110,20 @@ async function detachWorkspaceRelease(options) {
|
|
|
1067
1110
|
});
|
|
1068
1111
|
return {
|
|
1069
1112
|
order,
|
|
1070
|
-
packages: entries.map((entry) =>
|
|
1113
|
+
packages: entries.map((entry) => entry.result === null ? {
|
|
1114
|
+
name: entry.name,
|
|
1115
|
+
directory: entry.directory,
|
|
1116
|
+
released: false,
|
|
1117
|
+
dependencyBumps: entry.dependencyBumps
|
|
1118
|
+
} : {
|
|
1071
1119
|
name: entry.name,
|
|
1072
1120
|
directory: entry.directory,
|
|
1073
|
-
released:
|
|
1074
|
-
version: entry.result
|
|
1075
|
-
gitTag: entry.result
|
|
1076
|
-
type: entry.result
|
|
1121
|
+
released: true,
|
|
1122
|
+
version: entry.result.nextRelease.version,
|
|
1123
|
+
gitTag: entry.result.nextRelease.gitTag,
|
|
1124
|
+
type: entry.result.nextRelease.type,
|
|
1077
1125
|
dependencyBumps: entry.dependencyBumps
|
|
1078
|
-
})
|
|
1126
|
+
}),
|
|
1079
1127
|
detached: entries.map((entry) => ({
|
|
1080
1128
|
name: entry.name,
|
|
1081
1129
|
relativeDirectory: entry.relativeDirectory,
|
|
@@ -1125,9 +1173,6 @@ async function resumeWorkspaceRelease(options) {
|
|
|
1125
1173
|
name: entry.name,
|
|
1126
1174
|
directory: (0, node_path.resolve)(root, entry.relativeDirectory),
|
|
1127
1175
|
released: false,
|
|
1128
|
-
version: void 0,
|
|
1129
|
-
gitTag: void 0,
|
|
1130
|
-
type: void 0,
|
|
1131
1176
|
dependencyBumps: entry.dependencyBumps
|
|
1132
1177
|
});
|
|
1133
1178
|
continue;
|
|
@@ -1143,7 +1188,7 @@ async function resumeWorkspaceRelease(options) {
|
|
|
1143
1188
|
} catch (cause) {
|
|
1144
1189
|
throw new WorkspaceReleaseError(`Resuming ${entry.name} failed: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
1145
1190
|
}
|
|
1146
|
-
log(`${entry.name}: published ${entry.state.nextRelease.gitTag} (${releases.length} publish plugin${releases.length === 1 ? "" : "s"} ran)`);
|
|
1191
|
+
log(`${entry.name}: published ${entry.state.nextRelease.gitTag} (${String(releases.length)} publish plugin${releases.length === 1 ? "" : "s"} ran)`);
|
|
1147
1192
|
packages.push({
|
|
1148
1193
|
name: entry.name,
|
|
1149
1194
|
directory,
|
|
@@ -1180,7 +1225,7 @@ async function releaseWorkspace(options = {}) {
|
|
|
1180
1225
|
const graph = buildDependencyGraph(workspace.packages);
|
|
1181
1226
|
validateDependencyRangeShapes(graph);
|
|
1182
1227
|
const order = topologicalOrder(graph);
|
|
1183
|
-
log(`${packageName}: ${order.length} packages in release order: ${order.join(" -> ")}`);
|
|
1228
|
+
log(`${packageName}: ${String(order.length)} packages in release order: ${order.join(" -> ")}`);
|
|
1184
1229
|
const publishPlugins = resolvePublishPlugins(options.plugins ?? DEFAULT_PUBLISH_PLUGINS, workspace.root, { requireGitPlugin: !dryRun });
|
|
1185
1230
|
const analyzeCommitsConfig = options.analyzeCommits ?? {};
|
|
1186
1231
|
const generateNotesConfig = options.generateNotes ?? {};
|
|
@@ -1204,13 +1249,18 @@ async function releaseWorkspace(options = {}) {
|
|
|
1204
1249
|
};
|
|
1205
1250
|
})).map((entry) => {
|
|
1206
1251
|
const nextRelease = entry.result === false ? void 0 : entry.result.nextRelease;
|
|
1207
|
-
return {
|
|
1252
|
+
return nextRelease === void 0 ? {
|
|
1208
1253
|
name: entry.name,
|
|
1209
1254
|
directory: entry.directory,
|
|
1210
|
-
released:
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1255
|
+
released: false,
|
|
1256
|
+
dependencyBumps: entry.dependencyBumps
|
|
1257
|
+
} : {
|
|
1258
|
+
name: entry.name,
|
|
1259
|
+
directory: entry.directory,
|
|
1260
|
+
released: true,
|
|
1261
|
+
version: nextRelease.version,
|
|
1262
|
+
gitTag: nextRelease.gitTag,
|
|
1263
|
+
type: nextRelease.type,
|
|
1214
1264
|
dependencyBumps: entry.dependencyBumps
|
|
1215
1265
|
};
|
|
1216
1266
|
})
|
package/dist/index.d.cts
CHANGED
|
@@ -139,13 +139,13 @@ interface DependencyBump {
|
|
|
139
139
|
}
|
|
140
140
|
/** What the scoped plugins need to know about bumps recorded so far in the run, for the package they are about to analyse. */
|
|
141
141
|
interface DependencyBumpSource {
|
|
142
|
-
bumpsFor(dependent: string)
|
|
142
|
+
bumpsFor: (dependent: string) => readonly DependencyBump[];
|
|
143
143
|
}
|
|
144
144
|
/** A publish-pipeline plugin entry as the orchestrator accepts it: a module name, optionally with a config object. */
|
|
145
145
|
type PublishPluginSpec = string | readonly [string] | readonly [string, Record<string, unknown>];
|
|
146
146
|
/** The standard publish pipeline this orchestrator coordinates when a workspace configures none of its own. Every entry reuses the corresponding official plugin -- the orchestrator scopes and sequences them per package, it does not reimplement npm publishing, GitHub release creation, or changelog writing. */
|
|
147
147
|
export declare const DEFAULT_PUBLISH_PLUGINS: readonly PublishPluginSpec[];
|
|
148
|
-
/** The standard publish pipeline for `commitStrategy: 'single'`: the same as `DEFAULT_PUBLISH_PLUGINS` minus
|
|
148
|
+
/** The standard publish pipeline for `commitStrategy: 'single'`: the same as `DEFAULT_PUBLISH_PLUGINS` minus `@semantic-release/git`, which that mode never runs -- see `resolvePublishPlugins`'s `forbidGitPlugin` option for why it is rejected outright rather than merely unused. Single-commit mode does its own committing (one combined commit for every released package), so a `prepare`-step git plugin here would create the very per-package commits that mode exists to avoid. */
|
|
149
149
|
export declare const SINGLE_COMMIT_DEFAULT_PUBLISH_PLUGINS: readonly PublishPluginSpec[];
|
|
150
150
|
interface ScopedPlugins {
|
|
151
151
|
readonly analyzeCommits: (pluginConfig: Record<string, unknown>, context: AnalyzeCommitsContext & {
|
|
@@ -158,7 +158,7 @@ interface ScopedPlugins {
|
|
|
158
158
|
/**
|
|
159
159
|
* Builds the per-package `analyzeCommits` and `generateNotes` functions handed to semantic-release as inline plugins.
|
|
160
160
|
*
|
|
161
|
-
* Both apply the same path scoping before delegating to the real
|
|
161
|
+
* Both apply the same path scoping before delegating to the real `@semantic-release/commit-analyzer` and `@semantic-release/release-notes-generator`: the commit list semantic-release already fetched for the release range is filtered down to commits whose `git log --name-only` file list intersects the package's own directory, and only the filtered list reaches the standard plugin. Conventional-commit parsing and changelog formatting stay entirely inside the standard plugins.
|
|
162
162
|
*
|
|
163
163
|
* The `analyzeCommits` wrapper carries one addition beyond filtering: when the standard analyzer finds no releasable commits but a workspace dependency range of the package's has changed, it returns 'patch' anyway. A dependent whose only change is a dependency bump still needs a release for that range to reach the registry. "Has changed" is read from two sources, merged: bumps recorded in memory earlier in the current run (`scope.bumps`), and bumps recorded in the package's own filtered commit history via the trailer `dependency-bump-commit.ts` writes and reads -- the latter is what lets a run that starts after a previous run already committed and pushed the bump (a crash recovery, or simply a later run) reach the same decision, rather than depending on state that existed only inside the process that made the commit.
|
|
164
164
|
*/
|
|
@@ -226,11 +226,11 @@ interface ReleaseWorkspaceOptions {
|
|
|
226
226
|
readonly dryRun?: boolean;
|
|
227
227
|
/** Release branch configuration for semantic-release. Defaults to semantic-release's own default branch list. */
|
|
228
228
|
readonly branches?: readonly BranchSpec[];
|
|
229
|
-
/** Publish-pipeline plugins (changelog, npm, GitHub, git), each scoped per package by semantic-release's own `cwd`. Defaults to the standard pipeline in DEFAULT_PUBLISH_PLUGINS for `commitStrategy: 'per-package'`, or SINGLE_COMMIT_DEFAULT_PUBLISH_PLUGINS (the same list minus
|
|
229
|
+
/** Publish-pipeline plugins (changelog, npm, GitHub, git), each scoped per package by semantic-release's own `cwd`. Defaults to the standard pipeline in DEFAULT_PUBLISH_PLUGINS for `commitStrategy: 'per-package'`, or SINGLE_COMMIT_DEFAULT_PUBLISH_PLUGINS (the same list minus `@semantic-release/git`) for `commitStrategy: 'single'`. */
|
|
230
230
|
readonly plugins?: readonly PublishPluginSpec[];
|
|
231
|
-
/** Options for the wrapped
|
|
231
|
+
/** Options for the wrapped `@semantic-release/commit-analyzer`, applied per package after path filtering. */
|
|
232
232
|
readonly analyzeCommits?: Record<string, unknown>;
|
|
233
|
-
/** Options for the wrapped
|
|
233
|
+
/** Options for the wrapped `@semantic-release/release-notes-generator`, applied per package after path filtering. */
|
|
234
234
|
readonly generateNotes?: Record<string, unknown>;
|
|
235
235
|
/** Progress sink for the orchestrator's own narration (semantic-release logs its own detail). Defaults to `console.log`. */
|
|
236
236
|
readonly log?: (message: string) => void;
|
|
@@ -247,17 +247,22 @@ interface AppliedDependencyBump extends DependencyBump {
|
|
|
247
247
|
/** Which manifest field held the range that was rewritten. */
|
|
248
248
|
readonly field: DependencyField;
|
|
249
249
|
}
|
|
250
|
-
|
|
250
|
+
type PackageReleaseOutcome = {
|
|
251
251
|
readonly name: string;
|
|
252
252
|
readonly directory: string;
|
|
253
|
-
readonly released: boolean;
|
|
254
|
-
readonly version: string | undefined;
|
|
255
|
-
readonly gitTag: string | undefined;
|
|
256
|
-
/** The semantic-release release type ('minor', 'patch', ...), including the forced 'patch' of a dependency-bump-only release. */
|
|
257
|
-
readonly type: string | undefined;
|
|
258
253
|
/** Dependency ranges rewritten in this package's own manifest because a workspace dependency released earlier in the run. */
|
|
259
254
|
readonly dependencyBumps: readonly AppliedDependencyBump[];
|
|
260
|
-
}
|
|
255
|
+
} & ({
|
|
256
|
+
readonly released: true;
|
|
257
|
+
readonly version: string;
|
|
258
|
+
readonly gitTag: string;
|
|
259
|
+
readonly type: string;
|
|
260
|
+
} | {
|
|
261
|
+
readonly released: false;
|
|
262
|
+
readonly version?: undefined;
|
|
263
|
+
readonly gitTag?: undefined;
|
|
264
|
+
readonly type?: undefined;
|
|
265
|
+
});
|
|
261
266
|
interface WorkspaceReleaseOutcome {
|
|
262
267
|
/** The topological order the packages were released in. */
|
|
263
268
|
readonly order: readonly string[];
|
package/dist/index.d.ts
CHANGED
|
@@ -139,13 +139,13 @@ interface DependencyBump {
|
|
|
139
139
|
}
|
|
140
140
|
/** What the scoped plugins need to know about bumps recorded so far in the run, for the package they are about to analyse. */
|
|
141
141
|
interface DependencyBumpSource {
|
|
142
|
-
bumpsFor(dependent: string)
|
|
142
|
+
bumpsFor: (dependent: string) => readonly DependencyBump[];
|
|
143
143
|
}
|
|
144
144
|
/** A publish-pipeline plugin entry as the orchestrator accepts it: a module name, optionally with a config object. */
|
|
145
145
|
type PublishPluginSpec = string | readonly [string] | readonly [string, Record<string, unknown>];
|
|
146
146
|
/** The standard publish pipeline this orchestrator coordinates when a workspace configures none of its own. Every entry reuses the corresponding official plugin -- the orchestrator scopes and sequences them per package, it does not reimplement npm publishing, GitHub release creation, or changelog writing. */
|
|
147
147
|
export declare const DEFAULT_PUBLISH_PLUGINS: readonly PublishPluginSpec[];
|
|
148
|
-
/** The standard publish pipeline for `commitStrategy: 'single'`: the same as `DEFAULT_PUBLISH_PLUGINS` minus
|
|
148
|
+
/** The standard publish pipeline for `commitStrategy: 'single'`: the same as `DEFAULT_PUBLISH_PLUGINS` minus `@semantic-release/git`, which that mode never runs -- see `resolvePublishPlugins`'s `forbidGitPlugin` option for why it is rejected outright rather than merely unused. Single-commit mode does its own committing (one combined commit for every released package), so a `prepare`-step git plugin here would create the very per-package commits that mode exists to avoid. */
|
|
149
149
|
export declare const SINGLE_COMMIT_DEFAULT_PUBLISH_PLUGINS: readonly PublishPluginSpec[];
|
|
150
150
|
interface ScopedPlugins {
|
|
151
151
|
readonly analyzeCommits: (pluginConfig: Record<string, unknown>, context: AnalyzeCommitsContext & {
|
|
@@ -158,7 +158,7 @@ interface ScopedPlugins {
|
|
|
158
158
|
/**
|
|
159
159
|
* Builds the per-package `analyzeCommits` and `generateNotes` functions handed to semantic-release as inline plugins.
|
|
160
160
|
*
|
|
161
|
-
* Both apply the same path scoping before delegating to the real
|
|
161
|
+
* Both apply the same path scoping before delegating to the real `@semantic-release/commit-analyzer` and `@semantic-release/release-notes-generator`: the commit list semantic-release already fetched for the release range is filtered down to commits whose `git log --name-only` file list intersects the package's own directory, and only the filtered list reaches the standard plugin. Conventional-commit parsing and changelog formatting stay entirely inside the standard plugins.
|
|
162
162
|
*
|
|
163
163
|
* The `analyzeCommits` wrapper carries one addition beyond filtering: when the standard analyzer finds no releasable commits but a workspace dependency range of the package's has changed, it returns 'patch' anyway. A dependent whose only change is a dependency bump still needs a release for that range to reach the registry. "Has changed" is read from two sources, merged: bumps recorded in memory earlier in the current run (`scope.bumps`), and bumps recorded in the package's own filtered commit history via the trailer `dependency-bump-commit.ts` writes and reads -- the latter is what lets a run that starts after a previous run already committed and pushed the bump (a crash recovery, or simply a later run) reach the same decision, rather than depending on state that existed only inside the process that made the commit.
|
|
164
164
|
*/
|
|
@@ -226,11 +226,11 @@ interface ReleaseWorkspaceOptions {
|
|
|
226
226
|
readonly dryRun?: boolean;
|
|
227
227
|
/** Release branch configuration for semantic-release. Defaults to semantic-release's own default branch list. */
|
|
228
228
|
readonly branches?: readonly BranchSpec[];
|
|
229
|
-
/** Publish-pipeline plugins (changelog, npm, GitHub, git), each scoped per package by semantic-release's own `cwd`. Defaults to the standard pipeline in DEFAULT_PUBLISH_PLUGINS for `commitStrategy: 'per-package'`, or SINGLE_COMMIT_DEFAULT_PUBLISH_PLUGINS (the same list minus
|
|
229
|
+
/** Publish-pipeline plugins (changelog, npm, GitHub, git), each scoped per package by semantic-release's own `cwd`. Defaults to the standard pipeline in DEFAULT_PUBLISH_PLUGINS for `commitStrategy: 'per-package'`, or SINGLE_COMMIT_DEFAULT_PUBLISH_PLUGINS (the same list minus `@semantic-release/git`) for `commitStrategy: 'single'`. */
|
|
230
230
|
readonly plugins?: readonly PublishPluginSpec[];
|
|
231
|
-
/** Options for the wrapped
|
|
231
|
+
/** Options for the wrapped `@semantic-release/commit-analyzer`, applied per package after path filtering. */
|
|
232
232
|
readonly analyzeCommits?: Record<string, unknown>;
|
|
233
|
-
/** Options for the wrapped
|
|
233
|
+
/** Options for the wrapped `@semantic-release/release-notes-generator`, applied per package after path filtering. */
|
|
234
234
|
readonly generateNotes?: Record<string, unknown>;
|
|
235
235
|
/** Progress sink for the orchestrator's own narration (semantic-release logs its own detail). Defaults to `console.log`. */
|
|
236
236
|
readonly log?: (message: string) => void;
|
|
@@ -247,17 +247,22 @@ interface AppliedDependencyBump extends DependencyBump {
|
|
|
247
247
|
/** Which manifest field held the range that was rewritten. */
|
|
248
248
|
readonly field: DependencyField;
|
|
249
249
|
}
|
|
250
|
-
|
|
250
|
+
type PackageReleaseOutcome = {
|
|
251
251
|
readonly name: string;
|
|
252
252
|
readonly directory: string;
|
|
253
|
-
readonly released: boolean;
|
|
254
|
-
readonly version: string | undefined;
|
|
255
|
-
readonly gitTag: string | undefined;
|
|
256
|
-
/** The semantic-release release type ('minor', 'patch', ...), including the forced 'patch' of a dependency-bump-only release. */
|
|
257
|
-
readonly type: string | undefined;
|
|
258
253
|
/** Dependency ranges rewritten in this package's own manifest because a workspace dependency released earlier in the run. */
|
|
259
254
|
readonly dependencyBumps: readonly AppliedDependencyBump[];
|
|
260
|
-
}
|
|
255
|
+
} & ({
|
|
256
|
+
readonly released: true;
|
|
257
|
+
readonly version: string;
|
|
258
|
+
readonly gitTag: string;
|
|
259
|
+
readonly type: string;
|
|
260
|
+
} | {
|
|
261
|
+
readonly released: false;
|
|
262
|
+
readonly version?: undefined;
|
|
263
|
+
readonly gitTag?: undefined;
|
|
264
|
+
readonly type?: undefined;
|
|
265
|
+
});
|
|
261
266
|
interface WorkspaceReleaseOutcome {
|
|
262
267
|
/** The topological order the packages were released in. */
|
|
263
268
|
readonly order: readonly string[];
|