@farming-labs/docs 0.0.2-beta.3 → 0.0.2-beta.30
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 +1586 -227
- package/dist/index.d.mts +580 -9
- package/dist/index.mjs +6 -1
- package/package.json +14 -14
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,16 +57,19 @@ function readFileSafe(filePath) {
|
|
|
53
57
|
if (!fs.existsSync(filePath)) return null;
|
|
54
58
|
return fs.readFileSync(filePath, "utf-8");
|
|
55
59
|
}
|
|
56
|
-
/** Common locations where global CSS files live in Next.js projects. */
|
|
60
|
+
/** Common locations where global CSS files live in Next.js / SvelteKit projects. */
|
|
57
61
|
const GLOBAL_CSS_CANDIDATES = [
|
|
58
62
|
"app/globals.css",
|
|
59
63
|
"app/global.css",
|
|
60
64
|
"src/app/globals.css",
|
|
61
65
|
"src/app/global.css",
|
|
66
|
+
"src/app.css",
|
|
62
67
|
"styles/globals.css",
|
|
63
68
|
"styles/global.css",
|
|
64
69
|
"src/styles/globals.css",
|
|
65
|
-
"src/styles/global.css"
|
|
70
|
+
"src/styles/global.css",
|
|
71
|
+
"assets/css/main.css",
|
|
72
|
+
"assets/main.css"
|
|
66
73
|
];
|
|
67
74
|
/**
|
|
68
75
|
* Find existing global CSS files in the project.
|
|
@@ -125,14 +132,132 @@ function spawnAndWaitFor(command, args, cwd, waitFor, timeoutMs = 6e4) {
|
|
|
125
132
|
|
|
126
133
|
//#endregion
|
|
127
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
|
+
}
|
|
128
252
|
function docsConfigTemplate(cfg) {
|
|
253
|
+
const t = getThemeInfo(cfg.theme);
|
|
129
254
|
return `\
|
|
130
255
|
import { defineDocs } from "@farming-labs/docs";
|
|
131
|
-
import {
|
|
256
|
+
import { ${t.factory} } from "${t.nextImport}";
|
|
132
257
|
|
|
133
258
|
export default defineDocs({
|
|
134
259
|
entry: "${cfg.entry}",
|
|
135
|
-
theme:
|
|
260
|
+
theme: ${t.factory}({
|
|
136
261
|
ui: {
|
|
137
262
|
colors: { primary: "#6366f1" },
|
|
138
263
|
},
|
|
@@ -169,15 +294,15 @@ function nextConfigMergedTemplate(existingContent) {
|
|
|
169
294
|
}
|
|
170
295
|
return lines.join("\n");
|
|
171
296
|
}
|
|
172
|
-
function rootLayoutTemplate(globalCssRelPath = "app/globals.css") {
|
|
297
|
+
function rootLayoutTemplate(cfg, globalCssRelPath = "app/globals.css") {
|
|
173
298
|
let cssImport;
|
|
174
299
|
if (globalCssRelPath.startsWith("app/")) cssImport = "./" + globalCssRelPath.slice(4);
|
|
175
300
|
else if (globalCssRelPath.startsWith("src/app/")) cssImport = "./" + globalCssRelPath.slice(8);
|
|
176
301
|
else cssImport = "../" + globalCssRelPath;
|
|
177
302
|
return `\
|
|
178
303
|
import type { Metadata } from "next";
|
|
179
|
-
import { RootProvider } from "@farming-labs/
|
|
180
|
-
import docsConfig from "
|
|
304
|
+
import { RootProvider } from "@farming-labs/theme";
|
|
305
|
+
import docsConfig from "${nextRootLayoutConfigImport(cfg.useAlias)}";
|
|
181
306
|
import "${cssImport}";
|
|
182
307
|
|
|
183
308
|
export const metadata: Metadata = {
|
|
@@ -206,26 +331,23 @@ export default function RootLayout({
|
|
|
206
331
|
function globalCssTemplate(theme) {
|
|
207
332
|
return `\
|
|
208
333
|
@import "tailwindcss";
|
|
209
|
-
@import "@farming-labs/${theme}/css";
|
|
334
|
+
@import "@farming-labs/theme/${getThemeInfo(theme).nextCssImport}/css";
|
|
210
335
|
`;
|
|
211
336
|
}
|
|
212
|
-
/**
|
|
213
|
-
* Inject the fumadocs CSS import into an existing global.css.
|
|
214
|
-
* Returns the modified content, or null if already present.
|
|
215
|
-
*/
|
|
216
337
|
function injectCssImport(existingContent, theme) {
|
|
217
|
-
const importLine = `@import "@farming-labs/${theme}/css";`;
|
|
338
|
+
const importLine = `@import "@farming-labs/theme/${getThemeInfo(theme).nextCssImport}/css";`;
|
|
218
339
|
if (existingContent.includes(importLine)) return null;
|
|
340
|
+
if (existingContent.includes("@farming-labs/theme/") && existingContent.includes("/css")) return null;
|
|
219
341
|
const lines = existingContent.split("\n");
|
|
220
342
|
const lastImportIdx = lines.reduce((acc, l, i) => l.trimStart().startsWith("@import") ? i : acc, -1);
|
|
221
343
|
if (lastImportIdx >= 0) lines.splice(lastImportIdx + 1, 0, importLine);
|
|
222
344
|
else lines.unshift(importLine);
|
|
223
345
|
return lines.join("\n");
|
|
224
346
|
}
|
|
225
|
-
function docsLayoutTemplate() {
|
|
347
|
+
function docsLayoutTemplate(cfg) {
|
|
226
348
|
return `\
|
|
227
|
-
import docsConfig from "
|
|
228
|
-
import { createDocsLayout } from "@farming-labs/
|
|
349
|
+
import docsConfig from "${nextDocsLayoutConfigImport(cfg.useAlias)}";
|
|
350
|
+
import { createDocsLayout } from "@farming-labs/theme";
|
|
229
351
|
|
|
230
352
|
export default createDocsLayout(docsConfig);
|
|
231
353
|
`;
|
|
@@ -241,7 +363,8 @@ const config = {
|
|
|
241
363
|
export default config;
|
|
242
364
|
`;
|
|
243
365
|
}
|
|
244
|
-
|
|
366
|
+
/** @param useAlias - When false, paths (e.g. @/*) are omitted so no alias is added. */
|
|
367
|
+
function tsconfigTemplate(useAlias = false) {
|
|
245
368
|
return `\
|
|
246
369
|
{
|
|
247
370
|
"compilerOptions": {
|
|
@@ -258,8 +381,7 @@ function tsconfigTemplate() {
|
|
|
258
381
|
"isolatedModules": true,
|
|
259
382
|
"jsx": "react-jsx",
|
|
260
383
|
"incremental": true,
|
|
261
|
-
"plugins": [{ "name": "next" }]
|
|
262
|
-
"paths": { "@/*": ["./*"] }
|
|
384
|
+
"plugins": [{ "name": "next" }]${useAlias ? ",\n \"paths\": { \"@/*\": [\"./*\"] }" : ""}
|
|
263
385
|
},
|
|
264
386
|
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
|
265
387
|
"exclude": ["node_modules"]
|
|
@@ -304,6 +426,7 @@ Start by reading the [Installation](/${cfg.entry}/installation) guide, then foll
|
|
|
304
426
|
`;
|
|
305
427
|
}
|
|
306
428
|
function installationPageTemplate(cfg) {
|
|
429
|
+
const t = getThemeInfo(cfg.theme);
|
|
307
430
|
return `\
|
|
308
431
|
---
|
|
309
432
|
title: "Installation"
|
|
@@ -330,11 +453,11 @@ Your project includes a \`docs.config.ts\` at the root:
|
|
|
330
453
|
|
|
331
454
|
\`\`\`ts
|
|
332
455
|
import { defineDocs } from "@farming-labs/docs";
|
|
333
|
-
import {
|
|
456
|
+
import { ${t.factory} } from "${t.nextImport}";
|
|
334
457
|
|
|
335
458
|
export default defineDocs({
|
|
336
459
|
entry: "${cfg.entry}",
|
|
337
|
-
theme:
|
|
460
|
+
theme: ${t.factory}({
|
|
338
461
|
ui: { colors: { primary: "#6366f1" } },
|
|
339
462
|
}),
|
|
340
463
|
});
|
|
@@ -361,6 +484,7 @@ Head to the [Quickstart](/${cfg.entry}/quickstart) guide to start writing your f
|
|
|
361
484
|
`;
|
|
362
485
|
}
|
|
363
486
|
function quickstartPageTemplate(cfg) {
|
|
487
|
+
const t = getThemeInfo(cfg.theme);
|
|
364
488
|
return `\
|
|
365
489
|
---
|
|
366
490
|
title: "Quickstart"
|
|
@@ -423,7 +547,7 @@ console.log(greet("World"));
|
|
|
423
547
|
Edit \`docs.config.ts\` to change colors, typography, and component defaults:
|
|
424
548
|
|
|
425
549
|
\`\`\`ts
|
|
426
|
-
theme:
|
|
550
|
+
theme: ${t.factory}({
|
|
427
551
|
ui: {
|
|
428
552
|
colors: { primary: "#22c55e" },
|
|
429
553
|
},
|
|
@@ -441,216 +565,1451 @@ pnpm build
|
|
|
441
565
|
Deploy to Vercel, Netlify, or any Node.js hosting platform.
|
|
442
566
|
`;
|
|
443
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}";
|
|
444
573
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
let globalCssRelPath;
|
|
485
|
-
if (detectedCssFiles.length === 1) {
|
|
486
|
-
globalCssRelPath = detectedCssFiles[0];
|
|
487
|
-
p.log.info(`Found global CSS at ${pc.cyan(globalCssRelPath)}`);
|
|
488
|
-
} else if (detectedCssFiles.length > 1) {
|
|
489
|
-
const picked = await p.select({
|
|
490
|
-
message: "Multiple global CSS files found. Which one should we use?",
|
|
491
|
-
options: detectedCssFiles.map((f) => ({
|
|
492
|
-
value: f,
|
|
493
|
-
label: f
|
|
494
|
-
}))
|
|
495
|
-
});
|
|
496
|
-
if (p.isCancel(picked)) {
|
|
497
|
-
p.outro(pc.red("Init cancelled."));
|
|
498
|
-
process.exit(0);
|
|
499
|
-
}
|
|
500
|
-
globalCssRelPath = picked;
|
|
501
|
-
} else {
|
|
502
|
-
const cssPath = await p.text({
|
|
503
|
-
message: "Where is your global CSS file?",
|
|
504
|
-
placeholder: "app/globals.css",
|
|
505
|
-
defaultValue: "app/globals.css",
|
|
506
|
-
validate: (value) => {
|
|
507
|
-
if (!value) return "CSS file path is required";
|
|
508
|
-
if (!value.endsWith(".css")) return "Path must end with .css";
|
|
509
|
-
}
|
|
510
|
-
});
|
|
511
|
-
if (p.isCancel(cssPath)) {
|
|
512
|
-
p.outro(pc.red("Init cancelled."));
|
|
513
|
-
process.exit(0);
|
|
514
|
-
}
|
|
515
|
-
globalCssRelPath = cssPath;
|
|
516
|
-
}
|
|
517
|
-
const pkgJson = JSON.parse(readFileSafe(path.join(cwd, "package.json")));
|
|
518
|
-
const cfg = {
|
|
519
|
-
entry: entryPath,
|
|
520
|
-
theme,
|
|
521
|
-
projectName: pkgJson.name || "My Project"
|
|
522
|
-
};
|
|
523
|
-
const s = p.spinner();
|
|
524
|
-
s.start("Scaffolding docs files");
|
|
525
|
-
const written = [];
|
|
526
|
-
const skipped = [];
|
|
527
|
-
function write(rel, content, overwrite = false) {
|
|
528
|
-
if (writeFileSafe(path.join(cwd, rel), content, overwrite)) written.push(rel);
|
|
529
|
-
else skipped.push(rel);
|
|
530
|
-
}
|
|
531
|
-
write("docs.config.ts", docsConfigTemplate(cfg));
|
|
532
|
-
const existingNextConfig = readFileSafe(path.join(cwd, "next.config.ts")) ?? readFileSafe(path.join(cwd, "next.config.mjs")) ?? readFileSafe(path.join(cwd, "next.config.js"));
|
|
533
|
-
if (existingNextConfig) {
|
|
534
|
-
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";
|
|
535
|
-
const merged = nextConfigMergedTemplate(existingNextConfig);
|
|
536
|
-
if (merged !== existingNextConfig) {
|
|
537
|
-
writeFileSafe(path.join(cwd, configFile), merged, true);
|
|
538
|
-
written.push(configFile + " (updated)");
|
|
539
|
-
} else skipped.push(configFile + " (already configured)");
|
|
540
|
-
} else write("next.config.ts", nextConfigTemplate());
|
|
541
|
-
write("app/layout.tsx", rootLayoutTemplate(globalCssRelPath));
|
|
542
|
-
const globalCssAbsPath = path.join(cwd, globalCssRelPath);
|
|
543
|
-
const existingGlobalCss = readFileSafe(globalCssAbsPath);
|
|
544
|
-
if (existingGlobalCss) {
|
|
545
|
-
const injected = injectCssImport(existingGlobalCss, theme);
|
|
546
|
-
if (injected) {
|
|
547
|
-
writeFileSafe(globalCssAbsPath, injected, true);
|
|
548
|
-
written.push(globalCssRelPath + " (updated)");
|
|
549
|
-
} else skipped.push(globalCssRelPath + " (already configured)");
|
|
550
|
-
} else write(globalCssRelPath, globalCssTemplate(theme));
|
|
551
|
-
write(`app/${entryPath}/layout.tsx`, docsLayoutTemplate());
|
|
552
|
-
write("postcss.config.mjs", postcssConfigTemplate());
|
|
553
|
-
if (!fileExists(path.join(cwd, "tsconfig.json"))) write("tsconfig.json", tsconfigTemplate());
|
|
554
|
-
write(`app/${entryPath}/page.mdx`, welcomePageTemplate(cfg));
|
|
555
|
-
write(`app/${entryPath}/installation/page.mdx`, installationPageTemplate(cfg));
|
|
556
|
-
write(`app/${entryPath}/quickstart/page.mdx`, quickstartPageTemplate(cfg));
|
|
557
|
-
s.stop("Files scaffolded");
|
|
558
|
-
if (written.length > 0) p.log.success(`Created ${written.length} file${written.length > 1 ? "s" : ""}:\n` + written.map((f) => ` ${pc.green("+")} ${f}`).join("\n"));
|
|
559
|
-
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"));
|
|
560
|
-
const pm = detectPackageManager(cwd);
|
|
561
|
-
p.log.info(`Using ${pc.cyan(pm)} as package manager`);
|
|
562
|
-
const s2 = p.spinner();
|
|
563
|
-
s2.start("Installing dependencies");
|
|
564
|
-
try {
|
|
565
|
-
exec(`${installCommand(pm)} @farming-labs/docs @farming-labs/next @farming-labs/fumadocs`, cwd);
|
|
566
|
-
const devDeps = [
|
|
567
|
-
"@tailwindcss/postcss",
|
|
568
|
-
"postcss",
|
|
569
|
-
"tailwindcss",
|
|
570
|
-
"@types/mdx",
|
|
571
|
-
"@types/node"
|
|
572
|
-
];
|
|
573
|
-
const allDeps = {
|
|
574
|
-
...pkgJson.dependencies,
|
|
575
|
-
...pkgJson.devDependencies
|
|
576
|
-
};
|
|
577
|
-
const missingDevDeps = devDeps.filter((d) => !allDeps[d]);
|
|
578
|
-
if (missingDevDeps.length > 0) exec(`${devInstallCommand(pm)} ${missingDevDeps.join(" ")}`, cwd);
|
|
579
|
-
} catch {
|
|
580
|
-
s2.stop("Failed to install dependencies");
|
|
581
|
-
p.log.error(`Dependency installation failed. Run the install command manually:
|
|
582
|
-
${pc.cyan(`${installCommand(pm)} @farming-labs/docs`)}`);
|
|
583
|
-
p.outro(pc.yellow("Setup partially complete. Install deps and run dev server manually."));
|
|
584
|
-
process.exit(1);
|
|
585
|
-
}
|
|
586
|
-
s2.stop("Dependencies installed");
|
|
587
|
-
const startDev = await p.confirm({
|
|
588
|
-
message: "Start the dev server now?",
|
|
589
|
-
initialValue: true
|
|
590
|
-
});
|
|
591
|
-
if (p.isCancel(startDev) || !startDev) {
|
|
592
|
-
p.log.info(`You can start the dev server later with:
|
|
593
|
-
${pc.cyan(`${pm === "yarn" ? "yarn" : pm + " run"} dev`)}`);
|
|
594
|
-
p.outro(pc.green("Done! Happy documenting."));
|
|
595
|
-
process.exit(0);
|
|
596
|
-
}
|
|
597
|
-
p.log.step("Starting dev server...");
|
|
598
|
-
try {
|
|
599
|
-
const child = await spawnAndWaitFor("npx", [
|
|
600
|
-
"next",
|
|
601
|
-
"dev",
|
|
602
|
-
"--webpack"
|
|
603
|
-
], cwd, "Ready", 6e4);
|
|
604
|
-
const url = `http://localhost:3000/${entryPath}`;
|
|
605
|
-
console.log();
|
|
606
|
-
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.`);
|
|
607
|
-
p.outro(pc.green("Happy documenting!"));
|
|
608
|
-
await new Promise((resolve) => {
|
|
609
|
-
child.on("close", () => resolve());
|
|
610
|
-
process.on("SIGINT", () => {
|
|
611
|
-
child.kill("SIGINT");
|
|
612
|
-
resolve();
|
|
613
|
-
});
|
|
614
|
-
process.on("SIGTERM", () => {
|
|
615
|
-
child.kill("SIGTERM");
|
|
616
|
-
resolve();
|
|
617
|
-
});
|
|
618
|
-
});
|
|
619
|
-
} catch (err) {
|
|
620
|
-
p.log.error(`Could not start dev server. Try running manually:
|
|
621
|
-
${pc.cyan("npx next dev --webpack")}`);
|
|
622
|
-
p.outro(pc.yellow("Setup complete. Start the server manually."));
|
|
623
|
-
process.exit(1);
|
|
624
|
-
}
|
|
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
|
+
`;
|
|
625
613
|
}
|
|
614
|
+
function svelteDocsLayoutTemplate(cfg) {
|
|
615
|
+
return `\
|
|
616
|
+
<script>
|
|
617
|
+
import { DocsLayout } from "@farming-labs/svelte-theme";
|
|
618
|
+
import config from "${svelteLayoutConfigImport(cfg.useAlias)}";
|
|
626
619
|
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
else {
|
|
635
|
-
console.error(pc.red(`Unknown command: ${command}`));
|
|
636
|
-
console.error();
|
|
637
|
-
printHelp();
|
|
638
|
-
process.exit(1);
|
|
639
|
-
}
|
|
620
|
+
let { data, children } = $props();
|
|
621
|
+
<\/script>
|
|
622
|
+
|
|
623
|
+
<DocsLayout tree={data.tree} {config}>
|
|
624
|
+
{@render children()}
|
|
625
|
+
</DocsLayout>
|
|
626
|
+
`;
|
|
640
627
|
}
|
|
641
|
-
function
|
|
642
|
-
|
|
643
|
-
${
|
|
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)}";
|
|
644
638
|
|
|
645
|
-
$
|
|
646
|
-
|
|
639
|
+
let { data } = $props();
|
|
640
|
+
<\/script>
|
|
647
641
|
|
|
648
|
-
|
|
649
|
-
|
|
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
|
+
const contentDirName = cfg.entry ?? "docs";
|
|
1194
|
+
return `\
|
|
1195
|
+
import { createDocsServer } from "@farming-labs/nuxt/server";
|
|
1196
|
+
import config from "${cfg.useAlias ? "~/docs.config" : "../../docs.config"}";
|
|
1197
|
+
|
|
1198
|
+
const contentFiles = import.meta.glob("/${contentDirName}/**/*.{md,mdx}", {
|
|
1199
|
+
query: "?raw",
|
|
1200
|
+
import: "default",
|
|
1201
|
+
eager: true,
|
|
1202
|
+
}) as Record<string, string>;
|
|
1203
|
+
|
|
1204
|
+
export const docsServer = createDocsServer({
|
|
1205
|
+
...config,
|
|
1206
|
+
_preloadedContent: contentFiles,
|
|
1207
|
+
});
|
|
1208
|
+
`;
|
|
1209
|
+
}
|
|
1210
|
+
function nuxtServerApiDocsGetTemplate() {
|
|
1211
|
+
return `\
|
|
1212
|
+
import { getRequestURL } from "h3";
|
|
1213
|
+
import { docsServer } from "../utils/docs-server";
|
|
1214
|
+
|
|
1215
|
+
export default defineEventHandler((event) => {
|
|
1216
|
+
const url = getRequestURL(event);
|
|
1217
|
+
const request = new Request(url.href, {
|
|
1218
|
+
method: event.method,
|
|
1219
|
+
headers: event.headers,
|
|
1220
|
+
});
|
|
1221
|
+
return docsServer.GET({ request });
|
|
1222
|
+
});
|
|
1223
|
+
`;
|
|
1224
|
+
}
|
|
1225
|
+
function nuxtServerApiDocsPostTemplate() {
|
|
1226
|
+
return `\
|
|
1227
|
+
import { getRequestURL, readRawBody } from "h3";
|
|
1228
|
+
import { docsServer } from "../utils/docs-server";
|
|
1229
|
+
|
|
1230
|
+
export default defineEventHandler(async (event) => {
|
|
1231
|
+
const url = getRequestURL(event);
|
|
1232
|
+
const body = await readRawBody(event);
|
|
1233
|
+
const request = new Request(url.href, {
|
|
1234
|
+
method: "POST",
|
|
1235
|
+
headers: event.headers,
|
|
1236
|
+
body: body ?? undefined,
|
|
1237
|
+
});
|
|
1238
|
+
return docsServer.POST({ request });
|
|
1239
|
+
});
|
|
1240
|
+
`;
|
|
1241
|
+
}
|
|
1242
|
+
function nuxtServerApiDocsLoadTemplate() {
|
|
1243
|
+
return `\
|
|
1244
|
+
import { getQuery } from "h3";
|
|
1245
|
+
import { docsServer } from "../../utils/docs-server";
|
|
1246
|
+
|
|
1247
|
+
export default defineEventHandler(async (event) => {
|
|
1248
|
+
const query = getQuery(event);
|
|
1249
|
+
const pathname = (query.pathname as string) ?? "/docs";
|
|
1250
|
+
return docsServer.load(pathname);
|
|
1251
|
+
});
|
|
1252
|
+
`;
|
|
1253
|
+
}
|
|
1254
|
+
function nuxtDocsPageTemplate(cfg) {
|
|
1255
|
+
return `\
|
|
1256
|
+
<script setup lang="ts">
|
|
1257
|
+
import { DocsLayout, DocsContent } from "@farming-labs/nuxt-theme";
|
|
1258
|
+
import config from "${cfg.useAlias ? "~/docs.config" : "../../docs.config"}";
|
|
1259
|
+
|
|
1260
|
+
const route = useRoute();
|
|
1261
|
+
const pathname = computed(() => route.path);
|
|
1262
|
+
|
|
1263
|
+
const { data, error } = await useAsyncData(\`docs-\${pathname.value}\`, () =>
|
|
1264
|
+
$fetch("/api/docs/load", {
|
|
1265
|
+
query: { pathname: pathname.value },
|
|
1266
|
+
})
|
|
1267
|
+
);
|
|
1268
|
+
|
|
1269
|
+
if (error.value) {
|
|
1270
|
+
throw createError({
|
|
1271
|
+
statusCode: 404,
|
|
1272
|
+
statusMessage: "Page not found",
|
|
1273
|
+
});
|
|
1274
|
+
}
|
|
1275
|
+
<\/script>
|
|
1276
|
+
|
|
1277
|
+
<template>
|
|
1278
|
+
<div v-if="data" class="fd-docs-wrapper">
|
|
1279
|
+
<DocsLayout :tree="data.tree" :config="config">
|
|
1280
|
+
<DocsContent :data="data" :config="config" />
|
|
1281
|
+
</DocsLayout>
|
|
1282
|
+
</div>
|
|
1283
|
+
</template>
|
|
1284
|
+
`;
|
|
1285
|
+
}
|
|
1286
|
+
function nuxtConfigTemplate(cfg) {
|
|
1287
|
+
return `\
|
|
1288
|
+
export default defineNuxtConfig({
|
|
1289
|
+
compatibilityDate: "2024-11-01",
|
|
1290
|
+
|
|
1291
|
+
css: ["@farming-labs/nuxt-theme/${getThemeInfo(cfg.theme).nuxtCssTheme}/css"],
|
|
1292
|
+
|
|
1293
|
+
vite: {
|
|
1294
|
+
optimizeDeps: {
|
|
1295
|
+
include: ["@farming-labs/docs", "@farming-labs/nuxt", "@farming-labs/nuxt-theme"],
|
|
1296
|
+
},
|
|
1297
|
+
},
|
|
1298
|
+
|
|
1299
|
+
nitro: {
|
|
1300
|
+
moduleSideEffects: ["@farming-labs/nuxt/server"],
|
|
1301
|
+
},
|
|
1302
|
+
});
|
|
1303
|
+
`;
|
|
1304
|
+
}
|
|
1305
|
+
function nuxtWelcomePageTemplate(cfg) {
|
|
1306
|
+
return `\
|
|
1307
|
+
---
|
|
1308
|
+
order: 1
|
|
1309
|
+
title: Documentation
|
|
1310
|
+
description: Welcome to ${cfg.projectName} documentation
|
|
1311
|
+
icon: book
|
|
1312
|
+
---
|
|
1313
|
+
|
|
1314
|
+
# Welcome to ${cfg.projectName}
|
|
1315
|
+
|
|
1316
|
+
Get started with our documentation. Browse the pages on the left to learn more.
|
|
1317
|
+
|
|
1318
|
+
## Overview
|
|
1319
|
+
|
|
1320
|
+
This documentation was generated by \`@farming-labs/docs\`. Edit the markdown files in \`${cfg.entry}/\` to customize.
|
|
1321
|
+
|
|
1322
|
+
## Features
|
|
1323
|
+
|
|
1324
|
+
- **Markdown Support** — Write docs with standard Markdown
|
|
1325
|
+
- **Syntax Highlighting** — Code blocks with automatic highlighting
|
|
1326
|
+
- **Dark Mode** — Built-in theme switching
|
|
1327
|
+
- **Search** — Full-text search across all pages (⌘K)
|
|
1328
|
+
- **Responsive** — Works on any screen size
|
|
1329
|
+
|
|
1330
|
+
---
|
|
1331
|
+
|
|
1332
|
+
## Next Steps
|
|
1333
|
+
|
|
1334
|
+
Start by reading the [Installation](/${cfg.entry}/installation) guide, then follow the [Quickstart](/${cfg.entry}/quickstart) to build something.
|
|
1335
|
+
`;
|
|
1336
|
+
}
|
|
1337
|
+
function nuxtInstallationPageTemplate(cfg) {
|
|
1338
|
+
const t = getThemeInfo(cfg.theme);
|
|
1339
|
+
return `\
|
|
1340
|
+
---
|
|
1341
|
+
order: 3
|
|
1342
|
+
title: Installation
|
|
1343
|
+
description: How to install and set up ${cfg.projectName}
|
|
1344
|
+
icon: terminal
|
|
1345
|
+
---
|
|
1346
|
+
|
|
1347
|
+
# Installation
|
|
1348
|
+
|
|
1349
|
+
Follow these steps to install and configure ${cfg.projectName}.
|
|
1350
|
+
|
|
1351
|
+
## Prerequisites
|
|
1352
|
+
|
|
1353
|
+
- Node.js 18+
|
|
1354
|
+
- A package manager (pnpm, npm, or yarn)
|
|
1355
|
+
|
|
1356
|
+
## Install Dependencies
|
|
1357
|
+
|
|
1358
|
+
\`\`\`bash
|
|
1359
|
+
pnpm add @farming-labs/docs @farming-labs/nuxt @farming-labs/nuxt-theme
|
|
1360
|
+
\`\`\`
|
|
1361
|
+
|
|
1362
|
+
## Configuration
|
|
1363
|
+
|
|
1364
|
+
Your project includes a \`docs.config.ts\` at the root:
|
|
1365
|
+
|
|
1366
|
+
\`\`\`ts title="docs.config.ts"
|
|
1367
|
+
import { defineDocs } from "@farming-labs/docs";
|
|
1368
|
+
import { ${t.factory} } from "${t.nuxtImport}";
|
|
1369
|
+
|
|
1370
|
+
export default defineDocs({
|
|
1371
|
+
entry: "${cfg.entry}",
|
|
1372
|
+
contentDir: "${cfg.entry}",
|
|
1373
|
+
theme: ${t.factory}({
|
|
1374
|
+
ui: { colors: { primary: "#6366f1" } },
|
|
1375
|
+
}),
|
|
1376
|
+
});
|
|
1377
|
+
\`\`\`
|
|
1378
|
+
|
|
1379
|
+
## Project Structure
|
|
1380
|
+
|
|
1381
|
+
\`\`\`
|
|
1382
|
+
${cfg.entry}/ # Markdown content
|
|
1383
|
+
page.md
|
|
1384
|
+
installation/page.md
|
|
1385
|
+
quickstart/page.md
|
|
1386
|
+
server/
|
|
1387
|
+
utils/docs-server.ts # createDocsServer + preloaded content
|
|
1388
|
+
api/docs/
|
|
1389
|
+
load.get.ts # Page data API
|
|
1390
|
+
docs.get.ts # Search API
|
|
1391
|
+
docs.post.ts # AI chat API
|
|
1392
|
+
pages/
|
|
1393
|
+
${cfg.entry}/[[...slug]].vue # Docs catch-all page
|
|
1394
|
+
docs.config.ts
|
|
1395
|
+
nuxt.config.ts
|
|
1396
|
+
\`\`\`
|
|
1397
|
+
|
|
1398
|
+
## What's Next?
|
|
1399
|
+
|
|
1400
|
+
Head to the [Quickstart](/${cfg.entry}/quickstart) guide to start writing your first page.
|
|
1401
|
+
`;
|
|
1402
|
+
}
|
|
1403
|
+
function nuxtQuickstartPageTemplate(cfg) {
|
|
1404
|
+
const t = getThemeInfo(cfg.theme);
|
|
1405
|
+
return `\
|
|
1406
|
+
---
|
|
1407
|
+
order: 2
|
|
1408
|
+
title: Quickstart
|
|
1409
|
+
description: Get up and running in minutes
|
|
1410
|
+
icon: rocket
|
|
1411
|
+
---
|
|
1412
|
+
|
|
1413
|
+
# Quickstart
|
|
1414
|
+
|
|
1415
|
+
This guide walks you through creating your first documentation page.
|
|
1416
|
+
|
|
1417
|
+
## Creating a Page
|
|
1418
|
+
|
|
1419
|
+
Create a new folder under \`${cfg.entry}/\` with a \`page.md\` file:
|
|
1420
|
+
|
|
1421
|
+
\`\`\`bash
|
|
1422
|
+
mkdir -p ${cfg.entry}/my-page
|
|
1423
|
+
\`\`\`
|
|
1424
|
+
|
|
1425
|
+
Then create \`${cfg.entry}/my-page/page.md\`:
|
|
1426
|
+
|
|
1427
|
+
\`\`\`md
|
|
1428
|
+
---
|
|
1429
|
+
title: "My Page"
|
|
1430
|
+
description: "A custom documentation page"
|
|
1431
|
+
---
|
|
1432
|
+
|
|
1433
|
+
# My Page
|
|
1434
|
+
|
|
1435
|
+
Write your content here using **Markdown**.
|
|
1436
|
+
\`\`\`
|
|
1437
|
+
|
|
1438
|
+
Your page is now available at \`/${cfg.entry}/my-page\`.
|
|
1439
|
+
|
|
1440
|
+
## Customizing the Theme
|
|
1441
|
+
|
|
1442
|
+
Edit \`docs.config.ts\` to change colors and typography:
|
|
1443
|
+
|
|
1444
|
+
\`\`\`ts
|
|
1445
|
+
theme: ${t.factory}({
|
|
1446
|
+
ui: {
|
|
1447
|
+
colors: { primary: "#22c55e" },
|
|
1448
|
+
},
|
|
1449
|
+
}),
|
|
1450
|
+
\`\`\`
|
|
1451
|
+
|
|
1452
|
+
## Deploying
|
|
1453
|
+
|
|
1454
|
+
Build your docs for production:
|
|
1455
|
+
|
|
1456
|
+
\`\`\`bash
|
|
1457
|
+
pnpm build
|
|
1458
|
+
\`\`\`
|
|
1459
|
+
|
|
1460
|
+
Deploy to Vercel, Netlify, or any Node.js hosting platform.
|
|
1461
|
+
`;
|
|
1462
|
+
}
|
|
1463
|
+
function nuxtGlobalCssTemplate(theme) {
|
|
1464
|
+
return `\
|
|
1465
|
+
@import "@farming-labs/nuxt-theme/${theme}/css";
|
|
1466
|
+
`;
|
|
1467
|
+
}
|
|
1468
|
+
function nuxtCssImportLine(theme) {
|
|
1469
|
+
return `@import "@farming-labs/nuxt-theme/${theme}/css";`;
|
|
1470
|
+
}
|
|
1471
|
+
function injectNuxtCssImport(existingContent, theme) {
|
|
1472
|
+
const importLine = nuxtCssImportLine(theme);
|
|
1473
|
+
if (existingContent.includes(importLine)) return null;
|
|
1474
|
+
const lines = existingContent.split("\n");
|
|
1475
|
+
const lastImportIdx = lines.reduce((acc, l, i) => l.trimStart().startsWith("@import") ? i : acc, -1);
|
|
1476
|
+
if (lastImportIdx >= 0) lines.splice(lastImportIdx + 1, 0, importLine);
|
|
1477
|
+
else lines.unshift(importLine);
|
|
1478
|
+
return lines.join("\n");
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
//#endregion
|
|
1482
|
+
//#region src/cli/init.ts
|
|
1483
|
+
const EXAMPLES_REPO = "farming-labs/docs";
|
|
1484
|
+
const VALID_TEMPLATES = [
|
|
1485
|
+
"next",
|
|
1486
|
+
"nuxt",
|
|
1487
|
+
"sveltekit",
|
|
1488
|
+
"astro"
|
|
1489
|
+
];
|
|
1490
|
+
async function init(options = {}) {
|
|
1491
|
+
const cwd = process.cwd();
|
|
1492
|
+
p.intro(pc.bgCyan(pc.black(" @farming-labs/docs ")));
|
|
1493
|
+
if (options.template) {
|
|
1494
|
+
const template = options.template.toLowerCase();
|
|
1495
|
+
if (!VALID_TEMPLATES.includes(template)) {
|
|
1496
|
+
p.log.error(`Invalid ${pc.cyan("--template")}. Use one of: ${VALID_TEMPLATES.map((t) => pc.cyan(t)).join(", ")}`);
|
|
1497
|
+
process.exit(1);
|
|
1498
|
+
}
|
|
1499
|
+
let projectName = options.name?.trim();
|
|
1500
|
+
if (!projectName) {
|
|
1501
|
+
const nameAnswer = await p.text({
|
|
1502
|
+
message: "Project name? (we'll create this folder and bootstrap the app here)",
|
|
1503
|
+
placeholder: "my-docs",
|
|
1504
|
+
defaultValue: "my-docs",
|
|
1505
|
+
validate: (value) => {
|
|
1506
|
+
const v = (value ?? "").trim();
|
|
1507
|
+
if (!v) return "Project name is required";
|
|
1508
|
+
if (v.includes("/") || v.includes("\\")) return "Project name cannot contain path separators";
|
|
1509
|
+
if (v.includes(" ")) return "Project name cannot contain spaces";
|
|
1510
|
+
}
|
|
1511
|
+
});
|
|
1512
|
+
if (p.isCancel(nameAnswer)) {
|
|
1513
|
+
p.outro(pc.red("Init cancelled."));
|
|
1514
|
+
process.exit(0);
|
|
1515
|
+
}
|
|
1516
|
+
projectName = nameAnswer.trim();
|
|
1517
|
+
}
|
|
1518
|
+
const templateLabel = template === "next" ? "Next.js" : template === "nuxt" ? "Nuxt" : template === "sveltekit" ? "SvelteKit" : "Astro";
|
|
1519
|
+
const targetDir = path.join(cwd, projectName);
|
|
1520
|
+
const fs = await import("node:fs");
|
|
1521
|
+
if (fs.existsSync(targetDir)) {
|
|
1522
|
+
p.log.error(`Directory ${pc.cyan(projectName)} already exists. Choose a different ${pc.cyan("--name")} or remove it.`);
|
|
1523
|
+
process.exit(1);
|
|
1524
|
+
}
|
|
1525
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
1526
|
+
p.log.step(`Bootstrapping project with ${pc.cyan(`'${projectName}'`)} (${templateLabel})...`);
|
|
1527
|
+
try {
|
|
1528
|
+
exec(`npx degit ${EXAMPLES_REPO}/examples/${template} . --force`, targetDir);
|
|
1529
|
+
} catch (err) {
|
|
1530
|
+
p.log.error("Failed to bootstrap. Check your connection and that the repo exists.");
|
|
1531
|
+
process.exit(1);
|
|
1532
|
+
}
|
|
1533
|
+
p.log.success(`Bootstrapped ${pc.cyan(`'${projectName}'`)}. Installing dependencies...`);
|
|
1534
|
+
const pm = detectPackageManager(targetDir);
|
|
1535
|
+
try {
|
|
1536
|
+
if (pm === "pnpm") exec("pnpm install", targetDir);
|
|
1537
|
+
else if (pm === "yarn") exec("yarn install", targetDir);
|
|
1538
|
+
else if (pm === "bun") exec("bun install", targetDir);
|
|
1539
|
+
else exec("npm install", targetDir);
|
|
1540
|
+
} catch {
|
|
1541
|
+
p.log.warn("Dependency install failed. Run your package manager install command manually.");
|
|
1542
|
+
}
|
|
1543
|
+
const devCmd = pm === "yarn" ? "yarn dev" : pm === "bun" ? "bun dev" : `${pm} run dev`;
|
|
1544
|
+
p.outro(pc.green(`Done! Run ${pc.cyan(`cd ${projectName} && ${devCmd}`)} to start the dev server.`));
|
|
1545
|
+
process.exit(0);
|
|
1546
|
+
}
|
|
1547
|
+
let framework = detectFramework(cwd);
|
|
1548
|
+
if (framework) {
|
|
1549
|
+
const frameworkName = framework === "nextjs" ? "Next.js" : framework === "sveltekit" ? "SvelteKit" : framework === "astro" ? "Astro" : "Nuxt";
|
|
1550
|
+
p.log.success(`Detected framework: ${pc.cyan(frameworkName)}`);
|
|
1551
|
+
} else {
|
|
1552
|
+
p.log.warn("Could not auto-detect a framework from " + pc.cyan("package.json") + ".");
|
|
1553
|
+
const picked = await p.select({
|
|
1554
|
+
message: "Which framework are you using?",
|
|
1555
|
+
options: [
|
|
1556
|
+
{
|
|
1557
|
+
value: "nextjs",
|
|
1558
|
+
label: "Next.js",
|
|
1559
|
+
hint: "React framework with App Router"
|
|
1560
|
+
},
|
|
1561
|
+
{
|
|
1562
|
+
value: "sveltekit",
|
|
1563
|
+
label: "SvelteKit",
|
|
1564
|
+
hint: "Svelte framework with file-based routing"
|
|
1565
|
+
},
|
|
1566
|
+
{
|
|
1567
|
+
value: "astro",
|
|
1568
|
+
label: "Astro",
|
|
1569
|
+
hint: "Content-focused framework with island architecture"
|
|
1570
|
+
},
|
|
1571
|
+
{
|
|
1572
|
+
value: "nuxt",
|
|
1573
|
+
label: "Nuxt",
|
|
1574
|
+
hint: "Vue 3 framework with file-based routing and Nitro server"
|
|
1575
|
+
}
|
|
1576
|
+
]
|
|
1577
|
+
});
|
|
1578
|
+
if (p.isCancel(picked)) {
|
|
1579
|
+
p.outro(pc.red("Init cancelled."));
|
|
1580
|
+
process.exit(0);
|
|
1581
|
+
}
|
|
1582
|
+
framework = picked;
|
|
1583
|
+
}
|
|
1584
|
+
const theme = await p.select({
|
|
1585
|
+
message: "Which theme would you like to use?",
|
|
1586
|
+
options: [
|
|
1587
|
+
{
|
|
1588
|
+
value: "fumadocs",
|
|
1589
|
+
label: "Fumadocs (Default)",
|
|
1590
|
+
hint: "Clean, modern docs theme with sidebar, search, and dark mode"
|
|
1591
|
+
},
|
|
1592
|
+
{
|
|
1593
|
+
value: "darksharp",
|
|
1594
|
+
label: "Darksharp",
|
|
1595
|
+
hint: "All-black, sharp edges, zero-radius look"
|
|
1596
|
+
},
|
|
1597
|
+
{
|
|
1598
|
+
value: "pixel-border",
|
|
1599
|
+
label: "Pixel Border",
|
|
1600
|
+
hint: "Rounded borders, pixel-perfect spacing, refined sidebar"
|
|
1601
|
+
},
|
|
1602
|
+
{
|
|
1603
|
+
value: "colorful",
|
|
1604
|
+
label: "Colorful",
|
|
1605
|
+
hint: "Fumadocs-style neutral theme with description support"
|
|
1606
|
+
},
|
|
1607
|
+
{
|
|
1608
|
+
value: "darkbold",
|
|
1609
|
+
label: "DarkBold",
|
|
1610
|
+
hint: "Pure monochrome, Geist typography, clean minimalism"
|
|
1611
|
+
},
|
|
1612
|
+
{
|
|
1613
|
+
value: "shiny",
|
|
1614
|
+
label: "Shiny",
|
|
1615
|
+
hint: "Glossy, modern look with subtle shimmer effects"
|
|
1616
|
+
},
|
|
1617
|
+
{
|
|
1618
|
+
value: "greentree",
|
|
1619
|
+
label: "GreenTree",
|
|
1620
|
+
hint: "Emerald green accent, Inter font, Mintlify-inspired"
|
|
1621
|
+
}
|
|
1622
|
+
]
|
|
1623
|
+
});
|
|
1624
|
+
if (p.isCancel(theme)) {
|
|
1625
|
+
p.outro(pc.red("Init cancelled."));
|
|
1626
|
+
process.exit(0);
|
|
1627
|
+
}
|
|
1628
|
+
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)`;
|
|
1629
|
+
const useAlias = await p.confirm({
|
|
1630
|
+
message: `Use path aliases for imports? ${pc.dim(aliasHint)}`,
|
|
1631
|
+
initialValue: false
|
|
1632
|
+
});
|
|
1633
|
+
if (p.isCancel(useAlias)) {
|
|
1634
|
+
p.outro(pc.red("Init cancelled."));
|
|
1635
|
+
process.exit(0);
|
|
1636
|
+
}
|
|
1637
|
+
let astroAdapter;
|
|
1638
|
+
if (framework === "astro") {
|
|
1639
|
+
const adapter = await p.select({
|
|
1640
|
+
message: "Where will you deploy?",
|
|
1641
|
+
options: [
|
|
1642
|
+
{
|
|
1643
|
+
value: "vercel",
|
|
1644
|
+
label: "Vercel",
|
|
1645
|
+
hint: "Recommended for most projects"
|
|
1646
|
+
},
|
|
1647
|
+
{
|
|
1648
|
+
value: "netlify",
|
|
1649
|
+
label: "Netlify"
|
|
1650
|
+
},
|
|
1651
|
+
{
|
|
1652
|
+
value: "cloudflare",
|
|
1653
|
+
label: "Cloudflare Pages"
|
|
1654
|
+
},
|
|
1655
|
+
{
|
|
1656
|
+
value: "node",
|
|
1657
|
+
label: "Node.js / Docker",
|
|
1658
|
+
hint: "Self-hosted standalone server"
|
|
1659
|
+
}
|
|
1660
|
+
]
|
|
1661
|
+
});
|
|
1662
|
+
if (p.isCancel(adapter)) {
|
|
1663
|
+
p.outro(pc.red("Init cancelled."));
|
|
1664
|
+
process.exit(0);
|
|
1665
|
+
}
|
|
1666
|
+
astroAdapter = adapter;
|
|
1667
|
+
}
|
|
1668
|
+
const entry = await p.text({
|
|
1669
|
+
message: "Where should your docs live?",
|
|
1670
|
+
placeholder: "docs",
|
|
1671
|
+
defaultValue: "docs",
|
|
1672
|
+
validate: (value) => {
|
|
1673
|
+
if (!value) return "Entry path is required";
|
|
1674
|
+
if (value.startsWith("/")) return "Use a relative path (no leading /)";
|
|
1675
|
+
if (value.includes(" ")) return "Path cannot contain spaces";
|
|
1676
|
+
}
|
|
1677
|
+
});
|
|
1678
|
+
if (p.isCancel(entry)) {
|
|
1679
|
+
p.outro(pc.red("Init cancelled."));
|
|
1680
|
+
process.exit(0);
|
|
1681
|
+
}
|
|
1682
|
+
const entryPath = entry;
|
|
1683
|
+
const detectedCssFiles = detectGlobalCssFiles(cwd);
|
|
1684
|
+
let globalCssRelPath;
|
|
1685
|
+
const defaultCssPath = framework === "sveltekit" ? "src/app.css" : framework === "astro" ? "src/styles/global.css" : framework === "nuxt" ? "assets/css/main.css" : "app/globals.css";
|
|
1686
|
+
if (detectedCssFiles.length === 1) {
|
|
1687
|
+
globalCssRelPath = detectedCssFiles[0];
|
|
1688
|
+
p.log.info(`Found global CSS at ${pc.cyan(globalCssRelPath)}`);
|
|
1689
|
+
} else if (detectedCssFiles.length > 1) {
|
|
1690
|
+
const picked = await p.select({
|
|
1691
|
+
message: "Multiple global CSS files found. Which one should we use?",
|
|
1692
|
+
options: detectedCssFiles.map((f) => ({
|
|
1693
|
+
value: f,
|
|
1694
|
+
label: f
|
|
1695
|
+
}))
|
|
1696
|
+
});
|
|
1697
|
+
if (p.isCancel(picked)) {
|
|
1698
|
+
p.outro(pc.red("Init cancelled."));
|
|
1699
|
+
process.exit(0);
|
|
1700
|
+
}
|
|
1701
|
+
globalCssRelPath = picked;
|
|
1702
|
+
} else {
|
|
1703
|
+
const cssPath = await p.text({
|
|
1704
|
+
message: "Where is your global CSS file?",
|
|
1705
|
+
placeholder: defaultCssPath,
|
|
1706
|
+
defaultValue: defaultCssPath,
|
|
1707
|
+
validate: (value) => {
|
|
1708
|
+
if (!value) return "CSS file path is required";
|
|
1709
|
+
if (!value.endsWith(".css")) return "Path must end with .css";
|
|
1710
|
+
}
|
|
1711
|
+
});
|
|
1712
|
+
if (p.isCancel(cssPath)) {
|
|
1713
|
+
p.outro(pc.red("Init cancelled."));
|
|
1714
|
+
process.exit(0);
|
|
1715
|
+
}
|
|
1716
|
+
globalCssRelPath = cssPath;
|
|
1717
|
+
}
|
|
1718
|
+
const pkgJsonContent = readFileSafe(path.join(cwd, "package.json"));
|
|
1719
|
+
const pkgJson = pkgJsonContent ? JSON.parse(pkgJsonContent) : { name: "my-project" };
|
|
1720
|
+
const cfg = {
|
|
1721
|
+
entry: entryPath,
|
|
1722
|
+
theme,
|
|
1723
|
+
projectName: pkgJson.name || "My Project",
|
|
1724
|
+
framework,
|
|
1725
|
+
useAlias,
|
|
1726
|
+
astroAdapter
|
|
1727
|
+
};
|
|
1728
|
+
const s = p.spinner();
|
|
1729
|
+
s.start("Scaffolding docs files");
|
|
1730
|
+
const written = [];
|
|
1731
|
+
const skipped = [];
|
|
1732
|
+
function write(rel, content, overwrite = false) {
|
|
1733
|
+
if (writeFileSafe(path.join(cwd, rel), content, overwrite)) written.push(rel);
|
|
1734
|
+
else skipped.push(rel);
|
|
1735
|
+
}
|
|
1736
|
+
if (framework === "sveltekit") scaffoldSvelteKit(cwd, cfg, globalCssRelPath, write, skipped, written);
|
|
1737
|
+
else if (framework === "astro") scaffoldAstro(cwd, cfg, globalCssRelPath, write, skipped, written);
|
|
1738
|
+
else if (framework === "nuxt") scaffoldNuxt(cwd, cfg, globalCssRelPath, write, skipped, written);
|
|
1739
|
+
else scaffoldNextJs(cwd, cfg, globalCssRelPath, write, skipped, written);
|
|
1740
|
+
s.stop("Files scaffolded");
|
|
1741
|
+
if (written.length > 0) p.log.success(`Created ${written.length} file${written.length > 1 ? "s" : ""}:\n` + written.map((f) => ` ${pc.green("+")} ${f}`).join("\n"));
|
|
1742
|
+
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"));
|
|
1743
|
+
const pm = detectPackageManager(cwd);
|
|
1744
|
+
p.log.info(`Using ${pc.cyan(pm)} as package manager`);
|
|
1745
|
+
const s2 = p.spinner();
|
|
1746
|
+
s2.start("Installing dependencies");
|
|
1747
|
+
try {
|
|
1748
|
+
if (framework === "sveltekit") exec(`${installCommand(pm)} @farming-labs/docs @farming-labs/svelte @farming-labs/svelte-theme`, cwd);
|
|
1749
|
+
else if (framework === "astro") {
|
|
1750
|
+
const adapterPkg = getAstroAdapterPkg(cfg.astroAdapter ?? "vercel");
|
|
1751
|
+
exec(`${installCommand(pm)} @farming-labs/docs @farming-labs/astro @farming-labs/astro-theme ${adapterPkg}`, cwd);
|
|
1752
|
+
} else if (framework === "nuxt") exec(`${installCommand(pm)} @farming-labs/docs @farming-labs/nuxt @farming-labs/nuxt-theme`, cwd);
|
|
1753
|
+
else {
|
|
1754
|
+
exec(`${installCommand(pm)} @farming-labs/docs @farming-labs/next @farming-labs/theme`, cwd);
|
|
1755
|
+
const devDeps = [
|
|
1756
|
+
"@tailwindcss/postcss",
|
|
1757
|
+
"postcss",
|
|
1758
|
+
"tailwindcss",
|
|
1759
|
+
"@types/mdx",
|
|
1760
|
+
"@types/node"
|
|
1761
|
+
];
|
|
1762
|
+
const allDeps = {
|
|
1763
|
+
...pkgJson.dependencies,
|
|
1764
|
+
...pkgJson.devDependencies
|
|
1765
|
+
};
|
|
1766
|
+
const missingDevDeps = devDeps.filter((d) => !allDeps[d]);
|
|
1767
|
+
if (missingDevDeps.length > 0) exec(`${devInstallCommand(pm)} ${missingDevDeps.join(" ")}`, cwd);
|
|
1768
|
+
}
|
|
1769
|
+
} catch {
|
|
1770
|
+
s2.stop("Failed to install dependencies");
|
|
1771
|
+
p.log.error(`Dependency installation failed. Run the install command manually:
|
|
1772
|
+
${pc.cyan(`${installCommand(pm)} @farming-labs/docs`)}`);
|
|
1773
|
+
p.outro(pc.yellow("Setup partially complete. Install deps and run dev server manually."));
|
|
1774
|
+
process.exit(1);
|
|
1775
|
+
}
|
|
1776
|
+
s2.stop("Dependencies installed");
|
|
1777
|
+
const startDev = await p.confirm({
|
|
1778
|
+
message: "Start the dev server now?",
|
|
1779
|
+
initialValue: true
|
|
1780
|
+
});
|
|
1781
|
+
if (p.isCancel(startDev) || !startDev) {
|
|
1782
|
+
p.log.info(`You can start the dev server later with:
|
|
1783
|
+
${pc.cyan(`${pm === "yarn" ? "yarn" : pm + " run"} dev`)}`);
|
|
1784
|
+
p.outro(pc.green("Done! Happy documenting."));
|
|
1785
|
+
process.exit(0);
|
|
1786
|
+
}
|
|
1787
|
+
p.log.step("Starting dev server...");
|
|
1788
|
+
const devCommand = framework === "sveltekit" ? {
|
|
1789
|
+
cmd: "npx",
|
|
1790
|
+
args: ["vite", "dev"],
|
|
1791
|
+
waitFor: "ready"
|
|
1792
|
+
} : framework === "astro" ? {
|
|
1793
|
+
cmd: "npx",
|
|
1794
|
+
args: ["astro", "dev"],
|
|
1795
|
+
waitFor: "ready"
|
|
1796
|
+
} : framework === "nuxt" ? {
|
|
1797
|
+
cmd: "npx",
|
|
1798
|
+
args: ["nuxt", "dev"],
|
|
1799
|
+
waitFor: "Local"
|
|
1800
|
+
} : {
|
|
1801
|
+
cmd: "npx",
|
|
1802
|
+
args: [
|
|
1803
|
+
"next",
|
|
1804
|
+
"dev",
|
|
1805
|
+
"--webpack"
|
|
1806
|
+
],
|
|
1807
|
+
waitFor: "Ready"
|
|
1808
|
+
};
|
|
1809
|
+
const defaultPort = framework === "sveltekit" ? "5173" : framework === "astro" ? "4321" : framework === "nuxt" ? "3000" : "3000";
|
|
1810
|
+
try {
|
|
1811
|
+
const child = await spawnAndWaitFor(devCommand.cmd, devCommand.args, cwd, devCommand.waitFor, 6e4);
|
|
1812
|
+
const url = `http://localhost:${defaultPort}/${entryPath}`;
|
|
1813
|
+
console.log();
|
|
1814
|
+
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.`);
|
|
1815
|
+
p.outro(pc.green("Happy documenting!"));
|
|
1816
|
+
await new Promise((resolve) => {
|
|
1817
|
+
child.on("close", () => resolve());
|
|
1818
|
+
process.on("SIGINT", () => {
|
|
1819
|
+
child.kill("SIGINT");
|
|
1820
|
+
resolve();
|
|
1821
|
+
});
|
|
1822
|
+
process.on("SIGTERM", () => {
|
|
1823
|
+
child.kill("SIGTERM");
|
|
1824
|
+
resolve();
|
|
1825
|
+
});
|
|
1826
|
+
});
|
|
1827
|
+
} catch (err) {
|
|
1828
|
+
const manualCmd = framework === "sveltekit" ? "npx vite dev" : framework === "astro" ? "npx astro dev" : framework === "nuxt" ? "npx nuxt dev" : "npx next dev --webpack";
|
|
1829
|
+
p.log.error(`Could not start dev server. Try running manually:
|
|
1830
|
+
${pc.cyan(manualCmd)}`);
|
|
1831
|
+
p.outro(pc.yellow("Setup complete. Start the server manually."));
|
|
1832
|
+
process.exit(1);
|
|
1833
|
+
}
|
|
1834
|
+
}
|
|
1835
|
+
function scaffoldNextJs(cwd, cfg, globalCssRelPath, write, skipped, written) {
|
|
1836
|
+
write("docs.config.ts", docsConfigTemplate(cfg));
|
|
1837
|
+
const existingNextConfig = readFileSafe(path.join(cwd, "next.config.ts")) ?? readFileSafe(path.join(cwd, "next.config.mjs")) ?? readFileSafe(path.join(cwd, "next.config.js"));
|
|
1838
|
+
if (existingNextConfig) {
|
|
1839
|
+
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";
|
|
1840
|
+
const merged = nextConfigMergedTemplate(existingNextConfig);
|
|
1841
|
+
if (merged !== existingNextConfig) {
|
|
1842
|
+
writeFileSafe(path.join(cwd, configFile), merged, true);
|
|
1843
|
+
written.push(configFile + " (updated)");
|
|
1844
|
+
} else skipped.push(configFile + " (already configured)");
|
|
1845
|
+
} else write("next.config.ts", nextConfigTemplate());
|
|
1846
|
+
write("app/layout.tsx", rootLayoutTemplate(cfg, globalCssRelPath));
|
|
1847
|
+
const globalCssAbsPath = path.join(cwd, globalCssRelPath);
|
|
1848
|
+
const existingGlobalCss = readFileSafe(globalCssAbsPath);
|
|
1849
|
+
if (existingGlobalCss) {
|
|
1850
|
+
const injected = injectCssImport(existingGlobalCss, cfg.theme);
|
|
1851
|
+
if (injected) {
|
|
1852
|
+
writeFileSafe(globalCssAbsPath, injected, true);
|
|
1853
|
+
written.push(globalCssRelPath + " (updated)");
|
|
1854
|
+
} else skipped.push(globalCssRelPath + " (already configured)");
|
|
1855
|
+
} else write(globalCssRelPath, globalCssTemplate(cfg.theme));
|
|
1856
|
+
write(`app/${cfg.entry}/layout.tsx`, docsLayoutTemplate(cfg));
|
|
1857
|
+
write("postcss.config.mjs", postcssConfigTemplate());
|
|
1858
|
+
if (!fileExists(path.join(cwd, "tsconfig.json"))) write("tsconfig.json", tsconfigTemplate(cfg.useAlias));
|
|
1859
|
+
write(`app/${cfg.entry}/page.mdx`, welcomePageTemplate(cfg));
|
|
1860
|
+
write(`app/${cfg.entry}/installation/page.mdx`, installationPageTemplate(cfg));
|
|
1861
|
+
write(`app/${cfg.entry}/quickstart/page.mdx`, quickstartPageTemplate(cfg));
|
|
1862
|
+
}
|
|
1863
|
+
function scaffoldSvelteKit(cwd, cfg, globalCssRelPath, write, skipped, written) {
|
|
1864
|
+
write("src/lib/docs.config.ts", svelteDocsConfigTemplate(cfg));
|
|
1865
|
+
write("src/lib/docs.server.ts", svelteDocsServerTemplate(cfg));
|
|
1866
|
+
write(`src/routes/${cfg.entry}/+layout.svelte`, svelteDocsLayoutTemplate(cfg));
|
|
1867
|
+
write(`src/routes/${cfg.entry}/+layout.server.js`, svelteDocsLayoutServerTemplate(cfg));
|
|
1868
|
+
write(`src/routes/${cfg.entry}/[...slug]/+page.svelte`, svelteDocsPageTemplate(cfg));
|
|
1869
|
+
if (!readFileSafe(path.join(cwd, "src/routes/+layout.svelte"))) write("src/routes/+layout.svelte", svelteRootLayoutTemplate(globalCssRelPath));
|
|
1870
|
+
const globalCssAbsPath = path.join(cwd, globalCssRelPath);
|
|
1871
|
+
const existingGlobalCss = readFileSafe(globalCssAbsPath);
|
|
1872
|
+
const cssTheme = {
|
|
1873
|
+
fumadocs: "fumadocs",
|
|
1874
|
+
darksharp: "darksharp",
|
|
1875
|
+
"pixel-border": "pixel-border",
|
|
1876
|
+
colorful: "colorful",
|
|
1877
|
+
darkbold: "darkbold",
|
|
1878
|
+
shiny: "shiny",
|
|
1879
|
+
greentree: "greentree",
|
|
1880
|
+
default: "fumadocs"
|
|
1881
|
+
}[cfg.theme] || "fumadocs";
|
|
1882
|
+
if (existingGlobalCss) {
|
|
1883
|
+
const injected = injectSvelteCssImport(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, svelteGlobalCssTemplate(cssTheme));
|
|
1889
|
+
write(`${cfg.entry}/page.md`, svelteWelcomePageTemplate(cfg));
|
|
1890
|
+
write(`${cfg.entry}/installation/page.md`, svelteInstallationPageTemplate(cfg));
|
|
1891
|
+
write(`${cfg.entry}/quickstart/page.md`, svelteQuickstartPageTemplate(cfg));
|
|
1892
|
+
}
|
|
1893
|
+
function scaffoldAstro(cwd, cfg, globalCssRelPath, write, skipped, written) {
|
|
1894
|
+
write("src/lib/docs.config.ts", astroDocsConfigTemplate(cfg));
|
|
1895
|
+
write("src/lib/docs.server.ts", astroDocsServerTemplate(cfg));
|
|
1896
|
+
if (!fileExists(path.join(cwd, "astro.config.mjs")) && !fileExists(path.join(cwd, "astro.config.ts"))) write("astro.config.mjs", astroConfigTemplate(cfg.astroAdapter ?? "vercel"));
|
|
1897
|
+
write(`src/pages/${cfg.entry}/index.astro`, astroDocsIndexTemplate(cfg));
|
|
1898
|
+
write(`src/pages/${cfg.entry}/[...slug].astro`, astroDocsPageTemplate(cfg));
|
|
1899
|
+
write(`src/pages/api/${cfg.entry}.ts`, astroApiRouteTemplate(cfg));
|
|
1900
|
+
const globalCssAbsPath = path.join(cwd, globalCssRelPath);
|
|
1901
|
+
const existingGlobalCss = readFileSafe(globalCssAbsPath);
|
|
1902
|
+
const cssTheme = {
|
|
1903
|
+
fumadocs: "fumadocs",
|
|
1904
|
+
darksharp: "darksharp",
|
|
1905
|
+
"pixel-border": "pixel-border",
|
|
1906
|
+
colorful: "colorful",
|
|
1907
|
+
darkbold: "darkbold",
|
|
1908
|
+
shiny: "shiny",
|
|
1909
|
+
greentree: "greentree",
|
|
1910
|
+
default: "fumadocs"
|
|
1911
|
+
}[cfg.theme] || "fumadocs";
|
|
1912
|
+
if (existingGlobalCss) {
|
|
1913
|
+
const injected = injectAstroCssImport(existingGlobalCss, cssTheme);
|
|
1914
|
+
if (injected) {
|
|
1915
|
+
writeFileSafe(globalCssAbsPath, injected, true);
|
|
1916
|
+
written.push(globalCssRelPath + " (updated)");
|
|
1917
|
+
} else skipped.push(globalCssRelPath + " (already configured)");
|
|
1918
|
+
} else write(globalCssRelPath, astroGlobalCssTemplate(cssTheme));
|
|
1919
|
+
write(`${cfg.entry}/page.md`, astroWelcomePageTemplate(cfg));
|
|
1920
|
+
write(`${cfg.entry}/installation/page.md`, astroInstallationPageTemplate(cfg));
|
|
1921
|
+
write(`${cfg.entry}/quickstart/page.md`, astroQuickstartPageTemplate(cfg));
|
|
1922
|
+
}
|
|
1923
|
+
function scaffoldNuxt(cwd, cfg, globalCssRelPath, write, skipped, written) {
|
|
1924
|
+
write("docs.config.ts", nuxtDocsConfigTemplate(cfg));
|
|
1925
|
+
write("server/utils/docs-server.ts", nuxtDocsServerTemplate(cfg));
|
|
1926
|
+
write("server/api/docs.get.ts", nuxtServerApiDocsGetTemplate());
|
|
1927
|
+
write("server/api/docs.post.ts", nuxtServerApiDocsPostTemplate());
|
|
1928
|
+
write("server/api/docs/load.get.ts", nuxtServerApiDocsLoadTemplate());
|
|
1929
|
+
write(`pages/${cfg.entry}/[[...slug]].vue`, nuxtDocsPageTemplate(cfg));
|
|
1930
|
+
path.join(cwd, "nuxt.config.ts");
|
|
1931
|
+
if (!fileExists(path.join(cwd, "nuxt.config.ts")) && !fileExists(path.join(cwd, "nuxt.config.js"))) write("nuxt.config.ts", nuxtConfigTemplate(cfg));
|
|
1932
|
+
const cssTheme = {
|
|
1933
|
+
fumadocs: "fumadocs",
|
|
1934
|
+
darksharp: "darksharp",
|
|
1935
|
+
"pixel-border": "pixel-border",
|
|
1936
|
+
colorful: "colorful",
|
|
1937
|
+
darkbold: "darkbold",
|
|
1938
|
+
shiny: "shiny",
|
|
1939
|
+
greentree: "greentree",
|
|
1940
|
+
default: "fumadocs"
|
|
1941
|
+
}[cfg.theme] || "fumadocs";
|
|
1942
|
+
const globalCssAbsPath = path.join(cwd, globalCssRelPath);
|
|
1943
|
+
const existingGlobalCss = readFileSafe(globalCssAbsPath);
|
|
1944
|
+
if (existingGlobalCss) {
|
|
1945
|
+
const injected = injectNuxtCssImport(existingGlobalCss, cssTheme);
|
|
1946
|
+
if (injected) {
|
|
1947
|
+
writeFileSafe(globalCssAbsPath, injected, true);
|
|
1948
|
+
written.push(globalCssRelPath + " (updated)");
|
|
1949
|
+
} else skipped.push(globalCssRelPath + " (already configured)");
|
|
1950
|
+
} else write(globalCssRelPath, nuxtGlobalCssTemplate(cssTheme));
|
|
1951
|
+
write(`${cfg.entry}/page.md`, nuxtWelcomePageTemplate(cfg));
|
|
1952
|
+
write(`${cfg.entry}/installation/page.md`, nuxtInstallationPageTemplate(cfg));
|
|
1953
|
+
write(`${cfg.entry}/quickstart/page.md`, nuxtQuickstartPageTemplate(cfg));
|
|
1954
|
+
}
|
|
1955
|
+
|
|
1956
|
+
//#endregion
|
|
1957
|
+
//#region src/cli/index.ts
|
|
1958
|
+
const args = process.argv.slice(2);
|
|
1959
|
+
const command = args[0];
|
|
1960
|
+
/** Parse flags like --template next, --name my-docs, --theme darksharp, --entry docs */
|
|
1961
|
+
function parseFlags(argv) {
|
|
1962
|
+
const flags = {};
|
|
1963
|
+
for (let i = 0; i < argv.length; i++) {
|
|
1964
|
+
const arg = argv[i];
|
|
1965
|
+
if (arg.startsWith("--") && arg.includes("=")) {
|
|
1966
|
+
const [key, value] = arg.slice(2).split("=");
|
|
1967
|
+
flags[key] = value;
|
|
1968
|
+
} else if (arg.startsWith("--") && argv[i + 1] && !argv[i + 1].startsWith("--")) {
|
|
1969
|
+
flags[arg.slice(2)] = argv[i + 1];
|
|
1970
|
+
i++;
|
|
1971
|
+
}
|
|
1972
|
+
}
|
|
1973
|
+
return flags;
|
|
1974
|
+
}
|
|
1975
|
+
async function main() {
|
|
1976
|
+
const flags = parseFlags(args);
|
|
1977
|
+
const initOptions = {
|
|
1978
|
+
template: flags.template,
|
|
1979
|
+
name: flags.name,
|
|
1980
|
+
theme: flags.theme,
|
|
1981
|
+
entry: flags.entry
|
|
1982
|
+
};
|
|
1983
|
+
if (!command || command === "init") await init(initOptions);
|
|
1984
|
+
else if (command === "--help" || command === "-h") printHelp();
|
|
1985
|
+
else if (command === "--version" || command === "-v") printVersion();
|
|
1986
|
+
else {
|
|
1987
|
+
console.error(pc.red(`Unknown command: ${command}`));
|
|
1988
|
+
console.error();
|
|
1989
|
+
printHelp();
|
|
1990
|
+
process.exit(1);
|
|
1991
|
+
}
|
|
1992
|
+
}
|
|
1993
|
+
function printHelp() {
|
|
1994
|
+
console.log(`
|
|
1995
|
+
${pc.bold("@farming-labs/docs")} — Documentation framework CLI
|
|
1996
|
+
|
|
1997
|
+
${pc.dim("Usage:")}
|
|
1998
|
+
npx @farming-labs/docs ${pc.cyan("<command>")}
|
|
1999
|
+
|
|
2000
|
+
${pc.dim("Commands:")}
|
|
2001
|
+
${pc.cyan("init")} Scaffold docs in your project (default)
|
|
2002
|
+
|
|
2003
|
+
${pc.dim("Supported frameworks:")}
|
|
2004
|
+
Next.js, SvelteKit, Astro, Nuxt
|
|
650
2005
|
|
|
651
|
-
${pc.dim("Options:")}
|
|
652
|
-
${pc.cyan("
|
|
653
|
-
${pc.cyan("
|
|
2006
|
+
${pc.dim("Options for init:")}
|
|
2007
|
+
${pc.cyan("--template <name>")} Bootstrap a project (${pc.dim("next")}, ${pc.dim("nuxt")}, ${pc.dim("sveltekit")}, ${pc.dim("astro")}); use with ${pc.cyan("--name")}
|
|
2008
|
+
${pc.cyan("--name <project>")} Project folder name when using ${pc.cyan("--template")}; prompt if omitted (e.g. ${pc.dim("my-docs")})
|
|
2009
|
+
${pc.cyan("--theme <name>")} Skip theme prompt (e.g. ${pc.dim("darksharp")}, ${pc.dim("greentree")})
|
|
2010
|
+
${pc.cyan("--entry <path>")} Skip entry path prompt (e.g. ${pc.dim("docs")})
|
|
2011
|
+
${pc.cyan("-h, --help")} Show this help message
|
|
2012
|
+
${pc.cyan("-v, --version")} Show version
|
|
654
2013
|
`);
|
|
655
2014
|
}
|
|
656
2015
|
function printVersion() {
|