@farming-labs/docs 0.1.59 → 0.1.62

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1162 @@
1
+ import { $ as svelteDocsPublicHookTemplate, A as nextConfigMergedTemplate, B as nuxtServerApiDocsRouteTemplate, C as injectRootProviderIntoLayout, D as injectTanstackVitePlugins, E as injectTanstackRootProviderIntoRoute, F as nuxtDocsConfigTemplate, G as quickstartPageTemplate, H as nuxtServerDocsPublicMiddlewareTemplate, I as nuxtDocsPageTemplate, J as svelteDocsApiRouteTemplate, K as rootLayoutTemplate, L as nuxtGlobalCssTemplate, M as nextLocaleDocPageTemplate, N as nextLocalizedPageTemplate, O as installationPageTemplate, P as nuxtConfigTemplate, Q as svelteDocsPageTemplate, R as nuxtInstallationPageTemplate, S as injectNuxtCssImport, T as injectSvelteDocsPublicHook, U as nuxtWelcomePageTemplate, V as nuxtServerApiReferenceRouteTemplate, W as postcssConfigTemplate, X as svelteDocsLayoutServerTemplate, Y as svelteDocsConfigTemplate, Z as svelteDocsLayoutTemplate, _ as getAstroAdapterPkg, _t as tanstackViteConfigTemplate, a as astroDocsIndexTemplate, at as svelteWelcomePageTemplate, b as injectAstroDocsMiddleware, bt as welcomePageTemplate, c as astroDocsServerTemplate, ct as tanstackDocsCatchAllRouteTemplate, d as astroQuickstartPageTemplate, dt as tanstackDocsIndexRouteTemplate, et as svelteDocsServerTemplate, f as astroWelcomePageTemplate, ft as tanstackDocsPublicRouteTemplate, g as docsLayoutTemplate, gt as tanstackRootRouteTemplate, h as docsConfigTemplate, ht as tanstackQuickstartPageTemplate, i as astroDocsConfigTemplate, it as svelteRootLayoutTemplate, j as nextConfigTemplate, k as nextApiReferencePageTemplate, l as astroGlobalCssTemplate, lt as tanstackDocsConfigTemplate, m as customThemeTsTemplate, mt as tanstackInstallationPageTemplate, n as astroApiRouteTemplate, nt as svelteInstallationPageTemplate, o as astroDocsMiddlewareTemplate, ot as tanstackApiDocsRouteTemplate, p as customThemeCssTemplate, pt as tanstackDocsServerTemplate, q as svelteApiReferenceRouteTemplate, r as astroConfigTemplate, rt as svelteQuickstartPageTemplate, s as astroDocsPageTemplate, st as tanstackApiReferenceRouteTemplate, t as astroApiReferenceRouteTemplate, tt as svelteGlobalCssTemplate, u as astroInstallationPageTemplate, ut as tanstackDocsFunctionsTemplate, v as globalCssTemplate, vt as tanstackWelcomePageTemplate, w as injectSvelteCssImport, x as injectCssImport, y as injectAstroCssImport, yt as tsconfigTemplate, z as nuxtQuickstartPageTemplate } from "./templates-2M1vfnf-.mjs";
2
+ import { a as devInstallCommand, c as installCommand, d as writeFileSafe, i as detectPackageManagerFromLockfile, l as readFileSafe, n as detectGlobalCssFiles, o as exec, r as detectNextAppDir, s as fileExists, t as detectFramework, u as spawnAndWaitFor } from "./utils-DSMXVnEu.mjs";
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+ import pc from "picocolors";
6
+ import * as p from "@clack/prompts";
7
+
8
+ //#region src/cli/init.ts
9
+ const EXAMPLES_REPO = "farming-labs/docs";
10
+ const VALID_TEMPLATES = [
11
+ "next",
12
+ "nuxt",
13
+ "sveltekit",
14
+ "astro",
15
+ "tanstack-start"
16
+ ];
17
+ const COMMON_LOCALE_OPTIONS = [
18
+ {
19
+ value: "en",
20
+ label: "English",
21
+ hint: "en"
22
+ },
23
+ {
24
+ value: "fr",
25
+ label: "French",
26
+ hint: "fr"
27
+ },
28
+ {
29
+ value: "es",
30
+ label: "Spanish",
31
+ hint: "es"
32
+ },
33
+ {
34
+ value: "de",
35
+ label: "German",
36
+ hint: "de"
37
+ },
38
+ {
39
+ value: "pt",
40
+ label: "Portuguese",
41
+ hint: "pt"
42
+ },
43
+ {
44
+ value: "it",
45
+ label: "Italian",
46
+ hint: "it"
47
+ },
48
+ {
49
+ value: "ja",
50
+ label: "Japanese",
51
+ hint: "ja"
52
+ },
53
+ {
54
+ value: "ko",
55
+ label: "Korean",
56
+ hint: "ko"
57
+ },
58
+ {
59
+ value: "zh",
60
+ label: "Chinese",
61
+ hint: "zh"
62
+ },
63
+ {
64
+ value: "ar",
65
+ label: "Arabic",
66
+ hint: "ar"
67
+ },
68
+ {
69
+ value: "hi",
70
+ label: "Hindi",
71
+ hint: "hi"
72
+ },
73
+ {
74
+ value: "ru",
75
+ label: "Russian",
76
+ hint: "ru"
77
+ }
78
+ ];
79
+ function normalizeLocaleCode(value) {
80
+ const trimmed = value.trim();
81
+ if (!trimmed) return "";
82
+ const [language, ...rest] = trimmed.split("-");
83
+ const normalizedLanguage = language.toLowerCase();
84
+ if (rest.length === 0) return normalizedLanguage;
85
+ return `${normalizedLanguage}-${rest.join("-").toUpperCase()}`;
86
+ }
87
+ function parseLocaleInput(input) {
88
+ return Array.from(new Set(input.split(",").map((value) => normalizeLocaleCode(value)).filter(Boolean)));
89
+ }
90
+ function normalizeEntryPath(entry) {
91
+ return entry.replace(/^\/+|\/+$/g, "");
92
+ }
93
+ function getTanstackDocsRouteDir(entry) {
94
+ return path.posix.join("src/routes", normalizeEntryPath(entry));
95
+ }
96
+ function normalizeApiRouteRoot(routeRoot) {
97
+ return routeRoot.replace(/^\/+|\/+$/g, "");
98
+ }
99
+ function detectApiRouteRoot(cwd, framework, nextAppDir = "app") {
100
+ const defaultRoot = "api";
101
+ const detectFromRecursiveRouteFiles = (baseDir, matcher) => {
102
+ if (!fs.existsSync(baseDir)) return null;
103
+ const candidates = /* @__PURE__ */ new Map();
104
+ const walk = (dir, prefix = "") => {
105
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
106
+ const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
107
+ const fullPath = path.join(dir, entry.name);
108
+ if (entry.isDirectory()) {
109
+ walk(fullPath, relativePath);
110
+ continue;
111
+ }
112
+ if (!matcher(entry, relativePath)) continue;
113
+ const [topLevel] = relativePath.split("/");
114
+ if (!topLevel) continue;
115
+ candidates.set(topLevel, (candidates.get(topLevel) ?? 0) + 1);
116
+ }
117
+ };
118
+ walk(baseDir);
119
+ return Array.from(candidates.entries()).sort((a, b) => b[1] - a[1])[0]?.[0] ?? null;
120
+ };
121
+ if (framework === "nextjs") {
122
+ const appRoot = path.join(cwd, nextAppDir);
123
+ if (fs.existsSync(path.join(appRoot, defaultRoot))) return defaultRoot;
124
+ return detectFromRecursiveRouteFiles(appRoot, (_entry, relativePath) => /\/?route\.(?:[cm]?[jt]sx?)$/i.test(relativePath)) ?? defaultRoot;
125
+ }
126
+ if (framework === "tanstack-start") {
127
+ const routesRoot = path.join(cwd, "src/routes");
128
+ if (fs.existsSync(path.join(routesRoot, defaultRoot))) return defaultRoot;
129
+ return detectFromRecursiveRouteFiles(routesRoot, (_entry, relativePath) => /(?:^|\/)[^/]+\.(?:[cm]?[jt]sx?)$/i.test(relativePath)) ?? defaultRoot;
130
+ }
131
+ if (framework === "sveltekit") {
132
+ const routesRoot = path.join(cwd, "src/routes");
133
+ if (fs.existsSync(path.join(routesRoot, defaultRoot))) return defaultRoot;
134
+ return detectFromRecursiveRouteFiles(routesRoot, (_entry, relativePath) => /\/?\+server\.(?:[cm]?[jt]s)$/i.test(relativePath)) ?? defaultRoot;
135
+ }
136
+ if (framework === "astro") {
137
+ const pagesRoot = path.join(cwd, "src/pages");
138
+ if (fs.existsSync(path.join(pagesRoot, defaultRoot))) return defaultRoot;
139
+ return detectFromRecursiveRouteFiles(pagesRoot, (entry, relativePath) => /\.(?:[cm]?[jt]s)$/i.test(relativePath) && !relativePath.endsWith(".d.ts") && entry.isFile()) ?? defaultRoot;
140
+ }
141
+ const serverRoot = path.join(cwd, "server");
142
+ if (fs.existsSync(path.join(serverRoot, defaultRoot))) return defaultRoot;
143
+ return detectFromRecursiveRouteFiles(serverRoot, (entry, relativePath) => /\.(?:[cm]?[jt]s)$/i.test(relativePath) && !relativePath.endsWith(".d.ts") && entry.isFile()) ?? defaultRoot;
144
+ }
145
+ async function init(options = {}) {
146
+ const cwd = process.cwd();
147
+ p.intro(pc.bgCyan(pc.black(" @farming-labs/docs ")));
148
+ let projectType = "existing";
149
+ if (!options.template) {
150
+ const projectTypeAnswer = await p.select({
151
+ message: "Are you adding docs to an existing project or starting fresh?",
152
+ options: [{
153
+ value: "existing",
154
+ label: "Existing project",
155
+ hint: "Add docs to the current app in this directory"
156
+ }, {
157
+ value: "fresh",
158
+ label: "Fresh project",
159
+ hint: "Bootstrap a new app from a template (Next, Nuxt, SvelteKit, Astro, TanStack Start)"
160
+ }]
161
+ });
162
+ if (p.isCancel(projectTypeAnswer)) {
163
+ p.outro(pc.red("Init cancelled."));
164
+ process.exit(0);
165
+ }
166
+ projectType = projectTypeAnswer;
167
+ }
168
+ if (projectType === "fresh" || options.template) {
169
+ let template;
170
+ if (options.template) {
171
+ template = options.template.toLowerCase();
172
+ if (!VALID_TEMPLATES.includes(template)) {
173
+ p.log.error(`Invalid ${pc.cyan("--template")}. Use one of: ${VALID_TEMPLATES.map((t) => pc.cyan(t)).join(", ")}`);
174
+ process.exit(1);
175
+ }
176
+ } else {
177
+ const templateAnswer = await p.select({
178
+ message: "Which framework would you like to use?",
179
+ options: [
180
+ {
181
+ value: "next",
182
+ label: "Next.js",
183
+ hint: "React with App Router"
184
+ },
185
+ {
186
+ value: "nuxt",
187
+ label: "Nuxt",
188
+ hint: "Vue 3 with file-based routing"
189
+ },
190
+ {
191
+ value: "sveltekit",
192
+ label: "SvelteKit",
193
+ hint: "Svelte with file-based routing"
194
+ },
195
+ {
196
+ value: "astro",
197
+ label: "Astro",
198
+ hint: "Content-focused with islands"
199
+ },
200
+ {
201
+ value: "tanstack-start",
202
+ label: "TanStack Start",
203
+ hint: "React with TanStack Router and server functions"
204
+ }
205
+ ]
206
+ });
207
+ if (p.isCancel(templateAnswer)) {
208
+ p.outro(pc.red("Init cancelled."));
209
+ process.exit(0);
210
+ }
211
+ template = templateAnswer;
212
+ }
213
+ const defaultProjectName = "my-docs";
214
+ let projectName = options.name?.trim();
215
+ if (!projectName) {
216
+ const nameAnswer = await p.text({
217
+ message: "Project name? (we'll create this folder and bootstrap the app here)",
218
+ placeholder: defaultProjectName,
219
+ defaultValue: defaultProjectName,
220
+ validate: (value) => {
221
+ const v = (value ?? "").trim();
222
+ if (v.includes("/") || v.includes("\\")) return "Project name cannot contain path separators";
223
+ if (v.includes(" ")) return "Project name cannot contain spaces";
224
+ }
225
+ });
226
+ if (p.isCancel(nameAnswer)) {
227
+ p.outro(pc.red("Init cancelled."));
228
+ process.exit(0);
229
+ }
230
+ projectName = nameAnswer.trim() || defaultProjectName;
231
+ }
232
+ const templateLabel = template === "next" ? "Next.js" : template === "nuxt" ? "Nuxt" : template === "sveltekit" ? "SvelteKit" : template === "astro" ? "Astro" : "TanStack Start";
233
+ const targetDir = path.join(cwd, projectName);
234
+ const fs = await import("node:fs");
235
+ if (fs.existsSync(targetDir)) {
236
+ p.log.error(`Directory ${pc.cyan(projectName)} already exists. Choose a different ${pc.cyan("--name")} or remove it.`);
237
+ process.exit(1);
238
+ }
239
+ fs.mkdirSync(targetDir, { recursive: true });
240
+ p.log.step(`Bootstrapping project with ${pc.cyan(`'${projectName}'`)} (${templateLabel})...`);
241
+ try {
242
+ exec(`npx degit ${EXAMPLES_REPO}/examples/${template} . --force`, targetDir);
243
+ } catch {
244
+ p.log.error("Failed to bootstrap. Check your connection and that the repo exists.");
245
+ process.exit(1);
246
+ }
247
+ const pmAnswer = await p.select({
248
+ message: "Which package manager do you want to use in this new project?",
249
+ options: [
250
+ {
251
+ value: "pnpm",
252
+ label: "pnpm",
253
+ hint: "Fast, disk-efficient (recommended)"
254
+ },
255
+ {
256
+ value: "npm",
257
+ label: "npm",
258
+ hint: "Default Node.js package manager"
259
+ },
260
+ {
261
+ value: "yarn",
262
+ label: "yarn",
263
+ hint: "Classic yarn (script: yarn dev)"
264
+ },
265
+ {
266
+ value: "bun",
267
+ label: "bun",
268
+ hint: "Bun runtime + bun install/dev"
269
+ }
270
+ ]
271
+ });
272
+ if (p.isCancel(pmAnswer)) {
273
+ p.outro(pc.red("Init cancelled."));
274
+ process.exit(0);
275
+ }
276
+ const pmFresh = pmAnswer;
277
+ p.log.success(`Bootstrapped ${pc.cyan(`'${projectName}'`)}. Installing dependencies with ${pc.cyan(pmFresh)}...`);
278
+ const installCmd = pmFresh === "yarn" ? "yarn install" : pmFresh === "npm" ? "npm install" : pmFresh === "bun" ? "bun install" : "pnpm install";
279
+ try {
280
+ exec(installCmd, targetDir);
281
+ } catch {
282
+ p.log.warn(`${pmFresh} install failed. Run ${pc.cyan(installCmd)} manually inside the project.`);
283
+ }
284
+ const devCmd = pmFresh === "yarn" ? "yarn dev" : pmFresh === "npm" ? "npm run dev" : pmFresh === "bun" ? "bun dev" : "pnpm dev";
285
+ p.outro(pc.green(`Done! Run ${pc.cyan(`cd ${projectName} && ${devCmd}`)} to start the dev server and navigate to the /docs.`));
286
+ p.outro(pc.green("Happy documenting!"));
287
+ process.exit(0);
288
+ }
289
+ let framework = detectFramework(cwd);
290
+ if (framework) {
291
+ const frameworkName = framework === "nextjs" ? "Next.js" : framework === "tanstack-start" ? "TanStack Start" : framework === "sveltekit" ? "SvelteKit" : framework === "astro" ? "Astro" : "Nuxt";
292
+ p.log.success(`Detected framework: ${pc.cyan(frameworkName)}`);
293
+ } else {
294
+ p.log.warn("Could not auto-detect a framework from " + pc.cyan("package.json") + ".");
295
+ const picked = await p.select({
296
+ message: "Which framework are you using?",
297
+ options: [
298
+ {
299
+ value: "nextjs",
300
+ label: "Next.js",
301
+ hint: "React framework with App Router"
302
+ },
303
+ {
304
+ value: "tanstack-start",
305
+ label: "TanStack Start",
306
+ hint: "React with TanStack Router and server functions"
307
+ },
308
+ {
309
+ value: "sveltekit",
310
+ label: "SvelteKit",
311
+ hint: "Svelte framework with file-based routing"
312
+ },
313
+ {
314
+ value: "astro",
315
+ label: "Astro",
316
+ hint: "Content-focused framework with island architecture"
317
+ },
318
+ {
319
+ value: "nuxt",
320
+ label: "Nuxt",
321
+ hint: "Vue 3 framework with file-based routing and Nitro server"
322
+ }
323
+ ]
324
+ });
325
+ if (p.isCancel(picked)) {
326
+ p.outro(pc.red("Init cancelled."));
327
+ process.exit(0);
328
+ }
329
+ framework = picked;
330
+ }
331
+ let nextAppDir = "app";
332
+ if (framework === "nextjs") {
333
+ const detected = detectNextAppDir(cwd);
334
+ if (detected) {
335
+ nextAppDir = detected;
336
+ p.log.info(`Using App Router at ${pc.cyan(nextAppDir)} (detected ${detected === "src/app" ? "src directory" : "root app"})`);
337
+ } else {
338
+ const useSrcApp = await p.confirm({
339
+ message: "Do you use the src directory for the App Router? (e.g. src/app instead of app)",
340
+ initialValue: false
341
+ });
342
+ if (p.isCancel(useSrcApp)) {
343
+ p.outro(pc.red("Init cancelled."));
344
+ process.exit(0);
345
+ }
346
+ nextAppDir = useSrcApp ? "src/app" : "app";
347
+ }
348
+ }
349
+ const themeOptions = [
350
+ {
351
+ value: "fumadocs",
352
+ label: "Fumadocs (Default)",
353
+ hint: "Clean, modern docs theme with sidebar, search, and dark mode"
354
+ },
355
+ {
356
+ value: "darksharp",
357
+ label: "Darksharp",
358
+ hint: "All-black, sharp edges, zero-radius look"
359
+ },
360
+ {
361
+ value: "pixel-border",
362
+ label: "Pixel Border",
363
+ hint: "Rounded borders, pixel-perfect spacing, refined sidebar"
364
+ },
365
+ {
366
+ value: "colorful",
367
+ label: "Colorful",
368
+ hint: "Fumadocs-style neutral theme with description support"
369
+ },
370
+ {
371
+ value: "darkbold",
372
+ label: "DarkBold",
373
+ hint: "Pure monochrome, Geist typography, clean minimalism"
374
+ },
375
+ {
376
+ value: "shiny",
377
+ label: "Shiny",
378
+ hint: "Glossy, modern look with subtle shimmer effects"
379
+ },
380
+ {
381
+ value: "greentree",
382
+ label: "GreenTree",
383
+ hint: "Emerald green accent, Inter font, Mintlify-inspired"
384
+ },
385
+ {
386
+ value: "concrete",
387
+ label: "Concrete",
388
+ hint: "Brutalist poster-style theme with offset shadows and loud contrast"
389
+ },
390
+ {
391
+ value: "command-grid",
392
+ label: "Command Grid",
393
+ hint: "Paper-grid docs shell inspired by better-cmdk"
394
+ },
395
+ {
396
+ value: "hardline",
397
+ label: "Hardline",
398
+ hint: "Hard-edge theme with square corners and bold borders"
399
+ },
400
+ {
401
+ value: "custom",
402
+ label: "Create your own theme",
403
+ hint: "Scaffold a new theme file + CSS in themes/ (name asked next)"
404
+ }
405
+ ];
406
+ const themeValues = new Set(themeOptions.map((option) => option.value));
407
+ let theme;
408
+ if (options.theme) {
409
+ if (!themeValues.has(options.theme)) {
410
+ p.log.error(`Invalid ${pc.cyan("--theme")}. Use one of: ${themeOptions.map((option) => pc.cyan(option.value)).join(", ")}`);
411
+ process.exit(1);
412
+ }
413
+ theme = options.theme;
414
+ } else {
415
+ const themeAnswer = await p.select({
416
+ message: "Which theme would you like to use?",
417
+ options: themeOptions
418
+ });
419
+ if (p.isCancel(themeAnswer)) {
420
+ p.outro(pc.red("Init cancelled."));
421
+ process.exit(0);
422
+ }
423
+ theme = themeAnswer;
424
+ }
425
+ const defaultThemeName = "my-theme";
426
+ let customThemeName;
427
+ if (theme === "custom") {
428
+ const nameAnswer = await p.text({
429
+ message: "Theme name? (we'll create themes/<name>.ts and themes/<name>.css)",
430
+ placeholder: defaultThemeName,
431
+ defaultValue: defaultThemeName,
432
+ validate: (value) => {
433
+ const v = (value ?? "").trim().replace(/\.(ts|css)$/i, "");
434
+ if (v.includes("/") || v.includes("\\")) return "Theme name cannot contain path separators";
435
+ if (v.includes(" ")) return "Theme name cannot contain spaces";
436
+ if (v && !/^[a-z0-9_-]+$/i.test(v)) return "Use only letters, numbers, hyphens, and underscores";
437
+ }
438
+ });
439
+ if (p.isCancel(nameAnswer)) {
440
+ p.outro(pc.red("Init cancelled."));
441
+ process.exit(0);
442
+ }
443
+ customThemeName = nameAnswer.trim().replace(/\.(ts|css)$/i, "") || defaultThemeName;
444
+ }
445
+ const aliasHint = framework === "nextjs" ? `Uses ${pc.cyan("@/")} prefix (requires tsconfig paths)` : framework === "tanstack-start" ? `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)`;
446
+ const useAlias = await p.confirm({
447
+ message: `Use path aliases for imports? ${pc.dim(aliasHint)}`,
448
+ initialValue: false
449
+ });
450
+ if (p.isCancel(useAlias)) {
451
+ p.outro(pc.red("Init cancelled."));
452
+ process.exit(0);
453
+ }
454
+ let astroAdapter;
455
+ if (framework === "astro") {
456
+ const adapter = await p.select({
457
+ message: "Where will you deploy?",
458
+ options: [
459
+ {
460
+ value: "vercel",
461
+ label: "Vercel",
462
+ hint: "Recommended for most projects"
463
+ },
464
+ {
465
+ value: "netlify",
466
+ label: "Netlify"
467
+ },
468
+ {
469
+ value: "cloudflare",
470
+ label: "Cloudflare Pages"
471
+ },
472
+ {
473
+ value: "node",
474
+ label: "Node.js / Docker",
475
+ hint: "Self-hosted standalone server"
476
+ }
477
+ ]
478
+ });
479
+ if (p.isCancel(adapter)) {
480
+ p.outro(pc.red("Init cancelled."));
481
+ process.exit(0);
482
+ }
483
+ astroAdapter = adapter;
484
+ }
485
+ const defaultEntry = "docs";
486
+ let entryPath;
487
+ if (options.entry) {
488
+ const normalizedEntry = options.entry.trim();
489
+ if (normalizedEntry.startsWith("/")) {
490
+ p.log.error("Use a relative path for --entry (no leading /)");
491
+ process.exit(1);
492
+ }
493
+ if (normalizedEntry.includes(" ")) {
494
+ p.log.error("Path passed to --entry cannot contain spaces");
495
+ process.exit(1);
496
+ }
497
+ entryPath = normalizedEntry || defaultEntry;
498
+ } else {
499
+ const entry = await p.text({
500
+ message: "Where should your docs live?",
501
+ placeholder: defaultEntry,
502
+ defaultValue: defaultEntry,
503
+ validate: (value) => {
504
+ const v = (value ?? "").trim();
505
+ if (v.startsWith("/")) return "Use a relative path (no leading /)";
506
+ if (v.includes(" ")) return "Path cannot contain spaces";
507
+ }
508
+ });
509
+ if (p.isCancel(entry)) {
510
+ p.outro(pc.red("Init cancelled."));
511
+ process.exit(0);
512
+ }
513
+ entryPath = entry.trim() || defaultEntry;
514
+ }
515
+ const defaultApiRouteRoot = normalizeApiRouteRoot(options.apiRouteRoot?.trim() || detectApiRouteRoot(cwd, framework, nextAppDir));
516
+ let apiReferenceConfig;
517
+ let enableApiReference;
518
+ if (typeof options.apiReference === "boolean") enableApiReference = options.apiReference;
519
+ else if (typeof options.apiRouteRoot === "string" && options.apiRouteRoot.trim()) enableApiReference = true;
520
+ else {
521
+ const apiReferenceAnswer = await p.confirm({
522
+ message: "Do you want to scaffold API reference support?",
523
+ initialValue: false
524
+ });
525
+ if (p.isCancel(apiReferenceAnswer)) {
526
+ p.outro(pc.red("Init cancelled."));
527
+ process.exit(0);
528
+ }
529
+ enableApiReference = apiReferenceAnswer;
530
+ }
531
+ if (enableApiReference) {
532
+ let routeRoot = options.apiRouteRoot?.trim();
533
+ if (!routeRoot) {
534
+ const routeRootAnswer = await p.text({
535
+ message: "API route root to scan?",
536
+ placeholder: defaultApiRouteRoot,
537
+ defaultValue: defaultApiRouteRoot,
538
+ validate: (value) => {
539
+ const normalizedValue = normalizeApiRouteRoot((value ?? "").trim());
540
+ if (!normalizedValue) return "Route root cannot be empty";
541
+ if (normalizedValue.includes(" ")) return "Route root cannot contain spaces";
542
+ }
543
+ });
544
+ if (p.isCancel(routeRootAnswer)) {
545
+ p.outro(pc.red("Init cancelled."));
546
+ process.exit(0);
547
+ }
548
+ routeRoot = routeRootAnswer.trim() || defaultApiRouteRoot;
549
+ }
550
+ const normalizedRouteRoot = normalizeApiRouteRoot(routeRoot);
551
+ if (!normalizedRouteRoot) {
552
+ p.log.error("Route root cannot be empty");
553
+ process.exit(1);
554
+ }
555
+ if (normalizedRouteRoot.includes(" ")) {
556
+ p.log.error("Route root cannot contain spaces");
557
+ process.exit(1);
558
+ }
559
+ apiReferenceConfig = {
560
+ path: "api-reference",
561
+ routeRoot: normalizedRouteRoot
562
+ };
563
+ }
564
+ let docsI18n;
565
+ if (framework === "tanstack-start") p.log.info("Skipping i18n scaffold for TanStack Start. Configure localized routes manually if needed.");
566
+ else {
567
+ const enableI18n = await p.confirm({
568
+ message: "Do you want to scaffold internationalized docs ?",
569
+ initialValue: false
570
+ });
571
+ if (p.isCancel(enableI18n)) {
572
+ p.outro(pc.red("Init cancelled."));
573
+ process.exit(0);
574
+ }
575
+ if (!enableI18n) docsI18n = void 0;
576
+ else {
577
+ const selectedLocales = await p.multiselect({
578
+ message: "Which languages should we scaffold?",
579
+ options: COMMON_LOCALE_OPTIONS.map((option) => ({
580
+ value: option.value,
581
+ label: option.label,
582
+ hint: option.hint
583
+ }))
584
+ });
585
+ if (p.isCancel(selectedLocales)) {
586
+ p.outro(pc.red("Init cancelled."));
587
+ process.exit(0);
588
+ }
589
+ const extraLocalesAnswer = await p.text({
590
+ message: "Any additional locale codes? (comma-separated, optional)",
591
+ placeholder: "nl, sv, pt-BR",
592
+ defaultValue: "",
593
+ validate: (value) => {
594
+ return parseLocaleInput(value ?? "").every((locale) => /^[a-z]{2,3}(?:-[A-Z]{2})?$/.test(locale)) ? void 0 : "Use locale codes like en, fr, zh, or pt-BR";
595
+ }
596
+ });
597
+ if (p.isCancel(extraLocalesAnswer)) {
598
+ p.outro(pc.red("Init cancelled."));
599
+ process.exit(0);
600
+ }
601
+ const locales = Array.from(new Set([...(selectedLocales ?? []).map((locale) => normalizeLocaleCode(locale)), ...parseLocaleInput(extraLocalesAnswer ?? "")])).filter(Boolean);
602
+ if (locales.length === 0) {
603
+ p.log.error("Pick at least one locale to scaffold i18n support.");
604
+ p.outro(pc.red("Init cancelled."));
605
+ process.exit(1);
606
+ }
607
+ const defaultLocaleAnswer = await p.select({
608
+ message: "Which locale should be the default?",
609
+ options: locales.map((locale) => ({
610
+ value: locale,
611
+ label: locale,
612
+ hint: locale === "en" ? "Recommended default" : void 0
613
+ })),
614
+ initialValue: locales[0]
615
+ });
616
+ if (p.isCancel(defaultLocaleAnswer)) {
617
+ p.outro(pc.red("Init cancelled."));
618
+ process.exit(0);
619
+ }
620
+ docsI18n = {
621
+ locales,
622
+ defaultLocale: defaultLocaleAnswer
623
+ };
624
+ }
625
+ }
626
+ const detectedCssFiles = detectGlobalCssFiles(cwd);
627
+ let globalCssRelPath;
628
+ const defaultCssPath = framework === "tanstack-start" ? "src/styles/app.css" : framework === "sveltekit" ? "src/app.css" : framework === "astro" ? "src/styles/global.css" : framework === "nuxt" ? "assets/css/main.css" : framework === "nextjs" ? `${nextAppDir}/globals.css` : "app/globals.css";
629
+ if (detectedCssFiles.length === 1) {
630
+ globalCssRelPath = detectedCssFiles[0];
631
+ p.log.info(`Found global CSS at ${pc.cyan(globalCssRelPath)}`);
632
+ } else if (detectedCssFiles.length > 1) {
633
+ const picked = await p.select({
634
+ message: "Multiple global CSS files found. Which one should we use?",
635
+ options: detectedCssFiles.map((f) => ({
636
+ value: f,
637
+ label: f
638
+ }))
639
+ });
640
+ if (p.isCancel(picked)) {
641
+ p.outro(pc.red("Init cancelled."));
642
+ process.exit(0);
643
+ }
644
+ globalCssRelPath = picked;
645
+ } else {
646
+ const cssPathAnswer = await p.text({
647
+ message: "Where is your global CSS file?",
648
+ placeholder: defaultCssPath,
649
+ defaultValue: defaultCssPath,
650
+ validate: (value) => {
651
+ const v = (value ?? "").trim();
652
+ if (v && !v.endsWith(".css")) return "Path must end with .css";
653
+ }
654
+ });
655
+ if (p.isCancel(cssPathAnswer)) {
656
+ p.outro(pc.red("Init cancelled."));
657
+ process.exit(0);
658
+ }
659
+ globalCssRelPath = cssPathAnswer.trim() || defaultCssPath;
660
+ }
661
+ const pkgJsonContent = readFileSafe(path.join(cwd, "package.json"));
662
+ const pkgJson = pkgJsonContent ? JSON.parse(pkgJsonContent) : { name: "my-project" };
663
+ const projectName = pkgJson.name || "My Project";
664
+ const cfg = {
665
+ entry: entryPath,
666
+ theme,
667
+ customThemeName,
668
+ projectName,
669
+ framework,
670
+ useAlias,
671
+ astroAdapter,
672
+ i18n: docsI18n,
673
+ apiReference: apiReferenceConfig,
674
+ ...framework === "nextjs" && { nextAppDir }
675
+ };
676
+ const s = p.spinner();
677
+ s.start("Scaffolding docs files");
678
+ const written = [];
679
+ const skipped = [];
680
+ function write(rel, content, overwrite = false) {
681
+ if (writeFileSafe(path.join(cwd, rel), content, overwrite)) written.push(rel);
682
+ else skipped.push(rel);
683
+ }
684
+ if (framework === "tanstack-start") scaffoldTanstackStart(cwd, cfg, globalCssRelPath, write, skipped, written);
685
+ else if (framework === "sveltekit") scaffoldSvelteKit(cwd, cfg, globalCssRelPath, write, skipped, written);
686
+ else if (framework === "astro") scaffoldAstro(cwd, cfg, globalCssRelPath, write, skipped, written);
687
+ else if (framework === "nuxt") scaffoldNuxt(cwd, cfg, globalCssRelPath, write, skipped, written);
688
+ else scaffoldNextJs(cwd, cfg, globalCssRelPath, write, skipped, written);
689
+ s.stop("Files scaffolded");
690
+ if (written.length > 0) p.log.success(`Created ${written.length} file${written.length > 1 ? "s" : ""}:\n` + written.map((f) => ` ${pc.green("+")} ${f}`).join("\n"));
691
+ 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"));
692
+ let pm = detectPackageManagerFromLockfile(cwd);
693
+ if (pm) p.log.info(`Detected ${pc.cyan(pm)}`);
694
+ const pmAnswerExisting = await p.select({
695
+ ...pm ? { initialValue: pm } : {},
696
+ message: "Which package manager do you want to use in this project?",
697
+ options: [
698
+ {
699
+ value: "pnpm",
700
+ label: "pnpm",
701
+ hint: "Fast, disk-efficient (recommended)"
702
+ },
703
+ {
704
+ value: "npm",
705
+ label: "npm",
706
+ hint: "Default Node.js package manager"
707
+ },
708
+ {
709
+ value: "yarn",
710
+ label: "yarn",
711
+ hint: "Classic yarn (script: yarn dev)"
712
+ },
713
+ {
714
+ value: "bun",
715
+ label: "bun",
716
+ hint: "Bun runtime + bun install/dev"
717
+ }
718
+ ]
719
+ });
720
+ if (p.isCancel(pmAnswerExisting)) {
721
+ p.outro(pc.red("Init cancelled."));
722
+ process.exit(0);
723
+ }
724
+ pm = pmAnswerExisting;
725
+ p.log.info(`Using ${pc.cyan(pm)} as package manager`);
726
+ const s2 = p.spinner();
727
+ s2.start("Installing dependencies");
728
+ try {
729
+ if (framework === "tanstack-start") {
730
+ exec(`${installCommand(pm)} @farming-labs/docs @farming-labs/theme @farming-labs/tanstack-start`, cwd);
731
+ const devDeps = ["@tailwindcss/vite", "tailwindcss"];
732
+ if (useAlias) devDeps.push("vite-tsconfig-paths");
733
+ const allDeps = {
734
+ ...pkgJson.dependencies,
735
+ ...pkgJson.devDependencies
736
+ };
737
+ const missingDevDeps = devDeps.filter((d) => !allDeps[d]);
738
+ if (missingDevDeps.length > 0) exec(`${devInstallCommand(pm)} ${missingDevDeps.join(" ")}`, cwd);
739
+ } else if (framework === "sveltekit") exec(`${installCommand(pm)} @farming-labs/docs @farming-labs/svelte @farming-labs/svelte-theme`, cwd);
740
+ else if (framework === "astro") {
741
+ const adapterPkg = getAstroAdapterPkg(cfg.astroAdapter ?? "vercel");
742
+ exec(`${installCommand(pm)} @farming-labs/docs @farming-labs/astro @farming-labs/astro-theme ${adapterPkg}`, cwd);
743
+ } else if (framework === "nuxt") exec(`${installCommand(pm)} @farming-labs/docs @farming-labs/nuxt @farming-labs/nuxt-theme`, cwd);
744
+ else {
745
+ exec(`${installCommand(pm)} @farming-labs/docs @farming-labs/next @farming-labs/theme`, cwd);
746
+ const devDeps = [
747
+ "@tailwindcss/postcss",
748
+ "postcss",
749
+ "tailwindcss",
750
+ "@types/mdx",
751
+ "@types/node"
752
+ ];
753
+ const allDeps = {
754
+ ...pkgJson.dependencies,
755
+ ...pkgJson.devDependencies
756
+ };
757
+ const missingDevDeps = devDeps.filter((d) => !allDeps[d]);
758
+ if (missingDevDeps.length > 0) exec(`${devInstallCommand(pm)} ${missingDevDeps.join(" ")}`, cwd);
759
+ }
760
+ } catch {
761
+ s2.stop("Failed to install dependencies");
762
+ p.log.error(`Dependency installation failed. Run the install command manually:
763
+ ${pc.cyan(`${installCommand(pm)} @farming-labs/docs`)}`);
764
+ p.outro(pc.yellow("Setup partially complete. Install deps and run dev server manually."));
765
+ process.exit(1);
766
+ }
767
+ s2.stop("Dependencies installed");
768
+ const startDev = await p.confirm({
769
+ message: "Start the dev server now?",
770
+ initialValue: true
771
+ });
772
+ if (p.isCancel(startDev) || !startDev) {
773
+ p.log.info(`You can start the dev server later with:
774
+ ${pc.cyan(`${pm === "yarn" ? "yarn" : pm + " run"} dev`)}`);
775
+ p.outro(pc.green("Done! Happy documenting."));
776
+ process.exit(0);
777
+ }
778
+ p.log.step("Starting dev server...");
779
+ const devCommand = framework === "tanstack-start" ? {
780
+ cmd: "npx",
781
+ args: ["vite", "dev"],
782
+ waitFor: "ready"
783
+ } : framework === "sveltekit" ? {
784
+ cmd: "npx",
785
+ args: ["vite", "dev"],
786
+ waitFor: "ready"
787
+ } : framework === "astro" ? {
788
+ cmd: "npx",
789
+ args: ["astro", "dev"],
790
+ waitFor: "ready"
791
+ } : framework === "nuxt" ? {
792
+ cmd: "npx",
793
+ args: ["nuxt", "dev"],
794
+ waitFor: "Local"
795
+ } : {
796
+ cmd: "npx",
797
+ args: [
798
+ "next",
799
+ "dev",
800
+ "--webpack"
801
+ ],
802
+ waitFor: "Ready"
803
+ };
804
+ const defaultPort = framework === "tanstack-start" ? "5173" : framework === "sveltekit" ? "5173" : framework === "astro" ? "4321" : framework === "nuxt" ? "3000" : "3000";
805
+ try {
806
+ const child = await spawnAndWaitFor(devCommand.cmd, devCommand.args, cwd, devCommand.waitFor, 6e4);
807
+ const url = `http://localhost:${defaultPort}/${entryPath}`;
808
+ console.log();
809
+ 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.`);
810
+ p.outro(pc.green("Happy documenting!"));
811
+ await new Promise((resolve) => {
812
+ child.on("close", () => resolve());
813
+ process.on("SIGINT", () => {
814
+ child.kill("SIGINT");
815
+ resolve();
816
+ });
817
+ process.on("SIGTERM", () => {
818
+ child.kill("SIGTERM");
819
+ resolve();
820
+ });
821
+ });
822
+ } catch {
823
+ const manualCmd = framework === "tanstack-start" ? "npx vite dev" : framework === "sveltekit" ? "npx vite dev" : framework === "astro" ? "npx astro dev" : framework === "nuxt" ? "npx nuxt dev" : "pnpm dev";
824
+ p.log.error(`Could not start dev server. Try running manually:
825
+ ${pc.cyan(manualCmd)}`);
826
+ p.outro(pc.yellow("Setup complete. Start the server manually."));
827
+ process.exit(1);
828
+ }
829
+ }
830
+ function getScaffoldContentRoots(cfg) {
831
+ return cfg.i18n?.locales?.length ? cfg.i18n.locales.map((locale) => `${cfg.entry}/${locale}`) : [cfg.entry];
832
+ }
833
+ function scaffoldNextJs(cwd, cfg, globalCssRelPath, write, skipped, written) {
834
+ const appDir = cfg.nextAppDir ?? "app";
835
+ if (cfg.theme === "custom" && cfg.customThemeName) {
836
+ const baseName = cfg.customThemeName.replace(/\.(ts|css)$/i, "");
837
+ write(`themes/${baseName}.ts`, customThemeTsTemplate(baseName));
838
+ write(`themes/${baseName}.css`, customThemeCssTemplate(baseName));
839
+ }
840
+ write("docs.config.ts", docsConfigTemplate(cfg));
841
+ const existingNextConfig = readFileSafe(path.join(cwd, "next.config.ts")) ?? readFileSafe(path.join(cwd, "next.config.mjs")) ?? readFileSafe(path.join(cwd, "next.config.js"));
842
+ if (existingNextConfig) {
843
+ 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";
844
+ const merged = nextConfigMergedTemplate(existingNextConfig);
845
+ if (merged !== existingNextConfig) {
846
+ writeFileSafe(path.join(cwd, configFile), merged, true);
847
+ written.push(configFile + " (updated)");
848
+ } else skipped.push(configFile + " (already configured)");
849
+ } else write("next.config.ts", nextConfigTemplate());
850
+ const rootLayoutPath = path.join(cwd, `${appDir}/layout.tsx`);
851
+ const existingRootLayout = readFileSafe(rootLayoutPath);
852
+ if (!existingRootLayout) write(`${appDir}/layout.tsx`, rootLayoutTemplate(cfg, globalCssRelPath), true);
853
+ else if (!existingRootLayout.includes("RootProvider")) {
854
+ const injected = injectRootProviderIntoLayout(existingRootLayout);
855
+ if (injected) {
856
+ writeFileSafe(rootLayoutPath, injected, true);
857
+ written.push(`${appDir}/layout.tsx (injected RootProvider)`);
858
+ } else skipped.push(`${appDir}/layout.tsx (could not inject RootProvider)`);
859
+ } else skipped.push(`${appDir}/layout.tsx (already has RootProvider)`);
860
+ const globalCssAbsPath = path.join(cwd, globalCssRelPath);
861
+ const existingGlobalCss = readFileSafe(globalCssAbsPath);
862
+ if (existingGlobalCss) {
863
+ const injected = injectCssImport(existingGlobalCss, cfg.theme, cfg.customThemeName, globalCssRelPath);
864
+ if (injected) {
865
+ writeFileSafe(globalCssAbsPath, injected, true);
866
+ written.push(globalCssRelPath + " (updated)");
867
+ } else skipped.push(globalCssRelPath + " (already configured)");
868
+ } else write(globalCssRelPath, globalCssTemplate(cfg.theme, cfg.customThemeName, globalCssRelPath));
869
+ write(`${appDir}/${cfg.entry}/layout.tsx`, docsLayoutTemplate(cfg));
870
+ if (cfg.apiReference) {
871
+ const apiReferencePage = `${appDir}/${cfg.apiReference.path}/[[...slug]]/page.tsx`;
872
+ write(apiReferencePage, nextApiReferencePageTemplate(cfg, apiReferencePage));
873
+ }
874
+ write("postcss.config.mjs", postcssConfigTemplate());
875
+ if (!fileExists(path.join(cwd, "tsconfig.json"))) write("tsconfig.json", tsconfigTemplate(cfg.useAlias));
876
+ if (cfg.i18n?.locales.length) {
877
+ write(`${appDir}/components/locale-doc-page.tsx`, nextLocaleDocPageTemplate(cfg.i18n.defaultLocale));
878
+ write(`${appDir}/${cfg.entry}/page.tsx`, nextLocalizedPageTemplate({
879
+ locales: cfg.i18n.locales,
880
+ defaultLocale: cfg.i18n.defaultLocale,
881
+ componentName: "DocsIndexPage",
882
+ helperImport: "../components/locale-doc-page",
883
+ pageImports: cfg.i18n.locales.map((locale) => ({
884
+ locale,
885
+ importPath: `./${locale}/page.mdx`
886
+ }))
887
+ }));
888
+ write(`${appDir}/${cfg.entry}/installation/page.tsx`, nextLocalizedPageTemplate({
889
+ locales: cfg.i18n.locales,
890
+ defaultLocale: cfg.i18n.defaultLocale,
891
+ componentName: "InstallationPage",
892
+ helperImport: "../../components/locale-doc-page",
893
+ pageImports: cfg.i18n.locales.map((locale) => ({
894
+ locale,
895
+ importPath: `../${locale}/installation/page.mdx`
896
+ }))
897
+ }));
898
+ write(`${appDir}/${cfg.entry}/quickstart/page.tsx`, nextLocalizedPageTemplate({
899
+ locales: cfg.i18n.locales,
900
+ defaultLocale: cfg.i18n.defaultLocale,
901
+ componentName: "QuickstartPage",
902
+ helperImport: "../../components/locale-doc-page",
903
+ pageImports: cfg.i18n.locales.map((locale) => ({
904
+ locale,
905
+ importPath: `../${locale}/quickstart/page.mdx`
906
+ }))
907
+ }));
908
+ for (const locale of cfg.i18n.locales) {
909
+ const base = `${appDir}/${cfg.entry}/${locale}`;
910
+ write(`${base}/page.mdx`, welcomePageTemplate(cfg));
911
+ write(`${base}/installation/page.mdx`, installationPageTemplate(cfg));
912
+ write(`${base}/quickstart/page.mdx`, quickstartPageTemplate(cfg));
913
+ }
914
+ return;
915
+ }
916
+ write(`${appDir}/${cfg.entry}/page.mdx`, welcomePageTemplate(cfg));
917
+ write(`${appDir}/${cfg.entry}/installation/page.mdx`, installationPageTemplate(cfg));
918
+ write(`${appDir}/${cfg.entry}/quickstart/page.mdx`, quickstartPageTemplate(cfg));
919
+ }
920
+ function scaffoldTanstackStart(cwd, cfg, globalCssRelPath, write, skipped, written) {
921
+ if (cfg.theme === "custom" && cfg.customThemeName) {
922
+ const baseName = cfg.customThemeName.replace(/\.(ts|css)$/i, "");
923
+ write(`themes/${baseName}.ts`, customThemeTsTemplate(baseName));
924
+ write(`themes/${baseName}.css`, customThemeCssTemplate(baseName));
925
+ }
926
+ write("docs.config.ts", tanstackDocsConfigTemplate(cfg));
927
+ write("src/lib/docs.server.ts", tanstackDocsServerTemplate());
928
+ write("src/lib/docs.functions.ts", tanstackDocsFunctionsTemplate());
929
+ const routeDir = getTanstackDocsRouteDir(cfg.entry);
930
+ const docsIndexRoute = `${routeDir}/index.tsx`;
931
+ const docsCatchAllRoute = `${routeDir}/$.tsx`;
932
+ const apiRoute = "src/routes/api/docs.ts";
933
+ const publicRoute = "src/routes/$.ts";
934
+ write(docsIndexRoute, tanstackDocsIndexRouteTemplate({
935
+ entry: cfg.entry,
936
+ filePath: docsIndexRoute,
937
+ useAlias: cfg.useAlias,
938
+ projectName: cfg.projectName
939
+ }));
940
+ write(docsCatchAllRoute, tanstackDocsCatchAllRouteTemplate({
941
+ entry: cfg.entry,
942
+ filePath: docsCatchAllRoute,
943
+ useAlias: cfg.useAlias,
944
+ projectName: cfg.projectName
945
+ }));
946
+ write(apiRoute, tanstackApiDocsRouteTemplate(cfg.useAlias, apiRoute));
947
+ write(publicRoute, tanstackDocsPublicRouteTemplate(cfg.useAlias, publicRoute, cfg.entry));
948
+ if (cfg.apiReference) {
949
+ const apiReferenceIndexRoute = `src/routes/${cfg.apiReference.path}.index.ts`;
950
+ const apiReferenceCatchAllRoute = `src/routes/${cfg.apiReference.path}.$.ts`;
951
+ write(apiReferenceIndexRoute, tanstackApiReferenceRouteTemplate({
952
+ filePath: apiReferenceIndexRoute,
953
+ useAlias: cfg.useAlias,
954
+ apiReferencePath: cfg.apiReference.path,
955
+ catchAll: false
956
+ }));
957
+ write(apiReferenceCatchAllRoute, tanstackApiReferenceRouteTemplate({
958
+ filePath: apiReferenceCatchAllRoute,
959
+ useAlias: cfg.useAlias,
960
+ apiReferencePath: cfg.apiReference.path,
961
+ catchAll: true
962
+ }));
963
+ }
964
+ const rootRoutePath = path.join(cwd, "src/routes/__root.tsx");
965
+ const existingRootRoute = readFileSafe(rootRoutePath);
966
+ if (!existingRootRoute) write("src/routes/__root.tsx", tanstackRootRouteTemplate(globalCssRelPath), true);
967
+ else if (!existingRootRoute.includes("RootProvider")) {
968
+ const injected = injectTanstackRootProviderIntoRoute(existingRootRoute);
969
+ if (injected) {
970
+ writeFileSafe(rootRoutePath, injected, true);
971
+ written.push("src/routes/__root.tsx (injected RootProvider)");
972
+ } else skipped.push("src/routes/__root.tsx (could not inject RootProvider)");
973
+ } else skipped.push("src/routes/__root.tsx (already has RootProvider)");
974
+ const viteConfigRel = fileExists(path.join(cwd, "vite.config.ts")) ? "vite.config.ts" : fileExists(path.join(cwd, "vite.config.mts")) ? "vite.config.mts" : fileExists(path.join(cwd, "vite.config.js")) ? "vite.config.js" : "vite.config.ts";
975
+ const viteConfigPath = path.join(cwd, viteConfigRel);
976
+ const existingViteConfig = readFileSafe(viteConfigPath);
977
+ if (!existingViteConfig) write(viteConfigRel, tanstackViteConfigTemplate(cfg.useAlias), true);
978
+ else {
979
+ const injected = injectTanstackVitePlugins(existingViteConfig, cfg.useAlias);
980
+ if (injected) {
981
+ writeFileSafe(viteConfigPath, injected, true);
982
+ written.push(`${viteConfigRel} (updated)`);
983
+ } else skipped.push(`${viteConfigRel} (already configured)`);
984
+ }
985
+ const globalCssAbsPath = path.join(cwd, globalCssRelPath);
986
+ const existingGlobalCss = readFileSafe(globalCssAbsPath);
987
+ if (existingGlobalCss) {
988
+ const injected = injectCssImport(existingGlobalCss, cfg.theme, cfg.customThemeName, globalCssRelPath);
989
+ if (injected) {
990
+ writeFileSafe(globalCssAbsPath, injected, true);
991
+ written.push(globalCssRelPath + " (updated)");
992
+ } else skipped.push(globalCssRelPath + " (already configured)");
993
+ } else write(globalCssRelPath, globalCssTemplate(cfg.theme, cfg.customThemeName, globalCssRelPath));
994
+ for (const base of getScaffoldContentRoots(cfg)) {
995
+ write(`${base}/page.mdx`, tanstackWelcomePageTemplate(cfg));
996
+ write(`${base}/installation/page.mdx`, tanstackInstallationPageTemplate(cfg));
997
+ write(`${base}/quickstart/page.mdx`, tanstackQuickstartPageTemplate(cfg));
998
+ }
999
+ }
1000
+ function scaffoldSvelteKit(cwd, cfg, globalCssRelPath, write, skipped, written) {
1001
+ if (cfg.theme === "custom" && cfg.customThemeName) {
1002
+ const baseName = cfg.customThemeName.replace(/\.(ts|css)$/i, "");
1003
+ write(`themes/${baseName}.ts`, customThemeTsTemplate(baseName));
1004
+ write(`themes/${baseName}.css`, customThemeCssTemplate(baseName));
1005
+ }
1006
+ write("src/lib/docs.config.ts", svelteDocsConfigTemplate(cfg));
1007
+ write("src/lib/docs.server.ts", svelteDocsServerTemplate(cfg));
1008
+ write(`src/routes/${cfg.entry}/+layout.svelte`, svelteDocsLayoutTemplate(cfg));
1009
+ write(`src/routes/${cfg.entry}/+layout.server.js`, svelteDocsLayoutServerTemplate(cfg));
1010
+ write(`src/routes/${cfg.entry}/[...slug]/+page.svelte`, svelteDocsPageTemplate(cfg));
1011
+ if (cfg.i18n?.locales.length) write(`src/routes/${cfg.entry}/+page.svelte`, svelteDocsPageTemplate(cfg));
1012
+ const apiDocsRoute = "src/routes/api/docs/+server.ts";
1013
+ const publicHook = "src/hooks.server.ts";
1014
+ write(apiDocsRoute, svelteDocsApiRouteTemplate(apiDocsRoute, cfg.useAlias));
1015
+ const publicHookPath = path.join(cwd, publicHook);
1016
+ const existingPublicHook = readFileSafe(publicHookPath);
1017
+ if (existingPublicHook) {
1018
+ const injected = injectSvelteDocsPublicHook(existingPublicHook, publicHook, cfg.useAlias);
1019
+ if (injected) {
1020
+ writeFileSafe(publicHookPath, injected, true);
1021
+ written.push(`${publicHook} (composed docs public hook)`);
1022
+ } else skipped.push(`${publicHook} (already configured or could not compose docs public hook)`);
1023
+ } else write(publicHook, svelteDocsPublicHookTemplate(publicHook, cfg.useAlias));
1024
+ if (cfg.apiReference) {
1025
+ const apiReferenceIndexRoute = `src/routes/${cfg.apiReference.path}/+server.ts`;
1026
+ const apiReferenceCatchAllRoute = `src/routes/${cfg.apiReference.path}/[...slug]/+server.ts`;
1027
+ write(apiReferenceIndexRoute, svelteApiReferenceRouteTemplate(apiReferenceIndexRoute, cfg.useAlias));
1028
+ write(apiReferenceCatchAllRoute, svelteApiReferenceRouteTemplate(apiReferenceCatchAllRoute, cfg.useAlias));
1029
+ }
1030
+ if (!readFileSafe(path.join(cwd, "src/routes/+layout.svelte"))) write("src/routes/+layout.svelte", svelteRootLayoutTemplate(globalCssRelPath));
1031
+ const globalCssAbsPath = path.join(cwd, globalCssRelPath);
1032
+ const existingGlobalCss = readFileSafe(globalCssAbsPath);
1033
+ const cssTheme = {
1034
+ fumadocs: "fumadocs",
1035
+ darksharp: "darksharp",
1036
+ "pixel-border": "pixel-border",
1037
+ colorful: "colorful",
1038
+ darkbold: "darkbold",
1039
+ shiny: "shiny",
1040
+ greentree: "greentree",
1041
+ concrete: "concrete",
1042
+ "command-grid": "command-grid",
1043
+ hardline: "hardline",
1044
+ default: "fumadocs"
1045
+ }[cfg.theme] || "fumadocs";
1046
+ if (existingGlobalCss) {
1047
+ const injected = cfg.theme === "custom" && cfg.customThemeName ? injectSvelteCssImport(existingGlobalCss, "custom", cfg.customThemeName, globalCssRelPath) : injectSvelteCssImport(existingGlobalCss, cssTheme);
1048
+ if (injected) {
1049
+ writeFileSafe(globalCssAbsPath, injected, true);
1050
+ written.push(globalCssRelPath + " (updated)");
1051
+ } else skipped.push(globalCssRelPath + " (already configured)");
1052
+ } else write(globalCssRelPath, cfg.theme === "custom" && cfg.customThemeName ? svelteGlobalCssTemplate("custom", cfg.customThemeName, globalCssRelPath) : svelteGlobalCssTemplate(cssTheme));
1053
+ for (const base of getScaffoldContentRoots(cfg)) {
1054
+ write(`${base}/page.md`, svelteWelcomePageTemplate(cfg));
1055
+ write(`${base}/installation/page.md`, svelteInstallationPageTemplate(cfg));
1056
+ write(`${base}/quickstart/page.md`, svelteQuickstartPageTemplate(cfg));
1057
+ }
1058
+ }
1059
+ function scaffoldAstro(cwd, cfg, globalCssRelPath, write, skipped, written) {
1060
+ if (cfg.theme === "custom" && cfg.customThemeName) {
1061
+ const baseName = cfg.customThemeName.replace(/\.(ts|css)$/i, "");
1062
+ write(`themes/${baseName}.ts`, customThemeTsTemplate(baseName));
1063
+ write(`themes/${baseName}.css`, customThemeCssTemplate(baseName));
1064
+ }
1065
+ write("src/lib/docs.config.ts", astroDocsConfigTemplate(cfg));
1066
+ write("src/lib/docs.server.ts", astroDocsServerTemplate(cfg));
1067
+ if (!fileExists(path.join(cwd, "astro.config.mjs")) && !fileExists(path.join(cwd, "astro.config.ts"))) write("astro.config.mjs", astroConfigTemplate(cfg.astroAdapter ?? "vercel"));
1068
+ write(`src/pages/${cfg.entry}/index.astro`, astroDocsIndexTemplate(cfg));
1069
+ write(`src/pages/${cfg.entry}/[...slug].astro`, astroDocsPageTemplate(cfg));
1070
+ write(`src/pages/api/${cfg.entry}.ts`, astroApiRouteTemplate(cfg));
1071
+ const middleware = "src/middleware.ts";
1072
+ const middlewarePath = path.join(cwd, middleware);
1073
+ const existingMiddleware = readFileSafe(middlewarePath);
1074
+ if (existingMiddleware) {
1075
+ const injected = injectAstroDocsMiddleware(existingMiddleware, middleware, cfg.useAlias);
1076
+ if (injected) {
1077
+ writeFileSafe(middlewarePath, injected, true);
1078
+ written.push(`${middleware} (composed docs public middleware)`);
1079
+ } else skipped.push(`${middleware} (already configured or could not compose docs public middleware)`);
1080
+ } else write(middleware, astroDocsMiddlewareTemplate(middleware, cfg.useAlias));
1081
+ if (cfg.apiReference) {
1082
+ const apiReferenceIndexRoute = `src/pages/${cfg.apiReference.path}/index.ts`;
1083
+ const apiReferenceCatchAllRoute = `src/pages/${cfg.apiReference.path}/[...slug].ts`;
1084
+ write(apiReferenceIndexRoute, astroApiReferenceRouteTemplate(apiReferenceIndexRoute));
1085
+ write(apiReferenceCatchAllRoute, astroApiReferenceRouteTemplate(apiReferenceCatchAllRoute));
1086
+ }
1087
+ const globalCssAbsPath = path.join(cwd, globalCssRelPath);
1088
+ const existingGlobalCss = readFileSafe(globalCssAbsPath);
1089
+ const cssTheme = {
1090
+ fumadocs: "fumadocs",
1091
+ darksharp: "darksharp",
1092
+ "pixel-border": "pixel-border",
1093
+ colorful: "colorful",
1094
+ darkbold: "darkbold",
1095
+ shiny: "shiny",
1096
+ greentree: "greentree",
1097
+ concrete: "concrete",
1098
+ "command-grid": "command-grid",
1099
+ hardline: "hardline",
1100
+ default: "fumadocs"
1101
+ }[cfg.theme] || "fumadocs";
1102
+ if (existingGlobalCss) {
1103
+ const injected = cfg.theme === "custom" && cfg.customThemeName ? injectAstroCssImport(existingGlobalCss, "custom", cfg.customThemeName, globalCssRelPath) : injectAstroCssImport(existingGlobalCss, cssTheme);
1104
+ if (injected) {
1105
+ writeFileSafe(globalCssAbsPath, injected, true);
1106
+ written.push(globalCssRelPath + " (updated)");
1107
+ } else skipped.push(globalCssRelPath + " (already configured)");
1108
+ } else write(globalCssRelPath, cfg.theme === "custom" && cfg.customThemeName ? astroGlobalCssTemplate("custom", cfg.customThemeName, globalCssRelPath) : astroGlobalCssTemplate(cssTheme));
1109
+ for (const base of getScaffoldContentRoots(cfg)) {
1110
+ write(`${base}/page.md`, astroWelcomePageTemplate(cfg));
1111
+ write(`${base}/installation/page.md`, astroInstallationPageTemplate(cfg));
1112
+ write(`${base}/quickstart/page.md`, astroQuickstartPageTemplate(cfg));
1113
+ }
1114
+ }
1115
+ function scaffoldNuxt(cwd, cfg, globalCssRelPath, write, skipped, written) {
1116
+ if (cfg.theme === "custom" && cfg.customThemeName) {
1117
+ const baseName = cfg.customThemeName.replace(/\.(ts|css)$/i, "");
1118
+ write(`themes/${baseName}.ts`, customThemeTsTemplate(baseName));
1119
+ write(`themes/${baseName}.css`, customThemeCssTemplate(baseName));
1120
+ }
1121
+ write("docs.config.ts", nuxtDocsConfigTemplate(cfg));
1122
+ write("server/api/docs.ts", nuxtServerApiDocsRouteTemplate(cfg));
1123
+ write("server/middleware/docs-public.ts", nuxtServerDocsPublicMiddlewareTemplate(cfg));
1124
+ write(`pages/${cfg.entry}/[[...slug]].vue`, nuxtDocsPageTemplate(cfg));
1125
+ if (cfg.apiReference) {
1126
+ const apiReferenceIndexRoute = `server/routes/${cfg.apiReference.path}/index.ts`;
1127
+ const apiReferenceCatchAllRoute = `server/routes/${cfg.apiReference.path}/[...slug].ts`;
1128
+ write(apiReferenceIndexRoute, nuxtServerApiReferenceRouteTemplate(apiReferenceIndexRoute, cfg.useAlias));
1129
+ write(apiReferenceCatchAllRoute, nuxtServerApiReferenceRouteTemplate(apiReferenceCatchAllRoute, cfg.useAlias));
1130
+ }
1131
+ if (!fileExists(path.join(cwd, "nuxt.config.ts")) && !fileExists(path.join(cwd, "nuxt.config.js"))) write("nuxt.config.ts", nuxtConfigTemplate(cfg));
1132
+ const cssTheme = {
1133
+ fumadocs: "fumadocs",
1134
+ darksharp: "darksharp",
1135
+ "pixel-border": "pixel-border",
1136
+ colorful: "colorful",
1137
+ darkbold: "darkbold",
1138
+ shiny: "shiny",
1139
+ greentree: "greentree",
1140
+ concrete: "concrete",
1141
+ "command-grid": "command-grid",
1142
+ hardline: "hardline",
1143
+ default: "fumadocs"
1144
+ }[cfg.theme] || "fumadocs";
1145
+ const globalCssAbsPath = path.join(cwd, globalCssRelPath);
1146
+ const existingGlobalCss = readFileSafe(globalCssAbsPath);
1147
+ if (existingGlobalCss) {
1148
+ const injected = cfg.theme === "custom" && cfg.customThemeName ? injectNuxtCssImport(existingGlobalCss, "custom", cfg.customThemeName, globalCssRelPath) : injectNuxtCssImport(existingGlobalCss, cssTheme);
1149
+ if (injected) {
1150
+ writeFileSafe(globalCssAbsPath, injected, true);
1151
+ written.push(globalCssRelPath + " (updated)");
1152
+ } else skipped.push(globalCssRelPath + " (already configured)");
1153
+ } else write(globalCssRelPath, cfg.theme === "custom" && cfg.customThemeName ? nuxtGlobalCssTemplate("custom", cfg.customThemeName, globalCssRelPath) : nuxtGlobalCssTemplate(cssTheme));
1154
+ for (const base of getScaffoldContentRoots(cfg)) {
1155
+ write(`${base}/page.md`, nuxtWelcomePageTemplate(cfg));
1156
+ write(`${base}/installation/page.md`, nuxtInstallationPageTemplate(cfg));
1157
+ write(`${base}/quickstart/page.md`, nuxtQuickstartPageTemplate(cfg));
1158
+ }
1159
+ }
1160
+
1161
+ //#endregion
1162
+ export { init };