@ankhorage/paradox 0.0.9 → 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 +12 -0
- package/README.md +117 -13
- package/dist/analyze/analyze.d.ts +0 -3
- package/dist/analyze/analyze.js +47 -13
- package/dist/analyze/components.d.ts +4 -1
- package/dist/analyze/components.js +16 -2
- package/dist/analyze/exports.d.ts +1 -0
- package/dist/analyze/exports.js +16 -3
- package/dist/analyze/semantic/analyzeProject.d.ts +10 -0
- package/dist/analyze/semantic/analyzeProject.js +62 -0
- package/dist/analyze/semantic/associateDocBlocksWithSymbols.d.ts +5 -0
- package/dist/analyze/semantic/associateDocBlocksWithSymbols.js +51 -0
- package/dist/analyze/semantic/collectSourceFiles.d.ts +5 -0
- package/dist/analyze/semantic/collectSourceFiles.js +22 -0
- package/dist/analyze/semantic/createTypeScriptProgram.d.ts +13 -0
- package/dist/analyze/semantic/createTypeScriptProgram.js +23 -0
- package/dist/analyze/semantic/docBlocks.d.ts +10 -0
- package/dist/analyze/semantic/docBlocks.js +102 -0
- package/dist/analyze/semantic/exports.d.ts +29 -0
- package/dist/analyze/semantic/exports.js +399 -0
- package/dist/analyze/semantic/graphs.d.ts +13 -0
- package/dist/analyze/semantic/graphs.js +151 -0
- package/dist/analyze/semantic/isReactComponent.d.ts +5 -0
- package/dist/analyze/semantic/isReactComponent.js +30 -0
- package/dist/analyze/semantic/model.d.ts +109 -0
- package/dist/analyze/semantic/model.js +1 -0
- package/dist/analyze/semantic/paradoxComment.d.ts +16 -0
- package/dist/analyze/semantic/paradoxComment.js +64 -0
- package/dist/analyze/semantic/tagRegistry.d.ts +2 -0
- package/dist/analyze/semantic/tagRegistry.js +1 -0
- package/dist/analyze/semantic/utils.d.ts +4 -0
- package/dist/analyze/semantic/utils.js +13 -0
- package/dist/analyze/types.d.ts +55 -1
- 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 +56 -7
- package/dist/model/buildModel.js +16 -0
- package/dist/model/types.d.ts +62 -8
- package/dist/paths/policy.d.ts +0 -15
- package/dist/paths/policy.js +0 -9
- package/dist/render/renderers/diagrams.js +11 -0
- package/dist/render/renderers/markdown.js +235 -39
- package/package.json +15 -6
package/dist/model/types.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ export interface DocumentationModel {
|
|
|
12
12
|
modules: ModuleModel[];
|
|
13
13
|
exports: ExportModel[];
|
|
14
14
|
components: ComponentModel[];
|
|
15
|
+
graphs: GraphModel;
|
|
15
16
|
}
|
|
16
17
|
export interface GeneratedBadge {
|
|
17
18
|
id: string;
|
|
@@ -19,22 +20,26 @@ export interface GeneratedBadge {
|
|
|
19
20
|
value: string;
|
|
20
21
|
color: string;
|
|
21
22
|
}
|
|
22
|
-
|
|
23
|
+
interface UsageModel {
|
|
23
24
|
packageName: string;
|
|
24
25
|
commands: UsageCommandModel[];
|
|
25
26
|
}
|
|
26
|
-
|
|
27
|
+
interface UsageCommandModel {
|
|
27
28
|
name: string;
|
|
28
29
|
command: string;
|
|
29
30
|
}
|
|
30
|
-
|
|
31
|
+
interface ConfigModel {
|
|
31
32
|
exportName: string;
|
|
33
|
+
isReadme: boolean;
|
|
32
34
|
configFile: string;
|
|
33
35
|
factoryName: string | null;
|
|
36
|
+
members: ConfigMemberModel[];
|
|
34
37
|
}
|
|
35
38
|
export interface ExportModel {
|
|
36
39
|
name: string;
|
|
37
40
|
description: string | null;
|
|
41
|
+
isReadme: boolean;
|
|
42
|
+
examples: ExampleModel[];
|
|
38
43
|
kind: ExportKind;
|
|
39
44
|
modulePath: string;
|
|
40
45
|
sourceLocation: SourceLocationModel;
|
|
@@ -47,34 +52,44 @@ export type ExportKind = 'function' | 'type' | 'unknown';
|
|
|
47
52
|
export interface ComponentModel {
|
|
48
53
|
name: string;
|
|
49
54
|
description: string | null;
|
|
55
|
+
isReadme: boolean;
|
|
56
|
+
examples: ExampleModel[];
|
|
50
57
|
modulePath: string;
|
|
51
58
|
sourceLocation: SourceLocationModel;
|
|
52
59
|
exportPaths: string[];
|
|
53
60
|
props: PropModel[];
|
|
54
61
|
}
|
|
55
|
-
|
|
62
|
+
interface ExampleModel {
|
|
63
|
+
title: string | null;
|
|
64
|
+
language: string | null;
|
|
65
|
+
code: string;
|
|
66
|
+
}
|
|
67
|
+
interface SourceLocationModel {
|
|
56
68
|
filePath: string;
|
|
57
69
|
line: number;
|
|
58
70
|
column: number;
|
|
59
71
|
}
|
|
60
|
-
|
|
72
|
+
interface SignatureModel {
|
|
61
73
|
label: string;
|
|
62
74
|
parameters: ParameterModel[];
|
|
63
75
|
returnType: string | null;
|
|
64
76
|
returnDescription: string | null;
|
|
65
77
|
}
|
|
66
|
-
|
|
78
|
+
interface ParameterModel {
|
|
67
79
|
name: string;
|
|
68
80
|
type: string;
|
|
69
81
|
required: boolean;
|
|
70
82
|
description: string | null;
|
|
71
83
|
}
|
|
72
|
-
|
|
84
|
+
interface MemberModel {
|
|
73
85
|
name: string;
|
|
74
86
|
kind: 'property' | 'method';
|
|
75
87
|
type: string;
|
|
76
88
|
required: boolean;
|
|
77
89
|
description: string | null;
|
|
90
|
+
defaultValue?: string;
|
|
91
|
+
inheritedFrom?: string;
|
|
92
|
+
children?: MemberModel[];
|
|
78
93
|
}
|
|
79
94
|
export interface ModuleModel {
|
|
80
95
|
path: string;
|
|
@@ -82,9 +97,48 @@ export interface ModuleModel {
|
|
|
82
97
|
dependencies: string[];
|
|
83
98
|
exports: string[];
|
|
84
99
|
}
|
|
85
|
-
|
|
100
|
+
interface PropModel {
|
|
101
|
+
name: string;
|
|
102
|
+
type: string;
|
|
103
|
+
required: boolean;
|
|
104
|
+
defaultValue?: string;
|
|
105
|
+
description: string | null;
|
|
106
|
+
}
|
|
107
|
+
interface ConfigMemberModel {
|
|
86
108
|
name: string;
|
|
87
109
|
type: string;
|
|
88
110
|
required: boolean;
|
|
89
111
|
description: string | null;
|
|
112
|
+
defaultValue?: string;
|
|
113
|
+
inheritedFrom?: string;
|
|
114
|
+
children?: ConfigMemberModel[];
|
|
115
|
+
}
|
|
116
|
+
interface GraphModel {
|
|
117
|
+
imports: ImportEdgeModel[];
|
|
118
|
+
calls: CallEdgeModel[];
|
|
119
|
+
typeReferences: TypeReferenceEdgeModel[];
|
|
120
|
+
componentComposition: ComponentCompositionEdgeModel[];
|
|
121
|
+
}
|
|
122
|
+
interface ImportEdgeModel {
|
|
123
|
+
fromPath: string;
|
|
124
|
+
toPath: string;
|
|
125
|
+
sourcePath: string;
|
|
126
|
+
}
|
|
127
|
+
interface CallEdgeModel {
|
|
128
|
+
fromSymbol: string;
|
|
129
|
+
toSymbol: string;
|
|
130
|
+
callExpression: string;
|
|
131
|
+
sourcePath: string;
|
|
132
|
+
}
|
|
133
|
+
interface TypeReferenceEdgeModel {
|
|
134
|
+
fromSymbol: string;
|
|
135
|
+
toType: string;
|
|
136
|
+
sourcePath: string;
|
|
137
|
+
}
|
|
138
|
+
interface ComponentCompositionEdgeModel {
|
|
139
|
+
fromComponent: string;
|
|
140
|
+
toComponent: string;
|
|
141
|
+
jsxElement: string;
|
|
142
|
+
sourcePath: string;
|
|
90
143
|
}
|
|
144
|
+
export {};
|
package/dist/paths/policy.d.ts
CHANGED
|
@@ -1,12 +1,4 @@
|
|
|
1
1
|
import type { ParadoxConfig } from '../config/types.js';
|
|
2
|
-
export interface ResolvedRuntimePaths {
|
|
3
|
-
cwd: string;
|
|
4
|
-
configFilePath: string;
|
|
5
|
-
configDir: string;
|
|
6
|
-
packageRoot: string;
|
|
7
|
-
outputDir: string;
|
|
8
|
-
outputRoot: string;
|
|
9
|
-
}
|
|
10
2
|
export declare function findParadoxConfigFile(startDir: string): Promise<string | null>;
|
|
11
3
|
export declare function loadParadoxConfig(configFilePath: string): Promise<ParadoxConfig>;
|
|
12
4
|
export declare function resolvePackageRoot(config: ParadoxConfig, configDir: string): Promise<string>;
|
|
@@ -14,10 +6,3 @@ export declare function resolveOutputRoot(config: ParadoxConfig, packageRoot: st
|
|
|
14
6
|
outputDir: string;
|
|
15
7
|
outputRoot: string;
|
|
16
8
|
};
|
|
17
|
-
export declare function resolveWriteTargets(packageRoot: string, outputRoot: string): {
|
|
18
|
-
exportsMarkdown: string;
|
|
19
|
-
componentsMarkdown: string;
|
|
20
|
-
exportsJson: string;
|
|
21
|
-
paradoxJson: string;
|
|
22
|
-
readme: string;
|
|
23
|
-
};
|
package/dist/paths/policy.js
CHANGED
|
@@ -48,15 +48,6 @@ export function resolveOutputRoot(config, packageRoot) {
|
|
|
48
48
|
assertWithinRoot(outputRoot, packageRoot, `Resolved output directory escapes package root: ${outputRoot}`);
|
|
49
49
|
return { outputDir, outputRoot };
|
|
50
50
|
}
|
|
51
|
-
export function resolveWriteTargets(packageRoot, outputRoot) {
|
|
52
|
-
return {
|
|
53
|
-
exportsMarkdown: join(outputRoot, 'exports.md'),
|
|
54
|
-
componentsMarkdown: join(outputRoot, 'components.md'),
|
|
55
|
-
exportsJson: join(outputRoot, 'exports.json'),
|
|
56
|
-
paradoxJson: join(outputRoot, 'paradox.json'),
|
|
57
|
-
readme: join(packageRoot, 'README.md'),
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
51
|
async function pathExists(path) {
|
|
61
52
|
try {
|
|
62
53
|
await access(path);
|
|
@@ -73,6 +73,17 @@ function renderExportGraph(model) {
|
|
|
73
73
|
}
|
|
74
74
|
function renderEntrypointSequence(model) {
|
|
75
75
|
const lines = ['sequenceDiagram'];
|
|
76
|
+
const callEdges = model.graphs.calls;
|
|
77
|
+
if (callEdges.length > 0) {
|
|
78
|
+
const participants = new Map();
|
|
79
|
+
for (const edge of callEdges) {
|
|
80
|
+
participants.set(edge.fromSymbol, `participant ${toMermaidId(`participant-${edge.fromSymbol}`)} as ${edge.fromSymbol}`);
|
|
81
|
+
participants.set(edge.toSymbol, `participant ${toMermaidId(`participant-${edge.toSymbol}`)} as ${edge.toSymbol}`);
|
|
82
|
+
}
|
|
83
|
+
lines.push(...participants.values());
|
|
84
|
+
lines.push(...callEdges.map((edge) => ` ${toMermaidId(`participant-${edge.fromSymbol}`)}->>${toMermaidId(`participant-${edge.toSymbol}`)}: ${edge.callExpression}`));
|
|
85
|
+
return `${lines.join('\n')}\n`;
|
|
86
|
+
}
|
|
76
87
|
const participants = new Map();
|
|
77
88
|
for (const module of model.modules) {
|
|
78
89
|
participants.set(module.path, `participant ${toMermaidId(`participant-${module.path}`)} as ${module.path}`);
|
|
@@ -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,35 +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
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
lines
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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) {
|
|
55
|
+
const { config } = model;
|
|
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 ?? '')} |`);
|
|
48
85
|
}
|
|
49
|
-
lines.push('
|
|
86
|
+
lines.push('', '</details>', '');
|
|
50
87
|
}
|
|
88
|
+
}
|
|
89
|
+
function renderGeneratedDocumentation(lines, outputDir, diagrams) {
|
|
51
90
|
lines.push('## Generated documentation', '');
|
|
52
91
|
lines.push(`- [Interactive documentation app](./${outputDir}/index.html)`);
|
|
53
92
|
lines.push(`- [Public API reference](./${outputDir}/exports.md)`);
|
|
@@ -56,12 +95,19 @@ function renderReadme(model, outputDir, badges, diagrams) {
|
|
|
56
95
|
lines.push(`- [${diagram.title}](./${outputDir}/${diagram.path})`);
|
|
57
96
|
}
|
|
58
97
|
lines.push('');
|
|
98
|
+
}
|
|
99
|
+
function renderArchitecturePreview(lines, diagrams) {
|
|
59
100
|
lines.push('## Architecture preview', '');
|
|
60
101
|
if (diagrams.length > 0) {
|
|
102
|
+
lines.push('<details>');
|
|
103
|
+
lines.push('<summary>Architecture overview</summary>', '');
|
|
61
104
|
lines.push('```mermaid');
|
|
62
|
-
lines.push(diagrams[0]
|
|
105
|
+
lines.push(diagrams[0]?.content.trimEnd() ?? '');
|
|
63
106
|
lines.push('```', '');
|
|
107
|
+
lines.push('</details>', '');
|
|
64
108
|
}
|
|
109
|
+
}
|
|
110
|
+
function renderPathResolution(lines) {
|
|
65
111
|
lines.push('## Path resolution', '');
|
|
66
112
|
lines.push('- Config discovery: searches upward from `process.cwd()` for `paradox.config.ts/js/mjs/cjs` (required; no fallback).');
|
|
67
113
|
lines.push('- Package root: defaults to the directory containing `paradox.config.*`; `package.root` (when relative) resolves relative to that directory.');
|
|
@@ -69,22 +115,131 @@ function renderReadme(model, outputDir, badges, diagrams) {
|
|
|
69
115
|
lines.push('- Modes:');
|
|
70
116
|
lines.push(' - `safe`: writes generated artifacts only under the output directory');
|
|
71
117
|
lines.push(' - `write`: additionally updates `<packageRoot>/README.md`', '');
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
if (item.
|
|
82
|
-
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);
|
|
83
132
|
}
|
|
84
|
-
lines.push('');
|
|
85
133
|
}
|
|
86
134
|
}
|
|
87
|
-
|
|
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';
|
|
88
243
|
}
|
|
89
244
|
function renderExports(model) {
|
|
90
245
|
const lines = ['# Public API', ''];
|
|
@@ -131,10 +286,10 @@ function renderComponents(model) {
|
|
|
131
286
|
lines.push(`Export paths: ${component.exportPaths.map((path) => `\`${path}\``).join(', ')}`, '');
|
|
132
287
|
}
|
|
133
288
|
if (component.props.length > 0) {
|
|
134
|
-
lines.push('| Prop | Type | Required | Description |');
|
|
135
|
-
lines.push('| --- | --- | --- | --- |');
|
|
289
|
+
lines.push('| Prop | Type | Required | Default | Description |');
|
|
290
|
+
lines.push('| --- | --- | --- | --- | --- |');
|
|
136
291
|
for (const prop of component.props) {
|
|
137
|
-
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 ?? '')} |`);
|
|
138
293
|
}
|
|
139
294
|
lines.push('');
|
|
140
295
|
}
|
|
@@ -144,6 +299,23 @@ function renderComponents(model) {
|
|
|
144
299
|
function escapeTableCell(value) {
|
|
145
300
|
return value.replaceAll('|', '\\|');
|
|
146
301
|
}
|
|
302
|
+
function renderDefault(value) {
|
|
303
|
+
return value === undefined ? '—' : `\`${escapeTableCell(value)}\``;
|
|
304
|
+
}
|
|
305
|
+
function flattenConfigMembers(members, prefix = '') {
|
|
306
|
+
return members.flatMap((member) => {
|
|
307
|
+
const path = prefix ? `${prefix}.${member.name}` : member.name;
|
|
308
|
+
const current = {
|
|
309
|
+
path,
|
|
310
|
+
type: member.type,
|
|
311
|
+
required: member.required,
|
|
312
|
+
description: member.description,
|
|
313
|
+
defaultValue: member.defaultValue,
|
|
314
|
+
};
|
|
315
|
+
const children = member.children ? flattenConfigMembers(member.children, path) : [];
|
|
316
|
+
return [current, ...children];
|
|
317
|
+
});
|
|
318
|
+
}
|
|
147
319
|
function badgeLabel(model, badgePath) {
|
|
148
320
|
const fileName = badgePath.split('/').pop();
|
|
149
321
|
if (!fileName) {
|
|
@@ -153,3 +325,27 @@ function badgeLabel(model, badgePath) {
|
|
|
153
325
|
const badge = model.badges.find((entry) => entry.id === id);
|
|
154
326
|
return badge ? `${badge.label}: ${badge.value}` : badgePath;
|
|
155
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
|
+
];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ankhorage/paradox",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "Deterministic documentation generator for TypeScript packages.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"publishConfig": {
|
|
@@ -14,6 +14,14 @@
|
|
|
14
14
|
"type": "git",
|
|
15
15
|
"url": "git+https://github.com/ankhorage/paradox.git"
|
|
16
16
|
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"typescript",
|
|
19
|
+
"documentation",
|
|
20
|
+
"readme-generator",
|
|
21
|
+
"static-analysis",
|
|
22
|
+
"ts-morph",
|
|
23
|
+
"developer-tools"
|
|
24
|
+
],
|
|
17
25
|
"type": "module",
|
|
18
26
|
"main": "./dist/index.js",
|
|
19
27
|
"types": "./dist/index.d.ts",
|
|
@@ -39,10 +47,11 @@
|
|
|
39
47
|
"changeset:status": "changeset status --since=origin/main",
|
|
40
48
|
"docs": "bun src/cli.ts",
|
|
41
49
|
"docs:bunx": "bunx @ankhorage/paradox",
|
|
42
|
-
"format": "prettier --write .",
|
|
43
|
-
"format:check": "prettier --check .",
|
|
44
|
-
"
|
|
45
|
-
"lint
|
|
50
|
+
"format": "ankhorage-prettier --write .",
|
|
51
|
+
"format:check": "ankhorage-prettier --check .",
|
|
52
|
+
"knip": "ankhorage-knip",
|
|
53
|
+
"lint": "ankhorage-eslint . --max-warnings=0",
|
|
54
|
+
"lint:fix": "ankhorage-eslint . --fix --max-warnings=0",
|
|
46
55
|
"prepack": "bun run build",
|
|
47
56
|
"test": "bun test",
|
|
48
57
|
"typecheck": "bun x tsc --noEmit -p tsconfig.json",
|
|
@@ -52,7 +61,7 @@
|
|
|
52
61
|
"ts-morph": "^24.0.0"
|
|
53
62
|
},
|
|
54
63
|
"devDependencies": {
|
|
55
|
-
"@ankhorage/devtools": "^1.0.
|
|
64
|
+
"@ankhorage/devtools": "^1.0.5",
|
|
56
65
|
"@changesets/cli": "^2.31.0",
|
|
57
66
|
"@types/bun": "^1.3.13",
|
|
58
67
|
"typescript": "^5.6.3"
|