@ankhorage/paradox 0.0.6 → 0.0.8
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 +12 -0
- package/dist/analyze/analyze.js +3 -0
- package/dist/analyze/badges.d.ts +6 -0
- package/dist/analyze/badges.js +256 -0
- package/dist/analyze/types.d.ts +7 -0
- package/dist/analyze/usage.d.ts +6 -0
- package/dist/model/buildModel.d.ts +6 -0
- package/dist/model/buildModel.js +6 -0
- package/dist/model/types.d.ts +7 -0
- package/dist/render/render.js +4 -0
- package/dist/render/renderers/badges.d.ts +6 -0
- package/dist/render/renderers/badges.js +35 -0
- package/dist/render/renderers/html.js +11 -1
- package/dist/render/renderers/markdown.d.ts +1 -1
- package/dist/render/renderers/markdown.js +17 -3
- package/dist/render/types.d.ts +6 -0
- package/dist/write/write.js +5 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.0.8
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 46fd38d: Render Mermaid diagrams visually in the generated static documentation app.
|
|
8
|
+
|
|
9
|
+
The generated `paradox/index.html` now loads Mermaid in the browser, renders diagram blocks visually, and keeps the raw Mermaid source available in a collapsible section for offline fallback, copying, and debugging.
|
|
10
|
+
|
|
11
|
+
## 0.0.7
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- 5246138: Generate deterministic README quality badges from local repository metadata.
|
|
16
|
+
|
|
17
|
+
Paradox now computes README badge metadata from local package, TypeScript, linting, formatting, workflow, test, coverage, and documentation signals, then renders stable local SVG badge assets under `paradox/badges/` and includes them in generated README output without relying on external badge services.
|
|
18
|
+
|
|
3
19
|
## 0.0.6
|
|
4
20
|
|
|
5
21
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# @ankhorage/paradox
|
|
2
2
|
|
|
3
|
+
        
