@ankhorage/paradox 0.0.0
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/.changeset/README.md +5 -0
- package/.changeset/config.json +11 -0
- package/.github/workflows/docs.yml +45 -0
- package/.prettierignore +1 -0
- package/.prettierrc.js +5 -0
- package/CHANGELOG.md +13 -0
- package/LICENSE +21 -0
- package/README.md +49 -0
- package/bun.lock +663 -0
- package/eslint.config.mjs +12 -0
- package/package.json +36 -0
- package/paradox/components.md +1 -0
- package/paradox/exports.json +57 -0
- package/paradox/exports.md +67 -0
- package/paradox/paradox.json +62 -0
- package/paradox.config.ts +18 -0
- package/src/analyze/analyze.ts +46 -0
- package/src/analyze/components.ts +27 -0
- package/src/analyze/exports.ts +68 -0
- package/src/analyze/project.ts +11 -0
- package/src/analyze/types.ts +36 -0
- package/src/analyze/utils/getComponentPropsType.ts +26 -0
- package/src/analyze/utils/getParadoxComment.ts +24 -0
- package/src/analyze/utils/getPropsFromType.ts +24 -0
- package/src/analyze/utils/isReactComponent.ts +35 -0
- package/src/analyze/utils/parseParadoxComment.ts +23 -0
- package/src/analyze/utils/resolveExportSymbol.ts +8 -0
- package/src/cli.ts +30 -0
- package/src/config/defineParadoxConfig.ts +8 -0
- package/src/config/types.ts +20 -0
- package/src/index.ts +9 -0
- package/src/model/buildModel.ts +25 -0
- package/src/model/types.ts +19 -0
- package/src/render/render.ts +82 -0
- package/src/render/types.ts +10 -0
- package/src/write/write.ts +25 -0
- package/tests/__snapshots__/basic.components.md +10 -0
- package/tests/__snapshots__/basic.exports.md +13 -0
- package/tests/__snapshots__/basic.readme.md +13 -0
- package/tests/analyze.test.ts +60 -0
- package/tests/fixtures/basic/package.json +5 -0
- package/tests/fixtures/basic/src/index.ts +2 -0
- package/tests/fixtures/basic/src/internal.ts +6 -0
- package/tests/fixtures/basic/src/ui.ts +29 -0
- package/tests/fixtures/basic/tsconfig.json +11 -0
- package/tsconfig.eslint.json +16 -0
- package/tsconfig.json +14 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { AnalysisResult } from '../analyze/types.js';
|
|
2
|
+
import type { DocumentationModel } from './types.js';
|
|
3
|
+
|
|
4
|
+
/***
|
|
5
|
+
* Converts analysis output into a serializable documentation model.
|
|
6
|
+
*/
|
|
7
|
+
export function buildModel(analysis: AnalysisResult): DocumentationModel {
|
|
8
|
+
const exportsByName = new Map(
|
|
9
|
+
analysis.exports.map((item) => [
|
|
10
|
+
item.name,
|
|
11
|
+
{
|
|
12
|
+
name: item.name,
|
|
13
|
+
description: item.description,
|
|
14
|
+
kind: item.kind,
|
|
15
|
+
},
|
|
16
|
+
]),
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
packageName: analysis.packageName,
|
|
21
|
+
description: analysis.description,
|
|
22
|
+
exports: [...exportsByName.values()],
|
|
23
|
+
components: analysis.components,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { AnalysisComponent, AnalysisResult } from '../analyze/types.js';
|
|
2
|
+
|
|
3
|
+
/***
|
|
4
|
+
* Serializable model consumed by renderers and writers.
|
|
5
|
+
*/
|
|
6
|
+
export interface DocumentationModel {
|
|
7
|
+
packageName: string;
|
|
8
|
+
description: string | null;
|
|
9
|
+
exports: {
|
|
10
|
+
name: string;
|
|
11
|
+
description: string | null;
|
|
12
|
+
kind: string;
|
|
13
|
+
}[];
|
|
14
|
+
components: AnalysisComponent[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type SerializableAnalysisResult = Omit<AnalysisResult, 'exports'> & {
|
|
18
|
+
exports: DocumentationModel['exports'];
|
|
19
|
+
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { DocumentationModel } from '../model/types.js';
|
|
2
|
+
import type { RenderResult } from './types.js';
|
|
3
|
+
|
|
4
|
+
/***
|
|
5
|
+
* Renders the documentation model into README and artifact files.
|
|
6
|
+
*/
|
|
7
|
+
export function render(model: DocumentationModel): RenderResult {
|
|
8
|
+
return {
|
|
9
|
+
readme: renderReadme(model),
|
|
10
|
+
exportsMarkdown: renderExports(model),
|
|
11
|
+
components: renderComponents(model),
|
|
12
|
+
exportsJson: `${JSON.stringify(model.exports, null, 2)}\n`,
|
|
13
|
+
paradoxJson: `${JSON.stringify(model, null, 2)}\n`,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function renderReadme(model: DocumentationModel): string {
|
|
18
|
+
const lines = [`# ${model.packageName}`, ''];
|
|
19
|
+
|
|
20
|
+
if (model.description) {
|
|
21
|
+
lines.push(model.description, '');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (model.exports.length > 0) {
|
|
25
|
+
lines.push('## Package Exports', '');
|
|
26
|
+
|
|
27
|
+
for (const item of model.exports) {
|
|
28
|
+
lines.push(`### ${item.name}`, '');
|
|
29
|
+
lines.push(item.description ?? `\`${item.kind}\` export.`, '');
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return `${lines.join('\n').trimEnd()}\n`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function renderExports(model: DocumentationModel): string {
|
|
37
|
+
const lines = ['# Package Exports', ''];
|
|
38
|
+
|
|
39
|
+
for (const item of model.exports) {
|
|
40
|
+
lines.push(`## ${item.name}`, '');
|
|
41
|
+
lines.push(`Kind: \`${item.kind}\``, '');
|
|
42
|
+
|
|
43
|
+
if (item.description) {
|
|
44
|
+
lines.push(item.description, '');
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return `${lines.join('\n').trimEnd()}\n`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function renderComponents(model: DocumentationModel): string {
|
|
52
|
+
const lines = ['# Components', ''];
|
|
53
|
+
|
|
54
|
+
for (const component of model.components) {
|
|
55
|
+
lines.push(`## ${component.name}`, '');
|
|
56
|
+
|
|
57
|
+
if (component.description) {
|
|
58
|
+
lines.push(component.description, '');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (component.props.length > 0) {
|
|
62
|
+
lines.push('| Prop | Type | Required | Description |');
|
|
63
|
+
lines.push('| --- | --- | --- | --- |');
|
|
64
|
+
|
|
65
|
+
for (const prop of component.props) {
|
|
66
|
+
lines.push(
|
|
67
|
+
`| ${escapeTableCell(prop.name)} | \`${escapeTableCell(prop.type)}\` | ${
|
|
68
|
+
prop.required ? 'yes' : 'no'
|
|
69
|
+
} | ${escapeTableCell(prop.description ?? '')} |`,
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
lines.push('');
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return `${lines.join('\n').trimEnd()}\n`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function escapeTableCell(value: string): string {
|
|
81
|
+
return value.replaceAll('|', '\\|');
|
|
82
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { mkdir, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
|
|
4
|
+
import type { ParadoxConfig } from '../config/types.js';
|
|
5
|
+
import type { RenderResult } from '../render/types.js';
|
|
6
|
+
|
|
7
|
+
/***
|
|
8
|
+
* Writes generated documentation artifacts to the configured output paths.
|
|
9
|
+
*/
|
|
10
|
+
export async function write(result: RenderResult, config: ParadoxConfig): Promise<void> {
|
|
11
|
+
const root = config.package?.root ?? process.cwd();
|
|
12
|
+
const outputDir = config.output?.dir ?? 'paradox';
|
|
13
|
+
const mode = config.mode ?? 'safe';
|
|
14
|
+
|
|
15
|
+
await mkdir(join(root, outputDir), { recursive: true });
|
|
16
|
+
|
|
17
|
+
await writeFile(join(root, outputDir, 'exports.md'), result.exportsMarkdown);
|
|
18
|
+
await writeFile(join(root, outputDir, 'components.md'), result.components);
|
|
19
|
+
await writeFile(join(root, outputDir, 'exports.json'), result.exportsJson);
|
|
20
|
+
await writeFile(join(root, outputDir, 'paradox.json'), result.paradoxJson);
|
|
21
|
+
|
|
22
|
+
if (mode === 'write') {
|
|
23
|
+
await writeFile(join(root, 'README.md'), result.readme);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Components
|
|
2
|
+
|
|
3
|
+
## Button
|
|
4
|
+
|
|
5
|
+
Renders the fixture button component.
|
|
6
|
+
|
|
7
|
+
| Prop | Type | Required | Description |
|
|
8
|
+
| --- | --- | --- | --- |
|
|
9
|
+
| label | `string` | yes | Visible button label. |
|
|
10
|
+
| disabled | `boolean \| undefined` | no | Optional disabled state. |
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
|
|
4
|
+
import { describe, expect, test } from 'bun:test';
|
|
5
|
+
|
|
6
|
+
import { analyze, buildModel, render } from '../src/index.js';
|
|
7
|
+
|
|
8
|
+
const fixtureRoot = join(import.meta.dir, 'fixtures/basic');
|
|
9
|
+
const snapshotRoot = join(import.meta.dir, '__snapshots__');
|
|
10
|
+
|
|
11
|
+
describe('analyze', () => {
|
|
12
|
+
test('builds documentation from package entrypoints', async () => {
|
|
13
|
+
const analysis = await analyze({
|
|
14
|
+
docs: {
|
|
15
|
+
title: 'Fixture Docs',
|
|
16
|
+
description: 'Generated fixture docs.',
|
|
17
|
+
},
|
|
18
|
+
package: {
|
|
19
|
+
root: fixtureRoot,
|
|
20
|
+
entrypoints: ['src/index.ts'],
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
expect(analysis.exports.map((item) => item.name)).toEqual(['Button', 'ButtonProps']);
|
|
25
|
+
expect(analysis.exports.map((item) => item.name)).not.toContain('internalHelper');
|
|
26
|
+
|
|
27
|
+
expect(analysis.components).toEqual([
|
|
28
|
+
{
|
|
29
|
+
name: 'Button',
|
|
30
|
+
description: 'Renders the fixture button component.',
|
|
31
|
+
props: [
|
|
32
|
+
{
|
|
33
|
+
name: 'label',
|
|
34
|
+
type: 'string',
|
|
35
|
+
required: true,
|
|
36
|
+
description: 'Visible button label.',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: 'disabled',
|
|
40
|
+
type: 'boolean | undefined',
|
|
41
|
+
required: false,
|
|
42
|
+
description: 'Optional disabled state.',
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
},
|
|
46
|
+
]);
|
|
47
|
+
|
|
48
|
+
const output = render(buildModel(analysis));
|
|
49
|
+
|
|
50
|
+
await expectSnapshot('basic.readme.md', output.readme);
|
|
51
|
+
await expectSnapshot('basic.exports.md', output.exportsMarkdown);
|
|
52
|
+
await expectSnapshot('basic.components.md', output.components);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
async function expectSnapshot(name: string, actual: string): Promise<void> {
|
|
57
|
+
const expected = await readFile(join(snapshotRoot, name), 'utf-8');
|
|
58
|
+
|
|
59
|
+
expect(actual.trimEnd()).toBe(expected.trimEnd());
|
|
60
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
declare namespace JSX {
|
|
2
|
+
interface Element {
|
|
3
|
+
readonly type: string;
|
|
4
|
+
}
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/***
|
|
8
|
+
* Props accepted by the fixture button.
|
|
9
|
+
*/
|
|
10
|
+
export interface ButtonProps {
|
|
11
|
+
/***
|
|
12
|
+
* Visible button label.
|
|
13
|
+
*/
|
|
14
|
+
label: string;
|
|
15
|
+
|
|
16
|
+
/***
|
|
17
|
+
* Optional disabled state.
|
|
18
|
+
*/
|
|
19
|
+
disabled?: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/***
|
|
23
|
+
* Renders the fixture button component.
|
|
24
|
+
*/
|
|
25
|
+
export function Button(props: ButtonProps): JSX.Element {
|
|
26
|
+
return {
|
|
27
|
+
type: props.disabled ? 'disabled-button' : 'button',
|
|
28
|
+
};
|
|
29
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json.schemastore.org/tsconfig",
|
|
3
|
+
"extends": "./tsconfig.json",
|
|
4
|
+
"compilerOptions": {
|
|
5
|
+
"rootDir": ".",
|
|
6
|
+
"noEmit": true
|
|
7
|
+
},
|
|
8
|
+
"include": [
|
|
9
|
+
"src/**/*.ts",
|
|
10
|
+
"tests/**/*.ts",
|
|
11
|
+
"paradox.config.ts",
|
|
12
|
+
"eslint.config.mjs",
|
|
13
|
+
".prettierrc.js"
|
|
14
|
+
],
|
|
15
|
+
"exclude": ["dist", "node_modules"]
|
|
16
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"esModuleInterop": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"forceConsistentCasingInFileNames": true,
|
|
10
|
+
"noEmit": true,
|
|
11
|
+
"types": ["bun-types"]
|
|
12
|
+
},
|
|
13
|
+
"include": ["src/**/*.ts"]
|
|
14
|
+
}
|