Skip to content

Commit 292d328

Browse files
IEvangelistCopilot
andauthored
Add markdown API reference routes and enable page actions (#687)
* feat: add TypeScript API markdown generation and tests - Implement TypeScript API markdown generation in `typescript-api-markdown.ts` with functions for rendering index, modules, items, and members. - Create e2e tests for API markdown routes in `api-markdown-routes.spec.ts` to ensure correct markdown responses for C# and TypeScript routes. - Add unit tests for API markdown routes in `api-markdown.vitest.test.ts` to validate markdown generation for various API components including modules, types, and functions. * feat: enhance markdown normalization and update tests to validate formatting * feature: enable faster local builds, by omitting pagefind * fix: address API markdown lint feedback Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * test: stabilize API markdown route fixtures Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent cae6f91 commit 292d328

28 files changed

+2673
-11
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"install": "pnpm --dir ./src/frontend i",
77
"dev": "pnpm --dir ./src/frontend run dev",
88
"build": "pnpm --dir ./src/frontend run build",
9+
"build:skip-search": "pnpm --dir ./src/frontend run build:skip-search",
910
"preview": "pnpm --dir ./src/frontend run preview",
1011
"test": "pnpm --dir ./src/frontend run test",
1112
"test:all": "pnpm --dir ./src/frontend run test:all",

src/frontend/astro.config.mjs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ import starlightSidebarTopics from 'starlight-sidebar-topics';
2121
import starlightPageActions from 'starlight-page-actions';
2222
import jopSoftwarecookieconsent from '@jop-software/astro-cookieconsent';
2323

24+
const modeArgIndex = process.argv.indexOf('--mode');
25+
const isSkipSearchBuild = modeArgIndex >= 0 && process.argv[modeArgIndex + 1] === 'skip-search';
26+
2427
// https://astro.build/config
2528
export default defineConfig({
2629
prefetch: true,
@@ -34,6 +37,7 @@ export default defineConfig({
3437
iconPacks,
3538
}),
3639
starlight({
40+
pagefind: !isSkipSearchBuild,
3741
title: 'Aspire',
3842
routeMiddleware: ['./src/route-data-middleware'],
3943
defaultLocale: 'root',

src/frontend/package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,19 @@
2121
"start": "pnpm git-env && pnpm check-data && astro dev",
2222
"start:host": "pnpm git-env && pnpm check-data && astro dev --host",
2323
"build": "pnpm git-env && astro build",
24+
"build:skip-search": "pnpm git-env && astro build --mode skip-search",
2425
"build:production": "pnpm git-env && astro build --mode production",
2526
"preview": "astro preview",
2627
"preview:host": "astro preview --host",
2728
"astro": "pnpm git-env && astro",
2829
"test": "pnpm test:unit && pnpm test:e2e",
2930
"test:all": "pnpm lint && pnpm test",
30-
"test:unit": "pnpm test:unit:contracts && pnpm test:unit:components && pnpm test:unit:docs",
31+
"test:unit": "pnpm test:unit:contracts && pnpm test:unit:components && pnpm test:unit:docs && pnpm test:unit:api-markdown",
32+
"test:unit:api-markdown": "vitest run --config vitest.config.ts tests/unit/api-markdown.vitest.test.ts",
3133
"test:unit:contracts": "vitest run --config vitest.config.ts tests/unit/analytics-script-contracts.vitest.test.ts",
3234
"test:unit:components": "vitest run --config vitest.config.ts tests/unit/custom-components.vitest.test.ts tests/unit/site-tour.vitest.test.ts",
3335
"test:unit:docs": "vitest run --config vitest.config.ts tests/unit/filetree-format.vitest.test.ts",
36+
"test:e2e:api-markdown": "playwright test --config playwright.api-markdown.config.mjs",
3437
"test:e2e": "playwright test",
3538
"test:e2e:install": "playwright install chromium",
3639
"test:e2e:serve": "pnpm git-env && pnpm check-data && astro dev --host 127.0.0.1 --port 4321",
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { defineConfig } from '@playwright/test';
2+
3+
export default defineConfig({
4+
testDir: './tests/e2e',
5+
testMatch: 'api-markdown-routes.spec.ts',
6+
fullyParallel: true,
7+
workers: 1,
8+
reporter: [['list']],
9+
use: {
10+
baseURL: 'http://127.0.0.1:4322',
11+
},
12+
webServer: {
13+
command: 'pnpm git-env && pnpm check-data && astro dev --host 127.0.0.1 --port 4322',
14+
env: {
15+
...process.env,
16+
ASTRO_TELEMETRY_DISABLED: '1',
17+
E2E_TESTS: '1',
18+
},
19+
url: 'http://127.0.0.1:4322',
20+
reuseExistingServer: false,
21+
stdout: 'pipe',
22+
stderr: 'pipe',
23+
timeout: 120000,
24+
},
25+
});

src/frontend/src/middleware.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { defineMiddleware } from 'astro:middleware';
2+
3+
const nestedApiMarkdownPattern = /^\/reference\/api\/(?:csharp|typescript)\/.+\.md$/;
4+
5+
export const onRequest = defineMiddleware((context, next) => {
6+
const { pathname, search } = context.url;
7+
8+
if (nestedApiMarkdownPattern.test(pathname)) {
9+
return context.redirect(`${pathname}/${search}`, 308);
10+
}
11+
12+
return next();
13+
});
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import type { APIRoute } from 'astro';
2+
3+
import { markdownResponse } from '@utils/api-markdown-shared';
4+
import { renderCSharpIndexMarkdown } from '@utils/csharp-api-markdown';
5+
import { getPackages } from '@utils/packages';
6+
7+
export const prerender = true;
8+
9+
export const GET: APIRoute = async () => {
10+
const base = import.meta.env.BASE_URL.replace(/\/$/, '');
11+
const packages = (await getPackages()).map((entry) => entry.data);
12+
return markdownResponse(renderCSharpIndexMarkdown(packages, base));
13+
};
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import type { APIRoute } from 'astro';
2+
3+
import { markdownResponse } from '@utils/api-markdown-shared';
4+
import { renderCSharpPackageMarkdown } from '@utils/csharp-api-markdown';
5+
import type { PackageApiDocument } from '@utils/packages';
6+
import { getPackages, packageSlug } from '@utils/packages';
7+
8+
export const prerender = true;
9+
10+
type RouteProps = {
11+
pkg: PackageApiDocument;
12+
};
13+
14+
type StaticPath = {
15+
params: { package: string };
16+
props: RouteProps;
17+
};
18+
19+
export async function getStaticPaths(): Promise<StaticPath[]> {
20+
const packages = await getPackages();
21+
22+
return packages.map((entry) => ({
23+
params: { package: packageSlug(entry.data.package.name) },
24+
props: {
25+
pkg: entry.data,
26+
},
27+
}));
28+
}
29+
30+
export const GET: APIRoute = ({ props }) => {
31+
const base = import.meta.env.BASE_URL.replace(/\/$/, '');
32+
const routeProps = props as RouteProps;
33+
return markdownResponse(renderCSharpPackageMarkdown(routeProps.pkg, base));
34+
};
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import type { APIRoute } from 'astro';
2+
3+
import { markdownResponse } from '@utils/api-markdown-shared';
4+
import { renderCSharpTypeMarkdown } from '@utils/csharp-api-markdown';
5+
import type { PackageApiDocument, PackageType } from '@utils/packages';
6+
import { genericArity, getPackages, packageSlug, slugify } from '@utils/packages';
7+
8+
export const prerender = true;
9+
10+
type RouteProps = {
11+
allTypes: PackageType[];
12+
pkg: PackageApiDocument;
13+
type: PackageType;
14+
};
15+
16+
type StaticPath = {
17+
params: { package: string; type: string };
18+
props: RouteProps;
19+
};
20+
21+
export async function getStaticPaths(): Promise<StaticPath[]> {
22+
const packages = await getPackages();
23+
const paths: StaticPath[] = [];
24+
25+
for (const entry of packages) {
26+
const pkg = entry.data;
27+
const pkgSlug = packageSlug(pkg.package.name);
28+
29+
for (const type of pkg.types) {
30+
const typeSlug = slugify(type.name, genericArity(type));
31+
if (!typeSlug) {
32+
continue;
33+
}
34+
35+
paths.push({
36+
params: {
37+
package: pkgSlug,
38+
type: typeSlug,
39+
},
40+
props: {
41+
allTypes: pkg.types,
42+
pkg,
43+
type,
44+
},
45+
});
46+
}
47+
}
48+
49+
return paths;
50+
}
51+
52+
export const GET: APIRoute = ({ props }) => {
53+
const base = import.meta.env.BASE_URL.replace(/\/$/, '');
54+
const routeProps = props as RouteProps;
55+
56+
return markdownResponse(
57+
renderCSharpTypeMarkdown(routeProps.pkg, routeProps.type, routeProps.allTypes, base)
58+
);
59+
};

src/frontend/src/pages/reference/api/csharp/[package]/[type]/[memberKind].astro

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ const headings: { depth: 2 | 3; slug: string; text: string }[] = filteredMembers
101101
next: false,
102102
tableOfContents: false,
103103
pagefind: false,
104+
pageActions: true,
104105
}}
105106
sidebar={apiSidebar}
106107
headings={headings}
@@ -170,4 +171,4 @@ const headings: { depth: 2 | 3; slug: string; text: string }[] = filteredMembers
170171
flex-direction: column;
171172
gap: 1rem;
172173
}
173-
</style>
174+
</style>
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import type { APIRoute } from 'astro';
2+
3+
import { markdownResponse } from '@utils/api-markdown-shared';
4+
import { renderCSharpMemberKindMarkdown } from '@utils/csharp-api-markdown';
5+
import type { PackageApiDocument, PackageMember, PackageType } from '@utils/packages';
6+
import {
7+
genericArity,
8+
getPackages,
9+
memberKindOrder,
10+
memberKindSlugs,
11+
packageSlug,
12+
slugify,
13+
} from '@utils/packages';
14+
15+
export const prerender = true;
16+
17+
type RouteProps = {
18+
allTypes: PackageType[];
19+
memberKind: string;
20+
pkg: PackageApiDocument;
21+
type: PackageType;
22+
};
23+
24+
type StaticPath = {
25+
params: { memberKind: string; package: string; type: string };
26+
props: RouteProps;
27+
};
28+
29+
export async function getStaticPaths(): Promise<StaticPath[]> {
30+
const packages = await getPackages();
31+
const paths: StaticPath[] = [];
32+
33+
for (const entry of packages) {
34+
const pkg = entry.data;
35+
const pkgSlug = packageSlug(pkg.package.name);
36+
37+
for (const type of pkg.types) {
38+
const typeSlug = slugify(type.name, genericArity(type));
39+
if (!typeSlug) {
40+
continue;
41+
}
42+
43+
const members = type.members ?? [];
44+
for (const memberKind of memberKindOrder) {
45+
const memberKindSlug = memberKindSlugs[memberKind];
46+
if (!memberKindSlug || !members.some((member: PackageMember) => member.kind === memberKind)) {
47+
continue;
48+
}
49+
50+
paths.push({
51+
params: {
52+
memberKind: memberKindSlug,
53+
package: pkgSlug,
54+
type: typeSlug,
55+
},
56+
props: {
57+
allTypes: pkg.types,
58+
memberKind,
59+
pkg,
60+
type,
61+
},
62+
});
63+
}
64+
}
65+
}
66+
67+
return paths;
68+
}
69+
70+
export const GET: APIRoute = ({ props }) => {
71+
const base = import.meta.env.BASE_URL.replace(/\/$/, '');
72+
const routeProps = props as RouteProps;
73+
74+
return markdownResponse(
75+
renderCSharpMemberKindMarkdown(
76+
routeProps.pkg,
77+
routeProps.type,
78+
routeProps.memberKind,
79+
routeProps.allTypes,
80+
base
81+
)
82+
);
83+
};

0 commit comments

Comments
 (0)