@fumadocs/cli 1.5.0 → 1.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/dist/ai-C19ZOP53.js +100 -0
  2. package/dist/ai-C19ZOP53.js.map +1 -0
  3. package/dist/all-BBf7I8hG.js +27 -0
  4. package/dist/all-BBf7I8hG.js.map +1 -0
  5. package/dist/codemod/index.d.ts +2 -0
  6. package/dist/codemod/index.js +2 -0
  7. package/dist/codemod-auol-p4R.js +404 -0
  8. package/dist/codemod-auol-p4R.js.map +1 -0
  9. package/dist/config-Bfg4yG8S.d.ts +54 -0
  10. package/dist/config-Bfg4yG8S.d.ts.map +1 -0
  11. package/dist/config-Bw0kmF01.js +97 -0
  12. package/dist/config-Bw0kmF01.js.map +1 -0
  13. package/dist/config.d.ts +2 -52
  14. package/dist/config.js +1 -72
  15. package/dist/docs--WqEHTyl.js +738 -0
  16. package/dist/docs--WqEHTyl.js.map +1 -0
  17. package/dist/epub-jc_2B886.js +52 -0
  18. package/dist/epub-jc_2B886.js.map +1 -0
  19. package/dist/features/ai.d.ts +25 -0
  20. package/dist/features/ai.d.ts.map +1 -0
  21. package/dist/features/ai.js +2 -0
  22. package/dist/features/all.d.ts +5 -0
  23. package/dist/features/all.d.ts.map +1 -0
  24. package/dist/features/all.js +2 -0
  25. package/dist/features/docs/index.d.ts +10 -0
  26. package/dist/features/docs/index.d.ts.map +1 -0
  27. package/dist/features/docs/index.js +2 -0
  28. package/dist/features/epub.d.ts +5 -0
  29. package/dist/features/epub.d.ts.map +1 -0
  30. package/dist/features/epub.js +2 -0
  31. package/dist/features/feedback.d.ts +5 -0
  32. package/dist/features/feedback.d.ts.map +1 -0
  33. package/dist/features/feedback.js +2 -0
  34. package/dist/features/index.d.ts +2 -0
  35. package/dist/features/index.js +2 -0
  36. package/dist/features/lint.d.ts +7 -0
  37. package/dist/features/lint.d.ts.map +1 -0
  38. package/dist/features/lint.js +2 -0
  39. package/dist/features/llms.d.ts +27 -0
  40. package/dist/features/llms.d.ts.map +1 -0
  41. package/dist/features/llms.js +2 -0
  42. package/dist/features/mcp.d.ts +9 -0
  43. package/dist/features/mcp.d.ts.map +1 -0
  44. package/dist/features/mcp.js +2 -0
  45. package/dist/features/og.d.ts +8 -0
  46. package/dist/features/og.d.ts.map +1 -0
  47. package/dist/features/og.js +2 -0
  48. package/dist/features/search.d.ts +84 -0
  49. package/dist/features/search.d.ts.map +1 -0
  50. package/dist/features/search.js +2 -0
  51. package/dist/features/utils.d.ts +2 -0
  52. package/dist/features/utils.js +2 -0
  53. package/dist/features/webmcp.d.ts +12 -0
  54. package/dist/features/webmcp.d.ts.map +1 -0
  55. package/dist/features/webmcp.js +2 -0
  56. package/dist/features-CXFCgOWD.js +132 -0
  57. package/dist/features-CXFCgOWD.js.map +1 -0
  58. package/dist/feedback-Brj092vC.js +37 -0
  59. package/dist/feedback-Brj092vC.js.map +1 -0
  60. package/dist/fs-D6J9dOEx.js +14 -0
  61. package/dist/fs-D6J9dOEx.js.map +1 -0
  62. package/dist/index-Ck0EPdgu.d.ts +126 -0
  63. package/dist/index-Ck0EPdgu.d.ts.map +1 -0
  64. package/dist/index-Cs7hw2un.d.ts +76 -0
  65. package/dist/index-Cs7hw2un.d.ts.map +1 -0
  66. package/dist/index-DI66DoV8.d.ts +64 -0
  67. package/dist/index-DI66DoV8.d.ts.map +1 -0
  68. package/dist/index.js +155 -139
  69. package/dist/index.js.map +1 -1
  70. package/dist/{installer-Ci3Hf9it.js → installer-CzhVfs7i.js} +81 -8
  71. package/dist/installer-CzhVfs7i.js.map +1 -0
  72. package/dist/lint-zkXUULgO.js +137 -0
  73. package/dist/lint-zkXUULgO.js.map +1 -0
  74. package/dist/llms-Br0RJXXg.js +228 -0
  75. package/dist/llms-Br0RJXXg.js.map +1 -0
  76. package/dist/mcp-Ce_W93we.js +67 -0
  77. package/dist/mcp-Ce_W93we.js.map +1 -0
  78. package/dist/og-LFlX1Bl4.js +142 -0
  79. package/dist/og-LFlX1Bl4.js.map +1 -0
  80. package/dist/project/index.d.ts +2 -0
  81. package/dist/project/index.js +2 -0
  82. package/dist/project-DwRVTm4p.js +110 -0
  83. package/dist/project-DwRVTm4p.js.map +1 -0
  84. package/dist/registry/installer.d.ts +4 -5
  85. package/dist/registry/installer.d.ts.map +1 -1
  86. package/dist/registry/installer.js +1 -1
  87. package/dist/schema.json +1 -1
  88. package/dist/search-WfSdiGx-.js +381 -0
  89. package/dist/search-WfSdiGx-.js.map +1 -0
  90. package/dist/utils-CxJ7kw39.d.ts +43 -0
  91. package/dist/utils-CxJ7kw39.d.ts.map +1 -0
  92. package/dist/utils-DbMe7h6c.js +98 -0
  93. package/dist/utils-DbMe7h6c.js.map +1 -0
  94. package/dist/webmcp-DyTLrsXL.js +135 -0
  95. package/dist/webmcp-DyTLrsXL.js.map +1 -0
  96. package/package.json +28 -7
  97. package/dist/config.d.ts.map +0 -1
  98. package/dist/config.js.map +0 -1
  99. package/dist/installer-Ci3Hf9it.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,12 +1,17 @@
