@ankhorage/paradox 0.0.10 → 0.1.1
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 +82 -19
- package/dist/analyze/analyze.d.ts +0 -3
- package/dist/analyze/analyze.js +6 -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/sequenceScenarios.d.ts +14 -0
- package/dist/analyze/sequenceScenarios.js +174 -0
- package/dist/analyze/types.d.ts +20 -0
- package/dist/analyze/utils/parseParadoxComment.d.ts +7 -0
- package/dist/analyze/utils/parseParadoxComment.js +61 -14
- package/dist/cli.js +9 -0
- 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 +19 -0
- package/dist/model/buildModel.js +14 -0
- package/dist/model/types.d.ts +20 -0
- package/dist/render/renderers/diagrams.js +90 -28
- package/dist/render/renderers/markdown.js +258 -47
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -5,6 +5,15 @@ import { buildModel } from './model/buildModel.js';
|
|
|
5
5
|
import { findParadoxConfigFile, loadParadoxConfig, resolveOutputRoot, resolvePackageRoot, } from './paths/policy.js';
|
|
6
6
|
import { render } from './render/render.js';
|
|
7
7
|
import { write } from './write/write.js';
|
|
8
|
+
/***
|
|
9
|
+
* Runs the Paradox CLI.
|
|
10
|
+
*
|
|
11
|
+
* The command discovers the nearest Paradox config, resolves the package and output roots,
|
|
12
|
+
* analyzes the package, builds the documentation model, renders all documentation artifacts,
|
|
13
|
+
* and writes them to the configured output directory.
|
|
14
|
+
*
|
|
15
|
+
* @readme
|
|
16
|
+
*/
|
|
8
17
|
async function main() {
|
|
9
18
|
const cwd = process.cwd();
|
|
10
19
|
const configFilePath = await findParadoxConfigFile(cwd);
|
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,9 +76,18 @@ 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
|
}[];
|
|
83
|
+
sequenceScenarios: {
|
|
84
|
+
kind: 'bin' | 'export';
|
|
85
|
+
name: string;
|
|
86
|
+
sourcePath: string;
|
|
87
|
+
symbolName: string;
|
|
88
|
+
description: string | null;
|
|
89
|
+
isReadme: boolean;
|
|
90
|
+
}[];
|
|
73
91
|
usage: {
|
|
74
92
|
packageName: string;
|
|
75
93
|
commands: {
|
|
@@ -79,6 +97,7 @@ interface BuildModelInput {
|
|
|
79
97
|
} | null;
|
|
80
98
|
config: {
|
|
81
99
|
exportName: string;
|
|
100
|
+
isReadme: boolean;
|
|
82
101
|
members: ConfigMemberInput[];
|
|
83
102
|
} | null;
|
|
84
103
|
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(),
|
|
@@ -45,6 +46,14 @@ export function buildModel(analysis) {
|
|
|
45
46
|
.sort((left, right) => left.path.localeCompare(right.path)),
|
|
46
47
|
exports,
|
|
47
48
|
components: sortByName(analysis.components.map((component) => mapComponent(component, exportsByName.get(component.name)))),
|
|
49
|
+
sequenceScenarios: sortByName(analysis.sequenceScenarios.map((scenario) => ({
|
|
50
|
+
kind: scenario.kind,
|
|
51
|
+
name: scenario.name,
|
|
52
|
+
sourcePath: scenario.sourcePath,
|
|
53
|
+
symbolName: scenario.symbolName,
|
|
54
|
+
description: scenario.description,
|
|
55
|
+
isReadme: scenario.isReadme,
|
|
56
|
+
}))),
|
|
48
57
|
graphs: {
|
|
49
58
|
imports: [...analysis.graphs.imports],
|
|
50
59
|
calls: [...analysis.graphs.calls],
|
|
@@ -57,6 +66,8 @@ function mapExport(item, exportNames) {
|
|
|
57
66
|
return {
|
|
58
67
|
name: item.name,
|
|
59
68
|
description: item.description,
|
|
69
|
+
isReadme: item.isReadme,
|
|
70
|
+
examples: item.examples.map((example) => ({ ...example })),
|
|
60
71
|
kind: item.kind,
|
|
61
72
|
modulePath: item.modulePath,
|
|
62
73
|
sourceLocation: {
|
|
@@ -95,6 +106,8 @@ function mapComponent(component, exportModel) {
|
|
|
95
106
|
return {
|
|
96
107
|
name: component.name,
|
|
97
108
|
description: component.description,
|
|
109
|
+
isReadme: component.isReadme,
|
|
110
|
+
examples: component.examples.map((example) => ({ ...example })),
|
|
98
111
|
modulePath: component.modulePath,
|
|
99
112
|
sourceLocation: {
|
|
100
113
|
filePath: component.sourceLocation.filePath,
|
|
@@ -106,6 +119,7 @@ function mapComponent(component, exportModel) {
|
|
|
106
119
|
name: prop.name,
|
|
107
120
|
type: prop.type,
|
|
108
121
|
required: prop.required,
|
|
122
|
+
defaultValue: prop.defaultValue,
|
|
109
123
|
description: prop.description,
|
|
110
124
|
}))),
|
|
111
125
|
};
|
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
|
+
sequenceScenarios: SequenceScenarioModel[];
|
|
15
16
|
graphs: GraphModel;
|
|
16
17
|
}
|
|
17
18
|
export interface GeneratedBadge {
|
|
@@ -30,6 +31,7 @@ interface UsageCommandModel {
|
|
|
30
31
|
}
|
|
31
32
|
interface ConfigModel {
|
|
32
33
|
exportName: string;
|
|
34
|
+
isReadme: boolean;
|
|
33
35
|
configFile: string;
|
|
34
36
|
factoryName: string | null;
|
|
35
37
|
members: ConfigMemberModel[];
|
|
@@ -37,6 +39,8 @@ interface ConfigModel {
|
|
|
37
39
|
export interface ExportModel {
|
|
38
40
|
name: string;
|
|
39
41
|
description: string | null;
|
|
42
|
+
isReadme: boolean;
|
|
43
|
+
examples: ExampleModel[];
|
|
40
44
|
kind: ExportKind;
|
|
41
45
|
modulePath: string;
|
|
42
46
|
sourceLocation: SourceLocationModel;
|
|
@@ -49,11 +53,26 @@ export type ExportKind = 'function' | 'type' | 'unknown';
|
|
|
49
53
|
export interface ComponentModel {
|
|
50
54
|
name: string;
|
|
51
55
|
description: string | null;
|
|
56
|
+
isReadme: boolean;
|
|
57
|
+
examples: ExampleModel[];
|
|
52
58
|
modulePath: string;
|
|
53
59
|
sourceLocation: SourceLocationModel;
|
|
54
60
|
exportPaths: string[];
|
|
55
61
|
props: PropModel[];
|
|
56
62
|
}
|
|
63
|
+
export interface SequenceScenarioModel {
|
|
64
|
+
kind: 'bin' | 'export';
|
|
65
|
+
name: string;
|
|
66
|
+
sourcePath: string;
|
|
67
|
+
symbolName: string;
|
|
68
|
+
description: string | null;
|
|
69
|
+
isReadme: boolean;
|
|
70
|
+
}
|
|
71
|
+
interface ExampleModel {
|
|
72
|
+
title: string | null;
|
|
73
|
+
language: string | null;
|
|
74
|
+
code: string;
|
|
75
|
+
}
|
|
57
76
|
interface SourceLocationModel {
|
|
58
77
|
filePath: string;
|
|
59
78
|
line: number;
|
|
@@ -91,6 +110,7 @@ interface PropModel {
|
|
|
91
110
|
name: string;
|
|
92
111
|
type: string;
|
|
93
112
|
required: boolean;
|
|
113
|
+
defaultValue?: string;
|
|
94
114
|
description: string | null;
|
|
95
115
|
}
|
|
96
116
|
interface ConfigMemberModel {
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const MAX_SEQUENCE_CALL_EDGES = 12;
|
|
2
|
+
const MAX_SEQUENCE_PARTICIPANTS = 8;
|
|
1
3
|
/***
|
|
2
4
|
* Generates deterministic Mermaid diagrams for the documentation app.
|
|
3
5
|
*/
|
|
@@ -18,11 +20,7 @@ export function renderDiagramArtifacts(model) {
|
|
|
18
20
|
title: 'Export graph',
|
|
19
21
|
content: renderExportGraph(model),
|
|
20
22
|
},
|
|
21
|
-
|
|
22
|
-
path: 'diagrams/entrypoint-sequence.mmd',
|
|
23
|
-
title: 'Entrypoint sequence',
|
|
24
|
-
content: renderEntrypointSequence(model),
|
|
25
|
-
},
|
|
23
|
+
...renderSequenceArtifacts(model),
|
|
26
24
|
];
|
|
27
25
|
}
|
|
28
26
|
function renderArchitectureOverview(model) {
|
|
@@ -71,34 +69,88 @@ function renderExportGraph(model) {
|
|
|
71
69
|
}
|
|
72
70
|
return `${lines.join('\n')}\n`;
|
|
73
71
|
}
|
|
74
|
-
function
|
|
72
|
+
function renderSequenceArtifacts(model) {
|
|
73
|
+
return model.sequenceScenarios.flatMap((scenario) => {
|
|
74
|
+
const content = renderSequenceScenario(model, scenario);
|
|
75
|
+
if (content === null)
|
|
76
|
+
return [];
|
|
77
|
+
return [
|
|
78
|
+
{
|
|
79
|
+
path: `diagrams/sequences/${toFileStem(scenario.name)}.mmd`,
|
|
80
|
+
title: `${scenario.name} sequence`,
|
|
81
|
+
content,
|
|
82
|
+
},
|
|
83
|
+
];
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
function renderSequenceScenario(model, scenario) {
|
|
75
87
|
const lines = ['sequenceDiagram'];
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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`;
|
|
88
|
+
const reachableEdges = collectReachableCallEdges(model.graphs.calls, scenario.symbolName);
|
|
89
|
+
const participants = collectSequenceParticipants(reachableEdges);
|
|
90
|
+
if (reachableEdges.length === 0)
|
|
91
|
+
return null;
|
|
92
|
+
if (reachableEdges.length > MAX_SEQUENCE_CALL_EDGES ||
|
|
93
|
+
participants.length > MAX_SEQUENCE_PARTICIPANTS) {
|
|
94
|
+
return null;
|
|
86
95
|
}
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
participants.set(module.path, `participant ${toMermaidId(`participant-${module.path}`)} as ${module.path}`);
|
|
96
|
+
for (const participant of participants) {
|
|
97
|
+
lines.push(` participant ${toMermaidId(`participant-${participant}`)} as ${escapeLabel(participant)}`);
|
|
90
98
|
}
|
|
91
|
-
lines.
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
99
|
+
renderCallFlow(lines, reachableEdges, scenario.symbolName);
|
|
100
|
+
return `${lines.join('\n')}\n`;
|
|
101
|
+
}
|
|
102
|
+
function collectReachableCallEdges(callEdges, root) {
|
|
103
|
+
const outgoing = groupCallsBySource(callEdges);
|
|
104
|
+
const visited = new Set();
|
|
105
|
+
const ordered = [];
|
|
106
|
+
visitCallEdges(root, outgoing, visited, ordered);
|
|
107
|
+
return ordered;
|
|
108
|
+
}
|
|
109
|
+
function visitCallEdges(fromSymbol, outgoing, visited, ordered) {
|
|
110
|
+
for (const edge of outgoing.get(fromSymbol) ?? []) {
|
|
111
|
+
const key = getCallEdgeKey(edge);
|
|
112
|
+
if (visited.has(key))
|
|
113
|
+
continue;
|
|
114
|
+
visited.add(key);
|
|
115
|
+
ordered.push(edge);
|
|
116
|
+
visitCallEdges(edge.toSymbol, outgoing, visited, ordered);
|
|
97
117
|
}
|
|
98
|
-
|
|
99
|
-
|
|
118
|
+
}
|
|
119
|
+
function renderCallFlow(lines, callEdges, root) {
|
|
120
|
+
const outgoing = groupCallsBySource(callEdges);
|
|
121
|
+
const visited = new Set();
|
|
122
|
+
renderNestedCalls(lines, root, outgoing, visited);
|
|
123
|
+
}
|
|
124
|
+
function renderNestedCalls(lines, fromSymbol, outgoing, visited) {
|
|
125
|
+
for (const edge of outgoing.get(fromSymbol) ?? []) {
|
|
126
|
+
const key = getCallEdgeKey(edge);
|
|
127
|
+
if (visited.has(key))
|
|
128
|
+
continue;
|
|
129
|
+
visited.add(key);
|
|
130
|
+
const fromId = toMermaidId(`participant-${edge.fromSymbol}`);
|
|
131
|
+
const toId = toMermaidId(`participant-${edge.toSymbol}`);
|
|
132
|
+
lines.push(` ${fromId}->>${toId}: ${formatCallLabel(edge.callExpression)}`);
|
|
133
|
+
renderNestedCalls(lines, edge.toSymbol, outgoing, visited);
|
|
134
|
+
lines.push(` ${toId}-->>${fromId}: return`);
|
|
100
135
|
}
|
|
101
|
-
|
|
136
|
+
}
|
|
137
|
+
function groupCallsBySource(callEdges) {
|
|
138
|
+
const grouped = new Map();
|
|
139
|
+
for (const edge of callEdges) {
|
|
140
|
+
const existing = grouped.get(edge.fromSymbol) ?? [];
|
|
141
|
+
existing.push(edge);
|
|
142
|
+
grouped.set(edge.fromSymbol, existing);
|
|
143
|
+
}
|
|
144
|
+
return grouped;
|
|
145
|
+
}
|
|
146
|
+
function collectSequenceParticipants(callEdges) {
|
|
147
|
+
return uniqueSorted(callEdges.flatMap((edge) => [edge.fromSymbol, edge.toSymbol]));
|
|
148
|
+
}
|
|
149
|
+
function getCallEdgeKey(edge) {
|
|
150
|
+
return `${edge.fromSymbol}->${edge.toSymbol}@${edge.sourcePath}:${edge.callExpression}`;
|
|
151
|
+
}
|
|
152
|
+
function formatCallLabel(callExpression) {
|
|
153
|
+
return callExpression.endsWith(')') ? callExpression : `${callExpression}()`;
|
|
102
154
|
}
|
|
103
155
|
function renderFallbackEdge(modules, prefix) {
|
|
104
156
|
if (modules.length === 0) {
|
|
@@ -109,6 +161,16 @@ function renderFallbackEdge(modules, prefix) {
|
|
|
109
161
|
return ` ${toMermaidId(`${prefix}-${previous.path}`)} -.-> ${toMermaidId(`${prefix}-${module.path}`)}`;
|
|
110
162
|
});
|
|
111
163
|
}
|
|
164
|
+
function uniqueSorted(values) {
|
|
165
|
+
return [...new Set(values)].sort((left, right) => left.localeCompare(right));
|
|
166
|
+
}
|
|
167
|
+
function toFileStem(value) {
|
|
168
|
+
return value
|
|
169
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
|
|
170
|
+
.replace(/[^A-Za-z0-9]+/g, '-')
|
|
171
|
+
.replace(/^-+|-+$/g, '')
|
|
172
|
+
.toLowerCase();
|
|
173
|
+
}
|
|
112
174
|
function toMermaidId(value) {
|
|
113
175
|
return value.replace(/[^A-Za-z0-9_]/g, '_');
|
|
114
176
|
}
|