{"version":3,"file":"i18n-seam.cjs","names":[],"sources":["../../../../../src/services/scaffold/upgrade-audit/checks/i18n-seam.ts"],"sourcesContent":["import { Effect } from \"effect\";\nimport { Finding } from \"../domain\";\nimport { fileExists, readText } from \"../fs\";\n\nexport const APP_PATH = \"ui/src/app.ts\";\nexport const CATALOGS_PATH = \"ui/src/i18n/catalogs.ts\";\nexport const RUNTIME_PATH = \"ui/src/i18n/runtime.tsx\";\nexport const ROOT_PATH = \"ui/src/routes/__root.tsx\";\nexport const RUNTIME_IMPORT = \"@/i18n/runtime\";\n\nexport interface AppLocaleConfig {\n  readonly locales: readonly string[];\n  readonly defaultLocale: string;\n  readonly cookieName: string;\n}\n\nexport type I18nSeam = \"wired\" | \"unmounted\" | \"missing\" | \"absent-root\";\n\nexport interface I18nSeamState {\n  readonly appLocale: AppLocaleConfig | null;\n  readonly seam: I18nSeam;\n  readonly catalogsDrift: readonly string[];\n}\n\nexport const parseAppLocale = (source: string): AppLocaleConfig | null => {\n  const match = source.match(/export\\s+const\\s+appLocale[^=]*=\\s*\\{([\\s\\S]*?)\\}/);\n  if (!match) return null;\n  const body = match[1];\n  const locales = body.match(/locales:\\s*\\[([^\\]]*)\\]/)?.[1];\n  const defaultLocale = body.match(/defaultLocale:\\s*\"([^\"]+)\"/)?.[1];\n  const cookieName = body.match(/cookieName:\\s*\"([^\"]+)\"/)?.[1];\n  if (!locales || !defaultLocale || !cookieName) return null;\n  return {\n    locales: locales\n      .split(\",\")\n      .map((l) => l.trim().replace(/^[\"']|[\"']$/g, \"\"))\n      .filter(Boolean),\n    defaultLocale,\n    cookieName,\n  };\n};\n\nconst parseCatalogs = (source: string): AppLocaleConfig | null => {\n  const locales = source.match(/APP_LOCALES\\s*=\\s*\\[([^\\]]*)\\]/)?.[1];\n  const defaultLocale = source.match(/DEFAULT_APP_LOCALE[^=]*=\\s*\"([^\"]+)\"/)?.[1];\n  const cookieName = source.match(/APP_LOCALE_COOKIE[^=]*=\\s*\"([^\"]+)\"/)?.[1];\n  if (!locales || !defaultLocale || !cookieName) return null;\n  return {\n    locales: locales\n      .split(\",\")\n      .map((l) => l.trim().replace(/^[\"']|[\"']$/g, \"\"))\n      .filter(Boolean),\n    defaultLocale,\n    cookieName,\n  };\n};\n\nconst localeDrift = (appLocale: AppLocaleConfig, catalogs: AppLocaleConfig): string[] => {\n  const drift: string[] = [];\n  if (appLocale.defaultLocale !== catalogs.defaultLocale)\n    drift.push(\n      `defaultLocale: app.ts \"${appLocale.defaultLocale}\" vs catalogs \"${catalogs.defaultLocale}\"`,\n    );\n  if (appLocale.cookieName !== catalogs.cookieName)\n    drift.push(`cookieName: app.ts \"${appLocale.cookieName}\" vs catalogs \"${catalogs.cookieName}\"`);\n  const a = appLocale.locales.join(\",\");\n  const b = catalogs.locales.join(\",\");\n  if (a !== b) drift.push(`locales: app.ts [${a}] vs catalogs [${b}]`);\n  return drift;\n};\n\nexport const readI18nSeam = (projectDir: string): Effect.Effect<I18nSeamState, Error> =>\n  Effect.gen(function* () {\n    const appExists = yield* fileExists(projectDir, APP_PATH);\n    const appLocale = appExists ? parseAppLocale(yield* readText(projectDir, APP_PATH)) : null;\n\n    const runtimeExists = yield* fileExists(projectDir, RUNTIME_PATH);\n    const rootExists = yield* fileExists(projectDir, ROOT_PATH);\n\n    let seam: I18nSeam;\n    if (!runtimeExists) seam = \"missing\";\n    else if (!rootExists) seam = \"absent-root\";\n    else {\n      const rootSource = yield* readText(projectDir, ROOT_PATH);\n      seam = rootSource.includes(RUNTIME_IMPORT) ? \"wired\" : \"unmounted\";\n    }\n\n    let catalogsDrift: string[] = [];\n    if (appLocale && (yield* fileExists(projectDir, CATALOGS_PATH))) {\n      const catalogs = parseCatalogs(yield* readText(projectDir, CATALOGS_PATH));\n      if (catalogs) catalogsDrift = localeDrift(appLocale, catalogs);\n    }\n\n    return { appLocale, seam, catalogsDrift };\n  });\n\nexport const checkI18nSeam = (projectDir: string): Effect.Effect<Finding[], Error> =>\n  Effect.gen(function* () {\n    const { appLocale, seam, catalogsDrift } = yield* readI18nSeam(projectDir);\n    const findings: Finding[] = [];\n\n    // The host's auth plugin consumes shared @lingui/* (import: false) — only a\n    // wired core-ui provides it, so the seam matters even without appLocale.\n    if (seam === \"missing\") {\n      findings.push(\n        new Finding({\n          check: \"i18n-seam\",\n          path: RUNTIME_PATH,\n          severity: \"warn\",\n          message:\n            \"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\",\n        }),\n      );\n    } else if (seam === \"unmounted\") {\n      findings.push(\n        new Finding({\n          check: \"i18n-seam\",\n          path: RUNTIME_PATH,\n          severity: \"warn\",\n          message:\n            \"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\",\n        }),\n      );\n    } else if (seam === \"absent-root\") {\n      findings.push(\n        new Finding({\n          check: \"i18n-seam\",\n          path: ROOT_PATH,\n          severity: \"warn\",\n          message:\n            \"ui/src/i18n/runtime.tsx exists but ui/src/routes/__root.tsx is absent — the provider cannot be mounted; inspect the route tree\",\n        }),\n      );\n    }\n\n    if (appLocale && catalogsDrift.length > 0) {\n      findings.push(\n        new Finding({\n          check: \"i18n-seam\",\n          path: CATALOGS_PATH,\n          severity: \"warn\",\n          message: `i18n catalogs drift from app.ts's appLocale: ${catalogsDrift.join(\"; \")}`,\n        }),\n      );\n    }\n\n    return findings;\n  });\n"],"mappings":";;;AAIA,MAAa,WAAW;AACxB,MAAa,gBAAgB;AAC7B,MAAa,eAAe;AAC5B,MAAa,YAAY;AACzB,MAAa,iBAAiB;AAgB9B,MAAa,kBAAkB,WAA2C;CACxE,MAAM,QAAQ,OAAO,MAAM,mDAAmD;CAC9E,IAAI,CAAC,OAAO,OAAO;CACnB,MAAM,OAAO,MAAM;CACnB,MAAM,UAAU,KAAK,MAAM,yBAAyB,CAAC,GAAG;CACxD,MAAM,gBAAgB,KAAK,MAAM,4BAA4B,CAAC,GAAG;CACjE,MAAM,aAAa,KAAK,MAAM,yBAAyB,CAAC,GAAG;CAC3D,IAAI,CAAC,WAAW,CAAC,iBAAiB,CAAC,YAAY,OAAO;CACtD,OAAO;EACL,SAAS,QACN,MAAM,GAAG,CAAC,CACV,KAAK,MAAM,EAAE,KAAK,CAAC,CAAC,QAAQ,gBAAgB,EAAE,CAAC,CAAC,CAChD,OAAO,OAAO;EACjB;EACA;CACF;AACF"}