1
1
  #!/usr/bin/env node
2
- import { createOrLoadConfig, initConfig } from "./config.js";
3
- import { t as FumadocsComponentInstaller } from "./installer-Ci3Hf9it.js";
2
+ import { i as initConfig, n as createOrLoadConfig } from "./config-Bw0kmF01.js";
3
+ import { T as parseSourceFile } from "./codemod-auol-p4R.js";
4
+ import { t as exists } from "./fs-D6J9dOEx.js";
5
+ import { i as loadProject, n as frameworks } from "./project-DwRVTm4p.js";
6
+ import { n as isCancel$1, r as UIRegistries, t as FumadocsComponentInstaller } from "./installer-CzhVfs7i.js";
7
+ import { t as runFeature } from "./features-CXFCgOWD.js";
8
+ import { t as features } from "./all-BBf7I8hG.js";
4
9
  import fs from "node:fs/promises";
5
10
  import path from "node:path";
6
- import { Command } from "commander";
11
+ import { cac } from "cac";
7
12
  import picocolors from "picocolors";
8
13
  import { x } from "tinyexec";
9
- import { autocompleteMultiselect, cancel, group, intro, isCancel, log, outro, select, spinner } from "@clack/prompts";
14
+ import { autocompleteMultiselect, cancel, confirm, group, intro, log, note, outro, select, spinner } from "@clack/prompts";
10
15
  import { exec } from "node:child_process";
11
16
  import { promisify } from "node:util";
12
17
  import { HttpRegistryConnector, LocalRegistryConnector } from "fuma-cli/registry/connector";
@@ -61,13 +66,7 @@ async function runTree(args) {
61
66
  }
62
67
  //#endregion
63
68
  //#region package.json
64
- var version = "1.5.0";
65
- //#endregion
66
- //#region src/commands/shared.ts
67
- const UIRegistries = {
68
- "base-ui": "fumadocs/base-ui",
69
- "radix-ui": "fumadocs/radix-ui"
70
- };
69
+ var version = "1.6.1";
71
70
  //#endregion
72
71
  //#region src/commands/customise.ts
