@exadev/semantic-release-workspace 3.1.29 → 3.2.1

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/index.js CHANGED
@@ -677,6 +677,65 @@ function mustGet(map, key, what) {
677
677
  return value;
678
678
  }
679
679
  //#endregion
680
+ //#region src/bundling.ts
681
+ /**
682
+ * Validates `bundlingPackages` against the workspace and works out, for every package, which workspace dependencies it bundles.
683
+ *
684
+ * A package named in `bundlingPackages` declares that its build inlines every workspace sibling it lists in `devDependencies`, so consumers never install those siblings from the registry and their code reaches consumers only through this package. When such a sibling releases, this package's release type is at least the sibling's shipped release type (a `feat` in the sibling releases this package as a minor), instead of the patch an ordinary dependency bump forces. Edges in `dependencies`, `peerDependencies` and `optionalDependencies` are installed by consumers, so they keep the forced patch.
685
+ *
686
+ * A private package's own release type never changes: it keeps the patch a dependency bump forces, so its versions and tags are exactly what they would be without `bundlingPackages`. What it gains is a carried release type (see `BundledDependencies.carried`), which lets a bundling package that lists only the private package still see the release type of a private sibling the private package depends on.
687
+ *
688
+ * `bundlingPackages` names public workspace packages whose builds bundle their workspace `devDependencies`; `undefined` or an empty list leaves every package's release type as it was. Returns one entry per package in the graph. Throws `ReleaseConfigurationError` when a name is not a package in the workspace, or names a private package, which never publishes and so ships nothing to consumers.
689
+ */
690
+ function resolveBundledDependencies(graph, bundlingPackages) {
691
+ const bundling = new Set(bundlingPackages);
692
+ const unknown = [...bundling].filter((name) => !graph.packages.has(name));
693
+ if (unknown.length > 0) throw new ReleaseConfigurationError(`bundlingPackages names ${unknown.map((name) => `"${name}"`).join(", ")}, which ${unknown.length === 1 ? "is" : "are"} not a package in this workspace. Packages: ${[...graph.packages.keys()].join(", ")}.`);
694
+ const privateNames = [...bundling].filter((name) => mustGet(graph.packages, name, "package").private);
695
+ if (privateNames.length > 0) throw new ReleaseConfigurationError(`bundlingPackages names ${privateNames.map((name) => `"${name}"`).join(", ")}, which ${privateNames.length === 1 ? "is" : "are"} private. A private package is never published, so nothing it bundles reaches a consumer through it; list the public package whose build inlines it instead.`);
696
+ const resolved = /* @__PURE__ */ new Map();
697
+ for (const [name, pkg] of graph.packages) {
698
+ const edges = mustGet(graph.dependencies, name, "dependency edges");
699
+ const releaseTypeFrom = new Set(edges.filter((edge) => bundling.has(name) && edge.field === "devDependencies").map((edge) => edge.dependency));
700
+ const carried = /* @__PURE__ */ new Set([...releaseTypeFrom, ...edges.filter((edge) => pkg.private && isPrivateRuntimeEdge(edge, graph)).map((edge) => edge.dependency)]);
701
+ resolved.set(name, {
702
+ releaseTypeFrom,
703
+ carried
704
+ });
705
+ }
706
+ return resolved;
707
+ }
708
+ function isPrivateRuntimeEdge(edge, graph) {
709
+ return edge.field !== "devDependencies" && mustGet(graph.packages, edge.dependency, "package").private;
710
+ }
711
+ //#endregion
712
+ //#region src/release-type.ts
713
+ /** Every release type semantic-release knows, highest first: the order `@semantic-release/commit-analyzer` itself ranks them in (its `lib/default-release-types.js`), so comparing two types here agrees with how the analyzer picks the highest type among a package's own commits. */
714
+ const RELEASE_TYPES_HIGHEST_FIRST = [
715
+ "major",
716
+ "premajor",
717
+ "minor",
718
+ "preminor",
719
+ "patch",
720
+ "prepatch",
721
+ "prerelease"
722
+ ];
723
+ const KNOWN_RELEASE_TYPES = new Set(RELEASE_TYPES_HIGHEST_FIRST);
724
+ /** Whether `value` is one of semantic-release's release types. */
725
+ function isReleaseType(value) {
726
+ return typeof value === "string" && KNOWN_RELEASE_TYPES.has(value);
727
+ }
728
+ /** The highest of the given release types, ranked as `@semantic-release/commit-analyzer` ranks them, or `undefined` when `types` is empty. */
729
+ function highestReleaseType(types) {
730
+ let highest;
731
+ for (const type of types) if (highest === void 0 || RELEASE_TYPES_HIGHEST_FIRST.indexOf(type) < RELEASE_TYPES_HIGHEST_FIRST.indexOf(highest)) highest = type;
732
+ return highest;
733
+ }
734
+ /** `base`, raised to `other` when `other` is the higher release type; an `undefined` `other` leaves `base` as it is. */
735
+ function raiseReleaseType(base, other) {
736
+ return other === void 0 || RELEASE_TYPES_HIGHEST_FIRST.indexOf(base) <= RELEASE_TYPES_HIGHEST_FIRST.indexOf(other) ? base : other;
737
+ }
738
+ //#endregion
680
739
  //#region src/dependency-bump-commit.ts
