@ankhorage/paradox 0.1.9 → 0.1.10
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 +7 -27
- package/dist/analyze/analyze.js +9 -1
- package/dist/analyze/modules.d.ts +1 -0
- package/dist/analyze/modules.js +5 -1
- package/dist/analyze/readmeUsage.d.ts +14 -0
- package/dist/analyze/readmeUsage.js +72 -0
- package/dist/analyze/types.d.ts +8 -0
- package/dist/analyze/utils/parseParadoxComment.d.ts +2 -1
- package/dist/analyze/utils/parseParadoxComment.js +7 -0
- package/dist/config/types.d.ts +3 -0
- package/dist/doc-tags/registry.d.ts +7 -7
- package/dist/doc-tags/registry.js +10 -0
- package/dist/model/buildModel.d.ts +7 -0
- package/dist/model/buildModel.js +9 -0
- package/dist/model/types.d.ts +8 -0
- package/dist/render/renderers/markdown.js +42 -35
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
# @ankhorage/paradox
|
|
5
5
|
|
|
6
|
-
         
|
|
7
7
|
|
|
8
8
|
Deterministic documentation generator for TypeScript packages.
|
|
9
9
|
|
|
@@ -79,12 +79,12 @@ export default defineParadoxConfig({
|
|
|
79
79
|
<details>
|
|
80
80
|
<summary>Configuration options</summary>
|
|
81
81
|
|
|
82
|
-
| Field | Type
|
|
83
|
-
| ------- |
|
|
84
|
-
| mode | `'safe' \| 'write' \| undefined`
|
|
85
|
-
| docs | `{ title?: string; description?: string; } \| undefined`
|
|
86
|
-
| package | `{ root?: string; entrypoints?: string[]; } \| undefined`
|
|
87
|
-
| output | `{ dir?: string; } \| undefined`
|
|
82
|
+
| Field | Type | Required | Default | Description |
|
|
83
|
+
| ------- | --------------------------------------------------------------------------------------------- | -------- | ------- | ----------- |
|
|
84
|
+
| mode | `'safe' \| 'write' \| undefined` | no | — | |
|
|
85
|
+
| docs | `{ title?: string; description?: string; usage?: { entrypoints?: string[]; }; } \| undefined` | no | — | |
|
|
86
|
+
| package | `{ root?: string; entrypoints?: string[]; } \| undefined` | no | — | |
|
|
87
|
+
| output | `{ dir?: string; } \| undefined` | no | — | |
|
|
88
88
|
|
|
89
89
|
</details>
|
|
90
90
|
|
|
@@ -127,23 +127,3 @@ Module: `src/config/types.ts`
|
|
|
127
127
|
Source: `src/config/types.ts:7:1`
|
|
128
128
|
|
|
129
129
|
</details>
|
|
130
|
-
|
|
131
|
-
### Documentation
|
|
132
|
-
|
|
133
|
-
<details>
|
|
134
|
-
<summary>PARADOX_DOC_TAGS</summary>
|
|
135
|
-
|
|
136
|
-
Supported Paradox documentation tags.
|
|
137
|
-
|
|
138
|
-
Paradox supports doc tags inside triple-star documentation comments.
|
|
139
|
-
|
|
140
|
-
| name | syntax | description | applies to | repeatable | handler |
|
|
141
|
-
| --------- | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | ---------- | -------------- |
|
|
142
|
-
| `readme` | `@readme` | Includes a documentation block or exported symbol in README output. | block, symbol | no | `markReadme` |
|
|
143
|
-
| `config` | `@config` | 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. | interface, type | no | `markConfig` |
|
|
144
|
-
| `example` | `@example` | Adds a titled fenced code example to the generated documentation for a symbol. | symbol | yes | `parseExample` |
|
|
145
|
-
|
|
146
|
-
Module: `src/doc-tags/registry.ts`
|
|
147
|
-
Source: `src/doc-tags/registry.ts:8:14`
|
|
148
|
-
|
|
149
|
-
</details>
|
package/dist/analyze/analyze.js
CHANGED
|
@@ -5,6 +5,7 @@ import { analyzeComponents } from './components.js';
|
|
|
5
5
|
import { analyzeExports } from './exports.js';
|
|
6
6
|
import { analyzeModules } from './modules.js';
|
|
7
7
|
import { createProject } from './project.js';
|
|
8
|
+
import { analyzeReadmeUsage } from './readmeUsage.js';
|
|
8
9
|
import { createTypeScriptProgram } from './semantic/createTypeScriptProgram.js';
|
|
9
10
|
import { collectTypeMembers, resolveTypeReference } from './semantic/exports.js';
|
|
10
11
|
import { collectCallGraph, collectComponentCompositionGraph, collectImportGraph, } from './semantic/graphs.js';
|
|
@@ -21,10 +22,16 @@ export async function analyze(config, runtime) {
|
|
|
21
22
|
const badges = await analyzeBadges(root, pkg);
|
|
22
23
|
const project = createProject(root);
|
|
23
24
|
const entrypoints = config.package?.entrypoints ?? ['src/index.ts'];
|
|
25
|
+
const usageEntryPoints = config.docs?.usage?.entrypoints ?? [];
|
|
26
|
+
const readmeUsage = await analyzeReadmeUsage({ root, entrypoints: usageEntryPoints });
|
|
24
27
|
const program = createTypeScriptProgram({ root, entrypoints, project });
|
|
25
28
|
const { config: configMetadata, exports } = analyzeExports(project, { root, entrypoints });
|
|
26
29
|
const components = analyzeComponents(exports, { program });
|
|
27
|
-
const modules = analyzeModules(project, {
|
|
30
|
+
const modules = analyzeModules(project, {
|
|
31
|
+
root,
|
|
32
|
+
entrypoints,
|
|
33
|
+
excludePaths: usageEntryPoints,
|
|
34
|
+
});
|
|
28
35
|
const sourceFunctions = analyzeSourceFunctions(project, root);
|
|
29
36
|
const sequenceScenarios = analyzeSequenceScenarios({ project, root, pkg, exports });
|
|
30
37
|
const configExport = configMetadata
|
|
@@ -60,6 +67,7 @@ export async function analyze(config, runtime) {
|
|
|
60
67
|
badges,
|
|
61
68
|
sequenceScenarios,
|
|
62
69
|
usage,
|
|
70
|
+
readmeUsage,
|
|
63
71
|
config: configMetadata
|
|
64
72
|
? {
|
|
65
73
|
exportName: configMetadata.exportName,
|
package/dist/analyze/modules.js
CHANGED
|
@@ -5,6 +5,7 @@ import { isAbsolute, join, normalize, relative } from 'node:path';
|
|
|
5
5
|
export function analyzeModules(project, options) {
|
|
6
6
|
const rootPath = normalize(options.root);
|
|
7
7
|
const entrypointPaths = new Set(options.entrypoints.map((entrypoint) => normalize(isAbsolute(entrypoint) ? entrypoint : join(options.root, entrypoint))));
|
|
8
|
+
const excludedPaths = new Set((options.excludePaths ?? []).map((entrypoint) => normalize(isAbsolute(entrypoint) ? entrypoint : join(options.root, entrypoint))));
|
|
8
9
|
return project
|
|
9
10
|
.getSourceFiles()
|
|
10
11
|
.filter((sourceFile) => {
|
|
@@ -12,6 +13,7 @@ export function analyzeModules(project, options) {
|
|
|
12
13
|
const normalizedPath = toPosixPath(filePath);
|
|
13
14
|
return (!sourceFile.isDeclarationFile() &&
|
|
14
15
|
filePath.startsWith(rootPath) &&
|
|
16
|
+
!excludedPaths.has(filePath) &&
|
|
15
17
|
!normalizedPath.includes('/node_modules/'));
|
|
16
18
|
})
|
|
17
19
|
.map((sourceFile) => {
|
|
@@ -21,7 +23,9 @@ export function analyzeModules(project, options) {
|
|
|
21
23
|
.map((declaration) => declaration.getModuleSpecifierSourceFile())
|
|
22
24
|
.filter((dependency) => dependency != null)
|
|
23
25
|
.map((dependency) => normalize(dependency.getFilePath()))
|
|
24
|
-
.filter((dependency) => dependency.startsWith(rootPath) &&
|
|
26
|
+
.filter((dependency) => dependency.startsWith(rootPath) &&
|
|
27
|
+
!excludedPaths.has(dependency) &&
|
|
28
|
+
!toPosixPath(dependency).includes('/node_modules/'))
|
|
25
29
|
.map((dependency) => toPosixPath(relative(options.root, dependency)));
|
|
26
30
|
const exports = sourceFile
|
|
27
31
|
.getExportSymbols()
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface AnalysisReadmeUsage {
|
|
2
|
+
title: string | null;
|
|
3
|
+
description: string | null;
|
|
4
|
+
language: string;
|
|
5
|
+
code: string;
|
|
6
|
+
sourcePath: string;
|
|
7
|
+
}
|
|
8
|
+
/***
|
|
9
|
+
* Collects README usage examples from configured real source files.
|
|
10
|
+
*/
|
|
11
|
+
export declare function analyzeReadmeUsage(options: {
|
|
12
|
+
root: string;
|
|
13
|
+
entrypoints: readonly string[];
|
|
14
|
+
}): Promise<AnalysisReadmeUsage[]>;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { extname, isAbsolute, join, relative } from 'node:path';
|
|
3
|
+
import { parseParadoxComment } from './utils/parseParadoxComment.js';
|
|
4
|
+
const USAGE_TAG = `${String.fromCharCode(64)}usage`;
|
|
5
|
+
/***
|
|
6
|
+
* Collects README usage examples from configured real source files.
|
|
7
|
+
*/
|
|
8
|
+
export async function analyzeReadmeUsage(options) {
|
|
9
|
+
const entries = await Promise.all(options.entrypoints.map(async (entrypoint) => analyzeUsageEntrypoint(options.root, entrypoint)));
|
|
10
|
+
return entries.flat().sort((left, right) => left.sourcePath.localeCompare(right.sourcePath));
|
|
11
|
+
}
|
|
12
|
+
async function analyzeUsageEntrypoint(root, entrypoint) {
|
|
13
|
+
const absolutePath = isAbsolute(entrypoint) ? entrypoint : join(root, entrypoint);
|
|
14
|
+
const source = await readFile(absolutePath, 'utf-8');
|
|
15
|
+
const sourcePath = toPosixPath(relative(root, absolutePath));
|
|
16
|
+
const matches = findUsageComments(source);
|
|
17
|
+
return matches.map((match) => {
|
|
18
|
+
const parsed = parseParadoxComment(match.comment);
|
|
19
|
+
return {
|
|
20
|
+
title: getUsageTitle(parsed.description, sourcePath),
|
|
21
|
+
description: parsed.description,
|
|
22
|
+
language: getLanguage(sourcePath),
|
|
23
|
+
code: removeRange(source, match.start, match.end).trim(),
|
|
24
|
+
sourcePath,
|
|
25
|
+
};
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
function findUsageComments(source) {
|
|
29
|
+
const matches = [];
|
|
30
|
+
const pattern = /\/\*\*\*[\s\S]*?\*\//g;
|
|
31
|
+
for (const match of source.matchAll(pattern)) {
|
|
32
|
+
const [comment] = match;
|
|
33
|
+
if (!comment.includes(USAGE_TAG))
|
|
34
|
+
continue;
|
|
35
|
+
matches.push({
|
|
36
|
+
comment,
|
|
37
|
+
start: match.index,
|
|
38
|
+
end: match.index + comment.length,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
return matches;
|
|
42
|
+
}
|
|
43
|
+
function removeRange(source, start, end) {
|
|
44
|
+
const before = source.slice(0, start).trimEnd();
|
|
45
|
+
const after = source.slice(end).trimStart();
|
|
46
|
+
if (before.length === 0)
|
|
47
|
+
return after;
|
|
48
|
+
if (after.length === 0)
|
|
49
|
+
return before;
|
|
50
|
+
return `${before}\n\n${after}`;
|
|
51
|
+
}
|
|
52
|
+
function getUsageTitle(description, sourcePath) {
|
|
53
|
+
if (description === null)
|
|
54
|
+
return sourcePath;
|
|
55
|
+
const [firstLine = sourcePath] = description.split('\n');
|
|
56
|
+
return firstLine.trim() || sourcePath;
|
|
57
|
+
}
|
|
58
|
+
function getLanguage(sourcePath) {
|
|
59
|
+
const extension = extname(sourcePath).toLowerCase();
|
|
60
|
+
if (extension === '.tsx')
|
|
61
|
+
return 'tsx';
|
|
62
|
+
if (extension === '.ts')
|
|
63
|
+
return 'ts';
|
|
64
|
+
if (extension === '.jsx')
|
|
65
|
+
return 'jsx';
|
|
66
|
+
if (extension === '.js')
|
|
67
|
+
return 'js';
|
|
68
|
+
return '';
|
|
69
|
+
}
|
|
70
|
+
function toPosixPath(path) {
|
|
71
|
+
return path.replaceAll('\\', '/');
|
|
72
|
+
}
|
package/dist/analyze/types.d.ts
CHANGED
|
@@ -79,6 +79,13 @@ interface AnalysisUsageCommand {
|
|
|
79
79
|
name: string;
|
|
80
80
|
command: string;
|
|
81
81
|
}
|
|
82
|
+
interface AnalysisReadmeUsage {
|
|
83
|
+
title: string | null;
|
|
84
|
+
description: string | null;
|
|
85
|
+
language: string;
|
|
86
|
+
code: string;
|
|
87
|
+
sourcePath: string;
|
|
88
|
+
}
|
|
82
89
|
export interface AnalysisBadge {
|
|
83
90
|
id: string;
|
|
84
91
|
label: string;
|
|
@@ -156,6 +163,7 @@ export interface AnalysisResult {
|
|
|
156
163
|
badges: AnalysisBadge[];
|
|
157
164
|
sequenceScenarios: AnalysisSequenceScenario[];
|
|
158
165
|
usage: AnalysisUsage | null;
|
|
166
|
+
readmeUsage: AnalysisReadmeUsage[];
|
|
159
167
|
config: {
|
|
160
168
|
exportName: string;
|
|
161
169
|
isReadme: boolean;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/***
|
|
2
2
|
* Parsed representation of a Paradox doc comment.
|
|
3
3
|
*/
|
|
4
|
-
interface ParsedParadoxComment {
|
|
4
|
+
export interface ParsedParadoxComment {
|
|
5
5
|
description: string | null;
|
|
6
6
|
isConfig: boolean;
|
|
7
7
|
isReadme: boolean;
|
|
8
|
+
isUsage: boolean;
|
|
8
9
|
examples: ParsedExample[];
|
|
9
10
|
params: Record<string, string>;
|
|
10
11
|
returns: string | null;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const USAGE_TAG = `${String.fromCharCode(64)}usage`;
|
|
1
2
|
/***
|
|
2
3
|
* Parses a Paradox doc comment into structured metadata.
|
|
3
4
|
*/
|
|
@@ -7,6 +8,7 @@ export function parseParadoxComment(rawComment) {
|
|
|
7
8
|
const examples = [];
|
|
8
9
|
let isConfig = false;
|
|
9
10
|
let isReadme = false;
|
|
11
|
+
let isUsage = false;
|
|
10
12
|
const params = {};
|
|
11
13
|
let returns = null;
|
|
12
14
|
for (let index = 0; index < lines.length; index += 1) {
|
|
@@ -20,6 +22,10 @@ export function parseParadoxComment(rawComment) {
|
|
|
20
22
|
isReadme = true;
|
|
21
23
|
continue;
|
|
22
24
|
}
|
|
25
|
+
if (trimmed.startsWith(USAGE_TAG)) {
|
|
26
|
+
isUsage = true;
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
23
29
|
if (trimmed.startsWith('@example')) {
|
|
24
30
|
const parsed = parseExample(lines, index);
|
|
25
31
|
examples.push(parsed.example);
|
|
@@ -46,6 +52,7 @@ export function parseParadoxComment(rawComment) {
|
|
|
46
52
|
description: description.length > 0 ? description : null,
|
|
47
53
|
isConfig,
|
|
48
54
|
isReadme,
|
|
55
|
+
isUsage,
|
|
49
56
|
examples,
|
|
50
57
|
params,
|
|
51
58
|
returns,
|
package/dist/config/types.d.ts
CHANGED
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
/***
|
|
2
|
-
* Supported Paradox documentation tags.
|
|
3
|
-
*
|
|
4
|
-
* Paradox supports doc tags inside triple-star documentation comments.
|
|
5
|
-
*
|
|
6
|
-
* @readme
|
|
7
|
-
*/
|
|
8
1
|
export declare const PARADOX_DOC_TAGS: readonly [{
|
|
9
2
|
readonly name: "readme";
|
|
10
3
|
readonly syntax: "@readme";
|
|
@@ -26,6 +19,13 @@ export declare const PARADOX_DOC_TAGS: readonly [{
|
|
|
26
19
|
readonly appliesTo: readonly ["symbol"];
|
|
27
20
|
readonly repeatable: true;
|
|
28
21
|
readonly handler: "parseExample";
|
|
22
|
+
}, {
|
|
23
|
+
readonly name: "usage";
|
|
24
|
+
readonly syntax: "@usage";
|
|
25
|
+
readonly description: "Promotes a real source example into the generated README Usage section.";
|
|
26
|
+
readonly appliesTo: readonly ["block", "symbol"];
|
|
27
|
+
readonly repeatable: false;
|
|
28
|
+
readonly handler: "markUsage";
|
|
29
29
|
}];
|
|
30
30
|
export type ParadoxDocTagName = (typeof PARADOX_DOC_TAGS)[number]['name'];
|
|
31
31
|
export type ParadoxDocTagHandlerId = (typeof PARADOX_DOC_TAGS)[number]['handler'];
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
*
|
|
6
6
|
* @readme
|
|
7
7
|
*/
|
|
8
|
+
const DOC_TAG_PREFIX = '\u0040';
|
|
9
|
+
const USAGE_DOC_TAG = `${DOC_TAG_PREFIX}usage`;
|
|
8
10
|
export const PARADOX_DOC_TAGS = [
|
|
9
11
|
{
|
|
10
12
|
name: 'readme',
|
|
@@ -30,6 +32,14 @@ export const PARADOX_DOC_TAGS = [
|
|
|
30
32
|
repeatable: true,
|
|
31
33
|
handler: 'parseExample',
|
|
32
34
|
},
|
|
35
|
+
{
|
|
36
|
+
name: 'usage',
|
|
37
|
+
syntax: USAGE_DOC_TAG,
|
|
38
|
+
description: 'Promotes a real source example into the generated README Usage section.',
|
|
39
|
+
appliesTo: ['block', 'symbol'],
|
|
40
|
+
repeatable: false,
|
|
41
|
+
handler: 'markUsage',
|
|
42
|
+
},
|
|
33
43
|
];
|
|
34
44
|
/***
|
|
35
45
|
* Looks up documentation tag metadata by tag name.
|
|
@@ -107,6 +107,13 @@ interface BuildModelInput {
|
|
|
107
107
|
command: string;
|
|
108
108
|
}[];
|
|
109
109
|
} | null;
|
|
110
|
+
readmeUsage: {
|
|
111
|
+
title: string | null;
|
|
112
|
+
description: string | null;
|
|
113
|
+
language: string;
|
|
114
|
+
code: string;
|
|
115
|
+
sourcePath: string;
|
|
116
|
+
}[];
|
|
110
117
|
config: {
|
|
111
118
|
exportName: string;
|
|
112
119
|
isReadme: boolean;
|
package/dist/model/buildModel.js
CHANGED
|
@@ -24,6 +24,15 @@ export function buildModel(analysis) {
|
|
|
24
24
|
}))),
|
|
25
25
|
}
|
|
26
26
|
: null,
|
|
27
|
+
readmeUsage: analysis.readmeUsage
|
|
28
|
+
.map((usageEntry) => ({
|
|
29
|
+
title: usageEntry.title,
|
|
30
|
+
description: usageEntry.description,
|
|
31
|
+
language: usageEntry.language,
|
|
32
|
+
code: usageEntry.code,
|
|
33
|
+
sourcePath: usageEntry.sourcePath,
|
|
34
|
+
}))
|
|
35
|
+
.sort((left, right) => left.sourcePath.localeCompare(right.sourcePath)),
|
|
27
36
|
config: analysis.config !== null
|
|
28
37
|
? {
|
|
29
38
|
exportName: analysis.config.exportName,
|
package/dist/model/types.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export interface DocumentationModel {
|
|
|
7
7
|
description: string | null;
|
|
8
8
|
badges: GeneratedBadge[];
|
|
9
9
|
usage: UsageModel | null;
|
|
10
|
+
readmeUsage: ReadmeUsageModel[];
|
|
10
11
|
config: ConfigModel | null;
|
|
11
12
|
entrypoints: string[];
|
|
12
13
|
modules: ModuleModel[];
|
|
@@ -30,6 +31,13 @@ interface UsageCommandModel {
|
|
|
30
31
|
name: string;
|
|
31
32
|
command: string;
|
|
32
33
|
}
|
|
34
|
+
interface ReadmeUsageModel {
|
|
35
|
+
title: string | null;
|
|
36
|
+
description: string | null;
|
|
37
|
+
language: string;
|
|
38
|
+
code: string;
|
|
39
|
+
sourcePath: string;
|
|
40
|
+
}
|
|
33
41
|
interface ConfigModel {
|
|
34
42
|
exportName: string;
|
|
35
43
|
isReadme: boolean;
|
|
@@ -21,25 +21,41 @@ function renderReadme(model, outputDir, badges, diagrams) {
|
|
|
21
21
|
.map((badge) => ``)
|
|
22
22
|
.join(' '), '');
|
|
23
23
|
}
|
|
24
|
-
if (model.description)
|
|
24
|
+
if (model.description)
|
|
25
25
|
lines.push(model.description, '');
|
|
26
|
-
|
|
26
|
+
renderReadmeUsage(lines, model.readmeUsage);
|
|
27
27
|
if (model.usage !== null) {
|
|
28
|
-
lines.push('## Installation', '');
|
|
29
|
-
|
|
30
|
-
for (const command of model.usage.commands) {
|
|
28
|
+
lines.push('## Installation', '', '```bash');
|
|
29
|
+
for (const command of model.usage.commands)
|
|
31
30
|
lines.push(command.command);
|
|
32
|
-
}
|
|
33
31
|
lines.push('```', '');
|
|
34
32
|
}
|
|
35
33
|
renderCliScenarios(lines, model, outputDir, diagrams);
|
|
36
|
-
if (model.config?.isReadme)
|
|
34
|
+
if (model.config?.isReadme)
|
|
37
35
|
renderConfiguration(lines, model);
|
|
38
|
-
}
|
|
39
36
|
renderGeneratedDocumentation(lines, outputDir, diagrams);
|
|
40
37
|
renderReadmeApi(lines, model);
|
|
41
38
|
return `${lines.join('\n').trimEnd()}\n`;
|
|
42
39
|
}
|
|
40
|
+
function renderReadmeUsage(lines, entries) {
|
|
41
|
+
if (entries.length === 0)
|
|
42
|
+
return;
|
|
43
|
+
lines.push('## Usage', '');
|
|
44
|
+
for (const entry of entries) {
|
|
45
|
+
if (entry.title !== null)
|
|
46
|
+
lines.push(`### ${entry.title}`, '');
|
|
47
|
+
if (entry.description !== null) {
|
|
48
|
+
const [, ...rest] = entry.description.split('\n');
|
|
49
|
+
const description = rest.join('\n').trim();
|
|
50
|
+
if (description.length > 0)
|
|
51
|
+
lines.push(description, '');
|
|
52
|
+
}
|
|
53
|
+
lines.push(`Source: \`${entry.sourcePath}\``, '');
|
|
54
|
+
lines.push(`\`\`\`${entry.language}`);
|
|
55
|
+
lines.push(entry.code);
|
|
56
|
+
lines.push('```', '');
|
|
57
|
+
}
|
|
58
|
+
}
|
|
43
59
|
function renderCliScenarios(lines, model, outputDir, diagrams) {
|
|
44
60
|
const scenarios = model.sequenceScenarios.filter((scenario) => scenario.kind === 'bin' && scenario.isReadme);
|
|
45
61
|
if (scenarios.length === 0)
|
|
@@ -48,9 +64,8 @@ function renderCliScenarios(lines, model, outputDir, diagrams) {
|
|
|
48
64
|
for (const scenario of scenarios) {
|
|
49
65
|
lines.push('<details>');
|
|
50
66
|
lines.push(`<summary>${scenario.name}</summary>`, '');
|
|
51
|
-
if (scenario.description !== null)
|
|
67
|
+
if (scenario.description !== null)
|
|
52
68
|
lines.push(scenario.description, '');
|
|
53
|
-
}
|
|
54
69
|
const command = model.usage?.commands.find((item) => item.name === scenario.name);
|
|
55
70
|
if (command !== undefined) {
|
|
56
71
|
lines.push('```bash');
|
|
@@ -94,25 +109,24 @@ function renderConfiguration(lines, model) {
|
|
|
94
109
|
lines.push('export default config;');
|
|
95
110
|
}
|
|
96
111
|
lines.push('```', '');
|
|
97
|
-
if (config.members.length
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
}
|
|
105
|
-
lines.push('', '</details>', '');
|
|
112
|
+
if (config.members.length === 0)
|
|
113
|
+
return;
|
|
114
|
+
lines.push('<details>');
|
|
115
|
+
lines.push('<summary>Configuration options</summary>', '');
|
|
116
|
+
lines.push('| Field | Type | Required | Default | Description |');
|
|
117
|
+
lines.push('| --- | --- | --- | --- | --- |');
|
|
118
|
+
for (const configMember of flattenConfigMembers(config.members)) {
|
|
119
|
+
lines.push(`| ${escapeTableCell(configMember.path)} | \`${escapeTableCell(configMember.type)}\` | ${configMember.required ? 'yes' : 'no'} | ${renderDefault(configMember.defaultValue)} | ${escapeTableCell(configMember.description ?? '')} |`);
|
|
106
120
|
}
|
|
121
|
+
lines.push('', '</details>', '');
|
|
107
122
|
}
|
|
108
123
|
function renderGeneratedDocumentation(lines, outputDir, diagrams) {
|
|
109
124
|
lines.push('## Generated documentation', '');
|
|
110
125
|
lines.push(`- [Interactive documentation app](./${outputDir}/index.html)`);
|
|
111
126
|
lines.push(`- [Public API reference](./${outputDir}/exports.md)`);
|
|
112
127
|
lines.push(`- [Component registry](./${outputDir}/components.md)`);
|
|
113
|
-
for (const diagram of diagrams)
|
|
128
|
+
for (const diagram of diagrams)
|
|
114
129
|
lines.push(`- [${diagram.title}](./${outputDir}/${diagram.path})`);
|
|
115
|
-
}
|
|
116
130
|
lines.push('');
|
|
117
131
|
}
|
|
118
132
|
function renderReadmeApi(lines, model) {
|
|
@@ -123,12 +137,10 @@ function renderReadmeApi(lines, model) {
|
|
|
123
137
|
for (const group of groups) {
|
|
124
138
|
lines.push(`### ${group.title}`, '');
|
|
125
139
|
for (const item of group.items) {
|
|
126
|
-
if (item.kind === 'component')
|
|
140
|
+
if (item.kind === 'component')
|
|
127
141
|
renderComponentAccordion(lines, item.component, item.exportEntry);
|
|
128
|
-
|
|
129
|
-
else {
|
|
142
|
+
else
|
|
130
143
|
renderExportAccordion(lines, item.exportEntry);
|
|
131
|
-
}
|
|
132
144
|
}
|
|
133
145
|
}
|
|
134
146
|
}
|
|
@@ -205,9 +217,8 @@ function renderStructuredRows(lines, item) {
|
|
|
205
217
|
function getStructuredColumns(item) {
|
|
206
218
|
const columns = new Set();
|
|
207
219
|
for (const row of item.structuredRows) {
|
|
208
|
-
for (const column of Object.keys(row.values))
|
|
220
|
+
for (const column of Object.keys(row.values))
|
|
209
221
|
columns.add(column);
|
|
210
|
-
}
|
|
211
222
|
}
|
|
212
223
|
return [...columns];
|
|
213
224
|
}
|
|
@@ -216,9 +227,8 @@ function formatStructuredColumnHeader(column) {
|
|
|
216
227
|
}
|
|
217
228
|
function formatStructuredCell(column, value) {
|
|
218
229
|
const escaped = escapeTableCell(value);
|
|
219
|
-
if (column === 'syntax' || column === 'name' || column === 'handler')
|
|
230
|
+
if (column === 'syntax' || column === 'name' || column === 'handler')
|
|
220
231
|
return `\`${escaped}\``;
|
|
221
|
-
}
|
|
222
232
|
if (value === 'true')
|
|
223
233
|
return 'yes';
|
|
224
234
|
if (value === 'false')
|
|
@@ -290,9 +300,8 @@ function renderExports(model) {
|
|
|
290
300
|
lines.push(`Kind: \`${item.kind}\``);
|
|
291
301
|
lines.push(`Module: \`${item.modulePath}\``);
|
|
292
302
|
lines.push(`Source: \`${item.sourceLocation.filePath}:${item.sourceLocation.line}:${item.sourceLocation.column}\``, '');
|
|
293
|
-
if (item.description)
|
|
303
|
+
if (item.description)
|
|
294
304
|
lines.push(item.description, '');
|
|
295
|
-
}
|
|
296
305
|
renderStructuredRows(lines, item);
|
|
297
306
|
if (item.signatures.length > 0) {
|
|
298
307
|
lines.push('### Signatures', '');
|
|
@@ -322,9 +331,8 @@ function renderComponents(model) {
|
|
|
322
331
|
for (const component of model.components) {
|
|
323
332
|
lines.push(`## ${component.name}`, '');
|
|
324
333
|
lines.push(`Source: \`${component.sourceLocation.filePath}:${component.sourceLocation.line}:${component.sourceLocation.column}\``, '');
|
|
325
|
-
if (component.description)
|
|
334
|
+
if (component.description)
|
|
326
335
|
lines.push(component.description, '');
|
|
327
|
-
}
|
|
328
336
|
if (component.exportPaths.length > 0) {
|
|
329
337
|
lines.push(`Export paths: ${component.exportPaths.map((path) => `\`${path}\``).join(', ')}`, '');
|
|
330
338
|
}
|
|
@@ -361,9 +369,8 @@ function flattenConfigMembers(members, prefix = '') {
|
|
|
361
369
|
}
|
|
362
370
|
function badgeLabel(model, badgePath) {
|
|
363
371
|
const fileName = badgePath.split('/').pop();
|
|
364
|
-
if (!fileName)
|
|
372
|
+
if (!fileName)
|
|
365
373
|
return badgePath;
|
|
366
|
-
}
|
|
367
374
|
const id = fileName.replace(/\.svg$/, '');
|
|
368
375
|
const badge = model.badges.find((entry) => entry.id === id);
|
|
369
376
|
return badge ? `${badge.label}: ${badge.value}` : badgePath;
|