73
72
  async function customise(config, connector) {
@@ -87,9 +86,7 @@ async function customise(config, connector) {
87
86
  subRegistry,
88
87
  name: "layouts/docs"
89
88
  }],
90
- print() {
91
- printLayout(["fumadocs-ui/layouts/docs", "@/layouts/docs"], ["fumadocs-ui/layouts/docs/page", "@/layouts/docs/page"]);
92
- }
89
+ print: () => printLayout(config, ["fumadocs-ui/layouts/docs", "@/layouts/docs"], ["fumadocs-ui/layouts/docs/page", "@/layouts/docs/page"])
93
90
  },
94
91
  hint: "the default docs layout"
95
92
  },
@@ -101,9 +98,7 @@ async function customise(config, connector) {
101
98
  subRegistry,
102
99
  name: "layouts/notebook"
103
100
  }],
104
- print() {
105
- printLayout(["fumadocs-ui/layouts/notebook", "@/layouts/notebook"], ["fumadocs-ui/layouts/notebook/page", "@/layouts/notebook/page"]);
106
- }
101
+ print: () => printLayout(config, ["fumadocs-ui/layouts/notebook", "@/layouts/notebook"], ["fumadocs-ui/layouts/notebook/page", "@/layouts/notebook/page"])
107
102
  },
108
103
  hint: "a more compact version of docs layout"
109
104
  },
@@ -115,9 +110,7 @@ async function customise(config, connector) {
115
110
  subRegistry,
116
111
  name: "layouts/flux"
117
112
  }],
118
- print() {
119
- printLayout(["fumadocs-ui/layouts/flux", "@/layouts/flux"], ["fumadocs-ui/layouts/flux/page", "@/layouts/flux/page"]);
120
- }
113
+ print: () => printLayout(config, ["fumadocs-ui/layouts/flux", "@/layouts/flux"], ["fumadocs-ui/layouts/flux/page", "@/layouts/flux/page"])
121
114
  },
122
115
  hint: "the experimental variant of docs layout"
123
116
  },
@@ -129,9 +122,7 @@ async function customise(config, connector) {
129
122
  subRegistry,
130
123
  name: "layouts/glass"
131
124
  }],
132
- print() {
133
- printLayout(["fumadocs-ui/layouts/glass", "@/layouts/glass"], ["fumadocs-ui/layouts/glass/page", "@/layouts/glass/page"]);
134
- }
125
+ print: () => printLayout(config, ["fumadocs-ui/layouts/glass", "@/layouts/glass"], ["fumadocs-ui/layouts/glass/page", "@/layouts/glass/page"])
135
126
  },
136
127
  hint: "a docs layout with floating, translucent panels"
137
128
  },
@@ -143,9 +134,7 @@ async function customise(config, connector) {
143
134
  subRegistry,
144
135
  name: "layouts/home"
145
136
  }],
146
- print() {
147
- printLayout(["fumadocs-ui/layouts/home", `@/layouts/home`]);
148
- }
137
+ print: () => printLayout(config, ["fumadocs-ui/layouts/home", "@/layouts/home"])
149
138
  },
150
139
  hint: "the layout for other non-docs pages"
151
140
  }
@@ -168,9 +157,7 @@ async function customise(config, connector) {
168
157
  value: {
169
158
  id: "docs-min",
170
159
  targets: [{ name: "layouts/docs-min" }],
171
- print() {
172
- printLayout(["fumadocs-ui/layouts/docs", "@/layouts/docs"], ["fumadocs-ui/layouts/docs/page", "@/layouts/docs/page"]);
173
- }
160
+ print: () => printLayout(config, ["fumadocs-ui/layouts/docs", "@/layouts/docs"], ["fumadocs-ui/layouts/docs/page", "@/layouts/docs/page"])
174
161
  }
175
162
  },
176
163
  ...info.unlistedIndexes.flatMap((index) => {
@@ -229,15 +216,39 @@ async function customise(config, connector) {
229
216
  process.exit(0);
230
217
  } })).target;
231
218
  for (const target of targetInfo.targets) await installer.installInteractive(target.name, target.subRegistry);
232
- targetInfo.print?.();
219
+ await targetInfo.print?.();
233
220
  outro(picocolors.bold("Have fun!"));
234
221
  }
