diff --git a/web/src/api/pageSizeCallSites.guard.test.ts b/web/src/api/pageSizeCallSites.guard.test.ts
index 1f270c44f..244f9217d 100644
--- a/web/src/api/pageSizeCallSites.guard.test.ts
+++ b/web/src/api/pageSizeCallSites.guard.test.ts
@@ -1,6 +1,3 @@
-import { readFileSync, readdirSync } from 'node:fs';
-import { dirname, join, relative } from 'node:path';
-import { fileURLToPath } from 'node:url';
import { describe, expect, it } from 'vitest';
import { pageSizeSiteId, scanPageSizeSites } from './pageSizeScan';
@@ -265,8 +262,26 @@ const REGISTRY: RegistryEntry[] = [
}
];
-// This file lives in `src/api/`; the scan root is `src/`.
-const SRC_DIR = join(dirname(fileURLToPath(import.meta.url)), '..');
+// Enumerates every source file under `src/` via Vite's `import.meta.glob` — eagerly, as raw text
+// (`query: '?raw', import: 'default'`) — INSTEAD OF Node's `fs`/`path`/`url` (#650 follow-up).
+// This is the only file under `src` that ever needed real filesystem access, and `@types/node`
+// isn't wired into `tsconfig.app.json`'s project (deliberately: it covers production browser code
+// too, and a file-local `/// ` was tried and reverted — under `tsc -b`'s
+// single-program compilation it leaked Node's ambient `setTimeout` into the whole app project,
+// breaking three unrelated `window.setTimeout` mocks that expect the DOM signature). `import.meta
+// .glob` needs neither `node:fs` nor a tsconfig change: it's resolved by Vite at transform time,
+// natively available in the browser/app project, and is the idiomatic Vite/vitest way to enumerate
+// source files. Keys are POSIX paths from the project root, e.g. `/src/api/pageSizeScan.ts`.
+const rawSourceModules = import.meta.glob('/src/**/*.{ts,tsx,mts,cts}', {
+ query: '?raw',
+ import: 'default',
+ eager: true
+}) as Record;
+
+function basename(path: string): string {
+ const idx = path.lastIndexOf('/');
+ return idx === -1 ? path : path.slice(idx + 1);
+}
// Extracted from `listSourceFiles`'s inline condition so it's independently testable (#650
// follow-up round 4): a plant that adds a real `.mts` FILE and observes the guard notice it
@@ -275,7 +290,10 @@ const SRC_DIR = join(dirname(fileURLToPath(import.meta.url)), '..');
// because this repo has no committed `.mts`/`.cts` source and `scanPageSizeSites` parses any
// non-`.tsx` filename as plain TS regardless of extension. Testing this predicate directly, by
// filename, is what actually regression-pins the file-discovery fix rather than depending on the
-// repo happening to contain (or not contain) a matching file.
+// repo happening to contain (or not contain) a matching file. This predicate is still what the
+// glob's results are filtered THROUGH below (`listSourceFiles`) — the extension SET moved into the
+// glob literal, but discovery still runs every matched file through this same named, tested
+// function, not a second copy of the logic.
export function isScannableSourceFileName(name: string): boolean {
// `.mts`/`.cts` are legal TS extensions `tsconfig.app.json`'s `include` covers alongside
// `.ts`/`.tsx` — none exist in this repo today, but the glob must not silently skip one if it
@@ -285,20 +303,22 @@ export function isScannableSourceFileName(name: string): boolean {
);
}
-function listSourceFiles(dir: string): string[] {
- const out: string[] = [];
- for (const entry of readdirSync(dir, { withFileTypes: true })) {
- if (entry.name === 'generated') {
+interface ScannableSource {
+ /** Path relative to `src/`, e.g. `api/pageSizeScan.ts` — matches the REGISTRY's `file` field. */
+ file: string;
+ text: string;
+}
+
+function listSourceFiles(): ScannableSource[] {
+ const out: ScannableSource[] = [];
+ for (const [key, text] of Object.entries(rawSourceModules)) {
+ if (key.includes('/generated/')) {
continue;
}
- const full = join(dir, entry.name);
- if (entry.isDirectory()) {
- out.push(...listSourceFiles(full));
+ if (!isScannableSourceFileName(basename(key))) {
continue;
}
- if (isScannableSourceFileName(entry.name)) {
- out.push(full);
- }
+ out.push({ file: key.replace(/^\/src\//, ''), text });
}
return out;
}
@@ -312,15 +332,11 @@ interface DiscoveredSite {
}
function discoverPageSizeCallSites(): DiscoveredSite[] {
- const files = listSourceFiles(SRC_DIR);
const sites: DiscoveredSite[] = [];
- for (const absPath of files) {
- const relPath = relative(SRC_DIR, absPath);
- const text = readFileSync(absPath, 'utf8');
-
- for (const site of scanPageSizeSites(text, absPath)) {
- sites.push({ file: relPath, ...site });
+ for (const { file, text } of listSourceFiles()) {
+ for (const site of scanPageSizeSites(text, file)) {
+ sites.push({ file, ...site });
}
}
@@ -393,7 +409,7 @@ describe('pageSize call-site guard (#650)', () => {
);
it('scans a healthy number of source files (anti-vacuity: a broken glob must not pass on zero input)', () => {
- const files = listSourceFiles(SRC_DIR);
+ const files = listSourceFiles();
expect(files.length).toBeGreaterThan(50);
});