@ankhorage/paradox 0.0.10 → 0.1.0
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 +6 -0
- package/README.md +60 -18
- package/dist/analyze/analyze.d.ts +0 -3
- package/dist/analyze/analyze.js +3 -11
- package/dist/analyze/components.js +3 -0
- package/dist/analyze/exports.d.ts +1 -0
- package/dist/analyze/exports.js +16 -3
- package/dist/analyze/types.d.ts +11 -0
- package/dist/analyze/utils/parseParadoxComment.d.ts +7 -0
- package/dist/analyze/utils/parseParadoxComment.js +61 -14
- package/dist/config/defineParadoxConfig.d.ts +2 -0
- package/dist/config/defineParadoxConfig.js +2 -0
- package/dist/config/types.d.ts +1 -0
- package/dist/model/buildModel.d.ts +11 -0
- package/dist/model/buildModel.js +6 -0
- package/dist/model/types.d.ts +11 -0
- package/dist/render/renderers/markdown.js +220 -48
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
|
@@ -1,15 +1,41 @@
|
|
|
1
|
+
<!-- markdownlint-disable MD013 MD033 -->
|
|
2
|
+
<!-- This file is generated by Paradox. Do not edit manually. -->
|
|
3
|
+
|
|
1
4
|
# @ankhorage/paradox
|
|
2
5
|
|
|
3
|
-
         
