Skip to content

Commit 39f157f

Browse files
committed
feat(view): output all workspaces even when one 404s
Fixes #5444
1 parent d5c3289 commit 39f157f

File tree

4 files changed

+229
-3
lines changed

4 files changed

+229
-3
lines changed

lib/commands/view.js

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
const columns = require('cli-columns')
22
const { readFile } = require('fs/promises')
33
const jsonParse = require('json-parse-even-better-errors')
4-
const { log, output } = require('proc-log')
4+
const { log, output, META } = require('proc-log')
55
const npa = require('npm-package-arg')
66
const { resolve } = require('path')
77
const formatBytes = require('../utils/format-bytes.js')
@@ -11,6 +11,8 @@ const { inspect } = require('util')
1111
const { packument } = require('pacote')
1212
const Queryable = require('../utils/queryable.js')
1313
const BaseCommand = require('../base-cmd.js')
14+
const { getError } = require('../utils/error-message.js')
15+
const { jsonError, outputError } = require('../utils/output-error.js')
1416

1517
const readJson = file => readFile(file, 'utf8').then(jsonParse)
1618

@@ -76,10 +78,24 @@ class View extends BaseCommand {
7678
return this.exec([pkg, ...rest])
7779
}
7880

81+
const json = this.npm.config.get('json')
7982
await this.setWorkspaces()
8083

8184
for (const name of this.workspaceNames) {
82-
await this.#viewPackage(`${name}${pkg.slice(1)}`, rest, { workspace: true })
85+
try {
86+
await this.#viewPackage(`${name}${pkg.slice(1)}`, rest, { workspace: true })
87+
} catch (e) {
88+
const err = getError(e, { npm: this.npm, command: this })
89+
if (err.code !== 'E404') {
90+
throw e
91+
}
92+
if (json) {
93+
output.buffer({ [META]: true, jsonError: { [name]: jsonError(err, this.npm) } })
94+
} else {
95+
outputError(err)
96+
}
97+
process.exitCode = err.exitCode
98+
}
8399
}
84100
}
85101