|
|
4
|
+
|
|
3
5
|
Deterministic documentation generator for TypeScript packages.
|
|
4
6
|
|
|
5
7
|
## Usage
|
|
@@ -39,6 +41,7 @@ graph TD
|
|
|
39
41
|
package__ankhorage_paradox --> entrypoint_src_index_ts
|
|
40
42
|
module_src_analyze_analyze_ts["src/analyze/analyze.ts"]
|
|
41
43
|
package__ankhorage_paradox -.-> module_src_analyze_analyze_ts
|
|
44
|
+
module_src_analyze_analyze_ts --> module_src_analyze_badges_ts
|
|
42
45
|
module_src_analyze_analyze_ts --> module_src_analyze_components_ts
|
|
43
46
|
module_src_analyze_analyze_ts --> module_src_analyze_exports_ts
|
|
44
47
|
module_src_analyze_analyze_ts --> module_src_analyze_modules_ts
|
|
@@ -46,6 +49,10 @@ graph TD
|
|
|
46
49
|
module_src_analyze_analyze_ts --> module_src_analyze_types_ts
|
|
47
50
|
module_src_analyze_analyze_ts --> module_src_analyze_usage_ts
|
|
48
51
|
module_src_analyze_analyze_ts --> module_src_config_types_ts
|
|
52
|
+
module_src_analyze_badges_ts["src/analyze/badges.ts"]
|
|
53
|
+
package__ankhorage_paradox -.-> module_src_analyze_badges_ts
|
|
54
|
+
module_src_analyze_badges_ts --> module_src_analyze_types_ts
|
|
55
|
+
module_src_analyze_badges_ts --> module_src_analyze_usage_ts
|
|
49
56
|
module_src_analyze_components_ts["src/analyze/components.ts"]
|
|
50
57
|
package__ankhorage_paradox -.-> module_src_analyze_components_ts
|
|
51
58
|
module_src_analyze_components_ts --> module_src_analyze_types_ts
|
|
@@ -113,10 +120,15 @@ graph TD
|
|
|
113
120
|
module_src_render_render_ts["src/render/render.ts"]
|
|
114
121
|
package__ankhorage_paradox -.-> module_src_render_render_ts
|
|
115
122
|
module_src_render_render_ts --> module_src_model_types_ts
|
|
123
|
+
module_src_render_render_ts --> module_src_render_renderers_badges_ts
|
|
116
124
|
module_src_render_render_ts --> module_src_render_renderers_diagrams_ts
|
|
117
125
|
module_src_render_render_ts --> module_src_render_renderers_html_ts
|
|
118
126
|
module_src_render_render_ts --> module_src_render_renderers_markdown_ts
|
|
119
127
|
module_src_render_render_ts --> module_src_render_types_ts
|
|
128
|
+
module_src_render_renderers_badges_ts["src/render/renderers/badges.ts"]
|
|
129
|
+
package__ankhorage_paradox -.-> module_src_render_renderers_badges_ts
|
|
130
|
+
module_src_render_renderers_badges_ts --> module_src_model_types_ts
|
|
131
|
+
module_src_render_renderers_badges_ts --> module_src_render_types_ts
|
|
120
132
|
module_src_render_renderers_diagrams_ts["src/render/renderers/diagrams.ts"]
|
|
121
133
|
package__ankhorage_paradox -.-> module_src_render_renderers_diagrams_ts
|
|
122
134
|
module_src_render_renderers_diagrams_ts --> module_src_model_types_ts
|
package/dist/analyze/analyze.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
|
+
import { analyzeBadges } from './badges.js';
|
|
3
4
|
import { analyzeComponents } from './components.js';
|
|
4
5
|
import { analyzeExports } from './exports.js';
|
|
5
6
|
import { analyzeModules } from './modules.js';
|
|
@@ -12,6 +13,7 @@ export async function analyze(config, runtime) {
|
|
|
12
13
|
const root = runtime.packageRoot;
|
|
13
14
|
const pkg = await readPackageJson(root);
|
|
14
15
|
const usage = createUsageFromPackageJson(pkg);
|
|
16
|
+
const badges = await analyzeBadges(root, pkg);
|
|
15
17
|
const project = createProject(root);
|
|
16
18
|
const entrypoints = config.package?.entrypoints ?? ['src/index.ts'];
|
|
17
19
|
const { config: configMetadata, exports } = analyzeExports(project, {
|
|
@@ -31,6 +33,7 @@ export async function analyze(config, runtime) {
|
|
|
31
33
|
components,
|
|
32
34
|
entrypoints: entrypoints.map((entrypoint) => entrypoint.replaceAll('\\', '/')).sort(),
|
|
33
35
|
modules,
|
|
36
|
+
badges,
|
|
34
37
|
usage,
|
|
35
38
|
config: configMetadata,
|
|
36
39
|
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { AnalysisBadge } from './types.js';
|
|
2
|
+
import type { PackageJsonModel } from './usage.js';
|
|
3
|
+
/***
|
|
4
|
+
* Derives deterministic repository metadata badges from local repository files.
|
|
5
|
+
*/
|
|
6
|
+
export declare function analyzeBadges(root: string, pkg: PackageJsonModel): Promise<AnalysisBadge[]>;
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { access, readdir, readFile } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
const ESLINT_CONFIG_FILES = [
|
|
4
|
+
'eslint.config.js',
|
|
5
|
+
'eslint.config.mjs',
|
|
6
|
+
'eslint.config.cjs',
|
|
7
|
+
'eslint.config.ts',
|
|
8
|
+
'.eslintrc',
|
|
9
|
+
'.eslintrc.js',
|
|
10
|
+
'.eslintrc.cjs',
|
|
11
|
+
'.eslintrc.json',
|
|
12
|
+
'.eslintrc.yaml',
|
|
13
|
+
'.eslintrc.yml',
|
|
14
|
+
];
|
|
15
|
+
const PRETTIER_CONFIG_FILES = [
|
|
16
|
+
'.prettierrc',
|
|
17
|
+
'.prettierrc.js',
|
|
18
|
+
'.prettierrc.cjs',
|
|
19
|
+
'.prettierrc.json',
|
|
20
|
+
'.prettierrc.yaml',
|
|
21
|
+
'.prettierrc.yml',
|
|
22
|
+
'prettier.config.js',
|
|
23
|
+
'prettier.config.cjs',
|
|
24
|
+
'prettier.config.mjs',
|
|
25
|
+
'prettier.config.ts',
|
|
26
|
+
];
|
|
27
|
+
const BADGE_ORDER = [
|
|
28
|
+
'license',
|
|
29
|
+
'npm',
|
|
30
|
+
'runtime',
|
|
31
|
+
'typescript',
|
|
32
|
+
'eslint',
|
|
33
|
+
'prettier',
|
|
34
|
+
'build',
|
|
35
|
+
'tests',
|
|
36
|
+
'coverage',
|
|
37
|
+
'docs',
|
|
38
|
+
];
|
|
39
|
+
/***
|
|
40
|
+
* Derives deterministic repository metadata badges from local repository files.
|
|
41
|
+
*/
|
|
42
|
+
export async function analyzeBadges(root, pkg) {
|
|
43
|
+
const workflowFiles = await readWorkflowFiles(root);
|
|
44
|
+
const badges = [];
|
|
45
|
+
if ((await hasAnyFile(root, ESLINT_CONFIG_FILES)) ||
|
|
46
|
+
pkg.eslintConfig !== undefined ||
|
|
47
|
+
scriptContains(pkg, 'lint', 'eslint')) {
|
|
48
|
+
badges.push({
|
|
49
|
+
id: 'eslint',
|
|
50
|
+
label: 'eslint',
|
|
51
|
+
value: workflowRunsScript(workflowFiles, 'lint') ? 'checked' : 'configured',
|
|
52
|
+
color: workflowRunsScript(workflowFiles, 'lint') ? '0a7f3f' : '2563eb',
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
if ((await hasAnyFile(root, PRETTIER_CONFIG_FILES)) ||
|
|
56
|
+
pkg.prettier !== undefined ||
|
|
57
|
+
scriptContains(pkg, 'format', 'prettier') ||
|
|
58
|
+
scriptContains(pkg, 'format:check', 'prettier')) {
|
|
59
|
+
const prettierChecked = workflowRunsScript(workflowFiles, 'format') ||
|
|
60
|
+
workflowRunsScript(workflowFiles, 'format:check');
|
|
61
|
+
badges.push({
|
|
62
|
+
id: 'prettier',
|
|
63
|
+
label: 'prettier',
|
|
64
|
+
value: prettierChecked ? 'checked' : 'configured',
|
|
65
|
+
color: prettierChecked ? '0a7f3f' : '2563eb',
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
if (hasScript(pkg, 'build')) {
|
|
69
|
+
const buildChecked = workflowRunsScript(workflowFiles, 'build');
|
|
70
|
+
badges.push({
|
|
71
|
+
id: 'build',
|
|
72
|
+
label: 'build',
|
|
73
|
+
value: buildChecked ? 'checked' : 'configured',
|
|
74
|
+
color: buildChecked ? '0a7f3f' : '6f42c1',
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
if (hasScript(pkg, 'test')) {
|
|
78
|
+
const testsChecked = workflowRunsScript(workflowFiles, 'test');
|
|
79
|
+
badges.push({
|
|
80
|
+
id: 'tests',
|
|
81
|
+
label: 'tests',
|
|
82
|
+
value: testsChecked ? 'checked' : 'configured',
|
|
83
|
+
color: testsChecked ? '0a7f3f' : '6f42c1',
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
if (await hasTypeScriptStrictMode(root)) {
|
|
87
|
+
badges.push({
|
|
88
|
+
id: 'typescript',
|
|
89
|
+
label: 'typescript',
|
|
90
|
+
value: 'strict',
|
|
91
|
+
color: '2563eb',
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
if (supportsBun(pkg, workflowFiles)) {
|
|
95
|
+
badges.push({
|
|
96
|
+
id: 'runtime',
|
|
97
|
+
label: 'runtime',
|
|
98
|
+
value: 'bun',
|
|
99
|
+
color: 'f59e0b',
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
const coverage = await readCoveragePercent(root);
|
|
103
|
+
if (coverage !== null) {
|
|
104
|
+
badges.push({
|
|
105
|
+
id: 'coverage',
|
|
106
|
+
label: 'coverage',
|
|
107
|
+
value: `${formatPercent(coverage)}%`,
|
|
108
|
+
color: getCoverageColor(coverage),
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
if (typeof pkg.license === 'string' && pkg.license.length > 0) {
|
|
112
|
+
badges.push({
|
|
113
|
+
id: 'license',
|
|
114
|
+
label: 'license',
|
|
115
|
+
value: pkg.license,
|
|
116
|
+
color: '2563eb',
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
if (typeof pkg.version === 'string' && pkg.version.length > 0) {
|
|
120
|
+
badges.push({
|
|
121
|
+
id: 'npm',
|
|
122
|
+
label: 'npm',
|
|
123
|
+
value: `v${pkg.version}`,
|
|
124
|
+
color: 'cb3837',
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
badges.push({
|
|
128
|
+
id: 'docs',
|
|
129
|
+
label: 'docs',
|
|
130
|
+
value: 'paradox',
|
|
131
|
+
color: '0f766e',
|
|
132
|
+
});
|
|
133
|
+
return sortBadges(badges);
|
|
134
|
+
}
|
|
135
|
+
async function hasAnyFile(root, paths) {
|
|
136
|
+
for (const relativePath of paths) {
|
|
137
|
+
if (await fileExists(join(root, relativePath))) {
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
async function hasTypeScriptStrictMode(root) {
|
|
144
|
+
const tsconfig = await readJsonFile(join(root, 'tsconfig.json'));
|
|
145
|
+
return tsconfig?.compilerOptions?.strict === true;
|
|
146
|
+
}
|
|
147
|
+
async function readCoveragePercent(root) {
|
|
148
|
+
const coverage = await readJsonFile(join(root, 'coverage', 'coverage-summary.json'));
|
|
149
|
+
const value = coverage?.total?.lines?.pct;
|
|
150
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : null;
|
|
151
|
+
}
|
|
152
|
+
function formatPercent(value) {
|
|
153
|
+
return Number.isInteger(value) ? `${value}` : value.toFixed(1);
|
|
154
|
+
}
|
|
155
|
+
function getCoverageColor(value) {
|
|
156
|
+
if (value >= 90)
|
|
157
|
+
return '0a7f3f';
|
|
158
|
+
if (value >= 75)
|
|
159
|
+
return '65a30d';
|
|
160
|
+
if (value >= 50)
|
|
161
|
+
return 'ca8a04';
|
|
162
|
+
return 'dc2626';
|
|
163
|
+
}
|
|
164
|
+
function hasScript(pkg, name) {
|
|
165
|
+
return typeof pkg.scripts?.[name] === 'string' && pkg.scripts[name].length > 0;
|
|
166
|
+
}
|
|
167
|
+
function scriptContains(pkg, name, command) {
|
|
168
|
+
return pkg.scripts?.[name]?.includes(command) ?? false;
|
|
169
|
+
}
|
|
170
|
+
function supportsBun(pkg, workflowFiles) {
|
|
171
|
+
if (pkg.packageManager?.startsWith('bun@') ?? false) {
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
if (Object.values(pkg.scripts ?? {}).some((script) => script.includes('bun') || script.includes('bunx'))) {
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
177
|
+
return workflowFiles.some((workflow) => workflow.includes('oven-sh/setup-bun') ||
|
|
178
|
+
workflow.includes('bun install') ||
|
|
179
|
+
/\bbun(?:x)?\b/.test(workflow));
|
|
180
|
+
}
|
|
181
|
+
function workflowRunsScript(workflowFiles, scriptName) {
|
|
182
|
+
const escaped = escapeRegExp(scriptName);
|
|
183
|
+
const patterns = [
|
|
184
|
+
new RegExp(`\\bbun\\s+run\\s+${escaped}(?=\\s|$)`, 'i'),
|
|
185
|
+
new RegExp(`\\bnpm\\s+run\\s+${escaped}(?=\\s|$)`, 'i'),
|
|
186
|
+
new RegExp(`\\bpnpm\\s+${escaped}(?=\\s|$)`, 'i'),
|
|
187
|
+
new RegExp(`\\byarn\\s+${escaped}(?=\\s|$)`, 'i'),
|
|
188
|
+
];
|
|
189
|
+
return workflowFiles.some((workflow) => patterns.some((pattern) => pattern.test(workflow)));
|
|
190
|
+
}
|
|
191
|
+
function escapeRegExp(value) {
|
|
192
|
+
return value.replaceAll(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
193
|
+
}
|
|
194
|
+
async function readWorkflowFiles(root) {
|
|
195
|
+
const workflowsPath = join(root, '.github', 'workflows');
|
|
196
|
+
const entries = await readDirectory(workflowsPath);
|
|
197
|
+
const contents = await Promise.all(entries
|
|
198
|
+
.filter((entry) => entry.endsWith('.yml') || entry.endsWith('.yaml'))
|
|
199
|
+
.sort((a, b) => a.localeCompare(b))
|
|
200
|
+
.map((entry) => readFile(join(workflowsPath, entry), 'utf-8')));
|
|
201
|
+
return contents;
|
|
202
|
+
}
|
|
203
|
+
async function readDirectory(path) {
|
|
204
|
+
try {
|
|
205
|
+
return await readdir(path);
|
|
206
|
+
}
|
|
207
|
+
catch (error) {
|
|
208
|
+
if (isNotFoundError(error)) {
|
|
209
|
+
return [];
|
|
210
|
+
}
|
|
211
|
+
throw new Error(`Unable to read workflow directory: ${path}`, { cause: error });
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
function isNotFoundError(error) {
|
|
215
|
+
return typeof error === 'object' && error !== null && 'code' in error && error.code === 'ENOENT';
|
|
216
|
+
}
|
|
217
|
+
async function readJsonFile(path) {
|
|
218
|
+
let content;
|
|
219
|
+
try {
|
|
220
|
+
content = await readFile(path, 'utf-8');
|
|
221
|
+
}
|
|
222
|
+
catch (error) {
|
|
223
|
+
if (isNotFoundError(error)) {
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
throw new Error(`Unable to read JSON metadata file: ${path}`, { cause: error });
|
|
227
|
+
}
|
|
228
|
+
try {
|
|
229
|
+
return JSON.parse(content);
|
|
230
|
+
}
|
|
231
|
+
catch (error) {
|
|
232
|
+
throw new Error(`Unable to parse JSON metadata file: ${path}`, { cause: error });
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
async function fileExists(path) {
|
|
236
|
+
try {
|
|
237
|
+
await access(path);
|
|
238
|
+
return true;
|
|
239
|
+
}
|
|
240
|
+
catch (error) {
|
|
241
|
+
if (isNotFoundError(error)) {
|
|
242
|
+
return false;
|
|
243
|
+
}
|
|
244
|
+
throw error;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
function sortBadges(badges) {
|
|
248
|
+
return [...badges].sort((left, right) => {
|
|
249
|
+
const order = getBadgeOrder(left.id) - getBadgeOrder(right.id);
|
|
250
|
+
return order !== 0 ? order : left.id.localeCompare(right.id);
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
function getBadgeOrder(id) {
|
|
254
|
+
const index = BADGE_ORDER.indexOf(id);
|
|
255
|
+
return index === -1 ? BADGE_ORDER.length : index;
|
|
256
|
+
}
|
package/dist/analyze/types.d.ts
CHANGED
|
@@ -62,6 +62,12 @@ export interface AnalysisUsageCommand {
|
|
|
62
62
|
name: string;
|
|
63
63
|
command: string;
|
|
64
64
|
}
|
|
65
|
+
export interface AnalysisBadge {
|
|
66
|
+
id: string;
|
|
67
|
+
label: string;
|
|
68
|
+
value: string;
|
|
69
|
+
color: string;
|
|
70
|
+
}
|
|
65
71
|
export interface AnalysisModule {
|
|
66
72
|
path: string;
|
|
67
73
|
isEntrypoint: boolean;
|
|
@@ -79,6 +85,7 @@ export interface AnalysisResult {
|
|
|
79
85
|
components: AnalysisComponent[];
|
|
80
86
|
entrypoints: string[];
|
|
81
87
|
modules: AnalysisModule[];
|
|
88
|
+
badges: AnalysisBadge[];
|
|
82
89
|
usage: AnalysisUsage | null;
|
|
83
90
|
config: {
|
|
84
91
|
exportName: string;
|
package/dist/analyze/usage.d.ts
CHANGED
|
@@ -2,6 +2,12 @@ import type { AnalysisUsage } from './types.js';
|
|
|
2
2
|
export interface PackageJsonModel {
|
|
3
3
|
name: string;
|
|
4
4
|
description?: string;
|
|
5
|
+
version?: string;
|
|
6
|
+
license?: string;
|
|
7
|
+
packageManager?: string;
|
|
5
8
|
bin?: string | Record<string, string>;
|
|
9
|
+
scripts?: Record<string, string>;
|
|
10
|
+
eslintConfig?: unknown;
|
|
11
|
+
prettier?: unknown;
|
|
6
12
|
}
|
|
7
13
|
export declare function createUsageFromPackageJson(pkg: PackageJsonModel): AnalysisUsage | null;
|
package/dist/model/buildModel.js
CHANGED
|
@@ -9,6 +9,12 @@ export function buildModel(analysis) {
|
|
|
9
9
|
packageName: analysis.packageName,
|
|
10
10
|
packageId: analysis.packageId,
|
|
11
11
|
description: analysis.description,
|
|
12
|
+
badges: analysis.badges.map((badge) => ({
|
|
13
|
+
id: badge.id,
|
|
14
|
+
label: badge.label,
|
|
15
|
+
value: badge.value,
|
|
16
|
+
color: badge.color,
|
|
17
|
+
})),
|
|
12
18
|
usage: analysis.usage !== null
|
|
13
19
|
? {
|
|
14
20
|
packageName: analysis.usage.packageName,
|
package/dist/model/types.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export interface DocumentationModel {
|
|
|
5
5
|
packageName: string;
|
|
6
6
|
packageId: string;
|
|
7
7
|
description: string | null;
|
|
8
|
+
badges: GeneratedBadge[];
|
|
8
9
|
usage: UsageModel | null;
|
|
9
10
|
config: ConfigModel | null;
|
|
10
11
|
entrypoints: string[];
|
|
@@ -12,6 +13,12 @@ export interface DocumentationModel {
|
|
|
12
13
|
exports: ExportModel[];
|
|
13
14
|
components: ComponentModel[];
|
|
14
15
|
}
|
|
16
|
+
export interface GeneratedBadge {
|
|
17
|
+
id: string;
|
|
18
|
+
label: string;
|
|
19
|
+
value: string;
|
|
20
|
+
color: string;
|
|
21
|
+
}
|
|
15
22
|
export interface UsageModel {
|
|
16
23
|
packageName: string;
|
|
17
24
|
commands: UsageCommandModel[];
|
package/dist/render/render.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { renderBadgeArtifacts } from './renderers/badges.js';
|
|
1
2
|
import { renderDiagramArtifacts } from './renderers/diagrams.js';
|
|
2
3
|
import { renderHtml } from './renderers/html.js';
|
|
3
4
|
import { renderMarkdown } from './renderers/markdown.js';
|
|
@@ -5,6 +6,7 @@ import { renderMarkdown } from './renderers/markdown.js';
|
|
|
5
6
|
* Renders the documentation model into README and artifact files.
|
|
6
7
|
*/
|
|
7
8
|
export function render(model, options = {}) {
|
|
9
|
+
const badges = renderBadgeArtifacts(model);
|
|
8
10
|
const diagrams = renderDiagramArtifacts(model);
|
|
9
11
|
const outputDir = options.outputDir ?? 'paradox';
|
|
10
12
|
const result = {
|
|
@@ -14,11 +16,13 @@ export function render(model, options = {}) {
|
|
|
14
16
|
exportsJson: `${JSON.stringify(model.exports, null, 2)}\n`,
|
|
15
17
|
paradoxJson: `${JSON.stringify(model, null, 2)}\n`,
|
|
16
18
|
indexHtml: '',
|
|
19
|
+
badges,
|
|
17
20
|
diagrams,
|
|
18
21
|
};
|
|
19
22
|
const context = {
|
|
20
23
|
model,
|
|
21
24
|
outputDir,
|
|
25
|
+
badges,
|
|
22
26
|
diagrams,
|
|
23
27
|
result,
|
|
24
28
|
};
|
|
@@ -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
|
+
}
|
|
@@ -100,6 +100,8 @@ export function renderHtml({ diagrams, model, }) {
|
|
|
100
100
|
padding: 1rem;
|
|
101
101
|
}
|
|
102
102
|
.empty { font-style: italic; color: #6f7f9c; }
|
|
103
|
+
.mermaid { margin: 1rem 0; overflow: auto; }
|
|
104
|
+
details > summary { cursor: pointer; color: #5e6d8c; font-size: 0.9rem; margin-top: 0.5rem; user-select: none; }
|
|
103
105
|
@media (max-width: 900px) {
|
|
104
106
|
.layout { grid-template-columns: 1fr; }
|
|
105
107
|
.sidebar { position: static; max-height: none; border-right: 0; border-bottom: 1px solid #d8dfec; }
|
|
@@ -169,6 +171,10 @@ export function renderHtml({ diagrams, model, }) {
|
|
|
169
171
|
}
|
|
170
172
|
});
|
|
171
173
|
</script>
|
|
174
|
+
<script type="module">
|
|
175
|
+
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
|
|
176
|
+
mermaid.initialize({ startOnLoad: true });
|
|
177
|
+
</script>
|
|
172
178
|
</body>
|
|
173
179
|
</html>
|
|
174
180
|
`,
|
|
@@ -270,7 +276,11 @@ function renderDiagramCard(diagram) {
|
|
|
270
276
|
return `<article class="item" data-search="${escapeAttribute(`${diagram.title} ${diagram.path}`)}">
|
|
271
277
|
<h3>${escapeHtml(diagram.title)}</h3>
|
|
272
278
|
<p class="muted"><code>${escapeHtml(diagram.path)}</code></p>
|
|
273
|
-
<
|
|
279
|
+
<div class="mermaid">${escapeHtml(diagram.content)}</div>
|
|
280
|
+
<details>
|
|
281
|
+
<summary>View Mermaid source</summary>
|
|
282
|
+
<pre>${escapeHtml(diagram.content)}</pre>
|
|
283
|
+
</details>
|
|
274
284
|
</article>`;
|
|
275
285
|
}
|
|
276
286
|
function renderInlineCodeList(values) {
|
|
@@ -2,4 +2,4 @@ import type { RenderContext } from '../types.js';
|
|
|
2
2
|
/***
|
|
3
3
|
* Renders markdown artifacts from the documentation model.
|
|
4
4
|
*/
|
|
5
|
-
export declare function renderMarkdown({ diagrams, model, outputDir, }: RenderContext): Pick<RenderContext['result'], 'components' | 'exportsMarkdown' | 'readme'>;
|
|
5
|
+
export declare function renderMarkdown({ badges, diagrams, model, outputDir, }: RenderContext): Pick<RenderContext['result'], 'components' | 'exportsMarkdown' | 'readme'>;
|
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
/***
|
|
2
2
|
* Renders markdown artifacts from the documentation model.
|
|
3
3
|
*/
|
|
4
|
-
export function renderMarkdown({ diagrams, model, outputDir, }) {
|
|
4
|
+
export function renderMarkdown({ badges, diagrams, model, outputDir, }) {
|
|
5
5
|
return {
|
|
6
|
-
readme: renderReadme(model, outputDir, diagrams),
|
|
6
|
+
readme: renderReadme(model, outputDir, badges, diagrams),
|
|
7
7
|
exportsMarkdown: renderExports(model),
|
|
8
8
|
components: renderComponents(model),
|
|
9
9
|
};
|
|
10
10
|
}
|
|
11
|
-
function renderReadme(model, outputDir, diagrams) {
|
|
11
|
+
function renderReadme(model, outputDir, badges, diagrams) {
|
|
12
12
|
const lines = [`# ${model.packageName}`, ''];
|
|
13
|
+
if (badges.length > 0) {
|
|
14
|
+
lines.push(badges
|
|
15
|
+
.map((badge) => ``)
|
|
16
|
+
.join(' '), '');
|
|
17
|
+
}
|
|
13
18
|
if (model.description) {
|
|
14
19
|
lines.push(model.description, '');
|
|
15
20
|
}
|
|
@@ -139,3 +144,12 @@ function renderComponents(model) {
|
|
|
139
144
|
function escapeTableCell(value) {
|
|
140
145
|
return value.replaceAll('|', '\\|');
|
|
141
146
|
}
|
|
147
|
+
function badgeLabel(model, badgePath) {
|
|
148
|
+
const fileName = badgePath.split('/').pop();
|
|
149
|
+
if (!fileName) {
|
|
150
|
+
return badgePath;
|
|
151
|
+
}
|
|
152
|
+
const id = fileName.replace(/\.svg$/, '');
|
|
153
|
+
const badge = model.badges.find((entry) => entry.id === id);
|
|
154
|
+
return badge ? `${badge.label}: ${badge.value}` : badgePath;
|
|
155
|
+
}
|
package/dist/render/types.d.ts
CHANGED
|
@@ -7,6 +7,10 @@ export interface DiagramArtifact {
|
|
|
7
7
|
title: string;
|
|
8
8
|
content: string;
|
|
9
9
|
}
|
|
10
|
+
export interface BadgeArtifact {
|
|
11
|
+
path: string;
|
|
12
|
+
content: string;
|
|
13
|
+
}
|
|
10
14
|
export interface RenderResult {
|
|
11
15
|
readme: string;
|
|
12
16
|
exportsMarkdown: string;
|
|
@@ -15,10 +19,12 @@ export interface RenderResult {
|
|
|
15
19
|
paradoxJson: string;
|
|
16
20
|
indexHtml: string;
|
|
17
21
|
diagrams: DiagramArtifact[];
|
|
22
|
+
badges: BadgeArtifact[];
|
|
18
23
|
}
|
|
19
24
|
export interface RenderContext {
|
|
20
25
|
model: DocumentationModel;
|
|
21
26
|
outputDir: string;
|
|
22
27
|
diagrams: DiagramArtifact[];
|
|
28
|
+
badges: BadgeArtifact[];
|
|
23
29
|
result: RenderResult;
|
|
24
30
|
}
|
package/dist/write/write.js
CHANGED
|
@@ -13,6 +13,11 @@ export async function write(result, config, runtime) {
|
|
|
13
13
|
await writeFile(join(outputRoot, 'exports.json'), result.exportsJson);
|
|
14
14
|
await writeFile(join(outputRoot, 'paradox.json'), result.paradoxJson);
|
|
15
15
|
await writeFile(join(outputRoot, 'index.html'), result.indexHtml);
|
|
16
|
+
for (const badge of result.badges) {
|
|
17
|
+
const badgePath = join(outputRoot, badge.path);
|
|
18
|
+
await mkdir(dirname(badgePath), { recursive: true });
|
|
19
|
+
await writeFile(badgePath, badge.content);
|
|
20
|
+
}
|
|
16
21
|
for (const diagram of result.diagrams) {
|
|
17
22
|
const diagramPath = join(outputRoot, diagram.path);
|
|
18
23
|
await mkdir(dirname(diagramPath), { recursive: true });
|