@fumadocs/cli 1.4.1 → 1.6.0

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 (100) hide show
  1. package/dist/ai-BVuNjYXU.js +100 -0
  2. package/dist/ai-BVuNjYXU.js.map +1 -0
  3. package/dist/all-aUHxtoXL.js +27 -0
  4. package/dist/all-aUHxtoXL.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-CT2jj39D.d.ts +52 -0
  10. package/dist/config-CT2jj39D.d.ts.map +1 -0
  11. package/dist/config-D6uv73fs.js +94 -0
  12. package/dist/config-D6uv73fs.js.map +1 -0
  13. package/dist/config.d.ts +2 -52
  14. package/dist/config.js +1 -72
  15. package/dist/docs-DeIEl_Kt.js +738 -0
  16. package/dist/docs-DeIEl_Kt.js.map +1 -0
  17. package/dist/epub-CnQFJh09.js +52 -0
  18. package/dist/epub-CnQFJh09.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-C1kf2s8j.js +132 -0
  57. package/dist/features-C1kf2s8j.js.map +1 -0
  58. package/dist/feedback-COmWtc5C.js +37 -0
  59. package/dist/feedback-COmWtc5C.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-BCVGQxn9.d.ts +64 -0
  63. package/dist/index-BCVGQxn9.d.ts.map +1 -0
  64. package/dist/index-CR9T40ox.d.ts +76 -0
  65. package/dist/index-CR9T40ox.d.ts.map +1 -0
  66. package/dist/index-Ck0EPdgu.d.ts +126 -0
  67. package/dist/index-Ck0EPdgu.d.ts.map +1 -0
  68. package/dist/index.d.ts +1 -1
  69. package/dist/index.js +198 -124
  70. package/dist/index.js.map +1 -1
  71. package/dist/{installer-Chjj_R2z.js → installer-D0Obb2Md.js} +21 -8
  72. package/dist/installer-D0Obb2Md.js.map +1 -0
  73. package/dist/lint-zkXUULgO.js +137 -0
  74. package/dist/lint-zkXUULgO.js.map +1 -0
  75. package/dist/llms-CONd8Vnn.js +228 -0
  76. package/dist/llms-CONd8Vnn.js.map +1 -0
  77. package/dist/mcp-BT8BLZZ-.js +67 -0
  78. package/dist/mcp-BT8BLZZ-.js.map +1 -0
  79. package/dist/og-DRDOyQ_t.js +142 -0
  80. package/dist/og-DRDOyQ_t.js.map +1 -0
  81. package/dist/project/index.d.ts +2 -0
  82. package/dist/project/index.js +2 -0
  83. package/dist/project-DwRVTm4p.js +110 -0
  84. package/dist/project-DwRVTm4p.js.map +1 -0
  85. package/dist/registry/installer.d.ts +4 -5
  86. package/dist/registry/installer.d.ts.map +1 -1
  87. package/dist/registry/installer.js +1 -1
  88. package/dist/schema.json +1 -1
  89. package/dist/search-iphnSVVY.js +381 -0
  90. package/dist/search-iphnSVVY.js.map +1 -0
  91. package/dist/utils-B1Xy4l_a.d.ts +43 -0
  92. package/dist/utils-B1Xy4l_a.d.ts.map +1 -0
  93. package/dist/utils-DbMe7h6c.js +98 -0
  94. package/dist/utils-DbMe7h6c.js.map +1 -0
  95. package/dist/webmcp-C2PM_9md.js +135 -0
  96. package/dist/webmcp-C2PM_9md.js.map +1 -0
  97. package/package.json +27 -7
  98. package/dist/config.d.ts.map +0 -1
  99. package/dist/config.js.map +0 -1
  100. package/dist/installer-Chjj_R2z.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,12 +1,16 @@
1
1
  #!/usr/bin/env node
