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