LocalCommandRunner.js 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. /* eslint-disable
  2. no-return-assign,
  3. no-unused-vars,
  4. */
  5. // TODO: This file was created by bulk-decaffeinate.
  6. // Fix any style issues and re-enable lint.
  7. /*
  8. * decaffeinate suggestions:
  9. * DS101: Remove unnecessary use of Array.from
  10. * DS102: Remove unnecessary code created because of implicit returns
  11. * DS207: Consider shorter variations of null checks
  12. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  13. */
  14. import { spawn } from 'node:child_process'
  15. import { promisify } from 'node:util'
  16. import Path from 'node:path'
  17. import _ from 'lodash'
  18. import logger from '@overleaf/logger'
  19. let CommandRunner
  20. logger.debug('using standard command runner')
  21. export default CommandRunner = {
  22. run(
  23. projectId,
  24. command,
  25. directory,
  26. image,
  27. timeout,
  28. environment,
  29. compileGroup,
  30. cwd,
  31. callback
  32. ) {
  33. let key, value
  34. callback = _.once(callback)
  35. const spawnCwd = cwd ? Path.join(directory, cwd) : directory
  36. command = Array.from(command).map(arg =>
  37. arg.toString().replace('$COMPILE_DIR', directory)
  38. )
  39. logger.debug(
  40. { projectId, command, directory, cwd: spawnCwd },
  41. 'running command'
  42. )
  43. logger.warn('timeouts and sandboxing are not enabled with CommandRunner')
  44. // merge environment settings
  45. const env = {}
  46. for (key in process.env) {
  47. value = process.env[key]
  48. env[key] = value
  49. }
  50. for (key in environment) {
  51. value = environment[key]
  52. env[key] = value
  53. }
  54. // run command as detached process so it has its own process group (which can be killed if needed)
  55. const proc = spawn(command[0], command.slice(1), {
  56. cwd: spawnCwd,
  57. env,
  58. stdio: ['pipe', 'pipe', 'ignore'],
  59. detached: true,
  60. })
  61. let stdout = ''
  62. proc.stdout.setEncoding('utf8').on('data', data => (stdout += data))
  63. proc.on('error', function (err) {
  64. logger.err(
  65. { err, projectId, command, directory },
  66. 'error running command'
  67. )
  68. return callback(err)
  69. })
  70. proc.on('close', function (code, signal) {
  71. let err
  72. logger.debug({ code, signal, projectId }, 'command exited')
  73. if (signal === 'SIGTERM') {
  74. // signal from kill method below
  75. err = new Error('terminated')
  76. err.terminated = true
  77. return callback(err)
  78. } else if (code === 1) {
  79. // exit status from chktex
  80. err = new Error('exited')
  81. err.code = code
  82. return callback(err)
  83. } else {
  84. return callback(null, { stdout, exitCode: code })
  85. }
  86. })
  87. return proc.pid
  88. }, // return process id to allow job to be killed if necessary
  89. kill(pid, callback) {
  90. if (callback == null) {
  91. callback = function () {}
  92. }
  93. try {
  94. process.kill(-pid) // kill all processes in group
  95. } catch (err) {
  96. return callback(err)
  97. }
  98. return callback()
  99. },
  100. canRunSyncTeXInOutputDir() {
  101. return true
  102. },
  103. }
  104. CommandRunner.promises = {
  105. run: promisify(CommandRunner.run),
  106. kill: promisify(CommandRunner.kill),
  107. }