@ankhorage/paradox 0.0.5 → 0.0.7

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,21 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.0.7
4
+
5
+ ### Patch Changes
6
+
7
+ - 5246138: Generate deterministic README quality badges from local repository metadata.
8
+
9
+ Paradox now computes README badge metadata from local package, TypeScript, linting, formatting, workflow, test, coverage, and documentation signals, then renders stable local SVG badge assets under `paradox/badges/` and includes them in generated README output without relying on external badge services.
10
+
11
+ ## 0.0.6
12
+
13
+ ### Patch Changes
14
+
15
+ - 1a836cf: Generate a deterministic static documentation app with computed API metadata and Mermaid diagrams.
16
+
17
+ Paradox now emits an offline `paradox/index.html` documentation app, diagram artifacts, richer export metadata, function signature details, parameter and return descriptions, related symbols, and README links to the generated documentation outputs.
18
+
3
19
  ## 0.0.5
4
20
 
5
21
  ### Patch Changes
package/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # @ankhorage/paradox
2
2
 
3
+ ![license: MIT](./paradox/badges/license.svg) ![npm: v0.0.6](./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)
4
+
3
5
  Deterministic documentation generator for TypeScript packages.
4
6
 
5
7
  ## Usage
@@ -20,6 +22,134 @@ export default defineParadoxConfig({
20
22
  });
