chore(ts-migration): resolve source-text reads across .js/.jsx/.ts/.tsx
The check suite consumes source files through two entirely separate channels, and Phase 0 only hardened one of them. `withSourceResolution` wraps `ssrLoadModule`, which covers the 159 module loads. It does not and cannot see the other 30 sites, which read source as TEXT through `readFileSync` to assert structural facts - "the panel imports no local suggestion ranker", "no runtime path writes a definition". Renaming `base44Client.js` to `.ts` is what surfaced the difference: ENOENT in the middle of a suite that had been passing, from a line no grep for `ssrLoadModule` would ever have found. `resolveSourcePath()` in `ssr-resolve.mjs` reuses the existing `candidatesFor()` ordering - the written path first, then `.ts`, then `.tsx` - and returns a path relative to the root, so every call site keeps its `join(ROOT, ...)` as it was. An unresolvable path comes back unchanged, which keeps the two absence assertions honest: a check proving `store.js` is GONE still asks about the path it means, and now also notices if the file returns under another extension. Thirty-seven lines change, each a one-for-one replacement. No assertion text, no record() message, no ordering, no logic. The audit found the reads in four shapes, and two of them a path grep cannot see: - direct readFileSync(join(ROOT, 'src/x.jsx'), 'utf8') - via a const const P = join(ROOT, 'src/x.jsx') - dir + name ['node.js', 'patch.js'].map((f) => readFileSync(join(ROOT, 'src/lib/ui', f))) - path array for (const f of files) readFileSync(join(ROOT, f)) The third and fourth hide thirteen filenames in adjacent arrays, which is why the first estimate of this work was twenty-two files and the real number is thirty-five. One directory scan also filtered `/\.jsx?$/` over `src/pages/admin` and `src/components/agents`. That one does not crash - it quietly matches nothing once those directories are TypeScript, and the check passes having inspected an empty set. Widened to `/\.[jt]sx?$/`. A silent shrink is worse than a failure, and CI's FLOOR of 900 would not have caught it. Deliberately NOT touched, because they are correctly extension-specific: the `dist/assets` filter reads built bundles, which are `.js` whatever the source was; `scripts/stubs/react-hot-toast.js` and `scripts/browser-flows.js` are scripts, not migrated source; and every comment that mentions a `.js` filename says something true about a file that still has that name. Proven rather than assumed. `npm test` reports 1684/1691 before and after, the same seven failures. Then `src/api/base44Client.js` - the exact file whose rename broke the suite - was renamed to `.ts`, the suite re-run, and the check that reads it as text passed: "the app does not import the seed fixture". The rename was reverted and the file verified byte-identical. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HBG1wnuRfJKCstGB8Fekr8
This commit is contained in:
@@ -78,3 +78,31 @@ export function withSourceResolution(server, root = process.cwd()) {
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
/**
|
||||
* The same resolution, for source read as TEXT rather than loaded as a module.
|
||||
*
|
||||
* `skill-check.mjs` asserts structural facts by reading source files and
|
||||
* matching against their contents — "the panel imports no local suggestion
|
||||
* ranker", "no runtime path writes a definition". Those reads go through
|
||||
* `readFileSync`, not `ssrLoadModule`, so `withSourceResolution` above never
|
||||
* sees them: it wraps the loader, and this is a second, entirely separate
|
||||
* channel. Renaming `base44Client.js` to `.ts` is what surfaced the difference,
|
||||
* as an ENOENT in the middle of a suite that had been passing.
|
||||
*
|
||||
* Takes and returns a path RELATIVE to the project root, so the call site keeps
|
||||
* its `join(ROOT, …)` exactly as it was:
|
||||
*
|
||||
* readFileSync(join(ROOT, resolveSourcePath('src/api/base44Client.js')), 'utf8')
|
||||
*
|
||||
* Unresolvable paths come back unchanged, so the resulting error still names
|
||||
* the file the caller asked for rather than a candidate it invented. That also
|
||||
* keeps the deliberately absent ones honest: a check asserting a file is GONE
|
||||
* gets the path it asked about, and `existsSync` still answers false.
|
||||
*/
|
||||
export function resolveSourcePath(path, root = process.cwd()) {
|
||||
for (const candidate of candidatesFor(path)) {
|
||||
if (existsSync(join(root, candidate))) return candidate;
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user