mirror of
https://github.com/pnpm/pnpm.git
synced 2025-12-23 23:29:17 -05:00
feat: add --report-summary option for pnpm exec and pnpm run (#6098)
close #6008
This commit is contained in:
9
.changeset/gold-chicken-sit.md
Normal file
9
.changeset/gold-chicken-sit.md
Normal file
@@ -0,0 +1,9 @@
|
||||
---
|
||||
"@pnpm/plugin-commands-installation": minor
|
||||
"@pnpm/plugin-commands-script-runners": minor
|
||||
"@pnpm/plugin-commands-rebuild": minor
|
||||
"@pnpm/cli-utils": minor
|
||||
"pnpm": minor
|
||||
---
|
||||
|
||||
Add --report-summary for pnpm exec and pnpm run [#6008](https://github.com/pnpm/pnpm/issues/6008)
|
||||
@@ -1,30 +1,33 @@
|
||||
import { PnpmError } from '@pnpm/error'
|
||||
|
||||
interface ActionFailure {
|
||||
status: 'failure'
|
||||
duration?: number
|
||||
prefix: string
|
||||
message: string
|
||||
error: Error
|
||||
}
|
||||
|
||||
export interface RecursiveSummary {
|
||||
fails: ActionFailure[]
|
||||
passes: number
|
||||
}
|
||||
export type RecursiveSummary = Record<string, {
|
||||
status: 'passed' | 'queued' | 'running'
|
||||
duration?: number
|
||||
} | ActionFailure>
|
||||
|
||||
class RecursiveFailError extends PnpmError {
|
||||
public readonly fails: ActionFailure[]
|
||||
public readonly failures: ActionFailure[]
|
||||
public readonly passes: number
|
||||
|
||||
constructor (command: string, recursiveSummary: RecursiveSummary) {
|
||||
super('RECURSIVE_FAIL', `"${command}" failed in ${recursiveSummary.fails.length} packages`)
|
||||
constructor (command: string, recursiveSummary: RecursiveSummary, failures: ActionFailure[]) {
|
||||
super('RECURSIVE_FAIL', `"${command}" failed in ${failures.length} packages`)
|
||||
|
||||
this.fails = recursiveSummary.fails
|
||||
this.passes = recursiveSummary.passes
|
||||
this.failures = failures
|
||||
this.passes = Object.values(recursiveSummary).filter(({ status }) => status === 'passed').length
|
||||
}
|
||||
}
|
||||
|
||||
export function throwOnCommandFail (command: string, recursiveSummary: RecursiveSummary) {
|
||||
if (recursiveSummary.fails.length > 0) {
|
||||
throw new RecursiveFailError(command, recursiveSummary)
|
||||
const failures = Object.values(recursiveSummary).filter(({ status }) => status === 'failure') as ActionFailure[]
|
||||
if (failures.length > 0) {
|
||||
throw new RecursiveFailError(command, recursiveSummary, failures)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,10 +70,7 @@ export async function recursiveRebuild (
|
||||
workspacePackages,
|
||||
}) as RebuildOptions
|
||||
|
||||
const result = {
|
||||
fails: [],
|
||||
passes: 0,
|
||||
} as RecursiveSummary
|
||||
const result: RecursiveSummary = {}
|
||||
|
||||
const memReadLocalConfig = mem(readLocalConfig)
|
||||
|
||||
@@ -120,6 +117,7 @@ export async function recursiveRebuild (
|
||||
if (opts.ignoredPackages?.has(rootDir)) {
|
||||
return
|
||||
}
|
||||
result[rootDir] = { status: 'running' }
|
||||
const localConfig = await memReadLocalConfig(rootDir)
|
||||
await rebuild(
|
||||
[
|
||||
@@ -140,16 +138,17 @@ export async function recursiveRebuild (
|
||||
},
|
||||
}
|
||||
)
|
||||
result.passes++
|
||||
result[rootDir].status = 'passed'
|
||||
} catch (err: any) { // eslint-disable-line
|
||||
logger.info(err)
|
||||
|
||||
if (!opts.bail) {
|
||||
result.fails.push({
|
||||
result[rootDir] = {
|
||||
status: 'failure',
|
||||
error: err,
|
||||
message: err.message,
|
||||
prefix: rootDir,
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -64,7 +64,8 @@
|
||||
"path-name": "^1.0.0",
|
||||
"ramda": "npm:@pnpm/ramda@0.28.1",
|
||||
"realpath-missing": "^1.1.0",
|
||||
"render-help": "^1.0.3"
|
||||
"render-help": "^1.0.3",
|
||||
"write-json-file": "^4.3.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@pnpm/logger": "^5.0.0"
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import path from 'path'
|
||||
import { docsUrl, RecursiveSummary, throwOnCommandFail } from '@pnpm/cli-utils'
|
||||
import { Config, types } from '@pnpm/config'
|
||||
import { makeNodeRequireOption } from '@pnpm/lifecycle'
|
||||
@@ -13,10 +14,12 @@ import { existsInDir } from './existsInDir'
|
||||
import { makeEnv } from './makeEnv'
|
||||
import {
|
||||
PARALLEL_OPTION_HELP,
|
||||
REPORT_SUMMARY_OPTION_HELP,
|
||||
RESUME_FROM_OPTION_HELP,
|
||||
shorthands as runShorthands,
|
||||
} from './run'
|
||||
import { PnpmError } from '@pnpm/error'
|
||||
import writeJsonFile from 'write-json-file'
|
||||
|
||||
export const shorthands = {
|
||||
parallel: runShorthands.parallel,
|
||||
@@ -36,6 +39,7 @@ export function rcOptionsTypes () {
|
||||
], types),
|
||||
'shell-mode': Boolean,
|
||||
'resume-from': String,
|
||||
'report-summary': Boolean,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,6 +73,7 @@ The shell should understand the -c switch on UNIX or /d /s /c on Windows.',
|
||||
shortAlias: '-c',
|
||||
},
|
||||
RESUME_FROM_OPTION_HELP,
|
||||
REPORT_SUMMARY_OPTION_HELP,
|
||||
],
|
||||
},
|
||||
],
|
||||
@@ -97,6 +102,24 @@ export function getResumedPackageChunks ({
|
||||
return chunks.slice(chunkPosition)
|
||||
}
|
||||
|
||||
export async function writeRecursiveSummary (opts: { dir: string, summary: RecursiveSummary }) {
|
||||
await writeJsonFile(path.join(opts.dir, 'pnpm-exec-summary.json'), {
|
||||
executionStatus: opts.summary,
|
||||
})
|
||||
}
|
||||
|
||||
export function createEmptyRecursiveSummary (chunks: string[][]) {
|
||||
return chunks.flat().reduce<RecursiveSummary>((acc, prefix) => {
|
||||
acc[prefix] = { status: 'queued' }
|
||||
return acc
|
||||
}, {})
|
||||
}
|
||||
|
||||
export function getExecutionDuration (start: [number, number]) {
|
||||
const end = process.hrtime(start)
|
||||
return (end[0] * 1e9 + end[1]) / 1e6
|
||||
}
|
||||
|
||||
export async function handler (
|
||||
opts: Required<Pick<Config, 'selectedProjectsGraph'>> & {
|
||||
bail?: boolean
|
||||
@@ -107,6 +130,7 @@ export async function handler (
|
||||
workspaceConcurrency?: number
|
||||
shellMode?: boolean
|
||||
resumeFrom?: string
|
||||
reportSummary?: boolean
|
||||
} & Pick<Config, 'extraBinPaths' | 'extraEnv' | 'lockfileDir' | 'dir' | 'userAgent' | 'recursive' | 'workspaceDir'>,
|
||||
params: string[]
|
||||
) {
|
||||
@@ -116,11 +140,6 @@ export async function handler (
|
||||
}
|
||||
const limitRun = pLimit(opts.workspaceConcurrency ?? 4)
|
||||
|
||||
const result = {
|
||||
fails: [],
|
||||
passes: 0,
|
||||
} as RecursiveSummary
|
||||
|
||||
let chunks!: string[][]
|
||||
if (opts.recursive) {
|
||||
chunks = opts.sort
|
||||
@@ -153,6 +172,7 @@ export async function handler (
|
||||
})
|
||||
}
|
||||
|
||||
const result = createEmptyRecursiveSummary(chunks)
|
||||
const existsPnp = existsInDir.bind(null, '.pnp.cjs')
|
||||
const workspacePnpPath = opts.workspaceDir && await existsPnp(opts.workspaceDir)
|
||||
|
||||
@@ -160,6 +180,8 @@ export async function handler (
|
||||
for (const chunk of chunks) {
|
||||
await Promise.all(chunk.map(async (prefix: string) =>
|
||||
limitRun(async () => {
|
||||
result[prefix].status = 'running'
|
||||
const startTime = process.hrtime()
|
||||
try {
|
||||
const pnpPath = workspacePnpPath ?? await existsPnp(prefix)
|
||||
const extraEnv = {
|
||||
@@ -183,7 +205,8 @@ export async function handler (
|
||||
stdio: 'inherit',
|
||||
shell: opts.shellMode ?? false,
|
||||
})
|
||||
result.passes++
|
||||
result[prefix].status = 'passed'
|
||||
result[prefix].duration = getExecutionDuration(startTime)
|
||||
} catch (err: any) { // eslint-disable-line
|
||||
if (!opts.recursive && typeof err.exitCode === 'number') {
|
||||
exitCode = err.exitCode
|
||||
@@ -191,12 +214,15 @@ export async function handler (
|
||||
}
|
||||
logger.info(err)
|
||||
|
||||
result[prefix] = {
|
||||
status: 'failure',
|
||||
duration: getExecutionDuration(startTime),
|
||||
error: err,
|
||||
message: err.message,
|
||||
prefix,
|
||||
}
|
||||
|
||||
if (!opts.bail) {
|
||||
result.fails.push({
|
||||
error: err,
|
||||
message: err.message,
|
||||
prefix,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
@@ -204,6 +230,10 @@ export async function handler (
|
||||
err['code'] = 'ERR_PNPM_RECURSIVE_EXEC_FIRST_FAIL'
|
||||
}
|
||||
err['prefix'] = prefix
|
||||
opts.reportSummary && await writeRecursiveSummary({
|
||||
dir: opts.lockfileDir ?? opts.dir,
|
||||
summary: result,
|
||||
})
|
||||
/* eslint-enable @typescript-eslint/dot-notation */
|
||||
throw err
|
||||
}
|
||||
@@ -211,6 +241,10 @@ export async function handler (
|
||||
)))
|
||||
}
|
||||
|
||||
opts.reportSummary && await writeRecursiveSummary({
|
||||
dir: opts.lockfileDir ?? opts.dir,
|
||||
summary: result,
|
||||
})
|
||||
throwOnCommandFail('pnpm recursive exec', result)
|
||||
return { exitCode }
|
||||
}
|
||||
|
||||
@@ -49,6 +49,11 @@ export const SEQUENTIAL_OPTION_HELP = {
|
||||
name: '--sequential',
|
||||
}
|
||||
|
||||
export const REPORT_SUMMARY_OPTION_HELP = {
|
||||
description: 'Save the execution results of every package to "pnpm-exec-summary.json". Useful to inspect the execution time and status of each package.',
|
||||
name: '--report-summary',
|
||||
}
|
||||
|
||||
export const shorthands = {
|
||||
parallel: [
|
||||
'--workspace-concurrency=Infinity',
|
||||
@@ -84,6 +89,7 @@ export function cliOptionsTypes () {
|
||||
recursive: Boolean,
|
||||
reverse: Boolean,
|
||||
'resume-from': String,
|
||||
'report-summary': Boolean,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,6 +128,7 @@ For options that may be used with `-r`, see "pnpm help recursive"',
|
||||
RESUME_FROM_OPTION_HELP,
|
||||
...UNIVERSAL_OPTIONS,
|
||||
SEQUENTIAL_OPTION_HELP,
|
||||
REPORT_SUMMARY_OPTION_HELP,
|
||||
],
|
||||
},
|
||||
FILTERING,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import path from 'path'
|
||||
import { RecursiveSummary, throwOnCommandFail } from '@pnpm/cli-utils'
|
||||
import { throwOnCommandFail } from '@pnpm/cli-utils'
|
||||
import { Config } from '@pnpm/config'
|
||||
import { PnpmError } from '@pnpm/error'
|
||||
import {
|
||||
@@ -11,7 +11,7 @@ import { sortPackages } from '@pnpm/sort-packages'
|
||||
import pLimit from 'p-limit'
|
||||
import realpathMissing from 'realpath-missing'
|
||||
import { existsInDir } from './existsInDir'
|
||||
import { getResumedPackageChunks } from './exec'
|
||||
import { createEmptyRecursiveSummary, getExecutionDuration, getResumedPackageChunks, writeRecursiveSummary } from './exec'
|
||||
import { runScript } from './run'
|
||||
import { tryBuildRegExpFromCommand } from './regexpCommand'
|
||||
import { PackageScripts } from '@pnpm/types'
|
||||
@@ -25,11 +25,12 @@ export type RecursiveRunOpts = Pick<Config,
|
||||
| 'scriptShell'
|
||||
| 'shellEmulator'
|
||||
| 'stream'
|
||||
> & Required<Pick<Config, 'allProjects' | 'selectedProjectsGraph' | 'workspaceDir'>> &
|
||||
> & Required<Pick<Config, 'allProjects' | 'selectedProjectsGraph' | 'workspaceDir' | 'dir'>> &
|
||||
Partial<Pick<Config, 'extraBinPaths' | 'extraEnv' | 'bail' | 'reverse' | 'sort' | 'workspaceConcurrency'>> &
|
||||
{
|
||||
ifPresent?: boolean
|
||||
resumeFrom?: string
|
||||
reportSummary?: boolean
|
||||
}
|
||||
|
||||
export async function runRecursive (
|
||||
@@ -55,11 +56,6 @@ export async function runRecursive (
|
||||
})
|
||||
}
|
||||
|
||||
const result = {
|
||||
fails: [],
|
||||
passes: 0,
|
||||
} as RecursiveSummary
|
||||
|
||||
const limitRun = pLimit(opts.workspaceConcurrency ?? 4)
|
||||
const stdio =
|
||||
!opts.stream &&
|
||||
@@ -82,6 +78,8 @@ export async function runRecursive (
|
||||
}
|
||||
}
|
||||
|
||||
const result = createEmptyRecursiveSummary(packageChunks)
|
||||
|
||||
for (const chunk of packageChunks) {
|
||||
const selectedScripts = chunk.map(prefix => {
|
||||
const pkg = opts.selectedProjectsGraph[prefix]
|
||||
@@ -100,6 +98,8 @@ export async function runRecursive (
|
||||
) {
|
||||
return
|
||||
}
|
||||
result[prefix].status = 'running'
|
||||
const startTime = process.hrtime()
|
||||
hasCommand++
|
||||
try {
|
||||
const lifecycleOpts: RunLifecycleHookOptions = {
|
||||
@@ -125,21 +125,29 @@ export async function runRecursive (
|
||||
|
||||
const _runScript = runScript.bind(null, { manifest: pkg.package.manifest, lifecycleOpts, runScriptOptions: { enablePrePostScripts: opts.enablePrePostScripts ?? false }, passedThruArgs })
|
||||
await _runScript(scriptName)
|
||||
result.passes++
|
||||
result[prefix].status = 'passed'
|
||||
result[prefix].duration = getExecutionDuration(startTime)
|
||||
} catch (err: any) { // eslint-disable-line
|
||||
logger.info(err)
|
||||
|
||||
result[prefix] = {
|
||||
status: 'failure',
|
||||
duration: getExecutionDuration(startTime),
|
||||
error: err,
|
||||
message: err.message,
|
||||
prefix,
|
||||
}
|
||||
|
||||
if (!opts.bail) {
|
||||
result.fails.push({
|
||||
error: err,
|
||||
message: err.message,
|
||||
prefix,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
err['code'] = 'ERR_PNPM_RECURSIVE_RUN_FIRST_FAIL'
|
||||
err['prefix'] = prefix
|
||||
opts.reportSummary && await writeRecursiveSummary({
|
||||
dir: opts.workspaceDir ?? opts.dir,
|
||||
summary: result,
|
||||
})
|
||||
/* eslint-enable @typescript-eslint/dot-notation */
|
||||
throw err
|
||||
}
|
||||
@@ -158,7 +166,10 @@ export async function runRecursive (
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
opts.reportSummary && await writeRecursiveSummary({
|
||||
dir: opts.workspaceDir ?? opts.dir,
|
||||
summary: result,
|
||||
})
|
||||
throwOnCommandFail('pnpm recursive run', result)
|
||||
}
|
||||
|
||||
|
||||
@@ -696,3 +696,118 @@ test('pnpm exec in directory with path delimiter', async () => {
|
||||
}
|
||||
expect(error).toBeUndefined()
|
||||
})
|
||||
|
||||
test('pnpm recursive exec report summary', async () => {
|
||||
preparePackages([
|
||||
{
|
||||
name: 'project-1',
|
||||
version: '1.0.0',
|
||||
scripts: {
|
||||
build: 'node -e "setTimeout(() => console.log(\'project-1\'), 1000)"',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'project-2',
|
||||
version: '1.0.0',
|
||||
scripts: {
|
||||
build: 'exit 1',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'project-3',
|
||||
version: '1.0.0',
|
||||
scripts: {
|
||||
build: 'node -e "setTimeout(() => console.log(\'project-3\'), 1000)"',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'project-4',
|
||||
version: '1.0.0',
|
||||
scripts: {
|
||||
build: 'exit 1',
|
||||
},
|
||||
},
|
||||
])
|
||||
const { selectedProjectsGraph } = await readProjects(process.cwd(), [])
|
||||
let error
|
||||
try {
|
||||
await exec.handler({
|
||||
...DEFAULT_OPTS,
|
||||
dir: process.cwd(),
|
||||
selectedProjectsGraph,
|
||||
recursive: true,
|
||||
reportSummary: true,
|
||||
workspaceConcurrency: 3,
|
||||
}, ['npm', 'run', 'build'])
|
||||
} catch (err: any) { // eslint-disable-line
|
||||
error = err
|
||||
}
|
||||
expect(error.code).toBe('ERR_PNPM_RECURSIVE_FAIL')
|
||||
|
||||
const { default: { executionStatus } } = (await import(path.resolve('pnpm-exec-summary.json')))
|
||||
expect(executionStatus[path.resolve('project-1')].status).toBe('passed')
|
||||
expect(executionStatus[path.resolve('project-1')].duration).not.toBeFalsy()
|
||||
expect(executionStatus[path.resolve('project-2')].status).toBe('failure')
|
||||
expect(executionStatus[path.resolve('project-2')].duration).not.toBeFalsy()
|
||||
expect(executionStatus[path.resolve('project-3')].status).toBe('passed')
|
||||
expect(executionStatus[path.resolve('project-3')].duration).not.toBeFalsy()
|
||||
expect(executionStatus[path.resolve('project-4')].status).toBe('failure')
|
||||
expect(executionStatus[path.resolve('project-4')].duration).not.toBeFalsy()
|
||||
})
|
||||
|
||||
test('pnpm recursive exec report summary with --bail', async () => {
|
||||
preparePackages([
|
||||
{
|
||||
name: 'project-1',
|
||||
version: '1.0.0',
|
||||
scripts: {
|
||||
build: 'node -e "setTimeout(() => console.log(\'project-1\'), 1000)"',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'project-2',
|
||||
version: '1.0.0',
|
||||
scripts: {
|
||||
build: 'exit 1',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'project-3',
|
||||
version: '1.0.0',
|
||||
scripts: {
|
||||
build: 'node -e "setTimeout(() => console.log(\'project-3\'), 1000)"',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'project-4',
|
||||
version: '1.0.0',
|
||||
scripts: {
|
||||
build: 'exit 1',
|
||||
},
|
||||
},
|
||||
])
|
||||
const { selectedProjectsGraph } = await readProjects(process.cwd(), [])
|
||||
let error
|
||||
try {
|
||||
await exec.handler({
|
||||
...DEFAULT_OPTS,
|
||||
dir: process.cwd(),
|
||||
selectedProjectsGraph,
|
||||
recursive: true,
|
||||
reportSummary: true,
|
||||
bail: true,
|
||||
workspaceConcurrency: 3,
|
||||
}, ['npm', 'run', 'build'])
|
||||
} catch (err: any) { // eslint-disable-line
|
||||
error = err
|
||||
}
|
||||
expect(error.code).toBe('ERR_PNPM_RECURSIVE_EXEC_FIRST_FAIL')
|
||||
|
||||
const { default: { executionStatus } } = (await import(path.resolve('pnpm-exec-summary.json')))
|
||||
|
||||
expect(executionStatus[path.resolve('project-1')].status).toBe('running')
|
||||
expect(executionStatus[path.resolve('project-2')].status).toBe('failure')
|
||||
expect(executionStatus[path.resolve('project-2')].duration).not.toBeFalsy()
|
||||
expect(executionStatus[path.resolve('project-3')].status).toBe('running')
|
||||
expect(executionStatus[path.resolve('project-4')].status).toBe('queued')
|
||||
})
|
||||
|
||||
@@ -977,3 +977,117 @@ test('pnpm run with RegExp script selector should work on recursive', async () =
|
||||
expect(await fs.readFile('output-lint-3-b.txt', { encoding: 'utf-8' })).toEqual('3-b')
|
||||
expect(await fs.readFile('output-lint-3-c.txt', { encoding: 'utf-8' })).toEqual('3-c')
|
||||
})
|
||||
|
||||
test('pnpm recursive run report summary', async () => {
|
||||
preparePackages([
|
||||
{
|
||||
name: 'project-1',
|
||||
version: '1.0.0',
|
||||
scripts: {
|
||||
build: 'node -e "setTimeout(() => console.log(\'project-1\'), 1000)"',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'project-2',
|
||||
version: '1.0.0',
|
||||
scripts: {
|
||||
build: 'exit 1',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'project-3',
|
||||
version: '1.0.0',
|
||||
scripts: {
|
||||
build: 'node -e "setTimeout(() => console.log(\'project-3\'), 1000)"',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'project-4',
|
||||
version: '1.0.0',
|
||||
scripts: {
|
||||
build: 'exit 1',
|
||||
},
|
||||
},
|
||||
])
|
||||
let error
|
||||
try {
|
||||
await run.handler({
|
||||
...DEFAULT_OPTS,
|
||||
...await readProjects(process.cwd(), [{ namePattern: '*' }]),
|
||||
dir: process.cwd(),
|
||||
recursive: true,
|
||||
reportSummary: true,
|
||||
workspaceDir: process.cwd(),
|
||||
}, ['build'])
|
||||
} catch (err: any) { // eslint-disable-line
|
||||
error = err
|
||||
}
|
||||
expect(error.code).toBe('ERR_PNPM_RECURSIVE_FAIL')
|
||||
|
||||
const { default: { executionStatus } } = (await import(path.resolve('pnpm-exec-summary.json')))
|
||||
expect(executionStatus[path.resolve('project-1')].status).toBe('passed')
|
||||
expect(executionStatus[path.resolve('project-1')].duration).not.toBeFalsy()
|
||||
expect(executionStatus[path.resolve('project-2')].status).toBe('failure')
|
||||
expect(executionStatus[path.resolve('project-2')].duration).not.toBeFalsy()
|
||||
expect(executionStatus[path.resolve('project-3')].status).toBe('passed')
|
||||
expect(executionStatus[path.resolve('project-3')].duration).not.toBeFalsy()
|
||||
expect(executionStatus[path.resolve('project-4')].status).toBe('failure')
|
||||
expect(executionStatus[path.resolve('project-4')].duration).not.toBeFalsy()
|
||||
})
|
||||
|
||||
test('pnpm recursive run report summary with --bail', async () => {
|
||||
preparePackages([
|
||||
{
|
||||
name: 'project-1',
|
||||
version: '1.0.0',
|
||||
scripts: {
|
||||
build: 'node -e "setTimeout(() => console.log(\'project-1\'), 1000)"',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'project-2',
|
||||
version: '1.0.0',
|
||||
scripts: {
|
||||
build: 'exit 1',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'project-3',
|
||||
version: '1.0.0',
|
||||
scripts: {
|
||||
build: 'node -e "setTimeout(() => console.log(\'project-3\'), 1000)"',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'project-4',
|
||||
version: '1.0.0',
|
||||
scripts: {
|
||||
build: 'exit 1',
|
||||
},
|
||||
},
|
||||
])
|
||||
let error
|
||||
try {
|
||||
await run.handler({
|
||||
...DEFAULT_OPTS,
|
||||
...await readProjects(process.cwd(), [{ namePattern: '*' }]),
|
||||
dir: process.cwd(),
|
||||
recursive: true,
|
||||
reportSummary: true,
|
||||
workspaceDir: process.cwd(),
|
||||
bail: true,
|
||||
workspaceConcurrency: 3,
|
||||
}, ['build'])
|
||||
} catch (err: any) { // eslint-disable-line
|
||||
error = err
|
||||
}
|
||||
expect(error.code).toBe('ERR_PNPM_RECURSIVE_RUN_FIRST_FAIL')
|
||||
|
||||
const { default: { executionStatus } } = (await import(path.resolve('pnpm-exec-summary.json')))
|
||||
|
||||
expect(executionStatus[path.resolve('project-1')].status).toBe('running')
|
||||
expect(executionStatus[path.resolve('project-2')].status).toBe('failure')
|
||||
expect(executionStatus[path.resolve('project-2')].duration).not.toBeFalsy()
|
||||
expect(executionStatus[path.resolve('project-3')].status).toBe('running')
|
||||
expect(executionStatus[path.resolve('project-4')].status).toBe('queued')
|
||||
})
|
||||
|
||||
@@ -135,10 +135,7 @@ export async function recursive (
|
||||
forceShamefullyHoist: typeof opts.rawLocalConfig?.['shamefully-hoist'] !== 'undefined',
|
||||
}) as InstallOptions
|
||||
|
||||
const result = {
|
||||
fails: [],
|
||||
passes: 0,
|
||||
} as RecursiveSummary
|
||||
const result: RecursiveSummary = {}
|
||||
|
||||
const memReadLocalConfig = mem(readLocalConfig)
|
||||
|
||||
@@ -297,7 +294,7 @@ export async function recursive (
|
||||
if (opts.ignoredPackages?.has(rootDir)) {
|
||||
return
|
||||
}
|
||||
|
||||
result[rootDir] = { status: 'running' }
|
||||
const { manifest, writeProjectManifest } = manifestsByPath[rootDir]
|
||||
let currentInput = [...params]
|
||||
if (updateMatch != null) {
|
||||
@@ -369,16 +366,17 @@ export async function recursive (
|
||||
if (opts.save !== false) {
|
||||
await writeProjectManifest(newManifest)
|
||||
}
|
||||
result.passes++
|
||||
result[rootDir].status = 'passed'
|
||||
} catch (err: any) { // eslint-disable-line
|
||||
logger.info(err)
|
||||
|
||||
if (!opts.bail) {
|
||||
result.fails.push({
|
||||
result[rootDir] = {
|
||||
status: 'failure',
|
||||
error: err,
|
||||
message: err.message,
|
||||
prefix: rootDir,
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -404,7 +402,7 @@ export async function recursive (
|
||||
|
||||
throwOnFail(result)
|
||||
|
||||
if (!result.passes && cmdFullName === 'update' && opts.depth === 0) {
|
||||
if (!Object.values(result).filter(({ status }) => status === 'passed').length && cmdFullName === 'update' && opts.depth === 0) {
|
||||
throw new PnpmError('NO_PACKAGE_IN_DEPENDENCIES',
|
||||
'None of the specified packages were found in the dependencies of any of the projects.')
|
||||
}
|
||||
|
||||
17
pnpm-lock.yaml
generated
17
pnpm-lock.yaml
generated
@@ -1194,6 +1194,9 @@ importers:
|
||||
render-help:
|
||||
specifier: ^1.0.3
|
||||
version: 1.0.3
|
||||
write-json-file:
|
||||
specifier: ^4.3.0
|
||||
version: 4.3.0
|
||||
devDependencies:
|
||||
'@pnpm/filter-workspace-packages':
|
||||
specifier: workspace:*
|
||||
@@ -7975,7 +7978,7 @@ packages:
|
||||
'@pnpm/find-workspace-dir': 5.0.1
|
||||
'@pnpm/find-workspace-packages': 5.0.33(@pnpm/logger@5.0.0)(@yarnpkg/core@4.0.0-rc.14)(typanion@3.12.1)
|
||||
'@pnpm/logger': 5.0.0
|
||||
'@pnpm/types': 8.10.0
|
||||
'@pnpm/types': 8.9.0
|
||||
'@yarnpkg/core': 4.0.0-rc.14(typanion@3.12.1)
|
||||
load-json-file: 7.0.1
|
||||
meow: 10.1.5
|
||||
@@ -8524,7 +8527,6 @@ packages:
|
||||
/@pnpm/types@8.9.0:
|
||||
resolution: {integrity: sha512-3MYHYm8epnciApn6w5Fzx6sepawmsNU7l6lvIq+ER22/DPSrr83YMhU/EQWnf4lORn2YyiXFj0FJSyJzEtIGmw==}
|
||||
engines: {node: '>=14.6'}
|
||||
dev: false
|
||||
|
||||
/@pnpm/util.lex-comparator@1.0.0:
|
||||
resolution: {integrity: sha512-3aBQPHntVgk5AweBWZn+1I/fqZ9krK/w01197aYVkAJQGftb+BVWgEepxY5GChjSW12j52XX+CmfynYZ/p0DFQ==}
|
||||
@@ -8657,7 +8659,7 @@ packages:
|
||||
/@types/byline@4.2.33:
|
||||
resolution: {integrity: sha512-LJYez7wrWcJQQDknqZtrZuExMGP0IXmPl1rOOGDqLbu+H7UNNRfKNuSxCBcQMLH1EfjeWidLedC/hCc5dDfBog==}
|
||||
dependencies:
|
||||
'@types/node': 18.13.0
|
||||
'@types/node': 14.18.36
|
||||
dev: true
|
||||
|
||||
/@types/cacheable-request@6.0.3:
|
||||
@@ -8665,7 +8667,7 @@ packages:
|
||||
dependencies:
|
||||
'@types/http-cache-semantics': 4.0.1
|
||||
'@types/keyv': 3.1.4
|
||||
'@types/node': 18.13.0
|
||||
'@types/node': 14.18.36
|
||||
'@types/responselike': 1.0.0
|
||||
|
||||
/@types/concat-stream@2.0.0:
|
||||
@@ -8693,7 +8695,7 @@ packages:
|
||||
resolution: {integrity: sha512-8bVUjXZvJacUFkJXHdyZ9iH1Eaj5V7I8c4NdH5sQJsdXkqT4CA5Dhb4yb4VE/3asyx4L9ayZr1NIhTsWHczmMw==}
|
||||
dependencies:
|
||||
'@types/minimatch': 5.1.2
|
||||
'@types/node': 18.13.0
|
||||
'@types/node': 14.18.36
|
||||
dev: true
|
||||
|
||||
/@types/graceful-fs@4.1.6:
|
||||
@@ -8767,7 +8769,7 @@ packages:
|
||||
/@types/keyv@3.1.4:
|
||||
resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==}
|
||||
dependencies:
|
||||
'@types/node': 18.13.0
|
||||
'@types/node': 14.18.36
|
||||
|
||||
/@types/lodash@4.14.181:
|
||||
resolution: {integrity: sha512-n3tyKthHJbkiWhDZs3DkhkCzt2MexYHXlX0td5iMplyfwketaOeKboEVBqzceH7juqvEg3q5oUoBFxSLu7zFag==}
|
||||
@@ -8810,7 +8812,6 @@ packages:
|
||||
|
||||
/@types/node@14.18.36:
|
||||
resolution: {integrity: sha512-FXKWbsJ6a1hIrRxv+FoukuHnGTgEzKYGi7kilfMae96AL9UNkPFNWJEEYWzdRI9ooIkbr4AKldyuSTLql06vLQ==}
|
||||
dev: true
|
||||
|
||||
/@types/node@18.13.0:
|
||||
resolution: {integrity: sha512-gC3TazRzGoOnoKAhUx+Q0t8S9Tzs74z7m0ipwGpSqQrleP14hKxP4/JUeEQcD3W1/aIpnWl8pHowI7WokuZpXg==}
|
||||
@@ -8847,7 +8848,7 @@ packages:
|
||||
/@types/responselike@1.0.0:
|
||||
resolution: {integrity: sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==}
|
||||
dependencies:
|
||||
'@types/node': 18.13.0
|
||||
'@types/node': 14.18.36
|
||||
|
||||
/@types/retry@0.12.2:
|
||||
resolution: {integrity: sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==}
|
||||
|
||||
Reference in New Issue
Block a user