@pnpm/engine.pm.commands 1101.1.25 → 1101.2.1

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/lib/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { selfUpdate } from './self-updater/index.js';
2
- export { exePlatformPkgDirName, exePlatformPkgDirNameNext, installPnpm, installPnpmToStore, linkExePlatformBinary } from './self-updater/installPnpm.js';
2
+ export { exePlatformPkgDirName, exePlatformPkgDirNameNext, installPnpm, installPnpmToStore, linkExePlatformBinary, pnpmPackageNameToInstall } from './self-updater/installPnpm.js';
3
3
  export { verifyPnpmEngineIdentity, type VerifyPnpmEngineIdentityOptions } from './self-updater/verifyPnpmEngineIdentity.js';
4
4
  export { setup } from './setup/index.js';
5
5
  export { withCmd } from './with/index.js';
package/lib/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  export { selfUpdate } from './self-updater/index.js';
2
- export { exePlatformPkgDirName, exePlatformPkgDirNameNext, installPnpm, installPnpmToStore, linkExePlatformBinary } from './self-updater/installPnpm.js';
2
+ export { exePlatformPkgDirName, exePlatformPkgDirNameNext, installPnpm, installPnpmToStore, linkExePlatformBinary, pnpmPackageNameToInstall } from './self-updater/installPnpm.js';
3
3
  export { verifyPnpmEngineIdentity } from './self-updater/verifyPnpmEngineIdentity.js';
4
4
  export { setup } from './setup/index.js';
5
5
  export { withCmd } from './with/index.js';
@@ -3,6 +3,13 @@ import type { EnvLockfile } from '@pnpm/lockfile.types';
3
3
  import { type StoreController } from '@pnpm/store.controller';
4
4
  import type { Registries } from '@pnpm/types';
5
5
  import { type VerifyPnpmEngineIdentityOptions } from './verifyPnpmEngineIdentity.js';