681
740
  /**
682
741
  * The commit message format for a cross-package dependency-range bump, shared between the code that writes it (`bumpDependents` in `release.ts`) and the code that reads it back (`createScopedPlugins` in `plugins.ts`).
@@ -686,26 +745,30 @@ function mustGet(map, key, what) {
686
745
  const TRAILER_DEPENDENCY = "Bumped-Workspace-Dependency";
687
746
  const TRAILER_VERSION = "Bumped-Workspace-Dependency-Version";
688
747
  const TRAILER_RANGE = "Bumped-Workspace-Dependency-Range";
748
+ const TRAILER_RELEASE_TYPE = "Bumped-Workspace-Dependency-Release-Type";
689
749
  /** Builds the full commit message (subject and trailer) for one dependency-range bump. */
690
750
  function formatDependencyBumpMessage(info) {
691
751
  return `${`chore(deps): bump ${info.dependency} to ${info.range} in ${info.dependent} [skip ci]`}\n\n${[
692
752
  `${TRAILER_DEPENDENCY}: ${info.dependency}`,
693
753
  `${TRAILER_VERSION}: ${info.version}`,
694
- `${TRAILER_RANGE}: ${info.range}`
754
+ `${TRAILER_RANGE}: ${info.range}`,
755
+ `${TRAILER_RELEASE_TYPE}: ${info.releaseType}`
695
756
  ].join("\n")}`;
696
757
  }
697
758
  /**
698
- * Recovers the dependency bump a `formatDependencyBumpMessage` commit recorded, from its full git message (subject and body), or `undefined` if the message carries no such trailer. All three lines must be present for the commit to be treated as a bump commit at all -- a message missing even one is left alone rather than partially trusted.
759
+ * Recovers the dependency bump a `formatDependencyBumpMessage` commit recorded, from its full git message (subject and body), or `undefined` if the message carries no such trailer. The dependency, version and range lines must all be present for the commit to be treated as a bump commit at all -- a message missing even one is left alone rather than partially trusted. The release type line is optional, since bump commits written before it existed lack it; when it is absent or names no release type, `releaseType` is `undefined`.
699
760
  */
700
761
  function parseDependencyBumpTrailer(message) {
701
762
  const dependency = matchTrailerLine(message, TRAILER_DEPENDENCY);
702
763
  const version = matchTrailerLine(message, TRAILER_VERSION);
703
764
  const range = matchTrailerLine(message, TRAILER_RANGE);
704
765
  if (dependency === void 0 || version === void 0 || range === void 0) return;
766
+ const releaseType = matchTrailerLine(message, TRAILER_RELEASE_TYPE);
705
767
  return {
706
768
  dependency,
707
769
  version,
708
- range
770
+ range,
771
+ releaseType: isReleaseType(releaseType) ? releaseType : void 0
709
772
  };
710
773
  }
