@exadev/semantic-release-workspace 3.1.29 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -702,6 +702,65 @@ function mustGet(map, key, what) {
702
702
  return value;
703
703
  }
704
704
  //#endregion
705
+ //#region src/bundling.ts
706
+ /**
707
+ * Validates `bundlingPackages` against the workspace and works out, for every package, which workspace dependencies it bundles.
708
+ *
709
+ * 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.
710
+ *
711
+ * 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.
712
+ *
713
+ * `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.
714
+ */
715
+ function resolveBundledDependencies(graph, bundlingPackages) {
716
+ const bundling = new Set(bundlingPackages);
717
+ const unknown = [...bundling].filter((name) => !graph.packages.has(name));
718
+ 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(", ")}.`);
719
+ const privateNames = [...bundling].filter((name) => mustGet(graph.packages, name, "package").private);
720
+ 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.`);
721
+ const resolved = /* @__PURE__ */ new Map();
722
+ for (const [name, pkg] of graph.packages) {
723
+ const edges = mustGet(graph.dependencies, name, "dependency edges");
724
+ const releaseTypeFrom = new Set(edges.filter((edge) => bundling.has(name) && edge.field === "devDependencies").map((edge) => edge.dependency));
725
+ const carried = /* @__PURE__ */ new Set([...releaseTypeFrom, ...edges.filter((edge) => pkg.private && isPrivateRuntimeEdge(edge, graph)).map((edge) => edge.dependency)]);
726
+ resolved.set(name, {
727
+ releaseTypeFrom,
728
+ carried
729
+ });
730
+ }
731
+ return resolved;
732
+ }
733
+ function isPrivateRuntimeEdge(edge, graph) {
734
+ return edge.field !== "devDependencies" && mustGet(graph.packages, edge.dependency, "package").private;
735
+ }
736
+ //#endregion
737
+ //#region src/release-type.ts
738
+ /** 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. */
739
+ const RELEASE_TYPES_HIGHEST_FIRST = [
740
+ "major",
741
+ "premajor",
742
+ "minor",
743
+ "preminor",
744
+ "patch",
745
+ "prepatch",
746
+ "prerelease"
747
+ ];
748
+ const KNOWN_RELEASE_TYPES = new Set(RELEASE_TYPES_HIGHEST_FIRST);
749
+ /** Whether `value` is one of semantic-release's release types. */
750
+ function isReleaseType(value) {
751
+ return typeof value === "string" && KNOWN_RELEASE_TYPES.has(value);
752
+ }
753
+ /** The highest of the given release types, ranked as `@semantic-release/commit-analyzer` ranks them, or `undefined` when `types` is empty. */
754
+ function highestReleaseType(types) {
755
+ let highest;
756
+ for (const type of types) if (highest === void 0 || RELEASE_TYPES_HIGHEST_FIRST.indexOf(type) < RELEASE_TYPES_HIGHEST_FIRST.indexOf(highest)) highest = type;
757
+ return highest;
758
+ }
759
+ /** `base`, raised to `other` when `other` is the higher release type; an `undefined` `other` leaves `base` as it is. */
760
+ function raiseReleaseType(base, other) {
761
+ return other === void 0 || RELEASE_TYPES_HIGHEST_FIRST.indexOf(base) <= RELEASE_TYPES_HIGHEST_FIRST.indexOf(other) ? base : other;
762
+ }
763
+ //#endregion
705
764
  //#region src/dependency-bump-commit.ts
706
765
  /**
707
766
  * 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`).
@@ -711,26 +770,30 @@ function mustGet(map, key, what) {
711
770
  const TRAILER_DEPENDENCY = "Bumped-Workspace-Dependency";
712
771
  const TRAILER_VERSION = "Bumped-Workspace-Dependency-Version";
713
772
  const TRAILER_RANGE = "Bumped-Workspace-Dependency-Range";
773
+ const TRAILER_RELEASE_TYPE = "Bumped-Workspace-Dependency-Release-Type";
714
774
  /** Builds the full commit message (subject and trailer) for one dependency-range bump. */
715
775
  function formatDependencyBumpMessage(info) {
716
776
  return `${`chore(deps): bump ${info.dependency} to ${info.range} in ${info.dependent} [skip ci]`}\n\n${[
717
777
  `${TRAILER_DEPENDENCY}: ${info.dependency}`,
718
778
  `${TRAILER_VERSION}: ${info.version}`,
719
- `${TRAILER_RANGE}: ${info.range}`
779
+ `${TRAILER_RANGE}: ${info.range}`,
780
+ `${TRAILER_RELEASE_TYPE}: ${info.releaseType}`
720
781
  ].join("\n")}`;
721
782
  }
722
783
  /**
723
- * 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.
784
+ * 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`.
724
785
  */
725
786
  function parseDependencyBumpTrailer(message) {
726
787
  const dependency = matchTrailerLine(message, TRAILER_DEPENDENCY);
727
788
  const version = matchTrailerLine(message, TRAILER_VERSION);
728
789
  const range = matchTrailerLine(message, TRAILER_RANGE);
729
790
  if (dependency === void 0 || version === void 0 || range === void 0) return;
791
+ const releaseType = matchTrailerLine(message, TRAILER_RELEASE_TYPE);
730
792
  return {
731
793
  dependency,
732
794
  version,
733
- range
795
+ range,
796
+ releaseType: isReleaseType(releaseType) ? releaseType : void 0
734
797
  };
735
798
  }
