Files
overleaf-cep/services/clsi/test/acceptance/js/helpers/Client.js
Mathias Jakobsen 32da6548c8 Merge pull request #33277 from overleaf/mj-pandoc-clsi-two-step-download
[clsi] Use clsi-nginx for downloading pandoc exports

GitOrigin-RevId: b6013fae6f53c7af714634d700ceed491d724653
2026-05-08 08:09:18 +00:00

242 lines
5.9 KiB
JavaScript

import express from 'express'
import {
fetchJson,
fetchNothing,
fetchStream,
fetchString,
} from '@overleaf/fetch-utils'
import fs from 'node:fs'
import fsPromises from 'node:fs/promises'
import Settings from '@overleaf/settings'
import FormData from 'form-data'
const host = Settings.apis.clsi.url
function randomId() {
// Avoid ids starting with 0, which get a dummy PDF served.
return 'a' + Math.random().toString(16).slice(2)
}
function compile(projectId, data) {
if (data) {
// Enable pdf caching unless disabled explicitly.
data.options = Object.assign({}, { enablePdfCaching: true }, data.options)
}
return fetchJson(`${host}/project/${projectId}/compile`, {
method: 'POST',
json: {
compile: data,
},
})
}
async function convertDocument(path, type) {
const formData = new FormData()
formData.append('qqfile', fs.createReadStream(path))
return await fetchStream(`${host}/convert/document-to-latex?type=${type}`, {
method: 'POST',
body: formData,
})
}
async function convertProjectToDocument(
projectId,
userId,
type,
request,
responseFormat
) {
const url = new URL(
`${host}/project/${projectId}/user/${userId}/download/project-to-document`
)
url.searchParams.set('type', type)
if (responseFormat) {
url.searchParams.set('responseFormat', responseFormat)
}
const opts = { method: 'POST', json: { compile: request } }
if (responseFormat === 'json') {
return await fetchJson(url.href, opts)
}
return await fetchStream(url.href, opts)
}
async function stopCompile(projectId) {
return await fetchNothing(`${host}/project/${projectId}/compile/stop`, {
method: 'POST',
})
}
async function clearCache(projectId) {
return await fetchNothing(`${host}/project/${projectId}`, {
method: 'DELETE',
})
}
function getOutputFile(response, type) {
for (const file of response.compile.outputFiles) {
if (file.type === type && file.url.match(`output.${type}`)) {
return file
}
}
return null
}
function runFakeFilestoreService(directory) {
const app = express()
app.use(express.static(directory))
this.startFakeFilestoreApp(app)
}
function startFakeFilestoreApp(app) {
let server
before(function (done) {
server = app.listen(error => {
if (error) {
done(new Error('error starting server: ' + error.message))
} else {
const addr = server.address()
Settings.filestoreDomainOveride = `http://127.0.0.1:${addr.port}`
done()
}
})
})
after(function (done) {
server.close(done)
})
}
function syncFromCode(projectId, file, line, column) {
return syncFromCodeWithImage(projectId, file, line, column, '')
}
async function syncFromCodeWithImage(projectId, file, line, column, imageName) {
const url = new URL(`${host}/project/${projectId}/sync/code`)
url.searchParams.append('imageName', imageName)
url.searchParams.append('file', file)
url.searchParams.append('line', line)
url.searchParams.append('column', column)
return await fetchJson(url)
}
function syncFromPdf(projectId, page, h, v) {
return syncFromPdfWithImage(projectId, page, h, v, '')
}
function syncFromPdfWithImage(projectId, page, h, v, imageName) {
const url = new URL(`${host}/project/${projectId}/sync/pdf`)
url.searchParams.append('imageName', imageName)
url.searchParams.append('page', page)
url.searchParams.append('h', h)
url.searchParams.append('v', v)
return fetchJson(url)
}
function wordcount(projectId, file) {
const image = undefined
return wordcountWithImage(projectId, file, image)
}
async function wordcountWithImage(projectId, file, image) {
const url = new URL(`${host}/project/${projectId}/wordcount`)
if (image) {
url.searchParams.append('image', image)
}
url.searchParams.append('file', file)
return await fetchJson(url)
}
async function compileDirectory(projectId, baseDirectory, directory) {
const resources = []
let entities = fs.readdirSync(`${baseDirectory}/${directory}`)
let rootResourcePath = 'main.tex'
while (entities.length > 0) {
const entity = entities.pop()
const stat = fs.statSync(`${baseDirectory}/${directory}/${entity}`)
if (stat.isDirectory()) {
entities = entities.concat(
fs
.readdirSync(`${baseDirectory}/${directory}/${entity}`)
.map(subEntity => {
if (subEntity === 'main.tex') {
rootResourcePath = `${entity}/${subEntity}`
}
return `${entity}/${subEntity}`
})
)
} else if (stat.isFile() && entity !== 'output.pdf') {
const extension = entity.split('.').pop()
if (
[
'tex',
'bib',
'cls',
'sty',
'pdf_tex',
'Rtex',
'ist',
'md',
'Rmd',
'Rnw',
].indexOf(extension) > -1
) {
resources.push({
path: entity,
content: fs
.readFileSync(`${baseDirectory}/${directory}/${entity}`)
.toString(),
})
} else if (
['eps', 'ttf', 'png', 'jpg', 'pdf', 'jpeg'].indexOf(extension) > -1
) {
resources.push({
path: entity,
url: `http://filestore/${directory}/${entity}`,
modified: stat.mtime,
})
}
}
}
const req = {
resources,
rootResourcePath,
}
try {
const options = await fsPromises.readFile(
`${baseDirectory}/${directory}/options.json`
)
req.options = JSON.parse(options)
} catch (error) {
// noop
}
return await compile(projectId, req)
}
function smokeTest() {
return fetchString(`${host}/smoke_test_force`, {
method: 'GET',
})
}
export default {
randomId,
compile,
convertProjectToDocument,
convertDocument,
stopCompile,
clearCache,
getOutputFile,
smokeTest,
runFakeFilestoreService,
startFakeFilestoreApp,
syncFromCode,
syncFromCodeWithImage,
syncFromPdf,
syncFromPdfWithImage,
compileDirectory,
wordcount,
wordcountWithImage,
}