Skip to content

Support subpath exports in tspd gen-extern-signature - #11561

Merged
Timothee Guerin (timotheeguerin) merged 4 commits into
microsoft:mainfrom
timotheeguerin:tspd-subpath-signatures
Aug 28, 2026
Merged

Timothee Guerin (timotheeguerin) merged 4 commits into
microsoft:mainfrom
timotheeguerin:tspd-subpath-signatures

Conversation

@timotheeguerin

Copy link
Copy Markdown
Member

A library that exposes decorators from a subpath export could not be built with tspd. Only the first typespec export 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 $decorators trips 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.ts that failed to typecheck because it imported from the package root.

Now each exports entry with a typespec condition 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 though lib/streams/main.tsp imports ../main.tsp back. Signatures land in a directory matching the subpath and import from that same subpath:

generated-defs/MyLib.ts                  <- from `.`,         imports from "my-lib"
generated-defs/streams/MyLib.Streams.ts  <- from `./streams`, imports from "my-lib/streams"
// generated-defs/streams/MyLib.Streams.ts-test.ts
import { $decorators } from "my-lib/streams";
import type { MyLibStreamsDecorators } from "./MyLib.Streams.js";

const _decs: MyLibStreamsDecorators = $decorators["MyLib.Streams"];

A subpath with a typespec condition 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, /tmp on macOS), which duplicated the root's decorators into every subpath. Paths are now compared via host.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.

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
@microsoft-github-policy-service microsoft-github-policy-service Bot added the tspd Issues for the tspd tool label Aug 5, 2026
@pkg-pr-new

pkg-pr-new Bot commented Aug 5, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@typespec/tspd@11561

commit: 9f9467a

@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

All changed packages have been documented.

  • ✅ @typespec/tspd
Show changes

@typespec/tspd - fix ✏️

gen-extern-signature now generates signatures for sub path exports. Each export with a typespec condition is compiled on its own, entities are attributed to the export that first reaches their source file, and the generated files are written under a directory matching the sub path with $decorators imported from that same sub path.,> ,> ts,> // generated-defs/streams/MyLib.Streams.ts-test.ts,> import { $decorators } from "my-lib/streams";,> import type { MyLibStreamsDecorators } from "./MyLib.Streams.js";,> ,> const _decs: MyLibStreamsDecorators = $decorators["MyLib.Streams"];,>

@azure-sdk-automation

azure-sdk-automation Bot commented Aug 5, 2026 •

Copy link
Copy Markdown

You can try these changes here

🛝 Playground 🌐 Website 🛝 VSCode Extension

@microsoft-github-policy-service microsoft-github-policy-service Bot added the stale Mark a PR that hasn't been recently updated and will be closed. label Aug 20, 2026
@timotheeguerin Timothee Guerin (timotheeguerin) removed the stale Mark a PR that hasn't been recently updated and will be closed. label Aug 28, 2026

Copilot AI left a comment

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.

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 exports subpath (and handle symlinked paths via host.realpath).
  • Teach signature generation to emit subpath-aware imports (and optionally skip .ts-test.ts files 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.

Comment thread packages/tspd/src/ref-doc/lib.ts Resolved
Comment thread packages/tspd/README.md Resolved
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 28, 2026 12:57

Copilot AI left a comment

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.

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.`,
      },

Comment thread packages/tspd/README.md Resolved
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 28, 2026 13:06

Copilot AI left a comment

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.

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.

Merged via the queue into microsoft:main with commit 22ea5af Aug 28, 2026
33 checks passed
@timotheeguerin
Timothee Guerin (timotheeguerin) deleted the tspd-subpath-signatures branch August 28, 2026 17:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

tspd Issues for the tspd tool

Projects

None yet

Development

Successfully merging this pull request may close these issues.

tspd: gen-extern-signatures is broken for subpath exports

3 participants