Files
pnpm/pnpm11/exec/commands/test/exec.ts
arman b46f96165f fix: kill spawned process trees with taskkill on Windows error exits (#12925)
Main thread panicked: begin > end (105 > 28) when slicing
`@pnpm/npm-lifecycle@1100.0.0(patch_hash=e3541c…)(supports-color@10.2.2)`
at pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs:2490:51

---------

Co-authored-by: Zoltan Kochan <z@kochan.io>
2026-07-13 16:30:01 +02:00

90 lines
2.5 KiB
TypeScript

import fs from 'node:fs'
import path from 'node:path'
import { beforeEach, expect, jest, test } from '@jest/globals'
import { makeNodeRequireOption } from '@pnpm/exec.lifecycle'
import { prepareEmpty } from '@pnpm/prepare'
import { DEFAULT_OPTS } from './utils/index.js'
jest.unstable_mockModule('execa', () => ({
// The handler registers the returned subprocess with the child process
// tracker, so the mock returns a minimal subprocess-like object.
safeExeca: jest.fn(() => ({ pid: undefined, once: jest.fn() })),
sync: jest.fn(),
}))
const { safeExeca: execa } = await import('execa')
const { exec } = await import('@pnpm/exec.commands')
beforeEach(() => {
jest.mocked(execa).mockClear()
})
test('exec should set npm_config_user_agent', async () => {
prepareEmpty()
const userAgent = 'pnpm/0.0.0'
await exec.handler({
...DEFAULT_OPTS,
dir: process.cwd(),
selectedProjectsGraph: {},
userAgent,
}, ['eslint'])
expect(execa).toHaveBeenCalledWith('eslint', [], expect.objectContaining({
env: expect.objectContaining({
npm_config_user_agent: userAgent,
}),
}))
})
test('exec should set the NODE_OPTIONS env var', async () => {
prepareEmpty()
await exec.handler({
...DEFAULT_OPTS,
dir: process.cwd(),
selectedProjectsGraph: {},
nodeOptions: '--max-old-space-size=4096',
}, ['eslint'])
expect(execa).toHaveBeenCalledWith('eslint', [], expect.objectContaining({
env: expect.objectContaining({
NODE_OPTIONS: '--max-old-space-size=4096',
}),
}))
})
test('exec should merge node options with PnP require option', async () => {
prepareEmpty()
const pnpPath = path.join(process.cwd(), '.pnp.cjs')
fs.writeFileSync(pnpPath, '')
await exec.handler({
...DEFAULT_OPTS,
dir: process.cwd(),
selectedProjectsGraph: {},
workspaceDir: undefined,
nodeOptions: '--max-old-space-size=4096',
}, ['eslint'])
// The require path is quoted and backslash-escaped on Windows, so derive the
// expected value from the same helper exec uses instead of hardcoding it.
const { NODE_OPTIONS } = makeNodeRequireOption(pnpPath, { NODE_OPTIONS: '--max-old-space-size=4096' })
expect(execa).toHaveBeenCalledWith('eslint', [], expect.objectContaining({
env: expect.objectContaining({ NODE_OPTIONS }),
}))
})
test('exec should specify the command', async () => {
prepareEmpty()
await expect(exec.handler({
...DEFAULT_OPTS,
dir: process.cwd(),
selectedProjectsGraph: {},
}, [])
).rejects.toThrow("'pnpm exec' requires a command to run")
})