tap-snapshots/test/lib/commands/view.js.test.cjs

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,140 @@ [email protected] 'claudia'
363363
364364
`
365365

366+
exports[`test/lib/commands/view.js TAP workspaces 404 workspaces basic > must match snapshot 1`] = `
367+
368+
[[email protected] | ACME | deps: 2 | versions: 2
369+
green is a very important color
370+
371+
DEPRECATED!! - true
372+
373+
keywords: colors, green, crayola
374+
375+
bin: green
376+
377+
dist
378+
.tarball: http://hm.green.com/1.0.0.tgz
379+
.shasum: 123
380+
.integrity: ---
381+
.unpackedSize: 1.0 GB
382+
383+
dependencies:
384+
red: 1.0.0
385+
yellow: 1.0.0
386+
387+
maintainers:
388+
- claudia <[[email protected]>
389+
- isaacs <[[email protected]>
390+
391+
dist-tags:
392+
latest: 1.0.0
393+
error code E404
394+
error 404 404
395+
`
396+
397+
exports[`test/lib/commands/view.js TAP workspaces 404 workspaces json > must match snapshot 1`] = `
398+
{
399+
"green": {
400+
"_id": "green",
401+
"name": "green",
402+
"dist-tags": {
403+
"latest": "1.0.0"
404+
},
405+
"maintainers": [
406+
{
407+
"name": "claudia",
408+
"email": "[email protected]",
409+
"twitter": "cyellow"
410+
},
411+
{
412+
"name": "isaacs",
413+
"email": "[email protected]",
414+
"twitter": "iyellow"
415+
}
416+
],
417+
"keywords": [
418+
"colors",
419+
"green",
420+
"crayola"
421+
],
422+
"versions": [
423+
"1.0.0",
424+
"1.0.1"
425+
],
426+
"version": "1.0.0",
427+
"description": "green is a very important color",
428+
"bugs": {
429+
"url": "http://bugs.green.com"
430+
},
431+
"deprecated": true,
432+
"repository": {
433+
"url": "http://repository.green.com"
434+
},
435+
"license": {
436+
"type": "ACME"
437+
},
438+
"bin": {
439+
"green": "bin/green.js"
440+
},
441+
"dependencies": {
442+
"red": "1.0.0",
443+
"yellow": "1.0.0"
444+
},
445+
"dist": {
446+
"shasum": "123",
447+
"tarball": "http://hm.green.com/1.0.0.tgz",
448+
"integrity": "---",
449+
"fileCount": 1,
450+
"unpackedSize": 1000000000
451+
}
452+
},
453+
"error": {
454+
"missing-package": {
455+
"code": "E404",
456+
"summary": "404",
457+
"detail": ""
458+
}
459+
}
460+
}
461+
`
462+
463+
exports[`test/lib/commands/view.js TAP workspaces 404 workspaces non-404 error rejects > must match snapshot 1`] = `
464+
465+
[[email protected] | ACME | deps: 2 | versions: 2
466+
green is a very important color
467+
468+
DEPRECATED!! - true
469+
470+
keywords: colors, green, crayola
471+
472+
bin: green
473+
474+
dist
475+
.tarball: http://hm.green.com/1.0.0.tgz
476+
.shasum: 123
477+
.integrity: ---
478+
.unpackedSize: 1.0 GB
479+
480+
dependencies:
481+
red: 1.0.0
482+
yellow: 1.0.0
483+
484+
maintainers:
485+
- claudia <[[email protected]>
486+
- isaacs <[[email protected]>
487+
488+
dist-tags:
489+
latest: 1.0.0
490+
error Unknown error
491+
`
492+
493+
exports[`test/lib/commands/view.js TAP workspaces 404 workspaces non-404 error rejects with single arg > must match snapshot 1`] = `
494+
green:
495+
1.0.0
496+
unknown-error:
497+
error Unknown error
498+
`
499+
366500
exports[`test/lib/commands/view.js TAP workspaces all workspaces --json > must match snapshot 1`] = `
367501
{
368502
"green": {

test/fixtures/mock-logs.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ const logsByTitle = (logs) => ({
2424
module.exports = () => {
2525
const outputs = []
2626
const outputErrors = []
27+
const fullOutput = []
2728

2829
const levelLogs = []
2930
const logs = Object.defineProperties([], {
@@ -53,6 +54,7 @@ module.exports = () => {
5354
// in the future if/when we refactor what logs look like.
5455
if (!isLog(str)) {
5556
outputErrors.push(str)
57+
fullOutput.push(str)
5658
return
5759
}
5860

@@ -70,12 +72,14 @@ module.exports = () => {
7072
const level = stripAnsi(rawLevel)
7173

7274
logs.push(str.replaceAll(prefix, `${level} `))
75+
fullOutput.push(str.replaceAll(prefix, `${level} `))
7376
levelLogs.push({ level, message: str.replaceAll(prefix, '') })
7477
},
7578
},
7679
stdout: {
7780
write: (str) => {
7881
outputs.push(trimTrailingNewline(str))
82+
fullOutput.push(trimTrailingNewline(str))
7983
},
8084
},
8185
}
@@ -88,9 +92,12 @@ module.exports = () => {
8892
clearOutput: () => {
8993
outputs.length = 0
9094
outputErrors.length = 0
95+
fullOutput.length = 0
9196
},
9297
outputErrors,
9398
joinedOutputError: () => joinAndTrimTrailingNewlines(outputs),
99+
fullOutput,
100+
joinedFullOutput: () => joinAndTrimTrailingNewlines(fullOutput),
94101
logs,
95102
clearLogs: () => {
96103
levelLogs.length = 0

test/lib/commands/view.js

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,13 +271,24 @@ const packument = (nv, opts) => {
271271
},
272272
},
273273
}
274+
274275
if (nv.type === 'git') {
275276
return mocks[nv.hosted.project]
276277
}
278+
277279
if (nv.raw === './blue') {
278280
return mocks.blue
279281
}
280-
return mocks[nv.name]
282+
283+
if (mocks[nv.name]) {
284+
return mocks[nv.name]
285+
}
286+
287+
if (nv.name === 'unknown-error') {
288+
throw new Error('Unknown error')
289+
}
290+
291+
throw Object.assign(new Error('404'), { code: 'E404' })
281292
}
282293

283294
const loadMockNpm = async function (t, opts = {}) {
@@ -543,6 +554,24 @@ t.test('workspaces', async t => {
543554
},
544555
}
545556

557+
const prefixDir404 = {
558+
'test-workspace-b': {
559+
'package.json': JSON.stringify({
560+
name: 'missing-package',
561+
version: '1.2.3',
562+
}),
563+
},
564+
}
565+
566+
const prefixDirError = {
567+
'test-workspace-b': {
568+
'package.json': JSON.stringify({
569+
name: 'unknown-error',
570+
version: '1.2.3',
571+
}),
572+
},
573+
}
574+
546575
t.test('all workspaces', async t => {
547576
const { view, joinedOutput } = await loadMockNpm(t, {
548577
prefixDir,
@@ -624,6 +653,46 @@ t.test('workspaces', async t => {
624653
t.matchSnapshot(joinedOutput())
625654
t.matchSnapshot(logs.warn, 'should have warning of ignoring workspaces')
626655
})
656+
657+
t.test('404 workspaces', async t => {
658+
t.test('basic', async t => {
659+
const { view, joinedFullOutput } = await loadMockNpm(t, {
660+
prefixDir: { ...prefixDir, ...prefixDir404 },
661+
config: { workspaces: true, loglevel: 'error' },
662+
})
663+
await view.exec([])
664+
t.matchSnapshot(joinedFullOutput())
665+
t.equal(process.exitCode, 1)
666+
})
667+
668+
t.test('json', async t => {
669+
const { view, joinedFullOutput } = await loadMockNpm(t, {
670+
prefixDir: { ...prefixDir, ...prefixDir404 },
671+
config: { workspaces: true, json: true, loglevel: 'error' },
672+
})
673+
await view.exec([])
674+
t.matchSnapshot(joinedFullOutput())
675+
t.equal(process.exitCode, 1)
676+
})
677+
678+
t.test('non-404 error rejects', async t => {
679+
const { view, joinedFullOutput } = await loadMockNpm(t, {
680+
prefixDir: { ...prefixDir, ...prefixDirError },
681+
config: { workspaces: true, loglevel: 'error' },
682+
})
683+
await t.rejects(view.exec([]))
684+
t.matchSnapshot(joinedFullOutput())
685+
})
686+
687+
t.test('non-404 error rejects with single arg', async t => {
688+
const { view, joinedFullOutput } = await loadMockNpm(t, {
689+
prefixDir: { ...prefixDir, ...prefixDirError },
690+
config: { workspaces: true, loglevel: 'error' },
691+
})
692+
await t.rejects(view.exec(['.', 'version']))
693+
t.matchSnapshot(joinedFullOutput())
694+
})
695+
})
627696
})
628697

629698
t.test('completion', async t => {

0 commit comments

Comments
 (0)