create-dox 0.1.0 → 0.3.1
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/dist/chunk-673L75C2.js +370 -0
- package/dist/chunk-AFJGDIXU.js +966 -0
- package/dist/index.js +605 -312
- package/dist/migrate/index.js +8 -0
- package/dist/scaffold.js +7 -0
- package/package.json +24 -17
- package/README.md +0 -79
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/scaffold.ts
|
|
4
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readdirSync as readdirSync2 } from "fs";
|
|
5
|
+
import { resolve } from "path";
|
|
6
|
+
|
|
7
|
+
// src/download.ts
|
|
8
|
+
import { Readable, pipeline } from "stream";
|
|
9
|
+
import { promisify } from "util";
|
|
10
|
+
import tar from "tar";
|
|
11
|
+
var pipelineAsync = promisify(pipeline);
|
|
12
|
+
var TARBALL_URL = "https://codeload.github.com/kenny-io/Dox/tar.gz/main";
|
|
13
|
+
var EXCLUDE_PATHS = ["/cli/", "/packages/", "/node_modules/", "/.git/"];
|
|
14
|
+
function shouldInclude(path) {
|
|
15
|
+
for (const excluded of EXCLUDE_PATHS) {
|
|
16
|
+
if (path.includes(excluded)) {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
async function downloadTemplate(targetDir) {
|
|
23
|
+
console.log("");
|
|
24
|
+
console.log(" \u23F3 Downloading Dox template...");
|
|
25
|
+
const response = await fetch(TARBALL_URL);
|
|
26
|
+
if (!response.ok) {
|
|
27
|
+
throw new Error(`Failed to download template: ${response.status} ${response.statusText}`);
|
|
28
|
+
}
|
|
29
|
+
if (!response.body) {
|
|
30
|
+
throw new Error("Response body is empty");
|
|
31
|
+
}
|
|
32
|
+
const nodeStream = Readable.fromWeb(response.body);
|
|
33
|
+
await pipelineAsync(
|
|
34
|
+
nodeStream,
|
|
35
|
+
tar.extract({ cwd: targetDir, strip: 1, filter: shouldInclude })
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// src/customize.ts
|
|
40
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync, cpSync } from "fs";
|
|
41
|
+
import { join } from "path";
|
|
42
|
+
import { execSync } from "child_process";
|
|
43
|
+
var STARTER_PAGES = {
|
|
44
|
+
"introduction.mdx": `---
|
|
45
|
+
title: Introduction
|
|
46
|
+
description: Welcome to {NAME} \u2014 learn what it does, how the docs are organized, and where to start.
|
|
47
|
+
keywords:
|
|
48
|
+
- {NAME}
|
|
49
|
+
- documentation
|
|
50
|
+
- overview
|
|
51
|
+
- getting started
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Welcome
|
|
55
|
+
|
|
56
|
+
Welcome to the **{NAME}** documentation. This is your home base for guides, API
|
|
57
|
+
references, and everything you need to build with {NAME}. The site is powered by
|
|
58
|
+
[Dox](https://github.com/kenny-io/Dox), an agent-native docs platform \u2014 every page
|
|
59
|
+
is served to humans as polished HTML and to AI agents as structured JSON, JSON-LD,
|
|
60
|
+
and Markdown from the same URL, so assistants can read your docs accurately.
|
|
61
|
+
|
|
62
|
+
## What you'll find here
|
|
63
|
+
|
|
64
|
+
- **Guides** \u2014 step-by-step walkthroughs of common tasks and workflows.
|
|
65
|
+
- **API reference** \u2014 generated from your OpenAPI spec, with a live "Try It" console.
|
|
66
|
+
- **Quickstart** \u2014 install {NAME} and make your first call in a few minutes.
|
|
67
|
+
|
|
68
|
+
## Next steps
|
|
69
|
+
|
|
70
|
+
Start with the [Quickstart](/quickstart) to get {NAME} running, then make this site
|
|
71
|
+
your own by editing \`src/content/introduction.mdx\` and updating the navigation in
|
|
72
|
+
\`docs.json\`. Every change you save is instantly reflected for both readers and agents.
|
|
73
|
+
`,
|
|
74
|
+
"quickstart.mdx": `---
|
|
75
|
+
title: Quickstart
|
|
76
|
+
description: Install {NAME}, configure your API key, and make your first call in under five minutes.
|
|
77
|
+
keywords:
|
|
78
|
+
- {NAME}
|
|
79
|
+
- quickstart
|
|
80
|
+
- installation
|
|
81
|
+
- getting started
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Installation
|
|
85
|
+
|
|
86
|
+
Install {NAME} with your package manager of choice. We recommend pinning the
|
|
87
|
+
version in your project so builds stay reproducible across machines and CI:
|
|
88
|
+
|
|
89
|
+
\`\`\`bash
|
|
90
|
+
npm install {SLUG}
|
|
91
|
+
\`\`\`
|
|
92
|
+
|
|
93
|
+
## Basic usage
|
|
94
|
+
|
|
95
|
+
Import the client and initialize it with your API key. Keep the key in an
|
|
96
|
+
environment variable rather than committing it to source control, so it never
|
|
97
|
+
leaks into your repository or build logs:
|
|
98
|
+
|
|
99
|
+
\`\`\`ts
|
|
100
|
+
import { create } from '{SLUG}'
|
|
101
|
+
|
|
102
|
+
const client = create({ apiKey: process.env.API_KEY })
|
|
103
|
+
\`\`\`
|
|
104
|
+
|
|
105
|
+
## What's next
|
|
106
|
+
|
|
107
|
+
That's the basics \u2014 you're ready to build. Explore the guides for common workflows,
|
|
108
|
+
open the API reference to try endpoints against a live "Try It" console, or edit this
|
|
109
|
+
page at \`src/content/quickstart.mdx\` to document your own onboarding flow.
|
|
110
|
+
`
|
|
111
|
+
};
|
|
112
|
+
function buildStarterDocsJson({
|
|
113
|
+
enableAiChat,
|
|
114
|
+
repoUrl,
|
|
115
|
+
i18nLocales
|
|
116
|
+
}) {
|
|
117
|
+
const config = {};
|
|
118
|
+
if (enableAiChat) {
|
|
119
|
+
config.ai = { chat: true };
|
|
120
|
+
}
|
|
121
|
+
if (repoUrl) {
|
|
122
|
+
config.navbar = {
|
|
123
|
+
links: [{ label: "GitHub", href: repoUrl, type: "github" }],
|
|
124
|
+
primary: { label: "Get started", href: "/quickstart" }
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
if (i18nLocales && i18nLocales.length > 0) {
|
|
128
|
+
config.i18n = {
|
|
129
|
+
defaultLocale: "en",
|
|
130
|
+
locales: [{ code: "en", label: "English" }, ...i18nLocales]
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
config.tabs = [
|
|
134
|
+
{
|
|
135
|
+
tab: "Overview",
|
|
136
|
+
groups: [{ group: "Getting Started", pages: ["introduction", "quickstart"] }]
|
|
137
|
+
},
|
|
138
|
+
{ tab: "API Reference", api: { source: "openapi.yaml" } },
|
|
139
|
+
{ tab: "Changelog", href: "/changelog" }
|
|
140
|
+
];
|
|
141
|
+
return JSON.stringify(config, null, 2) + "\n";
|
|
142
|
+
}
|
|
143
|
+
function writeStarterContent(targetDir, projectName, slug, enableAiChat = true, repoUrl = "", i18nLocales) {
|
|
144
|
+
const contentDir = join(targetDir, "src", "content");
|
|
145
|
+
if (existsSync(contentDir)) {
|
|
146
|
+
const entries = readdirSync(contentDir);
|
|
147
|
+
for (const entry of entries) {
|
|
148
|
+
const fullPath = join(contentDir, entry);
|
|
149
|
+
execSync(`rm -rf "${fullPath}"`);
|
|
150
|
+
}
|
|
151
|
+
} else {
|
|
152
|
+
mkdirSync(contentDir, { recursive: true });
|
|
153
|
+
}
|
|
154
|
+
for (const [filename, template] of Object.entries(STARTER_PAGES)) {
|
|
155
|
+
const content = template.replace(/\{NAME\}/g, projectName).replace(/\{SLUG\}/g, slug);
|
|
156
|
+
writeFileSync(join(contentDir, filename), content, "utf8");
|
|
157
|
+
}
|
|
158
|
+
writeFileSync(
|
|
159
|
+
join(targetDir, "docs.json"),
|
|
160
|
+
buildStarterDocsJson({ enableAiChat, repoUrl: repoUrl || void 0, i18nLocales }),
|
|
161
|
+
"utf8"
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
function updateSiteConfig(targetDir, projectName, description, brandPreset, repoUrl) {
|
|
165
|
+
const siteFile = join(targetDir, "src", "data", "site.ts");
|
|
166
|
+
if (!existsSync(siteFile)) {
|
|
167
|
+
console.log(" \u26A0\uFE0F Could not find src/data/site.ts \u2014 skipping config update.");
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
let source = readFileSync(siteFile, "utf8");
|
|
171
|
+
source = source.replace(
|
|
172
|
+
/name:\s*'[^']*'/,
|
|
173
|
+
`name: '${projectName.replace(/'/g, "\\'")}'`
|
|
174
|
+
);
|
|
175
|
+
source = source.replace(
|
|
176
|
+
/description:\s*\n\s*'[^']*'/,
|
|
177
|
+
`description:
|
|
178
|
+
'${description.replace(/'/g, "\\'")}'`
|
|
179
|
+
);
|
|
180
|
+
source = source.replace(
|
|
181
|
+
/const brandPreset:\s*BrandPresetKey\s*=\s*'[^']*'/,
|
|
182
|
+
`const brandPreset: BrandPresetKey = '${brandPreset}'`
|
|
183
|
+
);
|
|
184
|
+
if (repoUrl) {
|
|
185
|
+
source = source.replace(
|
|
186
|
+
/repoUrl:\s*'[^']*'/,
|
|
187
|
+
`repoUrl: '${repoUrl}'`
|
|
188
|
+
);
|
|
189
|
+
source = source.replace(
|
|
190
|
+
/\{\s*label:\s*'GitHub',\s*href:\s*'[^']*'\s*\}/,
|
|
191
|
+
`{ label: 'GitHub', href: '${repoUrl}' }`
|
|
192
|
+
);
|
|
193
|
+
source = source.replace(
|
|
194
|
+
/\{\s*label:\s*'Support',\s*href:\s*'[^']*'\s*\}/,
|
|
195
|
+
`{ label: 'Support', href: '${repoUrl}/issues/new' }`
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
writeFileSync(siteFile, source, "utf8");
|
|
199
|
+
}
|
|
200
|
+
function patchApiReferenceGuard(targetDir) {
|
|
201
|
+
const filePath = join(targetDir, "src", "data", "api-reference.ts");
|
|
202
|
+
if (!existsSync(filePath)) return;
|
|
203
|
+
let source = readFileSync(filePath, "utf8");
|
|
204
|
+
source = source.replace(
|
|
205
|
+
/export async function buildApiNavigation\([^)]*\)[^{]*\{\n/,
|
|
206
|
+
(match) => `${match} if (apiReferenceConfig.specs.length === 0) return []
|
|
207
|
+
`
|
|
208
|
+
);
|
|
209
|
+
writeFileSync(filePath, source, "utf8");
|
|
210
|
+
}
|
|
211
|
+
function patchTopBarNavigation(targetDir) {
|
|
212
|
+
const filePath = join(targetDir, "src", "components", "layout", "top-bar.tsx");
|
|
213
|
+
if (!existsSync(filePath)) return;
|
|
214
|
+
const source = readFileSync(filePath, "utf8");
|
|
215
|
+
if (!source.includes("target={isExternal ? '_blank' : undefined}")) return;
|
|
216
|
+
const patched = source.replace(
|
|
217
|
+
/if \(collection\.href\) \{\n const isExternal[^\n]+\n return \(\n <a[\s\S]*?<\/a>\n \)\n \}/,
|
|
218
|
+
`if (collection.href) {
|
|
219
|
+
const isExternal = /^https?:\\/\\//.test(collection.href)
|
|
220
|
+
if (isExternal) {
|
|
221
|
+
return (
|
|
222
|
+
<a
|
|
223
|
+
key={collection.id}
|
|
224
|
+
href={collection.href}
|
|
225
|
+
target="_blank"
|
|
226
|
+
rel="noreferrer"
|
|
227
|
+
className={baseClasses}
|
|
228
|
+
>
|
|
229
|
+
{collection.label}
|
|
230
|
+
</a>
|
|
231
|
+
)
|
|
232
|
+
}
|
|
233
|
+
return (
|
|
234
|
+
<Link
|
|
235
|
+
key={collection.id}
|
|
236
|
+
href={collection.href}
|
|
237
|
+
className={baseClasses}
|
|
238
|
+
>
|
|
239
|
+
{collection.label}
|
|
240
|
+
</Link>
|
|
241
|
+
)
|
|
242
|
+
}`
|
|
243
|
+
);
|
|
244
|
+
writeFileSync(filePath, patched, "utf8");
|
|
245
|
+
}
|
|
246
|
+
function patchOpenApiFetch(targetDir) {
|
|
247
|
+
const filePath = join(targetDir, "src", "lib", "openapi", "fetch.ts");
|
|
248
|
+
if (!existsSync(filePath)) return;
|
|
249
|
+
let source = readFileSync(filePath, "utf8");
|
|
250
|
+
source = source.replace(
|
|
251
|
+
/const absolutePath = path\.isAbsolute\(filePath\) \? filePath : path\.resolve\(process\.cwd\(\), filePath\)/,
|
|
252
|
+
`const absolutePath = filePath.startsWith('/')
|
|
253
|
+
? path.resolve(process.cwd(), 'public', filePath.slice(1))
|
|
254
|
+
: path.resolve(process.cwd(), filePath)`
|
|
255
|
+
);
|
|
256
|
+
writeFileSync(filePath, source, "utf8");
|
|
257
|
+
}
|
|
258
|
+
function updateEnvExample(targetDir) {
|
|
259
|
+
const envFile = join(targetDir, ".env.example");
|
|
260
|
+
if (existsSync(envFile)) {
|
|
261
|
+
const envLocal = join(targetDir, ".env.local");
|
|
262
|
+
if (!existsSync(envLocal)) {
|
|
263
|
+
cpSync(envFile, envLocal);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// src/utils.ts
|
|
269
|
+
import { execSync as execSync2 } from "child_process";
|
|
270
|
+
import { basename } from "path";
|
|
271
|
+
function slugify(name) {
|
|
272
|
+
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
|
|
273
|
+
}
|
|
274
|
+
function run(cmd, cwd) {
|
|
275
|
+
execSync2(cmd, { cwd, stdio: "inherit" });
|
|
276
|
+
}
|
|
277
|
+
function initGit(targetDir) {
|
|
278
|
+
try {
|
|
279
|
+
run("git init", targetDir);
|
|
280
|
+
run("git add -A", targetDir);
|
|
281
|
+
run('git commit -m "Initial commit from create-dox"', targetDir);
|
|
282
|
+
} catch {
|
|
283
|
+
console.log(" \u26A0\uFE0F Could not initialize git (you can do this manually).");
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
function installDeps(targetDir) {
|
|
287
|
+
console.log("");
|
|
288
|
+
console.log(" \u{1F4E6} Installing dependencies...");
|
|
289
|
+
console.log("");
|
|
290
|
+
run("npm install", targetDir);
|
|
291
|
+
}
|
|
292
|
+
function logo() {
|
|
293
|
+
console.log("");
|
|
294
|
+
console.log(" \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557");
|
|
295
|
+
console.log(" \u2551 \u2551");
|
|
296
|
+
console.log(" \u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557 \u2551");
|
|
297
|
+
console.log(" \u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u255A\u2588\u2588\u2557\u2588\u2588\u2554\u255D \u2551");
|
|
298
|
+
console.log(" \u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2554\u255D \u2551");
|
|
299
|
+
console.log(" \u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u2588\u2588\u2557 \u2551");
|
|
300
|
+
console.log(" \u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2554\u255D \u2588\u2588\u2557 \u2551");
|
|
301
|
+
console.log(" \u2551 \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u2551");
|
|
302
|
+
console.log(" \u2551 \u2551");
|
|
303
|
+
console.log(" \u2551 Beautiful docs, zero lock-in. \u2551");
|
|
304
|
+
console.log(" \u2551 \u2551");
|
|
305
|
+
console.log(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D");
|
|
306
|
+
console.log("");
|
|
307
|
+
}
|
|
308
|
+
function success(projectDir, projectName) {
|
|
309
|
+
console.log("");
|
|
310
|
+
console.log(" \u2705 Your Dox project is ready!");
|
|
311
|
+
console.log("");
|
|
312
|
+
console.log(` \u{1F4C2} ${projectDir}`);
|
|
313
|
+
console.log("");
|
|
314
|
+
console.log(" Next steps:");
|
|
315
|
+
console.log("");
|
|
316
|
+
console.log(` cd ${basename(projectDir)}`);
|
|
317
|
+
console.log(" npm run dev");
|
|
318
|
+
console.log("");
|
|
319
|
+
console.log(` Then open http://localhost:3040 to see your ${projectName} docs.`);
|
|
320
|
+
console.log("");
|
|
321
|
+
console.log(" \u{1F4DD} Key files to edit:");
|
|
322
|
+
console.log(" \u2022 src/data/site.ts \u2014 name, links, branding");
|
|
323
|
+
console.log(" \u2022 docs.json \u2014 navigation structure");
|
|
324
|
+
console.log(" \u2022 src/content/*.mdx \u2014 your documentation");
|
|
325
|
+
console.log(" \u2022 openapi.yaml \u2014 API spec (optional)");
|
|
326
|
+
console.log("");
|
|
327
|
+
console.log(" Happy documenting! \u{1F680}");
|
|
328
|
+
console.log("");
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// src/scaffold.ts
|
|
332
|
+
async function scaffold(options) {
|
|
333
|
+
const {
|
|
334
|
+
projectDir,
|
|
335
|
+
projectName,
|
|
336
|
+
description,
|
|
337
|
+
brandPreset,
|
|
338
|
+
repoUrl,
|
|
339
|
+
doInstall,
|
|
340
|
+
enableAiChat = true,
|
|
341
|
+
i18nLocales
|
|
342
|
+
} = options;
|
|
343
|
+
const targetDir = resolve(projectDir);
|
|
344
|
+
if (existsSync2(targetDir) && readdirSync2(targetDir).length > 0) {
|
|
345
|
+
throw new Error(`Directory "${targetDir}" already exists and is not empty.`);
|
|
346
|
+
}
|
|
347
|
+
mkdirSync2(targetDir, { recursive: true });
|
|
348
|
+
const slug = slugify(projectName);
|
|
349
|
+
await downloadTemplate(targetDir);
|
|
350
|
+
writeStarterContent(targetDir, projectName, slug, enableAiChat, repoUrl, i18nLocales);
|
|
351
|
+
updateSiteConfig(targetDir, projectName, description, brandPreset, repoUrl);
|
|
352
|
+
patchApiReferenceGuard(targetDir);
|
|
353
|
+
patchTopBarNavigation(targetDir);
|
|
354
|
+
patchOpenApiFetch(targetDir);
|
|
355
|
+
updateEnvExample(targetDir);
|
|
356
|
+
if (doInstall) {
|
|
357
|
+
installDeps(targetDir);
|
|
358
|
+
}
|
|
359
|
+
initGit(targetDir);
|
|
360
|
+
return { projectDir: targetDir };
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
export {
|
|
364
|
+
slugify,
|
|
365
|
+
initGit,
|
|
366
|
+
installDeps,
|
|
367
|
+
logo,
|
|
368
|
+
success,
|
|
369
|
+
scaffold
|
|
370
|
+
};
|