@pnpm/releasing.versioning 1100.2.6 → 1100.3.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/CHANGELOG.md +20 -0
- package/lib/assembleReleasePlan.d.ts +22 -0
- package/lib/assembleReleasePlan.js +63 -15
- package/lib/index.d.ts +1 -1
- package/lib/index.js +1 -1
- package/package.json +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# @pnpm/releasing.versioning
|
|
2
2
|
|
|
3
|
+
## 1100.3.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Added `pnpm change check`. It validates the committed package versions against the `versioning.epics` bands and the `versioning.fixed` groups in `pnpm-workspace.yaml` and lists every violation. It is meant to run in CI, because `pnpm version -r` only checks the packages it releases.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- Updated dependencies:
|
|
12
|
+
- @pnpm/error@1100.1.4
|
|
13
|
+
- @pnpm/workspace.project-manifest-reader@1100.0.27
|
|
14
|
+
|
|
15
|
+
## 1100.2.7
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- Updated dependencies:
|
|
20
|
+
- @pnpm/types@1102.1.0
|
|
21
|
+
- @pnpm/workspace.project-manifest-reader@1100.0.26
|
|
22
|
+
|
|
3
23
|
## 1100.2.6
|
|
4
24
|
|
|
5
25
|
### Patch Changes
|
|
@@ -90,6 +90,28 @@ export declare function indexProjectRefs(projects: ReadonlyArray<{
|
|
|
90
90
|
/** The workspace-relative directory of a project, in canonical spelling. */
|
|
91
91
|
export declare function toProjectDir(workspaceDir: string, rootDir: string): string;
|
|
92
92
|
export declare function assembleReleasePlan(opts: AssembleReleasePlanOptions): ReleasePlan;
|
|
93
|
+
export interface VersioningInvariantViolation {
|
|
94
|
+
code: 'VERSIONING_EPIC_OUT_OF_BAND' | 'VERSIONING_FIXED_GROUP_MISMATCH';
|
|
95
|
+
message: string;
|
|
96
|
+
}
|
|
97
|
+
export interface CheckVersioningInvariantsOptions {
|
|
98
|
+
workspaceDir: string;
|
|
99
|
+
projects: WorkspaceProject[];
|
|
100
|
+
versioning?: VersioningSettings;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Validates that the committed versions already satisfy the invariants the
|
|
104
|
+
* configuration declares: every epic member's major sits inside its lead's
|
|
105
|
+
* band, and every fixed group shares one version. This is the static
|
|
106
|
+
* counterpart to the release-time enforcement in `assembleReleasePlan`, which
|
|
107
|
+
* only checks packages a plan actually releases — so a committed manifest that
|
|
108
|
+
* drifted out of band, or a fixed group that fell out of lockstep, would
|
|
109
|
+
* otherwise go unnoticed until a release that happens to touch it. Returns
|
|
110
|
+
* every violation so a caller can report them all at once; malformed
|
|
111
|
+
* configuration (unknown lead, epic overlap, a group straddling an epic) still
|
|
112
|
+
* throws, exactly as plan assembly would.
|
|
113
|
+
*/
|
|
114
|
+
export declare function checkVersioningInvariants(opts: CheckVersioningInvariantsOptions): VersioningInvariantViolation[];
|
|
93
115
|
/**
|
|
94
116
|
* The range that pnpm materializes for a workspace: spec at pack time, given
|
|
95
117
|
* the dependency's version at the dependent's previous release. Dependent
|
|
@@ -72,6 +72,55 @@ export function assembleReleasePlan(opts) {
|
|
|
72
72
|
selection = expanded;
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* Validates that the committed versions already satisfy the invariants the
|
|
77
|
+
* configuration declares: every epic member's major sits inside its lead's
|
|
78
|
+
* band, and every fixed group shares one version. This is the static
|
|
79
|
+
* counterpart to the release-time enforcement in `assembleReleasePlan`, which
|
|
80
|
+
* only checks packages a plan actually releases — so a committed manifest that
|
|
81
|
+
* drifted out of band, or a fixed group that fell out of lockstep, would
|
|
82
|
+
* otherwise go unnoticed until a release that happens to touch it. Returns
|
|
83
|
+
* every violation so a caller can report them all at once; malformed
|
|
84
|
+
* configuration (unknown lead, epic overlap, a group straddling an epic) still
|
|
85
|
+
* throws, exactly as plan assembly would.
|
|
86
|
+
*/
|
|
87
|
+
export function checkVersioningInvariants(opts) {
|
|
88
|
+
const refs = indexProjectRefs(opts.projects, opts.workspaceDir);
|
|
89
|
+
const participants = collectParticipants(opts.projects, refs, opts);
|
|
90
|
+
const lanesByDir = resolveLanes(refs, participants, opts.versioning);
|
|
91
|
+
const fixedGroups = resolveFixedGroups(refs, participants, opts.versioning);
|
|
92
|
+
validateFixedGroupLanes(fixedGroups, lanesByDir, opts.versioning);
|
|
93
|
+
const epics = resolveEpics(refs, participants, opts.versioning);
|
|
94
|
+
validateEpics(epics, fixedGroups);
|
|
95
|
+
// With no plan (no new versions), the band derives from the lead's current
|
|
96
|
+
// major, and members are checked against their current versions.
|
|
97
|
+
const noNewVersions = new Map();
|
|
98
|
+
const violations = [];
|
|
99
|
+
for (const epic of epics) {
|
|
100
|
+
const band = epicBand(epic, participants, noNewVersions);
|
|
101
|
+
for (const memberDir of [...epic.memberDirs].sort()) {
|
|
102
|
+
const member = participants.get(memberDir);
|
|
103
|
+
const memberMajor = Number(member.currentVersion.split('.')[0]);
|
|
104
|
+
if (!band.contains(memberMajor)) {
|
|
105
|
+
violations.push({
|
|
106
|
+
code: 'VERSIONING_EPIC_OUT_OF_BAND',
|
|
107
|
+
message: `${member.name} is at ${member.currentVersion}, whose major ${memberMajor} is outside the band ${band.low}-${band.high} of the epic led by "${epic.leadRef}" (major ${band.major}).`,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
for (const [index, group] of fixedGroups.entries()) {
|
|
113
|
+
const members = group.map((dir) => participants.get(dir));
|
|
114
|
+
if (new Set(members.map((member) => member.currentVersion)).size > 1) {
|
|
115
|
+
const detail = members.map((member) => `${member.name}@${member.currentVersion}`).join(', ');
|
|
116
|
+
violations.push({
|
|
117
|
+
code: 'VERSIONING_FIXED_GROUP_MISMATCH',
|
|
118
|
+
message: `The fixed group [${(opts.versioning?.fixed ?? [])[index].join(', ')}] is not in lockstep: ${detail}.`,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return violations;
|
|
123
|
+
}
|
|
75
124
|
function assemble(ctx, selection) {
|
|
76
125
|
const { participants, lanesByDir, fixedGroups, epics, opts } = ctx;
|
|
77
126
|
const pendingByDir = collectPendingIntents(ctx);
|
|
@@ -603,9 +652,13 @@ function epicRebaseFloor(epic, participants, newVersions) {
|
|
|
603
652
|
if (lead == null || newLeadVersion == null || parsePrerelease(newLeadVersion) != null)
|
|
604
653
|
return null;
|
|
605
654
|
const newMajor = Number(newLeadVersion.split('.')[0]);
|
|
606
|
-
const currentMajor =
|
|
655
|
+
const currentMajor = epicLeadBandMajor(lead.currentVersion);
|
|
607
656
|
return newMajor > currentMajor ? newMajor * 100 : null;
|
|
608
657
|
}
|
|
658
|
+
function epicLeadBandMajor(version) {
|
|
659
|
+
const [major, minor, patch] = stablePart(version).split('.').map(Number);
|
|
660
|
+
return parsePrerelease(version) != null && minor === 0 && patch === 0 ? Math.max(0, major - 1) : major;
|
|
661
|
+
}
|
|
609
662
|
/**
|
|
610
663
|
* Overrides the computed version of every bumped epic member with the band
|
|
611
664
|
* floor when its lead crosses to a new stable major. A member on a lane
|
|
@@ -627,15 +680,12 @@ function applyEpicBandVersions({ participants, state, newVersions, epics, lanesB
|
|
|
627
680
|
}
|
|
628
681
|
}
|
|
629
682
|
}
|
|
630
|
-
|
|
631
|
-
* The band of member majors an epic permits: `[leadMajor×100, leadMajor×100+99]`,
|
|
632
|
-
* where `leadMajor` is the major the plan establishes for the lead — its
|
|
633
|
-
* re-based major when the lead crosses to a new stable major, otherwise the
|
|
634
|
-
* lead's current major (a prerelease lead does not open the next band).
|
|
635
|
-
*/
|
|
636
|
-
function epicBandMajor(epic, participants, newVersions) {
|
|
683
|
+
function epicBand(epic, participants, newVersions) {
|
|
637
684
|
const floor = epicRebaseFloor(epic, participants, newVersions);
|
|
638
|
-
|
|
685
|
+
const major = floor != null ? floor / 100 : epicLeadBandMajor(participants.get(epic.leadDir).currentVersion);
|
|
686
|
+
const low = major * 100;
|
|
687
|
+
const high = low + 99;
|
|
688
|
+
return { major, low, high, contains: (memberMajor) => memberMajor >= low && memberMajor <= high };
|
|
639
689
|
}
|
|
640
690
|
/**
|
|
641
691
|
* Enforces that every released member's new major stays inside its epic's band.
|
|
@@ -646,17 +696,15 @@ function epicBandMajor(epic, participants, newVersions) {
|
|
|
646
696
|
*/
|
|
647
697
|
function enforceEpicBands(epics, participants, newVersions) {
|
|
648
698
|
for (const epic of epics) {
|
|
649
|
-
const
|
|
650
|
-
const low = bandMajor * 100;
|
|
651
|
-
const high = low + 99;
|
|
699
|
+
const band = epicBand(epic, participants, newVersions);
|
|
652
700
|
for (const memberDir of epic.memberDirs) {
|
|
653
701
|
const memberVersion = newVersions.get(memberDir);
|
|
654
702
|
if (memberVersion == null)
|
|
655
703
|
continue;
|
|
656
704
|
const memberMajor = Number(memberVersion.split('.')[0]);
|
|
657
|
-
if (memberMajor
|
|
658
|
-
throw new PnpmError('VERSIONING_EPIC_OUT_OF_BAND', `The release plan takes ${participants.get(memberDir).name} to ${memberVersion}, whose major ${memberMajor} is outside the band ${low}-${high} of the epic led by "${epic.leadRef}" (major ${
|
|
659
|
-
(memberMajor > high
|
|
705
|
+
if (!band.contains(memberMajor)) {
|
|
706
|
+
throw new PnpmError('VERSIONING_EPIC_OUT_OF_BAND', `The release plan takes ${participants.get(memberDir).name} to ${memberVersion}, whose major ${memberMajor} is outside the band ${band.low}-${band.high} of the epic led by "${epic.leadRef}" (major ${band.major}). ` +
|
|
707
|
+
(memberMajor > band.high
|
|
660
708
|
? 'The band is exhausted - the lead must advance to a new major to open the next band.'
|
|
661
709
|
: 'Re-base the member into the band, or remove it from the epic.'));
|
|
662
710
|
}
|
package/lib/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { type AppliedRelease, applyReleasePlan, type ApplyReleasePlanOptions, changelogStorage, } from './applyReleasePlan.js';
|
|
2
|
-
export { assembleReleasePlan, type AssembleReleasePlanOptions, type DependencyUpdate, indexProjectRefs, isDirRef, materializeWorkspaceRange, type PlannedRelease, type ProjectRefIndex, type ReleaseCause, type ReleasePlan, toProjectDir, type WorkspaceProject, } from './assembleReleasePlan.js';
|
|
2
|
+
export { assembleReleasePlan, type AssembleReleasePlanOptions, checkVersioningInvariants, type CheckVersioningInvariantsOptions, type DependencyUpdate, indexProjectRefs, isDirRef, materializeWorkspaceRange, type PlannedRelease, type ProjectRefIndex, type ReleaseCause, type ReleasePlan, toProjectDir, type VersioningInvariantViolation, type WorkspaceProject, } from './assembleReleasePlan.js';
|
|
3
3
|
export { composeChangelogSection, prependChangelogSection, renderChangelog, } from './changelog.js';
|
|
4
4
|
export { BUMP_TYPES, type ChangeIntent, CHANGES_DIR, type IntentBumpType, parseChangeIntent, readChangeIntents, type ReleaseBumpType, writeChangeIntent, type WriteChangeIntentOptions, } from './intents.js';
|
|
5
5
|
export { appendToLedger, buildConsumptionIndex, type Ledger, LEDGER_FILENAME, type LedgerEntry, ledgerEntryIds, normalizeProjectDir, type PackageConsumption, readLedger, } from './ledger.js';
|
package/lib/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { applyReleasePlan, changelogStorage, } from './applyReleasePlan.js';
|
|
2
|
-
export { assembleReleasePlan, indexProjectRefs, isDirRef, materializeWorkspaceRange, toProjectDir, } from './assembleReleasePlan.js';
|
|
2
|
+
export { assembleReleasePlan, checkVersioningInvariants, indexProjectRefs, isDirRef, materializeWorkspaceRange, toProjectDir, } from './assembleReleasePlan.js';
|
|
3
3
|
export { composeChangelogSection, prependChangelogSection, renderChangelog, } from './changelog.js';
|
|
4
4
|
export { BUMP_TYPES, CHANGES_DIR, parseChangeIntent, readChangeIntents, writeChangeIntent, } from './intents.js';
|
|
5
5
|
export { appendToLedger, buildConsumptionIndex, LEDGER_FILENAME, ledgerEntryIds, normalizeProjectDir, readLedger, } from './ledger.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pnpm/releasing.versioning",
|
|
3
|
-
"version": "1100.
|
|
3
|
+
"version": "1100.3.0",
|
|
4
4
|
"description": "Native workspace release management: change intents, release-plan assembly, and changelog writing",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pnpm",
|
|
@@ -27,9 +27,9 @@
|
|
|
27
27
|
"!*.map"
|
|
28
28
|
],
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@pnpm/error": "1100.1.
|
|
31
|
-
"@pnpm/types": "1102.
|
|
32
|
-
"@pnpm/workspace.project-manifest-reader": "1100.0.
|
|
30
|
+
"@pnpm/error": "1100.1.4",
|
|
31
|
+
"@pnpm/types": "1102.1.0",
|
|
32
|
+
"@pnpm/workspace.project-manifest-reader": "1100.0.27",
|
|
33
33
|
"@pnpm/workspace.spec-parser": "1100.0.1",
|
|
34
34
|
"human-id": "^4.2.1",
|
|
35
35
|
"semver": "^7.8.5",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@jest/globals": "30.4.1",
|
|
40
|
-
"@pnpm/releasing.versioning": "1100.
|
|
40
|
+
"@pnpm/releasing.versioning": "1100.3.0",
|
|
41
41
|
"@types/semver": "7.8.0",
|
|
42
42
|
"tempy": "3.0.0"
|
|
43
43
|
},
|