@exadev/semantic-release-workspace 3.1.28 → 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/README.md +28 -2
- package/dist/cli.js +334 -210
- package/dist/index.cjs +140 -21
- package/dist/index.d.cts +46 -3
- package/dist/index.d.ts +46 -3
- package/dist/index.js +140 -22
- package/package.json +1 -1
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.
|
|
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
|
|
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
|
|
856
|
+
const analysed = await (0, _semantic_release_commit_analyzer.analyzeCommits)(scope.analyzeCommitsConfig, {
|
|
793
857
|
...context,
|
|
794
858
|
commits
|
|
795
859
|
});
|
|
796
|
-
if (typeof
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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
|
|
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
|
|
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 };
|