fix(compiler): use TypeScript sys hooks instead of fs monkeypatching to detect writes/deletes in previous output directories

This commit is contained in:
2026-03-05 15:28:18 +00:00
parent a5dd5252db
commit e8e64a4ef3
3 changed files with 37 additions and 41 deletions

View File

@@ -1,5 +1,13 @@
# Changelog # Changelog
## 2026-03-05 - 4.2.1 - fix(compiler)
use TypeScript sys hooks instead of fs monkeypatching to detect writes/deletes in previous output directories
- Replace direct fs.* monkeypatching with interception of typescript.sys.writeFile, typescript.sys.deleteFile and typescript.sys.createDirectory
- Add guards for optional sys.deleteFile before overriding it and preserve original sys methods to restore after compilation
- Update diagnostic messages to reference TypeScript sys ops and add an informational message when no ops are observed
- Reduce surface area of changes by avoiding global fs changes and focusing on TypeScript's sys API for safer interception
## 2026-03-05 - 4.2.0 - feat(mod_compiler) ## 2026-03-05 - 4.2.0 - feat(mod_compiler)
add diagnostic interception of fs operations to detect and report unexpected file system changes in previously compiled output directories during compilation add diagnostic interception of fs operations to detect and report unexpected file system changes in previously compiled output directories during compilation

View File

@@ -3,6 +3,6 @@
*/ */
export const commitinfo = { export const commitinfo = {
name: '@git.zone/tsbuild', name: '@git.zone/tsbuild',
version: '4.2.0', version: '4.2.1',
description: 'A tool for compiling TypeScript files using the latest nightly features, offering flexible APIs and a CLI for streamlined development.' description: 'A tool for compiling TypeScript files using the latest nightly features, offering flexible APIs and a CLI for streamlined development.'
} }

View File

@@ -380,45 +380,33 @@ export class TsCompiler {
fileCount: absoluteFiles.length, fileCount: absoluteFiles.length,
}; };
// Diagnostic: intercept fs operations during compilation to detect // Diagnostic: intercept TypeScript sys operations during compilation to detect
// any unexpected deletions in previously compiled output directories // any unexpected writes/deletes in previously compiled output directories
const watchedDirs = successfulOutputDirs.filter(d => d !== destDir); const watchedDirs = successfulOutputDirs.filter(d => d !== destDir);
const origUnlink = fs.unlinkSync;
const origRm = fs.rmSync;
const origRmdir = fs.rmdirSync;
const origRename = fs.renameSync;
const origWriteFile = fs.writeFileSync;
let interceptedOps: string[] = []; let interceptedOps: string[] = [];
const origSysWriteFile = typescript.sys.writeFile;
const origSysDeleteFile = typescript.sys.deleteFile;
const origSysCreateDir = typescript.sys.createDirectory;
if (watchedDirs.length > 0 && !isQuiet && !isJson) { if (watchedDirs.length > 0 && !isQuiet && !isJson) {
(fs as any).unlinkSync = (p: string, ...args: any[]) => { typescript.sys.writeFile = (p: string, data: string, writeBom?: boolean) => {
if (watchedDirs.some(d => String(p).startsWith(d + '/'))) { if (watchedDirs.some(d => p.startsWith(d + '/'))) {
interceptedOps.push(`unlink: ${p}`); interceptedOps.push(`sys.writeFile: ${p}`);
} }
return origUnlink.call(fs, p, ...args); return origSysWriteFile.call(typescript.sys, p, data, writeBom);
}; };
(fs as any).rmSync = (p: string, ...args: any[]) => { if (origSysDeleteFile) {
if (watchedDirs.some(d => String(p).startsWith(d + '/'))) { typescript.sys.deleteFile = (p: string) => {
interceptedOps.push(`rm: ${p}`); if (watchedDirs.some(d => p.startsWith(d + '/'))) {
interceptedOps.push(`sys.deleteFile: ${p}`);
} }
return origRm.call(fs, p, ...args); return origSysDeleteFile!.call(typescript.sys, p);
}; };
(fs as any).rmdirSync = (p: string, ...args: any[]) => {
if (watchedDirs.some(d => String(p).startsWith(d + '/'))) {
interceptedOps.push(`rmdir: ${p}`);
} }
return origRmdir.call(fs, p, ...args); typescript.sys.createDirectory = (p: string) => {
}; if (watchedDirs.some(d => p.startsWith(d + '/'))) {
(fs as any).renameSync = (src: string, dest: string, ...args: any[]) => { interceptedOps.push(`sys.createDirectory: ${p}`);
if (watchedDirs.some(d => String(src).startsWith(d + '/') || String(dest).startsWith(d + '/'))) {
interceptedOps.push(`rename: ${src}${dest}`);
} }
return origRename.call(fs, src, dest, ...args); return origSysCreateDir.call(typescript.sys, p);
};
(fs as any).writeFileSync = (p: string, ...args: any[]) => {
if (watchedDirs.some(d => String(p).startsWith(d + '/'))) {
interceptedOps.push(`write: ${p}`);
}
return origWriteFile.call(fs, p, ...args);
}; };
} }
@@ -426,21 +414,21 @@ export class TsCompiler {
emittedFiles.push(...result.emittedFiles); emittedFiles.push(...result.emittedFiles);
errorSummaries.push(result.errorSummary); errorSummaries.push(result.errorSummary);
// Restore original fs methods and report any intercepted operations // Restore original sys methods and report any intercepted operations
if (watchedDirs.length > 0 && !isQuiet && !isJson) { if (watchedDirs.length > 0 && !isQuiet && !isJson) {
(fs as any).unlinkSync = origUnlink; typescript.sys.writeFile = origSysWriteFile;
(fs as any).rmSync = origRm; typescript.sys.deleteFile = origSysDeleteFile;
(fs as any).rmdirSync = origRmdir; typescript.sys.createDirectory = origSysCreateDir;
(fs as any).renameSync = origRename;
(fs as any).writeFileSync = origWriteFile;
if (interceptedOps.length > 0) { if (interceptedOps.length > 0) {
console.log(` ⚠️ [diag] ${interceptedOps.length} ops on previous output dirs during compilation:`); console.log(` ⚠️ [diag] ${interceptedOps.length} TypeScript sys ops on previous output dirs:`);
for (const op of interceptedOps.slice(0, 30)) { for (const op of interceptedOps.slice(0, 30)) {
console.log(` ${op.replace(this.cwd + '/', '')}`); console.log(` ${op.replace(this.cwd + '/', '')}`);
} }
if (interceptedOps.length > 30) { if (interceptedOps.length > 30) {
console.log(` ... and ${interceptedOps.length - 30} more`); console.log(` ... and ${interceptedOps.length - 30} more`);
} }
} else {
console.log(` [diag] No TypeScript sys ops on previous output dirs during this compilation`);
} }
} }
diagSnap('post-compile'); diagSnap('post-compile');