fix(bff): make published crossProject client declarations portable - #8797
Conversation
crossProject client `.d.ts` were unresolvable once published: - tsc keeps tsconfig `paths` aliases verbatim in declaration output (microsoft/TypeScript#30952), so `@shared/*` leaked to consumers as TS2307. - the client `.d.ts` was copied one directory shallower than tsc emitted it, breaking every relative specifier inside. - the referenced declarations (`dist/shared`, `dist/api`) were never added to `package.json#files`, so they were missing from the packed tarball. Fixes: - server-utils: run the tsconfig-paths rewrite on declaration emit via a new `afterDeclarations` transformer (Import/Export/ImportType/ImportEquals), keeping specifiers relative and extensionless. - plugin-bff: the client re-exports the handler declaration in place via a facade instead of copying it, so relative specifiers stay correct; publish every emitted `.d.ts`; and drive readDirectoryFiles from ApiRouter's `getApiFiles()` so stray artifacts never reach the client generator. Verified with an out-of-repo tarball consumer that type-checks the published client with `skipLibCheck: false` and asserts the type did not degrade to any. Co-Authored-By: Riff
✅ Deploy Preview for modernjs-byted ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
🦋 Changeset detectedLatest commit: 51d7ded The changes in this PR will be included in the next version bump. This PR includes changesets to release 119 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Rsdoctor Bundle Diff Analysis📊 Quick Summary
Generated by Rsdoctor GitHub Action |
…tions The declaration-emit alias rewrite dropped the module extension, so in ESM output (`package.json#type: "module"`) the `.d.ts` referenced `../shared/types` while the JS referenced `../shared/types.js`. `node16`/`nodenext` consumers then hit TS2835 — the same portability failure this rewrite prevents, just on the ESM branch. - server-utils: forward `moduleType` to the `afterDeclarations` transformer and align its early-return with the `before` hook, so ESM declaration specifiers carry `.js` (TS resolves `./x.js` back to `./x.d.ts`). - plugin-bff: the client facade re-exports with an explicit `.js` extension in ESM output too. - Add an ESM declaration fixture and cover both packages. Co-Authored-By: Riff
|
<agent: daily-job rstack ecosystem-ci> Ecosystem CI regression:
|
…Project declaration portability) Real merge of upstream mainline release commit eded841 ("Release v3.8.2 (web-infra-dev#8810)"), so the fork genuinely tracks upstream 3.8.2 instead of claiming it. Tag `v3.8.2` (e642cd1) is a parallel release commit: same parent (8edf91a) and the identical tree (35d89ac), so merging the mainline commit is patch-equivalent to merging the tag. Ancestry is asserted against eded841. The only executable upstream change in v3.8.1..v3.8.2 is 8edf91a "fix(bff): make published crossProject client declarations portable (web-infra-dev#8797)"; the release commit itself is version/CHANGELOG metadata plus the consumed changeset. Conflict resolutions (semantic, not side-taking): - packages/cli/plugin-bff/src/cli.ts, src/utils/clientGenerator.ts: kept the fork's extraction (cli/generator.ts + utils/client-generator/*). Upstream's inline generator body is superseded by those fork-owned modules; the upstream behavior is ported there in the following commit. - packages/cli/plugin-bff/tests/clientGenerator.test.ts: kept upstream's new coverage verbatim (apiFiles filtering, facade shapes, dist glob) on top of the fork's import ordering. These arrive RED until the port lands. - packages/server/utils/src/compilers/typescript/{index,tsconfigPathsPlugin}.ts: kept the fork side. The fork replaced the TypeScript Program pipeline with TS-Go plus a post-emit textual rewriter, so upstream's `afterDeclarations` AST transformer has no host here. Its behavior is reimplemented patch-equivalently on the TS-Go post-emit path in the following commit; upstream's declaration unit tests and fixtures auto-merged and are kept verbatim as the oracle. - packages/{runtime/plugin-i18n,solutions/app-tools,toolkit/create}/package.json: resolved fieldwise — upstream's 3.8.2 version identity, fork's entry points, peer ranges and dependency set. - tests/integration/bff-cross-project: upstream added its fixture under the historical typo directory `bff-corss-project`; the fork renamed that suite to `bff-cross-project`. Everything landed under the corrected path and no `bff-corss-project` directory exists in the merged tree. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…to the fork's TS-Go pipeline
Upstream's fix lands as a `program.emit({ afterDeclarations })` AST transform on a
TypeScript Program. The fork replaced that pipeline with TS-Go plus a post-emit
textual rewrite, so the same semantics are reimplemented on the seam that actually
runs, and the upstream unit tests + fixtures merged in with the release are the
oracle for it.
@modern-js/server-utils
- createResolvedTsgoConfig stops forcing `declaration: false`. The app's resolved
value survives; composite / declarationMap / emitDeclarationOnly / incremental /
noEmit normalization is unchanged, so this stays a one-shot server emit.
- The post-emit collector now picks up `.d.ts` / `.d.mts` / `.d.cts`, and the
output-to-source mapper handles their double extension — `path.parse('index.d.ts')`
reports name `index.d`, so the naive mapping resolved to a source that never
existed and skipped the alias rewrite for every declaration.
- tests/ts.test.ts keeps upstream's two declaration cases; only the quote-exact
assertions are loosened to `["']`, because TS-Go preserves the source's quote
style where tsc normalized to double quotes. Verified emit for all four
specifier kinds: ImportDeclaration, ExportDeclaration, inline `import("...")`
and `import x = require("...")`.
- tests/tsgo.test.ts asserted the old defect (`declaration: false`); it now asserts
the new contract.
@modern-js/plugin-bff
- clientGenerator takes `apiFiles` from ApiRouter (same API_FILE_RULES the runtime
router uses) instead of recursively reading the lambda directory, so emitted
`.d.ts`, compiled `.js`, tests and `_`-prefixed modules never reach generateClient.
- The declaration copy is replaced by a facade (`client-generator/type-facade.ts`)
that re-exports the handler declaration where the compiler emitted it. Copying
moved it one directory shallower and broke every relative specifier inside.
- `${distPath}/**/*.d.ts` joins the published files closure, so the declarations the
facade points at actually ship.
- Deliberate deviation from upstream cli.ts: the facade specifier always carries
`.js`. Upstream derives it from the app-level moduleType, but the fork's
writeClientModuleBoundary makes `dist/client` its own ESM package unconditionally,
so an extensionless specifier there is TS2834/TS2835 for node16/nodenext consumers
(reproduced, and covered by a test).
- A missing origin declaration is now a hard MissingClientDeclarationError rather
than a logged best-effort skip: setPackage advertises `types` for every generated
client, so a missing declaration means publishing a type surface that resolves to
nothing.
- The Effect typed-client declaration is untouched; it describes the generated
HttpApi client, not a handler module, so it is written directly, not as a facade.
tests/integration/bff-cross-project
- Upstream's portability test is ported to the fork's corrected suite path and
adapted: it drives TS-Go rather than `ts.createProgram`, since `typescript@7`
no longer exposes the legacy Program API. It builds, `pnpm pack`s, extracts into
an isolated consumer with no workspace or source aliases, and strict-type-checks
with `skipLibCheck: false`; an unused `@ts-expect-error` (TS2578) fails it, so a
type that degraded to `any` cannot pass. A second case pins the ESM-facade
deviation under node16/nodenext.
Mutation-checked: removing the `**/*.d.ts` files entry, restoring
`declaration: false`, or emitting an extensionless facade specifier each turns the
packed-consumer tests RED (TS2834 in the last case).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The fork now contains upstream's `Release v3.8.2 (web-infra-dev#8810)`, so the divergence gate's `dfcd414a` base (`git merge-base HEAD v3.8.1`) is stale: measured against it, 27 upstream-owned files report budget violations that are nothing but upstream's own 3.8.2 content arriving through the merge. Move `DEFAULT_DIVERGENCE_BASE_REF` to the mainline release commit `eded841256a7cffdaa622e3889fc83407debd3e4`, not the `v3.8.2` tag. Upstream cuts releases on a parallel commit — tag `v3.8.2` is `e642cd16a8`, sharing this commit's parent `8edf91adb1` and its tree `35d89ac688` — so the tag is patch-equivalent but is not an ancestor of HEAD and cannot be used as a base. Six upstream-owned files grow across the transition. Each was inspected against `git diff dfcd414 eded841 -- <file>` before being dispositioned, and none is a merge-resolution defect: - `plugin-bff/src/cli.ts` (216 -> 218) and `src/utils/clientGenerator.ts` (291 -> 338) grow because web-infra-dev#8797 grew upstream's own files while the fork keeps a thin entry and a re-export shim over its fork-owned generator. The fork already threads both new arguments (`moduleType`, `apiFiles`) from `src/cli/generator.ts`. - `server/utils/src/compilers/typescript/tsconfigPathsPlugin.ts` (203 -> 325, and 24 -> 22 hunks) grows because web-infra-dev#8797 added a tsc `afterDeclarations` transformer factory. The fork spawns tsgo and has no `Program` to register it on, so the same alias stripping happens post-emit in `importRewriter.ts`. - `server/utils/tests/fixtures/ts-declaration/api/declaration.ts` is a new upstream fixture that `biome check --write` reformats by one blank line. - `server/utils/src/compilers/typescript/index.ts` (454 -> 482) and `tests/ts.test.ts` (216 -> 230) are the capped Bucket-B patches that port web-infra-dev#8797 onto the tsgo pipeline (20/+4 and 8/+6 changed lines). Land the ledger rows that AGENTS.md Rule 5 requires for a base re-anchor (CLI-19, CLI-20, SRV-20 updated, SRV-21, SRV-22, SRV-23; notes N9 and N10), re-point every doc that named the old base, and record in FORK-DIVERGENCE.md §3 that the allowlist re-record itself has not run yet. The re-record is the one remaining step and it is not applied here: writing it requires `--record-growth`, which was denied by this environment's command policy. Until it runs, `--mode divergence` stops at the base-mismatch assertion and `divergence.test.js`'s "repo divergence allowlist keeps the current tree green" fails. That is a louder and more accurate signal than the 27 stale-base violations the previous state produced, and §3 carries the exact command. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…the exact Rule-5 20-line cap The ws1 port of upstream web-infra-dev#8797 landed at 24 added-plus-removed PR lines on this audited-base-owned file; the reviewed divergence writer refuses anything over the exact 20-line cap. Condensing the three explanatory comments keeps their content and brings the range delta to 19 without touching behavior. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Problem
bff.crossProjectclient.d.tsresolve locally but break once published (fixes #8739, supersedes #8735): consumers hitTS2307or the type degrades toany. Three causes:pathsverbatim in.d.ts(TS#30952), so@shared/*reaches consumers unresolved..d.tsis copied one directory shallower than tsc emitted it, mispointing every relative import.dist/shared/dist/apiare never added topackage.json#files, so they're absent from the tarball.Changes
afterDeclarationstransformer (Import/Export/ImportType/ImportEquals), keeping specifiers relative and extensionless. No public API change..d.ts(<distPath>/**/*.d.ts); and drivereadDirectoryFilesfromApiRouter.getApiFiles()so stray files never reachgenerateClient.Testing
Unit (alias rewrite, facade, file filtering) + an out-of-repo tarball consumer that
tscs the published client withskipLibCheck: falseand asserts no leak and noanydegradation.Co-Authored-By: Riff