Fix Quarto compile pipeline: install, logs, template, draft mode
Build and Deploy Verso / deploy (push) Successful in 10m58s

Dockerfile-base: remove TeX Live (no longer needed), install Quarto
  1.6.39 which bundles Typst for PDF output. This was the root cause
  of all compile failures — the server-ce monolith never had Quarto.

QuartoRunner: run quarto via /bin/sh so stderr is merged into stdout
  with 2>&1; write combined output to output.log (not output.stdout)
  so the PDF-preview log panel picks it up and shows raw output.
  Also write the log on error so failures are always visible.

CompileManager: guard DraftModeManager behind an isLatexFile check —
  injecting LaTeX preamble commands into a .qmd file corrupts it and
  causes a guaranteed compile failure when draft mode is requested.

ProjectCreationHandler + mainbasic.qmd: new projects now create
  main.qmd with a minimal Quarto/Typst frontmatter instead of the
  LaTeX main.tex; _createRootDoc names the file main.qmd accordingly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
claude
2026-05-31 12:59:43 +00:00
parent af54b5fd49
commit 0323fd4813
5 changed files with 51 additions and 84 deletions
+6 -43
View File
@@ -4,10 +4,6 @@
FROM phusion/baseimage:noble-1.0.3
# Makes sure LuaTex cache is writable
# -----------------------------------
ENV TEXMFVAR=/var/lib/overleaf/tmp/texmf-var
# Update to ensure dependencies are updated
# ------------------------------------------
ENV REBUILT_AFTER="2026-05-21"
@@ -39,45 +35,12 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
/etc/nginx/nginx.conf \
/etc/nginx/sites-enabled/default
# Install TexLive
# ---------------
# CTAN mirrors occasionally fail, in that case install TexLive using a
# different server, for example https://ctan.crest.fr
#
# # docker build \
# --build-arg TEXLIVE_MIRROR=https://ctan.crest.fr/tex-archive/systems/texlive/tlnet \
# -f Dockerfile-base -t sharelatex/sharelatex-base .
ARG TEXLIVE_MIRROR=https://mirror.ox.ac.uk/sites/ctan.org/systems/texlive/tlnet
RUN mkdir /install-tl-unx \
&& wget --quiet https://tug.org/texlive/files/texlive.asc \
&& gpg --import texlive.asc \
&& rm texlive.asc \
&& wget --quiet ${TEXLIVE_MIRROR}/install-tl-unx.tar.gz \
&& wget --quiet ${TEXLIVE_MIRROR}/install-tl-unx.tar.gz.sha512 \
&& wget --quiet ${TEXLIVE_MIRROR}/install-tl-unx.tar.gz.sha512.asc \
&& gpg --verify install-tl-unx.tar.gz.sha512.asc \
&& sha512sum -c install-tl-unx.tar.gz.sha512 \
&& tar -xz -C /install-tl-unx --strip-components=1 -f install-tl-unx.tar.gz \
&& rm install-tl-unx.tar.gz* \
&& echo "tlpdbopt_autobackup 0" >> /install-tl-unx/texlive.profile \
&& echo "tlpdbopt_install_docfiles 0" >> /install-tl-unx/texlive.profile \
&& echo "tlpdbopt_install_srcfiles 0" >> /install-tl-unx/texlive.profile \
&& echo "selected_scheme scheme-basic" >> /install-tl-unx/texlive.profile \
\
&& /install-tl-unx/install-tl \
-profile /install-tl-unx/texlive.profile \
-repository ${TEXLIVE_MIRROR} \
\
&& $(find /usr/local/texlive -name tlmgr) path add \
&& tlmgr install --repository ${TEXLIVE_MIRROR} \
latexmk \
texcount \
synctex \
etoolbox \
xetex \
&& tlmgr path add \
&& rm -rf /install-tl-unx
# Install Quarto (bundles Typst for PDF rendering — no LaTeX needed)
# ------------------------------------------------------------------
ARG QUARTO_VERSION=1.6.39
RUN curl -fsSL "https://github.com/quarto-dev/quarto-cli/releases/download/v${QUARTO_VERSION}/quarto-${QUARTO_VERSION}-linux-amd64.deb" -o /tmp/quarto.deb \
&& dpkg -i /tmp/quarto.deb \
&& rm /tmp/quarto.deb
# Set up overleaf user and home directory
+5 -1
View File
@@ -119,7 +119,11 @@ async function doCompile(request, stats, timings) {
)
// apply a series of file modifications/creations for draft mode and tikz
if (request.draft) {
// Draft mode injects LaTeX preamble commands — skip for Quarto files
const isLatexFile = /\.(tex|ltx|Rtex)$/i.test(
request.rootResourcePath || ''
)
if (request.draft && isLatexFile) {
await DraftModeManager.promises.injectDraftMode(
Path.join(compileDir, request.rootResourcePath)
)
+27 -36
View File
@@ -8,14 +8,7 @@ import fs from 'node:fs'
const ProcessTable = {}
function runQuarto(compileName, options, callback) {
const {
directory,
mainFile,
image,
environment,
compileGroup,
timings,
} = options
const { directory, mainFile, image, environment, compileGroup } = options
const timeout = options.timeout || 60000
logger.debug({ directory, timeout, mainFile, compileGroup }, 'starting quarto compile')
@@ -33,46 +26,44 @@ function runQuarto(compileName, options, callback) {
null,
function (error, output) {
delete ProcessTable[compileName]
if (error) return callback(error)
_writeLogOutput(compileName, directory, output, () => {
callback(null, output)
})
if (error) {
// Still try to write whatever output we captured before the error
_writeLogOutput(compileName, directory, output, () => callback(error))
return
}
_writeLogOutput(compileName, directory, output, () => callback(null, output))
}
)
}
function _buildQuartoCommand(mainFile) {
// Use Typst as the PDF engine — it is bundled with Quarto (>= 1.4) and
// does not require a separate LaTeX installation.
return [
// Run through a POSIX shell so we can merge stderr into stdout.
// Quarto writes all progress and error messages to stderr; without this
// merge the UI would show nothing in the log panel on failure.
// LocalCommandRunner replaces $COMPILE_DIR before the shell sees the string,
// so the substitution is safe and no shell variable expansion occurs.
const quartoArgs = [
'quarto',
'render',
Path.join('$COMPILE_DIR', mainFile),
'--to', 'typst',
'--output', 'output.pdf',
]
`$COMPILE_DIR/${mainFile}`,
'--to',
'typst',
'--output',
'output.pdf',
].join(' ')
return ['/bin/sh', '-c', `${quartoArgs} 2>&1`]
}
function _writeLogOutput(compileName, directory, output, callback) {
if (!output) return callback()
function _writeFile(file, content, cb) {
if (content && content.length > 0) {
fs.unlink(file, () => {
fs.writeFile(file, content, { flag: 'wx' }, err => {
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, file }, 'error writing log file')
logger.error({ err, compileName, logFile }, 'error writing quarto log')
}
cb()
})
})
} else {
cb()
}
}
_writeFile(Path.join(directory, 'output.stdout'), output.stdout, () => {
_writeFile(Path.join(directory, 'output.stderr'), output.stderr, () => {
callback()
})
})
@@ -88,7 +88,7 @@ async function createProjectFromSnippet(ownerId, projectName, docLines) {
async function createBasicProject(ownerId, projectName) {
const project = await _createBlankProject(ownerId, projectName)
const docLines = await _buildTemplate('mainbasic.tex', ownerId, projectName)
const docLines = await _buildTemplate('mainbasic.qmd', ownerId, projectName)
await _createRootDoc(project, ownerId, docLines)
AnalyticsManager.recordEventForUserInBackground(ownerId, 'project-created', {
@@ -304,7 +304,7 @@ async function _createRootDoc(project, ownerId, docLines) {
const { doc } = await ProjectEntityUpdateHandler.promises.addDoc(
project._id,
project.rootFolder[0]._id,
'main.tex',
'main.qmd',
docLines,
ownerId,
null
@@ -0,0 +1,9 @@
---
title: "<%= project_name %>"
author: "<%= user.first_name %> <%= user.last_name %>"
date: "<%= month %> <%= year %>"
format: typst
---
## Introduction