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) {