2
- import { createOrLoadConfig, initConfig } from "./config.js";
3
- import { t as FumadocsComponentInstaller } from "./installer-Chjj_R2z.js";
2
+ import { i as initConfig, n as createOrLoadConfig } from "./config-D6uv73fs.js";
3
+ import { n as isCancel$1, t as FumadocsComponentInstaller } from "./installer-D0Obb2Md.js";
4
+ import { t as exists } from "./fs-D6J9dOEx.js";
5
+ import { i as loadProject } from "./project-DwRVTm4p.js";
6
+ import { t as runFeature } from "./features-C1kf2s8j.js";
7
+ import { t as features } from "./all-aUHxtoXL.js";
4
8
  import fs from "node:fs/promises";
5
9
  import path from "node:path";
6
- import { Command } from "commander";
10
+ import { cac } from "cac";
7
11
  import picocolors from "picocolors";
8
12
  import { x } from "tinyexec";
9
- import { autocompleteMultiselect, cancel, group, intro, isCancel, log, outro, select, spinner } from "@clack/prompts";
13
+ import { autocompleteMultiselect, cancel, confirm, group, intro, log, note, outro, select, spinner } from "@clack/prompts";
10
14
  import { exec } from "node:child_process";
11
15
  import { promisify } from "node:util";
12
16
  import { HttpRegistryConnector, LocalRegistryConnector } from "fuma-cli/registry/connector";
@@ -61,7 +65,7 @@ async function runTree(args) {
61
65
  }
62
66
  //#endregion
63
67
  //#region package.json
64
- var version = "1.4.1";
68
+ var version = "1.6.0";
65
69
  //#endregion
66
70
  //#region src/commands/shared.ts
67
71
  const UIRegistries = {
@@ -121,6 +125,20 @@ async function customise(config, connector) {
121
125
  },
122
126
  hint: "the experimental variant of docs layout"
123
127
  },
128
+ {
129
+ label: "Glass Layout",
130
+ value: {
131
+ id: "glass",
132
+ targets: [{
133
+ subRegistry,
134
+ name: "layouts/glass"
135
+ }],
136
+ print() {
137
+ printLayout(["fumadocs-ui/layouts/glass", "@/layouts/glass"], ["fumadocs-ui/layouts/glass/page", "@/layouts/glass/page"]);
138
+ }
139
+ },
140
+ hint: "a docs layout with floating, translucent panels"
141
+ },
124
142
  {
125
143
  label: "Home Layout",
126
144
  value: {
@@ -179,7 +197,8 @@ async function customise(config, connector) {
179
197
  at: `@/layouts/${selected.id}/page/slots/${name}`,
180
198
  layoutId: selected.id,
181
199
  name,
182
- isPage: true
200
+ isPage: true,
201
+ uiLibrary: config.uiLibrary
183
202
  });
184
203
  }
185
204
  }
@@ -199,7 +218,8 @@ async function customise(config, connector) {
199
218
  at: `@/layouts/${selected.id}/slots/${name}`,
200
219
  layoutId: selected.id,
201
220
  name,
202
- isPage: false
221
+ isPage: false,
222
+ uiLibrary: config.uiLibrary
203
223
  });
204
224
  }
205
225
  }
@@ -225,19 +245,65 @@ function printLayout(...maps) {
225
245
  ...maps.map(([from, to]) => picocolors.greenBright(`"${from}" -> "${to}"`))
226
246
  ].join("\n"));
227
247
  }
