@pnpm/releasing.versioning 1100.1.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 +17 -0
- package/LICENSE +22 -0
- package/README.md +24 -0
- package/lib/applyReleasePlan.d.ts +35 -0
- package/lib/applyReleasePlan.js +115 -0
- package/lib/assembleReleasePlan.d.ts +91 -0
- package/lib/assembleReleasePlan.js +699 -0
- package/lib/changelog.d.ts +12 -0
- package/lib/changelog.js +84 -0
- package/lib/index.d.ts +6 -0
- package/lib/index.js +7 -0
- package/lib/intents.d.ts +17 -0
- package/lib/intents.js +76 -0
- package/lib/ledger.d.ts +50 -0
- package/lib/ledger.js +130 -0
- package/lib/pendingChangelog.d.ts +26 -0
- package/lib/pendingChangelog.js +84 -0
- package/package.json +56 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# @pnpm/releasing.versioning
|
|
2
|
+
|
|
3
|
+
## 1100.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Added `versioning.epics` to `pnpm-workspace.yaml`. An epic ties a group of member packages to a lead package, constraining every member's major version to a band derived from the lead's major: while the lead is on major `M`, members live in `M*100 … M*100+99`. Members move independently inside the band (patch, minor, and a `major` intent that stays in-band); a bump that would carry a member past the band ceiling is rejected until the lead advances its own major. When a release plan takes the lead to a new stable major, every member re-bases to the band floor in the same plan. Membership is matched with pnpm's package selectors — name globs, `./`-prefixed directory globs, and `!`-prefixed negations.
|
|
8
|
+
|
|
9
|
+
- Added native workspace release management [#12952](https://github.com/pnpm/pnpm/issues/12952): the new `pnpm change` command records change intents as changesets-compatible `.changeset/*.md` files (`pnpm change status` shows the pending release plan), and the bare `pnpm version -r` consumes them — bumping versions across the workspace with dependent propagation through `workspace:` ranges, fixed groups, a `maxBump` cap, `--filter` narrowing, and `--dry-run` — writing changelogs, and recording consumed intents in a committed ledger that keeps cherry-picks and merge-backs between release branches safe. Packages can be moved onto per-package release lanes with the new `pnpm lane <name> --filter <pkg>` command and back with `pnpm lane main --filter <pkg>` (`pnpm lane` shows the membership), releasing `X.Y.Z-lane.N` prereleases from the same runs that release stable versions of the packages on the main lane. Configuration lives under the new `versioning` key of `pnpm-workspace.yaml` (`fixed`, `ignore`, `maxBump`, `lanes`, `changelog`). When two workspace projects publish the same name, intent files, `versioning.lanes`, and `versioning.fixed`/`ignore` may reference a project by its workspace-relative directory path (e.g. `"./pnpm/npm/pnpm"`) — the one additive extension to the changesets format, applied automatically by `pnpm change`.
|
|
10
|
+
|
|
11
|
+
Release changelogs default to `registry` storage (`versioning.changelog.storage`): no `CHANGELOG.md` is committed. Each release's section is composed at publish time and packed into the published tarball on top of the previously published version's changelog, and the consumed change intents are garbage-collected by a later `pnpm version -r` only once the registry confirms the version is published with its section. Set `versioning.changelog.storage: repository` to keep committed `CHANGELOG.md` files instead.
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- Updated dependencies:
|
|
16
|
+
- @pnpm/types@1101.4.0
|
|
17
|
+
- @pnpm/workspace.project-manifest-reader@1100.0.15
|
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2015-2016 Rico Sta. Cruz and other contributors
|
|
4
|
+
Copyright (c) 2016-2026 Zoltan Kochan and other contributors
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# @pnpm/releasing.versioning
|
|
2
|
+
|
|
3
|
+
> Native workspace release management: change intents, release-plan assembly, and changelog writing
|
|
4
|
+
|
|
5
|
+
[](https://npmx.dev/package/@pnpm/releasing.versioning)
|
|
6
|
+
|
|
7
|
+
Implements the engine behind `pnpm change` and the bare `pnpm version -r`:
|
|
8
|
+
reading and writing changesets-compatible change-intent files from
|
|
9
|
+
`.changeset/*.md`, assembling a release plan (direct bumps, dependent
|
|
10
|
+
propagation through materialized `workspace:` ranges, fixed groups,
|
|
11
|
+
per-package release lanes), and applying it (manifest version updates,
|
|
12
|
+
changelog composition, the consumed-intents ledger, and intent-file cleanup).
|
|
13
|
+
|
|
14
|
+
See the [native monorepo versioning RFC](https://github.com/pnpm/rfcs/pull/18).
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
pnpm add @pnpm/releasing.versioning
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## License
|
|
23
|
+
|
|
24
|
+
MIT
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { VersioningChangelogStorage, VersioningSettings } from '@pnpm/types';
|
|
2
|
+
import { type ReleasePlan, type WorkspaceProject } from './assembleReleasePlan.js';
|
|
3
|
+
import type { ChangeIntent } from './intents.js';
|
|
4
|
+
/**
|
|
5
|
+
* Registry storage is the default: no CHANGELOG.md is committed; a release's
|
|
6
|
+
* section is parked (see {@link writePendingChangelog}) and packed into the
|
|
7
|
+
* published tarball. `repository` opts back into committed changelogs.
|
|
8
|
+
*/
|
|
9
|
+
export declare function changelogStorage(versioning?: VersioningSettings): VersioningChangelogStorage;
|
|
10
|
+
export interface ApplyReleasePlanOptions {
|
|
11
|
+
workspaceDir: string;
|
|
12
|
+
projects: WorkspaceProject[];
|
|
13
|
+
/**
|
|
14
|
+
* All intent files currently in the workspace, used to decide which files
|
|
15
|
+
* are fully consumed after this run and can be deleted.
|
|
16
|
+
*/
|
|
17
|
+
allIntents: ChangeIntent[];
|
|
18
|
+
versioning?: VersioningSettings;
|
|
19
|
+
/**
|
|
20
|
+
* In `registry` changelog storage, the gate that lets a ledgered release's
|
|
21
|
+
* intents be garbage-collected: it must resolve `true` only once the
|
|
22
|
+
* registry has published `${name}@${version}` and that tarball's
|
|
23
|
+
* CHANGELOG.md already contains `section`. Without it, registry-mode
|
|
24
|
+
* releases keep their intents (the repository is still their only prose).
|
|
25
|
+
* Ignored in `repository` storage, where the committed changelog already
|
|
26
|
+
* holds the prose and the ledger alone gates deletion.
|
|
27
|
+
*/
|
|
28
|
+
verifyPublished?: (name: string, version: string, section: string) => Promise<boolean>;
|
|
29
|
+
}
|
|
30
|
+
export interface AppliedRelease {
|
|
31
|
+
name: string;
|
|
32
|
+
currentVersion: string;
|
|
33
|
+
newVersion: string;
|
|
34
|
+
}
|
|
35
|
+
export declare function applyReleasePlan(plan: ReleasePlan, opts: ApplyReleasePlanOptions): Promise<AppliedRelease[]>;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import { readProjectManifest } from '@pnpm/workspace.project-manifest-reader';
|
|
3
|
+
import { indexProjectRefs } from './assembleReleasePlan.js';
|
|
4
|
+
import { composeChangelogSection, prependChangelogSection } from './changelog.js';
|
|
5
|
+
import { appendToLedger, buildConsumptionIndex } from './ledger.js';
|
|
6
|
+
import { listPendingChangelogs, readPendingChangelog, removePendingChangelog, writePendingChangelog } from './pendingChangelog.js';
|
|
7
|
+
/**
|
|
8
|
+
* Registry storage is the default: no CHANGELOG.md is committed; a release's
|
|
9
|
+
* section is parked (see {@link writePendingChangelog}) and packed into the
|
|
10
|
+
* published tarball. `repository` opts back into committed changelogs.
|
|
11
|
+
*/
|
|
12
|
+
export function changelogStorage(versioning) {
|
|
13
|
+
return versioning?.changelog?.storage ?? 'registry';
|
|
14
|
+
}
|
|
15
|
+
export async function applyReleasePlan(plan, opts) {
|
|
16
|
+
const storage = changelogStorage(opts.versioning);
|
|
17
|
+
const applied = await Promise.all(plan.releases.map(async (release) => {
|
|
18
|
+
const { manifest, writeProjectManifest } = await readProjectManifest(release.rootDir);
|
|
19
|
+
manifest.version = release.newVersion;
|
|
20
|
+
await writeProjectManifest(manifest);
|
|
21
|
+
return {
|
|
22
|
+
name: release.name,
|
|
23
|
+
currentVersion: release.currentVersion,
|
|
24
|
+
newVersion: release.newVersion,
|
|
25
|
+
};
|
|
26
|
+
}));
|
|
27
|
+
// In `repository` storage the section is committed to CHANGELOG.md now. In
|
|
28
|
+
// `registry` storage nothing is committed to the package; the section is
|
|
29
|
+
// parked until publish, when it is packed into the tarball.
|
|
30
|
+
await Promise.all(plan.releases.map(async (release) => {
|
|
31
|
+
const section = composeChangelogSection(release);
|
|
32
|
+
if (storage === 'repository') {
|
|
33
|
+
await prependChangelogSection(release.rootDir, release.name, section);
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
await writePendingChangelog(opts.workspaceDir, release.name, release.newVersion, section);
|
|
37
|
+
}
|
|
38
|
+
}));
|
|
39
|
+
const newEntries = {};
|
|
40
|
+
for (const release of plan.releases) {
|
|
41
|
+
if (release.intents.length === 0)
|
|
42
|
+
continue;
|
|
43
|
+
newEntries[`${release.name}@${release.newVersion}`] = {
|
|
44
|
+
dir: release.dir,
|
|
45
|
+
intents: release.intents.map((intent) => intent.id).sort(),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
const ledger = await appendToLedger(opts.workspaceDir, newEntries);
|
|
49
|
+
// An intent file is deletable once every project it names has a consumed
|
|
50
|
+
// ledger entry for it, with one exemption: while a project is still on a
|
|
51
|
+
// lane, entries against prerelease versions alone keep the file alive — its
|
|
52
|
+
// prose is still needed to compose the stable changelog section at
|
|
53
|
+
// graduation. Declined (`none`) entries demand no release and never block
|
|
54
|
+
// deletion. References here were already validated by the plan assembly,
|
|
55
|
+
// so an unresolvable one just keeps its file around.
|
|
56
|
+
//
|
|
57
|
+
// In `registry` storage the ledger alone does not authorize deletion: the
|
|
58
|
+
// repository is the only copy of the prose until the release is published,
|
|
59
|
+
// so an entry counts as consumed only once the registry confirms its version
|
|
60
|
+
// carries the composed section.
|
|
61
|
+
const refs = indexProjectRefs(opts.projects, opts.workspaceDir);
|
|
62
|
+
const consumedLedger = storage === 'repository'
|
|
63
|
+
? ledger
|
|
64
|
+
: await confirmPublished(ledger, opts);
|
|
65
|
+
const consumptionOf = buildConsumptionIndex(consumedLedger, refs.nameToDirs);
|
|
66
|
+
const laneDirs = new Set();
|
|
67
|
+
for (const ref of Object.keys(opts.versioning?.lanes ?? {})) {
|
|
68
|
+
for (const dir of refs.refToDirs(ref)) {
|
|
69
|
+
laneDirs.add(dir);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const deletable = opts.allIntents.filter((intent) => Object.entries(intent.releases).every(([ref, bumpType]) => {
|
|
73
|
+
if (bumpType === 'none')
|
|
74
|
+
return true;
|
|
75
|
+
const dirs = refs.refToDirs(ref);
|
|
76
|
+
if (dirs.length !== 1)
|
|
77
|
+
return false;
|
|
78
|
+
const consumption = consumptionOf(dirs[0]);
|
|
79
|
+
return consumption.allIds.has(intent.id) &&
|
|
80
|
+
!(laneDirs.has(dirs[0]) && consumption.prereleaseOnlyIds.has(intent.id));
|
|
81
|
+
}));
|
|
82
|
+
await Promise.all(deletable.map(async (intent) => fs.rm(intent.filePath)));
|
|
83
|
+
return applied;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Confirms the parked sections whose releases the registry reports published
|
|
87
|
+
* with that section, deletes those section files (their prose now lives in the
|
|
88
|
+
* published tarball), and returns the subset of `ledger` those confirmations
|
|
89
|
+
* cover — the consumed ledger that gates intent deletion. Driven by the parked
|
|
90
|
+
* files rather than the ledger, so a dependency-propagated release (which has a
|
|
91
|
+
* section but no consumed intents, hence no ledger entry) still gets its
|
|
92
|
+
* section collected. Every parked file belongs to an as-yet-unpublished
|
|
93
|
+
* release, so the confirmation cost is bounded by the release backlog, not by
|
|
94
|
+
* history.
|
|
95
|
+
*/
|
|
96
|
+
async function confirmPublished(ledger, opts) {
|
|
97
|
+
const { verifyPublished } = opts;
|
|
98
|
+
const confirmed = Object.create(null);
|
|
99
|
+
if (verifyPublished == null)
|
|
100
|
+
return confirmed;
|
|
101
|
+
const pending = await listPendingChangelogs(opts.workspaceDir);
|
|
102
|
+
await Promise.all(pending.map(async ({ name, version }) => {
|
|
103
|
+
const section = await readPendingChangelog(opts.workspaceDir, name, version);
|
|
104
|
+
if (section == null)
|
|
105
|
+
return;
|
|
106
|
+
if (!(await verifyPublished(name, version, section)))
|
|
107
|
+
return;
|
|
108
|
+
const key = `${name}@${version}`;
|
|
109
|
+
if (ledger[key] != null)
|
|
110
|
+
confirmed[key] = ledger[key];
|
|
111
|
+
await removePendingChangelog(opts.workspaceDir, name, version);
|
|
112
|
+
}));
|
|
113
|
+
return confirmed;
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=applyReleasePlan.js.map
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { ProjectManifest, VersioningSettings } from '@pnpm/types';
|
|
2
|
+
import type { ChangeIntent, ReleaseBumpType } from './intents.js';
|
|
3
|
+
import { type Ledger } from './ledger.js';
|
|
4
|
+
export interface WorkspaceProject {
|
|
5
|
+
rootDir: string;
|
|
6
|
+
manifest: ProjectManifest;
|
|
7
|
+
}
|
|
8
|
+
export type ReleaseCause = 'intent' | 'dependencies' | 'fixed' | 'epic';
|
|
9
|
+
export interface DependencyUpdate {
|
|
10
|
+
name: string;
|
|
11
|
+
newVersion: string;
|
|
12
|
+
}
|
|
13
|
+
export interface PlannedRelease {
|
|
14
|
+
name: string;
|
|
15
|
+
/** Workspace-relative project directory — the engine's unit of identity. */
|
|
16
|
+
dir: string;
|
|
17
|
+
rootDir: string;
|
|
18
|
+
currentVersion: string;
|
|
19
|
+
newVersion: string;
|
|
20
|
+
bumpType: ReleaseBumpType;
|
|
21
|
+
/**
|
|
22
|
+
* The intent files this release consumes for this package: the pending ones,
|
|
23
|
+
* plus — when the release graduates the package off a lane — the
|
|
24
|
+
* ones the ledger recorded against the lane's prerelease versions.
|
|
25
|
+
*/
|
|
26
|
+
intents: ChangeIntent[];
|
|
27
|
+
dependencyUpdates: DependencyUpdate[];
|
|
28
|
+
causes: ReleaseCause[];
|
|
29
|
+
}
|
|
30
|
+
export interface ReleasePlan {
|
|
31
|
+
releases: PlannedRelease[];
|
|
32
|
+
}
|
|
33
|
+
export interface AssembleReleasePlanOptions {
|
|
34
|
+
workspaceDir: string;
|
|
35
|
+
projects: WorkspaceProject[];
|
|
36
|
+
intents: ChangeIntent[];
|
|
37
|
+
ledger: Ledger;
|
|
38
|
+
versioning?: VersioningSettings;
|
|
39
|
+
/**
|
|
40
|
+
* Workspace-relative directories of the projects selected with --filter.
|
|
41
|
+
* The plan is narrowed to the selected packages' portion of the pending
|
|
42
|
+
* work, expanded with their fixed-group companions and range-invalidated
|
|
43
|
+
* dependents.
|
|
44
|
+
*/
|
|
45
|
+
filter?: Set<string>;
|
|
46
|
+
/**
|
|
47
|
+
* When set, every planned release gets the version `0.0.0-<suffix>` instead
|
|
48
|
+
* of the computed one, matching snapshot releases.
|
|
49
|
+
*/
|
|
50
|
+
snapshotSuffix?: string;
|
|
51
|
+
/**
|
|
52
|
+
* Enforce that every internal production dependency uses the `workspace:`
|
|
53
|
+
* protocol — a prerequisite for actually releasing. The release path
|
|
54
|
+
* (`pnpm version -r`) sets this; read-only callers (`pnpm change status`)
|
|
55
|
+
* leave it off so a diagnostic never fails on an unmigrated dependency.
|
|
56
|
+
*/
|
|
57
|
+
enforceWorkspaceProtocol?: boolean;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Whether a package reference is a workspace-relative directory path rather
|
|
61
|
+
* than a package name — the additive extension to the changesets format,
|
|
62
|
+
* needed only when workspace projects share a published name.
|
|
63
|
+
*/
|
|
64
|
+
export declare function isDirRef(ref: string): boolean;
|
|
65
|
+
/**
|
|
66
|
+
* Resolves package references — bare names, or `./`-prefixed
|
|
67
|
+
* workspace-relative directories — against the workspace. Names are aliases:
|
|
68
|
+
* one that matches several projects cannot identify any of them and callers
|
|
69
|
+
* must treat it as an error, never a silent pick.
|
|
70
|
+
*/
|
|
71
|
+
export interface ProjectRefIndex {
|
|
72
|
+
/** The directories a reference resolves to: `[]` unknown, 2+ ambiguous. */
|
|
73
|
+
refToDirs: (ref: string) => string[];
|
|
74
|
+
nameToDirs: (name: string) => string[];
|
|
75
|
+
}
|
|
76
|
+
export declare function indexProjectRefs(projects: ReadonlyArray<{
|
|
77
|
+
rootDir: string;
|
|
78
|
+
manifest: {
|
|
79
|
+
name?: string;
|
|
80
|
+
};
|
|
81
|
+
}>, workspaceDir: string): ProjectRefIndex;
|
|
82
|
+
/** The workspace-relative directory of a project, in canonical spelling. */
|
|
83
|
+
export declare function toProjectDir(workspaceDir: string, rootDir: string): string;
|
|
84
|
+
export declare function assembleReleasePlan(opts: AssembleReleasePlanOptions): ReleasePlan;
|
|
85
|
+
/**
|
|
86
|
+
* The range that pnpm materializes for a workspace: spec at pack time, given
|
|
87
|
+
* the dependency's version at the dependent's previous release. Dependent
|
|
88
|
+
* propagation republishes the dependent whenever the dependency's new version
|
|
89
|
+
* falls outside this range.
|
|
90
|
+
*/
|
|
91
|
+
export declare function materializeWorkspaceRange(spec: string, depCurrentVersion: string): string | null;
|