@farming-labs/docs 0.0.2-beta.2 → 0.0.2-beta.20
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/cli/index.mjs +1512 -186
- package/dist/index.d.mts +426 -2
- package/dist/index.mjs +5 -1
- package/package.json +1 -1
package/dist/cli/index.mjs
CHANGED
|
@@ -10,10 +10,14 @@ function detectFramework(cwd) {
|
|
|
10
10
|
const pkgPath = path.join(cwd, "package.json");
|
|
11
11
|
if (!fs.existsSync(pkgPath)) return null;
|
|
12
12
|
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
13
|
-
|
|
13
|
+
const allDeps = {
|
|
14
14
|
...pkg.dependencies,
|
|
15
15
|
...pkg.devDependencies
|
|
16
|
-
}
|
|
16
|
+
};
|
|
17
|
+
if (allDeps["next"]) return "nextjs";
|
|
18
|
+
if (allDeps["@sveltejs/kit"]) return "sveltekit";
|
|
19
|
+
if (allDeps["astro"]) return "astro";
|
|
20
|
+
if (allDeps["nuxt"]) return "nuxt";
|
|
17
21
|
return null;
|
|
18
22
|
}
|
|
19
23
|
function detectPackageManager(cwd) {
|
|
@@ -53,6 +57,27 @@ function readFileSafe(filePath) {
|
|
|
53
57
|
if (!fs.existsSync(filePath)) return null;
|
|
54
58
|
return fs.readFileSync(filePath, "utf-8");
|
|
55
59
|
}
|
|
60
|
+
/** Common locations where global CSS files live in Next.js / SvelteKit projects. */
|
|
61
|
+
const GLOBAL_CSS_CANDIDATES = [
|
|
62
|
+
"app/globals.css",
|
|
63
|
+
"app/global.css",
|
|
64
|
+
"src/app/globals.css",
|
|
65
|
+
"src/app/global.css",
|
|
66
|
+
"src/app.css",
|
|
67
|
+
"styles/globals.css",
|
|
68
|
+
"styles/global.css",
|
|
69
|
+
"src/styles/globals.css",
|
|
70
|
+
"src/styles/global.css",
|
|
71
|
+
"assets/css/main.css",
|
|
72
|
+
"assets/main.css"
|
|
73
|
+
];
|
|
74
|
+
/**
|
|
75
|
+
* Find existing global CSS files in the project.
|
|
76
|
+
* Returns relative paths that exist.
|
|
77
|
+
*/
|
|
78
|
+
function detectGlobalCssFiles(cwd) {
|
|
79
|
+
return GLOBAL_CSS_CANDIDATES.filter((rel) => fs.existsSync(path.join(cwd, rel)));
|
|
80
|
+
}
|
|
56
81
|
/**
|
|
57
82
|
* Run a shell command synchronously, inheriting stdio.
|
|
58
83
|
*/
|
|
@@ -107,14 +132,132 @@ function spawnAndWaitFor(command, args, cwd, waitFor, timeoutMs = 6e4) {
|
|
|
107
132
|
|
|
108
133
|
//#endregion
|
|
109
134
|
//#region src/cli/templates.ts
|
|
135
|
+
const THEME_INFO = {
|
|
136
|
+
fumadocs: {
|
|
137
|
+
factory: "fumadocs",
|
|
138
|
+
nextImport: "@farming-labs/theme",
|
|
139
|
+
svelteImport: "@farming-labs/svelte-theme",
|
|
140
|
+
astroImport: "@farming-labs/astro-theme",
|
|
141
|
+
nuxtImport: "@farming-labs/nuxt-theme",
|
|
142
|
+
nextCssImport: "default",
|
|
143
|
+
svelteCssTheme: "fumadocs",
|
|
144
|
+
astroCssTheme: "fumadocs",
|
|
145
|
+
nuxtCssTheme: "fumadocs"
|
|
146
|
+
},
|
|
147
|
+
darksharp: {
|
|
148
|
+
factory: "darksharp",
|
|
149
|
+
nextImport: "@farming-labs/theme/darksharp",
|
|
150
|
+
svelteImport: "@farming-labs/svelte-theme/darksharp",
|
|
151
|
+
astroImport: "@farming-labs/astro-theme/darksharp",
|
|
152
|
+
nuxtImport: "@farming-labs/nuxt-theme/darksharp",
|
|
153
|
+
nextCssImport: "darksharp",
|
|
154
|
+
svelteCssTheme: "darksharp",
|
|
155
|
+
astroCssTheme: "darksharp",
|
|
156
|
+
nuxtCssTheme: "darksharp"
|
|
157
|
+
},
|
|
158
|
+
"pixel-border": {
|
|
159
|
+
factory: "pixelBorder",
|
|
160
|
+
nextImport: "@farming-labs/theme/pixel-border",
|
|
161
|
+
svelteImport: "@farming-labs/svelte-theme/pixel-border",
|
|
162
|
+
astroImport: "@farming-labs/astro-theme/pixel-border",
|
|
163
|
+
nuxtImport: "@farming-labs/nuxt-theme/pixel-border",
|
|
164
|
+
nextCssImport: "pixel-border",
|
|
165
|
+
svelteCssTheme: "pixel-border",
|
|
166
|
+
astroCssTheme: "pixel-border",
|
|
167
|
+
nuxtCssTheme: "pixel-border"
|
|
168
|
+
},
|
|
169
|
+
colorful: {
|
|
170
|
+
factory: "colorful",
|
|
171
|
+
nextImport: "@farming-labs/theme/colorful",
|
|
172
|
+
svelteImport: "@farming-labs/svelte-theme/colorful",
|
|
173
|
+
astroImport: "@farming-labs/astro-theme/colorful",
|
|
174
|
+
nuxtImport: "@farming-labs/nuxt-theme/colorful",
|
|
175
|
+
nextCssImport: "colorful",
|
|
176
|
+
svelteCssTheme: "colorful",
|
|
177
|
+
astroCssTheme: "colorful",
|
|
178
|
+
nuxtCssTheme: "colorful"
|
|
179
|
+
},
|
|
180
|
+
darkbold: {
|
|
181
|
+
factory: "darkbold",
|
|
182
|
+
nextImport: "@farming-labs/theme/darkbold",
|
|
183
|
+
svelteImport: "@farming-labs/svelte-theme/darkbold",
|
|
184
|
+
astroImport: "@farming-labs/astro-theme/darkbold",
|
|
185
|
+
nuxtImport: "@farming-labs/nuxt-theme/darkbold",
|
|
186
|
+
nextCssImport: "darkbold",
|
|
187
|
+
svelteCssTheme: "darkbold",
|
|
188
|
+
astroCssTheme: "darkbold",
|
|
189
|
+
nuxtCssTheme: "darkbold"
|
|
190
|
+
},
|
|
191
|
+
shiny: {
|
|
192
|
+
factory: "shiny",
|
|
193
|
+
nextImport: "@farming-labs/theme/shiny",
|
|
194
|
+
svelteImport: "@farming-labs/svelte-theme/shiny",
|
|
195
|
+
astroImport: "@farming-labs/astro-theme/shiny",
|
|
196
|
+
nuxtImport: "@farming-labs/nuxt-theme/shiny",
|
|
197
|
+
nextCssImport: "shiny",
|
|
198
|
+
svelteCssTheme: "shiny",
|
|
199
|
+
astroCssTheme: "shiny",
|
|
200
|
+
nuxtCssTheme: "shiny"
|
|
201
|
+
},
|
|
202
|
+
greentree: {
|
|
203
|
+
factory: "greentree",
|
|
204
|
+
nextImport: "@farming-labs/theme/greentree",
|
|
205
|
+
svelteImport: "@farming-labs/svelte-theme/greentree",
|
|
206
|
+
astroImport: "@farming-labs/astro-theme/greentree",
|
|
207
|
+
nuxtImport: "@farming-labs/nuxt-theme/greentree",
|
|
208
|
+
nextCssImport: "greentree",
|
|
209
|
+
svelteCssTheme: "greentree",
|
|
210
|
+
astroCssTheme: "greentree",
|
|
211
|
+
nuxtCssTheme: "greentree"
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
function getThemeInfo(theme) {
|
|
215
|
+
return THEME_INFO[theme] ?? THEME_INFO.fumadocs;
|
|
216
|
+
}
|
|
217
|
+
/** Config import for Next.js app/layout.tsx → root docs.config */
|
|
218
|
+
function nextRootLayoutConfigImport(useAlias) {
|
|
219
|
+
return useAlias ? "@/docs.config" : "../docs.config";
|
|
220
|
+
}
|
|
221
|
+
/** Config import for Next.js app/{entry}/layout.tsx → root docs.config */
|
|
222
|
+
function nextDocsLayoutConfigImport(useAlias) {
|
|
223
|
+
return useAlias ? "@/docs.config" : "../../docs.config";
|
|
224
|
+
}
|
|
225
|
+
/** Config import for SvelteKit src/lib/docs.server.ts → src/lib/docs.config */
|
|
226
|
+
function svelteServerConfigImport(useAlias) {
|
|
227
|
+
return useAlias ? "$lib/docs.config" : "./docs.config";
|
|
228
|
+
}
|
|
229
|
+
/** Config import for SvelteKit src/routes/{entry}/+layout.svelte → src/lib/docs.config */
|
|
230
|
+
function svelteLayoutConfigImport(useAlias) {
|
|
231
|
+
return useAlias ? "$lib/docs.config" : "../../lib/docs.config";
|
|
232
|
+
}
|
|
233
|
+
/** Config import for SvelteKit src/routes/{entry}/[...slug]/+page.svelte → src/lib/docs.config */
|
|
234
|
+
function sveltePageConfigImport(useAlias) {
|
|
235
|
+
return useAlias ? "$lib/docs.config" : "../../../lib/docs.config";
|
|
236
|
+
}
|
|
237
|
+
/** Server import for SvelteKit +layout.server.js → src/lib/docs.server */
|
|
238
|
+
function svelteLayoutServerImport(useAlias) {
|
|
239
|
+
return useAlias ? "$lib/docs.server" : "../../lib/docs.server";
|
|
240
|
+
}
|
|
241
|
+
function astroServerConfigImport(useAlias) {
|
|
242
|
+
return useAlias ? "@/lib/docs.config" : "./docs.config";
|
|
243
|
+
}
|
|
244
|
+
function astroPageConfigImport(useAlias, depth) {
|
|
245
|
+
if (useAlias) return "@/lib/docs.config";
|
|
246
|
+
return `${"../".repeat(depth)}lib/docs.config`;
|
|
247
|
+
}
|
|
248
|
+
function astroPageServerImport(useAlias, depth) {
|
|
249
|
+
if (useAlias) return "@/lib/docs.server";
|
|
250
|
+
return `${"../".repeat(depth)}lib/docs.server`;
|
|
251
|
+
}
|
|
110
252
|
function docsConfigTemplate(cfg) {
|
|
253
|
+
const t = getThemeInfo(cfg.theme);
|
|
111
254
|
return `\
|
|
112
255
|
import { defineDocs } from "@farming-labs/docs";
|
|
113
|
-
import {
|
|
256
|
+
import { ${t.factory} } from "${t.nextImport}";
|
|
114
257
|
|
|
115
258
|
export default defineDocs({
|
|
116
259
|
entry: "${cfg.entry}",
|
|
117
|
-
theme:
|
|
260
|
+
theme: ${t.factory}({
|
|
118
261
|
ui: {
|
|
119
262
|
colors: { primary: "#6366f1" },
|
|
120
263
|
},
|
|
@@ -151,12 +294,16 @@ function nextConfigMergedTemplate(existingContent) {
|
|
|
151
294
|
}
|
|
152
295
|
return lines.join("\n");
|
|
153
296
|
}
|
|
154
|
-
function rootLayoutTemplate() {
|
|
297
|
+
function rootLayoutTemplate(cfg, globalCssRelPath = "app/globals.css") {
|
|
298
|
+
let cssImport;
|
|
299
|
+
if (globalCssRelPath.startsWith("app/")) cssImport = "./" + globalCssRelPath.slice(4);
|
|
300
|
+
else if (globalCssRelPath.startsWith("src/app/")) cssImport = "./" + globalCssRelPath.slice(8);
|
|
301
|
+
else cssImport = "../" + globalCssRelPath;
|
|
155
302
|
return `\
|
|
156
303
|
import type { Metadata } from "next";
|
|
157
|
-
import { RootProvider } from "@farming-labs/
|
|
158
|
-
import docsConfig from "
|
|
159
|
-
import "
|
|
304
|
+
import { RootProvider } from "@farming-labs/theme";
|
|
305
|
+
import docsConfig from "${nextRootLayoutConfigImport(cfg.useAlias)}";
|
|
306
|
+
import "${cssImport}";
|
|
160
307
|
|
|
161
308
|
export const metadata: Metadata = {
|
|
162
309
|
title: {
|
|
@@ -184,26 +331,23 @@ export default function RootLayout({
|
|
|
184
331
|
function globalCssTemplate(theme) {
|
|
185
332
|
return `\
|
|
186
333
|
@import "tailwindcss";
|
|
187
|
-
@import "@farming-labs/${theme}/css";
|
|
334
|
+
@import "@farming-labs/theme/${getThemeInfo(theme).nextCssImport}/css";
|
|
188
335
|
`;
|
|
189
336
|
}
|
|
190
|
-
/**
|
|
191
|
-
* Inject the fumadocs CSS import into an existing global.css.
|
|
192
|
-
* Returns the modified content, or null if already present.
|
|
193
|
-
*/
|
|
194
337
|
function injectCssImport(existingContent, theme) {
|
|
195
|
-
const importLine = `@import "@farming-labs/${theme}/css";`;
|
|
338
|
+
const importLine = `@import "@farming-labs/theme/${getThemeInfo(theme).nextCssImport}/css";`;
|
|
196
339
|
if (existingContent.includes(importLine)) return null;
|
|
340
|
+
if (existingContent.includes("@farming-labs/theme/") && existingContent.includes("/css")) return null;
|
|
197
341
|
const lines = existingContent.split("\n");
|
|
198
342
|
const lastImportIdx = lines.reduce((acc, l, i) => l.trimStart().startsWith("@import") ? i : acc, -1);
|
|
199
343
|
if (lastImportIdx >= 0) lines.splice(lastImportIdx + 1, 0, importLine);
|
|
200
344
|
else lines.unshift(importLine);
|
|
201
345
|
return lines.join("\n");
|
|
202
346
|
}
|
|
203
|
-
function docsLayoutTemplate() {
|
|
347
|
+
function docsLayoutTemplate(cfg) {
|
|
204
348
|
return `\
|
|
205
|
-
import docsConfig from "
|
|
206
|
-
import { createDocsLayout } from "@farming-labs/
|
|
349
|
+
import docsConfig from "${nextDocsLayoutConfigImport(cfg.useAlias)}";
|
|
350
|
+
import { createDocsLayout } from "@farming-labs/theme";
|
|
207
351
|
|
|
208
352
|
export default createDocsLayout(docsConfig);
|
|
209
353
|
`;
|
|
@@ -282,6 +426,7 @@ Start by reading the [Installation](/${cfg.entry}/installation) guide, then foll
|
|
|
282
426
|
`;
|
|
283
427
|
}
|
|
284
428
|
function installationPageTemplate(cfg) {
|
|
429
|
+
const t = getThemeInfo(cfg.theme);
|
|
285
430
|
return `\
|
|
286
431
|
---
|
|
287
432
|
title: "Installation"
|
|
@@ -308,11 +453,11 @@ Your project includes a \`docs.config.ts\` at the root:
|
|
|
308
453
|
|
|
309
454
|
\`\`\`ts
|
|
310
455
|
import { defineDocs } from "@farming-labs/docs";
|
|
311
|
-
import {
|
|
456
|
+
import { ${t.factory} } from "${t.nextImport}";
|
|
312
457
|
|
|
313
458
|
export default defineDocs({
|
|
314
459
|
entry: "${cfg.entry}",
|
|
315
|
-
theme:
|
|
460
|
+
theme: ${t.factory}({
|
|
316
461
|
ui: { colors: { primary: "#6366f1" } },
|
|
317
462
|
}),
|
|
318
463
|
});
|
|
@@ -339,6 +484,7 @@ Head to the [Quickstart](/${cfg.entry}/quickstart) guide to start writing your f
|
|
|
339
484
|
`;
|
|
340
485
|
}
|
|
341
486
|
function quickstartPageTemplate(cfg) {
|
|
487
|
+
const t = getThemeInfo(cfg.theme);
|
|
342
488
|
return `\
|
|
343
489
|
---
|
|
344
490
|
title: "Quickstart"
|
|
@@ -401,7 +547,7 @@ console.log(greet("World"));
|
|
|
401
547
|
Edit \`docs.config.ts\` to change colors, typography, and component defaults:
|
|
402
548
|
|
|
403
549
|
\`\`\`ts
|
|
404
|
-
theme:
|
|
550
|
+
theme: ${t.factory}({
|
|
405
551
|
ui: {
|
|
406
552
|
colors: { primary: "#22c55e" },
|
|
407
553
|
},
|
|
@@ -419,178 +565,1358 @@ pnpm build
|
|
|
419
565
|
Deploy to Vercel, Netlify, or any Node.js hosting platform.
|
|
420
566
|
`;
|
|
421
567
|
}
|
|
568
|
+
function svelteDocsConfigTemplate(cfg) {
|
|
569
|
+
const t = getThemeInfo(cfg.theme);
|
|
570
|
+
return `\
|
|
571
|
+
import { defineDocs } from "@farming-labs/docs";
|
|
572
|
+
import { ${t.factory} } from "${t.svelteImport}";
|
|
422
573
|
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
const cfg = {
|
|
463
|
-
entry: entryPath,
|
|
464
|
-
theme,
|
|
465
|
-
projectName: pkgJson.name || "My Project"
|
|
466
|
-
};
|
|
467
|
-
const s = p.spinner();
|
|
468
|
-
s.start("Scaffolding docs files");
|
|
469
|
-
const written = [];
|
|
470
|
-
const skipped = [];
|
|
471
|
-
function write(rel, content, overwrite = false) {
|
|
472
|
-
if (writeFileSafe(path.join(cwd, rel), content, overwrite)) written.push(rel);
|
|
473
|
-
else skipped.push(rel);
|
|
474
|
-
}
|
|
475
|
-
write("docs.config.ts", docsConfigTemplate(cfg));
|
|
476
|
-
const existingNextConfig = readFileSafe(path.join(cwd, "next.config.ts")) ?? readFileSafe(path.join(cwd, "next.config.mjs")) ?? readFileSafe(path.join(cwd, "next.config.js"));
|
|
477
|
-
if (existingNextConfig) {
|
|
478
|
-
const configFile = fileExists(path.join(cwd, "next.config.ts")) ? "next.config.ts" : fileExists(path.join(cwd, "next.config.mjs")) ? "next.config.mjs" : "next.config.js";
|
|
479
|
-
const merged = nextConfigMergedTemplate(existingNextConfig);
|
|
480
|
-
if (merged !== existingNextConfig) {
|
|
481
|
-
writeFileSafe(path.join(cwd, configFile), merged, true);
|
|
482
|
-
written.push(configFile + " (updated)");
|
|
483
|
-
} else skipped.push(configFile + " (already configured)");
|
|
484
|
-
} else write("next.config.ts", nextConfigTemplate());
|
|
485
|
-
write("app/layout.tsx", rootLayoutTemplate());
|
|
486
|
-
const globalCssPath = path.join(cwd, "app/global.css");
|
|
487
|
-
const existingGlobalCss = readFileSafe(globalCssPath);
|
|
488
|
-
if (existingGlobalCss) {
|
|
489
|
-
const injected = injectCssImport(existingGlobalCss, theme);
|
|
490
|
-
if (injected) {
|
|
491
|
-
writeFileSafe(globalCssPath, injected, true);
|
|
492
|
-
written.push("app/global.css (updated)");
|
|
493
|
-
} else skipped.push("app/global.css (already configured)");
|
|
494
|
-
} else write("app/global.css", globalCssTemplate(theme));
|
|
495
|
-
write(`app/${entryPath}/layout.tsx`, docsLayoutTemplate());
|
|
496
|
-
write("postcss.config.mjs", postcssConfigTemplate());
|
|
497
|
-
if (!fileExists(path.join(cwd, "tsconfig.json"))) write("tsconfig.json", tsconfigTemplate());
|
|
498
|
-
write(`app/${entryPath}/page.mdx`, welcomePageTemplate(cfg));
|
|
499
|
-
write(`app/${entryPath}/installation/page.mdx`, installationPageTemplate(cfg));
|
|
500
|
-
write(`app/${entryPath}/quickstart/page.mdx`, quickstartPageTemplate(cfg));
|
|
501
|
-
s.stop("Files scaffolded");
|
|
502
|
-
if (written.length > 0) p.log.success(`Created ${written.length} file${written.length > 1 ? "s" : ""}:\n` + written.map((f) => ` ${pc.green("+")} ${f}`).join("\n"));
|
|
503
|
-
if (skipped.length > 0) p.log.info(`Skipped ${skipped.length} existing file${skipped.length > 1 ? "s" : ""}:\n` + skipped.map((f) => ` ${pc.dim("-")} ${f}`).join("\n"));
|
|
504
|
-
const pm = detectPackageManager(cwd);
|
|
505
|
-
p.log.info(`Using ${pc.cyan(pm)} as package manager`);
|
|
506
|
-
const s2 = p.spinner();
|
|
507
|
-
s2.start("Installing dependencies");
|
|
508
|
-
try {
|
|
509
|
-
exec(`${installCommand(pm)} @farming-labs/docs @farming-labs/next @farming-labs/fumadocs`, cwd);
|
|
510
|
-
const devDeps = [
|
|
511
|
-
"@tailwindcss/postcss",
|
|
512
|
-
"postcss",
|
|
513
|
-
"tailwindcss",
|
|
514
|
-
"@types/mdx",
|
|
515
|
-
"@types/node"
|
|
516
|
-
];
|
|
517
|
-
const allDeps = {
|
|
518
|
-
...pkgJson.dependencies,
|
|
519
|
-
...pkgJson.devDependencies
|
|
520
|
-
};
|
|
521
|
-
const missingDevDeps = devDeps.filter((d) => !allDeps[d]);
|
|
522
|
-
if (missingDevDeps.length > 0) exec(`${devInstallCommand(pm)} ${missingDevDeps.join(" ")}`, cwd);
|
|
523
|
-
} catch {
|
|
524
|
-
s2.stop("Failed to install dependencies");
|
|
525
|
-
p.log.error(`Dependency installation failed. Run the install command manually:
|
|
526
|
-
${pc.cyan(`${installCommand(pm)} @farming-labs/docs`)}`);
|
|
527
|
-
p.outro(pc.yellow("Setup partially complete. Install deps and run dev server manually."));
|
|
528
|
-
process.exit(1);
|
|
529
|
-
}
|
|
530
|
-
s2.stop("Dependencies installed");
|
|
531
|
-
const startDev = await p.confirm({
|
|
532
|
-
message: "Start the dev server now?",
|
|
533
|
-
initialValue: true
|
|
534
|
-
});
|
|
535
|
-
if (p.isCancel(startDev) || !startDev) {
|
|
536
|
-
p.log.info(`You can start the dev server later with:
|
|
537
|
-
${pc.cyan(`${pm === "yarn" ? "yarn" : pm + " run"} dev`)}`);
|
|
538
|
-
p.outro(pc.green("Done! Happy documenting."));
|
|
539
|
-
process.exit(0);
|
|
540
|
-
}
|
|
541
|
-
p.log.step("Starting dev server...");
|
|
542
|
-
try {
|
|
543
|
-
const child = await spawnAndWaitFor("npx", [
|
|
544
|
-
"next",
|
|
545
|
-
"dev",
|
|
546
|
-
"--webpack"
|
|
547
|
-
], cwd, "Ready", 6e4);
|
|
548
|
-
const url = `http://localhost:3000/${entryPath}`;
|
|
549
|
-
console.log();
|
|
550
|
-
p.log.success(`Dev server is running! Your docs are live at:\n\n ${pc.cyan(pc.underline(url))}\n\n Press ${pc.dim("Ctrl+C")} to stop the server.`);
|
|
551
|
-
p.outro(pc.green("Happy documenting!"));
|
|
552
|
-
await new Promise((resolve) => {
|
|
553
|
-
child.on("close", () => resolve());
|
|
554
|
-
process.on("SIGINT", () => {
|
|
555
|
-
child.kill("SIGINT");
|
|
556
|
-
resolve();
|
|
557
|
-
});
|
|
558
|
-
process.on("SIGTERM", () => {
|
|
559
|
-
child.kill("SIGTERM");
|
|
560
|
-
resolve();
|
|
561
|
-
});
|
|
562
|
-
});
|
|
563
|
-
} catch (err) {
|
|
564
|
-
p.log.error(`Could not start dev server. Try running manually:
|
|
565
|
-
${pc.cyan("npx next dev --webpack")}`);
|
|
566
|
-
p.outro(pc.yellow("Setup complete. Start the server manually."));
|
|
567
|
-
process.exit(1);
|
|
568
|
-
}
|
|
574
|
+
export default defineDocs({
|
|
575
|
+
entry: "${cfg.entry}",
|
|
576
|
+
theme: ${t.factory}({
|
|
577
|
+
ui: {
|
|
578
|
+
colors: { primary: "#6366f1" },
|
|
579
|
+
},
|
|
580
|
+
}),
|
|
581
|
+
|
|
582
|
+
nav: {
|
|
583
|
+
title: "${cfg.projectName}",
|
|
584
|
+
url: "/${cfg.entry}",
|
|
585
|
+
},
|
|
586
|
+
|
|
587
|
+
breadcrumb: { enabled: true },
|
|
588
|
+
|
|
589
|
+
metadata: {
|
|
590
|
+
titleTemplate: "%s – ${cfg.projectName}",
|
|
591
|
+
description: "Documentation for ${cfg.projectName}",
|
|
592
|
+
},
|
|
593
|
+
});
|
|
594
|
+
`;
|
|
595
|
+
}
|
|
596
|
+
function svelteDocsServerTemplate(cfg) {
|
|
597
|
+
return `\
|
|
598
|
+
import { createDocsServer } from "@farming-labs/svelte/server";
|
|
599
|
+
import config from "${svelteServerConfigImport(cfg.useAlias)}";
|
|
600
|
+
|
|
601
|
+
// preload for production
|
|
602
|
+
const contentFiles = import.meta.glob("/${cfg.entry ?? "docs"}/**/*.{md,mdx,svx}", {
|
|
603
|
+
query: "?raw",
|
|
604
|
+
import: "default",
|
|
605
|
+
eager: true,
|
|
606
|
+
}) as Record<string, string>;
|
|
607
|
+
|
|
608
|
+
export const { load, GET, POST } = createDocsServer({
|
|
609
|
+
...config,
|
|
610
|
+
_preloadedContent: contentFiles,
|
|
611
|
+
});
|
|
612
|
+
`;
|
|
569
613
|
}
|
|
614
|
+
function svelteDocsLayoutTemplate(cfg) {
|
|
615
|
+
return `\
|
|
616
|
+
<script>
|
|
617
|
+
import { DocsLayout } from "@farming-labs/svelte-theme";
|
|
618
|
+
import config from "${svelteLayoutConfigImport(cfg.useAlias)}";
|
|
570
619
|
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
else {
|
|
579
|
-
console.error(pc.red(`Unknown command: ${command}`));
|
|
580
|
-
console.error();
|
|
581
|
-
printHelp();
|
|
582
|
-
process.exit(1);
|
|
583
|
-
}
|
|
620
|
+
let { data, children } = $props();
|
|
621
|
+
<\/script>
|
|
622
|
+
|
|
623
|
+
<DocsLayout tree={data.tree} {config}>
|
|
624
|
+
{@render children()}
|
|
625
|
+
</DocsLayout>
|
|
626
|
+
`;
|
|
584
627
|
}
|
|
585
|
-
function
|
|
586
|
-
|
|
587
|
-
${
|
|
628
|
+
function svelteDocsLayoutServerTemplate(cfg) {
|
|
629
|
+
return `\
|
|
630
|
+
export { load } from "${svelteLayoutServerImport(cfg.useAlias)}";
|
|
631
|
+
`;
|
|
632
|
+
}
|
|
633
|
+
function svelteDocsPageTemplate(cfg) {
|
|
634
|
+
return `\
|
|
635
|
+
<script>
|
|
636
|
+
import { DocsContent } from "@farming-labs/svelte-theme";
|
|
637
|
+
import config from "${sveltePageConfigImport(cfg.useAlias)}";
|
|
588
638
|
|
|
589
|
-
$
|
|
590
|
-
|
|
639
|
+
let { data } = $props();
|
|
640
|
+
<\/script>
|
|
591
641
|
|
|
592
|
-
|
|
593
|
-
|
|
642
|
+
<DocsContent {data} {config} />
|
|
643
|
+
`;
|
|
644
|
+
}
|
|
645
|
+
function svelteRootLayoutTemplate(globalCssRelPath) {
|
|
646
|
+
let cssImport;
|
|
647
|
+
if (globalCssRelPath.startsWith("src/")) cssImport = "./" + globalCssRelPath.slice(4);
|
|
648
|
+
else cssImport = "../" + globalCssRelPath;
|
|
649
|
+
return `\
|
|
650
|
+
<script>
|
|
651
|
+
import "${cssImport}";
|
|
652
|
+
|
|
653
|
+
let { children } = $props();
|
|
654
|
+
<\/script>
|
|
655
|
+
|
|
656
|
+
{@render children()}
|
|
657
|
+
`;
|
|
658
|
+
}
|
|
659
|
+
function svelteGlobalCssTemplate(theme) {
|
|
660
|
+
return `\
|
|
661
|
+
@import "@farming-labs/svelte-theme/${theme}/css";
|
|
662
|
+
`;
|
|
663
|
+
}
|
|
664
|
+
function svelteCssImportLine(theme) {
|
|
665
|
+
return `@import "@farming-labs/svelte-theme/${theme}/css";`;
|
|
666
|
+
}
|
|
667
|
+
function injectSvelteCssImport(existingContent, theme) {
|
|
668
|
+
const importLine = svelteCssImportLine(theme);
|
|
669
|
+
if (existingContent.includes(importLine)) return null;
|
|
670
|
+
const lines = existingContent.split("\n");
|
|
671
|
+
const lastImportIdx = lines.reduce((acc, l, i) => l.trimStart().startsWith("@import") ? i : acc, -1);
|
|
672
|
+
if (lastImportIdx >= 0) lines.splice(lastImportIdx + 1, 0, importLine);
|
|
673
|
+
else lines.unshift(importLine);
|
|
674
|
+
return lines.join("\n");
|
|
675
|
+
}
|
|
676
|
+
function svelteWelcomePageTemplate(cfg) {
|
|
677
|
+
return `\
|
|
678
|
+
---
|
|
679
|
+
title: "Documentation"
|
|
680
|
+
description: "Welcome to ${cfg.projectName} documentation"
|
|
681
|
+
---
|
|
682
|
+
|
|
683
|
+
# Welcome to ${cfg.projectName}
|
|
684
|
+
|
|
685
|
+
Get started with our documentation. Browse the pages on the left to learn more.
|
|
686
|
+
|
|
687
|
+
## Overview
|
|
688
|
+
|
|
689
|
+
This documentation was generated by \`@farming-labs/docs\`. Edit the markdown files in \`${cfg.entry}/\` to customize.
|
|
690
|
+
|
|
691
|
+
## Features
|
|
692
|
+
|
|
693
|
+
- **Markdown Support** — Write docs with standard Markdown
|
|
694
|
+
- **Syntax Highlighting** — Code blocks with automatic highlighting
|
|
695
|
+
- **Dark Mode** — Built-in theme switching
|
|
696
|
+
- **Search** — Full-text search across all pages
|
|
697
|
+
- **Responsive** — Works on any screen size
|
|
698
|
+
|
|
699
|
+
---
|
|
700
|
+
|
|
701
|
+
## Next Steps
|
|
702
|
+
|
|
703
|
+
Start by reading the [Installation](/${cfg.entry}/installation) guide, then follow the [Quickstart](/${cfg.entry}/quickstart) to build something.
|
|
704
|
+
`;
|
|
705
|
+
}
|
|
706
|
+
function svelteInstallationPageTemplate(cfg) {
|
|
707
|
+
const t = getThemeInfo(cfg.theme);
|
|
708
|
+
return `\
|
|
709
|
+
---
|
|
710
|
+
title: "Installation"
|
|
711
|
+
description: "How to install and set up ${cfg.projectName}"
|
|
712
|
+
---
|
|
713
|
+
|
|
714
|
+
# Installation
|
|
715
|
+
|
|
716
|
+
Follow these steps to install and configure ${cfg.projectName}.
|
|
717
|
+
|
|
718
|
+
## Prerequisites
|
|
719
|
+
|
|
720
|
+
- Node.js 18+
|
|
721
|
+
- A package manager (pnpm, npm, or yarn)
|
|
722
|
+
|
|
723
|
+
## Install Dependencies
|
|
724
|
+
|
|
725
|
+
\`\`\`bash
|
|
726
|
+
pnpm add @farming-labs/docs @farming-labs/svelte @farming-labs/svelte-theme
|
|
727
|
+
\`\`\`
|
|
728
|
+
|
|
729
|
+
## Configuration
|
|
730
|
+
|
|
731
|
+
Your project includes a \`docs.config.ts\` in \`src/lib/\`:
|
|
732
|
+
|
|
733
|
+
\`\`\`ts title="src/lib/docs.config.ts"
|
|
734
|
+
import { defineDocs } from "@farming-labs/docs";
|
|
735
|
+
import { ${t.factory} } from "${t.svelteImport}";
|
|
736
|
+
|
|
737
|
+
export default defineDocs({
|
|
738
|
+
entry: "${cfg.entry}",
|
|
739
|
+
contentDir: "${cfg.entry}",
|
|
740
|
+
theme: ${t.factory}({
|
|
741
|
+
ui: { colors: { primary: "#6366f1" } },
|
|
742
|
+
}),
|
|
743
|
+
});
|
|
744
|
+
\`\`\`
|
|
745
|
+
|
|
746
|
+
## Project Structure
|
|
747
|
+
|
|
748
|
+
\`\`\`
|
|
749
|
+
${cfg.entry}/ # Markdown content
|
|
750
|
+
page.md # /${cfg.entry}
|
|
751
|
+
installation/
|
|
752
|
+
page.md # /${cfg.entry}/installation
|
|
753
|
+
quickstart/
|
|
754
|
+
page.md # /${cfg.entry}/quickstart
|
|
755
|
+
src/
|
|
756
|
+
lib/
|
|
757
|
+
docs.config.ts # Docs configuration
|
|
758
|
+
docs.server.ts # Server-side docs loader
|
|
759
|
+
routes/
|
|
760
|
+
${cfg.entry}/
|
|
761
|
+
+layout.svelte # Docs layout
|
|
762
|
+
+layout.server.js # Layout data loader
|
|
763
|
+
[...slug]/
|
|
764
|
+
+page.svelte # Dynamic doc page
|
|
765
|
+
\`\`\`
|
|
766
|
+
|
|
767
|
+
## What's Next?
|
|
768
|
+
|
|
769
|
+
Head to the [Quickstart](/${cfg.entry}/quickstart) guide to start writing your first page.
|
|
770
|
+
`;
|
|
771
|
+
}
|
|
772
|
+
function svelteQuickstartPageTemplate(cfg) {
|
|
773
|
+
const t = getThemeInfo(cfg.theme);
|
|
774
|
+
return `\
|
|
775
|
+
---
|
|
776
|
+
title: "Quickstart"
|
|
777
|
+
description: "Get up and running in minutes"
|
|
778
|
+
---
|
|
779
|
+
|
|
780
|
+
# Quickstart
|
|
781
|
+
|
|
782
|
+
This guide walks you through creating your first documentation page.
|
|
783
|
+
|
|
784
|
+
## Creating a Page
|
|
785
|
+
|
|
786
|
+
Create a new folder under \`${cfg.entry}/\` with a \`page.md\` file:
|
|
787
|
+
|
|
788
|
+
\`\`\`bash
|
|
789
|
+
mkdir -p ${cfg.entry}/my-page
|
|
790
|
+
\`\`\`
|
|
791
|
+
|
|
792
|
+
Then create \`${cfg.entry}/my-page/page.md\`:
|
|
793
|
+
|
|
794
|
+
\`\`\`md
|
|
795
|
+
---
|
|
796
|
+
title: "My Page"
|
|
797
|
+
description: "A custom documentation page"
|
|
798
|
+
---
|
|
799
|
+
|
|
800
|
+
# My Page
|
|
801
|
+
|
|
802
|
+
Write your content here using **Markdown**.
|
|
803
|
+
\`\`\`
|
|
804
|
+
|
|
805
|
+
Your page is now available at \`/${cfg.entry}/my-page\`.
|
|
806
|
+
|
|
807
|
+
## Code Blocks
|
|
808
|
+
|
|
809
|
+
Code blocks are automatically syntax-highlighted:
|
|
810
|
+
|
|
811
|
+
\`\`\`typescript
|
|
812
|
+
function greet(name: string): string {
|
|
813
|
+
return \\\`Hello, \\\${name}!\\\`;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
console.log(greet("World"));
|
|
817
|
+
\`\`\`
|
|
818
|
+
|
|
819
|
+
## Customizing the Theme
|
|
820
|
+
|
|
821
|
+
Edit \`src/lib/docs.config.ts\` to change colors, typography, and component defaults:
|
|
822
|
+
|
|
823
|
+
\`\`\`ts title="src/lib/docs.config.ts"
|
|
824
|
+
theme: ${t.factory}({
|
|
825
|
+
ui: {
|
|
826
|
+
colors: { primary: "#22c55e" },
|
|
827
|
+
},
|
|
828
|
+
}),
|
|
829
|
+
\`\`\`
|
|
830
|
+
|
|
831
|
+
## Deploying
|
|
832
|
+
|
|
833
|
+
Build your docs for production:
|
|
834
|
+
|
|
835
|
+
\`\`\`bash
|
|
836
|
+
pnpm build
|
|
837
|
+
\`\`\`
|
|
838
|
+
|
|
839
|
+
Deploy to Vercel, Netlify, or any Node.js hosting platform.
|
|
840
|
+
`;
|
|
841
|
+
}
|
|
842
|
+
function astroDocsConfigTemplate(cfg) {
|
|
843
|
+
const t = getThemeInfo(cfg.theme);
|
|
844
|
+
return `\
|
|
845
|
+
import { defineDocs } from "@farming-labs/docs";
|
|
846
|
+
import { ${t.factory} } from "${t.astroImport}";
|
|
847
|
+
|
|
848
|
+
export default defineDocs({
|
|
849
|
+
entry: "${cfg.entry}",
|
|
850
|
+
contentDir: "${cfg.entry}",
|
|
851
|
+
theme: ${t.factory}({
|
|
852
|
+
ui: {
|
|
853
|
+
colors: { primary: "#6366f1" },
|
|
854
|
+
},
|
|
855
|
+
}),
|
|
856
|
+
|
|
857
|
+
nav: {
|
|
858
|
+
title: "${cfg.projectName}",
|
|
859
|
+
url: "/${cfg.entry}",
|
|
860
|
+
},
|
|
861
|
+
|
|
862
|
+
breadcrumb: { enabled: true },
|
|
863
|
+
|
|
864
|
+
metadata: {
|
|
865
|
+
titleTemplate: "%s – ${cfg.projectName}",
|
|
866
|
+
description: "Documentation for ${cfg.projectName}",
|
|
867
|
+
},
|
|
868
|
+
});
|
|
869
|
+
`;
|
|
870
|
+
}
|
|
871
|
+
function astroDocsServerTemplate(cfg) {
|
|
872
|
+
return `\
|
|
873
|
+
import { createDocsServer } from "@farming-labs/astro/server";
|
|
874
|
+
import config from "${astroServerConfigImport(cfg.useAlias)}";
|
|
875
|
+
|
|
876
|
+
const contentFiles = import.meta.glob("/${cfg.entry ?? "docs"}/**/*.{md,mdx}", {
|
|
877
|
+
query: "?raw",
|
|
878
|
+
import: "default",
|
|
879
|
+
eager: true,
|
|
880
|
+
}) as Record<string, string>;
|
|
881
|
+
|
|
882
|
+
export const { load, GET, POST } = createDocsServer({
|
|
883
|
+
...config,
|
|
884
|
+
_preloadedContent: contentFiles,
|
|
885
|
+
});
|
|
886
|
+
`;
|
|
887
|
+
}
|
|
888
|
+
const ASTRO_ADAPTER_INFO = {
|
|
889
|
+
vercel: {
|
|
890
|
+
pkg: "@astrojs/vercel",
|
|
891
|
+
import: "@astrojs/vercel"
|
|
892
|
+
},
|
|
893
|
+
netlify: {
|
|
894
|
+
pkg: "@astrojs/netlify",
|
|
895
|
+
import: "@astrojs/netlify"
|
|
896
|
+
},
|
|
897
|
+
node: {
|
|
898
|
+
pkg: "@astrojs/node",
|
|
899
|
+
import: "@astrojs/node"
|
|
900
|
+
},
|
|
901
|
+
cloudflare: {
|
|
902
|
+
pkg: "@astrojs/cloudflare",
|
|
903
|
+
import: "@astrojs/cloudflare"
|
|
904
|
+
}
|
|
905
|
+
};
|
|
906
|
+
function getAstroAdapterPkg(adapter) {
|
|
907
|
+
return ASTRO_ADAPTER_INFO[adapter]?.pkg ?? ASTRO_ADAPTER_INFO.vercel.pkg;
|
|
908
|
+
}
|
|
909
|
+
function astroConfigTemplate(adapter = "vercel") {
|
|
910
|
+
const info = ASTRO_ADAPTER_INFO[adapter] ?? ASTRO_ADAPTER_INFO.vercel;
|
|
911
|
+
const adapterCall = adapter === "node" ? `${adapter}({ mode: "standalone" })` : `${adapter}()`;
|
|
912
|
+
return `\
|
|
913
|
+
import { defineConfig } from "astro/config";
|
|
914
|
+
import ${adapter} from "${info.import}";
|
|
915
|
+
|
|
916
|
+
export default defineConfig({
|
|
917
|
+
output: "server",
|
|
918
|
+
adapter: ${adapterCall},
|
|
919
|
+
});
|
|
920
|
+
`;
|
|
921
|
+
}
|
|
922
|
+
function astroDocsPageTemplate(cfg) {
|
|
923
|
+
return `\
|
|
924
|
+
---
|
|
925
|
+
import DocsLayout from "@farming-labs/astro-theme/src/components/DocsLayout.astro";
|
|
926
|
+
import DocsContent from "@farming-labs/astro-theme/src/components/DocsContent.astro";
|
|
927
|
+
import SearchDialog from "@farming-labs/astro-theme/src/components/SearchDialog.astro";
|
|
928
|
+
import config from "${astroPageConfigImport(cfg.useAlias, 2)}";
|
|
929
|
+
import { load } from "${astroPageServerImport(cfg.useAlias, 2)}";
|
|
930
|
+
import "${`@farming-labs/astro-theme/${getThemeInfo(cfg.theme).astroCssTheme}/css`}";
|
|
931
|
+
|
|
932
|
+
const data = await load(Astro.url.pathname);
|
|
933
|
+
---
|
|
934
|
+
|
|
935
|
+
<html lang="en">
|
|
936
|
+
<head>
|
|
937
|
+
<meta charset="utf-8" />
|
|
938
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
939
|
+
<title>{data.title} – Docs</title>
|
|
940
|
+
</head>
|
|
941
|
+
<body>
|
|
942
|
+
<DocsLayout tree={data.tree} config={config}>
|
|
943
|
+
<DocsContent data={data} config={config} />
|
|
944
|
+
</DocsLayout>
|
|
945
|
+
<SearchDialog config={config} />
|
|
946
|
+
</body>
|
|
947
|
+
</html>
|
|
948
|
+
`;
|
|
949
|
+
}
|
|
950
|
+
function astroDocsIndexTemplate(cfg) {
|
|
951
|
+
return `\
|
|
952
|
+
---
|
|
953
|
+
import DocsLayout from "@farming-labs/astro-theme/src/components/DocsLayout.astro";
|
|
954
|
+
import DocsContent from "@farming-labs/astro-theme/src/components/DocsContent.astro";
|
|
955
|
+
import SearchDialog from "@farming-labs/astro-theme/src/components/SearchDialog.astro";
|
|
956
|
+
import config from "${astroPageConfigImport(cfg.useAlias, 2)}";
|
|
957
|
+
import { load } from "${astroPageServerImport(cfg.useAlias, 2)}";
|
|
958
|
+
import "${`@farming-labs/astro-theme/${getThemeInfo(cfg.theme).astroCssTheme}/css`}";
|
|
959
|
+
|
|
960
|
+
const data = await load(Astro.url.pathname);
|
|
961
|
+
---
|
|
962
|
+
|
|
963
|
+
<html lang="en">
|
|
964
|
+
<head>
|
|
965
|
+
<meta charset="utf-8" />
|
|
966
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
967
|
+
<title>{data.title} – Docs</title>
|
|
968
|
+
</head>
|
|
969
|
+
<body>
|
|
970
|
+
<DocsLayout tree={data.tree} config={config}>
|
|
971
|
+
<DocsContent data={data} config={config} />
|
|
972
|
+
</DocsLayout>
|
|
973
|
+
<SearchDialog config={config} />
|
|
974
|
+
</body>
|
|
975
|
+
</html>
|
|
976
|
+
`;
|
|
977
|
+
}
|
|
978
|
+
function astroApiRouteTemplate(cfg) {
|
|
979
|
+
return `\
|
|
980
|
+
import type { APIRoute } from "astro";
|
|
981
|
+
import { GET as docsGET, POST as docsPOST } from "${astroPageServerImport(cfg.useAlias, 2)}";
|
|
982
|
+
|
|
983
|
+
export const GET: APIRoute = async ({ request }) => {
|
|
984
|
+
return docsGET({ request });
|
|
985
|
+
};
|
|
986
|
+
|
|
987
|
+
export const POST: APIRoute = async ({ request }) => {
|
|
988
|
+
return docsPOST({ request });
|
|
989
|
+
};
|
|
990
|
+
`;
|
|
991
|
+
}
|
|
992
|
+
function astroGlobalCssTemplate(theme) {
|
|
993
|
+
return `\
|
|
994
|
+
@import "@farming-labs/astro-theme/${theme}/css";
|
|
995
|
+
`;
|
|
996
|
+
}
|
|
997
|
+
function astroCssImportLine(theme) {
|
|
998
|
+
return `@import "@farming-labs/astro-theme/${theme}/css";`;
|
|
999
|
+
}
|
|
1000
|
+
function injectAstroCssImport(existingContent, theme) {
|
|
1001
|
+
const importLine = astroCssImportLine(theme);
|
|
1002
|
+
if (existingContent.includes(importLine)) return null;
|
|
1003
|
+
const lines = existingContent.split("\n");
|
|
1004
|
+
const lastImportIdx = lines.reduce((acc, l, i) => l.trimStart().startsWith("@import") ? i : acc, -1);
|
|
1005
|
+
if (lastImportIdx >= 0) lines.splice(lastImportIdx + 1, 0, importLine);
|
|
1006
|
+
else lines.unshift(importLine);
|
|
1007
|
+
return lines.join("\n");
|
|
1008
|
+
}
|
|
1009
|
+
function astroWelcomePageTemplate(cfg) {
|
|
1010
|
+
return `\
|
|
1011
|
+
---
|
|
1012
|
+
title: "Documentation"
|
|
1013
|
+
description: "Welcome to ${cfg.projectName} documentation"
|
|
1014
|
+
---
|
|
1015
|
+
|
|
1016
|
+
# Welcome to ${cfg.projectName}
|
|
1017
|
+
|
|
1018
|
+
Get started with our documentation. Browse the pages on the left to learn more.
|
|
1019
|
+
|
|
1020
|
+
## Overview
|
|
1021
|
+
|
|
1022
|
+
This documentation was generated by \`@farming-labs/docs\`. Edit the markdown files in \`${cfg.entry}/\` to customize.
|
|
1023
|
+
|
|
1024
|
+
## Features
|
|
1025
|
+
|
|
1026
|
+
- **Markdown Support** — Write docs with standard Markdown
|
|
1027
|
+
- **Syntax Highlighting** — Code blocks with automatic highlighting
|
|
1028
|
+
- **Dark Mode** — Built-in theme switching
|
|
1029
|
+
- **Search** — Full-text search across all pages
|
|
1030
|
+
- **Responsive** — Works on any screen size
|
|
1031
|
+
|
|
1032
|
+
---
|
|
1033
|
+
|
|
1034
|
+
## Next Steps
|
|
1035
|
+
|
|
1036
|
+
Start by reading the [Installation](/${cfg.entry}/installation) guide, then follow the [Quickstart](/${cfg.entry}/quickstart) to build something.
|
|
1037
|
+
`;
|
|
1038
|
+
}
|
|
1039
|
+
function astroInstallationPageTemplate(cfg) {
|
|
1040
|
+
const t = getThemeInfo(cfg.theme);
|
|
1041
|
+
return `\
|
|
1042
|
+
---
|
|
1043
|
+
title: "Installation"
|
|
1044
|
+
description: "How to install and set up ${cfg.projectName}"
|
|
1045
|
+
---
|
|
1046
|
+
|
|
1047
|
+
# Installation
|
|
1048
|
+
|
|
1049
|
+
Follow these steps to install and configure ${cfg.projectName}.
|
|
1050
|
+
|
|
1051
|
+
## Prerequisites
|
|
1052
|
+
|
|
1053
|
+
- Node.js 18+
|
|
1054
|
+
- A package manager (pnpm, npm, or yarn)
|
|
1055
|
+
|
|
1056
|
+
## Install Dependencies
|
|
1057
|
+
|
|
1058
|
+
\\\`\\\`\\\`bash
|
|
1059
|
+
pnpm add @farming-labs/docs @farming-labs/astro @farming-labs/astro-theme
|
|
1060
|
+
\\\`\\\`\\\`
|
|
1061
|
+
|
|
1062
|
+
## Configuration
|
|
1063
|
+
|
|
1064
|
+
Your project includes a \\\`docs.config.ts\\\` in \\\`src/lib/\\\`:
|
|
1065
|
+
|
|
1066
|
+
\\\`\\\`\\\`ts title="src/lib/docs.config.ts"
|
|
1067
|
+
import { defineDocs } from "@farming-labs/docs";
|
|
1068
|
+
import { ${t.factory} } from "${t.astroImport}";
|
|
1069
|
+
|
|
1070
|
+
export default defineDocs({
|
|
1071
|
+
entry: "${cfg.entry}",
|
|
1072
|
+
contentDir: "${cfg.entry}",
|
|
1073
|
+
theme: ${t.factory}({
|
|
1074
|
+
ui: { colors: { primary: "#6366f1" } },
|
|
1075
|
+
}),
|
|
1076
|
+
});
|
|
1077
|
+
\\\`\\\`\\\`
|
|
1078
|
+
|
|
1079
|
+
## Project Structure
|
|
1080
|
+
|
|
1081
|
+
\\\`\\\`\\\`
|
|
1082
|
+
${cfg.entry}/ # Markdown content
|
|
1083
|
+
page.md # /${cfg.entry}
|
|
1084
|
+
installation/
|
|
1085
|
+
page.md # /${cfg.entry}/installation
|
|
1086
|
+
quickstart/
|
|
1087
|
+
page.md # /${cfg.entry}/quickstart
|
|
1088
|
+
src/
|
|
1089
|
+
lib/
|
|
1090
|
+
docs.config.ts # Docs configuration
|
|
1091
|
+
docs.server.ts # Server-side docs loader
|
|
1092
|
+
pages/
|
|
1093
|
+
${cfg.entry}/
|
|
1094
|
+
index.astro # Docs index page
|
|
1095
|
+
[...slug].astro # Dynamic doc page
|
|
1096
|
+
api/
|
|
1097
|
+
${cfg.entry}.ts # Search/AI API route
|
|
1098
|
+
\\\`\\\`\\\`
|
|
1099
|
+
|
|
1100
|
+
## What's Next?
|
|
1101
|
+
|
|
1102
|
+
Head to the [Quickstart](/${cfg.entry}/quickstart) guide to start writing your first page.
|
|
1103
|
+
`;
|
|
1104
|
+
}
|
|
1105
|
+
function astroQuickstartPageTemplate(cfg) {
|
|
1106
|
+
const t = getThemeInfo(cfg.theme);
|
|
1107
|
+
return `\
|
|
1108
|
+
---
|
|
1109
|
+
title: "Quickstart"
|
|
1110
|
+
description: "Get up and running in minutes"
|
|
1111
|
+
---
|
|
1112
|
+
|
|
1113
|
+
# Quickstart
|
|
1114
|
+
|
|
1115
|
+
This guide walks you through creating your first documentation page.
|
|
1116
|
+
|
|
1117
|
+
## Creating a Page
|
|
1118
|
+
|
|
1119
|
+
Create a new folder under \\\`${cfg.entry}/\\\` with a \\\`page.md\\\` file:
|
|
1120
|
+
|
|
1121
|
+
\\\`\\\`\\\`bash
|
|
1122
|
+
mkdir -p ${cfg.entry}/my-page
|
|
1123
|
+
\\\`\\\`\\\`
|
|
1124
|
+
|
|
1125
|
+
Then create \\\`${cfg.entry}/my-page/page.md\\\`:
|
|
1126
|
+
|
|
1127
|
+
\\\`\\\`\\\`md
|
|
1128
|
+
---
|
|
1129
|
+
title: "My Page"
|
|
1130
|
+
description: "A custom documentation page"
|
|
1131
|
+
---
|
|
1132
|
+
|
|
1133
|
+
# My Page
|
|
1134
|
+
|
|
1135
|
+
Write your content here using **Markdown**.
|
|
1136
|
+
\\\`\\\`\\\`
|
|
1137
|
+
|
|
1138
|
+
Your page is now available at \\\`/${cfg.entry}/my-page\\\`.
|
|
1139
|
+
|
|
1140
|
+
## Customizing the Theme
|
|
1141
|
+
|
|
1142
|
+
Edit \\\`src/lib/docs.config.ts\\\` to change colors, typography, and component defaults:
|
|
1143
|
+
|
|
1144
|
+
\\\`\\\`\\\`ts title="src/lib/docs.config.ts"
|
|
1145
|
+
theme: ${t.factory}({
|
|
1146
|
+
ui: {
|
|
1147
|
+
colors: { primary: "#22c55e" },
|
|
1148
|
+
},
|
|
1149
|
+
}),
|
|
1150
|
+
\\\`\\\`\\\`
|
|
1151
|
+
|
|
1152
|
+
## Deploying
|
|
1153
|
+
|
|
1154
|
+
Build your docs for production:
|
|
1155
|
+
|
|
1156
|
+
\\\`\\\`\\\`bash
|
|
1157
|
+
pnpm build
|
|
1158
|
+
\\\`\\\`\\\`
|
|
1159
|
+
|
|
1160
|
+
Deploy to Vercel, Netlify, or any Node.js hosting platform.
|
|
1161
|
+
`;
|
|
1162
|
+
}
|
|
1163
|
+
function nuxtDocsConfigTemplate(cfg) {
|
|
1164
|
+
const t = getThemeInfo(cfg.theme);
|
|
1165
|
+
return `\
|
|
1166
|
+
import { defineDocs } from "@farming-labs/docs";
|
|
1167
|
+
import { ${t.factory} } from "${t.nuxtImport}";
|
|
1168
|
+
|
|
1169
|
+
export default defineDocs({
|
|
1170
|
+
entry: "${cfg.entry}",
|
|
1171
|
+
contentDir: "${cfg.entry}",
|
|
1172
|
+
theme: ${t.factory}({
|
|
1173
|
+
ui: {
|
|
1174
|
+
colors: { primary: "#6366f1" },
|
|
1175
|
+
},
|
|
1176
|
+
}),
|
|
1177
|
+
|
|
1178
|
+
nav: {
|
|
1179
|
+
title: "${cfg.projectName}",
|
|
1180
|
+
url: "/${cfg.entry}",
|
|
1181
|
+
},
|
|
1182
|
+
|
|
1183
|
+
breadcrumb: { enabled: true },
|
|
1184
|
+
|
|
1185
|
+
metadata: {
|
|
1186
|
+
titleTemplate: "%s – ${cfg.projectName}",
|
|
1187
|
+
description: "Documentation for ${cfg.projectName}",
|
|
1188
|
+
},
|
|
1189
|
+
});
|
|
1190
|
+
`;
|
|
1191
|
+
}
|
|
1192
|
+
function nuxtDocsServerTemplate(cfg) {
|
|
1193
|
+
return `\
|
|
1194
|
+
import { createDocsServer } from "@farming-labs/nuxt/server";
|
|
1195
|
+
import config from "../../docs.config";
|
|
1196
|
+
|
|
1197
|
+
const contentFiles = import.meta.glob("/${cfg.entry ?? "docs"}/**/*.{md,mdx}", {
|
|
1198
|
+
query: "?raw",
|
|
1199
|
+
import: "default",
|
|
1200
|
+
eager: true,
|
|
1201
|
+
}) as Record<string, string>;
|
|
1202
|
+
|
|
1203
|
+
export const docsServer = createDocsServer({
|
|
1204
|
+
...config,
|
|
1205
|
+
_preloadedContent: contentFiles,
|
|
1206
|
+
});
|
|
1207
|
+
`;
|
|
1208
|
+
}
|
|
1209
|
+
function nuxtServerApiDocsGetTemplate() {
|
|
1210
|
+
return `\
|
|
1211
|
+
import { getRequestURL } from "h3";
|
|
1212
|
+
import { docsServer } from "../utils/docs-server";
|
|
1213
|
+
|
|
1214
|
+
export default defineEventHandler((event) => {
|
|
1215
|
+
const url = getRequestURL(event);
|
|
1216
|
+
const request = new Request(url.href, {
|
|
1217
|
+
method: event.method,
|
|
1218
|
+
headers: event.headers,
|
|
1219
|
+
});
|
|
1220
|
+
return docsServer.GET({ request });
|
|
1221
|
+
});
|
|
1222
|
+
`;
|
|
1223
|
+
}
|
|
1224
|
+
function nuxtServerApiDocsPostTemplate() {
|
|
1225
|
+
return `\
|
|
1226
|
+
import { getRequestURL, readRawBody } from "h3";
|
|
1227
|
+
import { docsServer } from "../utils/docs-server";
|
|
1228
|
+
|
|
1229
|
+
export default defineEventHandler(async (event) => {
|
|
1230
|
+
const url = getRequestURL(event);
|
|
1231
|
+
const body = await readRawBody(event);
|
|
1232
|
+
const request = new Request(url.href, {
|
|
1233
|
+
method: "POST",
|
|
1234
|
+
headers: event.headers,
|
|
1235
|
+
body: body ?? undefined,
|
|
1236
|
+
});
|
|
1237
|
+
return docsServer.POST({ request });
|
|
1238
|
+
});
|
|
1239
|
+
`;
|
|
1240
|
+
}
|
|
1241
|
+
function nuxtServerApiDocsLoadTemplate() {
|
|
1242
|
+
return `\
|
|
1243
|
+
import { getQuery } from "h3";
|
|
1244
|
+
import { docsServer } from "../../utils/docs-server";
|
|
1245
|
+
|
|
1246
|
+
export default defineEventHandler(async (event) => {
|
|
1247
|
+
const query = getQuery(event);
|
|
1248
|
+
const pathname = (query.pathname as string) ?? "/docs";
|
|
1249
|
+
return docsServer.load(pathname);
|
|
1250
|
+
});
|
|
1251
|
+
`;
|
|
1252
|
+
}
|
|
1253
|
+
function nuxtDocsPageTemplate(cfg) {
|
|
1254
|
+
return `\
|
|
1255
|
+
<script setup lang="ts">
|
|
1256
|
+
import { DocsLayout, DocsContent } from "@farming-labs/nuxt-theme";
|
|
1257
|
+
import config from "${cfg.useAlias ? "~/docs.config" : "../../docs.config"}";
|
|
1258
|
+
|
|
1259
|
+
const route = useRoute();
|
|
1260
|
+
const pathname = computed(() => route.path);
|
|
1261
|
+
|
|
1262
|
+
const { data, error } = await useAsyncData(\`docs-\${pathname.value}\`, () =>
|
|
1263
|
+
$fetch("/api/docs/load", {
|
|
1264
|
+
query: { pathname: pathname.value },
|
|
1265
|
+
})
|
|
1266
|
+
);
|
|
1267
|
+
|
|
1268
|
+
if (error.value) {
|
|
1269
|
+
throw createError({
|
|
1270
|
+
statusCode: 404,
|
|
1271
|
+
statusMessage: "Page not found",
|
|
1272
|
+
});
|
|
1273
|
+
}
|
|
1274
|
+
<\/script>
|
|
1275
|
+
|
|
1276
|
+
<template>
|
|
1277
|
+
<div v-if="data" class="fd-docs-wrapper">
|
|
1278
|
+
<DocsLayout :tree="data.tree" :config="config">
|
|
1279
|
+
<DocsContent :data="data" :config="config" />
|
|
1280
|
+
</DocsLayout>
|
|
1281
|
+
</div>
|
|
1282
|
+
</template>
|
|
1283
|
+
`;
|
|
1284
|
+
}
|
|
1285
|
+
function nuxtConfigTemplate(cfg) {
|
|
1286
|
+
return `\
|
|
1287
|
+
export default defineNuxtConfig({
|
|
1288
|
+
compatibilityDate: "2024-11-01",
|
|
1289
|
+
|
|
1290
|
+
css: ["@farming-labs/nuxt-theme/${getThemeInfo(cfg.theme).nuxtCssTheme}/css"],
|
|
1291
|
+
|
|
1292
|
+
vite: {
|
|
1293
|
+
optimizeDeps: {
|
|
1294
|
+
include: ["@farming-labs/docs", "@farming-labs/nuxt", "@farming-labs/nuxt-theme"],
|
|
1295
|
+
},
|
|
1296
|
+
},
|
|
1297
|
+
|
|
1298
|
+
nitro: {
|
|
1299
|
+
moduleSideEffects: ["@farming-labs/nuxt/server"],
|
|
1300
|
+
},
|
|
1301
|
+
});
|
|
1302
|
+
`;
|
|
1303
|
+
}
|
|
1304
|
+
function nuxtWelcomePageTemplate(cfg) {
|
|
1305
|
+
return `\
|
|
1306
|
+
---
|
|
1307
|
+
order: 1
|
|
1308
|
+
title: Documentation
|
|
1309
|
+
description: Welcome to ${cfg.projectName} documentation
|
|
1310
|
+
icon: book
|
|
1311
|
+
---
|
|
1312
|
+
|
|
1313
|
+
# Welcome to ${cfg.projectName}
|
|
1314
|
+
|
|
1315
|
+
Get started with our documentation. Browse the pages on the left to learn more.
|
|
1316
|
+
|
|
1317
|
+
## Overview
|
|
1318
|
+
|
|
1319
|
+
This documentation was generated by \`@farming-labs/docs\`. Edit the markdown files in \`${cfg.entry}/\` to customize.
|
|
1320
|
+
|
|
1321
|
+
## Features
|
|
1322
|
+
|
|
1323
|
+
- **Markdown Support** — Write docs with standard Markdown
|
|
1324
|
+
- **Syntax Highlighting** — Code blocks with automatic highlighting
|
|
1325
|
+
- **Dark Mode** — Built-in theme switching
|
|
1326
|
+
- **Search** — Full-text search across all pages (⌘K)
|
|
1327
|
+
- **Responsive** — Works on any screen size
|
|
1328
|
+
|
|
1329
|
+
---
|
|
1330
|
+
|
|
1331
|
+
## Next Steps
|
|
1332
|
+
|
|
1333
|
+
Start by reading the [Installation](/${cfg.entry}/installation) guide, then follow the [Quickstart](/${cfg.entry}/quickstart) to build something.
|
|
1334
|
+
`;
|
|
1335
|
+
}
|
|
1336
|
+
function nuxtInstallationPageTemplate(cfg) {
|
|
1337
|
+
const t = getThemeInfo(cfg.theme);
|
|
1338
|
+
return `\
|
|
1339
|
+
---
|
|
1340
|
+
order: 3
|
|
1341
|
+
title: Installation
|
|
1342
|
+
description: How to install and set up ${cfg.projectName}
|
|
1343
|
+
icon: terminal
|
|
1344
|
+
---
|
|
1345
|
+
|
|
1346
|
+
# Installation
|
|
1347
|
+
|
|
1348
|
+
Follow these steps to install and configure ${cfg.projectName}.
|
|
1349
|
+
|
|
1350
|
+
## Prerequisites
|
|
1351
|
+
|
|
1352
|
+
- Node.js 18+
|
|
1353
|
+
- A package manager (pnpm, npm, or yarn)
|
|
1354
|
+
|
|
1355
|
+
## Install Dependencies
|
|
1356
|
+
|
|
1357
|
+
\`\`\`bash
|
|
1358
|
+
pnpm add @farming-labs/docs @farming-labs/nuxt @farming-labs/nuxt-theme
|
|
1359
|
+
\`\`\`
|
|
1360
|
+
|
|
1361
|
+
## Configuration
|
|
1362
|
+
|
|
1363
|
+
Your project includes a \`docs.config.ts\` at the root:
|
|
1364
|
+
|
|
1365
|
+
\`\`\`ts title="docs.config.ts"
|
|
1366
|
+
import { defineDocs } from "@farming-labs/docs";
|
|
1367
|
+
import { ${t.factory} } from "${t.nuxtImport}";
|
|
1368
|
+
|
|
1369
|
+
export default defineDocs({
|
|
1370
|
+
entry: "${cfg.entry}",
|
|
1371
|
+
contentDir: "${cfg.entry}",
|
|
1372
|
+
theme: ${t.factory}({
|
|
1373
|
+
ui: { colors: { primary: "#6366f1" } },
|
|
1374
|
+
}),
|
|
1375
|
+
});
|
|
1376
|
+
\`\`\`
|
|
1377
|
+
|
|
1378
|
+
## Project Structure
|
|
1379
|
+
|
|
1380
|
+
\`\`\`
|
|
1381
|
+
${cfg.entry}/ # Markdown content
|
|
1382
|
+
page.md
|
|
1383
|
+
installation/page.md
|
|
1384
|
+
quickstart/page.md
|
|
1385
|
+
server/
|
|
1386
|
+
utils/docs-server.ts # createDocsServer + preloaded content
|
|
1387
|
+
api/docs/
|
|
1388
|
+
load.get.ts # Page data API
|
|
1389
|
+
docs.get.ts # Search API
|
|
1390
|
+
docs.post.ts # AI chat API
|
|
1391
|
+
pages/
|
|
1392
|
+
${cfg.entry}/[[...slug]].vue # Docs catch-all page
|
|
1393
|
+
docs.config.ts
|
|
1394
|
+
nuxt.config.ts
|
|
1395
|
+
\`\`\`
|
|
1396
|
+
|
|
1397
|
+
## What's Next?
|
|
1398
|
+
|
|
1399
|
+
Head to the [Quickstart](/${cfg.entry}/quickstart) guide to start writing your first page.
|
|
1400
|
+
`;
|
|
1401
|
+
}
|
|
1402
|
+
function nuxtQuickstartPageTemplate(cfg) {
|
|
1403
|
+
const t = getThemeInfo(cfg.theme);
|
|
1404
|
+
return `\
|
|
1405
|
+
---
|
|
1406
|
+
order: 2
|
|
1407
|
+
title: Quickstart
|
|
1408
|
+
description: Get up and running in minutes
|
|
1409
|
+
icon: rocket
|
|
1410
|
+
---
|
|
1411
|
+
|
|
1412
|
+
# Quickstart
|
|
1413
|
+
|
|
1414
|
+
This guide walks you through creating your first documentation page.
|
|
1415
|
+
|
|
1416
|
+
## Creating a Page
|
|
1417
|
+
|
|
1418
|
+
Create a new folder under \`${cfg.entry}/\` with a \`page.md\` file:
|
|
1419
|
+
|
|
1420
|
+
\`\`\`bash
|
|
1421
|
+
mkdir -p ${cfg.entry}/my-page
|
|
1422
|
+
\`\`\`
|
|
1423
|
+
|
|
1424
|
+
Then create \`${cfg.entry}/my-page/page.md\`:
|
|
1425
|
+
|
|
1426
|
+
\`\`\`md
|
|
1427
|
+
---
|
|
1428
|
+
title: "My Page"
|
|
1429
|
+
description: "A custom documentation page"
|
|
1430
|
+
---
|
|
1431
|
+
|
|
1432
|
+
# My Page
|
|
1433
|
+
|
|
1434
|
+
Write your content here using **Markdown**.
|
|
1435
|
+
\`\`\`
|
|
1436
|
+
|
|
1437
|
+
Your page is now available at \`/${cfg.entry}/my-page\`.
|
|
1438
|
+
|
|
1439
|
+
## Customizing the Theme
|
|
1440
|
+
|
|
1441
|
+
Edit \`docs.config.ts\` to change colors and typography:
|
|
1442
|
+
|
|
1443
|
+
\`\`\`ts
|
|
1444
|
+
theme: ${t.factory}({
|
|
1445
|
+
ui: {
|
|
1446
|
+
colors: { primary: "#22c55e" },
|
|
1447
|
+
},
|
|
1448
|
+
}),
|
|
1449
|
+
\`\`\`
|
|
1450
|
+
|
|
1451
|
+
## Deploying
|
|
1452
|
+
|
|
1453
|
+
Build your docs for production:
|
|
1454
|
+
|
|
1455
|
+
\`\`\`bash
|
|
1456
|
+
pnpm build
|
|
1457
|
+
\`\`\`
|
|
1458
|
+
|
|
1459
|
+
Deploy to Vercel, Netlify, or any Node.js hosting platform.
|
|
1460
|
+
`;
|
|
1461
|
+
}
|
|
1462
|
+
function nuxtGlobalCssTemplate(theme) {
|
|
1463
|
+
return `\
|
|
1464
|
+
@import "@farming-labs/nuxt-theme/${theme}/css";
|
|
1465
|
+
`;
|
|
1466
|
+
}
|
|
1467
|
+
function nuxtCssImportLine(theme) {
|
|
1468
|
+
return `@import "@farming-labs/nuxt-theme/${theme}/css";`;
|
|
1469
|
+
}
|
|
1470
|
+
function injectNuxtCssImport(existingContent, theme) {
|
|
1471
|
+
const importLine = nuxtCssImportLine(theme);
|
|
1472
|
+
if (existingContent.includes(importLine)) return null;
|
|
1473
|
+
const lines = existingContent.split("\n");
|
|
1474
|
+
const lastImportIdx = lines.reduce((acc, l, i) => l.trimStart().startsWith("@import") ? i : acc, -1);
|
|
1475
|
+
if (lastImportIdx >= 0) lines.splice(lastImportIdx + 1, 0, importLine);
|
|
1476
|
+
else lines.unshift(importLine);
|
|
1477
|
+
return lines.join("\n");
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
//#endregion
|
|
1481
|
+
//#region src/cli/init.ts
|
|
1482
|
+
async function init() {
|
|
1483
|
+
const cwd = process.cwd();
|
|
1484
|
+
p.intro(pc.bgCyan(pc.black(" @farming-labs/docs ")));
|
|
1485
|
+
let framework = detectFramework(cwd);
|
|
1486
|
+
if (framework) {
|
|
1487
|
+
const frameworkName = framework === "nextjs" ? "Next.js" : framework === "sveltekit" ? "SvelteKit" : framework === "astro" ? "Astro" : "Nuxt";
|
|
1488
|
+
p.log.success(`Detected framework: ${pc.cyan(frameworkName)}`);
|
|
1489
|
+
} else {
|
|
1490
|
+
p.log.warn("Could not auto-detect a framework from " + pc.cyan("package.json") + ".");
|
|
1491
|
+
const picked = await p.select({
|
|
1492
|
+
message: "Which framework are you using?",
|
|
1493
|
+
options: [
|
|
1494
|
+
{
|
|
1495
|
+
value: "nextjs",
|
|
1496
|
+
label: "Next.js",
|
|
1497
|
+
hint: "React framework with App Router"
|
|
1498
|
+
},
|
|
1499
|
+
{
|
|
1500
|
+
value: "sveltekit",
|
|
1501
|
+
label: "SvelteKit",
|
|
1502
|
+
hint: "Svelte framework with file-based routing"
|
|
1503
|
+
},
|
|
1504
|
+
{
|
|
1505
|
+
value: "astro",
|
|
1506
|
+
label: "Astro",
|
|
1507
|
+
hint: "Content-focused framework with island architecture"
|
|
1508
|
+
},
|
|
1509
|
+
{
|
|
1510
|
+
value: "nuxt",
|
|
1511
|
+
label: "Nuxt",
|
|
1512
|
+
hint: "Vue 3 framework with file-based routing and Nitro server"
|
|
1513
|
+
}
|
|
1514
|
+
]
|
|
1515
|
+
});
|
|
1516
|
+
if (p.isCancel(picked)) {
|
|
1517
|
+
p.outro(pc.red("Init cancelled."));
|
|
1518
|
+
process.exit(0);
|
|
1519
|
+
}
|
|
1520
|
+
framework = picked;
|
|
1521
|
+
}
|
|
1522
|
+
const theme = await p.select({
|
|
1523
|
+
message: "Which theme would you like to use?",
|
|
1524
|
+
options: [
|
|
1525
|
+
{
|
|
1526
|
+
value: "fumadocs",
|
|
1527
|
+
label: "Fumadocs (Default)",
|
|
1528
|
+
hint: "Clean, modern docs theme with sidebar, search, and dark mode"
|
|
1529
|
+
},
|
|
1530
|
+
{
|
|
1531
|
+
value: "darksharp",
|
|
1532
|
+
label: "Darksharp",
|
|
1533
|
+
hint: "All-black, sharp edges, zero-radius look"
|
|
1534
|
+
},
|
|
1535
|
+
{
|
|
1536
|
+
value: "pixel-border",
|
|
1537
|
+
label: "Pixel Border",
|
|
1538
|
+
hint: "Rounded borders, pixel-perfect spacing, refined sidebar"
|
|
1539
|
+
},
|
|
1540
|
+
{
|
|
1541
|
+
value: "colorful",
|
|
1542
|
+
label: "Colorful",
|
|
1543
|
+
hint: "Fumadocs-style neutral theme with description support"
|
|
1544
|
+
},
|
|
1545
|
+
{
|
|
1546
|
+
value: "darkbold",
|
|
1547
|
+
label: "DarkBold",
|
|
1548
|
+
hint: "Pure monochrome, Geist typography, clean minimalism"
|
|
1549
|
+
},
|
|
1550
|
+
{
|
|
1551
|
+
value: "shiny",
|
|
1552
|
+
label: "Shiny",
|
|
1553
|
+
hint: "Glossy, modern look with subtle shimmer effects"
|
|
1554
|
+
},
|
|
1555
|
+
{
|
|
1556
|
+
value: "greentree",
|
|
1557
|
+
label: "GreenTree",
|
|
1558
|
+
hint: "Emerald green accent, Inter font, Mintlify-inspired"
|
|
1559
|
+
}
|
|
1560
|
+
]
|
|
1561
|
+
});
|
|
1562
|
+
if (p.isCancel(theme)) {
|
|
1563
|
+
p.outro(pc.red("Init cancelled."));
|
|
1564
|
+
process.exit(0);
|
|
1565
|
+
}
|
|
1566
|
+
const aliasHint = framework === "nextjs" ? `Uses ${pc.cyan("@/")} prefix (requires tsconfig paths)` : framework === "sveltekit" ? `Uses ${pc.cyan("$lib/")} prefix (SvelteKit built-in)` : framework === "nuxt" ? `Uses ${pc.cyan("~/")} prefix (Nuxt built-in)` : `Uses ${pc.cyan("@/")} prefix (requires tsconfig paths)`;
|
|
1567
|
+
const useAlias = await p.confirm({
|
|
1568
|
+
message: `Use path aliases for imports? ${pc.dim(aliasHint)}`,
|
|
1569
|
+
initialValue: false
|
|
1570
|
+
});
|
|
1571
|
+
if (p.isCancel(useAlias)) {
|
|
1572
|
+
p.outro(pc.red("Init cancelled."));
|
|
1573
|
+
process.exit(0);
|
|
1574
|
+
}
|
|
1575
|
+
let astroAdapter;
|
|
1576
|
+
if (framework === "astro") {
|
|
1577
|
+
const adapter = await p.select({
|
|
1578
|
+
message: "Where will you deploy?",
|
|
1579
|
+
options: [
|
|
1580
|
+
{
|
|
1581
|
+
value: "vercel",
|
|
1582
|
+
label: "Vercel",
|
|
1583
|
+
hint: "Recommended for most projects"
|
|
1584
|
+
},
|
|
1585
|
+
{
|
|
1586
|
+
value: "netlify",
|
|
1587
|
+
label: "Netlify"
|
|
1588
|
+
},
|
|
1589
|
+
{
|
|
1590
|
+
value: "cloudflare",
|
|
1591
|
+
label: "Cloudflare Pages"
|
|
1592
|
+
},
|
|
1593
|
+
{
|
|
1594
|
+
value: "node",
|
|
1595
|
+
label: "Node.js / Docker",
|
|
1596
|
+
hint: "Self-hosted standalone server"
|
|
1597
|
+
}
|
|
1598
|
+
]
|
|
1599
|
+
});
|
|
1600
|
+
if (p.isCancel(adapter)) {
|
|
1601
|
+
p.outro(pc.red("Init cancelled."));
|
|
1602
|
+
process.exit(0);
|
|
1603
|
+
}
|
|
1604
|
+
astroAdapter = adapter;
|
|
1605
|
+
}
|
|
1606
|
+
const entry = await p.text({
|
|
1607
|
+
message: "Where should your docs live?",
|
|
1608
|
+
placeholder: "docs",
|
|
1609
|
+
defaultValue: "docs",
|
|
1610
|
+
validate: (value) => {
|
|
1611
|
+
if (!value) return "Entry path is required";
|
|
1612
|
+
if (value.startsWith("/")) return "Use a relative path (no leading /)";
|
|
1613
|
+
if (value.includes(" ")) return "Path cannot contain spaces";
|
|
1614
|
+
}
|
|
1615
|
+
});
|
|
1616
|
+
if (p.isCancel(entry)) {
|
|
1617
|
+
p.outro(pc.red("Init cancelled."));
|
|
1618
|
+
process.exit(0);
|
|
1619
|
+
}
|
|
1620
|
+
const entryPath = entry;
|
|
1621
|
+
const detectedCssFiles = detectGlobalCssFiles(cwd);
|
|
1622
|
+
let globalCssRelPath;
|
|
1623
|
+
const defaultCssPath = framework === "sveltekit" ? "src/app.css" : framework === "astro" ? "src/styles/global.css" : framework === "nuxt" ? "assets/css/main.css" : "app/globals.css";
|
|
1624
|
+
if (detectedCssFiles.length === 1) {
|
|
1625
|
+
globalCssRelPath = detectedCssFiles[0];
|
|
1626
|
+
p.log.info(`Found global CSS at ${pc.cyan(globalCssRelPath)}`);
|
|
1627
|
+
} else if (detectedCssFiles.length > 1) {
|
|
1628
|
+
const picked = await p.select({
|
|
1629
|
+
message: "Multiple global CSS files found. Which one should we use?",
|
|
1630
|
+
options: detectedCssFiles.map((f) => ({
|
|
1631
|
+
value: f,
|
|
1632
|
+
label: f
|
|
1633
|
+
}))
|
|
1634
|
+
});
|
|
1635
|
+
if (p.isCancel(picked)) {
|
|
1636
|
+
p.outro(pc.red("Init cancelled."));
|
|
1637
|
+
process.exit(0);
|
|
1638
|
+
}
|
|
1639
|
+
globalCssRelPath = picked;
|
|
1640
|
+
} else {
|
|
1641
|
+
const cssPath = await p.text({
|
|
1642
|
+
message: "Where is your global CSS file?",
|
|
1643
|
+
placeholder: defaultCssPath,
|
|
1644
|
+
defaultValue: defaultCssPath,
|
|
1645
|
+
validate: (value) => {
|
|
1646
|
+
if (!value) return "CSS file path is required";
|
|
1647
|
+
if (!value.endsWith(".css")) return "Path must end with .css";
|
|
1648
|
+
}
|
|
1649
|
+
});
|
|
1650
|
+
if (p.isCancel(cssPath)) {
|
|
1651
|
+
p.outro(pc.red("Init cancelled."));
|
|
1652
|
+
process.exit(0);
|
|
1653
|
+
}
|
|
1654
|
+
globalCssRelPath = cssPath;
|
|
1655
|
+
}
|
|
1656
|
+
const pkgJsonContent = readFileSafe(path.join(cwd, "package.json"));
|
|
1657
|
+
const pkgJson = pkgJsonContent ? JSON.parse(pkgJsonContent) : { name: "my-project" };
|
|
1658
|
+
const cfg = {
|
|
1659
|
+
entry: entryPath,
|
|
1660
|
+
theme,
|
|
1661
|
+
projectName: pkgJson.name || "My Project",
|
|
1662
|
+
framework,
|
|
1663
|
+
useAlias,
|
|
1664
|
+
astroAdapter
|
|
1665
|
+
};
|
|
1666
|
+
const s = p.spinner();
|
|
1667
|
+
s.start("Scaffolding docs files");
|
|
1668
|
+
const written = [];
|
|
1669
|
+
const skipped = [];
|
|
1670
|
+
function write(rel, content, overwrite = false) {
|
|
1671
|
+
if (writeFileSafe(path.join(cwd, rel), content, overwrite)) written.push(rel);
|
|
1672
|
+
else skipped.push(rel);
|
|
1673
|
+
}
|
|
1674
|
+
if (framework === "sveltekit") scaffoldSvelteKit(cwd, cfg, globalCssRelPath, write, skipped, written);
|
|
1675
|
+
else if (framework === "astro") scaffoldAstro(cwd, cfg, globalCssRelPath, write, skipped, written);
|
|
1676
|
+
else if (framework === "nuxt") scaffoldNuxt(cwd, cfg, globalCssRelPath, write, skipped, written);
|
|
1677
|
+
else scaffoldNextJs(cwd, cfg, globalCssRelPath, write, skipped, written);
|
|
1678
|
+
s.stop("Files scaffolded");
|
|
1679
|
+
if (written.length > 0) p.log.success(`Created ${written.length} file${written.length > 1 ? "s" : ""}:\n` + written.map((f) => ` ${pc.green("+")} ${f}`).join("\n"));
|
|
1680
|
+
if (skipped.length > 0) p.log.info(`Skipped ${skipped.length} existing file${skipped.length > 1 ? "s" : ""}:\n` + skipped.map((f) => ` ${pc.dim("-")} ${f}`).join("\n"));
|
|
1681
|
+
const pm = detectPackageManager(cwd);
|
|
1682
|
+
p.log.info(`Using ${pc.cyan(pm)} as package manager`);
|
|
1683
|
+
const s2 = p.spinner();
|
|
1684
|
+
s2.start("Installing dependencies");
|
|
1685
|
+
try {
|
|
1686
|
+
if (framework === "sveltekit") exec(`${installCommand(pm)} @farming-labs/docs @farming-labs/svelte @farming-labs/svelte-theme`, cwd);
|
|
1687
|
+
else if (framework === "astro") {
|
|
1688
|
+
const adapterPkg = getAstroAdapterPkg(cfg.astroAdapter ?? "vercel");
|
|
1689
|
+
exec(`${installCommand(pm)} @farming-labs/docs @farming-labs/astro @farming-labs/astro-theme ${adapterPkg}`, cwd);
|
|
1690
|
+
} else if (framework === "nuxt") exec(`${installCommand(pm)} @farming-labs/docs @farming-labs/nuxt @farming-labs/nuxt-theme`, cwd);
|
|
1691
|
+
else {
|
|
1692
|
+
exec(`${installCommand(pm)} @farming-labs/docs @farming-labs/next @farming-labs/theme`, cwd);
|
|
1693
|
+
const devDeps = [
|
|
1694
|
+
"@tailwindcss/postcss",
|
|
1695
|
+
"postcss",
|
|
1696
|
+
"tailwindcss",
|
|
1697
|
+
"@types/mdx",
|
|
1698
|
+
"@types/node"
|
|
1699
|
+
];
|
|
1700
|
+
const allDeps = {
|
|
1701
|
+
...pkgJson.dependencies,
|
|
1702
|
+
...pkgJson.devDependencies
|
|
1703
|
+
};
|
|
1704
|
+
const missingDevDeps = devDeps.filter((d) => !allDeps[d]);
|
|
1705
|
+
if (missingDevDeps.length > 0) exec(`${devInstallCommand(pm)} ${missingDevDeps.join(" ")}`, cwd);
|
|
1706
|
+
}
|
|
1707
|
+
} catch {
|
|
1708
|
+
s2.stop("Failed to install dependencies");
|
|
1709
|
+
p.log.error(`Dependency installation failed. Run the install command manually:
|
|
1710
|
+
${pc.cyan(`${installCommand(pm)} @farming-labs/docs`)}`);
|
|
1711
|
+
p.outro(pc.yellow("Setup partially complete. Install deps and run dev server manually."));
|
|
1712
|
+
process.exit(1);
|
|
1713
|
+
}
|
|
1714
|
+
s2.stop("Dependencies installed");
|
|
1715
|
+
const startDev = await p.confirm({
|
|
1716
|
+
message: "Start the dev server now?",
|
|
1717
|
+
initialValue: true
|
|
1718
|
+
});
|
|
1719
|
+
if (p.isCancel(startDev) || !startDev) {
|
|
1720
|
+
p.log.info(`You can start the dev server later with:
|
|
1721
|
+
${pc.cyan(`${pm === "yarn" ? "yarn" : pm + " run"} dev`)}`);
|
|
1722
|
+
p.outro(pc.green("Done! Happy documenting."));
|
|
1723
|
+
process.exit(0);
|
|
1724
|
+
}
|
|
1725
|
+
p.log.step("Starting dev server...");
|
|
1726
|
+
const devCommand = framework === "sveltekit" ? {
|
|
1727
|
+
cmd: "npx",
|
|
1728
|
+
args: ["vite", "dev"],
|
|
1729
|
+
waitFor: "ready"
|
|
1730
|
+
} : framework === "astro" ? {
|
|
1731
|
+
cmd: "npx",
|
|
1732
|
+
args: ["astro", "dev"],
|
|
1733
|
+
waitFor: "ready"
|
|
1734
|
+
} : framework === "nuxt" ? {
|
|
1735
|
+
cmd: "npx",
|
|
1736
|
+
args: ["nuxt", "dev"],
|
|
1737
|
+
waitFor: "Local"
|
|
1738
|
+
} : {
|
|
1739
|
+
cmd: "npx",
|
|
1740
|
+
args: [
|
|
1741
|
+
"next",
|
|
1742
|
+
"dev",
|
|
1743
|
+
"--webpack"
|
|
1744
|
+
],
|
|
1745
|
+
waitFor: "Ready"
|
|
1746
|
+
};
|
|
1747
|
+
const defaultPort = framework === "sveltekit" ? "5173" : framework === "astro" ? "4321" : framework === "nuxt" ? "3000" : "3000";
|
|
1748
|
+
try {
|
|
1749
|
+
const child = await spawnAndWaitFor(devCommand.cmd, devCommand.args, cwd, devCommand.waitFor, 6e4);
|
|
1750
|
+
const url = `http://localhost:${defaultPort}/${entryPath}`;
|
|
1751
|
+
console.log();
|
|
1752
|
+
p.log.success(`Dev server is running! Your docs are live at:\n\n ${pc.cyan(pc.underline(url))}\n\n Press ${pc.dim("Ctrl+C")} to stop the server.`);
|
|
1753
|
+
p.outro(pc.green("Happy documenting!"));
|
|
1754
|
+
await new Promise((resolve) => {
|
|
1755
|
+
child.on("close", () => resolve());
|
|
1756
|
+
process.on("SIGINT", () => {
|
|
1757
|
+
child.kill("SIGINT");
|
|
1758
|
+
resolve();
|
|
1759
|
+
});
|
|
1760
|
+
process.on("SIGTERM", () => {
|
|
1761
|
+
child.kill("SIGTERM");
|
|
1762
|
+
resolve();
|
|
1763
|
+
});
|
|
1764
|
+
});
|
|
1765
|
+
} catch (err) {
|
|
1766
|
+
const manualCmd = framework === "sveltekit" ? "npx vite dev" : framework === "astro" ? "npx astro dev" : framework === "nuxt" ? "npx nuxt dev" : "npx next dev --webpack";
|
|
1767
|
+
p.log.error(`Could not start dev server. Try running manually:
|
|
1768
|
+
${pc.cyan(manualCmd)}`);
|
|
1769
|
+
p.outro(pc.yellow("Setup complete. Start the server manually."));
|
|
1770
|
+
process.exit(1);
|
|
1771
|
+
}
|
|
1772
|
+
}
|
|
1773
|
+
function scaffoldNextJs(cwd, cfg, globalCssRelPath, write, skipped, written) {
|
|
1774
|
+
write("docs.config.ts", docsConfigTemplate(cfg));
|
|
1775
|
+
const existingNextConfig = readFileSafe(path.join(cwd, "next.config.ts")) ?? readFileSafe(path.join(cwd, "next.config.mjs")) ?? readFileSafe(path.join(cwd, "next.config.js"));
|
|
1776
|
+
if (existingNextConfig) {
|
|
1777
|
+
const configFile = fileExists(path.join(cwd, "next.config.ts")) ? "next.config.ts" : fileExists(path.join(cwd, "next.config.mjs")) ? "next.config.mjs" : "next.config.js";
|
|
1778
|
+
const merged = nextConfigMergedTemplate(existingNextConfig);
|
|
1779
|
+
if (merged !== existingNextConfig) {
|
|
1780
|
+
writeFileSafe(path.join(cwd, configFile), merged, true);
|
|
1781
|
+
written.push(configFile + " (updated)");
|
|
1782
|
+
} else skipped.push(configFile + " (already configured)");
|
|
1783
|
+
} else write("next.config.ts", nextConfigTemplate());
|
|
1784
|
+
write("app/layout.tsx", rootLayoutTemplate(cfg, globalCssRelPath));
|
|
1785
|
+
const globalCssAbsPath = path.join(cwd, globalCssRelPath);
|
|
1786
|
+
const existingGlobalCss = readFileSafe(globalCssAbsPath);
|
|
1787
|
+
if (existingGlobalCss) {
|
|
1788
|
+
const injected = injectCssImport(existingGlobalCss, cfg.theme);
|
|
1789
|
+
if (injected) {
|
|
1790
|
+
writeFileSafe(globalCssAbsPath, injected, true);
|
|
1791
|
+
written.push(globalCssRelPath + " (updated)");
|
|
1792
|
+
} else skipped.push(globalCssRelPath + " (already configured)");
|
|
1793
|
+
} else write(globalCssRelPath, globalCssTemplate(cfg.theme));
|
|
1794
|
+
write(`app/${cfg.entry}/layout.tsx`, docsLayoutTemplate(cfg));
|
|
1795
|
+
write("postcss.config.mjs", postcssConfigTemplate());
|
|
1796
|
+
if (!fileExists(path.join(cwd, "tsconfig.json"))) write("tsconfig.json", tsconfigTemplate());
|
|
1797
|
+
write(`app/${cfg.entry}/page.mdx`, welcomePageTemplate(cfg));
|
|
1798
|
+
write(`app/${cfg.entry}/installation/page.mdx`, installationPageTemplate(cfg));
|
|
1799
|
+
write(`app/${cfg.entry}/quickstart/page.mdx`, quickstartPageTemplate(cfg));
|
|
1800
|
+
}
|
|
1801
|
+
function scaffoldSvelteKit(cwd, cfg, globalCssRelPath, write, skipped, written) {
|
|
1802
|
+
write("src/lib/docs.config.ts", svelteDocsConfigTemplate(cfg));
|
|
1803
|
+
write("src/lib/docs.server.ts", svelteDocsServerTemplate(cfg));
|
|
1804
|
+
write(`src/routes/${cfg.entry}/+layout.svelte`, svelteDocsLayoutTemplate(cfg));
|
|
1805
|
+
write(`src/routes/${cfg.entry}/+layout.server.js`, svelteDocsLayoutServerTemplate(cfg));
|
|
1806
|
+
write(`src/routes/${cfg.entry}/[...slug]/+page.svelte`, svelteDocsPageTemplate(cfg));
|
|
1807
|
+
if (!readFileSafe(path.join(cwd, "src/routes/+layout.svelte"))) write("src/routes/+layout.svelte", svelteRootLayoutTemplate(globalCssRelPath));
|
|
1808
|
+
const globalCssAbsPath = path.join(cwd, globalCssRelPath);
|
|
1809
|
+
const existingGlobalCss = readFileSafe(globalCssAbsPath);
|
|
1810
|
+
const cssTheme = {
|
|
1811
|
+
fumadocs: "fumadocs",
|
|
1812
|
+
darksharp: "darksharp",
|
|
1813
|
+
"pixel-border": "pixel-border",
|
|
1814
|
+
colorful: "colorful",
|
|
1815
|
+
darkbold: "darkbold",
|
|
1816
|
+
shiny: "shiny",
|
|
1817
|
+
greentree: "greentree",
|
|
1818
|
+
default: "fumadocs"
|
|
1819
|
+
}[cfg.theme] || "fumadocs";
|
|
1820
|
+
if (existingGlobalCss) {
|
|
1821
|
+
const injected = injectSvelteCssImport(existingGlobalCss, cssTheme);
|
|
1822
|
+
if (injected) {
|
|
1823
|
+
writeFileSafe(globalCssAbsPath, injected, true);
|
|
1824
|
+
written.push(globalCssRelPath + " (updated)");
|
|
1825
|
+
} else skipped.push(globalCssRelPath + " (already configured)");
|
|
1826
|
+
} else write(globalCssRelPath, svelteGlobalCssTemplate(cssTheme));
|
|
1827
|
+
write(`${cfg.entry}/page.md`, svelteWelcomePageTemplate(cfg));
|
|
1828
|
+
write(`${cfg.entry}/installation/page.md`, svelteInstallationPageTemplate(cfg));
|
|
1829
|
+
write(`${cfg.entry}/quickstart/page.md`, svelteQuickstartPageTemplate(cfg));
|
|
1830
|
+
}
|
|
1831
|
+
function scaffoldAstro(cwd, cfg, globalCssRelPath, write, skipped, written) {
|
|
1832
|
+
write("src/lib/docs.config.ts", astroDocsConfigTemplate(cfg));
|
|
1833
|
+
write("src/lib/docs.server.ts", astroDocsServerTemplate(cfg));
|
|
1834
|
+
if (!fileExists(path.join(cwd, "astro.config.mjs")) && !fileExists(path.join(cwd, "astro.config.ts"))) write("astro.config.mjs", astroConfigTemplate(cfg.astroAdapter ?? "vercel"));
|
|
1835
|
+
write(`src/pages/${cfg.entry}/index.astro`, astroDocsIndexTemplate(cfg));
|
|
1836
|
+
write(`src/pages/${cfg.entry}/[...slug].astro`, astroDocsPageTemplate(cfg));
|
|
1837
|
+
write(`src/pages/api/${cfg.entry}.ts`, astroApiRouteTemplate(cfg));
|
|
1838
|
+
const globalCssAbsPath = path.join(cwd, globalCssRelPath);
|
|
1839
|
+
const existingGlobalCss = readFileSafe(globalCssAbsPath);
|
|
1840
|
+
const cssTheme = {
|
|
1841
|
+
fumadocs: "fumadocs",
|
|
1842
|
+
darksharp: "darksharp",
|
|
1843
|
+
"pixel-border": "pixel-border",
|
|
1844
|
+
colorful: "colorful",
|
|
1845
|
+
darkbold: "darkbold",
|
|
1846
|
+
shiny: "shiny",
|
|
1847
|
+
greentree: "greentree",
|
|
1848
|
+
default: "fumadocs"
|
|
1849
|
+
}[cfg.theme] || "fumadocs";
|
|
1850
|
+
if (existingGlobalCss) {
|
|
1851
|
+
const injected = injectAstroCssImport(existingGlobalCss, cssTheme);
|
|
1852
|
+
if (injected) {
|
|
1853
|
+
writeFileSafe(globalCssAbsPath, injected, true);
|
|
1854
|
+
written.push(globalCssRelPath + " (updated)");
|
|
1855
|
+
} else skipped.push(globalCssRelPath + " (already configured)");
|
|
1856
|
+
} else write(globalCssRelPath, astroGlobalCssTemplate(cssTheme));
|
|
1857
|
+
write(`${cfg.entry}/page.md`, astroWelcomePageTemplate(cfg));
|
|
1858
|
+
write(`${cfg.entry}/installation/page.md`, astroInstallationPageTemplate(cfg));
|
|
1859
|
+
write(`${cfg.entry}/quickstart/page.md`, astroQuickstartPageTemplate(cfg));
|
|
1860
|
+
}
|
|
1861
|
+
function scaffoldNuxt(cwd, cfg, globalCssRelPath, write, skipped, written) {
|
|
1862
|
+
write("docs.config.ts", nuxtDocsConfigTemplate(cfg));
|
|
1863
|
+
write("server/utils/docs-server.ts", nuxtDocsServerTemplate(cfg));
|
|
1864
|
+
write("server/api/docs.get.ts", nuxtServerApiDocsGetTemplate());
|
|
1865
|
+
write("server/api/docs.post.ts", nuxtServerApiDocsPostTemplate());
|
|
1866
|
+
write("server/api/docs/load.get.ts", nuxtServerApiDocsLoadTemplate());
|
|
1867
|
+
write(`pages/${cfg.entry}/[[...slug]].vue`, nuxtDocsPageTemplate(cfg));
|
|
1868
|
+
path.join(cwd, "nuxt.config.ts");
|
|
1869
|
+
if (!fileExists(path.join(cwd, "nuxt.config.ts")) && !fileExists(path.join(cwd, "nuxt.config.js"))) write("nuxt.config.ts", nuxtConfigTemplate(cfg));
|
|
1870
|
+
const cssTheme = {
|
|
1871
|
+
fumadocs: "fumadocs",
|
|
1872
|
+
darksharp: "darksharp",
|
|
1873
|
+
"pixel-border": "pixel-border",
|
|
1874
|
+
colorful: "colorful",
|
|
1875
|
+
darkbold: "darkbold",
|
|
1876
|
+
shiny: "shiny",
|
|
1877
|
+
greentree: "greentree",
|
|
1878
|
+
default: "fumadocs"
|
|
1879
|
+
}[cfg.theme] || "fumadocs";
|
|
1880
|
+
const globalCssAbsPath = path.join(cwd, globalCssRelPath);
|
|
1881
|
+
const existingGlobalCss = readFileSafe(globalCssAbsPath);
|
|
1882
|
+
if (existingGlobalCss) {
|
|
1883
|
+
const injected = injectNuxtCssImport(existingGlobalCss, cssTheme);
|
|
1884
|
+
if (injected) {
|
|
1885
|
+
writeFileSafe(globalCssAbsPath, injected, true);
|
|
1886
|
+
written.push(globalCssRelPath + " (updated)");
|
|
1887
|
+
} else skipped.push(globalCssRelPath + " (already configured)");
|
|
1888
|
+
} else write(globalCssRelPath, nuxtGlobalCssTemplate(cssTheme));
|
|
1889
|
+
write(`${cfg.entry}/page.md`, nuxtWelcomePageTemplate(cfg));
|
|
1890
|
+
write(`${cfg.entry}/installation/page.md`, nuxtInstallationPageTemplate(cfg));
|
|
1891
|
+
write(`${cfg.entry}/quickstart/page.md`, nuxtQuickstartPageTemplate(cfg));
|
|
1892
|
+
}
|
|
1893
|
+
|
|
1894
|
+
//#endregion
|
|
1895
|
+
//#region src/cli/index.ts
|
|
1896
|
+
const command = process.argv.slice(2)[0];
|
|
1897
|
+
async function main() {
|
|
1898
|
+
if (!command || command === "init") await init();
|
|
1899
|
+
else if (command === "--help" || command === "-h") printHelp();
|
|
1900
|
+
else if (command === "--version" || command === "-v") printVersion();
|
|
1901
|
+
else {
|
|
1902
|
+
console.error(pc.red(`Unknown command: ${command}`));
|
|
1903
|
+
console.error();
|
|
1904
|
+
printHelp();
|
|
1905
|
+
process.exit(1);
|
|
1906
|
+
}
|
|
1907
|
+
}
|
|
1908
|
+
function printHelp() {
|
|
1909
|
+
console.log(`
|
|
1910
|
+
${pc.bold("@farming-labs/docs")} — Documentation framework CLI
|
|
1911
|
+
|
|
1912
|
+
${pc.dim("Usage:")}
|
|
1913
|
+
npx @farming-labs/docs ${pc.cyan("<command>")}
|
|
1914
|
+
|
|
1915
|
+
${pc.dim("Commands:")}
|
|
1916
|
+
${pc.cyan("init")} Scaffold docs in your project (default)
|
|
1917
|
+
|
|
1918
|
+
${pc.dim("Supported frameworks:")}
|
|
1919
|
+
Next.js, SvelteKit, Astro, Nuxt
|
|
594
1920
|
|
|
595
1921
|
${pc.dim("Options:")}
|
|
596
1922
|
${pc.cyan("-h, --help")} Show this help message
|