Build and Deploy Verso / deploy (push) Successful in 11m33s
Typst's CLI requires options before positional arguments (INPUT OUTPUT). Placing --synctex after output.pdf caused it to be treated as an extra positional arg and rejected with 'unexpected argument'. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
107 lines
3.7 KiB
JavaScript
107 lines
3.7 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 Typst jobs: compileName → PID (or docker container id)
|
|
const ProcessTable = {}
|
|
|
|
// Compiles a standalone Typst document (.typ) straight to output.pdf. We reuse
|
|
// the Typst that ships inside Quarto via `quarto typst compile`, so there is no
|
|
// extra binary to install. This is deliberately the simplest of the three
|
|
// runners: Typst only ever produces a PDF, so there is no format detection,
|
|
// no HTML asset directory and no extension merging (cf. QuartoRunner).
|
|
function runTypst(compileName, options, callback) {
|
|
const { directory, mainFile, image, environment, compileGroup } = options
|
|
const timeout = options.timeout || 60000
|
|
|
|
logger.debug(
|
|
{ directory, timeout, mainFile, compileGroup },
|
|
'starting typst compile'
|
|
)
|
|
|
|
const command = _buildTypstCommand(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 Typst itself. A compile failure
|
|
// (exit code 1) is not a server error — the absence of output.pdf is
|
|
// enough 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 _buildTypstCommand(mainFile) {
|
|
// Run through a POSIX shell so stderr (where Typst writes its diagnostics)
|
|
// is merged into stdout (2>&1). LocalCommandRunner replaces $COMPILE_DIR
|
|
// before the shell sees it; the output path is relative because the shell
|
|
// CWD is already the compile directory.
|
|
// --synctex generates output.synctex.gz alongside the PDF, enabling
|
|
// bidirectional editor↔PDF sync (same infrastructure as LaTeX SyncTeX).
|
|
const inputPath = `$COMPILE_DIR/${mainFile}`
|
|
const cmd = `quarto typst compile --synctex output.synctex.gz ${inputPath} output.pdf 2>&1`
|
|
return ['/bin/sh', '-c', cmd]
|
|
}
|
|
|
|
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. Typst's
|
|
// `error:`/`warning:` + `┌─ file:line:col` diagnostics are understood by the
|
|
// Quarto/Typst log parser on the web side.
|
|
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 typst log')
|
|
}
|
|
callback()
|
|
})
|
|
})
|
|
}
|
|
|
|
function isRunning(compileName) {
|
|
return ProcessTable[compileName] != null
|
|
}
|
|
|
|
function killTypst(compileName, callback) {
|
|
logger.debug({ compileName }, 'killing running typst compile')
|
|
if (!isRunning(compileName)) {
|
|
logger.warn({ compileName }, 'no such compile to kill')
|
|
return callback(null)
|
|
}
|
|
CommandRunner.kill(ProcessTable[compileName], callback)
|
|
}
|
|
|
|
export default {
|
|
isRunning,
|
|
runTypst,
|
|
killTypst,
|
|
promises: {
|
|
runTypst: promisify(runTypst),
|
|
killTypst: promisify(killTypst),
|
|
},
|
|
}
|