From f9c53fe1475eae1a454759a2ed244a9a127ef42d Mon Sep 17 00:00:00 2001 From: Miguel Serrano Date: Wed, 20 May 2026 10:07:04 +0200 Subject: [PATCH] [web] Added `DEFAULT_LATEX_COMPILER` env (#32455) This is mainly intended to be used in CE/Server Pro GitOrigin-RevId: 277f9afca389a1e7b00db2d987129432fb1707b5 --- .../app/src/Features/Compile/ClsiManager.mjs | 4 +-- .../Features/Project/ProjectController.mjs | 1 + .../Project/ProjectOptionsHandler.mjs | 3 +- .../Features/Templates/TemplatesManager.mjs | 4 ++- services/web/app/src/models/Project.mjs | 3 +- .../web/app/views/project/editor/_meta.pug | 1 + services/web/config/settings.defaults.js | 8 +++++ .../compiler-settings/compiler-setting.tsx | 36 +++++++++---------- services/web/frontend/js/utils/meta.ts | 2 ++ .../frontend/helpers/editor-providers.tsx | 1 + .../unit/src/Compile/ClsiManager.test.mjs | 2 ++ .../Project/ProjectOptionsHandler.test.mjs | 1 + .../src/Templates/TemplatesManager.test.mjs | 1 + 13 files changed, 42 insertions(+), 25 deletions(-) diff --git a/services/web/app/src/Features/Compile/ClsiManager.mjs b/services/web/app/src/Features/Compile/ClsiManager.mjs index d56e634b77..14667ce4da 100644 --- a/services/web/app/src/Features/Compile/ClsiManager.mjs +++ b/services/web/app/src/Features/Compile/ClsiManager.mjs @@ -36,7 +36,7 @@ const NewBackendCloudClsiCookieManager = ClsiCookieManagerFactory( Settings.apis.clsi_new?.backendGroupName ) -const VALID_COMPILERS = ['pdflatex', 'latex', 'xelatex', 'lualatex'] +const VALID_COMPILERS = Settings.safeCompilers const OUTPUT_FILE_TIMEOUT_MS = 60000 const CLSI_COOKIES_ENABLED = (Settings.clsiCookie?.key ?? '') !== '' @@ -747,7 +747,7 @@ async function _buildRequest(projectId, userId, options) { throw new Errors.NotFoundError(`project does not exist: ${projectId}`) } if (!VALID_COMPILERS.includes(project.compiler)) { - project.compiler = 'pdflatex' + project.compiler = Settings.defaultLatexCompiler } const historyId = project.overleaf.history.id let { baseHistoryVersion } = options diff --git a/services/web/app/src/Features/Project/ProjectController.mjs b/services/web/app/src/Features/Project/ProjectController.mjs index 85d3befde1..82a135dcca 100644 --- a/services/web/app/src/Features/Project/ProjectController.mjs +++ b/services/web/app/src/Features/Project/ProjectController.mjs @@ -955,6 +955,7 @@ const _ProjectController = { capabilities, roMirrorOnClientNoLocalStorage: Settings.adminOnlyLogin || project.name.startsWith('Debug: '), + defaultLatexCompiler: Settings.defaultLatexCompiler, languages: Settings.languages, learnedWords, editorThemes: THEME_LIST, diff --git a/services/web/app/src/Features/Project/ProjectOptionsHandler.mjs b/services/web/app/src/Features/Project/ProjectOptionsHandler.mjs index 8fcc085528..e1d8830fa4 100644 --- a/services/web/app/src/Features/Project/ProjectOptionsHandler.mjs +++ b/services/web/app/src/Features/Project/ProjectOptionsHandler.mjs @@ -5,7 +5,6 @@ import { db, ObjectId } from '../../infrastructure/mongodb.mjs' import Errors from '../Errors/Errors.js' import mongodb from 'mongodb-legacy' import OError from '@overleaf/o-error' -const safeCompilers = ['xelatex', 'pdflatex', 'latex', 'lualatex'] const { ReturnDocument } = mongodb @@ -16,7 +15,7 @@ const ProjectOptionsHandler = { */ normalizeCompiler(compiler) { compiler = compiler.toLowerCase() - if (!safeCompilers.includes(compiler)) { + if (!settings.safeCompilers.includes(compiler)) { throw new OError('invalid compiler', { compiler }) } return compiler diff --git a/services/web/app/src/Features/Templates/TemplatesManager.mjs b/services/web/app/src/Features/Templates/TemplatesManager.mjs index 8ec52d4b1d..b29ba2dd71 100644 --- a/services/web/app/src/Features/Templates/TemplatesManager.mjs +++ b/services/web/app/src/Features/Templates/TemplatesManager.mjs @@ -32,7 +32,9 @@ const TemplatesManager = { userId, imageName ) { - compiler = ProjectOptionsHandler.normalizeCompiler(compiler || 'pdflatex') + compiler = ProjectOptionsHandler.normalizeCompiler( + compiler || settings.defaultLatexCompiler + ) imageName = ProjectOptionsHandler.normalizeImageName( imageName || 'wl_texlive:2018.1' ) diff --git a/services/web/app/src/models/Project.mjs b/services/web/app/src/models/Project.mjs index 1d42668687..ffa49dc6ed 100644 --- a/services/web/app/src/models/Project.mjs +++ b/services/web/app/src/models/Project.mjs @@ -1,5 +1,6 @@ import mongoose from '../infrastructure/Mongoose.mjs' import _ from 'lodash' +import settings from '@overleaf/settings' import { FolderSchema } from './Folder.mjs' import Errors from '../Features/Errors/Errors.js' @@ -36,7 +37,7 @@ export const ProjectSchema = new Schema( mainBibliographyDoc_id: { type: ObjectId }, version: { type: Number }, // incremented for every change in the project structure (folders and filenames) publicAccesLevel: { type: String, default: 'private' }, - compiler: { type: String, default: 'pdflatex' }, + compiler: { type: String, default: settings.defaultLatexCompiler }, spellCheckLanguage: { type: String, default: 'en' }, deletedByExternalDataSource: { type: Boolean, default: false }, description: { type: String, default: '' }, diff --git a/services/web/app/views/project/editor/_meta.pug b/services/web/app/views/project/editor/_meta.pug index 7ac843ba7c..87b188eafa 100644 --- a/services/web/app/views/project/editor/_meta.pug +++ b/services/web/app/views/project/editor/_meta.pug @@ -28,6 +28,7 @@ meta(name="ol-showAiFeatures" data-type="boolean" content=showAiFeatures) meta(name="ol-hasUnlimitedAi" data-type="boolean" content=hasUnlimitedAi) meta(name="ol-hasAiFreeTier" data-type="boolean" content=hasAiFreeTier) meta(name="ol-detachRole" data-type="string" content=detachRole) +meta(name="ol-defaultLatexCompiler" data-type="string" content=defaultLatexCompiler) meta(name="ol-imageNames" data-type="json" content=imageNames) meta(name="ol-languages" data-type="json" content=languages) meta(name="ol-editorThemes" data-type="json" content=editorThemes) diff --git a/services/web/config/settings.defaults.js b/services/web/config/settings.defaults.js index 8d7d208fea..b349f29336 100644 --- a/services/web/config/settings.defaults.js +++ b/services/web/config/settings.defaults.js @@ -115,6 +115,8 @@ const httpPermissionsPolicy = { }, } +const safeCompilers = ['xelatex', 'pdflatex', 'latex', 'lualatex'] + module.exports = { env: 'server-ce', @@ -460,6 +462,12 @@ module.exports = { disableChat: process.env.OVERLEAF_DISABLE_CHAT === 'true', disableLinkSharing: process.env.OVERLEAF_DISABLE_LINK_SHARING === 'true', + safeCompilers, + defaultLatexCompiler: safeCompilers.includes( + process.env.DEFAULT_LATEX_COMPILER + ) + ? process.env.DEFAULT_LATEX_COMPILER + : 'pdflatex', enableSubscriptions: false, restrictedCountries: [], enableOnboardingEmails: process.env.ENABLE_ONBOARDING_EMAILS === 'true', diff --git a/services/web/frontend/js/features/settings/components/compiler-settings/compiler-setting.tsx b/services/web/frontend/js/features/settings/components/compiler-settings/compiler-setting.tsx index fb512b306f..45f9c29424 100644 --- a/services/web/frontend/js/features/settings/components/compiler-settings/compiler-setting.tsx +++ b/services/web/frontend/js/features/settings/components/compiler-settings/compiler-setting.tsx @@ -1,3 +1,4 @@ +import { useState } from 'react' import { useProjectSettingsContext } from '@/features/editor-left-menu/context/project-settings-context' import DropdownSetting from '../dropdown-setting' import type { Option } from '../dropdown-setting' @@ -5,28 +6,25 @@ import { useTranslation } from 'react-i18next' import { usePermissionsContext } from '@/features/ide-react/context/permissions-context' import { ProjectCompiler } from '@ol-types/project-settings' import { useSetCompilationSettingWithEvent } from '@/features/editor-left-menu/hooks/use-set-compilation-setting' +import getMeta from '@/utils/meta' +import _ from 'lodash' -const OPTIONS: Option[] = [ - { - value: 'pdflatex', - label: 'pdfLaTeX', - }, - { - value: 'latex', - label: 'LaTeX', - }, - { - value: 'xelatex', - label: 'XeLaTeX', - }, - { - value: 'lualatex', - label: 'LuaLaTeX', - }, -] +function getCompilerOptions(): Option[] { + const compilerOptions = ['pdfLaTeX', 'LaTeX', 'XeLaTeX', 'LuaLaTeX'] + const defaultCompiler = getMeta('ol-defaultLatexCompiler') as ProjectCompiler + const sortedOptions = _.sortBy( + compilerOptions, + option => option.toLowerCase() !== defaultCompiler.toLowerCase() + ) + return sortedOptions.map(option => ({ + value: option.toLowerCase() as ProjectCompiler, + label: option, + })) +} export default function CompilerSetting() { const { compiler, setCompiler } = useProjectSettingsContext() + const [compilerOptions] = useState(() => getCompilerOptions()) const { t } = useTranslation() const { write } = usePermissionsContext() const changeCompiler = useSetCompilationSettingWithEvent( @@ -40,7 +38,7 @@ export default function CompilerSetting() { label={t('compiler')} description={t('the_latex_engine_used_for_compiling')} disabled={!write} - options={OPTIONS} + options={compilerOptions} onChange={changeCompiler} value={compiler} translateOptions="no" diff --git a/services/web/frontend/js/utils/meta.ts b/services/web/frontend/js/utils/meta.ts index bfe7c249f2..1f0e1a60ff 100644 --- a/services/web/frontend/js/utils/meta.ts +++ b/services/web/frontend/js/utils/meta.ts @@ -7,6 +7,7 @@ import { ExposedSettings } from '../../../types/exposed-settings' import { type ImageName, OverallThemeMeta, + ProjectCompiler, type SpellCheckLanguage, } from '../../../types/project-settings' import { CurrencyCode } from '../../../types/subscription/currency' @@ -116,6 +117,7 @@ export interface Meta { 'ol-currentUrl': string 'ol-customerIoEnabled': boolean 'ol-debugPdfDetach': boolean + 'ol-defaultLatexCompiler': ProjectCompiler 'ol-detachRole': 'detached' | 'detacher' | '' 'ol-dictionariesRoot': 'string' 'ol-domainCaptureEnabled': boolean | undefined diff --git a/services/web/test/frontend/helpers/editor-providers.tsx b/services/web/test/frontend/helpers/editor-providers.tsx index ce92da2684..d226058195 100644 --- a/services/web/test/frontend/helpers/editor-providers.tsx +++ b/services/web/test/frontend/helpers/editor-providers.tsx @@ -197,6 +197,7 @@ export function EditorProviders({ 'dropbox', 'link-sharing', ]) + window.metaAttributesCache.set('ol-defaultLatexCompiler', 'pdflatex') const scope = merge( { diff --git a/services/web/test/unit/src/Compile/ClsiManager.test.mjs b/services/web/test/unit/src/Compile/ClsiManager.test.mjs index 288c923d7c..5e6ff9d0ea 100644 --- a/services/web/test/unit/src/Compile/ClsiManager.test.mjs +++ b/services/web/test/unit/src/Compile/ClsiManager.test.mjs @@ -182,6 +182,8 @@ describe('ClsiManager', function () { }, enablePdfCaching: true, clsiCookie: { key: 'clsiserver' }, + safeCompilers: ['pdflatex', 'latex', 'xelatex', 'lualatex'], + defaultLatexCompiler: 'pdflatex', } ctx.ClsiCacheHandler = { clearCache: sinon.stub().resolves(), diff --git a/services/web/test/unit/src/Project/ProjectOptionsHandler.test.mjs b/services/web/test/unit/src/Project/ProjectOptionsHandler.test.mjs index 03053e4de5..29314b8531 100644 --- a/services/web/test/unit/src/Project/ProjectOptionsHandler.test.mjs +++ b/services/web/test/unit/src/Project/ProjectOptionsHandler.test.mjs @@ -35,6 +35,7 @@ describe('ProjectOptionsHandler', function () { { imageName: 'texlive-0000.0', imageDesc: 'test image 0' }, { imageName: 'texlive-1234.5', imageDesc: 'test image 1' }, ], + safeCompilers: ['pdflatex', 'latex', 'xelatex', 'lualatex'], }, })) diff --git a/services/web/test/unit/src/Templates/TemplatesManager.test.mjs b/services/web/test/unit/src/Templates/TemplatesManager.test.mjs index 48ee3d0da4..037cc472c3 100644 --- a/services/web/test/unit/src/Templates/TemplatesManager.test.mjs +++ b/services/web/test/unit/src/Templates/TemplatesManager.test.mjs @@ -112,6 +112,7 @@ describe('TemplatesManager', function () { dumpFolder: ctx.dumpFolder, }, siteUrl: (ctx.siteUrl = 'http://127.0.0.1:3000'), + defaultLatexCompiler: 'pdflatex', apis: { v1: { url: (ctx.v1Url = 'http://overleaf.com'),