235
- function printLayout(...maps) {
222
+ /** point the imports of route files at the installed layouts */
223
+ async function rewriteLayoutImports(config, map) {
224
+ const dir = path.join(config.baseDir, frameworks[config.framework].routesDir);
225
+ const entries = await fs.readdir(dir, {
226
+ recursive: true,
227
+ withFileTypes: true
228
+ }).catch(() => []);
229
+ const updated = [];
230
+ for (const entry of entries) {
231
+ if (!entry.isFile() || !entry.name.endsWith(".tsx")) continue;
232
+ const file = path.join(entry.parentPath, entry.name);
233
+ const source = parseSourceFile(file, await fs.readFile(file, "utf-8"));
234
+ for (const node of source.program.body) {
235
+ if (node.type !== "ImportDeclaration") continue;
236
+ const to = map.get(node.source.value);
237
+ if (to) source.s.overwrite(node.source.start + 1, node.source.end - 1, to);
238
+ }
239
+ if (!source.s.hasChanged()) continue;
240
+ await source.save();
241
+ updated.push(file);
242
+ }
243
+ return updated;
244
+ }
245
+ async function printLayout(config, ...maps) {
236
246
  intro(picocolors.bold("What is Next?"));
247
+ const updated = await rewriteLayoutImports(config, new Map(maps));
237
248
  log.info([
238
249
  "You can check the installed layouts in `layouts` folder.",
239
250
  picocolors.dim("---"),
240
- "Open your `layout.tsx` files, replace the imports of components:",
251
+ updated.length > 0 ? `Updated the imports of ${updated.join(", ")}.` : "Open your `layout.tsx` files, replace the imports of components:",
241
252
  ...maps.map(([from, to]) => picocolors.greenBright(`"${from}" -> "${to}"`))
242
253
  ].join("\n"));
243
254
  }
@@ -474,7 +485,7 @@ async function add(input, connector, config) {
474
485
  message: "Select components to install",
475
486
  options: groups.flat().sort((a, b) => a.label.localeCompare(b.label))
476
487
  });
