Build and Deploy Verso / deploy (push) Successful in 7m37s
A project whose root file is a .typ file now compiles straight to PDF with Typst, as a third engine beside Quarto (.qmd) and latexmk (.tex). Dispatch stays purely extension-based. CLSI: - New TypstRunner.js: runs `quarto typst compile <main>.typ output.pdf` (reuses the Typst bundled in Quarto, so no extra binary / Docker change). stderr is merged into output.log. - CompileManager: _isTypstFile + a TypstRunner branch in _getRunner, and TypstRunner added to the isRunning check and stopCompile kill list. - RequestParser: 'typst' added to VALID_COMPILERS. web: - settings.defaults: 'typ' added to validRootDocExtensions and the text extensions (so .typ opens in the editor); 'typst' added to safeCompilers. - output-files: the Quarto/Typst log parser (which already understands Typst `error:`/`warning:` + `┌─ file:line:col` diagnostics) now also handles .typ compiles, so their errors/warnings populate the log tabs. Polish: - New-project menu: "Blank Typst project" + "Example Typst project" in both the main and welcome dropdowns, backed by createBasicProject/createExampleProject flavour 'typst', a new mainbasic.typ template and an example-project-typst presentation (math, an image, a table, lists). - Compiler dropdown gains a "Typst" option (cosmetic; dispatch is by extension). README updated: three compilers side by side, with a Writing-a-Typst-document section. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
105 lines
3.5 KiB
JavaScript
105 lines
3.5 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.
|
|
const inputPath = `$COMPILE_DIR/${mainFile}`
|
|
const cmd = `quarto typst compile ${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),
|
|
},
|
|
}
|