Skip to content

Commit 851dee7

Browse files
committed
fix(typescript): robust outDir containment; gate temp outDir cleanup in watch mode\n\n- Guard containment with cross-drive root check and absolute-relative detection\n- Skip over-exclusion when filterRoot is false by falling back to process.cwd()\n- Delete temp outDir only for non-watch builds; clean up in closeWatcher for watch\n- Docs: clarify effective outDir exclusion and watch-mode cleanup timing\n\nRefs #1920
1 parent 7f97dd1 commit 851dee7

File tree

2 files changed

+29
-12
lines changed

2 files changed

+29
-12
lines changed

packages/typescript/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ Default: `null`
7979

8080
A [picomatch pattern](https://github.com/micromatch/picomatch), or array of patterns, which specifies the files in the build the plugin should operate on. By default all `.ts` and `.tsx` files are targeted. If your `tsconfig.json` (or plugin `compilerOptions`) sets `allowJs: true`, the default include expands to also cover `.js`, `.jsx`, `.mjs`, and `.cjs` files so that JavaScript sources are downleveled by TypeScript as well.
8181

82-
> Note: When `allowJs` is enabled and no `outDir` is configured, this plugin creates a temporary output directory for TypeScript emit to avoid TS5055 (overwriting input files). The directory is excluded from processing and removed after the build.
82+
> Note: When `allowJs` is enabled and no `outDir` is configured, this plugin creates a temporary output directory for TypeScript emit to avoid TS5055 (overwriting input files). The effective `outDir` (whether user-provided or the temporary one) is excluded from plugin processing to prevent re-processing emitted files. The temporary directory is removed after a non-watch build completes; in watch mode it is removed when the watcher stops.
8383
8484
### `filterRoot`
8585

packages/typescript/src/index.ts

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -69,18 +69,23 @@ export default function typescript(options: RollupTypescriptOptions = {}): Plugi
6969
const effectiveOutDir = parsedOptions.options.outDir
7070
? path.resolve(parsedOptions.options.outDir)
7171
: null;
72-
const filterBase = (filterRoot ?? parsedOptions.options.rootDir) || null;
73-
const filterBaseAbs = filterBase ? path.resolve(filterBase) : null;
72+
// Determine the base used for containment checks. If pattern resolution is disabled
73+
// (filterRoot === false), fall back to process.cwd() so we don't accidentally
74+
// exclude sources when e.g. outDir='.'.
75+
const willResolvePatterns = filterRoot !== false;
76+
const configuredBase = willResolvePatterns ? filterRoot ?? parsedOptions.options.rootDir : null;
77+
const filterBaseAbs = configuredBase ? path.resolve(configuredBase) : null;
7478
if (effectiveOutDir) {
75-
// Avoid excluding sources: skip when the filter base (rootDir) lives inside outDir
76-
// Use path.relative for a robust cross-platform containment check.
77-
const outDirContainsFilterBase = filterBaseAbs
78-
? (() => {
79-
const rel = path.relative(effectiveOutDir, filterBaseAbs);
80-
// rel === '' -> same dir; rel starts with '..' -> outside
81-
return rel === '' || !(rel === '..' || rel.startsWith(`..${path.sep}`));
82-
})()
83-
: false;
79+
// Avoid excluding sources: skip when the filter base (or cwd fallback) lives inside outDir.
80+
// Use path.relative with root equality guard for cross-platform correctness.
81+
const baseForContainment = filterBaseAbs ?? process.cwd();
82+
const outDirContainsFilterBase = (() => {
83+
// Different roots (e.g., drive letters on Windows) cannot be in a parent/child relationship
84+
if (path.parse(effectiveOutDir).root !== path.parse(baseForContainment).root) return false;
85+
const rel = path.relative(effectiveOutDir, baseForContainment);
86+
// rel === '' -> same dir; absolute or '..' => outside
87+
return rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel));
88+
})();
8489
if (!outDirContainsFilterBase) {
8590
filterExclude.push(normalizePath(path.join(effectiveOutDir, '**')));
8691
}
@@ -146,7 +151,19 @@ export default function typescript(options: RollupTypescriptOptions = {}): Plugi
146151
// ESLint doesn't understand optional chaining
147152
// eslint-disable-next-line
148153
program?.close();
154+
if (autoOutDir) {
155+
try {
156+
fs.rmSync(autoOutDir, { recursive: true, force: true });
157+
} catch {
158+
// ignore cleanup failures
159+
}
160+
autoOutDir = null;
161+
}
149162
}
163+
},
164+
165+
// Ensure temp outDir is removed exactly once when watch stops
166+
closeWatcher() {
150167
if (autoOutDir) {
151168
try {
152169
fs.rmSync(autoOutDir, { recursive: true, force: true });

0 commit comments

Comments
 (0)