text: 'use strict'
  
  exports = module.exports = lifecycle
  exports.makeEnv = makeEnv
  exports._incorrectWorkingDirectory = _incorrectWorkingDirectory
  
  const spawn = require('./lib/spawn')
  const path = require('path')
  const Stream = require('stream').Stream
  const fs = require('graceful-fs')
  const chain = require('slide').chain
  const uidNumber = require('uid-number')
  const umask = require('umask')
  const which = require('which')
  
  let PATH = 'PATH'
  
  // windows calls it's path 'Path' usually, but this is not guaranteed.
  if (process.platform === 'win32') {
    PATH = 'Path'
    Object.keys(process.env).forEach(function (e) {
      if (e.match(/^PATH$/i)) {
        PATH = e
      }
    })
  }
  
  function logid (pkg, stage) {
    return pkg._id + '~' + stage + ':'
  }
  
  function lifecycle (pkg, stage, wd, opts) {
    return new Promise((resolve, reject) => {
      while (pkg && pkg._data) pkg = pkg._data
      if (!pkg) return reject(new Error('Invalid package data'))
  
      opts.log.info('lifecycle', logid(pkg, stage), pkg._id)
      if (!pkg.scripts) pkg.scripts = {}
  
      if (stage === 'prepublish' && opts.ignorePrepublish) {
        opts.log.info('lifecycle', logid(pkg, stage), 'ignored because ignore-prepublish is set to true', pkg._id)
        delete pkg.scripts.prepublish
      }
  
      if (!pkg.scripts[stage]) return resolve()
  
      validWd(wd || path.resolve(opts.dir, pkg.name), function (er, wd) {
        if (er) return reject(er)
  
        if ((wd.indexOf(opts.dir) !== 0 || _incorrectWorkingDirectory(wd, pkg)) &&
            !opts.unsafePerm && pkg.scripts[stage]) {
          opts.log.warn('lifecycle', logid(pkg, stage), 'cannot run in wd',
            '%s %s (wd=%s)', pkg._id, pkg.scripts[stage], wd
          )
          return resolve()
        }
  
        // set the env variables, then run scripts as a child process.
        var env = makeEnv(pkg, opts)
        env.npm_lifecycle_event = stage
        env.npm_node_execpath = env.NODE = env.NODE || process.execPath
        env.npm_execpath = require.main.filename
        env.INIT_CWD = process.cwd()
  
        // 'nobody' typically doesn't have permission to write to /tmp
        // even if it's never used, sh freaks out.
        if (!opts.unsafePerm) env.TMPDIR = wd
  
        lifecycle_(pkg, stage, wd, opts, env, (er) => {
          if (er) return reject(er)
          return resolve()
        })
      })
    })
  }
  
  function _incorrectWorkingDirectory (wd, pkg) {
    return wd.lastIndexOf(pkg.name) !== wd.length - pkg.name.length
  }
  
  function lifecycle_ (pkg, stage, wd, opts, env, cb) {
    var pathArr = []
    var p = wd.split(/[\\/]node_modules[\\/]/)
    var acc = path.resolve(p.shift())
  
    p.forEach(function (pp) {
      pathArr.unshift(path.join(acc, 'node_modules', '.bin'))
      acc = path.join(acc, 'node_modules', pp)
    })
    pathArr.unshift(path.join(acc, 'node_modules', '.bin'))
  
    // we also unshift the bundled node-gyp-bin folder so that
    // the bundled one will be used for installing things.
    pathArr.unshift(path.join(__dirname, '..', '..', 'bin', 'node-gyp-bin'))
  
    if (shouldPrependCurrentNodeDirToPATH(opts)) {
      // prefer current node interpreter in child scripts
      pathArr.push(path.dirname(process.execPath))
    }
  
    if (env[PATH]) pathArr.push(env[PATH])
    env[PATH] = pathArr.join(process.platform === 'win32' ? ';' : ':')
  
    var packageLifecycle = pkg.scripts && pkg.scripts.hasOwnProperty(stage)
  
    if (opts.ignoreScripts) {
      opts.log.info('lifecycle', logid(pkg, stage), 'ignored because ignore-scripts is set to true', pkg._id)
      packageLifecycle = false
    } else if (packageLifecycle) {
      // define this here so it's available to all scripts.
      env.npm_lifecycle_script = pkg.scripts[stage]
    } else {
      opts.log.silly('lifecycle', logid(pkg, stage), 'no script for ' + stage + ', continuing')
    }
  
    function done (er) {
      if (er) {
        if (opts.force) {
          opts.log.info('lifecycle', logid(pkg, stage), 'forced, continuing', er)
          er = null
        } else if (opts.failOk) {
          opts.log.warn('lifecycle', logid(pkg, stage), 'continuing anyway', er.message)
          er = null
        }
      }
      cb(er)
    }
  
    chain(
      [
        packageLifecycle && [runPackageLifecycle, pkg, env, wd, opts],
        [runHookLifecycle, pkg, env, wd, opts]
      ],
      done
    )
  }
  
  function shouldPrependCurrentNodeDirToPATH (opts) {
    const cfgsetting = opts.scriptsPrependNodePath
    if (cfgsetting === false) return false
    if (cfgsetting === true) return true
  
    var isDifferentNodeInPath
  
    var isWindows = process.platform === 'win32'
    var foundExecPath
    try {
      foundExecPath = which.sync(path.basename(process.execPath), {pathExt: isWindows ? ';' : ':'})
      // Apply `fs.realpath()` here to avoid false positives when `node` is a symlinked executable.
      isDifferentNodeInPath = fs.realpathSync(process.execPath).toUpperCase() !==
          fs.realpathSync(foundExecPath).toUpperCase()
    } catch (e) {
      isDifferentNodeInPath = true
    }
  
    if (cfgsetting === 'warn-only') {
      if (isDifferentNodeInPath && !shouldPrependCurrentNodeDirToPATH.hasWarned) {
        if (foundExecPath) {
          opts.log.warn('lifecycle', 'The node binary used for scripts is', foundExecPath, 'but npm is using', process.execPath, 'itself. Use the `--scripts-prepend-node-path` option to include the path for the node binary npm was executed with.')
        } else {
          opts.log.warn('lifecycle', 'npm is using', process.execPath, 'but there is no node binary in the current PATH. Use the `--scripts-prepend-node-path` option to include the path for the node binary npm was executed with.')
        }
        shouldPrependCurrentNodeDirToPATH.hasWarned = true
      }
  
      return false
    }
  
    return isDifferentNodeInPath
  }
  
  function validWd (d, cb) {
    fs.stat(d, function (er, st) {
      if (er || !st.isDirectory()) {
        var p = path.dirname(d)
        if (p === d) {
          return cb(new Error('Could not find suitable wd'))
        }
        return validWd(p, cb)
      }
      return cb(null, d)
    })
  }
  
  function runPackageLifecycle (pkg, env, wd, opts, cb) {
    // run package lifecycle scripts in the package root, or the nearest parent.
    var stage = env.npm_lifecycle_event
    var cmd = env.npm_lifecycle_script
  
    var note = '\n> ' + pkg._id + ' ' + stage + ' ' + wd +
               '\n> ' + cmd + '\n'
    runCmd(note, cmd, pkg, env, stage, wd, opts, cb)
  }
  
  var running = false
  var queue = []
  function dequeue () {
    running = false
    if (queue.length) {
      var r = queue.shift()
      runCmd.apply(null, r)
    }
  }
  
  function runCmd (note, cmd, pkg, env, stage, wd, opts, cb) {
    if (running) {
      queue.push([note, cmd, pkg, env, stage, wd, opts, cb])
      return
    }
  
    running = true
    opts.log.pause()
    var unsafe = opts.unsafePerm
    var user = unsafe ? null : opts.user
    var group = unsafe ? null : opts.group
  
    if (opts.log.level !== 'silent') {
      opts.log.clearProgress()
      console.log(note)
      opts.log.showProgress()
    }
    opts.log.verbose('lifecycle', logid(pkg, stage), 'unsafe-perm in lifecycle', unsafe)
  
    if (process.platform === 'win32') {
      unsafe = true
    }
  
    if (unsafe) {
      runCmd_(cmd, pkg, env, wd, opts, stage, unsafe, 0, 0, cb)
    } else {
      uidNumber(user, group, function (er, uid, gid) {
        runCmd_(cmd, pkg, env, wd, opts, stage, unsafe, uid, gid, cb)
      })
    }
  }
  
  function runCmd_ (cmd, pkg, env, wd, opts, stage, unsafe, uid, gid, cb_) {
    function cb (er) {
      cb_.apply(null, arguments)
      opts.log.resume()
      process.nextTick(dequeue)
    }
  
    var conf = {
      cwd: wd,
      env: env,
      stdio: [ 0, 1, 2 ]
    }
  
    if (!unsafe) {
      conf.uid = uid ^ 0
      conf.gid = gid ^ 0
    }
  
    var sh = 'sh'
    var shFlag = '-c'
  
    var customShell = opts.scriptShell
  
    if (customShell) {
      sh = customShell
    } else if (process.platform === 'win32') {
      sh = process.env.comspec || 'cmd'
      shFlag = '/d /s /c'
      conf.windowsVerbatimArguments = true
    }
  
    opts.log.verbose('lifecycle', logid(pkg, stage), 'PATH:', env[PATH])
    opts.log.verbose('lifecycle', logid(pkg, stage), 'CWD:', wd)
    opts.log.silly('lifecycle', logid(pkg, stage), 'Args:', [shFlag, cmd])
  
    var proc = spawn(sh, [shFlag, cmd], conf, opts.log)
  
    proc.on('error', procError)
    proc.on('close', function (code, signal) {
      opts.log.silly('lifecycle', logid(pkg, stage), 'Returned: code:', code, ' signal:', signal)
      if (signal) {
        process.kill(process.pid, signal)
      } else if (code) {
        var er = new Error('Exit status ' + code)
        er.errno = code
      }
      procError(er)
    })
    process.once('SIGTERM', procKill)
    process.once('SIGINT', procInterupt)
  
    function procError (er) {
      if (er) {
        opts.log.info('lifecycle', logid(pkg, stage), 'Failed to exec ' + stage + ' script')
        er.message = pkg._id + ' ' + stage + ': `' + cmd + '`\n' +
                     er.message
        if (er.code !== 'EPERM') {
          er.code = 'ELIFECYCLE'
        }
        fs.stat(opts.dir, function (statError, d) {
          if (statError && statError.code === 'ENOENT' && opts.dir.split(path.sep).slice(-1)[0] === 'node_modules') {
            opts.log.warn('', 'Local package.json exists, but node_modules missing, did you mean to install?')
          }
        })
        er.pkgid = pkg._id
        er.stage = stage
        er.script = cmd
        er.pkgname = pkg.name
      }
      process.removeListener('SIGTERM', procKill)
      process.removeListener('SIGTERM', procInterupt)
      process.removeListener('SIGINT', procKill)
      return cb(er)
    }
    function procKill () {
      proc.kill()
    }
    function procInterupt () {
      proc.kill('SIGINT')
      proc.on('exit', function () {
        process.exit()
      })
      process.once('SIGINT', procKill)
    }
  }
  
  function runHookLifecycle (pkg, env, wd, opts, cb) {
    // check for a hook script, run if present.
    var stage = env.npm_lifecycle_event
    var hook = path.join(opts.dir, '.hooks', stage)
    var cmd = hook
  
    fs.stat(hook, function (er) {
      if (er) return cb()
      var note = '\n> ' + pkg._id + ' ' + stage + ' ' + wd +
                 '\n> ' + cmd
      runCmd(note, hook, pkg, env, stage, wd, opts, cb)
    })
  }
  
  function makeEnv (data, opts, prefix, env) {
    prefix = prefix || 'npm_package_'
    if (!env) {
      env = {}
      for (var i in process.env) {
        if (!i.match(/^npm_/)) {
          env[i] = process.env[i]
        }
      }
  
      // express and others respect the NODE_ENV value.
      if (opts.production) env.NODE_ENV = 'production'
    } else if (!data.hasOwnProperty('_lifecycleEnv')) {
      Object.defineProperty(data, '_lifecycleEnv',
        {
          value: env,
          enumerable: false
        }
      )
    }
  
    for (i in data) {
      if (i.charAt(0) !== '_') {
        var envKey = (prefix + i).replace(/[^a-zA-Z0-9_]/g, '_')
        if (i === 'readme') {
          continue
        }
        if (data[i] && typeof data[i] === 'object') {
          try {
            // quick and dirty detection for cyclical structures
            JSON.stringify(data[i])
            makeEnv(data[i], opts, envKey + '_', env)
          } catch (ex) {
            // usually these are package objects.
            // just get the path and basic details.
            var d = data[i]
            makeEnv(
              { name: d.name, version: d.version, path: d.path },
              opts,
              envKey + '_',
              env
            )
          }
        } else {
          env[envKey] = String(data[i])
          env[envKey] = env[envKey].indexOf('\n') !== -1
                          ? JSON.stringify(env[envKey])
                          : env[envKey]
        }
      }
    }
  
    if (prefix !== 'npm_package_') return env
  
    prefix = 'npm_config_'
    var pkgConfig = {}
    var pkgVerConfig = {}
    var namePref = data.name + ':'
    var verPref = data.name + '@' + data.version + ':'
  
    Object.keys(opts.config).forEach(function (i) {
      // in some rare cases (e.g. working with nerf darts), there are segmented
      // "private" (underscore-prefixed) config names -- don't export
      if ((i.charAt(0) === '_' && i.indexOf('_' + namePref) !== 0) || i.match(/:_/)) {
        return
      }
      var value = opts.config[i]
      if (value instanceof Stream || Array.isArray(value)) return
      if (i.match(/umask/)) value = umask.toString(value)
      if (!value) value = ''
      else if (typeof value === 'number') value = '' + value
      else if (typeof value !== 'string') value = JSON.stringify(value)
  
      value = value.indexOf('\n') !== -1
            ? JSON.stringify(value)
            : value
      i = i.replace(/^_+/, '')
      var k
      if (i.indexOf(namePref) === 0) {
        k = i.substr(namePref.length).replace(/[^a-zA-Z0-9_]/g, '_')
        pkgConfig[k] = value
      } else if (i.indexOf(verPref) === 0) {
        k = i.substr(verPref.length).replace(/[^a-zA-Z0-9_]/g, '_')
        pkgVerConfig[k] = value
      }
      var envKey = (prefix + i).replace(/[^a-zA-Z0-9_]/g, '_')
      env[envKey] = value
    })
  
    prefix = 'npm_package_config_'
    ;[pkgConfig, pkgVerConfig].forEach(function (conf) {
      for (var i in conf) {
        var envKey = (prefix + i)
        env[envKey] = conf[i]
      }
    })
  
    return env
  }
  