Merge pull request #33162 from overleaf/cd-upload-generated-files-to-filetree

Cd upload generated files to filetree

GitOrigin-RevId: 2d2774e57c42452fba3a2582fde7153ffcde59bf
This commit is contained in:
Chris Dryden
2026-05-07 08:07:06 +00:00
committed by Copybot
parent 8f0979d6a2
commit df01c7e9e1
9 changed files with 745 additions and 41 deletions
@@ -1,10 +1,12 @@
import path from 'path-browserify'
import type {
OutputStream,
OutputFileData,
ProjectFileData,
PyodideWorkerRequest,
PyodideWorkerResponse,
} from './pyodide-worker-messages'
import type { BatchUploadItem } from '@/infrastructure/batch-file-uploader'
import type { FileUploader } from './python-runner'
export type OutputCallback = (
stream: OutputStream,
@@ -23,7 +25,7 @@ export type LifecycleCallback = (
executionId: string
success: boolean
outputs: string[]
outputFiles: OutputFileData[]
failedUploads: string[]
}
) => void
@@ -37,17 +39,20 @@ export class PyodideWorkerClient {
private pendingMessages: PyodideWorkerRequest[] = []
private outputCallback: OutputCallback | null
private lifecycleCallback: LifecycleCallback | null
private fileUploader: FileUploader
constructor(options: {
baseAssetPath: string
createWorker: () => Worker
onOutput?: OutputCallback
onLifecycle?: LifecycleCallback
fileUploader: FileUploader
}) {
this.baseAssetPath = options.baseAssetPath
this.createWorker = options.createWorker
this.outputCallback = options.onOutput ?? null
this.lifecycleCallback = options.onLifecycle ?? null
this.fileUploader = options.fileUploader
this.worker = this.createWorker()
this.worker.addEventListener('message', this.receive)
@@ -119,7 +124,7 @@ export class PyodideWorkerClient {
}
}
private receive = (event: MessageEvent<PyodideWorkerResponse>) => {
private receive = async (event: MessageEvent<PyodideWorkerResponse>) => {
// Discard messages from a previously terminated worker
if (event.target !== this.worker) {
return
@@ -158,15 +163,55 @@ export class PyodideWorkerClient {
)
return
case 'run-code-result':
case 'run-code-result': {
let success = response.success
const failedUploads: string[] = []
if (success && response.outputFiles.length > 0) {
const items: BatchUploadItem[] = response.outputFiles.map(file => ({
file: new Blob([file.content as Uint8Array<ArrayBuffer>]),
name: path.basename(file.relativePath),
relativePath: file.relativePath,
}))
try {
const results = await this.fileUploader(items)
for (const result of results) {
if (result.status === 'error') {
failedUploads.push(result.relativePath!)
this.outputCallback?.(
'stderr',
`Failed to upload output file ${result.relativePath!}: ${result.error}`,
response.fileId,
response.executionId
)
}
}
if (failedUploads.length > 0) {
success = false
}
} catch (err) {
const message = err instanceof Error ? err.message : String(err)
this.outputCallback?.(
'stderr',
`Failed to upload output files: ${message}`,
response.fileId,
response.executionId
)
failedUploads.push(...items.map(item => item.relativePath!))
success = false
}
}
this.lifecycleCallback?.({
type: 'run-finished',
fileId: response.fileId,
executionId: response.executionId,
success: response.success,
success,
outputs: response.outputs,
outputFiles: response.outputFiles,
failedUploads,
})
}
}
}
}
@@ -5,6 +5,12 @@ import { v4 as uuid } from 'uuid'
import { debugConsole } from '@/utils/debugging'
import { PyodideWorkerClient } from './pyodide-worker-client'
import type { OutputStream } from './pyodide-worker-messages'
import type {
BatchUploadItem,
UploadResult,
} from '@/infrastructure/batch-file-uploader'
export type FileUploader = (items: BatchUploadItem[]) => Promise<UploadResult[]>
const MAX_OUTPUT_LINES = 100
@@ -45,6 +51,8 @@ export class PythonRunner {
private readonly baseAssetPath: string
private readonly createWorker: () => Worker
private readonly getExecutionContext: () => Promise<ExecutionContext | null>
private readonly fileUploader: FileUploader
private listeners = new Set<Listener>()
private activeExecutionId: string | null = null
@@ -54,12 +62,14 @@ export class PythonRunner {
fileId: string,
baseAssetPath: string,
getExecutionContext: () => Promise<ExecutionContext | null>,
createWorker: () => Worker
createWorker: () => Worker,
fileUploader: FileUploader
) {
this.fileId = fileId
this.baseAssetPath = baseAssetPath
this.createWorker = createWorker
this.getExecutionContext = getExecutionContext
this.fileUploader = fileUploader
}
subscribe = (listener: Listener): (() => void) => {
@@ -100,6 +110,7 @@ export class PythonRunner {
this.client = new PyodideWorkerClient({
baseAssetPath: this.baseAssetPath,
createWorker: this.createWorker,
fileUploader: this.fileUploader,
onLifecycle: event => {
switch (event.type) {
case 'loaded':
@@ -111,15 +122,17 @@ export class PythonRunner {
this.updateState({ status: 'errored', error: event.error })
return
case 'run-finished':
case 'run-finished': {
if (
event.fileId !== this.fileId ||
this.activeExecutionId !== event.executionId
) {
return
}
this.activeExecutionId = null
this.updateState({ status: 'finished' })
}
}
},
onOutput: (stream, line, fileId, executionId) => {