@ankhorage/paradox 0.1.16 → 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 +12 -0
- package/README.md +23 -10
- package/dist/analyze/analyze.d.ts +1 -0
- package/dist/analyze/analyze.js +8 -0
- package/dist/analyze/readmeConfig.d.ts +14 -0
- package/dist/analyze/readmeConfig.js +57 -0
- package/dist/analyze/types.d.ts +8 -0
- package/dist/cli/standalone.js +1 -1
- package/dist/config/types.d.ts +1 -0
- package/dist/doc-tags/registry.d.ts +2 -2
- package/dist/doc-tags/registry.js +2 -2
- package/dist/model/buildModel.d.ts +7 -0
- package/dist/model/buildModel.js +9 -21
- package/dist/model/types.d.ts +8 -2
- package/dist/render/renderers/markdown.js +19 -29
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.18
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- eaa56ad: Render README Configuration examples from the actual tagged Paradox config file instead of synthesized boilerplate.
|
|
8
|
+
|
|
9
|
+
## 0.1.17
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 8eac93d: Support section-level README Usage prose through `docs.usage.description` without requiring a source-backed usage example.
|
|
14
|
+
|
|
3
15
|
## 0.1.16
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
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,25 +66,38 @@ 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
|
|
|
79
92
|
<details>
|
|
80
93
|
<summary>Configuration options</summary>
|
|
81
94
|
|
|
82
|
-
| Field | Type
|
|
83
|
-
| ------- |
|
|
84
|
-
| mode | `'safe' \| 'write' \| undefined`
|
|
85
|
-
| docs | `{ title?: string; description?: string; usage?: { entrypoints?: string[]; }; } \| undefined` | no | — | |
|
|
86
|
-
| package | `{ root?: string; entrypoints?: string[]; } \| undefined`
|
|
87
|
-
| output | `{ dir?: string; } \| undefined`
|
|
95
|
+
| Field | Type | Required | Default | Description |
|
|
96
|
+
| ------- | ------------------------------------------------------------------------------------------------------------------- | -------- | ------- | ----------- |
|
|
97
|
+
| mode | `'safe' \| 'write' \| undefined` | no | — | |
|
|
98
|
+
| docs | `{ title?: string; description?: string; usage?: { description?: string; entrypoints?: string[]; }; } \| undefined` | no | — | |
|
|
99
|
+
| package | `{ root?: string; entrypoints?: string[]; } \| undefined` | no | — | |
|
|
100
|
+
| output | `{ dir?: string; } \| undefined` | no | — | |
|
|
88
101
|
|
|
89
102
|
</details>
|
|
90
103
|
|
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';
|
|
@@ -22,8 +23,13 @@ export async function analyze(config, runtime) {
|
|
|
22
23
|
const badges = await analyzeBadges(root, pkg);
|
|
23
24
|
const project = createProject(root);
|
|
24
25
|
const entrypoints = config.package?.entrypoints ?? ['src/index.ts'];
|
|
26
|
+
const readmeUsageDescription = config.docs?.usage?.description ?? null;
|
|
25
27
|
const usageEntryPoints = config.docs?.usage?.entrypoints ?? [];
|
|
26
28
|
const readmeUsage = await analyzeReadmeUsage({ root, entrypoints: usageEntryPoints });
|
|
29
|
+
const readmeConfig = await analyzeReadmeConfig({
|
|
30
|
+
root,
|
|
31
|
+
configFilePath: runtime.configFilePath ?? null,
|
|
32
|
+
});
|
|
27
33
|
const program = createTypeScriptProgram({ root, entrypoints, project });
|
|
28
34
|
const { config: configMetadata, exports } = analyzeExports(project, { root, entrypoints });
|
|
29
35
|
const components = analyzeComponents(exports, { program });
|
|
@@ -67,7 +73,9 @@ export async function analyze(config, runtime) {
|
|
|
67
73
|
badges,
|
|
68
74
|
sequenceScenarios,
|
|
69
75
|
usage,
|
|
76
|
+
readmeUsageDescription,
|
|
70
77
|
readmeUsage,
|
|
78
|
+
readmeConfig,
|
|
71
79
|
config: configMetadata
|
|
72
80
|
? {
|
|
73
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;
|
|
@@ -163,7 +169,9 @@ export interface AnalysisResult {
|
|
|
163
169
|
badges: AnalysisBadge[];
|
|
164
170
|
sequenceScenarios: AnalysisSequenceScenario[];
|
|
165
171
|
usage: AnalysisUsage | null;
|
|
172
|
+
readmeUsageDescription: string | null;
|
|
166
173
|
readmeUsage: AnalysisReadmeUsage[];
|
|
174
|
+
readmeConfig: AnalysisReadmeConfig | null;
|
|
167
175
|
config: {
|
|
168
176
|
exportName: string;
|
|
169
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 });
|
package/dist/config/types.d.ts
CHANGED
|
@@ -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
|
},
|
|
@@ -107,6 +107,7 @@ interface BuildModelInput {
|
|
|
107
107
|
command: string;
|
|
108
108
|
}[];
|
|
109
109
|
} | null;
|
|
110
|
+
readmeUsageDescription: string | null;
|
|
110
111
|
readmeUsage: {
|
|
111
112
|
title: string | null;
|
|
112
113
|
description: string | null;
|
|
@@ -114,6 +115,12 @@ interface BuildModelInput {
|
|
|
114
115
|
code: string;
|
|
115
116
|
sourcePath: string;
|
|
116
117
|
}[];
|
|
118
|
+
readmeConfig: {
|
|
119
|
+
description: string | null;
|
|
120
|
+
language: string;
|
|
121
|
+
code: string;
|
|
122
|
+
sourcePath: string;
|
|
123
|
+
} | null;
|
|
117
124
|
config: {
|
|
118
125
|
exportName: string;
|
|
119
126
|
isReadme: boolean;
|
package/dist/model/buildModel.js
CHANGED
|
@@ -24,6 +24,7 @@ export function buildModel(analysis) {
|
|
|
24
24
|
}))),
|
|
25
25
|
}
|
|
26
26
|
: null,
|
|
27
|
+
readmeUsageDescription: analysis.readmeUsageDescription,
|
|
27
28
|
readmeUsage: analysis.readmeUsage
|
|
28
29
|
.map((usageEntry) => ({
|
|
29
30
|
title: usageEntry.title,
|
|
@@ -33,14 +34,18 @@ export function buildModel(analysis) {
|
|
|
33
34
|
sourcePath: usageEntry.sourcePath,
|
|
34
35
|
}))
|
|
35
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,
|
|
36
45
|
config: analysis.config !== null
|
|
37
46
|
? {
|
|
38
47
|
exportName: analysis.config.exportName,
|
|
39
48
|
isReadme: analysis.config.isReadme,
|
|
40
|
-
configFile: getDefaultConfigFileName(analysis.packageId),
|
|
41
|
-
factoryName: findConfigFactoryName(analysis.config.exportName, [
|
|
42
|
-
...exportsByName.keys(),
|
|
43
|
-
]),
|
|
44
49
|
members: analysis.config.members,
|
|
45
50
|
}
|
|
46
51
|
: null,
|
|
@@ -149,23 +154,6 @@ function mapComponent(component, exportModel) {
|
|
|
149
154
|
}))),
|
|
150
155
|
};
|
|
151
156
|
}
|
|
152
|
-
/***
|
|
153
|
-
* Finds the conventional config factory export for a config type when present.
|
|
154
|
-
*/
|
|
155
|
-
function findConfigFactoryName(configExportName, exportNames) {
|
|
156
|
-
const prefix = configExportName.endsWith('Config')
|
|
157
|
-
? configExportName.slice(0, -'Config'.length)
|
|
158
|
-
: configExportName;
|
|
159
|
-
const expectedFactoryName = `define${prefix}Config`;
|
|
160
|
-
return exportNames.includes(expectedFactoryName) ? expectedFactoryName : null;
|
|
161
|
-
}
|
|
162
|
-
/***
|
|
163
|
-
* Derives the default config file name from a package id.
|
|
164
|
-
*/
|
|
165
|
-
function getDefaultConfigFileName(packageId) {
|
|
166
|
-
const packageBaseName = packageId.split('/').pop() ?? packageId;
|
|
167
|
-
return `${packageBaseName}.config.ts`;
|
|
168
|
-
}
|
|
169
157
|
/***
|
|
170
158
|
* Returns a copy of items sorted by their `name` property.
|
|
171
159
|
*/
|
package/dist/model/types.d.ts
CHANGED
|
@@ -7,7 +7,9 @@ export interface DocumentationModel {
|
|
|
7
7
|
description: string | null;
|
|
8
8
|
badges: GeneratedBadge[];
|
|
9
9
|
usage: UsageModel | null;
|
|
10
|
+
readmeUsageDescription: string | null;
|
|
10
11
|
readmeUsage: ReadmeUsageModel[];
|
|
12
|
+
readmeConfig: ReadmeConfigModel | null;
|
|
11
13
|
config: ConfigModel | null;
|
|
12
14
|
entrypoints: string[];
|
|
13
15
|
modules: ModuleModel[];
|
|
@@ -38,11 +40,15 @@ interface ReadmeUsageModel {
|
|
|
38
40
|
code: string;
|
|
39
41
|
sourcePath: string;
|
|
40
42
|
}
|
|
43
|
+
interface ReadmeConfigModel {
|
|
44
|
+
description: string | null;
|
|
45
|
+
language: string;
|
|
46
|
+
code: string;
|
|
47
|
+
sourcePath: string;
|
|
48
|
+
}
|
|
41
49
|
interface ConfigModel {
|
|
42
50
|
exportName: string;
|
|
43
51
|
isReadme: boolean;
|
|
44
|
-
configFile: string;
|
|
45
|
-
factoryName: string | null;
|
|
46
52
|
members: ConfigMemberModel[];
|
|
47
53
|
}
|
|
48
54
|
export interface ExportModel {
|
|
@@ -23,7 +23,7 @@ function renderReadme(model, outputDir, badges, diagrams) {
|
|
|
23
23
|
}
|
|
24
24
|
if (model.description)
|
|
25
25
|
lines.push(model.description, '');
|
|
26
|
-
renderReadmeUsage(lines, model.readmeUsage);
|
|
26
|
+
renderReadmeUsage(lines, model.readmeUsageDescription, model.readmeUsage);
|
|
27
27
|
if (model.usage !== null) {
|
|
28
28
|
lines.push('## Installation', '', '```bash');
|
|
29
29
|
for (const command of model.usage.commands)
|
|
@@ -31,24 +31,25 @@ 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`;
|
|
39
38
|
}
|
|
40
|
-
function renderReadmeUsage(lines, entries) {
|
|
41
|
-
if (entries.length === 0)
|
|
39
|
+
function renderReadmeUsage(lines, description, entries) {
|
|
40
|
+
if (description === null && entries.length === 0)
|
|
42
41
|
return;
|
|
43
42
|
lines.push('## Usage', '');
|
|
43
|
+
if (description !== null)
|
|
44
|
+
lines.push(description, '');
|
|
44
45
|
for (const entry of entries) {
|
|
45
46
|
if (entry.title !== null)
|
|
46
47
|
lines.push(`### ${entry.title}`, '');
|
|
47
48
|
if (entry.description !== null) {
|
|
48
49
|
const [, ...rest] = entry.description.split('\n');
|
|
49
|
-
const
|
|
50
|
-
if (
|
|
51
|
-
lines.push(
|
|
50
|
+
const entryDescription = rest.join('\n').trim();
|
|
51
|
+
if (entryDescription.length > 0)
|
|
52
|
+
lines.push(entryDescription, '');
|
|
52
53
|
}
|
|
53
54
|
lines.push(`Source: \`${entry.sourcePath}\``, '');
|
|
54
55
|
lines.push(`\`\`\`${entry.language}`);
|
|
@@ -86,30 +87,19 @@ function findScenarioDiagram(diagrams, scenario) {
|
|
|
86
87
|
return diagrams.find((diagram) => diagram.path === `diagrams/sequences/${toFileStem(scenario.name)}.mmd`);
|
|
87
88
|
}
|
|
88
89
|
function renderConfiguration(lines, model) {
|
|
89
|
-
const
|
|
90
|
-
|
|
90
|
+
const config = model.config?.isReadme ? model.config : null;
|
|
91
|
+
const example = model.readmeConfig;
|
|
92
|
+
if (config === null && example === null)
|
|
91
93
|
return;
|
|
92
94
|
lines.push('## Configuration', '');
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
lines.push(
|
|
97
|
-
lines.push(
|
|
98
|
-
lines.push(
|
|
99
|
-
lines.push(' // ...');
|
|
100
|
-
lines.push('});');
|
|
101
|
-
}
|
|
102
|
-
else {
|
|
103
|
-
lines.push(`import type { ${config.exportName} } from '${model.packageId}';`);
|
|
104
|
-
lines.push('');
|
|
105
|
-
lines.push('const config = {');
|
|
106
|
-
lines.push(' // ...');
|
|
107
|
-
lines.push(`} satisfies ${config.exportName};`);
|
|
108
|
-
lines.push('');
|
|
109
|
-
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('```', '');
|
|
110
101
|
}
|
|
111
|
-
|
|
112
|
-
if (config.members.length === 0)
|
|
102
|
+
if (config === null || config.members.length === 0)
|
|
113
103
|
return;
|
|
114
104
|
lines.push('<details>');
|
|
115
105
|
lines.push('<summary>Configuration options</summary>', '');
|