Repository navigation
Support subpath exports in tspd gen-extern-signature - #11561
Timothee Guerin (timotheeguerin) merged 4 commits into
Conversation
Compile each package.json export with a typespec condition separately, attribute each entity to the first export reaching its source file, and emit subpath signatures under generated-defs/<subpath>/ importing $decorators from the matching package subpath. Fixes microsoft#8913
commit: |
|
All changed packages have been documented.
Show changes
|
|
You can try these changes here
|
There was a problem hiding this comment.
Pull request overview
This PR updates @typespec/tspd’s gen-extern-signature flow to support libraries that expose TypeSpec decorators/functions via subpath exports (multiple package.json.exports entries with a typespec condition). It compiles each TypeSpec export entry independently, attributes entities to the first export that reaches their declaring source file (with realpath-based identity to avoid symlink duplication), and emits generated signature outputs under per-subpath directories while importing $decorators/$functions from the matching subpath.
Changes:
- Add multi-export compilation and source-file ownership attribution so signatures are generated per
exportssubpath (and handle symlinked paths viahost.realpath). - Teach signature generation to emit subpath-aware imports (and optionally skip
.ts-test.tsfiles when a subpath lacks a JS export). - Add tests, docs, diagnostics, and a Chronus entry describing/validating subpath export behavior.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/tspd/test/gen-extern-signature/subpath-exports.test.ts | Adds unit tests covering subpath export resolution, per-subpath output layout, imports, warnings, and symlink realpath behavior. |
| packages/tspd/src/ref-doc/lib.ts | Introduces a new warning diagnostic for subpath exports missing a JS export condition. |
| packages/tspd/src/gen-extern-signatures/gen-extern-signatures.ts | Core implementation: resolve typespec exports, compile each export, dedupe/compare source paths via realpath, filter entities by export ownership, and write outputs into subpath directories. |
| packages/tspd/src/gen-extern-signatures/components/entity-signatures.tsx | Updates signature rendering to support subpath-aware module descriptors and conditional emission of typecheck files. |
| packages/tspd/README.md | Documents how gen-extern-signature behaves with subpath exports and output layout. |
| .chronus/changes/tspd-subpath-signatures-2026-7-5-0-0-0.md | Adds changelog entry announcing subpath export support in generated extern signatures. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.
Suppressed comments (1)
Previously missed (1) — in code that hasn't changed since the last review.
packages/tspd/src/ref-doc/lib.ts:18
- The diagnostic text has a minor formatting/clarity issue: missing space before the parenthetical, and the wording "Sub export" is confusing when the subpath is the root ("."). Consider making it generic ("Export") and adding the missing space.
"sub-export-missing-js": {
severity: "warning",
messages: {
default: paramMessage`Sub export "${"subpath"}" declares decorators or functions but has no JS export condition("import" or "default") in package.json. Skipping generation of the signature typecheck file.`,
},
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.
Suppressed comments (4)
packages/tspd/src/ref-doc/lib.ts:17
- Diagnostic message has a missing space before the parenthesis in
condition("import" or "default"), which reads like a typo and makes the message harder to parse.
"sub-export-missing-js": {
severity: "warning",
messages: {
default: paramMessage`Sub export "${"subpath"}" declares decorators or functions but has no JS export condition("import" or "default") in package.json. Skipping generation of the signature typecheck file.`,
},
packages/tspd/README.md:20
- The new section header uses "Sub path" (two words) but the rest of the implementation and tests use "subpath" as a single word; updating the header avoids inconsistent terminology.
## Sub path exports
packages/tspd/README.md:26
- These sentences use "sub path" (two words) but elsewhere (including variable names) use "subpath"; using one spelling consistently improves readability/searchability.
Each export is compiled on its own and a decorator or function is attributed to the first export that reaches the file declaring it. This means entities declared in the root entrypoint stay on the root even when a sub path entrypoint imports it back.
Signatures for the root export(`.`) are written to `generated-defs/`, signatures for a sub path are written to a matching directory and import `$decorators`/`$functions` from that same sub path:
packages/tspd/README.md:33
- This line uses "sub path" (two words) while the rest of the doc/code uses "subpath"; updating keeps terminology consistent.
For this to work the sub path must expose its own JS module (an `import` or `default` condition) exporting the `$decorators` of the decorators it declares, and its TypeSpec entrypoint must import that JS module.
A library that exposes decorators from a subpath export could not be built with
tspd. Only the firsttypespecexport was ever compiled, so decorators declared in a subpath entrypoint got no signatures at all — and there was no way to make them work: implementing them in the root$decoratorstrips the library linter and leaks them to consumers who never imported the subpath, while implementing them in the subpath's$decorators(the structurally correct place) produced a.ts-test.tsthat failed to typecheck because it imported from the package root.Now each
exportsentry with atypespeccondition is compiled on its own, and a decorator or function is attributed to the first export that reaches the file declaring it — so entities in the root entrypoint stay on the root even thoughlib/streams/main.tspimports../main.tspback. Signatures land in a directory matching the subpath and import from that same subpath:A subpath with a
typespeccondition but no JS condition now gets a warning instead of an unbuildable typecheck file.This also reverts the stop-gap from #8915 that disabled
additionalImports. Fixes #8913.Along the way this fixes a latent path-identity bug: the same file resolves under different paths in different programs when reached through a symlink (pnpm workspace links,
/tmpon macOS), which duplicated the root's decorators into every subpath. Paths are now compared viahost.realpath.Regenerating the signatures of all 14 libraries plus the compiler's own script produces zero diff, so nothing shifts for existing single-export packages.