codekingpro/portable-devtools
114k
1const tar = require('tar')
2const ssri = require('ssri')
3const { log, output } = require('proc-log')
4const formatBytes = require('./format-bytes.js')
5const localeCompare = require('@isaacs/string-locale-compare')('en', {
6 sensitivity: 'case',
7 numeric: true,
8})
9
10const logTar = (tarball, { unicode = false, json, key } = {}) => {
11 if (json) {
12 output.buffer(key == null ? tarball : { [key]: tarball })
13 return
14 }
15 log.notice('')
16 log.notice('', `${unicode ? '📦 ' : 'package:'} ${tarball.name}@${tarball.version}`)
17 log.notice('Tarball Contents')
18 if (tarball.files.length) {
19 log.notice(
20 '',
21 tarball.files.map(f =>
22 /^node_modules\//.test(f.path) ? null : `${formatBytes(f.size, false)} ${f.path}`
23 ).filter(f => f).join('\n')
24 )
25 }
26 if (tarball.bundled.length) {
27 log.notice('Bundled Dependencies')
28 tarball.bundled.forEach(name => log.notice('', name))
29 }
30 log.notice('Tarball Details')
31 log.notice('', `name: ${tarball.name}`)
32 log.notice('', `version: ${tarball.version}`)
33 if (tarball.filename) {
34 log.notice('', `filename: ${tarball.filename}`)
35 }
36 log.notice('', `package size: ${formatBytes(tarball.size)}`)
37 log.notice('', `unpacked size: ${formatBytes(tarball.unpackedSize)}`)
38 log.notice('', `shasum: ${tarball.shasum}`)
39 log.notice('', `integrity: ${tarball.integrity.toString().slice(0, 20)}[...]${tarball.integrity.toString().slice(80)}`)
40 if (tarball.bundled.length) {
41 log.notice('', `bundled deps: ${tarball.bundled.length}`)
42 log.notice('', `bundled files: ${tarball.entryCount - tarball.files.length}`)
43 log.notice('', `own files: ${tarball.files.length}`)
44 }
45 log.notice('', `total files: ${tarball.entryCount}`)
46 log.notice('', '')
47}
48
49const getContents = async (manifest, tarball) => {
50 const files = []
51 const bundled = new Set()
52 let totalEntries = 0
53 let totalEntrySize = 0
54
55 // reads contents of tarball
56 const stream = tar.t({
57 onentry (entry) {
58 totalEntries++
59 totalEntrySize += entry.size
60 const p = entry.path
61 if (p.startsWith('package/node_modules/') && p !== 'package/node_modules/') {
62 const name = p.match(/^package\/node_modules\/((?:@[^/]+\/)?[^/]+)/)[1]
63 bundled.add(name)
64 }
65 files.push({
66 path: entry.path.replace(/^package\//, ''),
67 size: entry.size,
68 mode: entry.mode,
69 })
70 },
71 })
72 stream.end(tarball)
73
74 const integrity = ssri.fromData(tarball, {
75 algorithms: ['sha1', 'sha512'],
76 })
77
78 const comparator = ({ path: a }, { path: b }) => localeCompare(a, b)
79
80 const isUpper = str => {
81 const ch = str.charAt(0)
82 return ch === ch.toUpperCase()
83 }
84
85 const uppers = files.filter(file => isUpper(file.path))
86 const others = files.filter(file => !isUpper(file.path))
87
88 uppers.sort(comparator)
89 others.sort(comparator)
90
91 const shasum = integrity.sha1[0].hexDigest()
92 return {
93 id: manifest._id || `${manifest.name}@${manifest.version}`,
94 name: manifest.name,
95 version: manifest.version,
96 size: tarball.length,
97 unpackedSize: totalEntrySize,
98 shasum,
99 integrity: ssri.parse(integrity.sha512[0]),
100 // @scope/packagename.tgz => scope-packagename.tgz
101 // we can safely use these global replace rules due to npm package naming rules
102 filename: `${manifest.name.replace('@', '').replace('/', '-')}-${manifest.version}.tgz`,
103 files: uppers.concat(others),
104 entryCount: totalEntries,
105 bundled: Array.from(bundled),
106 }
107}
108
109module.exports = { logTar, getContents }
110 