@ankhorage/paradox 0.0.5 → 0.0.7
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/CHANGELOG.md +16 -0
- package/README.md +141 -0
- package/dist/analyze/analyze.js +10 -0
- package/dist/analyze/badges.d.ts +6 -0
- package/dist/analyze/badges.js +256 -0
- package/dist/analyze/components.js +3 -0
- package/dist/analyze/exports.js +39 -9
- package/dist/analyze/modules.d.ts +9 -0
- package/dist/analyze/modules.js +44 -0
- package/dist/analyze/types.d.ts +48 -0
- package/dist/analyze/usage.d.ts +6 -0
- package/dist/analyze/utils/getExportMetadata.d.ts +12 -0
- package/dist/analyze/utils/getExportMetadata.js +162 -0
- package/dist/analyze/utils/getPropsFromType.js +3 -1
- package/dist/analyze/utils/parseParadoxComment.d.ts +2 -0
- package/dist/analyze/utils/parseParadoxComment.js +19 -1
- package/dist/cli.js +2 -2
- package/dist/model/buildModel.d.ts +46 -0
- package/dist/model/buildModel.js +55 -4
- package/dist/model/types.d.ts +48 -0
- package/dist/render/render.d.ts +3 -1
- package/dist/render/render.js +25 -87
- package/dist/render/renderers/badges.d.ts +6 -0
- package/dist/render/renderers/badges.js +35 -0
- package/dist/render/renderers/diagrams.d.ts +6 -0
- package/dist/render/renderers/diagrams.js +106 -0
- package/dist/render/renderers/html.d.ts +5 -0
- package/dist/render/renderers/html.js +317 -0
- package/dist/render/renderers/markdown.d.ts +5 -0
- package/dist/render/renderers/markdown.js +155 -0
- package/dist/render/types.d.ts +20 -0
- package/dist/write/write.js +12 -1
- package/package.json +1 -1
package/dist/render/render.js
CHANGED
|
@@ -1,95 +1,33 @@
|
|
|
1
|
+
import { renderBadgeArtifacts } from './renderers/badges.js';
|
|
2
|
+
import { renderDiagramArtifacts } from './renderers/diagrams.js';
|
|
3
|
+
import { renderHtml } from './renderers/html.js';
|
|
4
|
+
import { renderMarkdown } from './renderers/markdown.js';
|
|
1
5
|
/***
|
|
2
6
|
* Renders the documentation model into README and artifact files.
|
|
3
7
|
*/
|
|
4
|
-
export function render(model) {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
export function render(model, options = {}) {
|
|
9
|
+
const badges = renderBadgeArtifacts(model);
|
|
10
|
+
const diagrams = renderDiagramArtifacts(model);
|
|
11
|
+
const outputDir = options.outputDir ?? 'paradox';
|
|
12
|
+
const result = {
|
|
13
|
+
readme: '',
|
|
14
|
+
exportsMarkdown: '',
|
|
15
|
+
components: '',
|
|
9
16
|
exportsJson: `${JSON.stringify(model.exports, null, 2)}\n`,
|
|
10
17
|
paradoxJson: `${JSON.stringify(model, null, 2)}\n`,
|
|
18
|
+
indexHtml: '',
|
|
19
|
+
badges,
|
|
20
|
+
diagrams,
|
|
11
21
|
};
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
for (const command of model.usage.commands) {
|
|
22
|
-
lines.push(command.command);
|
|
23
|
-
}
|
|
24
|
-
lines.push('```', '');
|
|
25
|
-
}
|
|
26
|
-
if (model.config !== null) {
|
|
27
|
-
lines.push('## Configuration', '');
|
|
28
|
-
lines.push(`Create a \`${model.config.configFile}\` file:`, '');
|
|
29
|
-
lines.push('```ts');
|
|
30
|
-
if (model.config.factoryName !== null) {
|
|
31
|
-
lines.push(`import { ${model.config.factoryName} } from '${model.packageId}';`);
|
|
32
|
-
lines.push('');
|
|
33
|
-
lines.push(`export default ${model.config.factoryName}({`);
|
|
34
|
-
lines.push(' // ...');
|
|
35
|
-
lines.push('});');
|
|
36
|
-
}
|
|
37
|
-
else {
|
|
38
|
-
lines.push(`import type { ${model.config.exportName} } from '${model.packageId}';`);
|
|
39
|
-
lines.push('');
|
|
40
|
-
lines.push('const config = {');
|
|
41
|
-
lines.push(' // ...');
|
|
42
|
-
lines.push(`} satisfies ${model.config.exportName};`);
|
|
43
|
-
lines.push('');
|
|
44
|
-
lines.push('export default config;');
|
|
45
|
-
}
|
|
46
|
-
lines.push('```', '');
|
|
47
|
-
}
|
|
48
|
-
lines.push('## Path resolution', '');
|
|
49
|
-
lines.push('- Config discovery: searches upward from `process.cwd()` for `paradox.config.ts/js/mjs/cjs` (required; no fallback).');
|
|
50
|
-
lines.push('- Package root: defaults to the directory containing `paradox.config.*`; `package.root` (when relative) resolves relative to that directory.');
|
|
51
|
-
lines.push('- Output directory: defaults to `paradox/`; `output.dir` (when relative) resolves relative to the resolved package root and must stay inside it.');
|
|
52
|
-
lines.push('- Modes:');
|
|
53
|
-
lines.push(' - `safe`: writes generated artifacts only under the output directory');
|
|
54
|
-
lines.push(' - `write`: additionally updates `<packageRoot>/README.md`', '');
|
|
55
|
-
if (model.exports.length > 0) {
|
|
56
|
-
lines.push('## Public API', '');
|
|
57
|
-
for (const item of model.exports) {
|
|
58
|
-
lines.push(`### ${item.name}`, '');
|
|
59
|
-
lines.push(item.description ?? `\`${item.kind}\` export.`, '');
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
return `${lines.join('\n').trimEnd()}\n`;
|
|
63
|
-
}
|
|
64
|
-
function renderExports(model) {
|
|
65
|
-
const lines = ['# Public API', ''];
|
|
66
|
-
for (const item of model.exports) {
|
|
67
|
-
lines.push(`## ${item.name}`, '');
|
|
68
|
-
lines.push(`Kind: \`${item.kind}\``, '');
|
|
69
|
-
if (item.description) {
|
|
70
|
-
lines.push(item.description, '');
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
return `${lines.join('\n').trimEnd()}\n`;
|
|
74
|
-
}
|
|
75
|
-
function renderComponents(model) {
|
|
76
|
-
const lines = ['# Components', ''];
|
|
77
|
-
for (const component of model.components) {
|
|
78
|
-
lines.push(`## ${component.name}`, '');
|
|
79
|
-
if (component.description) {
|
|
80
|
-
lines.push(component.description, '');
|
|
81
|
-
}
|
|
82
|
-
if (component.props.length > 0) {
|
|
83
|
-
lines.push('| Prop | Type | Required | Description |');
|
|
84
|
-
lines.push('| --- | --- | --- | --- |');
|
|
85
|
-
for (const prop of component.props) {
|
|
86
|
-
lines.push(`| ${escapeTableCell(prop.name)} | \`${escapeTableCell(prop.type)}\` | ${prop.required ? 'yes' : 'no'} | ${escapeTableCell(prop.description ?? '')} |`);
|
|
87
|
-
}
|
|
88
|
-
lines.push('');
|
|
89
|
-
}
|
|
22
|
+
const context = {
|
|
23
|
+
model,
|
|
24
|
+
outputDir,
|
|
25
|
+
badges,
|
|
26
|
+
diagrams,
|
|
27
|
+
result,
|
|
28
|
+
};
|
|
29
|
+
for (const renderer of [renderMarkdown, renderHtml]) {
|
|
30
|
+
Object.assign(result, renderer(context));
|
|
90
31
|
}
|
|
91
|
-
return
|
|
92
|
-
}
|
|
93
|
-
function escapeTableCell(value) {
|
|
94
|
-
return value.replaceAll('|', '\\|');
|
|
32
|
+
return result;
|
|
95
33
|
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { DocumentationModel } from '../../model/types.js';
|
|
2
|
+
import type { BadgeArtifact } from '../types.js';
|
|
3
|
+
/***
|
|
4
|
+
* Renders deterministic local SVG badges for repository metadata.
|
|
5
|
+
*/
|
|
6
|
+
export declare function renderBadgeArtifacts(model: DocumentationModel): BadgeArtifact[];
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/***
|
|
2
|
+
* Renders deterministic local SVG badges for repository metadata.
|
|
3
|
+
*/
|
|
4
|
+
export function renderBadgeArtifacts(model) {
|
|
5
|
+
return model.badges.map((badge) => ({
|
|
6
|
+
path: `badges/${badge.id}.svg`,
|
|
7
|
+
content: renderBadgeSvg(badge),
|
|
8
|
+
}));
|
|
9
|
+
}
|
|
10
|
+
function renderBadgeSvg(badge) {
|
|
11
|
+
const labelWidth = getTextWidth(badge.label);
|
|
12
|
+
const valueWidth = getTextWidth(badge.value);
|
|
13
|
+
const totalWidth = labelWidth + valueWidth;
|
|
14
|
+
return [
|
|
15
|
+
`<svg xmlns="http://www.w3.org/2000/svg" width="${totalWidth}" height="20" role="img" aria-label="${escapeXml(`${badge.label}: ${badge.value}`)}">`,
|
|
16
|
+
`<title>${escapeXml(`${badge.label}: ${badge.value}`)}</title>`,
|
|
17
|
+
`<rect width="${labelWidth}" height="20" fill="#374151"/>`,
|
|
18
|
+
`<rect x="${labelWidth}" width="${valueWidth}" height="20" fill="#${badge.color}"/>`,
|
|
19
|
+
`<text x="${labelWidth / 2}" y="14" fill="#ffffff" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" font-size="11" text-anchor="middle">${escapeXml(badge.label)}</text>`,
|
|
20
|
+
`<text x="${labelWidth + valueWidth / 2}" y="14" fill="#ffffff" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" font-size="11" text-anchor="middle">${escapeXml(badge.value)}</text>`,
|
|
21
|
+
'</svg>',
|
|
22
|
+
'',
|
|
23
|
+
].join('\n');
|
|
24
|
+
}
|
|
25
|
+
function getTextWidth(value) {
|
|
26
|
+
return Math.max(38, value.length * 7 + 10);
|
|
27
|
+
}
|
|
28
|
+
function escapeXml(value) {
|
|
29
|
+
return value
|
|
30
|
+
.replaceAll('&', '&')
|
|
31
|
+
.replaceAll('<', '<')
|
|
32
|
+
.replaceAll('>', '>')
|
|
33
|
+
.replaceAll('"', '"')
|
|
34
|
+
.replaceAll("'", ''');
|
|
35
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { DocumentationModel } from '../../model/types.js';
|
|
2
|
+
import type { DiagramArtifact } from '../types.js';
|
|
3
|
+
/***
|
|
4
|
+
* Generates deterministic Mermaid diagrams for the documentation app.
|
|
5
|
+
*/
|
|
6
|
+
export declare function renderDiagramArtifacts(model: DocumentationModel): DiagramArtifact[];
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/***
|
|
2
|
+
* Generates deterministic Mermaid diagrams for the documentation app.
|
|
3
|
+
*/
|
|
4
|
+
export function renderDiagramArtifacts(model) {
|
|
5
|
+
return [
|
|
6
|
+
{
|
|
7
|
+
path: 'diagrams/architecture-overview.mmd',
|
|
8
|
+
title: 'Architecture overview',
|
|
9
|
+
content: renderArchitectureOverview(model),
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
path: 'diagrams/module-relationships.mmd',
|
|
13
|
+
title: 'Module relationships',
|
|
14
|
+
content: renderModuleRelationships(model),
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
path: 'diagrams/export-graph.mmd',
|
|
18
|
+
title: 'Export graph',
|
|
19
|
+
content: renderExportGraph(model),
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
path: 'diagrams/entrypoint-sequence.mmd',
|
|
23
|
+
title: 'Entrypoint sequence',
|
|
24
|
+
content: renderEntrypointSequence(model),
|
|
25
|
+
},
|
|
26
|
+
];
|
|
27
|
+
}
|
|
28
|
+
function renderArchitectureOverview(model) {
|
|
29
|
+
const lines = ['graph TD'];
|
|
30
|
+
const packageId = toMermaidId(`package-${model.packageId}`);
|
|
31
|
+
lines.push(` ${packageId}["${escapeLabel(model.packageName)}"]`);
|
|
32
|
+
for (const entrypoint of model.entrypoints) {
|
|
33
|
+
const entrypointId = toMermaidId(`entrypoint-${entrypoint}`);
|
|
34
|
+
lines.push(` ${entrypointId}["${escapeLabel(entrypoint)}"]`);
|
|
35
|
+
lines.push(` ${packageId} --> ${entrypointId}`);
|
|
36
|
+
}
|
|
37
|
+
for (const module of model.modules) {
|
|
38
|
+
const moduleId = toMermaidId(`module-${module.path}`);
|
|
39
|
+
lines.push(` ${moduleId}["${escapeLabel(module.path)}"]`);
|
|
40
|
+
if (!module.isEntrypoint) {
|
|
41
|
+
lines.push(` ${packageId} -.-> ${moduleId}`);
|
|
42
|
+
}
|
|
43
|
+
for (const dependency of module.dependencies) {
|
|
44
|
+
lines.push(` ${moduleId} --> ${toMermaidId(`module-${dependency}`)}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return `${lines.join('\n')}\n`;
|
|
48
|
+
}
|
|
49
|
+
function renderModuleRelationships(model) {
|
|
50
|
+
const lines = ['graph LR'];
|
|
51
|
+
for (const module of model.modules) {
|
|
52
|
+
lines.push(` ${toMermaidId(`module-${module.path}`)}["${escapeLabel(module.path)}"]`);
|
|
53
|
+
}
|
|
54
|
+
const edges = model.modules.flatMap((module) => module.dependencies.map((dependency) => ` ${toMermaidId(`module-${module.path}`)} --> ${toMermaidId(`module-${dependency}`)}`));
|
|
55
|
+
lines.push(...(edges.length > 0 ? edges : renderFallbackEdge(model.modules, 'module')));
|
|
56
|
+
return `${lines.join('\n')}\n`;
|
|
57
|
+
}
|
|
58
|
+
function renderExportGraph(model) {
|
|
59
|
+
const lines = ['graph LR'];
|
|
60
|
+
const exportedNames = new Set(model.exports.map((item) => item.name));
|
|
61
|
+
for (const module of model.modules) {
|
|
62
|
+
lines.push(` ${toMermaidId(`module-${module.path}`)}["${escapeLabel(module.path)}"]`);
|
|
63
|
+
}
|
|
64
|
+
for (const item of model.exports) {
|
|
65
|
+
const exportId = toMermaidId(`export-${item.name}`);
|
|
66
|
+
lines.push(` ${exportId}["${escapeLabel(item.name)}"]`);
|
|
67
|
+
lines.push(` ${toMermaidId(`module-${item.modulePath}`)} --> ${exportId}`);
|
|
68
|
+
for (const relatedSymbol of item.relatedSymbols.filter((symbol) => exportedNames.has(symbol))) {
|
|
69
|
+
lines.push(` ${exportId} -.-> ${toMermaidId(`export-${relatedSymbol}`)}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return `${lines.join('\n')}\n`;
|
|
73
|
+
}
|
|
74
|
+
function renderEntrypointSequence(model) {
|
|
75
|
+
const lines = ['sequenceDiagram'];
|
|
76
|
+
const participants = new Map();
|
|
77
|
+
for (const module of model.modules) {
|
|
78
|
+
participants.set(module.path, `participant ${toMermaidId(`participant-${module.path}`)} as ${module.path}`);
|
|
79
|
+
}
|
|
80
|
+
lines.push(...participants.values());
|
|
81
|
+
const interactions = model.modules.flatMap((module) => module.dependencies.map((dependency) => ` ${toMermaidId(`participant-${module.path}`)}->>${toMermaidId(`participant-${dependency}`)}: imports`));
|
|
82
|
+
if (interactions.length === 0) {
|
|
83
|
+
const packageId = toMermaidId(`participant-${model.packageId}`);
|
|
84
|
+
lines.push(` participant ${packageId} as ${model.packageName}`);
|
|
85
|
+
lines.push(` Note over ${packageId}: No internal module relationships detected.`);
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
lines.push(...interactions);
|
|
89
|
+
}
|
|
90
|
+
return `${lines.join('\n')}\n`;
|
|
91
|
+
}
|
|
92
|
+
function renderFallbackEdge(modules, prefix) {
|
|
93
|
+
if (modules.length === 0) {
|
|
94
|
+
return [' empty["No modules analyzed"]'];
|
|
95
|
+
}
|
|
96
|
+
return modules.slice(1).map((module, index) => {
|
|
97
|
+
const previous = modules[index];
|
|
98
|
+
return ` ${toMermaidId(`${prefix}-${previous.path}`)} -.-> ${toMermaidId(`${prefix}-${module.path}`)}`;
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
function toMermaidId(value) {
|
|
102
|
+
return value.replace(/[^A-Za-z0-9_]/g, '_');
|
|
103
|
+
}
|
|
104
|
+
function escapeLabel(value) {
|
|
105
|
+
return value.replaceAll('"', '"');
|
|
106
|
+
}
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
/***
|
|
2
|
+
* Renders a deterministic static HTML documentation app.
|
|
3
|
+
*/
|
|
4
|
+
export function renderHtml({ diagrams, model, }) {
|
|
5
|
+
const navigationItems = [
|
|
6
|
+
...model.exports.map((item) => ({
|
|
7
|
+
href: `#symbol-${toAnchorId(item.name)}`,
|
|
8
|
+
label: item.name,
|
|
9
|
+
meta: `${item.kind} • ${item.modulePath}`,
|
|
10
|
+
})),
|
|
11
|
+
...model.components.map((component) => ({
|
|
12
|
+
href: `#component-${toAnchorId(component.name)}`,
|
|
13
|
+
label: component.name,
|
|
14
|
+
meta: `component • ${component.modulePath}`,
|
|
15
|
+
})),
|
|
16
|
+
].sort((left, right) => left.label.localeCompare(right.label));
|
|
17
|
+
const exportsByModule = groupBy(model.exports, (item) => item.modulePath);
|
|
18
|
+
return {
|
|
19
|
+
indexHtml: `<!doctype html>
|
|
20
|
+
<html lang="en">
|
|
21
|
+
<head>
|
|
22
|
+
<meta charset="utf-8" />
|
|
23
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
24
|
+
<title>${escapeHtml(model.packageName)} • Paradox</title>
|
|
25
|
+
<style>
|
|
26
|
+
:root {
|
|
27
|
+
color-scheme: light;
|
|
28
|
+
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
29
|
+
background: #f5f7fb;
|
|
30
|
+
color: #11203b;
|
|
31
|
+
}
|
|
32
|
+
* { box-sizing: border-box; }
|
|
33
|
+
body { margin: 0; }
|
|
34
|
+
a { color: #2459d3; text-decoration: none; }
|
|
35
|
+
a:hover { text-decoration: underline; }
|
|
36
|
+
code, pre { font-family: "SFMono-Regular", ui-monospace, SFMono-Regular, Menlo, monospace; }
|
|
37
|
+
.layout {
|
|
38
|
+
display: grid;
|
|
39
|
+
grid-template-columns: minmax(16rem, 22rem) minmax(0, 1fr);
|
|
40
|
+
min-height: 100vh;
|
|
41
|
+
}
|
|
42
|
+
.sidebar {
|
|
43
|
+
padding: 1.5rem;
|
|
44
|
+
border-right: 1px solid #d8dfec;
|
|
45
|
+
background: #ffffff;
|
|
46
|
+
position: sticky;
|
|
47
|
+
top: 0;
|
|
48
|
+
max-height: 100vh;
|
|
49
|
+
overflow: auto;
|
|
50
|
+
}
|
|
51
|
+
.content {
|
|
52
|
+
padding: 2rem;
|
|
53
|
+
}
|
|
54
|
+
.panel, .item {
|
|
55
|
+
background: #ffffff;
|
|
56
|
+
border: 1px solid #d8dfec;
|
|
57
|
+
border-radius: 0.9rem;
|
|
58
|
+
padding: 1rem 1.2rem;
|
|
59
|
+
margin-bottom: 1rem;
|
|
60
|
+
box-shadow: 0 8px 24px rgba(17, 32, 59, 0.06);
|
|
61
|
+
}
|
|
62
|
+
.summary {
|
|
63
|
+
display: grid;
|
|
64
|
+
gap: 1rem;
|
|
65
|
+
grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
|
|
66
|
+
}
|
|
67
|
+
.summary strong {
|
|
68
|
+
display: block;
|
|
69
|
+
font-size: 1.5rem;
|
|
70
|
+
}
|
|
71
|
+
.search {
|
|
72
|
+
width: 100%;
|
|
73
|
+
padding: 0.75rem 0.9rem;
|
|
74
|
+
border-radius: 0.75rem;
|
|
75
|
+
border: 1px solid #c7d2e5;
|
|
76
|
+
margin: 1rem 0 1.5rem;
|
|
77
|
+
}
|
|
78
|
+
.nav-list, .meta-list {
|
|
79
|
+
list-style: none;
|
|
80
|
+
padding: 0;
|
|
81
|
+
margin: 0;
|
|
82
|
+
}
|
|
83
|
+
.nav-list li + li { margin-top: 0.8rem; }
|
|
84
|
+
.muted { color: #5e6d8c; }
|
|
85
|
+
.chips { display: flex; flex-wrap: wrap; gap: 0.5rem; padding: 0; list-style: none; }
|
|
86
|
+
.chip {
|
|
87
|
+
border-radius: 999px;
|
|
88
|
+
background: #e8eefb;
|
|
89
|
+
color: #24427a;
|
|
90
|
+
padding: 0.25rem 0.65rem;
|
|
91
|
+
font-size: 0.85rem;
|
|
92
|
+
}
|
|
93
|
+
table { width: 100%; border-collapse: collapse; margin-top: 0.8rem; }
|
|
94
|
+
th, td { padding: 0.6rem; border-bottom: 1px solid #e2e7f2; text-align: left; vertical-align: top; }
|
|
95
|
+
pre {
|
|
96
|
+
overflow: auto;
|
|
97
|
+
border-radius: 0.75rem;
|
|
98
|
+
background: #0f1729;
|
|
99
|
+
color: #e7edf7;
|
|
100
|
+
padding: 1rem;
|
|
101
|
+
}
|
|
102
|
+
.empty { font-style: italic; color: #6f7f9c; }
|
|
103
|
+
@media (max-width: 900px) {
|
|
104
|
+
.layout { grid-template-columns: 1fr; }
|
|
105
|
+
.sidebar { position: static; max-height: none; border-right: 0; border-bottom: 1px solid #d8dfec; }
|
|
106
|
+
.content { padding: 1rem; }
|
|
107
|
+
}
|
|
108
|
+
</style>
|
|
109
|
+
</head>
|
|
110
|
+
<body>
|
|
111
|
+
<div class="layout">
|
|
112
|
+
<aside class="sidebar">
|
|
113
|
+
<p class="muted">Generated with Paradox</p>
|
|
114
|
+
<h1>${escapeHtml(model.packageName)}</h1>
|
|
115
|
+
<p>${escapeHtml(model.description ?? 'Deterministic package documentation.')}</p>
|
|
116
|
+
<input id="search" class="search" type="search" placeholder="Search symbols, files, and metadata" />
|
|
117
|
+
<ul class="nav-list">
|
|
118
|
+
${navigationItems
|
|
119
|
+
.map((item) => `<li><a href="${escapeAttribute(item.href)}">${escapeHtml(item.label)}</a><div class="muted">${escapeHtml(item.meta)}</div></li>`)
|
|
120
|
+
.join('')}
|
|
121
|
+
</ul>
|
|
122
|
+
</aside>
|
|
123
|
+
<main class="content">
|
|
124
|
+
<section class="panel">
|
|
125
|
+
<h2>Package overview</h2>
|
|
126
|
+
<div class="summary">
|
|
127
|
+
<div><strong>${model.exports.length}</strong><span class="muted">public exports</span></div>
|
|
128
|
+
<div><strong>${model.components.length}</strong><span class="muted">components</span></div>
|
|
129
|
+
<div><strong>${model.modules.length}</strong><span class="muted">modules</span></div>
|
|
130
|
+
<div><strong>${model.entrypoints.length}</strong><span class="muted">entrypoints</span></div>
|
|
131
|
+
</div>
|
|
132
|
+
<h3>Entrypoints</h3>
|
|
133
|
+
<ul class="meta-list">
|
|
134
|
+
${model.entrypoints.map((entrypoint) => `<li><code>${escapeHtml(entrypoint)}</code></li>`).join('')}
|
|
135
|
+
</ul>
|
|
136
|
+
</section>
|
|
137
|
+
<section class="panel">
|
|
138
|
+
<h2>Modules</h2>
|
|
139
|
+
${model.modules.map(renderModuleCard).join('')}
|
|
140
|
+
</section>
|
|
141
|
+
<section class="panel">
|
|
142
|
+
<h2>Exports by module</h2>
|
|
143
|
+
${[...exportsByModule.entries()]
|
|
144
|
+
.map(([modulePath, exports]) => `
|
|
145
|
+
<section>
|
|
146
|
+
<h3>${escapeHtml(modulePath)}</h3>
|
|
147
|
+
${exports.map((item) => renderExportCard(item)).join('')}
|
|
148
|
+
</section>`)
|
|
149
|
+
.join('')}
|
|
150
|
+
</section>
|
|
151
|
+
<section class="panel">
|
|
152
|
+
<h2>Component registry</h2>
|
|
153
|
+
${model.components.length === 0 ? '<p class="empty">No components were detected.</p>' : model.components.map(renderComponentCard).join('')}
|
|
154
|
+
</section>
|
|
155
|
+
<section class="panel">
|
|
156
|
+
<h2>Diagrams</h2>
|
|
157
|
+
${diagrams.map(renderDiagramCard).join('')}
|
|
158
|
+
</section>
|
|
159
|
+
</main>
|
|
160
|
+
</div>
|
|
161
|
+
<script>
|
|
162
|
+
const search = document.getElementById('search');
|
|
163
|
+
const items = Array.from(document.querySelectorAll('[data-search]'));
|
|
164
|
+
search?.addEventListener('input', () => {
|
|
165
|
+
const value = search.value.trim().toLowerCase();
|
|
166
|
+
for (const item of items) {
|
|
167
|
+
const haystack = (item.getAttribute('data-search') || '').toLowerCase();
|
|
168
|
+
item.style.display = value === '' || haystack.includes(value) ? '' : 'none';
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
</script>
|
|
172
|
+
</body>
|
|
173
|
+
</html>
|
|
174
|
+
`,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
function renderModuleCard(module) {
|
|
178
|
+
return `<article class="item" data-search="${escapeAttribute([module.path, ...module.dependencies, ...module.exports].join(' '))}">
|
|
179
|
+
<h3>${escapeHtml(module.path)}</h3>
|
|
180
|
+
<p class="muted">${module.isEntrypoint ? 'Configured entrypoint' : 'Referenced module'}</p>
|
|
181
|
+
<p><strong>Dependencies:</strong> ${renderInlineCodeList(module.dependencies)}</p>
|
|
182
|
+
<p><strong>Exports:</strong> ${renderInlineCodeList(module.exports)}</p>
|
|
183
|
+
</article>`;
|
|
184
|
+
}
|
|
185
|
+
function renderExportCard(item) {
|
|
186
|
+
return `<article class="item" id="symbol-${toAnchorId(item.name)}" data-search="${escapeAttribute([
|
|
187
|
+
item.name,
|
|
188
|
+
item.kind,
|
|
189
|
+
item.modulePath,
|
|
190
|
+
item.description ?? '',
|
|
191
|
+
...item.relatedSymbols,
|
|
192
|
+
...item.signatures.map((signature) => signature.label),
|
|
193
|
+
].join(' '))}">
|
|
194
|
+
<h4>${escapeHtml(item.name)}</h4>
|
|
195
|
+
<p class="muted">${escapeHtml(item.kind)} • <code>${escapeHtml(item.sourceLocation.filePath)}:${item.sourceLocation.line}:${item.sourceLocation.column}</code></p>
|
|
196
|
+
${item.description ? `<p>${escapeHtml(item.description)}</p>` : ''}
|
|
197
|
+
<p><strong>Export paths:</strong> ${renderInlineCodeList(item.exportPaths)}</p>
|
|
198
|
+
<div><strong>Related symbols:</strong> ${item.relatedSymbols.length > 0 ? renderChipList(item.relatedSymbols) : '<span class="empty">None</span>'}</div>
|
|
199
|
+
${item.signatures.length > 0 ? renderSignatureBlock(item) : ''}
|
|
200
|
+
${item.members.length > 0 ? renderMemberTable(item) : ''}
|
|
201
|
+
</article>`;
|
|
202
|
+
}
|
|
203
|
+
function renderSignatureBlock(item) {
|
|
204
|
+
return item.signatures
|
|
205
|
+
.map((signature) => `<div>
|
|
206
|
+
<h5>Signature</h5>
|
|
207
|
+
<pre>${escapeHtml(signature.label)}</pre>
|
|
208
|
+
${signature.parameters.length > 0
|
|
209
|
+
? `<table>
|
|
210
|
+
<thead><tr><th>Parameter</th><th>Type</th><th>Required</th><th>Description</th></tr></thead>
|
|
211
|
+
<tbody>
|
|
212
|
+
${signature.parameters
|
|
213
|
+
.map((parameter) => `<tr>
|
|
214
|
+
<td><code>${escapeHtml(parameter.name)}</code></td>
|
|
215
|
+
<td><code>${escapeHtml(parameter.type)}</code></td>
|
|
216
|
+
<td>${parameter.required ? 'yes' : 'no'}</td>
|
|
217
|
+
<td>${escapeHtml(parameter.description ?? '')}</td>
|
|
218
|
+
</tr>`)
|
|
219
|
+
.join('')}
|
|
220
|
+
</tbody>
|
|
221
|
+
</table>`
|
|
222
|
+
: ''}
|
|
223
|
+
<p><strong>Returns:</strong> <code>${escapeHtml(signature.returnType ?? 'void')}</code>${signature.returnDescription ? ` — ${escapeHtml(signature.returnDescription)}` : ''}</p>
|
|
224
|
+
</div>`)
|
|
225
|
+
.join('');
|
|
226
|
+
}
|
|
227
|
+
function renderMemberTable(item) {
|
|
228
|
+
return `<table>
|
|
229
|
+
<thead><tr><th>Member</th><th>Kind</th><th>Type</th><th>Required</th><th>Description</th></tr></thead>
|
|
230
|
+
<tbody>
|
|
231
|
+
${item.members
|
|
232
|
+
.map((member) => `<tr>
|
|
233
|
+
<td><code>${escapeHtml(member.name)}</code></td>
|
|
234
|
+
<td>${escapeHtml(member.kind)}</td>
|
|
235
|
+
<td><code>${escapeHtml(member.type)}</code></td>
|
|
236
|
+
<td>${member.required ? 'yes' : 'no'}</td>
|
|
237
|
+
<td>${escapeHtml(member.description ?? '')}</td>
|
|
238
|
+
</tr>`)
|
|
239
|
+
.join('')}
|
|
240
|
+
</tbody>
|
|
241
|
+
</table>`;
|
|
242
|
+
}
|
|
243
|
+
function renderComponentCard(component) {
|
|
244
|
+
return `<article class="item" id="component-${toAnchorId(component.name)}" data-search="${escapeAttribute([
|
|
245
|
+
component.name,
|
|
246
|
+
component.modulePath,
|
|
247
|
+
component.description ?? '',
|
|
248
|
+
...component.props.map((prop) => `${prop.name} ${prop.type}`),
|
|
249
|
+
].join(' '))}">
|
|
250
|
+
<h3>${escapeHtml(component.name)}</h3>
|
|
251
|
+
<p class="muted"><code>${escapeHtml(component.sourceLocation.filePath)}:${component.sourceLocation.line}:${component.sourceLocation.column}</code></p>
|
|
252
|
+
${component.description ? `<p>${escapeHtml(component.description)}</p>` : ''}
|
|
253
|
+
<p><strong>Export paths:</strong> ${renderInlineCodeList(component.exportPaths)}</p>
|
|
254
|
+
<table>
|
|
255
|
+
<thead><tr><th>Prop</th><th>Type</th><th>Required</th><th>Description</th></tr></thead>
|
|
256
|
+
<tbody>
|
|
257
|
+
${component.props
|
|
258
|
+
.map((prop) => `<tr>
|
|
259
|
+
<td><code>${escapeHtml(prop.name)}</code></td>
|
|
260
|
+
<td><code>${escapeHtml(prop.type)}</code></td>
|
|
261
|
+
<td>${prop.required ? 'yes' : 'no'}</td>
|
|
262
|
+
<td>${escapeHtml(prop.description ?? '')}</td>
|
|
263
|
+
</tr>`)
|
|
264
|
+
.join('')}
|
|
265
|
+
</tbody>
|
|
266
|
+
</table>
|
|
267
|
+
</article>`;
|
|
268
|
+
}
|
|
269
|
+
function renderDiagramCard(diagram) {
|
|
270
|
+
return `<article class="item" data-search="${escapeAttribute(`${diagram.title} ${diagram.path}`)}">
|
|
271
|
+
<h3>${escapeHtml(diagram.title)}</h3>
|
|
272
|
+
<p class="muted"><code>${escapeHtml(diagram.path)}</code></p>
|
|
273
|
+
<pre>${escapeHtml(diagram.content)}</pre>
|
|
274
|
+
</article>`;
|
|
275
|
+
}
|
|
276
|
+
function renderInlineCodeList(values) {
|
|
277
|
+
if (values.length === 0) {
|
|
278
|
+
return '<span class="empty">None</span>';
|
|
279
|
+
}
|
|
280
|
+
return values.map((value) => `<code>${escapeHtml(value)}</code>`).join(', ');
|
|
281
|
+
}
|
|
282
|
+
function renderChipList(values) {
|
|
283
|
+
return `<ul class="chips">${values
|
|
284
|
+
.map((value) => `<li class="chip">${escapeHtml(value)}</li>`)
|
|
285
|
+
.join('')}</ul>`;
|
|
286
|
+
}
|
|
287
|
+
function groupBy(items, key) {
|
|
288
|
+
const groups = new Map();
|
|
289
|
+
for (const item of items) {
|
|
290
|
+
const groupKey = key(item);
|
|
291
|
+
const group = groups.get(groupKey);
|
|
292
|
+
if (group) {
|
|
293
|
+
group.push(item);
|
|
294
|
+
}
|
|
295
|
+
else {
|
|
296
|
+
groups.set(groupKey, [item]);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
return new Map([...groups.entries()].sort(([left], [right]) => left.localeCompare(right)));
|
|
300
|
+
}
|
|
301
|
+
function toAnchorId(value) {
|
|
302
|
+
return value
|
|
303
|
+
.toLowerCase()
|
|
304
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
305
|
+
.replace(/^-|-$/g, '');
|
|
306
|
+
}
|
|
307
|
+
function escapeHtml(value) {
|
|
308
|
+
return value
|
|
309
|
+
.replaceAll('&', '&')
|
|
310
|
+
.replaceAll('<', '<')
|
|
311
|
+
.replaceAll('>', '>')
|
|
312
|
+
.replaceAll('"', '"')
|
|
313
|
+
.replaceAll("'", ''');
|
|
314
|
+
}
|
|
315
|
+
function escapeAttribute(value) {
|
|
316
|
+
return escapeHtml(value).replaceAll('\n', ' ');
|
|
317
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { RenderContext } from '../types.js';
|
|
2
|
+
/***
|
|
3
|
+
* Renders markdown artifacts from the documentation model.
|
|
4
|
+
*/
|
|
5
|
+
export declare function renderMarkdown({ badges, diagrams, model, outputDir, }: RenderContext): Pick<RenderContext['result'], 'components' | 'exportsMarkdown' | 'readme'>;
|