09f5329a07
Build and Deploy Verso / deploy (push) Successful in 10m37s
LocalCommandRunner: attach captured stdout to the error object when exit code is 1, so callers can read Quarto's output even on failure. QuartoRunner: stop propagating plain 'exited' errors from Quarto up to CompileManager. A Quarto exit-code-1 is a compile failure, not a server error — CLSI already detects failure by the absence of output.pdf and returns status='failure' (HTTP 200). Previously it fell through to the generic error handler (HTTP 500), which caused the frontend to show "Server Error" instead of the log panel. Only true process-level errors (terminated, timedout) are propagated. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
106 lines
3.1 KiB
JavaScript
106 lines
3.1 KiB
JavaScript
import Path from 'node:path'
|
|
import { promisify } from 'node:util'
|
|
import logger from '@overleaf/logger'
|
|
import CommandRunner from './CommandRunner.js'
|
|
import fs from 'node:fs'
|
|
|
|
// Maps currently-running Quarto jobs: compileName → PID (or docker container id)
|
|
const ProcessTable = {}
|
|
|
|
function runQuarto(compileName, options, callback) {
|
|
const { directory, mainFile, image, environment, compileGroup } = options
|
|
const timeout = options.timeout || 60000
|
|
|
|
logger.debug(
|
|
{ directory, timeout, mainFile, compileGroup },
|
|
'starting quarto compile'
|
|
)
|
|
|
|
const command = _buildQuartoCommand(mainFile)
|
|
|
|
ProcessTable[compileName] = CommandRunner.run(
|
|
compileName,
|
|
command,
|
|
directory,
|
|
image,
|
|
timeout,
|
|
environment || {},
|
|
compileGroup,
|
|
null,
|
|
function (error, output) {
|
|
delete ProcessTable[compileName]
|
|
|
|
// Propagate real process-level errors (killed, timed out) but NOT
|
|
// ordinary non-zero exit codes from Quarto itself. A Quarto compile
|
|
// failure (exit code 1) is not a server error — the absence of
|
|
// output.pdf is sufficient for CompileController to return 'failure'.
|
|
if (error && (error.terminated || error.timedout)) {
|
|
return callback(error)
|
|
}
|
|
|
|
// On exit-code-1 errors LocalCommandRunner attaches stdout to the
|
|
// error object; merge it so _writeLogOutput can persist it.
|
|
const combined = output || (error ? { stdout: error.stdout || '' } : null)
|
|
_writeLogOutput(compileName, directory, combined, () =>
|
|
callback(null, combined)
|
|
)
|
|
}
|
|
)
|
|
}
|
|
|
|
function _buildQuartoCommand(mainFile) {
|
|
// Run through a POSIX shell so stderr is merged into stdout (2>&1).
|
|
// Quarto writes all progress and error messages to stderr; without this
|
|
// the log panel would be empty on failure.
|
|
// LocalCommandRunner replaces $COMPILE_DIR before the shell sees the
|
|
// string, so no shell variable expansion occurs for that token.
|
|
const quartoArgs = [
|
|
'quarto',
|
|
'render',
|
|
`$COMPILE_DIR/${mainFile}`,
|
|
'--to',
|
|
'typst',
|
|
'--output',
|
|
'output.pdf',
|
|
].join(' ')
|
|
return ['/bin/sh', '-c', `${quartoArgs} 2>&1`]
|
|
}
|
|
|
|
function _writeLogOutput(compileName, directory, output, callback) {
|
|
const content = (output && output.stdout) || ''
|
|
if (!content) return callback()
|
|
// Write to output.log so the PDF-preview log panel picks it up
|
|
const logFile = Path.join(directory, 'output.log')
|
|
fs.unlink(logFile, () => {
|
|
fs.writeFile(logFile, content, { flag: 'wx' }, err => {
|
|
if (err) {
|
|
logger.error({ err, compileName, logFile }, 'error writing quarto log')
|
|
}
|
|
callback()
|
|
})
|
|
})
|
|
}
|
|
|
|
function isRunning(compileName) {
|
|
return ProcessTable[compileName] != null
|
|
}
|
|
|
|
function killQuarto(compileName, callback) {
|
|
logger.debug({ compileName }, 'killing running quarto compile')
|
|
if (!isRunning(compileName)) {
|
|
logger.warn({ compileName }, 'no such compile to kill')
|
|
return callback(null)
|
|
}
|
|
CommandRunner.kill(ProcessTable[compileName], callback)
|
|
}
|
|
|
|
export default {
|
|
isRunning,
|
|
runQuarto,
|
|
killQuarto,
|
|
promises: {
|
|
runQuarto: promisify(runQuarto),
|
|
killQuarto: promisify(killQuarto),
|
|
},
|
|
}
|