From a6a006aaece27143fd3f7db17b384747e1a60c47 Mon Sep 17 00:00:00 2001 From: Juergen Kunz Date: Thu, 5 Mar 2026 16:45:07 +0000 Subject: [PATCH] fix(compiler): move output directory cleaning to separate phase before compilation Restructured compileGlob() into three distinct phases: 1. Resolve glob patterns and clean ALL output directories upfront 2. Compile all TypeScript tasks (no filesystem cleanup during this phase) 3. Unpack all outputs after all compilations complete This prevents XFS metadata corruption from rm operations overlapping with TypeScript compilation writes to sibling directories. --- ts/mod_compiler/classes.tscompiler.ts | 46 +++++++++++++++------------ 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/ts/mod_compiler/classes.tscompiler.ts b/ts/mod_compiler/classes.tscompiler.ts index a76d188..23ce3ac 100644 --- a/ts/mod_compiler/classes.tscompiler.ts +++ b/ts/mod_compiler/classes.tscompiler.ts @@ -330,26 +330,25 @@ export class TsCompiler { console.log(''); } - // Collect unpack tasks to perform AFTER all compilations complete. - // This prevents filesystem metadata corruption on XFS where heavy write - // activity during subsequent compilations can make freshly-renamed entries - // in previously-unpacked directories invisible or lost. - const pendingUnpacks: Array<{ pattern: string; destDir: string }> = []; + // Phase 1: Resolve glob patterns and clean ALL output directories upfront. + // This ensures no rm/rmSync activity overlaps with TypeScript compilation, + // preventing XFS metadata corruption from concurrent metadata operations. + interface IResolvedTask { + pattern: string; + destPath: string; + destDir: string; + absoluteFiles: string[]; + } + const resolvedTasks: IResolvedTask[] = []; for (const pattern of Object.keys(globPatterns)) { const destPath = globPatterns[pattern]; if (!pattern || !destPath) continue; - // Get files matching the glob pattern const files = await FsHelpers.listFilesWithGlob(this.cwd, pattern); - - // Transform to absolute paths const absoluteFiles = smartpath.transform.toAbsolute(files, this.cwd) as string[]; - - // Get destination directory as absolute path const destDir = smartpath.transform.toAbsolute(destPath, this.cwd) as string; - // Clear the destination directory before compilation if it exists if (await FsHelpers.directoryExists(destDir)) { if (!isQuiet && !isJson) { console.log(`🧹 Clearing output directory: ${destPath}`); @@ -357,10 +356,16 @@ export class TsCompiler { await FsHelpers.removeDirectory(destDir); } - // Update compiler options with the output directory + resolvedTasks.push({ pattern, destPath, destDir, absoluteFiles }); + } + + // Phase 2: Compile all tasks. No filesystem cleanup happens during this phase. + const pendingUnpacks: Array<{ pattern: string; destDir: string }> = []; + + for (const task of resolvedTasks) { const options: CompilerOptions = { ...customOptions, - outDir: destDir, + outDir: task.destDir, listEmittedFiles: true, }; @@ -368,23 +373,22 @@ export class TsCompiler { const taskInfo: ITaskInfo = { taskNumber: currentTask, totalTasks, - sourcePattern: pattern, - destDir: destPath, - fileCount: absoluteFiles.length, + sourcePattern: task.pattern, + destDir: task.destPath, + fileCount: task.absoluteFiles.length, }; - const result = await this.compileFiles(absoluteFiles, options, taskInfo); + const result = await this.compileFiles(task.absoluteFiles, options, taskInfo); emittedFiles.push(...result.emittedFiles); errorSummaries.push(result.errorSummary); - // Queue unpack for after all compilations (don't modify output dirs between compilations) if (result.errorSummary.totalErrors === 0) { - pendingUnpacks.push({ pattern, destDir }); - successfulOutputDirs.push(destDir); + pendingUnpacks.push({ pattern: task.pattern, destDir: task.destDir }); + successfulOutputDirs.push(task.destDir); } } - // Perform all unpacks after all compilations are done. + // Phase 3: Perform all unpacks after all compilations are done. // This ensures no output directory is modified while subsequent compilations // are performing heavy filesystem writes to sibling directories. for (const { pattern, destDir } of pendingUnpacks) {