6
+ /**
7
+ * Package name to install for a switch to `pnpmVersion`. From v12 the unscoped
8
+ * `pnpm` is itself the native exe (equal content to `@pnpm/exe`), so v12+ always
9
+ * converges on `pnpm`, even from a SEA `@pnpm/exe` build. Earlier majors keep
10
+ * `pnpm` (JS) and `@pnpm/exe` (SEA) distinct, preserving the running identity.
11
+ */
12
+ export declare function pnpmPackageNameToInstall(pnpmVersion: string): string;
6
13
  export interface InstallPnpmResult {
7
14
  binDir: string;
8
15
  baseDir: string;
@@ -52,10 +59,14 @@ export declare function installPnpmToStore(pnpmVersion: string, opts: {
52
59
  */
53
60
  export declare function exePlatformPkgDirName(platform: NodeJS.Platform, arch: string, libcFamily: string | null): string;
54
61
  /**
55
- * Future scope-local directory name of the `@pnpm/exe` platform package, under
56
- * the `exe.<platform>-<arch>[-musl]` scheme that matches the workspace
57
- * directory layout. `linkExePlatformBinary` checks this as a fallback so a
58
- * future rename of the published packages works without touching this logic.
62
+ * Scope-local directory name of the platform package under the
63
+ * `exe.<platform>-<arch>[-musl]` scheme, i.e. the published package
64
+ * `@pnpm/exe.<platform>-<arch>[-musl]`. pnpm v12 (the Rust port) ships its
65
+ * native binaries under exactly this convention, so `linkExePlatformBinary`
66
+ * relinks a v12 install with no v12-specific logic. `@pnpm/exe` (the
67
+ * TypeScript SEA build) is expected to adopt the same scheme in a future
68
+ * release, which is why the legacy `@pnpm/<os>-<arch>` name is still checked
69
+ * first as a fallback.
59
70
  */
60
71
  export declare function exePlatformPkgDirNameNext(platform: NodeJS.Platform, arch: string, libcFamily: string | null): string;
61
- export declare function linkExePlatformBinary(installDir: string): void;
72
+ export declare function linkExePlatformBinary(installDir: string, wrapperPkgName?: string): void;
@@ -10,21 +10,35 @@ import { cleanOrphanedInstallDirs, createGlobalCacheKey, createInstallDir, findG
10
10
  import { headlessInstall } from '@pnpm/installing.deps-restorer';
11
11
  import { registerProject } from '@pnpm/store.controller';
12
12
  import { familySync } from 'detect-libc';
13
+ import semver from 'semver';
13
14
  import { symlinkDir } from 'symlink-dir';
14
15
  import { verifyPnpmEngineIdentity } from './verifyPnpmEngineIdentity.js';
15
- // @pnpm/exe has platform-specific binaries, so its GVS hash must
16
- // include ENGINE_NAME for correct per-platform resolution.
17
- const PNPM_ALLOW_BUILDS = { '@pnpm/exe': true };
16
+ // Both pnpm wrappers (`@pnpm/exe`, unscoped `pnpm`) carry platform-specific
17
+ // binaries; marking them buildable puts ENGINE_NAME in the GVS hash so each
18
+ // platform resolves to its own entry instead of colliding.
19
+ const PNPM_ALLOW_BUILDS = { '@pnpm/exe': true, 'pnpm': true };
20
+ /**
21
+ * Package name to install for a switch to `pnpmVersion`. From v12 the unscoped
22
+ * `pnpm` is itself the native exe (equal content to `@pnpm/exe`), so v12+ always
23
+ * converges on `pnpm`, even from a SEA `@pnpm/exe` build. Earlier majors keep
24
+ * `pnpm` (JS) and `@pnpm/exe` (SEA) distinct, preserving the running identity.
25
+ */
26
+ export function pnpmPackageNameToInstall(pnpmVersion) {
27
+ const parsed = semver.parse(pnpmVersion, { loose: true });
28
+ if (parsed != null && parsed.major >= 12)
29
+ return 'pnpm';
30
+ return getCurrentPackageName();
31
+ }
18
32
  /**
19
33
  * Installs pnpm to the global packages directory (for self-update).
20
34
  * Creates an entry in globalPkgDir that is visible to `pnpm ls -g`.
21
35
  */
22
36
  export async function installPnpm(pnpmVersion, opts) {
23
- const currentPkgName = getCurrentPackageName();
37
+ const pkgName = pnpmPackageNameToInstall(pnpmVersion);
24
38
  const wantedLockfile = opts.envLockfile
25
- ? buildLockfileFromEnvLockfile(opts.envLockfile, currentPkgName, pnpmVersion)
39
+ ? buildLockfileFromEnvLockfile(opts.envLockfile, pkgName, pnpmVersion)
26
40
  : undefined;
27
- const result = await installPnpmToGlobalDir(opts, currentPkgName, pnpmVersion, wantedLockfile);
41
+ const result = await installPnpmToGlobalDir(opts, pkgName, pnpmVersion, wantedLockfile);
28
42
  return {
29
43
  alreadyExisted: result.alreadyExisted,
30
44
  baseDir: result.installDir,
@@ -37,12 +51,12 @@ export async function installPnpm(pnpmVersion, opts) {
37
51
  * Returns the bin directory where the pnpm binary can be found.
38
52
  */
39
53
  export async function installPnpmToStore(pnpmVersion, opts) {
40
- const currentPkgName = getCurrentPackageName();
41
- const wantedLockfile = buildLockfileFromEnvLockfile(opts.envLockfile, currentPkgName, pnpmVersion);
54
+ const pkgName = pnpmPackageNameToInstall(pnpmVersion);
55
+ const wantedLockfile = buildLockfileFromEnvLockfile(opts.envLockfile, pkgName, pnpmVersion);
42
56
  const globalVirtualStoreDir = path.join(opts.storeDir, 'links');
43
57
  // Compute the GVS hash for the pnpm package to find its path
44
- const pnpmGvsPath = findPnpmGvsPath(wantedLockfile, currentPkgName, globalVirtualStoreDir, PNPM_ALLOW_BUILDS);
45
- const pnpmPkgDir = path.join(pnpmGvsPath, 'node_modules', currentPkgName);
58
+ const pnpmGvsPath = findPnpmGvsPath(wantedLockfile, pkgName, globalVirtualStoreDir, PNPM_ALLOW_BUILDS);
59
+ const pnpmPkgDir = path.join(pnpmGvsPath, 'node_modules', pkgName);
46
60
  const binDir = path.join(pnpmGvsPath, 'bin');
47
61
  // Check if already installed in the GVS
48
62
  if (fs.existsSync(path.join(pnpmPkgDir, 'package.json'))) {
@@ -69,7 +83,7 @@ export async function installPnpmToStore(pnpmVersion, opts) {
69
83
  packageManager: opts.packageManager,
70
84
  });
71
85
  // Now the GVS should be populated — create bins alongside the GVS entry
72
- linkExePlatformBinary(pnpmGvsPath);
86
+ linkExePlatformBinary(pnpmGvsPath, pkgName);
73
87
  await linkBins(path.join(pnpmGvsPath, 'node_modules'), binDir, { warn: noop });
74
88
  return { binDir };
75
89
  }
@@ -143,7 +157,7 @@ async function installPnpmToGlobalDir(opts, pkgName, version, wantedLockfile) {
143
157
  else {
144
158
  await installFromResolution(installDir, opts, [`${pkgName}@${version}`]);
145
159
  }
146
- linkExePlatformBinary(installDir);
160
+ linkExePlatformBinary(installDir, pkgName);
147
161
  await linkBins(path.join(installDir, 'node_modules'), binDir, { warn: noop });
148
162
  // Create hash symlink for the global packages system
149
163
  const pkgJson = JSON.parse(fs.readFileSync(path.join(installDir, 'package.json'), 'utf8'));
@@ -258,42 +272,46 @@ function legacyOsSegment(platform, libcFamily) {
258
272
  }
259
273
  }
260
274
  /**
261
- * Future scope-local directory name of the `@pnpm/exe` platform package, under
262
- * the `exe.<platform>-<arch>[-musl]` scheme that matches the workspace
263
- * directory layout. `linkExePlatformBinary` checks this as a fallback so a
264
- * future rename of the published packages works without touching this logic.
275
+ * Scope-local directory name of the platform package under the
276
+ * `exe.<platform>-<arch>[-musl]` scheme, i.e. the published package
277
+ * `@pnpm/exe.<platform>-<arch>[-musl]`. pnpm v12 (the Rust port) ships its
278
+ * native binaries under exactly this convention, so `linkExePlatformBinary`
279
+ * relinks a v12 install with no v12-specific logic. `@pnpm/exe` (the
280
+ * TypeScript SEA build) is expected to adopt the same scheme in a future
281
+ * release, which is why the legacy `@pnpm/<os>-<arch>` name is still checked
282
+ * first as a fallback.
265
283
  */
266
284
  export function exePlatformPkgDirNameNext(platform, arch, libcFamily) {
267
285
  const normalizedArch = platform === 'win32' && arch === 'ia32' ? 'x86' : arch;
268
286
  const libcSuffix = platform === 'linux' && libcFamily === 'musl' ? '-musl' : '';
269
287
  return `exe.${platform}-${normalizedArch}${libcSuffix}`;
270
288
  }
271
- // @pnpm/exe bundles Node.js via optional platform-specific packages
272
- // (e.g. @pnpm/macos-arm64, @pnpm/linuxstatic-x64; or, after a future rename,
273
- // @pnpm/exe.darwin-arm64, @pnpm/exe.linux-x64-musl). Its postinstall script
274
- // links the correct binary into the @pnpm/exe package dir. Since scripts are
275
- // disabled during install (to support systems without Node.js), we replicate
276
- // that linking here, checking both naming schemes so self-update works across
277
- // the rename.
278
- export function linkExePlatformBinary(installDir) {
279
- const exePkgDir = path.join(installDir, 'node_modules', '@pnpm', 'exe');
280
- if (!fs.existsSync(exePkgDir))
289
+ // The wrapper's preinstall links the platform binary into the wrapper dir, but
290
+ // scripts are disabled during pnpm's own installs, so replicate it here — trying
291
+ // the legacy and the newer `exe.<target>` platform-package names.
292
+ export function linkExePlatformBinary(installDir, wrapperPkgName = '@pnpm/exe') {
293
+ const wrapperDir = path.join(installDir, 'node_modules', ...wrapperPkgName.split('/'));
294
+ if (!fs.existsSync(wrapperDir))
281
295
  return;
282
- // In pnpm's symlinked node_modules layout, the platform package is not hoisted
283
- // to the top-level node_modules. It's a dependency of @pnpm/exe and lives as a
284
- // sibling in the virtual store. Resolve through the @pnpm/exe symlink to find it.
285
- const exeRealDir = fs.realpathSync(exePkgDir);
286
296
  const platform = process.platform;
287
297
  const arch = process.arch;
288
298
  const libcFamily = familySync();
289
299
  const executable = platform === 'win32' ? 'pnpm.exe' : 'pnpm';
300
+ // Resolve the platform binary by its explicit adjacent path in the real
301
+ // virtual store, not via Node resolution: a `node_modules` walk could be
302
+ // shadowed by a higher-precedence `@pnpm/<dirName>` in a repo-controlled
303
+ // `store-dir`. `@pnpm/exe`'s parent is already `@pnpm`; `pnpm` descends into it.
304
+ const wrapperRealDir = fs.realpathSync(wrapperDir);
305
+ const scopeDir = wrapperPkgName.startsWith('@')
306
+ ? path.dirname(wrapperRealDir)
307
+ : path.join(path.dirname(wrapperRealDir), '@pnpm');
290
308
  const candidateDirNames = [
291
309
  exePlatformPkgDirName(platform, arch, libcFamily),
292
310
  exePlatformPkgDirNameNext(platform, arch, libcFamily),
293
311
  ];
294
312
  let src;
295
313
  for (const dirName of candidateDirNames) {
296
- const candidate = path.join(path.dirname(exeRealDir), dirName, executable);
314
+ const candidate = path.join(scopeDir, dirName, executable);
297
315
  if (fs.existsSync(candidate)) {
298
316
  src = candidate;
299
317
  break;
@@ -301,27 +319,40 @@ export function linkExePlatformBinary(installDir) {
301
319
  }
302
320
  if (src == null)
303
321
  return;
304
- const dest = path.join(exePkgDir, executable);
322
+ const dest = path.join(wrapperDir, executable);
305
323
  forceLink(src, dest);
306
324
  if (platform === 'win32') {
307
- // Aliases (pn / pnpx / pnx) need to be .exe hardlinks of the SEA binary,
325
+ // Aliases (pn / pnpx / pnx) need to be .exe hardlinks of the native binary,
308
326
  // not the .cmd wrappers we ship in the tarball. cmd-shim's Bash shim for
309
327
  // a .cmd target wraps it in `exec cmd /C ...`, and MSYS2 / Git Bash
310
328
  // mangles `/C` into a Windows path — cmd.exe then falls into interactive
311
329
  // mode and prints its banner instead of running the alias. .exe sources
312
- // sidestep cmd-shim's wrapper. The SEA binary detects which name it was
330
+ // sidestep cmd-shim's wrapper. The native binary detects which name it was
313
331
  // launched as via process.execPath and prepends `dlx` for pnpx / pnx.
314
332
  // See https://github.com/pnpm/pnpm/issues/11486.
315
333
  for (const alias of ['pn', 'pnpx', 'pnx']) {
316
- forceLink(src, path.join(exePkgDir, `${alias}.exe`));
334
+ forceLink(src, path.join(wrapperDir, `${alias}.exe`));
335
+ }
336
+ const wrapperPkgJsonPath = path.join(wrapperDir, 'package.json');
337
+ const wrapperPkg = JSON.parse(fs.readFileSync(wrapperPkgJsonPath, 'utf8'));
338
+ wrapperPkg.bin.pnpm = 'pnpm.exe';
339
+ wrapperPkg.bin.pn = 'pn.exe';
340
+ wrapperPkg.bin.pnpx = 'pnpx.exe';
341
+ wrapperPkg.bin.pnx = 'pnx.exe';
342
+ // Temp file + rename, not in-place: package.json is hard-linked from the
343
+ // content-addressable store, so writing in place would mutate the shared blob.
344
+ const tempPkgJsonPath = `${wrapperPkgJsonPath}.pnpm-tmp`;
345
+ try {
346
+ fs.writeFileSync(tempPkgJsonPath, JSON.stringify(wrapperPkg, null, 2));
347
+ fs.renameSync(tempPkgJsonPath, wrapperPkgJsonPath);
348
+ }
349
+ catch (err) {
350
+ try {
351
+ fs.rmSync(tempPkgJsonPath, { force: true });
352
+ }
353
+ catch { }
354
+ throw err;
317
355
  }
318
- const exePkgJsonPath = path.join(exePkgDir, 'package.json');
319
- const exePkg = JSON.parse(fs.readFileSync(exePkgJsonPath, 'utf8'));
320
- exePkg.bin.pnpm = 'pnpm.exe';
321
- exePkg.bin.pn = 'pn.exe';
322
- exePkg.bin.pnpx = 'pnpx.exe';
323
- exePkg.bin.pnx = 'pnx.exe';
324
- fs.writeFileSync(exePkgJsonPath, JSON.stringify(exePkg, null, 2));
325
356
  }
326
357
  }
327
358
  function forceLink(src, dest) {
@@ -4,7 +4,7 @@ import { linkBins } from '@pnpm/bins.linker';
4
4
  import { isExecutedByCorepack, packageManager } from '@pnpm/cli.meta';
5
5
  import { docsUrl } from '@pnpm/cli.utils';
6
6
  import { parsePackageManager, shouldPersistLockfile, types as allTypes } from '@pnpm/config.reader';
7
- import { getPublishedByPolicy } from '@pnpm/config.version-policy';
7
+ import { createPackageVersionPolicyOrThrow, getPublishedByPolicy } from '@pnpm/config.version-policy';
8
8
  import { PnpmError } from '@pnpm/error';
9
9
  import { createResolver, makeResolutionStrict } from '@pnpm/installing.client';
10
10
  import { resolvePackageManagerIntegrities } from '@pnpm/installing.env-installer';
@@ -52,9 +52,20 @@ export async function handler(opts, params) {
52
52
  throw new PnpmError('CANT_SELF_UPDATE_IN_COREPACK', 'You should update pnpm with corepack');
53
53
  }
54
54
  globalInfo('Checking for updates...');
55
+ // Resolve the engine version exactly as a regular install would. The
56
+ // no-downgrade trust check reads per-version trust evidence (`_npmUser` /
57
+ // `dist.attestations`) that abbreviated metadata never carries — so it
58
+ // always needs full metadata, regardless of `registrySupportsTimeField`
59
+ // (which only concerns the `time` field). Time-based resolution needs
60
+ // only `time`. `minimumReleaseAge` is handled by the resolver's on-demand
61
+ // abbreviated→full upgrade, so it isn't requested up front here.
62
+ const fullMetadata = (opts.trustPolicy === 'no-downgrade' ||
63
+ (opts.resolutionMode === 'time-based' && !opts.registrySupportsTimeField));
55
64
  const { resolve: baseResolve } = createResolver({
56
65
  ...opts,
57
66
  configByUri: opts.configByUri,
67
+ fullMetadata,
68
+ filterMetadata: fullMetadata,
58
69
  ignoreMissingTimeField: opts.minimumReleaseAgeIgnoreMissingTime,
59
70
  });
60
71
  // self-update has nowhere to "defer to" either — wrap the resolver
@@ -81,6 +92,15 @@ export async function handler(opts, params) {
81
92
  projectDir: opts.dir,
82
93
  publishedBy,
83
94
  publishedByExclude,
95
+ // Unlike `dlx` (whose real install re-resolves through the store
96
+ // controller), this `resolve` is self-update's only version selection,
97
+ // so the trust policy has to be passed here for the no-downgrade check
98
+ // to run.
99
+ trustPolicy: opts.trustPolicy,
100
+ trustPolicyExclude: opts.trustPolicyExclude
101
+ ? createPackageVersionPolicyOrThrow(opts.trustPolicyExclude, 'trustPolicyExclude')
102
+ : undefined,
103
+ trustPolicyIgnoreAfter: opts.trustPolicyIgnoreAfter,
84
104
  });
85
105
  if (!resolution?.manifest) {
86
106
  throw new PnpmError('CANNOT_RESOLVE_PNPM', `Cannot find "${bareSpecifier}" version of pnpm`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pnpm/engine.pm.commands",
3
- "version": "1101.1.25",
3
+ "version": "1101.2.1",
4
4
  "description": "pnpm commands for self-updating and setting up pnpm",
5
5
  "keywords": [
6
6
  "pnpm",
@@ -36,29 +36,29 @@
36
36
  "semver": "^7.8.4",
37
37
  "symlink-dir": "^10.0.1",
38
38
  "@pnpm/bins.linker": "1100.0.16",
39
- "@pnpm/config.reader": "1101.10.1",
39
+ "@pnpm/building.policy": "1100.0.12",
40
+ "@pnpm/cli.meta": "1100.0.8",
40
41
  "@pnpm/cli.utils": "1101.0.13",
41
42
  "@pnpm/config.pick-registry-for-package": "1100.0.9",
42
- "@pnpm/deps.graph-hasher": "1100.2.6",
43
- "@pnpm/cli.meta": "1100.0.8",
43
+ "@pnpm/config.reader": "1101.11.1",
44
+ "@pnpm/config.version-policy": "1100.1.6",
45
+ "@pnpm/deps.graph-hasher": "1100.2.8",
44
46
  "@pnpm/deps.security.signatures": "1101.2.3",
47
+ "@pnpm/global.packages": "1100.0.10",
48
+ "@pnpm/global.commands": "1100.0.32",
45
49
  "@pnpm/error": "1100.0.1",
46
- "@pnpm/global.packages": "1100.0.9",
47
- "@pnpm/installing.client": "1100.2.10",
48
- "@pnpm/global.commands": "1100.0.30",
49
- "@pnpm/lockfile.fs": "1100.1.7",
50
- "@pnpm/installing.deps-restorer": "1102.1.1",
51
- "@pnpm/installing.env-installer": "1102.0.2",
52
- "@pnpm/lockfile.types": "1100.0.12",
50
+ "@pnpm/installing.env-installer": "1102.0.4",
51
+ "@pnpm/lockfile.fs": "1100.1.9",
52
+ "@pnpm/installing.deps-restorer": "1102.1.3",
53
+ "@pnpm/lockfile.types": "1100.0.13",
54
+ "@pnpm/resolving.npm-resolver": "1102.1.2",
53
55
  "@pnpm/shell.path": "1100.0.1",
54
- "@pnpm/building.policy": "1100.0.11",
55
56
  "@pnpm/network.auth-header": "1101.1.3",
56
- "@pnpm/store.controller": "1102.0.2",
57
- "@pnpm/config.version-policy": "1100.1.6",
58
- "@pnpm/resolving.npm-resolver": "1102.1.0",
59
- "@pnpm/store.connection-manager": "1100.3.2",
57
+ "@pnpm/store.connection-manager": "1100.3.4",
60
58
  "@pnpm/types": "1101.3.2",
61
- "@pnpm/workspace.project-manifest-reader": "1100.0.14"
59
+ "@pnpm/workspace.project-manifest-reader": "1100.0.14",
60
+ "@pnpm/store.controller": "1102.0.3",
61
+ "@pnpm/installing.client": "1100.2.12"
62
62
  },
63
63
  "peerDependencies": {
64
64
  "@pnpm/logger": "^1100.0.0"
@@ -69,11 +69,11 @@
69
69
  "@types/ramda": "0.31.1",
70
70
  "@types/semver": "7.7.1",
71
71
  "@pnpm/constants": "1100.0.0",
72
- "@pnpm/engine.pm.commands": "1101.1.25",
73
72
  "@pnpm/error": "1100.0.1",
74
- "@pnpm/prepare": "1100.0.17",
75
- "@pnpm/testing.mock-agent": "1101.0.4",
76
- "@pnpm/logger": "1100.0.0"
73
+ "@pnpm/logger": "1100.0.0",
74
+ "@pnpm/engine.pm.commands": "1101.2.1",
75
+ "@pnpm/prepare": "1100.0.18",
76
+ "@pnpm/testing.mock-agent": "1101.0.4"
77
77
  },
78
78
  "engines": {
79
79
  "node": ">=22.13"