@ankhorage/paradox 0.1.8 → 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 CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.10
4
+
5
+ ### Patch Changes
6
+
7
+ - 47b7ced: Add generated README usage support from real source examples marked through configured usage entrypoints, while keeping existing package command usage intact.
8
+
9
+ ## 0.1.9
10
+
11
+ ### Patch Changes
12
+
13
+ - 9b43e55: Keep generated README files compact by linking diagram artifacts from the generated documentation section instead of embedding the architecture preview inline.
14
+
3
15
  ## 0.1.8
4
16
 
5
17
  ### Patch Changes
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
 
4
4
  # @ankhorage/paradox
5
5
 
6
- ![license: MIT](./paradox/badges/license.svg) ![npm: v0.1.8](./paradox/badges/npm.svg) ![runtime: bun](./paradox/badges/runtime.svg) ![typescript: strict](./paradox/badges/typescript.svg) ![eslint: checked](./paradox/badges/eslint.svg) ![prettier: checked](./paradox/badges/prettier.svg) ![build: checked](./paradox/badges/build.svg) ![tests: checked](./paradox/badges/tests.svg) ![docs: paradox](./paradox/badges/docs.svg)
6
+ ![license: MIT](./paradox/badges/license.svg) ![npm: v0.1.10](./paradox/badges/npm.svg) ![runtime: bun](./paradox/badges/runtime.svg) ![typescript: strict](./paradox/badges/typescript.svg) ![eslint: checked](./paradox/badges/eslint.svg) ![prettier: checked](./paradox/badges/prettier.svg) ![build: checked](./paradox/badges/build.svg) ![tests: checked](./paradox/badges/tests.svg) ![docs: paradox](./paradox/badges/docs.svg)
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 | Required | Default | Description |
83
- | ------- | --------------------------------------------------------- | -------- | ------- | ----------- |
84
- | mode | `'safe' \| 'write' \| undefined` | no | — | |
85
- | docs | `{ title?: string; description?: string; } \| undefined` | no | — | |
86
- | package | `{ root?: string; entrypoints?: string[]; } \| undefined` | no | — | |
87
- | output | `{ dir?: string; } \| undefined` | no | — | |
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
 
