11cb0ef41Sopenharmony_ciconst reifyFinish = require('../utils/reify-finish.js')
21cb0ef41Sopenharmony_ciconst runScript = require('@npmcli/run-script')
31cb0ef41Sopenharmony_ciconst fs = require('fs/promises')
41cb0ef41Sopenharmony_ciconst log = require('../utils/log-shim.js')
51cb0ef41Sopenharmony_ciconst validateLockfile = require('../utils/validate-lockfile.js')
61cb0ef41Sopenharmony_ci
71cb0ef41Sopenharmony_ciconst ArboristWorkspaceCmd = require('../arborist-cmd.js')
81cb0ef41Sopenharmony_ci
91cb0ef41Sopenharmony_ciclass CI extends ArboristWorkspaceCmd {
101cb0ef41Sopenharmony_ci  static description = 'Clean install a project'
111cb0ef41Sopenharmony_ci  static name = 'ci'
121cb0ef41Sopenharmony_ci
131cb0ef41Sopenharmony_ci  // These are in the order they will show up in when running "-h"
141cb0ef41Sopenharmony_ci  static params = [
151cb0ef41Sopenharmony_ci    'install-strategy',
161cb0ef41Sopenharmony_ci    'legacy-bundling',
171cb0ef41Sopenharmony_ci    'global-style',
181cb0ef41Sopenharmony_ci    'omit',
191cb0ef41Sopenharmony_ci    'include',
201cb0ef41Sopenharmony_ci    'strict-peer-deps',
211cb0ef41Sopenharmony_ci    'foreground-scripts',
221cb0ef41Sopenharmony_ci    'ignore-scripts',
231cb0ef41Sopenharmony_ci    'audit',
241cb0ef41Sopenharmony_ci    'bin-links',
251cb0ef41Sopenharmony_ci    'fund',
261cb0ef41Sopenharmony_ci    'dry-run',
271cb0ef41Sopenharmony_ci    ...super.params,
281cb0ef41Sopenharmony_ci  ]
291cb0ef41Sopenharmony_ci
301cb0ef41Sopenharmony_ci  async exec () {
311cb0ef41Sopenharmony_ci    if (this.npm.global) {
321cb0ef41Sopenharmony_ci      throw Object.assign(new Error('`npm ci` does not work for global packages'), {
331cb0ef41Sopenharmony_ci        code: 'ECIGLOBAL',
341cb0ef41Sopenharmony_ci      })
351cb0ef41Sopenharmony_ci    }
361cb0ef41Sopenharmony_ci
371cb0ef41Sopenharmony_ci    const where = this.npm.prefix
381cb0ef41Sopenharmony_ci    const Arborist = require('@npmcli/arborist')
391cb0ef41Sopenharmony_ci    const opts = {
401cb0ef41Sopenharmony_ci      ...this.npm.flatOptions,
411cb0ef41Sopenharmony_ci      packageLock: true, // npm ci should never skip lock files
421cb0ef41Sopenharmony_ci      path: where,
431cb0ef41Sopenharmony_ci      save: false, // npm ci should never modify the lockfile or package.json
441cb0ef41Sopenharmony_ci      workspaces: this.workspaceNames,
451cb0ef41Sopenharmony_ci    }
461cb0ef41Sopenharmony_ci
471cb0ef41Sopenharmony_ci    const arb = new Arborist(opts)
481cb0ef41Sopenharmony_ci    await arb.loadVirtual().catch(er => {
491cb0ef41Sopenharmony_ci      log.verbose('loadVirtual', er.stack)
501cb0ef41Sopenharmony_ci      const msg =
511cb0ef41Sopenharmony_ci        'The `npm ci` command can only install with an existing package-lock.json or\n' +
521cb0ef41Sopenharmony_ci        'npm-shrinkwrap.json with lockfileVersion >= 1. Run an install with npm@5 or\n' +
531cb0ef41Sopenharmony_ci        'later to generate a package-lock.json file, then try again.'
541cb0ef41Sopenharmony_ci      throw this.usageError(msg)
551cb0ef41Sopenharmony_ci    })
561cb0ef41Sopenharmony_ci
571cb0ef41Sopenharmony_ci    // retrieves inventory of packages from loaded virtual tree (lock file)
581cb0ef41Sopenharmony_ci    const virtualInventory = new Map(arb.virtualTree.inventory)
591cb0ef41Sopenharmony_ci
601cb0ef41Sopenharmony_ci    // build ideal tree step needs to come right after retrieving the virtual
611cb0ef41Sopenharmony_ci    // inventory since it's going to erase the previous ref to virtualTree
621cb0ef41Sopenharmony_ci    await arb.buildIdealTree()
631cb0ef41Sopenharmony_ci
641cb0ef41Sopenharmony_ci    // verifies that the packages from the ideal tree will match
651cb0ef41Sopenharmony_ci    // the same versions that are present in the virtual tree (lock file)
661cb0ef41Sopenharmony_ci    // throws a validation error in case of mismatches
671cb0ef41Sopenharmony_ci    const errors = validateLockfile(virtualInventory, arb.idealTree.inventory)
681cb0ef41Sopenharmony_ci    if (errors.length) {
691cb0ef41Sopenharmony_ci      throw this.usageError(
701cb0ef41Sopenharmony_ci        '`npm ci` can only install packages when your package.json and ' +
711cb0ef41Sopenharmony_ci        'package-lock.json or npm-shrinkwrap.json are in sync. Please ' +
721cb0ef41Sopenharmony_ci        'update your lock file with `npm install` ' +
731cb0ef41Sopenharmony_ci        'before continuing.\n\n' +
741cb0ef41Sopenharmony_ci        errors.join('\n')
751cb0ef41Sopenharmony_ci      )
761cb0ef41Sopenharmony_ci    }
771cb0ef41Sopenharmony_ci
781cb0ef41Sopenharmony_ci    // Only remove node_modules after we've successfully loaded the virtual
791cb0ef41Sopenharmony_ci    // tree and validated the lockfile
801cb0ef41Sopenharmony_ci    await this.npm.time('npm-ci:rm', async () => {
811cb0ef41Sopenharmony_ci      const path = `${where}/node_modules`
821cb0ef41Sopenharmony_ci      // get the list of entries so we can skip the glob for performance
831cb0ef41Sopenharmony_ci      const entries = await fs.readdir(path, null).catch(er => [])
841cb0ef41Sopenharmony_ci      return Promise.all(entries.map(f => fs.rm(`${path}/${f}`, { force: true, recursive: true })))
851cb0ef41Sopenharmony_ci    })
861cb0ef41Sopenharmony_ci
871cb0ef41Sopenharmony_ci    await arb.reify(opts)
881cb0ef41Sopenharmony_ci
891cb0ef41Sopenharmony_ci    const ignoreScripts = this.npm.config.get('ignore-scripts')
901cb0ef41Sopenharmony_ci    // run the same set of scripts that `npm install` runs.
911cb0ef41Sopenharmony_ci    if (!ignoreScripts) {
921cb0ef41Sopenharmony_ci      const scripts = [
931cb0ef41Sopenharmony_ci        'preinstall',
941cb0ef41Sopenharmony_ci        'install',
951cb0ef41Sopenharmony_ci        'postinstall',
961cb0ef41Sopenharmony_ci        'prepublish', // XXX should we remove this finally??
971cb0ef41Sopenharmony_ci        'preprepare',
981cb0ef41Sopenharmony_ci        'prepare',
991cb0ef41Sopenharmony_ci        'postprepare',
1001cb0ef41Sopenharmony_ci      ]
1011cb0ef41Sopenharmony_ci      const scriptShell = this.npm.config.get('script-shell') || undefined
1021cb0ef41Sopenharmony_ci      for (const event of scripts) {
1031cb0ef41Sopenharmony_ci        await runScript({
1041cb0ef41Sopenharmony_ci          path: where,
1051cb0ef41Sopenharmony_ci          args: [],
1061cb0ef41Sopenharmony_ci          scriptShell,
1071cb0ef41Sopenharmony_ci          stdio: 'inherit',
1081cb0ef41Sopenharmony_ci          banner: !this.npm.silent,
1091cb0ef41Sopenharmony_ci          event,
1101cb0ef41Sopenharmony_ci        })
1111cb0ef41Sopenharmony_ci      }
1121cb0ef41Sopenharmony_ci    }
1131cb0ef41Sopenharmony_ci    await reifyFinish(this.npm, arb)
1141cb0ef41Sopenharmony_ci  }
1151cb0ef41Sopenharmony_ci}
1161cb0ef41Sopenharmony_ci
1171cb0ef41Sopenharmony_cimodule.exports = CI
118