index.js 2.8 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. const {spawn} = require('cross-spawn')
  2. const commandConvert = require('./command')
  3. const varValueConvert = require('./variable')
  4. module.exports = crossEnv
  5. const envSetterRegex = /(\w+)=('(.*)'|"(.*)"|(.*))/
  6. function crossEnv(args, options = {}) {
  7. const [envSetters, command, commandArgs] = parseCommand(args)
  8. const env = getEnvVars(envSetters)
  9. if (command) {
  10. const proc = spawn(
  11. // run `path.normalize` for command(on windows)
  12. commandConvert(command, env, true),
  13. // by default normalize is `false`, so not run for cmd args
  14. commandArgs.map(arg => commandConvert(arg, env)),
  15. {
  16. stdio: 'inherit',
  17. shell: options.shell,
  18. env,
  19. },
  20. )
  21. process.on('SIGTERM', () => proc.kill('SIGTERM'))
  22. process.on('SIGINT', () => proc.kill('SIGINT'))
  23. process.on('SIGBREAK', () => proc.kill('SIGBREAK'))
  24. process.on('SIGHUP', () => proc.kill('SIGHUP'))
  25. proc.on('exit', (code, signal) => {
  26. let crossEnvExitCode = code
  27. // exit code could be null when OS kills the process(out of memory, etc) or due to node handling it
  28. // but if the signal is SIGINT the user exited the process so we want exit code 0
  29. if (crossEnvExitCode === null) {
  30. crossEnvExitCode = signal === 'SIGINT' ? 0 : 1
  31. }
  32. process.exit(crossEnvExitCode) //eslint-disable-line no-process-exit
  33. })
  34. return proc
  35. }
  36. return null
  37. }
  38. function parseCommand(args) {
  39. const envSetters = {}
  40. let command = null
  41. let commandArgs = []
  42. for (let i = 0; i < args.length; i++) {
  43. const match = envSetterRegex.exec(args[i])
  44. if (match) {
  45. let value
  46. if (typeof match[3] !== 'undefined') {
  47. value = match[3]
  48. } else if (typeof match[4] === 'undefined') {
  49. value = match[5]
  50. } else {
  51. value = match[4]
  52. }
  53. envSetters[match[1]] = value
  54. } else {
  55. // No more env setters, the rest of the line must be the command and args
  56. let cStart = []
  57. cStart = args
  58. .slice(i)
  59. // Regex:
  60. // match "\'" or "'"
  61. // or match "\" if followed by [$"\] (lookahead)
  62. .map(a => {
  63. const re = /\\\\|(\\)?'|([\\])(?=[$"\\])/g
  64. // Eliminate all matches except for "\'" => "'"
  65. return a.replace(re, m => {
  66. if (m === '\\\\') return '\\'
  67. if (m === "\\'") return "'"
  68. return ''
  69. })
  70. })
  71. command = cStart[0]
  72. commandArgs = cStart.slice(1)
  73. break
  74. }
  75. }
  76. return [envSetters, command, commandArgs]
  77. }
  78. function getEnvVars(envSetters) {
  79. const envVars = {...process.env}
  80. if (process.env.APPDATA) {
  81. envVars.APPDATA = process.env.APPDATA
  82. }
  83. Object.keys(envSetters).forEach(varName => {
  84. envVars[varName] = varValueConvert(envSetters[varName], varName)
  85. })
  86. return envVars
  87. }