1const Fetcher = require('./fetcher.js')
2const FileFetcher = require('./file.js')
3const { Minipass } = require('minipass')
4const tarCreateOptions = require('./util/tar-create-options.js')
5const packlist = require('npm-packlist')
6const tar = require('tar')
7const _prepareDir = Symbol('_prepareDir')
8const { resolve } = require('path')
9const _readPackageJson = Symbol.for('package.Fetcher._readPackageJson')
10
11const runScript = require('@npmcli/run-script')
12
13const _tarballFromResolved = Symbol.for('pacote.Fetcher._tarballFromResolved')
14class DirFetcher extends Fetcher {
15  constructor (spec, opts) {
16    super(spec, opts)
17    // just the fully resolved filename
18    this.resolved = this.spec.fetchSpec
19
20    this.tree = opts.tree || null
21    this.Arborist = opts.Arborist || null
22  }
23
24  // exposes tarCreateOptions as public API
25  static tarCreateOptions (manifest) {
26    return tarCreateOptions(manifest)
27  }
28
29  get types () {
30    return ['directory']
31  }
32
33  [_prepareDir] () {
34    return this.manifest().then(mani => {
35      if (!mani.scripts || !mani.scripts.prepare) {
36        return
37      }
38
39      // we *only* run prepare.
40      // pre/post-pack is run by the npm CLI for publish and pack,
41      // but this function is *also* run when installing git deps
42      const stdio = this.opts.foregroundScripts ? 'inherit' : 'pipe'
43
44      // hide the banner if silent opt is passed in, or if prepare running
45      // in the background.
46      const banner = this.opts.silent ? false : stdio === 'inherit'
47
48      return runScript({
49        pkg: mani,
50        event: 'prepare',
51        path: this.resolved,
52        stdio,
53        banner,
54        env: {
55          npm_package_resolved: this.resolved,
56          npm_package_integrity: this.integrity,
57          npm_package_json: resolve(this.resolved, 'package.json'),
58        },
59      })
60    })
61  }
62
63  [_tarballFromResolved] () {
64    if (!this.tree && !this.Arborist) {
65      throw new Error('DirFetcher requires either a tree or an Arborist constructor to pack')
66    }
67
68    const stream = new Minipass()
69    stream.resolved = this.resolved
70    stream.integrity = this.integrity
71
72    const { prefix, workspaces } = this.opts
73
74    // run the prepare script, get the list of files, and tar it up
75    // pipe to the stream, and proxy errors the chain.
76    this[_prepareDir]()
77      .then(async () => {
78        if (!this.tree) {
79          const arb = new this.Arborist({ path: this.resolved })
80          this.tree = await arb.loadActual()
81        }
82        return packlist(this.tree, { path: this.resolved, prefix, workspaces })
83      })
84      .then(files => tar.c(tarCreateOptions(this.package), files)
85        .on('error', er => stream.emit('error', er)).pipe(stream))
86      .catch(er => stream.emit('error', er))
87    return stream
88  }
89
90  manifest () {
91    if (this.package) {
92      return Promise.resolve(this.package)
93    }
94
95    return this[_readPackageJson](this.resolved + '/package.json')
96      .then(mani => this.package = {
97        ...mani,
98        _integrity: this.integrity && String(this.integrity),
99        _resolved: this.resolved,
100        _from: this.from,
101      })
102  }
103
104  packument () {
105    return FileFetcher.prototype.packument.apply(this)
106  }
107}
108module.exports = DirFetcher
109