Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/npm/cli.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/content/commands/npm-view.md3
-rw-r--r--lib/view.js186
-rw-r--r--tap-snapshots/test-lib-utils-npm-usage.js-TAP.test.js3
-rw-r--r--tap-snapshots/test-lib-view.js-TAP.test.js257
-rw-r--r--test/lib/view.js126
5 files changed, 511 insertions, 64 deletions
diff --git a/docs/content/commands/npm-view.md b/docs/content/commands/npm-view.md
index bf09c2ba4..90d521885 100644
--- a/docs/content/commands/npm-view.md
+++ b/docs/content/commands/npm-view.md
@@ -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:
diff --git a/lib/view.js b/lib/view.js
index e0df1e231..fb280f0d5 100644
--- a/lib/view.js
+++ b/lib/view.js
@@ -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'))
@@ -25,6 +26,15 @@ class View extends BaseCommand {
}
/* 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'
}
@@ -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]
@@ -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)
@@ -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 = []
@@ -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 = []
@@ -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
diff --git a/tap-snapshots/test-lib-utils-npm-usage.js-TAP.test.js b/tap-snapshots/test-lib-utils-npm-usage.js-TAP.test.js
index 4f9c1b44e..66d274057 100644
--- a/tap-snapshots/test-lib-utils-npm-usage.js-TAP.test.js
+++ b/tap-snapshots/test-lib-utils-npm-usage.js-TAP.test.js
@@ -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
diff --git a/tap-snapshots/test-lib-view.js-TAP.test.js b/tap-snapshots/test-lib-view.js-TAP.test.js
index f8a9fe464..02810e31a 100644
--- a/tap-snapshots/test-lib-view.js-TAP.test.js
+++ b/tap-snapshots/test-lib-view.js-TAP.test.js
@@ -270,3 +270,260 @@ dist-tags:
published a year ago
`
+
+exports[`test/lib/view.js TAP workspaces all workspaces --json > must match snapshot 1`] = `
+
+{
+ "green": {
+ "_id": "green",
+ "name": "green",
+ "dist-tags": {
+ "latest": "1.0.0"
+ },
+ "maintainers": [
+ {
+ "name": "claudia",
+ "email": "c@yellow.com",
+ "twitter": "cyellow"
+ },
+ {
+ "name": "isaacs",
+ "email": "i@yellow.com",
+ "twitter": "iyellow"
+ }
+ ],
+ "keywords": [
+ "colors",
+ "green",
+ "crayola"
+ ],
+ "versions": [
+ "1.0.0",
+ "1.0.1"
+ ],
+ "version": "1.0.0",
+ "description": "green is a very important color",
+ "bugs": {
+ "url": "http://bugs.green.com"
+ },
+ "deprecated": true,
+ "repository": {
+ "url": "http://repository.green.com"
+ },
+ "license": {
+ "type": "ACME"
+ },
+ "bin": {
+ "green": "bin/green.js"
+ },
+ "dependencies": {
+ "red": "1.0.0",
+ "yellow": "1.0.0"
+ },
+ "dist": {
+ "shasum": "123",
+ "tarball": "http://hm.green.com/1.0.0.tgz",
+ "integrity": "---",
+ "fileCount": 1,
+ "unpackedSize": 1
+ }
+ },
+ "orange": {
+ "name": "orange",
+ "dist-tags": {
+ "latest": "1.0.0"
+ },
+ "versions": [
+ "1.0.0",
+ "1.0.1"
+ ],
+ "version": "1.0.0",
+ "homepage": "http://hm.orange.com",
+ "license": {},
+ "dist": {
+ "shasum": "123",
+ "tarball": "http://hm.orange.com/1.0.0.tgz",
+ "integrity": "---",
+ "fileCount": 1,
+ "unpackedSize": 1
+ }
+ }
+}
+`
+
+exports[`test/lib/view.js TAP workspaces all workspaces > must match snapshot 1`] = `
+
+
+green@1.0.0 | ACME | deps: 2 | versions: 2
+green is a very important color
+
+DEPRECATED!! - true
+
+keywords:colors, green, crayola
+
+bin:green
+
+dist
+.tarball:http://hm.green.com/1.0.0.tgz
+.shasum:123
+.integrity:---
+.unpackedSize:1 B
+
+dependencies:
+red: 1.0.0
+yellow: 1.0.0
+
+maintainers:
+-claudia <c@yellow.com>
+-isaacs <i@yellow.com>
+
+dist-tags:
+latest: 1.0.0
+
+orange@1.0.0 | Proprietary | deps: none | versions: 2
+http://hm.orange.com
+
+dist
+.tarball:http://hm.orange.com/1.0.0.tgz
+.shasum:123
+.integrity:---
+.unpackedSize:1 B
+
+dist-tags:
+latest: 1.0.0
+`
+
+exports[`test/lib/view.js TAP workspaces all workspaces nonexistent field --json > must match snapshot 1`] = `
+
+`
+
+exports[`test/lib/view.js TAP workspaces all workspaces nonexistent field > must match snapshot 1`] = `
+
+green:
+orange:
+`
+
+exports[`test/lib/view.js TAP workspaces all workspaces single field --json > must match snapshot 1`] = `
+
+{
+ "green": "green",
+ "orange": "orange"
+}
+`
+
+exports[`test/lib/view.js TAP workspaces all workspaces single field > must match snapshot 1`] = `
+
+green:
+green
+orange:
+orange
+`
+
+exports[`test/lib/view.js TAP workspaces one specific workspace > must match snapshot 1`] = `
+
+
+green@1.0.0 | ACME | deps: 2 | versions: 2
+green is a very important color
+
+DEPRECATED!! - true
+
+keywords:colors, green, crayola
+
+bin:green
+
+dist
+.tarball:http://hm.green.com/1.0.0.tgz
+.shasum:123
+.integrity:---
+.unpackedSize:1 B
+
+dependencies:
+red: 1.0.0
+yellow: 1.0.0
+
+maintainers:
+-claudia <c@yellow.com>
+-isaacs <i@yellow.com>
+
+dist-tags:
+latest: 1.0.0
+`
+
+exports[`test/lib/view.js TAP workspaces remote package name > must match snapshot 1`] = `
+Ignoring workspaces for remote package
+`
+
+exports[`test/lib/view.js TAP workspaces remote package name > must match snapshot 2`] = `
+
+
+pink@1.0.0 | Proprietary | deps: none | versions: 2
+
+dist
+.tarball:http://hm.pink.com/1.0.0.tgz
+.shasum:123
+.integrity:---
+.unpackedSize:1 B
+
+dist-tags:
+latest: 1.0.0
+`
+
+exports[`test/lib/view.js TAP workspaces single workspace --json > must match snapshot 1`] = `
+
+{
+ "green": {
+ "_id": "green",
+ "name": "green",
+ "dist-tags": {
+ "latest": "1.0.0"
+ },
+ "maintainers": [
+ {
+ "name": "claudia",
+ "email": "c@yellow.com",
+ "twitter": "cyellow"
+ },
+ {
+ "name": "isaacs",
+ "email": "i@yellow.com",
+ "twitter": "iyellow"
+ }
+ ],
+ "keywords": [
+ "colors",
+ "green",
+ "crayola"
+ ],
+ "versions": [
+ "1.0.0",
+ "1.0.1"
+ ],
+ "version": "1.0.0",
+ "description": "green is a very important color",
+ "bugs": {
+ "url": "http://bugs.green.com"
+ },
+ "deprecated": true,
+ "repository": {
+ "url": "http://repository.green.com"
+ },
+ "license": {
+ "type": "ACME"
+ },
+ "bin": {
+ "green": "bin/green.js"
+ },
+ "dependencies": {
+ "red": "1.0.0",
+ "yellow": "1.0.0"
+ },
+ "dist": {
+ "shasum": "123",
+ "tarball": "http://hm.green.com/1.0.0.tgz",
+ "integrity": "---",
+ "fileCount": 1,
+ "unpackedSize": 1
+ }
+ }
+}
+`
diff --git a/test/lib/view.js b/test/lib/view.js
index d136a1f41..91ce18786 100644
--- a/test/lib/view.js
+++ b/test/lib/view.js
@@ -238,6 +238,7 @@ const packument = (nv, opts) => {
}
t.beforeEach(cleanLogs)
+
t.test('should log package info', t => {
const View = requireInject('../../lib/view.js', {
pacote: {
@@ -548,6 +549,131 @@ t.test('throws when unpublished', (t) => {
})
})
+t.test('workspaces', t => {
+ t.beforeEach((done) => {
+ warnMsg = undefined
+ config.json = false
+ done()
+ })
+ const testDir = t.testdir({
+ 'package.json': JSON.stringify({
+ name: 'workspaces-test-package',
+ version: '1.2.3',
+ workspaces: ['test-workspace-a', 'test-workspace-b'],
+ }),
+ 'test-workspace-a': {
+ 'package.json': JSON.stringify({
+ name: 'green',
+ version: '1.2.3',
+ }),
+ },
+ 'test-workspace-b': {
+ 'package.json': JSON.stringify({
+ name: 'orange',
+ version: '1.2.3',
+ }),
+ },
+ })
+ const View = requireInject('../../lib/view.js', {
+ pacote: {
+ packument,
+ },
+ })
+ const config = {
+ tag: 'latest',
+ }
+ let warnMsg
+ const npm = mockNpm({
+ log: {
+ warn: (msg) => {
+ warnMsg = msg
+ },
+ },
+ config,
+ localPrefix: testDir,
+ })
+ const view = new View(npm)
+
+ t.test('all workspaces', t => {
+ view.execWorkspaces([], [], (err) => {
+ t.error(err)
+ t.matchSnapshot(logs)
+ t.end()
+ })
+ })
+
+ t.test('one specific workspace', t => {
+ view.execWorkspaces([], ['green'], (err) => {
+ t.error(err)
+ t.matchSnapshot(logs)
+ t.end()
+ })
+ })
+
+ t.test('all workspaces --json', t => {
+ config.json = true
+ view.execWorkspaces([], [], (err) => {
+ t.error(err)
+ t.matchSnapshot(logs)
+ t.end()
+ })
+ })
+
+ t.test('all workspaces single field', t => {
+ view.execWorkspaces(['.', 'name'], [], (err) => {
+ t.error(err)
+ t.matchSnapshot(logs)
+ t.end()
+ })
+ })
+
+ t.test('all workspaces nonexistent field', t => {
+ view.execWorkspaces(['.', 'foo'], [], (err) => {
+ t.error(err)
+ t.matchSnapshot(logs)
+ t.end()
+ })
+ })
+
+ t.test('all workspaces nonexistent field --json', t => {
+ config.json = true
+ view.execWorkspaces(['.', 'foo'], [], (err) => {
+ t.error(err)
+ t.matchSnapshot(logs)
+ t.end()
+ })
+ })
+
+ t.test('all workspaces single field --json', t => {
+ config.json = true
+ view.execWorkspaces(['.', 'name'], [], (err) => {
+ t.error(err)
+ t.matchSnapshot(logs)
+ t.end()
+ })
+ })
+
+ t.test('single workspace --json', t => {
+ config.json = true
+ view.execWorkspaces([], ['green'], (err) => {
+ t.error(err)
+ t.matchSnapshot(logs)
+ t.end()
+ })
+ })
+
+ t.test('remote package name', t => {
+ view.execWorkspaces(['pink'], [], (err) => {
+ t.error(err)
+ t.matchSnapshot(warnMsg)
+ t.matchSnapshot(logs)
+ t.end()
+ })
+ })
+
+ t.end()
+})
+
t.test('completion', async t => {
const View = requireInject('../../lib/view.js', {
pacote: {