|
|
4
7
|
|
|
5
8
|
Deterministic documentation generator for TypeScript packages.
|
|
6
9
|
|
|
7
|
-
##
|
|
10
|
+
## Installation
|
|
8
11
|
|
|
9
12
|
```bash
|
|
10
13
|
bunx @ankhorage/paradox
|
|
11
14
|
```
|
|
12
15
|
|
|
16
|
+
## Documentation Tags
|
|
17
|
+
|
|
18
|
+
<details>
|
|
19
|
+
<summary>@readme</summary>
|
|
20
|
+
|
|
21
|
+
Includes a documentation block or exported symbol in README output.
|
|
22
|
+
|
|
23
|
+
</details>
|
|
24
|
+
|
|
25
|
+
<details>
|
|
26
|
+
<summary>@config</summary>
|
|
27
|
+
|
|
28
|
+
Marks a type or interface as part of the Paradox configuration model. `@config` alone does not imply README inclusion; use `@config` plus `@readme` for README output.
|
|
29
|
+
|
|
30
|
+
</details>
|
|
31
|
+
|
|
32
|
+
<details>
|
|
33
|
+
<summary>@example</summary>
|
|
34
|
+
|
|
35
|
+
Adds a titled fenced code example to the generated documentation for a symbol.
|
|
36
|
+
|
|
37
|
+
</details>
|
|
38
|
+
|
|
13
39
|
## Configuration
|
|
14
40
|
|
|
15
41
|
Create a `paradox.config.ts` file:
|
|
@@ -22,14 +48,17 @@ export default defineParadoxConfig({
|
|
|
22
48
|
});
|
|
23
49
|
```
|
|
24
50
|
|
|
25
|
-
|
|
51
|
+
<details>
|
|
52
|
+
<summary>Configuration options</summary>
|
|
26
53
|
|
|
27
54
|
| Field | Type | Required | Default | Description |
|
|
28
55
|
| ------- | --------------------------------------------------------- | -------- | ------- | ----------- |
|
|
29
|
-
| mode | `'safe' \| 'write' \| undefined` | no |
|
|
30
|
-
| docs | `{ title?: string; description?: string; } \| undefined` | no |
|
|
31
|
-
| package | `{ root?: string; entrypoints?: string[]; } \| undefined` | no |
|
|
32
|
-
| output | `{ dir?: string; } \| undefined` | no |
|
|
56
|
+
| mode | `'safe' \| 'write' \| undefined` | no | — | |
|
|
57
|
+
| docs | `{ title?: string; description?: string; } \| undefined` | no | — | |
|
|
58
|
+
| package | `{ root?: string; entrypoints?: string[]; } \| undefined` | no | — | |
|
|
59
|
+
| output | `{ dir?: string; } \| undefined` | no | — | |
|
|
60
|
+
|
|
61
|
+
</details>
|
|
33
62
|
|
|
34
63
|
## Generated documentation
|
|
35
64
|
|
|
@@ -43,6 +72,9 @@ export default defineParadoxConfig({
|
|
|
43
72
|
|
|
44
73
|
## Architecture preview
|
|
45
74
|
|
|
75
|
+
<details>
|
|
76
|
+
<summary>Architecture overview</summary>
|
|
77
|
+
|
|
46
78
|
```mermaid
|
|
47
79
|
graph TD
|
|
48
80
|
package__ankhorage_paradox["@ankhorage/paradox"]
|
|
@@ -212,6 +244,8 @@ graph TD
|
|
|
212
244
|
module_src_write_write_ts --> module_src_render_types_ts
|
|
213
245
|
```
|
|
214
246
|
|
|
247
|
+
</details>
|
|
248
|
+
|
|
215
249
|
## Path resolution
|
|
216
250
|
|
|
217
251
|
- Config discovery: searches upward from `process.cwd()` for `paradox.config.ts/js/mjs/cjs` (required; no fallback).
|
|
@@ -223,21 +257,29 @@ graph TD
|
|
|
223
257
|
|
|
224
258
|
## Public API
|
|
225
259
|
|
|
226
|
-
###
|
|
260
|
+
### Configuration
|
|
261
|
+
|
|
262
|
+
<details>
|
|
263
|
+
<summary>defineParadoxConfig</summary>
|
|
264
|
+
|
|
265
|
+
```ts
|
|
266
|
+
defineParadoxConfig(config: ParadoxConfig) => ParadoxConfig
|
|
267
|
+
```
|
|
227
268
|
|
|
228
269
|
Defines a Paradox configuration object without changing its shape.
|
|
229
270
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
- Export paths: `src/index.ts`
|
|
234
|
-
- Related symbols: `ParadoxConfig`
|
|
271
|
+
Module: `src/config/defineParadoxConfig.ts`
|
|
272
|
+
Source: `src/config/defineParadoxConfig.ts:8:1`
|
|
273
|
+
Related symbols: `ParadoxConfig`
|
|
235
274
|
|
|
236
|
-
|
|
275
|
+
</details>
|
|
276
|
+
|
|
277
|
+
<details>
|
|
278
|
+
<summary>ParadoxConfig</summary>
|
|
237
279
|
|
|
238
280
|
Configuration for running Paradox.
|
|
239
281
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
282
|
+
Module: `src/config/types.ts`
|
|
283
|
+
Source: `src/config/types.ts:7:1`
|
|
284
|
+
|
|
285
|
+
</details>
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import type { ParadoxConfig } from '../config/types.js';
|
|
2
2
|
import type { AnalysisResult } from './types.js';
|
|
3
|
-
/***
|
|
4
|
-
* Runs the source analysis pipeline for a configured package.
|
|
5
|
-
*/
|
|
6
3
|
export declare function analyze(config: ParadoxConfig, runtime: {
|
|
7
4
|
packageRoot: string;
|
|
8
5
|
}): Promise<AnalysisResult>;
|
package/dist/analyze/analyze.js
CHANGED
|
@@ -9,9 +9,6 @@ import { createTypeScriptProgram } from './semantic/createTypeScriptProgram.js';
|
|
|
9
9
|
import { collectTypeMembers, resolveTypeReference } from './semantic/exports.js';
|
|
10
10
|
import { collectCallGraph, collectComponentCompositionGraph, collectImportGraph, } from './semantic/graphs.js';
|
|
11
11
|
import { createUsageFromPackageJson } from './usage.js';
|
|
12
|
-
/***
|
|
13
|
-
* Runs the source analysis pipeline for a configured package.
|
|
14
|
-
*/
|
|
15
12
|
export async function analyze(config, runtime) {
|
|
16
13
|
const root = runtime.packageRoot;
|
|
17
14
|
const pkg = await readPackageJson(root);
|
|
@@ -20,15 +17,9 @@ export async function analyze(config, runtime) {
|
|
|
20
17
|
const project = createProject(root);
|
|
21
18
|
const entrypoints = config.package?.entrypoints ?? ['src/index.ts'];
|
|
22
19
|
const program = createTypeScriptProgram({ root, entrypoints, project });
|
|
23
|
-
const { config: configMetadata, exports } = analyzeExports(project, {
|
|
24
|
-
root,
|
|
25
|
-
entrypoints,
|
|
26
|
-
});
|
|
20
|
+
const { config: configMetadata, exports } = analyzeExports(project, { root, entrypoints });
|
|
27
21
|
const components = analyzeComponents(exports, { program });
|
|
28
|
-
const modules = analyzeModules(project, {
|
|
29
|
-
root,
|
|
30
|
-
entrypoints,
|
|
31
|
-
});
|
|
22
|
+
const modules = analyzeModules(project, { root, entrypoints });
|
|
32
23
|
const configExport = configMetadata
|
|
33
24
|
? (exports.find((entry) => entry.name === configMetadata.exportName) ?? null)
|
|
34
25
|
: null;
|
|
@@ -63,6 +54,7 @@ export async function analyze(config, runtime) {
|
|
|
63
54
|
config: configMetadata
|
|
64
55
|
? {
|
|
65
56
|
exportName: configMetadata.exportName,
|
|
57
|
+
isReadme: configMetadata.isReadme,
|
|
66
58
|
members: mapTypeMembers(configMembers),
|
|
67
59
|
}
|
|
68
60
|
: null,
|
|
@@ -17,6 +17,7 @@ export function analyzeComponents(exports, options = {}) {
|
|
|
17
17
|
name: member.name,
|
|
18
18
|
type: member.type,
|
|
19
19
|
required: member.required,
|
|
20
|
+
...(member.defaultValue !== undefined ? { defaultValue: member.defaultValue } : {}),
|
|
20
21
|
description: member.description ?? null,
|
|
21
22
|
})) ?? [];
|
|
22
23
|
const propsType = getComponentPropsType(e.node);
|
|
@@ -25,6 +26,8 @@ export function analyzeComponents(exports, options = {}) {
|
|
|
25
26
|
components.push({
|
|
26
27
|
name: e.name,
|
|
27
28
|
description: e.description,
|
|
29
|
+
isReadme: e.isReadme,
|
|
30
|
+
examples: e.examples,
|
|
28
31
|
modulePath: e.modulePath,
|
|
29
32
|
sourceLocation: e.sourceLocation,
|
|
30
33
|
exportPaths: e.exportPaths,
|
package/dist/analyze/exports.js
CHANGED
|
@@ -19,13 +19,12 @@ export function analyzeExports(project, options) {
|
|
|
19
19
|
continue;
|
|
20
20
|
}
|
|
21
21
|
const rawComment = getParadoxComment(decl);
|
|
22
|
-
const parsed = rawComment
|
|
23
|
-
? parseParadoxComment(rawComment)
|
|
24
|
-
: { description: null, isConfig: false, params: {}, returns: null };
|
|
22
|
+
const parsed = rawComment ? parseParadoxComment(rawComment) : createEmptyMetadata();
|
|
25
23
|
const name = resolved.getName();
|
|
26
24
|
if (parsed.isConfig) {
|
|
27
25
|
config = {
|
|
28
26
|
exportName: name,
|
|
27
|
+
isReadme: parsed.isReadme,
|
|
29
28
|
};
|
|
30
29
|
}
|
|
31
30
|
const metadata = getExportMetadata({
|
|
@@ -40,6 +39,8 @@ export function analyzeExports(project, options) {
|
|
|
40
39
|
? {
|
|
41
40
|
...existing,
|
|
42
41
|
description: existing.description ?? parsed.description,
|
|
42
|
+
isReadme: existing.isReadme || parsed.isReadme,
|
|
43
|
+
examples: existing.examples.length > 0 ? existing.examples : parsed.examples,
|
|
43
44
|
exportPaths: uniqueSorted([...existing.exportPaths, ...metadata.exportPaths]),
|
|
44
45
|
relatedSymbols: uniqueSorted([
|
|
45
46
|
...existing.relatedSymbols,
|
|
@@ -52,6 +53,8 @@ export function analyzeExports(project, options) {
|
|
|
52
53
|
name,
|
|
53
54
|
node: decl,
|
|
54
55
|
description: parsed.description,
|
|
56
|
+
isReadme: parsed.isReadme,
|
|
57
|
+
examples: parsed.examples,
|
|
55
58
|
kind: inferKind(decl),
|
|
56
59
|
...metadata,
|
|
57
60
|
});
|
|
@@ -87,3 +90,13 @@ function uniqueSorted(values) {
|
|
|
87
90
|
function toPosixPath(path) {
|
|
88
91
|
return path.replaceAll('\\', '/');
|
|
89
92
|
}
|
|
93
|
+
function createEmptyMetadata() {
|
|
94
|
+
return {
|
|
95
|
+
description: null,
|
|
96
|
+
isConfig: false,
|
|
97
|
+
isReadme: false,
|
|
98
|
+
examples: [],
|
|
99
|
+
params: {},
|
|
100
|
+
returns: null,
|
|
101
|
+
};
|
|
102
|
+
}
|
package/dist/analyze/types.d.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import type { Node } from 'ts-morph';
|
|
2
|
+
interface AnalysisExample {
|
|
3
|
+
title: string | null;
|
|
4
|
+
language: string | null;
|
|
5
|
+
code: string;
|
|
6
|
+
}
|
|
2
7
|
/***
|
|
3
8
|
* Describes one exported declaration discovered in a package.
|
|
4
9
|
*/
|
|
@@ -33,6 +38,8 @@ export interface AnalysisExport {
|
|
|
33
38
|
name: string;
|
|
34
39
|
node: Node;
|
|
35
40
|
description: string | null;
|
|
41
|
+
isReadme: boolean;
|
|
42
|
+
examples: AnalysisExample[];
|
|
36
43
|
kind: 'function' | 'type' | 'unknown';
|
|
37
44
|
modulePath: string;
|
|
38
45
|
sourceLocation: AnalysisSourceLocation;
|
|
@@ -47,6 +54,8 @@ export interface AnalysisExport {
|
|
|
47
54
|
export interface AnalysisComponent {
|
|
48
55
|
name: string;
|
|
49
56
|
description: string | null;
|
|
57
|
+
isReadme: boolean;
|
|
58
|
+
examples: AnalysisExample[];
|
|
50
59
|
modulePath: string;
|
|
51
60
|
sourceLocation: AnalysisSourceLocation;
|
|
52
61
|
exportPaths: string[];
|
|
@@ -54,6 +63,7 @@ export interface AnalysisComponent {
|
|
|
54
63
|
name: string;
|
|
55
64
|
type: string;
|
|
56
65
|
required: boolean;
|
|
66
|
+
defaultValue?: string;
|
|
57
67
|
description: string | null;
|
|
58
68
|
}[];
|
|
59
69
|
}
|
|
@@ -129,6 +139,7 @@ export interface AnalysisResult {
|
|
|
129
139
|
usage: AnalysisUsage | null;
|
|
130
140
|
config: {
|
|
131
141
|
exportName: string;
|
|
142
|
+
isReadme: boolean;
|
|
132
143
|
members: AnalysisTypeMember[];
|
|
133
144
|
} | null;
|
|
134
145
|
graphs: AnalysisGraphs;
|
|
@@ -4,9 +4,16 @@
|
|
|
4
4
|
interface ParsedParadoxComment {
|
|
5
5
|
description: string | null;
|
|
6
6
|
isConfig: boolean;
|
|
7
|
+
isReadme: boolean;
|
|
8
|
+
examples: ParsedExample[];
|
|
7
9
|
params: Record<string, string>;
|
|
8
10
|
returns: string | null;
|
|
9
11
|
}
|
|
12
|
+
interface ParsedExample {
|
|
13
|
+
title: string | null;
|
|
14
|
+
language: string | null;
|
|
15
|
+
code: string;
|
|
16
|
+
}
|
|
10
17
|
/***
|
|
11
18
|
* Parses a Paradox doc comment into structured metadata.
|
|
12
19
|
*/
|
|
@@ -2,20 +2,29 @@
|
|
|
2
2
|
* Parses a Paradox doc comment into structured metadata.
|
|
3
3
|
*/
|
|
4
4
|
export function parseParadoxComment(rawComment) {
|
|
5
|
-
const lines = rawComment
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
.split('\n')
|
|
9
|
-
.map((line) => line.replace(/^\s*\*\s?/, '').trimEnd());
|
|
5
|
+
const lines = normalizeCommentLines(rawComment);
|
|
6
|
+
const descriptionLines = [];
|
|
7
|
+
const examples = [];
|
|
10
8
|
let isConfig = false;
|
|
9
|
+
let isReadme = false;
|
|
11
10
|
const params = {};
|
|
12
11
|
let returns = null;
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
13
|
+
const line = lines[index] ?? '';
|
|
15
14
|
const trimmed = line.trimStart();
|
|
16
15
|
if (trimmed.startsWith('@config')) {
|
|
17
16
|
isConfig = true;
|
|
18
|
-
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
if (trimmed.startsWith('@readme')) {
|
|
20
|
+
isReadme = true;
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
if (trimmed.startsWith('@example')) {
|
|
24
|
+
const parsed = parseExample(lines, index);
|
|
25
|
+
examples.push(parsed.example);
|
|
26
|
+
index = parsed.nextIndex;
|
|
27
|
+
continue;
|
|
19
28
|
}
|
|
20
29
|
if (trimmed.startsWith('@param ')) {
|
|
21
30
|
const paramBody = trimmed.slice('@param '.length).trim();
|
|
@@ -23,21 +32,59 @@ export function parseParadoxComment(rawComment) {
|
|
|
23
32
|
if (name) {
|
|
24
33
|
params[name] = descriptionParts.join(' ').trim();
|
|
25
34
|
}
|
|
26
|
-
|
|
35
|
+
continue;
|
|
27
36
|
}
|
|
28
37
|
if (trimmed.startsWith('@returns') || trimmed.startsWith('@return')) {
|
|
29
38
|
const returnBody = trimmed.replace(/^@returns?/, '').trim();
|
|
30
39
|
returns = returnBody.length > 0 ? returnBody : null;
|
|
31
|
-
|
|
40
|
+
continue;
|
|
32
41
|
}
|
|
33
|
-
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
.trim();
|
|
42
|
+
descriptionLines.push(line);
|
|
43
|
+
}
|
|
44
|
+
const description = descriptionLines.join('\n').trim();
|
|
37
45
|
return {
|
|
38
46
|
description: description.length > 0 ? description : null,
|
|
39
47
|
isConfig,
|
|
48
|
+
isReadme,
|
|
49
|
+
examples,
|
|
40
50
|
params,
|
|
41
51
|
returns,
|
|
42
52
|
};
|
|
43
53
|
}
|
|
54
|
+
function parseExample(lines, startIndex) {
|
|
55
|
+
const header = lines[startIndex]?.trimStart() ?? '';
|
|
56
|
+
const title = header.slice('@example'.length).trim();
|
|
57
|
+
let language = null;
|
|
58
|
+
const codeLines = [];
|
|
59
|
+
let index = startIndex + 1;
|
|
60
|
+
while (index < lines.length && (lines[index] ?? '').trim() === '') {
|
|
61
|
+
index += 1;
|
|
62
|
+
}
|
|
63
|
+
const firstCodeLine = lines[index]?.trim() ?? '';
|
|
64
|
+
if (firstCodeLine.startsWith('```')) {
|
|
65
|
+
language = firstCodeLine.slice('```'.length).trim() || null;
|
|
66
|
+
index += 1;
|
|
67
|
+
while (index < lines.length) {
|
|
68
|
+
const current = lines[index] ?? '';
|
|
69
|
+
if (current.trim() === '```')
|
|
70
|
+
break;
|
|
71
|
+
codeLines.push(current);
|
|
72
|
+
index += 1;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
example: {
|
|
77
|
+
title: title.length > 0 ? title : null,
|
|
78
|
+
language,
|
|
79
|
+
code: codeLines.join('\n').trimEnd(),
|
|
80
|
+
},
|
|
81
|
+
nextIndex: index,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function normalizeCommentLines(rawComment) {
|
|
85
|
+
return rawComment
|
|
86
|
+
.replace(/^\/\*\*\*/, '')
|
|
87
|
+
.replace(/\*\/$/, '')
|
|
88
|
+
.split('\n')
|
|
89
|
+
.map((line) => line.replace(/^\s*\*\s?/, '').trimEnd());
|
|
90
|
+
}
|
package/dist/config/types.d.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import type { DocumentationModel, ExportKind } from './types.js';
|
|
2
|
+
interface ExampleInput {
|
|
3
|
+
title: string | null;
|
|
4
|
+
language: string | null;
|
|
5
|
+
code: string;
|
|
6
|
+
}
|
|
2
7
|
interface ExportMemberInput {
|
|
3
8
|
name: string;
|
|
4
9
|
kind: 'property' | 'method';
|
|
@@ -31,6 +36,8 @@ interface BuildModelInput {
|
|
|
31
36
|
exports: {
|
|
32
37
|
name: string;
|
|
33
38
|
description: string | null;
|
|
39
|
+
isReadme: boolean;
|
|
40
|
+
examples: ExampleInput[];
|
|
34
41
|
kind: ExportKind;
|
|
35
42
|
modulePath: string;
|
|
36
43
|
sourceLocation: {
|
|
@@ -56,6 +63,8 @@ interface BuildModelInput {
|
|
|
56
63
|
components: {
|
|
57
64
|
name: string;
|
|
58
65
|
description: string | null;
|
|
66
|
+
isReadme: boolean;
|
|
67
|
+
examples: ExampleInput[];
|
|
59
68
|
modulePath: string;
|
|
60
69
|
sourceLocation: {
|
|
61
70
|
filePath: string;
|
|
@@ -67,6 +76,7 @@ interface BuildModelInput {
|
|
|
67
76
|
name: string;
|
|
68
77
|
type: string;
|
|
69
78
|
required: boolean;
|
|
79
|
+
defaultValue?: string;
|
|
70
80
|
description: string | null;
|
|
71
81
|
}[];
|
|
72
82
|
}[];
|
|
@@ -79,6 +89,7 @@ interface BuildModelInput {
|
|
|
79
89
|
} | null;
|
|
80
90
|
config: {
|
|
81
91
|
exportName: string;
|
|
92
|
+
isReadme: boolean;
|
|
82
93
|
members: ConfigMemberInput[];
|
|
83
94
|
} | null;
|
|
84
95
|
entrypoints: string[];
|
package/dist/model/buildModel.js
CHANGED
|
@@ -27,6 +27,7 @@ export function buildModel(analysis) {
|
|
|
27
27
|
config: analysis.config !== null
|
|
28
28
|
? {
|
|
29
29
|
exportName: analysis.config.exportName,
|
|
30
|
+
isReadme: analysis.config.isReadme,
|
|
30
31
|
configFile: getDefaultConfigFileName(analysis.packageId),
|
|
31
32
|
factoryName: findConfigFactoryName(analysis.config.exportName, [
|
|
32
33
|
...exportsByName.keys(),
|
|
@@ -57,6 +58,8 @@ function mapExport(item, exportNames) {
|
|
|
57
58
|
return {
|
|
58
59
|
name: item.name,
|
|
59
60
|
description: item.description,
|
|
61
|
+
isReadme: item.isReadme,
|
|
62
|
+
examples: item.examples.map((example) => ({ ...example })),
|
|
60
63
|
kind: item.kind,
|
|
61
64
|
modulePath: item.modulePath,
|
|
62
65
|
sourceLocation: {
|
|
@@ -95,6 +98,8 @@ function mapComponent(component, exportModel) {
|
|
|
95
98
|
return {
|
|
96
99
|
name: component.name,
|
|
97
100
|
description: component.description,
|
|
101
|
+
isReadme: component.isReadme,
|
|
102
|
+
examples: component.examples.map((example) => ({ ...example })),
|
|
98
103
|
modulePath: component.modulePath,
|
|
99
104
|
sourceLocation: {
|
|
100
105
|
filePath: component.sourceLocation.filePath,
|
|
@@ -106,6 +111,7 @@ function mapComponent(component, exportModel) {
|
|
|
106
111
|
name: prop.name,
|
|
107
112
|
type: prop.type,
|
|
108
113
|
required: prop.required,
|
|
114
|
+
defaultValue: prop.defaultValue,
|
|
109
115
|
description: prop.description,
|
|
110
116
|
}))),
|
|
111
117
|
};
|
package/dist/model/types.d.ts
CHANGED
|
@@ -30,6 +30,7 @@ interface UsageCommandModel {
|
|
|
30
30
|
}
|
|
31
31
|
interface ConfigModel {
|
|
32
32
|
exportName: string;
|
|
33
|
+
isReadme: boolean;
|
|
33
34
|
configFile: string;
|
|
34
35
|
factoryName: string | null;
|
|
35
36
|
members: ConfigMemberModel[];
|
|
@@ -37,6 +38,8 @@ interface ConfigModel {
|
|
|
37
38
|
export interface ExportModel {
|
|
38
39
|
name: string;
|
|
39
40
|
description: string | null;
|
|
41
|
+
isReadme: boolean;
|
|
42
|
+
examples: ExampleModel[];
|
|
40
43
|
kind: ExportKind;
|
|
41
44
|
modulePath: string;
|
|
42
45
|
sourceLocation: SourceLocationModel;
|
|
@@ -49,11 +52,18 @@ export type ExportKind = 'function' | 'type' | 'unknown';
|
|
|
49
52
|
export interface ComponentModel {
|
|
50
53
|
name: string;
|
|
51
54
|
description: string | null;
|
|
55
|
+
isReadme: boolean;
|
|
56
|
+
examples: ExampleModel[];
|
|
52
57
|
modulePath: string;
|
|
53
58
|
sourceLocation: SourceLocationModel;
|
|
54
59
|
exportPaths: string[];
|
|
55
60
|
props: PropModel[];
|
|
56
61
|
}
|
|
62
|
+
interface ExampleModel {
|
|
63
|
+
title: string | null;
|
|
64
|
+
language: string | null;
|
|
65
|
+
code: string;
|
|
66
|
+
}
|
|
57
67
|
interface SourceLocationModel {
|
|
58
68
|
filePath: string;
|
|
59
69
|
line: number;
|
|
@@ -91,6 +101,7 @@ interface PropModel {
|
|
|
91
101
|
name: string;
|
|
92
102
|
type: string;
|
|
93
103
|
required: boolean;
|
|
104
|
+
defaultValue?: string;
|
|
94
105
|
description: string | null;
|
|
95
106
|
}
|
|
96
107
|
interface ConfigMemberModel {
|
|
@@ -9,7 +9,13 @@ export function renderMarkdown({ badges, diagrams, model, outputDir, }) {
|
|
|
9
9
|
};
|
|
10
10
|
}
|
|
11
11
|
function renderReadme(model, outputDir, badges, diagrams) {
|
|
12
|
-
const lines = [
|
|
12
|
+
const lines = [
|
|
13
|
+
'<!-- markdownlint-disable MD013 MD033 -->',
|
|
14
|
+
'<!-- This file is generated by Paradox. Do not edit manually. -->',
|
|
15
|
+
'',
|
|
16
|
+
`# ${model.packageName}`,
|
|
17
|
+
'',
|
|
18
|
+
];
|
|
13
19
|
if (badges.length > 0) {
|
|
14
20
|
lines.push(badges
|
|
15
21
|
.map((badge) => ``)
|
|
@@ -19,45 +25,68 @@ function renderReadme(model, outputDir, badges, diagrams) {
|
|
|
19
25
|
lines.push(model.description, '');
|
|
20
26
|
}
|
|
21
27
|
if (model.usage !== null) {
|
|
22
|
-
lines.push('##
|
|
28
|
+
lines.push('## Installation', '');
|
|
23
29
|
lines.push('```bash');
|
|
24
30
|
for (const command of model.usage.commands) {
|
|
25
31
|
lines.push(command.command);
|
|
26
32
|
}
|
|
27
33
|
lines.push('```', '');
|
|
28
34
|
}
|
|
35
|
+
renderDocumentationTags(lines);
|
|
36
|
+
if (model.config?.isReadme) {
|
|
37
|
+
renderConfiguration(lines, model);
|
|
38
|
+
}
|
|
39
|
+
renderGeneratedDocumentation(lines, outputDir, diagrams);
|
|
40
|
+
renderArchitecturePreview(lines, diagrams);
|
|
41
|
+
renderPathResolution(lines);
|
|
42
|
+
renderReadmeApi(lines, model);
|
|
43
|
+
return `${lines.join('\n').trimEnd()}\n`;
|
|
44
|
+
}
|
|
45
|
+
function renderDocumentationTags(lines) {
|
|
46
|
+
lines.push('## Documentation Tags', '');
|
|
47
|
+
for (const tag of DOCUMENTATION_TAGS) {
|
|
48
|
+
lines.push('<details>');
|
|
49
|
+
lines.push(`<summary>@${tag.name}</summary>`, '');
|
|
50
|
+
lines.push(tag.description, '');
|
|
51
|
+
lines.push('</details>', '');
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function renderConfiguration(lines, model) {
|
|
29
55
|
const { config } = model;
|
|
30
|
-
if (config
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
lines.push('');
|
|
56
|
+
if (config === null)
|
|
57
|
+
return;
|
|
58
|
+
lines.push('## Configuration', '');
|
|
59
|
+
lines.push(`Create a \`${config.configFile}\` file:`, '');
|
|
60
|
+
lines.push('```ts');
|
|
61
|
+
if (config.factoryName !== null) {
|
|
62
|
+
lines.push(`import { ${config.factoryName} } from '${model.packageId}';`);
|
|
63
|
+
lines.push('');
|
|
64
|
+
lines.push(`export default ${config.factoryName}({`);
|
|
65
|
+
lines.push(' // ...');
|
|
66
|
+
lines.push('});');
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
lines.push(`import type { ${config.exportName} } from '${model.packageId}';`);
|
|
70
|
+
lines.push('');
|
|
71
|
+
lines.push('const config = {');
|
|
72
|
+
lines.push(' // ...');
|
|
73
|
+
lines.push(`} satisfies ${config.exportName};`);
|
|
74
|
+
lines.push('');
|
|
75
|
+
lines.push('export default config;');
|
|
76
|
+
}
|
|
77
|
+
lines.push('```', '');
|
|
78
|
+
if (config.members.length > 0) {
|
|
79
|
+
lines.push('<details>');
|
|
80
|
+
lines.push('<summary>Configuration options</summary>', '');
|
|
81
|
+
lines.push('| Field | Type | Required | Default | Description |');
|
|
82
|
+
lines.push('| --- | --- | --- | --- | --- |');
|
|
83
|
+
for (const configMember of flattenConfigMembers(config.members)) {
|
|
84
|
+
lines.push(`| ${escapeTableCell(configMember.path)} | \`${escapeTableCell(configMember.type)}\` | ${configMember.required ? 'yes' : 'no'} | ${renderDefault(configMember.defaultValue)} | ${escapeTableCell(configMember.description ?? '')} |`);
|
|
59
85
|
}
|
|
86
|
+
lines.push('', '</details>', '');
|
|
60
87
|
}
|
|
88
|
+
}
|
|
89
|
+
function renderGeneratedDocumentation(lines, outputDir, diagrams) {
|
|
61
90
|
lines.push('## Generated documentation', '');
|
|
62
91
|
lines.push(`- [Interactive documentation app](./${outputDir}/index.html)`);
|
|
63
92
|
lines.push(`- [Public API reference](./${outputDir}/exports.md)`);
|
|
@@ -66,12 +95,19 @@ function renderReadme(model, outputDir, badges, diagrams) {
|
|
|
66
95
|
lines.push(`- [${diagram.title}](./${outputDir}/${diagram.path})`);
|
|
67
96
|
}
|
|
68
97
|
lines.push('');
|
|
98
|
+
}
|
|
99
|
+
function renderArchitecturePreview(lines, diagrams) {
|
|
69
100
|
lines.push('## Architecture preview', '');
|
|
70
101
|
if (diagrams.length > 0) {
|
|
102
|
+
lines.push('<details>');
|
|
103
|
+
lines.push('<summary>Architecture overview</summary>', '');
|
|
71
104
|
lines.push('```mermaid');
|
|
72
|
-
lines.push(diagrams[0]
|
|
105
|
+
lines.push(diagrams[0]?.content.trimEnd() ?? '');
|
|
73
106
|
lines.push('```', '');
|
|
107
|
+
lines.push('</details>', '');
|
|
74
108
|
}
|
|
109
|
+
}
|
|
110
|
+
function renderPathResolution(lines) {
|
|
75
111
|
lines.push('## Path resolution', '');
|
|
76
112
|
lines.push('- Config discovery: searches upward from `process.cwd()` for `paradox.config.ts/js/mjs/cjs` (required; no fallback).');
|
|
77
113
|
lines.push('- Package root: defaults to the directory containing `paradox.config.*`; `package.root` (when relative) resolves relative to that directory.');
|
|
@@ -79,22 +115,131 @@ function renderReadme(model, outputDir, badges, diagrams) {
|
|
|
79
115
|
lines.push('- Modes:');
|
|
80
116
|
lines.push(' - `safe`: writes generated artifacts only under the output directory');
|
|
81
117
|
lines.push(' - `write`: additionally updates `<packageRoot>/README.md`', '');
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
if (item.
|
|
92
|
-
lines
|
|
118
|
+
}
|
|
119
|
+
function renderReadmeApi(lines, model) {
|
|
120
|
+
const groups = getReadmeGroups(model);
|
|
121
|
+
if (groups.length === 0)
|
|
122
|
+
return;
|
|
123
|
+
lines.push('## Public API', '');
|
|
124
|
+
for (const group of groups) {
|
|
125
|
+
lines.push(`### ${group.title}`, '');
|
|
126
|
+
for (const item of group.items) {
|
|
127
|
+
if (item.kind === 'component') {
|
|
128
|
+
renderComponentAccordion(lines, item.component, item.exportEntry);
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
renderExportAccordion(lines, item.exportEntry);
|
|
93
132
|
}
|
|
94
|
-
lines.push('');
|
|
95
133
|
}
|
|
96
134
|
}
|
|
97
|
-
|
|
135
|
+
}
|
|
136
|
+
function renderComponentAccordion(lines, component, exportEntry) {
|
|
137
|
+
lines.push('<details>');
|
|
138
|
+
lines.push(`<summary>${component.name}</summary>`, '');
|
|
139
|
+
renderSignature(lines, exportEntry);
|
|
140
|
+
if (component.description)
|
|
141
|
+
lines.push(component.description, '');
|
|
142
|
+
renderExamples(lines, component.examples);
|
|
143
|
+
if (exportEntry && exportEntry.relatedSymbols.length > 0) {
|
|
144
|
+
lines.push(`Related types: ${exportEntry.relatedSymbols.map((symbol) => `\`${symbol}\``).join(', ')}`, '');
|
|
145
|
+
}
|
|
146
|
+
if (component.props.length > 0) {
|
|
147
|
+
lines.push('<details>');
|
|
148
|
+
lines.push('<summary>Props</summary>', '');
|
|
149
|
+
lines.push('| Prop | Type | Required | Default | Description |');
|
|
150
|
+
lines.push('| --- | --- | --- | --- | --- |');
|
|
151
|
+
for (const prop of component.props) {
|
|
152
|
+
lines.push(`| ${escapeTableCell(prop.name)} | \`${escapeTableCell(prop.type)}\` | ${prop.required ? 'yes' : 'no'} | ${renderDefault(prop.defaultValue)} | ${escapeTableCell(prop.description ?? '')} |`);
|
|
153
|
+
}
|
|
154
|
+
lines.push('', '</details>', '');
|
|
155
|
+
}
|
|
156
|
+
lines.push('</details>', '');
|
|
157
|
+
}
|
|
158
|
+
function renderExportAccordion(lines, item) {
|
|
159
|
+
lines.push('<details>');
|
|
160
|
+
lines.push(`<summary>${item.name}</summary>`, '');
|
|
161
|
+
renderSignature(lines, item);
|
|
162
|
+
lines.push(item.description ?? `\`${item.kind}\` export.`, '');
|
|
163
|
+
renderExamples(lines, item.examples);
|
|
164
|
+
lines.push(`Module: \`${item.modulePath}\``);
|
|
165
|
+
lines.push(`Source: \`${item.sourceLocation.filePath}:${item.sourceLocation.line}:${item.sourceLocation.column}\``);
|
|
166
|
+
if (item.relatedSymbols.length > 0) {
|
|
167
|
+
lines.push(`Related symbols: ${item.relatedSymbols.map((symbol) => `\`${symbol}\``).join(', ')}`);
|
|
168
|
+
}
|
|
169
|
+
lines.push('', '</details>', '');
|
|
170
|
+
}
|
|
171
|
+
function renderSignature(lines, item) {
|
|
172
|
+
const signature = item?.signatures[0]?.label;
|
|
173
|
+
if (!signature)
|
|
174
|
+
return;
|
|
175
|
+
lines.push('```ts');
|
|
176
|
+
lines.push(`${item.name}${signature}`);
|
|
177
|
+
lines.push('```', '');
|
|
178
|
+
}
|
|
179
|
+
function renderExamples(lines, examples) {
|
|
180
|
+
for (const example of examples) {
|
|
181
|
+
if (example.title)
|
|
182
|
+
lines.push(`#### ${example.title}`, '');
|
|
183
|
+
lines.push(`\`\`\`${example.language ?? ''}`);
|
|
184
|
+
lines.push(example.code);
|
|
185
|
+
lines.push('```', '');
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
function getReadmeGroups(model) {
|
|
189
|
+
const exportsByName = new Map(model.exports.map((entry) => [entry.name, entry]));
|
|
190
|
+
const componentNames = new Set(model.components.map((component) => component.name));
|
|
191
|
+
const groups = new Map();
|
|
192
|
+
for (const component of model.components.filter((entry) => entry.isReadme)) {
|
|
193
|
+
addReadmeItem(groups, getReadmeCategory(component.modulePath, component.name), {
|
|
194
|
+
kind: 'component',
|
|
195
|
+
component,
|
|
196
|
+
exportEntry: exportsByName.get(component.name),
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
for (const item of model.exports.filter((entry) => entry.isReadme)) {
|
|
200
|
+
if (componentNames.has(item.name))
|
|
201
|
+
continue;
|
|
202
|
+
addReadmeItem(groups, getReadmeCategory(item.modulePath, item.name), {
|
|
203
|
+
kind: 'export',
|
|
204
|
+
exportEntry: item,
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
return CATEGORY_ORDER.flatMap((title) => {
|
|
208
|
+
const items = groups.get(title);
|
|
209
|
+
if (!items || items.length === 0)
|
|
210
|
+
return [];
|
|
211
|
+
return [{ title, items: sortReadmeItems(items) }];
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
function addReadmeItem(groups, title, item) {
|
|
215
|
+
const existing = groups.get(title) ?? [];
|
|
216
|
+
existing.push(item);
|
|
217
|
+
groups.set(title, existing);
|
|
218
|
+
}
|
|
219
|
+
function sortReadmeItems(items) {
|
|
220
|
+
return [...items].sort((left, right) => getReadmeItemName(left).localeCompare(getReadmeItemName(right)));
|
|
221
|
+
}
|
|
222
|
+
function getReadmeItemName(item) {
|
|
223
|
+
return item.kind === 'component' ? item.component.name : item.exportEntry.name;
|
|
224
|
+
}
|
|
225
|
+
function getReadmeCategory(modulePath, name) {
|
|
226
|
+
if (modulePath.includes('/config/'))
|
|
227
|
+
return 'Configuration';
|
|
228
|
+
if (modulePath.includes('/primitives/'))
|
|
229
|
+
return 'Primitives';
|
|
230
|
+
if (modulePath.includes('/components/'))
|
|
231
|
+
return 'Components';
|
|
232
|
+
if (modulePath.includes('/patterns/'))
|
|
233
|
+
return 'Patterns';
|
|
234
|
+
if (modulePath.includes('/layout/'))
|
|
235
|
+
return 'Layout';
|
|
236
|
+
if (modulePath.includes('/hooks/') || /^use[A-Z]/.test(name))
|
|
237
|
+
return 'Hooks';
|
|
238
|
+
if (modulePath.includes('/utils/'))
|
|
239
|
+
return 'Utilities';
|
|
240
|
+
if (modulePath.endsWith('types.ts') || modulePath.includes('/types/'))
|
|
241
|
+
return 'Types';
|
|
242
|
+
return 'Utilities';
|
|
98
243
|
}
|
|
99
244
|
function renderExports(model) {
|
|
100
245
|
const lines = ['# Public API', ''];
|
|
@@ -141,10 +286,10 @@ function renderComponents(model) {
|
|
|
141
286
|
lines.push(`Export paths: ${component.exportPaths.map((path) => `\`${path}\``).join(', ')}`, '');
|
|
142
287
|
}
|
|
143
288
|
if (component.props.length > 0) {
|
|
144
|
-
lines.push('| Prop | Type | Required | Description |');
|
|
145
|
-
lines.push('| --- | --- | --- | --- |');
|
|
289
|
+
lines.push('| Prop | Type | Required | Default | Description |');
|
|
290
|
+
lines.push('| --- | --- | --- | --- | --- |');
|
|
146
291
|
for (const prop of component.props) {
|
|
147
|
-
lines.push(`| ${escapeTableCell(prop.name)} | \`${escapeTableCell(prop.type)}\` | ${prop.required ? 'yes' : 'no'} | ${escapeTableCell(prop.description ?? '')} |`);
|
|
292
|
+
lines.push(`| ${escapeTableCell(prop.name)} | \`${escapeTableCell(prop.type)}\` | ${prop.required ? 'yes' : 'no'} | ${renderDefault(prop.defaultValue)} | ${escapeTableCell(prop.description ?? '')} |`);
|
|
148
293
|
}
|
|
149
294
|
lines.push('');
|
|
150
295
|
}
|
|
@@ -154,6 +299,9 @@ function renderComponents(model) {
|
|
|
154
299
|
function escapeTableCell(value) {
|
|
155
300
|
return value.replaceAll('|', '\\|');
|
|
156
301
|
}
|
|
302
|
+
function renderDefault(value) {
|
|
303
|
+
return value === undefined ? '—' : `\`${escapeTableCell(value)}\``;
|
|
304
|
+
}
|
|
157
305
|
function flattenConfigMembers(members, prefix = '') {
|
|
158
306
|
return members.flatMap((member) => {
|
|
159
307
|
const path = prefix ? `${prefix}.${member.name}` : member.name;
|
|
@@ -177,3 +325,27 @@ function badgeLabel(model, badgePath) {
|
|
|
177
325
|
const badge = model.badges.find((entry) => entry.id === id);
|
|
178
326
|
return badge ? `${badge.label}: ${badge.value}` : badgePath;
|
|
179
327
|
}
|
|
328
|
+
const CATEGORY_ORDER = [
|
|
329
|
+
'Configuration',
|
|
330
|
+
'Primitives',
|
|
331
|
+
'Components',
|
|
332
|
+
'Patterns',
|
|
333
|
+
'Layout',
|
|
334
|
+
'Hooks',
|
|
335
|
+
'Utilities',
|
|
336
|
+
'Types',
|
|
337
|
+
];
|
|
338
|
+
const DOCUMENTATION_TAGS = [
|
|
339
|
+
{
|
|
340
|
+
name: 'readme',
|
|
341
|
+
description: 'Includes a documentation block or exported symbol in README output.',
|
|
342
|
+
},
|
|
343
|
+
{
|
|
344
|
+
name: 'config',
|
|
345
|
+
description: 'Marks a type or interface as part of the Paradox configuration model. `@config` alone does not imply README inclusion; use `@config` plus `@readme` for README output.',
|
|
346
|
+
},
|
|
347
|
+
{
|
|
348
|
+
name: 'example',
|
|
349
|
+
description: 'Adds a titled fenced code example to the generated documentation for a symbol.',
|
|
350
|
+
},
|
|
351
|
+
];
|