228
- function printSlot({ at, layoutId, name, isPage }) {
248
+ function printSlot({ at, layoutId, name, isPage, uiLibrary }) {
229
249
  intro(picocolors.bold("What is Next?"));
230
250
  log.info(`You can check the installed layout slot in "${at}".`);
231
251
  const code = getSlotCode({
232
252
  at,
233
253
  layoutId,
234
254
  name,
235
- isPage
255
+ isPage,
256
+ uiLibrary
236
257
  });
237
- if (code) if (isPage) log.info(`${picocolors.bold("At your <DocsPage /> component, update your \"slots\" prop:")}\n\n${code}`);
238
- else log.info(`${picocolors.bold("At your <DocsLayout /> component, update your \"slots\" prop:")}\n\n${code}`);
258
+ if (code) {
259
+ const layoutComponent = layoutId === "glass" ? "<GlassLayout />" : "<DocsLayout />";
260
+ if (isPage) log.info(`${picocolors.bold("At your <DocsPage /> component, update your \"slots\" prop:")}\n\n${code}`);
261
+ else log.info(`${picocolors.bold(`At your ${layoutComponent} component, update your "slots" prop:`)}\n\n${code}`);
262
+ }
239
263
  }
240
- function getSlotCode({ at, layoutId, name, isPage }) {
264
+ function getSlotCode({ at, layoutId, name, isPage, uiLibrary }) {
265
+ if (layoutId === "glass") {
266
+ if (isPage) return;
267
+ switch (name) {
268
+ case "header": return `import { Header } from '${at}';
269
+
270
+ return (
271
+ <GlassLayout
272
+ slots={{
273
+ header: Header,
274
+ }}
275
+ >
276
+ ...
277
+ </GlassLayout>
278
+ );`;
279
+ case "sidebar": {
280
+ const imports = [
281
+ "Sidebar",
282
+ "SidebarDrawer",
283
+ "SidebarProvider",
284
+ "useSidebar"
285
+ ];
286
+ if (uiLibrary === "base-ui") imports.push("drawerHandle");
287
+ return `import { ${imports.join(", ")} } from '${at}';
288
+
289
+ return (
290
+ <GlassLayout
291
+ slots={{
292
+ sidebar: {
293
+ main: Sidebar,
294
+ provider: SidebarProvider,
295
+ use: useSidebar,
296
+ drawer: SidebarDrawer,${uiLibrary === "base-ui" ? "\n drawerHandle," : ""}
297
+ },
298
+ }}
299
+ >
300
+ ...
301
+ </GlassLayout>
302
+ );`;
303
+ }
304
+ default: return;
305
+ }
306
+ }
241
307
  if (isPage) switch (name) {
242
308
  case "toc":
243
309
  if (layoutId === "flux") return `import { TOCProvider, TOC } from '${at}';
@@ -400,16 +466,19 @@ async function add(input, connector, config) {
400
466
  hint: item.description
401
467
  }));
402
468
  }
469
+ const groups = await Promise.all([
470
+ scan(void 0, "common"),
471
+ scan("fumadocs/sanity", "sanity"),
472
+ scan("fumadocs/openapi", "openapi"),
473
+ scan("fumadocs/api-docs", "api-docs"),
474
+ scan(subRegistry, "ui")
475
+ ]);
403
476
  spin.stop(picocolors.bold(picocolors.greenBright("registry fetched")));
404
477
  const value = await autocompleteMultiselect({
405
478
  message: "Select components to install",
406
- options: [
407
- ...await scan(void 0, "common"),
408
- ...await scan("fumadocs/sanity", "sanity"),
409
- ...await scan(subRegistry, "ui")
410
- ].sort((a, b) => a.label.localeCompare(b.label))
479
+ options: groups.flat().sort((a, b) => a.label.localeCompare(b.label))
411
480
  });
412
- if (isCancel(value)) {
481
+ if (isCancel$1(value)) {
413
482
  outro("Ended");
414
483
  return;
415
484
  }
@@ -422,16 +491,6 @@ async function add(input, connector, config) {
422
491
  outro(picocolors.bold(picocolors.greenBright("Successful")));
423
492
  }
424
493
  //#endregion
425
- //#region src/utils/fs.ts
426
- async function exists(pathLike) {
427
- try {
428
- await fs.access(pathLike);
429
- return true;
430
- } catch {
431
- return false;
432
- }
433
- }
434
- //#endregion
435
494
  //#region src/commands/export-epub.ts
436
495
  const execAsync = promisify(exec);
437
496
  async function readPackageJson(cwd) {
@@ -452,60 +511,6 @@ const EPUB_BUILD_PATHS = {
452
511
  "react-router-spa": "build/client/export/epub",
453
512
  waku: "dist/public/export/epub"
454
513
  };
455
- const API_ROUTE_TEMPLATE = `import { source } from '@/lib/source';
456
- import { exportEpub } from 'fumadocs-epub';
457
-
458
- export const revalidate = false;
459
-
460
- export async function GET(request: Request): Promise<Response> {
461
- // Require EXPORT_SECRET to prevent unauthenticated abuse. Pass via Authorization: Bearer <secret>
462
- const secret = process.env.EXPORT_SECRET;
463
- if (!secret) {
464
- return new Response('EXPORT_SECRET is not configured. Set it in your environment to protect this endpoint.', { status: 503 });
465
- }
466
- const authHeader = request.headers.get('authorization');
467
- const token = authHeader?.replace(/^Bearer\\s+/i, '') ?? '';
468
- if (token !== secret) {
469
- return new Response('Unauthorized', { status: authHeader ? 403 : 401 });
470
- }
471
- const buffer = await exportEpub({
472
- source,
473
- title: 'Documentation',
474
- author: 'Your Team',
475
- description: 'Exported documentation',
476
- cover: '/cover.png',
477
- });
478
- return new Response(new Uint8Array(buffer), {
479
- headers: {
480
- 'Content-Type': 'application/epub+zip',
481
- 'Content-Disposition': 'attachment; filename="docs.epub"',
482
- },
483
- });
484
- }
485
- `;
486
- async function findAppDir(cwd) {
487
- for (const appPath of ["app", "src/app"]) {
488
- const fullPath = path.join(cwd, appPath);
489
- if (await exists(fullPath)) return fullPath;
490
- }
491
- return null;
492
- }
493
- async function scaffoldEpubRoute(cwd) {
494
- const appDir = await findAppDir(cwd);
495
- if (!appDir) {
496
- console.error(picocolors.red("Could not find app directory (app/ or src/app/)"));
497
- return false;
498
- }
499
- const routePath = path.join(appDir, "export", "epub", "route.ts");
500
- if (await exists(routePath)) {
501
- console.log(picocolors.yellow("EPUB route already exists at"), routePath);
502
- return true;
503
- }
504
- await fs.mkdir(path.dirname(routePath), { recursive: true });
505
- await fs.writeFile(routePath, API_ROUTE_TEMPLATE);
506
- console.log(picocolors.green("Created EPUB route at"), routePath);
507
- return true;
508
- }
509
514
  async function exportEpub(options) {
510
515
  const cwd = process.cwd();
511
516
  const outputPath = path.resolve(cwd, options.output ?? "docs.epub");
@@ -518,39 +523,6 @@ async function exportEpub(options) {
518
523
  process.exit(1);
519
524
  }
520
525
  const pkg = await readPackageJson(cwd);
521
- 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"));
522
- const hasNextInPkg = !!(pkg ? {
523
- ...pkg.dependencies,
524
- ...pkg.devDependencies,
525
- ...pkg.peerDependencies
526
- } : {}).next;
527
- 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"));
528
- if (!(hasNextConfig || hasNextInPkg && hasAppOrPages) && framework === "next") {
529
- console.error(picocolors.red("Next.js project not found. Run this command from a Fumadocs Next.js project root."));
530
- process.exit(1);
531
- }
532
- if (framework === "next") {
533
- spin.start("Scaffolding EPUB route");
534
- const scaffolded = await scaffoldEpubRoute(cwd);
535
- spin.stop(scaffolded ? "EPUB route ready" : "Scaffolding failed");
536
- if (!scaffolded) process.exit(1);
537
- }
538
- if (options.scaffoldOnly) {
539
- console.log(picocolors.cyan("\nTo export:"));
540
- console.log(" 1. Add fumadocs-epub to your dependencies: pnpm add fumadocs-epub");
541
- console.log(" 2. Ensure includeProcessedMarkdown: true in your docs collection config");
542
- if (framework === "next") {
543
- console.log(" 3. Set EXPORT_SECRET in your environment to protect the /export/epub endpoint");
544
- console.log(" 4. Run production build: pnpm build");
545
- console.log(" 5. Start the server (e.g. pnpm start) and keep it running");
546
- console.log(" 6. Run: fumadocs export epub --framework next");
547
- } else {
548
- console.log(` 3. Add a prerender route that outputs EPUB to ${buildPath}`);
549
- console.log(" 4. Run production build: pnpm build");
550
- console.log(` 5. Run: fumadocs export epub --framework ${framework}`);
551
- }
552
- return;
553
- }
554
526
  if (!pkg) {
555
527
  console.error(picocolors.red("Cannot read or parse package.json. Ensure it exists and is valid JSON."));
556
528
  process.exit(1);
@@ -588,6 +560,7 @@ async function exportEpub(options) {
588
560
  });
589
561
  if (!res.ok) {
590
562
  if (res.status === 401 || res.status === 403) console.error(picocolors.red("Auth failed. Check that EXPORT_SECRET matches the value in your app."));
563
+ else if (res.status === 404) console.error(picocolors.red("Export route not found, run `fumadocs feature epub` to create it."));
591
564
  else console.error(picocolors.red(`Server returned ${res.status}. Ensure the app is running (e.g. pnpm start) on port ${port}.`));
592
565
  process.exit(1);
593
566
  }
@@ -619,31 +592,124 @@ async function exportEpub(options) {
619
592
  spin.stop(picocolors.green(`EPUB saved to ${outputPath}`));
620
593
  }
621
594
  //#endregion
595
+ //#region src/commands/feature.ts
596
+ function registerFeatureCommands(cli, createConnector) {
597
+ const ids = features.map((feature) => feature.id).join(", ");
598
+ register(cli.command("feature <id>", `configure a feature on your project (${ids})`), features).action(async (id, options) => {
599
+ const feature = features.find((item) => item.id === id);
600
+ if (!feature) throw new Error(`unknown feature: ${id}, available: ${ids}`);
601
+ await run(feature, options, createConnector(options.dir));
602
+ });
603
+ register(cli.command("init", "set up Fumadocs on an existing app, same as `feature docs`"), [features[0]]).action(async (options) => {
604
+ await run(features[0], options, createConnector(options.dir));
605
+ });
606
+ }
607
+ function register(command, list) {
608
+ 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");
609
+ 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(", ")})`);
610
+ else command.option(`--${key}`, option.message);
611
+ return command;
612
+ }
613
+ async function run(feature, options, connector) {
614
+ intro(picocolors.bgBlack(picocolors.whiteBright(feature.title)));
615
+ const config = await createOrLoadConfig(options.config);
616
+ const project = await loadProject(config);
617
+ log.info(`${project.framework}${project.static ? " (static)" : ""}, ${project.packageManager}`);
618
+ const values = {};
619
+ for (const [key, option] of Object.entries(feature.options ?? {})) {
620
+ const given = options[key];
621
+ if ("choices" in option) {
622
+ if (given !== void 0) {
623
+ if (!option.choices.some((choice) => choice.value === given)) throw new Error(`invalid value for --${key}: ${given}`);
624
+ values[key] = given;
625
+ continue;
626
+ }
627
+ const value = await select({
628
+ message: option.message,
629
+ options: option.choices
630
+ });
631
+ if (isCancel$1(value)) {
632
+ cancel("Stopped.");
633
+ process.exit(0);
634
+ }
635
+ values[key] = value;
636
+ } else if (given !== void 0) values[key] = given === true;
637
+ else if (options.yes) values[key] = option.initialValue ?? false;
638
+ else {
639
+ const value = await confirm({
640
+ message: option.message,
641
+ initialValue: option.initialValue
642
+ });
643
+ if (isCancel$1(value)) {
644
+ cancel("Stopped.");
645
+ process.exit(0);
646
+ }
647
+ values[key] = value;
648
+ }
649
+ }
650
+ const spin = spinner();
651
+ const io = {
652
+ installDependencies: options.install !== false,
653
+ log: (message) => spin.message(message),
654
+ async confirmOverwrite(file) {
655
+ if (options.yes) return true;
656
+ spin.clear();
657
+ const value = await confirm({
658
+ message: `Overwrite ${file}?`,
659
+ initialValue: false
660
+ });
661
+ if (isCancel$1(value)) {
662
+ cancel("Stopped.");
663
+ process.exit(0);
664
+ }
665
+ spin.start();
666
+ return value;
667
+ }
668
+ };
669
+ spin.start(`Applying ${feature.title}`);
670
+ try {
671
+ const { notes } = await runFeature(feature, values, {
672
+ project,
673
+ connector,
674
+ io
675
+ });
676
+ spin.stop(`${feature.title} applied`);
677
+ if (notes.length > 0) note(notes.join("\n\n"), "What is Next?");
678
+ } catch (e) {
679
+ spin.error(e instanceof Error ? e.message : String(e));
680
+ process.exit(1);
681
+ }
682
+ outro(picocolors.bold(picocolors.greenBright("Done")));
683
+ }
684
+ //#endregion
622
685
  //#region src/index.ts
623
- const program = new Command().option("--config <string>");
624
- program.name("fumadocs").description("CLI to setup Fumadocs, init a config").version(version).action(async () => {
686
+ const cli = cac("fumadocs");
687
+ cli.option("--config <string>", "path to the config file");
688
+ cli.command("", "init a `cli.json` config file").action(async () => {
625
689
  if (await initConfig()) console.log(picocolors.green("Initialized a `./cli.json` config file."));
626
690
  else console.log(picocolors.redBright("A config file already exists."));
627
691
  });
628
- 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) => {
692
+ 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) => {
629
693
  await customise(await createOrLoadConfig(options.config), createClientFromDir(options.dir));
630
694
  });
631
695
  const dirShortcuts = {
632
696
  ":preview": "https://preview.fumadocs.dev/registry",
633
697
  ":dev": "http://localhost:3000/registry"
634
698
  };
635
- 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) => {
699
+ 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) => {
636
700
  const config = await createOrLoadConfig(options.config);
637
701
  await add(input, createClientFromDir(options.dir), config);
638
702
  });
