diff --git a/config/scripts/build-workspace-packages.mjs b/config/scripts/build-workspace-packages.mjs index d4984a459c0..ba32e2e4c1a 100644 --- a/config/scripts/build-workspace-packages.mjs +++ b/config/scripts/build-workspace-packages.mjs @@ -11,6 +11,7 @@ import { } from 'node:fs' import { createRequire } from 'node:module' import { dirname, join, relative, resolve } from 'node:path' +import { resolvePnpmCliInvocation } from './pnpm-cli-invocation.mjs' const root = resolve(import.meta.dirname, '../..') const PACKAGES = [{ name: '@orca/process-host', dir: 'src/packages/process-host' }] @@ -90,14 +91,11 @@ function writeStamp(packageDir, inputs) { function runPackageBuild(name) { // Why not @orca/process-host: this builds it, so its dist may not exist yet. - const pnpmEntry = process.env.npm_execpath - const [program, args] = /\.[cm]?js$/.test(pnpmEntry ?? '') - ? [process.execPath, [pnpmEntry, '--filter', name, 'build']] - : ['pnpm', ['--filter', name, 'build']] - const result = spawnSync(program, args, { + const { command, prefixArgs, shell } = resolvePnpmCliInvocation() + const result = spawnSync(command, [...prefixArgs, '--filter', name, 'build'], { cwd: root, stdio: 'inherit', - shell: program === 'pnpm' && process.platform === 'win32' + shell }) if (result.error) { throw result.error diff --git a/config/scripts/pnpm-cli-invocation.mjs b/config/scripts/pnpm-cli-invocation.mjs index bac0becb6a0..856d5eb07b6 100644 --- a/config/scripts/pnpm-cli-invocation.mjs +++ b/config/scripts/pnpm-cli-invocation.mjs @@ -4,6 +4,8 @@ import { statSync } from 'node:fs' import { win32 } from 'node:path' const JS_CLI_EXTENSION = /\.[cm]?js$/i +// Why: `npx` and `npm run` export npm's own CLI as npm_execpath, which would run npm, not pnpm. +const NPM_OWN_CLI_PATH = /(?:^|[/\\])np[mx]-cli\.js$/i function isFile(path) { try { @@ -20,7 +22,11 @@ export function resolvePnpmCliInvocation({ pnpmHome = process.env.PNPM_HOME, fileExists = isFile } = {}) { - if (typeof npmExecPath === 'string' && npmExecPath.length > 0) { + if ( + typeof npmExecPath === 'string' && + npmExecPath.length > 0 && + !NPM_OWN_CLI_PATH.test(npmExecPath) + ) { if (JS_CLI_EXTENSION.test(npmExecPath)) { return { command: nodeExecPath, prefixArgs: [npmExecPath], shell: false } } diff --git a/config/scripts/pnpm-cli-invocation.test.mjs b/config/scripts/pnpm-cli-invocation.test.mjs index f02a98e8c0b..333075f01e2 100644 --- a/config/scripts/pnpm-cli-invocation.test.mjs +++ b/config/scripts/pnpm-cli-invocation.test.mjs @@ -37,6 +37,20 @@ describe('resolvePnpmCliInvocation', () => { }) }) + it.each([ + ['/usr/share/nodejs/npm/bin/npx-cli.js', 'linux', 'pnpm', false], + ['/usr/local/lib/node_modules/npm/bin/npm-cli.js', 'darwin', 'pnpm', false], + ['/Users/dev/.volta/tools/image/npm/10.8.0/bin/npm-cli.js', 'darwin', 'pnpm', false], + ['C:\\Program Files\\nodejs\\node_modules\\npm\\bin\\npx-cli.js', 'win32', 'pnpm.cmd', true] + ])( + 'ignores the npm CLI %s that `npx` or `npm run` exports', + (npmExecPath, platform, command, shell) => { + expect( + resolvePnpmCliInvocation({ npmExecPath, nodeExecPath, platform, pnpmHome: '' }) + ).toEqual({ command, prefixArgs: [], shell }) + } + ) + it('does not wrap a Windows native pnpm.exe in node', () => { expect( resolvePnpmCliInvocation({ diff --git a/config/scripts/run-terminal-layout-parity.mjs b/config/scripts/run-terminal-layout-parity.mjs index 7d8a48cd8f3..320e6ba66fc 100644 --- a/config/scripts/run-terminal-layout-parity.mjs +++ b/config/scripts/run-terminal-layout-parity.mjs @@ -13,11 +13,12 @@ import { writeFileSync } from 'node:fs' import { tmpdir } from 'node:os' -import { basename, join, resolve } from 'node:path' +import { join, resolve } from 'node:path' import { pathToFileURL } from 'node:url' import { parseArgs } from 'node:util' import { runProcessSync } from '@orca/process-host' import { describeProcessFailure } from './process-failure-message.mjs' +import { resolvePnpmCliInvocation } from './pnpm-cli-invocation.mjs' const repoRoot = resolve(import.meta.dirname, '../..') const BUILD_STAMP = join('out', '.terminal-layout-parity-build') @@ -88,16 +89,9 @@ function git(args) { return result.stdout.trim() } -// npm_execpath is pnpm's JS entry (run under node), a native pnpm binary (corepack), or unset. function pnpm(args, cwd, env = process.env) { - const entry = process.env.npm_execpath - if (entry && /\.[cm]?js$/i.test(entry)) { - run(process.execPath, [entry, ...args], cwd, env) - } else if (entry && /pnpm/i.test(basename(entry)) && existsSync(entry)) { - run(entry, args, cwd, env) - } else { - run('pnpm', args, cwd, env) - } + const { command, prefixArgs } = resolvePnpmCliInvocation() + run(command, [...prefixArgs, ...args], cwd, env) } /** A detached worktree per commit, installed and built once; a failed build throws before stamping. */