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.
This commit is contained in:
@@ -330,26 +330,25 @@ export class TsCompiler {
|
|||||||
console.log('');
|
console.log('');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect unpack tasks to perform AFTER all compilations complete.
|
// Phase 1: Resolve glob patterns and clean ALL output directories upfront.
|
||||||
// This prevents filesystem metadata corruption on XFS where heavy write
|
// This ensures no rm/rmSync activity overlaps with TypeScript compilation,
|
||||||
// activity during subsequent compilations can make freshly-renamed entries
|
// preventing XFS metadata corruption from concurrent metadata operations.
|
||||||
// in previously-unpacked directories invisible or lost.
|
interface IResolvedTask {
|
||||||
const pendingUnpacks: Array<{ pattern: string; destDir: string }> = [];
|
pattern: string;
|
||||||
|
destPath: string;
|
||||||
|
destDir: string;
|
||||||
|
absoluteFiles: string[];
|
||||||
|
}
|
||||||
|
const resolvedTasks: IResolvedTask[] = [];
|
||||||
|
|
||||||
for (const pattern of Object.keys(globPatterns)) {
|
for (const pattern of Object.keys(globPatterns)) {
|
||||||
const destPath = globPatterns[pattern];
|
const destPath = globPatterns[pattern];
|
||||||
if (!pattern || !destPath) continue;
|
if (!pattern || !destPath) continue;
|
||||||
|
|
||||||
// Get files matching the glob pattern
|
|
||||||
const files = await FsHelpers.listFilesWithGlob(this.cwd, pattern);
|
const files = await FsHelpers.listFilesWithGlob(this.cwd, pattern);
|
||||||
|
|
||||||
// Transform to absolute paths
|
|
||||||
const absoluteFiles = smartpath.transform.toAbsolute(files, this.cwd) as string[];
|
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;
|
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 (await FsHelpers.directoryExists(destDir)) {
|
||||||
if (!isQuiet && !isJson) {
|
if (!isQuiet && !isJson) {
|
||||||
console.log(`🧹 Clearing output directory: ${destPath}`);
|
console.log(`🧹 Clearing output directory: ${destPath}`);
|
||||||
@@ -357,10 +356,16 @@ export class TsCompiler {
|
|||||||
await FsHelpers.removeDirectory(destDir);
|
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 = {
|
const options: CompilerOptions = {
|
||||||
...customOptions,
|
...customOptions,
|
||||||
outDir: destDir,
|
outDir: task.destDir,
|
||||||
listEmittedFiles: true,
|
listEmittedFiles: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -368,23 +373,22 @@ export class TsCompiler {
|
|||||||
const taskInfo: ITaskInfo = {
|
const taskInfo: ITaskInfo = {
|
||||||
taskNumber: currentTask,
|
taskNumber: currentTask,
|
||||||
totalTasks,
|
totalTasks,
|
||||||
sourcePattern: pattern,
|
sourcePattern: task.pattern,
|
||||||
destDir: destPath,
|
destDir: task.destPath,
|
||||||
fileCount: absoluteFiles.length,
|
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);
|
emittedFiles.push(...result.emittedFiles);
|
||||||
errorSummaries.push(result.errorSummary);
|
errorSummaries.push(result.errorSummary);
|
||||||
|
|
||||||
// Queue unpack for after all compilations (don't modify output dirs between compilations)
|
|
||||||
if (result.errorSummary.totalErrors === 0) {
|
if (result.errorSummary.totalErrors === 0) {
|
||||||
pendingUnpacks.push({ pattern, destDir });
|
pendingUnpacks.push({ pattern: task.pattern, destDir: task.destDir });
|
||||||
successfulOutputDirs.push(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
|
// This ensures no output directory is modified while subsequent compilations
|
||||||
// are performing heavy filesystem writes to sibling directories.
|
// are performing heavy filesystem writes to sibling directories.
|
||||||
for (const { pattern, destDir } of pendingUnpacks) {
|
for (const { pattern, destDir } of pendingUnpacks) {
|
||||||
|
|||||||
Reference in New Issue
Block a user