711
774
  function matchTrailerLine(message, key) {
@@ -743,10 +806,11 @@ const STEP_PLUGINS_THE_ORCHESTRATOR_OWNS = /* @__PURE__ */ new Set(["@semantic-r
743
806
  *
744
807
  * 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.
745
808
  *
746
- * 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.
809
+ * The `analyzeCommits` wrapper carries two additions 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. And when a bumped dependency is one the package bundles (`scope.bundled.releaseTypeFrom`), the package's release type is raised to at least that dependency's shipped release type, whether or not the package has releasable commits of its own, because the dependency's change reaches consumers only inside this package. "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.
747
810
  */
748
811
  function createScopedPlugins(scope) {
749
812
  let cached;
813
+ let carriedReleaseType;
750
814
  async function commitsForPackage(context) {
751
815
  const from = hasGitHead(context.lastRelease) ? context.lastRelease.gitHead : void 0;
752
816
  if (cached === void 0) cached = {
@@ -764,13 +828,26 @@ function createScopedPlugins(scope) {
764
828
  return {
765
829
  async analyzeCommits(_pluginConfig, context) {
766
830
  const commits = await commitsForPackage(context);
767
- const type = await analyzeCommits(scope.analyzeCommitsConfig, {
831
+ const analysed = await analyzeCommits(scope.analyzeCommitsConfig, {
768
832
  ...context,
769
833
  commits
770
834
  });
771
- if (typeof type === "string") return type;
835
+ if (typeof analysed === "string" && !isReleaseType(analysed)) throw new ReleaseConfigurationError(`@semantic-release/commit-analyzer returned "${analysed}" for ${scope.pkg.name}, which is not a semantic-release release type.`);
836
+ const own = typeof analysed === "string" ? analysed : void 0;
772
837
  const bumps = mergeDependencyBumps(scope.bumps.bumpsFor(scope.pkg.name), commits);
838
+ carriedReleaseType = releaseTypeOfBumps(bumps.filter((bump) => scope.bundled?.carried.has(bump.dependency) === true));
839
+ const bundledBumps = bumps.filter((bump) => scope.bundled?.releaseTypeFrom.has(bump.dependency) === true);
840
+ const fromBundled = releaseTypeOfBumps(bundledBumps);
841
+ if (own !== void 0) {
842
+ const type = raiseReleaseType(own, fromBundled);
843
+ if (type !== own) context.logger.log(`${scope.pkg.name} bundles ${describeBundledBumps(bundledBumps)}; raising its ${own} release to ${type}.`);
844
+ return type;
845
+ }
773
846
  if (bumps.length === 0) return false;
847
+ if (fromBundled !== void 0) {
848
+ context.logger.log(`No releasable commits under ${scope.pkg.relativeDirectory}, but it bundles ${describeBundledBumps(bundledBumps)}; releasing it as ${fromBundled}.`);
849
+ return fromBundled;
850
+ }
774
851
  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.`);
775
852
  return "patch";
776
853
  },
@@ -788,9 +865,19 @@ function createScopedPlugins(scope) {
788
865
  ...bumps.map((bump) => describeDependencyBump(bump))
789
866
  ].join("\n");
790
867
  return typeof notes === "string" ? `${notes}\n\n${section}` : section;
868
+ },
869
+ shippedReleaseType(releaseType) {
870
+ return raiseReleaseType(releaseType, carriedReleaseType);
791
871
  }
792
872
  };
793
873
  }
874
+ /** The highest release type among `bumps`, or `undefined` when there are none. A bump with no recorded type counts as the patch it always forced. */
875
+ function releaseTypeOfBumps(bumps) {
876
+ return highestReleaseType(bumps.map((bump) => bump.releaseType ?? "patch"));
877
+ }
878
+ function describeBundledBumps(bumps) {
879
+ return bumps.map((bump) => `${bump.dependency} (released ${bump.version}, ${bump.releaseType ?? "patch"})`).join(", ");
880
+ }
794
881
  /**
795
882
  * Combines the bumps recorded in memory earlier in the current run with bumps recovered from the package's own filtered commit history (a bump commit from this run, already visible because it touches the package's own directory, or one left over from a previous run), de-duplicated by dependency name. The in-memory entry wins on overlap: it carries the manifest field and dependent name a `resolved-at-publish` bump has no commit to recover from at all.
796
883
  */
@@ -989,6 +1076,7 @@ async function attemptSingleCommitRelease(options) {
989
1076
  requireGitPlugin: false,
990
1077
  forbidGitPlugin: true
991
1078
  });
1079
+ const bundled = resolveBundledDependencies(graph, options.bundlingPackages);
992
1080
  const analyzeCommitsConfig = options.analyzeCommits ?? {};
993
1081
  const generateNotesConfig = options.generateNotes ?? {};
994
1082
  const tagFormat = validateTagFormat(options.tagFormat ?? "${name}@${version}");
@@ -1010,6 +1098,7 @@ async function attemptSingleCommitRelease(options) {
1010
1098
  analyzeCommitsConfig,
1011
1099
  generateNotesConfig,
1012
1100
  bumpsForThisPackage,
1101
+ bundled: mustGet(bundled, name, "bundled dependencies"),
1013
1102
  env,
1014
1103
  branches: options.branches,
1015
1104
  onCommitsResolved: (commits) => {
@@ -1047,7 +1136,10 @@ async function attemptSingleCommitRelease(options) {
1047
1136
  notes: nextRelease.notes,
1048
1137
  bumps: bumpsForThisPackage
1049
1138
  });
1050
- for (const bump of planDependentBumps(pkg, nextRelease.version, graph)) {
1139
+ for (const bump of planDependentBumps(pkg, {
1140
+ version: nextRelease.version,
1141
+ releaseType: nextRelease.shippedReleaseType
1142
+ }, graph)) {
1051
1143
  const forDependent = pendingBumps.get(bump.dependent) ?? [];
1052
1144
  forDependent.push(bump);
1053
1145
  pendingBumps.set(bump.dependent, forDependent);
@@ -1153,6 +1245,7 @@ async function analysePackage(pkg, options) {
1153
1245
  analyzeCommitsConfig: options.analyzeCommitsConfig,
1154
1246
  generateNotesConfig: options.generateNotesConfig,
1155
1247
  bumps: { bumpsFor: () => options.bumpsForThisPackage },
1248
+ bundled: options.bundled,
1156
1249
  onCommitsResolved: options.onCommitsResolved
1157
1250
  });
1158
1251
  const semanticReleaseOptions = {
@@ -1178,13 +1271,15 @@ async function analysePackage(pkg, options) {
1178
1271
  if (result.nextRelease.notes === void 0) throw new ReleaseConfigurationError(`Internal error: ${pkg.name} released with no notes computed.`);
1179
1272
  return {
1180
1273
  type: result.nextRelease.type,
1274
+ shippedReleaseType: scoped.shippedReleaseType(result.nextRelease.type),
1181
1275
  version: result.nextRelease.version,
1182
1276
  gitTag: result.nextRelease.gitTag,
1183
1277
  notes: result.nextRelease.notes
1184
1278
  };
1185
1279
  }
1186
1280
  /** Pure classification of what a released package's version does to each dependent's declared range -- the same logic `bumpDependents` in `release.ts` applies, minus the file write/commit/push: `commitStrategy: 'single'` defers every write to phase 3, applying the same classification once analysis has finished for the whole run. */
1187
- function planDependentBumps(released, version, graph) {
1281
+ function planDependentBumps(released, release, graph) {
1282
+ const { version, releaseType } = release;
1188
1283
  const applied = [];
1189
1284
  const dependents = graph.dependents.get(released.name);
1190
1285
  if (dependents === void 0) return applied;
@@ -1197,7 +1292,8 @@ function planDependentBumps(released, version, graph) {
1197
1292
  field: edge.field,
1198
1293
  version,
1199
1294
  range: update.kind === "rewritten" ? update.range : edge.range,
1200
- kind: update.kind
1295
+ kind: update.kind,
1296
+ releaseType
1201
1297
  });
1202
1298
  }
1203
1299
  return applied;
@@ -1291,14 +1387,16 @@ async function detachWorkspaceRelease(options) {
1291
1387
  plugins: options.plugins ?? DEFAULT_PUBLISH_PLUGINS,
1292
1388
  packagePlugins: options.packagePlugins
1293
1389
  }, workspace.root, { requireGitPlugin: !dryRun });
1390
+ const bundled = resolveBundledDependencies(graph, options.bundlingPackages);
1294
1391
  const analyzeCommitsConfig = options.analyzeCommits ?? {};
1295
1392
  const generateNotesConfig = options.generateNotes ?? {};
1296
1393
  const entries = await runReleaseLoop(graph, order, workspace, dryRun, log, async (pkg, bumpsForThisPackage) => {
1297
- const state = await runPackageDetach(pkg, {
1394
+ const { state, releaseType } = await runPackageDetach(pkg, {
1298
1395
  publishPlugins: mustGet(publishPlugins, pkg.name, "publish plugins"),
1299
1396
  analyzeCommitsConfig,
1300
1397
  generateNotesConfig,
1301
1398
  bumpsForThisPackage,
1399
+ bundled: mustGet(bundled, pkg.name, "bundled dependencies"),
1302
1400
  tagFormat: validateTagFormat(options.tagFormat ?? "${name}@${version}"),
1303
1401
  dryRun,
1304
1402
  env,
@@ -1307,6 +1405,7 @@ async function detachWorkspaceRelease(options) {
1307
1405
  return {
1308
1406
  released: state !== null,
1309
1407
  version: state?.nextRelease.version,
1408
+ releaseType,
1310
1409
  result: state
1311
1410
  };
1312
1411
  });
@@ -1339,7 +1438,8 @@ async function runPackageDetach(pkg, options) {
1339
1438
  pkg,
1340
1439
  analyzeCommitsConfig: options.analyzeCommitsConfig,
1341
1440
  generateNotesConfig: options.generateNotesConfig,
1342
- bumps: { bumpsFor: () => options.bumpsForThisPackage }
1441
+ bumps: { bumpsFor: () => options.bumpsForThisPackage },
1442
+ bundled: options.bundled
1343
1443
  });
1344
1444
  const cliOptions = {
1345
1445
  tagFormat: formatTagForPackage(options.tagFormat, pkg.name),
@@ -1350,10 +1450,14 @@ async function runPackageDetach(pkg, options) {
1350
1450
  if (options.dryRun) cliOptions.dryRun = true;
1351
1451
  if (options.branches !== void 0) cliOptions.branches = options.branches;
1352
1452
  try {
1353
- return await detachRelease(cliOptions, {
1453
+ const state = await detachRelease(cliOptions, {
1354
1454
  cwd: pkg.directory,
1355
1455
  env: { ...options.env }
1356
1456
  });
1457
+ return {
1458
+ state,
1459
+ releaseType: state === null ? void 0 : scoped.shippedReleaseType(state.nextRelease.type)
1460
+ };
1357
1461
  } catch (cause) {
1358
1462
  throw new WorkspaceReleaseError(`Detaching ${pkg.name} failed: ${cause instanceof Error ? cause.message : String(cause)}`);
1359
1463
  }
@@ -1433,17 +1537,19 @@ async function releaseWorkspace(options = {}) {
1433
1537
  plugins: options.plugins ?? DEFAULT_PUBLISH_PLUGINS,
1434
1538
  packagePlugins: options.packagePlugins
1435
1539
  }, workspace.root, { requireGitPlugin: !dryRun });
1540
+ const bundled = resolveBundledDependencies(graph, options.bundlingPackages);
1436
1541
  const analyzeCommitsConfig = options.analyzeCommits ?? {};
1437
1542
  const generateNotesConfig = options.generateNotes ?? {};
1438
1543
  return {
1439
1544
  order,
1440
1545
  packages: (await runReleaseLoop(graph, order, workspace, dryRun, log, async (pkg, bumpsForThisPackage) => {
1441
- const result = await runPackageRelease(pkg, {
1546
+ const { result, releaseType } = await runPackageRelease(pkg, {
1442
1547
  tagFormat: validateTagFormat(options.tagFormat ?? "${name}@${version}"),
1443
1548
  publishPlugins: mustGet(publishPlugins, pkg.name, "publish plugins"),
1444
1549
  analyzeCommitsConfig,
1445
1550
  generateNotesConfig,
1446
1551
  bumpsForThisPackage,
1552
+ bundled: mustGet(bundled, pkg.name, "bundled dependencies"),
1447
1553
  dryRun,
1448
1554
  env,
1449
1555
  branches: options.branches
@@ -1452,6 +1558,7 @@ async function releaseWorkspace(options = {}) {
1452
1558
  return {
1453
1559
  released: nextRelease !== void 0,
1454
1560
  version: nextRelease?.version,
1561
+ releaseType,
1455
1562
  result
1456
1563
  };
1457
1564
  })).map((entry) => {
@@ -1474,7 +1581,7 @@ async function releaseWorkspace(options = {}) {
1474
1581
  };
1475
1582
  }
1476
1583
  /**
1477
- * The per-package release loop shared by `releaseWorkspace` (normal per-package releases) and `detachWorkspaceRelease` (gated tag-only releases): iterate the topological `order`, call the caller's own `releaseOne` for each package, record which dependency-range bumps were already applied to it, and -- when it released -- bump every workspace dependent's manifest before that dependent's own turn (see `bumpDependents`'s doc comment for why this happens immediately rather than being staged). The two call sites differ only in how they call semantic-release (`semanticRelease(...)` vs `detachRelease(...)`) and how they read "did it release, and what version" off the result, both captured by `releaseOne`.
1584
+ * The per-package release loop shared by `releaseWorkspace` (normal per-package releases) and `detachWorkspaceRelease` (gated tag-only releases): iterate the topological `order`, call the caller's own `releaseOne` for each package, record which dependency-range bumps were already applied to it, and -- when it released -- bump every workspace dependent's manifest before that dependent's own turn (see `bumpDependents`'s doc comment for why this happens immediately rather than being staged). The two call sites differ only in how they call semantic-release (`semanticRelease(...)` vs `detachRelease(...)`) and how they read "did it release, at what version and with which shipped release type" off the result, all captured by `releaseOne`.
1478
1585
  */
1479
1586
  async function runReleaseLoop(graph, order, workspace, dryRun, log, releaseOne) {
1480
1587
  const pendingBumps = /* @__PURE__ */ new Map();
@@ -1484,7 +1591,7 @@ async function runReleaseLoop(graph, order, workspace, dryRun, log, releaseOne)
1484
1591
  const pkg = mustGet(graph.packages, name, "package");
1485
1592
  const bumpsForThisPackage = pendingBumps.get(name) ?? [];
1486
1593
  log(`Releasing ${name} from ${pkg.relativeDirectory}${bumpsForThisPackage.length > 0 ? ` (dependency ranges already bumped: ${bumpsForThisPackage.map((bump) => bump.dependency).join(", ")})` : ""}`);
1487
- const { released, version, result } = dryRun ? await releaseOne(pkg, bumpsForThisPackage) : await releasePackageAcrossBranchMoves(name, branchTracker(workspace, log), async () => await releaseOne(pkg, bumpsForThisPackage), log);
1594
+ const { released, version, releaseType, result } = dryRun ? await releaseOne(pkg, bumpsForThisPackage) : await releasePackageAcrossBranchMoves(name, branchTracker(workspace, log), async () => await releaseOne(pkg, bumpsForThisPackage), log);
1488
1595
  entries.push({
1489
1596
  name,
1490
1597
  directory: pkg.directory,
@@ -1493,13 +1600,16 @@ async function runReleaseLoop(graph, order, workspace, dryRun, log, releaseOne)
1493
1600
  dependencyBumps: bumpsForThisPackage
1494
1601
  });
1495
1602
  pendingBumps.delete(name);
1496
- if (!released || version === void 0) {
1603
+ if (!released || version === void 0 || releaseType === void 0) {
1497
1604
  log(`${name}: no release`);
1498
1605
  continue;
1499
1606
  }
1500
1607
  log(`${name}: released ${version}`);
1501
1608
  identity ??= await resolveCommitIdentity({ cwd: workspace.root });
1502
- const bumps = await bumpDependents(pkg, version, graph, {
1609
+ const bumps = await bumpDependents(pkg, {
1610
+ version,
1611
+ releaseType
1612
+ }, graph, {
1503
1613
  workspace,
1504
1614
  dryRun,
1505
1615
  identity,
@@ -1518,7 +1628,8 @@ async function runPackageRelease(pkg, options) {
1518
1628
  pkg,
1519
1629
  analyzeCommitsConfig: options.analyzeCommitsConfig,
1520
1630
  generateNotesConfig: options.generateNotesConfig,
1521
- bumps: { bumpsFor: () => options.bumpsForThisPackage }
1631
+ bumps: { bumpsFor: () => options.bumpsForThisPackage },
1632
+ bundled: options.bundled
1522
1633
  });
1523
1634
  const semanticReleaseOptions = {
1524
1635
  tagFormat: formatTagForPackage(options.tagFormat, pkg.name),
@@ -1529,10 +1640,14 @@ async function runPackageRelease(pkg, options) {
1529
1640
  if (options.dryRun) semanticReleaseOptions.dryRun = true;
1530
1641
  if (options.branches !== void 0) semanticReleaseOptions.branches = options.branches;
1531
1642
  try {
1532
- return await semanticRelease(semanticReleaseOptions, {
1643
+ const result = await semanticRelease(semanticReleaseOptions, {
1533
1644
  cwd: pkg.directory,
1534
1645
  env: { ...options.env }
1535
1646
  });
1647
+ return {
1648
+ result,
1649
+ releaseType: result === false ? void 0 : scoped.shippedReleaseType(result.nextRelease.type)
1650
+ };
1536
1651
  } catch (cause) {
1537
1652
  throw new WorkspaceReleaseError(`Release of ${pkg.name} failed: ${cause instanceof Error ? cause.message : String(cause)}`);
1538
1653
  }
@@ -1544,10 +1659,11 @@ const LOCKFILE_FILENAME = "pnpm-lock.yaml";
1544
1659
  *
1545
1660
  * The bump is committed (and pushed) right away rather than staged, because a dependent's semantic-release run analyses git history, not the working tree: an uncommitted bump would be invisible to its commit analysis, and would be stranded uncommitted if the dependent then released nothing -- a manifest that names versions the registry has never seen. Committing immediately means the dependent's own run sees the bump commit (it touches only the dependent's directory, so it passes that dependent's path filter), the forced-patch logic in the scoped analyzer covers the case where that bump is the dependent's only change, and a run interrupted partway leaves the remote describing exactly what was published. Pushing immediately mirrors what semantic-release itself does with release commits. The lockfile is regenerated (`pnpm install --lockfile-only`) and committed alongside the manifest for the same reason: a manifest bump committed without it leaves `pnpm-lock.yaml` naming the old range, which `pnpm install --frozen-lockfile` (what CI runs) then rejects.
1546
1661
  */
1547
- async function bumpDependents(released, version, graph, options) {
1662
+ async function bumpDependents(released, release, graph, options) {
1548
1663
  const applied = [];
1549
1664
  const dependents = graph.dependents.get(released.name);
1550
1665
  if (dependents === void 0) return applied;
1666
+ const { version, releaseType } = release;
1551
1667
  for (const edge of dependents) {
1552
1668
  const update = updateDependencyRange(edge.range, version);
1553
1669
  if (update.kind === "wildcard") continue;
@@ -1560,6 +1676,7 @@ async function bumpDependents(released, version, graph, options) {
1560
1676
  dependency: released.name,
1561
1677
  version,
1562
1678
  range: update.range,
1679
+ releaseType,
1563
1680
  dependent: edge.dependent
1564
1681
  });
1565
1682
  await commitFiles([`${dependent.relativeDirectory}/package.json`, LOCKFILE_FILENAME], message, {
@@ -1576,7 +1693,8 @@ async function bumpDependents(released, version, graph, options) {
1576
1693
  field: edge.field,
1577
1694
  version,
1578
1695
  range: update.kind === "rewritten" ? update.range : edge.range,
1579
- kind: update.kind
1696
+ kind: update.kind,
1697
+ releaseType
1580
1698
  });
1581
1699
  }
1582
1700
  return applied;
@@ -1604,4 +1722,4 @@ function branchTracker(workspace, log) {
1604
1722
  };
1605
1723
  }
1606
1724
  //#endregion
1607
- export { DEFAULT_PUBLISH_PLUGINS, DependencyCycleError, GitCommandError, ReleaseConfigurationError, SINGLE_COMMIT_DEFAULT_PUBLISH_PLUGINS, UnsupportedDependencyRangeError, WorkspaceDiscoveryError, WorkspaceReleaseError, WorkspaceStateError, buildDependencyGraph, classifyDependencyRange, createScopedPlugins, discoverWorkspace, filterCommitsToDirectory, orderedPackages, packageName, readManifest, releaseWorkspace, resolvePublishPlugins, resolveWorkspacePublishPlugins, resumeWorkspaceRelease, topologicalOrder, updateDependencyRange, writeDependencyRange };
1725
+ export { DEFAULT_PUBLISH_PLUGINS, DependencyCycleError, GitCommandError, ReleaseConfigurationError, SINGLE_COMMIT_DEFAULT_PUBLISH_PLUGINS, UnsupportedDependencyRangeError, WorkspaceDiscoveryError, WorkspaceReleaseError, WorkspaceStateError, buildDependencyGraph, classifyDependencyRange, createScopedPlugins, discoverWorkspace, filterCommitsToDirectory, orderedPackages, packageName, readManifest, releaseWorkspace, resolveBundledDependencies, resolvePublishPlugins, resolveWorkspacePublishPlugins, resumeWorkspaceRelease, topologicalOrder, updateDependencyRange, writeDependencyRange };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exadev/semantic-release-workspace",
3
- "version": "3.1.29",
3
+ "version": "3.2.1",
4
4
  "description": "Independent per-package semantic-release orchestration for pnpm workspaces, without lockstep versioning.",
5
5
  "type": "module",
6
6
  "repository": {