1 | 'use strict'
|
2 |
|
3 |
|
4 | module.exports = view
|
5 |
|
6 | const BB = require('bluebird')
|
7 |
|
8 | const byteSize = require('byte-size')
|
9 | const color = require('ansicolors')
|
10 | const columns = require('cli-columns')
|
11 | const relativeDate = require('tiny-relative-date')
|
12 | const style = require('ansistyles')
|
13 | var npm = require('./npm.js')
|
14 | var readJson = require('read-package-json')
|
15 | var log = require('npmlog')
|
16 | var util = require('util')
|
17 | var semver = require('semver')
|
18 | var mapToRegistry = require('./utils/map-to-registry.js')
|
19 | var npa = require('npm-package-arg')
|
20 | var path = require('path')
|
21 | var usage = require('./utils/usage')
|
22 |
|
23 | view.usage = usage(
|
24 | 'view',
|
25 | 'npm view [<@scope>/]<pkg>[@<version>] [<field>[.subfield]...]'
|
26 | )
|
27 |
|
28 | view.completion = function (opts, cb) {
|
29 | if (opts.conf.argv.remain.length <= 2) {
|
30 |
|
31 |
|
32 | return cb()
|
33 | }
|
34 |
|
35 | var tag = npm.config.get('tag')
|
36 | mapToRegistry(opts.conf.argv.remain[2], npm.config, function (er, uri, auth) {
|
37 | if (er) return cb(er)
|
38 |
|
39 | npm.registry.get(uri, { auth: auth }, function (er, d) {
|
40 | if (er) return cb(er)
|
41 | var dv = d.versions[d['dist-tags'][tag]]
|
42 | var fields = []
|
43 | d.versions = Object.keys(d.versions).sort(semver.compareLoose)
|
44 | fields = getFields(d).concat(getFields(dv))
|
45 | cb(null, fields)
|
46 | })
|
47 | })
|
48 |
|
49 | function getFields (d, f, pref) {
|
50 | f = f || []
|
51 | if (!d) return f
|
52 | pref = pref || []
|
53 | Object.keys(d).forEach(function (k) {
|
54 | if (k.charAt(0) === '_' || k.indexOf('.') !== -1) return
|
55 | var p = pref.concat(k).join('.')
|
56 | f.push(p)
|
57 | if (Array.isArray(d[k])) {
|
58 | d[k].forEach(function (val, i) {
|
59 | var pi = p + '[' + i + ']'
|
60 | if (val && typeof val === 'object') getFields(val, f, [p])
|
61 | else f.push(pi)
|
62 | })
|
63 | return
|
64 | }
|
65 | if (typeof d[k] === 'object') getFields(d[k], f, [p])
|
66 | })
|
67 | return f
|
68 | }
|
69 | }
|
70 |
|
71 | function view (args, silent, cb) {
|
72 | if (typeof cb !== 'function') {
|
73 | cb = silent
|
74 | silent = false
|
75 | }
|
76 |
|
77 | if (!args.length) args = ['.']
|
78 |
|
79 | var pkg = args.shift()
|
80 | var nv
|
81 | if (/^[.]@/.test(pkg)) {
|
82 | nv = npa.resolve(null, pkg.slice(2))
|
83 | } else {
|
84 | nv = npa(pkg)
|
85 | }
|
86 | var name = nv.name
|
87 | var local = (name === '.' || !name)
|
88 |
|
89 | if (npm.config.get('global') && local) {
|
90 | return cb(new Error('Cannot use view command in global mode.'))
|
91 | }
|
92 |
|
93 | if (local) {
|
94 | var dir = npm.prefix
|
95 | readJson(path.resolve(dir, 'package.json'), function (er, d) {
|
96 | d = d || {}
|
97 | if (er && er.code !== 'ENOENT' && er.code !== 'ENOTDIR') return cb(er)
|
98 | if (!d.name) return cb(new Error('Invalid package.json'))
|
99 |
|
100 | var p = d.name
|
101 | nv = npa(p)
|
102 | if (pkg && ~pkg.indexOf('@')) {
|
103 | nv.rawSpec = pkg.split('@')[pkg.indexOf('@')]
|
104 | }
|
105 |
|
106 | fetchAndRead(nv, args, silent, cb)
|
107 | })
|
108 | } else {
|
109 | fetchAndRead(nv, args, silent, cb)
|
110 | }
|
111 | }
|
112 |
|
113 | function fetchAndRead (nv, args, silent, cb) {
|
114 |
|
115 | var name = nv.name
|
116 | var version = nv.rawSpec || npm.config.get('tag')
|
117 |
|
118 | mapToRegistry(name, npm.config, function (er, uri, auth) {
|
119 | if (er) return cb(er)
|
120 |
|
121 | npm.registry.get(uri, { auth: auth }, function (er, data) {
|
122 | if (er) return cb(er)
|
123 | if (data['dist-tags'] && data['dist-tags'][version]) {
|
124 | version = data['dist-tags'][version]
|
125 | }
|
126 |
|
127 | if (data.time && data.time.unpublished) {
|
128 | var u = data.time.unpublished
|
129 | er = new Error('Unpublished by ' + u.name + ' on ' + u.time)
|
130 | er.statusCode = 404
|
131 | er.code = 'E404'
|
132 | er.pkgid = data._id
|
133 | return cb(er, data)
|
134 | }
|
135 |
|
136 | var results = []
|
137 | var error = null
|
138 | var versions = data.versions || {}
|
139 | data.versions = Object.keys(versions).sort(semver.compareLoose)
|
140 | if (!args.length) args = ['']
|
141 |
|
142 |
|
143 | if (args.indexOf('readme') === -1) {
|
144 | delete data.readme
|
145 | }
|
146 |
|
147 | Object.keys(versions).forEach(function (v) {
|
148 | if (semver.satisfies(v, version, true)) {
|
149 | args.forEach(function (args) {
|
150 |
|
151 | if (args.indexOf('readme') !== -1) {
|
152 | delete versions[v].readme
|
153 | }
|
154 | results.push(showFields(data, versions[v], args))
|
155 | })
|
156 | }
|
157 | })
|
158 | var retval = results.reduce(reducer, {})
|
159 |
|
160 | if (args.length === 1 && args[0] === '') {
|
161 | retval = cleanBlanks(retval)
|
162 | log.silly('cleanup', retval)
|
163 | }
|
164 |
|
165 | if (error || silent) {
|
166 | cb(error, retval)
|
167 | } else if (
|
168 | !npm.config.get('json') &&
|
169 | args.length === 1 &&
|
170 | args[0] === ''
|
171 | ) {
|
172 | data.version = version
|
173 | BB.all(results.map((v) => prettyView(data, v[Object.keys(v)[0]][''])))
|
174 | .nodeify(cb)
|
175 | .then(() => retval)
|
176 | } else {
|
177 | printData(retval, data._id, cb.bind(null, error, retval))
|
178 | }
|
179 | })
|
180 | })
|
181 | }
|
182 |
|
183 | function prettyView (packument, manifest) {
|
184 |
|
185 | const unicode = npm.config.get('unicode')
|
186 | return BB.try(() => {
|
187 | if (!manifest) {
|
188 | log.error(
|
189 | 'view',
|
190 | 'No matching versions.\n' +
|
191 | 'To see a list of versions, run:\n' +
|
192 | `> npm view ${packument.name} versions`
|
193 | )
|
194 | return
|
195 | }
|
196 | const tags = []
|
197 | Object.keys(packument['dist-tags']).forEach((t) => {
|
198 | const version = packument['dist-tags'][t]
|
199 | tags.push(`${style.bright(color.green(t))}: ${version}`)
|
200 | })
|
201 | const unpackedSize = manifest.dist.unpackedSize &&
|
202 | byteSize(manifest.dist.unpackedSize)
|
203 | const licenseField = manifest.license || manifest.licence || 'Proprietary'
|
204 | const info = {
|
205 | name: color.green(manifest.name),
|
206 | version: color.green(manifest.version),
|
207 | bins: Object.keys(manifest.bin || {}).map(color.yellow),
|
208 | versions: color.yellow(packument.versions.length + ''),
|
209 | description: manifest.description,
|
210 | deprecated: manifest.deprecated,
|
211 | keywords: (packument.keywords || []).map(color.yellow),
|
212 | license: typeof licenseField === 'string'
|
213 | ? licenseField
|
214 | : (licenseField.type || 'Proprietary'),
|
215 | deps: Object.keys(manifest.dependencies || {}).map((dep) => {
|
216 | return `${color.yellow(dep)}: ${manifest.dependencies[dep]}`
|
217 | }),
|
218 | publisher: manifest._npmUser && unparsePerson({
|
219 | name: color.yellow(manifest._npmUser.name),
|
220 | email: color.cyan(manifest._npmUser.email)
|
221 | }),
|
222 | modified: color.yellow(relativeDate(packument.time[packument.version])),
|
223 | maintainers: (packument.maintainers || []).map((u) => unparsePerson({
|
224 | name: color.yellow(u.name),
|
225 | email: color.cyan(u.email)
|
226 | })),
|
227 | repo: (
|
228 | manifest.bugs && (manifest.bugs.url || manifest.bugs)
|
229 | ) || (
|
230 | manifest.repository && (manifest.repository.url || manifest.repository)
|
231 | ),
|
232 | site: (
|
233 | manifest.homepage && (manifest.homepage.url || manifest.homepage)
|
234 | ),
|
235 | stars: color.yellow('' + packument.users ? Object.keys(packument.users || {}).length : 0),
|
236 | tags,
|
237 | tarball: color.cyan(manifest.dist.tarball),
|
238 | shasum: color.yellow(manifest.dist.shasum),
|
239 | integrity: manifest.dist.integrity && color.yellow(manifest.dist.integrity),
|
240 | fileCount: manifest.dist.fileCount && color.yellow(manifest.dist.fileCount),
|
241 | unpackedSize: unpackedSize && color.yellow(unpackedSize.value) + ' ' + unpackedSize.unit
|
242 | }
|
243 | if (info.license.toLowerCase().trim() === 'proprietary') {
|
244 | info.license = style.bright(color.red(info.license))
|
245 | } else {
|
246 | info.license = color.green(info.license)
|
247 | }
|
248 | console.log('')
|
249 | console.log(
|
250 | style.underline(style.bright(`${info.name}@${info.version}`)) +
|
251 | ' | ' + info.license +
|
252 | ' | deps: ' + (info.deps.length ? color.cyan(info.deps.length) : color.green('none')) +
|
253 | ' | versions: ' + info.versions
|
254 | )
|
255 | info.description && console.log(info.description)
|
256 | if (info.repo || info.site) {
|
257 | info.site && console.log(color.cyan(info.site))
|
258 | }
|
259 |
|
260 | const warningSign = unicode ? ' ⚠️ ' : '!!'
|
261 | info.deprecated && console.log(
|
262 | `\n${style.bright(color.red('DEPRECATED'))}${
|
263 | warningSign
|
264 | } - ${info.deprecated}`
|
265 | )
|
266 |
|
267 | if (info.keywords.length) {
|
268 | console.log('')
|
269 | console.log('keywords:', info.keywords.join(', '))
|
270 | }
|
271 |
|
272 | if (info.bins.length) {
|
273 | console.log('')
|
274 | console.log('bin:', info.bins.join(', '))
|
275 | }
|
276 |
|
277 | console.log('')
|
278 | console.log('dist')
|
279 | console.log('.tarball:', info.tarball)
|
280 | console.log('.shasum:', info.shasum)
|
281 | info.integrity && console.log('.integrity:', info.integrity)
|
282 | info.unpackedSize && console.log('.unpackedSize:', info.unpackedSize)
|
283 |
|
284 | const maxDeps = 24
|
285 | if (info.deps.length) {
|
286 | console.log('')
|
287 | console.log('dependencies:')
|
288 | console.log(columns(info.deps.slice(0, maxDeps), {padding: 1}))
|
289 | if (info.deps.length > maxDeps) {
|
290 | console.log(`(...and ${info.deps.length - maxDeps} more.)`)
|
291 | }
|
292 | }
|
293 |
|
294 | if (info.maintainers && info.maintainers.length) {
|
295 | console.log('')
|
296 | console.log('maintainers:')
|
297 | info.maintainers.forEach((u) => console.log('-', u))
|
298 | }
|
299 |
|
300 | console.log('')
|
301 | console.log('dist-tags:')
|
302 | console.log(columns(info.tags))
|
303 |
|
304 | if (info.publisher || info.modified) {
|
305 | let publishInfo = 'published'
|
306 | if (info.modified) { publishInfo += ` ${info.modified}` }
|
307 | if (info.publisher) { publishInfo += ` by ${info.publisher}` }
|
308 | console.log('')
|
309 | console.log(publishInfo)
|
310 | }
|
311 | })
|
312 | }
|
313 |
|
314 | function cleanBlanks (obj) {
|
315 | var clean = {}
|
316 | Object.keys(obj).forEach(function (version) {
|
317 | clean[version] = obj[version]['']
|
318 | })
|
319 | return clean
|
320 | }
|
321 |
|
322 | function reducer (l, r) {
|
323 | if (r) {
|
324 | Object.keys(r).forEach(function (v) {
|
325 | l[v] = l[v] || {}
|
326 | Object.keys(r[v]).forEach(function (t) {
|
327 | l[v][t] = r[v][t]
|
328 | })
|
329 | })
|
330 | }
|
331 |
|
332 | return l
|
333 | }
|
334 |
|
335 |
|
336 | function showFields (data, version, fields) {
|
337 | var o = {}
|
338 | ;[data, version].forEach(function (s) {
|
339 | Object.keys(s).forEach(function (k) {
|
340 | o[k] = s[k]
|
341 | })
|
342 | })
|
343 | return search(o, fields.split('.'), version.version, fields)
|
344 | }
|
345 |
|
346 | function search (data, fields, version, title) {
|
347 | var field
|
348 | var tail = fields
|
349 | while (!field && fields.length) field = tail.shift()
|
350 | fields = [field].concat(tail)
|
351 | var o
|
352 | if (!field && !tail.length) {
|
353 | o = {}
|
354 | o[version] = {}
|
355 | o[version][title] = data
|
356 | return o
|
357 | }
|
358 | var index = field.match(/(.+)\[([^\]]+)\]$/)
|
359 | if (index) {
|
360 | field = index[1]
|
361 | index = index[2]
|
362 | if (data.field && data.field.hasOwnProperty(index)) {
|
363 | return search(data[field][index], tail, version, title)
|
364 | } else {
|
365 | field = field + '[' + index + ']'
|
366 | }
|
367 | }
|
368 | if (Array.isArray(data)) {
|
369 | if (data.length === 1) {
|
370 | return search(data[0], fields, version, title)
|
371 | }
|
372 | var results = []
|
373 | data.forEach(function (data, i) {
|
374 | var tl = title.length
|
375 | var newt = title.substr(0, tl - fields.join('.').length - 1) +
|
376 | '[' + i + ']' + [''].concat(fields).join('.')
|
377 | results.push(search(data, fields.slice(), version, newt))
|
378 | })
|
379 | results = results.reduce(reducer, {})
|
380 | return results
|
381 | }
|
382 | if (!data.hasOwnProperty(field)) return undefined
|
383 | data = data[field]
|
384 | if (tail.length) {
|
385 | if (typeof data === 'object') {
|
386 |
|
387 | return search(data, tail, version, title)
|
388 | } else {
|
389 | return new Error('Not an object: ' + data)
|
390 | }
|
391 | }
|
392 | o = {}
|
393 | o[version] = {}
|
394 | o[version][title] = data
|
395 | return o
|
396 | }
|
397 |
|
398 | function printData (data, name, cb) {
|
399 | var versions = Object.keys(data)
|
400 | var msg = ''
|
401 | var msgJson = []
|
402 | var includeVersions = versions.length > 1
|
403 | var includeFields
|
404 |
|
405 | versions.forEach(function (v) {
|
406 | var fields = Object.keys(data[v])
|
407 | includeFields = includeFields || (fields.length > 1)
|
408 | if (npm.config.get('json')) msgJson.push({})
|
409 | fields.forEach(function (f) {
|
410 | var d = cleanup(data[v][f])
|
411 | if (fields.length === 1 && npm.config.get('json')) {
|
412 | msgJson[msgJson.length - 1][f] = d
|
413 | }
|
414 | if (includeVersions || includeFields || typeof d !== 'string') {
|
415 | if (npm.config.get('json')) {
|
416 | msgJson[msgJson.length - 1][f] = d
|
417 | } else {
|
418 | d = util.inspect(d, { showHidden: false, depth: 5, colors: npm.color, maxArrayLength: null })
|
419 | }
|
420 | } else if (typeof d === 'string' && npm.config.get('json')) {
|
421 | d = JSON.stringify(d)
|
422 | }
|
423 | if (!npm.config.get('json')) {
|
424 | if (f && includeFields) f += ' = '
|
425 | if (d.indexOf('\n') !== -1) d = ' \n' + d
|
426 | msg += (includeVersions ? name + '@' + v + ' ' : '') +
|
427 | (includeFields ? f : '') + d + '\n'
|
428 | }
|
429 | })
|
430 | })
|
431 |
|
432 | if (npm.config.get('json')) {
|
433 | if (msgJson.length && Object.keys(msgJson[0]).length === 1) {
|
434 | var k = Object.keys(msgJson[0])[0]
|
435 | msgJson = msgJson.map(function (m) { return m[k] })
|
436 | }
|
437 |
|
438 | if (msgJson.length === 1) {
|
439 | msg = JSON.stringify(msgJson[0], null, 2) + '\n'
|
440 | } else if (msgJson.length > 1) {
|
441 | msg = JSON.stringify(msgJson, null, 2) + '\n'
|
442 | }
|
443 | }
|
444 |
|
445 |
|
446 |
|
447 | if (/^\s*\n/.test(msg)) msg += '\n'
|
448 |
|
449 |
|
450 |
|
451 | log.disableProgress()
|
452 |
|
453 |
|
454 | process.stdout.write(msg, () => cb(null, data))
|
455 | }
|
456 | function cleanup (data) {
|
457 | if (Array.isArray(data)) {
|
458 | return data.map(cleanup)
|
459 | }
|
460 | if (!data || typeof data !== 'object') return data
|
461 |
|
462 | if (typeof data.versions === 'object' &&
|
463 | data.versions &&
|
464 | !Array.isArray(data.versions)) {
|
465 | data.versions = Object.keys(data.versions || {})
|
466 | }
|
467 |
|
468 | var keys = Object.keys(data)
|
469 | keys.forEach(function (d) {
|
470 | if (d.charAt(0) === '_') delete data[d]
|
471 | else if (typeof data[d] === 'object') data[d] = cleanup(data[d])
|
472 | })
|
473 | keys = Object.keys(data)
|
474 | if (keys.length <= 3 &&
|
475 | data.name &&
|
476 | (keys.length === 1 ||
|
477 | (keys.length === 3 && data.email && data.url) ||
|
478 | (keys.length === 2 && (data.email || data.url)))) {
|
479 | data = unparsePerson(data)
|
480 | }
|
481 | return data
|
482 | }
|
483 | function unparsePerson (d) {
|
484 | if (typeof d === 'string') return d
|
485 | return d.name +
|
486 | (d.email ? ' <' + d.email + '>' : '') +
|
487 | (d.url ? ' (' + d.url + ')' : '')
|
488 | }
|