11cb0ef41Sopenharmony_ciconst { createHook, executionAsyncId } = require('async_hooks')
21cb0ef41Sopenharmony_ciconst { EventEmitter } = require('events')
31cb0ef41Sopenharmony_ciconst { homedir, tmpdir } = require('os')
41cb0ef41Sopenharmony_ciconst { dirname, join } = require('path')
51cb0ef41Sopenharmony_ciconst { mkdir, rm } = require('fs/promises')
61cb0ef41Sopenharmony_ciconst mockLogs = require('./mock-logs')
71cb0ef41Sopenharmony_ciconst pkg = require('../../package.json')
81cb0ef41Sopenharmony_ci
91cb0ef41Sopenharmony_ciconst chain = new Map()
101cb0ef41Sopenharmony_ciconst sandboxes = new Map()
111cb0ef41Sopenharmony_ci
121cb0ef41Sopenharmony_ci// keep a reference to the real process
131cb0ef41Sopenharmony_ciconst _process = process
141cb0ef41Sopenharmony_ci
151cb0ef41Sopenharmony_cicreateHook({
161cb0ef41Sopenharmony_ci  init: (asyncId, type, triggerAsyncId, resource) => {
171cb0ef41Sopenharmony_ci    // track parentage of asyncIds
181cb0ef41Sopenharmony_ci    chain.set(asyncId, triggerAsyncId)
191cb0ef41Sopenharmony_ci  },
201cb0ef41Sopenharmony_ci  before: (asyncId) => {
211cb0ef41Sopenharmony_ci    // find the nearest parent id that has a sandbox
221cb0ef41Sopenharmony_ci    let parent = asyncId
231cb0ef41Sopenharmony_ci    while (chain.has(parent) && !sandboxes.has(parent)) {
241cb0ef41Sopenharmony_ci      parent = chain.get(parent)
251cb0ef41Sopenharmony_ci    }
261cb0ef41Sopenharmony_ci
271cb0ef41Sopenharmony_ci    process = sandboxes.has(parent)
281cb0ef41Sopenharmony_ci      ? sandboxes.get(parent)
291cb0ef41Sopenharmony_ci      : _process
301cb0ef41Sopenharmony_ci  },
311cb0ef41Sopenharmony_ci}).enable()
321cb0ef41Sopenharmony_ci
331cb0ef41Sopenharmony_ciconst _data = Symbol('sandbox.data')
341cb0ef41Sopenharmony_ciconst _dirs = Symbol('sandbox.dirs')
351cb0ef41Sopenharmony_ciconst _test = Symbol('sandbox.test')
361cb0ef41Sopenharmony_ciconst _mocks = Symbol('sandbox.mocks')
371cb0ef41Sopenharmony_ciconst _npm = Symbol('sandbox.npm')
381cb0ef41Sopenharmony_ciconst _parent = Symbol('sandbox.parent')
391cb0ef41Sopenharmony_ciconst _output = Symbol('sandbox.output')
401cb0ef41Sopenharmony_ciconst _proxy = Symbol('sandbox.proxy')
411cb0ef41Sopenharmony_ciconst _get = Symbol('sandbox.proxy.get')
421cb0ef41Sopenharmony_ciconst _set = Symbol('sandbox.proxy.set')
431cb0ef41Sopenharmony_ciconst _logs = Symbol('sandbox.logs')
441cb0ef41Sopenharmony_ci
451cb0ef41Sopenharmony_ci// we can't just replace these values everywhere because they're known to be
461cb0ef41Sopenharmony_ci// very short strings that could be present all over the place, so we only
471cb0ef41Sopenharmony_ci// replace them if they're located within quotes for now
481cb0ef41Sopenharmony_ciconst vagueRedactedDefaults = [
491cb0ef41Sopenharmony_ci  'editor',
501cb0ef41Sopenharmony_ci  'shell',
511cb0ef41Sopenharmony_ci]
521cb0ef41Sopenharmony_ci
531cb0ef41Sopenharmony_ciconst normalize = (str) => str
541cb0ef41Sopenharmony_ci  .replace(/\r\n/g, '\n') // normalize line endings (for ini)
551cb0ef41Sopenharmony_ci  .replace(/[A-z]:\\/g, '\\') // turn windows roots to posix ones
561cb0ef41Sopenharmony_ci  .replace(/\\+/g, '/') // replace \ with /
571cb0ef41Sopenharmony_ci
581cb0ef41Sopenharmony_ciclass Sandbox extends EventEmitter {
591cb0ef41Sopenharmony_ci  constructor (test, options = {}) {
601cb0ef41Sopenharmony_ci    super()
611cb0ef41Sopenharmony_ci
621cb0ef41Sopenharmony_ci    this[_test] = test
631cb0ef41Sopenharmony_ci    this[_mocks] = options.mocks || {}
641cb0ef41Sopenharmony_ci    this[_data] = new Map()
651cb0ef41Sopenharmony_ci    this[_output] = []
661cb0ef41Sopenharmony_ci    const tempDir = `${test.testdirName}-sandbox`
671cb0ef41Sopenharmony_ci    this[_dirs] = {
681cb0ef41Sopenharmony_ci      temp: tempDir,
691cb0ef41Sopenharmony_ci      global: options.global || join(tempDir, 'global'),
701cb0ef41Sopenharmony_ci      home: options.home || join(tempDir, 'home'),
711cb0ef41Sopenharmony_ci      project: options.project || join(tempDir, 'project'),
721cb0ef41Sopenharmony_ci      cache: options.cache || join(tempDir, 'cache'),
731cb0ef41Sopenharmony_ci    }
741cb0ef41Sopenharmony_ci
751cb0ef41Sopenharmony_ci    this[_proxy] = new Proxy(_process, {
761cb0ef41Sopenharmony_ci      get: this[_get].bind(this),
771cb0ef41Sopenharmony_ci      set: this[_set].bind(this),
781cb0ef41Sopenharmony_ci    })
791cb0ef41Sopenharmony_ci    this[_proxy].env = { ...options.env }
801cb0ef41Sopenharmony_ci    this[_proxy].argv = []
811cb0ef41Sopenharmony_ci
821cb0ef41Sopenharmony_ci    test.cleanSnapshot = this.cleanSnapshot.bind(this)
831cb0ef41Sopenharmony_ci    test.afterEach(() => this.reset())
841cb0ef41Sopenharmony_ci    test.teardown(() => this.teardown())
851cb0ef41Sopenharmony_ci  }
861cb0ef41Sopenharmony_ci
871cb0ef41Sopenharmony_ci  get config () {
881cb0ef41Sopenharmony_ci    return this[_npm] && this[_npm].config
891cb0ef41Sopenharmony_ci  }
901cb0ef41Sopenharmony_ci
911cb0ef41Sopenharmony_ci  get logs () {
921cb0ef41Sopenharmony_ci    return this[_logs]
931cb0ef41Sopenharmony_ci  }
941cb0ef41Sopenharmony_ci
951cb0ef41Sopenharmony_ci  get global () {
961cb0ef41Sopenharmony_ci    return this[_dirs].global
971cb0ef41Sopenharmony_ci  }
981cb0ef41Sopenharmony_ci
991cb0ef41Sopenharmony_ci  get home () {
1001cb0ef41Sopenharmony_ci    return this[_dirs].home
1011cb0ef41Sopenharmony_ci  }
1021cb0ef41Sopenharmony_ci
1031cb0ef41Sopenharmony_ci  get project () {
1041cb0ef41Sopenharmony_ci    return this[_dirs].project
1051cb0ef41Sopenharmony_ci  }
1061cb0ef41Sopenharmony_ci
1071cb0ef41Sopenharmony_ci  get cache () {
1081cb0ef41Sopenharmony_ci    return this[_dirs].cache
1091cb0ef41Sopenharmony_ci  }
1101cb0ef41Sopenharmony_ci
1111cb0ef41Sopenharmony_ci  get process () {
1121cb0ef41Sopenharmony_ci    return this[_proxy]
1131cb0ef41Sopenharmony_ci  }
1141cb0ef41Sopenharmony_ci
1151cb0ef41Sopenharmony_ci  get output () {
1161cb0ef41Sopenharmony_ci    return this[_output].map((line) => line.join(' ')).join('\n')
1171cb0ef41Sopenharmony_ci  }
1181cb0ef41Sopenharmony_ci
1191cb0ef41Sopenharmony_ci  cleanSnapshot (snapshot) {
1201cb0ef41Sopenharmony_ci    let clean = normalize(snapshot)
1211cb0ef41Sopenharmony_ci
1221cb0ef41Sopenharmony_ci    const viewer = _process.platform === 'win32'
1231cb0ef41Sopenharmony_ci      ? /"browser"([^:]+|$)/g
1241cb0ef41Sopenharmony_ci      : /"man"([^:]+|$)/g
1251cb0ef41Sopenharmony_ci
1261cb0ef41Sopenharmony_ci    // the global prefix is platform dependent
1271cb0ef41Sopenharmony_ci    const realGlobalPrefix = _process.platform === 'win32'
1281cb0ef41Sopenharmony_ci      ? dirname(_process.execPath)
1291cb0ef41Sopenharmony_ci      : dirname(dirname(_process.execPath))
1301cb0ef41Sopenharmony_ci
1311cb0ef41Sopenharmony_ci    const cache = _process.platform === 'win32'
1321cb0ef41Sopenharmony_ci      ? /\{HOME\}\/npm-cache(\r?\n|"|\/|$)/g
1331cb0ef41Sopenharmony_ci      : /\{HOME\}\/\.npm(\n|"|\/|$)/g
1341cb0ef41Sopenharmony_ci
1351cb0ef41Sopenharmony_ci    // and finally replace some paths we know could be present
1361cb0ef41Sopenharmony_ci    clean = clean
1371cb0ef41Sopenharmony_ci      .replace(viewer, '"{VIEWER}"$1')
1381cb0ef41Sopenharmony_ci      .split(normalize(this[_proxy].execPath)).join('{EXECPATH}')
1391cb0ef41Sopenharmony_ci      .split(normalize(_process.execPath)).join('{REALEXECPATH}')
1401cb0ef41Sopenharmony_ci      .split(normalize(this.global)).join('{GLOBALPREFIX}')
1411cb0ef41Sopenharmony_ci      .split(normalize(realGlobalPrefix)).join('{REALGLOBALREFIX}')
1421cb0ef41Sopenharmony_ci      .split(normalize(this.project)).join('{LOCALPREFIX}')
1431cb0ef41Sopenharmony_ci      .split(normalize(this.home)).join('{HOME}')
1441cb0ef41Sopenharmony_ci      .replace(cache, '{CACHE}$1')
1451cb0ef41Sopenharmony_ci      .split(normalize(dirname(dirname(__dirname)))).join('{NPMDIR}')
1461cb0ef41Sopenharmony_ci      .split(normalize(tmpdir())).join('{TMP}')
1471cb0ef41Sopenharmony_ci      .split(normalize(homedir())).join('{REALHOME}')
1481cb0ef41Sopenharmony_ci      .split(this[_proxy].platform).join('{PLATFORM}')
1491cb0ef41Sopenharmony_ci      .split(this[_proxy].arch).join('{ARCH}')
1501cb0ef41Sopenharmony_ci      .replace(new RegExp(process.version, 'g'), '{NODE-VERSION}')
1511cb0ef41Sopenharmony_ci      .replace(new RegExp(pkg.version, 'g'), '{NPM-VERSION}')
1521cb0ef41Sopenharmony_ci
1531cb0ef41Sopenharmony_ci    // We do the defaults after everything else so that they don't cause the
1541cb0ef41Sopenharmony_ci    // other cleaners to miss values we would have clobbered here.  For
1551cb0ef41Sopenharmony_ci    // instance if execPath is /home/user/.nvm/versions/node/1.0.0/bin/node,
1561cb0ef41Sopenharmony_ci    // and we replaced the node version first, the real execPath we're trying
1571cb0ef41Sopenharmony_ci    // to replace would no longer be represented, and be missed.
1581cb0ef41Sopenharmony_ci    if (this[_npm]) {
1591cb0ef41Sopenharmony_ci      // replace vague default config values that are present within quotes
1601cb0ef41Sopenharmony_ci      // with placeholders
1611cb0ef41Sopenharmony_ci      for (const name of vagueRedactedDefaults) {
1621cb0ef41Sopenharmony_ci        const value = this[_npm].config.defaults[name]
1631cb0ef41Sopenharmony_ci        clean = clean.split(`"${normalize(value)}"`).join(`"{${name.toUpperCase()}}"`)
1641cb0ef41Sopenharmony_ci      }
1651cb0ef41Sopenharmony_ci    }
1661cb0ef41Sopenharmony_ci
1671cb0ef41Sopenharmony_ci    return clean
1681cb0ef41Sopenharmony_ci  }
1691cb0ef41Sopenharmony_ci
1701cb0ef41Sopenharmony_ci  // test.afterEach hook
1711cb0ef41Sopenharmony_ci  reset () {
1721cb0ef41Sopenharmony_ci    this.removeAllListeners()
1731cb0ef41Sopenharmony_ci    this[_parent] = undefined
1741cb0ef41Sopenharmony_ci    this[_output] = []
1751cb0ef41Sopenharmony_ci    this[_data].clear()
1761cb0ef41Sopenharmony_ci    this[_proxy].env = {}
1771cb0ef41Sopenharmony_ci    this[_proxy].argv = []
1781cb0ef41Sopenharmony_ci    this[_npm] = undefined
1791cb0ef41Sopenharmony_ci  }
1801cb0ef41Sopenharmony_ci
1811cb0ef41Sopenharmony_ci  // test.teardown hook
1821cb0ef41Sopenharmony_ci  teardown () {
1831cb0ef41Sopenharmony_ci    if (this[_parent]) {
1841cb0ef41Sopenharmony_ci      const sandboxProcess = sandboxes.get(this[_parent])
1851cb0ef41Sopenharmony_ci      sandboxProcess.removeAllListeners('log')
1861cb0ef41Sopenharmony_ci      sandboxes.delete(this[_parent])
1871cb0ef41Sopenharmony_ci    }
1881cb0ef41Sopenharmony_ci    if (this[_npm]) {
1891cb0ef41Sopenharmony_ci      this[_npm].unload()
1901cb0ef41Sopenharmony_ci    }
1911cb0ef41Sopenharmony_ci    return rm(this[_dirs].temp, { recursive: true, force: true }).catch(() => null)
1921cb0ef41Sopenharmony_ci  }
1931cb0ef41Sopenharmony_ci
1941cb0ef41Sopenharmony_ci  // proxy get handler
1951cb0ef41Sopenharmony_ci  [_get] (target, prop, receiver) {
1961cb0ef41Sopenharmony_ci    if (this[_data].has(prop)) {
1971cb0ef41Sopenharmony_ci      return this[_data].get(prop)
1981cb0ef41Sopenharmony_ci    }
1991cb0ef41Sopenharmony_ci
2001cb0ef41Sopenharmony_ci    if (this[prop] !== undefined) {
2011cb0ef41Sopenharmony_ci      return Reflect.get(this, prop, this)
2021cb0ef41Sopenharmony_ci    }
2031cb0ef41Sopenharmony_ci
2041cb0ef41Sopenharmony_ci    return Reflect.get(target, prop, receiver)
2051cb0ef41Sopenharmony_ci  }
2061cb0ef41Sopenharmony_ci
2071cb0ef41Sopenharmony_ci  // proxy set handler
2081cb0ef41Sopenharmony_ci  [_set] (target, prop, value) {
2091cb0ef41Sopenharmony_ci    if (prop === 'env') {
2101cb0ef41Sopenharmony_ci      value = {
2111cb0ef41Sopenharmony_ci        ...value,
2121cb0ef41Sopenharmony_ci        HOME: this.home,
2131cb0ef41Sopenharmony_ci      }
2141cb0ef41Sopenharmony_ci    }
2151cb0ef41Sopenharmony_ci
2161cb0ef41Sopenharmony_ci    if (prop === 'argv') {
2171cb0ef41Sopenharmony_ci      value = [
2181cb0ef41Sopenharmony_ci        process.execPath,
2191cb0ef41Sopenharmony_ci        join(dirname(process.execPath), 'npm'),
2201cb0ef41Sopenharmony_ci        ...value,
2211cb0ef41Sopenharmony_ci      ]
2221cb0ef41Sopenharmony_ci    }
2231cb0ef41Sopenharmony_ci
2241cb0ef41Sopenharmony_ci    return this[_data].set(prop, value)
2251cb0ef41Sopenharmony_ci  }
2261cb0ef41Sopenharmony_ci
2271cb0ef41Sopenharmony_ci  async run (command, argv = []) {
2281cb0ef41Sopenharmony_ci    await Promise.all([
2291cb0ef41Sopenharmony_ci      mkdir(this.project, { recursive: true }),
2301cb0ef41Sopenharmony_ci      mkdir(this.home, { recursive: true }),
2311cb0ef41Sopenharmony_ci      mkdir(this.global, { recursive: true }),
2321cb0ef41Sopenharmony_ci    ])
2331cb0ef41Sopenharmony_ci
2341cb0ef41Sopenharmony_ci    // attach the sandbox process now, doing it after the promise above is
2351cb0ef41Sopenharmony_ci    // necessary to make sure that only async calls spawned as part of this
2361cb0ef41Sopenharmony_ci    // call to run will receive the sandbox. if we attach it too early, we
2371cb0ef41Sopenharmony_ci    // end up interfering with tap
2381cb0ef41Sopenharmony_ci    this[_parent] = executionAsyncId()
2391cb0ef41Sopenharmony_ci    this[_data].set('_asyncId', this[_parent])
2401cb0ef41Sopenharmony_ci    sandboxes.set(this[_parent], this[_proxy])
2411cb0ef41Sopenharmony_ci    process = this[_proxy]
2421cb0ef41Sopenharmony_ci
2431cb0ef41Sopenharmony_ci    this[_proxy].argv = [
2441cb0ef41Sopenharmony_ci      '--prefix', this.project,
2451cb0ef41Sopenharmony_ci      '--userconfig', join(this.home, '.npmrc'),
2461cb0ef41Sopenharmony_ci      '--globalconfig', join(this.global, 'npmrc'),
2471cb0ef41Sopenharmony_ci      '--cache', this.cache,
2481cb0ef41Sopenharmony_ci      command,
2491cb0ef41Sopenharmony_ci      ...argv,
2501cb0ef41Sopenharmony_ci    ]
2511cb0ef41Sopenharmony_ci
2521cb0ef41Sopenharmony_ci    const mockedLogs = mockLogs(this[_mocks])
2531cb0ef41Sopenharmony_ci    this[_logs] = mockedLogs.logs
2541cb0ef41Sopenharmony_ci    const definitions = this[_test].mock('@npmcli/config/lib/definitions')
2551cb0ef41Sopenharmony_ci    const Npm = this[_test].mock('../../lib/npm.js', {
2561cb0ef41Sopenharmony_ci      '@npmcli/config/lib/definitions': definitions,
2571cb0ef41Sopenharmony_ci      '../../lib/utils/update-notifier.js': async () => {},
2581cb0ef41Sopenharmony_ci      ...this[_mocks],
2591cb0ef41Sopenharmony_ci      ...mockedLogs.logMocks,
2601cb0ef41Sopenharmony_ci    })
2611cb0ef41Sopenharmony_ci    this.process.on('log', (l, ...args) => {
2621cb0ef41Sopenharmony_ci      if (l !== 'pause' && l !== 'resume') {
2631cb0ef41Sopenharmony_ci        this[_logs].push([l, ...args])
2641cb0ef41Sopenharmony_ci      }
2651cb0ef41Sopenharmony_ci    })
2661cb0ef41Sopenharmony_ci
2671cb0ef41Sopenharmony_ci    this[_npm] = new Npm()
2681cb0ef41Sopenharmony_ci    this[_npm].output = (...args) => this[_output].push(args)
2691cb0ef41Sopenharmony_ci    await this[_npm].load()
2701cb0ef41Sopenharmony_ci
2711cb0ef41Sopenharmony_ci    const cmd = this[_npm].argv.shift()
2721cb0ef41Sopenharmony_ci    return this[_npm].exec(cmd, this[_npm].argv)
2731cb0ef41Sopenharmony_ci  }
2741cb0ef41Sopenharmony_ci
2751cb0ef41Sopenharmony_ci  async complete (command, argv, partial) {
2761cb0ef41Sopenharmony_ci    if (!Array.isArray(argv)) {
2771cb0ef41Sopenharmony_ci      partial = argv
2781cb0ef41Sopenharmony_ci      argv = []
2791cb0ef41Sopenharmony_ci    }
2801cb0ef41Sopenharmony_ci
2811cb0ef41Sopenharmony_ci    await Promise.all([
2821cb0ef41Sopenharmony_ci      mkdir(this.project, { recursive: true }),
2831cb0ef41Sopenharmony_ci      mkdir(this.home, { recursive: true }),
2841cb0ef41Sopenharmony_ci      mkdir(this.global, { recursive: true }),
2851cb0ef41Sopenharmony_ci    ])
2861cb0ef41Sopenharmony_ci
2871cb0ef41Sopenharmony_ci    // attach the sandbox process now, doing it after the promise above is
2881cb0ef41Sopenharmony_ci    // necessary to make sure that only async calls spawned as part of this
2891cb0ef41Sopenharmony_ci    // call to run will receive the sandbox. if we attach it too early, we
2901cb0ef41Sopenharmony_ci    // end up interfering with tap
2911cb0ef41Sopenharmony_ci    this[_parent] = executionAsyncId()
2921cb0ef41Sopenharmony_ci    this[_data].set('_asyncId', this[_parent])
2931cb0ef41Sopenharmony_ci    sandboxes.set(this[_parent], this[_proxy])
2941cb0ef41Sopenharmony_ci    process = this[_proxy]
2951cb0ef41Sopenharmony_ci
2961cb0ef41Sopenharmony_ci    this[_proxy].argv = [
2971cb0ef41Sopenharmony_ci      '--prefix', this.project,
2981cb0ef41Sopenharmony_ci      '--userconfig', join(this.home, '.npmrc'),
2991cb0ef41Sopenharmony_ci      '--globalconfig', join(this.global, 'npmrc'),
3001cb0ef41Sopenharmony_ci      '--cache', this.cache,
3011cb0ef41Sopenharmony_ci      command,
3021cb0ef41Sopenharmony_ci      ...argv,
3031cb0ef41Sopenharmony_ci    ]
3041cb0ef41Sopenharmony_ci
3051cb0ef41Sopenharmony_ci    const mockedLogs = mockLogs(this[_mocks])
3061cb0ef41Sopenharmony_ci    this[_logs] = mockedLogs.logs
3071cb0ef41Sopenharmony_ci    const definitions = this[_test].mock('@npmcli/config/lib/definitions')
3081cb0ef41Sopenharmony_ci    const Npm = this[_test].mock('../../lib/npm.js', {
3091cb0ef41Sopenharmony_ci      '@npmcli/config/lib/definitions': definitions,
3101cb0ef41Sopenharmony_ci      '../../lib/utils/update-notifier.js': async () => {},
3111cb0ef41Sopenharmony_ci      ...this[_mocks],
3121cb0ef41Sopenharmony_ci      ...mockedLogs.logMocks,
3131cb0ef41Sopenharmony_ci    })
3141cb0ef41Sopenharmony_ci    this.process.on('log', (l, ...args) => {
3151cb0ef41Sopenharmony_ci      if (l !== 'pause' && l !== 'resume') {
3161cb0ef41Sopenharmony_ci        this[_logs].push([l, ...args])
3171cb0ef41Sopenharmony_ci      }
3181cb0ef41Sopenharmony_ci    })
3191cb0ef41Sopenharmony_ci
3201cb0ef41Sopenharmony_ci    this[_npm] = new Npm()
3211cb0ef41Sopenharmony_ci    this[_npm].output = (...args) => this[_output].push(args)
3221cb0ef41Sopenharmony_ci    await this[_npm].load()
3231cb0ef41Sopenharmony_ci
3241cb0ef41Sopenharmony_ci    const Cmd = Npm.cmd(command)
3251cb0ef41Sopenharmony_ci    return Cmd.completion({
3261cb0ef41Sopenharmony_ci      partialWord: partial,
3271cb0ef41Sopenharmony_ci      conf: {
3281cb0ef41Sopenharmony_ci        argv: {
3291cb0ef41Sopenharmony_ci          remain: ['npm', command, ...argv],
3301cb0ef41Sopenharmony_ci        },
3311cb0ef41Sopenharmony_ci      },
3321cb0ef41Sopenharmony_ci    })
3331cb0ef41Sopenharmony_ci  }
3341cb0ef41Sopenharmony_ci}
3351cb0ef41Sopenharmony_ci
3361cb0ef41Sopenharmony_cimodule.exports = Sandbox
337