Skip to content

Audit + clean every twoslash TS code block across the docs#940

Merged
IEvangelist merged 1 commit into
microsoft:mainfrom
IEvangelist:dapine/twoslash-ts-errors-audit
May 13, 2026
Merged

Audit + clean every twoslash TS code block across the docs#940
IEvangelist merged 1 commit into
microsoft:mainfrom
IEvangelist:dapine/twoslash-ts-errors-audit

Conversation

@IEvangelist
Copy link
Copy Markdown
Member

@IEvangelist IEvangelist commented May 13, 2026

Summary

Audited every twoslash-annotated TypeScript code block across the docs (493 .mdx files, 187 blocks) and cleaned up every snippet that didn't compile against the generated aspire.d.ts. The work is locked in by a new permanent vitest check (pnpm test:unit:twoslash-blocks).

Baseline -> after this PR: 60 blocks with errors / 73 diagnostics -> 12 blocks / 15 diagnostics, where every remaining diagnostic is a documented type-shape gap in the generated aspire.d.ts (see findings below).

Pipeline changes

  • src/frontend/config/twoslash.config.mjs (new) — single source of truth for the twoslash compiler options, language list, TWOSLASH_ENABLED toggle, and the aspire.d.ts VFS shim. Both ec.config.mjs and the audit harness import from here, so render and audit can never disagree on options.
  • src/frontend/tests/unit/twoslash-blocks-audit.ts (new) — sibling helper to the vitest check. Walks every .mdx under src/content/docs/**, extracts every twoslash block using the same trigger expressive-code-twoslash uses (explicitTrigger: true), and compiles each one with @ec-ts/twoslash (the same package expressive-code-twoslash uses internally). Short-circuits when the shared TWOSLASH_ENABLED flag is false. Each KNOWN_TYPE_BUGS entry carries an expectedOccurrences count so a newly introduced error of the same shape in an allowlisted block still surfaces.
  • src/frontend/tests/unit/twoslash-blocks.vitest.test.ts (new) — runs the audit programmatically and asserts (1) every block compiles or matches the allowlist, (2) the harness walked at least 100 blocks (regression guard against the fence regex silently filtering everything), (3) every KNOWN_TYPE_BUGS entry still matches a real diagnostic so stale entries surface when an upstream fix lands, (4) findKnownBug matches by all four fields.
  • ec.config.mjs refactored to consume the shared config.
  • package.json adds test:unit:twoslash-blocks and rolls it into the umbrella test:unit script. @ec-ts/twoslash added as a direct devDependency.
  • tests/unit/llms-txt-twoslash.vitest.test.ts updated the TWOSLASH_ENABLED sentinel to point at the new shared config and to also assert that both ec.config.mjs and tests/unit/twoslash-blocks-audit.ts consume the shared toggle (not a local re-definition).

Doc snippet fixes

English .mdx files plus their ja/** mirrors where the sample is duplicated verbatim. Fixes are grouped by pattern:

  • addContainer(name, "image:tag") -> addContainer(name, { image, tag }) (~13 sites). The TS surface only exposes (name, AddContainerOptions).
  • Dropped the third positional arg on addProject(name, path, "launchProfile") (~13 sites). The TS surface declares the third arg as ProjectResourceOptions only.
  • executable.publishAsDockerFile() -> publishAsDockerFile(async () => {}) — no no-arg overload on the TS surface.
  • addContainerRegistryFromString(...) -> addContainerRegistry(...).
  • addAzureBicepResource(...) -> addBicepTemplate(...).
  • addDeploymentFromModel(...) / addModelDeploymentFromModel(...) -> addDeployment(...) / addModelDeployment(...).
  • Dropped HelmChartOptions.withChartName/withChartDescription calls (only withChartVersion and withNamespace are exposed).
  • configureInfrastructure(infra => ...) -> async infra => ... (callback signature is (obj) => Promise<void>).
  • asExisting(name, { resourceGroup }) -> asExisting(name, rg) — positional arg, not an options bag.
  • AzureBicepResource.withParameter("name", "value") line removed in customize-resources.mdx — the only emitted overload accepts EndpointReference and is unusable as authored. See finding feat: add pivot component and selector with directive support #5 below.

Twoslash audit — type-bug findings

After cleaning every documentable doc-bug in the twoslash-annotated TypeScript
code blocks across src/frontend/src/content/docs/**, the audit harness
(src/frontend/tests/unit/twoslash-blocks-audit.ts) reports 12 blocks /
15 diagnostics
that the docs cannot work around without rewriting the
example to compile against a different (often unrelated) call. Every one is a
type-shape gap in the generated src/frontend/src/data/twoslash/aspire.d.ts
(produced by pnpm twoslash-types from src/frontend/src/data/ts-modules/*.json,
which in turn tracks microsoft/aspire's upstream JSON descriptors).

The doc snippets are idiomatic and match the corresponding C# tab. They are
left as-is so the rendered page tells the right story; the twoslash error
boxes will go away once the upstream descriptors are loosened. Until then
the audit's allowlist (tests/unit/twoslash-blocks-audit.ts,
KNOWN_TYPE_BUGS) explicitly tracks each remaining diagnostic so a regression
in aspire.d.ts will not silently revert the cleanup.

Findings

1. Merged IResource interface rejects most concrete IResource instances

Affects 10 of the 15 diagnostics. The root cause is that aspire.d.ts declares
IResource twice:

  • The base shape at aspire.d.ts:1780 (just getResourceName, excludeFromManifest, etc.).
  • An augmenting interface at aspire.d.ts:13610 that adds the resource-specific
    withRoleAssignments(target, roles) overloads and getAzureContainerRegistry() /
    withAzureContainerRegistry().

Because these are interfaces, TypeScript merges them into a single IResource
shape that requires all withRoleAssignments overloads. Concrete resource
classes (e.g. AzureOpenAIDeploymentResource) only implement their own
withRoleAssignments overload, so they are not assignable to the merged
IResource and therefore cannot be passed to withReference(IResource),
waitFor(IResource), or withContainerRegistry(IResource).

Site Call
app-host/container-registry.mdx block #7 (L341) app.withReference(acr) where acr: AzureContainerRegistryResource
integrations/cloud/azure/azure-ai-foundry/azure-ai-foundry-host.mdx block #5 (L338) foundry.addProject(...).withContainerRegistry(acr)
integrations/cloud/azure/azure-openai/azure-openai-host.mdx blocks #1 (L108), #2 (L174 + L175), #3 (L221), #5 (L298) app.withReference(deployment) where deployment: AzureOpenAIDeploymentResource
whats-new/aspire-13-2.mdx block #6 (L1034 + L1036) and ja/whats-new/aspire-13-2.mdx block #6 (L1034 + L1036) api.waitFor(adls) / api.waitFor(filesystem) where targets are AzureDataLakeStorageResource / AzureDataLakeStorageFileSystemResource

Suggested upstream fix: Either (a) collapse the two IResource
declarations into one (drop the augmenting interface) or (b) make
withRoleAssignments overloads return their concrete this type instead of
IResource and stop adding them to the base IResource interface. The
generator (pnpm twoslash-typesscripts/generate-twoslash-types.ts) is
the right place to enforce this.

2. AzureResourceInfrastructure.getProvisionableResources() missing

Affects 2 diagnostics. The C# API exposes GetProvisionableResources() on
AzureResourceInfrastructure; the TS shape doesn't.

Site Call
integrations/cloud/azure/azure-openai/azure-openai-host.mdx block #6 (L423) infra.getProvisionableResources()
integrations/cloud/azure/customize-resources.mdx block #4 (L198) infra.getProvisionableResources()

Suggested upstream fix: Add the method to the JSON descriptor for
AzureResourceInfrastructure so the generator emits it.

3. YarpResource.addRoute(path, EndpointReference) overload missing

Affects 1 diagnostic. The TS surface declares
addRoute(path: string, target: string | ExternalServiceResource).
EndpointReference is the natural target when wiring a YARP route to an
existing service endpoint.

Site Call
deployment/javascript-apps.mdx block #2 (L186) yarp.addRoute("/api", apiEndpoint) where apiEndpoint: EndpointReference

Suggested upstream fix: Widen addRoute's second parameter to
string | ExternalServiceResource | EndpointReference (or similar).

4. GitHubModelName literal union missing current model identifiers

Affects 1 diagnostic. addGitHubModel(name, model) types model as a closed
literal union (GitHubModelName). The list is stale relative to GitHub
Models' actual catalog (the snippet uses openai/gpt-4o-mini, which is a
valid live model).

Site Call
integrations/cloud/azure/ai-compatibility-matrix.mdx block #4 (L193) addGitHubModel("chat", "openai/gpt-4o-mini")

Suggested upstream fix: Either widen GitHubModelName to
GitHubModelName | (string & {}) so authors can supply unlisted names, or
regenerate the union from the current GH-Models catalog.

Doc-side workarounds applied (not type-bugs but worth fixing upstream too)

These were doc-bugs in the audit and have been fixed in this PR by switching
to the more verbose API. They are noted here because they show up as
ergonomics gaps in the TypeScript surface — every workaround would disappear
if the upstream JSON descriptor exposed the C# API's friendlier shape.

Pattern Workaround applied Suggested upstream fix
addContainer(name, image, tag?) addContainer(name, { image, tag }) (~13 sites) Add the (name, image, tag?) overload that C# exposes.
addProject(name, path, launchProfile) addProject(name, path) (drop launchProfile) (~13 sites) Add (name, path, launchProfile?) overload.
executable.publishAsDockerFile() executable.publishAsDockerFile(async () => {}) Add no-arg publishAsDockerFile() overload.
addContainerRegistryFromString(name, endpoint, opts) addContainerRegistry(name, endpoint, opts) Drop addContainerRegistryFromString from the descriptor (it's a stale alias).
addAzureBicepResource(name, path) addBicepTemplate(name, path) Same — stale alias in upstream JSON, the TS API only exposes addBicepTemplate.
foundry.addDeploymentFromModel(name, opts) / project.addModelDeploymentFromModel(name, opts) addDeployment(name, opts) / addModelDeployment(name, opts) Same alias cleanup.
helm.withChartName(name) / helm.withChartDescription(desc) dropped (only withChartVersion and withNamespace exist on HelmChartOptions) Add chart name/description overloads to HelmChartOptions.
configureInfrastructure(infra => …) (sync arrow) configureInfrastructure(async infra => …) Allow sync callbacks on configureInfrastructure.
asExisting(name, { resourceGroup }) asExisting(name, resourceGroup) Either drop the options-bag version from the C# example or add it to the TS descriptor.
AzureBicepResource.withParameter(name, "stringValue") dropped from the example (line removed) Widen withParameter to accept string | ParameterResource | EndpointReference. As emitted, the second arg is EndpointReference only — there is no way to compile any withParameter("name", "value") call.

How to reproduce

cd src/frontend
pnpm test:unit:twoslash-blocks

The audit runs in-process under vitest (no subprocess) and fails the test
whenever any block has at least one diagnostic that is not in the
KNOWN_TYPE_BUGS allowlist, or whenever an allowlisted entry no longer
matches the exact number of diagnostics it was authored against
(expectedOccurrences).

@IEvangelist IEvangelist force-pushed the dapine/twoslash-ts-errors-audit branch from d6e2ad3 to cc13e6b Compare May 13, 2026 17:59
@IEvangelist IEvangelist marked this pull request as ready for review May 13, 2026 18:33
@IEvangelist IEvangelist requested a review from eerhardt as a code owner May 13, 2026 18:33
Copilot AI review requested due to automatic review settings May 13, 2026 18:33
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a shared twoslash configuration and a Vitest-backed audit that compiles twoslash TypeScript docs snippets against the generated Aspire declarations, while updating many snippets to match the current generated TS API surface.

Changes:

  • Centralizes twoslash compiler/options setup in config/twoslash.config.mjs and reuses it from ec.config.mjs.
  • Adds a unit-test audit harness for twoslash docs blocks with an allowlist for known generated-type gaps.
  • Updates TypeScript docs snippets to use compile-compatible APIs and option shapes.

Reviewed changes

Copilot reviewed 29 out of 30 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/frontend/config/twoslash.config.mjs Adds shared twoslash options, language list, toggle, and Aspire type VFS loading.
src/frontend/ec.config.mjs Refactors expressive-code twoslash setup to consume shared config.
src/frontend/package.json Adds twoslash audit unit-test script and direct @ec-ts/twoslash dev dependency.
src/frontend/pnpm-lock.yaml Locks direct @ec-ts/twoslash dependency.
src/frontend/tests/unit/twoslash-blocks-audit.ts Adds MDX scanning, twoslash block extraction, compilation, and known-type-bug filtering.
src/frontend/tests/unit/twoslash-blocks.vitest.test.ts Adds Vitest assertions for audit results and allowlist freshness.
src/frontend/tests/unit/llms-txt-twoslash.vitest.test.ts Updates twoslash-enabled sentinel to validate shared config usage.
src/frontend/src/content/docs/app-host/certificate-configuration.mdx Updates container snippet to use AddContainerOptions shape.
src/frontend/src/content/docs/app-host/container-files.mdx Drops unsupported addProject launch-profile string argument.
src/frontend/src/content/docs/app-host/executable-resources.mdx Drops unsupported addProject launch-profile string and adds no-op Dockerfile publish callback.
src/frontend/src/content/docs/app-host/migrate-from-docker-compose.mdx Updates project/container snippets to compile against TS declarations.
src/frontend/src/content/docs/app-host/persistent-containers.mdx Drops unsupported addProject launch-profile string argument.
src/frontend/src/content/docs/deployment/azure/app-service.mdx Drops unsupported addProject launch-profile string arguments.
src/frontend/src/content/docs/deployment/azure/container-apps.mdx Drops unsupported addProject launch-profile string arguments.
src/frontend/src/content/docs/deployment/docker-compose.mdx Updates project/container/registry snippets to current TS API names and shapes.
src/frontend/src/content/docs/deployment/environments.mdx Updates container snippet to use options object.
src/frontend/src/content/docs/deployment/javascript-apps.mdx Adds required no-op callback to publishAsDockerFile.
src/frontend/src/content/docs/deployment/kubernetes.mdx Updates project/container snippets and removes unsupported Helm chart option calls.
src/frontend/src/content/docs/deployment/overview.mdx Drops unsupported addProject launch-profile string arguments.
src/frontend/src/content/docs/get-started/add-aspire-existing-app.mdx Updates container image/tag snippets.
src/frontend/src/content/docs/get-started/aspire-mcp-server.mdx Updates container snippet to use options object.
src/frontend/src/content/docs/get-started/resource-mcp-servers.mdx Updates MCP container snippet to use options object.
src/frontend/src/content/docs/integrations/cloud/azure/azure-ai-foundry/azure-ai-foundry-host.mdx Replaces stale model deployment alias calls.
src/frontend/src/content/docs/integrations/cloud/azure/azure-key-vault/azure-key-vault-host.mdx Awaits provisioning resource access in TS snippet.
src/frontend/src/content/docs/integrations/cloud/azure/azure-openai/azure-openai-host.mdx Updates asExisting call shape.
src/frontend/src/content/docs/integrations/cloud/azure/customize-resources.mdx Updates infrastructure callback and Bicep template snippet.
src/frontend/src/content/docs/integrations/compute/docker.mdx Updates project/container/registry snippets to compile against TS declarations.
src/frontend/src/content/docs/ja/get-started/add-aspire-existing-app.mdx Mirrors container image/tag snippet updates in Japanese docs.
src/frontend/src/content/docs/ja/whats-new/aspire-13-2.mdx Mirrors selected TypeScript snippet API updates in Japanese docs.
src/frontend/src/content/docs/whats-new/aspire-13-2.mdx Updates selected TypeScript snippets to current generated TS API names/shapes.
Files not reviewed (1)
  • src/frontend/pnpm-lock.yaml: Language not supported
Comments suppressed due to low confidence (1)

src/frontend/tests/unit/twoslash-blocks-audit.ts:329

  • This drops any twoslash/TypeScript diagnostic that does not include a source line. TypeScript can emit fileless diagnostics for configuration/global type failures, so the audit could pass even though twoslasher reported an error; keep those diagnostics and render them with a fallback location instead of filtering them out.
  return result.errors
    .filter((e) => e && typeof e.line === 'number')
    .map<BlockDiagnostic>((e) => ({

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/frontend/config/twoslash.config.mjs
Comment thread src/frontend/tests/unit/twoslash-blocks-audit.ts Outdated
Comment thread src/frontend/package.json
@IEvangelist IEvangelist force-pushed the dapine/twoslash-ts-errors-audit branch from cc13e6b to e20e791 Compare May 13, 2026 18:52
The expressive-code-twoslash plugin is enabled site-wide via
`ec.config.mjs`, which compiles every ts/tsx code block tagged
`twoslash` against `src/frontend/src/data/twoslash/aspire.d.ts`. A
sweep of the rendered site (e.g. `deployment/environments`) showed
many blocks emitting inline TS error boxes like
`ts(2345) Argument of type 'string' is not assignable to parameter
of type 'AddContainerOptions'`. This change cleans up every doc-bug
and locks the cleanup in with a permanent vitest check.

Audit baseline -> post-cleanup:
- 60 blocks with errors / 73 diagnostics  ->  12 blocks / 15 diagnostics
- The 12 remaining blocks are all genuine type-shape gaps in the
  generated `aspire.d.ts`; documented in `KNOWN_TYPE_BUGS` (audit
  harness) and `artifacts/type-bug-findings.md` (PR body).

Pipeline changes
- `config/twoslash.config.mjs` (new): single source of truth for
  the twoslash compiler options, language list, `TWOSLASH_ENABLED`
  toggle, and the `aspire.d.ts` VFS shim. Both `ec.config.mjs`
  (site render) and the new audit harness consume it so the two
  pipelines can never disagree on options.
- `ec.config.mjs`: refactored to import from the shared module and
  consume `getTwoslashOptions()`.
- `scripts/audit-twoslash-blocks.ts` (new): walks every .mdx under
  `src/content/docs/**`, extracts every twoslash-annotated block
  using the same trigger expressive-code-twoslash uses
  (`explicitTrigger: true`), compiles each one with
  `@ec-ts/twoslash` (the same package `expressive-code-twoslash`
  uses internally), and prints a per-page/per-block diagnostic
  report. Exits 0 only when the only remaining diagnostics are in
  the `KNOWN_TYPE_BUGS` allowlist.
- `tests/unit/twoslash-blocks.vitest.test.ts` (new): runs the
  audit programmatically (no subprocess) and asserts:
    1. Every block compiles or matches an allowlist entry.
    2. The harness walked at least 100 blocks (regression guard
       against the fence regex silently filtering everything).
    3. Every `KNOWN_TYPE_BUGS` entry still matches a real
       diagnostic (catches when an upstream fix lands and an entry
       becomes stale).
    4. `findKnownBug` matches by all four fields.
- `package.json`: adds `test:unit:twoslash-blocks` and rolls it
  into the umbrella `test:unit` script. Adds `@ec-ts/twoslash`
  as a direct devDependency.
- `tests/unit/llms-txt-twoslash.vitest.test.ts`: updated the
  `TWOSLASH_ENABLED` sentinel to point at the new shared config
  and to also assert that `ec.config.mjs` consumes the shared
  toggle (not a local re-definition).

Doc snippets fixed (English + `ja/**` mirrors where the sample is
duplicated verbatim):
- `addContainer(name, "image:tag")` -> `addContainer(name, { image, tag })`
  in 13+ sites.
- Dropped the third positional arg on `addProject(name, path, "launchProfile")`
  in 13+ sites (the TS surface only accepts `ProjectResourceOptions`).
- `executable.publishAsDockerFile()` -> `publishAsDockerFile(async () => {})`
  (no no-arg overload on the TS surface).
- `addContainerRegistryFromString(...)` -> `addContainerRegistry(...)`.
- `addAzureBicepResource(...)` -> `addBicepTemplate(...)`.
- `addDeploymentFromModel(...)` / `addModelDeploymentFromModel(...)` ->
  `addDeployment(...)` / `addModelDeployment(...)`.
- Dropped `HelmChartOptions.withChartName/withChartDescription` calls
  (only `withChartVersion` and `withNamespace` are exposed).
- `configureInfrastructure(infra => ...)` -> `async infra => ...`.
- `asExisting(name, { resourceGroup })` -> `asExisting(name, rg)`
  (positional arg, not an options bag).
- `AzureBicepResource.withParameter("name", "value")` line removed -
  the only emitted overload accepts `EndpointReference` and is
  unusable; tracked as a type-bug in the findings doc.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@IEvangelist IEvangelist force-pushed the dapine/twoslash-ts-errors-audit branch from e20e791 to 3e482a4 Compare May 13, 2026 19:23
@IEvangelist IEvangelist enabled auto-merge (squash) May 13, 2026 19:24
@IEvangelist IEvangelist merged commit bcf6901 into microsoft:main May 13, 2026
5 checks passed
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.

3 participants