@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.js
CHANGED
|
@@ -4,7 +4,6 @@ import { dirname, relative, resolve, sep } from "node:path";
|
|
|
4
4
|
import { glob } from "tinyglobby";
|
|
5
5
|
import { parse } from "yaml";
|
|
6
6
|
import { execFile } from "node:child_process";
|
|
7
|
-
import { promisify } from "node:util";
|
|
8
7
|
import validateNpmPackageName from "validate-npm-package-name";
|
|
9
8
|
import { analyzeCommits } from "@semantic-release/commit-analyzer";
|
|
10
9
|
import { generateNotes } from "@semantic-release/release-notes-generator";
|
|
@@ -45,7 +44,7 @@ var ReleaseConfigurationError = class extends WorkspaceReleaseError {};
|
|
|
45
44
|
var GitCommandError = class extends WorkspaceReleaseError {
|
|
46
45
|
exitCode;
|
|
47
46
|
constructor(args, cwd, exitCode, detail) {
|
|
48
|
-
super(`git ${args.join(" ")} failed in ${cwd}${exitCode === void 0 ? "" : ` (exit ${exitCode})`}: ${detail}`);
|
|
47
|
+
super(`git ${args.join(" ")} failed in ${cwd}${exitCode === void 0 ? "" : ` (exit ${String(exitCode)})`}: ${detail}`);
|
|
49
48
|
this.exitCode = exitCode;
|
|
50
49
|
}
|
|
51
50
|
};
|
|
@@ -58,10 +57,30 @@ var PnpmCommandError = class extends WorkspaceReleaseError {
|
|
|
58
57
|
}
|
|
59
58
|
};
|
|
60
59
|
//#endregion
|
|
60
|
+
//#region src/exec-file.ts
|
|
61
|
+
/**
|
|
62
|
+
* 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.
|
|
63
|
+
*/
|
|
64
|
+
async function execFile$1(command, args, options) {
|
|
65
|
+
return new Promise((resolve, reject) => {
|
|
66
|
+
execFile(command, [...args], {
|
|
67
|
+
cwd: options.cwd,
|
|
68
|
+
env: options.env,
|
|
69
|
+
maxBuffer: options.maxBuffer
|
|
70
|
+
}, (error, stdout, stderr) => {
|
|
71
|
+
if (error) {
|
|
72
|
+
reject(error instanceof Error ? error : new Error(error.message));
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
resolve({
|
|
76
|
+
stdout,
|
|
77
|
+
stderr
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
//#endregion
|
|
61
83
|
//#region src/git.ts
|
|
62
|
-
const execFileAsync$1 = promisify(execFile);
|
|
63
|
-
/** `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. */
|
|
64
|
-
const GIT_MAX_BUFFER_BYTES = 104857600;
|
|
65
84
|
/** 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. */
|
|
66
85
|
const COMMIT_RECORD_SEPARATOR = "";
|
|
67
86
|
/** 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. */
|
|
@@ -89,18 +108,22 @@ const GIT_REPOSITORY_DISCOVERY_ENV_KEYS = [
|
|
|
89
108
|
*/
|
|
90
109
|
function sanitizeGitEnv(env) {
|
|
91
110
|
const sanitized = { ...env };
|
|
92
|
-
for (const key of GIT_REPOSITORY_DISCOVERY_ENV_KEYS)
|
|
111
|
+
for (const key of GIT_REPOSITORY_DISCOVERY_ENV_KEYS) Reflect.deleteProperty(sanitized, key);
|
|
93
112
|
return sanitized;
|
|
94
113
|
}
|
|
95
114
|
const SANITIZED_PROCESS_GIT_ENV = sanitizeGitEnv(process.env);
|
|
115
|
+
/** `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. */
|
|
116
|
+
async function execGit(args, cwd, maxBuffer = 104857600) {
|
|
117
|
+
const { stdout } = await execFile$1("git", args, {
|
|
118
|
+
cwd,
|
|
119
|
+
maxBuffer,
|
|
120
|
+
env: SANITIZED_PROCESS_GIT_ENV
|
|
121
|
+
});
|
|
122
|
+
return stdout;
|
|
123
|
+
}
|
|
96
124
|
async function git(args, options) {
|
|
97
125
|
try {
|
|
98
|
-
|
|
99
|
-
cwd: options.cwd,
|
|
100
|
-
maxBuffer: GIT_MAX_BUFFER_BYTES,
|
|
101
|
-
env: SANITIZED_PROCESS_GIT_ENV
|
|
102
|
-
});
|
|
103
|
-
return stdout;
|
|
126
|
+
return await execGit(args, options.cwd);
|
|
104
127
|
} catch (cause) {
|
|
105
128
|
throw toGitCommandError(args, options.cwd, cause);
|
|
106
129
|
}
|
|
@@ -214,9 +237,11 @@ async function workingTreeChanges(options) {
|
|
|
214
237
|
const paths = [];
|
|
215
238
|
for (let index = 0; index < tokens.length; index += 1) {
|
|
216
239
|
const entry = tokens[index];
|
|
217
|
-
if (entry === void 0
|
|
240
|
+
if (entry === void 0) continue;
|
|
218
241
|
const statusCode = entry.slice(0, 2);
|
|
219
|
-
|
|
242
|
+
const path = entry.slice(3);
|
|
243
|
+
if (path === "") continue;
|
|
244
|
+
paths.push(path);
|
|
220
245
|
if (statusCode.includes("R") || statusCode.includes("C")) index += 1;
|
|
221
246
|
}
|
|
222
247
|
return paths;
|
|
@@ -281,7 +306,7 @@ async function readManifest(path) {
|
|
|
281
306
|
const { name, version } = parsed;
|
|
282
307
|
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.`);
|
|
283
308
|
const validity = validateNpmPackageName(name);
|
|
284
|
-
if (!validity.validForOldPackages) throw new WorkspaceDiscoveryError(`${path} has an invalid "name" ("${name}"): ${
|
|
309
|
+
if (!validity.validForOldPackages) throw new WorkspaceDiscoveryError(`${path} has an invalid "name" ("${name}"): ${validity.errors.join("; ")}`);
|
|
285
310
|
if (typeof version !== "string" || version.length === 0) throw new WorkspaceDiscoveryError(`${path} has no "version".`);
|
|
286
311
|
const dependencies = /* @__PURE__ */ new Map();
|
|
287
312
|
for (const field of DEPENDENCY_FIELDS) {
|
|
@@ -584,6 +609,10 @@ function matchTrailerLine(message, key) {
|
|
|
584
609
|
}
|
|
585
610
|
//#endregion
|
|
586
611
|
//#region src/plugins.ts
|
|
612
|
+
/** 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. */
|
|
613
|
+
function hasGitHead(lastRelease) {
|
|
614
|
+
return typeof lastRelease === "object" && lastRelease !== null && "gitHead" in lastRelease && typeof lastRelease.gitHead === "string";
|
|
615
|
+
}
|
|
587
616
|
/** 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. */
|
|
588
617
|
const DEFAULT_PUBLISH_PLUGINS = [
|
|
589
618
|
"@semantic-release/changelog",
|
|
@@ -594,7 +623,7 @@ const DEFAULT_PUBLISH_PLUGINS = [
|
|
|
594
623
|
message: "chore(release): ${nextRelease.gitTag} [skip ci]"
|
|
595
624
|
}]
|
|
596
625
|
];
|
|
597
|
-
/** The standard publish pipeline for `commitStrategy: 'single'`: the same as `DEFAULT_PUBLISH_PLUGINS` minus
|
|
626
|
+
/** 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. */
|
|
598
627
|
const SINGLE_COMMIT_DEFAULT_PUBLISH_PLUGINS = [
|
|
599
628
|
"@semantic-release/changelog",
|
|
600
629
|
"@semantic-release/npm",
|
|
@@ -604,14 +633,14 @@ const STEP_PLUGINS_THE_ORCHESTRATOR_OWNS = /* @__PURE__ */ new Set(["@semantic-r
|
|
|
604
633
|
/**
|
|
605
634
|
* Builds the per-package `analyzeCommits` and `generateNotes` functions handed to semantic-release as inline plugins.
|
|
606
635
|
*
|
|
607
|
-
* Both apply the same path scoping before delegating to the real
|
|
636
|
+
* 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.
|
|
608
637
|
*
|
|
609
638
|
* 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.
|
|
610
639
|
*/
|
|
611
640
|
function createScopedPlugins(scope) {
|
|
612
641
|
let cached;
|
|
613
642
|
async function commitsForPackage(context) {
|
|
614
|
-
const from = context.lastRelease
|
|
643
|
+
const from = hasGitHead(context.lastRelease) ? context.lastRelease.gitHead : void 0;
|
|
615
644
|
if (cached === void 0) cached = {
|
|
616
645
|
from,
|
|
617
646
|
paths: changedPathsSince(from, { cwd: context.cwd })
|
|
@@ -631,10 +660,10 @@ function createScopedPlugins(scope) {
|
|
|
631
660
|
...context,
|
|
632
661
|
commits
|
|
633
662
|
});
|
|
634
|
-
if (type) return type;
|
|
663
|
+
if (typeof type === "string") return type;
|
|
635
664
|
const bumps = mergeDependencyBumps(scope.bumps.bumpsFor(scope.pkg.name), commits);
|
|
636
665
|
if (bumps.length === 0) return false;
|
|
637
|
-
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.`);
|
|
666
|
+
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.`);
|
|
638
667
|
return "patch";
|
|
639
668
|
},
|
|
640
669
|
async generateNotes(_pluginConfig, context) {
|
|
@@ -650,7 +679,7 @@ function createScopedPlugins(scope) {
|
|
|
650
679
|
"",
|
|
651
680
|
...bumps.map((bump) => describeDependencyBump(bump))
|
|
652
681
|
].join("\n");
|
|
653
|
-
return notes ? `${notes}\n\n${section}` : section;
|
|
682
|
+
return typeof notes === "string" ? `${notes}\n\n${section}` : section;
|
|
654
683
|
}
|
|
655
684
|
};
|
|
656
685
|
}
|
|
@@ -722,7 +751,6 @@ function parsePublishPluginSpec(spec) {
|
|
|
722
751
|
}
|
|
723
752
|
//#endregion
|
|
724
753
|
//#region src/pnpm.ts
|
|
725
|
-
const execFileAsync = promisify(execFile);
|
|
726
754
|
/**
|
|
727
755
|
* 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.
|
|
728
756
|
*
|
|
@@ -730,7 +758,7 @@ const execFileAsync = promisify(execFile);
|
|
|
730
758
|
*/
|
|
731
759
|
async function regenerateLockfile(options) {
|
|
732
760
|
try {
|
|
733
|
-
await
|
|
761
|
+
await execFile$1("pnpm", ["install", "--lockfile-only"], { cwd: options.cwd });
|
|
734
762
|
} catch (cause) {
|
|
735
763
|
const stderr = cause instanceof Error && "stderr" in cause && typeof cause.stderr === "string" ? cause.stderr.trim() : "";
|
|
736
764
|
const detail = stderr !== "" ? stderr : cause instanceof Error ? cause.message : String(cause);
|
|
@@ -746,7 +774,7 @@ async function regenerateLockfile(options) {
|
|
|
746
774
|
*
|
|
747
775
|
* 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.
|
|
748
776
|
* 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.
|
|
749
|
-
* 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 --
|
|
777
|
+
* 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.
|
|
750
778
|
* 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.
|
|
751
779
|
* 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`.
|
|
752
780
|
*
|
|
@@ -763,7 +791,7 @@ async function releaseWorkspaceSingleCommit(options) {
|
|
|
763
791
|
const graph = buildDependencyGraph(workspace.packages);
|
|
764
792
|
validateDependencyRangeShapes(graph);
|
|
765
793
|
const order = topologicalOrder(graph);
|
|
766
|
-
log(`${packageName}: ${order.length} packages in release order: ${order.join(" -> ")} (commitStrategy: single)`);
|
|
794
|
+
log(`${packageName}: ${String(order.length)} packages in release order: ${order.join(" -> ")} (commitStrategy: single)`);
|
|
767
795
|
const resolvedPlugins = resolvePublishPlugins(options.plugins ?? SINGLE_COMMIT_DEFAULT_PUBLISH_PLUGINS, workspace.root, {
|
|
768
796
|
requireGitPlugin: false,
|
|
769
797
|
forbidGitPlugin: true
|
|
@@ -789,19 +817,26 @@ async function releaseWorkspaceSingleCommit(options) {
|
|
|
789
817
|
bumpsForThisPackage,
|
|
790
818
|
env,
|
|
791
819
|
branches: options.branches,
|
|
792
|
-
onCommitsResolved: (commits) =>
|
|
820
|
+
onCommitsResolved: (commits) => {
|
|
821
|
+
capturedCommits.set(name, commits);
|
|
822
|
+
},
|
|
793
823
|
onContextCaptured: (context) => {
|
|
794
824
|
captured.branch = context.branch;
|
|
795
825
|
captured.repositoryUrl = context.repositoryUrl;
|
|
796
826
|
}
|
|
797
827
|
});
|
|
798
|
-
outcomes.push({
|
|
828
|
+
outcomes.push(nextRelease === void 0 ? {
|
|
799
829
|
name,
|
|
800
830
|
directory: pkg.directory,
|
|
801
|
-
released:
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
831
|
+
released: false,
|
|
832
|
+
dependencyBumps: bumpsForThisPackage
|
|
833
|
+
} : {
|
|
834
|
+
name,
|
|
835
|
+
directory: pkg.directory,
|
|
836
|
+
released: true,
|
|
837
|
+
version: nextRelease.version,
|
|
838
|
+
gitTag: nextRelease.gitTag,
|
|
839
|
+
type: nextRelease.type,
|
|
805
840
|
dependencyBumps: bumpsForThisPackage
|
|
806
841
|
});
|
|
807
842
|
if (nextRelease === void 0) {
|
|
@@ -829,7 +864,7 @@ async function releaseWorkspaceSingleCommit(options) {
|
|
|
829
864
|
};
|
|
830
865
|
const branch = captured.branch;
|
|
831
866
|
const repositoryUrl = captured.repositoryUrl;
|
|
832
|
-
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.`);
|
|
867
|
+
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.`);
|
|
833
868
|
const shared = {
|
|
834
869
|
env,
|
|
835
870
|
branch,
|
|
@@ -855,7 +890,7 @@ async function releaseWorkspaceSingleCommit(options) {
|
|
|
855
890
|
}
|
|
856
891
|
if (anyRangeRewritten) await regenerateLockfile({ cwd: workspace.root });
|
|
857
892
|
const touchedPaths = await workingTreeChanges({ cwd: repoRoot });
|
|
858
|
-
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.`);
|
|
893
|
+
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.`);
|
|
859
894
|
const identity = await resolveCommitIdentity({ cwd: repoRoot });
|
|
860
895
|
await commitFiles(touchedPaths, describeCombinedCommit(planned), {
|
|
861
896
|
cwd: repoRoot,
|
|
@@ -865,7 +900,7 @@ async function releaseWorkspaceSingleCommit(options) {
|
|
|
865
900
|
const tagNames = planned.map((release) => release.gitTag);
|
|
866
901
|
for (const tagName of tagNames) await createTag(tagName, commitSha, { cwd: repoRoot });
|
|
867
902
|
await pushHeadAndTags(tagNames, { cwd: repoRoot });
|
|
868
|
-
log(`${packageName}: committed ${commitSha} and pushed ${tagNames.length} tag(s): ${tagNames.join(", ")}`);
|
|
903
|
+
log(`${packageName}: committed ${commitSha} and pushed ${String(tagNames.length)} tag(s): ${tagNames.join(", ")}`);
|
|
869
904
|
for (const release of planned) {
|
|
870
905
|
const releases = [];
|
|
871
906
|
for (const [modulePath, pluginConfig] of resolvedPlugins) {
|
|
@@ -957,10 +992,18 @@ function describeCombinedCommit(planned) {
|
|
|
957
992
|
}
|
|
958
993
|
function buildPluginContext(release, shared, capturedCommits, releases) {
|
|
959
994
|
const logger = {
|
|
960
|
-
log: (...args) =>
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
995
|
+
log: (...args) => {
|
|
996
|
+
shared.log(`[${release.pkg.name}] ${args.map(String).join(" ")}`);
|
|
997
|
+
},
|
|
998
|
+
warn: (...args) => {
|
|
999
|
+
shared.log(`[${release.pkg.name}] warn: ${args.map(String).join(" ")}`);
|
|
1000
|
+
},
|
|
1001
|
+
error: (...args) => {
|
|
1002
|
+
shared.log(`[${release.pkg.name}] error: ${args.map(String).join(" ")}`);
|
|
1003
|
+
},
|
|
1004
|
+
success: (...args) => {
|
|
1005
|
+
shared.log(`[${release.pkg.name}] ${args.map(String).join(" ")}`);
|
|
1006
|
+
}
|
|
964
1007
|
};
|
|
965
1008
|
return {
|
|
966
1009
|
cwd: release.pkg.directory,
|
|
@@ -1020,7 +1063,7 @@ async function detachWorkspaceRelease(options) {
|
|
|
1020
1063
|
const graph = buildDependencyGraph(workspace.packages);
|
|
1021
1064
|
validateDependencyRangeShapes(graph);
|
|
1022
1065
|
const order = topologicalOrder(graph);
|
|
1023
|
-
log(`${packageName}: ${order.length} packages in release order (gated -- tag only, publish deferred): ${order.join(" -> ")}`);
|
|
1066
|
+
log(`${packageName}: ${String(order.length)} packages in release order (gated -- tag only, publish deferred): ${order.join(" -> ")}`);
|
|
1024
1067
|
const publishPlugins = resolvePublishPlugins(options.plugins ?? DEFAULT_PUBLISH_PLUGINS, workspace.root, { requireGitPlugin: !dryRun });
|
|
1025
1068
|
const analyzeCommitsConfig = options.analyzeCommits ?? {};
|
|
1026
1069
|
const generateNotesConfig = options.generateNotes ?? {};
|
|
@@ -1042,15 +1085,20 @@ async function detachWorkspaceRelease(options) {
|
|
|
1042
1085
|
});
|
|
1043
1086
|
return {
|
|
1044
1087
|
order,
|
|
1045
|
-
packages: entries.map((entry) =>
|
|
1088
|
+
packages: entries.map((entry) => entry.result === null ? {
|
|
1089
|
+
name: entry.name,
|
|
1090
|
+
directory: entry.directory,
|
|
1091
|
+
released: false,
|
|
1092
|
+
dependencyBumps: entry.dependencyBumps
|
|
1093
|
+
} : {
|
|
1046
1094
|
name: entry.name,
|
|
1047
1095
|
directory: entry.directory,
|
|
1048
|
-
released:
|
|
1049
|
-
version: entry.result
|
|
1050
|
-
gitTag: entry.result
|
|
1051
|
-
type: entry.result
|
|
1096
|
+
released: true,
|
|
1097
|
+
version: entry.result.nextRelease.version,
|
|
1098
|
+
gitTag: entry.result.nextRelease.gitTag,
|
|
1099
|
+
type: entry.result.nextRelease.type,
|
|
1052
1100
|
dependencyBumps: entry.dependencyBumps
|
|
1053
|
-
})
|
|
1101
|
+
}),
|
|
1054
1102
|
detached: entries.map((entry) => ({
|
|
1055
1103
|
name: entry.name,
|
|
1056
1104
|
relativeDirectory: entry.relativeDirectory,
|
|
@@ -1100,9 +1148,6 @@ async function resumeWorkspaceRelease(options) {
|
|
|
1100
1148
|
name: entry.name,
|
|
1101
1149
|
directory: resolve(root, entry.relativeDirectory),
|
|
1102
1150
|
released: false,
|
|
1103
|
-
version: void 0,
|
|
1104
|
-
gitTag: void 0,
|
|
1105
|
-
type: void 0,
|
|
1106
1151
|
dependencyBumps: entry.dependencyBumps
|
|
1107
1152
|
});
|
|
1108
1153
|
continue;
|
|
@@ -1118,7 +1163,7 @@ async function resumeWorkspaceRelease(options) {
|
|
|
1118
1163
|
} catch (cause) {
|
|
1119
1164
|
throw new WorkspaceReleaseError(`Resuming ${entry.name} failed: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
1120
1165
|
}
|
|
1121
|
-
log(`${entry.name}: published ${entry.state.nextRelease.gitTag} (${releases.length} publish plugin${releases.length === 1 ? "" : "s"} ran)`);
|
|
1166
|
+
log(`${entry.name}: published ${entry.state.nextRelease.gitTag} (${String(releases.length)} publish plugin${releases.length === 1 ? "" : "s"} ran)`);
|
|
1122
1167
|
packages.push({
|
|
1123
1168
|
name: entry.name,
|
|
1124
1169
|
directory,
|
|
@@ -1155,7 +1200,7 @@ async function releaseWorkspace(options = {}) {
|
|
|
1155
1200
|
const graph = buildDependencyGraph(workspace.packages);
|
|
1156
1201
|
validateDependencyRangeShapes(graph);
|
|
1157
1202
|
const order = topologicalOrder(graph);
|
|
1158
|
-
log(`${packageName}: ${order.length} packages in release order: ${order.join(" -> ")}`);
|
|
1203
|
+
log(`${packageName}: ${String(order.length)} packages in release order: ${order.join(" -> ")}`);
|
|
1159
1204
|
const publishPlugins = resolvePublishPlugins(options.plugins ?? DEFAULT_PUBLISH_PLUGINS, workspace.root, { requireGitPlugin: !dryRun });
|
|
1160
1205
|
const analyzeCommitsConfig = options.analyzeCommits ?? {};
|
|
1161
1206
|
const generateNotesConfig = options.generateNotes ?? {};
|
|
@@ -1179,13 +1224,18 @@ async function releaseWorkspace(options = {}) {
|
|
|
1179
1224
|
};
|
|
1180
1225
|
})).map((entry) => {
|
|
1181
1226
|
const nextRelease = entry.result === false ? void 0 : entry.result.nextRelease;
|
|
1182
|
-
return {
|
|
1227
|
+
return nextRelease === void 0 ? {
|
|
1183
1228
|
name: entry.name,
|
|
1184
1229
|
directory: entry.directory,
|
|
1185
|
-
released:
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1230
|
+
released: false,
|
|
1231
|
+
dependencyBumps: entry.dependencyBumps
|
|
1232
|
+
} : {
|
|
1233
|
+
name: entry.name,
|
|
1234
|
+
directory: entry.directory,
|
|
1235
|
+
released: true,
|
|
1236
|
+
version: nextRelease.version,
|
|
1237
|
+
gitTag: nextRelease.gitTag,
|
|
1238
|
+
type: nextRelease.type,
|
|
1189
1239
|
dependencyBumps: entry.dependencyBumps
|
|
1190
1240
|
};
|
|
1191
1241
|
})
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exadev/semantic-release-workspace",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.6",
|
|
4
4
|
"description": "Independent per-package semantic-release orchestration for pnpm workspaces, without lockstep versioning.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -75,7 +75,7 @@
|
|
|
75
75
|
"ci"
|
|
76
76
|
],
|
|
77
77
|
"license": "MIT",
|
|
78
|
-
"packageManager": "pnpm@
|
|
78
|
+
"packageManager": "pnpm@12.4.1+sha512.2e81e399d73fe8390dab25e06aa788ab7a5908248d2f5a370f82b481147a6a7a367bf8048f9a6fdb6460f21a66f0542dedb8b94ca2c8723596741920b1656d4c",
|
|
79
79
|
"dependencies": {
|
|
80
80
|
"@exadev/release-gate": "^1.0.0",
|
|
81
81
|
"commander": "^15.0.0",
|
|
@@ -89,7 +89,7 @@
|
|
|
89
89
|
"@commitlint/cli": "^21.2.2",
|
|
90
90
|
"@commitlint/config-conventional": "^21.2.2",
|
|
91
91
|
"@eslint/js": "^10.0.1",
|
|
92
|
-
"@exadev/eslint-config": "
|
|
92
|
+
"@exadev/eslint-config": "2.12.1",
|
|
93
93
|
"@semantic-release/changelog": "^7.0.0",
|
|
94
94
|
"@semantic-release/commit-analyzer": "^13.0.1",
|
|
95
95
|
"@semantic-release/git": "^11.0.1",
|