diff --git a/website/README.md b/website/README.md index d5f4e582..956e66a2 100644 --- a/website/README.md +++ b/website/README.md @@ -46,15 +46,20 @@ Sidebar order is defined in `astro.config.mjs`, not by directory order. | :--------------------- | :--------------------------------------------------------- | | `pnpm install` | Install dependencies | | `pnpm dev` | Start local dev server at `localhost:4321` | -| `pnpm build` | Build the production site to `./dist/` | +| `pnpm build` | Build the production site to `./dist/` and test its output | | `pnpm preview` | Preview the production build locally | | `pnpm test` | Test site plugins with Node's built-in test runner | +| `pnpm test:build` | Test rendered Markdown/MDX tables in `dist/` after a build | | `pnpm refresh:openapi` | Generate the OpenAPI snapshot from `scope-core` | ## Authoring docs - Use `.md` for plain Markdown, `.mdx` whenever the page contains JSX (e.g. Starlight ``). +- Use standard pipe-delimited Markdown tables in both `.md` and `.mdx`. + Keep `markdown.gfm: true` explicit in [astro.config.mjs](astro.config.mjs): + the installed MDX integration does not inherit Astro's Markdown processor + defaults, so otherwise MDX tables render as plain text. - Write internal Markdown links and literal MDX `href`/`src` attributes relative to the site root, such as `/getting-started/access/`. [src/plugins/remark-base-path.mjs](src/plugins/remark-base-path.mjs) @@ -109,6 +114,8 @@ SITE=https://microsoft.github.io BASE_PATH=/scope pnpm preview Open `/scope/` on the preview server. Keep `BASE_PATH` the same for the build and preview so assets, navigation, and search use the same URLs. +`pnpm build` runs `pnpm test:build` after generating the site, so local and +CI builds catch table-rendering regressions in both Markdown and MDX pages. ## Learn more diff --git a/website/astro.config.mjs b/website/astro.config.mjs index 3f662e00..13542618 100644 --- a/website/astro.config.mjs +++ b/website/astro.config.mjs @@ -55,6 +55,8 @@ export default defineConfig({ base, server: { port: docPort }, markdown: { + // The installed MDX integration does not inherit the processor's GFM default. + gfm: true, remarkPlugins: [[remarkBasePath, { base }], remarkHttpSnippets], }, integrations: [ diff --git a/website/package.json b/website/package.json index 513b77f9..0cc7a877 100644 --- a/website/package.json +++ b/website/package.json @@ -6,9 +6,10 @@ "scripts": { "dev": "worktree-env && astro dev", "start": "worktree-env && astro dev", - "build": "astro build", + "build": "astro build && pnpm test:build", "preview": "worktree-env && astro preview", "test": "node --test src/plugins/*.test.mjs", + "test:build": "node --test tests/*.test.mjs", "astro": "astro", "refresh:openapi": "cd .. && pnpm --filter api generate:openapi" }, diff --git a/website/tests/tables.test.mjs b/website/tests/tables.test.mjs new file mode 100644 index 00000000..1de1c168 --- /dev/null +++ b/website/tests/tables.test.mjs @@ -0,0 +1,59 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import assert from 'node:assert/strict'; +import { readFile } from 'node:fs/promises'; +import { test } from 'node:test'; + +async function readTables(page) { + const html = await readFile(new URL(`../dist/${page}/index.html`, import.meta.url), 'utf8'); + return html.match(/]*>[\s\S]*?<\/table>/g) ?? []; +} + +function assertTable(table, headers, fields) { + assert.deepEqual( + Array.from(table.matchAll(/]*>([\s\S]*?)<\/th>/g), ([, text]) => text), + headers, + ); + assert.deepEqual( + Array.from( + table.matchAll(/]*>\s*]*>]*>([^<]+)<\/code>/g), + ([, field]) => field, + ), + fields, + ); + assert.equal(Array.from(table.matchAll(/ { + const tables = await readTables('guides/defining-profiles'); + assert.equal(tables.length, 1, 'The profile anatomy must render as an HTML table, not pipe-delimited text'); + assertTable(tables[0], ['Field', 'Layer', 'Description'], [ + 'name', + 'description', + 'workerType', + 'model', + 'agentVersion', + 'mcpServers', + 'skillRevisions', + 'extensions', + ]); + assert.match(tables[0], /Choosing a coding agent<\/a>/); + assert.match(tables[0], /VS Code Copilot only\.<\/strong>/); +}); + +test('preserves tables in the plain Markdown profile schema reference', async () => { + const tables = await readTables('reference/profile-schema'); + assert.equal(tables.length, 2); + assertTable(tables[0], ['Field', 'Type', 'Required', 'Description'], ['id', 'name', 'description']); + assertTable(tables[1], ['Field', 'Type', 'Required', 'Description'], [ + 'id', + 'profileId', + 'workerType', + 'model', + 'agentVersion', + 'mcpServers', + 'skillRevisions', + 'createdAt', + ]); +});