mirror of
https://github.com/pnpm/pnpm.git
synced 2026-07-19 04:02:32 -04:00
6
.changeset/violet-islands-wink.md
Normal file
6
.changeset/violet-islands-wink.md
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
"@pnpm/plugin-commands-script-runners": patch
|
||||||
|
"pnpm": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
`exec` now also streams prefixed output when `--recursive` or `--parallel` is specified just as `run` does [#8065](https://github.com/pnpm/pnpm/issues/8065).
|
||||||
@@ -47,6 +47,7 @@
|
|||||||
"@pnpm/command": "workspace:*",
|
"@pnpm/command": "workspace:*",
|
||||||
"@pnpm/common-cli-options-help": "workspace:*",
|
"@pnpm/common-cli-options-help": "workspace:*",
|
||||||
"@pnpm/config": "workspace:*",
|
"@pnpm/config": "workspace:*",
|
||||||
|
"@pnpm/core-loggers": "workspace:*",
|
||||||
"@pnpm/crypto.base32-hash": "workspace:*",
|
"@pnpm/crypto.base32-hash": "workspace:*",
|
||||||
"@pnpm/error": "workspace:*",
|
"@pnpm/error": "workspace:*",
|
||||||
"@pnpm/lifecycle": "workspace:*",
|
"@pnpm/lifecycle": "workspace:*",
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import path from 'path'
|
import path from 'path'
|
||||||
import { docsUrl, type RecursiveSummary, throwOnCommandFail, readProjectManifestOnly } from '@pnpm/cli-utils'
|
import { docsUrl, type RecursiveSummary, throwOnCommandFail, readProjectManifestOnly } from '@pnpm/cli-utils'
|
||||||
|
import { type LifecycleMessage, lifecycleLogger } from '@pnpm/core-loggers'
|
||||||
import { type Config, types } from '@pnpm/config'
|
import { type Config, types } from '@pnpm/config'
|
||||||
import { makeNodeRequireOption } from '@pnpm/lifecycle'
|
import { makeNodeRequireOption } from '@pnpm/lifecycle'
|
||||||
import { logger } from '@pnpm/logger'
|
import { logger } from '@pnpm/logger'
|
||||||
@@ -39,6 +40,7 @@ export function rcOptionsTypes (): Record<string, unknown> {
|
|||||||
'use-node-version',
|
'use-node-version',
|
||||||
'unsafe-perm',
|
'unsafe-perm',
|
||||||
'workspace-concurrency',
|
'workspace-concurrency',
|
||||||
|
'reporter-hide-prefix',
|
||||||
], types),
|
], types),
|
||||||
'shell-mode': Boolean,
|
'shell-mode': Boolean,
|
||||||
'resume-from': String,
|
'resume-from': String,
|
||||||
@@ -134,7 +136,7 @@ export async function handler (
|
|||||||
resumeFrom?: string
|
resumeFrom?: string
|
||||||
reportSummary?: boolean
|
reportSummary?: boolean
|
||||||
implicitlyFellbackFromRun?: boolean
|
implicitlyFellbackFromRun?: boolean
|
||||||
} & Pick<Config, 'extraBinPaths' | 'extraEnv' | 'lockfileDir' | 'modulesDir' | 'dir' | 'userAgent' | 'recursive' | 'workspaceDir' | 'nodeOptions'>,
|
} & Pick<Config, 'extraBinPaths' | 'extraEnv' | 'lockfileDir' | 'modulesDir' | 'dir' | 'userAgent' | 'recursive' | 'reporterHidePrefix' | 'workspaceDir' | 'nodeOptions'>,
|
||||||
params: string[]
|
params: string[]
|
||||||
): Promise<{ exitCode: number }> {
|
): Promise<{ exitCode: number }> {
|
||||||
// For backward compatibility
|
// For backward compatibility
|
||||||
@@ -184,6 +186,7 @@ export async function handler (
|
|||||||
'./node_modules/.bin',
|
'./node_modules/.bin',
|
||||||
...opts.extraBinPaths,
|
...opts.extraBinPaths,
|
||||||
]
|
]
|
||||||
|
const reporterShowPrefix = opts.recursive && !opts.reporterHidePrefix
|
||||||
for (const chunk of chunks) {
|
for (const chunk of chunks) {
|
||||||
// eslint-disable-next-line no-await-in-loop
|
// eslint-disable-next-line no-await-in-loop
|
||||||
await Promise.all(chunk.map(async (prefix: string) =>
|
await Promise.all(chunk.map(async (prefix: string) =>
|
||||||
@@ -205,12 +208,47 @@ export async function handler (
|
|||||||
prependPaths,
|
prependPaths,
|
||||||
userAgent: opts.userAgent,
|
userAgent: opts.userAgent,
|
||||||
})
|
})
|
||||||
await execa(params[0], params.slice(1), {
|
const [cmd, ...args] = params
|
||||||
cwd: prefix,
|
if (reporterShowPrefix) {
|
||||||
env,
|
const manifest = await readProjectManifestOnly(prefix)
|
||||||
stdio: 'inherit',
|
const child = execa(cmd, args, {
|
||||||
shell: opts.shellMode ?? false,
|
cwd: prefix,
|
||||||
})
|
env,
|
||||||
|
stdio: 'pipe',
|
||||||
|
shell: opts.shellMode ?? false,
|
||||||
|
})
|
||||||
|
const lifecycleOpts = {
|
||||||
|
wd: prefix,
|
||||||
|
depPath: manifest.name ?? path.relative(opts.dir, prefix),
|
||||||
|
stage: '(exec)',
|
||||||
|
} satisfies Partial<LifecycleMessage>
|
||||||
|
const logFn = (stdio: 'stdout' | 'stderr') => (data: unknown): void => {
|
||||||
|
for (const line of String(data).split('\n')) {
|
||||||
|
lifecycleLogger.debug({
|
||||||
|
...lifecycleOpts,
|
||||||
|
stdio,
|
||||||
|
line,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
child.stdout!.on('data', logFn('stdout'))
|
||||||
|
child.stderr!.on('data', logFn('stderr'))
|
||||||
|
void child.once('close', exitCode => {
|
||||||
|
lifecycleLogger.debug({
|
||||||
|
...lifecycleOpts,
|
||||||
|
exitCode: exitCode ?? 1,
|
||||||
|
optional: false,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
await child
|
||||||
|
} else {
|
||||||
|
await execa(cmd, args, {
|
||||||
|
cwd: prefix,
|
||||||
|
env,
|
||||||
|
stdio: 'inherit',
|
||||||
|
shell: opts.shellMode ?? false,
|
||||||
|
})
|
||||||
|
}
|
||||||
result[prefix].status = 'passed'
|
result[prefix].status = 'passed'
|
||||||
result[prefix].duration = getExecutionDuration(startTime)
|
result[prefix].duration = getExecutionDuration(startTime)
|
||||||
} catch (err: any) { // eslint-disable-line
|
} catch (err: any) { // eslint-disable-line
|
||||||
|
|||||||
121
exec/plugin-commands-script-runners/test/exec.logs.ts
Normal file
121
exec/plugin-commands-script-runners/test/exec.logs.ts
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
import fs from 'fs'
|
||||||
|
import path from 'path'
|
||||||
|
import { readProjects } from '@pnpm/filter-workspace-packages'
|
||||||
|
import { logger } from '@pnpm/logger'
|
||||||
|
import { exec } from '@pnpm/plugin-commands-script-runners'
|
||||||
|
import { preparePackages } from '@pnpm/prepare'
|
||||||
|
import writeYamlFile from 'write-yaml-file'
|
||||||
|
import { DEFAULT_OPTS } from './utils'
|
||||||
|
|
||||||
|
jest.mock('@pnpm/logger', () => {
|
||||||
|
const debug = jest.fn()
|
||||||
|
|
||||||
|
return {
|
||||||
|
logger: () => ({ debug }),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const lifecycleLogger = logger('lifecycle')
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
(lifecycleLogger.debug as jest.Mock).mockClear()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('pnpm exec --recursive prints prefixes', async () => {
|
||||||
|
preparePackages([
|
||||||
|
{
|
||||||
|
location: 'packages/foo',
|
||||||
|
package: { name: 'foo' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
location: 'packages/bar',
|
||||||
|
package: { name: 'bar' },
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
await writeYamlFile('pnpm-workspace.yaml', {
|
||||||
|
packages: ['packages/*'],
|
||||||
|
})
|
||||||
|
|
||||||
|
const { selectedProjectsGraph } = await readProjects(process.cwd(), [])
|
||||||
|
|
||||||
|
const scriptFile = path.resolve('script.js')
|
||||||
|
fs.writeFileSync(scriptFile, `
|
||||||
|
console.log('hello from stdout')
|
||||||
|
console.error('hello from stderr')
|
||||||
|
console.log('name is ' + require(require('path').resolve('package.json')).name)
|
||||||
|
`)
|
||||||
|
|
||||||
|
await exec.handler({
|
||||||
|
...DEFAULT_OPTS,
|
||||||
|
dir: process.cwd(),
|
||||||
|
recursive: true,
|
||||||
|
bail: true,
|
||||||
|
selectedProjectsGraph,
|
||||||
|
}, [process.execPath, scriptFile])
|
||||||
|
|
||||||
|
for (const name of ['foo', 'bar']) {
|
||||||
|
const loggerOpts = {
|
||||||
|
wd: path.resolve('packages', name),
|
||||||
|
depPath: name,
|
||||||
|
stage: '(exec)',
|
||||||
|
}
|
||||||
|
expect(lifecycleLogger.debug).toHaveBeenCalledWith({
|
||||||
|
...loggerOpts,
|
||||||
|
line: 'hello from stdout',
|
||||||
|
stdio: 'stdout',
|
||||||
|
})
|
||||||
|
expect(lifecycleLogger.debug).toHaveBeenCalledWith({
|
||||||
|
...loggerOpts,
|
||||||
|
line: 'hello from stderr',
|
||||||
|
stdio: 'stderr',
|
||||||
|
})
|
||||||
|
expect(lifecycleLogger.debug).toHaveBeenCalledWith({
|
||||||
|
...loggerOpts,
|
||||||
|
line: `name is ${name}`,
|
||||||
|
stdio: 'stdout',
|
||||||
|
})
|
||||||
|
expect(lifecycleLogger.debug).toHaveBeenCalledWith({
|
||||||
|
...loggerOpts,
|
||||||
|
optional: false,
|
||||||
|
exitCode: 0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test('pnpm exec --recursive --reporter-hide-prefix does not print prefixes', async () => {
|
||||||
|
preparePackages([
|
||||||
|
{
|
||||||
|
location: 'packages/foo',
|
||||||
|
package: { name: 'foo' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
location: 'packages/bar',
|
||||||
|
package: { name: 'bar' },
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
await writeYamlFile('pnpm-workspace.yaml', {
|
||||||
|
packages: ['packages/*'],
|
||||||
|
})
|
||||||
|
|
||||||
|
const { selectedProjectsGraph } = await readProjects(process.cwd(), [])
|
||||||
|
|
||||||
|
const scriptFile = path.resolve('script.js')
|
||||||
|
fs.writeFileSync(scriptFile, `
|
||||||
|
console.log('hello from stdout')
|
||||||
|
console.error('hello from stderr')
|
||||||
|
console.log('name is ' + require(require('path').resolve('package.json')).name)
|
||||||
|
`)
|
||||||
|
|
||||||
|
await exec.handler({
|
||||||
|
...DEFAULT_OPTS,
|
||||||
|
dir: process.cwd(),
|
||||||
|
recursive: true,
|
||||||
|
bail: true,
|
||||||
|
reporterHidePrefix: true,
|
||||||
|
selectedProjectsGraph,
|
||||||
|
}, [process.execPath, scriptFile])
|
||||||
|
|
||||||
|
expect(lifecycleLogger.debug).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
@@ -27,6 +27,9 @@
|
|||||||
{
|
{
|
||||||
"path": "../../config/config"
|
"path": "../../config/config"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"path": "../../packages/core-loggers"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"path": "../../packages/crypto.base32-hash"
|
"path": "../../packages/crypto.base32-hash"
|
||||||
},
|
},
|
||||||
|
|||||||
3
pnpm-lock.yaml
generated
3
pnpm-lock.yaml
generated
@@ -1381,6 +1381,9 @@ importers:
|
|||||||
'@pnpm/config':
|
'@pnpm/config':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../../config/config
|
version: link:../../config/config
|
||||||
|
'@pnpm/core-loggers':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../packages/core-loggers
|
||||||
'@pnpm/crypto.base32-hash':
|
'@pnpm/crypto.base32-hash':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../../packages/crypto.base32-hash
|
version: link:../../packages/crypto.base32-hash
|
||||||
|
|||||||
Reference in New Issue
Block a user