@@ -99,198 +99,6 @@ export default defineParadoxConfig({
99
99
  - [isParadoxDocTagName sequence](./paradox/diagrams/sequences/is-paradox-doc-tag-name.mmd)
100
100
  - [paradox sequence](./paradox/diagrams/sequences/paradox.mmd)
101
101
 
102
- ## Architecture preview
103
-
104
- <details>
105
- <summary>Architecture overview</summary>
106
-
107
- ```mermaid
108
- graph TD
109
- package__ankhorage_paradox["@ankhorage/paradox"]
110
- entrypoint_src_index_ts["src/index.ts"]
111
- package__ankhorage_paradox --> entrypoint_src_index_ts
112
- module_src_analyze_analyze_ts["src/analyze/analyze.ts"]
113
- package__ankhorage_paradox -.-> module_src_analyze_analyze_ts
114
- module_src_analyze_analyze_ts --> module_src_analyze_badges_ts
115
- module_src_analyze_analyze_ts --> module_src_analyze_components_ts
116
- module_src_analyze_analyze_ts --> module_src_analyze_exports_ts
117
- module_src_analyze_analyze_ts --> module_src_analyze_modules_ts
118
- module_src_analyze_analyze_ts --> module_src_analyze_project_ts
119
- module_src_analyze_analyze_ts --> module_src_analyze_semantic_createTypeScriptProgram_ts
120
- module_src_analyze_analyze_ts --> module_src_analyze_semantic_exports_ts
121
- module_src_analyze_analyze_ts --> module_src_analyze_semantic_graphs_ts
122
- module_src_analyze_analyze_ts --> module_src_analyze_sequenceScenarios_ts
123
- module_src_analyze_analyze_ts --> module_src_analyze_sourceFunctions_ts
124
- module_src_analyze_analyze_ts --> module_src_analyze_types_ts
125
- module_src_analyze_analyze_ts --> module_src_analyze_usage_ts
126
- module_src_analyze_analyze_ts --> module_src_config_types_ts
127
- module_src_analyze_badges_ts["src/analyze/badges.ts"]
128
- package__ankhorage_paradox -.-> module_src_analyze_badges_ts
129
- module_src_analyze_badges_ts --> module_src_analyze_types_ts
130
- module_src_analyze_badges_ts --> module_src_analyze_usage_ts
131
- module_src_analyze_components_ts["src/analyze/components.ts"]
132
- package__ankhorage_paradox -.-> module_src_analyze_components_ts
133
- module_src_analyze_components_ts --> module_src_analyze_semantic_exports_ts
134
- module_src_analyze_components_ts --> module_src_analyze_semantic_model_ts
135
- module_src_analyze_components_ts --> module_src_analyze_types_ts
136
- module_src_analyze_components_ts --> module_src_analyze_utils_getComponentPropsType_ts
137
- module_src_analyze_components_ts --> module_src_analyze_utils_getPropsFromType_ts
138
- module_src_analyze_components_ts --> module_src_analyze_utils_isReactComponent_ts
139
- module_src_analyze_exports_ts["src/analyze/exports.ts"]
140
- package__ankhorage_paradox -.-> module_src_analyze_exports_ts
141
- module_src_analyze_exports_ts --> module_src_analyze_types_ts
142
- module_src_analyze_exports_ts --> module_src_analyze_utils_getExportMetadata_ts
143
- module_src_analyze_exports_ts --> module_src_analyze_utils_getParadoxComment_ts
144
- module_src_analyze_exports_ts --> module_src_analyze_utils_parseParadoxComment_ts
145
- module_src_analyze_exports_ts --> module_src_analyze_utils_resolveExportSymbol_ts
146
- module_src_analyze_modules_ts["src/analyze/modules.ts"]
147
- package__ankhorage_paradox -.-> module_src_analyze_modules_ts
148
- module_src_analyze_modules_ts --> module_src_analyze_types_ts
149
- module_src_analyze_project_ts["src/analyze/project.ts"]
150
- package__ankhorage_paradox -.-> module_src_analyze_project_ts
151
- module_src_analyze_semantic_analyzeProject_ts["src/analyze/semantic/analyzeProject.ts"]
152
- package__ankhorage_paradox -.-> module_src_analyze_semantic_analyzeProject_ts
153
- module_src_analyze_semantic_analyzeProject_ts --> module_src_analyze_semantic_associateDocBlocksWithSymbols_ts
154
- module_src_analyze_semantic_analyzeProject_ts --> module_src_analyze_semantic_collectSourceFiles_ts
155
- module_src_analyze_semantic_analyzeProject_ts --> module_src_analyze_semantic_createTypeScriptProgram_ts
156
- module_src_analyze_semantic_analyzeProject_ts --> module_src_analyze_semantic_docBlocks_ts
157
- module_src_analyze_semantic_analyzeProject_ts --> module_src_analyze_semantic_exports_ts
158
- module_src_analyze_semantic_analyzeProject_ts --> module_src_analyze_semantic_graphs_ts
159
- module_src_analyze_semantic_analyzeProject_ts --> module_src_analyze_semantic_model_ts
160
- module_src_analyze_semantic_analyzeProject_ts --> module_src_analyze_semantic_tagRegistry_ts
161
- module_src_analyze_semantic_associateDocBlocksWithSymbols_ts["src/analyze/semantic/associateDocBlocksWithSymbols.ts"]
162
- package__ankhorage_paradox -.-> module_src_analyze_semantic_associateDocBlocksWithSymbols_ts
163
- module_src_analyze_semantic_associateDocBlocksWithSymbols_ts --> module_src_analyze_semantic_model_ts
164
- module_src_analyze_semantic_associateDocBlocksWithSymbols_ts --> module_src_analyze_semantic_utils_ts
165
- module_src_analyze_semantic_collectSourceFiles_ts["src/analyze/semantic/collectSourceFiles.ts"]
166
- package__ankhorage_paradox -.-> module_src_analyze_semantic_collectSourceFiles_ts
167
- module_src_analyze_semantic_collectSourceFiles_ts --> module_src_analyze_semantic_model_ts
168
- module_src_analyze_semantic_collectSourceFiles_ts --> module_src_analyze_semantic_utils_ts
169
- module_src_analyze_semantic_createTypeScriptProgram_ts["src/analyze/semantic/createTypeScriptProgram.ts"]
170
- package__ankhorage_paradox -.-> module_src_analyze_semantic_createTypeScriptProgram_ts
171
- module_src_analyze_semantic_createTypeScriptProgram_ts --> module_src_analyze_semantic_model_ts
172
- module_src_analyze_semantic_createTypeScriptProgram_ts --> module_src_analyze_semantic_utils_ts
173
- module_src_analyze_semantic_docBlocks_ts["src/analyze/semantic/docBlocks.ts"]
174
- package__ankhorage_paradox -.-> module_src_analyze_semantic_docBlocks_ts
175
- module_src_analyze_semantic_docBlocks_ts --> module_src_analyze_semantic_model_ts
176
- module_src_analyze_semantic_docBlocks_ts --> module_src_analyze_semantic_tagRegistry_ts
177
- module_src_analyze_semantic_docBlocks_ts --> module_src_analyze_semantic_utils_ts
178
- module_src_analyze_semantic_exports_ts["src/analyze/semantic/exports.ts"]
179
- package__ankhorage_paradox -.-> module_src_analyze_semantic_exports_ts
180
- module_src_analyze_semantic_exports_ts --> module_src_analyze_semantic_isReactComponent_ts
181
- module_src_analyze_semantic_exports_ts --> module_src_analyze_semantic_model_ts
182
- module_src_analyze_semantic_exports_ts --> module_src_analyze_semantic_paradoxComment_ts
183
- module_src_analyze_semantic_exports_ts --> module_src_analyze_semantic_utils_ts
184
- module_src_analyze_semantic_graphs_ts["src/analyze/semantic/graphs.ts"]
185
- package__ankhorage_paradox -.-> module_src_analyze_semantic_graphs_ts
186
- module_src_analyze_semantic_graphs_ts --> module_src_analyze_semantic_exports_ts
187
- module_src_analyze_semantic_graphs_ts --> module_src_analyze_semantic_model_ts
188
- module_src_analyze_semantic_graphs_ts --> module_src_analyze_semantic_utils_ts
189
- module_src_analyze_semantic_isReactComponent_ts["src/analyze/semantic/isReactComponent.ts"]
190
- package__ankhorage_paradox -.-> module_src_analyze_semantic_isReactComponent_ts
191
- module_src_analyze_semantic_model_ts["src/analyze/semantic/model.ts"]
192
- package__ankhorage_paradox -.-> module_src_analyze_semantic_model_ts
193
- module_src_analyze_semantic_paradoxComment_ts["src/analyze/semantic/paradoxComment.ts"]
194
- package__ankhorage_paradox -.-> module_src_analyze_semantic_paradoxComment_ts
195
- module_src_analyze_semantic_tagRegistry_ts["src/analyze/semantic/tagRegistry.ts"]
196
- package__ankhorage_paradox -.-> module_src_analyze_semantic_tagRegistry_ts
197
- module_src_analyze_semantic_utils_ts["src/analyze/semantic/utils.ts"]
198
- package__ankhorage_paradox -.-> module_src_analyze_semantic_utils_ts
199
- module_src_analyze_sequenceScenarios_ts["src/analyze/sequenceScenarios.ts"]
200
- package__ankhorage_paradox -.-> module_src_analyze_sequenceScenarios_ts
201
- module_src_analyze_sequenceScenarios_ts --> module_src_analyze_semantic_utils_ts
202
- module_src_analyze_sequenceScenarios_ts --> module_src_analyze_types_ts
203
- module_src_analyze_sequenceScenarios_ts --> module_src_analyze_usage_ts
204
- module_src_analyze_sequenceScenarios_ts --> module_src_analyze_utils_getParadoxComment_ts
205
- module_src_analyze_sequenceScenarios_ts --> module_src_analyze_utils_parseParadoxComment_ts
206
- module_src_analyze_sourceFunctions_ts["src/analyze/sourceFunctions.ts"]
207
- package__ankhorage_paradox -.-> module_src_analyze_sourceFunctions_ts
208
- module_src_analyze_sourceFunctions_ts --> module_src_analyze_types_ts
209
- module_src_analyze_sourceFunctions_ts --> module_src_analyze_utils_getParadoxComment_ts
210
- module_src_analyze_sourceFunctions_ts --> module_src_analyze_utils_parseParadoxComment_ts
211
- module_src_analyze_types_ts["src/analyze/types.ts"]
212
- package__ankhorage_paradox -.-> module_src_analyze_types_ts
213
- module_src_analyze_usage_ts["src/analyze/usage.ts"]
214
- package__ankhorage_paradox -.-> module_src_analyze_usage_ts
215
- module_src_analyze_usage_ts --> module_src_analyze_types_ts
216
- module_src_analyze_utils_getComponentPropsType_ts["src/analyze/utils/getComponentPropsType.ts"]
217
- package__ankhorage_paradox -.-> module_src_analyze_utils_getComponentPropsType_ts
218
- module_src_analyze_utils_getExportMetadata_ts["src/analyze/utils/getExportMetadata.ts"]
219
- package__ankhorage_paradox -.-> module_src_analyze_utils_getExportMetadata_ts
220
- module_src_analyze_utils_getExportMetadata_ts --> module_src_analyze_types_ts
221
- module_src_analyze_utils_getExportMetadata_ts --> module_src_analyze_utils_getParadoxComment_ts
222
- module_src_analyze_utils_getExportMetadata_ts --> module_src_analyze_utils_parseParadoxComment_ts
223
- module_src_analyze_utils_getParadoxComment_ts["src/analyze/utils/getParadoxComment.ts"]
224
- package__ankhorage_paradox -.-> module_src_analyze_utils_getParadoxComment_ts
225
- module_src_analyze_utils_getPropsFromType_ts["src/analyze/utils/getPropsFromType.ts"]
226
- package__ankhorage_paradox -.-> module_src_analyze_utils_getPropsFromType_ts
227
- module_src_analyze_utils_getPropsFromType_ts --> module_src_analyze_types_ts
228
- module_src_analyze_utils_getPropsFromType_ts --> module_src_analyze_utils_getParadoxComment_ts
229
- module_src_analyze_utils_getPropsFromType_ts --> module_src_analyze_utils_parseParadoxComment_ts
230
- module_src_analyze_utils_isReactComponent_ts["src/analyze/utils/isReactComponent.ts"]
231
- package__ankhorage_paradox -.-> module_src_analyze_utils_isReactComponent_ts
232
- module_src_analyze_utils_parseParadoxComment_ts["src/analyze/utils/parseParadoxComment.ts"]
233
- package__ankhorage_paradox -.-> module_src_analyze_utils_parseParadoxComment_ts
234
- module_src_analyze_utils_resolveExportSymbol_ts["src/analyze/utils/resolveExportSymbol.ts"]
235
- package__ankhorage_paradox -.-> module_src_analyze_utils_resolveExportSymbol_ts
236
- module_src_cli_ts["src/cli.ts"]
237
- package__ankhorage_paradox -.-> module_src_cli_ts
238
- module_src_cli_ts --> module_src_analyze_analyze_ts
239
- module_src_cli_ts --> module_src_model_buildModel_ts
240
- module_src_cli_ts --> module_src_paths_policy_ts
241
- module_src_cli_ts --> module_src_render_render_ts
242
- module_src_cli_ts --> module_src_write_write_ts
243
- module_src_config_defineParadoxConfig_ts["src/config/defineParadoxConfig.ts"]
244
- package__ankhorage_paradox -.-> module_src_config_defineParadoxConfig_ts
245
- module_src_config_defineParadoxConfig_ts --> module_src_config_types_ts
246
- module_src_config_types_ts["src/config/types.ts"]
247
- package__ankhorage_paradox -.-> module_src_config_types_ts
248
- module_src_doc_tags_registry_ts["src/doc-tags/registry.ts"]
249
- package__ankhorage_paradox -.-> module_src_doc_tags_registry_ts
250
- module_src_index_ts["src/index.ts"]
251
- module_src_model_buildModel_ts["src/model/buildModel.ts"]
252
- package__ankhorage_paradox -.-> module_src_model_buildModel_ts
253
- module_src_model_buildModel_ts --> module_src_model_types_ts
254
- module_src_model_types_ts["src/model/types.ts"]
255
- package__ankhorage_paradox -.-> module_src_model_types_ts
256
- module_src_paths_policy_ts["src/paths/policy.ts"]
257
- package__ankhorage_paradox -.-> module_src_paths_policy_ts
258
- module_src_paths_policy_ts --> module_src_config_types_ts
259
- module_src_render_render_ts["src/render/render.ts"]
260
- package__ankhorage_paradox -.-> module_src_render_render_ts
261
- module_src_render_render_ts --> module_src_model_types_ts
262
- module_src_render_render_ts --> module_src_render_renderers_badges_ts
263
- module_src_render_render_ts --> module_src_render_renderers_diagrams_ts
264
- module_src_render_render_ts --> module_src_render_renderers_html_ts
265
- module_src_render_render_ts --> module_src_render_renderers_markdown_ts
266
- module_src_render_render_ts --> module_src_render_types_ts
267
- module_src_render_renderers_badges_ts["src/render/renderers/badges.ts"]
268
- package__ankhorage_paradox -.-> module_src_render_renderers_badges_ts
269
- module_src_render_renderers_badges_ts --> module_src_model_types_ts
270
- module_src_render_renderers_badges_ts --> module_src_render_types_ts
271
- module_src_render_renderers_diagrams_ts["src/render/renderers/diagrams.ts"]
272
- package__ankhorage_paradox -.-> module_src_render_renderers_diagrams_ts
273
- module_src_render_renderers_diagrams_ts --> module_src_model_types_ts
274
- module_src_render_renderers_diagrams_ts --> module_src_render_types_ts
275
- module_src_render_renderers_html_ts["src/render/renderers/html.ts"]
276
- package__ankhorage_paradox -.-> module_src_render_renderers_html_ts
277
- module_src_render_renderers_html_ts --> module_src_model_types_ts
278
- module_src_render_renderers_html_ts --> module_src_render_types_ts
279
- module_src_render_renderers_markdown_ts["src/render/renderers/markdown.ts"]
280
- package__ankhorage_paradox -.-> module_src_render_renderers_markdown_ts
281
- module_src_render_renderers_markdown_ts --> module_src_model_types_ts
282
- module_src_render_renderers_markdown_ts --> module_src_render_types_ts
283
- module_src_render_types_ts["src/render/types.ts"]
284
- package__ankhorage_paradox -.-> module_src_render_types_ts
285
- module_src_render_types_ts --> module_src_model_types_ts
286
- module_src_write_write_ts["src/write/write.ts"]
287
- package__ankhorage_paradox -.-> module_src_write_write_ts
288
- module_src_write_write_ts --> module_src_config_types_ts
289
- module_src_write_write_ts --> module_src_render_types_ts
290
- ```
291
-
292
- </details>
293
-
294
102
  ## Public API
295
103
 
296
104
  ### Config
@@ -319,23 +127,3 @@ Module: `src/config/types.ts`
319
127
  Source: `src/config/types.ts:7:1`
320
128
 
321
129
  </details>
322
-
323
- ### Documentation
324
-
325
- <details>
326
- <summary>PARADOX_DOC_TAGS</summary>
327
-
328
- Supported Paradox documentation tags.
329
-
330
- Paradox supports doc tags inside triple-star documentation comments.
331
-
332
- | name | syntax | description | applies to | repeatable | handler |
333
- | --------- | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | ---------- | -------------- |
334
- | `readme` | `@readme` | Includes a documentation block or exported symbol in README output. | block, symbol | no | `markReadme` |
335
- | `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` |
336
- | `example` | `@example` | Adds a titled fenced code example to the generated documentation for a symbol. | symbol | yes | `parseExample` |
337
-
338
- Module: `src/doc-tags/registry.ts`
339
- Source: `src/doc-tags/registry.ts:8:14`
340
-
341
- </details>
@@ -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, { root, entrypoints });
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,
@@ -6,4 +6,5 @@ import type { AnalysisModule } from './types.js';
6
6
  export declare function analyzeModules(project: Project, options: {
7
7
  root: string;
8
8
  entrypoints: readonly string[];
9
+ excludePaths?: readonly string[];
9
10
  }): AnalysisModule[];
@@ -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) && !toPosixPath(dependency).includes('/node_modules/'))
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
+ }
@@ -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,
@@ -9,6 +9,9 @@ export interface ParadoxConfig {
9
9
  docs?: {
10
10
  title?: string;
11
11
  description?: string;
12
+ usage?: {
13
+ entrypoints?: string[];
14
+ };
12
15
  };
13
16
  package?: {
14
17
  root?: string;
@@ -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;
@@ -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,
@@ -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,26 +21,41 @@ function renderReadme(model, outputDir, badges, diagrams) {
21
21
  .map((badge) => `![${badgeLabel(model, badge.path)}](./${outputDir}/${badge.path})`)
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
- lines.push('```bash');
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
- renderArchitecturePreview(lines, diagrams);
41
37
  renderReadmeApi(lines, model);
42
38
  return `${lines.join('\n').trimEnd()}\n`;
43
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
+ }
44
59
  function renderCliScenarios(lines, model, outputDir, diagrams) {
45
60
  const scenarios = model.sequenceScenarios.filter((scenario) => scenario.kind === 'bin' && scenario.isReadme);
46
61
  if (scenarios.length === 0)
@@ -49,9 +64,8 @@ function renderCliScenarios(lines, model, outputDir, diagrams) {
49
64
  for (const scenario of scenarios) {
50
65
  lines.push('<details>');
51
66
  lines.push(`<summary>${scenario.name}</summary>`, '');
52
- if (scenario.description !== null) {
67
+ if (scenario.description !== null)
53
68
  lines.push(scenario.description, '');
54
- }
55
69
  const command = model.usage?.commands.find((item) => item.name === scenario.name);
56
70
  if (command !== undefined) {
57
71
  lines.push('```bash');
@@ -95,38 +109,26 @@ function renderConfiguration(lines, model) {
95
109
  lines.push('export default config;');
96
110
  }
97
111
  lines.push('```', '');
98
- if (config.members.length > 0) {
99
- lines.push('<details>');
100
- lines.push('<summary>Configuration options</summary>', '');
101
- lines.push('| Field | Type | Required | Default | Description |');
102
- lines.push('| --- | --- | --- | --- | --- |');
103
- for (const configMember of flattenConfigMembers(config.members)) {
104
- lines.push(`| ${escapeTableCell(configMember.path)} | \`${escapeTableCell(configMember.type)}\` | ${configMember.required ? 'yes' : 'no'} | ${renderDefault(configMember.defaultValue)} | ${escapeTableCell(configMember.description ?? '')} |`);
105
- }
106
- 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 ?? '')} |`);
107
120
  }
121
+ lines.push('', '</details>', '');
108
122
  }
109
123
  function renderGeneratedDocumentation(lines, outputDir, diagrams) {
110
124
  lines.push('## Generated documentation', '');
111
125
  lines.push(`- [Interactive documentation app](./${outputDir}/index.html)`);
112
126
  lines.push(`- [Public API reference](./${outputDir}/exports.md)`);
113
127
  lines.push(`- [Component registry](./${outputDir}/components.md)`);
114
- for (const diagram of diagrams) {
128
+ for (const diagram of diagrams)
115
129
  lines.push(`- [${diagram.title}](./${outputDir}/${diagram.path})`);
116
- }
117
130
  lines.push('');
118
131
  }
119
- function renderArchitecturePreview(lines, diagrams) {
120
- lines.push('## Architecture preview', '');
121
- if (diagrams.length > 0) {
122
- lines.push('<details>');
123
- lines.push('<summary>Architecture overview</summary>', '');
124
- lines.push('```mermaid');
125
- lines.push(diagrams[0]?.content.trimEnd() ?? '');
126
- lines.push('```', '');
127
- lines.push('</details>', '');
128
- }
129
- }
130
132
  function renderReadmeApi(lines, model) {
131
133
  const groups = getReadmeGroups(model);
132
134
  if (groups.length === 0)
@@ -135,12 +137,10 @@ function renderReadmeApi(lines, model) {
135
137
  for (const group of groups) {
136
138
  lines.push(`### ${group.title}`, '');
137
139
  for (const item of group.items) {
138
- if (item.kind === 'component') {
140
+ if (item.kind === 'component')
139
141
  renderComponentAccordion(lines, item.component, item.exportEntry);
140
- }
141
- else {
142
+ else
142
143
  renderExportAccordion(lines, item.exportEntry);
143
- }
144
144
  }
145
145
  }
146
146
  }
@@ -217,9 +217,8 @@ function renderStructuredRows(lines, item) {
217
217
  function getStructuredColumns(item) {
218
218
  const columns = new Set();
219
219
  for (const row of item.structuredRows) {
220
- for (const column of Object.keys(row.values)) {
220
+ for (const column of Object.keys(row.values))
221
221
  columns.add(column);
222
- }
223
222
  }
224
223
  return [...columns];
225
224
  }
@@ -228,9 +227,8 @@ function formatStructuredColumnHeader(column) {
228
227
  }
229
228
  function formatStructuredCell(column, value) {
230
229
  const escaped = escapeTableCell(value);
231
- if (column === 'syntax' || column === 'name' || column === 'handler') {
230
+ if (column === 'syntax' || column === 'name' || column === 'handler')
232
231
  return `\`${escaped}\``;
233
- }
234
232
  if (value === 'true')
235
233
  return 'yes';
236
234
  if (value === 'false')
@@ -302,9 +300,8 @@ function renderExports(model) {
302
300
  lines.push(`Kind: \`${item.kind}\``);
303
301
  lines.push(`Module: \`${item.modulePath}\``);
304
302
  lines.push(`Source: \`${item.sourceLocation.filePath}:${item.sourceLocation.line}:${item.sourceLocation.column}\``, '');
305
- if (item.description) {
303
+ if (item.description)
306
304
  lines.push(item.description, '');
307
- }
308
305
  renderStructuredRows(lines, item);
309
306
  if (item.signatures.length > 0) {
310
307
  lines.push('### Signatures', '');
@@ -334,9 +331,8 @@ function renderComponents(model) {
334
331
  for (const component of model.components) {
335
332
  lines.push(`## ${component.name}`, '');
336
333
  lines.push(`Source: \`${component.sourceLocation.filePath}:${component.sourceLocation.line}:${component.sourceLocation.column}\``, '');
337
- if (component.description) {
334
+ if (component.description)
338
335
  lines.push(component.description, '');
339
- }
340
336
  if (component.exportPaths.length > 0) {
341
337
  lines.push(`Export paths: ${component.exportPaths.map((path) => `\`${path}\``).join(', ')}`, '');
342
338
  }
@@ -373,9 +369,8 @@ function flattenConfigMembers(members, prefix = '') {
373
369
  }
374
370
  function badgeLabel(model, badgePath) {
375
371
  const fileName = badgePath.split('/').pop();
376
- if (!fileName) {
372
+ if (!fileName)
377
373
  return badgePath;
378
- }
379
374
  const id = fileName.replace(/\.svg$/, '');
380
375
  const badge = model.badges.find((entry) => entry.id === id);
381
376
  return badge ? `${badge.label}: ${badge.value}` : badgePath;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ankhorage/paradox",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "description": "Deterministic documentation generator for TypeScript packages.",
5
5
  "license": "MIT",
6
6
  "publishConfig": {