Skip to content

fix: repair broken type surfaces and ESM config entry before odysseus release#1827

Open
bgub wants to merge 4 commits into
mainfrom
bg/fix-release-api-blockers
Open

fix: repair broken type surfaces and ESM config entry before odysseus release#1827
bgub wants to merge 4 commits into
mainfrom
bg/fix-release-api-blockers

Conversation

@bgub

@bgub bgub commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

TL;DR

Fixes three release blockers found while auditing the odysseus public API surfaces: gt-react ships broken type declarations, gt-next/config crashes at import time from ESM configs, and gt-i18n's shipped declarations reference an undeclared type.

Code Changes

gt-react — broken types entry

  • src/index.types.ts re-exported initializeGT from @generaltranslation/react-core/pure, which no longer exports it (removed in feat!: clean up @generaltranslation/react-core public API surface #1815; the runtime entries were updated but the shared types entry was missed). pnpm --filter gt-react typecheck failed with TS2305, consumers with skipLibCheck: false got a hard error on any gt-react import, and everyone else got any for initializeGT. Now aliases the local initializeGTSRA wrapper, matching index.server.ts.

gt-next — ESM config build unusable

  • dist/config.mjs executed bare require.resolve/require at module-evaluation time (version probing) and __dirname (SWC wasm path), none of which exist in plain-Node ESM. Any next.config.mjs / "type": "module" project crashed on import 'gt-next/config'; the SWC compiler silently downgraded to 'none'. All in-repo test apps use next.config.ts (transpiled to CJS), which is why nothing caught it.
  • Added src/plugin/nodeCompat.ts exporting a CJS/ESM-safe nodeRequire and distDir, used by getStableNextVersionInfo.ts and config.ts (including the lazy require('@generaltranslation/compiler') in the webpack hook). polyfillRequire: false stays as-is — the compat module is only imported from the config graph, which is never client-bundled.
  • tsconfig.json: module: CommonJSESNext (typecheck-only; tsdown does the real transpile). Required for import.meta, and aligns with every other package in the repo — gt-next was the lone outlier.

gt-i18n — dangling type in shipped declarations

  • dist/internal.d.mts declared interpolateMessage(... options: InterpolationOptions ...) but never declared or imported InterpolationOptions: the alias was tagged @internal and stripInternal: true removed the declaration while keeping the reference. Since gt-react's and gt-next's own d.ts import from gt-i18n/internal, any app compiling with skipLibCheck: false broke just by importing them.
  • Moved the alias into translation-functions/types/options.ts (next to NormalizedLookupOptions, which it wraps) and dropped the @internal tag. The emitted declarations now import it from the shared options chunk, and it's nameable from gt-i18n/internal/types via the existing export type *.

Patch changesets added for gt-react, gt-next, and gt-i18n.

Notes / Flags

  • Verification: typecheck + build + full test suites pass for gt-react (21), gt-next (223), gt-i18n (251); gt-tanstack-start (re-exports initializeGT) typechecks. Plain-Node smoke tests: await import('gt-next/config') (ESM) and require('gt-next/config') (CJS) both load; nodeCompat resolves distDir to <pkg>/dist and next/package.json in both formats.
  • The SWC path couldn't be exercised fully end-to-end locally: the workspace's next@15.2.3 is below the plugin's 16.1.0 gate, so validateCompiler downgrades before wasm resolution. The distDir resolution itself is verified in both formats.
  • The module: ESNext tsconfig flip is the one change with wider blast radius — worth a second look, though tsc is noEmit-only for this package.
  • Process gap that let these ship: CI runs lint/build/test but never typecheck, and tsdown's dts emit passes unresolved re-exports through. Recommend adding turbo typecheck and a plain-Node ESM import smoke test to the release gate (not included in this PR).

Greptile Summary

This PR fixes three release blockers discovered while auditing the odysseus public API surfaces before release: a broken initializeGT re-export in gt-react's shared types entry, an ESM crash on import 'gt-next/config' caused by bare require/__dirname usage in the .mjs build, and a dangling InterpolationOptions reference in gt-i18n's shipped declarations.

  • gt-react (index.types.ts): Drops the removed @generaltranslation/react-core/pure re-export of initializeGT and aliases the local initializeGTSRA wrapper instead, matching index.server.ts and restoring correct types for all consumers.
  • gt-next (nodeCompat.ts, config.ts, getStableNextVersionInfo.ts, tsconfig.json): Introduces a CJS/ESM-safe nodeRequire/distDir compat helper; replaces all bare require, require.resolve, and __dirname uses in the config graph with it. The tsconfig module setting is updated from CommonJS to ESNext to allow import.meta to typecheck — note that moduleResolution remains "node", which is a mismatched combination for module: ESNext and may produce TypeScript diagnostics (see inline comment).
  • gt-i18n (options.ts, interpolateMessage.ts): Promotes InterpolationOptions from an @internal-tagged alias (stripped from .d.mts by stripInternal) to a proper public export in the shared options module, resolving the dangling type reference in shipped declarations.

Confidence Score: 4/5

The three targeted fixes are correct and well-scoped; the moduleResolution: "node" + module: "ESNext" mismatch in the next package tsconfig is the only thing worth a follow-up.

All three bug fixes are mechanically sound: the initializeGT alias points at the right local wrapper, nodeRequire/distDir correctly handle both module formats at runtime, and InterpolationOptions is now properly declared in the shared options module. The one loose end is the tsconfig.json module/moduleResolution pairing — "ESNext" + "node" is a known mismatched combination in TypeScript 5.x that can surface diagnostics in editors and strict CI configurations, even though it doesn't block the build today.

packages/next/tsconfig.json — the module/moduleResolution mismatch is worth revisiting before the next typecheck-in-CI effort described in the PR notes.

Important Files Changed

Filename Overview
packages/next/src/plugin/nodeCompat.ts New ESM/CJS compat shim exporting nodeRequire and distDir; runtime branching is correct and import.meta.url is only evaluated in the ESM path.
packages/next/tsconfig.json Flipped module from CommonJS to ESNext to allow import.meta to typecheck; moduleResolution stays "node", which is a mismatched combination for ESNext — harmless here (tsdown builds, tsc is noEmit-only) but TypeScript may emit diagnostics about it.
packages/next/src/config.ts Replaces __dirname with distDir for wasm path resolution and require(...) with nodeRequire(...) in the lazy webpack hook; both substitutions are correct and preserve original semantics.
packages/next/src/plugin/getStableNextVersionInfo.ts Replaces bare require/require.resolve calls with nodeRequire/nodeRequire.resolve throughout; semantics unchanged, ESM-safe.
packages/react/src/index.types.ts Drops the broken re-export of initializeGT from @generaltranslation/react-core/pure (removed upstream) and aliases the local initializeGTSRA instead, matching index.server.ts.
packages/i18n/src/translation-functions/types/options.ts Adds InterpolationOptions as a named public export (alias for NormalizedLookupOptions<StringFormat>), fixing the dangling declaration in shipped .d.mts files.
packages/i18n/src/translation-functions/utils/interpolation/interpolateMessage.ts Removes the @internal-tagged InterpolationOptions alias and its associated StringFormat import, importing the type from the shared options module instead.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["next.config.mjs / next.config.ts"] -->|"import 'gt-next/config'"| B["dist/config.mjs OR dist/config.js"]
    B --> C["nodeCompat.ts"]
    C -->|"typeof require === 'function'"| D{"CJS or ESM?"}
    D -->|"CJS (dist/config.js)"| E["use native require"]
    D -->|"ESM (dist/config.mjs)"| F["createRequire(import.meta.url)"]
    E --> G["nodeRequire"]
    F --> G
    G --> H["getStableNextVersionInfo.ts\n(version probing)"]
    G --> I["lazy require('@generaltranslation/compiler')\n(webpack hook)"]
    C -->|"path.resolve(nodeCompatDirname, '..')"| J["distDir = dist/"]
    J --> K["path.resolve(distDir, './gt_swc_plugin.wasm')"]
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A["next.config.mjs / next.config.ts"] -->|"import 'gt-next/config'"| B["dist/config.mjs OR dist/config.js"]
    B --> C["nodeCompat.ts"]
    C -->|"typeof require === 'function'"| D{"CJS or ESM?"}
    D -->|"CJS (dist/config.js)"| E["use native require"]
    D -->|"ESM (dist/config.mjs)"| F["createRequire(import.meta.url)"]
    E --> G["nodeRequire"]
    F --> G
    G --> H["getStableNextVersionInfo.ts\n(version probing)"]
    G --> I["lazy require('@generaltranslation/compiler')\n(webpack hook)"]
    C -->|"path.resolve(nodeCompatDirname, '..')"| J["distDir = dist/"]
    J --> K["path.resolve(distDir, './gt_swc_plugin.wasm')"]
Loading

Fix All in Claude Code Fix All in Codex

Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 1
packages/next/tsconfig.json:4-5
`module: "ESNext"` paired with `moduleResolution: "node"` is a mismatched combination — TypeScript 5.x expects `moduleResolution` to be `"bundler"`, `"node16"`, or `"nodenext"` when `module` is `"ESNext"`. While this is typecheck-only (`tsc` is noEmit here and tsdown handles the real build), editors and CI runs may surface a TS5110 diagnostic on this combination. Using `"bundler"` would be the idiomatic match for ESNext output in a monorepo with a bundler-driven build.

```suggestion
    "module": "ESNext",
    "moduleResolution": "bundler",
```

Reviews (1): Last reviewed commit: "fix(i18n): declare InterpolationOptions ..." | Re-trigger Greptile

Greptile also left 1 inline comment on this PR.

@bgub bgub requested a review from a team as a code owner July 4, 2026 02:01
Comment on lines +4 to 5
"module": "ESNext",
"moduleResolution": "node",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 module: "ESNext" paired with moduleResolution: "node" is a mismatched combination — TypeScript 5.x expects moduleResolution to be "bundler", "node16", or "nodenext" when module is "ESNext". While this is typecheck-only (tsc is noEmit here and tsdown handles the real build), editors and CI runs may surface a TS5110 diagnostic on this combination. Using "bundler" would be the idiomatic match for ESNext output in a monorepo with a bundler-driven build.

Suggested change
"module": "ESNext",
"moduleResolution": "node",
"module": "ESNext",
"moduleResolution": "bundler",
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/next/tsconfig.json
Line: 4-5

Comment:
`module: "ESNext"` paired with `moduleResolution: "node"` is a mismatched combination — TypeScript 5.x expects `moduleResolution` to be `"bundler"`, `"node16"`, or `"nodenext"` when `module` is `"ESNext"`. While this is typecheck-only (`tsc` is noEmit here and tsdown handles the real build), editors and CI runs may surface a TS5110 diagnostic on this combination. Using `"bundler"` would be the idiomatic match for ESNext output in a monorepo with a bundler-driven build.

```suggestion
    "module": "ESNext",
    "moduleResolution": "bundler",
```

How can I resolve this? If you propose a fix, please make it concise.

Fix in Claude Code Fix in Codex

@bgub

bgub commented Jul 4, 2026

Copy link
Copy Markdown
Contributor Author

Pushed a follow-up commit: widened InterpolationOptions from NormalizedLookupOptions<StringFormat> to LookupOptionsFor<StringFormat>. Once the type actually resolves (the point of this PR), react-core's resolveStringContent fails typecheck — it passes un-normalized options to interpolateMessage, which the runtime deliberately supports ($format ?? 'STRING', $locale filled from sourceLocale; the tagged-template fallback path relies on the STRING default). The widened alias is the honest signature; no runtime change. Verified: gt-i18n + react-core typecheck, build, and tests pass.

Base automatically changed from odysseus to main July 4, 2026 02:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant