Skip to content

Commit

Permalink
feat(view): add workspace support
Browse files Browse the repository at this point in the history
PR-URL: #3001
Credit: @wraithgar
Close: #3001
Reviewed-by: @nlf
  • Loading branch information
wraithgar committed Mar 31, 2021
1 parent ec520ce commit 32717a6
Show file tree
Hide file tree
Showing 5 changed files with 511 additions and 64 deletions.
3 changes: 1 addition & 2 deletions docs/content/commands/npm-view.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@ aliases: info, show, v

### Description

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

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

Expand Down
186 changes: 124 additions & 62 deletions lib/view.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@ const fs = require('fs')
const jsonParse = require('json-parse-even-better-errors')
const log = require('npmlog')
const npa = require('npm-package-arg')
const path = require('path')
const { resolve } = require('path')
const relativeDate = require('tiny-relative-date')
const semver = require('semver')
const style = require('ansistyles')
const { inspect, promisify } = require('util')
const { packument } = require('pacote')
const getWorkspaces = require('./workspaces/get-workspaces.js')

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

/* istanbul ignore next - see test/lib/load-all-commands.js */
static get params () {
return [
'json',
'workspace',
'workspaces',
]
}

/* istanbul ignore next - see test/lib/load-all-commands.js */
static get name () {
return 'view'
Expand Down Expand Up @@ -85,43 +95,116 @@ class View extends BaseCommand {
this.view(args).then(() => cb()).catch(cb)
}

execWorkspaces (args, filters, cb) {
this.viewWorkspaces(args, filters).then(() => cb()).catch(cb)
}

async view (args) {
if (!args.length)
args = ['.']
let pkg = args.shift()
const local = /^\.@/.test(pkg) || pkg === '.'

const opts = {
...this.npm.flatOptions,
preferOnline: true,
fullMetadata: true,
if (local) {
if (this.npm.config.get('global'))
throw new Error('Cannot use view command in global mode.')
const dir = this.npm.prefix
const manifest = await readJson(resolve(dir, 'package.json'))
if (!manifest.name)
throw new Error('Invalid package.json, no "name" field')
// put the version back if it existed
pkg = `${manifest.name}${pkg.slice(1)}`
}
let wholePackument = false
if (!args.length) {
args = ['']
wholePackument = true
}
const [pckmnt, data] = await this.getData(pkg, args)

if (!this.npm.config.get('json') && wholePackument) {
// pretty view (entire packument)
data.map((v) => this.prettyView(pckmnt, v[Object.keys(v)[0]]['']))
} else {
// JSON formatted output (JSON or specific attributes from packument)
let reducedData = data.reduce(reducer, {})
if (wholePackument) {
// No attributes
reducedData = cleanBlanks(reducedData)
log.silly('view', reducedData)
}
// disable the progress bar entirely, as we can't meaningfully update it
// if we may have partial lines printed.
log.disableProgress()

const msg = await this.jsonData(reducedData, pckmnt._id)
if (msg !== '')
console.log(msg)
}
}

async viewWorkspaces (args, filters) {
if (!args.length)
args = ['.']

const pkg = args.shift()
let nv
if (/^[.]@/.test(pkg))
nv = npa.resolve(null, pkg.slice(2))
else
nv = npa(pkg)

const name = nv.name
const local = (name === '.' || !name)
const local = /^\.@/.test(pkg) || pkg === '.'
if (!local) {
this.npm.log.warn('Ignoring workspaces for remote package')
return this.view([pkg, ...args])
}
let wholePackument = false
if (!args.length) {
wholePackument = true
args = [''] // getData relies on this
}
const results = {}
const workspaces =
await getWorkspaces(filters, { path: this.npm.localPrefix })
for (const workspace of [...workspaces.entries()]) {
const wsPkg = `${workspace[0]}${pkg.slice(1)}`
const [pckmnt, data] = await this.getData(wsPkg, args)

let reducedData = data.reduce(reducer, {})
if (wholePackument) {
// No attributes
reducedData = cleanBlanks(reducedData)
log.silly('view', reducedData)
}

if (this.npm.config.get('global') && local)
throw new Error('Cannot use view command in global mode.')
if (!this.npm.config.get('json')) {
if (wholePackument)
data.map((v) => this.prettyView(pckmnt, v[Object.keys(v)[0]]['']))
else {
console.log(`${workspace[0]}:`)
const msg = await this.jsonData(reducedData, pckmnt._id)
if (msg !== '')
console.log(msg)
}
} else {
const msg = await this.jsonData(reducedData, pckmnt._id)
if (msg !== '')
results[workspace[0]] = JSON.parse(msg)
}
}
if (Object.keys(results).length > 0)
console.log(JSON.stringify(results, null, 2))
}

if (local) {
const dir = this.npm.prefix
const manifest = await readJson(path.resolve(dir, 'package.json'))
if (!manifest.name)
throw new Error('Invalid package.json, no "name" field')
const p = manifest.name
nv = npa(p)
if (pkg && ~pkg.indexOf('@'))
nv.rawSpec = pkg.split('@')[pkg.indexOf('@')]
async getData (pkg, args) {
const opts = {
...this.npm.flatOptions,
preferOnline: true,
fullMetadata: true,
}

const spec = npa(pkg)

// get the data about this package
let version = nv.rawSpec || this.npm.config.get('tag')
let version = spec.rawSpec || this.npm.config.get('tag')

const pckmnt = await packument(nv, opts)
const pckmnt = await packument(spec, opts)

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

const results = []
const data = []
const versions = pckmnt.versions || {}
pckmnt.versions = Object.keys(versions).sort(semver.compareLoose)
if (!args.length)
args = ['']

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

results.push(showFields(pckmnt, versions[v], arg))
data.push(showFields(pckmnt, versions[v], arg))
})
}
})
let retval = results.reduce(reducer, {})