639
- 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) => {
703
+ registerFeatureCommands(cli, createClientFromDir);
704
+ 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) => {
705
+ if (format !== "epub") throw new Error(`unsupported format: ${format}, only \`epub\` is supported.`);
706
+ if (!options.framework) throw new Error("option `--framework <name>` is required.");
640
707
  await exportEpub({
641
- output: options.output,
642
708
  framework: options.framework,
643
- scaffoldOnly: options.scaffoldOnly
709
+ output: options.output
644
710
  });
645
711
  });
646
- 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 }) => {
712
+ 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 }) => {
647
713
  const jsExtensions = [
648
714
  ".js",
649
715
  ".tsx",
@@ -666,7 +732,15 @@ function createClientFromDir(dir = "https://fumadocs.dev/registry") {
666
732
  if (dir in dirShortcuts) dir = dirShortcuts[dir];
667
733
  return dir.startsWith("http://") || dir.startsWith("https://") ? new HttpRegistryConnector(dir) : new LocalRegistryConnector(dir);
668
734
  }
669
- program.parse();
735
+ cli.help();
736
+ cli.version(version);
737
+ try {
738
+ cli.parse(process.argv, { run: false });
739
+ await cli.runMatchedCommand();
740
+ } catch (e) {
741
+ console.error(picocolors.redBright(e instanceof Error ? e.message : String(e)));
742
+ process.exit(1);
743
+ }
670
744
  //#endregion
671
745
  export {};
672
746