@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.
@@ -0,0 +1,12 @@
1
+ import { type PlannedRelease } from './assembleReleasePlan.js';
2
+ export declare function composeChangelogSection(release: PlannedRelease): string;
3
+ /**
4
+ * Places `section` at the top of a package's changelog: under the existing
5
+ * `# <name>` title when `existing` has one, or under a freshly created title
6
+ * when `existing` is `null`. This is the composition used both to write a
7
+ * committed CHANGELOG.md (`repository` storage) and to build the changelog
8
+ * packed into a published tarball on top of the previous version's
9
+ * (`registry` storage).
10
+ */
11
+ export declare function renderChangelog(existing: string | null, pkgName: string, section: string): string;
12
+ export declare function prependChangelogSection(pkgDir: string, pkgName: string, section: string): Promise<void>;
@@ -0,0 +1,84 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import util from 'node:util';
4
+ import { isDirRef } from './assembleReleasePlan.js';
5
+ import { normalizeProjectDir } from './ledger.js';
6
+ const SECTION_TITLES = {
7
+ major: 'Major Changes',
8
+ minor: 'Minor Changes',
9
+ patch: 'Patch Changes',
10
+ };
11
+ export function composeChangelogSection(release) {
12
+ const entriesByBump = { major: [], minor: [], patch: [] };
13
+ for (const intent of release.intents) {
14
+ const bumpType = releaseBumpFor(intent.releases, release);
15
+ if (bumpType == null || bumpType === 'none' || intent.summary === '')
16
+ continue;
17
+ entriesByBump[bumpType].push(formatListItem(intent.summary));
18
+ }
19
+ if (release.dependencyUpdates.length > 0) {
20
+ const depLines = release.dependencyUpdates.map((dep) => ` - ${dep.name}@${dep.newVersion}`);
21
+ entriesByBump.patch.push(`- Updated dependencies:\n${depLines.join('\n')}`);
22
+ }
23
+ const parts = [`## ${release.newVersion}`];
24
+ for (const bumpType of ['major', 'minor', 'patch']) {
25
+ if (entriesByBump[bumpType].length === 0)
26
+ continue;
27
+ parts.push(`### ${SECTION_TITLES[bumpType]}`);
28
+ parts.push(entriesByBump[bumpType].join('\n\n'));
29
+ }
30
+ return `${parts.join('\n\n')}\n`;
31
+ }
32
+ /**
33
+ * The bump an intent declares for this release, whichever way the intent
34
+ * references the project — by name (sound only when unambiguous, which plan
35
+ * assembly guarantees) or by directory.
36
+ */
37
+ function releaseBumpFor(releases, release) {
38
+ for (const [ref, bumpType] of Object.entries(releases)) {
39
+ if (ref === release.name)
40
+ return bumpType;
41
+ if (isDirRef(ref) && normalizeProjectDir(ref) === release.dir)
42
+ return bumpType;
43
+ }
44
+ return undefined;
45
+ }
46
+ function formatListItem(summary) {
47
+ const [firstLine, ...restLines] = summary.split('\n');
48
+ const rest = restLines.map((line) => (line === '' ? '' : ` ${line}`));
49
+ return ['- ' + firstLine, ...rest].join('\n');
50
+ }
51
+ /**
52
+ * Places `section` at the top of a package's changelog: under the existing
53
+ * `# <name>` title when `existing` has one, or under a freshly created title
54
+ * when `existing` is `null`. This is the composition used both to write a
55
+ * committed CHANGELOG.md (`repository` storage) and to build the changelog
56
+ * packed into a published tarball on top of the previous version's
57
+ * (`registry` storage).
58
+ */
59
+ export function renderChangelog(existing, pkgName, section) {
60
+ if (existing == null) {
61
+ return `# ${pkgName}\n\n${section}`;
62
+ }
63
+ const newlineIndex = existing.indexOf('\n');
64
+ const firstLine = newlineIndex === -1 ? existing : existing.slice(0, newlineIndex);
65
+ if (firstLine.startsWith('# ')) {
66
+ const body = (newlineIndex === -1 ? '' : existing.slice(newlineIndex + 1)).replace(/^[\r\n]+/, '');
67
+ return `${firstLine}\n\n${section}${body === '' ? '' : `\n${body}`}`;
68
+ }
69
+ return `${section}\n${existing}`;
70
+ }
71
+ export async function prependChangelogSection(pkgDir, pkgName, section) {
72
+ const changelogPath = path.join(pkgDir, 'CHANGELOG.md');
73
+ let existing = null;
74
+ try {
75
+ existing = await fs.readFile(changelogPath, 'utf8');
76
+ }
77
+ catch (err) {
78
+ if (!(util.types.isNativeError(err) && 'code' in err && err.code === 'ENOENT')) {
79
+ throw err;
80
+ }
81
+ }
82
+ await fs.writeFile(changelogPath, renderChangelog(existing, pkgName, section), 'utf8');
83
+ }
84
+ //# sourceMappingURL=changelog.js.map
package/lib/index.d.ts ADDED
@@ -0,0 +1,6 @@
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';
3
+ export { composeChangelogSection, prependChangelogSection, renderChangelog, } from './changelog.js';
4
+ export { BUMP_TYPES, type ChangeIntent, CHANGES_DIR, type IntentBumpType, parseChangeIntent, readChangeIntents, type ReleaseBumpType, writeChangeIntent, type WriteChangeIntentOptions, } from './intents.js';
5
+ export { appendToLedger, buildConsumptionIndex, type Ledger, LEDGER_FILENAME, type LedgerEntry, ledgerEntryIds, normalizeProjectDir, type PackageConsumption, readLedger, } from './ledger.js';
6
+ export { listPendingChangelogs, PENDING_CHANGELOGS_DIR, type PendingChangelog, pendingChangelogPath, readPendingChangelog, removePendingChangelog, writePendingChangelog, } from './pendingChangelog.js';
package/lib/index.js ADDED
@@ -0,0 +1,7 @@
1
+ export { applyReleasePlan, changelogStorage, } from './applyReleasePlan.js';
2
+ export { assembleReleasePlan, indexProjectRefs, isDirRef, materializeWorkspaceRange, toProjectDir, } from './assembleReleasePlan.js';
3
+ export { composeChangelogSection, prependChangelogSection, renderChangelog, } from './changelog.js';
4
+ export { BUMP_TYPES, CHANGES_DIR, parseChangeIntent, readChangeIntents, writeChangeIntent, } from './intents.js';
5
+ export { appendToLedger, buildConsumptionIndex, LEDGER_FILENAME, ledgerEntryIds, normalizeProjectDir, readLedger, } from './ledger.js';
6
+ export { listPendingChangelogs, PENDING_CHANGELOGS_DIR, pendingChangelogPath, readPendingChangelog, removePendingChangelog, writePendingChangelog, } from './pendingChangelog.js';
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,17 @@
1
+ export declare const CHANGES_DIR = ".changeset";
2
+ export declare const BUMP_TYPES: readonly ['none', 'patch', 'minor', 'major'];
3
+ export type IntentBumpType = typeof BUMP_TYPES[number];
4
+ export type ReleaseBumpType = Exclude<IntentBumpType, 'none'>;
5
+ export interface ChangeIntent {
6
+ id: string;
7
+ filePath: string;
8
+ releases: Record<string, IntentBumpType>;
9
+ summary: string;
10
+ }
11
+ export declare function parseChangeIntent(content: string, id: string, filePath: string): ChangeIntent;
12
+ export declare function readChangeIntents(workspaceDir: string): Promise<ChangeIntent[]>;
13
+ export interface WriteChangeIntentOptions {
14
+ releases: Record<string, IntentBumpType>;
15
+ summary: string;
16
+ }
17
+ export declare function writeChangeIntent(workspaceDir: string, opts: WriteChangeIntentOptions): Promise<string>;
package/lib/intents.js ADDED
@@ -0,0 +1,76 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import util from 'node:util';
4
+ import { PnpmError } from '@pnpm/error';
5
+ import { humanId } from 'human-id';
6
+ import * as yaml from 'yaml';
7
+ export const CHANGES_DIR = '.changeset';
8
+ export const BUMP_TYPES = ['none', 'patch', 'minor', 'major'];
9
+ export function parseChangeIntent(content, id, filePath) {
10
+ const lines = content.replace(/^\uFEFF/, '').split(/\r?\n/);
11
+ const closingIndex = lines[0]?.trim() === '---'
12
+ ? lines.findIndex((line, index) => index > 0 && line.trim() === '---')
13
+ : -1;
14
+ if (closingIndex === -1) {
15
+ throw new PnpmError('INVALID_CHANGE_INTENT', `Change intent file ${filePath} has no YAML frontmatter`);
16
+ }
17
+ let frontmatter;
18
+ try {
19
+ frontmatter = yaml.parse(lines.slice(1, closingIndex).join('\n')) ?? {};
20
+ }
21
+ catch (err) {
22
+ throw new PnpmError('INVALID_CHANGE_INTENT', `Change intent file ${filePath} has invalid YAML frontmatter: ${util.types.isNativeError(err) ? err.message : String(err)}`);
23
+ }
24
+ if (typeof frontmatter !== 'object' || frontmatter === null || Array.isArray(frontmatter)) {
25
+ throw new PnpmError('INVALID_CHANGE_INTENT', `Change intent file ${filePath} frontmatter must be a mapping of package names to bump types`);
26
+ }
27
+ const releases = {};
28
+ for (const [pkgName, bumpType] of Object.entries(frontmatter)) {
29
+ if (typeof bumpType !== 'string' || !BUMP_TYPES.includes(bumpType)) {
30
+ throw new PnpmError('INVALID_CHANGE_INTENT', `Change intent file ${filePath} declares an invalid bump type for ${pkgName}: ${String(bumpType)}. Expected one of ${BUMP_TYPES.join(', ')}`);
31
+ }
32
+ releases[pkgName] = bumpType;
33
+ }
34
+ return {
35
+ id,
36
+ filePath,
37
+ releases,
38
+ summary: lines.slice(closingIndex + 1).join('\n').trim(),
39
+ };
40
+ }
41
+ export async function readChangeIntents(workspaceDir) {
42
+ const changesDir = path.join(workspaceDir, CHANGES_DIR);
43
+ let fileNames;
44
+ try {
45
+ fileNames = await fs.readdir(changesDir);
46
+ }
47
+ catch (err) {
48
+ if (util.types.isNativeError(err) && 'code' in err && err.code === 'ENOENT') {
49
+ return [];
50
+ }
51
+ throw err;
52
+ }
53
+ const intentFileNames = fileNames
54
+ .filter((fileName) => fileName.endsWith('.md') && fileName.toLowerCase() !== 'readme.md')
55
+ .sort();
56
+ return Promise.all(intentFileNames.map(async (fileName) => {
57
+ const filePath = path.join(changesDir, fileName);
58
+ const content = await fs.readFile(filePath, 'utf8');
59
+ return parseChangeIntent(content, fileName.slice(0, -'.md'.length), filePath);
60
+ }));
61
+ }
62
+ export async function writeChangeIntent(workspaceDir, opts) {
63
+ const changesDir = path.join(workspaceDir, CHANGES_DIR);
64
+ await fs.mkdir(changesDir, { recursive: true });
65
+ const existing = new Set(await fs.readdir(changesDir));
66
+ let id = humanId({ separator: '-', capitalize: false });
67
+ while (existing.has(`${id}.md`)) {
68
+ id = humanId({ separator: '-', capitalize: false });
69
+ }
70
+ const frontmatterLines = Object.entries(opts.releases)
71
+ .map(([pkgName, bumpType]) => `${JSON.stringify(pkgName)}: ${bumpType}`);
72
+ const content = `---\n${frontmatterLines.join('\n')}\n---\n\n${opts.summary.trim()}\n`;
73
+ await fs.writeFile(path.join(changesDir, `${id}.md`), content, 'utf8');
74
+ return id;
75
+ }
76
+ //# sourceMappingURL=intents.js.map
@@ -0,0 +1,50 @@
1
+ export declare const LEDGER_FILENAME = "ledger.yaml";
2
+ /**
3
+ * One consumed release: the workspace-relative directory of the project that
4
+ * released (the engine's unit of identity — package names may collide across
5
+ * workspace projects) and the ids of the intent files the release consumed.
6
+ * The bare id-list shape is accepted when read, for hand-written entries;
7
+ * its project is then resolved by the package name in the entry key, which
8
+ * must be unambiguous.
9
+ */
10
+ export type LedgerEntry = string[] | {
11
+ dir: string;
12
+ intents: string[];
13
+ };
14
+ /**
15
+ * The committed, append-only record of consumed change intents: maps
16
+ * `<package name>@<released version>` to the released project and the ids of
17
+ * the intent files consumed by that release. Consumption is scoped per
18
+ * project — an intent file is fully consumed only once every project it
19
+ * names has an entry — which is what makes cherry-picked releases on
20
+ * maintenance branches and merge-backs safe, and what lets one intent be
21
+ * half-consumed by a package on a release lane.
22
+ */
23
+ export type Ledger = Record<string, LedgerEntry>;
24
+ export declare function ledgerEntryIds(entry: LedgerEntry): string[];
25
+ export declare function readLedger(workspaceDir: string): Promise<Ledger>;
26
+ export declare function appendToLedger(workspaceDir: string, newEntries: Record<string, {
27
+ dir: string;
28
+ intents: string[];
29
+ }>): Promise<Ledger>;
30
+ export interface PackageConsumption {
31
+ /** Intent ids the ledger records against any released version of the project. */
32
+ allIds: Set<string>;
33
+ /** Intent ids recorded only against prerelease versions of the project. */
34
+ prereleaseOnlyIds: Set<string>;
35
+ }
36
+ /**
37
+ * Indexes the ledger by workspace-relative project directory in a single
38
+ * pass. Bare id-list entries carry no directory, so their project is
39
+ * resolved from the entry key's package name via `resolveNameDirs`; a name
40
+ * matching several projects cannot be attributed and is an error — write
41
+ * such entries in the `dir`/`intents` shape instead. Entries whose name no
42
+ * longer exists in the workspace are inert. Projects without entries map to
43
+ * an empty consumption, so lookups never miss.
44
+ */
45
+ export declare function buildConsumptionIndex(ledger: Ledger, resolveNameDirs: (pkgName: string) => string[]): (projectDir: string) => PackageConsumption;
46
+ /**
47
+ * The canonical spelling of a workspace-relative project directory: forward
48
+ * slashes, no leading `./`, no trailing slash.
49
+ */
50
+ export declare function normalizeProjectDir(dir: string): string;
package/lib/ledger.js ADDED
@@ -0,0 +1,130 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import util from 'node:util';
4
+ import { PnpmError } from '@pnpm/error';
5
+ import * as yaml from 'yaml';
6
+ import { CHANGES_DIR } from './intents.js';
7
+ export const LEDGER_FILENAME = 'ledger.yaml';
8
+ export function ledgerEntryIds(entry) {
9
+ return Array.isArray(entry) ? entry : entry.intents;
10
+ }
11
+ export async function readLedger(workspaceDir) {
12
+ const ledgerPath = path.join(workspaceDir, CHANGES_DIR, LEDGER_FILENAME);
13
+ let content;
14
+ try {
15
+ content = await fs.readFile(ledgerPath, 'utf8');
16
+ }
17
+ catch (err) {
18
+ if (util.types.isNativeError(err) && 'code' in err && err.code === 'ENOENT') {
19
+ return {};
20
+ }
21
+ throw err;
22
+ }
23
+ const parsed = yaml.parse(content) ?? {};
24
+ if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
25
+ throw new PnpmError('INVALID_VERSIONING_LEDGER', `Expected ${ledgerPath} to be a mapping of package@version keys to consumed-intent entries`);
26
+ }
27
+ // A null prototype so a key like "__proto__" lands as an own property
28
+ // instead of mutating the prototype.
29
+ const ledger = Object.create(null);
30
+ for (const [key, entry] of Object.entries(parsed)) {
31
+ if (!isValidLedgerEntry(entry)) {
32
+ throw new PnpmError('INVALID_VERSIONING_LEDGER', `Invalid entry for ${key} in ${ledgerPath}. Expected a list of intent ids, or a mapping with "dir" and "intents"`);
33
+ }
34
+ ledger[key] = entry;
35
+ }
36
+ return ledger;
37
+ }
38
+ function isValidLedgerEntry(entry) {
39
+ if (Array.isArray(entry)) {
40
+ return entry.every((id) => typeof id === 'string');
41
+ }
42
+ if (typeof entry !== 'object' || entry === null)
43
+ return false;
44
+ const { dir, intents } = entry;
45
+ return typeof dir === 'string' && Array.isArray(intents) && intents.every((id) => typeof id === 'string');
46
+ }
47
+ export async function appendToLedger(workspaceDir, newEntries) {
48
+ const ledger = await readLedger(workspaceDir);
49
+ if (Object.keys(newEntries).length === 0)
50
+ return ledger;
51
+ for (const [key, entry] of Object.entries(newEntries)) {
52
+ const existingIds = ledger[key] != null ? ledgerEntryIds(ledger[key]) : [];
53
+ ledger[key] = {
54
+ dir: entry.dir,
55
+ intents: Array.from(new Set([...existingIds, ...entry.intents])).sort(),
56
+ };
57
+ }
58
+ const sorted = Object.fromEntries(Object.entries(ledger).sort(([left], [right]) => left.localeCompare(right)));
59
+ const changesDir = path.join(workspaceDir, CHANGES_DIR);
60
+ await fs.mkdir(changesDir, { recursive: true });
61
+ await fs.writeFile(path.join(changesDir, LEDGER_FILENAME), yaml.stringify(sorted), 'utf8');
62
+ return sorted;
63
+ }
64
+ /**
65
+ * Indexes the ledger by workspace-relative project directory in a single
66
+ * pass. Bare id-list entries carry no directory, so their project is
67
+ * resolved from the entry key's package name via `resolveNameDirs`; a name
68
+ * matching several projects cannot be attributed and is an error — write
69
+ * such entries in the `dir`/`intents` shape instead. Entries whose name no
70
+ * longer exists in the workspace are inert. Projects without entries map to
71
+ * an empty consumption, so lookups never miss.
72
+ */
73
+ export function buildConsumptionIndex(ledger, resolveNameDirs) {
74
+ const stableIdsByDir = new Map();
75
+ const prereleaseIdsByDir = new Map();
76
+ for (const [key, entry] of Object.entries(ledger)) {
77
+ const atIndex = key.lastIndexOf('@');
78
+ if (atIndex <= 0)
79
+ continue;
80
+ const version = key.slice(atIndex + 1);
81
+ let dir;
82
+ if (Array.isArray(entry)) {
83
+ const pkgName = key.slice(0, atIndex);
84
+ const dirs = resolveNameDirs(pkgName);
85
+ if (dirs.length === 0)
86
+ continue;
87
+ if (dirs.length > 1) {
88
+ throw new PnpmError('INVALID_VERSIONING_LEDGER', `The ledger entry ${key} names ${pkgName}, which matches multiple workspace projects (${dirs.join(', ')}). Rewrite the entry with an explicit "dir".`);
89
+ }
90
+ dir = dirs[0];
91
+ }
92
+ else {
93
+ dir = normalizeProjectDir(entry.dir);
94
+ }
95
+ // Build metadata (after "+") may itself contain hyphens and never makes a
96
+ // version a prerelease.
97
+ const isPrerelease = version.split('+')[0].includes('-');
98
+ const byDir = isPrerelease ? prereleaseIdsByDir : stableIdsByDir;
99
+ let idSet = byDir.get(dir);
100
+ if (idSet == null) {
101
+ idSet = new Set();
102
+ byDir.set(dir, idSet);
103
+ }
104
+ for (const id of ledgerEntryIds(entry)) {
105
+ idSet.add(id);
106
+ }
107
+ }
108
+ const consumptionByDir = new Map();
109
+ for (const dir of new Set([...stableIdsByDir.keys(), ...prereleaseIdsByDir.keys()])) {
110
+ const stableIds = stableIdsByDir.get(dir) ?? new Set();
111
+ const prereleaseIds = prereleaseIdsByDir.get(dir) ?? new Set();
112
+ consumptionByDir.set(dir, {
113
+ allIds: new Set([...stableIds, ...prereleaseIds]),
114
+ prereleaseOnlyIds: new Set([...prereleaseIds].filter((id) => !stableIds.has(id))),
115
+ });
116
+ }
117
+ return (projectDir) => consumptionByDir.get(projectDir) ?? { allIds: new Set(), prereleaseOnlyIds: new Set() };
118
+ }
119
+ /**
120
+ * The canonical spelling of a workspace-relative project directory: forward
121
+ * slashes, no leading `./`, no trailing slash.
122
+ */
123
+ export function normalizeProjectDir(dir) {
124
+ let normalized = dir.replaceAll('\\', '/');
125
+ while (normalized.startsWith('./')) {
126
+ normalized = normalized.slice(2);
127
+ }
128
+ return normalized.replace(/\/+$/, '');
129
+ }
130
+ //# sourceMappingURL=ledger.js.map
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Directory (under the workspace's `.changeset/`) holding the composed
3
+ * changelog sections of releases whose intents are not yet published. In
4
+ * `registry` changelog storage no `CHANGELOG.md` is committed, so a release's
5
+ * section is parked here at `pnpm version -r` time and consumed at publish,
6
+ * when it is prepended to the previously published tarball's changelog. Each
7
+ * file is garbage-collected together with the intents it was composed from,
8
+ * under the same registry-confirmed gate.
9
+ */
10
+ export declare const PENDING_CHANGELOGS_DIR = "changelogs";
11
+ export declare function pendingChangelogPath(workspaceDir: string, pkgName: string, version: string): string;
12
+ export interface PendingChangelog {
13
+ name: string;
14
+ version: string;
15
+ }
16
+ /**
17
+ * The `package@version` of every parked section. The release-driven garbage
18
+ * collector consults this rather than the ledger so it also collects the
19
+ * sections of dependency-propagated releases, which carry no consumed intents
20
+ * and therefore have no ledger entry.
21
+ */
22
+ export declare function listPendingChangelogs(workspaceDir: string): Promise<PendingChangelog[]>;
23
+ export declare function writePendingChangelog(workspaceDir: string, pkgName: string, version: string, section: string): Promise<void>;
24
+ export declare function readPendingChangelog(workspaceDir: string, pkgName: string, version: string): Promise<string | null>;
25
+ /** Removes a parked section. A missing file is not an error — it may already have been collected. */
26
+ export declare function removePendingChangelog(workspaceDir: string, pkgName: string, version: string): Promise<void>;
@@ -0,0 +1,84 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import util from 'node:util';
4
+ import { CHANGES_DIR } from './intents.js';
5
+ /**
6
+ * Directory (under the workspace's `.changeset/`) holding the composed
7
+ * changelog sections of releases whose intents are not yet published. In
8
+ * `registry` changelog storage no `CHANGELOG.md` is committed, so a release's
9
+ * section is parked here at `pnpm version -r` time and consumed at publish,
10
+ * when it is prepended to the previously published tarball's changelog. Each
11
+ * file is garbage-collected together with the intents it was composed from,
12
+ * under the same registry-confirmed gate.
13
+ */
14
+ export const PENDING_CHANGELOGS_DIR = 'changelogs';
15
+ /**
16
+ * A published `package@version` names one artifact, so it is a stable key for
17
+ * its parked section. The only character in the key that a filesystem rejects
18
+ * is the `/` of a scoped name, encoded here as `!` (a character neither a
19
+ * package name nor a semver version can contain, so the mapping is reversible).
20
+ */
21
+ function pendingChangelogFilename(pkgName, version) {
22
+ return `${pkgName}@${version}`.replaceAll('/', '!') + '.md';
23
+ }
24
+ export function pendingChangelogPath(workspaceDir, pkgName, version) {
25
+ return path.join(workspaceDir, CHANGES_DIR, PENDING_CHANGELOGS_DIR, pendingChangelogFilename(pkgName, version));
26
+ }
27
+ /**
28
+ * The `package@version` of every parked section. The release-driven garbage
29
+ * collector consults this rather than the ledger so it also collects the
30
+ * sections of dependency-propagated releases, which carry no consumed intents
31
+ * and therefore have no ledger entry.
32
+ */
33
+ export async function listPendingChangelogs(workspaceDir) {
34
+ const dir = path.join(workspaceDir, CHANGES_DIR, PENDING_CHANGELOGS_DIR);
35
+ let fileNames;
36
+ try {
37
+ fileNames = await fs.readdir(dir);
38
+ }
39
+ catch (err) {
40
+ if (util.types.isNativeError(err) && 'code' in err && err.code === 'ENOENT') {
41
+ return [];
42
+ }
43
+ throw err;
44
+ }
45
+ const pending = [];
46
+ for (const fileName of fileNames) {
47
+ if (!fileName.endsWith('.md'))
48
+ continue;
49
+ const key = fileName.slice(0, -'.md'.length);
50
+ const atIndex = key.lastIndexOf('@');
51
+ if (atIndex <= 0)
52
+ continue;
53
+ pending.push({ name: key.slice(0, atIndex).replaceAll('!', '/'), version: key.slice(atIndex + 1) });
54
+ }
55
+ return pending;
56
+ }
57
+ export async function writePendingChangelog(workspaceDir, pkgName, version, section) {
58
+ const filePath = pendingChangelogPath(workspaceDir, pkgName, version);
59
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
60
+ await fs.writeFile(filePath, section, 'utf8');
61
+ }
62
+ export async function readPendingChangelog(workspaceDir, pkgName, version) {
63
+ try {
64
+ return await fs.readFile(pendingChangelogPath(workspaceDir, pkgName, version), 'utf8');
65
+ }
66
+ catch (err) {
67
+ if (util.types.isNativeError(err) && 'code' in err && err.code === 'ENOENT') {
68
+ return null;
69
+ }
70
+ throw err;
71
+ }
72
+ }
73
+ /** Removes a parked section. A missing file is not an error — it may already have been collected. */
74
+ export async function removePendingChangelog(workspaceDir, pkgName, version) {
75
+ try {
76
+ await fs.rm(pendingChangelogPath(workspaceDir, pkgName, version));
77
+ }
78
+ catch (err) {
79
+ if (!(util.types.isNativeError(err) && 'code' in err && err.code === 'ENOENT')) {
80
+ throw err;
81
+ }
82
+ }
83
+ }
84
+ //# sourceMappingURL=pendingChangelog.js.map
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@pnpm/releasing.versioning",
3
+ "version": "1100.1.0",
4
+ "description": "Native workspace release management: change intents, release-plan assembly, and changelog writing",
5
+ "keywords": [
6
+ "pnpm",
7
+ "pnpm11"
8
+ ],
9
+ "license": "MIT",
10
+ "funding": "https://opencollective.com/pnpm",
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "https://github.com/pnpm/pnpm/tree/main/pnpm11/releasing/versioning"
14
+ },
15
+ "homepage": "https://github.com/pnpm/pnpm/tree/main/pnpm11/releasing/versioning#readme",
16
+ "bugs": {
17
+ "url": "https://github.com/pnpm/pnpm/issues"
18
+ },
19
+ "type": "module",
20
+ "main": "lib/index.js",
21
+ "types": "lib/index.d.ts",
22
+ "exports": {
23
+ ".": "./lib/index.js"
24
+ },
25
+ "files": [
26
+ "lib",
27
+ "!*.map"
28
+ ],
29
+ "dependencies": {
30
+ "@pnpm/error": "1100.0.1",
31
+ "@pnpm/types": "1101.4.0",
32
+ "@pnpm/workspace.project-manifest-reader": "1100.0.15",
33
+ "@pnpm/workspace.spec-parser": "1100.0.0",
34
+ "human-id": "^4.2.0",
35
+ "semver": "^7.8.4",
36
+ "yaml": "^2.9.0"
37
+ },
38
+ "devDependencies": {
39
+ "@jest/globals": "30.4.1",
40
+ "@pnpm/releasing.versioning": "1100.1.0",
41
+ "@types/semver": "7.7.1",
42
+ "tempy": "3.0.0"
43
+ },
44
+ "engines": {
45
+ "node": ">=22.13"
46
+ },
47
+ "jest": {
48
+ "preset": "@pnpm/jest-config"
49
+ },
50
+ "scripts": {
51
+ "lint": "eslint \"src/**/*.ts\" \"test/**/*.ts\"",
52
+ "test": "pn compile && pn .test",
53
+ "compile": "tsgo --build && pn lint --fix",
54
+ ".test": "cross-env NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules --disable-warning=ExperimentalWarning --disable-warning=DEP0169\" jest"
55
+ }
56
+ }