@pnpm/engine.pm.commands 1000.0.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/LICENSE +22 -0
- package/lib/index.d.ts +3 -0
- package/lib/index.js +4 -0
- package/lib/self-updater/index.d.ts +3 -0
- package/lib/self-updater/index.js +4 -0
- package/lib/self-updater/installPnpm.d.ts +42 -0
- package/lib/self-updater/installPnpm.js +280 -0
- package/lib/self-updater/selfUpdate.d.ts +9 -0
- package/lib/self-updater/selfUpdate.js +149 -0
- package/lib/setup/index.d.ts +2 -0
- package/lib/setup/index.js +3 -0
- package/lib/setup/setup.d.ts +9 -0
- package/lib/setup/setup.js +165 -0
- package/package.json +78 -0
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/lib/index.d.ts
ADDED
package/lib/index.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { type GlobalAddOptions } from '@pnpm/global.commands';
|
|
2
|
+
import type { EnvLockfile } from '@pnpm/lockfile.types';
|
|
3
|
+
import type { StoreController } from '@pnpm/store.controller';
|
|
4
|
+
import type { Registries } from '@pnpm/types';
|
|
5
|
+
export interface InstallPnpmResult {
|
|
6
|
+
binDir: string;
|
|
7
|
+
baseDir: string;
|
|
8
|
+
alreadyExisted: boolean;
|
|
9
|
+
}
|
|
10
|
+
export interface InstallPnpmOptions extends GlobalAddOptions {
|
|
11
|
+
envLockfile?: EnvLockfile;
|
|
12
|
+
storeController?: StoreController;
|
|
13
|
+
storeDir?: string;
|
|
14
|
+
packageManager?: {
|
|
15
|
+
name: string;
|
|
16
|
+
version: string;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Installs pnpm to the global packages directory (for self-update).
|
|
21
|
+
* Creates an entry in globalPkgDir that is visible to `pnpm ls -g`.
|
|
22
|
+
*/
|
|
23
|
+
export declare function installPnpm(pnpmVersion: string, opts: InstallPnpmOptions): Promise<InstallPnpmResult>;
|
|
24
|
+
/**
|
|
25
|
+
* Installs pnpm to the global virtual store (for version switching).
|
|
26
|
+
* Does NOT create an entry in globalPkgDir — the package lives only in the store.
|
|
27
|
+
* Returns the bin directory where the pnpm binary can be found.
|
|
28
|
+
*/
|
|
29
|
+
export declare function installPnpmToStore(pnpmVersion: string, opts: {
|
|
30
|
+
envLockfile: EnvLockfile;
|
|
31
|
+
storeController: StoreController;
|
|
32
|
+
storeDir: string;
|
|
33
|
+
registries: Registries;
|
|
34
|
+
virtualStoreDirMaxLength: number;
|
|
35
|
+
packageManager?: {
|
|
36
|
+
name: string;
|
|
37
|
+
version: string;
|
|
38
|
+
};
|
|
39
|
+
}): Promise<{
|
|
40
|
+
binDir: string;
|
|
41
|
+
}>;
|
|
42
|
+
export declare function linkExePlatformBinary(installDir: string): void;
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import util from 'node:util';
|
|
4
|
+
import { linkBins } from '@pnpm/bins.linker';
|
|
5
|
+
import { getCurrentPackageName } from '@pnpm/cli.meta';
|
|
6
|
+
import { iterateHashedGraphNodes, iteratePkgMeta, lockfileToDepGraph, } from '@pnpm/deps.graph-hasher';
|
|
7
|
+
import { installGlobalPackages } from '@pnpm/global.commands';
|
|
8
|
+
import { cleanOrphanedInstallDirs, createGlobalCacheKey, createInstallDir, findGlobalPackage, getHashLink, } from '@pnpm/global.packages';
|
|
9
|
+
import { headlessInstall } from '@pnpm/installing.deps-restorer';
|
|
10
|
+
import symlinkDir from 'symlink-dir';
|
|
11
|
+
/**
|
|
12
|
+
* Installs pnpm to the global packages directory (for self-update).
|
|
13
|
+
* Creates an entry in globalPkgDir that is visible to `pnpm ls -g`.
|
|
14
|
+
*/
|
|
15
|
+
export async function installPnpm(pnpmVersion, opts) {
|
|
16
|
+
const currentPkgName = getCurrentPackageName();
|
|
17
|
+
const wantedLockfile = opts.envLockfile
|
|
18
|
+
? buildLockfileFromEnvLockfile(opts.envLockfile, currentPkgName, pnpmVersion)
|
|
19
|
+
: undefined;
|
|
20
|
+
const result = await installPnpmToGlobalDir(opts, currentPkgName, pnpmVersion, wantedLockfile);
|
|
21
|
+
return {
|
|
22
|
+
alreadyExisted: result.alreadyExisted,
|
|
23
|
+
baseDir: result.installDir,
|
|
24
|
+
binDir: result.binDir,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Installs pnpm to the global virtual store (for version switching).
|
|
29
|
+
* Does NOT create an entry in globalPkgDir — the package lives only in the store.
|
|
30
|
+
* Returns the bin directory where the pnpm binary can be found.
|
|
31
|
+
*/
|
|
32
|
+
export async function installPnpmToStore(pnpmVersion, opts) {
|
|
33
|
+
const currentPkgName = getCurrentPackageName();
|
|
34
|
+
const wantedLockfile = buildLockfileFromEnvLockfile(opts.envLockfile, currentPkgName, pnpmVersion);
|
|
35
|
+
const globalVirtualStoreDir = path.join(opts.storeDir, 'links');
|
|
36
|
+
// Compute the GVS hash for the pnpm package to find its path
|
|
37
|
+
const pnpmGvsPath = findPnpmGvsPath(wantedLockfile, currentPkgName, globalVirtualStoreDir);
|
|
38
|
+
const pnpmPkgDir = path.join(pnpmGvsPath, 'node_modules', currentPkgName);
|
|
39
|
+
const binDir = path.join(pnpmGvsPath, 'bin');
|
|
40
|
+
// Check if already installed in the GVS
|
|
41
|
+
if (fs.existsSync(path.join(pnpmPkgDir, 'package.json'))) {
|
|
42
|
+
if (!fs.existsSync(binDir)) {
|
|
43
|
+
await linkBins(path.join(pnpmGvsPath, 'node_modules'), binDir, { warn: noop });
|
|
44
|
+
}
|
|
45
|
+
return { binDir };
|
|
46
|
+
}
|
|
47
|
+
// Install to a temporary directory — headless install with GVS enabled
|
|
48
|
+
// will populate the global virtual store
|
|
49
|
+
const tmpInstallDir = path.join(opts.storeDir, '.tmp', `pnpm-${pnpmVersion}-${Date.now()}`);
|
|
50
|
+
fs.mkdirSync(tmpInstallDir, { recursive: true });
|
|
51
|
+
try {
|
|
52
|
+
await installFromLockfile(tmpInstallDir, binDir, {
|
|
53
|
+
wantedLockfile,
|
|
54
|
+
storeController: opts.storeController,
|
|
55
|
+
storeDir: opts.storeDir,
|
|
56
|
+
registries: opts.registries,
|
|
57
|
+
virtualStoreDirMaxLength: opts.virtualStoreDirMaxLength,
|
|
58
|
+
packageManager: opts.packageManager,
|
|
59
|
+
});
|
|
60
|
+
// Now the GVS should be populated — create bins alongside the GVS entry
|
|
61
|
+
linkExePlatformBinary(pnpmGvsPath);
|
|
62
|
+
await linkBins(path.join(pnpmGvsPath, 'node_modules'), binDir, { warn: noop });
|
|
63
|
+
return { binDir };
|
|
64
|
+
}
|
|
65
|
+
finally {
|
|
66
|
+
try {
|
|
67
|
+
fs.rmSync(tmpInstallDir, { recursive: true, force: true });
|
|
68
|
+
}
|
|
69
|
+
catch { }
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function noop(_message) { }
|
|
73
|
+
function findPnpmGvsPath(lockfile, pkgName, globalVirtualStoreDir) {
|
|
74
|
+
const graph = lockfileToDepGraph(lockfile);
|
|
75
|
+
const pkgMetaIterator = iteratePkgMeta(lockfile, graph);
|
|
76
|
+
for (const { hash, pkgMeta } of iterateHashedGraphNodes(graph, pkgMetaIterator)) {
|
|
77
|
+
if (pkgMeta.name === pkgName) {
|
|
78
|
+
return path.join(globalVirtualStoreDir, hash);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
throw new Error(`Could not find ${pkgName} in lockfile`);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Installs pnpm to the global packages directory.
|
|
85
|
+
* Bins are created within the install dir's own bin/ subdirectory.
|
|
86
|
+
*
|
|
87
|
+
* When a `wantedLockfile` is provided, a frozen headless install is performed
|
|
88
|
+
* using the lockfile's integrity hashes for security. Otherwise, full resolution
|
|
89
|
+
* is performed via `installGlobalPackages`.
|
|
90
|
+
*/
|
|
91
|
+
async function installPnpmToGlobalDir(opts, pkgName, version, wantedLockfile) {
|
|
92
|
+
const globalDir = opts.globalPkgDir;
|
|
93
|
+
cleanOrphanedInstallDirs(globalDir);
|
|
94
|
+
// Check if already installed globally
|
|
95
|
+
const existing = findGlobalPackage(globalDir, pkgName);
|
|
96
|
+
if (existing) {
|
|
97
|
+
const pkgJsonPath = path.join(existing.installDir, 'node_modules', pkgName, 'package.json');
|
|
98
|
+
try {
|
|
99
|
+
const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf8'));
|
|
100
|
+
if (pkgJson.version === version) {
|
|
101
|
+
const binDir = path.join(existing.installDir, 'bin');
|
|
102
|
+
return { alreadyExisted: true, installDir: existing.installDir, binDir };
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
catch { }
|
|
106
|
+
}
|
|
107
|
+
const installDir = createInstallDir(globalDir);
|
|
108
|
+
const binDir = path.join(installDir, 'bin');
|
|
109
|
+
try {
|
|
110
|
+
if (wantedLockfile != null && opts.storeController != null && opts.storeDir != null) {
|
|
111
|
+
await installFromLockfile(installDir, binDir, {
|
|
112
|
+
wantedLockfile,
|
|
113
|
+
storeController: opts.storeController,
|
|
114
|
+
storeDir: opts.storeDir,
|
|
115
|
+
registries: opts.registries,
|
|
116
|
+
virtualStoreDirMaxLength: opts.virtualStoreDirMaxLength,
|
|
117
|
+
packageManager: opts.packageManager,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
await installFromResolution(installDir, opts, [`${pkgName}@${version}`]);
|
|
122
|
+
}
|
|
123
|
+
linkExePlatformBinary(installDir);
|
|
124
|
+
await linkBins(path.join(installDir, 'node_modules'), binDir, { warn: noop });
|
|
125
|
+
// Create hash symlink for the global packages system
|
|
126
|
+
const pkgJson = JSON.parse(fs.readFileSync(path.join(installDir, 'package.json'), 'utf8'));
|
|
127
|
+
const aliases = Object.keys(pkgJson.dependencies ?? {});
|
|
128
|
+
const cacheHash = createGlobalCacheKey({ aliases, registries: opts.registries });
|
|
129
|
+
const hashLink = getHashLink(globalDir, cacheHash);
|
|
130
|
+
await symlinkDir(installDir, hashLink, { overwrite: true });
|
|
131
|
+
return { alreadyExisted: false, installDir, binDir };
|
|
132
|
+
}
|
|
133
|
+
catch (err) {
|
|
134
|
+
try {
|
|
135
|
+
fs.rmSync(installDir, { recursive: true, force: true });
|
|
136
|
+
}
|
|
137
|
+
catch { }
|
|
138
|
+
throw err;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
async function installFromLockfile(installDir, binDir, opts) {
|
|
142
|
+
const rootImporter = opts.wantedLockfile.importers['.'];
|
|
143
|
+
const dependencies = rootImporter?.dependencies ?? {};
|
|
144
|
+
fs.writeFileSync(path.join(installDir, 'package.json'), JSON.stringify({ dependencies }));
|
|
145
|
+
await headlessInstall({
|
|
146
|
+
wantedLockfile: opts.wantedLockfile,
|
|
147
|
+
lockfileDir: installDir,
|
|
148
|
+
storeController: opts.storeController,
|
|
149
|
+
storeDir: opts.storeDir,
|
|
150
|
+
registries: opts.registries,
|
|
151
|
+
enableGlobalVirtualStore: true,
|
|
152
|
+
globalVirtualStoreDir: path.join(opts.storeDir, 'links'),
|
|
153
|
+
ignoreScripts: true,
|
|
154
|
+
ignoreDepScripts: true,
|
|
155
|
+
force: false,
|
|
156
|
+
engineStrict: false,
|
|
157
|
+
currentEngine: {
|
|
158
|
+
pnpmVersion: opts.packageManager?.version ?? '',
|
|
159
|
+
},
|
|
160
|
+
include: {
|
|
161
|
+
dependencies: true,
|
|
162
|
+
devDependencies: false,
|
|
163
|
+
optionalDependencies: true,
|
|
164
|
+
},
|
|
165
|
+
selectedProjectDirs: [installDir],
|
|
166
|
+
allProjects: {
|
|
167
|
+
[installDir]: {
|
|
168
|
+
binsDir: binDir,
|
|
169
|
+
buildIndex: 0,
|
|
170
|
+
manifest: { dependencies },
|
|
171
|
+
modulesDir: path.join(installDir, 'node_modules'),
|
|
172
|
+
id: '.',
|
|
173
|
+
rootDir: installDir,
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
hoistedDependencies: {},
|
|
177
|
+
virtualStoreDirMaxLength: opts.virtualStoreDirMaxLength,
|
|
178
|
+
sideEffectsCacheRead: false,
|
|
179
|
+
sideEffectsCacheWrite: false,
|
|
180
|
+
rawConfig: {},
|
|
181
|
+
unsafePerm: false,
|
|
182
|
+
userAgent: '',
|
|
183
|
+
packageManager: opts.packageManager ?? { name: 'pnpm', version: '' },
|
|
184
|
+
pruneStore: false,
|
|
185
|
+
pendingBuilds: [],
|
|
186
|
+
skipped: new Set(),
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
async function installFromResolution(installDir, opts, params) {
|
|
190
|
+
const include = {
|
|
191
|
+
dependencies: true,
|
|
192
|
+
devDependencies: false,
|
|
193
|
+
optionalDependencies: true,
|
|
194
|
+
};
|
|
195
|
+
const fetchFullMetadata = Boolean(opts.supportedArchitectures?.libc);
|
|
196
|
+
await installGlobalPackages({
|
|
197
|
+
...opts,
|
|
198
|
+
global: false,
|
|
199
|
+
bin: path.join(installDir, 'node_modules/.bin'),
|
|
200
|
+
dir: installDir,
|
|
201
|
+
lockfileDir: installDir,
|
|
202
|
+
rootProjectManifestDir: installDir,
|
|
203
|
+
rootProjectManifest: undefined,
|
|
204
|
+
saveProd: true,
|
|
205
|
+
saveDev: false,
|
|
206
|
+
saveOptional: false,
|
|
207
|
+
savePeer: false,
|
|
208
|
+
workspaceDir: undefined,
|
|
209
|
+
sharedWorkspaceLockfile: false,
|
|
210
|
+
lockfileOnly: false,
|
|
211
|
+
fetchFullMetadata,
|
|
212
|
+
include,
|
|
213
|
+
includeDirect: include,
|
|
214
|
+
allowBuilds: {},
|
|
215
|
+
}, params);
|
|
216
|
+
}
|
|
217
|
+
// @pnpm/exe bundles Node.js via optional platform-specific packages (e.g. @pnpm/macos-arm64).
|
|
218
|
+
// Its postinstall script links the correct binary into the @pnpm/exe package dir.
|
|
219
|
+
// Since scripts are disabled during install (to support systems without Node.js),
|
|
220
|
+
// we replicate that linking here.
|
|
221
|
+
export function linkExePlatformBinary(installDir) {
|
|
222
|
+
const platform = process.platform === 'win32'
|
|
223
|
+
? 'win'
|
|
224
|
+
: process.platform === 'darwin'
|
|
225
|
+
? 'macos'
|
|
226
|
+
: process.platform;
|
|
227
|
+
const arch = platform === 'win' && process.arch === 'ia32' ? 'x86' : process.arch;
|
|
228
|
+
const exePkgDir = path.join(installDir, 'node_modules', '@pnpm', 'exe');
|
|
229
|
+
if (!fs.existsSync(exePkgDir))
|
|
230
|
+
return;
|
|
231
|
+
// In pnpm's symlinked node_modules layout, the platform package is not hoisted
|
|
232
|
+
// to the top-level node_modules. It's a dependency of @pnpm/exe and lives as a
|
|
233
|
+
// sibling in the virtual store. Resolve through the @pnpm/exe symlink to find it.
|
|
234
|
+
const exeRealDir = fs.realpathSync(exePkgDir);
|
|
235
|
+
const platformPkgDir = path.join(path.dirname(exeRealDir), `${platform}-${arch}`);
|
|
236
|
+
const executable = platform === 'win' ? 'pnpm.exe' : 'pnpm';
|
|
237
|
+
const src = path.join(platformPkgDir, executable);
|
|
238
|
+
if (!fs.existsSync(src))
|
|
239
|
+
return;
|
|
240
|
+
const dest = path.join(exePkgDir, executable);
|
|
241
|
+
try {
|
|
242
|
+
fs.unlinkSync(dest);
|
|
243
|
+
}
|
|
244
|
+
catch (err) {
|
|
245
|
+
if (!util.types.isNativeError(err) || !('code' in err) || err.code !== 'ENOENT') {
|
|
246
|
+
throw err;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
fs.linkSync(src, dest);
|
|
250
|
+
fs.chmodSync(dest, 0o755);
|
|
251
|
+
if (platform === 'win') {
|
|
252
|
+
const exePkgJsonPath = path.join(exePkgDir, 'package.json');
|
|
253
|
+
const exePkg = JSON.parse(fs.readFileSync(exePkgJsonPath, 'utf8'));
|
|
254
|
+
fs.writeFileSync(path.join(exePkgDir, 'pnpm'), 'This file intentionally left blank');
|
|
255
|
+
exePkg.bin.pnpm = 'pnpm.exe';
|
|
256
|
+
fs.writeFileSync(exePkgJsonPath, JSON.stringify(exePkg, null, 2));
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
function buildLockfileFromEnvLockfile(envLockfile, pkgName, version) {
|
|
260
|
+
const dependencies = {};
|
|
261
|
+
dependencies[pkgName] = version;
|
|
262
|
+
const packages = {};
|
|
263
|
+
for (const [depPath, snapshot] of Object.entries(envLockfile.snapshots)) {
|
|
264
|
+
packages[depPath] = {
|
|
265
|
+
...snapshot,
|
|
266
|
+
...envLockfile.packages[depPath],
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
return {
|
|
270
|
+
lockfileVersion: envLockfile.lockfileVersion,
|
|
271
|
+
importers: {
|
|
272
|
+
['.']: {
|
|
273
|
+
specifiers: { [pkgName]: version },
|
|
274
|
+
dependencies,
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
packages: packages,
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
//# sourceMappingURL=installPnpm.js.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type Config } from '@pnpm/config.reader';
|
|
2
|
+
import { type CreateStoreControllerOptions } from '@pnpm/store.connection-manager';
|
|
3
|
+
export declare function rcOptionsTypes(): Record<string, unknown>;
|
|
4
|
+
export declare function cliOptionsTypes(): Record<string, unknown>;
|
|
5
|
+
export declare const commandNames: string[];
|
|
6
|
+
export declare const skipPackageManagerCheck = true;
|
|
7
|
+
export declare function help(): string;
|
|
8
|
+
export type SelfUpdateCommandOptions = CreateStoreControllerOptions & Pick<Config, 'globalPkgDir' | 'lockfileDir' | 'managePackageManagerVersions' | 'modulesDir' | 'pnpmHomeDir' | 'rootProjectManifestDir' | 'wantedPackageManager'>;
|
|
9
|
+
export declare function handler(opts: SelfUpdateCommandOptions, params: string[]): Promise<undefined | string>;
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { linkBins } from '@pnpm/bins.linker';
|
|
3
|
+
import { isExecutedByCorepack, packageManager } from '@pnpm/cli.meta';
|
|
4
|
+
import { docsUrl } from '@pnpm/cli.utils';
|
|
5
|
+
import { types as allTypes } from '@pnpm/config.reader';
|
|
6
|
+
import { PnpmError } from '@pnpm/error';
|
|
7
|
+
import { createResolver } from '@pnpm/installing.client';
|
|
8
|
+
import { resolvePackageManagerIntegrities } from '@pnpm/installing.env-installer';
|
|
9
|
+
import { globalWarn } from '@pnpm/logger';
|
|
10
|
+
import { whichVersionIsPinned } from '@pnpm/resolving.npm-resolver';
|
|
11
|
+
import { createStoreController } from '@pnpm/store.connection-manager';
|
|
12
|
+
import { readProjectManifest } from '@pnpm/workspace.project-manifest-reader';
|
|
13
|
+
import { pick } from 'ramda';
|
|
14
|
+
import { renderHelp } from 'render-help';
|
|
15
|
+
import semver from 'semver';
|
|
16
|
+
import { installPnpm } from './installPnpm.js';
|
|
17
|
+
export function rcOptionsTypes() {
|
|
18
|
+
return pick([], allTypes);
|
|
19
|
+
}
|
|
20
|
+
export function cliOptionsTypes() {
|
|
21
|
+
return {
|
|
22
|
+
...rcOptionsTypes(),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export const commandNames = ['self-update'];
|
|
26
|
+
export const skipPackageManagerCheck = true;
|
|
27
|
+
export function help() {
|
|
28
|
+
return renderHelp({
|
|
29
|
+
description: 'Updates pnpm to the latest version (or the one specified)',
|
|
30
|
+
descriptionLists: [],
|
|
31
|
+
url: docsUrl('self-update'),
|
|
32
|
+
usages: [
|
|
33
|
+
'pnpm self-update',
|
|
34
|
+
'pnpm self-update 9',
|
|
35
|
+
'pnpm self-update next-10',
|
|
36
|
+
'pnpm self-update 9.10.0',
|
|
37
|
+
],
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
export async function handler(opts, params) {
|
|
41
|
+
if (isExecutedByCorepack()) {
|
|
42
|
+
throw new PnpmError('CANT_SELF_UPDATE_IN_COREPACK', 'You should update pnpm with corepack');
|
|
43
|
+
}
|
|
44
|
+
const { resolve } = createResolver({ ...opts, authConfig: opts.rawConfig });
|
|
45
|
+
const pkgName = 'pnpm';
|
|
46
|
+
const bareSpecifier = params[0] ?? 'latest';
|
|
47
|
+
const resolution = await resolve({ alias: pkgName, bareSpecifier }, {
|
|
48
|
+
lockfileDir: opts.lockfileDir ?? opts.dir,
|
|
49
|
+
preferredVersions: {},
|
|
50
|
+
projectDir: opts.dir,
|
|
51
|
+
});
|
|
52
|
+
if (!resolution?.manifest) {
|
|
53
|
+
throw new PnpmError('CANNOT_RESOLVE_PNPM', `Cannot find "${bareSpecifier}" version of pnpm`);
|
|
54
|
+
}
|
|
55
|
+
if (opts.wantedPackageManager?.name === packageManager.name) {
|
|
56
|
+
if (opts.wantedPackageManager?.version !== resolution.manifest.version) {
|
|
57
|
+
const { manifest, writeProjectManifest } = await readProjectManifest(opts.rootProjectManifestDir);
|
|
58
|
+
if (manifest.devEngines?.packageManager) {
|
|
59
|
+
if (Array.isArray(manifest.devEngines.packageManager)) {
|
|
60
|
+
const pnpmEntry = manifest.devEngines.packageManager.find((e) => e.name === 'pnpm');
|
|
61
|
+
if (pnpmEntry) {
|
|
62
|
+
const updated = updateVersionConstraint(pnpmEntry.version, resolution.manifest.version);
|
|
63
|
+
if (updated !== pnpmEntry.version) {
|
|
64
|
+
pnpmEntry.version = updated;
|
|
65
|
+
await writeProjectManifest(manifest);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
else if (manifest.devEngines.packageManager.name === 'pnpm') {
|
|
70
|
+
const updated = updateVersionConstraint(manifest.devEngines.packageManager.version, resolution.manifest.version);
|
|
71
|
+
if (updated !== manifest.devEngines.packageManager.version) {
|
|
72
|
+
manifest.devEngines.packageManager.version = updated;
|
|
73
|
+
await writeProjectManifest(manifest);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
const store = await createStoreController(opts);
|
|
77
|
+
await resolvePackageManagerIntegrities(resolution.manifest.version, {
|
|
78
|
+
registries: opts.registries,
|
|
79
|
+
rootDir: opts.rootProjectManifestDir,
|
|
80
|
+
storeController: store.ctrl,
|
|
81
|
+
storeDir: store.dir,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
manifest.packageManager = `pnpm@${resolution.manifest.version}`;
|
|
86
|
+
await writeProjectManifest(manifest);
|
|
87
|
+
}
|
|
88
|
+
return `The current project has been updated to use pnpm v${resolution.manifest.version}`;
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
return `The current project is already set to use pnpm v${resolution.manifest.version}`;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (resolution.manifest.version === packageManager.version) {
|
|
95
|
+
return `The currently active ${packageManager.name} v${packageManager.version} is already "${bareSpecifier}" and doesn't need an update`;
|
|
96
|
+
}
|
|
97
|
+
const store = await createStoreController(opts);
|
|
98
|
+
// Resolve integrities and write env lockfile to pnpm-lock.yaml
|
|
99
|
+
const envLockfile = await resolvePackageManagerIntegrities(resolution.manifest.version, {
|
|
100
|
+
registries: opts.registries,
|
|
101
|
+
rootDir: opts.pnpmHomeDir,
|
|
102
|
+
storeController: store.ctrl,
|
|
103
|
+
storeDir: store.dir,
|
|
104
|
+
});
|
|
105
|
+
const { baseDir, alreadyExisted } = await installPnpm(resolution.manifest.version, {
|
|
106
|
+
...opts,
|
|
107
|
+
envLockfile,
|
|
108
|
+
storeController: store.ctrl,
|
|
109
|
+
storeDir: store.dir,
|
|
110
|
+
});
|
|
111
|
+
// Link bins to pnpmHomeDir so the updated pnpm is the active global binary
|
|
112
|
+
await linkBins(path.join(baseDir, 'node_modules'), opts.pnpmHomeDir, { warn: globalWarn });
|
|
113
|
+
if (alreadyExisted) {
|
|
114
|
+
return `The ${bareSpecifier} version, v${resolution.manifest.version}, is already present on the system. It was activated by linking it from ${baseDir}.`;
|
|
115
|
+
}
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Returns the updated version constraint for devEngines.packageManager.
|
|
120
|
+
* - Exact versions and simple ranges (^, ~) are updated to the new version,
|
|
121
|
+
* preserving the range operator.
|
|
122
|
+
* - Ranges that still satisfy the new version are returned unchanged
|
|
123
|
+
* (the exact version will be pinned in the lockfile instead).
|
|
124
|
+
* - Complex ranges (>=x <y, etc.) that no longer satisfy the new version
|
|
125
|
+
* fall back to a caret range with the new version (`^${newVersion}`).
|
|
126
|
+
*/
|
|
127
|
+
function updateVersionConstraint(current, newVersion) {
|
|
128
|
+
if (current == null)
|
|
129
|
+
return newVersion;
|
|
130
|
+
// Range that still satisfies the new version — leave it as-is (lockfile handles pinning)
|
|
131
|
+
if (semver.satisfies(newVersion, current, { includePrerelease: true }))
|
|
132
|
+
return current;
|
|
133
|
+
// Determine the pinning style of the current specifier
|
|
134
|
+
const pinnedVersion = whichVersionIsPinned(current);
|
|
135
|
+
if (pinnedVersion == null) {
|
|
136
|
+
// Complex range that can't be updated while preserving its structure — fall back to ^version
|
|
137
|
+
return `^${newVersion}`;
|
|
138
|
+
}
|
|
139
|
+
return versionSpecFromPinned(newVersion, pinnedVersion);
|
|
140
|
+
}
|
|
141
|
+
function versionSpecFromPinned(version, pinnedVersion) {
|
|
142
|
+
switch (pinnedVersion) {
|
|
143
|
+
case 'none':
|
|
144
|
+
case 'major': return `^${version}`;
|
|
145
|
+
case 'minor': return `~${version}`;
|
|
146
|
+
case 'patch': return version;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
//# sourceMappingURL=selfUpdate.js.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare const rcOptionsTypes: () => Record<string, unknown>;
|
|
2
|
+
export declare const cliOptionsTypes: () => Record<string, unknown>;
|
|
3
|
+
export declare const shorthands: {};
|
|
4
|
+
export declare const commandNames: string[];
|
|
5
|
+
export declare function help(): string;
|
|
6
|
+
export declare function handler(opts: {
|
|
7
|
+
force?: boolean;
|
|
8
|
+
pnpmHomeDir: string;
|
|
9
|
+
}): Promise<string>;
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { detectIfCurrentPkgIsExecutable, packageManager } from '@pnpm/cli.meta';
|
|
5
|
+
import { docsUrl } from '@pnpm/cli.utils';
|
|
6
|
+
import { logger } from '@pnpm/logger';
|
|
7
|
+
import { addDirToEnvPath, } from '@pnpm/os.env.path-extender';
|
|
8
|
+
import { renderHelp } from 'render-help';
|
|
9
|
+
export const rcOptionsTypes = () => ({});
|
|
10
|
+
export const cliOptionsTypes = () => ({
|
|
11
|
+
force: Boolean,
|
|
12
|
+
});
|
|
13
|
+
export const shorthands = {};
|
|
14
|
+
export const commandNames = ['setup'];
|
|
15
|
+
export function help() {
|
|
16
|
+
return renderHelp({
|
|
17
|
+
description: 'Sets up pnpm',
|
|
18
|
+
descriptionLists: [
|
|
19
|
+
{
|
|
20
|
+
title: 'Options',
|
|
21
|
+
list: [
|
|
22
|
+
{
|
|
23
|
+
description: 'Override the PNPM_HOME env variable in case it already exists',
|
|
24
|
+
name: '--force',
|
|
25
|
+
shortAlias: '-f',
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
url: docsUrl('setup'),
|
|
31
|
+
usages: ['pnpm setup'],
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
function getExecPath() {
|
|
35
|
+
if (detectIfCurrentPkgIsExecutable()) {
|
|
36
|
+
// If the pnpm CLI is a single executable application (SEA), we use the path
|
|
37
|
+
// to the exe file instead of the js path.
|
|
38
|
+
return process.execPath;
|
|
39
|
+
}
|
|
40
|
+
return process.argv[1] ?? process.cwd();
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Install the CLI as a global package using `pnpm add -g file:<dir>`.
|
|
44
|
+
* This places pnpm in the standard global directory alongside other
|
|
45
|
+
* globally installed packages.
|
|
46
|
+
*/
|
|
47
|
+
function installCliGlobally(execPath, pnpmHomeDir) {
|
|
48
|
+
const execDir = path.dirname(execPath);
|
|
49
|
+
const execName = path.basename(execPath);
|
|
50
|
+
const pkgJsonPath = path.join(execDir, 'package.json');
|
|
51
|
+
// Write a package.json if one doesn't already exist.
|
|
52
|
+
// (Updated tarballs on GitHub Pages will ship with package.json already.)
|
|
53
|
+
let createdPkgJson = false;
|
|
54
|
+
if (!fs.existsSync(pkgJsonPath)) {
|
|
55
|
+
fs.writeFileSync(pkgJsonPath, JSON.stringify({
|
|
56
|
+
name: '@pnpm/exe',
|
|
57
|
+
version: packageManager.version,
|
|
58
|
+
bin: { pnpm: execName },
|
|
59
|
+
}));
|
|
60
|
+
createdPkgJson = true;
|
|
61
|
+
}
|
|
62
|
+
logger.info({
|
|
63
|
+
message: `Installing pnpm CLI globally from ${execDir}`,
|
|
64
|
+
prefix: process.cwd(),
|
|
65
|
+
});
|
|
66
|
+
try {
|
|
67
|
+
const { status, error } = spawnSync(execPath, ['add', '-g', `file:${execDir}`], {
|
|
68
|
+
stdio: 'inherit',
|
|
69
|
+
env: {
|
|
70
|
+
...process.env,
|
|
71
|
+
PNPM_HOME: pnpmHomeDir,
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
if (error)
|
|
75
|
+
throw error;
|
|
76
|
+
if (status !== 0) {
|
|
77
|
+
throw new Error(`Failed to install pnpm globally (exit code ${status})`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
finally {
|
|
81
|
+
if (createdPkgJson) {
|
|
82
|
+
fs.unlinkSync(pkgJsonPath);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function createPnpxScripts(targetDir) {
|
|
87
|
+
// Why script files instead of aliases?
|
|
88
|
+
// 1. Aliases wouldn't work on all platform, such as Windows Command Prompt or POSIX `sh`.
|
|
89
|
+
// 2. Aliases wouldn't work on all environments, such as non-interactive shells and CI environments.
|
|
90
|
+
// 3. Aliases must be set for different shells while script files are limited to only 2 types: POSIX and Windows.
|
|
91
|
+
// 4. Aliases cannot be located with the `which` or `where` command.
|
|
92
|
+
// 5. Editing rc files is more error-prone than just write new files to the filesystem.
|
|
93
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
94
|
+
// windows can also use shell script via mingw or cygwin so no filter
|
|
95
|
+
const shellScript = [
|
|
96
|
+
'#!/bin/sh',
|
|
97
|
+
'exec pnpm dlx "$@"',
|
|
98
|
+
].join('\n');
|
|
99
|
+
fs.writeFileSync(path.join(targetDir, 'pnpx'), shellScript, { mode: 0o755 });
|
|
100
|
+
if (process.platform === 'win32') {
|
|
101
|
+
const batchScript = [
|
|
102
|
+
'@echo off',
|
|
103
|
+
'pnpm dlx %*',
|
|
104
|
+
].join('\n');
|
|
105
|
+
fs.writeFileSync(path.join(targetDir, 'pnpx.cmd'), batchScript);
|
|
106
|
+
const powershellScript = 'pnpm dlx @args';
|
|
107
|
+
fs.writeFileSync(path.join(targetDir, 'pnpx.ps1'), powershellScript);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
export async function handler(opts) {
|
|
111
|
+
const execPath = getExecPath();
|
|
112
|
+
if (execPath.match(/\.[cm]?js$/) == null) {
|
|
113
|
+
installCliGlobally(execPath, opts.pnpmHomeDir);
|
|
114
|
+
createPnpxScripts(opts.pnpmHomeDir);
|
|
115
|
+
}
|
|
116
|
+
try {
|
|
117
|
+
const report = await addDirToEnvPath(opts.pnpmHomeDir, {
|
|
118
|
+
configSectionName: 'pnpm',
|
|
119
|
+
proxyVarName: 'PNPM_HOME',
|
|
120
|
+
overwrite: opts.force,
|
|
121
|
+
position: 'start',
|
|
122
|
+
});
|
|
123
|
+
return renderSetupOutput(report);
|
|
124
|
+
}
|
|
125
|
+
catch (err) { // eslint-disable-line
|
|
126
|
+
switch (err.code) {
|
|
127
|
+
case 'ERR_PNPM_BAD_ENV_FOUND':
|
|
128
|
+
err.hint = 'If you want to override the existing env variable, use the --force option';
|
|
129
|
+
break;
|
|
130
|
+
case 'ERR_PNPM_BAD_SHELL_SECTION':
|
|
131
|
+
err.hint = 'If you want to override the existing configuration section, use the --force option';
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
throw err;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
function renderSetupOutput(report) {
|
|
138
|
+
if (report.oldSettings === report.newSettings) {
|
|
139
|
+
return 'No changes to the environment were made. Everything is already up to date.';
|
|
140
|
+
}
|
|
141
|
+
const output = [];
|
|
142
|
+
if (report.configFile) {
|
|
143
|
+
output.push(reportConfigChange(report.configFile));
|
|
144
|
+
}
|
|
145
|
+
output.push(`Next configuration changes were made:
|
|
146
|
+
${report.newSettings}`);
|
|
147
|
+
if (report.configFile == null) {
|
|
148
|
+
output.push('Setup complete. Open a new terminal to start using pnpm.');
|
|
149
|
+
}
|
|
150
|
+
else if (report.configFile.changeType !== 'skipped') {
|
|
151
|
+
output.push(`To start using pnpm, run:
|
|
152
|
+
source ${report.configFile.path}
|
|
153
|
+
`);
|
|
154
|
+
}
|
|
155
|
+
return output.join('\n\n');
|
|
156
|
+
}
|
|
157
|
+
function reportConfigChange(configReport) {
|
|
158
|
+
switch (configReport.changeType) {
|
|
159
|
+
case 'created': return `Created ${configReport.path}`;
|
|
160
|
+
case 'appended': return `Appended new lines to ${configReport.path}`;
|
|
161
|
+
case 'modified': return `Replaced configuration in ${configReport.path}`;
|
|
162
|
+
case 'skipped': return `Configuration already up to date in ${configReport.path}`;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
//# sourceMappingURL=setup.js.map
|
package/package.json
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pnpm/engine.pm.commands",
|
|
3
|
+
"version": "1000.0.0",
|
|
4
|
+
"description": "pnpm commands for self-updating and setting up pnpm",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"pnpm",
|
|
7
|
+
"pnpm11"
|
|
8
|
+
],
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"funding": "https://opencollective.com/pnpm",
|
|
11
|
+
"repository": "https://github.com/pnpm/pnpm/tree/main/engine/pm/commands",
|
|
12
|
+
"homepage": "https://github.com/pnpm/pnpm/tree/main/engine/pm/commands#readme",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/pnpm/pnpm/issues"
|
|
15
|
+
},
|
|
16
|
+
"type": "module",
|
|
17
|
+
"main": "lib/index.js",
|
|
18
|
+
"types": "lib/index.d.ts",
|
|
19
|
+
"exports": {
|
|
20
|
+
".": "./lib/index.js"
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"lib",
|
|
24
|
+
"!*.map"
|
|
25
|
+
],
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@pnpm/os.env.path-extender": "^2.0.3",
|
|
28
|
+
"ramda": "npm:@pnpm/ramda@0.28.1",
|
|
29
|
+
"render-help": "^2.0.0",
|
|
30
|
+
"semver": "^7.7.2",
|
|
31
|
+
"symlink-dir": "^7.0.0",
|
|
32
|
+
"@pnpm/bins.linker": "1000.2.6",
|
|
33
|
+
"@pnpm/cli.meta": "1000.0.11",
|
|
34
|
+
"@pnpm/deps.graph-hasher": "1002.0.8",
|
|
35
|
+
"@pnpm/cli.utils": "1001.2.8",
|
|
36
|
+
"@pnpm/config.reader": "1004.4.2",
|
|
37
|
+
"@pnpm/error": "1000.0.5",
|
|
38
|
+
"@pnpm/global.packages": "1000.0.0-0",
|
|
39
|
+
"@pnpm/installing.client": "1001.1.4",
|
|
40
|
+
"@pnpm/global.commands": "1000.0.0-0",
|
|
41
|
+
"@pnpm/installing.deps-restorer": "1006.0.0",
|
|
42
|
+
"@pnpm/lockfile.types": "1002.0.2",
|
|
43
|
+
"@pnpm/resolving.npm-resolver": "1004.4.1",
|
|
44
|
+
"@pnpm/installing.env-installer": "1000.0.19",
|
|
45
|
+
"@pnpm/store.connection-manager": "1002.2.4",
|
|
46
|
+
"@pnpm/store.controller": "1004.0.0",
|
|
47
|
+
"@pnpm/types": "1000.9.0",
|
|
48
|
+
"@pnpm/workspace.project-manifest-reader": "1001.1.4"
|
|
49
|
+
},
|
|
50
|
+
"peerDependencies": {
|
|
51
|
+
"@pnpm/logger": ">=1001.0.0 <1002.0.0"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"@jest/globals": "30.0.5",
|
|
55
|
+
"@types/cross-spawn": "^6.0.6",
|
|
56
|
+
"@types/ramda": "0.29.12",
|
|
57
|
+
"@types/semver": "7.7.1",
|
|
58
|
+
"cross-spawn": "^7.0.6",
|
|
59
|
+
"nock": "13.3.4",
|
|
60
|
+
"@pnpm/engine.pm.commands": "1000.0.0",
|
|
61
|
+
"@pnpm/error": "1000.0.5",
|
|
62
|
+
"@pnpm/logger": "1001.0.1",
|
|
63
|
+
"@pnpm/prepare": "1000.0.4",
|
|
64
|
+
"@pnpm/shell.path": "1000.0.0"
|
|
65
|
+
},
|
|
66
|
+
"engines": {
|
|
67
|
+
"node": ">=22.13"
|
|
68
|
+
},
|
|
69
|
+
"jest": {
|
|
70
|
+
"preset": "@pnpm/jest-config"
|
|
71
|
+
},
|
|
72
|
+
"scripts": {
|
|
73
|
+
"lint": "eslint \"src/**/*.ts\" \"test/**/*.ts\"",
|
|
74
|
+
"test": "pnpm run compile && pnpm run _test",
|
|
75
|
+
"compile": "tsgo --build && pnpm run lint --fix",
|
|
76
|
+
"_test": "cross-env NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules --disable-warning=ExperimentalWarning --disable-warning=DEP0169\" jest"
|
|
77
|
+
}
|
|
78
|
+
}
|