@pnpm/releasing.commands 1100.5.4 → 1100.6.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 +996 -0
- package/lib/change/index.d.ts +41 -0
- package/lib/change/index.js +245 -0
- package/lib/deploy/createDeployFiles.d.ts +5 -2
- package/lib/deploy/createDeployFiles.js +42 -5
- package/lib/deploy/deploy.js +5 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.js +2 -0
- package/lib/lane/index.d.ts +24 -0
- package/lib/lane/index.js +157 -0
- package/lib/publish/batchPublish.js +1 -1
- package/lib/publish/extractManifestFromPacked.d.ts +7 -0
- package/lib/publish/extractManifestFromPacked.js +55 -16
- package/lib/publish/otp.js +8 -5
- package/lib/publish/pack.d.ts +1 -1
- package/lib/publish/pack.js +61 -7
- package/lib/publish/previousChangelog.d.ts +19 -0
- package/lib/publish/previousChangelog.js +182 -0
- package/lib/publish/publish.d.ts +2 -2
- package/lib/publish/publish.js +2 -2
- package/lib/publish/recursivePublish.d.ts +1 -1
- package/lib/stage/list.js +5 -0
- package/lib/version/index.d.ts +3 -1
- package/lib/version/index.js +90 -1
- package/package.json +48 -43
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { Config } from '@pnpm/config.reader';
|
|
2
|
+
import { type ChangeIntent, type ReleasePlan, type WorkspaceProject } from '@pnpm/releasing.versioning';
|
|
3
|
+
import type { Project, VersioningSettings } from '@pnpm/types';
|
|
4
|
+
export declare function rcOptionsTypes(): Record<string, unknown>;
|
|
5
|
+
export declare function cliOptionsTypes(): Record<string, unknown>;
|
|
6
|
+
export declare const commandNames: string[];
|
|
7
|
+
export declare function help(): string;
|
|
8
|
+
export type ChangeCommandOptions = Pick<Config, 'changedFilesIgnorePattern' | 'dir' | 'testPattern' | 'versioning' | 'workspaceDir'> & {
|
|
9
|
+
allProjects?: Project[];
|
|
10
|
+
bump?: string;
|
|
11
|
+
summary?: string;
|
|
12
|
+
};
|
|
13
|
+
export declare function handler(opts: ChangeCommandOptions, params: string[]): Promise<string>;
|
|
14
|
+
export declare function renderReleasePlan(plan: ReleasePlan): string;
|
|
15
|
+
export interface ReleasableProject {
|
|
16
|
+
name: string;
|
|
17
|
+
/** Workspace-relative project directory. */
|
|
18
|
+
dir: string;
|
|
19
|
+
/**
|
|
20
|
+
* How an intent file or versioning config should reference this project:
|
|
21
|
+
* the bare name, or the `./`-prefixed directory when the name is shared by
|
|
22
|
+
* several workspace projects.
|
|
23
|
+
*/
|
|
24
|
+
ref: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The projects a change intent may demand a release from: named, carrying a
|
|
28
|
+
* valid semver version, and not frozen by `versioning.ignore`. Matches the
|
|
29
|
+
* participant set of the release-plan assembler.
|
|
30
|
+
*/
|
|
31
|
+
export declare function getReleasableProjects(allProjects: Array<Pick<Project, 'manifest' | 'rootDir'>>, workspaceDir: string, versioning?: VersioningSettings): ReleasableProject[];
|
|
32
|
+
export declare function toWorkspaceProjects(allProjects: Array<Pick<Project, 'manifest' | 'rootDir'>>): WorkspaceProject[];
|
|
33
|
+
export type { ChangeIntent };
|
|
34
|
+
export declare const change: {
|
|
35
|
+
handler: typeof handler;
|
|
36
|
+
help: typeof help;
|
|
37
|
+
commandNames: string[];
|
|
38
|
+
cliOptionsTypes: typeof cliOptionsTypes;
|
|
39
|
+
rcOptionsTypes: typeof rcOptionsTypes;
|
|
40
|
+
recursiveByDefault: boolean;
|
|
41
|
+
};
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { checkbox, input, Separator } from '@inquirer/prompts';
|
|
2
|
+
import { PnpmError } from '@pnpm/error';
|
|
3
|
+
import { assembleReleasePlan, BUMP_TYPES, indexProjectRefs, readChangeIntents, readLedger, toProjectDir, writeChangeIntent, } from '@pnpm/releasing.versioning';
|
|
4
|
+
import { getChangedProjects } from '@pnpm/workspace.projects-filter';
|
|
5
|
+
import { safeExeca as execa } from 'execa';
|
|
6
|
+
import { renderHelp } from 'render-help';
|
|
7
|
+
import { valid } from 'semver';
|
|
8
|
+
export function rcOptionsTypes() {
|
|
9
|
+
return {};
|
|
10
|
+
}
|
|
11
|
+
export function cliOptionsTypes() {
|
|
12
|
+
return {
|
|
13
|
+
bump: String,
|
|
14
|
+
summary: String,
|
|
15
|
+
recursive: Boolean,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export const commandNames = ['change'];
|
|
19
|
+
export function help() {
|
|
20
|
+
return renderHelp({
|
|
21
|
+
description: 'Records a change intent: which packages a change affects, the bump type for each, and a summary that becomes the changelog entry. The intent file is written to .changeset/ in the changesets format.',
|
|
22
|
+
usages: [
|
|
23
|
+
'pnpm change [--bump <type>] [--summary <text>] [<pkg>...]',
|
|
24
|
+
'pnpm change status',
|
|
25
|
+
],
|
|
26
|
+
descriptionLists: [
|
|
27
|
+
{
|
|
28
|
+
title: 'Options',
|
|
29
|
+
list: [
|
|
30
|
+
{
|
|
31
|
+
description: `Bump type for the named packages: ${BUMP_TYPES.join(', ')}. "none" records an explicit decline — the change needs no release`,
|
|
32
|
+
name: '--bump <type>',
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
description: 'The summary for the changelog entry. Runs non-interactively when given together with package names',
|
|
36
|
+
name: '--summary <text>',
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
export async function handler(opts, params) {
|
|
44
|
+
const workspaceDir = opts.workspaceDir;
|
|
45
|
+
if (!workspaceDir) {
|
|
46
|
+
throw new PnpmError('WORKSPACE_ONLY', 'pnpm change is only supported in a workspace');
|
|
47
|
+
}
|
|
48
|
+
// Only the exact no-option invocation is the status form, so a package
|
|
49
|
+
// that happens to be named "status" stays recordable.
|
|
50
|
+
if (params.length === 1 && params[0] === 'status' && opts.bump == null && opts.summary == null) {
|
|
51
|
+
return renderStatus(workspaceDir, opts);
|
|
52
|
+
}
|
|
53
|
+
return recordChange(workspaceDir, opts, params);
|
|
54
|
+
}
|
|
55
|
+
async function recordChange(workspaceDir, opts, params) {
|
|
56
|
+
const releasable = getReleasableProjects(opts.allProjects ?? [], workspaceDir, opts.versioning);
|
|
57
|
+
if (releasable.length === 0) {
|
|
58
|
+
throw new PnpmError('VERSIONING_NO_PACKAGES', 'No releasable packages found in this workspace');
|
|
59
|
+
}
|
|
60
|
+
const releasableDirs = new Set(releasable.map((project) => project.dir));
|
|
61
|
+
const refs = indexProjectRefs(opts.allProjects ?? [], workspaceDir);
|
|
62
|
+
for (const ref of params) {
|
|
63
|
+
const dirs = refs.refToDirs(ref);
|
|
64
|
+
if (dirs.length > 1) {
|
|
65
|
+
throw new PnpmError('VERSIONING_AMBIGUOUS_PACKAGE', `${ref} matches multiple workspace projects: ${dirs.map((dir) => `./${dir}`).join(', ')}. Reference the project by directory instead.`);
|
|
66
|
+
}
|
|
67
|
+
if (dirs.length === 0 || !releasableDirs.has(dirs[0])) {
|
|
68
|
+
throw new PnpmError('VERSIONING_UNKNOWN_PACKAGE', `${ref} is not a releasable package of this workspace`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (opts.bump != null && !BUMP_TYPES.includes(opts.bump)) {
|
|
72
|
+
throw new PnpmError('VERSIONING_INVALID_BUMP', `Invalid bump type: ${opts.bump}. Expected one of ${BUMP_TYPES.join(', ')}`);
|
|
73
|
+
}
|
|
74
|
+
const pkgRefs = params.length > 0
|
|
75
|
+
? params
|
|
76
|
+
: await promptForPackages(releasable, workspaceDir, opts);
|
|
77
|
+
const releases = opts.bump != null
|
|
78
|
+
? Object.fromEntries(pkgRefs.map((ref) => [ref, opts.bump]))
|
|
79
|
+
: await promptBumpTypes(pkgRefs);
|
|
80
|
+
const summary = opts.summary ??
|
|
81
|
+
await input({ message: 'Summary of the change (becomes the changelog entry):', required: true });
|
|
82
|
+
const id = await writeChangeIntent(workspaceDir, { releases, summary });
|
|
83
|
+
return `Recorded change intent .changeset/${id}.md`;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* The affected-packages picker, changesets-style: the packages whose
|
|
87
|
+
* directories the branch touched are grouped first (under a "changed
|
|
88
|
+
* packages" heading) and preselected, the rest listed below under "unchanged
|
|
89
|
+
* packages". A name shared by several projects is offered under its directory
|
|
90
|
+
* reference so the written intent stays unambiguous. Change detection is
|
|
91
|
+
* best-effort — outside a git repo, or when no base branch can be found, the
|
|
92
|
+
* list falls back to a flat, unselected picker.
|
|
93
|
+
*/
|
|
94
|
+
async function promptForPackages(releasable, workspaceDir, opts) {
|
|
95
|
+
const changedDirs = await detectChangedDirs(releasable, workspaceDir, opts);
|
|
96
|
+
const label = (project) => project.ref === project.name ? project.name : `${project.name} (./${project.dir})`;
|
|
97
|
+
let choices;
|
|
98
|
+
if (changedDirs.size > 0) {
|
|
99
|
+
const changed = releasable.filter((project) => changedDirs.has(project.dir));
|
|
100
|
+
const unchanged = releasable.filter((project) => !changedDirs.has(project.dir));
|
|
101
|
+
choices = [
|
|
102
|
+
new Separator('changed packages'),
|
|
103
|
+
...changed.map((project) => ({ value: project.ref, name: label(project), checked: true })),
|
|
104
|
+
...(unchanged.length > 0 ? [new Separator('unchanged packages')] : []),
|
|
105
|
+
...unchanged.map((project) => ({ value: project.ref, name: label(project) })),
|
|
106
|
+
];
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
choices = releasable.map((project) => ({ value: project.ref, name: label(project) }));
|
|
110
|
+
}
|
|
111
|
+
return checkbox({
|
|
112
|
+
message: 'Which packages does this change affect?',
|
|
113
|
+
choices,
|
|
114
|
+
required: true,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* The workspace-relative directories the current branch changed, relative to
|
|
119
|
+
* the base branch, using the same detection behind `--filter="[<ref>]"`.
|
|
120
|
+
* Returns an empty set on any failure so the picker degrades to a flat list.
|
|
121
|
+
*/
|
|
122
|
+
async function detectChangedDirs(releasable, workspaceDir, opts) {
|
|
123
|
+
const baseCommit = await detectBaseCommit(workspaceDir);
|
|
124
|
+
if (baseCommit == null)
|
|
125
|
+
return new Set();
|
|
126
|
+
try {
|
|
127
|
+
const projectDirs = (opts.allProjects ?? []).map((project) => project.rootDir);
|
|
128
|
+
const [changedRootDirs] = await getChangedProjects(projectDirs, baseCommit, {
|
|
129
|
+
workspaceDir,
|
|
130
|
+
testPattern: opts.testPattern,
|
|
131
|
+
changedFilesIgnorePattern: opts.changedFilesIgnorePattern,
|
|
132
|
+
});
|
|
133
|
+
const releasableDirs = new Set(releasable.map((project) => project.dir));
|
|
134
|
+
return new Set(changedRootDirs
|
|
135
|
+
.map((rootDir) => toProjectDir(workspaceDir, rootDir))
|
|
136
|
+
.filter((dir) => releasableDirs.has(dir)));
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
return new Set();
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/** The merge-base of HEAD with the default branch, or `undefined`. */
|
|
143
|
+
async function detectBaseCommit(cwd) {
|
|
144
|
+
for (const branch of ['main', 'master']) {
|
|
145
|
+
try {
|
|
146
|
+
// eslint-disable-next-line no-await-in-loop
|
|
147
|
+
const { stdout } = await execa('git', ['merge-base', 'HEAD', branch], { cwd });
|
|
148
|
+
const commit = String(stdout).trim();
|
|
149
|
+
if (commit !== '')
|
|
150
|
+
return commit;
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
// Try the next candidate branch.
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return undefined;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* The changesets-style bump picker: ask which packages get a major bump, then
|
|
160
|
+
* which of the rest get a minor, and default whatever remains to patch. One
|
|
161
|
+
* multiselect per level reads far better than a per-package prompt when many
|
|
162
|
+
* packages are affected.
|
|
163
|
+
*/
|
|
164
|
+
async function promptBumpTypes(pkgRefs) {
|
|
165
|
+
const bumpByRef = new Map();
|
|
166
|
+
let remaining = [...pkgRefs];
|
|
167
|
+
for (const bumpType of ['major', 'minor']) {
|
|
168
|
+
if (remaining.length === 0)
|
|
169
|
+
break;
|
|
170
|
+
// eslint-disable-next-line no-await-in-loop
|
|
171
|
+
const chosen = new Set(await checkbox({
|
|
172
|
+
message: `Which packages should have a ${bumpType} bump?`,
|
|
173
|
+
choices: remaining.map((ref) => ({ value: ref })),
|
|
174
|
+
}));
|
|
175
|
+
for (const ref of chosen)
|
|
176
|
+
bumpByRef.set(ref, bumpType);
|
|
177
|
+
remaining = remaining.filter((ref) => !chosen.has(ref));
|
|
178
|
+
}
|
|
179
|
+
for (const ref of remaining)
|
|
180
|
+
bumpByRef.set(ref, 'patch');
|
|
181
|
+
// Emit in the original selection order rather than grouped by bump level.
|
|
182
|
+
return Object.fromEntries(pkgRefs.map((ref) => [ref, bumpByRef.get(ref)]));
|
|
183
|
+
}
|
|
184
|
+
async function renderStatus(workspaceDir, opts) {
|
|
185
|
+
const intents = await readChangeIntents(workspaceDir);
|
|
186
|
+
const ledger = await readLedger(workspaceDir);
|
|
187
|
+
const plan = assembleReleasePlan({
|
|
188
|
+
workspaceDir,
|
|
189
|
+
projects: toWorkspaceProjects(opts.allProjects ?? []),
|
|
190
|
+
intents,
|
|
191
|
+
ledger,
|
|
192
|
+
versioning: opts.versioning,
|
|
193
|
+
});
|
|
194
|
+
if (plan.releases.length === 0) {
|
|
195
|
+
return 'No pending changes.';
|
|
196
|
+
}
|
|
197
|
+
const consumedIds = new Set(plan.releases.flatMap((release) => release.intents.map((intent) => intent.id)));
|
|
198
|
+
let output = 'Pending change intents:\n';
|
|
199
|
+
for (const intent of intents.filter(({ id }) => consumedIds.has(id))) {
|
|
200
|
+
output += ` .changeset/${intent.id}.md\n`;
|
|
201
|
+
}
|
|
202
|
+
output += '\n';
|
|
203
|
+
output += renderReleasePlan(plan);
|
|
204
|
+
return output;
|
|
205
|
+
}
|
|
206
|
+
export function renderReleasePlan(plan) {
|
|
207
|
+
let output = 'Release plan:\n';
|
|
208
|
+
for (const release of plan.releases) {
|
|
209
|
+
output += ` ${release.name}: ${release.currentVersion} → ${release.newVersion} (${release.bumpType}, via ${release.causes.join('+')})\n`;
|
|
210
|
+
}
|
|
211
|
+
return output;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* The projects a change intent may demand a release from: named, carrying a
|
|
215
|
+
* valid semver version, and not frozen by `versioning.ignore`. Matches the
|
|
216
|
+
* participant set of the release-plan assembler.
|
|
217
|
+
*/
|
|
218
|
+
export function getReleasableProjects(allProjects, workspaceDir, versioning) {
|
|
219
|
+
const refs = indexProjectRefs(allProjects, workspaceDir);
|
|
220
|
+
const ignoredDirs = new Set((versioning?.ignore ?? []).flatMap((ref) => refs.refToDirs(ref)));
|
|
221
|
+
return allProjects
|
|
222
|
+
.filter(({ manifest }) => manifest.name != null &&
|
|
223
|
+
manifest.version != null &&
|
|
224
|
+
valid(manifest.version) != null)
|
|
225
|
+
.map((project) => ({ name: project.manifest.name, dir: toProjectDir(workspaceDir, project.rootDir) }))
|
|
226
|
+
.filter(({ dir }) => !ignoredDirs.has(dir))
|
|
227
|
+
.map(({ name, dir }) => ({
|
|
228
|
+
name,
|
|
229
|
+
dir,
|
|
230
|
+
ref: refs.nameToDirs(name).length > 1 ? `./${dir}` : name,
|
|
231
|
+
}))
|
|
232
|
+
.sort((left, right) => left.ref.localeCompare(right.ref));
|
|
233
|
+
}
|
|
234
|
+
export function toWorkspaceProjects(allProjects) {
|
|
235
|
+
return allProjects.map((project) => ({ rootDir: project.rootDir, manifest: project.manifest }));
|
|
236
|
+
}
|
|
237
|
+
export const change = {
|
|
238
|
+
handler,
|
|
239
|
+
help,
|
|
240
|
+
commandNames,
|
|
241
|
+
cliOptionsTypes,
|
|
242
|
+
rcOptionsTypes,
|
|
243
|
+
recursiveByDefault: true,
|
|
244
|
+
};
|
|
245
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import type { LockfileObject } from '@pnpm/lockfile.types';
|
|
2
|
-
import type { PnpmSettings, Project, ProjectId, ProjectManifest } from '@pnpm/types';
|
|
2
|
+
import type { DependenciesField, PnpmSettings, Project, ProjectId, ProjectManifest } from '@pnpm/types';
|
|
3
3
|
export interface CreateDeployFilesOptions {
|
|
4
4
|
allProjects: Array<Pick<Project, 'manifest' | 'rootDirRealPath'>>;
|
|
5
5
|
deployDir: string;
|
|
6
|
+
include: {
|
|
7
|
+
[dependenciesField in DependenciesField]: boolean;
|
|
8
|
+
};
|
|
6
9
|
lockfile: LockfileObject;
|
|
7
10
|
lockfileDir: string;
|
|
8
11
|
patchedDependencies?: PnpmSettings['patchedDependencies'];
|
|
@@ -20,4 +23,4 @@ export interface DeployFiles {
|
|
|
20
23
|
manifest: ProjectManifest;
|
|
21
24
|
workspaceManifest?: DeployWorkspaceManifest;
|
|
22
25
|
}
|
|
23
|
-
export declare function createDeployFiles({ allProjects, deployDir, lockfile, lockfileDir, patchedDependencies, selectedProjectManifest, projectId, rootProjectManifestDir, allowBuilds, }: CreateDeployFilesOptions): DeployFiles;
|
|
26
|
+
export declare function createDeployFiles({ allProjects, deployDir, include, lockfile, lockfileDir, patchedDependencies, selectedProjectManifest, projectId, rootProjectManifestDir, allowBuilds, }: CreateDeployFilesOptions): DeployFiles;
|
|
@@ -3,7 +3,7 @@ import url from 'node:url';
|
|
|
3
3
|
import * as dp from '@pnpm/deps.path';
|
|
4
4
|
import normalizePath from 'normalize-path';
|
|
5
5
|
const DEPENDENCIES_FIELD = ['dependencies', 'devDependencies', 'optionalDependencies'];
|
|
6
|
-
export function createDeployFiles({ allProjects, deployDir, lockfile, lockfileDir, patchedDependencies, selectedProjectManifest, projectId, rootProjectManifestDir, allowBuilds, }) {
|
|
6
|
+
export function createDeployFiles({ allProjects, deployDir, include, lockfile, lockfileDir, patchedDependencies, selectedProjectManifest, projectId, rootProjectManifestDir, allowBuilds, }) {
|
|
7
7
|
const deployedProjectRealPath = path.resolve(lockfileDir, projectId);
|
|
8
8
|
const inputSnapshot = lockfile.importers[projectId];
|
|
9
9
|
const targetSnapshot = {
|
|
@@ -61,13 +61,17 @@ export function createDeployFiles({ allProjects, deployDir, lockfile, lockfileDi
|
|
|
61
61
|
targetSpecifiers[name] = targetDependencies[name] = version;
|
|
62
62
|
continue;
|
|
63
63
|
}
|
|
64
|
+
resolveResult.packageName ??= name;
|
|
64
65
|
targetSpecifiers[name] = targetDependencies[name] =
|
|
65
66
|
resolveResult.resolvedPath === deployedProjectRealPath ? 'link:.' : createFileUrlDepPath(resolveResult, allProjects);
|
|
66
67
|
}
|
|
67
68
|
}
|
|
69
|
+
const deployPackageSnapshots = filterDeployPackageSnapshots(targetSnapshot, targetPackageSnapshots, include);
|
|
68
70
|
const result = {
|
|
69
71
|
lockfile: {
|
|
70
72
|
...lockfile,
|
|
73
|
+
// The deployed manifest contains concrete versions, and catalogs are not copied to the target.
|
|
74
|
+
catalogs: undefined,
|
|
71
75
|
patchedDependencies: undefined,
|
|
72
76
|
overrides: undefined, // the effects of the overrides should already be part of the package snapshots
|
|
73
77
|
packageExtensionsChecksum: undefined, // the effects of the package extensions should already be part of the package snapshots
|
|
@@ -79,7 +83,7 @@ export function createDeployFiles({ allProjects, deployDir, lockfile, lockfileDi
|
|
|
79
83
|
importers: {
|
|
80
84
|
['.']: targetSnapshot,
|
|
81
85
|
},
|
|
82
|
-
packages:
|
|
86
|
+
packages: deployPackageSnapshots,
|
|
83
87
|
},
|
|
84
88
|
manifest: {
|
|
85
89
|
...selectedProjectManifest,
|
|
@@ -109,6 +113,37 @@ export function createDeployFiles({ allProjects, deployDir, lockfile, lockfileDi
|
|
|
109
113
|
}
|
|
110
114
|
return result;
|
|
111
115
|
}
|
|
116
|
+
function filterDeployPackageSnapshots(importer, packages, include) {
|
|
117
|
+
const queue = [];
|
|
118
|
+
const enqueue = (dependencies) => {
|
|
119
|
+
for (const [alias, reference] of Object.entries(dependencies ?? {})) {
|
|
120
|
+
const depPath = dp.refToRelative(reference, alias);
|
|
121
|
+
if (depPath != null && packages[depPath] != null)
|
|
122
|
+
queue.push(depPath);
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
if (include.dependencies)
|
|
126
|
+
enqueue(importer.dependencies);
|
|
127
|
+
if (include.devDependencies)
|
|
128
|
+
enqueue(importer.devDependencies);
|
|
129
|
+
if (include.optionalDependencies)
|
|
130
|
+
enqueue(importer.optionalDependencies);
|
|
131
|
+
const reachable = new Set();
|
|
132
|
+
let head = 0;
|
|
133
|
+
while (head < queue.length) {
|
|
134
|
+
const depPath = queue[head++];
|
|
135
|
+
if (reachable.has(depPath))
|
|
136
|
+
continue;
|
|
137
|
+
reachable.add(depPath);
|
|
138
|
+
const snapshot = packages[depPath];
|
|
139
|
+
if (snapshot == null)
|
|
140
|
+
continue;
|
|
141
|
+
enqueue(snapshot.dependencies);
|
|
142
|
+
if (include.optionalDependencies)
|
|
143
|
+
enqueue(snapshot.optionalDependencies);
|
|
144
|
+
}
|
|
145
|
+
return Object.fromEntries(Array.from(reachable, (depPath) => [depPath, packages[depPath]]));
|
|
146
|
+
}
|
|
112
147
|
function convertPackageSnapshot(inputSnapshot, opts) {
|
|
113
148
|
const inputResolution = inputSnapshot.resolution;
|
|
114
149
|
let outputResolution;
|
|
@@ -178,6 +213,7 @@ function convertResolvedDependencies(input, opts) {
|
|
|
178
213
|
output[key] = 'link:.'; // the path is relative to the lockfile dir, which means '.' would reference the deploy dir
|
|
179
214
|
continue;
|
|
180
215
|
}
|
|
216
|
+
resolveResult.packageName ??= key;
|
|
181
217
|
output[key] = createFileUrlDepPath(resolveResult, opts.allProjects);
|
|
182
218
|
}
|
|
183
219
|
return output;
|
|
@@ -194,7 +230,7 @@ function resolveLinkOrFile(pkgVer, opts) {
|
|
|
194
230
|
const resolveSchemeResult = resolveScheme('file:', lockfileDir) ?? resolveScheme('link:', projectRootDirRealPath);
|
|
195
231
|
if (resolveSchemeResult)
|
|
196
232
|
return resolveSchemeResult;
|
|
197
|
-
const { nonSemverVersion, patchHash, peerDepGraphHash, version } = dp.parse(pkgVer);
|
|
233
|
+
const { name, nonSemverVersion, patchHash, peerDepGraphHash, version } = dp.parse(pkgVer);
|
|
198
234
|
if (!nonSemverVersion)
|
|
199
235
|
return undefined;
|
|
200
236
|
if (version) {
|
|
@@ -207,12 +243,13 @@ function resolveLinkOrFile(pkgVer, opts) {
|
|
|
207
243
|
throw new Error(`Something goes wrong, suffix should be undefined but isn't: ${parseResult.suffix}`);
|
|
208
244
|
}
|
|
209
245
|
parseResult.suffix = `${patchHash ?? ''}${peerDepGraphHash ?? ''}`;
|
|
246
|
+
parseResult.packageName = name;
|
|
210
247
|
return parseResult;
|
|
211
248
|
}
|
|
212
|
-
function createFileUrlDepPath({ resolvedPath, suffix }, allProjects) {
|
|
249
|
+
function createFileUrlDepPath({ resolvedPath, suffix, packageName }, allProjects) {
|
|
213
250
|
const depFileUrl = url.pathToFileURL(resolvedPath).toString();
|
|
214
251
|
const project = allProjects.find(project => project.rootDirRealPath === resolvedPath);
|
|
215
|
-
const name = project?.manifest.name ?? path.basename(resolvedPath);
|
|
252
|
+
const name = project?.manifest.name ?? packageName ?? path.basename(resolvedPath);
|
|
216
253
|
return `${name}@${depFileUrl}${suffix ?? ''}`;
|
|
217
254
|
}
|
|
218
255
|
//# sourceMappingURL=createDeployFiles.js.map
|
package/lib/deploy/deploy.js
CHANGED
|
@@ -333,6 +333,11 @@ async function deployFromSharedLockfile(opts, selectedProject, deployDir) {
|
|
|
333
333
|
const deployFiles = createDeployFiles({
|
|
334
334
|
allProjects,
|
|
335
335
|
deployDir,
|
|
336
|
+
include: {
|
|
337
|
+
dependencies: opts.production !== false,
|
|
338
|
+
devDependencies: opts.dev !== false,
|
|
339
|
+
optionalDependencies: opts.optional !== false,
|
|
340
|
+
},
|
|
336
341
|
lockfile,
|
|
337
342
|
lockfileDir,
|
|
338
343
|
patchedDependencies: opts.patchedDependencies,
|
package/lib/index.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
export { change } from './change/index.js';
|
|
1
2
|
export { deploy } from './deploy/index.js';
|
|
3
|
+
export { lane } from './lane/index.js';
|
|
2
4
|
export { packApp } from './pack-app/index.js';
|
|
3
5
|
export { pack, publish } from './publish/index.js';
|
|
4
6
|
export * as stage from './stage/index.js';
|
package/lib/index.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
export { change } from './change/index.js';
|
|
1
2
|
export { deploy } from './deploy/index.js';
|
|
3
|
+
export { lane } from './lane/index.js';
|
|
2
4
|
export { packApp } from './pack-app/index.js';
|
|
3
5
|
export { pack, publish } from './publish/index.js';
|
|
4
6
|
export * as stage from './stage/index.js';
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Config } from '@pnpm/config.reader';
|
|
2
|
+
import type { Project, ProjectsGraph } from '@pnpm/types';
|
|
3
|
+
export declare function rcOptionsTypes(): Record<string, unknown>;
|
|
4
|
+
export declare function cliOptionsTypes(): Record<string, unknown>;
|
|
5
|
+
export declare const commandNames: string[];
|
|
6
|
+
/**
|
|
7
|
+
* The reserved name of the default lane: every package is on it unless
|
|
8
|
+
* assigned elsewhere, packages on it release stable versions, and no
|
|
9
|
+
* prerelease lane can take the name.
|
|
10
|
+
*/
|
|
11
|
+
export declare const MAIN_LANE = "main";
|
|
12
|
+
export declare function help(): string;
|
|
13
|
+
export type LaneCommandOptions = Pick<Config, 'dir' | 'filter' | 'versioning' | 'workspaceDir'> & {
|
|
14
|
+
allProjects?: Project[];
|
|
15
|
+
selectedProjectsGraph?: ProjectsGraph;
|
|
16
|
+
};
|
|
17
|
+
export declare function handler(opts: LaneCommandOptions, params: string[]): Promise<string>;
|
|
18
|
+
export declare const lane: {
|
|
19
|
+
handler: typeof handler;
|
|
20
|
+
help: typeof help;
|
|
21
|
+
commandNames: string[];
|
|
22
|
+
cliOptionsTypes: typeof cliOptionsTypes;
|
|
23
|
+
rcOptionsTypes: typeof rcOptionsTypes;
|
|
24
|
+
};
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { PnpmError } from '@pnpm/error';
|
|
2
|
+
import { indexProjectRefs, toProjectDir } from '@pnpm/releasing.versioning';
|
|
3
|
+
import { updateWorkspaceManifest } from '@pnpm/workspace.workspace-manifest-writer';
|
|
4
|
+
import { renderHelp } from 'render-help';
|
|
5
|
+
import { getReleasableProjects } from '../change/index.js';
|
|
6
|
+
export function rcOptionsTypes() {
|
|
7
|
+
return {};
|
|
8
|
+
}
|
|
9
|
+
export function cliOptionsTypes() {
|
|
10
|
+
return {
|
|
11
|
+
recursive: Boolean,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
export const commandNames = ['lane'];
|
|
15
|
+
/**
|
|
16
|
+
* The reserved name of the default lane: every package is on it unless
|
|
17
|
+
* assigned elsewhere, packages on it release stable versions, and no
|
|
18
|
+
* prerelease lane can take the name.
|
|
19
|
+
*/
|
|
20
|
+
export const MAIN_LANE = 'main';
|
|
21
|
+
export function help() {
|
|
22
|
+
return renderHelp({
|
|
23
|
+
description: 'Manages per-package release lanes. A lane is a parallel release track: while a package is on one, the bare "pnpm version -r" releases it as X.Y.Z-<lane>.N prereleases while the rest of the workspace keeps releasing stable versions. Moving a package back to the main lane releases its accumulated stable version on the next run. Membership lives under the versioning.lanes key of pnpm-workspace.yaml; this command is a convenience editor for that key.',
|
|
24
|
+
usages: [
|
|
25
|
+
'pnpm lane',
|
|
26
|
+
'pnpm lane <name> --filter <pattern>',
|
|
27
|
+
'pnpm lane main --filter <pattern>',
|
|
28
|
+
],
|
|
29
|
+
descriptionLists: [
|
|
30
|
+
{
|
|
31
|
+
title: 'Options',
|
|
32
|
+
list: [
|
|
33
|
+
{
|
|
34
|
+
description: 'Select the packages to move between lanes',
|
|
35
|
+
name: '--filter <pattern>',
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
export async function handler(opts, params) {
|
|
43
|
+
const workspaceDir = opts.workspaceDir;
|
|
44
|
+
if (!workspaceDir) {
|
|
45
|
+
throw new PnpmError('WORKSPACE_ONLY', 'pnpm lane is only supported in a workspace');
|
|
46
|
+
}
|
|
47
|
+
if (params.length === 0) {
|
|
48
|
+
return renderLanes(opts.versioning?.lanes ?? {});
|
|
49
|
+
}
|
|
50
|
+
const laneName = params[0];
|
|
51
|
+
if ((opts.filter ?? []).length === 0) {
|
|
52
|
+
throw new PnpmError('VERSIONING_LANE_FILTER_REQUIRED', 'Select the packages to move with --filter, e.g. "pnpm lane alpha --filter <pkg>..."');
|
|
53
|
+
}
|
|
54
|
+
const refs = indexProjectRefs(opts.allProjects ?? [], workspaceDir);
|
|
55
|
+
const releasableDirs = new Set(getReleasableProjects(opts.allProjects ?? [], workspaceDir, opts.versioning).map((project) => project.dir));
|
|
56
|
+
const selected = Object.values(opts.selectedProjectsGraph ?? {})
|
|
57
|
+
.map((node) => ({
|
|
58
|
+
name: node.package.manifest.name,
|
|
59
|
+
dir: toProjectDir(workspaceDir, node.package.rootDir),
|
|
60
|
+
}))
|
|
61
|
+
.filter((project) => project.name != null && releasableDirs.has(project.dir));
|
|
62
|
+
if (selected.length === 0) {
|
|
63
|
+
throw new PnpmError('VERSIONING_NO_PACKAGES', 'The filter selected no releasable packages');
|
|
64
|
+
}
|
|
65
|
+
// Existing entries may reference projects by name or by directory; resolve
|
|
66
|
+
// them so assignments and removals key on the project, not the spelling.
|
|
67
|
+
const lanes = { ...opts.versioning?.lanes };
|
|
68
|
+
const laneByDir = new Map();
|
|
69
|
+
for (const [key, lane] of Object.entries(lanes)) {
|
|
70
|
+
for (const dir of refs.refToDirs(key)) {
|
|
71
|
+
laneByDir.set(dir, { key, lane });
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
let output;
|
|
75
|
+
if (laneName === MAIN_LANE) {
|
|
76
|
+
for (const project of selected) {
|
|
77
|
+
const existing = laneByDir.get(project.dir);
|
|
78
|
+
if (existing != null) {
|
|
79
|
+
delete lanes[existing.key];
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
output = `Moved to the main lane:\n${selected.map((project) => ` ${refFor(project, refs)}\n`).join('')}` +
|
|
83
|
+
'The accumulated stable versions release on the next "pnpm version -r" run.';
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
if (laneName.toLowerCase() === MAIN_LANE) {
|
|
87
|
+
throw new PnpmError('VERSIONING_INVALID_LANE_NAME', `Invalid lane name: ${laneName}. "main" is the reserved default lane; spell it in lowercase to move packages back onto it.`);
|
|
88
|
+
}
|
|
89
|
+
// A purely numeric lane name is rejected because semver parses an
|
|
90
|
+
// all-digit prerelease identifier as a number, which changes sorting
|
|
91
|
+
// semantics.
|
|
92
|
+
if (!/^[0-9A-Z-]+$/i.test(laneName) || /^\d+$/.test(laneName)) {
|
|
93
|
+
throw new PnpmError('VERSIONING_INVALID_LANE_NAME', `Invalid lane name: ${laneName}. Lane names may contain only alphanumerics and hyphens, and cannot be purely numeric.`);
|
|
94
|
+
}
|
|
95
|
+
for (const project of selected) {
|
|
96
|
+
const existing = laneByDir.get(project.dir);
|
|
97
|
+
if (existing != null && existing.lane !== laneName) {
|
|
98
|
+
throw new PnpmError('VERSIONING_ALREADY_ON_LANE', `${refFor(project, refs)} is already on the "${existing.lane}" lane. Move it back with "pnpm lane main" first.`);
|
|
99
|
+
}
|
|
100
|
+
if (existing == null) {
|
|
101
|
+
lanes[refFor(project, refs)] = laneName;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
output = `Moved to the "${laneName}" lane:\n${selected.map((project) => ` ${refFor(project, refs)}\n`).join('')}`;
|
|
105
|
+
}
|
|
106
|
+
const versioning = { ...opts.versioning };
|
|
107
|
+
if (Object.keys(lanes).length > 0) {
|
|
108
|
+
versioning.lanes = lanes;
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
delete versioning.lanes;
|
|
112
|
+
}
|
|
113
|
+
await updateWorkspaceManifest(workspaceDir, {
|
|
114
|
+
updatedFields: {
|
|
115
|
+
versioning: Object.keys(versioning).length > 0 ? versioning : undefined,
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
return output;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* How the project is referenced in versioning.lanes and in output: the bare
|
|
122
|
+
* name, or the directory path when the name is shared by several projects.
|
|
123
|
+
*/
|
|
124
|
+
function refFor(project, refs) {
|
|
125
|
+
return refs.nameToDirs(project.name).length > 1 ? `./${project.dir}` : project.name;
|
|
126
|
+
}
|
|
127
|
+
function renderLanes(lanes) {
|
|
128
|
+
const laneEntries = Object.entries(lanes);
|
|
129
|
+
if (laneEntries.length === 0) {
|
|
130
|
+
return 'All packages are on the main lane.';
|
|
131
|
+
}
|
|
132
|
+
const byLane = new Map();
|
|
133
|
+
for (const [ref, laneName] of laneEntries) {
|
|
134
|
+
let members = byLane.get(laneName);
|
|
135
|
+
if (members == null) {
|
|
136
|
+
members = [];
|
|
137
|
+
byLane.set(laneName, members);
|
|
138
|
+
}
|
|
139
|
+
members.push(ref);
|
|
140
|
+
}
|
|
141
|
+
let output = 'Lanes:\n';
|
|
142
|
+
for (const [laneName, members] of Array.from(byLane.entries()).sort(([left], [right]) => left.localeCompare(right))) {
|
|
143
|
+
output += ` ${laneName}:\n`;
|
|
144
|
+
for (const ref of members.sort()) {
|
|
145
|
+
output += ` ${ref}\n`;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return output;
|
|
149
|
+
}
|
|
150
|
+
export const lane = {
|
|
151
|
+
handler,
|
|
152
|
+
help,
|
|
153
|
+
commandNames,
|
|
154
|
+
cliOptionsTypes,
|
|
155
|
+
rcOptionsTypes,
|
|
156
|
+
};
|
|
157
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -135,7 +135,7 @@ async function multiPublishToRegistry(registry, group, opts) {
|
|
|
135
135
|
operation: async (otp) => npmFetch(BATCH_PUBLISH_ENDPOINT, {
|
|
136
136
|
...publishOptions,
|
|
137
137
|
access: publishOptions.access ?? undefined,
|
|
138
|
-
otp,
|
|
138
|
+
otp: otp ?? publishOptions.otp,
|
|
139
139
|
method: 'PUT',
|
|
140
140
|
body,
|
|
141
141
|
ignoreBody: true,
|
|
@@ -5,6 +5,13 @@ export type TarballSuffix = typeof TARBALL_SUFFIXES[number];
|
|
|
5
5
|
export type TarballPath = `${string}${TarballSuffix}`;
|
|
6
6
|
export declare const isTarballPath: (path: string) => path is TarballPath;
|
|
7
7
|
export declare function extractManifestFromPacked<Output = ExportedManifest>(tarballPath: TarballPath): Promise<Output>;
|
|
8
|
+
/**
|
|
9
|
+
* Read the publish manifest from a pre-built tarball, filling in its `readme` from the tarball's
|
|
10
|
+
* root README file when the manifest doesn't already declare one. This mirrors the npm CLI, which
|
|
11
|
+
* reads the readme out of the tarball (via pacote's `fullReadJson`) so the registry gets it as
|
|
12
|
+
* metadata even though it isn't stored in the packed `package.json`.
|
|
13
|
+
*/
|
|
14
|
+
export declare function extractPublishManifestFromPacked(tarballPath: TarballPath): Promise<ExportedManifest>;
|
|
8
15
|
export declare class PublishArchiveMissingManifestError extends PnpmError {
|
|
9
16
|
readonly tarballPath: string;
|
|
10
17
|
constructor(tarballPath: string);
|