@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.
- package/README.md +324 -0
- package/dist/cli.js +389 -0
- package/dist/index.d.ts +827 -0
- package/dist/index.js +79 -0
- package/dist/react-styled.d.ts +588 -0
- package/dist/react-styled.js +766 -0
- package/dist/react.d.ts +221 -0
- package/dist/react.js +35 -0
- package/dist/shared/chunk-7hg53zpt.js +1160 -0
- package/dist/shared/chunk-e5fkh3kh.js +679 -0
- package/dist/shared/chunk-taeg9090.js +171 -0
- package/package.json +84 -0
- package/templates/embedded/README.md +81 -0
- package/templates/embedded/docusaurus/sidebars.js +38 -0
- package/templates/embedded/fumadocs/meta.json +26 -0
- package/templates/standalone/index.html +34 -0
- package/templates/standalone/package.json +17 -0
- package/templates/standalone/src/main.ts +223 -0
- package/templates/standalone/src/styles.css +43 -0
- package/templates/standalone/vite.config.ts +9 -0
|
@@ -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, '&')
|
|
173
|
+
.replace(/</g, '<')
|
|
174
|
+
.replace(/>/g, '>')
|
|
175
|
+
.replace(/"/g, '"');
|
|
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();
|