Files
pnpm/pnpm11/exec/commands/test/index.ts
Zoltan Kochan 4edeef9176 style(ts): port the perfectionist lint rules to ESLint (#16415)
Add TypeScript ports of the perfectionist rules that the Rust workspace
enforces through dylint.toml to @pnpm/eslint-config, with the same limits,
and fix every finding that needs no refactor.

The size and shape rules (function length, cognitive complexity, nesting,
local bindings, file length) are enforced for packages that already meet
them. The packages that do not are listed in PENDING_SIZE_AND_SHAPE_REFACTOR
in eslint.config.mjs and are refactored in follow-up changes.

The custom rules mirror perfectionist's semantics rather than reusing
sonarjs or max-lines-per-function, so that one limit means the same thing
in both codebases: a closure is a level of the body that contains it, a
nested function declaration is measured on its own, and a test-framework
callback plays the role of a #[test] function.

Bugs found and fixed along the way:
- a registry error body cut at exactly 64 KiB lost its truncation marker
- removing a dependency could leave dangling .bin shims
- constructor/__proto__ names crashed the projects graph, were skipped by
  the injected-deps syncer, and were dropped from writeWantedLockfile's
  returned lockfile

The CI chunk runner learns to run the node:test suite of the ESLint
config package.
2026-09-30 15:10:08 +02:00

889 lines
25 KiB
TypeScript

/// <reference path="../../../__typings__/index.d.ts" />
import fs from 'node:fs'
import path from 'node:path'
import util from 'node:util'
import { expect, test } from '@jest/globals'
import type { PnpmError } from '@pnpm/error'
import {
restart,
run,
} from '@pnpm/exec.commands'
import { prepare, preparePackages } from '@pnpm/prepare'
import { createTestIpcServer } from '@pnpm/test-ipc-server'
import { filterProjectsBySelectorObjectsFromDir } from '@pnpm/workspace.projects-filter'
import { safeExeca as execa } from 'execa'
import isWindows from 'is-windows'
import { writeYamlFileSync } from 'write-yaml-file'
import { DEFAULT_OPTS, REGISTRY_URL } from './utils/index.js'
const pnpmBin = path.join(import.meta.dirname, '../../../pnpm/bin/pnpm.mjs')
const skipOnWindows = isWindows() ? test.skip : test
const onlyOnWindows = !isWindows() ? test.skip : test
test('pnpm run: returns correct exit code', async () => {
prepare({
scripts: {
exit0: 'exit 0',
exit1: 'exit 1',
},
})
await run.handler({
...DEFAULT_OPTS,
bin: 'node_modules/.bin',
dir: process.cwd(),
extraBinPaths: [],
extraEnv: {},
pnpmHomeDir: '',
}, ['exit0'])
let err!: Error & { errno: number }
try {
await run.handler({
...DEFAULT_OPTS,
bail: true,
bin: 'node_modules/.bin',
dir: process.cwd(),
extraBinPaths: [],
extraEnv: {},
pnpmHomeDir: '',
}, ['exit1'])
} catch (_err: any) { // eslint-disable-line
err = _err
}
expect(err.errno).toBe(1)
})
test('pnpm run --no-bail runs the script to completion but still exits non-zero on failure', async () => {
prepare({
scripts: {
exit1: 'node recordArgs && exit 1',
},
})
fs.writeFileSync('args.json', '[]', 'utf8')
fs.writeFileSync('recordArgs.js', RECORD_ARGS_FILE, 'utf8')
let err: unknown
try {
await run.handler({
...DEFAULT_OPTS,
bin: 'node_modules/.bin',
bail: false,
dir: process.cwd(),
extraBinPaths: [],
extraEnv: {},
pnpmHomeDir: '',
}, ['exit1'])
} catch (_err: unknown) {
err = _err
}
expect(util.types.isNativeError(err)).toBe(true)
expect((err as PnpmError).code).toBe('ERR_PNPM_RUN_FAILED')
const { default: args } = await import(path.resolve('args.json'))
expect(args).toStrictEqual([[]])
})
test('pnpm run with regex and --no-bail runs every matched script but exits non-zero when one fails', async () => {
prepare({
scripts: {
'lint:a': 'node -e "require(\'fs\').writeFileSync(\'lint-a.txt\', \'a\')"',
'lint:b': 'node -e "require(\'fs\').writeFileSync(\'lint-b.txt\', \'b\'); process.exit(1)"',
'lint:c': 'node -e "require(\'fs\').writeFileSync(\'lint-c.txt\', \'c\')"',
},
})
let err: unknown
try {
await run.handler({
...DEFAULT_OPTS,
bin: 'node_modules/.bin',
bail: false,
dir: process.cwd(),
extraBinPaths: [],
extraEnv: {},
pnpmHomeDir: '',
}, ['/^lint:/'])
} catch (_err: unknown) {
err = _err
}
expect(util.types.isNativeError(err)).toBe(true)
expect((err as PnpmError).code).toBe('ERR_PNPM_RUN_FAILED')
// Every matched script ran to completion, even though lint:b failed.
expect(fs.readFileSync('lint-a.txt', 'utf8')).toBe('a')
expect(fs.readFileSync('lint-b.txt', 'utf8')).toBe('b')
expect(fs.readFileSync('lint-c.txt', 'utf8')).toBe('c')
})
test('pnpm run with regex and --no-bail exits zero when all matched scripts pass', async () => {
prepare({
scripts: {
'lint:a': 'node -e "process.exit(0)"',
'lint:b': 'node -e "process.exit(0)"',
},
})
await run.handler({
...DEFAULT_OPTS,
bin: 'node_modules/.bin',
bail: false,
dir: process.cwd(),
extraBinPaths: [],
extraEnv: {},
pnpmHomeDir: '',
}, ['/^lint:/'])
})
const RECORD_ARGS_FILE = 'require(\'fs\').writeFileSync(\'args.json\', JSON.stringify(require(\'./args.json\').concat([process.argv.slice(2)])), \'utf8\')'
test('run: pass the args to the command that is specified in the build script', async () => {
prepare({
scripts: {
foo: 'node recordArgs',
postfoo: 'node recordArgs',
prefoo: 'node recordArgs',
},
})
fs.writeFileSync('args.json', '[]', 'utf8')
fs.writeFileSync('recordArgs.js', RECORD_ARGS_FILE, 'utf8')
await run.handler({
...DEFAULT_OPTS,
bin: 'node_modules/.bin',
dir: process.cwd(),
extraBinPaths: [],
extraEnv: {},
pnpmHomeDir: '',
}, ['foo', 'arg', '--flag=true', '--help', '-h'])
const { default: args } = await import(path.resolve('args.json'))
expect(args).toStrictEqual([['arg', '--flag=true', '--help', '-h']])
})
test('run: pass the args to the command that is specified in the build script of a package.yaml manifest', async () => {
prepare({
scripts: {
foo: 'node recordArgs',
postfoo: 'node recordArgs',
prefoo: 'node recordArgs',
},
}, { manifestFormat: 'YAML' })
fs.writeFileSync('args.json', '[]', 'utf8')
fs.writeFileSync('recordArgs.js', RECORD_ARGS_FILE, 'utf8')
await run.handler({
...DEFAULT_OPTS,
bin: 'node_modules/.bin',
dir: process.cwd(),
extraBinPaths: [],
extraEnv: {},
pnpmHomeDir: '',
}, ['foo', 'arg', '--flag=true', '--help', '-h'])
const { default: args } = await import(path.resolve('args.json'))
expect(args).toStrictEqual([['arg', '--flag=true', '--help', '-h']])
})
test('test: pass the args to the command that is specified in the build script of a package.yaml manifest', async () => {
prepare({
scripts: {
posttest: 'node recordArgs',
pretest: 'node recordArgs',
test: 'node recordArgs',
},
}, { manifestFormat: 'YAML' })
fs.writeFileSync('args.json', '[]', 'utf8')
fs.writeFileSync('recordArgs.js', RECORD_ARGS_FILE, 'utf8')
await run.handler({
...DEFAULT_OPTS,
bin: 'node_modules/.bin',
dir: process.cwd(),
extraBinPaths: [],
extraEnv: {},
pnpmHomeDir: '',
}, ['test', 'arg', '--flag=true', '--help', '-h'])
const { default: args } = await import(path.resolve('args.json'))
expect(args).toStrictEqual([['arg', '--flag=true', '--help', '-h']])
})
test('run start: pass the args to the command that is specified in the build script of a package.yaml manifest', async () => {
prepare({
scripts: {
poststart: 'node recordArgs',
prestart: 'node recordArgs',
start: 'node recordArgs',
},
}, { manifestFormat: 'YAML' })
fs.writeFileSync('args.json', '[]', 'utf8')
fs.writeFileSync('recordArgs.js', RECORD_ARGS_FILE, 'utf8')
await run.handler({
...DEFAULT_OPTS,
bin: 'node_modules/.bin',
dir: process.cwd(),
extraBinPaths: [],
extraEnv: {},
pnpmHomeDir: '',
}, ['start', 'arg', '--flag=true', '--help', '-h'])
const { default: args } = await import(path.resolve('args.json'))
expect(args).toStrictEqual([['arg', '--flag=true', '--help', '-h']])
})
test('run stop: pass the args to the command that is specified in the build script of a package.yaml manifest', async () => {
prepare({
scripts: {
poststop: 'node recordArgs',
prestop: 'node recordArgs',
stop: 'node recordArgs',
},
}, { manifestFormat: 'YAML' })
fs.writeFileSync('args.json', '[]', 'utf8')
fs.writeFileSync('recordArgs.js', RECORD_ARGS_FILE, 'utf8')
await run.handler({
...DEFAULT_OPTS,
bin: 'node_modules/.bin',
dir: process.cwd(),
extraBinPaths: [],
extraEnv: {},
pnpmHomeDir: '',
}, ['stop', 'arg', '--flag=true', '--help', '-h'])
const { default: args } = await import(path.resolve('args.json'))
expect(args).toStrictEqual([['arg', '--flag=true', '--help', '-h']])
})
test('restart: run stop, restart and start', async () => {
await using server = await createTestIpcServer()
prepare({
scripts: {
poststop: server.sendLineScript('poststop'),
prestop: server.sendLineScript('prestop'),
stop: server.sendLineScript('stop'),
postrestart: server.sendLineScript('postrestart'),
prerestart: server.sendLineScript('prerestart'),
restart: server.sendLineScript('restart'),
poststart: server.sendLineScript('poststart'),
prestart: server.sendLineScript('prestart'),
start: server.sendLineScript('start'),
},
})
await restart.handler({
...DEFAULT_OPTS,
bin: 'node_modules/.bin',
dir: process.cwd(),
extraBinPaths: [],
extraEnv: {},
pnpmHomeDir: '',
}, [])
expect(server.getLines()).toStrictEqual([
'stop',
'restart',
'start',
])
})
test('restart: run stop, restart and start and all the pre/post scripts', async () => {
await using server = await createTestIpcServer()
prepare({
scripts: {
poststop: server.sendLineScript('poststop'),
prestop: server.sendLineScript('prestop'),
stop: `${server.sendLineScript('stop')} && pnpm poststop`,
postrestart: server.sendLineScript('postrestart'),
prerestart: server.sendLineScript('prerestart'),
restart: server.sendLineScript('restart'),
poststart: server.sendLineScript('poststart'),
prestart: server.sendLineScript('prestart'),
start: server.sendLineScript('start'),
},
})
await restart.handler({
...DEFAULT_OPTS,
bin: 'node_modules/.bin',
dir: process.cwd(),
enablePrePostScripts: true,
extraBinPaths: [],
extraEnv: {},
pnpmHomeDir: '',
}, [])
expect(server.getLines()).toStrictEqual([
'prestop',
'stop',
'poststop',
'prerestart',
'restart',
'postrestart',
'prestart',
'start',
'poststart',
])
})
test('restart: run stop and start with their pre/post scripts when there is no restart script', async () => {
await using server = await createTestIpcServer()
prepare({
scripts: {
poststop: server.sendLineScript('poststop'),
prestop: server.sendLineScript('prestop'),
stop: server.sendLineScript('stop'),
poststart: server.sendLineScript('poststart'),
prestart: server.sendLineScript('prestart'),
start: server.sendLineScript('start'),
},
})
await restart.handler({
...DEFAULT_OPTS,
bin: 'node_modules/.bin',
dir: process.cwd(),
enablePrePostScripts: true,
extraBinPaths: [],
extraEnv: {},
pnpmHomeDir: '',
}, [])
expect(server.getLines()).toStrictEqual([
'prestop',
'stop',
'poststop',
'prestart',
'start',
'poststart',
])
})
test('"pnpm run" prints the list of available commands', async () => {
prepare({
scripts: {
foo: 'echo hi',
test: 'ts-node test',
},
})
const output = await run.handler({
...DEFAULT_OPTS,
bin: 'node_modules/.bin',
dir: process.cwd(),
extraBinPaths: [],
extraEnv: {},
pnpmHomeDir: '',
}, [])
expect(output).toBe(`\
Lifecycle scripts:
test
ts-node test
Commands available via "pnpm run":
foo
echo hi`)
})
test('"pnpm run" prints the list of available commands, including commands of the root workspace project', async () => {
preparePackages([
{
location: '.',
package: {
scripts: {
build: 'echo root',
test: 'test-all',
},
},
},
{
name: 'foo',
version: '1.0.0',
scripts: {
foo: 'echo hi',
test: 'ts-node test',
},
},
])
writeYamlFileSync('pnpm-workspace.yaml', {})
const workspaceDir = process.cwd()
const { allProjects, selectedProjectsGraph } = await filterProjectsBySelectorObjectsFromDir(process.cwd(), [])
{
process.chdir('foo')
const output = await run.handler({
...DEFAULT_OPTS,
allProjects,
bin: 'node_modules/.bin',
dir: process.cwd(),
extraBinPaths: [],
extraEnv: {},
pnpmHomeDir: '',
selectedProjectsGraph,
workspaceDir,
}, [])
expect(output).toBe(`\
Lifecycle scripts:
test
ts-node test
Commands available via "pnpm run":
foo
echo hi
Commands of the root workspace project (to run them, use "pnpm -w run"):
build
echo root
test
test-all`)
}
{
process.chdir('..')
const output = await run.handler({
...DEFAULT_OPTS,
bin: 'node_modules/.bin',
allProjects,
dir: process.cwd(),
extraBinPaths: [],
extraEnv: {},
pnpmHomeDir: '',
selectedProjectsGraph,
workspaceDir,
}, [])
expect(output).toBe(`\
Lifecycle scripts:
test
test-all
Commands available via "pnpm run":
build
echo root`)
}
})
test('pnpm run does not fail with --if-present even if the wanted script is not present', async () => {
prepare({})
await run.handler({
...DEFAULT_OPTS,
bin: 'node_modules/.bin',
dir: process.cwd(),
extraBinPaths: [],
extraEnv: {},
ifPresent: true,
pnpmHomeDir: '',
}, ['build'])
})
test('if a script is not found but is present in the root, print an info message about it in the error message', async () => {
preparePackages([
{
location: '.',
package: {
scripts: {
build: 'node -e "process.stdout.write(\'root\')"',
},
},
},
{
name: 'foo',
version: '1.0.0',
},
])
writeYamlFileSync('pnpm-workspace.yaml', {})
await execa(pnpmBin, [
'install',
'-r',
'--registry',
REGISTRY_URL,
'--store-dir',
path.resolve(DEFAULT_OPTS.storeDir),
])
const { allProjects, selectedProjectsGraph } = await filterProjectsBySelectorObjectsFromDir(process.cwd(), [])
let err!: PnpmError
try {
await run.handler({
...DEFAULT_OPTS,
allProjects,
dir: path.resolve('foo'),
selectedProjectsGraph,
workspaceDir: process.cwd(),
}, ['build'])
} catch (_err: any) { // eslint-disable-line
err = _err
}
expect(err).toBeTruthy()
expect(err.hint).toMatch(/But script matched with build is present in the root/)
})
test('scripts work with PnP', async () => {
prepare({
scripts: {
foo: 'hello-world-js-bin > ./output.txt',
},
})
await execa(pnpmBin, ['add', '@pnpm.e2e/hello-world-js-bin@1.0.0'], {
env: {
pnpm_config_registry: REGISTRY_URL,
pnpm_config_node_linker: 'pnp',
pnpm_config_symlink: 'false',
},
})
await run.handler({
...DEFAULT_OPTS,
bin: 'node_modules/.bin',
dir: process.cwd(),
extraBinPaths: [],
extraEnv: {},
pnpmHomeDir: '',
}, ['foo'])
// https://github.com/pnpm/registry-mock/blob/ac2e129eb262009d2e7cd43ed869c31097793073/packages/hello-world-js-bin%401.0.0/index.js#L2
const helloWorldJsBinOutput = 'Hello world!\n'
const fooOutput = fs.readFileSync(path.resolve('output.txt')).toString()
expect(fooOutput).toStrictEqual(helloWorldJsBinOutput)
})
// A .exe file to configure as the scriptShell option would be necessary to test
// this behavior on Windows. Skip this test for now since compiling a custom
// .exe would be quite involved and hard to maintain.
skipOnWindows('pnpm run with custom shell', async () => {
prepare({
scripts: {
build: 'foo bar',
},
dependencies: {
'@pnpm.e2e/shell-mock': '0.0.0',
},
})
await execa(pnpmBin, [
'install',
`--registry=${REGISTRY_URL}`,
'--store-dir',
path.resolve(DEFAULT_OPTS.storeDir),
])
await run.handler({
...DEFAULT_OPTS,
bin: 'node_modules/.bin',
dir: process.cwd(),
extraBinPaths: [],
extraEnv: {},
pnpmHomeDir: '',
scriptShell: path.resolve('node_modules/.bin/shell-mock'),
}, ['build'])
expect((await import(path.resolve('shell-input.json'))).default).toStrictEqual(['-c', 'foo bar'])
})
onlyOnWindows('pnpm shows error if script-shell is .cmd', async () => {
prepare({
scripts: {
build: 'foo bar',
},
dependencies: {
'@pnpm.e2e/shell-mock': '0.0.0',
},
})
await execa(pnpmBin, [
'install',
`--registry=${REGISTRY_URL}`,
'--store-dir',
path.resolve(DEFAULT_OPTS.storeDir),
])
async function runScript () {
await run.handler({
...DEFAULT_OPTS,
bail: true,
bin: 'node_modules/.bin',
dir: process.cwd(),
extraBinPaths: [],
extraEnv: {},
pnpmHomeDir: '',
scriptShell: path.resolve('node_modules/.bin/shell-mock.cmd'),
}, ['build'])
}
await expect(runScript).rejects.toMatchObject({
code: 'ERR_PNPM_INVALID_SCRIPT_SHELL_WINDOWS',
})
})
test('pnpm run with RegExp script selector should work', async () => {
prepare({
scripts: {
'build:a': 'node -e "require(\'fs\').writeFileSync(\'./output-build-a.txt\', \'a\', \'utf8\')"',
'build:b': 'node -e "require(\'fs\').writeFileSync(\'./output-build-b.txt\', \'b\', \'utf8\')"',
'build:c': 'node -e "require(\'fs\').writeFileSync(\'./output-build-c.txt\', \'c\', \'utf8\')"',
build: 'node -e "require(\'fs\').writeFileSync(\'./output-build-a.txt\', \'should not run\', \'utf8\')"',
'lint:a': 'node -e "require(\'fs\').writeFileSync(\'./output-lint-a.txt\', \'a\', \'utf8\')"',
'lint:b': 'node -e "require(\'fs\').writeFileSync(\'./output-lint-b.txt\', \'b\', \'utf8\')"',
'lint:c': 'node -e "require(\'fs\').writeFileSync(\'./output-lint-c.txt\', \'c\', \'utf8\')"',
lint: 'node -e "require(\'fs\').writeFileSync(\'./output-lint-a.txt\', \'should not run\', \'utf8\')"',
},
})
await run.handler({
...DEFAULT_OPTS,
bin: 'node_modules/.bin',
dir: process.cwd(),
extraBinPaths: [],
extraEnv: {},
pnpmHomeDir: '',
}, ['/^(lint|build):.*/'])
expect(fs.readFileSync('output-build-a.txt', { encoding: 'utf-8' })).toBe('a')
expect(fs.readFileSync('output-build-b.txt', { encoding: 'utf-8' })).toBe('b')
expect(fs.readFileSync('output-build-c.txt', { encoding: 'utf-8' })).toBe('c')
expect(fs.readFileSync('output-lint-a.txt', { encoding: 'utf-8' })).toBe('a')
expect(fs.readFileSync('output-lint-b.txt', { encoding: 'utf-8' })).toBe('b')
expect(fs.readFileSync('output-lint-c.txt', { encoding: 'utf-8' })).toBe('c')
})
test('pnpm run with RegExp script selector should work also for pre/post script', async () => {
prepare({
scripts: {
'build:a': 'node -e "require(\'fs\').writeFileSync(\'./output-a.txt\', \'a\', \'utf8\')"',
'prebuild:a': 'node -e "require(\'fs\').writeFileSync(\'./output-pre-a.txt\', \'pre-a\', \'utf8\')"',
},
})
await run.handler({
...DEFAULT_OPTS,
bin: 'node_modules/.bin',
dir: process.cwd(),
extraBinPaths: [],
extraEnv: {},
pnpmHomeDir: '',
enablePrePostScripts: true,
}, ['/build:.*/'])
expect(fs.readFileSync('output-a.txt', { encoding: 'utf-8' })).toBe('a')
expect(fs.readFileSync('output-pre-a.txt', { encoding: 'utf-8' })).toBe('pre-a')
})
test('pnpm run with RegExp script selector should work parallel as a default behavior (parallel execution limits number is four)', async () => {
await using serverA = await createTestIpcServer()
await using serverB = await createTestIpcServer()
prepare({
scripts: {
'build:a': `node -e "let i = 20;setInterval(() => {if (!--i) process.exit(0); console.log(Date.now());},50)" | ${serverA.generateSendStdinScript()}`,
'build:b': `node -e "let i = 40;setInterval(() => {if (!--i) process.exit(0); console.log(Date.now());},25)" | ${serverB.generateSendStdinScript()}`,
},
})
await run.handler({
...DEFAULT_OPTS,
bin: 'node_modules/.bin',
dir: process.cwd(),
extraBinPaths: [],
extraEnv: {},
pnpmHomeDir: '',
}, ['/build:.*/'])
const outputsA = serverA.getLines().map(line => Number.parseInt(line))
const outputsB = serverB.getLines().map(line => Number.parseInt(line))
expect(Math.max(outputsA[0], outputsB[0]) < Math.min(outputsA[outputsA.length - 1], outputsB[outputsB.length - 1])).toBeTruthy()
})
test('pnpm run with RegExp script selector should work sequentially with --workspace-concurrency=1', async () => {
await using serverA = await createTestIpcServer()
await using serverB = await createTestIpcServer()
prepare({
scripts: {
'build:a': `node -e "let i = 2;setInterval(() => {if (!i--) process.exit(0); console.log(Date.now()); },16)" | ${serverA.generateSendStdinScript()}`,
'build:b': `node -e "let i = 2;setInterval(() => {if (!i--) process.exit(0); console.log(Date.now()); },16)" | ${serverB.generateSendStdinScript()}`,
},
})
await run.handler({
...DEFAULT_OPTS,
bin: 'node_modules/.bin',
dir: process.cwd(),
extraBinPaths: [],
extraEnv: {},
pnpmHomeDir: '',
workspaceConcurrency: 1,
}, ['/build:.*/'])
const outputsA = serverA.getLines().map(line => Number.parseInt(line))
const outputsB = serverB.getLines().map(line => Number.parseInt(line))
expect(outputsA[0] < outputsB[0] && outputsA[1] < outputsB[1]).toBeTruthy()
})
test.each(['d', 'g', 'i', 'm', 'u', 'v', 'y', 's'])('pnpm run with RegExp script selector with flag %s should throw error', async (flag) => {
await using serverA = await createTestIpcServer()
await using serverB = await createTestIpcServer()
prepare({
scripts: {
'build:a': `node -e "let i = 2;setInterval(() => {if (!i--) process.exit(0); console.log(Date.now()); },16)" | ${serverA.generateSendStdinScript()}`,
'build:b': `node -e "let i = 2;setInterval(() => {if (!i--) process.exit(0); console.log(Date.now()); },16)" | ${serverB.generateSendStdinScript()}`,
},
})
let err!: Error
try {
await run.handler({
...DEFAULT_OPTS,
bin: 'node_modules/.bin',
dir: process.cwd(),
extraBinPaths: [],
extraEnv: {},
pnpmHomeDir: '',
workspaceConcurrency: 1,
}, [`/build:.*/${flag}`])
} catch (_err: any) { // eslint-disable-line
err = _err
}
expect(err.message).toBe('RegExp flags are not supported in script command selector')
})
test('pnpm run with slightly incorrect command suggests correct one', async () => {
prepare({
scripts: {
build: 'echo 0',
},
})
// cspell:ignore buil
await expect(run.handler({
...DEFAULT_OPTS,
bin: 'node_modules/.bin',
dir: process.cwd(),
extraBinPaths: [],
extraEnv: {},
pnpmHomeDir: '',
workspaceConcurrency: 1,
}, ['buil'])).rejects.toMatchObject({
code: 'ERR_PNPM_NO_SCRIPT',
hint: 'Command "buil" not found. Did you mean "pnpm run build"?',
})
})
test('pnpm run with custom node-options', async () => {
prepare({
scripts: {
build: 'node -e "if (process.env.NODE_OPTIONS !== \'--max-old-space-size=1200\') { process.exit(1) }"',
},
})
await run.handler({
...DEFAULT_OPTS,
bin: 'node_modules/.bin',
dir: process.cwd(),
extraBinPaths: [],
extraEnv: {},
pnpmHomeDir: '',
nodeOptions: '--max-old-space-size=1200',
workspaceConcurrency: 1,
}, ['build'])
})
test('pnpm run without node version', async () => {
prepare({
scripts: {
'assert-node-version': `node -e "assert.equal(process.version, '${process.version}')"`,
},
})
await run.handler({
...DEFAULT_OPTS,
bin: 'node_modules/.bin',
dir: process.cwd(),
extraBinPaths: [],
extraEnv: {},
pnpmHomeDir: process.cwd(),
workspaceConcurrency: 1,
}, ['assert-node-version'])
})
test('cliOptionsTypes exposes sequential as Boolean flag', () => {
const options = run.cliOptionsTypes()
expect(options.sequential).toBe(Boolean)
})
test('RegExp script matching executes multiple scripts in package.json order when sequential is enabled', async () => {
prepare({
scripts: {
'build:z': 'node -e "require(\'fs\').appendFileSync(\'./order.log\', \'z\')"',
'build:a': 'node -e "require(\'fs\').appendFileSync(\'./order.log\', \'a\')"',
'build:m': 'node -e "require(\'fs\').appendFileSync(\'./order.log\', \'m\')"',
},
})
await run.handler({
...DEFAULT_OPTS,
bin: 'node_modules/.bin',
dir: process.cwd(),
extraBinPaths: [],
extraEnv: {},
pnpmHomeDir: '',
sequential: true,
cliOptions: { sequential: true },
}, ['/^build:.*/'])
const outputLog = fs.readFileSync(path.join(process.cwd(), 'order.log'), 'utf-8')
expect(outputLog).toBe('zam')
})
test('passing --sequential option sets effective workspaceConcurrency to 1 for matched scripts without timing flakes', async () => {
prepare({
scripts: {
'test:a': 'node -e "require(\'fs\').appendFileSync(\'./seq.log\', \'A_start\\n\'); setTimeout(() => { require(\'fs\').appendFileSync(\'./seq.log\', \'A_end\\n\') }, 50)"',
'test:b': 'node -e "require(\'fs\').appendFileSync(\'./seq.log\', \'B_start\\n\'); setTimeout(() => { require(\'fs\').appendFileSync(\'./seq.log\', \'B_end\\n\') }, 50)"',
},
})
await run.handler({
...DEFAULT_OPTS,
bin: 'node_modules/.bin',
dir: process.cwd(),
extraBinPaths: [],
extraEnv: {},
pnpmHomeDir: '',
sequential: true,
cliOptions: { sequential: true },
}, ['/^test:.*/'])
const outputLog = fs.readFileSync(path.join(process.cwd(), 'seq.log'), 'utf-8').trim().split('\n')
expect(outputLog).toEqual(['A_start', 'A_end', 'B_start', 'B_end'])
})
test('shorthands maps -s to --sequential and --workspace-concurrency=1', () => {
expect(run.shorthands.s).toEqual(['--sequential', '--workspace-concurrency=1'])
})