types-not-docs 0.1.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/README.md +88 -0
- package/bin/types-not-docs +2 -0
- package/dist/cli.d.ts +6 -0
- package/dist/cli.js +133 -0
- package/dist/generator.d.ts +13 -0
- package/dist/generator.js +284 -0
- package/dist/parser.d.ts +9 -0
- package/dist/parser.js +222 -0
- package/dist/types.d.ts +89 -0
- package/dist/types.js +6 -0
- package/package.json +46 -0
package/README.md
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# types-not-docs
|
|
2
|
+
|
|
3
|
+
Generate markdown documentation from TypeScript types. One source of truth, docs that stay in sync.
|
|
4
|
+
|
|
5
|
+
Inspired by [Ship types, not docs](https://shiptypes.com) by Boris Tane.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g types-not-docs
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Or run directly with npx:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npx types-not-docs "./src/**/*.ts" -o docs.md
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# Generate docs for all TypeScript files in src/
|
|
23
|
+
types-not-docs "./src/**/*.ts" -o docs.md
|
|
24
|
+
|
|
25
|
+
# With a custom title
|
|
26
|
+
types-not-docs "./src/**/*.ts" -o docs.md -t "My SDK Reference"
|
|
27
|
+
|
|
28
|
+
# Exclude additional patterns
|
|
29
|
+
types-not-docs "./src/**/*.ts" -o docs.md --exclude "**/__mocks__/**"
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Options
|
|
33
|
+
|
|
34
|
+
| Option | Description | Default |
|
|
35
|
+
|--------|-------------|---------|
|
|
36
|
+
| `<glob>` | Glob pattern for TypeScript files | (required) |
|
|
37
|
+
| `-o, --output <file>` | Output file | stdout |
|
|
38
|
+
| `-t, --title <title>` | Document title | "API Reference" |
|
|
39
|
+
| `--exclude <patterns...>` | Glob patterns to exclude | node_modules, tests, dist |
|
|
40
|
+
|
|
41
|
+
## What it extracts
|
|
42
|
+
|
|
43
|
+
- **Interfaces** - Properties, types, required/optional, JSDoc descriptions
|
|
44
|
+
- **Type aliases** - Union types, generics, etc.
|
|
45
|
+
- **Functions** - Parameters, return types, async, JSDoc with @param tags
|
|
46
|
+
- **Arrow functions** - `export const fn = () => {}` syntax
|
|
47
|
+
|
|
48
|
+
## Example output
|
|
49
|
+
|
|
50
|
+
```markdown
|
|
51
|
+
## TokenMetadata
|
|
52
|
+
|
|
53
|
+
Token metadata information.
|
|
54
|
+
|
|
55
|
+
| Property | Type | Required | Description |
|
|
56
|
+
|----------|------|----------|-------------|
|
|
57
|
+
| name | `string` | | The token's display name |
|
|
58
|
+
| symbol | `string` | | The token's ticker symbol |
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## getTokenMetadata()
|
|
63
|
+
|
|
64
|
+
Fetches token metadata from the chain.
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
async function getTokenMetadata(rpc: RpcClient, mint: Address): Promise<TokenMetadata>
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**Parameters:**
|
|
71
|
+
|
|
72
|
+
| Name | Type | Required | Description |
|
|
73
|
+
|------|------|----------|-------------|
|
|
74
|
+
| rpc | `RpcClient` | ✓ | The RPC client instance |
|
|
75
|
+
| mint | `Address` | ✓ | The mint address to inspect |
|
|
76
|
+
|
|
77
|
+
**Returns:** `Promise<TokenMetadata>`
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Why?
|
|
81
|
+
|
|
82
|
+
Documentation drifts from code. It's a lossy copy that inevitably falls out of sync.
|
|
83
|
+
|
|
84
|
+
Types are the contract. By generating docs from types, they stay in sync automatically. This is especially valuable for AI agents that need to understand your SDK to use it.
|
|
85
|
+
|
|
86
|
+
## License
|
|
87
|
+
|
|
88
|
+
MIT
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
/**
|
|
4
|
+
* CLI entry point for types-not-docs.
|
|
5
|
+
* Parses TypeScript files and generates markdown documentation.
|
|
6
|
+
*/
|
|
7
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
8
|
+
if (k2 === undefined) k2 = k;
|
|
9
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
10
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
11
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
12
|
+
}
|
|
13
|
+
Object.defineProperty(o, k2, desc);
|
|
14
|
+
}) : (function(o, m, k, k2) {
|
|
15
|
+
if (k2 === undefined) k2 = k;
|
|
16
|
+
o[k2] = m[k];
|
|
17
|
+
}));
|
|
18
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
19
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
20
|
+
}) : function(o, v) {
|
|
21
|
+
o["default"] = v;
|
|
22
|
+
});
|
|
23
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
24
|
+
var ownKeys = function(o) {
|
|
25
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
26
|
+
var ar = [];
|
|
27
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
28
|
+
return ar;
|
|
29
|
+
};
|
|
30
|
+
return ownKeys(o);
|
|
31
|
+
};
|
|
32
|
+
return function (mod) {
|
|
33
|
+
if (mod && mod.__esModule) return mod;
|
|
34
|
+
var result = {};
|
|
35
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
36
|
+
__setModuleDefault(result, mod);
|
|
37
|
+
return result;
|
|
38
|
+
};
|
|
39
|
+
})();
|
|
40
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
41
|
+
const commander_1 = require("commander");
|
|
42
|
+
const glob_1 = require("glob");
|
|
43
|
+
const fs = __importStar(require("fs"));
|
|
44
|
+
const path = __importStar(require("path"));
|
|
45
|
+
const parser_1 = require("./parser");
|
|
46
|
+
const generator_1 = require("./generator");
|
|
47
|
+
// Get version from package.json
|
|
48
|
+
const packageJson = JSON.parse(fs.readFileSync(path.join(__dirname, "../package.json"), "utf-8"));
|
|
49
|
+
commander_1.program
|
|
50
|
+
.name("types-not-docs")
|
|
51
|
+
.description("Generate markdown documentation from TypeScript types")
|
|
52
|
+
.version(packageJson.version)
|
|
53
|
+
.argument("<glob>", "Glob pattern for TypeScript files (e.g., ./src/**/*.ts)")
|
|
54
|
+
.option("-o, --output <file>", "Output file (defaults to stdout)")
|
|
55
|
+
.option("-t, --title <title>", "Document title", "API Reference")
|
|
56
|
+
.option("--exclude <patterns...>", "Glob patterns to exclude", [
|
|
57
|
+
"**/node_modules/**",
|
|
58
|
+
"**/*.test.ts",
|
|
59
|
+
"**/*.spec.ts",
|
|
60
|
+
"**/dist/**",
|
|
61
|
+
])
|
|
62
|
+
.addHelpText("after", `
|
|
63
|
+
Examples:
|
|
64
|
+
$ types-not-docs "./src/**/*.ts"
|
|
65
|
+
$ types-not-docs "./src/**/*.ts" -o docs.md
|
|
66
|
+
$ types-not-docs "./src/**/*.ts" -o docs.md -t "My SDK Reference"
|
|
67
|
+
$ types-not-docs "./src/**/*.ts" --exclude "**/__tests__/**"
|
|
68
|
+
`)
|
|
69
|
+
.action(async (globPattern, options) => {
|
|
70
|
+
try {
|
|
71
|
+
await run(globPattern, options);
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
console.error("Error:", error.message);
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
commander_1.program.configureOutput({
|
|
79
|
+
outputError: (str, write) => {
|
|
80
|
+
write(str);
|
|
81
|
+
if (str.includes("too many arguments")) {
|
|
82
|
+
write("\nHint: Did you forget -t before the title? Use: -t \"My Title\"\n");
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
commander_1.program.parse();
|
|
87
|
+
async function run(globPattern, options) {
|
|
88
|
+
// Find files matching the glob pattern
|
|
89
|
+
const files = await (0, glob_1.glob)(globPattern, {
|
|
90
|
+
ignore: options.exclude,
|
|
91
|
+
absolute: true,
|
|
92
|
+
});
|
|
93
|
+
if (files.length === 0) {
|
|
94
|
+
throw new Error(`No files found matching pattern: ${globPattern}`);
|
|
95
|
+
}
|
|
96
|
+
console.error(`Found ${files.length} file(s)`);
|
|
97
|
+
// Parse all files
|
|
98
|
+
const parsedFiles = [];
|
|
99
|
+
let totalInterfaces = 0;
|
|
100
|
+
let totalTypes = 0;
|
|
101
|
+
let totalFunctions = 0;
|
|
102
|
+
for (const file of files) {
|
|
103
|
+
try {
|
|
104
|
+
const parsed = (0, parser_1.parseFile)(file);
|
|
105
|
+
// Only include files that have exported types
|
|
106
|
+
if (parsed.interfaces.length > 0 ||
|
|
107
|
+
parsed.typeAliases.length > 0 ||
|
|
108
|
+
parsed.functions.length > 0) {
|
|
109
|
+
parsedFiles.push(parsed);
|
|
110
|
+
totalInterfaces += parsed.interfaces.length;
|
|
111
|
+
totalTypes += parsed.typeAliases.length;
|
|
112
|
+
totalFunctions += parsed.functions.length;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
console.error(`Warning: Failed to parse ${file}: ${error.message}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (parsedFiles.length === 0) {
|
|
120
|
+
throw new Error("No exported types found in any files");
|
|
121
|
+
}
|
|
122
|
+
console.error(`Parsed ${parsedFiles.length} file(s): ${totalInterfaces} interfaces, ${totalTypes} types, ${totalFunctions} functions`);
|
|
123
|
+
// Generate markdown
|
|
124
|
+
const markdown = (0, generator_1.generateMarkdown)(parsedFiles, { title: options.title });
|
|
125
|
+
// Output
|
|
126
|
+
if (options.output) {
|
|
127
|
+
fs.writeFileSync(options.output, markdown, "utf-8");
|
|
128
|
+
console.error(`Wrote documentation to ${options.output}`);
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
console.log(markdown);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Markdown generator for parsed TypeScript types.
|
|
3
|
+
* Generates API reference-style documentation with tables.
|
|
4
|
+
*/
|
|
5
|
+
import type { ParsedFile } from "./types";
|
|
6
|
+
export interface GeneratorOptions {
|
|
7
|
+
/** Title for the document */
|
|
8
|
+
title?: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Generate markdown documentation from parsed files.
|
|
12
|
+
*/
|
|
13
|
+
export declare function generateMarkdown(files: ParsedFile[], options?: GeneratorOptions): string;
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Markdown generator for parsed TypeScript types.
|
|
4
|
+
* Generates API reference-style documentation with tables.
|
|
5
|
+
*/
|
|
6
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
|
+
if (k2 === undefined) k2 = k;
|
|
8
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
9
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
10
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
11
|
+
}
|
|
12
|
+
Object.defineProperty(o, k2, desc);
|
|
13
|
+
}) : (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
o[k2] = m[k];
|
|
16
|
+
}));
|
|
17
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
18
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
19
|
+
}) : function(o, v) {
|
|
20
|
+
o["default"] = v;
|
|
21
|
+
});
|
|
22
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
23
|
+
var ownKeys = function(o) {
|
|
24
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
25
|
+
var ar = [];
|
|
26
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
27
|
+
return ar;
|
|
28
|
+
};
|
|
29
|
+
return ownKeys(o);
|
|
30
|
+
};
|
|
31
|
+
return function (mod) {
|
|
32
|
+
if (mod && mod.__esModule) return mod;
|
|
33
|
+
var result = {};
|
|
34
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
35
|
+
__setModuleDefault(result, mod);
|
|
36
|
+
return result;
|
|
37
|
+
};
|
|
38
|
+
})();
|
|
39
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
exports.generateMarkdown = generateMarkdown;
|
|
41
|
+
const path = __importStar(require("path"));
|
|
42
|
+
/**
|
|
43
|
+
* Generate markdown documentation from parsed files.
|
|
44
|
+
*/
|
|
45
|
+
function generateMarkdown(files, options = {}) {
|
|
46
|
+
const { title = "API Reference" } = options;
|
|
47
|
+
const lines = [];
|
|
48
|
+
// Collect all items with their source files
|
|
49
|
+
const allInterfaces = [];
|
|
50
|
+
const allTypeAliases = [];
|
|
51
|
+
const allFunctions = [];
|
|
52
|
+
for (const file of files) {
|
|
53
|
+
const sourceFile = path.basename(file.filePath);
|
|
54
|
+
for (const iface of file.interfaces) {
|
|
55
|
+
allInterfaces.push({ item: iface, sourceFile });
|
|
56
|
+
}
|
|
57
|
+
for (const alias of file.typeAliases) {
|
|
58
|
+
allTypeAliases.push({ item: alias, sourceFile });
|
|
59
|
+
}
|
|
60
|
+
for (const fn of file.functions) {
|
|
61
|
+
allFunctions.push({ item: fn, sourceFile });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
// Detect duplicate names
|
|
65
|
+
const duplicateNames = findDuplicateNames([
|
|
66
|
+
...allInterfaces.map((i) => i.item.name),
|
|
67
|
+
...allTypeAliases.map((t) => t.item.name),
|
|
68
|
+
...allFunctions.map((f) => f.item.name),
|
|
69
|
+
]);
|
|
70
|
+
// Title
|
|
71
|
+
lines.push(`# ${title}`);
|
|
72
|
+
lines.push("");
|
|
73
|
+
// Table of Contents
|
|
74
|
+
if (allInterfaces.length + allTypeAliases.length + allFunctions.length > 0) {
|
|
75
|
+
lines.push("## Table of Contents");
|
|
76
|
+
lines.push("");
|
|
77
|
+
if (allInterfaces.length > 0) {
|
|
78
|
+
lines.push("### Interfaces");
|
|
79
|
+
lines.push("");
|
|
80
|
+
for (const { item, sourceFile } of allInterfaces) {
|
|
81
|
+
const displayName = getDisplayName(item.name, sourceFile, duplicateNames);
|
|
82
|
+
const anchor = getAnchor(item.name, sourceFile, duplicateNames);
|
|
83
|
+
lines.push(`- [${displayName}](#${anchor})`);
|
|
84
|
+
}
|
|
85
|
+
lines.push("");
|
|
86
|
+
}
|
|
87
|
+
if (allTypeAliases.length > 0) {
|
|
88
|
+
lines.push("### Types");
|
|
89
|
+
lines.push("");
|
|
90
|
+
for (const { item, sourceFile } of allTypeAliases) {
|
|
91
|
+
const displayName = getDisplayName(item.name, sourceFile, duplicateNames);
|
|
92
|
+
const anchor = getAnchor(item.name, sourceFile, duplicateNames);
|
|
93
|
+
lines.push(`- [${displayName}](#${anchor})`);
|
|
94
|
+
}
|
|
95
|
+
lines.push("");
|
|
96
|
+
}
|
|
97
|
+
if (allFunctions.length > 0) {
|
|
98
|
+
lines.push("### Functions");
|
|
99
|
+
lines.push("");
|
|
100
|
+
for (const { item, sourceFile } of allFunctions) {
|
|
101
|
+
const displayName = getDisplayName(item.name, sourceFile, duplicateNames);
|
|
102
|
+
const anchor = getAnchor(item.name, sourceFile, duplicateNames);
|
|
103
|
+
lines.push(`- [${displayName}()](#${anchor})`);
|
|
104
|
+
}
|
|
105
|
+
lines.push("");
|
|
106
|
+
}
|
|
107
|
+
lines.push("---");
|
|
108
|
+
lines.push("");
|
|
109
|
+
}
|
|
110
|
+
// Interfaces
|
|
111
|
+
for (const { item, sourceFile } of allInterfaces) {
|
|
112
|
+
lines.push(...generateInterface(item, sourceFile, duplicateNames));
|
|
113
|
+
lines.push("");
|
|
114
|
+
}
|
|
115
|
+
// Type Aliases
|
|
116
|
+
for (const { item, sourceFile } of allTypeAliases) {
|
|
117
|
+
lines.push(...generateTypeAlias(item, sourceFile, duplicateNames));
|
|
118
|
+
lines.push("");
|
|
119
|
+
}
|
|
120
|
+
// Functions
|
|
121
|
+
for (const { item, sourceFile } of allFunctions) {
|
|
122
|
+
lines.push(...generateFunction(item, sourceFile, duplicateNames));
|
|
123
|
+
lines.push("");
|
|
124
|
+
}
|
|
125
|
+
return lines.join("\n");
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Find names that appear more than once.
|
|
129
|
+
*/
|
|
130
|
+
function findDuplicateNames(names) {
|
|
131
|
+
const seen = new Set();
|
|
132
|
+
const duplicates = new Set();
|
|
133
|
+
for (const name of names) {
|
|
134
|
+
if (seen.has(name)) {
|
|
135
|
+
duplicates.add(name);
|
|
136
|
+
}
|
|
137
|
+
seen.add(name);
|
|
138
|
+
}
|
|
139
|
+
return duplicates;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Get display name, adding source file suffix for duplicates.
|
|
143
|
+
*/
|
|
144
|
+
function getDisplayName(name, sourceFile, duplicates) {
|
|
145
|
+
if (duplicates.has(name)) {
|
|
146
|
+
return `${name} (${sourceFile})`;
|
|
147
|
+
}
|
|
148
|
+
return name;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Get anchor, adding source file suffix for duplicates.
|
|
152
|
+
*/
|
|
153
|
+
function getAnchor(name, sourceFile, duplicates) {
|
|
154
|
+
if (duplicates.has(name)) {
|
|
155
|
+
const combined = `${name}-${sourceFile}`;
|
|
156
|
+
return toAnchor(combined);
|
|
157
|
+
}
|
|
158
|
+
return toAnchor(name);
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Generate markdown for an interface.
|
|
162
|
+
*/
|
|
163
|
+
function generateInterface(iface, sourceFile, duplicates) {
|
|
164
|
+
const lines = [];
|
|
165
|
+
const displayName = getDisplayName(iface.name, sourceFile, duplicates);
|
|
166
|
+
lines.push(`## ${displayName}`);
|
|
167
|
+
lines.push("");
|
|
168
|
+
if (iface.extends && iface.extends.length > 0) {
|
|
169
|
+
lines.push(`*Extends: ${iface.extends.join(", ")}*`);
|
|
170
|
+
lines.push("");
|
|
171
|
+
}
|
|
172
|
+
if (iface.description) {
|
|
173
|
+
lines.push(iface.description);
|
|
174
|
+
lines.push("");
|
|
175
|
+
}
|
|
176
|
+
if (iface.properties.length > 0) {
|
|
177
|
+
lines.push(...generatePropertiesTable(iface.properties));
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
lines.push("*No properties*");
|
|
181
|
+
}
|
|
182
|
+
lines.push("");
|
|
183
|
+
lines.push("---");
|
|
184
|
+
return lines;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Generate a properties table.
|
|
188
|
+
*/
|
|
189
|
+
function generatePropertiesTable(properties) {
|
|
190
|
+
const lines = [];
|
|
191
|
+
lines.push("| Property | Type | Required | Description |");
|
|
192
|
+
lines.push("|----------|------|----------|-------------|");
|
|
193
|
+
for (const prop of properties) {
|
|
194
|
+
const required = prop.required ? "✓" : "";
|
|
195
|
+
const description = prop.description || "";
|
|
196
|
+
const type = escapeMarkdown(prop.type);
|
|
197
|
+
lines.push(`| ${prop.name} | \`${type}\` | ${required} | ${description} |`);
|
|
198
|
+
}
|
|
199
|
+
return lines;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Generate markdown for a type alias.
|
|
203
|
+
*/
|
|
204
|
+
function generateTypeAlias(alias, sourceFile, duplicates) {
|
|
205
|
+
const lines = [];
|
|
206
|
+
const displayName = getDisplayName(alias.name, sourceFile, duplicates);
|
|
207
|
+
lines.push(`## ${displayName}`);
|
|
208
|
+
lines.push("");
|
|
209
|
+
if (alias.description) {
|
|
210
|
+
lines.push(alias.description);
|
|
211
|
+
lines.push("");
|
|
212
|
+
}
|
|
213
|
+
lines.push("```typescript");
|
|
214
|
+
lines.push(`type ${alias.name} = ${alias.type}`);
|
|
215
|
+
lines.push("```");
|
|
216
|
+
lines.push("");
|
|
217
|
+
lines.push("---");
|
|
218
|
+
return lines;
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Generate markdown for a function.
|
|
222
|
+
*/
|
|
223
|
+
function generateFunction(fn, sourceFile, duplicates) {
|
|
224
|
+
const lines = [];
|
|
225
|
+
const displayName = getDisplayName(fn.name, sourceFile, duplicates);
|
|
226
|
+
const asyncPrefix = fn.isAsync ? "async " : "";
|
|
227
|
+
lines.push(`## ${displayName}()`);
|
|
228
|
+
lines.push("");
|
|
229
|
+
if (fn.description) {
|
|
230
|
+
lines.push(fn.description);
|
|
231
|
+
lines.push("");
|
|
232
|
+
}
|
|
233
|
+
// Signature
|
|
234
|
+
const params = fn.parameters.map((p) => {
|
|
235
|
+
const opt = p.required ? "" : "?";
|
|
236
|
+
return `${p.name}${opt}: ${p.type}`;
|
|
237
|
+
});
|
|
238
|
+
const signature = `${asyncPrefix}function ${fn.name}(${params.join(", ")}): ${fn.returnType}`;
|
|
239
|
+
lines.push("```typescript");
|
|
240
|
+
lines.push(signature);
|
|
241
|
+
lines.push("```");
|
|
242
|
+
lines.push("");
|
|
243
|
+
// Parameters table
|
|
244
|
+
if (fn.parameters.length > 0) {
|
|
245
|
+
lines.push("**Parameters:**");
|
|
246
|
+
lines.push("");
|
|
247
|
+
lines.push(...generateParametersTable(fn.parameters));
|
|
248
|
+
lines.push("");
|
|
249
|
+
}
|
|
250
|
+
// Return type
|
|
251
|
+
lines.push(`**Returns:** \`${escapeMarkdown(fn.returnType)}\``);
|
|
252
|
+
lines.push("");
|
|
253
|
+
lines.push("---");
|
|
254
|
+
return lines;
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Generate a parameters table.
|
|
258
|
+
*/
|
|
259
|
+
function generateParametersTable(parameters) {
|
|
260
|
+
const lines = [];
|
|
261
|
+
lines.push("| Name | Type | Required | Description |");
|
|
262
|
+
lines.push("|------|------|----------|-------------|");
|
|
263
|
+
for (const param of parameters) {
|
|
264
|
+
const required = param.required ? "✓" : "";
|
|
265
|
+
const description = param.description || "";
|
|
266
|
+
const type = escapeMarkdown(param.type);
|
|
267
|
+
lines.push(`| ${param.name} | \`${type}\` | ${required} | ${description} |`);
|
|
268
|
+
}
|
|
269
|
+
return lines;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Convert a name to a markdown anchor.
|
|
273
|
+
*/
|
|
274
|
+
function toAnchor(name) {
|
|
275
|
+
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-");
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Escape markdown special characters in type strings.
|
|
279
|
+
* For now, just handle pipe characters in union types.
|
|
280
|
+
*/
|
|
281
|
+
function escapeMarkdown(text) {
|
|
282
|
+
// Escape pipe characters for table cells
|
|
283
|
+
return text.replace(/\|/g, "\\|");
|
|
284
|
+
}
|
package/dist/parser.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TypeScript AST parser using ts-morph.
|
|
3
|
+
* Extracts interfaces, type aliases, and functions from source files.
|
|
4
|
+
*/
|
|
5
|
+
import type { ParsedFile } from "./types";
|
|
6
|
+
/**
|
|
7
|
+
* Parse a TypeScript file and extract all exported types.
|
|
8
|
+
*/
|
|
9
|
+
export declare function parseFile(filePath: string): ParsedFile;
|
package/dist/parser.js
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* TypeScript AST parser using ts-morph.
|
|
4
|
+
* Extracts interfaces, type aliases, and functions from source files.
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.parseFile = parseFile;
|
|
8
|
+
const ts_morph_1 = require("ts-morph");
|
|
9
|
+
/**
|
|
10
|
+
* Parse a TypeScript file and extract all exported types.
|
|
11
|
+
*/
|
|
12
|
+
function parseFile(filePath) {
|
|
13
|
+
const project = new ts_morph_1.Project({
|
|
14
|
+
skipAddingFilesFromTsConfig: true,
|
|
15
|
+
});
|
|
16
|
+
const sourceFile = project.addSourceFileAtPath(filePath);
|
|
17
|
+
return {
|
|
18
|
+
filePath,
|
|
19
|
+
interfaces: parseInterfaces(sourceFile),
|
|
20
|
+
typeAliases: parseTypeAliases(sourceFile),
|
|
21
|
+
functions: parseFunctions(sourceFile),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
// =============================================================================
|
|
25
|
+
// Interface Parsing
|
|
26
|
+
// =============================================================================
|
|
27
|
+
/**
|
|
28
|
+
* Parse all exported interfaces from a source file.
|
|
29
|
+
*/
|
|
30
|
+
function parseInterfaces(sourceFile) {
|
|
31
|
+
return sourceFile
|
|
32
|
+
.getInterfaces()
|
|
33
|
+
.filter((iface) => iface.isExported())
|
|
34
|
+
.map((iface) => ({
|
|
35
|
+
kind: "interface",
|
|
36
|
+
name: iface.getName(),
|
|
37
|
+
description: getJsDocDescription(iface),
|
|
38
|
+
properties: parseProperties(iface),
|
|
39
|
+
extends: getExtendsClause(iface),
|
|
40
|
+
}));
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Parse all properties from an interface, including method signatures.
|
|
44
|
+
*/
|
|
45
|
+
function parseProperties(iface) {
|
|
46
|
+
const properties = [];
|
|
47
|
+
// Regular properties
|
|
48
|
+
for (const prop of iface.getProperties()) {
|
|
49
|
+
properties.push({
|
|
50
|
+
name: prop.getName(),
|
|
51
|
+
type: prop.getType().getText(prop),
|
|
52
|
+
required: !prop.hasQuestionToken(),
|
|
53
|
+
description: getJsDocDescription(prop),
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
// Method signatures (treated as properties with function type)
|
|
57
|
+
for (const method of iface.getMethods()) {
|
|
58
|
+
const params = method
|
|
59
|
+
.getParameters()
|
|
60
|
+
.map((p) => `${p.getName()}: ${p.getType().getText(p)}`)
|
|
61
|
+
.join(", ");
|
|
62
|
+
const returnType = method.getReturnType().getText(method);
|
|
63
|
+
const funcType = `(${params}) => ${returnType}`;
|
|
64
|
+
properties.push({
|
|
65
|
+
name: method.getName(),
|
|
66
|
+
type: funcType,
|
|
67
|
+
required: !method.hasQuestionToken(),
|
|
68
|
+
description: getJsDocDescription(method),
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
return properties;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Get the names of interfaces that this interface extends.
|
|
75
|
+
*/
|
|
76
|
+
function getExtendsClause(iface) {
|
|
77
|
+
const extendsClauses = iface.getExtends();
|
|
78
|
+
if (extendsClauses.length === 0) {
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
return extendsClauses.map((clause) => clause.getText());
|
|
82
|
+
}
|
|
83
|
+
// =============================================================================
|
|
84
|
+
// Type Alias Parsing
|
|
85
|
+
// =============================================================================
|
|
86
|
+
/**
|
|
87
|
+
* Parse all exported type aliases from a source file.
|
|
88
|
+
*/
|
|
89
|
+
function parseTypeAliases(sourceFile) {
|
|
90
|
+
return sourceFile
|
|
91
|
+
.getTypeAliases()
|
|
92
|
+
.filter((alias) => alias.isExported())
|
|
93
|
+
.map((alias) => ({
|
|
94
|
+
kind: "type",
|
|
95
|
+
name: alias.getName(),
|
|
96
|
+
type: alias.getType().getText(alias),
|
|
97
|
+
description: getJsDocDescription(alias),
|
|
98
|
+
}));
|
|
99
|
+
}
|
|
100
|
+
// =============================================================================
|
|
101
|
+
// Function Parsing
|
|
102
|
+
// =============================================================================
|
|
103
|
+
/**
|
|
104
|
+
* Parse all exported functions from a source file.
|
|
105
|
+
* Includes both regular function declarations and arrow functions.
|
|
106
|
+
*/
|
|
107
|
+
function parseFunctions(sourceFile) {
|
|
108
|
+
const functions = [];
|
|
109
|
+
// Regular function declarations: export function foo() {}
|
|
110
|
+
for (const fn of sourceFile.getFunctions()) {
|
|
111
|
+
if (fn.isExported()) {
|
|
112
|
+
functions.push(parseFunctionDeclaration(fn));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
// Arrow functions: export const foo = () => {}
|
|
116
|
+
for (const varDecl of sourceFile.getVariableDeclarations()) {
|
|
117
|
+
const varStatement = varDecl.getVariableStatement();
|
|
118
|
+
if (varStatement?.isExported()) {
|
|
119
|
+
const init = varDecl.getInitializer();
|
|
120
|
+
if (ts_morph_1.Node.isArrowFunction(init)) {
|
|
121
|
+
functions.push(parseArrowFunction(varDecl, init));
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return functions;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Parse a regular function declaration.
|
|
129
|
+
*/
|
|
130
|
+
function parseFunctionDeclaration(fn) {
|
|
131
|
+
const jsDocs = fn.getJsDocs();
|
|
132
|
+
const paramDescriptions = extractParamDescriptions(jsDocs);
|
|
133
|
+
return {
|
|
134
|
+
kind: "function",
|
|
135
|
+
name: fn.getName() || "anonymous",
|
|
136
|
+
description: getJsDocDescription(fn),
|
|
137
|
+
parameters: parseParameters(fn.getParameters(), paramDescriptions),
|
|
138
|
+
returnType: fn.getReturnType().getText(fn),
|
|
139
|
+
isAsync: fn.isAsync(),
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Parse an arrow function assigned to a variable.
|
|
144
|
+
*/
|
|
145
|
+
function parseArrowFunction(varDecl, arrowFn) {
|
|
146
|
+
// JSDoc is on the variable statement, not the arrow function
|
|
147
|
+
const varStatement = varDecl.getVariableStatement();
|
|
148
|
+
const jsDocs = varStatement?.getJsDocs() || [];
|
|
149
|
+
const paramDescriptions = extractParamDescriptions(jsDocs);
|
|
150
|
+
return {
|
|
151
|
+
kind: "function",
|
|
152
|
+
name: varDecl.getName(),
|
|
153
|
+
description: getJsDocDescriptionFromDocs(jsDocs),
|
|
154
|
+
parameters: parseParameters(arrowFn.getParameters(), paramDescriptions),
|
|
155
|
+
returnType: arrowFn.getReturnType().getText(arrowFn),
|
|
156
|
+
isAsync: arrowFn.isAsync(),
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Parse function parameters with their JSDoc descriptions.
|
|
161
|
+
*/
|
|
162
|
+
function parseParameters(params, paramDescriptions) {
|
|
163
|
+
return params.map((param) => ({
|
|
164
|
+
name: param.getName(),
|
|
165
|
+
type: param.getType().getText(param),
|
|
166
|
+
required: !param.isOptional() && !param.hasInitializer(),
|
|
167
|
+
description: paramDescriptions.get(param.getName()),
|
|
168
|
+
}));
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Extract @param descriptions from JSDoc comments.
|
|
172
|
+
* Only handles top-level params (not nested like @param input.authority).
|
|
173
|
+
*/
|
|
174
|
+
function extractParamDescriptions(jsDocs) {
|
|
175
|
+
const descriptions = new Map();
|
|
176
|
+
if (jsDocs.length === 0) {
|
|
177
|
+
return descriptions;
|
|
178
|
+
}
|
|
179
|
+
// Use the last JSDoc - the one immediately preceding the declaration
|
|
180
|
+
const jsDoc = jsDocs[jsDocs.length - 1];
|
|
181
|
+
for (const tag of jsDoc.getTags()) {
|
|
182
|
+
if (tag.getTagName() === "param" && ts_morph_1.Node.isJSDocParameterTag(tag)) {
|
|
183
|
+
const paramName = tag.getName();
|
|
184
|
+
const comment = tag.getCommentText()?.trim();
|
|
185
|
+
// Only store top-level params (no dots like input.authority)
|
|
186
|
+
if (paramName && !paramName.includes(".") && comment) {
|
|
187
|
+
// Remove leading dash if present ("- description" -> "description")
|
|
188
|
+
const description = comment.replace(/^-\s*/, "");
|
|
189
|
+
descriptions.set(paramName, description);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return descriptions;
|
|
194
|
+
}
|
|
195
|
+
// =============================================================================
|
|
196
|
+
// JSDoc Utilities
|
|
197
|
+
// =============================================================================
|
|
198
|
+
/**
|
|
199
|
+
* Extract JSDoc description from a node.
|
|
200
|
+
* Returns undefined if no JSDoc is present.
|
|
201
|
+
*/
|
|
202
|
+
function getJsDocDescription(node) {
|
|
203
|
+
const jsDocs = node.getJsDocs?.();
|
|
204
|
+
return getJsDocDescriptionFromDocs(jsDocs);
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Extract description from JSDoc array.
|
|
208
|
+
* Uses the last JSDoc comment (the one immediately preceding the declaration).
|
|
209
|
+
*/
|
|
210
|
+
function getJsDocDescriptionFromDocs(jsDocs) {
|
|
211
|
+
if (!jsDocs || jsDocs.length === 0) {
|
|
212
|
+
return undefined;
|
|
213
|
+
}
|
|
214
|
+
// Use the last JSDoc - this is the one immediately preceding the declaration.
|
|
215
|
+
// Earlier JSDocs might be file-level comments or comments for previous code.
|
|
216
|
+
const jsDoc = jsDocs[jsDocs.length - 1];
|
|
217
|
+
const description = jsDoc.getDescription?.();
|
|
218
|
+
if (!description || description.trim() === "") {
|
|
219
|
+
return undefined;
|
|
220
|
+
}
|
|
221
|
+
return description.trim();
|
|
222
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal type model for types-not-docs.
|
|
3
|
+
* These types represent the extracted information from TypeScript source files.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* A property of an interface or inline object type.
|
|
7
|
+
*/
|
|
8
|
+
export interface ParsedProperty {
|
|
9
|
+
/** Property name */
|
|
10
|
+
name: string;
|
|
11
|
+
/** Type as a string (e.g., "string", "Address | null", "Promise<Order>") */
|
|
12
|
+
type: string;
|
|
13
|
+
/** Whether the property is required (not optional) */
|
|
14
|
+
required: boolean;
|
|
15
|
+
/** Description from JSDoc comment, if present */
|
|
16
|
+
description?: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* A parsed TypeScript interface.
|
|
20
|
+
*/
|
|
21
|
+
export interface ParsedInterface {
|
|
22
|
+
kind: "interface";
|
|
23
|
+
/** Interface name */
|
|
24
|
+
name: string;
|
|
25
|
+
/** Description from JSDoc comment, if present */
|
|
26
|
+
description?: string;
|
|
27
|
+
/** Interface properties */
|
|
28
|
+
properties: ParsedProperty[];
|
|
29
|
+
/** Names of interfaces this extends, if any */
|
|
30
|
+
extends?: string[];
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* A parsed TypeScript type alias.
|
|
34
|
+
*/
|
|
35
|
+
export interface ParsedTypeAlias {
|
|
36
|
+
kind: "type";
|
|
37
|
+
/** Type alias name */
|
|
38
|
+
name: string;
|
|
39
|
+
/** The type definition as a string (e.g., "'a' | 'b' | 'c'") */
|
|
40
|
+
type: string;
|
|
41
|
+
/** Description from JSDoc comment, if present */
|
|
42
|
+
description?: string;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* A parameter of a function.
|
|
46
|
+
*/
|
|
47
|
+
export interface ParsedParameter {
|
|
48
|
+
/** Parameter name */
|
|
49
|
+
name: string;
|
|
50
|
+
/** Type as a string */
|
|
51
|
+
type: string;
|
|
52
|
+
/** Whether the parameter is required (not optional, no default value) */
|
|
53
|
+
required: boolean;
|
|
54
|
+
/** Description from @param JSDoc tag, if present */
|
|
55
|
+
description?: string;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* A parsed exported function.
|
|
59
|
+
*/
|
|
60
|
+
export interface ParsedFunction {
|
|
61
|
+
kind: "function";
|
|
62
|
+
/** Function name */
|
|
63
|
+
name: string;
|
|
64
|
+
/** Description from JSDoc comment, if present */
|
|
65
|
+
description?: string;
|
|
66
|
+
/** Function parameters */
|
|
67
|
+
parameters: ParsedParameter[];
|
|
68
|
+
/** Return type as a string */
|
|
69
|
+
returnType: string;
|
|
70
|
+
/** Whether the function is async */
|
|
71
|
+
isAsync: boolean;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Union of all parsed type kinds.
|
|
75
|
+
*/
|
|
76
|
+
export type ParsedType = ParsedInterface | ParsedTypeAlias | ParsedFunction;
|
|
77
|
+
/**
|
|
78
|
+
* All extracted types from a single source file.
|
|
79
|
+
*/
|
|
80
|
+
export interface ParsedFile {
|
|
81
|
+
/** File path (relative or absolute) */
|
|
82
|
+
filePath: string;
|
|
83
|
+
/** All exported interfaces */
|
|
84
|
+
interfaces: ParsedInterface[];
|
|
85
|
+
/** All exported type aliases */
|
|
86
|
+
typeAliases: ParsedTypeAlias[];
|
|
87
|
+
/** All exported functions */
|
|
88
|
+
functions: ParsedFunction[];
|
|
89
|
+
}
|
package/dist/types.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "types-not-docs",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Generate markdown documentation from TypeScript types",
|
|
5
|
+
"main": "dist/cli.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"types-not-docs": "./bin/types-not-docs"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"dev": "tsc --watch",
|
|
12
|
+
"prepublishOnly": "npm run build"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"typescript",
|
|
16
|
+
"documentation",
|
|
17
|
+
"markdown",
|
|
18
|
+
"types",
|
|
19
|
+
"cli",
|
|
20
|
+
"api-reference",
|
|
21
|
+
"sdk"
|
|
22
|
+
],
|
|
23
|
+
"author": "",
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"type": "commonjs",
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=18"
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"dist",
|
|
31
|
+
"bin"
|
|
32
|
+
],
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "https://github.com/your-username/types-not-docs.git"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"commander": "^14.0.3",
|
|
39
|
+
"glob": "^13.0.3",
|
|
40
|
+
"ts-morph": "^27.0.2"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@types/node": "^25.2.3",
|
|
44
|
+
"typescript": "^5.9.3"
|
|
45
|
+
}
|
|
46
|
+
}
|