everything-dev
Version:
A consolidated product package for building Module Federation apps with oRPC APIs.
96 lines (94 loc) • 4.51 kB
JavaScript
import { Finding } from "../domain.mjs";
import { fileExists, readText } from "../fs.mjs";
import { Effect } from "effect";
//#region src/services/scaffold/upgrade-audit/checks/i18n-seam.ts
const APP_PATH = "ui/src/app.ts";
const CATALOGS_PATH = "ui/src/i18n/catalogs.ts";
const RUNTIME_PATH = "ui/src/i18n/runtime.tsx";
const ROOT_PATH = "ui/src/routes/__root.tsx";
const RUNTIME_IMPORT = "@/i18n/runtime";
const parseAppLocale = (source) => {
const match = source.match(/export\s+const\s+appLocale[^=]*=\s*\{([\s\S]*?)\}/);
if (!match) return null;
const body = match[1];
const locales = body.match(/locales:\s*\[([^\]]*)\]/)?.[1];
const defaultLocale = body.match(/defaultLocale:\s*"([^"]+)"/)?.[1];
const cookieName = body.match(/cookieName:\s*"([^"]+)"/)?.[1];
if (!locales || !defaultLocale || !cookieName) return null;
return {
locales: locales.split(",").map((l) => l.trim().replace(/^["']|["']$/g, "")).filter(Boolean),
defaultLocale,
cookieName
};
};
const parseCatalogs = (source) => {
const locales = source.match(/APP_LOCALES\s*=\s*\[([^\]]*)\]/)?.[1];
const defaultLocale = source.match(/DEFAULT_APP_LOCALE[^=]*=\s*"([^"]+)"/)?.[1];
const cookieName = source.match(/APP_LOCALE_COOKIE[^=]*=\s*"([^"]+)"/)?.[1];
if (!locales || !defaultLocale || !cookieName) return null;
return {
locales: locales.split(",").map((l) => l.trim().replace(/^["']|["']$/g, "")).filter(Boolean),
defaultLocale,
cookieName
};
};
const localeDrift = (appLocale, catalogs) => {
const drift = [];
if (appLocale.defaultLocale !== catalogs.defaultLocale) drift.push(`defaultLocale: app.ts "${appLocale.defaultLocale}" vs catalogs "${catalogs.defaultLocale}"`);
if (appLocale.cookieName !== catalogs.cookieName) drift.push(`cookieName: app.ts "${appLocale.cookieName}" vs catalogs "${catalogs.cookieName}"`);
const a = appLocale.locales.join(",");
const b = catalogs.locales.join(",");
if (a !== b) drift.push(`locales: app.ts [${a}] vs catalogs [${b}]`);
return drift;
};
const readI18nSeam = (projectDir) => Effect.gen(function* () {
const appLocale = (yield* fileExists(projectDir, "ui/src/app.ts")) ? parseAppLocale(yield* readText(projectDir, APP_PATH)) : null;
const runtimeExists = yield* fileExists(projectDir, RUNTIME_PATH);
const rootExists = yield* fileExists(projectDir, ROOT_PATH);
let seam;
if (!runtimeExists) seam = "missing";
else if (!rootExists) seam = "absent-root";
else seam = (yield* readText(projectDir, "ui/src/routes/__root.tsx")).includes("@/i18n/runtime") ? "wired" : "unmounted";
let catalogsDrift = [];
if (appLocale && (yield* fileExists(projectDir, "ui/src/i18n/catalogs.ts"))) {
const catalogs = parseCatalogs(yield* readText(projectDir, CATALOGS_PATH));
if (catalogs) catalogsDrift = localeDrift(appLocale, catalogs);
}
return {
appLocale,
seam,
catalogsDrift
};
});
const checkI18nSeam = (projectDir) => Effect.gen(function* () {
const { appLocale, seam, catalogsDrift } = yield* readI18nSeam(projectDir);
const findings = [];
if (seam === "missing") findings.push(new Finding({
check: "i18n-seam",
path: RUNTIME_PATH,
severity: "blocker",
message: "the shared i18n runtime is not wired — the UI bundle shares no @lingui/*, so the host's auth plugin fails SSR composition with RUNTIME-012 (shareKey @lingui/core). Run `pnpm i18n-seam` to scaffold ui/src/i18n and mount AppI18nProvider"
}));
else if (seam === "unmounted") findings.push(new Finding({
check: "i18n-seam",
path: RUNTIME_PATH,
severity: "blocker",
message: "ui/src/i18n/runtime.tsx exists but no route imports it — the provider never enters the bundle, so @lingui/* stays out of the share scope (RUNTIME-012 at SSR composition). Mount AppI18nProvider in the root route"
}));
else if (seam === "absent-root") findings.push(new Finding({
check: "i18n-seam",
path: ROOT_PATH,
severity: "blocker",
message: "ui/src/i18n/runtime.tsx exists but ui/src/routes/__root.tsx is absent — the provider cannot be mounted and the bundle shares no @lingui/* (RUNTIME-012 at SSR composition); inspect the route tree"
}));
if (appLocale && catalogsDrift.length > 0) findings.push(new Finding({
check: "i18n-seam",
path: CATALOGS_PATH,
severity: "warn",
message: `i18n catalogs drift from app.ts's appLocale: ${catalogsDrift.join("; ")}`
}));
return findings;
});
//#endregion
export { APP_PATH, CATALOGS_PATH, ROOT_PATH, RUNTIME_IMPORT, RUNTIME_PATH, checkI18nSeam, parseAppLocale, readI18nSeam };
//# sourceMappingURL=i18n-seam.mjs.map