@ankhorage/paradox 0.1.17 → 0.1.18
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 +17 -4
- package/dist/analyze/analyze.d.ts +1 -0
- package/dist/analyze/analyze.js +6 -0
- package/dist/analyze/readmeConfig.d.ts +14 -0
- package/dist/analyze/readmeConfig.js +57 -0
- package/dist/analyze/types.d.ts +7 -0
- package/dist/cli/standalone.js +1 -1
- package/dist/doc-tags/registry.d.ts +2 -2
- package/dist/doc-tags/registry.js +2 -2
- package/dist/model/buildModel.d.ts +6 -0
- package/dist/model/buildModel.js +8 -21
- package/dist/model/types.d.ts +7 -2
- package/dist/render/renderers/markdown.js +11 -23
- 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
|
|
|
@@ -66,13 +66,26 @@ sequenceDiagram
|
|
|
66
66
|
|
|
67
67
|
## Configuration
|
|
68
68
|
|
|
69
|
-
|
|
69
|
+
Canonical Paradox configuration for this package.
|
|
70
70
|
|
|
71
71
|
```ts
|
|
72
|
-
import { defineParadoxConfig } from '
|
|
72
|
+
import { defineParadoxConfig } from './src/config/defineParadoxConfig.js';
|
|
73
73
|
|
|
74
74
|
export default defineParadoxConfig({
|
|
75
|
-
|
|
75
|
+
mode: 'write',
|
|
76
|
+
|
|
77
|
+
docs: {
|
|
78
|
+
title: '@ankhorage/paradox',
|
|
79
|
+
description: 'Deterministic documentation generator for TypeScript packages.',
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
package: {
|
|
83
|
+
entrypoints: ['src/index.ts'],
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
output: {
|
|
87
|
+
dir: 'paradox',
|
|
88
|
+
},
|
|
76
89
|
});
|
|
77
90
|
```
|
|
78
91
|
|
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 { analyzeReadmeConfig } from './readmeConfig.js';
|
|
8
9
|
import { analyzeReadmeUsage } from './readmeUsage.js';
|
|
9
10
|
import { createTypeScriptProgram } from './semantic/createTypeScriptProgram.js';
|
|
10
11
|
import { collectTypeMembers, resolveTypeReference } from './semantic/exports.js';
|
|
@@ -25,6 +26,10 @@ export async function analyze(config, runtime) {
|
|
|
25
26
|
const readmeUsageDescription = config.docs?.usage?.description ?? null;
|
|
26
27
|
const usageEntryPoints = config.docs?.usage?.entrypoints ?? [];
|
|
27
28
|
const readmeUsage = await analyzeReadmeUsage({ root, entrypoints: usageEntryPoints });
|
|
29
|
+
const readmeConfig = await analyzeReadmeConfig({
|
|
30
|
+
root,
|
|
31
|
+
configFilePath: runtime.configFilePath ?? null,
|
|
32
|
+
});
|
|
28
33
|
const program = createTypeScriptProgram({ root, entrypoints, project });
|
|
29
34
|
const { config: configMetadata, exports } = analyzeExports(project, { root, entrypoints });
|
|
30
35
|
const components = analyzeComponents(exports, { program });
|
|
@@ -70,6 +75,7 @@ export async function analyze(config, runtime) {
|
|
|
70
75
|
usage,
|
|
71
76
|
readmeUsageDescription,
|
|
72
77
|
readmeUsage,
|
|
78
|
+
readmeConfig,
|
|
73
79
|
config: configMetadata
|
|
74
80
|
? {
|
|
75
81
|
exportName: configMetadata.exportName,
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface AnalysisReadmeConfig {
|
|
2
|
+
description: string | null;
|
|
3
|
+
language: string;
|
|
4
|
+
code: string;
|
|
5
|
+
sourcePath: string;
|
|
6
|
+
}
|
|
7
|
+
/***
|
|
8
|
+
* Collects a README configuration example from the actual Paradox config file when its
|
|
9
|
+
* leading Paradox comment is marked with both @config and @readme.
|
|
10
|
+
*/
|
|
11
|
+
export declare function analyzeReadmeConfig(options: {
|
|
12
|
+
root: string;
|
|
13
|
+
configFilePath: string | null;
|
|
14
|
+
}): Promise<AnalysisReadmeConfig | null>;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { extname, relative } from 'node:path';
|
|
3
|
+
import { parseParadoxComment } from './utils/parseParadoxComment.js';
|
|
4
|
+
/***
|
|
5
|
+
* Collects a README configuration example from the actual Paradox config file when its
|
|
6
|
+
* leading Paradox comment is marked with both @config and @readme.
|
|
7
|
+
*/
|
|
8
|
+
export async function analyzeReadmeConfig(options) {
|
|
9
|
+
if (options.configFilePath === null)
|
|
10
|
+
return null;
|
|
11
|
+
const source = await readFile(options.configFilePath, 'utf-8');
|
|
12
|
+
const match = findLeadingParadoxComment(source);
|
|
13
|
+
if (match === null)
|
|
14
|
+
return null;
|
|
15
|
+
const parsed = parseParadoxComment(match.comment);
|
|
16
|
+
if (!parsed.isConfig || !parsed.isReadme)
|
|
17
|
+
return null;
|
|
18
|
+
const sourcePath = toPosixPath(relative(options.root, options.configFilePath));
|
|
19
|
+
return {
|
|
20
|
+
description: parsed.description,
|
|
21
|
+
language: getLanguage(sourcePath),
|
|
22
|
+
code: removeRange(source, match.start, match.end).trim(),
|
|
23
|
+
sourcePath,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function findLeadingParadoxComment(source) {
|
|
27
|
+
const match = /^\s*(\/\*\*\*[\s\S]*?\*\/)/.exec(source);
|
|
28
|
+
const comment = match?.[1];
|
|
29
|
+
if (match === null || comment === undefined)
|
|
30
|
+
return null;
|
|
31
|
+
const start = match[0].indexOf(comment);
|
|
32
|
+
return {
|
|
33
|
+
comment,
|
|
34
|
+
start,
|
|
35
|
+
end: start + comment.length,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function removeRange(source, start, end) {
|
|
39
|
+
const before = source.slice(0, start).trimEnd();
|
|
40
|
+
const after = source.slice(end).trimStart();
|
|
41
|
+
if (before.length === 0)
|
|
42
|
+
return after;
|
|
43
|
+
if (after.length === 0)
|
|
44
|
+
return before;
|
|
45
|
+
return `${before}\n\n${after}`;
|
|
46
|
+
}
|
|
47
|
+
function getLanguage(sourcePath) {
|
|
48
|
+
const extension = extname(sourcePath).toLowerCase();
|
|
49
|
+
if (extension === '.ts')
|
|
50
|
+
return 'ts';
|
|
51
|
+
if (extension === '.js' || extension === '.mjs' || extension === '.cjs')
|
|
52
|
+
return 'js';
|
|
53
|
+
return '';
|
|
54
|
+
}
|
|
55
|
+
function toPosixPath(path) {
|
|
56
|
+
return path.replaceAll('\\', '/');
|
|
57
|
+
}
|
package/dist/analyze/types.d.ts
CHANGED
|
@@ -86,6 +86,12 @@ interface AnalysisReadmeUsage {
|
|
|
86
86
|
code: string;
|
|
87
87
|
sourcePath: string;
|
|
88
88
|
}
|
|
89
|
+
interface AnalysisReadmeConfig {
|
|
90
|
+
description: string | null;
|
|
91
|
+
language: string;
|
|
92
|
+
code: string;
|
|
93
|
+
sourcePath: string;
|
|
94
|
+
}
|
|
89
95
|
export interface AnalysisBadge {
|
|
90
96
|
id: string;
|
|
91
97
|
label: string;
|
|
@@ -165,6 +171,7 @@ export interface AnalysisResult {
|
|
|
165
171
|
usage: AnalysisUsage | null;
|
|
166
172
|
readmeUsageDescription: string | null;
|
|
167
173
|
readmeUsage: AnalysisReadmeUsage[];
|
|
174
|
+
readmeConfig: AnalysisReadmeConfig | null;
|
|
168
175
|
config: {
|
|
169
176
|
exportName: string;
|
|
170
177
|
isReadme: boolean;
|
package/dist/cli/standalone.js
CHANGED
|
@@ -24,7 +24,7 @@ async function main() {
|
|
|
24
24
|
const config = await loadParadoxConfig(configFilePath);
|
|
25
25
|
const packageRoot = await resolvePackageRoot(config, configDir);
|
|
26
26
|
const { outputDir, outputRoot } = resolveOutputRoot(config, packageRoot);
|
|
27
|
-
const analysis = await analyze(config, { packageRoot });
|
|
27
|
+
const analysis = await analyze(config, { packageRoot, configFilePath });
|
|
28
28
|
const model = buildModel(analysis);
|
|
29
29
|
const result = render(model, { outputDir });
|
|
30
30
|
await write(result, config, { packageRoot, outputRoot });
|
|
@@ -8,8 +8,8 @@ export declare const PARADOX_DOC_TAGS: readonly [{
|
|
|
8
8
|
}, {
|
|
9
9
|
readonly name: "config";
|
|
10
10
|
readonly syntax: "@config";
|
|
11
|
-
readonly description: "Marks a type
|
|
12
|
-
readonly appliesTo: readonly ["interface", "type"];
|
|
11
|
+
readonly description: "Marks a configuration type, interface, or source block. Pair with @readme to include the schema or actual config source in README Configuration output.";
|
|
12
|
+
readonly appliesTo: readonly ["block", "interface", "type"];
|
|
13
13
|
readonly repeatable: false;
|
|
14
14
|
readonly handler: "markConfig";
|
|
15
15
|
}, {
|
|
@@ -19,8 +19,8 @@ export const PARADOX_DOC_TAGS = [
|
|
|
19
19
|
{
|
|
20
20
|
name: 'config',
|
|
21
21
|
syntax: '@config',
|
|
22
|
-
description: 'Marks a type
|
|
23
|
-
appliesTo: ['interface', 'type'],
|
|
22
|
+
description: 'Marks a configuration type, interface, or source block. Pair with @readme to include the schema or actual config source in README Configuration output.',
|
|
23
|
+
appliesTo: ['block', 'interface', 'type'],
|
|
24
24
|
repeatable: false,
|
|
25
25
|
handler: 'markConfig',
|
|
26
26
|
},
|
|
@@ -115,6 +115,12 @@ interface BuildModelInput {
|
|
|
115
115
|
code: string;
|
|
116
116
|
sourcePath: string;
|
|
117
117
|
}[];
|
|
118
|
+
readmeConfig: {
|
|
119
|
+
description: string | null;
|
|
120
|
+
language: string;
|
|
121
|
+
code: string;
|
|
122
|
+
sourcePath: string;
|
|
123
|
+
} | null;
|
|
118
124
|
config: {
|
|
119
125
|
exportName: string;
|
|
120
126
|
isReadme: boolean;
|
package/dist/model/buildModel.js
CHANGED
|
@@ -34,14 +34,18 @@ export function buildModel(analysis) {
|
|
|
34
34
|
sourcePath: usageEntry.sourcePath,
|
|
35
35
|
}))
|
|
36
36
|
.sort((left, right) => left.sourcePath.localeCompare(right.sourcePath)),
|
|
37
|
+
readmeConfig: analysis.readmeConfig !== null
|
|
38
|
+
? {
|
|
39
|
+
description: analysis.readmeConfig.description,
|
|
40
|
+
language: analysis.readmeConfig.language,
|
|
41
|
+
code: analysis.readmeConfig.code,
|
|
42
|
+
sourcePath: analysis.readmeConfig.sourcePath,
|
|
43
|
+
}
|
|
44
|
+
: null,
|
|
37
45
|
config: analysis.config !== null
|
|
38
46
|
? {
|
|
39
47
|
exportName: analysis.config.exportName,
|
|
40
48
|
isReadme: analysis.config.isReadme,
|
|
41
|
-
configFile: getDefaultConfigFileName(analysis.packageId),
|
|
42
|
-
factoryName: findConfigFactoryName(analysis.config.exportName, [
|
|
43
|
-
...exportsByName.keys(),
|
|
44
|
-
]),
|
|
45
49
|
members: analysis.config.members,
|
|
46
50
|
}
|
|
47
51
|
: null,
|
|
@@ -150,23 +154,6 @@ function mapComponent(component, exportModel) {
|
|
|
150
154
|
}))),
|
|
151
155
|
};
|
|
152
156
|
}
|
|
153
|
-
/***
|
|
154
|
-
* Finds the conventional config factory export for a config type when present.
|
|
155
|
-
*/
|
|
156
|
-
function findConfigFactoryName(configExportName, exportNames) {
|
|
157
|
-
const prefix = configExportName.endsWith('Config')
|
|
158
|
-
? configExportName.slice(0, -'Config'.length)
|
|
159
|
-
: configExportName;
|
|
160
|
-
const expectedFactoryName = `define${prefix}Config`;
|
|
161
|
-
return exportNames.includes(expectedFactoryName) ? expectedFactoryName : null;
|
|
162
|
-
}
|
|
163
|
-
/***
|
|
164
|
-
* Derives the default config file name from a package id.
|
|
165
|
-
*/
|
|
166
|
-
function getDefaultConfigFileName(packageId) {
|
|
167
|
-
const packageBaseName = packageId.split('/').pop() ?? packageId;
|
|
168
|
-
return `${packageBaseName}.config.ts`;
|
|
169
|
-
}
|
|
170
157
|
/***
|
|
171
158
|
* Returns a copy of items sorted by their `name` property.
|
|
172
159
|
*/
|
package/dist/model/types.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ export interface DocumentationModel {
|
|
|
9
9
|
usage: UsageModel | null;
|
|
10
10
|
readmeUsageDescription: string | null;
|
|
11
11
|
readmeUsage: ReadmeUsageModel[];
|
|
12
|
+
readmeConfig: ReadmeConfigModel | null;
|
|
12
13
|
config: ConfigModel | null;
|
|
13
14
|
entrypoints: string[];
|
|
14
15
|
modules: ModuleModel[];
|
|
@@ -39,11 +40,15 @@ interface ReadmeUsageModel {
|
|
|
39
40
|
code: string;
|
|
40
41
|
sourcePath: string;
|
|
41
42
|
}
|
|
43
|
+
interface ReadmeConfigModel {
|
|
44
|
+
description: string | null;
|
|
45
|
+
language: string;
|
|
46
|
+
code: string;
|
|
47
|
+
sourcePath: string;
|
|
48
|
+
}
|
|
42
49
|
interface ConfigModel {
|
|
43
50
|
exportName: string;
|
|
44
51
|
isReadme: boolean;
|
|
45
|
-
configFile: string;
|
|
46
|
-
factoryName: string | null;
|
|
47
52
|
members: ConfigMemberModel[];
|
|
48
53
|
}
|
|
49
54
|
export interface ExportModel {
|
|
@@ -31,8 +31,7 @@ function renderReadme(model, outputDir, badges, diagrams) {
|
|
|
31
31
|
lines.push('```', '');
|
|
32
32
|
}
|
|
33
33
|
renderCliScenarios(lines, model, outputDir, diagrams);
|
|
34
|
-
|
|
35
|
-
renderConfiguration(lines, model);
|
|
34
|
+
renderConfiguration(lines, model);
|
|
36
35
|
renderGeneratedDocumentation(lines, outputDir, diagrams);
|
|
37
36
|
renderReadmeApi(lines, model);
|
|
38
37
|
return `${lines.join('\n').trimEnd()}\n`;
|
|
@@ -88,30 +87,19 @@ function findScenarioDiagram(diagrams, scenario) {
|
|
|
88
87
|
return diagrams.find((diagram) => diagram.path === `diagrams/sequences/${toFileStem(scenario.name)}.mmd`);
|
|
89
88
|
}
|
|
90
89
|
function renderConfiguration(lines, model) {
|
|
91
|
-
const
|
|
92
|
-
|
|
90
|
+
const config = model.config?.isReadme ? model.config : null;
|
|
91
|
+
const example = model.readmeConfig;
|
|
92
|
+
if (config === null && example === null)
|
|
93
93
|
return;
|
|
94
94
|
lines.push('## Configuration', '');
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
lines.push(
|
|
99
|
-
lines.push(
|
|
100
|
-
lines.push(
|
|
101
|
-
lines.push(' // ...');
|
|
102
|
-
lines.push('});');
|
|
103
|
-
}
|
|
104
|
-
else {
|
|
105
|
-
lines.push(`import type { ${config.exportName} } from '${model.packageId}';`);
|
|
106
|
-
lines.push('');
|
|
107
|
-
lines.push('const config = {');
|
|
108
|
-
lines.push(' // ...');
|
|
109
|
-
lines.push(`} satisfies ${config.exportName};`);
|
|
110
|
-
lines.push('');
|
|
111
|
-
lines.push('export default config;');
|
|
95
|
+
if (example !== null) {
|
|
96
|
+
if (example.description !== null)
|
|
97
|
+
lines.push(example.description, '');
|
|
98
|
+
lines.push(`\`\`\`${example.language}`);
|
|
99
|
+
lines.push(example.code);
|
|
100
|
+
lines.push('```', '');
|
|
112
101
|
}
|
|
113
|
-
|
|
114
|
-
if (config.members.length === 0)
|
|
102
|
+
if (config === null || config.members.length === 0)
|
|
115
103
|
return;
|
|
116
104
|
lines.push('<details>');
|
|
117
105
|
lines.push('<summary>Configuration options</summary>', '');
|