Skip to content

Commit df64bc5

Browse files
slang25claudeIEvangelistCopilot
authored
Add TypeScript twoslash support to docs code blocks (#729)
* Add TypeScript twoslash support to docs code blocks Wires up expressive-code-twoslash with a generated Aspire SDK .d.ts so TS samples get hover types. Adds the `twoslash` meta to TS code blocks across docs and the homepage AppHostBuilder component. Also fixes nav bar / mobile TOC stacking at ≥72rem where expressive-code's `:root .main-pane { z-index: 1 }` was trapping the TOC below content. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Remove @Noerrors from docs twoslash blocks Inherit class baseType from src/data/pkgs/*.json so subclasses like ViteAppResource/NodeAppResource/PythonAppResource pick up methods defined on their parent class (publishAsDockerFile, publishAsHostedAgent). Fix misaligned samples (withVolume args, withHostPort, publishAsExisting, userNameParameter/passwordParameter, executionContext.isRunMode, redis host/port accessors). Drop twoslash marker from the hypothetical user-integration example where types don't resolve by design. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Let twoslash tooltips escape the AppHostBuilder container Remove overflow:hidden from the outer .container so the popup (positioned by floating-ui into the expressive-code wrapper) can extend past the bottom edge. Top-corner rounding moves to .header to preserve the visual against the container's rounded border. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Address PR review comments - Add fallback value to --sl-z-index-toc var in site.css so the right-sidebar z-index still resolves when the CSS variable is absent - Guard the twoslash popup-fix IIFE with window.__aspireTwoslashFixInitialized so document-level listeners only register once across Astro view transitions - Downgrade typescript to ^5.9.3 to stay within the peer range declared by expressive-code-twoslash (^5.5.0) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Address additional PR review comments - postgres-host.mdx: pass port directly to withHostPort(5050); the TS SDK signature takes a number, not an object - ec.config.mjs: use canonical lib name 'es2022' instead of the filename 'lib.es2022.d.ts' so TypeScript loads the standard library correctly during twoslash compilation - ec.config.mjs: point the missing-types warning at the 'pnpm twoslash-types' script rather than the raw tsx invocation Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Update src/frontend/src/content/docs/extensibility/multi-language-integration-authoring.mdx Co-authored-by: David Pine <david.pine@microsoft.com> * Update src/frontend/src/content/docs/app-host/migrate-from-docker-compose.mdx Co-authored-by: David Pine <david.pine@microsoft.com> * Update src/frontend/src/content/docs/app-host/migrate-from-docker-compose.mdx Co-authored-by: David Pine <david.pine@microsoft.com> * Address review round 2 on twoslash docs PR - generate-twoslash-types.ts: type the JSON.parse results so the @typescript-eslint/no-unsafe-* rules pass (previously failed CI) - Add tests/unit/twoslash-types-generator.vitest.test.ts covering the key shape of aspire.d.ts (createBuilder, IDistributedApplicationBuilder, class inheritance from pkgs/, camelCasing, options-object overloads) and wire test:unit:twoslash-types into the test:unit pipeline - ec.config.mjs: drop the explicit `lib` array — twoslash's VFS only loads the lib files literally named in `lib`, so triple-slash references inside lib.es2022.d.ts (es5 → Date, dom → URL, etc.) were silently dropped. Letting `target: ESNext` pick lib.esnext.full.d.ts restores the full standard library. Also remove the now-redundant console.d.ts shim - multi-language-integration-authoring.mdx: drop `twoslash` flag from the apphost.ts sample — the article shows authoring custom exported APIs that aren't part of the generated .d.ts, so the sample correctly uses hypothetical types - executable-resources.mdx: publishAsDockerFile() takes no args - migrate-from-docker-compose.mdx: switch withVolume() calls to the options-object overload as suggested Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Regenerate TS module JSON with AtsTransformer fixes Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Make twoslash-types generator test cross-platform execFileSync('pnpm', ...) fails on Windows with ENOENT because spawn doesn't resolve .cmd shims. Invoke tsx directly via node instead, which also sidesteps the DEP0190 warning from shell:true. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Stabilize frontend e2e CI by using astro preview + 60s timeout Tests were flaking in CI (4-5 flaky + 1 failed each run) because playwright's webServer was running astro dev, and this PR's twoslash processing makes first-request page loads expensive. Under 2 parallel workers, concurrent requests for twoslash pages pegged the dev server, causing unrelated page.goto calls (e.g. on / and /get-started/install-cli/) to exceed the 30s test timeout. Switch CI to astro preview (dist/ already exists from the Build frontend step) so pages are served as static files. Kept astro dev for local dev so interactive work doesn't require a rebuild. Also bumped per-test timeout to 60s as a safety net against any residual slow pages. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Set E2E_TESTS=1 at CI build time so cookie consent loads under preview After switching CI's playwright webServer to astro preview, the 6 cookie-consent e2e tests began failing because hideFromBots in config/cookie.config.ts is evaluated at build time (read by astro.config.mjs at static build). Previously astro dev was started by playwright with E2E_TESTS=1 set, so the flag flipped correctly; under preview, the env has to be set during the preceding build step. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: David Pine <david.pine@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 4d96323 commit df64bc5

78 files changed

Lines changed: 1732 additions & 570 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/frontend-build.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,12 @@ jobs:
7575
env:
7676
MODE: production
7777
ASTRO_TELEMETRY_DISABLED: 1
78+
# The built artifact is consumed by `astro preview` during the
79+
# e2e step below. `config/cookie.config.ts` reads `E2E_TESTS`
80+
# at build time to disable the cookie-consent bot check; without
81+
# this, the preview serves a production-configured modal that
82+
# playwright is mistaken for a bot and never opens.
83+
E2E_TESTS: 1
7884
run: pnpm build:production
7985

8086
# Upload before the dist sanity-check so the artifact is always

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ test-results/
2020
.astro/
2121
.cache/
2222
.netlify/
23+
.twoslash-types/
2324
tmp/
2425

2526
# Logs

src/frontend/ec.config.mjs

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,66 @@
1+
import { readFileSync, existsSync } from 'node:fs';
2+
import { fileURLToPath } from 'node:url';
3+
import { dirname, resolve } from 'node:path';
4+
15
import { pluginCollapsibleSections } from '@expressive-code/plugin-collapsible-sections';
26
import { pluginLineNumbers } from '@expressive-code/plugin-line-numbers';
7+
import ecTwoSlash from 'expressive-code-twoslash';
38
import { pluginDisableCopy } from './src/expressive-code-plugins/disable-copy.mjs';
49

10+
const __dirname = dirname(fileURLToPath(import.meta.url));
11+
const ASPIRE_TYPES_PATH = resolve(__dirname, '.twoslash-types/aspire.d.ts');
12+
const aspireTypes = existsSync(ASPIRE_TYPES_PATH)
13+
? readFileSync(ASPIRE_TYPES_PATH, 'utf8')
14+
: '';
15+
16+
if (!aspireTypes) {
17+
// Non-fatal — twoslash blocks that import the SDK will just show `any`.
18+
// Run `pnpm twoslash-types` to refresh.
19+
console.warn('[ec] .twoslash-types/aspire.d.ts missing — run `pnpm twoslash-types`');
20+
}
21+
522
/** @type {import('@astrojs/starlight/expressive-code').StarlightExpressiveCodeOptions} */
623
export default {
7-
plugins: [pluginCollapsibleSections(), pluginLineNumbers(), pluginDisableCopy()],
24+
plugins: [
25+
pluginCollapsibleSections(),
26+
pluginLineNumbers(),
27+
pluginDisableCopy(),
28+
ecTwoSlash({
29+
// Only run on TS blocks that opt in via the `twoslash` meta flag.
30+
instanceConfigs: {
31+
// Docs samples use both `ts` and `typescript` fence languages; accept both.
32+
twoslash: { explicitTrigger: true, languages: ['ts', 'tsx', 'typescript'] },
33+
},
34+
includeJsDoc: true,
35+
twoslashOptions: {
36+
compilerOptions: {
37+
// ts.ModuleResolutionKind.Bundler so `./.modules/aspire.js` falls
38+
// through to the virtual `.modules/aspire.ts` file declared below.
39+
moduleResolution: 100,
40+
// ts.ModuleKind.ESNext (paired with bundler resolution).
41+
module: 99,
42+
// ts.ScriptTarget.ESNext.
43+
target: 99,
44+
strict: true,
45+
noEmit: true,
46+
// Omit `lib` so twoslash falls back to `lib.esnext.full.d.ts`
47+
// (target: ESNext) — that bundle pulls in `Date`, `URL`, DOM,
48+
// and other common globals via triple-slash references. Pinning
49+
// an explicit `lib` array breaks those references in the VFS.
50+
},
51+
handbookOptions: {
52+
// Keep type squigglies rendered inline but don't fail the build when
53+
// a sample has unannotated compiler errors — the generated SDK is a
54+
// best-effort shape and docs samples shouldn't need `// @errors` tags.
55+
noErrorValidation: true,
56+
},
57+
// Virtual files merged into the Twoslash VFS. The Aspire SDK types
58+
// are declared at `.modules/aspire.ts` so docs samples that import
59+
// `'./.modules/aspire.js'` resolve against the real API surface.
60+
extraFiles: aspireTypes ? { '.modules/aspire.ts': aspireTypes } : {},
61+
},
62+
}),
63+
],
864
defaultProps: {
965
showLineNumbers: false,
1066
},

src/frontend/package.json

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,21 +16,23 @@
1616
"scripts": {
1717
"git-env": "node ./scripts/write-git-env.mjs",
1818
"check-data": "node ./scripts/check-data-files.mjs",
19-
"dev": "pnpm git-env && pnpm check-data && astro dev",
20-
"dev:host": "pnpm git-env && pnpm check-data && astro dev --host",
21-
"start": "pnpm git-env && pnpm check-data && astro dev",
22-
"start:host": "pnpm git-env && pnpm check-data && astro dev --host",
23-
"build": "pnpm git-env && astro build",
24-
"build:skip-search": "pnpm git-env && astro build --mode skip-search",
25-
"build:production": "pnpm git-env && astro build --mode production",
19+
"twoslash-types": "tsx ./scripts/generate-twoslash-types.ts",
20+
"dev": "pnpm git-env && pnpm check-data && pnpm twoslash-types && astro dev",
21+
"dev:host": "pnpm git-env && pnpm check-data && pnpm twoslash-types && astro dev --host",
22+
"start": "pnpm git-env && pnpm check-data && pnpm twoslash-types && astro dev",
23+
"start:host": "pnpm git-env && pnpm check-data && pnpm twoslash-types && astro dev --host",
24+
"build": "pnpm git-env && pnpm twoslash-types && astro build",
25+
"build:skip-search": "pnpm git-env && pnpm twoslash-types && astro build --mode skip-search",
26+
"build:production": "pnpm git-env && pnpm twoslash-types && astro build --mode production",
2627
"preview": "astro preview",
2728
"preview:host": "astro preview --host",
2829
"astro": "pnpm git-env && astro",
2930
"test": "pnpm test:unit && pnpm test:e2e",
3031
"test:all": "pnpm lint && pnpm test",
31-
"test:unit": "pnpm test:unit:contracts && pnpm test:unit:components && pnpm test:unit:docs && pnpm test:unit:api-markdown && pnpm test:unit:ts-api",
32+
"test:unit": "pnpm test:unit:contracts && pnpm test:unit:components && pnpm test:unit:docs && pnpm test:unit:api-markdown && pnpm test:unit:ts-api && pnpm test:unit:twoslash-types",
3233
"test:unit:api-markdown": "vitest run --config vitest.config.ts tests/unit/api-markdown.vitest.test.ts",
3334
"test:unit:ts-api": "vitest run --config vitest.config.ts tests/unit/ts-api-routes.vitest.test.ts tests/unit/ts-api-search.vitest.test.ts",
35+
"test:unit:twoslash-types": "vitest run --config vitest.config.ts tests/unit/twoslash-types-generator.vitest.test.ts",
3436
"test:unit:contracts": "vitest run --config vitest.config.ts tests/unit/analytics-script-contracts.vitest.test.ts",
3537
"test:unit:components": "vitest run --config vitest.config.ts tests/unit/custom-components.vitest.test.ts tests/unit/site-tour.vitest.test.ts",
3638
"test:unit:docs": "vitest run --config vitest.config.ts tests/unit/filetree-format.vitest.test.ts",
@@ -88,13 +90,15 @@
8890
"astro-vtbot": "^2.1.12",
8991
"eslint": "^10.2.0",
9092
"eslint-config-prettier": "^10.1.8",
93+
"expressive-code-twoslash": "^0.6.1",
9194
"globals": "^17.5.0",
9295
"http-proxy-agent": "^9.0.0",
9396
"https-proxy-agent": "^9.0.0",
9497
"node-fetch": "^3.3.2",
9598
"prettier": "^3.8.2",
9699
"prettier-plugin-astro": "^0.14.1",
97100
"tsx": "^4.21.0",
101+
"typescript": "^5.9.3",
98102
"typescript-eslint": "^8.58.2",
99103
"vitest": "^4.1.4"
100104
},

src/frontend/playwright.config.mjs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ export default defineConfig({
1010
forbidOnly: isCI,
1111
retries: isCI ? 2 : 0,
1212
workers: isCI ? 2 : undefined,
13+
// Default (30s) is tight once twoslash code blocks add server-side work
14+
// during dev mode. 60s gives `page.goto` enough headroom without masking
15+
// real regressions.
16+
timeout: 60_000,
1317
reporter: isCI
1418
? [
1519
['github'],
@@ -50,7 +54,15 @@ export default defineConfig({
5054
},
5155
],
5256
webServer: {
53-
command: `pnpm git-env && pnpm check-data && astro dev --host 127.0.0.1 --port ${e2ePort}`,
57+
// In CI the preceding `pnpm build:production` step has already produced
58+
// `dist/`, so serve the built site via `astro preview`. Under `astro dev`
59+
// the first request to each page triggers twoslash processing, which
60+
// stacks up under parallel test workers and blows past the default
61+
// 30s test timeout. Locally we keep `astro dev` so interactive work
62+
// doesn't require a full rebuild.
63+
command: isCI
64+
? `astro preview --host 127.0.0.1 --port ${e2ePort}`
65+
: `pnpm git-env && pnpm check-data && astro dev --host 127.0.0.1 --port ${e2ePort}`,
5466
env: {
5567
...process.env,
5668
ASTRO_TELEMETRY_DISABLED: '1',

0 commit comments

Comments
 (0)