pnpm outdated|audit should exit with exit code 1 when issues are found

`pnpm outdated` should exit with 1, when outdated found

`pnpm audit` should exit with 1 when issues are found

ref #2706
PR (#2718)
This commit is contained in:
Zoltan Kochan
2020-07-27 23:12:42 +03:00
committed by GitHub
parent 873f08b045
commit a64b7250c2
11 changed files with 117 additions and 53 deletions

View File

@@ -0,0 +1,7 @@
---
"@pnpm/plugin-commands-outdated": major
---
Return `Promise<{ output: string, exitCode: number }>` instead of `Promise<string>`.
`exitCode` is `1` when there are any outdated packages in the dependencies.

View File

@@ -0,0 +1,7 @@
---
"@pnpm/plugin-commands-audit": major
---
Return `Promise<{ output: string, exitCode: number }>` instead of `Promise<string>`.
`exitCode` is `1` when there are any packages with vulnerabilities in the dependencies.

View File

@@ -110,8 +110,14 @@ export async function handler (
retries: opts.fetchRetries,
},
})
const vulnerabilities = auditReport.metadata.vulnerabilities
const totalVulnerabilityCount = Object.values(vulnerabilities).reduce((sum, vulnerabilitiesCount) => sum + vulnerabilitiesCount, 0)
const exitCode = totalVulnerabilityCount > 0 ? 1 : 0
if (opts.json) {
return JSON.stringify(auditReport, null, 2)
return {
exitCode,
output: JSON.stringify(auditReport, null, 2),
}
}
let output = ''
@@ -128,11 +134,13 @@ export async function handler (
['More info', advisory.url],
], TABLE_OPTIONS)
}
return `${output}${reportSummary(auditReport.metadata.vulnerabilities)}`
return {
exitCode,
output: `${output}${reportSummary(auditReport.metadata.vulnerabilities, totalVulnerabilityCount)}`,
}
}
function reportSummary (vulnerabilities: AuditVulnerabilityCounts) {
const totalVulnerabilityCount = Object.values(vulnerabilities).reduce((sum, vulnerabilitiesCount) => sum + vulnerabilitiesCount, 0)
function reportSummary (vulnerabilities: AuditVulnerabilityCounts, totalVulnerabilityCount: number) {
if (totalVulnerabilityCount === 0) return 'No known vulnerabilities found'
return `${chalk.red(totalVulnerabilityCount)} vulnerabilities found\nSeverity: ${
Object.entries(vulnerabilities)

View File

@@ -4,7 +4,7 @@ import stripAnsi = require('strip-ansi')
import test = require('tape')
test('audit', async (t) => {
const output = await audit.handler({
const { output, exitCode } = await audit.handler({
dir: path.join(__dirname, 'packages/has-vulnerabilities'),
include: {
dependencies: true,
@@ -15,6 +15,7 @@ test('audit', async (t) => {
default: 'https://registry.npmjs.org/',
},
})
t.equal(exitCode, 1)
t.equal(
stripAnsi(output),
`┌─────────────────────┬───────────────────────────────────┐
@@ -133,7 +134,7 @@ Severity: 6 low | 3 moderate | 2 high`)
})
test('audit --dev', async (t) => {
const output = await audit.handler({
const { output, exitCode } = await audit.handler({
dir: path.join(__dirname, 'packages/has-vulnerabilities'),
include: {
dependencies: false,
@@ -145,6 +146,7 @@ test('audit --dev', async (t) => {
},
})
t.equal(exitCode, 1)
t.equal(
stripAnsi(output),
`┌─────────────────────┬──────────────────────────────────┐
@@ -164,7 +166,7 @@ Severity: 1 moderate`)
})
test('audit --audit-level', async (t) => {
const output = await audit.handler({
const { output, exitCode } = await audit.handler({
auditLevel: 'moderate',
dir: path.join(__dirname, 'packages/has-vulnerabilities'),
include: {
@@ -240,7 +242,7 @@ Severity: 6 low | 3 moderate | 2 high`)
})
test('audit: no vulnerabilities', async (t) => {
const output = await audit.handler({
const { output, exitCode } = await audit.handler({
dir: path.join(__dirname, '../../../fixtures/has-outdated-deps'),
include: {
dependencies: true,
@@ -253,11 +255,12 @@ test('audit: no vulnerabilities', async (t) => {
})
t.equal(stripAnsi(output), 'No known vulnerabilities found')
t.equal(exitCode, 0)
t.end()
})
test('audit --json', async (t) => {
const output = await audit.handler({
const { output, exitCode } = await audit.handler({
dir: path.join(__dirname, 'packages/has-vulnerabilities'),
include: {
dependencies: true,
@@ -272,5 +275,6 @@ test('audit --json', async (t) => {
const json = JSON.parse(output)
t.ok(json.metadata)
t.equal(exitCode, 1)
t.end()
})

View File

@@ -173,12 +173,12 @@ export async function handler (
]
const [ outdatedPackages ] = (await outdatedDepsOfProjects(packages, params, { ...opts, include }))
if (!outdatedPackages.length) return ''
if (!outdatedPackages.length) return { output: '', exitCode: 0 }
if (opts.table !== false) {
return renderOutdatedTable(outdatedPackages, opts)
return { output: renderOutdatedTable(outdatedPackages, opts), exitCode: 1 }
} else {
return renderOutdatedList(outdatedPackages, opts)
return { output: renderOutdatedList(outdatedPackages, opts), exitCode: 1 }
}
}

View File

@@ -62,12 +62,12 @@ export default async (
})
}
if (R.isEmpty(outdatedMap)) return ''
if (R.isEmpty(outdatedMap)) return { output: '', exitCode: 0 }
if (opts.table !== false) {
return renderOutdatedTable(outdatedMap, opts)
return { output: renderOutdatedTable(outdatedMap, opts), exitCode: 1 }
}
return renderOutdatedList(outdatedMap, opts)
return { output: renderOutdatedList(outdatedMap, opts), exitCode: 1 }
}
function renderOutdatedTable (outdatedMap: Record<string, OutdatedInWorkspace>, opts: { long?: boolean }) {

View File

@@ -42,12 +42,13 @@ test('pnpm outdated: show details', async (t) => {
await fs.copyFile(path.join(hasOutdatedDepsFixture, 'node_modules/.pnpm/lock.yaml'), path.resolve('node_modules/.pnpm/lock.yaml'))
await fs.copyFile(path.join(hasOutdatedDepsFixture, 'package.json'), path.resolve('package.json'))
const output = await outdated.handler({
const { output, exitCode } = await outdated.handler({
...OUTDATED_OPTIONS,
dir: process.cwd(),
long: true,
})
t.equal(exitCode, 1)
t.equal(stripAnsi(output), `\
┌───────────────────┬─────────┬────────────┬─────────────────────────────────────────────┐
│ Package │ Current │ Latest │ Details │
@@ -73,12 +74,13 @@ test('pnpm outdated: showing only prod or dev dependencies', async (t) => {
await fs.copyFile(path.join(hasOutdatedDepsFixture, 'package.json'), path.resolve('package.json'))
{
const output = await outdated.handler({
const { output, exitCode } = await outdated.handler({
...OUTDATED_OPTIONS,
dir: process.cwd(),
production: false,
})
t.equal(exitCode, 1)
t.equal(stripAnsi(output), `\
┌───────────────────┬─────────┬────────┐
│ Package │ Current │ Latest │
@@ -89,12 +91,13 @@ test('pnpm outdated: showing only prod or dev dependencies', async (t) => {
}
{
const output = await outdated.handler({
const { output, exitCode } = await outdated.handler({
...OUTDATED_OPTIONS,
dev: false,
dir: process.cwd(),
})
t.equal(exitCode, 1)
t.equal(stripAnsi(output), `\
┌─────────────┬─────────┬────────────┐
│ Package │ Current │ Latest │
@@ -117,12 +120,13 @@ test('pnpm outdated: no table', async (t) => {
await fs.copyFile(path.join(hasOutdatedDepsFixture, 'package.json'), path.resolve('package.json'))
{
const output = await outdated.handler({
const { output, exitCode } = await outdated.handler({
...OUTDATED_OPTIONS,
dir: process.cwd(),
table: false,
})
t.equal(exitCode, 1)
t.equal(stripAnsi(output), `deprecated
1.0.0 => Deprecated
@@ -135,13 +139,14 @@ is-positive (dev)
}
{
const output = await outdated.handler({
const { output, exitCode } = await outdated.handler({
...OUTDATED_OPTIONS,
dir: process.cwd(),
long: true,
table: false,
})
t.equal(exitCode, 1)
t.equal(stripAnsi(output), `deprecated
1.0.0 => Deprecated
This package is deprecated. Lorem ipsum
@@ -168,11 +173,12 @@ test('pnpm outdated: only current lockfile is available', async (t) => {
await fs.copyFile(path.join(hasOutdatedDepsFixture, 'node_modules/.pnpm/lock.yaml'), path.resolve('node_modules/.pnpm/lock.yaml'))
await fs.copyFile(path.join(hasOutdatedDepsFixture, 'package.json'), path.resolve('package.json'))
const output = await outdated.handler({
const { output, exitCode } = await outdated.handler({
...OUTDATED_OPTIONS,
dir: process.cwd(),
})
t.equal(exitCode, 1)
t.equal(stripAnsi(output), `\
┌───────────────────┬─────────┬────────────┐
│ Package │ Current │ Latest │
@@ -193,11 +199,12 @@ test('pnpm outdated: only wanted lockfile is available', async (t) => {
await fs.copyFile(path.join(hasOutdatedDepsFixture, 'pnpm-lock.yaml'), path.resolve('pnpm-lock.yaml'))
await fs.copyFile(path.join(hasOutdatedDepsFixture, 'package.json'), path.resolve('package.json'))
const output = await outdated.handler({
const { output, exitCode } = await outdated.handler({
...OUTDATED_OPTIONS,
dir: process.cwd(),
})
t.equal(exitCode, 1)
t.equal(stripAnsi(output), `\
┌───────────────────┬────────────────────────┬────────────┐
│ Package │ Current │ Latest │
@@ -215,24 +222,26 @@ test('pnpm outdated: only wanted lockfile is available', async (t) => {
test('pnpm outdated does not print anything when all is good', async (t) => {
process.chdir(hasNotOutdatedDepsFixture)
const output = await outdated.handler({
const { output, exitCode } = await outdated.handler({
...OUTDATED_OPTIONS,
dir: process.cwd(),
})
t.equal(output, '')
t.equal(exitCode, 0)
t.end()
})
test('pnpm outdated with external lockfile', async (t) => {
process.chdir(hasOutdatedDepsFixtureAndExternalLockfile)
const output = await outdated.handler({
const { output, exitCode } = await outdated.handler({
...OUTDATED_OPTIONS,
dir: process.cwd(),
lockfileDir: path.resolve('..'),
})
t.equal(exitCode, 1)
t.equal(stripAnsi(output), `\
┌─────────────┬──────────────────────┬────────┐
│ Package │ Current │ Latest │
@@ -250,7 +259,7 @@ test(`pnpm outdated should fail when there is no ${WANTED_LOCKFILE} file in the
let err!: PnpmError
try {
const output = await outdated.handler({
await outdated.handler({
...OUTDATED_OPTIONS,
dir: process.cwd(),
})
@@ -264,22 +273,24 @@ test(`pnpm outdated should fail when there is no ${WANTED_LOCKFILE} file in the
test(`pnpm outdated should return empty when there is no lockfile and no dependencies`, async (t) => {
prepare(t)
const output = await outdated.handler({
const { output, exitCode } = await outdated.handler({
...OUTDATED_OPTIONS,
dir: process.cwd(),
})
t.equal(output, '')
t.equal(exitCode, 0)
t.end()
})
test('pnpm outdated: print only compatible versions', async (t) => {
const output = await outdated.handler({
const { output, exitCode } = await outdated.handler({
...OUTDATED_OPTIONS,
compatible: true,
dir: hasMajorOutdatedDepsFixture,
})
t.equal(exitCode, 1)
t.equal(stripAnsi(output), `\
┌─────────────┬─────────┬────────┐
│ Package │ Current │ Latest │

View File

@@ -49,7 +49,7 @@ test('pnpm recursive outdated', async (t) => {
})
{
const output = await outdated.handler({
const { output, exitCode } = await outdated.handler({
...DEFAULT_OPTS,
allProjects,
dir: process.cwd(),
@@ -57,6 +57,7 @@ test('pnpm recursive outdated', async (t) => {
selectedProjectsGraph,
})
t.equal(exitCode, 1)
t.equal(stripAnsi(output as unknown as string), `\
┌───────────────────┬─────────┬────────┬──────────────────────┐
│ Package │ Current │ Latest │ Dependents │
@@ -73,7 +74,7 @@ test('pnpm recursive outdated', async (t) => {
}
{
const output = await outdated.handler({
const { output, exitCode } = await outdated.handler({
...DEFAULT_OPTS,
allProjects,
dir: process.cwd(),
@@ -82,6 +83,7 @@ test('pnpm recursive outdated', async (t) => {
selectedProjectsGraph,
})
t.equal(exitCode, 1)
t.equal(stripAnsi(output as unknown as string), `\
┌───────────────────┬─────────┬────────┬────────────┐
│ Package │ Current │ Latest │ Dependents │
@@ -92,7 +94,7 @@ test('pnpm recursive outdated', async (t) => {
}
{
const output = await outdated.handler({
const { output, exitCode } = await outdated.handler({
...DEFAULT_OPTS,
allProjects,
dir: process.cwd(),
@@ -101,6 +103,7 @@ test('pnpm recursive outdated', async (t) => {
selectedProjectsGraph,
})
t.equal(exitCode, 1)
t.equal(stripAnsi(output as unknown as string), `\
┌───────────────────┬─────────┬────────┬──────────────────────┬─────────────────────────────────────────────┐
│ Package │ Current │ Latest │ Dependents │ Details │
@@ -117,7 +120,7 @@ test('pnpm recursive outdated', async (t) => {
}
{
const output = await outdated.handler({
const { output, exitCode } = await outdated.handler({
...DEFAULT_OPTS,
allProjects,
dir: process.cwd(),
@@ -126,6 +129,7 @@ test('pnpm recursive outdated', async (t) => {
table: false,
})
t.equal(exitCode, 1)
t.equal(stripAnsi(output as unknown as string), `\
is-negative
1.0.0 => 2.1.0
@@ -146,7 +150,7 @@ Dependent: project-2
}
{
const output = await outdated.handler({
const { output, exitCode } = await outdated.handler({
...DEFAULT_OPTS,
allProjects,
dir: process.cwd(),
@@ -156,6 +160,7 @@ Dependent: project-2
table: false,
})
t.equal(exitCode, 1)
t.equal(stripAnsi(output as unknown as string), `\
is-negative
1.0.0 => 2.1.0
@@ -180,7 +185,7 @@ https://github.com/kevva/is-positive#readme
}
{
const output = await outdated.handler({
const { output, exitCode } = await outdated.handler({
...DEFAULT_OPTS,
allProjects,
dir: process.cwd(),
@@ -188,6 +193,7 @@ https://github.com/kevva/is-positive#readme
selectedProjectsGraph,
}, ['is-positive'])
t.equal(exitCode, 1)
t.equal(stripAnsi(output as unknown as string), `\
┌─────────────┬─────────┬────────┬──────────────────────┐
│ Package │ Current │ Latest │ Dependents │
@@ -243,7 +249,7 @@ test('pnpm recursive outdated in workspace with shared lockfile', async (t) => {
})
{
const output = await outdated.handler({
const { output, exitCode } = await outdated.handler({
...DEFAULT_OPTS,
allProjects,
dir: process.cwd(),
@@ -251,6 +257,7 @@ test('pnpm recursive outdated in workspace with shared lockfile', async (t) => {
selectedProjectsGraph,
})
t.equal(exitCode, 1)
t.equal(stripAnsi(output as unknown as string), `\
┌───────────────────┬─────────┬────────┬──────────────────────┐
│ Package │ Current │ Latest │ Dependents │
@@ -265,7 +272,7 @@ test('pnpm recursive outdated in workspace with shared lockfile', async (t) => {
}
{
const output = await outdated.handler({
const { output, exitCode } = await outdated.handler({
...DEFAULT_OPTS,
allProjects,
dir: process.cwd(),
@@ -274,6 +281,7 @@ test('pnpm recursive outdated in workspace with shared lockfile', async (t) => {
selectedProjectsGraph,
})
t.equal(exitCode, 1)
t.equal(stripAnsi(output as unknown as string), `\
┌───────────────────┬─────────┬────────┬────────────┐
│ Package │ Current │ Latest │ Dependents │
@@ -284,7 +292,7 @@ test('pnpm recursive outdated in workspace with shared lockfile', async (t) => {
}
{
const output = await outdated.handler({
const { output, exitCode } = await outdated.handler({
...DEFAULT_OPTS,
allProjects,
dir: process.cwd(),
@@ -292,6 +300,7 @@ test('pnpm recursive outdated in workspace with shared lockfile', async (t) => {
selectedProjectsGraph,
}, ['is-positive'])
t.equal(exitCode, 1)
t.equal(stripAnsi(output as unknown as string), `\
┌─────────────┬─────────┬────────┬──────────────────────┐
│ Package │ Current │ Latest │ Dependents │

View File

@@ -39,10 +39,12 @@ export const GLOBAL_OPTIONS = R.pick([
'workspace-packages',
], allTypes)
export type CommandResponse = string | { output: string, exitCode: number } | void
export type Command = (
opts: PnpmOptions,
params: string[]
) => string | void | Promise<string | void>
) => CommandResponse | Promise<CommandResponse>
const commands: Array<{
cliOptionsTypes: () => Object,

View File

@@ -194,8 +194,8 @@ export default async function run (inputArgv: string[]) {
}
// NOTE: we defer the next stage, otherwise reporter might not catch all the logs
await new Promise((resolve, reject) => {
setTimeout(() => {
const [output, exitCode] = await new Promise((resolve, reject) => {
setTimeout(async () => {
if (config.force === true) {
logger.warn({
message: 'using --force I sure hope you know what you are doing',
@@ -216,33 +216,36 @@ export default async function run (inputArgv: string[]) {
})
try {
const result = pnpmCmds[cmd || 'help'](
let result = pnpmCmds[cmd || 'help'](
// TypeScript doesn't currently infer that the type of config
// is `Omit<typeof config, 'reporter'>` after the `delete config.reporter` statement
config as Omit<typeof config, 'reporter'>,
cliParams
)
if (result instanceof Promise) {
result
.then((output) => {
if (typeof output === 'string') {
write(output)
}
resolve()
})
.catch(reject)
} else {
if (typeof result === 'string') {
write(result)
}
resolve()
result = await result
}
if (!result) {
resolve([null, 0])
return
}
if (typeof result === 'string') {
resolve([result, 0])
return
}
resolve([result['output'], result['exitCode']])
} catch (err) {
reject(err)
}
}, 0)
})
if (output) {
write(output)
}
if (!cmd) {
process.exit(1)
}
if (exitCode) {
process.exit(exitCode)
}
}

View File

@@ -2,6 +2,7 @@ import prepare from '@pnpm/prepare'
import rimraf = require('@zkochan/rimraf')
import execa = require('execa')
import fs = require('mz/fs')
import path = require('path')
import tape = require('tape')
import promisifyTape from 'tape-promise'
import {
@@ -11,6 +12,8 @@ import {
} from './utils'
const test = promisifyTape(tape)
const fixtures = path.join(__dirname, '../../../fixtures')
const hasOutdatedDepsFixture = path.join(fixtures, 'has-outdated-deps')
test('some commands pass through to npm', t => {
const result = execPnpmSync(['dist-tag', 'ls', 'is-positive'])
@@ -132,3 +135,13 @@ test('pnpx works', t => {
t.end()
})
test('exit code from plugin is used to end the process', t => {
process.chdir(hasOutdatedDepsFixture)
const result = execPnpmSync(['outdated'])
t.equal(result.status, 1)
t.ok(result.stdout.toString().includes('is-positive'))
t.end()
})