736
799
  function matchTrailerLine(message, key) {
@@ -768,10 +831,11 @@ const STEP_PLUGINS_THE_ORCHESTRATOR_OWNS = /* @__PURE__ */ new Set(["@semantic-r
768
831
  *
769
832
  * 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.
770
833
  *
771
- * 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.
834
+ * 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.
772
835
  */
773
836
  function createScopedPlugins(scope) {
774
837
  let cached;
838
+ let carriedReleaseType;
775
839
  async function commitsForPackage(context) {
776
840
  const from = hasGitHead(context.lastRelease) ? context.lastRelease.gitHead : void 0;
777
841
  if (cached === void 0) cached = {
@@ -789,13 +853,26 @@ function createScopedPlugins(scope) {
789
853
  return {
790
854
  async analyzeCommits(_pluginConfig, context) {
791
855
  const commits = await commitsForPackage(context);
792
- const type = await (0, _semantic_release_commit_analyzer.analyzeCommits)(scope.analyzeCommitsConfig, {
856
+ const analysed = await (0, _semantic_release_commit_analyzer.analyzeCommits)(scope.analyzeCommitsConfig, {
793
857
  ...context,
794
858
  commits
795
859
  });
796
- if (typeof type === "string") return type;
860
+ 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.`);
861
+ const own = typeof analysed === "string" ? analysed : void 0;
797
862
  const bumps = mergeDependencyBumps(scope.bumps.bumpsFor(scope.pkg.name), commits);
863
+ carriedReleaseType = releaseTypeOfBumps(bumps.filter((bump) => scope.bundled?.carried.has(bump.dependency) === true));
864
+ const bundledBumps = bumps.filter((bump) => scope.bundled?.releaseTypeFrom.has(bump.dependency) === true);
865
+ const fromBundled = releaseTypeOfBumps(bundledBumps);
866
+ if (own !== void 0) {
867
+ const type = raiseReleaseType(own, fromBundled);
868
+ if (type !== own) context.logger.log(`${scope.pkg.name} bundles ${describeBundledBumps(bundledBumps)}; raising its ${own} release to ${type}.`);
869
+ return type;
870
+ }
798
871
  if (bumps.length === 0) return false;
872
+ if (fromBundled !== void 0) {
873
+ context.logger.log(`No releasable commits under ${scope.pkg.relativeDirectory}, but it bundles ${describeBundledBumps(bundledBumps)}; releasing it as ${fromBundled}.`);
874
+ return fromBundled;
875
+ }
799
876
  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.`);
800
877
  return "patch";
801
878
  },
@@ -813,9 +890,19 @@ function createScopedPlugins(scope) {
813
890
  ...bumps.map((bump) => describeDependencyBump(bump))
814
891
  ].join("\n");
815
892
  return typeof notes === "string" ? `${notes}\n\n${section}` : section;
893
+ },
894
+ shippedReleaseType(releaseType) {
895
+ return raiseReleaseType(releaseType, carriedReleaseType);
816
896
  }
817
897
  };
818
898
  }
899
+ /** 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. */
900
+ function releaseTypeOfBumps(bumps) {
901
+ return highestReleaseType(bumps.map((bump) => bump.releaseType ?? "patch"));
902
+ }
903
+ function describeBundledBumps(bumps) {
904
+ return bumps.map((bump) => `${bump.dependency} (released ${bump.version}, ${bump.releaseType ?? "patch"})`).join(", ");
905
+ }
819
906
  /**
820
907
  * 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.
821
908
  */
@@ -1014,6 +1101,7 @@ async function attemptSingleCommitRelease(options) {
1014
1101
  requireGitPlugin: false,
1015
1102
  forbidGitPlugin: true
1016
1103
  });
1104
+ const bundled = resolveBundledDependencies(graph, options.bundlingPackages);
1017
1105
  const analyzeCommitsConfig = options.analyzeCommits ?? {};
1018
1106
  const generateNotesConfig = options.generateNotes ?? {};
1019
1107
  const tagFormat = validateTagFormat(options.tagFormat ?? "${name}@${version}");
@@ -1035,6 +1123,7 @@ async function attemptSingleCommitRelease(options) {
1035
1123
  analyzeCommitsConfig,
1036
1124
  generateNotesConfig,
1037
1125
  bumpsForThisPackage,
1126
+ bundled: mustGet(bundled, name, "bundled dependencies"),
1038
1127
  env,
1039
1128
  branches: options.branches,
1040
1129
  onCommitsResolved: (commits) => {
@@ -1072,7 +1161,10 @@ async function attemptSingleCommitRelease(options) {
1072
1161
  notes: nextRelease.notes,
1073
1162
  bumps: bumpsForThisPackage
1074
1163
  });
1075
- for (const bump of planDependentBumps(pkg, nextRelease.version, graph)) {
1164
+ for (const bump of planDependentBumps(pkg, {
1165
+ version: nextRelease.version,
1166
+ releaseType: nextRelease.shippedReleaseType
1167
+ }, graph)) {
1076
1168
  const forDependent = pendingBumps.get(bump.dependent) ?? [];
1077
1169
  forDependent.push(bump);
1078
1170
  pendingBumps.set(bump.dependent, forDependent);
@@ -1178,6 +1270,7 @@ async function analysePackage(pkg, options) {
1178
1270
  analyzeCommitsConfig: options.analyzeCommitsConfig,
1179
1271
  generateNotesConfig: options.generateNotesConfig,
1180
1272
  bumps: { bumpsFor: () => options.bumpsForThisPackage },
1273
+ bundled: options.bundled,
1181
1274
  onCommitsResolved: options.onCommitsResolved
1182
1275
  });
1183
1276
  const semanticReleaseOptions = {
@@ -1203,13 +1296,15 @@ async function analysePackage(pkg, options) {
1203
1296
  if (result.nextRelease.notes === void 0) throw new ReleaseConfigurationError(`Internal error: ${pkg.name} released with no notes computed.`);
1204
1297
  return {
1205
1298
  type: result.nextRelease.type,
1299
+ shippedReleaseType: scoped.shippedReleaseType(result.nextRelease.type),
1206
1300
  version: result.nextRelease.version,
1207
1301
  gitTag: result.nextRelease.gitTag,
1208
1302
  notes: result.nextRelease.notes
1209
1303
  };
1210
1304
  }
1211
1305
  /** 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. */
1212
- function planDependentBumps(released, version, graph) {
1306
+ function planDependentBumps(released, release, graph) {
1307
+ const { version, releaseType } = release;
1213
1308
  const applied = [];
1214
1309
  const dependents = graph.dependents.get(released.name);
1215
1310
  if (dependents === void 0) return applied;
@@ -1222,7 +1317,8 @@ function planDependentBumps(released, version, graph) {
1222
1317
  field: edge.field,
1223
1318
  version,
1224
1319
  range: update.kind === "rewritten" ? update.range : edge.range,
1225
- kind: update.kind
1320
+ kind: update.kind,
1321
+ releaseType
1226
1322
  });
1227
1323
  }
1228
1324
  return applied;
@@ -1316,14 +1412,16 @@ async function detachWorkspaceRelease(options) {
1316
1412
  plugins: options.plugins ?? DEFAULT_PUBLISH_PLUGINS,
1317
1413
  packagePlugins: options.packagePlugins
1318
1414
  }, workspace.root, { requireGitPlugin: !dryRun });
1415
+ const bundled = resolveBundledDependencies(graph, options.bundlingPackages);
1319
1416
  const analyzeCommitsConfig = options.analyzeCommits ?? {};
1320
1417
  const generateNotesConfig = options.generateNotes ?? {};
1321
1418
  const entries = await runReleaseLoop(graph, order, workspace, dryRun, log, async (pkg, bumpsForThisPackage) => {
1322
- const state = await runPackageDetach(pkg, {
1419
+ const { state, releaseType } = await runPackageDetach(pkg, {
1323
1420
  publishPlugins: mustGet(publishPlugins, pkg.name, "publish plugins"),
1324
1421
  analyzeCommitsConfig,
1325
1422
  generateNotesConfig,
1326
1423
  bumpsForThisPackage,
1424
+ bundled: mustGet(bundled, pkg.name, "bundled dependencies"),
1327
1425
  tagFormat: validateTagFormat(options.tagFormat ?? "${name}@${version}"),
1328
1426
  dryRun,
1329
1427
  env,
@@ -1332,6 +1430,7 @@ async function detachWorkspaceRelease(options) {
1332
1430
  return {
1333
1431
  released: state !== null,
1334
1432
  version: state?.nextRelease.version,
1433
+ releaseType,
1335
1434
  result: state
1336
1435
  };
1337
1436
  });
@@ -1364,7 +1463,8 @@ async function runPackageDetach(pkg, options) {
1364
1463
  pkg,
1365
1464
  analyzeCommitsConfig: options.analyzeCommitsConfig,
1366
1465
  generateNotesConfig: options.generateNotesConfig,
1367
- bumps: { bumpsFor: () => options.bumpsForThisPackage }
1466
+ bumps: { bumpsFor: () => options.bumpsForThisPackage },
1467
+ bundled: options.bundled
1368
1468
  });
1369
1469
  const cliOptions = {
1370
1470
  tagFormat: formatTagForPackage(options.tagFormat, pkg.name),
@@ -1375,10 +1475,14 @@ async function runPackageDetach(pkg, options) {
1375
1475
  if (options.dryRun) cliOptions.dryRun = true;
1376
1476
  if (options.branches !== void 0) cliOptions.branches = options.branches;
1377
1477
  try {
1378
- return await (0, _exadev_release_gate.detachRelease)(cliOptions, {
1478
+ const state = await (0, _exadev_release_gate.detachRelease)(cliOptions, {
1379
1479
  cwd: pkg.directory,
1380
1480
  env: { ...options.env }
1381
1481
  });
1482
+ return {
1483
+ state,
1484
+ releaseType: state === null ? void 0 : scoped.shippedReleaseType(state.nextRelease.type)
1485
+ };
1382
1486
  } catch (cause) {
1383
1487
  throw new WorkspaceReleaseError(`Detaching ${pkg.name} failed: ${cause instanceof Error ? cause.message : String(cause)}`);
1384
1488
  }
@@ -1458,17 +1562,19 @@ async function releaseWorkspace(options = {}) {
1458
1562
  plugins: options.plugins ?? DEFAULT_PUBLISH_PLUGINS,
1459
1563
  packagePlugins: options.packagePlugins
1460
1564
  }, workspace.root, { requireGitPlugin: !dryRun });
1565
+ const bundled = resolveBundledDependencies(graph, options.bundlingPackages);
1461
1566
  const analyzeCommitsConfig = options.analyzeCommits ?? {};
1462
1567
  const generateNotesConfig = options.generateNotes ?? {};
1463
1568
  return {
1464
1569
  order,
1465
1570
  packages: (await runReleaseLoop(graph, order, workspace, dryRun, log, async (pkg, bumpsForThisPackage) => {
1466
- const result = await runPackageRelease(pkg, {
1571
+ const { result, releaseType } = await runPackageRelease(pkg, {
1467
1572
  tagFormat: validateTagFormat(options.tagFormat ?? "${name}@${version}"),
1468
1573
  publishPlugins: mustGet(publishPlugins, pkg.name, "publish plugins"),
1469
1574
  analyzeCommitsConfig,
1470
1575
  generateNotesConfig,
1471
1576
  bumpsForThisPackage,
1577
+ bundled: mustGet(bundled, pkg.name, "bundled dependencies"),
1472
1578
  dryRun,
1473
1579
  env,
1474
1580
  branches: options.branches
@@ -1477,6 +1583,7 @@ async function releaseWorkspace(options = {}) {
1477
1583
  return {
1478
1584
  released: nextRelease !== void 0,
1479
1585
  version: nextRelease?.version,
1586
+ releaseType,
1480
1587
  result
1481
1588
  };
1482
1589
  })).map((entry) => {
@@ -1499,7 +1606,7 @@ async function releaseWorkspace(options = {}) {
1499
1606
  };
1500
1607
  }
1501
1608
  /**
1502
- * 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`.
1609
+ * 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`.
1503
1610
  */
1504
1611
  async function runReleaseLoop(graph, order, workspace, dryRun, log, releaseOne) {
1505
1612
  const pendingBumps = /* @__PURE__ */ new Map();
@@ -1509,7 +1616,7 @@ async function runReleaseLoop(graph, order, workspace, dryRun, log, releaseOne)
1509
1616
  const pkg = mustGet(graph.packages, name, "package");
1510
1617
  const bumpsForThisPackage = pendingBumps.get(name) ?? [];
1511
1618
  log(`Releasing ${name} from ${pkg.relativeDirectory}${bumpsForThisPackage.length > 0 ? ` (dependency ranges already bumped: ${bumpsForThisPackage.map((bump) => bump.dependency).join(", ")})` : ""}`);
1512
- const { released, version, result } = dryRun ? await releaseOne(pkg, bumpsForThisPackage) : await releasePackageAcrossBranchMoves(name, branchTracker(workspace, log), async () => await releaseOne(pkg, bumpsForThisPackage), log);
1619
+ const { released, version, releaseType, result } = dryRun ? await releaseOne(pkg, bumpsForThisPackage) : await releasePackageAcrossBranchMoves(name, branchTracker(workspace, log), async () => await releaseOne(pkg, bumpsForThisPackage), log);
1513
1620
  entries.push({
1514
1621
  name,
1515
1622
  directory: pkg.directory,
@@ -1518,13 +1625,16 @@ async function runReleaseLoop(graph, order, workspace, dryRun, log, releaseOne)
1518
1625
  dependencyBumps: bumpsForThisPackage
1519
1626
  });
1520
1627
  pendingBumps.delete(name);
1521
- if (!released || version === void 0) {
1628
+ if (!released || version === void 0 || releaseType === void 0) {
1522
1629
  log(`${name}: no release`);
1523
1630
  continue;
1524
1631
  }
1525
1632
  log(`${name}: released ${version}`);
1526
1633
  identity ??= await resolveCommitIdentity({ cwd: workspace.root });
1527
- const bumps = await bumpDependents(pkg, version, graph, {
1634
+ const bumps = await bumpDependents(pkg, {
1635
+ version,
1636
+ releaseType
1637
+ }, graph, {
1528
1638
  workspace,
1529
1639
  dryRun,
1530
1640
  identity,
@@ -1543,7 +1653,8 @@ async function runPackageRelease(pkg, options) {
1543
1653
  pkg,
1544
1654
  analyzeCommitsConfig: options.analyzeCommitsConfig,
1545
1655
  generateNotesConfig: options.generateNotesConfig,
1546
- bumps: { bumpsFor: () => options.bumpsForThisPackage }
1656
+ bumps: { bumpsFor: () => options.bumpsForThisPackage },
1657
+ bundled: options.bundled
1547
1658
  });
1548
1659
  const semanticReleaseOptions = {
1549
1660
  tagFormat: formatTagForPackage(options.tagFormat, pkg.name),
@@ -1554,10 +1665,14 @@ async function runPackageRelease(pkg, options) {
1554
1665
  if (options.dryRun) semanticReleaseOptions.dryRun = true;
1555
1666
  if (options.branches !== void 0) semanticReleaseOptions.branches = options.branches;
1556
1667
  try {
1557
- return await (0, semantic_release.default)(semanticReleaseOptions, {
1668
+ const result = await (0, semantic_release.default)(semanticReleaseOptions, {
1558
1669
  cwd: pkg.directory,
1559
1670
  env: { ...options.env }
1560
1671
  });
1672
+ return {
1673
+ result,
1674
+ releaseType: result === false ? void 0 : scoped.shippedReleaseType(result.nextRelease.type)
1675
+ };
1561
1676
  } catch (cause) {
1562
1677
  throw new WorkspaceReleaseError(`Release of ${pkg.name} failed: ${cause instanceof Error ? cause.message : String(cause)}`);
1563
1678
  }
@@ -1569,10 +1684,11 @@ const LOCKFILE_FILENAME = "pnpm-lock.yaml";
1569
1684
  *
1570
1685
  * 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.
1571
1686
  */
1572
- async function bumpDependents(released, version, graph, options) {
1687
+ async function bumpDependents(released, release, graph, options) {
1573
1688
  const applied = [];
1574
1689
  const dependents = graph.dependents.get(released.name);
1575
1690
  if (dependents === void 0) return applied;
1691
+ const { version, releaseType } = release;
1576
1692
  for (const edge of dependents) {
1577
1693
  const update = updateDependencyRange(edge.range, version);
1578
1694
  if (update.kind === "wildcard") continue;
@@ -1585,6 +1701,7 @@ async function bumpDependents(released, version, graph, options) {
1585
1701
  dependency: released.name,
1586
1702
  version,
1587
1703
  range: update.range,
1704
+ releaseType,
1588
1705
  dependent: edge.dependent
1589
1706
  });
1590
1707
  await commitFiles([`${dependent.relativeDirectory}/package.json`, LOCKFILE_FILENAME], message, {
@@ -1601,7 +1718,8 @@ async function bumpDependents(released, version, graph, options) {
1601
1718
  field: edge.field,
1602
1719
  version,
1603
1720
  range: update.kind === "rewritten" ? update.range : edge.range,
1604
- kind: update.kind
1721
+ kind: update.kind,
1722
+ releaseType
1605
1723
  });
1606
1724
  }
1607
1725
  return applied;
@@ -1647,6 +1765,7 @@ exports.orderedPackages = orderedPackages;
1647
1765
  exports.packageName = packageName;
1648
1766
  exports.readManifest = readManifest;
1649
1767
  exports.releaseWorkspace = releaseWorkspace;
1768
+ exports.resolveBundledDependencies = resolveBundledDependencies;
1650
1769
  exports.resolvePublishPlugins = resolvePublishPlugins;
1651
1770
  exports.resolveWorkspacePublishPlugins = resolveWorkspacePublishPlugins;
1652
1771
  exports.resumeWorkspaceRelease = resumeWorkspaceRelease;
package/dist/index.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { AnalyzeCommitsContext, BranchSpec, Commit, GenerateNotesContext } from "semantic-release";
1
+ import { AnalyzeCommitsContext, BranchSpec, Commit, GenerateNotesContext, ReleaseType } from "semantic-release";
2
2
  import { ReleaseGateState } from "@exadev/release-gate";
3
3
  //#region src/package-name.d.ts
4
4
  export declare const packageName = "@exadev/semantic-release-workspace";
@@ -132,6 +132,31 @@ export declare function classifyDependencyRange(current: string): DependencyRang
132
132
  */
133
133
  export declare function updateDependencyRange(current: string, version: string): DependencyRangeUpdate;
134
134
  //#endregion
135
+ //#region src/bundling.d.ts
136
+ /**
137
+ * Which of a package's workspace dependencies ship inside its published artefact, as far as release types are concerned. Built by `resolveBundledDependencies`.
138
+ */
139
+ interface BundledDependencies {
140
+ /**
141
+ * The dependencies whose release type sets this package's own when they release: every workspace sibling in the `devDependencies` of a package named in `bundlingPackages`. Empty for every other package, which keeps the forced patch release a dependency bump has always given it.
142
+ */
143
+ readonly releaseTypeFrom: ReadonlySet<string>;
144
+ /**
145
+ * The dependencies whose shipped release type this package passes on to whatever bundles it: everything in `releaseTypeFrom`, plus, for a private package, every private sibling in its `dependencies`, `peerDependencies` or `optionalDependencies`. A private package is never installed from the registry, and neither is a private sibling it needs at run time, so a bundle that inlines the one inlines the other too.
146
+ */
147
+ readonly carried: ReadonlySet<string>;
148
+ }
149
+ /**
150
+ * Validates `bundlingPackages` against the workspace and works out, for every package, which workspace dependencies it bundles.
151
+ *
152
+ * 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.
153
+ *
154
+ * 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.
155
+ *
156
+ * `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.
157
+ */
158
+ export declare function resolveBundledDependencies(graph: DependencyGraph, bundlingPackages: readonly string[] | undefined): ReadonlyMap<string, BundledDependencies>;
159
+ //#endregion
135
160
  //#region src/plugins.d.ts
136
161
  /**
137
162
  * One workspace dependency range that changed because its package released a new version during this run. `rewritten` means the dependent's manifest was edited on disk; `resolved-at-publish` means a bare `workspace:^`-style range whose on-disk text is unchanged. Both count towards the dependent's release.
@@ -142,6 +167,10 @@ interface DependencyBump {
142
167
  /** The range as it now stands in the dependent's manifest -- the new concrete range for `rewritten`, the untouched `workspace:` range for `resolved-at-publish`. */
143
168
  readonly range: string;
144
169
  readonly kind: 'rewritten' | 'resolved-at-publish';
170
+ /**
171
+ * The highest release type in the code the dependency ships as of this release: its own release type, raised by the shipped release type of every sibling it bundles (see `BundledDependencies`). A dependent that bundles the dependency takes at least this release type; any other dependent ignores it and keeps the forced patch. Absent only for a bump recovered from a commit written before its trailer recorded the type, or supplied by a `DependencyBumpSource` that does not know it, and then counts as a patch.
172
+ */
173
+ readonly releaseType?: ReleaseType | undefined;
145
174
  }
146
175
  /** What the scoped plugins need to know about bumps recorded so far in the run, for the package they are about to analyse. */
147
176
  interface DependencyBumpSource {
@@ -167,19 +196,25 @@ interface ScopedPlugins {
167
196
  readonly generateNotes: (pluginConfig: Record<string, unknown>, context: GenerateNotesContext & {
168
197
  cwd: string;
169
198
  }) => Promise<string | false | undefined>;
199
+ /**
200
+ * The release type the package's released code carries to whatever bundles it: the highest of `releaseType` (the type the package itself released as) and the release types of the bumps it carries (`BundledDependencies.carried`) from the most recent `analyzeCommits` call. A carried bump with no recorded type counts as a patch. `releaseType` is the type semantic-release released the package as.
201
+ */
202
+ readonly shippedReleaseType: (releaseType: ReleaseType) => ReleaseType;
170
203
  }
171
204
  /**
172
205
  * Builds the per-package `analyzeCommits` and `generateNotes` functions handed to semantic-release as inline plugins.
173
206
  *
174
207
  * 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.
175
208
  *
176
- * 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.
209
+ * 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.
177
210
  */
178
211
  export declare function createScopedPlugins(scope: {
179
212
  readonly pkg: WorkspacePackage;
180
213
  readonly analyzeCommitsConfig: Record<string, unknown>;
181
214
  readonly generateNotesConfig: Record<string, unknown>;
182
215
  readonly bumps: DependencyBumpSource;
216
+ /** Which of the package's workspace dependencies it bundles; see `resolveBundledDependencies`. Omitted for a package that bundles none, which keeps the forced patch for every dependency bump. */
217
+ readonly bundled?: BundledDependencies;
183
218
  /** Called with the path-filtered commit list every time this package's commits are resolved (from either step, whichever runs first). Optional: `commitStrategy: 'single'` uses it to capture the same filtered list `success()`'s GitHub plugin step needs later, without recomputing `changedPathsSince`/`filterCommitsToDirectory` itself from the outside. */
184
219
  readonly onCommitsResolved?: (commits: readonly Commit[]) => void;
185
220
  }): ScopedPlugins;
@@ -287,12 +322,20 @@ interface ReleaseWorkspaceOptions {
287
322
  * Raise it for a busier branch or a slower workspace; the cost of a higher bound is only paid when attempts are actually being lost.
288
323
  */
289
324
  readonly pushAttempts?: number;
325
+ /**
326
+ * Names of public packages whose build bundles every workspace sibling they list in `devDependencies`, so consumers never install those siblings from the registry. When such a sibling releases, the bundling package releases as at least the sibling's release type (a `feat` in a bundled package makes the bundling package a minor, a breaking change a major) instead of the patch a dependency bump otherwise forces; with releasable commits of its own, it releases as the higher of the two. A private sibling's release type reaches the bundling package through private packages that depend on it at run time, too, so a sibling bundled only indirectly still counts. Every other package, and every `dependencies`, `peerDependencies` or `optionalDependencies` edge, keeps the forced patch, and private packages' own versions and tags are unchanged.
327
+ *
328
+ * Off unless named, because a workspace `devDependencies` entry is not bundled in general: a public package's test helper or lint config is a workspace devDependency whose changes never reach its consumers. A name that is not a package in the workspace, or is a private package, is rejected. Applies under every `commitStrategy` and to `gatePublish`. See `resolveBundledDependencies`.
329
+ */
330
+ readonly bundlingPackages?: readonly string[];
290
331
  }
291
332
  /** One dependency-range change applied to a dependent package's manifest during the run, attached to the dependent's own outcome. */
292
333
  interface AppliedDependencyBump extends DependencyBump {
293
334
  readonly dependent: string;
294
335
  /** Which manifest field held the range that was rewritten. */
295
336
  readonly field: DependencyField;
337
+ /** Always known for a bump made in this run: see `DependencyBump.releaseType`. */
338
+ readonly releaseType: ReleaseType;
296
339
  }
297
340
  type PackageReleaseOutcome = {
298
341
  readonly name: string;
@@ -353,4 +396,4 @@ export declare class GitCommandError extends WorkspaceReleaseError {
353
396
  /** The workspace's git state does not support the release operation -- for example a detached HEAD, which names no branch that dependency-bump commits could be pushed to. */
354
397
  export declare class WorkspaceStateError extends WorkspaceReleaseError {}
355
398
  //#endregion
356
- export type { AppliedDependencyBump, CommitStrategy, DependencyBump, DependencyBumpSource, DependencyField, DependencyGraph, DependencyRangeShape, DependencyRangeUpdate, DetachedPackageRelease, PackageManifest, PackagePluginSpecs, PackageReleaseOutcome, PublishPluginPackage, PublishPluginSpec, ReleaseWorkspaceOptions, ResolvedPublishPlugin, ResumeWorkspaceReleaseOptions, ScopedPlugins, Workspace, WorkspaceDependency, WorkspacePackage, WorkspaceReleaseOutcome };
399
+ export type { AppliedDependencyBump, BundledDependencies, CommitStrategy, DependencyBump, DependencyBumpSource, DependencyField, DependencyGraph, DependencyRangeShape, DependencyRangeUpdate, DetachedPackageRelease, PackageManifest, PackagePluginSpecs, PackageReleaseOutcome, PublishPluginPackage, PublishPluginSpec, ReleaseWorkspaceOptions, ResolvedPublishPlugin, ResumeWorkspaceReleaseOptions, ScopedPlugins, Workspace, WorkspaceDependency, WorkspacePackage, WorkspaceReleaseOutcome };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { AnalyzeCommitsContext, BranchSpec, Commit, GenerateNotesContext } from "semantic-release";
1
+ import { AnalyzeCommitsContext, BranchSpec, Commit, GenerateNotesContext, ReleaseType } from "semantic-release";
2
2
  import { ReleaseGateState } from "@exadev/release-gate";
3
3
  //#region src/package-name.d.ts
4
4
  export declare const packageName = "@exadev/semantic-release-workspace";
@@ -132,6 +132,31 @@ export declare function classifyDependencyRange(current: string): DependencyRang
132
132
  */
133
133
  export declare function updateDependencyRange(current: string, version: string): DependencyRangeUpdate;
134
134
  //#endregion
135
+ //#region src/bundling.d.ts
136
+ /**
137
+ * Which of a package's workspace dependencies ship inside its published artefact, as far as release types are concerned. Built by `resolveBundledDependencies`.
138
+ */
139
+ interface BundledDependencies {
140
+ /**
141
+ * The dependencies whose release type sets this package's own when they release: every workspace sibling in the `devDependencies` of a package named in `bundlingPackages`. Empty for every other package, which keeps the forced patch release a dependency bump has always given it.
142
+ */
143
+ readonly releaseTypeFrom: ReadonlySet<string>;
144
+ /**
145
+ * The dependencies whose shipped release type this package passes on to whatever bundles it: everything in `releaseTypeFrom`, plus, for a private package, every private sibling in its `dependencies`, `peerDependencies` or `optionalDependencies`. A private package is never installed from the registry, and neither is a private sibling it needs at run time, so a bundle that inlines the one inlines the other too.
146
+ */
147
+ readonly carried: ReadonlySet<string>;
148
+ }
149
+ /**
150
+ * Validates `bundlingPackages` against the workspace and works out, for every package, which workspace dependencies it bundles.
151
+ *
152
+ * 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.
153
+ *
154
+ * 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.
155
+ *
156
+ * `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.
157
+ */
158
+ export declare function resolveBundledDependencies(graph: DependencyGraph, bundlingPackages: readonly string[] | undefined): ReadonlyMap<string, BundledDependencies>;
159
+ //#endregion
135
160
  //#region src/plugins.d.ts
136
161
  /**
137
162
  * One workspace dependency range that changed because its package released a new version during this run. `rewritten` means the dependent's manifest was edited on disk; `resolved-at-publish` means a bare `workspace:^`-style range whose on-disk text is unchanged. Both count towards the dependent's release.
@@ -142,6 +167,10 @@ interface DependencyBump {
142
167
  /** The range as it now stands in the dependent's manifest -- the new concrete range for `rewritten`, the untouched `workspace:` range for `resolved-at-publish`. */
143
168
  readonly range: string;
144
169
  readonly kind: 'rewritten' | 'resolved-at-publish';
170
+ /**
171
+ * The highest release type in the code the dependency ships as of this release: its own release type, raised by the shipped release type of every sibling it bundles (see `BundledDependencies`). A dependent that bundles the dependency takes at least this release type; any other dependent ignores it and keeps the forced patch. Absent only for a bump recovered from a commit written before its trailer recorded the type, or supplied by a `DependencyBumpSource` that does not know it, and then counts as a patch.
172
+ */
173
+ readonly releaseType?: ReleaseType | undefined;
145
174
  }
146
175
  /** What the scoped plugins need to know about bumps recorded so far in the run, for the package they are about to analyse. */
147
176
  interface DependencyBumpSource {
@@ -167,19 +196,25 @@ interface ScopedPlugins {
167
196
  readonly generateNotes: (pluginConfig: Record<string, unknown>, context: GenerateNotesContext & {
168
197
  cwd: string;
169
198
  }) => Promise<string | false | undefined>;
199
+ /**
200
+ * The release type the package's released code carries to whatever bundles it: the highest of `releaseType` (the type the package itself released as) and the release types of the bumps it carries (`BundledDependencies.carried`) from the most recent `analyzeCommits` call. A carried bump with no recorded type counts as a patch. `releaseType` is the type semantic-release released the package as.
201
+ */
202
+ readonly shippedReleaseType: (releaseType: ReleaseType) => ReleaseType;
170
203
  }
171
204
  /**
172
205
  * Builds the per-package `analyzeCommits` and `generateNotes` functions handed to semantic-release as inline plugins.
173
206
  *
174
207
  * 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.
175
208
  *
176
- * 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.
209
+ * 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.
177
210
  */
178
211
  export declare function createScopedPlugins(scope: {
179
212
  readonly pkg: WorkspacePackage;
180
213
  readonly analyzeCommitsConfig: Record<string, unknown>;
181
214
  readonly generateNotesConfig: Record<string, unknown>;
182
215
  readonly bumps: DependencyBumpSource;
216
+ /** Which of the package's workspace dependencies it bundles; see `resolveBundledDependencies`. Omitted for a package that bundles none, which keeps the forced patch for every dependency bump. */
217
+ readonly bundled?: BundledDependencies;
183
218
  /** Called with the path-filtered commit list every time this package's commits are resolved (from either step, whichever runs first). Optional: `commitStrategy: 'single'` uses it to capture the same filtered list `success()`'s GitHub plugin step needs later, without recomputing `changedPathsSince`/`filterCommitsToDirectory` itself from the outside. */
184
219
  readonly onCommitsResolved?: (commits: readonly Commit[]) => void;
185
220
  }): ScopedPlugins;
@@ -287,12 +322,20 @@ interface ReleaseWorkspaceOptions {
287
322
  * Raise it for a busier branch or a slower workspace; the cost of a higher bound is only paid when attempts are actually being lost.
288
323
  */
289
324
  readonly pushAttempts?: number;
325
+ /**
326
+ * Names of public packages whose build bundles every workspace sibling they list in `devDependencies`, so consumers never install those siblings from the registry. When such a sibling releases, the bundling package releases as at least the sibling's release type (a `feat` in a bundled package makes the bundling package a minor, a breaking change a major) instead of the patch a dependency bump otherwise forces; with releasable commits of its own, it releases as the higher of the two. A private sibling's release type reaches the bundling package through private packages that depend on it at run time, too, so a sibling bundled only indirectly still counts. Every other package, and every `dependencies`, `peerDependencies` or `optionalDependencies` edge, keeps the forced patch, and private packages' own versions and tags are unchanged.
327
+ *
328
+ * Off unless named, because a workspace `devDependencies` entry is not bundled in general: a public package's test helper or lint config is a workspace devDependency whose changes never reach its consumers. A name that is not a package in the workspace, or is a private package, is rejected. Applies under every `commitStrategy` and to `gatePublish`. See `resolveBundledDependencies`.
329
+ */
330
+ readonly bundlingPackages?: readonly string[];
290
331
  }
291
332
  /** One dependency-range change applied to a dependent package's manifest during the run, attached to the dependent's own outcome. */
292
333
  interface AppliedDependencyBump extends DependencyBump {
293
334
  readonly dependent: string;
294
335
  /** Which manifest field held the range that was rewritten. */
295
336
  readonly field: DependencyField;
337
+ /** Always known for a bump made in this run: see `DependencyBump.releaseType`. */
338
+ readonly releaseType: ReleaseType;
296
339
  }
297
340
  type PackageReleaseOutcome = {
298
341
  readonly name: string;
@@ -353,4 +396,4 @@ export declare class GitCommandError extends WorkspaceReleaseError {
353
396
  /** The workspace's git state does not support the release operation -- for example a detached HEAD, which names no branch that dependency-bump commits could be pushed to. */
354
397
  export declare class WorkspaceStateError extends WorkspaceReleaseError {}
355
398
  //#endregion
356
- export type { AppliedDependencyBump, CommitStrategy, DependencyBump, DependencyBumpSource, DependencyField, DependencyGraph, DependencyRangeShape, DependencyRangeUpdate, DetachedPackageRelease, PackageManifest, PackagePluginSpecs, PackageReleaseOutcome, PublishPluginPackage, PublishPluginSpec, ReleaseWorkspaceOptions, ResolvedPublishPlugin, ResumeWorkspaceReleaseOptions, ScopedPlugins, Workspace, WorkspaceDependency, WorkspacePackage, WorkspaceReleaseOutcome };
399
+ export type { AppliedDependencyBump, BundledDependencies, CommitStrategy, DependencyBump, DependencyBumpSource, DependencyField, DependencyGraph, DependencyRangeShape, DependencyRangeUpdate, DetachedPackageRelease, PackageManifest, PackagePluginSpecs, PackageReleaseOutcome, PublishPluginPackage, PublishPluginSpec, ReleaseWorkspaceOptions, ResolvedPublishPlugin, ResumeWorkspaceReleaseOptions, ScopedPlugins, Workspace, WorkspaceDependency, WorkspacePackage, WorkspaceReleaseOutcome };