477
- if (isCancel(value)) {
488
+ if (isCancel$1(value)) {
478
489
  outro("Ended");
479
490
  return;
480
491
  }
@@ -487,16 +498,6 @@ async function add(input, connector, config) {
487
498
  outro(picocolors.bold(picocolors.greenBright("Successful")));
488
499
  }
489
500
  //#endregion
490
- //#region src/utils/fs.ts
491
- async function exists(pathLike) {
492
- try {
493
- await fs.access(pathLike);
494
- return true;
495
- } catch {
496
- return false;
497
- }
498
- }
499
- //#endregion
500
501
  //#region src/commands/export-epub.ts
501
502
  const execAsync = promisify(exec);
502
503
  async function readPackageJson(cwd) {
@@ -517,60 +518,6 @@ const EPUB_BUILD_PATHS = {
517
518
  "react-router-spa": "build/client/export/epub",
518
519
  waku: "dist/public/export/epub"
519
520
  };
520
- const API_ROUTE_TEMPLATE = `import { source } from '@/lib/source';
521
- import { exportEpub } from 'fumadocs-epub';
522
-
523
- export const revalidate = false;
524
-
525
- export async function GET(request: Request): Promise<Response> {
526
- // Require EXPORT_SECRET to prevent unauthenticated abuse. Pass via Authorization: Bearer <secret>
527
- const secret = process.env.EXPORT_SECRET;
528
- if (!secret) {
529
- return new Response('EXPORT_SECRET is not configured. Set it in your environment to protect this endpoint.', { status: 503 });
530
- }
531
- const authHeader = request.headers.get('authorization');
532
- const token = authHeader?.replace(/^Bearer\\s+/i, '') ?? '';
533
- if (token !== secret) {
534
- return new Response('Unauthorized', { status: authHeader ? 403 : 401 });
535
- }
536
- const buffer = await exportEpub({
537
- source,
538
- title: 'Documentation',
539
- author: 'Your Team',
540
- description: 'Exported documentation',
541
- cover: '/cover.png',
542
- });
543
- return new Response(new Uint8Array(buffer), {
544
- headers: {
545
- 'Content-Type': 'application/epub+zip',
546
- 'Content-Disposition': 'attachment; filename="docs.epub"',
547
- },
548
- });
549
- }
550
- `;
551
- async function findAppDir(cwd) {
552
- for (const appPath of ["app", "src/app"]) {
553
- const fullPath = path.join(cwd, appPath);
554
- if (await exists(fullPath)) return fullPath;
555
- }
556
- return null;
557
- }
558
- async function scaffoldEpubRoute(cwd) {
559
- const appDir = await findAppDir(cwd);
560
- if (!appDir) {
561
- console.error(picocolors.red("Could not find app directory (app/ or src/app/)"));
562
- return false;
563
- }
564
- const routePath = path.join(appDir, "export", "epub", "route.ts");
565
- if (await exists(routePath)) {
566
- console.log(picocolors.yellow("EPUB route already exists at"), routePath);
567
- return true;
568
- }
569
- await fs.mkdir(path.dirname(routePath), { recursive: true });
570
- await fs.writeFile(routePath, API_ROUTE_TEMPLATE);
571
- console.log(picocolors.green("Created EPUB route at"), routePath);
572
- return true;
573
- }
574
521
  async function exportEpub(options) {
575
522
  const cwd = process.cwd();
576
523
  const outputPath = path.resolve(cwd, options.output ?? "docs.epub");
@@ -583,39 +530,6 @@ async function exportEpub(options) {
583
530
  process.exit(1);
584
531
  }
585
532
  const pkg = await readPackageJson(cwd);
586
- const hasNextConfig = await exists(path.join(cwd, "next.config.js")) || await exists(path.join(cwd, "next.config.ts")) || await exists(path.join(cwd, "next.config.mjs"));
587
- const hasNextInPkg = !!(pkg ? {
588
- ...pkg.dependencies,
589
- ...pkg.devDependencies,
590
- ...pkg.peerDependencies
591
- } : {}).next;
592
- const hasAppOrPages = await exists(path.join(cwd, "app")) || await exists(path.join(cwd, "pages")) || await exists(path.join(cwd, "src", "app")) || await exists(path.join(cwd, "src", "pages"));
593
- if (!(hasNextConfig || hasNextInPkg && hasAppOrPages) && framework === "next") {
594
- console.error(picocolors.red("Next.js project not found. Run this command from a Fumadocs Next.js project root."));
595
- process.exit(1);
596
- }
597
- if (framework === "next") {
598
- spin.start("Scaffolding EPUB route");
599
- const scaffolded = await scaffoldEpubRoute(cwd);
600
- spin.stop(scaffolded ? "EPUB route ready" : "Scaffolding failed");
601
- if (!scaffolded) process.exit(1);
602
- }
603
- if (options.scaffoldOnly) {
604
- console.log(picocolors.cyan("\nTo export:"));
605
- console.log(" 1. Add fumadocs-epub to your dependencies: pnpm add fumadocs-epub");
606
- console.log(" 2. Ensure includeProcessedMarkdown: true in your docs collection config");
607
- if (framework === "next") {
608
- console.log(" 3. Set EXPORT_SECRET in your environment to protect the /export/epub endpoint");
609
- console.log(" 4. Run production build: pnpm build");
610
- console.log(" 5. Start the server (e.g. pnpm start) and keep it running");
611
- console.log(" 6. Run: fumadocs export epub --framework next");
612
- } else {
613
- console.log(` 3. Add a prerender route that outputs EPUB to ${buildPath}`);
614
- console.log(" 4. Run production build: pnpm build");
615
- console.log(` 5. Run: fumadocs export epub --framework ${framework}`);
616
- }
617
- return;
618
- }
619
533
  if (!pkg) {
620
534
  console.error(picocolors.red("Cannot read or parse package.json. Ensure it exists and is valid JSON."));
621
535
  process.exit(1);
@@ -653,6 +567,7 @@ async function exportEpub(options) {
653
567
  });
654
568
  if (!res.ok) {
655
569
  if (res.status === 401 || res.status === 403) console.error(picocolors.red("Auth failed. Check that EXPORT_SECRET matches the value in your app."));
570
+ else if (res.status === 404) console.error(picocolors.red("Export route not found, run `fumadocs feature epub` to create it."));
656
571
  else console.error(picocolors.red(`Server returned ${res.status}. Ensure the app is running (e.g. pnpm start) on port ${port}.`));
657
572
  process.exit(1);
658
573
  }
@@ -684,31 +599,124 @@ async function exportEpub(options) {
684
599
  spin.stop(picocolors.green(`EPUB saved to ${outputPath}`));
685
600
  }
686
601
  //#endregion
602
+ //#region src/commands/feature.ts
603
+ function registerFeatureCommands(cli, createConnector) {
604
+ const ids = features.map((feature) => feature.id).join(", ");
605
+ register(cli.command("feature <id>", `configure a feature on your project (${ids})`), features).action(async (id, options) => {
606
+ const feature = features.find((item) => item.id === id);
607
+ if (!feature) throw new Error(`unknown feature: ${id}, available: ${ids}`);
608
+ await run(feature, options, createConnector(options.dir));
609
+ });
610
+ register(cli.command("init", "set up Fumadocs on an existing app, same as `feature docs`"), [features[0]]).action(async (options) => {
611
+ await run(features[0], options, createConnector(options.dir));
612
+ });
613
+ }
614
+ function register(command, list) {
615
+ command.option("--dir <string>", "the root url or directory to resolve registry").option("-y, --yes", "skip prompts, overwrite existing files").option("--no-install", "write dependencies to package.json without installing");
616
+ for (const feature of list) for (const [key, option] of Object.entries(feature.options ?? {})) if ("choices" in option) command.option(`--${key} <value>`, `${option.message} (${option.choices.map((choice) => choice.value).join(", ")})`);
617
+ else command.option(`--${key}`, option.message);
618
+ return command;
619
+ }
620
+ async function run(feature, options, connector) {
621
+ intro(picocolors.bgBlack(picocolors.whiteBright(feature.title)));
622
+ const config = await createOrLoadConfig(options.config);
623
+ const project = await loadProject(config);
624
+ log.info(`${project.framework}${project.static ? " (static)" : ""}, ${project.packageManager}`);
625
+ const values = {};
626
+ for (const [key, option] of Object.entries(feature.options ?? {})) {
627
+ const given = options[key];
628
+ if ("choices" in option) {
629
+ if (given !== void 0) {
630
+ if (!option.choices.some((choice) => choice.value === given)) throw new Error(`invalid value for --${key}: ${given}`);
631
+ values[key] = given;
632
+ continue;
633
+ }
634
+ const value = await select({
635
+ message: option.message,
636
+ options: option.choices
637
+ });
638
+ if (isCancel$1(value)) {
639
+ cancel("Stopped.");
640
+ process.exit(0);
641
+ }
642
+ values[key] = value;
643
+ } else if (given !== void 0) values[key] = given === true;
644
+ else if (options.yes) values[key] = option.initialValue ?? false;
645
+ else {
646
+ const value = await confirm({
647
+ message: option.message,
648
+ initialValue: option.initialValue
649
+ });
650
+ if (isCancel$1(value)) {
651
+ cancel("Stopped.");
652
+ process.exit(0);
653
+ }
654
+ values[key] = value;
655
+ }
656
+ }
657
+ const spin = spinner();
658
+ const io = {
659
+ installDependencies: options.install !== false,
660
+ log: (message) => spin.message(message),
661
+ async confirmOverwrite(file) {
662
+ if (options.yes) return true;
663
+ spin.clear();
664
+ const value = await confirm({
665
+ message: `Overwrite ${file}?`,
666
+ initialValue: false
667
+ });
668
+ if (isCancel$1(value)) {
669
+ cancel("Stopped.");
670
+ process.exit(0);
671
+ }
672
+ spin.start();
673
+ return value;
674
+ }
675
+ };
676
+ spin.start(`Applying ${feature.title}`);
677
+ try {
678
+ const { notes } = await runFeature(feature, values, {
679
+ project,
680
+ connector,
681
+ io
682
+ });
683
+ spin.stop(`${feature.title} applied`);
684
+ if (notes.length > 0) note(notes.join("\n\n"), "What is Next?");
685
+ } catch (e) {
686
+ spin.error(e instanceof Error ? e.message : String(e));
687
+ process.exit(1);
688
+ }
689
+ outro(picocolors.bold(picocolors.greenBright("Done")));
690
+ }
691
+ //#endregion
687
692
  //#region src/index.ts
688
- const program = new Command().option("--config <string>");
689
- program.name("fumadocs").description("CLI to setup Fumadocs, init a config").version(version).action(async () => {
693
+ const cli = cac("fumadocs");
694
+ cli.option("--config <string>", "path to the config file");
695
+ cli.command("", "init a `cli.json` config file").action(async () => {
690
696
  if (await initConfig()) console.log(picocolors.green("Initialized a `./cli.json` config file."));
691
697
  else console.log(picocolors.redBright("A config file already exists."));
692
698
  });
693
- program.command("customise").alias("customize").description("simple way to customize layouts with Fumadocs UI").option("--dir <string>", "the root url or directory to resolve registry").action(async (options) => {
699
+ cli.command("customise", "simple way to customize layouts with Fumadocs UI").alias("customize").option("--dir <string>", "the root url or directory to resolve registry").action(async (options) => {
694
700
  await customise(await createOrLoadConfig(options.config), createClientFromDir(options.dir));
695
701
  });
696
702
  const dirShortcuts = {
697
703
  ":preview": "https://preview.fumadocs.dev/registry",
698
704
  ":dev": "http://localhost:3000/registry"
699
705
  };
700
- program.command("add").description("add a new component to your docs").argument("[components...]", "components to download").option("--dir <string>", "the root url or directory to resolve registry").action(async (input, options) => {
706
+ cli.command("add [...components]", "add a new component to your docs").option("--dir <string>", "the root url or directory to resolve registry").action(async (input, options) => {
701
707
  const config = await createOrLoadConfig(options.config);
702
708
  await add(input, createClientFromDir(options.dir), config);
703
709
  });
704
- program.command("export").description("export documentation to various formats").command("epub").description("export documentation to EPUB format (run after production build)").requiredOption("--framework <name>", "framework: next, astro, tanstack-start, react-router, waku").option("--output <path>", "output file path", "docs.epub").option("--scaffold-only", "only scaffold the EPUB route, do not copy").action(async (options) => {
710
+ registerFeatureCommands(cli, createClientFromDir);
711
+ cli.command("export <format>", "export documentation to various formats (run after production build)").option("--framework <name>", "framework: next, astro, tanstack-start, react-router, waku").option("--output <path>", "output file path", { default: "docs.epub" }).action(async (format, options) => {
712
+ if (format !== "epub") throw new Error(`unsupported format: ${format}, only \`epub\` is supported.`);
713
+ if (!options.framework) throw new Error("option `--framework <name>` is required.");
705
714
  await exportEpub({
706
- output: options.output,
707
715
  framework: options.framework,
708
- scaffoldOnly: options.scaffoldOnly
716
+ output: options.output
709
717
  });
710
718
  });
711
- program.command("tree").argument("[json_or_args]", "JSON output of `tree` command or arguments for the `tree` command").argument("[output]", "output path of file").option("--js", "output as JavaScript file").option("--no-root", "remove the root node").option("--import-name <name>", "where to import components (JS only)").action(async (str, output, { js, root, importName }) => {
719
+ cli.command("tree [json_or_args] [output]", "generate a file tree for the Files component, from a directory or JSON output of `tree`").option("--js", "output as JavaScript file").option("--no-root", "remove the root node").option("--import-name <name>", "where to import components (JS only)").action(async (str, output, { js, root, importName }) => {
712
720
  const jsExtensions = [
713
721
  ".js",
714
722
  ".tsx",
@@ -731,7 +739,15 @@ function createClientFromDir(dir = "https://fumadocs.dev/registry") {
731
739
  if (dir in dirShortcuts) dir = dirShortcuts[dir];
732
740
  return dir.startsWith("http://") || dir.startsWith("https://") ? new HttpRegistryConnector(dir) : new LocalRegistryConnector(dir);
733
741
  }
734
- program.parse();
742
+ cli.help();
743
+ cli.version(version);
744
+ try {
745
+ cli.parse(process.argv, { run: false });
746
+ await cli.runMatchedCommand();
747
+ } catch (e) {
748
+ console.error(picocolors.redBright(e instanceof Error ? e.message : String(e)));
749
+ process.exit(1);
750
+ }
735
751
  //#endregion
736
752
  export {};
737
753