tar.js 3.4 KB

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