fix: the dlx and create commands should set npm_config_user_agent (#4317)

close #3985
This commit is contained in:
Zoltan Kochan
2022-02-11 00:50:45 +02:00
committed by GitHub
parent fe1ff2fb1d
commit cd4f9341e9
9 changed files with 550 additions and 495 deletions

View File

@@ -0,0 +1,6 @@
---
"@pnpm/plugin-commands-script-runners": patch
"pnpm": patch
---
The `pnpx`, `pnpm dlx`, `pnpm create`, and `pnpm exec` commands should set the `npm_config_user_agent` env variable [#3985](https://github.com/pnpm/pnpm/issues/3985).

View File

@@ -6,8 +6,8 @@ import { OUTPUT_OPTIONS } from '@pnpm/common-cli-options-help'
import { Config } from '@pnpm/config'
import rimraf from '@zkochan/rimraf'
import execa from 'execa'
import PATH from 'path-name'
import renderHelp from 'render-help'
import { makeEnv } from './makeEnv'
export const commandNames = ['dlx']
@@ -43,7 +43,7 @@ export function help () {
export async function handler (
opts: {
package?: string[]
} & Pick<Config, 'reporter'>,
} & Pick<Config, 'reporter' | 'userAgent'>,
params: string[]
) {
const prefix = path.join(fs.realpathSync(os.tmpdir()), `dlx-${process.pid.toString()}`)
@@ -69,13 +69,7 @@ export async function handler (
stdio: 'inherit',
})
await execa(versionless(scopeless(params[0])), params.slice(1), {
env: {
...process.env,
[PATH]: [
bins,
process.env[PATH],
].join(path.delimiter),
},
env: makeEnv({ userAgent: opts.userAgent, prependPaths: [bins] }),
stdio: 'inherit',
})
}

View File

@@ -8,10 +8,10 @@ import sortPackages from '@pnpm/sort-packages'
import { Project } from '@pnpm/types'
import execa from 'execa'
import pLimit from 'p-limit'
import PATH from 'path-name'
import pick from 'ramda/src/pick'
import renderHelp from 'render-help'
import existsInDir from './existsInDir'
import { makeEnv } from './makeEnv'
import {
PARALLEL_OPTION_HELP,
shorthands as runShorthands,
@@ -70,7 +70,7 @@ export async function handler (
reverse?: boolean
sort?: boolean
workspaceConcurrency?: number
} & Pick<Config, 'extraBinPaths' | 'lockfileDir' | 'dir' | 'recursive' | 'workspaceDir'>,
} & Pick<Config, 'extraBinPaths' | 'lockfileDir' | 'dir' | 'userAgent' | 'recursive' | 'workspaceDir'>,
params: string[]
) {
// For backward compatibility
@@ -119,18 +119,20 @@ export async function handler (
const extraEnv = pnpPath
? makeNodeRequireOption(pnpPath)
: {}
await execa(params[0], params.slice(1), {
cwd: prefix,
env: {
...process.env,
const env = makeEnv({
extraEnv: {
...extraEnv,
[PATH]: [
path.join(opts.dir, 'node_modules/.bin'),
...opts.extraBinPaths,
process.env[PATH],
].join(path.delimiter),
PNPM_PACKAGE_NAME: opts.selectedProjectsGraph?.[prefix]?.package.manifest.name,
},
prependPaths: [
path.join(opts.dir, 'node_modules/.bin'),
...opts.extraBinPaths,
],
userAgent: opts.userAgent,
})
await execa(params[0], params.slice(1), {
cwd: prefix,
env,
stdio: 'inherit',
})
result.passes++

View File

@@ -0,0 +1,20 @@
import path from 'path'
import PATH from 'path-name'
export function makeEnv (
opts: {
extraEnv?: NodeJS.ProcessEnv
userAgent?: string
prependPaths: string[]
}
) {
return {
...process.env,
...opts.extraEnv,
npm_config_user_agent: opts.userAgent ?? 'pnpm',
[PATH]: [
...opts.prependPaths,
process.env[PATH],
].join(path.delimiter),
}
}

View File

@@ -114,7 +114,7 @@ For options that may be used with `-r`, see "pnpm help recursive"',
export type RunOpts =
& Omit<RecursiveRunOpts, 'allProjects' | 'selectedProjectsGraph' | 'workspaceDir'>
& { recursive?: boolean }
& Pick<Config, 'dir' | 'engineStrict' | 'extraBinPaths' | 'reporter' | 'scriptsPrependNodePath' | 'scriptShell' | 'shellEmulator' | 'enablePrePostScripts'>
& Pick<Config, 'dir' | 'engineStrict' | 'extraBinPaths' | 'reporter' | 'scriptsPrependNodePath' | 'scriptShell' | 'shellEmulator' | 'enablePrePostScripts' | 'userAgent'>
& (
& { recursive?: false }
& Partial<Pick<Config, 'allProjects' | 'selectedProjectsGraph' | 'workspaceDir'>>

View File

@@ -8,10 +8,15 @@ beforeEach((execa as jest.Mock).mockClear)
test('dlx should work with scoped packages', async () => {
prepareEmpty()
const userAgent = 'pnpm/0.0.0'
await dlx.handler({}, ['@foo/bar'])
await dlx.handler({ userAgent }, ['@foo/bar'])
expect(execa).toBeCalledWith('bar', [], expect.anything())
expect(execa).toBeCalledWith('bar', [], expect.objectContaining({
env: expect.objectContaining({
npm_config_user_agent: userAgent,
}),
}))
})
test('dlx should work with versioned packages', async () => {

View File

@@ -0,0 +1,484 @@
import { promises as fs } from 'fs'
import path from 'path'
import PnpmError from '@pnpm/error'
import { readProjects } from '@pnpm/filter-workspace-packages'
import { exec, run } from '@pnpm/plugin-commands-script-runners'
import prepare, { prepareEmpty, preparePackages } from '@pnpm/prepare'
import rimraf from '@zkochan/rimraf'
import execa from 'execa'
import { DEFAULT_OPTS, REGISTRY } from './utils'
const pnpmBin = path.join(__dirname, '../../pnpm/bin/pnpm.cjs')
test('pnpm recursive exec', async () => {
preparePackages([
{
name: 'project-1',
version: '1.0.0',
dependencies: {
'json-append': '1',
},
scripts: {
build: 'node -e "process.stdout.write(\'project-1\')" | json-append ../output1.json && node -e "process.stdout.write(\'project-1\')" | json-append ../output2.json',
},
},
{
name: 'project-2',
version: '1.0.0',
dependencies: {
'json-append': '1',
'project-1': '1',
},
scripts: {
build: 'node -e "process.stdout.write(\'project-2\')" | json-append ../output1.json',
postbuild: 'node -e "process.stdout.write(\'project-2-postbuild\')" | json-append ../output1.json',
prebuild: 'node -e "process.stdout.write(\'project-2-prebuild\')" | json-append ../output1.json',
},
},
{
name: 'project-3',
version: '1.0.0',
dependencies: {
'json-append': '1',
'project-1': '1',
},
scripts: {
build: 'node -e "process.stdout.write(\'project-3\')" | json-append ../output2.json',
},
},
])
const { selectedProjectsGraph } = await readProjects(process.cwd(), [])
await execa('pnpm', [
'install',
'-r',
'--registry',
REGISTRY,
'--store-dir',
path.resolve(DEFAULT_OPTS.storeDir),
])
await exec.handler({
...DEFAULT_OPTS,
dir: process.cwd(),
recursive: true,
selectedProjectsGraph,
}, ['npm', 'run', 'build'])
const { default: outputs1 } = await import(path.resolve('output1.json'))
const { default: outputs2 } = await import(path.resolve('output2.json'))
expect(outputs1).toStrictEqual(['project-1', 'project-2-prebuild', 'project-2', 'project-2-postbuild'])
expect(outputs2).toStrictEqual(['project-1', 'project-3'])
})
test('exec inside a workspace package', async () => {
preparePackages([
{
name: 'project-1',
version: '1.0.0',
dependencies: {
'json-append': '1',
},
scripts: {
build: 'node -e "process.stdout.write(\'project-1\')" | json-append ../output1.json && node -e "process.stdout.write(\'project-1\')" | json-append ../output2.json',
},
},
{
name: 'project-2',
version: '1.0.0',
dependencies: {
'json-append': '1',
'project-1': '1',
},
scripts: {
build: 'node -e "process.stdout.write(\'project-2\')" | json-append ../output1.json',
postbuild: 'node -e "process.stdout.write(\'project-2-postbuild\')" | json-append ../output1.json',
prebuild: 'node -e "process.stdout.write(\'project-2-prebuild\')" | json-append ../output1.json',
},
},
{
name: 'project-3',
version: '1.0.0',
dependencies: {
'json-append': '1',
'project-1': '1',
},
scripts: {
build: 'node -e "process.stdout.write(\'project-3\')" | json-append ../output2.json',
},
},
])
await execa('pnpm', [
'install',
'-r',
'--registry',
REGISTRY,
'--store-dir',
path.resolve(DEFAULT_OPTS.storeDir),
])
await exec.handler({
...DEFAULT_OPTS,
dir: path.resolve('project-1'),
recursive: false,
selectedProjectsGraph: {},
}, ['npm', 'run', 'build'])
const { default: outputs1 } = await import(path.resolve('output1.json'))
const { default: outputs2 } = await import(path.resolve('output2.json'))
expect(outputs1).toStrictEqual(['project-1'])
expect(outputs2).toStrictEqual(['project-1'])
})
test('pnpm recursive exec sets PNPM_PACKAGE_NAME env var', async () => {
preparePackages([
{
name: 'foo',
version: '1.0.0',
},
])
const { selectedProjectsGraph } = await readProjects(process.cwd(), [])
await exec.handler({
...DEFAULT_OPTS,
dir: process.cwd(),
recursive: true,
selectedProjectsGraph,
}, ['node', '-e', 'require(\'fs\').writeFileSync(\'pkgname\', process.env.PNPM_PACKAGE_NAME, \'utf8\')'])
expect(await fs.readFile('foo/pkgname', 'utf8')).toBe('foo')
})
test('testing the bail config with "pnpm recursive exec"', async () => {
preparePackages([
{
name: 'project-1',
version: '1.0.0',
dependencies: {
'json-append': '1',
},
scripts: {
build: 'node -e "process.stdout.write(\'project-1\')" | json-append ../output.json',
},
},
{
name: 'project-2',
version: '1.0.0',
dependencies: {
'json-append': '1',
'project-1': '1',
},
scripts: {
build: 'exit 1 && node -e "process.stdout.write(\'project-2\')" | json-append ../output.json',
},
},
{
name: 'project-3',
version: '1.0.0',
dependencies: {
'json-append': '1',
'project-1': '1',
},
scripts: {
build: 'node -e "process.stdout.write(\'project-3\')" | json-append ../output.json',
},
},
])
const { selectedProjectsGraph } = await readProjects(process.cwd(), [])
await execa('pnpm', [
'install',
'-r',
'--registry',
REGISTRY,
'--store-dir',
path.resolve(DEFAULT_OPTS.storeDir),
])
let failed = false
let err1!: PnpmError
try {
await exec.handler({
...DEFAULT_OPTS,
dir: process.cwd(),
recursive: true,
selectedProjectsGraph,
}, ['npm', 'run', 'build', '--no-bail'])
} catch (_err: any) { // eslint-disable-line
err1 = _err
failed = true
}
expect(err1.code).toBe('ERR_PNPM_RECURSIVE_FAIL')
expect(failed).toBeTruthy()
const { default: outputs } = await import(path.resolve('output.json'))
expect(outputs).toStrictEqual(['project-1', 'project-3'])
await rimraf('./output.json')
failed = false
let err2!: PnpmError
try {
await exec.handler({
...DEFAULT_OPTS,
dir: process.cwd(),
recursive: true,
selectedProjectsGraph,
}, ['npm', 'run', 'build'])
} catch (_err: any) { // eslint-disable-line
err2 = _err
failed = true
}
expect(err2.code).toBe('ERR_PNPM_RECURSIVE_FAIL')
expect(failed).toBeTruthy()
})
test('pnpm recursive exec --no-sort', async () => {
preparePackages([
{
name: 'a-dependent',
version: '1.0.0',
dependencies: {
'b-dependency': '1.0.0',
'json-append': '1',
},
scripts: {
build: 'node -e "process.stdout.write(\'a-dependent\')" | json-append ../output.json',
},
},
{
name: 'b-dependency',
version: '1.0.0',
dependencies: {
'json-append': '1',
},
scripts: {
build: 'node -e "process.stdout.write(\'b-dependency\')" | json-append ../output.json',
},
},
])
const { selectedProjectsGraph } = await readProjects(process.cwd(), [])
await execa('pnpm', [
'install',
'-r',
'--registry',
REGISTRY,
'--store-dir',
path.resolve(DEFAULT_OPTS.storeDir),
])
await exec.handler({
...DEFAULT_OPTS,
dir: process.cwd(),
recursive: true,
selectedProjectsGraph,
sort: false,
workspaceConcurrency: 1,
}, ['npm', 'run', 'build'])
const { default: outputs } = await import(path.resolve('output.json'))
expect(outputs).toStrictEqual(['a-dependent', 'b-dependency'])
})
test('pnpm recursive exec --reverse', async () => {
preparePackages([
{
name: 'project-1',
version: '1.0.0',
dependencies: {
'json-append': '1',
},
scripts: {
build: 'node -e "process.stdout.write(\'project-1\')" | json-append ../output1.json',
},
},
{
name: 'project-2',
version: '1.0.0',
dependencies: {
'json-append': '1',
'project-1': '1',
},
scripts: {
build: 'node -e "process.stdout.write(\'project-2\')" | json-append ../output1.json',
},
},
{
name: 'project-3',
version: '1.0.0',
dependencies: {
'json-append': '1',
'project-1': '1',
},
scripts: {
build: 'node -e "process.stdout.write(\'project-3\')" | json-append ../output1.json',
},
},
])
const { selectedProjectsGraph } = await readProjects(process.cwd(), [])
await execa(pnpmBin, [
'install',
'-r',
'--registry',
REGISTRY,
'--store-dir',
path.resolve(DEFAULT_OPTS.storeDir),
])
await exec.handler({
...DEFAULT_OPTS,
dir: process.cwd(),
selectedProjectsGraph,
recursive: true,
sort: true,
reverse: true,
}, ['npm', 'run', 'build'])
const { default: outputs1 } = await import(path.resolve('output1.json'))
expect(outputs1[outputs1.length - 1]).toBe('project-1')
})
test('pnpm exec on single project', async () => {
prepare({})
await exec.handler({
...DEFAULT_OPTS,
dir: process.cwd(),
recursive: false,
selectedProjectsGraph: {},
}, ['node', '-e', 'require("fs").writeFileSync("output.json", "[]", "utf8")'])
const { default: outputs } = await import(path.resolve('output.json'))
expect(outputs).toStrictEqual([])
})
test('pnpm exec on single project should return non-zero exit code when the process fails', async () => {
prepare({})
{
const { exitCode } = await exec.handler({
...DEFAULT_OPTS,
dir: process.cwd(),
recursive: false,
selectedProjectsGraph: {},
}, ['node', '-e', 'process.exitCode=1'])
expect(exitCode).toBe(1)
}
{
const runResult = await run.handler({
...DEFAULT_OPTS,
argv: {
original: ['pnpm', 'node', '-e', 'process.exitCode=1'],
},
dir: process.cwd(),
fallbackCommandUsed: true,
recursive: false,
selectedProjectsGraph: {},
}, ['node'])
expect(runResult['exitCode']).toBe(1)
}
})
test('pnpm exec outside of projects', async () => {
prepareEmpty()
await exec.handler({
...DEFAULT_OPTS,
dir: process.cwd(),
recursive: false,
selectedProjectsGraph: {},
}, ['node', '-e', 'require("fs").writeFileSync("output.json", "[]", "utf8")'])
const { default: outputs } = await import(path.resolve('output.json'))
expect(outputs).toStrictEqual([])
})
test('pnpm recursive exec works with PnP', async () => {
preparePackages([
{
name: 'project-1',
version: '1.0.0',
dependencies: {
'json-append': '1',
},
scripts: {
build: 'node -e "process.stdout.write(\'project-1\')" | json-append ../output1.json && node -e "process.stdout.write(\'project-1\')" | json-append ../output2.json',
},
},
{
name: 'project-2',
version: '1.0.0',
dependencies: {
'json-append': '1',
'project-1': '1',
},
scripts: {
build: 'node -e "process.stdout.write(\'project-2\')" | json-append ../output1.json',
postbuild: 'node -e "process.stdout.write(\'project-2-postbuild\')" | json-append ../output1.json',
prebuild: 'node -e "process.stdout.write(\'project-2-prebuild\')" | json-append ../output1.json',
},
},
{
name: 'project-3',
version: '1.0.0',
dependencies: {
'json-append': '1',
'project-1': '1',
},
scripts: {
build: 'node -e "process.stdout.write(\'project-3\')" | json-append ../output2.json',
},
},
])
const { selectedProjectsGraph } = await readProjects(process.cwd(), [])
await execa(pnpmBin, [
'install',
'-r',
'--registry',
REGISTRY,
'--store-dir',
path.resolve(DEFAULT_OPTS.storeDir),
], {
env: {
NPM_CONFIG_NODE_LINKER: 'pnp',
NPM_CONFIG_SYMLINK: 'false',
},
})
await exec.handler({
...DEFAULT_OPTS,
dir: process.cwd(),
recursive: true,
selectedProjectsGraph,
}, ['npm', 'run', 'build'])
const { default: outputs1 } = await import(path.resolve('output1.json'))
const { default: outputs2 } = await import(path.resolve('output2.json'))
expect(outputs1).toStrictEqual(['project-1', 'project-2-prebuild', 'project-2', 'project-2-postbuild'])
expect(outputs2).toStrictEqual(['project-1', 'project-3'])
})

View File

@@ -1,484 +1,26 @@
import { promises as fs } from 'fs'
import path from 'path'
import PnpmError from '@pnpm/error'
import { readProjects } from '@pnpm/filter-workspace-packages'
import { exec, run } from '@pnpm/plugin-commands-script-runners'
import prepare, { prepareEmpty, preparePackages } from '@pnpm/prepare'
import rimraf from '@zkochan/rimraf'
import execa from 'execa'
import { DEFAULT_OPTS, REGISTRY } from './utils'
import { exec } from '@pnpm/plugin-commands-script-runners'
import { prepareEmpty } from '@pnpm/prepare'
import { DEFAULT_OPTS } from './utils'
const pnpmBin = path.join(__dirname, '../../pnpm/bin/pnpm.cjs')
jest.mock('execa')
test('pnpm recursive exec', async () => {
preparePackages([
{
name: 'project-1',
version: '1.0.0',
beforeEach((execa as jest.Mock).mockClear)
dependencies: {
'json-append': '1',
},
scripts: {
build: 'node -e "process.stdout.write(\'project-1\')" | json-append ../output1.json && node -e "process.stdout.write(\'project-1\')" | json-append ../output2.json',
},
},
{
name: 'project-2',
version: '1.0.0',
dependencies: {
'json-append': '1',
'project-1': '1',
},
scripts: {
build: 'node -e "process.stdout.write(\'project-2\')" | json-append ../output1.json',
postbuild: 'node -e "process.stdout.write(\'project-2-postbuild\')" | json-append ../output1.json',
prebuild: 'node -e "process.stdout.write(\'project-2-prebuild\')" | json-append ../output1.json',
},
},
{
name: 'project-3',
version: '1.0.0',
dependencies: {
'json-append': '1',
'project-1': '1',
},
scripts: {
build: 'node -e "process.stdout.write(\'project-3\')" | json-append ../output2.json',
},
},
])
const { selectedProjectsGraph } = await readProjects(process.cwd(), [])
await execa('pnpm', [
'install',
'-r',
'--registry',
REGISTRY,
'--store-dir',
path.resolve(DEFAULT_OPTS.storeDir),
])
await exec.handler({
...DEFAULT_OPTS,
dir: process.cwd(),
recursive: true,
selectedProjectsGraph,
}, ['npm', 'run', 'build'])
const { default: outputs1 } = await import(path.resolve('output1.json'))
const { default: outputs2 } = await import(path.resolve('output2.json'))
expect(outputs1).toStrictEqual(['project-1', 'project-2-prebuild', 'project-2', 'project-2-postbuild'])
expect(outputs2).toStrictEqual(['project-1', 'project-3'])
})
test('exec inside a workspace package', async () => {
preparePackages([
{
name: 'project-1',
version: '1.0.0',
dependencies: {
'json-append': '1',
},
scripts: {
build: 'node -e "process.stdout.write(\'project-1\')" | json-append ../output1.json && node -e "process.stdout.write(\'project-1\')" | json-append ../output2.json',
},
},
{
name: 'project-2',
version: '1.0.0',
dependencies: {
'json-append': '1',
'project-1': '1',
},
scripts: {
build: 'node -e "process.stdout.write(\'project-2\')" | json-append ../output1.json',
postbuild: 'node -e "process.stdout.write(\'project-2-postbuild\')" | json-append ../output1.json',
prebuild: 'node -e "process.stdout.write(\'project-2-prebuild\')" | json-append ../output1.json',
},
},
{
name: 'project-3',
version: '1.0.0',
dependencies: {
'json-append': '1',
'project-1': '1',
},
scripts: {
build: 'node -e "process.stdout.write(\'project-3\')" | json-append ../output2.json',
},
},
])
await execa('pnpm', [
'install',
'-r',
'--registry',
REGISTRY,
'--store-dir',
path.resolve(DEFAULT_OPTS.storeDir),
])
await exec.handler({
...DEFAULT_OPTS,
dir: path.resolve('project-1'),
recursive: false,
selectedProjectsGraph: {},
}, ['npm', 'run', 'build'])
const { default: outputs1 } = await import(path.resolve('output1.json'))
const { default: outputs2 } = await import(path.resolve('output2.json'))
expect(outputs1).toStrictEqual(['project-1'])
expect(outputs2).toStrictEqual(['project-1'])
})
test('pnpm recursive exec sets PNPM_PACKAGE_NAME env var', async () => {
preparePackages([
{
name: 'foo',
version: '1.0.0',
},
])
const { selectedProjectsGraph } = await readProjects(process.cwd(), [])
await exec.handler({
...DEFAULT_OPTS,
dir: process.cwd(),
recursive: true,
selectedProjectsGraph,
}, ['node', '-e', 'require(\'fs\').writeFileSync(\'pkgname\', process.env.PNPM_PACKAGE_NAME, \'utf8\')'])
expect(await fs.readFile('foo/pkgname', 'utf8')).toBe('foo')
})
test('testing the bail config with "pnpm recursive exec"', async () => {
preparePackages([
{
name: 'project-1',
version: '1.0.0',
dependencies: {
'json-append': '1',
},
scripts: {
build: 'node -e "process.stdout.write(\'project-1\')" | json-append ../output.json',
},
},
{
name: 'project-2',
version: '1.0.0',
dependencies: {
'json-append': '1',
'project-1': '1',
},
scripts: {
build: 'exit 1 && node -e "process.stdout.write(\'project-2\')" | json-append ../output.json',
},
},
{
name: 'project-3',
version: '1.0.0',
dependencies: {
'json-append': '1',
'project-1': '1',
},
scripts: {
build: 'node -e "process.stdout.write(\'project-3\')" | json-append ../output.json',
},
},
])
const { selectedProjectsGraph } = await readProjects(process.cwd(), [])
await execa('pnpm', [
'install',
'-r',
'--registry',
REGISTRY,
'--store-dir',
path.resolve(DEFAULT_OPTS.storeDir),
])
let failed = false
let err1!: PnpmError
try {
await exec.handler({
...DEFAULT_OPTS,
dir: process.cwd(),
recursive: true,
selectedProjectsGraph,
}, ['npm', 'run', 'build', '--no-bail'])
} catch (_err: any) { // eslint-disable-line
err1 = _err
failed = true
}
expect(err1.code).toBe('ERR_PNPM_RECURSIVE_FAIL')
expect(failed).toBeTruthy()
const { default: outputs } = await import(path.resolve('output.json'))
expect(outputs).toStrictEqual(['project-1', 'project-3'])
await rimraf('./output.json')
failed = false
let err2!: PnpmError
try {
await exec.handler({
...DEFAULT_OPTS,
dir: process.cwd(),
recursive: true,
selectedProjectsGraph,
}, ['npm', 'run', 'build'])
} catch (_err: any) { // eslint-disable-line
err2 = _err
failed = true
}
expect(err2.code).toBe('ERR_PNPM_RECURSIVE_FAIL')
expect(failed).toBeTruthy()
})
test('pnpm recursive exec --no-sort', async () => {
preparePackages([
{
name: 'a-dependent',
version: '1.0.0',
dependencies: {
'b-dependency': '1.0.0',
'json-append': '1',
},
scripts: {
build: 'node -e "process.stdout.write(\'a-dependent\')" | json-append ../output.json',
},
},
{
name: 'b-dependency',
version: '1.0.0',
dependencies: {
'json-append': '1',
},
scripts: {
build: 'node -e "process.stdout.write(\'b-dependency\')" | json-append ../output.json',
},
},
])
const { selectedProjectsGraph } = await readProjects(process.cwd(), [])
await execa('pnpm', [
'install',
'-r',
'--registry',
REGISTRY,
'--store-dir',
path.resolve(DEFAULT_OPTS.storeDir),
])
await exec.handler({
...DEFAULT_OPTS,
dir: process.cwd(),
recursive: true,
selectedProjectsGraph,
sort: false,
workspaceConcurrency: 1,
}, ['npm', 'run', 'build'])
const { default: outputs } = await import(path.resolve('output.json'))
expect(outputs).toStrictEqual(['a-dependent', 'b-dependency'])
})
test('pnpm recursive exec --reverse', async () => {
preparePackages([
{
name: 'project-1',
version: '1.0.0',
dependencies: {
'json-append': '1',
},
scripts: {
build: 'node -e "process.stdout.write(\'project-1\')" | json-append ../output1.json',
},
},
{
name: 'project-2',
version: '1.0.0',
dependencies: {
'json-append': '1',
'project-1': '1',
},
scripts: {
build: 'node -e "process.stdout.write(\'project-2\')" | json-append ../output1.json',
},
},
{
name: 'project-3',
version: '1.0.0',
dependencies: {
'json-append': '1',
'project-1': '1',
},
scripts: {
build: 'node -e "process.stdout.write(\'project-3\')" | json-append ../output1.json',
},
},
])
const { selectedProjectsGraph } = await readProjects(process.cwd(), [])
await execa(pnpmBin, [
'install',
'-r',
'--registry',
REGISTRY,
'--store-dir',
path.resolve(DEFAULT_OPTS.storeDir),
])
await exec.handler({
...DEFAULT_OPTS,
dir: process.cwd(),
selectedProjectsGraph,
recursive: true,
sort: true,
reverse: true,
}, ['npm', 'run', 'build'])
const { default: outputs1 } = await import(path.resolve('output1.json'))
expect(outputs1[outputs1.length - 1]).toBe('project-1')
})
test('pnpm exec on single project', async () => {
prepare({})
await exec.handler({
...DEFAULT_OPTS,
dir: process.cwd(),
recursive: false,
selectedProjectsGraph: {},
}, ['node', '-e', 'require("fs").writeFileSync("output.json", "[]", "utf8")'])
const { default: outputs } = await import(path.resolve('output.json'))
expect(outputs).toStrictEqual([])
})
test('pnpm exec on single project should return non-zero exit code when the process fails', async () => {
prepare({})
{
const { exitCode } = await exec.handler({
...DEFAULT_OPTS,
dir: process.cwd(),
recursive: false,
selectedProjectsGraph: {},
}, ['node', '-e', 'process.exitCode=1'])
expect(exitCode).toBe(1)
}
{
const runResult = await run.handler({
...DEFAULT_OPTS,
argv: {
original: ['pnpm', 'node', '-e', 'process.exitCode=1'],
},
dir: process.cwd(),
fallbackCommandUsed: true,
recursive: false,
selectedProjectsGraph: {},
}, ['node'])
expect(runResult['exitCode']).toBe(1)
}
})
test('pnpm exec outside of projects', async () => {
test('exec should set npm_config_user_agent', async () => {
prepareEmpty()
const userAgent = 'pnpm/0.0.0'
await exec.handler({
...DEFAULT_OPTS,
dir: process.cwd(),
recursive: false,
selectedProjectsGraph: {},
}, ['node', '-e', 'require("fs").writeFileSync("output.json", "[]", "utf8")'])
userAgent,
}, ['eslint'])
const { default: outputs } = await import(path.resolve('output.json'))
expect(outputs).toStrictEqual([])
})
test('pnpm recursive exec works with PnP', async () => {
preparePackages([
{
name: 'project-1',
version: '1.0.0',
dependencies: {
'json-append': '1',
},
scripts: {
build: 'node -e "process.stdout.write(\'project-1\')" | json-append ../output1.json && node -e "process.stdout.write(\'project-1\')" | json-append ../output2.json',
},
},
{
name: 'project-2',
version: '1.0.0',
dependencies: {
'json-append': '1',
'project-1': '1',
},
scripts: {
build: 'node -e "process.stdout.write(\'project-2\')" | json-append ../output1.json',
postbuild: 'node -e "process.stdout.write(\'project-2-postbuild\')" | json-append ../output1.json',
prebuild: 'node -e "process.stdout.write(\'project-2-prebuild\')" | json-append ../output1.json',
},
},
{
name: 'project-3',
version: '1.0.0',
dependencies: {
'json-append': '1',
'project-1': '1',
},
scripts: {
build: 'node -e "process.stdout.write(\'project-3\')" | json-append ../output2.json',
},
},
])
const { selectedProjectsGraph } = await readProjects(process.cwd(), [])
await execa(pnpmBin, [
'install',
'-r',
'--registry',
REGISTRY,
'--store-dir',
path.resolve(DEFAULT_OPTS.storeDir),
], {
env: {
NPM_CONFIG_NODE_LINKER: 'pnp',
NPM_CONFIG_SYMLINK: 'false',
},
})
await exec.handler({
...DEFAULT_OPTS,
dir: process.cwd(),
recursive: true,
selectedProjectsGraph,
}, ['npm', 'run', 'build'])
const { default: outputs1 } = await import(path.resolve('output1.json'))
const { default: outputs2 } = await import(path.resolve('output2.json'))
expect(outputs1).toStrictEqual(['project-1', 'project-2-prebuild', 'project-2', 'project-2-postbuild'])
expect(outputs2).toStrictEqual(['project-1', 'project-3'])
expect(execa).toBeCalledWith('eslint', [], expect.objectContaining({
env: expect.objectContaining({
npm_config_user_agent: userAgent,
}),
}))
})

View File

@@ -1,4 +1,5 @@
import path from 'path'
import packageManager from '@pnpm/cli-meta'
import findWorkspaceDir from '@pnpm/find-workspace-dir'
import storePath from '@pnpm/store-path'
import npx from '@zkochan/libnpx/index'
@@ -9,6 +10,7 @@ const PNPM_PATH = path.join(__dirname, 'pnpm.cjs')
// eslint-disable-next-line @typescript-eslint/no-floating-promises
; (async () => {
const workspaceRoot = await findWorkspaceDir(process.cwd())
process.env['npm_config_user_agent'] = `${packageManager.name}/${packageManager.version} npm/? node/${process.version} ${process.platform} ${process.arch}`
if (workspaceRoot) {
process.env[PATH] = `${path.join(workspaceRoot, 'node_modules/.bin')}${path.delimiter}${process.env[PATH] ?? ''}`
}