1const { resolve } = require('path')
2const npa = require('npm-package-arg')
3const semver = require('semver')
4
5const ArboristWorkspaceCmd = require('../arborist-cmd.js')
6class Rebuild extends ArboristWorkspaceCmd {
7  static description = 'Rebuild a package'
8  static name = 'rebuild'
9  static params = [
10    'global',
11    'bin-links',
12    'foreground-scripts',
13    'ignore-scripts',
14    ...super.params,
15  ]
16
17  static usage = ['[<package-spec>] ...]']
18
19  // TODO
20  /* istanbul ignore next */
21  static async completion (opts, npm) {
22    const completion = require('../utils/completion/installed-deep.js')
23    return completion(npm, opts)
24  }
25
26  async exec (args) {
27    const globalTop = resolve(this.npm.globalDir, '..')
28    const where = this.npm.global ? globalTop : this.npm.prefix
29    const Arborist = require('@npmcli/arborist')
30    const arb = new Arborist({
31      ...this.npm.flatOptions,
32      path: where,
33      // TODO when extending ReifyCmd
34      // workspaces: this.workspaceNames,
35    })
36
37    if (args.length) {
38      // get the set of nodes matching the name that we want rebuilt
39      const tree = await arb.loadActual()
40      const specs = args.map(arg => {
41        const spec = npa(arg)
42        if (spec.rawSpec === '*') {
43          return spec
44        }
45
46        if (spec.type !== 'range' && spec.type !== 'version' && spec.type !== 'directory') {
47          throw new Error('`npm rebuild` only supports SemVer version/range specifiers')
48        }
49
50        return spec
51      })
52      const nodes = tree.inventory.filter(node => this.isNode(specs, node))
53
54      await arb.rebuild({ nodes })
55    } else {
56      await arb.rebuild()
57    }
58
59    this.npm.output('rebuilt dependencies successfully')
60  }
61
62  isNode (specs, node) {
63    return specs.some(spec => {
64      if (spec.type === 'directory') {
65        return node.path === spec.fetchSpec
66      }
67
68      if (spec.name !== node.name) {
69        return false
70      }
71
72      if (spec.rawSpec === '' || spec.rawSpec === '*') {
73        return true
74      }
75
76      const { version } = node.package
77      // TODO: add tests for a package with missing version
78      return semver.satisfies(version, spec.fetchSpec)
79    })
80  }
81}
82module.exports = Rebuild
83