1const pacote = require('pacote')
2const libpack = require('libnpmpack')
3const npa = require('npm-package-arg')
4const log = require('../utils/log-shim')
5const { getContents, logTar } = require('../utils/tar.js')
6const BaseCommand = require('../base-command.js')
7
8class Pack extends BaseCommand {
9  static description = 'Create a tarball from a package'
10  static name = 'pack'
11  static params = [
12    'dry-run',
13    'json',
14    'pack-destination',
15    'workspace',
16    'workspaces',
17    'include-workspace-root',
18  ]
19
20  static usage = ['<package-spec>']
21  static workspaces = true
22  static ignoreImplicitWorkspace = false
23
24  async exec (args) {
25    if (args.length === 0) {
26      args = ['.']
27    }
28
29    const unicode = this.npm.config.get('unicode')
30    const json = this.npm.config.get('json')
31
32    // Get the manifests and filenames first so we can bail early on manifest
33    // errors before making any tarballs
34    const manifests = []
35    for (const arg of args) {
36      const spec = npa(arg)
37      const manifest = await pacote.manifest(spec, this.npm.flatOptions)
38      if (!manifest._id) {
39        throw new Error('Invalid package, must have name and version')
40      }
41      manifests.push({ arg, manifest })
42    }
43
44    // Load tarball names up for printing afterward to isolate from the
45    // noise generated during packing
46    const tarballs = []
47    for (const { arg, manifest } of manifests) {
48      const tarballData = await libpack(arg, {
49        ...this.npm.flatOptions,
50        foregroundScripts: this.npm.config.isDefault('foreground-scripts')
51          ? true
52          : this.npm.config.get('foreground-scripts'),
53        prefix: this.npm.localPrefix,
54        workspaces: this.workspacePaths,
55      })
56      const pkgContents = await getContents(manifest, tarballData)
57      tarballs.push(pkgContents)
58    }
59
60    if (json) {
61      this.npm.output(JSON.stringify(tarballs, null, 2))
62      return
63    }
64
65    for (const tar of tarballs) {
66      logTar(tar, { unicode })
67      this.npm.output(tar.filename.replace(/^@/, '').replace(/\//, '-'))
68    }
69  }
70
71  async execWorkspaces (args) {
72    // If they either ask for nothing, or explicitly include '.' in the args,
73    // we effectively translate that into each workspace requested
74
75    const useWorkspaces = args.length === 0 || args.includes('.')
76
77    if (!useWorkspaces) {
78      log.warn('Ignoring workspaces for specified package(s)')
79      return this.exec(args)
80    }
81
82    await this.setWorkspaces()
83    return this.exec([...this.workspacePaths, ...args.filter(a => a !== '.')])
84  }
85}
86module.exports = Pack
87