Skip to content

Commit 1ab68f5

Browse files
committed
Add Docusaurus docs site and CI pipeline for API docs
- Introduced a Docusaurus-based documentation site with custom theming, navigation, and both hand-written and generated API reference content. - Added DocFX and DocFxMarkdownGen configuration for extracting and filtering C# API docs from built DLLs. - Implemented a post-processing script to normalize API links and link BCL types to Microsoft Learn. - Added a GitHub Actions workflow to build libraries, generate API docs, build the site, and deploy to GitHub Pages. - Updated .gitignore to exclude generated and build artifacts. - Committed package.json and package-lock.json to lock dependencies for reproducible builds. - Added initial authored documentation pages and supporting assets (logos, favicon, CSS, sidebar config, etc.).
1 parent 90db381 commit 1ab68f5

26 files changed

Lines changed: 21514 additions & 2 deletions

.github/workflows/deploy.yml

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
name: Deploy Documentation
2+
3+
# Builds the C# API reference (DocFX → Markdown) and the Docusaurus site,
4+
# then publishes to GitHub Pages.
5+
#
6+
# Pipeline:
7+
# 1. dotnet build — compile the SDK libraries (produces the DLLs + XML docs).
8+
# 2. docfx metadata — extract API YAML, then dfmg (DocFxMarkdownGen) → docs/api Markdown.
9+
# 3. npm build — Docusaurus builds the static site (consuming docs/api).
10+
# 4. deploy — upload the artifact and publish to GitHub Pages.
11+
#
12+
# The build job runs on Windows because one library targets .NET Framework 4.8 +
13+
# WPF (CodeFactory.WinVs.Wpf), which cannot be built on Linux, and DocFX reads
14+
# the compiled assemblies.
15+
16+
on:
17+
push:
18+
branches: [main]
19+
paths:
20+
- 'src/**'
21+
- 'website/**'
22+
- 'docfx/**'
23+
- '.github/workflows/deploy.yml'
24+
workflow_dispatch:
25+
26+
# Allow the GITHUB_TOKEN to deploy to Pages and verify the deployment origin.
27+
permissions:
28+
contents: read
29+
pages: write
30+
id-token: write
31+
32+
# Only one concurrent deployment; let an in-progress run finish.
33+
concurrency:
34+
group: pages
35+
cancel-in-progress: false
36+
37+
jobs:
38+
build:
39+
name: Build site
40+
runs-on: windows-latest
41+
defaults:
42+
run:
43+
shell: bash
44+
steps:
45+
- name: Checkout
46+
uses: actions/checkout@v4
47+
48+
# --- Toolchains -------------------------------------------------------
49+
- name: Setup .NET
50+
uses: actions/setup-dotnet@v4
51+
with:
52+
dotnet-version: '8.0.x'
53+
54+
- name: Setup Node.js
55+
uses: actions/setup-node@v4
56+
with:
57+
node-version: '20'
58+
cache: npm
59+
cache-dependency-path: website/package-lock.json
60+
61+
# --- Step 1: dotnet build --------------------------------------------
62+
# Build the three library projects. DocFX reads the resulting DLLs (and
63+
# their XML doc files) directly, which avoids invoking MSBuild from inside
64+
# DocFX (fragile against preview VS toolchains).
65+
- name: Build SDK libraries
66+
run: |
67+
dotnet build src/CodeFactoryForWindows/CodeFactory/CodeFactory.csproj -c Release
68+
dotnet build src/CodeFactoryForWindows/CodeFactory.WinVs/CodeFactory.WinVs.csproj -c Release
69+
dotnet build src/CodeFactoryForWindows/CodeFactory.WinVs.Wpf/CodeFactory.WinVs.Wpf.csproj -c Release
70+
71+
# --- Step 2: docfx metadata + Markdown bridge ------------------------
72+
# docfx is the metadata extractor; docfxmarkdowngen installs the `dfmg`
73+
# command that converts the YAML to Markdown using docfx/config.yaml.
74+
- name: Install DocFX and Markdown generator
75+
run: |
76+
dotnet tool install -g docfx
77+
dotnet tool install -g docfxmarkdowngen
78+
echo "$HOME/.dotnet/tools" >> "$GITHUB_PATH"
79+
80+
- name: Generate API metadata (YAML)
81+
working-directory: docfx
82+
run: docfx metadata docfx.json --logLevel Warning
83+
84+
- name: Convert API metadata to Markdown
85+
working-directory: docfx
86+
# `dfmg` loads ./config.yaml from the working directory.
87+
run: dfmg
88+
89+
- name: Normalize generated API links
90+
# Appends .md to relative cross-reference links (so Docusaurus resolves
91+
# them via the doc graph), links BCL (System.*/Microsoft.*) types to
92+
# Microsoft Learn, and drops empty namespace index entries.
93+
run: node docfx/postprocess-api.mjs
94+
95+
- name: Polish generated API index title
96+
working-directory: website/docs/api
97+
run: |
98+
sed -i 's/^title: Index$/title: API Reference/' index.md
99+
sed -i 's/^sidebar_label: Index$/sidebar_label: Overview/' index.md
100+
101+
# --- Step 3: npm build -----------------------------------------------
102+
- name: Install site dependencies
103+
working-directory: website
104+
run: npm ci
105+
106+
- name: Build Docusaurus site
107+
working-directory: website
108+
run: npm run build
109+
110+
- name: Upload Pages artifact
111+
uses: actions/upload-pages-artifact@v3
112+
with:
113+
path: website/build
114+
115+
# --- Step 4: deploy -----------------------------------------------------
116+
deploy:
117+
name: Deploy to GitHub Pages
118+
needs: build
119+
runs-on: ubuntu-latest
120+
environment:
121+
name: github-pages
122+
url: ${{ steps.deployment.outputs.page_url }}
123+
steps:
124+
- name: Deploy
125+
id: deployment
126+
uses: actions/deploy-pages@v4

