diff --git a/server-ce/Dockerfile-base b/server-ce/Dockerfile-base index 1677469009..80c403c4b4 100644 --- a/server-ce/Dockerfile-base +++ b/server-ce/Dockerfile-base @@ -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 diff --git a/services/clsi/app/js/CompileManager.js b/services/clsi/app/js/CompileManager.js index 2802685a14..d406bf95ad 100644 --- a/services/clsi/app/js/CompileManager.js +++ b/services/clsi/app/js/CompileManager.js @@ -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) ) diff --git a/services/clsi/app/js/QuartoRunner.js b/services/clsi/app/js/QuartoRunner.js index 0a31b6a4c9..5a3e89dc33 100644 --- a/services/clsi/app/js/QuartoRunner.js +++ b/services/clsi/app/js/QuartoRunner.js @@ -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 => { - if (err) { - logger.error({ err, compileName, file }, 'error writing log file') - } - cb() - }) - }) - } else { - cb() - } - } - - _writeFile(Path.join(directory, 'output.stdout'), output.stdout, () => { - _writeFile(Path.join(directory, 'output.stderr'), output.stderr, () => { + 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() }) }) diff --git a/services/web/app/src/Features/Project/ProjectCreationHandler.mjs b/services/web/app/src/Features/Project/ProjectCreationHandler.mjs index eb78a4582d..94b9f68381 100644 --- a/services/web/app/src/Features/Project/ProjectCreationHandler.mjs +++ b/services/web/app/src/Features/Project/ProjectCreationHandler.mjs @@ -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 diff --git a/services/web/app/templates/project_files/mainbasic.qmd b/services/web/app/templates/project_files/mainbasic.qmd new file mode 100644 index 0000000000..c2958ceb27 --- /dev/null +++ b/services/web/app/templates/project_files/mainbasic.qmd @@ -0,0 +1,9 @@ +--- +title: "<%= project_name %>" +author: "<%= user.first_name %> <%= user.last_name %>" +date: "<%= month %> <%= year %>" +format: typst +--- + +## Introduction +