@ankhorage/paradox 0.0.2 → 0.0.4

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.
Files changed (97) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +9 -0
  3. package/dist/analyze/analyze.d.ts +8 -0
  4. package/dist/analyze/analyze.js +34 -0
  5. package/dist/analyze/components.d.ts +5 -0
  6. package/dist/analyze/components.js +21 -0
  7. package/dist/analyze/exports.d.ts +16 -0
  8. package/dist/analyze/exports.js +52 -0
  9. package/dist/analyze/project.d.ts +5 -0
  10. package/dist/analyze/project.js +10 -0
  11. package/dist/analyze/types.d.ts +45 -0
  12. package/dist/analyze/types.js +1 -0
  13. package/dist/analyze/usage.d.ts +7 -0
  14. package/dist/analyze/usage.js +40 -0
  15. package/dist/analyze/utils/getComponentPropsType.d.ts +5 -0
  16. package/dist/analyze/utils/getComponentPropsType.js +21 -0
  17. package/dist/analyze/utils/getParadoxComment.d.ts +5 -0
  18. package/dist/analyze/utils/getParadoxComment.js +19 -0
  19. package/dist/analyze/utils/getPropsFromType.d.ts +6 -0
  20. package/dist/analyze/utils/getPropsFromType.js +19 -0
  21. package/dist/analyze/utils/isReactComponent.d.ts +5 -0
  22. package/dist/analyze/utils/isReactComponent.js +30 -0
  23. package/dist/analyze/utils/parseParadoxComment.d.ts +12 -0
  24. package/dist/analyze/utils/parseParadoxComment.js +25 -0
  25. package/{src/analyze/utils/resolveExportSymbol.ts → dist/analyze/utils/resolveExportSymbol.d.ts} +1 -4
  26. package/dist/analyze/utils/resolveExportSymbol.js +6 -0
  27. package/dist/cli.d.ts +2 -0
  28. package/dist/cli.js +26 -0
  29. package/{src/config/defineParadoxConfig.ts → dist/config/defineParadoxConfig.d.ts} +1 -4
  30. package/dist/config/defineParadoxConfig.js +6 -0
  31. package/dist/config/types.d.ts +19 -0
  32. package/dist/config/types.js +1 -0
  33. package/dist/index.js +1 -0
  34. package/dist/model/buildModel.d.ts +36 -0
  35. package/dist/model/buildModel.js +65 -0
  36. package/dist/model/types.d.ts +42 -0
  37. package/dist/model/types.js +1 -0
  38. package/dist/paths/policy.d.ts +23 -0
  39. package/dist/paths/policy.js +105 -0
  40. package/dist/render/render.d.ts +6 -0
  41. package/dist/render/render.js +95 -0
  42. package/dist/render/types.d.ts +10 -0
  43. package/dist/render/types.js +1 -0
  44. package/dist/write/write.d.ts +9 -0
  45. package/dist/write/write.js +18 -0
  46. package/package.json +27 -3
  47. package/.changeset/README.md +0 -5
  48. package/.changeset/config.json +0 -11
  49. package/.changeset/stable-usage-model.md +0 -5
  50. package/.github/workflows/docs.yml +0 -45
  51. package/.prettierignore +0 -1
  52. package/.prettierrc.js +0 -5
  53. package/bun.lock +0 -663
  54. package/eslint.config.mjs +0 -12
  55. package/knip.json +0 -8
  56. package/paradox/components.md +0 -1
  57. package/paradox/exports.json +0 -12
  58. package/paradox/exports.md +0 -13
  59. package/paradox/paradox.json +0 -32
  60. package/paradox.config.ts +0 -18
  61. package/src/analyze/analyze.ts +0 -45
  62. package/src/analyze/components.ts +0 -27
  63. package/src/analyze/exports.ts +0 -87
  64. package/src/analyze/project.ts +0 -11
  65. package/src/analyze/types.ts +0 -53
  66. package/src/analyze/usage.ts +0 -53
  67. package/src/analyze/utils/getComponentPropsType.ts +0 -26
  68. package/src/analyze/utils/getParadoxComment.ts +0 -24
  69. package/src/analyze/utils/getPropsFromType.ts +0 -24
  70. package/src/analyze/utils/isReactComponent.ts +0 -35
  71. package/src/analyze/utils/parseParadoxComment.ts +0 -37
  72. package/src/cli.ts +0 -30
  73. package/src/config/types.ts +0 -22
  74. package/src/model/buildModel.ts +0 -112
  75. package/src/model/types.ts +0 -49
  76. package/src/render/render.ts +0 -115
  77. package/src/render/types.ts +0 -10
  78. package/src/write/write.ts +0 -25
  79. package/tests/__snapshots__/basic.components.md +0 -10
  80. package/tests/__snapshots__/basic.exports.md +0 -19
  81. package/tests/__snapshots__/basic.readme.md +0 -37
  82. package/tests/__snapshots__/multi-bin.readme.md +0 -16
  83. package/tests/analyze.test.ts +0 -153
  84. package/tests/fixtures/basic/package.json +0 -8
  85. package/tests/fixtures/basic/src/config.ts +0 -8
  86. package/tests/fixtures/basic/src/index.ts +0 -3
  87. package/tests/fixtures/basic/src/internal.ts +0 -6
  88. package/tests/fixtures/basic/src/ui.ts +0 -29
  89. package/tests/fixtures/basic/tsconfig.json +0 -11
  90. package/tests/fixtures/multi-bin/package.json +0 -9
  91. package/tests/fixtures/multi-bin/src/alpha.ts +0 -1
  92. package/tests/fixtures/multi-bin/src/beta.ts +0 -1
  93. package/tests/fixtures/multi-bin/src/index.ts +0 -4
  94. package/tests/fixtures/multi-bin/tsconfig.json +0 -11
  95. package/tsconfig.eslint.json +0 -16
  96. package/tsconfig.json +0 -14
  97. /package/{src/index.ts → dist/index.d.ts} +0 -0
