Files
krow_talent_app/scripts/ssr-resolve.mjs
Aravind 1775395256 chore(ts-migration): establish TypeScript migration checkpoint
Phases 0-3 of the JS/JSX -> TS/TSX migration. No runtime behaviour changes:
every converted file emits byte-identical JavaScript, verified file by file.

Phase 0 - harness hardening, before any rename:
  - scripts/ssr-resolve.mjs wraps `ssrLoadModule` so the ~170 literal module
    paths in the check scripts resolve .js/.jsx/.ts/.tsx. Without it the first
    rename would have silently destroyed the 1642-check suite that guards the
    Owliver flow.
  - eslint.config.js gains a TypeScript block. Its `files` globs listed only
    {js,mjs,cjs,jsx}, so a renamed file would have dropped out of the run while
    `eslint .` went on exiting 0 - the quietest failure mode available.
  - MIGRATION_BASELINE.md records the measured starting point, including the
    pre-existing seed-fixture failure and the already-broken standalone
    owliver-baseline.mjs, so neither is later mistaken for migration damage.

Phase 1 - tsconfig.json succeeds jsconfig.json, carrying every option across at
its old value. `types` moves from [] to ["vite/client"], which fixes the eight
import.meta errors; @types/node is deliberately excluded so setTimeout stays a
number in browser code. allowJs and checkJs stay on, strict stays off.

Phase 2 - src/types/{api,entities,user}.ts. Transport envelope, error shape,
the entity-name union (the same 18 names are written down twice today, in
httpClient and base44Client, with nothing checking they agree), and the user
record. Every field transcribed from the API contract, the migrations and the
/me projection in me.go - not inferred. Entity record shapes are deliberately
absent: derivable, but nothing consumes them yet.

Phase 3 - nine leaf utilities renamed to .ts with annotations added only where
they could be established from existing usage. Entity records are typed `any`
with a comment naming what they are, rather than a guessed interface.

Verified: tsc 64 errors (65 before; one pre-existing TS2559 genuinely fixed,
none introduced), lint unchanged at 0 errors, skill-check 1641/1642 with only
the known failure, Owliver baseline section 59/59 green, production build
succeeds with the API origin inlined.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HBG1wnuRfJKCstGB8Fekr8
2026-09-11 15:52:01 +05:30

81 lines
3.6 KiB
JavaScript

/**
* Extension-agnostic SSR module loading, for the duration of the TypeScript
* migration.
*
* Every script in here addresses modules by literal path — `ssrLoadModule(
* '/src/lib/skills/registry.js')` — about a hundred and seventy times across
* `skill-check.mjs`, `owliver-capture.mjs`, `render-page.mjs` and
* `seed-fixture.mjs`. That is fine while every source file is JavaScript and
* fatal the moment one is not: renaming `registry.js` to `registry.ts` turns
* the check suite's very first load into a failure, and the suite is the only
* evidence the Owliver flow still behaves the way it did.
*
* Rewriting all those call sites would be a large, noisy, error-prone diff
* against the file that guards the migration — exactly the wrong thing to
* disturb. So the loader is wrapped once instead and the call sites keep the
* paths they already have, which stay readable as the names of real files.
*
* Resolution is by existence on disk, not by catching a failed load. A load
* that fails for a real reason — a syntax error, a bad import inside the module
* — must surface as itself; retrying under another extension would bury it
* behind a second, more confusing error about a file that was never there.
*
* The path as written is always tried first, so while a module is still
* JavaScript this changes nothing at all.
*
* This file is temporary. When `src` holds no `.js` or `.jsx` any more, the
* call sites can be renamed in one pass and this wrapper deleted.
*/
import { existsSync } from 'node:fs';
import { join } from 'node:path';
/**
* The candidate paths for one module specifier, in the order they are tried.
*
* Only `.js` and `.jsx` are rewritten. Anything else — a bare specifier, a
* `.mjs` file, something under `/node_modules` — is returned untouched, because
* nothing in this migration renames it.
*
* `.js` is allowed to become `.tsx` as well as `.ts`. Not because any `.js`
* file here contains JSX today — none of the 93 does, checked with a parser
* rather than a guess — but because one may be renamed that way: the only
* `.js` under `src/pages` is `admin/positions/nodes.js`, sitting among seven
* sibling `nodes.jsx` files, and whoever converts that directory will
* reasonably want all eight to end in `.tsx`. The extra candidate costs one
* `existsSync` that answers no.
*/
export function candidatesFor(path) {
const specifier = String(path);
if (!/\.jsx?$/.test(specifier)) return [specifier];
const stem = specifier.replace(/\.jsx?$/, '');
return [...new Set([specifier, `${stem}.ts`, `${stem}.tsx`])];
}
/**
* Wraps `server.ssrLoadModule` so it finds a module whichever of the four
* extensions it currently carries.
*
* Mutates and returns the server, so it reads as one line after `createServer`
* and every later call — including the dynamically-built paths, which is why
* this is done here rather than at the call sites — goes through it.
*
* `root` is where the leading-slash paths are rooted; it defaults to the
* process's working directory, which is what every caller here uses.
*/
export function withSourceResolution(server, root = process.cwd()) {
const load = server.ssrLoadModule.bind(server);
server.ssrLoadModule = (path, options) => {
for (const candidate of candidatesFor(path)) {
if (existsSync(join(root, candidate.replace(/^\//, '')))) {
return load(candidate, options);
}
}
/* Nothing on disk under any extension. Load the path as written so the
error names what the caller actually asked for. */
return load(path, options);
};
return server;
}