@openpkg-ts/doc-generator 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.
@@ -0,0 +1,171 @@
1
+ import { createRequire } from "node:module";
2
+ var __create = Object.create;
3
+ var __getProtoOf = Object.getPrototypeOf;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __toESM = (mod, isNodeMode, target) => {
8
+ target = mod != null ? __create(__getProtoOf(mod)) : {};
9
+ const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
10
+ for (let key of __getOwnPropNames(mod))
11
+ if (!__hasOwnProp.call(to, key))
12
+ __defProp(to, key, {
13
+ get: () => mod[key],
14
+ enumerable: true
15
+ });
16
+ return to;
17
+ };
18
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
19
+
20
+ // src/core/query.ts
21
+ function formatSchema(schema) {
22
+ if (!schema)
23
+ return "unknown";
24
+ if (typeof schema === "string")
25
+ return schema;
26
+ if (typeof schema === "object" && schema !== null) {
27
+ if ("$ref" in schema && typeof schema.$ref === "string") {
28
+ return schema.$ref.replace("#/types/", "");
29
+ }
30
+ if ("anyOf" in schema && Array.isArray(schema.anyOf)) {
31
+ return schema.anyOf.map((s) => formatSchema(s)).join(" | ");
32
+ }
33
+ if ("allOf" in schema && Array.isArray(schema.allOf)) {
34
+ return schema.allOf.map((s) => formatSchema(s)).join(" & ");
35
+ }
36
+ if ("type" in schema && schema.type === "array") {
37
+ const items = "items" in schema ? formatSchema(schema.items) : "unknown";
38
+ return `${items}[]`;
39
+ }
40
+ if ("type" in schema && schema.type === "tuple" && "items" in schema) {
41
+ const items = schema.items.map(formatSchema).join(", ");
42
+ return `[${items}]`;
43
+ }
44
+ if ("type" in schema && schema.type === "object") {
45
+ if ("properties" in schema && schema.properties) {
46
+ const props = Object.entries(schema.properties).map(([k, v]) => `${k}: ${formatSchema(v)}`).join("; ");
47
+ return `{ ${props} }`;
48
+ }
49
+ return "object";
50
+ }
51
+ if ("type" in schema && typeof schema.type === "string") {
52
+ return schema.type;
53
+ }
54
+ }
55
+ return "unknown";
56
+ }
57
+ function formatTypeParameters(typeParams) {
58
+ if (!typeParams?.length)
59
+ return "";
60
+ const params = typeParams.map((tp) => {
61
+ let str = tp.name;
62
+ if (tp.constraint)
63
+ str += ` extends ${tp.constraint}`;
64
+ if (tp.default)
65
+ str += ` = ${tp.default}`;
66
+ return str;
67
+ });
68
+ return `<${params.join(", ")}>`;
69
+ }
70
+ function formatParameters(sig) {
71
+ if (!sig?.parameters?.length)
72
+ return "()";
73
+ const params = sig.parameters.map((p) => {
74
+ const optional = p.required === false ? "?" : "";
75
+ const rest = p.rest ? "..." : "";
76
+ const type = formatSchema(p.schema);
77
+ return `${rest}${p.name}${optional}: ${type}`;
78
+ });
79
+ return `(${params.join(", ")})`;
80
+ }
81
+ function formatReturnType(sig) {
82
+ if (!sig?.returns)
83
+ return "void";
84
+ return formatSchema(sig.returns.schema);
85
+ }
86
+ function buildSignatureString(exp, sigIndex = 0) {
87
+ const sig = exp.signatures?.[sigIndex];
88
+ const typeParams = formatTypeParameters(exp.typeParameters || sig?.typeParameters);
89
+ switch (exp.kind) {
90
+ case "function": {
91
+ const params = formatParameters(sig);
92
+ const returnType = formatReturnType(sig);
93
+ return `function ${exp.name}${typeParams}${params}: ${returnType}`;
94
+ }
95
+ case "class": {
96
+ const ext = exp.extends ? ` extends ${exp.extends}` : "";
97
+ const impl = exp.implements?.length ? ` implements ${exp.implements.join(", ")}` : "";
98
+ return `class ${exp.name}${typeParams}${ext}${impl}`;
99
+ }
100
+ case "interface": {
101
+ const ext = exp.extends ? ` extends ${exp.extends}` : "";
102
+ return `interface ${exp.name}${typeParams}${ext}`;
103
+ }
104
+ case "type": {
105
+ const typeValue = typeof exp.type === "string" ? exp.type : formatSchema(exp.schema);
106
+ return `type ${exp.name}${typeParams} = ${typeValue}`;
107
+ }
108
+ case "enum": {
109
+ return `enum ${exp.name}`;
110
+ }
111
+ case "variable": {
112
+ const typeValue = typeof exp.type === "string" ? exp.type : formatSchema(exp.schema);
113
+ return `const ${exp.name}: ${typeValue}`;
114
+ }
115
+ default:
116
+ return exp.name;
117
+ }
118
+ }
119
+ function resolveTypeRef(ref, spec) {
120
+ const id = ref.replace("#/types/", "");
121
+ return spec.types?.find((t) => t.id === id);
122
+ }
123
+ function isMethod(member) {
124
+ return !!member.signatures?.length;
125
+ }
126
+ function isProperty(member) {
127
+ return !member.signatures?.length;
128
+ }
129
+ function getMethods(members) {
130
+ return members?.filter(isMethod) ?? [];
131
+ }
132
+ function getProperties(members) {
133
+ return members?.filter(isProperty) ?? [];
134
+ }
135
+ function groupByVisibility(members) {
136
+ const groups = {
137
+ public: [],
138
+ protected: [],
139
+ private: []
140
+ };
141
+ for (const member of members ?? []) {
142
+ const visibility = member.visibility ?? "public";
143
+ groups[visibility].push(member);
144
+ }
145
+ return groups;
146
+ }
147
+ function sortByName(items) {
148
+ return [...items].sort((a, b) => a.name.localeCompare(b.name));
149
+ }
150
+ function sortByKindThenName(exports) {
151
+ const kindOrder = {
152
+ function: 0,
153
+ class: 1,
154
+ interface: 2,
155
+ type: 3,
156
+ enum: 4,
157
+ variable: 5,
158
+ namespace: 6,
159
+ module: 7,
160
+ reference: 8,
161
+ external: 9
162
+ };
163
+ return [...exports].sort((a, b) => {
164
+ const kindDiff = kindOrder[a.kind] - kindOrder[b.kind];
165
+ if (kindDiff !== 0)
166
+ return kindDiff;
167
+ return a.name.localeCompare(b.name);
168
+ });
169
+ }
170
+
171
+ export { __toESM, __require, formatSchema, formatTypeParameters, formatParameters, formatReturnType, buildSignatureString, resolveTypeRef, isMethod, isProperty, getMethods, getProperties, groupByVisibility, sortByName, sortByKindThenName };
package/package.json ADDED
@@ -0,0 +1,84 @@
1
+ {
2
+ "name": "@openpkg-ts/doc-generator",
3
+ "version": "0.1.0",
4
+ "description": "API doc generator consuming OpenPkg specs. TypeDoc alternative for modern doc frameworks.",
5
+ "keywords": [
6
+ "openpkg",
7
+ "documentation",
8
+ "api-docs",
9
+ "typescript",
10
+ "react"
11
+ ],
12
+ "homepage": "https://github.com/doccov/doccov#readme",
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "git+https://github.com/doccov/doccov.git",
16
+ "directory": "packages/doc-generator"
17
+ },
18
+ "license": "MIT",
19
+ "author": "Ryan Waits",
20
+ "type": "module",
21
+ "main": "./dist/index.js",
22
+ "types": "./dist/index.d.ts",
23
+ "exports": {
24
+ ".": {
25
+ "import": "./dist/index.js",
26
+ "types": "./dist/index.d.ts"
27
+ },
28
+ "./react": {
29
+ "import": "./dist/react.js",
30
+ "types": "./dist/react.d.ts"
31
+ },
32
+ "./react/styled": {
33
+ "import": "./dist/react-styled.js",
34
+ "types": "./dist/react-styled.d.ts"
35
+ },
36
+ "./cli": {
37
+ "import": "./dist/cli.js",
38
+ "types": "./dist/cli.d.ts"
39
+ }
40
+ },
41
+ "bin": {
42
+ "openpkg-docs": "./dist/cli.js"
43
+ },
44
+ "files": [
45
+ "dist",
46
+ "templates"
47
+ ],
48
+ "scripts": {
49
+ "build": "bunup",
50
+ "dev": "bunup --watch",
51
+ "lint": "biome check src/",
52
+ "lint:fix": "biome check --write src/",
53
+ "typecheck": "tsc --noEmit",
54
+ "test": "bun test",
55
+ "test:watch": "bun test --watch"
56
+ },
57
+ "dependencies": {
58
+ "@openpkg-ts/spec": "workspace:*",
59
+ "commander": "^14.0.0"
60
+ },
61
+ "peerDependencies": {
62
+ "react": "^18 || ^19",
63
+ "tailwindcss": "^4"
64
+ },
65
+ "peerDependenciesMeta": {
66
+ "react": {
67
+ "optional": true
68
+ },
69
+ "tailwindcss": {
70
+ "optional": true
71
+ }
72
+ },
73
+ "devDependencies": {
74
+ "@types/bun": "latest",
75
+ "@types/node": "^20.0.0",
76
+ "@types/react": "^19.0.0",
77
+ "bunup": "latest",
78
+ "react": "^19.0.0",
79
+ "typescript": "^5.0.0"
80
+ },
81
+ "publishConfig": {
82
+ "access": "public"
83
+ }
84
+ }
@@ -0,0 +1,81 @@
1
+ # Embedded API Docs Template
2
+
3
+ This template shows how to integrate generated MDX files into existing documentation sites.
4
+
5
+ ## Usage
6
+
7
+ Generate MDX files for your API:
8
+
9
+ ```bash
10
+ npx openpkg-docs generate ./openpkg.json --format mdx --out ./docs/api --nav fumadocs
11
+ ```
12
+
13
+ ## Fumadocs Integration
14
+
15
+ 1. Copy generated files to `content/docs/api/`
16
+ 2. Use the generated `meta.json` for navigation
17
+ 3. Import and use components in your MDX
18
+
19
+ ```tsx
20
+ // Example: content/docs/api/functions/use-state.mdx
21
+ ---
22
+ title: useState
23
+ description: React state hook
24
+ ---
25
+
26
+ # useState
27
+
28
+ \`function useState<T>(initial: T): [T, (value: T) => void]\`
29
+
30
+ ...
31
+ ```
32
+
33
+ ## Docusaurus Integration
34
+
35
+ 1. Copy generated files to `docs/api/`
36
+ 2. Use generated `sidebars.js` for navigation
37
+
38
+ ```js
39
+ // sidebars.js
40
+ const apiSidebar = require('./docs/api/sidebars.js');
41
+
42
+ module.exports = {
43
+ docs: [
44
+ // ... your existing sidebar
45
+ {
46
+ type: 'category',
47
+ label: 'API Reference',
48
+ items: apiSidebar,
49
+ },
50
+ ],
51
+ };
52
+ ```
53
+
54
+ ## File Structure
55
+
56
+ After generation:
57
+
58
+ ```
59
+ docs/api/
60
+ ├── meta.json # Fumadocs navigation
61
+ ├── sidebars.js # Docusaurus navigation
62
+ ├── functions/
63
+ │ ├── use-state.mdx
64
+ │ └── use-effect.mdx
65
+ ├── classes/
66
+ │ └── store.mdx
67
+ ├── interfaces/
68
+ │ └── options.mdx
69
+ └── types/
70
+ └── config.mdx
71
+ ```
72
+
73
+ ## Customization
74
+
75
+ Override frontmatter in generated files or use the JSON output for custom rendering:
76
+
77
+ ```bash
78
+ npx openpkg-docs generate ./openpkg.json --format json --out ./api.json
79
+ ```
80
+
81
+ Then build your own components using the JSON data.
@@ -0,0 +1,38 @@
1
+ // Auto-generated sidebar configuration for Docusaurus
2
+ // Usage: Import in your main sidebars.js and add to your sidebar config
3
+
4
+ module.exports = [
5
+ {
6
+ type: 'category',
7
+ label: 'Functions',
8
+ items: [
9
+ { type: 'doc', id: 'api/use-state', label: 'useState' },
10
+ { type: 'doc', id: 'api/use-effect', label: 'useEffect' },
11
+ { type: 'doc', id: 'api/use-callback', label: 'useCallback' },
12
+ ],
13
+ },
14
+ {
15
+ type: 'category',
16
+ label: 'Classes',
17
+ items: [
18
+ { type: 'doc', id: 'api/store', label: 'Store' },
19
+ { type: 'doc', id: 'api/client', label: 'Client' },
20
+ ],
21
+ },
22
+ {
23
+ type: 'category',
24
+ label: 'Interfaces',
25
+ items: [
26
+ { type: 'doc', id: 'api/options', label: 'Options' },
27
+ { type: 'doc', id: 'api/config', label: 'Config' },
28
+ ],
29
+ },
30
+ {
31
+ type: 'category',
32
+ label: 'Types',
33
+ items: [
34
+ { type: 'doc', id: 'api/callback', label: 'Callback' },
35
+ { type: 'doc', id: 'api/handler', label: 'Handler' },
36
+ ],
37
+ },
38
+ ];
@@ -0,0 +1,26 @@
1
+ {
2
+ "root": true,
3
+ "title": "API Reference",
4
+ "pages": [
5
+ {
6
+ "title": "Functions",
7
+ "pages": ["use-state", "use-effect", "use-callback"],
8
+ "defaultOpen": true
9
+ },
10
+ {
11
+ "title": "Classes",
12
+ "pages": ["store", "client"],
13
+ "defaultOpen": true
14
+ },
15
+ {
16
+ "title": "Interfaces",
17
+ "pages": ["options", "config"],
18
+ "defaultOpen": true
19
+ },
20
+ {
21
+ "title": "Types",
22
+ "pages": ["callback", "handler"],
23
+ "defaultOpen": true
24
+ }
25
+ ]
26
+ }
@@ -0,0 +1,34 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>API Documentation</title>
7
+ <link rel="stylesheet" href="/src/styles.css">
8
+ </head>
9
+ <body class="bg-white dark:bg-zinc-900 text-zinc-900 dark:text-zinc-100">
10
+ <div id="app" class="max-w-4xl mx-auto px-6 py-12">
11
+ <header class="mb-12">
12
+ <h1 class="text-4xl font-bold mb-4">API Reference</h1>
13
+ <p class="text-zinc-600 dark:text-zinc-400">Documentation generated from OpenPkg spec</p>
14
+ </header>
15
+
16
+ <div id="search" class="mb-8">
17
+ <input
18
+ type="search"
19
+ id="search-input"
20
+ placeholder="Search documentation..."
21
+ class="w-full px-4 py-2 rounded-lg border border-zinc-200 dark:border-zinc-700 bg-white dark:bg-zinc-800 focus:outline-none focus:ring-2 focus:ring-blue-500"
22
+ >
23
+ <div id="search-results" class="mt-4"></div>
24
+ </div>
25
+
26
+ <main id="content">
27
+ <!-- API docs content will be injected here -->
28
+ <p class="text-zinc-500">Loading documentation...</p>
29
+ </main>
30
+ </div>
31
+
32
+ <script type="module" src="/src/main.ts"></script>
33
+ </body>
34
+ </html>
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "api-docs",
3
+ "private": true,
4
+ "type": "module",
5
+ "scripts": {
6
+ "dev": "vite",
7
+ "build": "vite build",
8
+ "preview": "vite preview",
9
+ "search": "pagefind --site dist"
10
+ },
11
+ "devDependencies": {
12
+ "vite": "^6.0.0",
13
+ "pagefind": "^1.3.0",
14
+ "@tailwindcss/vite": "^4.0.0",
15
+ "tailwindcss": "^4.0.0"
16
+ }
17
+ }
@@ -0,0 +1,223 @@
1
+ // Main entry for standalone API docs site
2
+ // This is a template that gets customized during build
3
+
4
+ async function init() {
5
+ const content = document.getElementById('content');
6
+
7
+ // Load API data
8
+ try {
9
+ const response = await fetch('/api.json');
10
+ if (!response.ok) throw new Error('Failed to load API data');
11
+
12
+ const spec = await response.json();
13
+ renderDocs(spec);
14
+ } catch (err) {
15
+ if (content) {
16
+ content.innerHTML = `<p class="text-red-500">Failed to load documentation: ${err}</p>`;
17
+ }
18
+ }
19
+
20
+ // Initialize Pagefind search if available
21
+ initSearch();
22
+ }
23
+
24
+ function renderDocs(spec: {
25
+ name: string;
26
+ description?: string;
27
+ exports: Array<{
28
+ id: string;
29
+ name: string;
30
+ kind: string;
31
+ signature: string;
32
+ description?: string;
33
+ deprecated?: boolean;
34
+ parameters?: Array<{
35
+ name: string;
36
+ type: string;
37
+ required: boolean;
38
+ description?: string;
39
+ }>;
40
+ returns?: {
41
+ type: string;
42
+ description?: string;
43
+ };
44
+ members?: Array<{
45
+ name: string;
46
+ kind: string;
47
+ type?: string;
48
+ description?: string;
49
+ }>;
50
+ }>;
51
+ }) {
52
+ const content = document.getElementById('content');
53
+ if (!content) return;
54
+
55
+ // Group by kind
56
+ const byKind: Record<string, typeof spec.exports> = {};
57
+ for (const exp of spec.exports) {
58
+ if (!byKind[exp.kind]) byKind[exp.kind] = [];
59
+ byKind[exp.kind].push(exp);
60
+ }
61
+
62
+ const kindOrder = ['function', 'class', 'interface', 'type', 'enum', 'variable'];
63
+ const sections = kindOrder
64
+ .filter((kind) => byKind[kind]?.length)
65
+ .map((kind) => {
66
+ const exports = byKind[kind];
67
+ const cards = exports.map(renderExportCard).join('');
68
+ return `
69
+ <section class="kind-section">
70
+ <h2 class="text-2xl font-semibold mb-6 capitalize">${kind}s</h2>
71
+ ${cards}
72
+ </section>
73
+ `;
74
+ })
75
+ .join('');
76
+
77
+ content.innerHTML = sections;
78
+ }
79
+
80
+ function renderExportCard(exp: {
81
+ id: string;
82
+ name: string;
83
+ signature: string;
84
+ description?: string;
85
+ deprecated?: boolean;
86
+ parameters?: Array<{
87
+ name: string;
88
+ type: string;
89
+ required: boolean;
90
+ description?: string;
91
+ }>;
92
+ returns?: {
93
+ type: string;
94
+ description?: string;
95
+ };
96
+ members?: Array<{
97
+ name: string;
98
+ kind: string;
99
+ type?: string;
100
+ description?: string;
101
+ }>;
102
+ }): string {
103
+ const deprecated = exp.deprecated
104
+ ? '<span class="text-orange-500 text-sm ml-2">[Deprecated]</span>'
105
+ : '';
106
+ const desc = exp.description
107
+ ? `<p class="mt-3 text-zinc-600 dark:text-zinc-400">${escapeHtml(exp.description)}</p>`
108
+ : '';
109
+
110
+ let details = '';
111
+
112
+ // Parameters
113
+ if (exp.parameters?.length) {
114
+ const rows = exp.parameters
115
+ .map(
116
+ (p) => `
117
+ <tr>
118
+ <td><code>${escapeHtml(p.name)}</code></td>
119
+ <td><code>${escapeHtml(p.type)}</code></td>
120
+ <td>${p.required ? 'Yes' : 'No'}</td>
121
+ <td>${p.description ? escapeHtml(p.description) : '-'}</td>
122
+ </tr>
123
+ `,
124
+ )
125
+ .join('');
126
+
127
+ details += `
128
+ <h4 class="font-medium mt-4 mb-2">Parameters</h4>
129
+ <table class="param-table">
130
+ <thead><tr><th>Name</th><th>Type</th><th>Required</th><th>Description</th></tr></thead>
131
+ <tbody>${rows}</tbody>
132
+ </table>
133
+ `;
134
+ }
135
+
136
+ // Returns
137
+ if (exp.returns) {
138
+ details += `
139
+ <h4 class="font-medium mt-4 mb-2">Returns</h4>
140
+ <p><code>${escapeHtml(exp.returns.type)}</code>${exp.returns.description ? ` - ${escapeHtml(exp.returns.description)}` : ''}</p>
141
+ `;
142
+ }
143
+
144
+ // Members
145
+ if (exp.members?.length) {
146
+ const items = exp.members
147
+ .map(
148
+ (m) => `
149
+ <li><code>${escapeHtml(m.name)}</code>${m.type ? `: <code>${escapeHtml(m.type)}</code>` : ''}${m.description ? ` - ${escapeHtml(m.description)}` : ''}</li>
150
+ `,
151
+ )
152
+ .join('');
153
+
154
+ details += `
155
+ <h4 class="font-medium mt-4 mb-2">Members</h4>
156
+ <ul class="list-disc list-inside space-y-1">${items}</ul>
157
+ `;
158
+ }
159
+
160
+ return `
161
+ <article class="export-card" id="${exp.id}">
162
+ <h3 class="text-lg font-semibold">${escapeHtml(exp.name)}${deprecated}</h3>
163
+ <pre class="signature"><code>${escapeHtml(exp.signature)}</code></pre>
164
+ ${desc}
165
+ ${details}
166
+ </article>
167
+ `;
168
+ }
169
+
170
+ function escapeHtml(str: string): string {
171
+ return str
172
+ .replace(/&/g, '&amp;')
173
+ .replace(/</g, '&lt;')
174
+ .replace(/>/g, '&gt;')
175
+ .replace(/"/g, '&quot;');
176
+ }
177
+
178
+ async function initSearch() {
179
+ const searchInput = document.getElementById('search-input') as HTMLInputElement | null;
180
+ const searchResults = document.getElementById('search-results');
181
+
182
+ if (!searchInput || !searchResults) return;
183
+
184
+ // Try to load Pagefind
185
+ try {
186
+ // @ts-ignore - Pagefind is loaded at runtime
187
+ const pagefind = await import('/_pagefind/pagefind.js');
188
+ await pagefind.init();
189
+
190
+ searchInput.addEventListener('input', async (e) => {
191
+ const query = (e.target as HTMLInputElement).value;
192
+ if (!query) {
193
+ searchResults.innerHTML = '';
194
+ return;
195
+ }
196
+
197
+ const search = await pagefind.search(query);
198
+ const results = await Promise.all(
199
+ search.results
200
+ .slice(0, 5)
201
+ .map((r: { data: () => Promise<{ url: string; title: string; excerpt: string }> }) =>
202
+ r.data(),
203
+ ),
204
+ );
205
+
206
+ searchResults.innerHTML = results
207
+ .map(
208
+ (r: { url: string; title: string; excerpt: string }) => `
209
+ <a href="${r.url}" class="block p-3 rounded hover:bg-zinc-100 dark:hover:bg-zinc-800">
210
+ <div class="font-medium">${r.title}</div>
211
+ <div class="text-sm text-zinc-500">${r.excerpt}</div>
212
+ </a>
213
+ `,
214
+ )
215
+ .join('');
216
+ });
217
+ } catch {
218
+ // Pagefind not available, fallback to JSON search
219
+ searchInput.placeholder = 'Search (JSON fallback)...';
220
+ }
221
+ }
222
+
223
+ init();