Audit + clean every twoslash TS code block across the docs#940
Merged
IEvangelist merged 1 commit intoMay 13, 2026
Merged
Conversation
d6e2ad3 to
cc13e6b
Compare
Contributor
There was a problem hiding this comment.
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.mjsand reuses it fromec.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
twoslasherreported 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.
cc13e6b to
e20e791
Compare
eerhardt
approved these changes
May 13, 2026
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>
e20e791 to
3e482a4
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Audited every
twoslash-annotated TypeScript code block across the docs (493.mdxfiles, 187 blocks) and cleaned up every snippet that didn't compile against the generatedaspire.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_ENABLEDtoggle, and theaspire.d.tsVFS shim. Bothec.config.mjsand 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.mdxundersrc/content/docs/**, extracts every twoslash block using the same triggerexpressive-code-twoslashuses (explicitTrigger: true), and compiles each one with@ec-ts/twoslash(the same packageexpressive-code-twoslashuses internally). Short-circuits when the sharedTWOSLASH_ENABLEDflag isfalse. EachKNOWN_TYPE_BUGSentry carries anexpectedOccurrencescount 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) everyKNOWN_TYPE_BUGSentry still matches a real diagnostic so stale entries surface when an upstream fix lands, (4)findKnownBugmatches by all four fields.ec.config.mjsrefactored to consume the shared config.package.jsonaddstest:unit:twoslash-blocksand rolls it into the umbrellatest:unitscript.@ec-ts/twoslashadded as a direct devDependency.tests/unit/llms-txt-twoslash.vitest.test.tsupdated theTWOSLASH_ENABLEDsentinel to point at the new shared config and to also assert that bothec.config.mjsandtests/unit/twoslash-blocks-audit.tsconsume the shared toggle (not a local re-definition).Doc snippet fixes
English
.mdxfiles plus theirja/**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).addProject(name, path, "launchProfile")(~13 sites). The TS surface declares the third arg asProjectResourceOptionsonly.executable.publishAsDockerFile()->publishAsDockerFile(async () => {})— no no-arg overload on the TS surface.addContainerRegistryFromString(...)->addContainerRegistry(...).addAzureBicepResource(...)->addBicepTemplate(...).addDeploymentFromModel(...)/addModelDeploymentFromModel(...)->addDeployment(...)/addModelDeployment(...).HelmChartOptions.withChartName/withChartDescriptioncalls (onlywithChartVersionandwithNamespaceare 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 incustomize-resources.mdx— the only emitted overload acceptsEndpointReferenceand 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 TypeScriptcode 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-typesfromsrc/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 regressionin
aspire.d.tswill not silently revert the cleanup.Findings
1. Merged
IResourceinterface rejects most concreteIResourceinstancesAffects 10 of the 15 diagnostics. The root cause is that
aspire.d.tsdeclaresIResourcetwice:aspire.d.ts:1780(justgetResourceName,excludeFromManifest, etc.).aspire.d.ts:13610that adds the resource-specificwithRoleAssignments(target, roles)overloads andgetAzureContainerRegistry()/withAzureContainerRegistry().Because these are interfaces, TypeScript merges them into a single
IResourceshape that requires all
withRoleAssignmentsoverloads. Concrete resourceclasses (e.g.
AzureOpenAIDeploymentResource) only implement their ownwithRoleAssignmentsoverload, so they are not assignable to the mergedIResourceand therefore cannot be passed towithReference(IResource),waitFor(IResource), orwithContainerRegistry(IResource).app-host/container-registry.mdxblock #7 (L341)app.withReference(acr)whereacr: AzureContainerRegistryResourceintegrations/cloud/azure/azure-ai-foundry/azure-ai-foundry-host.mdxblock #5 (L338)foundry.addProject(...).withContainerRegistry(acr)integrations/cloud/azure/azure-openai/azure-openai-host.mdxblocks #1 (L108), #2 (L174 + L175), #3 (L221), #5 (L298)app.withReference(deployment)wheredeployment: AzureOpenAIDeploymentResourcewhats-new/aspire-13-2.mdxblock #6 (L1034 + L1036) andja/whats-new/aspire-13-2.mdxblock #6 (L1034 + L1036)api.waitFor(adls)/api.waitFor(filesystem)where targets areAzureDataLakeStorageResource/AzureDataLakeStorageFileSystemResourceSuggested upstream fix: Either (a) collapse the two
IResourcedeclarations into one (drop the augmenting interface) or (b) make
withRoleAssignmentsoverloads return their concretethistype instead ofIResourceand stop adding them to the baseIResourceinterface. Thegenerator (
pnpm twoslash-types→scripts/generate-twoslash-types.ts) isthe right place to enforce this.
2.
AzureResourceInfrastructure.getProvisionableResources()missingAffects 2 diagnostics. The C# API exposes
GetProvisionableResources()onAzureResourceInfrastructure; the TS shape doesn't.integrations/cloud/azure/azure-openai/azure-openai-host.mdxblock #6 (L423)infra.getProvisionableResources()integrations/cloud/azure/customize-resources.mdxblock #4 (L198)infra.getProvisionableResources()Suggested upstream fix: Add the method to the JSON descriptor for
AzureResourceInfrastructureso the generator emits it.3.
YarpResource.addRoute(path, EndpointReference)overload missingAffects 1 diagnostic. The TS surface declares
addRoute(path: string, target: string | ExternalServiceResource).EndpointReferenceis the natural target when wiring a YARP route to anexisting service endpoint.
deployment/javascript-apps.mdxblock #2 (L186)yarp.addRoute("/api", apiEndpoint)whereapiEndpoint: EndpointReferenceSuggested upstream fix: Widen
addRoute's second parameter tostring | ExternalServiceResource | EndpointReference(or similar).4.
GitHubModelNameliteral union missing current model identifiersAffects 1 diagnostic.
addGitHubModel(name, model)typesmodelas a closedliteral union (
GitHubModelName). The list is stale relative to GitHubModels' actual catalog (the snippet uses
openai/gpt-4o-mini, which is avalid live model).
integrations/cloud/azure/ai-compatibility-matrix.mdxblock #4 (L193)addGitHubModel("chat", "openai/gpt-4o-mini")Suggested upstream fix: Either widen
GitHubModelNametoGitHubModelName | (string & {})so authors can supply unlisted names, orregenerate 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.
addContainer(name, image, tag?)addContainer(name, { image, tag })(~13 sites)(name, image, tag?)overload that C# exposes.addProject(name, path, launchProfile)addProject(name, path)(drop launchProfile) (~13 sites)(name, path, launchProfile?)overload.executable.publishAsDockerFile()executable.publishAsDockerFile(async () => {})publishAsDockerFile()overload.addContainerRegistryFromString(name, endpoint, opts)addContainerRegistry(name, endpoint, opts)addContainerRegistryFromStringfrom the descriptor (it's a stale alias).addAzureBicepResource(name, path)addBicepTemplate(name, path)addBicepTemplate.foundry.addDeploymentFromModel(name, opts)/project.addModelDeploymentFromModel(name, opts)addDeployment(name, opts)/addModelDeployment(name, opts)helm.withChartName(name)/helm.withChartDescription(desc)withChartVersionandwithNamespaceexist onHelmChartOptions)HelmChartOptions.configureInfrastructure(infra => …)(sync arrow)configureInfrastructure(async infra => …)configureInfrastructure.asExisting(name, { resourceGroup })asExisting(name, resourceGroup)AzureBicepResource.withParameter(name, "stringValue")withParameterto acceptstring | ParameterResource | EndpointReference. As emitted, the second arg isEndpointReferenceonly — there is no way to compile anywithParameter("name", "value")call.How to reproduce
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_BUGSallowlist, or whenever an allowlisted entry no longermatches the exact number of diagnostics it was authored against
(
expectedOccurrences).