21
23
  ```
22
24
 
25
+ ## Generated documentation
26
+
27
+ - [Interactive documentation app](./paradox/index.html)
28
+ - [Public API reference](./paradox/exports.md)
29
+ - [Component registry](./paradox/components.md)
30
+ - [Architecture overview](./paradox/diagrams/architecture-overview.mmd)
31
+ - [Module relationships](./paradox/diagrams/module-relationships.mmd)
32
+ - [Export graph](./paradox/diagrams/export-graph.mmd)
33
+ - [Entrypoint sequence](./paradox/diagrams/entrypoint-sequence.mmd)
34
+
35
+ ## Architecture preview
36
+
37
+ ```mermaid
38
+ graph TD
39
+ package__ankhorage_paradox["@ankhorage/paradox"]
40
+ entrypoint_src_index_ts["src/index.ts"]
41
+ package__ankhorage_paradox --> entrypoint_src_index_ts
42
+ module_src_analyze_analyze_ts["src/analyze/analyze.ts"]
43
+ package__ankhorage_paradox -.-> module_src_analyze_analyze_ts
44
+ module_src_analyze_analyze_ts --> module_src_analyze_badges_ts
45
+ module_src_analyze_analyze_ts --> module_src_analyze_components_ts
46
+ module_src_analyze_analyze_ts --> module_src_analyze_exports_ts
47
+ module_src_analyze_analyze_ts --> module_src_analyze_modules_ts
48
+ module_src_analyze_analyze_ts --> module_src_analyze_project_ts
49
+ module_src_analyze_analyze_ts --> module_src_analyze_types_ts
50
+ module_src_analyze_analyze_ts --> module_src_analyze_usage_ts
51
+ module_src_analyze_analyze_ts --> module_src_config_types_ts
52
+ module_src_analyze_badges_ts["src/analyze/badges.ts"]
53
+ package__ankhorage_paradox -.-> module_src_analyze_badges_ts
54
+ module_src_analyze_badges_ts --> module_src_analyze_types_ts
55
+ module_src_analyze_badges_ts --> module_src_analyze_usage_ts
56
+ module_src_analyze_components_ts["src/analyze/components.ts"]
57
+ package__ankhorage_paradox -.-> module_src_analyze_components_ts
58
+ module_src_analyze_components_ts --> module_src_analyze_types_ts
59
+ module_src_analyze_components_ts --> module_src_analyze_utils_getComponentPropsType_ts
60
+ module_src_analyze_components_ts --> module_src_analyze_utils_getPropsFromType_ts
61
+ module_src_analyze_components_ts --> module_src_analyze_utils_isReactComponent_ts
62
+ module_src_analyze_exports_ts["src/analyze/exports.ts"]
63
+ package__ankhorage_paradox -.-> module_src_analyze_exports_ts
64
+ module_src_analyze_exports_ts --> module_src_analyze_types_ts
65
+ module_src_analyze_exports_ts --> module_src_analyze_utils_getExportMetadata_ts
66
+ module_src_analyze_exports_ts --> module_src_analyze_utils_getParadoxComment_ts
67
+ module_src_analyze_exports_ts --> module_src_analyze_utils_parseParadoxComment_ts
68
+ module_src_analyze_exports_ts --> module_src_analyze_utils_resolveExportSymbol_ts
69
+ module_src_analyze_modules_ts["src/analyze/modules.ts"]
70
+ package__ankhorage_paradox -.-> module_src_analyze_modules_ts
71
+ module_src_analyze_modules_ts --> module_src_analyze_types_ts
72
+ module_src_analyze_project_ts["src/analyze/project.ts"]
73
+ package__ankhorage_paradox -.-> module_src_analyze_project_ts
74
+ module_src_analyze_types_ts["src/analyze/types.ts"]
75
+ package__ankhorage_paradox -.-> module_src_analyze_types_ts
76
+ module_src_analyze_usage_ts["src/analyze/usage.ts"]
77
+ package__ankhorage_paradox -.-> module_src_analyze_usage_ts
78
+ module_src_analyze_usage_ts --> module_src_analyze_types_ts
79
+ module_src_analyze_utils_getComponentPropsType_ts["src/analyze/utils/getComponentPropsType.ts"]
80
+ package__ankhorage_paradox -.-> module_src_analyze_utils_getComponentPropsType_ts
81
+ module_src_analyze_utils_getExportMetadata_ts["src/analyze/utils/getExportMetadata.ts"]
82
+ package__ankhorage_paradox -.-> module_src_analyze_utils_getExportMetadata_ts
83
+ module_src_analyze_utils_getExportMetadata_ts --> module_src_analyze_types_ts
84
+ module_src_analyze_utils_getExportMetadata_ts --> module_src_analyze_utils_getParadoxComment_ts
85
+ module_src_analyze_utils_getExportMetadata_ts --> module_src_analyze_utils_parseParadoxComment_ts
86
+ module_src_analyze_utils_getParadoxComment_ts["src/analyze/utils/getParadoxComment.ts"]
87
+ package__ankhorage_paradox -.-> module_src_analyze_utils_getParadoxComment_ts
88
+ module_src_analyze_utils_getPropsFromType_ts["src/analyze/utils/getPropsFromType.ts"]
89
+ package__ankhorage_paradox -.-> module_src_analyze_utils_getPropsFromType_ts
90
+ module_src_analyze_utils_getPropsFromType_ts --> module_src_analyze_types_ts
91
+ module_src_analyze_utils_getPropsFromType_ts --> module_src_analyze_utils_getParadoxComment_ts
92
+ module_src_analyze_utils_getPropsFromType_ts --> module_src_analyze_utils_parseParadoxComment_ts
93
+ module_src_analyze_utils_isReactComponent_ts["src/analyze/utils/isReactComponent.ts"]
94
+ package__ankhorage_paradox -.-> module_src_analyze_utils_isReactComponent_ts
95
+ module_src_analyze_utils_parseParadoxComment_ts["src/analyze/utils/parseParadoxComment.ts"]
96
+ package__ankhorage_paradox -.-> module_src_analyze_utils_parseParadoxComment_ts
97
+ module_src_analyze_utils_resolveExportSymbol_ts["src/analyze/utils/resolveExportSymbol.ts"]
98
+ package__ankhorage_paradox -.-> module_src_analyze_utils_resolveExportSymbol_ts
99
+ module_src_cli_ts["src/cli.ts"]
100
+ package__ankhorage_paradox -.-> module_src_cli_ts
101
+ module_src_cli_ts --> module_src_analyze_analyze_ts
102
+ module_src_cli_ts --> module_src_model_buildModel_ts
103
+ module_src_cli_ts --> module_src_paths_policy_ts
104
+ module_src_cli_ts --> module_src_render_render_ts
105
+ module_src_cli_ts --> module_src_write_write_ts
106
+ module_src_config_defineParadoxConfig_ts["src/config/defineParadoxConfig.ts"]
107
+ package__ankhorage_paradox -.-> module_src_config_defineParadoxConfig_ts
108
+ module_src_config_defineParadoxConfig_ts --> module_src_config_types_ts
109
+ module_src_config_types_ts["src/config/types.ts"]
110
+ package__ankhorage_paradox -.-> module_src_config_types_ts
111
+ module_src_index_ts["src/index.ts"]
112
+ module_src_model_buildModel_ts["src/model/buildModel.ts"]
113
+ package__ankhorage_paradox -.-> module_src_model_buildModel_ts
114
+ module_src_model_buildModel_ts --> module_src_model_types_ts
115
+ module_src_model_types_ts["src/model/types.ts"]
116
+ package__ankhorage_paradox -.-> module_src_model_types_ts
117
+ module_src_paths_policy_ts["src/paths/policy.ts"]
118
+ package__ankhorage_paradox -.-> module_src_paths_policy_ts
119
+ module_src_paths_policy_ts --> module_src_config_types_ts
120
+ module_src_render_render_ts["src/render/render.ts"]
121
+ package__ankhorage_paradox -.-> module_src_render_render_ts
122
+ module_src_render_render_ts --> module_src_model_types_ts
123
+ module_src_render_render_ts --> module_src_render_renderers_badges_ts
124
+ module_src_render_render_ts --> module_src_render_renderers_diagrams_ts
125
+ module_src_render_render_ts --> module_src_render_renderers_html_ts
126
+ module_src_render_render_ts --> module_src_render_renderers_markdown_ts
127
+ module_src_render_render_ts --> module_src_render_types_ts
128
+ module_src_render_renderers_badges_ts["src/render/renderers/badges.ts"]
129
+ package__ankhorage_paradox -.-> module_src_render_renderers_badges_ts
130
+ module_src_render_renderers_badges_ts --> module_src_model_types_ts
131
+ module_src_render_renderers_badges_ts --> module_src_render_types_ts
132
+ module_src_render_renderers_diagrams_ts["src/render/renderers/diagrams.ts"]
133
+ package__ankhorage_paradox -.-> module_src_render_renderers_diagrams_ts
134
+ module_src_render_renderers_diagrams_ts --> module_src_model_types_ts
135
+ module_src_render_renderers_diagrams_ts --> module_src_render_types_ts
136
+ module_src_render_renderers_html_ts["src/render/renderers/html.ts"]
137
+ package__ankhorage_paradox -.-> module_src_render_renderers_html_ts
138
+ module_src_render_renderers_html_ts --> module_src_model_types_ts
139
+ module_src_render_renderers_html_ts --> module_src_render_types_ts
140
+ module_src_render_renderers_markdown_ts["src/render/renderers/markdown.ts"]
141
+ package__ankhorage_paradox -.-> module_src_render_renderers_markdown_ts
142
+ module_src_render_renderers_markdown_ts --> module_src_model_types_ts
143
+ module_src_render_renderers_markdown_ts --> module_src_render_types_ts
144
+ module_src_render_types_ts["src/render/types.ts"]
145
+ package__ankhorage_paradox -.-> module_src_render_types_ts
146
+ module_src_render_types_ts --> module_src_model_types_ts
147
+ module_src_write_write_ts["src/write/write.ts"]
148
+ package__ankhorage_paradox -.-> module_src_write_write_ts
149
+ module_src_write_write_ts --> module_src_config_types_ts
150
+ module_src_write_write_ts --> module_src_render_types_ts
151
+ ```
152
+
23
153
  ## Path resolution
24
154
 
25
155
  - Config discovery: searches upward from `process.cwd()` for `paradox.config.ts/js/mjs/cjs` (required; no fallback).
@@ -35,6 +165,17 @@ export default defineParadoxConfig({
35
165
 
36
166
  Defines a Paradox configuration object without changing its shape.
37
167
 
168
+ - Kind: `function`
169
+ - Module: `src/config/defineParadoxConfig.ts`
170
+ - Source: `src/config/defineParadoxConfig.ts:6:1`
171
+ - Export paths: `src/index.ts`
172
+ - Related symbols: `ParadoxConfig`
173
+
38
174
  ### ParadoxConfig
39
175
 
40
176
  Configuration for running Paradox.
177
+
178
+ - Kind: `type`
179
+ - Module: `src/config/types.ts`
180
+ - Source: `src/config/types.ts:6:1`
181
+ - Export paths: `src/index.ts`
@@ -1,7 +1,9 @@
1
1
  import { readFile } from 'node:fs/promises';
2
2
  import { join } from 'node:path';
3
+ import { analyzeBadges } from './badges.js';
3
4
  import { analyzeComponents } from './components.js';
4
5
  import { analyzeExports } from './exports.js';
6
+ import { analyzeModules } from './modules.js';
5
7
  import { createProject } from './project.js';
6
8
  import { createUsageFromPackageJson } from './usage.js';
7
9
  /***
@@ -11,6 +13,7 @@ export async function analyze(config, runtime) {
11
13
  const root = runtime.packageRoot;
12
14
  const pkg = await readPackageJson(root);
13
15
  const usage = createUsageFromPackageJson(pkg);
16
+ const badges = await analyzeBadges(root, pkg);
14
17
  const project = createProject(root);
15
18
  const entrypoints = config.package?.entrypoints ?? ['src/index.ts'];
16
19
  const { config: configMetadata, exports } = analyzeExports(project, {
@@ -18,12 +21,19 @@ export async function analyze(config, runtime) {
18
21
  entrypoints,
19
22
  });
20
23
  const components = analyzeComponents(exports);
24
+ const modules = analyzeModules(project, {
25
+ root,
26
+ entrypoints,
27
+ });
21
28
  return {
22
29
  packageName: config.docs?.title ?? pkg.name,
23
30
  packageId: pkg.name,
24
31
  description: config.docs?.description ?? pkg.description ?? null,
25
32
  exports,
26
33
  components,
34
+ entrypoints: entrypoints.map((entrypoint) => entrypoint.replaceAll('\\', '/')).sort(),
35
+ modules,
36
+ badges,
27
37
  usage,
28
38
  config: configMetadata,
29
39
  };
@@ -0,0 +1,6 @@
1
+ import type { AnalysisBadge } from './types.js';
2
+ import type { PackageJsonModel } from './usage.js';
3
+ /***
4
+ * Derives deterministic repository metadata badges from local repository files.
5
+ */
6
+ export declare function analyzeBadges(root: string, pkg: PackageJsonModel): Promise<AnalysisBadge[]>;
@@ -0,0 +1,256 @@
1
+ import { access, readdir, readFile } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ const ESLINT_CONFIG_FILES = [
4
+ 'eslint.config.js',
5
+ 'eslint.config.mjs',
6
+ 'eslint.config.cjs',
7
+ 'eslint.config.ts',
8
+ '.eslintrc',
9
+ '.eslintrc.js',
10
+ '.eslintrc.cjs',
11
+ '.eslintrc.json',
12
+ '.eslintrc.yaml',
13
+ '.eslintrc.yml',
14
+ ];
15
+ const PRETTIER_CONFIG_FILES = [
16
+ '.prettierrc',
17
+ '.prettierrc.js',
18
+ '.prettierrc.cjs',
19
+ '.prettierrc.json',
20
+ '.prettierrc.yaml',
21
+ '.prettierrc.yml',
22
+ 'prettier.config.js',
23
+ 'prettier.config.cjs',
24
+ 'prettier.config.mjs',
25
+ 'prettier.config.ts',
26
+ ];
27
+ const BADGE_ORDER = [
28
+ 'license',
29
+ 'npm',
30
+ 'runtime',
31
+ 'typescript',
32
+ 'eslint',
33
+ 'prettier',
34
+ 'build',
35
+ 'tests',
36
+ 'coverage',
37
+ 'docs',
38
+ ];
39
+ /***
40
+ * Derives deterministic repository metadata badges from local repository files.
41
+ */
42
+ export async function analyzeBadges(root, pkg) {
43
+ const workflowFiles = await readWorkflowFiles(root);
44
+ const badges = [];
45
+ if ((await hasAnyFile(root, ESLINT_CONFIG_FILES)) ||
46
+ pkg.eslintConfig !== undefined ||
47
+ scriptContains(pkg, 'lint', 'eslint')) {
48
+ badges.push({
49
+ id: 'eslint',
50
+ label: 'eslint',
51
+ value: workflowRunsScript(workflowFiles, 'lint') ? 'checked' : 'configured',
52
+ color: workflowRunsScript(workflowFiles, 'lint') ? '0a7f3f' : '2563eb',
53
+ });
54
+ }
55
+ if ((await hasAnyFile(root, PRETTIER_CONFIG_FILES)) ||
56
+ pkg.prettier !== undefined ||
57
+ scriptContains(pkg, 'format', 'prettier') ||
58
+ scriptContains(pkg, 'format:check', 'prettier')) {
59
+ const prettierChecked = workflowRunsScript(workflowFiles, 'format') ||
60
+ workflowRunsScript(workflowFiles, 'format:check');
61
+ badges.push({
62
+ id: 'prettier',
63
+ label: 'prettier',
64
+ value: prettierChecked ? 'checked' : 'configured',
65
+ color: prettierChecked ? '0a7f3f' : '2563eb',
66
+ });
67
+ }
68
+ if (hasScript(pkg, 'build')) {
69
+ const buildChecked = workflowRunsScript(workflowFiles, 'build');
70
+ badges.push({
71
+ id: 'build',
72
+ label: 'build',
73
+ value: buildChecked ? 'checked' : 'configured',
74
+ color: buildChecked ? '0a7f3f' : '6f42c1',
75
+ });
76
+ }
77
+ if (hasScript(pkg, 'test')) {
78
+ const testsChecked = workflowRunsScript(workflowFiles, 'test');
79
+ badges.push({
80
+ id: 'tests',
81
+ label: 'tests',
82
+ value: testsChecked ? 'checked' : 'configured',
83
+ color: testsChecked ? '0a7f3f' : '6f42c1',
84
+ });
85
+ }
86
+ if (await hasTypeScriptStrictMode(root)) {
87
+ badges.push({
88
+ id: 'typescript',
89
+ label: 'typescript',
90
+ value: 'strict',
91
+ color: '2563eb',
92
+ });
93
+ }
94
+ if (supportsBun(pkg, workflowFiles)) {
95
+ badges.push({
96
+ id: 'runtime',
97
+ label: 'runtime',
98
+ value: 'bun',
99
+ color: 'f59e0b',
100
+ });
101
+ }
102
+ const coverage = await readCoveragePercent(root);
103
+ if (coverage !== null) {
104
+ badges.push({
105
+ id: 'coverage',
106
+ label: 'coverage',
107
+ value: `${formatPercent(coverage)}%`,
108
+ color: getCoverageColor(coverage),
109
+ });
110
+ }
111
+ if (typeof pkg.license === 'string' && pkg.license.length > 0) {
112
+ badges.push({
113
+ id: 'license',
114
+ label: 'license',
115
+ value: pkg.license,
116
+ color: '2563eb',
117
+ });
118
+ }
119
+ if (typeof pkg.version === 'string' && pkg.version.length > 0) {
120
+ badges.push({
121
+ id: 'npm',
122
+ label: 'npm',
123
+ value: `v${pkg.version}`,
124
+ color: 'cb3837',
125
+ });
126
+ }
127
+ badges.push({
128
+ id: 'docs',
129
+ label: 'docs',
130
+ value: 'paradox',
131
+ color: '0f766e',
132
+ });
133
+ return sortBadges(badges);
134
+ }
135
+ async function hasAnyFile(root, paths) {
136
+ for (const relativePath of paths) {
137
+ if (await fileExists(join(root, relativePath))) {
138
+ return true;
139
+ }
140
+ }
141
+ return false;
142
+ }
143
+ async function hasTypeScriptStrictMode(root) {
144
+ const tsconfig = await readJsonFile(join(root, 'tsconfig.json'));
145
+ return tsconfig?.compilerOptions?.strict === true;
146
+ }
147
+ async function readCoveragePercent(root) {
148
+ const coverage = await readJsonFile(join(root, 'coverage', 'coverage-summary.json'));
149
+ const value = coverage?.total?.lines?.pct;
150
+ return typeof value === 'number' && Number.isFinite(value) ? value : null;
151
+ }
152
+ function formatPercent(value) {
153
+ return Number.isInteger(value) ? `${value}` : value.toFixed(1);
154
+ }
155
+ function getCoverageColor(value) {
156
+ if (value >= 90)
157
+ return '0a7f3f';
158
+ if (value >= 75)
159
+ return '65a30d';
160
+ if (value >= 50)
161
+ return 'ca8a04';
162
+ return 'dc2626';
163
+ }
164
+ function hasScript(pkg, name) {
165
+ return typeof pkg.scripts?.[name] === 'string' && pkg.scripts[name].length > 0;
166
+ }
167
+ function scriptContains(pkg, name, command) {
168
+ return pkg.scripts?.[name]?.includes(command) ?? false;
169
+ }
170
+ function supportsBun(pkg, workflowFiles) {
171
+ if (pkg.packageManager?.startsWith('bun@') ?? false) {
172
+ return true;
173
+ }
174
+ if (Object.values(pkg.scripts ?? {}).some((script) => script.includes('bun') || script.includes('bunx'))) {
175
+ return true;
176
+ }
177
+ return workflowFiles.some((workflow) => workflow.includes('oven-sh/setup-bun') ||
178
+ workflow.includes('bun install') ||
179
+ /\bbun(?:x)?\b/.test(workflow));
180
+ }
181
+ function workflowRunsScript(workflowFiles, scriptName) {
182
+ const escaped = escapeRegExp(scriptName);
183
+ const patterns = [
184
+ new RegExp(`\\bbun\\s+run\\s+${escaped}(?=\\s|$)`, 'i'),
185
+ new RegExp(`\\bnpm\\s+run\\s+${escaped}(?=\\s|$)`, 'i'),
186
+ new RegExp(`\\bpnpm\\s+${escaped}(?=\\s|$)`, 'i'),
187
+ new RegExp(`\\byarn\\s+${escaped}(?=\\s|$)`, 'i'),
188
+ ];
189
+ return workflowFiles.some((workflow) => patterns.some((pattern) => pattern.test(workflow)));
190
+ }
191
+ function escapeRegExp(value) {
192
+ return value.replaceAll(/[.*+?^${}()|[\]\\]/g, '\\$&');
193
+ }
194
+ async function readWorkflowFiles(root) {
195
+ const workflowsPath = join(root, '.github', 'workflows');
196
+ const entries = await readDirectory(workflowsPath);
197
+ const contents = await Promise.all(entries
198
+ .filter((entry) => entry.endsWith('.yml') || entry.endsWith('.yaml'))
199
+ .sort((a, b) => a.localeCompare(b))
200
+ .map((entry) => readFile(join(workflowsPath, entry), 'utf-8')));
201
+ return contents;
202
+ }
203
+ async function readDirectory(path) {
204
+ try {
205
+ return await readdir(path);
206
+ }
207
+ catch (error) {
208
+ if (isNotFoundError(error)) {
209
+ return [];
210
+ }
211
+ throw new Error(`Unable to read workflow directory: ${path}`, { cause: error });
212
+ }
213
+ }
214
+ function isNotFoundError(error) {
215
+ return typeof error === 'object' && error !== null && 'code' in error && error.code === 'ENOENT';
216
+ }
217
+ async function readJsonFile(path) {
218
+ let content;
219
+ try {
220
+ content = await readFile(path, 'utf-8');
221
+ }
222
+ catch (error) {
223
+ if (isNotFoundError(error)) {
224
+ return null;
225
+ }
226
+ throw new Error(`Unable to read JSON metadata file: ${path}`, { cause: error });
227
+ }
228
+ try {
229
+ return JSON.parse(content);
230
+ }
231
+ catch (error) {
232
+ throw new Error(`Unable to parse JSON metadata file: ${path}`, { cause: error });
233
+ }
234
+ }
235
+ async function fileExists(path) {
236
+ try {
237
+ await access(path);
238
+ return true;
239
+ }
240
+ catch (error) {
241
+ if (isNotFoundError(error)) {
242
+ return false;
243
+ }
244
+ throw error;
245
+ }
246
+ }
247
+ function sortBadges(badges) {
248
+ return [...badges].sort((left, right) => {
249
+ const order = getBadgeOrder(left.id) - getBadgeOrder(right.id);
250
+ return order !== 0 ? order : left.id.localeCompare(right.id);
251
+ });
252
+ }
253
+ function getBadgeOrder(id) {
254
+ const index = BADGE_ORDER.indexOf(id);
255
+ return index === -1 ? BADGE_ORDER.length : index;
256
+ }
@@ -14,6 +14,9 @@ export function analyzeComponents(exports) {
14
14
  components.push({
15
15
  name: e.name,
16
16
  description: e.description,
17
+ modulePath: e.modulePath,
18
+ sourceLocation: e.sourceLocation,
19
+ exportPaths: e.exportPaths,
17
20
  props,
18
21
  });
19
22
  }
@@ -1,4 +1,5 @@
1
- import { isAbsolute, join, normalize } from 'node:path';
1
+ import { isAbsolute, join, normalize, relative } from 'node:path';
2
+ import { getExportMetadata } from './utils/getExportMetadata.js';
2
3
  import { getParadoxComment } from './utils/getParadoxComment.js';
3
4
  import { parseParadoxComment } from './utils/parseParadoxComment.js';
4
5
  import { resolveExportSymbol } from './utils/resolveExportSymbol.js';
@@ -6,9 +7,10 @@ import { resolveExportSymbol } from './utils/resolveExportSymbol.js';
6
7
  * Collects exported declarations from configured package entrypoints.
7
8
  */
8
9
  export function analyzeExports(project, options) {
9
- const exports = [];
10
+ const exportsByName = new Map();
10
11
  let config = null;
11
12
  for (const sourceFile of getEntryPointSourceFiles(project, options)) {
13
+ const entrypointPath = toPosixPath(relative(options.root, sourceFile.getFilePath()));
12
14
  const exported = sourceFile.getExportSymbols();
13
15
  for (const symbol of exported) {
14
16
  const resolved = resolveExportSymbol(symbol);
@@ -16,22 +18,44 @@ export function analyzeExports(project, options) {
16
18
  const rawComment = getParadoxComment(decl);
17
19
  const parsed = rawComment
18
20
  ? parseParadoxComment(rawComment)
19
- : { description: null, isConfig: false };
21
+ : { description: null, isConfig: false, params: {}, returns: null };
22
+ const name = resolved.getName();
20
23
  if (parsed.isConfig) {
21
24
  config = {
22
- exportName: resolved.getName(),
25
+ exportName: name,
23
26
  };
24
27
  }
25
- exports.push({
26
- name: resolved.getName(),
28
+ const metadata = getExportMetadata({
29
+ name,
27
30
  node: decl,
28
- description: parsed.description,
29
- kind: inferKind(decl),
31
+ root: options.root,
32
+ entrypointPath,
33
+ symbol: resolved,
30
34
  });
35
+ const existing = exportsByName.get(name);
36
+ exportsByName.set(name, existing
37
+ ? {
38
+ ...existing,
39
+ description: existing.description ?? parsed.description,
40
+ exportPaths: uniqueSorted([...existing.exportPaths, ...metadata.exportPaths]),
41
+ relatedSymbols: uniqueSorted([
42
+ ...existing.relatedSymbols,
43
+ ...metadata.relatedSymbols,
44
+ ]),
45
+ signatures: existing.signatures.length > 0 ? existing.signatures : metadata.signatures,
46
+ members: existing.members.length > 0 ? existing.members : metadata.members,
47
+ }
48
+ : {
49
+ name,
50
+ node: decl,
51
+ description: parsed.description,
52
+ kind: inferKind(decl),
53
+ ...metadata,
54
+ });
31
55
  }
32
56
  }
33
57
  return {
34
- exports,
58
+ exports: [...exportsByName.values()],
35
59
  config,
36
60
  };
37
61
  }
@@ -50,3 +74,9 @@ function inferKind(node) {
50
74
  return 'type';
51
75
  return 'unknown';
52
76
  }
77
+ function uniqueSorted(values) {
78
+ return [...new Set(values)].sort((left, right) => left.localeCompare(right));
79
+ }
80
+ function toPosixPath(path) {
81
+ return path.replaceAll('\\', '/');
82
+ }
@@ -0,0 +1,9 @@
1
+ import type { Project } from 'ts-morph';
2
+ import type { AnalysisModule } from './types.js';
3
+ /***
4
+ * Builds a deterministic module relationship graph for documentation renderers.
5
+ */
6
+ export declare function analyzeModules(project: Project, options: {
7
+ root: string;
8
+ entrypoints: readonly string[];
9
+ }): AnalysisModule[];
@@ -0,0 +1,44 @@
1
+ import { isAbsolute, join, normalize, relative } from 'node:path';
2
+ /***
3
+ * Builds a deterministic module relationship graph for documentation renderers.
4
+ */
5
+ export function analyzeModules(project, options) {
6
+ const rootPath = normalize(options.root);
7
+ const entrypointPaths = new Set(options.entrypoints.map((entrypoint) => normalize(isAbsolute(entrypoint) ? entrypoint : join(options.root, entrypoint))));
8
+ return project
9
+ .getSourceFiles()
10
+ .filter((sourceFile) => {
11
+ const filePath = normalize(sourceFile.getFilePath());
12
+ const normalizedPath = toPosixPath(filePath);
13
+ return (!sourceFile.isDeclarationFile() &&
14
+ filePath.startsWith(rootPath) &&
15
+ !normalizedPath.includes('/node_modules/'));
16
+ })
17
+ .map((sourceFile) => {
18
+ const path = toPosixPath(relative(options.root, sourceFile.getFilePath()));
19
+ const dependencies = sourceFile
20
+ .getImportDeclarations()
21
+ .map((declaration) => declaration.getModuleSpecifierSourceFile())
22
+ .filter((dependency) => dependency != null)
23
+ .map((dependency) => normalize(dependency.getFilePath()))
24
+ .filter((dependency) => dependency.startsWith(rootPath) && !toPosixPath(dependency).includes('/node_modules/'))
25
+ .map((dependency) => toPosixPath(relative(options.root, dependency)));
26
+ const exports = sourceFile
27
+ .getExportSymbols()
28
+ .map((symbol) => symbol.getName())
29
+ .sort((left, right) => left.localeCompare(right));
30
+ return {
31
+ path,
32
+ isEntrypoint: entrypointPaths.has(normalize(sourceFile.getFilePath())),
33
+ dependencies: uniqueSorted(dependencies),
34
+ exports: uniqueSorted(exports),
35
+ };
36
+ })
37
+ .sort((left, right) => left.path.localeCompare(right.path));
38
+ }
39
+ function uniqueSorted(values) {
40
+ return [...new Set(values)].sort((left, right) => left.localeCompare(right));
41
+ }
42
+ function toPosixPath(path) {
43
+ return path.replaceAll('\\', '/');
44
+ }