Skip to content

Commit 32717a6

Browse files
committed
feat(view): add workspace support
PR-URL: #3001 Credit: @wraithgar Close: #3001 Reviewed-by: @nlf
1 parent ec520ce commit 32717a6

File tree

5 files changed

+511
-64
lines changed

5 files changed

+511
-64
lines changed

docs/content/commands/npm-view.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,7 @@ aliases: info, show, v
1414

1515
### Description
1616

17-
This command shows data about a package and prints it to the stream
18-
referenced by the `outfd` config, which defaults to stdout.
17+
This command shows data about a package and prints it to stdout.
1918

2019
As an example, to view information about the `connect` package from the registry, you would run:
2120

lib/view.js

Lines changed: 124 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,13 @@ const fs = require('fs')
77
const jsonParse = require('json-parse-even-better-errors')
88
const log = require('npmlog')
99
const npa = require('npm-package-arg')
10-
const path = require('path')
10+
const { resolve } = require('path')
1111
const relativeDate = require('tiny-relative-date')
1212
const semver = require('semver')
1313
const style = require('ansistyles')
1414
const { inspect, promisify } = require('util')
1515
const { packument } = require('pacote')
16+
const getWorkspaces = require('./workspaces/get-workspaces.js')
1617

1718
const readFile = promisify(fs.readFile)
1819
const readJson = async file => jsonParse(await readFile(file, 'utf8'))
@@ -24,6 +25,15 @@ class View extends BaseCommand {
2425
return 'View registry info'
2526
}
2627

28+
/* istanbul ignore next - see test/lib/load-all-commands.js */
29+
static get params () {
30+
return [
31+
'json',
32+
'workspace',
33+
'workspaces',
34+
]
35+
}
36+
2737
/* istanbul ignore next - see test/lib/load-all-commands.js */
2838
static get name () {
2939
return 'view'
@@ -85,43 +95,116 @@ class View extends BaseCommand {
8595
this.view(args).then(() => cb()).catch(cb)
8696
}
8797

98+
execWorkspaces (args, filters, cb) {
99+
this.viewWorkspaces(args, filters).then(() => cb()).catch(cb)
100+
}
101+
88102
async view (args) {
89103
if (!args.length)
90104
args = ['.']
105+
let pkg = args.shift()
106+
const local = /^\.@/.test(pkg) || pkg === '.'
91107

92-
const opts = {
93-
...this.npm.flatOptions,
94-
preferOnline: true,
95-
fullMetadata: true,
108+
if (local) {
109+
if (this.npm.config.get('global'))
110+
throw new Error('Cannot use view command in global mode.')
111+
const dir = this.npm.prefix
112+
const manifest = await readJson(resolve(dir, 'package.json'))
113+
if (!manifest.name)
114+
throw new Error('Invalid package.json, no "name" field')
115+
// put the version back if it existed
116+
pkg = `${manifest.name}${pkg.slice(1)}`
96117
}
118+
let wholePackument = false
119+
if (!args.length) {
120+
args = ['']
121+
wholePackument = true
122+
}
123+
const [pckmnt, data] = await this.getData(pkg, args)
124+
125+
if (!this.npm.config.get('json') && wholePackument) {
126+
// pretty view (entire packument)
127+
data.map((v) => this.prettyView(pckmnt, v[Object.keys(v)[0]]['']))
128+
} else {
129+
// JSON formatted output (JSON or specific attributes from packument)
130+
let reducedData = data.reduce(reducer, {})
131+
if (wholePackument) {
132+
// No attributes
133+
reducedData = cleanBlanks(reducedData)
134+
log.silly('view', reducedData)
135+
}
136+
// disable the progress bar entirely, as we can't meaningfully update it
137+
// if we may have partial lines printed.
138+
log.disableProgress()
139+
140+
const msg = await this.jsonData(reducedData, pckmnt._id)
141+
if (msg !== '')
142+
console.log(msg)
143+
}
144+
}
145+
146+
async viewWorkspaces (args, filters) {
147+
if (!args.length)
148+
args = ['.']
149+
97150
const pkg = args.shift()
98-
let nv
99-
if (/^[.]@/.test(pkg))
100-
nv = npa.resolve(null, pkg.slice(2))
101-
else
102-
nv = npa(pkg)
103151

104-
const name = nv.name
105-
const local = (name === '.' || !name)
152+
const local = /^\.@/.test(pkg) || pkg === '.'
153+
if (!local) {
154+
this.npm.log.warn('Ignoring workspaces for remote package')
155+
return this.view([pkg, ...args])
156+
}
157+
let wholePackument = false
158+
if (!args.length) {
159+
wholePackument = true
160+
args = [''] // getData relies on this
161+
}
162+
const results = {}
163+
const workspaces =
164+
await getWorkspaces(filters, { path: this.npm.localPrefix })
165+
for (const workspace of [...workspaces.entries()]) {
166+
const wsPkg = `${workspace[0]}${pkg.slice(1)}`
167+
const [pckmnt, data] = await this.getData(wsPkg, args)
168+
169+
let reducedData = data.reduce(reducer, {})
170+
if (wholePackument) {
171+
// No attributes
172+
reducedData = cleanBlanks(reducedData)
173+
log.silly('view', reducedData)
174+
}
106175

107-
if (this.npm.config.get('global') && local)
108-
throw new Error('Cannot use view command in global mode.')
176+
if (!this.npm.config.get('json')) {
177+
if (wholePackument)
178+
data.map((v) => this.prettyView(pckmnt, v[Object.keys(v)[0]]['']))
179+
else {
180+
console.log(`${workspace[0]}:`)
181+
const msg = await this.jsonData(reducedData, pckmnt._id)
182+
if (msg !== '')
183+
console.log(msg)
184+
}
185+
} else {
186+
const msg = await this.jsonData(reducedData, pckmnt._id)
187+
if (msg !== '')
188+
results[workspace[0]] = JSON.parse(msg)
189+
}
190+
}
191+
if (Object.keys(results).length > 0)
192+
console.log(JSON.stringify(results, null, 2))
193+
}
109194

110-
if (local) {
111-
const dir = this.npm.prefix
112-
const manifest = await readJson(path.resolve(dir, 'package.json'))
113-
if (!manifest.name)
114-
throw new Error('Invalid package.json, no "name" field')
115-
const p = manifest.name
116-
nv = npa(p)
117-
if (pkg && ~pkg.indexOf('@'))
118-
nv.rawSpec = pkg.split('@')[pkg.indexOf('@')]
195+
async getData (pkg, args) {
196+
const opts = {
197+
...this.npm.flatOptions,
198+
preferOnline: true,
199+
fullMetadata: true,
119200
}
120201

202+
const spec = npa(pkg)
203+
121204
// get the data about this package
122-
let version = nv.rawSpec || this.npm.config.get('tag')
205+
let version = spec.rawSpec || this.npm.config.get('tag')
123206

124-
const pckmnt = await packument(nv, opts)
207+
const pckmnt = await packument(spec, opts)
125208

126209
if (pckmnt['dist-tags'] && pckmnt['dist-tags'][version])
127210
version = pckmnt['dist-tags'][version]
@@ -135,11 +218,9 @@ class View extends BaseCommand {
135218
throw er
136219
}
137220

138-
const results = []
221+
const data = []
139222
const versions = pckmnt.versions || {}
140223
pckmnt.versions = Object.keys(versions).sort(semver.compareLoose)
141-
if (!args.length)
142-
args = ['']
143224

144225
// remove readme unless we asked for it
145226
if (args.indexOf('readme') === -1)
@@ -152,36 +233,22 @@ class View extends BaseCommand {
152233
if (args.indexOf('readme') !== -1)
153234
delete versions[v].readme
154235

155-
results.push(showFields(pckmnt, versions[v], arg))
236+
data.push(showFields(pckmnt, versions[v], arg))
156237
})
157238
}
158239
})
159-
let retval = results.reduce(reducer, {})
160-
161-
if (args.length === 1 && args[0] === '') {
162-
retval = cleanBlanks(retval)
163-
log.silly('view', retval)
164-
}
165240