.gitignore

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -430,3 +430,13 @@ FodyWeavers.xsd
430430

431431
# Signer Key
432432
*.snk
433+
434+
# DocFX generated output
435+
_site/
436+
437+
# Generated API YAML files (built from DLLs by docfx metadata)
438+
api/*.yml
439+
api/.manifest
440+
441+
# Build artifacts
442+
obj/

docfx/.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Generated DocFX output — produced by `docfx metadata` / `docfx build`.
2+
/api/
3+
/_site/
4+
/obj/

docfx/config.yaml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# DocFxMarkdownGen configuration — the bridge that turns DocFX's YAML managed
2+
# reference (written by `docfx metadata` into docfx/api) into Docusaurus Markdown
3+
# (written into website/docs/api).
4+
#
5+
# DocFX itself has no Markdown emitter for API reference; DocFxMarkdownGen (the
6+
# `dfmg` tool) reads the *.yml that `docfx metadata` produces and renders Markdown
7+
# with the YAML front matter Docusaurus expects (title/sidebar_label/description).
8+
#
9+
# `dfmg` loads this file as `config.yaml` from its working directory (docfx/), so
10+
# the pipeline simply runs `dfmg` from docfx/ after `docfx metadata`.
11+
# Tool: https://github.com/Jan0660/DocFxMarkdownGen
12+
13+
# Where `docfx metadata` wrote the managed-reference YAML.
14+
yamlPath: ./api
15+
16+
# Where the rendered Markdown should land — the Docusaurus docs/api folder that
17+
# sidebars.js consumes via { type: 'autogenerated', dirName: 'api' }.
18+
outputPath: ../website/docs/api
19+
20+
# Slug for the generated index page. RELATIVE to the docs plugin routeBasePath
21+
# ("docs"), so "/api" resolves to the route /docs/api. Do NOT use "/docs/api"
22+
# here — Docusaurus prepends the base again, producing /docs/docs/api and
23+
# breaking the index link on every page.
24+
indexSlug: /api

docfx/docfx.json

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
{
2+
"$schema": "https://raw.githubusercontent.com/dotnet/docfx/main/schemas/docfx.schema.json",
3+
"metadata": [
4+
{
5+
"//": "Extracts API metadata (YAML managed reference) from the compiled SDK libraries.",
6+
"//2": "Reading the built DLLs (+ their XML doc files) avoids invoking MSBuild from",
7+
"//3": "inside DocFX, which is fragile against preview VS toolchains. The DLLs are",
8+
"//4": "produced by `dotnet build -c Release` in the pipeline's first step.",
9+
"//5": "Only the shipped libraries are documented — the Packager tool is excluded.",
10+
"src": [
11+
{
12+
"src": "../src/CodeFactoryForWindows",
13+
"files": [
14+
"CodeFactory/bin/Release/netstandard2.0/CodeFactory.dll",
15+
"CodeFactory.WinVs/bin/Release/netstandard2.0/CodeFactory.WinVs.dll",
16+
"CodeFactory.WinVs.Wpf/bin/Release/CodeFactory.WinVs.Wpf.dll"
17+
]
18+
}
19+
],
20+
"dest": "api",
21+
"filter": "filterConfig.yml",
22+
"outputFormat": "mref",
23+
"includePrivateMembers": false,
24+
"namespaceLayout": "nested",
25+
"memberLayout": "samePage",
26+
"EnumSortOrder": "alphabetic"
27+
}
28+
],
29+
"build": {
30+
"//": "A minimal build section is required by DocFX even though the public site is",
31+
"//2": "rendered by Docusaurus. `docfx build` is optional; we only run `docfx metadata`.",
32+
"content": [
33+
{
34+
"files": ["api/**.yml", "api/index.md"]
35+
}
36+
],
37+
"dest": "_site",
38+
"globalMetadata": {
39+
"_appTitle": "CodeFactory for Windows SDK",
40+
"_enableSearch": false
41+
}
42+
}
43+
}

docfx/filterConfig.yml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# DocFX API filter — controls which symbols reach the generated reference.
2+
#
3+
# DocFX already excludes private/internal *accessibility* by default
4+
# (includePrivateMembers: false in docfx.json). These rules additionally strip:
5+
# 1. Anything under an `*.Internal` namespace (implementation detail surface).
6+
# 2. Members marked [Obsolete] — deprecated API should not be documented.
7+
# 3. Members hidden from IntelliSense via [EditorBrowsable(Never)].
8+
#
9+
# Rules are evaluated top-to-bottom; the first matching include/exclude wins.
10+
# See: https://dotnet.github.io/docfx/docs/dotnet-api-docs.html#filter-configuration
11+
12+
apiRules:
13+
# --- 1. Strip internal namespaces (the namespace node and everything under it).
14+
- exclude:
15+
uidRegex: '^CodeFactory.*\.Internal($|\.)'
16+
type: Namespace
17+
- exclude:
18+
uidRegex: '^CodeFactory.*\.Internal\.'
19+
20+
# --- 2. Strip obsolete/deprecated members.
21+
- exclude:
22+
hasAttribute:
23+
uid: System.ObsoleteAttribute
24+
25+
# --- 3. Strip members explicitly hidden from IntelliSense.
26+
- exclude:
27+
hasAttribute:
28+
uid: System.ComponentModel.EditorBrowsableAttribute
29+
ctorArguments:
30+
- System.ComponentModel.EditorBrowsableState.Never
31+
32+
# --- 4. Strip auto-generated resource accessor classes (FactoryMessages, etc.).
33+
- exclude:
34+
uidRegex: '^CodeFactory.*Messages$'
35+
type: Type

docfx/postprocess-api.mjs

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
// Post-process the Markdown that DocFxMarkdownGen writes into website/docs/api.
2+
//
3+
// Why: the generator emits inline cross-reference links WITHOUT a file
4+
// extension, e.g. `[ActionException](../CodeFactory/ActionException)`. Docusaurus
5+
// only resolves relative links to the correct doc permalink when they end in
6+
// `.md` (it matches the target file in the doc graph). Without `.md`, the link
7+
// is treated as a raw URL and resolved with trailing-slash math — which drops
8+
// the `api` segment on namespace summary pages (served at /docs/api/<NS> while
9+
// their source file lives at <NS>/<NS>.md). Appending `.md` makes Docusaurus
10+
// map every link to the real page, immune to that routing quirk and to the
11+
// backtick-named generic-type files.
12+
//
13+
// Run after `dfmg`, before `npm run build`.
14+
15+
import { readdir, readFile, writeFile } from 'node:fs/promises';
16+
import { join } from 'node:path';
17+
import { fileURLToPath } from 'node:url';
18+
19+
const apiDir = join(fileURLToPath(new URL('.', import.meta.url)), '..', 'website', 'docs', 'api');
20+
21+
// Match a Markdown link target that is a relative path (starts with ./ or ../),
22+
// capturing the path and an optional #anchor. We append `.md` to the path part
23+
// when it doesn't already have a markdown extension.
24+
const linkRe = /\]\((\.\.?\/[^)#]+?)(#[^)]*)?\)/g;
25+
26+
// A fully-qualified BCL type name: `System.*` or `Microsoft.*`.
27+
const BCL_NAME = '(?:System|Microsoft)\\.[A-Za-z0-9_.]+';
28+
29+
// Generic BCL type: outer name followed by `<...>` (rendered type args) or
30+
// `%60N` (DocFxMarkdownGen's encoded arity artifact, e.g. IReadOnlyList%601).
31+
// The whole span is linked to the OUTER type's arity-suffixed Learn page; the
32+
// `<...>` part stays as plain display text (inner type args are not linked).
33+
const BCL_GENERIC = new RegExp(`(?<!\\[)\`(${BCL_NAME})(<[^\`]*>|%60\\d+)\`(?!\\]\\()`, 'g');
34+
35+
// Non-generic BCL type: a code span that is exactly one fully-qualified type,
36+
// e.g. `System.String`. Excludes `<`/`%` so it never overlaps BCL_GENERIC.
37+
const BCL_TYPE = new RegExp(`(?<!\\[)\`(${BCL_NAME})\`(?!\\]\\()`, 'g');
38+
39+
// Build the canonical Microsoft Learn .NET API URL for a fully-qualified type.
40+
// Convention: lowercase the dotted name; generic arity is suffixed with -N.
41+
// e.g. System.String -> .../api/system.string
42+
// System.Threading.Tasks.Task (arity 2) -> .../api/system.threading.tasks.task-2
43+
function msLearnUrl(typeName, arity = 0) {
44+
const slug = typeName.toLowerCase() + (arity > 0 ? `-${arity}` : '');
45+
return `https://learn.microsoft.com/en-us/dotnet/api/${slug}`;
46+
}
47+
48+
// Count top-level type arguments in an angle-bracket group like `<A, B<C,D>>`
49+
// (nested commas don't count) -> the generic arity.
50+
function genericArity(angle) {
51+
const inner = angle.slice(1, -1);
52+
let depth = 0;
53+
let commas = 0;
54+
for (const ch of inner) {
55+
if (ch === '<') depth++;
56+
else if (ch === '>') depth--;
57+
else if (ch === ',' && depth === 0) commas++;
58+
}
59+
return commas + 1;
60+
}
61+
62+
// Link BCL type code spans to Microsoft Learn, but never inside fenced code
63+
// blocks (declarations show C# keywords, not these spans, but be safe).
64+
function linkBclTypes(content) {
65+
let inFence = false;
66+
return content
67+
.split('\n')
68+
.map((line) => {
69+
if (/^\s*```/.test(line)) {
70+
inFence = !inFence;
71+
return line;
72+
}
73+
if (inFence) return line;
74+
// Generics first (they contain `<`/`%`, which BCL_TYPE excludes).
75+
let out = line.replace(BCL_GENERIC, (_m, outer, suffix) => {
76+
const arity = suffix.startsWith('<') ? genericArity(suffix) : Number(suffix.slice(3));
77+
// Keep rendered type args as display text; drop the %60N artifact.
78+
const display = suffix.startsWith('<') ? `${outer}${suffix}` : outer;
79+
return `[\`${display}\`](${msLearnUrl(outer, arity)})`;
80+
});
81+
out = out.replace(BCL_TYPE, (_m, type) => `[\`${type}\`](${msLearnUrl(type)})`);
82+
return out;
83+
})
84+
.join('\n');
85+
}
86+
87+
async function* walk(dir) {
88+
for (const entry of await readdir(dir, { withFileTypes: true })) {
89+
const full = join(dir, entry.name);
90+
if (entry.isDirectory()) yield* walk(full);
91+
else if (entry.name.endsWith('.md')) yield full;
92+
}
93+
}
94+
95+
let files = 0;
96+
let rewrites = 0;
97+
let dropped = 0;
98+
let bclLinks = 0;
99+
100+
for await (const file of walk(apiDir)) {
101+
const original = await readFile(file, 'utf8');
102+
let updated = original.replace(linkRe, (match, path, anchor = '') => {
103+
if (/\.mdx?$/i.test(path)) return match; // already has an extension
104+
rewrites++;
105+
return `](${path}.md${anchor})`;
106+
});
107+
108+
// Link BCL parameter/return/base types to Microsoft Learn.
109+
const beforeBcl = updated;
110+
updated = linkBclTypes(updated);
111+
if (updated !== beforeBcl) {
112+
bclLinks +=
113+
(beforeBcl.match(BCL_GENERIC) || []).length + (beforeBcl.match(BCL_TYPE) || []).length;
114+
}
115+
116+
// Drop index entries with empty link text, e.g. `* [](./Foo/Foo.md)`. These are
117+
// emitted for namespaces that contain only sub-namespaces (no documented types),
118+
// so DocFxMarkdownGen never generates the target page — the link is both empty
119+
// and broken.
120+
updated = updated.replace(/^\s*[-*] \[\]\([^)]*\)\s*$\n?/gm, () => {
121+
dropped++;
122+
return '';
123+
});
124+
125+
if (updated !== original) {
126+
await writeFile(file, updated);
127+
files++;
128+
}
129+
}
130+
131+
console.log(
132+
`postprocess-api: appended .md to ${rewrites} link(s), linked ${bclLinks} BCL type(s) to Microsoft Learn, ` +
133+
`dropped ${dropped} empty entry(ies) across ${files} file(s).`,
134+
);

docs/README.md

Lines changed: 0 additions & 2 deletions
This file was deleted.

0 commit comments

Comments
 (0)