package/eslint.config.mjs DELETED
@@ -1,12 +0,0 @@
1
- import path from 'node:path';
2
- import { fileURLToPath } from 'node:url';
3
-
4
- import { createConfig } from '@ankhorage/devtools/eslint';
5
-
6
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
7
-
8
- export default createConfig({
9
- tsconfigRootDir: __dirname,
10
- project: ['./tsconfig.eslint.json'],
11
- files: ['src/**/*.ts', 'tests/*.ts', 'paradox.config.ts'],
12
- });
package/knip.json DELETED
@@ -1,8 +0,0 @@
1
- {
2
- "$schema": "https://unpkg.com/knip@latest/schema.json",
3
- "entry": ["src/index.ts", "eslint.config.mjs", ".prettierrc.js", "paradox.config.ts"],
4
- "project": ["src/**/*.ts", "tests/**/*.ts"],
5
- "ignore": ["tests/fixtures/**"],
6
- "ignoreBinaries": ["eslint", "prettier"],
7
- "ignoreUnresolved": ["bun-types"]
8
- }
@@ -1 +0,0 @@
1
- # Components
@@ -1,12 +0,0 @@
1
- [
2
- {
3
- "name": "defineParadoxConfig",
4
- "description": "Defines a Paradox configuration object without changing its shape.",
5
- "kind": "function"
6
- },
7
- {
8
- "name": "ParadoxConfig",
9
- "description": "Configuration for running Paradox.",
10
- "kind": "type"
11
- }
12
- ]
@@ -1,13 +0,0 @@
1
- # Public API
2
-
3
- ## defineParadoxConfig
4
-
5
- Kind: `function`
6
-
7
- Defines a Paradox configuration object without changing its shape.
8
-
9
- ## ParadoxConfig
10
-
11
- Kind: `type`
12
-
13
- Configuration for running Paradox.
@@ -1,32 +0,0 @@
1
- {
2
- "packageName": "@ankhorage/paradox",
3
- "packageId": "@ankhorage/paradox",
4
- "description": "Deterministic documentation generator for TypeScript packages.",
5
- "usage": {
6
- "packageName": "@ankhorage/paradox",
7
- "commands": [
8
- {
9
- "name": "paradox",
10
- "command": "bunx @ankhorage/paradox"
11
- }
12
- ]
13
- },
14
- "config": {
15
- "exportName": "ParadoxConfig",
16
- "configFile": "paradox.config.ts",
17
- "factoryName": "defineParadoxConfig"
18
- },
19
- "exports": [
20
- {
21
- "name": "defineParadoxConfig",
22
- "description": "Defines a Paradox configuration object without changing its shape.",
23
- "kind": "function"
24
- },
25
- {
26
- "name": "ParadoxConfig",
27
- "description": "Configuration for running Paradox.",
28
- "kind": "type"
29
- }
30
- ],
31
- "components": []
32
- }
package/paradox.config.ts DELETED
@@ -1,18 +0,0 @@
1
- import { defineParadoxConfig } from './src/config/defineParadoxConfig.js';
2
-
3
- export default defineParadoxConfig({
4
- mode: 'write',
5
-
6
- docs: {
7
- title: '@ankhorage/paradox',
8
- description: 'Deterministic documentation generator for TypeScript packages.',
9
- },
10
-
11
- package: {
12
- entrypoints: ['src/index.ts'],
13
- },
14
-
15
- output: {
16
- dir: 'paradox',
17
- },
18
- });
@@ -1,45 +0,0 @@
1
- import { readFile } from 'node:fs/promises';
2
- import { join } from 'node:path';
3
-
4
- import type { ParadoxConfig } from '../config/types.js';
5
- import { analyzeComponents } from './components.js';
6
- import { analyzeExports } from './exports.js';
7
- import { createProject } from './project.js';
8
- import type { AnalysisResult } from './types.js';
9
- import { createUsageFromPackageJson, type PackageJsonModel } from './usage.js';
10
-
11
- /***
12
- * Runs the source analysis pipeline for a configured package.
13
- */
14
- export async function analyze(config: ParadoxConfig): Promise<AnalysisResult> {
15
- const root = config.package?.root ?? process.cwd();
16
-
17
- const pkg = await readPackageJson(root);
18
- const usage = createUsageFromPackageJson(pkg);
19
-
20
- const project = createProject(root);
21
- const entrypoints = config.package?.entrypoints ?? ['src/index.ts'];
22
-
23
- const { config: configMetadata, exports } = analyzeExports(project, {
24
- root,
25
- entrypoints,
26
- });
27
- const components = analyzeComponents(exports);
28
-
29
- return {
30
- packageName: config.docs?.title ?? pkg.name,
31
- packageId: pkg.name,
32
- description: config.docs?.description ?? pkg.description ?? null,
33
-
34
- exports,
35
- components,
36
- usage,
37
- config: configMetadata,
38
- };
39
- }
40
-
41
- async function readPackageJson(root: string): Promise<PackageJsonModel> {
42
- const raw = await readFile(join(root, 'package.json'), 'utf-8');
43
-
44
- return JSON.parse(raw) as PackageJsonModel;
45
- }
@@ -1,27 +0,0 @@
1
- import type { AnalysisComponent, AnalysisExport } from './types.js';
2
- import { getComponentPropsType } from './utils/getComponentPropsType.js';
3
- import { getPropsFromType } from './utils/getPropsFromType.js';
4
- import { isReactComponent } from './utils/isReactComponent.js';
5
-
6
- /***
7
- * Extracts React components and their props from analyzed exports.
8
- */
9
- export function analyzeComponents(exports: readonly AnalysisExport[]): AnalysisComponent[] {
10
- const components: AnalysisComponent[] = [];
11
-
12
- for (const e of exports) {
13
- if (!isReactComponent(e.node)) continue;
14
-
15
- const propsType = getComponentPropsType(e.node);
16
-
17
- const props = propsType != null ? getPropsFromType(propsType) : [];
18
-
19
- components.push({
20
- name: e.name,
21
- description: e.description,
22
- props,
23
- });
24
- }
25
-
26
- return components;
27
- }
@@ -1,87 +0,0 @@
1
- import { isAbsolute, join, normalize } from 'node:path';
2
-
3
- import type { Node, Project } from 'ts-morph';
4
-
5
- import type { AnalysisExport } from './types.js';
6
- import { getParadoxComment } from './utils/getParadoxComment.js';
7
- import { parseParadoxComment } from './utils/parseParadoxComment.js';
8
- import { resolveExportSymbol } from './utils/resolveExportSymbol.js';
9
-
10
- interface AnalyzeExportsResult {
11
- exports: AnalysisExport[];
12
- config: {
13
- exportName: string;
14
- } | null;
15
- }
16
-
17
- /***
18
- * Collects exported declarations from configured package entrypoints.
19
- */
20
- export function analyzeExports(
21
- project: Project,
22
- options: {
23
- root: string;
24
- entrypoints: readonly string[];
25
- },
26
- ): AnalyzeExportsResult {
27
- const exports: AnalysisExport[] = [];
28
- let config: AnalyzeExportsResult['config'] = null;
29
-
30
- for (const sourceFile of getEntryPointSourceFiles(project, options)) {
31
- const exported = sourceFile.getExportSymbols();
32
-
33
- for (const symbol of exported) {
34
- const resolved = resolveExportSymbol(symbol);
35
- const [decl] = resolved.getDeclarations();
36
-
37
- const rawComment = getParadoxComment(decl);
38
- const parsed = rawComment
39
- ? parseParadoxComment(rawComment)
40
- : { description: null, isConfig: false };
41
-
42
- if (parsed.isConfig) {
43
- config = {
44
- exportName: resolved.getName(),
45
- };
46
- }
47
-
48
- exports.push({
49
- name: resolved.getName(),
50
- node: decl,
51
- description: parsed.description,
52
- kind: inferKind(decl),
53
- });
54
- }
55
- }
56
-
57
- return {
58
- exports,
59
- config,
60
- };
61
- }
62
-
63
- function getEntryPointSourceFiles(
64
- project: Project,
65
- options: {
66
- root: string;
67
- entrypoints: readonly string[];
68
- },
69
- ) {
70
- return options.entrypoints
71
- .map((entrypoint) => {
72
- const absolutePath = normalize(
73
- isAbsolute(entrypoint) ? entrypoint : join(options.root, entrypoint),
74
- );
75
-
76
- return project.getSourceFile(
77
- (sourceFile) => normalize(sourceFile.getFilePath()) === absolutePath,
78
- );
79
- })
80
- .filter((sourceFile) => sourceFile != null);
81
- }
82
-
83
- function inferKind(node: Node): AnalysisExport['kind'] {
84
- if ('getParameters' in node) return 'function';
85
- if ('getProperties' in node || 'getMembers' in node) return 'type';
86
- return 'unknown';
87
- }
@@ -1,11 +0,0 @@
1
- import { Project } from 'ts-morph';
2
-
3
- /***
4
- * Creates the ts-morph project used by the analyzer.
5
- */
6
- export function createProject(root: string): Project {
7
- return new Project({
8
- tsConfigFilePath: `${root}/tsconfig.json`,
9
- skipAddingFilesFromTsConfig: false,
10
- });
11
- }
@@ -1,53 +0,0 @@
1
- import type { Node } from 'ts-morph';
2
-
3
- /***
4
- * Describes one exported declaration discovered in a package.
5
- */
6
- export interface AnalysisExport {
7
- name: string;
8
- node: Node;
9
- description: string | null;
10
- kind: 'function' | 'type' | 'unknown';
11
- }
12
-
13
- /***
14
- * Describes one React component and its extracted props.
15
- */
16
- export interface AnalysisComponent {
17
- name: string;
18
- description: string | null;
19
- props: {
20
- name: string;
21
- type: string;
22
- required: boolean;
23
- description: string | null;
24
- }[];
25
- }
26
-
27
- export interface AnalysisUsage {
28
- packageName: string;
29
- commands: AnalysisUsageCommand[];
30
- }
31
-
32
- export interface AnalysisUsageCommand {
33
- name: string;
34
- command: string;
35
- }
36
-
37
- /***
38
- * Complete analysis output used to build the documentation model.
39
- */
40
- export interface AnalysisResult {
41
- packageName: string;
42
- packageId: string;
43
- description: string | null;
44
-
45
- exports: AnalysisExport[];
46
- components: AnalysisComponent[];
47
-
48
- usage: AnalysisUsage | null;
49
-
50
- config: {
51
- exportName: string;
52
- } | null;
53
- }
@@ -1,53 +0,0 @@
1
- import type { AnalysisUsage } from './types.js';
2
-
3
- export interface PackageJsonModel {
4
- name: string;
5
- description?: string;
6
- bin?: string | Record<string, string>;
7
- }
8
-
9
- export function createUsageFromPackageJson(pkg: PackageJsonModel): AnalysisUsage | null {
10
- if (pkg.bin == null) return null;
11
-
12
- if (typeof pkg.bin === 'string') {
13
- return {
14
- packageName: pkg.name,
15
- commands: [
16
- {
17
- name: getPackageBaseName(pkg.name),
18
- command: `bunx ${pkg.name}`,
19
- },
20
- ],
21
- };
22
- }
23
-
24
- const entries = Object.keys(pkg.bin).sort((a, b) => a.localeCompare(b));
25
-
26
- if (entries.length === 0) return null;
27
-
28
- if (entries.length === 1) {
29
- const [name] = entries;
30
-
31
- return {
32
- packageName: pkg.name,
33
- commands: [
34
- {
35
- name,
36
- command: `bunx ${pkg.name}`,
37
- },
38
- ],
39
- };
40
- }
41
-
42
- return {
43
- packageName: pkg.name,
44
- commands: entries.map((name) => ({
45
- name,
46
- command: `bunx ${pkg.name} ${name}`,
47
- })),
48
- };
49
- }
50
-
51
- function getPackageBaseName(packageName: string): string {
52
- return packageName.split('/').pop() ?? packageName;
53
- }
@@ -1,26 +0,0 @@
1
- import { Node, type Type } from 'ts-morph';
2
-
3
- /***
4
- * Returns the first parameter type for a React component declaration.
5
- */
6
- export function getComponentPropsType(node: Node): Type | null {
7
- const callSignature = getCallSignature(node);
8
- const firstParam = callSignature?.getParameters()[0];
9
- const firstDecl = firstParam?.getDeclarations()[0];
10
-
11
- if (firstDecl) return firstParam.getTypeAtLocation(firstDecl);
12
-
13
- return null;
14
- }
15
-
16
- function getCallSignature(node: Node) {
17
- if (Node.isFunctionDeclaration(node)) {
18
- return node.getType().getCallSignatures()[0] ?? null;
19
- }
20
-
21
- if (Node.isVariableDeclaration(node)) {
22
- return node.getType().getCallSignatures()[0] ?? null;
23
- }
24
-
25
- return null;
26
- }
@@ -1,24 +0,0 @@
1
- import type { Node } from 'ts-morph';
2
-
3
- /***
4
- * Reads the nearest Paradox doc comment attached to a declaration.
5
- */
6
- export function getParadoxComment(node: Node): string | null {
7
- const sourceFile = node.getSourceFile();
8
- const text = sourceFile.getFullText();
9
- const nodeStart = node.getStart(false);
10
- const beforeNode = text.slice(0, nodeStart);
11
- const commentStart = beforeNode.lastIndexOf('/***');
12
-
13
- if (commentStart === -1) return null;
14
-
15
- const commentEnd = text.indexOf('*/', commentStart);
16
-
17
- if (commentEnd === -1 || commentEnd > nodeStart) return null;
18
-
19
- const between = text.slice(commentEnd + 2, nodeStart);
20
-
21
- if (!/^[\s;]*(export\s+)?(default\s+)?$/.test(between)) return null;
22
-
23
- return text.slice(commentStart, commentEnd + 2);
24
- }
@@ -1,24 +0,0 @@
1
- import type { Type } from 'ts-morph';
2
-
3
- import type { AnalysisComponent } from '../types.js';
4
- import { getParadoxComment } from './getParadoxComment.js';
5
- import { parseParadoxComment } from './parseParadoxComment.js';
6
-
7
- /***
8
- * Extracts prop names, types, required flags, and descriptions from a type.
9
- */
10
- export function getPropsFromType(type: Type): AnalysisComponent['props'] {
11
- return type.getProperties().map((property) => {
12
- const [declaration] = property.getDeclarations();
13
- const propertyType = property.getTypeAtLocation(declaration);
14
- const rawComment = getParadoxComment(declaration);
15
- const parsed = rawComment ? parseParadoxComment(rawComment) : { description: null };
16
-
17
- return {
18
- name: property.getName(),
19
- type: propertyType.getText(declaration),
20
- required: !property.isOptional(),
21
- description: parsed.description,
22
- };
23
- });
24
- }
@@ -1,35 +0,0 @@
1
- import { Node } from 'ts-morph';
2
-
3
- /***
4
- * Detects simple React component declarations by name and return type.
5
- */
6
- export function isReactComponent(node: Node): boolean {
7
- const name = getDeclarationName(node);
8
-
9
- if (!name || !/^[A-Z]/.test(name)) return false;
10
-
11
- const callSignature = getCallSignature(node);
12
- const returnType = callSignature?.getReturnType().getText() ?? null;
13
-
14
- if (returnType == null) return false;
15
-
16
- return /JSX\.Element|ReactElement|ReactNode|Element/.test(returnType);
17
- }
18
-
19
- function getDeclarationName(node: Node): string | null {
20
- if (Node.isFunctionDeclaration(node)) return node.getName() ?? null;
21
- if (Node.isVariableDeclaration(node)) return node.getName();
22
- return null;
23
- }
24
-
25
- function getCallSignature(node: Node) {
26
- if (Node.isFunctionDeclaration(node)) {
27
- return node.getType().getCallSignatures()[0] ?? null;
28
- }
29
-
30
- if (Node.isVariableDeclaration(node)) {
31
- return node.getType().getCallSignatures()[0] ?? null;
32
- }
33
-
34
- return null;
35
- }
@@ -1,37 +0,0 @@
1
- /***
2
- * Parsed representation of a Paradox doc comment.
3
- */
4
- interface ParsedParadoxComment {
5
- description: string | null;
6
- isConfig: boolean;
7
- }
8
-
9
- /***
10
- * Parses a Paradox doc comment into structured metadata.
11
- */
12
- export function parseParadoxComment(rawComment: string): ParsedParadoxComment {
13
- const lines = rawComment
14
- .replace(/^\/\*\*\*/, '')
15
- .replace(/\*\/$/, '')
16
- .split('\n')
17
- .map((line) => line.replace(/^\s*\*\s?/, '').trimEnd());
18
-
19
- let isConfig = false;
20
-
21
- const description = lines
22
- .filter((line) => {
23
- if (line.trimStart().startsWith('@config')) {
24
- isConfig = true;
25
- return false;
26
- }
27
-
28
- return true;
29
- })
30
- .join('\n')
31
- .trim();
32
-
33
- return {
34
- description: description.length > 0 ? description : null,
35
- isConfig,
36
- };
37
- }
package/src/cli.ts DELETED
@@ -1,30 +0,0 @@
1
- #!/usr/bin/env bun
2
- import { join } from 'node:path';
3
- import { pathToFileURL } from 'node:url';
4
-
5
- import { analyze } from './analyze/analyze.js';
6
- import type { ParadoxConfig } from './config/types.js';
7
- import { buildModel } from './model/buildModel.js';
8
- import { render } from './render/render.js';
9
- import { write } from './write/write.js';
10
-
11
- async function main(): Promise<void> {
12
- const config = await loadConfig(process.cwd());
13
- const analysis = await analyze(config);
14
- const model = buildModel(analysis);
15
- const result = render(model);
16
-
17
- await write(result, config);
18
- }
19
-
20
- async function loadConfig(root: string): Promise<ParadoxConfig> {
21
- const configUrl = pathToFileURL(join(root, 'paradox.config.ts')).href;
22
- const mod = (await import(configUrl)) as { default?: ParadoxConfig };
23
-
24
- return mod.default ?? {};
25
- }
26
-
27
- main().catch((error: unknown) => {
28
- console.error(error);
29
- process.exit(1);
30
- });
@@ -1,22 +0,0 @@
1
- /***
2
- * Configuration for running Paradox.
3
- *
4
- * @config
5
- */
6
- export interface ParadoxConfig {
7
- mode?: 'safe' | 'write';
8
-
9
- docs?: {
10
- title?: string;
11
- description?: string;
12
- };
13
-
14
- package?: {
15
- root?: string;
16
- entrypoints?: string[];
17
- };
18
-
19
- output?: {
20
- dir?: string;
21
- };
22
- }