@reicek/neataptic-ts 0.1.25 → 0.1.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/copilot-instructions.md +11 -0
- package/.github/skills/trace-analyzer-extension/SKILL.md +3 -3
- package/.github/skills/trace-analyzer-extension/assets/extension-checklist.md +1 -1
- package/.github/skills/trace-analyzer-extension/references/analyzer-extension-workflow.md +1 -1
- package/.github/skills/trace-audit-reporting/SKILL.md +3 -3
- package/.github/skills/trace-audit-reporting/references/trace-analysis-workflow.md +1 -1
- package/package.json +19 -13
- package/plans/Flappy_Bird_Folder_Documentation_Pass.md +4 -4
- package/plans/README.md +24 -0
- package/plans/Roadmap.md +62 -40
- package/plans/analyze-trace-solid-split.plans.md +66 -0
- package/plans/architecture-solid-split.plans.md +9 -15
- package/plans/asciiMaze-typescript-repair.plans.md +1 -1
- package/plans/generate-docs-solid-split.plans.md +87 -0
- package/plans/methods-docs.plans.md +25 -1
- package/plans/methods-solid-split.plans.md +14 -14
- package/plans/neat-docs.plans.md +9 -1
- package/plans/neat-test-surface-repair.plans.md +1 -1
- package/plans/render-docs-html-solid-split.plans.md +68 -0
- package/plans/src-no-explicit-any-cleanup.plans.md +1 -1
- package/plans/utils-docs.plans.md +6 -1
- package/scripts/analyze-trace/analyze-trace.analysis.ts +479 -0
- package/scripts/analyze-trace/analyze-trace.constants.ts +35 -0
- package/scripts/analyze-trace/analyze-trace.io.ts +69 -0
- package/scripts/analyze-trace/analyze-trace.report.ts +100 -0
- package/scripts/analyze-trace/analyze-trace.shared.ts +116 -0
- package/scripts/analyze-trace/analyze-trace.ts +45 -0
- package/scripts/analyze-trace/analyze-trace.types.ts +72 -0
- package/scripts/assets/theme.css +80 -23
- package/scripts/copy-examples.ts +239 -0
- package/scripts/export-onnx.ts +223 -0
- package/scripts/generate-bench-tables.ts +378 -37
- package/scripts/generate-docs/generate-docs.constants.ts +107 -0
- package/scripts/generate-docs/generate-docs.order.ts +355 -0
- package/scripts/generate-docs/generate-docs.state.ts +31 -0
- package/scripts/generate-docs/generate-docs.targets.ts +165 -0
- package/scripts/generate-docs/generate-docs.ts +63 -0
- package/scripts/generate-docs/generate-docs.types.ts +112 -0
- package/scripts/generate-docs/output/generate-docs.output.folder-index.utils.ts +167 -0
- package/scripts/generate-docs/output/generate-docs.output.ordering.utils.ts +353 -0
- package/scripts/generate-docs/output/generate-docs.output.readme.utils.ts +420 -0
- package/scripts/generate-docs/output/generate-docs.output.ts +123 -0
- package/scripts/generate-docs/output/generate-docs.output.warnings.utils.ts +219 -0
- package/scripts/generate-docs/symbols/generate-docs.symbols.collection.utils.ts +365 -0
- package/scripts/generate-docs/symbols/generate-docs.symbols.jsdoc.utils.ts +373 -0
- package/scripts/generate-docs/symbols/generate-docs.symbols.normalize.utils.ts +155 -0
- package/scripts/generate-docs/symbols/generate-docs.symbols.render.utils.ts +149 -0
- package/scripts/generate-docs/symbols/generate-docs.symbols.signature.utils.ts +289 -0
- package/scripts/generate-docs/symbols/generate-docs.symbols.ts +11 -0
- package/scripts/mermaid-cli.mjs +102 -22
- package/scripts/mermaid-cli.ts +736 -0
- package/scripts/render-docs-html/render-docs-html.assets.ts +54 -0
- package/scripts/render-docs-html/render-docs-html.mermaid.ts +245 -0
- package/scripts/{render-docs-html.sidebar.ts → render-docs-html/render-docs-html.navigation.ts} +141 -144
- package/scripts/render-docs-html/render-docs-html.pages.ts +333 -0
- package/scripts/render-docs-html/render-docs-html.shared.ts +333 -0
- package/scripts/render-docs-html/render-docs-html.types.ts +42 -0
- package/scripts/render-docs-html.ts +23 -587
- package/scripts/run-docs.ts +238 -0
- package/scripts/write-dist-docs-pkg.ts +40 -0
- package/src/README.md +75 -75
- package/src/architecture/connection/README.md +5 -5
- package/src/architecture/layer/README.md +508 -508
- package/src/architecture/network/README.md +1458 -1458
- package/src/architecture/network/activate/README.md +694 -694
- package/src/architecture/network/bootstrap/README.md +77 -77
- package/src/architecture/network/connect/README.md +74 -74
- package/src/architecture/network/deterministic/README.md +135 -135
- package/src/architecture/network/evolve/README.md +364 -364
- package/src/architecture/network/gating/README.md +130 -130
- package/src/architecture/network/genetic/README.md +399 -399
- package/src/architecture/network/mutate/README.md +897 -897
- package/src/architecture/network/onnx/README.md +720 -720
- package/src/architecture/network/onnx/export/README.md +728 -728
- package/src/architecture/network/onnx/export/layers/README.md +450 -450
- package/src/architecture/network/onnx/import/README.md +618 -618
- package/src/architecture/network/onnx/schema/README.md +32 -32
- package/src/architecture/network/prune/README.md +245 -245
- package/src/architecture/network/remove/README.md +135 -135
- package/src/architecture/network/runtime/README.md +106 -106
- package/src/architecture/network/serialize/README.md +542 -542
- package/src/architecture/network/slab/README.md +608 -608
- package/src/architecture/network/standalone/README.md +212 -212
- package/src/architecture/network/stats/README.md +84 -84
- package/src/architecture/network/topology/README.md +465 -465
- package/src/architecture/network/training/README.md +200 -200
- package/src/architecture/node/README.md +5 -5
- package/src/architecture/nodePool/README.md +14 -14
- package/src/methods/README.md +99 -99
- package/src/methods/activation/README.md +189 -189
- package/src/methods/cost/README.md +131 -131
- package/src/methods/rate/README.md +86 -86
- package/src/multithreading/README.md +77 -77
- package/src/multithreading/workers/browser/README.md +8 -8
- package/src/multithreading/workers/node/README.md +8 -8
- package/src/neat/README.md +148 -148
- package/src/neat/adaptive/README.md +120 -120
- package/src/neat/adaptive/acceptance/README.md +40 -40
- package/src/neat/adaptive/complexity/README.md +137 -137
- package/src/neat/adaptive/core/README.md +197 -197
- package/src/neat/adaptive/lineage/README.md +90 -90
- package/src/neat/adaptive/mutation/README.md +284 -284
- package/src/neat/compat/README.md +43 -43
- package/src/neat/compat/core/README.md +90 -90
- package/src/neat/diversity/README.md +35 -35
- package/src/neat/diversity/core/README.md +88 -88
- package/src/neat/evaluate/README.md +85 -85
- package/src/neat/evaluate/auto-distance/README.md +75 -75
- package/src/neat/evaluate/entropy-compat/README.md +37 -37
- package/src/neat/evaluate/entropy-sharing/README.md +43 -43
- package/src/neat/evaluate/fitness/README.md +23 -23
- package/src/neat/evaluate/novelty/README.md +120 -120
- package/src/neat/evaluate/objectives/README.md +17 -17
- package/src/neat/evaluate/shared/README.md +94 -94
- package/src/neat/evolve/README.md +96 -96
- package/src/neat/evolve/adaptive/README.md +60 -60
- package/src/neat/evolve/objectives/README.md +63 -63
- package/src/neat/evolve/offspring/README.md +56 -56
- package/src/neat/evolve/population/README.md +171 -171
- package/src/neat/evolve/runtime/README.md +79 -79
- package/src/neat/evolve/speciation/README.md +74 -74
- package/src/neat/evolve/warnings/README.md +10 -10
- package/src/neat/export/README.md +114 -114
- package/src/neat/helpers/README.md +50 -50
- package/src/neat/init/README.md +9 -9
- package/src/neat/lineage/core/README.md +101 -101
- package/src/neat/multiobjective/category/README.md +74 -74
- package/src/neat/multiobjective/crowding/README.md +272 -272
- package/src/neat/multiobjective/dominance/README.md +171 -171
- package/src/neat/multiobjective/fronts/README.md +68 -68
- package/src/neat/multiobjective/metrics/README.md +43 -43
- package/src/neat/multiobjective/objectives/README.md +31 -31
- package/src/neat/multiobjective/shared/README.md +27 -27
- package/src/neat/mutation/README.md +97 -97
- package/src/neat/mutation/add-conn/README.md +115 -115
- package/src/neat/mutation/add-node/README.md +126 -126
- package/src/neat/mutation/flow/README.md +149 -149
- package/src/neat/mutation/repair/README.md +185 -185
- package/src/neat/mutation/select/README.md +117 -117
- package/src/neat/mutation/shared/README.md +32 -32
- package/src/neat/objectives/README.md +25 -25
- package/src/neat/objectives/core/README.md +67 -67
- package/src/neat/pruning/README.md +40 -40
- package/src/neat/pruning/core/README.md +171 -171
- package/src/neat/pruning/facade/README.md +32 -32
- package/src/neat/rng/README.md +104 -104
- package/src/neat/rng/core/README.md +137 -137
- package/src/neat/rng/facade/README.md +50 -50
- package/src/neat/selection/README.md +111 -111
- package/src/neat/selection/core/README.md +227 -227
- package/src/neat/selection/facade/README.md +61 -61
- package/src/neat/shared/README.md +163 -163
- package/src/neat/speciation/README.md +31 -31
- package/src/neat/speciation/threshold/README.md +35 -35
- package/src/neat/species/README.md +25 -25
- package/src/neat/species/core/README.md +20 -20
- package/src/neat/species/core/shared/README.md +18 -18
- package/src/neat/species/history/context/README.md +22 -22
- package/src/neat/telemetry/accessors/README.md +58 -58
- package/src/neat/telemetry/exports/README.md +233 -233
- package/src/neat/telemetry/facade/README.md +252 -252
- package/src/neat/telemetry/facade/archive/README.md +57 -57
- package/src/neat/telemetry/facade/buffer/README.md +43 -43
- package/src/neat/telemetry/facade/lineage/README.md +12 -12
- package/src/neat/telemetry/facade/objectives/README.md +44 -44
- package/src/neat/telemetry/facade/runtime/README.md +26 -26
- package/src/neat/telemetry/facade/species/README.md +27 -27
- package/src/neat/telemetry/metrics/README.md +696 -696
- package/src/neat/telemetry/recorder/README.md +57 -57
- package/src/neat/telemetry/types/README.md +32 -32
- package/src/neat/topology-intent/README.md +75 -75
- package/src/utils/README.md +193 -193
- package/test/examples/asciiMaze/browser-entry/README.md +92 -92
- package/test/examples/asciiMaze/dashboardManager/README.md +109 -109
- package/test/examples/asciiMaze/dashboardManager/telemetry/README.md +28 -28
- package/test/examples/asciiMaze/evolutionEngine/README.md +1527 -1527
- package/test/examples/asciiMaze/mazeMovement/README.md +105 -105
- package/test/examples/asciiMaze/mazeMovement/finalization/README.md +16 -16
- package/test/examples/asciiMaze/mazeMovement/policy/README.md +57 -57
- package/test/examples/asciiMaze/mazeMovement/runtime/README.md +52 -52
- package/test/examples/asciiMaze/mazeMovement/shaping/README.md +46 -46
- package/test/examples/flappy_bird/browser-entry/README.md +508 -508
- package/test/examples/flappy_bird/browser-entry/host/README.md +101 -101
- package/test/examples/flappy_bird/browser-entry/host/resize/README.md +144 -144
- package/test/examples/flappy_bird/browser-entry/network-view/README.md +194 -194
- package/test/examples/flappy_bird/browser-entry/playback/README.md +278 -278
- package/test/examples/flappy_bird/browser-entry/playback/background/README.md +129 -129
- package/test/examples/flappy_bird/browser-entry/playback/background/ground-grid/README.md +502 -502
- package/test/examples/flappy_bird/browser-entry/playback/frame-render/README.md +139 -139
- package/test/examples/flappy_bird/browser-entry/playback/snapshot/README.md +10 -10
- package/test/examples/flappy_bird/browser-entry/playback/trail/README.md +43 -43
- package/test/examples/flappy_bird/browser-entry/playback/worker-channel/README.md +30 -30
- package/test/examples/flappy_bird/browser-entry/runtime/README.md +59 -59
- package/test/examples/flappy_bird/browser-entry/visualization/README.md +276 -276
- package/test/examples/flappy_bird/browser-entry/worker-channel/README.md +16 -16
- package/test/examples/flappy_bird/constants/README.md +1070 -1070
- package/test/examples/flappy_bird/environment/README.md +22 -22
- package/test/examples/flappy_bird/evaluation/README.md +32 -32
- package/test/examples/flappy_bird/evaluation/rollout/README.md +141 -141
- package/test/examples/flappy_bird/flappy-evolution-worker/README.md +425 -425
- package/test/examples/flappy_bird/simulation-shared/README.md +170 -170
- package/test/examples/flappy_bird/simulation-shared/observation/README.md +109 -109
- package/test/examples/flappy_bird/trainer/README.md +325 -325
- package/test/examples/flappy_bird/trainer/evaluation/README.md +74 -74
- package/scripts/analyze-trace.ts +0 -590
- package/scripts/copy-examples.mjs +0 -114
- package/scripts/export-onnx.mjs +0 -86
- package/scripts/generate-bench-tables.mjs +0 -182
- package/scripts/generate-docs.ts +0 -2900
- package/scripts/write-dist-docs-pkg.mjs +0 -16
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Page discovery, markdown rendering, and HTML shell emission for generated docs.
|
|
3
|
+
*
|
|
4
|
+
* This chapter owns the page-level pipeline: find README files, collect their
|
|
5
|
+
* metadata, render markdown with stable anchors, assemble the left rail and TOC,
|
|
6
|
+
* and emit the final HTML shell.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { setTimeout } from 'node:timers/promises';
|
|
10
|
+
import path from 'path';
|
|
11
|
+
import fg from 'fast-glob';
|
|
12
|
+
import fs from 'fs-extra';
|
|
13
|
+
import { marked } from 'marked';
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
buildDocsSidebarHtml,
|
|
17
|
+
buildExamplesTocLinksHtml,
|
|
18
|
+
} from './render-docs-html.navigation.js';
|
|
19
|
+
import {
|
|
20
|
+
buildMermaidBootstrapScript,
|
|
21
|
+
collectMermaidBlocks,
|
|
22
|
+
renderMermaidDiagramPlaceholder,
|
|
23
|
+
} from './render-docs-html.mermaid.js';
|
|
24
|
+
import {
|
|
25
|
+
buildRelativeDocsHref,
|
|
26
|
+
DOCS_DIR,
|
|
27
|
+
escapeHtml,
|
|
28
|
+
hasPublishedDocsPage,
|
|
29
|
+
rewriteCompiledDocsContentLinks,
|
|
30
|
+
slugify,
|
|
31
|
+
} from './render-docs-html.shared.js';
|
|
32
|
+
import type {
|
|
33
|
+
MermaidBlockReference,
|
|
34
|
+
PageMeta,
|
|
35
|
+
} from './render-docs-html.types.js';
|
|
36
|
+
|
|
37
|
+
const RETRIABLE_FILE_SYSTEM_ERROR_CODES = new Set([
|
|
38
|
+
'UNKNOWN',
|
|
39
|
+
'EPERM',
|
|
40
|
+
'EBUSY',
|
|
41
|
+
'EACCES',
|
|
42
|
+
]);
|
|
43
|
+
const DOCS_WRITE_MAX_ATTEMPTS = 6;
|
|
44
|
+
const DOCS_WRITE_INITIAL_RETRY_DELAY_MS = 120;
|
|
45
|
+
|
|
46
|
+
interface TocFileHeading {
|
|
47
|
+
file: string;
|
|
48
|
+
anchor: string;
|
|
49
|
+
symbols: { name: string; anchor: string }[];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Collects markdown page metadata and Mermaid fences from the docs tree. */
|
|
53
|
+
export async function collectDocsPages(): Promise<{
|
|
54
|
+
pages: PageMeta[];
|
|
55
|
+
mermaidBlocks: MermaidBlockReference[];
|
|
56
|
+
}> {
|
|
57
|
+
const readmes = await fg(['**/README.md'], { cwd: DOCS_DIR, absolute: true });
|
|
58
|
+
const pages: PageMeta[] = [];
|
|
59
|
+
const mermaidBlocks: MermaidBlockReference[] = [];
|
|
60
|
+
|
|
61
|
+
for (const markdownFilePath of readmes) {
|
|
62
|
+
const markdown = await fs.readFile(markdownFilePath, 'utf8');
|
|
63
|
+
const title =
|
|
64
|
+
mdTitle(markdown) ||
|
|
65
|
+
path.relative(DOCS_DIR, path.dirname(markdownFilePath)) ||
|
|
66
|
+
'Documentation';
|
|
67
|
+
const relDir = path
|
|
68
|
+
.relative(DOCS_DIR, path.dirname(markdownFilePath))
|
|
69
|
+
.replace(/\\/g, '/');
|
|
70
|
+
|
|
71
|
+
pages.push({
|
|
72
|
+
abs: markdownFilePath,
|
|
73
|
+
relDir,
|
|
74
|
+
title,
|
|
75
|
+
markdown,
|
|
76
|
+
});
|
|
77
|
+
mermaidBlocks.push(...collectMermaidBlocks(markdown, markdownFilePath));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return { pages, mermaidBlocks };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Emits the generated HTML page for every discovered markdown page. */
|
|
84
|
+
export async function emitDocsPages(pages: readonly PageMeta[]): Promise<void> {
|
|
85
|
+
const generatedPageDirectories = new Set(pages.map((page) => page.relDir));
|
|
86
|
+
|
|
87
|
+
for (const page of pages) {
|
|
88
|
+
const pageHtml = renderDocsPage(page, pages, generatedPageDirectories);
|
|
89
|
+
const outputFilePath = path.join(path.dirname(page.abs), 'index.html');
|
|
90
|
+
await writeFileWithRetry(outputFilePath, pageHtml, 'utf8');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
console.log('HTML docs generated.');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Renders one docs page shell. */
|
|
97
|
+
function renderDocsPage(
|
|
98
|
+
page: PageMeta,
|
|
99
|
+
pages: readonly PageMeta[],
|
|
100
|
+
generatedPageDirectories: ReadonlySet<string>,
|
|
101
|
+
): string {
|
|
102
|
+
const tableOfContents = extractFileHeadings(page.markdown);
|
|
103
|
+
const renderedBody = renderMarkdownToHtml(page.markdown);
|
|
104
|
+
const htmlBody = rewriteCompiledDocsContentLinks(
|
|
105
|
+
renderedBody.htmlBody,
|
|
106
|
+
page.relDir,
|
|
107
|
+
generatedPageDirectories,
|
|
108
|
+
);
|
|
109
|
+
const rootExamplesTocHtml = buildExamplesTocLinksHtml({
|
|
110
|
+
currentDir: page.relDir,
|
|
111
|
+
generatedPageDirectories,
|
|
112
|
+
hasPublishedDocsPage,
|
|
113
|
+
buildRelativeDocsHref,
|
|
114
|
+
escapeHtml,
|
|
115
|
+
});
|
|
116
|
+
const sidebarHtml = buildDocsSidebarHtml({
|
|
117
|
+
currentDir: page.relDir,
|
|
118
|
+
pages,
|
|
119
|
+
generatedPageDirectories,
|
|
120
|
+
hasPublishedDocsPage,
|
|
121
|
+
buildRelativeDocsHref,
|
|
122
|
+
escapeHtml,
|
|
123
|
+
});
|
|
124
|
+
const tocHtml = buildPageTocHtml(
|
|
125
|
+
page.relDir,
|
|
126
|
+
tableOfContents,
|
|
127
|
+
rootExamplesTocHtml,
|
|
128
|
+
);
|
|
129
|
+
const relativePathToRoot = path
|
|
130
|
+
.relative(path.dirname(page.abs), DOCS_DIR)
|
|
131
|
+
.replace(/\\/g, '/');
|
|
132
|
+
const cssHref =
|
|
133
|
+
(relativePathToRoot ? `${relativePathToRoot}/` : '') + 'assets/theme.css';
|
|
134
|
+
const mermaidBootstrapScript = renderedBody.hasMermaidDiagram
|
|
135
|
+
? buildMermaidBootstrapScript(relativePathToRoot)
|
|
136
|
+
: '';
|
|
137
|
+
const examplesHref = `${relativePathToRoot || '.'}/examples/index.html`;
|
|
138
|
+
const onExamplesPage = page.relDir.startsWith('examples');
|
|
139
|
+
const docsActiveClass = !onExamplesPage ? ' class="active"' : '';
|
|
140
|
+
const examplesActiveClass = onExamplesPage ? ' class="active"' : '';
|
|
141
|
+
|
|
142
|
+
return `<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><title>${
|
|
143
|
+
page.title
|
|
144
|
+
} – NeatapticTS Docs</title><meta name="viewport" content="width=device-width,initial-scale=1">\n<link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><link href="https://fonts.googleapis.com/css2?family=Raleway:wght@400;600;700&family=Open+Sans:wght@400;600&display=swap" rel="stylesheet">\n<link rel="stylesheet" href="${cssHref}"></head><body class="${
|
|
145
|
+
page.relDir === '' ? 'is-root' : ''
|
|
146
|
+
}">\n<header class="topbar"><div class="inner topbar-inner-connected"><div class="brand"><a class="brand-link" href="${
|
|
147
|
+
relativePathToRoot || '.'
|
|
148
|
+
}/index.html"><span class="brand-title-lockup" aria-hidden="true"><span class="brand-title-row brand-title-row-top"><span class="brand-title-corner">╔══</span><span class="brand-label">NeatapticTS</span><span class="brand-title-corner">══╗</span></span></span><span class="brand-title-accessible">NeatapticTS</span></a></div><nav class="main-nav"><a href="${
|
|
149
|
+
relativePathToRoot || '.'
|
|
150
|
+
}/index.html">Home</a><a href="${
|
|
151
|
+
relativePathToRoot || '.'
|
|
152
|
+
}/index.html"${docsActiveClass}>Docs</a><a href="${examplesHref}"${examplesActiveClass}>Examples</a><a href="https://github.com/reicek/NeatapticTS" target="_blank" rel="noopener">GitHub</a></nav></div></header>\n<div class="layout"><aside class="sidebar docs-panel docs-panel-left">${sidebarHtml}</aside><main class="content docs-panel docs-panel-center">${htmlBody}<footer class="site-footer">Generated from source JSDoc • <a href="https://github.com/reicek/NeatapticTS">GitHub</a></footer></main><aside class="toc docs-panel docs-panel-right">${tocHtml}</aside></div>${buildDocsLayoutInteractionScript()}${mermaidBootstrapScript}</body></html>`;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** Extracts file and symbol headings for the page table of contents. */
|
|
156
|
+
function extractFileHeadings(markdown: string): TocFileHeading[] {
|
|
157
|
+
const fileHeadings: TocFileHeading[] = [];
|
|
158
|
+
const markdownLines = markdown.split(/\r?\n/);
|
|
159
|
+
let currentFileHeading: TocFileHeading | null = null;
|
|
160
|
+
|
|
161
|
+
for (const markdownLine of markdownLines) {
|
|
162
|
+
const fileMatch = /^##\s+(.+\.ts)\s*$/.exec(markdownLine);
|
|
163
|
+
if (fileMatch) {
|
|
164
|
+
const fileName = fileMatch[1];
|
|
165
|
+
currentFileHeading = {
|
|
166
|
+
file: fileName,
|
|
167
|
+
anchor: slugify(fileName),
|
|
168
|
+
symbols: [],
|
|
169
|
+
};
|
|
170
|
+
fileHeadings.push(currentFileHeading);
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const symbolMatch = /^###\s+(.+)\s*$/.exec(markdownLine);
|
|
175
|
+
if (symbolMatch && currentFileHeading) {
|
|
176
|
+
const symbolName = symbolMatch[1];
|
|
177
|
+
currentFileHeading.symbols.push({
|
|
178
|
+
name: symbolName,
|
|
179
|
+
anchor: slugify(symbolName),
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return fileHeadings;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** Renders markdown to HTML with stable heading ids and Mermaid placeholders. */
|
|
188
|
+
function renderMarkdownToHtml(markdown: string): {
|
|
189
|
+
htmlBody: string;
|
|
190
|
+
hasMermaidDiagram: boolean;
|
|
191
|
+
} {
|
|
192
|
+
const renderer = new marked.Renderer();
|
|
193
|
+
const originalCodeRenderer = renderer.code?.bind(renderer);
|
|
194
|
+
let hasMermaidDiagram = false;
|
|
195
|
+
|
|
196
|
+
(renderer as unknown as { heading: (token: any) => string }).heading = ({
|
|
197
|
+
text,
|
|
198
|
+
depth,
|
|
199
|
+
raw,
|
|
200
|
+
}: any) => {
|
|
201
|
+
const source = (raw ?? text ?? '')
|
|
202
|
+
.toString()
|
|
203
|
+
.replace(/<[^>]+>/g, '')
|
|
204
|
+
.trim();
|
|
205
|
+
const id = slugify(source);
|
|
206
|
+
return `<h${depth} id="${id}">${text}</h${depth}>`;
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
(renderer as unknown as { code: (token: any) => string }).code = (
|
|
210
|
+
token: any,
|
|
211
|
+
) => {
|
|
212
|
+
const { text, lang } = token;
|
|
213
|
+
if ((lang ?? '').toString().trim().toLowerCase() === 'mermaid') {
|
|
214
|
+
hasMermaidDiagram = true;
|
|
215
|
+
return renderMermaidDiagramPlaceholder((text ?? '').toString());
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return originalCodeRenderer?.(token) ?? '';
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
return {
|
|
222
|
+
htmlBody: marked.parse(markdown, { async: false, renderer }),
|
|
223
|
+
hasMermaidDiagram,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/** Builds the right-rail TOC for one page. */
|
|
228
|
+
function buildPageTocHtml(
|
|
229
|
+
currentDir: string,
|
|
230
|
+
fileHeadings: readonly TocFileHeading[],
|
|
231
|
+
rootExamplesTocHtml: string,
|
|
232
|
+
): string {
|
|
233
|
+
if (fileHeadings.length > 0) {
|
|
234
|
+
return `<div class="page-toc"><h2>Files</h2>${fileHeadings
|
|
235
|
+
.map(
|
|
236
|
+
(fileHeading) =>
|
|
237
|
+
`<div class="toc-file"><a href="#${fileHeading.anchor}">${fileHeading.file}</a>${
|
|
238
|
+
fileHeading.symbols.length > 0
|
|
239
|
+
? `<ul>${fileHeading.symbols
|
|
240
|
+
.map(
|
|
241
|
+
(symbol) =>
|
|
242
|
+
`<li><a href="#${symbol.anchor}">${symbol.name}</a></li>`,
|
|
243
|
+
)
|
|
244
|
+
.join('')}</ul>`
|
|
245
|
+
: ''
|
|
246
|
+
}</div>`,
|
|
247
|
+
)
|
|
248
|
+
.join('')}</div>`;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (currentDir === '' && rootExamplesTocHtml) {
|
|
252
|
+
return `<div class="page-toc"><h2>Examples</h2><div class="toc-file">${rootExamplesTocHtml}</div></div>`;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return '';
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/** Keeps the panel-expansion interaction script out of the root entrypoint. */
|
|
259
|
+
function buildDocsLayoutInteractionScript(): string {
|
|
260
|
+
return `<script>
|
|
261
|
+
const docsPanels = Array.from(document.querySelectorAll('.docs-panel'));
|
|
262
|
+
|
|
263
|
+
function setActiveDocsPanel(nextPanel) {
|
|
264
|
+
docsPanels.forEach((panel) => {
|
|
265
|
+
panel.classList.toggle('is-active', panel === nextPanel);
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
document.addEventListener('click', (event) => {
|
|
270
|
+
const target = event.target;
|
|
271
|
+
if (!(target instanceof Element)) {
|
|
272
|
+
setActiveDocsPanel(null);
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const activePanel = target.closest('.docs-panel');
|
|
277
|
+
setActiveDocsPanel(activePanel instanceof HTMLElement ? activePanel : null);
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
document.addEventListener('keydown', (event) => {
|
|
281
|
+
if (event.key === 'Escape') {
|
|
282
|
+
setActiveDocsPanel(null);
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
</script>`;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/** Returns the first markdown heading or undefined when absent. */
|
|
289
|
+
function mdTitle(markdown: string): string | undefined {
|
|
290
|
+
return markdown.match(/^#\s+(.+)$/m)?.[1];
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/** Narrows unknown errors to retryable filesystem failures. */
|
|
294
|
+
function isRetriableFileSystemError(
|
|
295
|
+
error: unknown,
|
|
296
|
+
): error is NodeJS.ErrnoException {
|
|
297
|
+
if (!(error instanceof Error)) {
|
|
298
|
+
return false;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const code = (error as NodeJS.ErrnoException).code;
|
|
302
|
+
return Boolean(code && RETRIABLE_FILE_SYSTEM_ERROR_CODES.has(code));
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/** Writes a page file with bounded retry logic for transient Windows locks. */
|
|
306
|
+
async function writeFileWithRetry(
|
|
307
|
+
filePath: string,
|
|
308
|
+
content: string,
|
|
309
|
+
encoding: BufferEncoding,
|
|
310
|
+
): Promise<void> {
|
|
311
|
+
let retryDelayMilliseconds = DOCS_WRITE_INITIAL_RETRY_DELAY_MS;
|
|
312
|
+
|
|
313
|
+
for (
|
|
314
|
+
let attemptNumber = 1;
|
|
315
|
+
attemptNumber <= DOCS_WRITE_MAX_ATTEMPTS;
|
|
316
|
+
attemptNumber += 1
|
|
317
|
+
) {
|
|
318
|
+
try {
|
|
319
|
+
await fs.writeFile(filePath, content, encoding);
|
|
320
|
+
return;
|
|
321
|
+
} catch (error: unknown) {
|
|
322
|
+
const canRetry =
|
|
323
|
+
isRetriableFileSystemError(error) &&
|
|
324
|
+
attemptNumber < DOCS_WRITE_MAX_ATTEMPTS;
|
|
325
|
+
if (!canRetry) {
|
|
326
|
+
throw error;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
await setTimeout(retryDelayMilliseconds);
|
|
330
|
+
retryDelayMilliseconds = Math.min(retryDelayMilliseconds * 2, 1_000);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Shared path, link, and escaping helpers for generated docs rendering.
|
|
3
|
+
*
|
|
4
|
+
* Navigation, page rendering, and content-link rewriting all depend on the
|
|
5
|
+
* same docs-relative and repo-relative path semantics. Centralizing them here
|
|
6
|
+
* keeps those rules consistent across the folder boundary.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import path from 'path';
|
|
10
|
+
import fs from 'fs-extra';
|
|
11
|
+
|
|
12
|
+
export const DOCS_DIR = path.resolve('docs');
|
|
13
|
+
|
|
14
|
+
const GITHUB_REPOSITORY_BLOB_BASE_URL =
|
|
15
|
+
'https://github.com/reicek/NeatapticTS/blob/develop';
|
|
16
|
+
const GITHUB_REPOSITORY_TREE_BASE_URL =
|
|
17
|
+
'https://github.com/reicek/NeatapticTS/tree/develop';
|
|
18
|
+
|
|
19
|
+
/** Escapes text for safe HTML insertion. */
|
|
20
|
+
export function escapeHtml(value: string): string {
|
|
21
|
+
return value
|
|
22
|
+
.replace(/&/g, '&')
|
|
23
|
+
.replace(/</g, '<')
|
|
24
|
+
.replace(/>/g, '>')
|
|
25
|
+
.replace(/"/g, '"')
|
|
26
|
+
.replace(/'/g, ''');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Creates deterministic heading ids and anchor slugs. */
|
|
30
|
+
export function slugify(value: string): string {
|
|
31
|
+
return value
|
|
32
|
+
.toLowerCase()
|
|
33
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
34
|
+
.replace(/^-+|-+$/g, '')
|
|
35
|
+
.replace(/-{2,}/g, '-');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Builds a docs-relative link between two generated page directories. */
|
|
39
|
+
export function buildRelativeDocsHref(
|
|
40
|
+
currentDir: string,
|
|
41
|
+
targetDir: string,
|
|
42
|
+
): string {
|
|
43
|
+
const relativeLink =
|
|
44
|
+
path.posix.relative(currentDir || '.', targetDir || '.') || '.';
|
|
45
|
+
return `${relativeLink === '.' ? '.' : relativeLink}/index.html`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Checks whether a generated docs page exists or will be emitted in this run. */
|
|
49
|
+
export function hasPublishedDocsPage(
|
|
50
|
+
relDir: string,
|
|
51
|
+
generatedPageDirectories: ReadonlySet<string>,
|
|
52
|
+
): boolean {
|
|
53
|
+
if (generatedPageDirectories.has(relDir)) {
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return fs.existsSync(path.resolve(DOCS_DIR, relDir, 'index.html'));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Returns true for protocol, protocol-relative, or same-page anchor hrefs. */
|
|
61
|
+
export function isExternalOrAnchorHref(href: string): boolean {
|
|
62
|
+
return (
|
|
63
|
+
/^[a-z][a-z0-9+.-]*:/i.test(href) ||
|
|
64
|
+
href.startsWith('//') ||
|
|
65
|
+
href.startsWith('#')
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Normalizes a repo-relative path to portable forward-slash form. */
|
|
70
|
+
export function normalizeRepoRelativeHref(href: string): string {
|
|
71
|
+
return href
|
|
72
|
+
.trim()
|
|
73
|
+
.replace(/\\/g, '/')
|
|
74
|
+
.replace(/^\.\//, '')
|
|
75
|
+
.replace(/^\//, '')
|
|
76
|
+
.replace(/\/{2,}/g, '/');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Rewrites markdown-generated content links so docs pages link to compiled docs
|
|
81
|
+
* or repository URLs instead of raw unresolved markdown-relative paths.
|
|
82
|
+
*/
|
|
83
|
+
export function rewriteCompiledDocsContentLinks(
|
|
84
|
+
htmlBody: string,
|
|
85
|
+
currentDir: string,
|
|
86
|
+
generatedPageDirectories: ReadonlySet<string>,
|
|
87
|
+
): string {
|
|
88
|
+
return htmlBody.replace(
|
|
89
|
+
/\bhref=(['"])([^'"]+)\1/g,
|
|
90
|
+
(_match, quote: string, href: string) => {
|
|
91
|
+
return `href=${quote}${resolveCompiledDocsContentHref(
|
|
92
|
+
href,
|
|
93
|
+
currentDir,
|
|
94
|
+
generatedPageDirectories,
|
|
95
|
+
)}${quote}`;
|
|
96
|
+
},
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Resolves one content href to a compiled docs page or repository URL. */
|
|
101
|
+
function resolveCompiledDocsContentHref(
|
|
102
|
+
href: string,
|
|
103
|
+
currentDir: string,
|
|
104
|
+
generatedPageDirectories: ReadonlySet<string>,
|
|
105
|
+
): string {
|
|
106
|
+
if (!href || isExternalOrAnchorHref(href)) {
|
|
107
|
+
return href;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const docsRelativeTarget = normalizeDocsRelativeTarget(currentDir, href);
|
|
111
|
+
const docsTarget = resolveDocsTargetFromDocsRelativePath(docsRelativeTarget);
|
|
112
|
+
if (
|
|
113
|
+
docsTarget !== undefined &&
|
|
114
|
+
hasPublishedDocsPage(docsTarget, generatedPageDirectories)
|
|
115
|
+
) {
|
|
116
|
+
return buildRelativeDocsHref(currentDir, docsTarget);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const explicitRepositoryPath = resolveExplicitRepositoryPath(href);
|
|
120
|
+
if (explicitRepositoryPath) {
|
|
121
|
+
return resolveRepositoryPathHref(
|
|
122
|
+
explicitRepositoryPath,
|
|
123
|
+
currentDir,
|
|
124
|
+
generatedPageDirectories,
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const contextualRepositoryPath = resolveContextualRepositoryPath(
|
|
129
|
+
currentDir,
|
|
130
|
+
href,
|
|
131
|
+
);
|
|
132
|
+
if (contextualRepositoryPath) {
|
|
133
|
+
return resolveRepositoryPathHref(
|
|
134
|
+
contextualRepositoryPath,
|
|
135
|
+
currentDir,
|
|
136
|
+
generatedPageDirectories,
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return href;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Normalizes a docs-relative target from the current generated page directory. */
|
|
144
|
+
function normalizeDocsRelativeTarget(currentDir: string, href: string): string {
|
|
145
|
+
return normalizeRepoRelativeHref(
|
|
146
|
+
path.posix.normalize(path.posix.join(currentDir || '.', href)),
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** Resolves a docs page directory from a docs-relative markdown target. */
|
|
151
|
+
function resolveDocsTargetFromDocsRelativePath(
|
|
152
|
+
docsRelativePath: string,
|
|
153
|
+
): string | undefined {
|
|
154
|
+
const normalizedPath = docsRelativePath.replace(/\/$/, '');
|
|
155
|
+
if (normalizedPath === 'README.md') {
|
|
156
|
+
return '';
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (normalizedPath.endsWith('/README.md')) {
|
|
160
|
+
return normalizedPath.slice(0, -'/README.md'.length);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (normalizedPath.endsWith('/index.html')) {
|
|
164
|
+
return normalizedPath.slice(0, -'/index.html'.length);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (normalizedPath === 'index.html') {
|
|
168
|
+
return '';
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return normalizedPath.endsWith('.md') || normalizedPath.endsWith('.html')
|
|
172
|
+
? undefined
|
|
173
|
+
: normalizedPath;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Resolves repo-root hrefs that are already expressed as repository paths. */
|
|
177
|
+
function resolveExplicitRepositoryPath(href: string): string | undefined {
|
|
178
|
+
const normalizedHref = normalizeRepoRelativeHref(href);
|
|
179
|
+
if (!normalizedHref) {
|
|
180
|
+
return undefined;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (
|
|
184
|
+
normalizedHref === 'docs' ||
|
|
185
|
+
normalizedHref.startsWith('docs/') ||
|
|
186
|
+
normalizedHref === 'src' ||
|
|
187
|
+
normalizedHref.startsWith('src/') ||
|
|
188
|
+
normalizedHref === 'test/examples' ||
|
|
189
|
+
normalizedHref.startsWith('test/examples/')
|
|
190
|
+
) {
|
|
191
|
+
return normalizedHref;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return undefined;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Resolves repo-relative source paths for content links that are expressed
|
|
199
|
+
* relative to the current generated docs page.
|
|
200
|
+
*/
|
|
201
|
+
function resolveContextualRepositoryPath(
|
|
202
|
+
currentDir: string,
|
|
203
|
+
href: string,
|
|
204
|
+
): string | undefined {
|
|
205
|
+
const currentSourceDirectory = resolveCurrentSourceDirectory(currentDir);
|
|
206
|
+
if (!currentSourceDirectory) {
|
|
207
|
+
return undefined;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return normalizeRepoRelativeHref(
|
|
211
|
+
path.posix.normalize(path.posix.join(currentSourceDirectory, href)),
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/** Maps the current generated docs page back to its source directory. */
|
|
216
|
+
function resolveCurrentSourceDirectory(currentDir: string): string | undefined {
|
|
217
|
+
if (currentDir === '') {
|
|
218
|
+
return 'src';
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const exampleDocsMatch = /^examples\/([^/]+)\/docs(?:\/(.*))?$/.exec(
|
|
222
|
+
currentDir,
|
|
223
|
+
);
|
|
224
|
+
if (exampleDocsMatch) {
|
|
225
|
+
const [, exampleDirName, subpath = ''] = exampleDocsMatch;
|
|
226
|
+
return normalizeRepoRelativeHref(
|
|
227
|
+
path.posix.join('test/examples', exampleDirName, subpath),
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (currentDir === 'examples') {
|
|
232
|
+
return 'test/examples';
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (currentDir.startsWith('examples/')) {
|
|
236
|
+
return normalizeRepoRelativeHref(
|
|
237
|
+
currentDir.replace(/^examples\//, 'test/examples/'),
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return normalizeRepoRelativeHref(path.posix.join('src', currentDir));
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/** Resolves a repo path to a compiled docs href or GitHub repository URL. */
|
|
245
|
+
function resolveRepositoryPathHref(
|
|
246
|
+
repositoryPath: string,
|
|
247
|
+
currentDir: string,
|
|
248
|
+
generatedPageDirectories: ReadonlySet<string>,
|
|
249
|
+
): string {
|
|
250
|
+
const docsTarget =
|
|
251
|
+
resolvePublishedDocsTargetFromRepositoryPath(repositoryPath);
|
|
252
|
+
if (
|
|
253
|
+
docsTarget !== undefined &&
|
|
254
|
+
hasPublishedDocsPage(docsTarget, generatedPageDirectories)
|
|
255
|
+
) {
|
|
256
|
+
return buildRelativeDocsHref(currentDir, docsTarget);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return buildGitHubRepositoryHref(repositoryPath);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/** Maps a repo path to the matching generated docs page directory when one exists. */
|
|
263
|
+
function resolvePublishedDocsTargetFromRepositoryPath(
|
|
264
|
+
repositoryPath: string,
|
|
265
|
+
): string | undefined {
|
|
266
|
+
const normalizedPath = normalizeRepoRelativeHref(repositoryPath).replace(
|
|
267
|
+
/\/$/,
|
|
268
|
+
'',
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
if (normalizedPath === 'docs' || normalizedPath === 'docs/index.html') {
|
|
272
|
+
return '';
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (normalizedPath.startsWith('docs/')) {
|
|
276
|
+
return normalizedPath.slice('docs/'.length).replace(/\/index\.html$/, '');
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (
|
|
280
|
+
normalizedPath === 'src' ||
|
|
281
|
+
normalizedPath === 'src/' ||
|
|
282
|
+
normalizedPath === 'src/README.md'
|
|
283
|
+
) {
|
|
284
|
+
return '';
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (
|
|
288
|
+
normalizedPath.startsWith('src/') &&
|
|
289
|
+
normalizedPath.endsWith('/README.md')
|
|
290
|
+
) {
|
|
291
|
+
return normalizedPath.slice('src/'.length, -'/README.md'.length);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (
|
|
295
|
+
normalizedPath === 'test/examples' ||
|
|
296
|
+
normalizedPath === 'test/examples/'
|
|
297
|
+
) {
|
|
298
|
+
return 'examples';
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const exampleReadmeMatch = /^test\/examples\/([^/]+)\/README\.md$/.exec(
|
|
302
|
+
normalizedPath,
|
|
303
|
+
);
|
|
304
|
+
if (exampleReadmeMatch) {
|
|
305
|
+
return `examples/${exampleReadmeMatch[1]}/docs`;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const nestedExampleReadmeMatch =
|
|
309
|
+
/^test\/examples\/([^/]+)\/(.+)\/README\.md$/.exec(normalizedPath);
|
|
310
|
+
if (nestedExampleReadmeMatch) {
|
|
311
|
+
return `examples/${nestedExampleReadmeMatch[1]}/docs/${nestedExampleReadmeMatch[2]}`;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return undefined;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/** Builds a GitHub blob or tree link for a repo-relative path. */
|
|
318
|
+
function buildGitHubRepositoryHref(repoRelativePath: string): string {
|
|
319
|
+
const normalizedPath = normalizeRepoRelativeHref(repoRelativePath).replace(
|
|
320
|
+
/\/$/,
|
|
321
|
+
'',
|
|
322
|
+
);
|
|
323
|
+
const absoluteRepositoryPath = path.resolve(normalizedPath);
|
|
324
|
+
const pathExistsInRepository = fs.existsSync(absoluteRepositoryPath);
|
|
325
|
+
const urlBase =
|
|
326
|
+
pathExistsInRepository && fs.statSync(absoluteRepositoryPath).isFile()
|
|
327
|
+
? GITHUB_REPOSITORY_BLOB_BASE_URL
|
|
328
|
+
: path.posix.extname(normalizedPath)
|
|
329
|
+
? GITHUB_REPOSITORY_BLOB_BASE_URL
|
|
330
|
+
: GITHUB_REPOSITORY_TREE_BASE_URL;
|
|
331
|
+
|
|
332
|
+
return `${urlBase}/${normalizedPath}`;
|
|
333
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Shared contracts for the HTML docs renderer boundary.
|
|
3
|
+
*
|
|
4
|
+
* The renderer now spans page discovery, Mermaid validation, navigation, and
|
|
5
|
+
* asset bootstrapping. These types keep the public chapter interfaces aligned
|
|
6
|
+
* without pushing low-level implementation details back into the root script.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** Markdown page discovered under the generated docs tree. */
|
|
10
|
+
export interface PageMeta {
|
|
11
|
+
abs: string;
|
|
12
|
+
relDir: string;
|
|
13
|
+
title: string;
|
|
14
|
+
markdown: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** One Mermaid fenced block collected from a README surface. */
|
|
18
|
+
export interface MermaidBlockReference {
|
|
19
|
+
readmePath: string;
|
|
20
|
+
blockNumber: number;
|
|
21
|
+
diagram: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface SidebarRenderHelpers {
|
|
25
|
+
generatedPageDirectories: ReadonlySet<string>;
|
|
26
|
+
hasPublishedDocsPage: (
|
|
27
|
+
relDir: string,
|
|
28
|
+
generatedPageDirectories: ReadonlySet<string>,
|
|
29
|
+
) => boolean;
|
|
30
|
+
buildRelativeDocsHref: (currentDir: string, targetDir: string) => string;
|
|
31
|
+
escapeHtml: (value: string) => string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Shared render context for sidebar fragments. */
|
|
35
|
+
export interface SidebarRenderContext extends SidebarRenderHelpers {
|
|
36
|
+
currentDir: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Render context for the full docs sidebar. */
|
|
40
|
+
export interface DocsSidebarRenderContext extends SidebarRenderContext {
|
|
41
|
+
pages: readonly PageMeta[];
|
|
42
|
+
}
|