From 9cbba288fc49a428615db5a5d3ad8a5ef973cc71 Mon Sep 17 00:00:00 2001 From: Zoltan Kochan Date: Mon, 12 Jan 2026 15:41:17 +0100 Subject: [PATCH] fix(exec): preserve user execution cwd (#10445) close #5759 close #10403 --- .changeset/happy-paws-walk.md | 6 ++++ .../src/exec.ts | 3 +- .../plugin-commands-script-runners/src/run.ts | 1 + .../test/exec.e2e.ts | 1 + pnpm/test/exec.ts | 29 ++++++++++++++++++- 5 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 .changeset/happy-paws-walk.md diff --git a/.changeset/happy-paws-walk.md b/.changeset/happy-paws-walk.md new file mode 100644 index 0000000000..809dbae153 --- /dev/null +++ b/.changeset/happy-paws-walk.md @@ -0,0 +1,6 @@ +--- +"@pnpm/plugin-commands-script-runners": patch +"pnpm": patch +--- + +When running "pnpm exec" from a subdirectory of a project, don't change the current working directory to the root of the project [#5759](https://github.com/pnpm/pnpm/issues/5759). diff --git a/exec/plugin-commands-script-runners/src/exec.ts b/exec/plugin-commands-script-runners/src/exec.ts index 359e172641..3f66fc5283 100644 --- a/exec/plugin-commands-script-runners/src/exec.ts +++ b/exec/plugin-commands-script-runners/src/exec.ts @@ -148,6 +148,7 @@ export type ExecOpts = Required> & { implicitlyFellbackFromRun?: boolean } & Pick { '--store-dir', path.resolve(DEFAULT_OPTS.storeDir), ]) + process.chdir('project-1') await exec.handler({ ...DEFAULT_OPTS, dir: path.resolve('project-1'), diff --git a/pnpm/test/exec.ts b/pnpm/test/exec.ts index 5409d2b121..78450c05d5 100644 --- a/pnpm/test/exec.ts +++ b/pnpm/test/exec.ts @@ -1,5 +1,7 @@ +import fs from 'fs' +import path from 'path' import { prepare, preparePackages } from '@pnpm/prepare' -import { execPnpmSync } from './utils/index.js' +import { execPnpm, execPnpmSync } from './utils/index.js' test('exec with executionEnv', async () => { prepare({ @@ -71,3 +73,28 @@ test('recursive exec when some packages define different executionEnv', async () '>>> node-version-unset: v19.0.0', ]) }) + +test("exec should respect the caller's current working directory", async () => { + prepare({ + name: 'root', + version: '1.0.0', + }) + + const projectRoot = process.cwd() + fs.mkdirSync('some-directory', { recursive: true }) + const subdirPath = path.join(projectRoot, 'some-directory') + + await execPnpm(['install']) + + const cmdFilePath = path.join(subdirPath, 'cwd.txt') + + execPnpmSync( + ['exec', 'node', '-e', `require('fs').writeFileSync(${JSON.stringify(cmdFilePath)}, process.cwd(), 'utf8')`], + { + cwd: subdirPath, + expectSuccess: true, + } + ) + + expect(fs.readFileSync(cmdFilePath, 'utf8')).toBe(subdirPath) +})