166241
if (
167242
!this.npm.config.get('json') &&
168243
args.length === 1 &&
169244
args[0] === ''
170-
) {
171-
// general view
245+
)
172246
pckmnt.version = version
173-
await Promise.all(
174-
results.map((v) => this.prettyView(pckmnt, v[Object.keys(v)[0]]['']))
175-
)
176-
return retval
177-
} else {
178-
// view by field name
179-
await this.printData(retval, pckmnt._id)
180-
return retval
181-
}
247+
248+
return [pckmnt, data]
182249
}
183250

184-
async printData (data, name) {
251+
async jsonData (data, name) {
185252
const versions = Object.keys(data)
186253
let msg = ''
187254
let msgJson = []
@@ -233,16 +300,10 @@ class View extends BaseCommand {
233300
msg = JSON.stringify(msgJson, null, 2) + '\n'
234301
}
235302

236-
// disable the progress bar entirely, as we can't meaningfully update it if
237-
// we may have partial lines printed.
238-
log.disableProgress()
239-
240-
// only log if there is something to log
241-
if (msg !== '')
242-
console.log(msg.trim())
303+
return msg.trim()
243304
}
244305

245-
async prettyView (packument, manifest) {
306+
prettyView (packument, manifest) {
246307
// More modern, pretty printing of default view
247308
const unicode = this.npm.config.get('unicode')
248309
const tags = []
@@ -375,17 +436,18 @@ function cleanBlanks (obj) {
375436
return clean
376437
}
377438

378-
function reducer (l, r) {
379-
if (r) {
380-
Object.keys(r).forEach((v) => {
381-
l[v] = l[v] || {}
382-
Object.keys(r[v]).forEach((t) => {
383-
l[v][t] = r[v][t]
439+
// takes an array of objects and merges them into one object
440+
function reducer (acc, cur) {
441+
if (cur) {
442+
Object.keys(cur).forEach((v) => {
443+
acc[v] = acc[v] || {}
444+
Object.keys(cur[v]).forEach((t) => {
445+
acc[v][t] = cur[v][t]
384446
})
385447
})
386448
}
387449

388-
return l
450+
return acc
389451
}
390452

391453
// return whatever was printed

tap-snapshots/test-lib-utils-npm-usage.js-TAP.test.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -920,6 +920,9 @@ All commands:
920920
Usage:
921921
npm view [<@scope>/]<pkg>[@<version>] [<field>[.subfield]...]
922922
923+
Options:
924+
[--json] [-w|--workspace <workspace>|-w|--workspace <workspace>] [-ws|--workspaces]
925+
923926
aliases: v, info, show
924927
925928
Run "npm help view" for more info

0 commit comments

Comments
 (0)