if (args.length === 1 && args[0] === '') {
retval = cleanBlanks(retval)
log.silly('view', retval)
}

if (
!this.npm.config.get('json') &&
args.length === 1 &&
args[0] === ''
) {
// general view
)
pckmnt.version = version
await Promise.all(
results.map((v) => this.prettyView(pckmnt, v[Object.keys(v)[0]]['']))
)
return retval
} else {
// view by field name
await this.printData(retval, pckmnt._id)
return retval
}

return [pckmnt, data]
}

async printData (data, name) {
async jsonData (data, name) {
const versions = Object.keys(data)
let msg = ''
let msgJson = []
Expand Down Expand Up @@ -233,16 +300,10 @@ class View extends BaseCommand {
msg = JSON.stringify(msgJson, null, 2) + '\n'
}

// disable the progress bar entirely, as we can't meaningfully update it if
// we may have partial lines printed.
log.disableProgress()

// only log if there is something to log
if (msg !== '')
console.log(msg.trim())
return msg.trim()
}

async prettyView (packument, manifest) {
prettyView (packument, manifest) {
// More modern, pretty printing of default view
const unicode = this.npm.config.get('unicode')
const tags = []
Expand Down Expand Up @@ -375,17 +436,18 @@ function cleanBlanks (obj) {
return clean
}

function reducer (l, r) {
if (r) {
Object.keys(r).forEach((v) => {
l[v] = l[v] || {}
Object.keys(r[v]).forEach((t) => {
l[v][t] = r[v][t]
// takes an array of objects and merges them into one object
function reducer (acc, cur) {
if (cur) {
Object.keys(cur).forEach((v) => {
acc[v] = acc[v] || {}
Object.keys(cur[v]).forEach((t) => {
acc[v][t] = cur[v][t]
})
})
}

return l
return acc
}

// return whatever was printed
Expand Down
3 changes: 3 additions & 0 deletions tap-snapshots/test-lib-utils-npm-usage.js-TAP.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -920,6 +920,9 @@ All commands:
Usage:
npm view [<@scope>/]<pkg>[@<version>] [<field>[.subfield]...]
Options:
[--json] [-w|--workspace <workspace>|-w|--workspace <workspace>] [-ws|--workspaces]
aliases: v, info, show
Run "npm help view" for more info
Expand Down
Loading

0 comments on commit 32717a6

Please sign in to comment.