@fumadocs/cli 0.2.0 → 1.0.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.
package/dist/index.js CHANGED
@@ -1,81 +1,29 @@
1
1
  #!/usr/bin/env node
2
- import {
3
- exists,
4
- isRelative
5
- } from "./chunk-DG6SFM2G.js";
6
2
 
7
3
  // src/index.ts
8
- import fs10 from "fs/promises";
9
- import path9 from "path";
4
+ import fs5 from "fs/promises";
5
+ import path4 from "path";
10
6
  import { Command } from "commander";
11
- import picocolors5 from "picocolors";
12
- import { isCancel as isCancel5, outro as outro4, select as select2 } from "@clack/prompts";
13
-
14
- // src/commands/init.ts
15
- import * as process2 from "process";
16
- import path3 from "path";
17
- import {
18
- cancel,
19
- confirm,
20
- intro,
21
- isCancel,
22
- log,
23
- note,
24
- spinner
25
- } from "@clack/prompts";
26
- import picocolors from "picocolors";
27
- import { x } from "tinyexec";
28
-
29
- // src/utils/get-package-manager.ts
30
- import { detect } from "package-manager-detector";
31
- async function getPackageManager() {
32
- const result = await detect();
33
- return result?.name ?? "npm";
34
- }
35
-
36
- // src/utils/is-src.ts
37
- import path from "path";
38
- async function isSrc() {
39
- return exists("./src");
40
- }
41
- function resolveAppPath(filePath, src2) {
42
- return src2 ? path.join("./src", filePath) : filePath;
43
- }
7
+ import picocolors3 from "picocolors";
44
8
 
45
- // src/config.ts
9
+ // src/utils/add/install-component.ts
10
+ import path2 from "path";
46
11
  import fs from "fs/promises";
47
- var src = await isSrc();
48
- var defaultConfig = {
49
- aliases: {
50
- cn: src ? "./src/lib/utils.ts" : "./lib/utils.ts",
51
- componentsDir: src ? "./src/components" : "./components",
52
- uiDir: src ? "./src/components/ui" : "./components/ui",
53
- libDir: src ? "./src/lib" : "./lib"
54
- }
55
- };
56
- async function loadConfig(file = "./cli.json") {
57
- try {
58
- const content = await fs.readFile(file);
59
- return JSON.parse(content.toString());
60
- } catch {
61
- return {};
62
- }
63
- }
64
- async function initConfig(file = "./cli.json") {
65
- if (await fs.stat(file).then(() => true).catch(() => false)) {
66
- return false;
67
- }
68
- await fs.writeFile(file, JSON.stringify(defaultConfig, null, 2));
69
- return true;
70
- }
12
+ import { confirm, isCancel, log, outro } from "@clack/prompts";
71
13
 
72
- // src/utils/transform-references.ts
73
- import path2 from "path";
14
+ // src/utils/typescript.ts
15
+ import { Project } from "ts-morph";
16
+ function createEmptyProject() {
17
+ return new Project({
18
+ compilerOptions: {}
19
+ });
20
+ }
74
21
 
75
22
  // src/constants.ts
76
23
  var typescriptExtensions = [".ts", ".tsx", ".js", ".jsx"];
77
24
 
78
25
  // src/utils/transform-references.ts
26
+ import path from "path";
79
27
  function transformReferences(file, transform) {
80
28
  for (const specifier of file.getImportStringLiterals()) {
81
29
  const result = transform(specifier.getLiteralValue());
@@ -83,643 +31,267 @@ function transformReferences(file, transform) {
83
31
  specifier.setLiteralValue(result);
84
32
  }
85
33
  }
86
- function toReferencePath(sourceFile, referenceFile) {
87
- const extname = path2.extname(referenceFile);
88
- const importPath = path2.relative(
89
- path2.dirname(sourceFile),
90
- path2.join(
91
- path2.dirname(referenceFile),
92
- path2.basename(
93
- referenceFile,
94
- typescriptExtensions.includes(extname) ? extname : void 0
95
- )
96
- )
97
- ).replaceAll(path2.sep, "/");
34
+ function toImportSpecifier(sourceFile, referenceFile) {
35
+ const extname = path.extname(referenceFile);
36
+ const removeExt = typescriptExtensions.includes(extname);
37
+ const importPath = path.relative(
38
+ path.dirname(sourceFile),
39
+ removeExt ? referenceFile.substring(0, referenceFile.length - extname.length) : referenceFile
40
+ ).replaceAll(path.sep, "/");
98
41
  return importPath.startsWith("../") ? importPath : `./${importPath}`;
99
42
  }
100
- function resolveReference(ref, resolver) {
101
- if (ref.startsWith("./") || ref.startsWith("../")) {
102
- return {
103
- type: "file",
104
- path: path2.join(resolver.relativeTo, ref)
105
- };
106
- }
107
- if (ref.startsWith("@/")) {
108
- const rest = ref.slice("@/".length);
109
- if (!resolver.alias) throw new Error("alias resolver is not configured");
110
- return {
111
- type: "file",
112
- path: path2.join(resolver.alias.dir, rest)
113
- };
114
- }
115
- if (ref.startsWith("@")) {
116
- const segments = ref.split("/");
117
- return {
118
- type: "dep",
119
- name: segments.slice(0, 2).join("/")
120
- };
121
- }
122
- return {
123
- type: "dep",
124
- name: ref.split("/")[0]
125
- };
126
- }
127
43
 
128
- // src/utils/typescript.ts
129
- import { Project } from "ts-morph";
130
- function createEmptyProject() {
131
- return new Project({
132
- compilerOptions: {}
133
- });
44
+ // src/registry/schema.ts
45
+ import { z } from "zod";
46
+ var namespaces = [
47
+ "components",
48
+ "lib",
49
+ "css",
50
+ "route",
51
+ "ui",
52
+ "block"
53
+ ];
54
+ var indexSchema = z.object({
55
+ name: z.string(),
56
+ title: z.string().optional(),
57
+ description: z.string().optional()
58
+ });
59
+ var fileSchema = z.object({
60
+ type: z.literal(namespaces),
61
+ path: z.string(),
62
+ target: z.string().optional(),
63
+ content: z.string()
64
+ });
65
+ var componentSchema = z.object({
66
+ name: z.string(),
67
+ title: z.string().optional(),
68
+ description: z.string().optional(),
69
+ files: z.array(fileSchema),
70
+ dependencies: z.record(z.string(), z.string().or(z.null())),
71
+ devDependencies: z.record(z.string(), z.string().or(z.null())),
72
+ subComponents: z.array(z.string()).default([])
73
+ });
74
+ var rootSchema = z.object({
75
+ name: z.string(),
76
+ index: z.array(indexSchema),
77
+ components: z.array(componentSchema)
78
+ });
79
+
80
+ // src/registry/client.ts
81
+ import { z as z2 } from "zod";
82
+ function validateRegistryIndex(indexes) {
83
+ return z2.array(indexSchema).parse(indexes);
84
+ }
85
+ function validateRegistryComponent(component) {
86
+ return componentSchema.parse(component);
134
87
  }
135
88
 
136
- // src/commands/init.ts
137
- async function init(plugin, config = {}) {
138
- intro(
139
- picocolors.bgCyan(picocolors.black(picocolors.bold("Installing Plugins")))
140
- );
141
- const ctx = {
142
- src: await isSrc(),
143
- outFileMap: /* @__PURE__ */ new Map(),
144
- ...config
145
- };
146
- const files = await plugin.files(ctx);
89
+ // src/utils/add/install-component.ts
90
+ function createComponentInstaller(options) {
91
+ const { config, resolver } = options;
147
92
  const project = createEmptyProject();
148
- for (const [name, content] of Object.entries(files)) {
149
- const file = getOutputPath(name, ctx);
150
- ctx.outFileMap.set(name, file);
151
- log.step(picocolors.green(`Writing ${file} \u2605`));
152
- if (await exists(file)) {
153
- const value = await confirm({
154
- message: `${file} already exists`,
155
- active: "Override",
156
- inactive: "Skip"
157
- });
158
- if (isCancel(value)) {
159
- cancel("Operation cancelled.");
160
- process2.exit(0);
93
+ const installedFiles = /* @__PURE__ */ new Set();
94
+ const downloadedComps = /* @__PURE__ */ new Map();
95
+ function buildFileList(downloaded) {
96
+ const map = /* @__PURE__ */ new Map();
97
+ for (const item of downloaded) {
98
+ for (const file of item.files) {
99
+ const filePath = file.target ?? file.path;
100
+ if (map.has(filePath)) {
101
+ console.warn(
102
+ `noticed duplicated output file for ${filePath}, ignoring for now.`
103
+ );
104
+ continue;
105
+ }
106
+ map.set(filePath, file);
161
107
  }
162
- if (!value) continue;
163
108
  }
164
- const sourceFile = project.createSourceFile(file, content, {
165
- overwrite: true
166
- });
167
- transformReferences(sourceFile, (specifier) => {
168
- const resolved = resolveReference(specifier, {
169
- alias: {
170
- type: "append",
171
- dir: ctx.src ? "src" : ""
172
- },
173
- relativeTo: path3.dirname(file)
174
- });
175
- if (resolved.type !== "file") return;
176
- return toReferencePath(file, getOutputPath(resolved.path, ctx));
177
- });
178
- await sourceFile.save();
109
+ return Array.from(map.values());
179
110
  }
180
- if (plugin.dependencies.length > 0) {
181
- const manager = await getPackageManager();
182
- const value = await confirm({
183
- message: `This plugin contains additional dependencies, do you want to install them? (detected: ${manager})`
184
- });
185
- if (isCancel(value)) {
186
- cancel("Operation cancelled.");
187
- process2.exit(0);
188
- }
189
- if (value) {
190
- const spin = spinner();
191
- spin.start("Installing dependencies");
192
- await x(manager, ["install", ...plugin.dependencies]);
193
- spin.stop("Successfully installed.");
194
- }
195
- }
196
- if (plugin.transform) {
197
- const value = await confirm({
198
- message: "This plugin contains changes to your files, do you want to apply them?"
199
- });
200
- if (isCancel(value)) {
201
- cancel("Operation cancelled.");
202
- process2.exit(0);
203
- }
204
- if (value) {
205
- await plugin.transform(ctx);
206
- note(
207
- `You can format the output with Prettier or other code formatting tools
208
- prettier . --write`,
209
- picocolors.bold(picocolors.green("Changes Applied"))
111
+ return {
112
+ async install(name) {
113
+ const downloaded = await this.download(name);
114
+ const dependencies = {};
115
+ const devDependencies = {};
116
+ for (const item of downloaded) {
117
+ Object.assign(dependencies, item.dependencies);
118
+ Object.assign(devDependencies, item.devDependencies);
119
+ }
120
+ const fileList = buildFileList(downloaded);
121
+ for (const file of fileList) {
122
+ const filePath = file.target ?? file.path;
123
+ if (installedFiles.has(filePath)) continue;
124
+ const outPath = this.resolveOutputPath(file);
125
+ const output = typescriptExtensions.includes(path2.extname(filePath)) ? this.transform(outPath, file, fileList) : file.content;
126
+ const status = await fs.readFile(outPath).then((res) => {
127
+ if (res.toString() === output) return "ignore";
128
+ return "need-update";
129
+ }).catch(() => "write");
130
+ installedFiles.add(filePath);
131
+ if (status === "ignore") continue;
132
+ if (status === "need-update") {
133
+ const override = await confirm({
134
+ message: `Do you want to override ${outPath}?`,
135
+ initialValue: false
136
+ });
137
+ if (isCancel(override)) {
138
+ outro("Ended");
139
+ process.exit(0);
140
+ }
141
+ if (!override) continue;
142
+ }
143
+ await fs.mkdir(path2.dirname(outPath), { recursive: true });
144
+ await fs.writeFile(outPath, output);
145
+ log.step(`downloaded ${outPath}`);
146
+ }
147
+ return {
148
+ dependencies,
149
+ devDependencies
150
+ };
151
+ },
152
+ /**
153
+ * return a list of components, merged with child components.
154
+ */
155
+ async download(name) {
156
+ const cached = downloadedComps.get(name);
157
+ if (cached) return cached;
158
+ const comp = validateRegistryComponent(
159
+ await resolver(`${name}.json`).catch((e) => {
160
+ log.error(`component ${name} not found:`);
161
+ log.error(String(e));
162
+ process.exit(1);
163
+ })
210
164
  );
211
- } else {
212
- await plugin.transformRejected?.(ctx);
213
- }
214
- }
215
- const instructions = await plugin.instructions(ctx);
216
- for (const text of instructions) {
217
- if (text.type === "text") {
218
- log.message(text.text, {
219
- symbol: "\u25CB"
165
+ const result = [comp];
166
+ downloadedComps.set(name, result);
167
+ for (const sub of comp.subComponents) {
168
+ result.push(...await this.download(sub));
169
+ }
170
+ return result;
171
+ },
172
+ transform(filePath, file, fileList) {
173
+ const sourceFile = project.createSourceFile(filePath, file.content, {
174
+ overwrite: true
220
175
  });
221
- }
222
- if (text.type === "code") {
223
- note(text.code, text.title);
224
- }
225
- if (text.type === "title") {
226
- log.step(text.text);
227
- }
228
- }
229
- }
230
- function getOutputPath(ref, config) {
231
- if (path3.isAbsolute(ref)) throw new Error(`path cannot be absolute: ${ref}`);
232
- if (ref === "utils/cn" || ref === "utils/cn.ts") {
233
- return config.aliases?.cn ?? defaultConfig.aliases.cn;
234
- }
235
- if (ref.startsWith("components")) {
236
- return path3.join(
237
- config.aliases?.componentsDir ?? defaultConfig.aliases.componentsDir,
238
- path3.relative("components", ref)
239
- );
240
- }
241
- if (ref.startsWith("lib") || ref.startsWith("utils")) {
242
- return path3.join(
243
- config.aliases?.libDir ?? defaultConfig.aliases.libDir,
244
- path3.relative("lib", ref)
245
- );
246
- }
247
- return ref;
248
- }
249
-
250
- // src/utils/add/install-component.ts
251
- import path4 from "path";
252
- import fs2 from "fs/promises";
253
- import { confirm as confirm2, isCancel as isCancel2, log as log2, outro } from "@clack/prompts";
254
- var downloadedFiles = /* @__PURE__ */ new Set();
255
- async function installComponent(name, resolver, config = {}) {
256
- const project = createEmptyProject();
257
- return downloadComponent(name, {
258
- project,
259
- config,
260
- resolver
261
- });
262
- }
263
- var downloadedComps = /* @__PURE__ */ new Map();
264
- async function downloadComponent(name, ctx) {
265
- const cached = downloadedComps.get(name);
266
- if (cached) return cached;
267
- const comp = await ctx.resolver(`${name}.json`);
268
- if (!comp) return;
269
- downloadedComps.set(name, comp);
270
- for (const file of comp.files) {
271
- if (downloadedFiles.has(file.path)) continue;
272
- const outPath = resolveOutputPath(file.path, ctx.config);
273
- const output = typescriptExtensions.includes(path4.extname(file.path)) ? transformTypeScript(outPath, file, ctx) : file.content;
274
- let canWrite = true;
275
- const requireOverride = await fs2.readFile(outPath).then((res) => res.toString() !== output).catch(() => false);
276
- if (requireOverride) {
277
- const value = await confirm2({
278
- message: `Do you want to override ${outPath}?`
176
+ transformReferences(sourceFile, (specifier) => {
177
+ const prefix = "@/";
178
+ if (specifier.startsWith(prefix)) {
179
+ const lookup = specifier.substring(prefix.length);
180
+ const target = fileList.find((item) => {
181
+ const filePath2 = item.target ?? item.path;
182
+ return filePath2 === lookup;
183
+ });
184
+ if (!target) {
185
+ console.warn(`cannot find the referenced file of ${specifier}`);
186
+ return specifier;
187
+ }
188
+ return toImportSpecifier(filePath, this.resolveOutputPath(target));
189
+ }
279
190
  });
280
- if (isCancel2(value)) {
281
- outro("Ended");
282
- process.exit(0);
191
+ return sourceFile.getFullText();
192
+ },
193
+ resolveOutputPath(ref) {
194
+ if (ref.target) {
195
+ return path2.join(config.baseDir, ref.target);
283
196
  }
284
- canWrite = value;
197
+ const base = path2.basename(ref.path);
198
+ const dir = {
199
+ components: config.aliases.componentsDir,
200
+ block: config.aliases.blockDir,
201
+ ui: config.aliases.uiDir,
202
+ css: config.aliases.cssDir,
203
+ lib: config.aliases.libDir,
204
+ route: "./"
205
+ }[ref.type];
206
+ return path2.join(config.baseDir, dir, base);
285
207
  }
286
- if (canWrite) {
287
- await fs2.mkdir(path4.dirname(outPath), { recursive: true });
288
- await fs2.writeFile(outPath, output);
289
- log2.step(`downloaded ${outPath}`);
290
- }
291
- downloadedFiles.add(file.path);
292
- }
293
- for (const sub of comp.subComponents) {
294
- const downloaded = await downloadComponent(sub, ctx);
295
- if (!downloaded) continue;
296
- Object.assign(comp.dependencies, downloaded.dependencies);
297
- Object.assign(comp.devDependencies, downloaded.devDependencies);
298
- }
299
- return comp;
300
- }
301
- function resolveOutputPath(ref, config) {
302
- const sep = ref.indexOf(":");
303
- if (sep === -1) return ref;
304
- const namespace = ref.slice(0, sep), file = ref.slice(sep + 1);
305
- if (namespace === "components") {
306
- return path4.join(
307
- config.aliases?.componentsDir ?? defaultConfig.aliases.componentsDir,
308
- file
309
- );
310
- }
311
- return path4.join(
312
- config.aliases?.libDir ?? defaultConfig.aliases.libDir,
313
- file
314
- );
315
- }
316
- function transformTypeScript(filePath, file, ctx) {
317
- const sourceFile = ctx.project.createSourceFile(filePath, file.content, {
318
- overwrite: true
319
- });
320
- transformReferences(sourceFile, (specifier) => {
321
- if (specifier in file.imports) {
322
- const outputPath = resolveOutputPath(file.imports[specifier], ctx.config);
323
- return toReferencePath(filePath, outputPath);
324
- }
325
- });
326
- return sourceFile.getFullText();
208
+ };
327
209
  }
328
210
  function remoteResolver(url) {
329
211
  return async (file) => {
330
212
  const res = await fetch(`${url}/${file}`);
331
- if (!res.ok) return;
332
- return res.json();
213
+ if (!res.ok) {
214
+ throw new Error(`failed to fetch ${url}/${file}: ${res.statusText}`);
215
+ }
216
+ return await res.json();
333
217
  };
334
218
  }
335
219
  function localResolver(dir) {
336
220
  return async (file) => {
337
- return await fs2.readFile(path4.join(dir, file)).then((res) => JSON.parse(res.toString())).catch(() => void 0);
221
+ const filePath = path2.join(dir, file);
222
+ return await fs.readFile(filePath).then((res) => JSON.parse(res.toString())).catch((e) => {
223
+ throw new Error(`failed to resolve local file "${filePath}"`, {
224
+ cause: e
225
+ });
226
+ });
338
227
  };
339
228
  }
340
229
 
341
- // src/plugins/i18n.ts
342
- import path7 from "path";
343
- import { log as log3 } from "@clack/prompts";
344
-
345
- // src/generated.js
346
- var generated = { "lib/i18n": "import type { I18nConfig } from 'fumadocs-core/i18n';\n\nexport const i18n: I18nConfig = {\n defaultLanguage: 'en',\n languages: ['en', 'cn'],\n};\n", "middleware": "import { createI18nMiddleware } from 'fumadocs-core/i18n';\nimport { i18n } from '@/lib/i18n';\n\nexport default createI18nMiddleware(i18n);\n\nexport const config = {\n // Matcher ignoring `/_next/` and `/api/`\n matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],\n};\n", "scripts/generate-docs": "import * as OpenAPI from 'fumadocs-openapi';\nimport { rimraf } from 'rimraf';\n\nconst out = './content/docs/(api)';\n\nasync function generate() {\n // clean generated files\n await rimraf(out, {\n filter(v) {\n return !v.endsWith('index.mdx') && !v.endsWith('meta.json');\n },\n });\n\n await OpenAPI.generateFiles({\n // input files\n input: ['./openapi.json'],\n output: out,\n });\n}\n\nvoid generate();\n" };
347
-
348
- // src/utils/i18n/transform-layout-config.ts
230
+ // src/config.ts
349
231
  import fs3 from "fs/promises";
350
- import { SyntaxKind } from "ts-morph";
351
- async function transformLayoutConfig(project, filePath) {
352
- let content;
353
- try {
354
- content = await fs3.readFile(filePath).then((res) => res.toString());
355
- } catch {
356
- return;
357
- }
358
- const sourceFile = project.createSourceFile(filePath, content, {
359
- overwrite: true
360
- });
361
- const configExport = sourceFile.getDescendantsOfKind(SyntaxKind.VariableDeclaration).find((node) => node.getName() === "baseOptions");
362
- if (!configExport) return;
363
- const init2 = configExport.getInitializerIfKind(
364
- SyntaxKind.ObjectLiteralExpression
365
- );
366
- if (!init2) return;
367
- if (init2.getProperty("i18n")) return;
368
- init2.addPropertyAssignment({
369
- name: "i18n",
370
- initializer: "true"
371
- });
372
- return sourceFile.save();
373
- }
374
-
375
- // src/utils/move-files.ts
376
- import fs4 from "fs/promises";
377
- import path5 from "path";
378
- var transformExtensions = [".js", ".ts", ".tsx", ".jsx"];
379
- async function moveFiles(from, to, filter, project, src2, originalDir = from) {
380
- function isIncluded(file) {
381
- if (!transformExtensions.includes(path5.extname(file))) return false;
382
- return filter(path5.resolve(file));
383
- }
384
- const stats = await fs4.lstat(from).catch(() => void 0);
385
- if (!stats) return;
386
- if (stats.isDirectory()) {
387
- const items = await fs4.readdir(from);
388
- await Promise.all(
389
- items.map(async (item) => {
390
- await moveFiles(
391
- path5.join(from, item),
392
- path5.join(to, item),
393
- filter,
394
- project,
395
- src2,
396
- originalDir
397
- );
398
- })
399
- );
400
- await fs4.rmdir(from).catch(() => {
401
- });
402
- }
403
- if (!stats.isFile() || !isIncluded(from)) return;
404
- const content = await fs4.readFile(from);
405
- const sourceFile = project.createSourceFile(from, content.toString(), {
406
- overwrite: true
407
- });
408
- transformReferences(sourceFile, (specifier) => {
409
- const resolved = resolveReference(specifier, {
410
- alias: {
411
- type: "append",
412
- dir: src2 ? "src" : ""
413
- },
414
- relativeTo: path5.dirname(from)
415
- });
416
- if (resolved.type !== "file") return;
417
- if (
418
- // ignore if the file is also moved
419
- isRelative(originalDir, from) && isIncluded(resolved.path)
420
- )
421
- return;
422
- return toReferencePath(to, resolved.path);
423
- });
424
- await sourceFile.save();
425
- await fs4.mkdir(path5.dirname(to), { recursive: true });
426
- await fs4.rename(from, to);
427
- }
428
232
 
429
- // src/utils/i18n/transform-source-i18n.ts
430
- import * as fs5 from "fs/promises";
431
- import path6 from "path";
432
- import { StructureKind, SyntaxKind as SyntaxKind2 } from "ts-morph";
433
- async function transformSourceI18n(project, filePath, config) {
434
- let content;
233
+ // src/utils/fs.ts
234
+ import fs2 from "fs/promises";
235
+ import path3 from "path";
236
+ async function exists(pathLike) {
435
237
  try {
436
- content = await fs5.readFile(filePath).then((res) => res.toString());
238
+ await fs2.access(pathLike);
239
+ return true;
437
240
  } catch {
438
- return;
241
+ return false;
439
242
  }
440
- const sourceFile = project.createSourceFile(filePath, content, {
441
- overwrite: true
442
- });
443
- sourceFile.addImportDeclaration({
444
- kind: StructureKind.ImportDeclaration,
445
- moduleSpecifier: toReferencePath(
446
- filePath,
447
- path6.join(config.aliases?.libDir ?? defaultConfig.aliases.libDir, "i18n")
448
- ),
449
- namedImports: ["i18n"]
450
- });
451
- const sourceExport = sourceFile.getDescendantsOfKind(SyntaxKind2.VariableDeclaration).find((node) => node.getName() === "source");
452
- if (!sourceExport) return;
453
- const loaderCall = sourceExport.getFirstDescendantByKind(
454
- SyntaxKind2.ObjectLiteralExpression
455
- );
456
- if (!loaderCall || loaderCall.getProperty("i18n")) return;
457
- loaderCall.addPropertyAssignment({
458
- name: "i18n",
459
- initializer: "i18n"
460
- });
461
- return sourceFile.save();
462
243
  }
463
244
 
464
- // src/utils/i18n/transform-root-layout.ts
465
- import fs6 from "fs/promises";
466
- import { StructureKind as StructureKind2, ts } from "ts-morph";
467
- var ScriptKind = ts.ScriptKind;
468
- var SyntaxKind3 = ts.SyntaxKind;
469
- async function transformRootLayout(project, filePath) {
470
- let content;
471
- try {
472
- content = await fs6.readFile(filePath).then((res) => res.toString());
473
- } catch {
474
- return;
475
- }
476
- const sourceFile = project.createSourceFile(filePath, content, {
477
- overwrite: true,
478
- scriptKind: ScriptKind.TSX
479
- });
480
- runTransform(sourceFile);
481
- return sourceFile.save();
245
+ // src/utils/is-src.ts
246
+ async function isSrc() {
247
+ return exists("./src");
482
248
  }
483
- function runTransform(sourceFile) {
484
- const rootProvider = sourceFile.getDescendantsOfKind(SyntaxKind3.JsxElement).find(
485
- (node) => node.getOpeningElement().getTagNameNode().getFullText() === "RootProvider"
486
- );
487
- if (!rootProvider) return;
488
- const parent = rootProvider.getParentIfKind(SyntaxKind3.JsxElement);
489
- if (parent) {
490
- const inner = parent.getJsxChildren().map((v) => v.getFullText()).filter((v) => v.length > 0).join("\n");
491
- parent.setBodyText(
492
- `<I18nProvider locale={(await params).lang} locales={[
493
- { locale: 'en', name: 'English' }
494
- ]}>
495
- ${inner.trim()}
496
- </I18nProvider>`
497
- );
498
- sourceFile.addImportDeclaration({
499
- kind: StructureKind2.ImportDeclaration,
500
- moduleSpecifier: "fumadocs-ui/i18n",
501
- namedImports: ["I18nProvider"]
502
- });
503
- }
504
- const func = sourceFile.getDescendantsOfKind(SyntaxKind3.FunctionDeclaration).find((v) => v.isDefaultExport());
505
- func?.toggleModifier("async", true);
506
- const param = func?.getParameters().at(0);
507
- param?.setType(`{ params: Promise<{ lang: string }>, children: ReactNode }`);
508
- param?.set({
509
- name: `{ params, children }`
249
+
250
+ // src/config.ts
251
+ import { z as z3 } from "zod";
252
+ function createConfigSchema(isSrc2) {
253
+ const defaultAliases = {
254
+ uiDir: "./components/ui",
255
+ componentsDir: "./components",
256
+ blockDir: "./components",
257
+ cssDir: "./styles",
258
+ libDir: "./lib"
259
+ };
260
+ return z3.object({
261
+ aliases: z3.object({
262
+ uiDir: z3.string().default(defaultAliases.uiDir),
263
+ componentsDir: z3.string().default(defaultAliases.uiDir),
264
+ blockDir: z3.string().default(defaultAliases.blockDir),
265
+ cssDir: z3.string().default(defaultAliases.componentsDir),
266
+ libDir: z3.string().default(defaultAliases.libDir)
267
+ }).default(defaultAliases),
268
+ baseDir: z3.string().default(isSrc2 ? "src" : ""),
269
+ commands: z3.object({
270
+ /**
271
+ * command to format output code automatically
272
+ */
273
+ format: z3.string().optional()
274
+ }).default({})
510
275
  });
511
276
  }
512
-
513
- // src/plugins/i18n.ts
514
- import picocolors2 from "picocolors";
515
- var i18nPlugin = {
516
- files: ({ src: src2 }) => ({
517
- "lib/i18n.ts": generated["lib/i18n"],
518
- [src2 ? "src/middleware.ts" : "middleware.ts"]: generated.middleware
519
- }),
520
- dependencies: [],
521
- instructions: () => [
522
- {
523
- type: "title",
524
- text: `1. Update the params of ${picocolors2.bold("page.tsx")} and ${picocolors2.bold("layout.tsx")}, and make them async if necessary.`
525
- },
526
- {
527
- type: "code",
528
- title: "layout.tsx",
529
- code: `
530
- export default async function Layout({
531
- params,
532
- }: {
533
- ${picocolors2.underline(picocolors2.bold("params: Promise<{ lang: string }>"))}
534
- })
535
- `.trim()
536
- },
537
- {
538
- type: "code",
539
- title: "page.tsx",
540
- code: `
541
- export default async function Page({
542
- params,
543
- }: {
544
- ${picocolors2.underline(picocolors2.bold("params: Promise<{ lang: string; slug?: string[] }>"))}
545
- })
546
- `.trim()
547
- },
548
- {
549
- type: "title",
550
- text: "2. Update references to your `source` object"
551
- },
552
- {
553
- type: "text",
554
- text: "You can follow the instructions in https://fumadocs.vercel.app/docs/ui/internationalization#source section."
555
- }
556
- ],
557
- async transform(ctx) {
558
- const project = createEmptyProject();
559
- await Promise.all([
560
- transformLayoutConfig(
561
- project,
562
- resolveAppPath("./app/layout.config.tsx", ctx.src)
563
- ),
564
- transformRootLayout(project, resolveAppPath("./app/layout.tsx", ctx.src)),
565
- transformSourceI18n(
566
- project,
567
- path7.join(
568
- ctx.aliases?.libDir ?? defaultConfig.aliases.libDir,
569
- "source.ts"
570
- ),
571
- ctx
572
- )
573
- ]);
574
- await moveFiles(
575
- resolveAppPath("./app", ctx.src),
576
- resolveAppPath("./app/[lang]", ctx.src),
577
- (v) => {
578
- const parsed = path7.parse(v);
579
- if (parsed.ext === ".css") return false;
580
- return parsed.name !== "layout.config" && !isRelative("./app/api", v);
581
- },
582
- project,
583
- ctx.src
584
- );
585
- log3.success(
586
- "Moved the ./app files to a [lang] route group, and modified your root layout to add `<I18nProvider />`."
587
- );
588
- },
589
- transformRejected() {
590
- log3.info(
591
- `Please create a [lang] route group and move all special files into the folder.
592
- See https://nextjs.org/docs/app/building-your-application/routing/internationalization for more info.`
593
- );
594
- }
595
- };
596
-
597
- // src/plugins/openapi.ts
598
- import fs8 from "fs/promises";
599
- import path8 from "path";
600
- import { StructureKind as StructureKind3 } from "ts-morph";
601
-
602
- // src/utils/transform-tailwind.ts
603
- import fs7 from "fs/promises";
604
- import { SyntaxKind as SyntaxKind4 } from "ts-morph";
605
- import { log as log4 } from "@clack/prompts";
606
- var tailwindConfigPaths = [
607
- "tailwind.config.js",
608
- "tailwind.config.mjs",
609
- "tailwind.config.ts",
610
- "tailwind.config.mts"
611
- ];
612
- async function findTailwindConfig() {
613
- for (const configPath of tailwindConfigPaths) {
614
- if (await exists(configPath)) {
615
- return configPath;
616
- }
617
- }
277
+ async function createOrLoadConfig(file = "./cli.json") {
278
+ const inited = await initConfig(file);
279
+ if (inited) return inited;
280
+ const content = (await fs3.readFile(file)).toString();
281
+ const src = await isSrc();
282
+ const configSchema = createConfigSchema(src);
283
+ return configSchema.parse(JSON.parse(content));
618
284
  }
619
- async function transformTailwind(project, options) {
620
- const file = await findTailwindConfig();
621
- if (!file) {
622
- log4.error(
623
- "Cannot find Tailwind CSS configuration file, Tailwind CSS is required for this."
624
- );
285
+ async function initConfig(file = "./cli.json") {
286
+ if (await fs3.stat(file).then(() => true).catch(() => false)) {
625
287
  return;
626
288
  }
627
- const configFile = project.createSourceFile(
628
- file,
629
- await fs7.readFile(file).then((res) => res.toString()),
630
- { overwrite: true }
631
- );
632
- const exports = configFile.getExportAssignments();
633
- if (exports.length === 0) return;
634
- const contentNode = exports[0].getDescendantsOfKind(SyntaxKind4.PropertyAssignment).find((a) => a.getName() === "content");
635
- if (!contentNode) throw new Error("No `content` detected");
636
- const arr = contentNode.getFirstDescendantByKind(
637
- SyntaxKind4.ArrayLiteralExpression
638
- );
639
- arr?.addElements(options.addContents.map((v) => JSON.stringify(v)));
640
- await configFile.save();
641
- }
642
-
643
- // src/plugins/openapi.ts
644
- var openapiPlugin = {
645
- files: () => ({
646
- "scripts/generate-docs.mjs": generated["scripts/generate-docs"]
647
- }),
648
- dependencies: ["fumadocs-openapi", "rimraf", "shiki"],
649
- async transform(ctx) {
650
- const project = createEmptyProject();
651
- await Promise.all([
652
- transformSource(project, ctx),
653
- transformTailwind(project, {
654
- addContents: [`./node_modules/fumadocs-openapi/dist/**/*.js`]
655
- }),
656
- addScript()
657
- ]);
658
- },
659
- instructions: async () => [
660
- {
661
- type: "text",
662
- text: `I've made some changes to your Tailwind CSS config.
663
- You can add the APIPage component to your page.tsx:`
664
- },
665
- {
666
- type: "code",
667
- title: "page.tsx",
668
- code: `import defaultMdxComponents from 'fumadocs-ui/mdx';
669
- import { openapi } from '@/lib/source';
670
-
671
- <MDX
672
- components={{
673
- ...defaultMdxComponents,
674
- APIPage: openapi.APIPage,
675
- }}
676
- />;`
677
- },
678
- {
679
- type: "text",
680
- text: `Paste your OpenAPI schema to ./openapi.json, and use this script to generate docs:`
681
- },
682
- {
683
- type: "code",
684
- title: "Terminal",
685
- code: `${await getPackageManager()} run build:docs`
686
- }
687
- ]
688
- };
689
- async function addScript() {
690
- const content = await fs8.readFile("package.json");
691
- const parsed = JSON.parse(content.toString());
692
- if (typeof parsed.scripts !== "object") return;
693
- parsed.scripts ??= {};
694
- Object.assign(parsed.scripts ?? {}, {
695
- "build:docs": "node ./scripts/generate-docs.mjs"
696
- });
697
- await fs8.writeFile("package.json", JSON.stringify(parsed, null, 2));
698
- }
699
- async function transformSource(project, config) {
700
- const source = path8.join(
701
- config.aliases?.libDir ?? defaultConfig.aliases.libDir,
702
- "source.ts"
703
- );
704
- const content = await fs8.readFile(source).catch(() => "");
705
- const file = project.createSourceFile(source, content.toString(), {
706
- overwrite: true
707
- });
708
- file.addImportDeclaration({
709
- kind: StructureKind3.ImportDeclaration,
710
- namedImports: ["createOpenAPI"],
711
- moduleSpecifier: "fumadocs-openapi/server"
712
- });
713
- file.addStatements(`export const openapi = createOpenAPI();`);
714
- await file.save();
289
+ const src = await isSrc();
290
+ const defaultConfig = createConfigSchema(src).parse({});
291
+ await fs3.writeFile(file, JSON.stringify(defaultConfig, null, 2));
292
+ return defaultConfig;
715
293
  }
716
294
 
717
- // src/plugins/index.ts
718
- var plugins = {
719
- i18n: i18nPlugin,
720
- openapi: openapiPlugin
721
- };
722
-
723
295
  // src/commands/file-tree.ts
724
296
  var scanned = ["file", "directory", "link"];
725
297
  function treeToMdx(input, noRoot = false) {
@@ -756,9 +328,9 @@ export default (${treeToMdx(input, noRoot)})`;
756
328
  }
757
329
 
758
330
  // src/utils/file-tree/run-tree.ts
759
- import { x as x2 } from "tinyexec";
331
+ import { x } from "tinyexec";
760
332
  async function runTree(args) {
761
- const out = await x2("tree", [args, "--gitignore", "--prune", "-J"]);
333
+ const out = await x("tree", [args, "--gitignore", "--prune", "-J"]);
762
334
  try {
763
335
  return JSON.parse(out.stdout);
764
336
  } catch (e) {
@@ -771,7 +343,7 @@ async function runTree(args) {
771
343
  // package.json
772
344
  var package_default = {
773
345
  name: "@fumadocs/cli",
774
- version: "0.2.0",
346
+ version: "1.0.0",
775
347
  description: "The CLI tool for Fumadocs",
776
348
  keywords: [
777
349
  "NextJs",
@@ -805,19 +377,20 @@ var package_default = {
805
377
  "types:check": "tsc --noEmit"
806
378
  },
807
379
  dependencies: {
808
- "@clack/prompts": "^0.10.1",
380
+ "@clack/prompts": "^0.11.0",
809
381
  commander: "^14.0.0",
810
382
  "package-manager-detector": "^1.3.0",
811
383
  picocolors: "^1.1.1",
812
384
  tinyexec: "^1.0.1",
813
- "ts-morph": "^26.0.0"
385
+ "ts-morph": "^26.0.0",
386
+ zod: "^4.0.17"
814
387
  },
815
388
  devDependencies: {
816
- "@types/node": "22.15.19",
817
- "@types/react": "^19.1.4",
389
+ shadcn: "2.9.3-canary.0",
390
+ "@types/node": "24.2.1",
818
391
  "eslint-config-custom": "workspace:*",
819
392
  tsconfig: "workspace:*",
820
- tsx: "^4.19.4"
393
+ tsx: "^4.20.3"
821
394
  },
822
395
  publishConfig: {
823
396
  access: "public"
@@ -826,37 +399,44 @@ var package_default = {
826
399
 
827
400
  // src/commands/customise.ts
828
401
  import {
829
- cancel as cancel2,
830
- confirm as confirm4,
402
+ cancel,
403
+ confirm as confirm3,
831
404
  group,
832
- intro as intro3,
833
- log as log6,
405
+ intro as intro2,
406
+ log as log3,
834
407
  outro as outro3,
835
408
  select
836
409
  } from "@clack/prompts";
837
- import picocolors4 from "picocolors";
410
+ import picocolors2 from "picocolors";
838
411
 
839
412
  // src/commands/add.ts
840
413
  import {
841
- intro as intro2,
842
- isCancel as isCancel4,
843
- log as log5,
414
+ intro,
415
+ isCancel as isCancel3,
416
+ log as log2,
844
417
  multiselect,
845
418
  outro as outro2,
846
- spinner as spinner3
419
+ spinner as spinner2
847
420
  } from "@clack/prompts";
848
- import picocolors3 from "picocolors";
421
+ import picocolors from "picocolors";
422
+
423
+ // src/utils/get-package-manager.ts
424
+ import { detect } from "package-manager-detector";
425
+ async function getPackageManager() {
426
+ const result = await detect();
427
+ return result?.name ?? "npm";
428
+ }
849
429
 
850
430
  // src/utils/add/install-deps.ts
851
- import { confirm as confirm3, isCancel as isCancel3, spinner as spinner2 } from "@clack/prompts";
852
- import { x as x3 } from "tinyexec";
431
+ import { confirm as confirm2, isCancel as isCancel2, spinner } from "@clack/prompts";
432
+ import { x as x2 } from "tinyexec";
853
433
 
854
434
  // src/utils/add/get-deps.ts
855
- import fs9 from "fs/promises";
435
+ import fs4 from "fs/promises";
856
436
  async function getDeps() {
857
437
  const dependencies = /* @__PURE__ */ new Map();
858
438
  if (!await exists("package.json")) return dependencies;
859
- const content = await fs9.readFile("package.json");
439
+ const content = await fs4.readFile("package.json");
860
440
  const parsed = JSON.parse(content.toString());
861
441
  if ("dependencies" in parsed && typeof parsed.dependencies === "object") {
862
442
  const records = parsed.dependencies;
@@ -874,30 +454,27 @@ async function getDeps() {
874
454
  }
875
455
 
876
456
  // src/utils/add/install-deps.ts
877
- async function installDeps(results) {
457
+ async function installDeps(deps, devDeps) {
878
458
  const installed = await getDeps();
879
- const deps = {};
880
- const devDeps = {};
881
- for (const result of results) {
882
- Object.assign(deps, result.dependencies);
883
- Object.assign(devDeps, result.devDependencies);
459
+ function toList(deps2) {
460
+ return Object.entries(deps2).filter(([k]) => !installed.has(k)).map(([k, v]) => v === null || v.length === 0 ? k : `${k}@${v}`);
884
461
  }
885
- const items = Object.entries(deps).filter(([k]) => !installed.has(k)).map(([k, v]) => v.length === 0 ? k : `${k}@${v}`);
886
- const devItems = Object.entries(devDeps).filter(([k]) => !installed.has(k)).map(([k, v]) => v.length === 0 ? k : `${k}@${v}`);
462
+ const items = toList(deps);
463
+ const devItems = toList(devDeps);
887
464
  if (items.length > 0 || devItems.length > 0) {
888
465
  const manager = await getPackageManager();
889
- const value = await confirm3({
466
+ const value = await confirm2({
890
467
  message: `Do you want to install with ${manager}?
891
468
  ${[...items, ...devItems].map((v) => `- ${v}`).join("\n")}`
892
469
  });
893
- if (isCancel3(value)) {
470
+ if (isCancel2(value)) {
894
471
  return;
895
472
  }
896
473
  if (value) {
897
- const spin = spinner2();
474
+ const spin = spinner();
898
475
  spin.start("Installing dependencies...");
899
- if (items.length > 0) await x3(manager, ["install", ...items]);
900
- if (devItems.length > 0) await x3(manager, ["install", ...devItems, "-D"]);
476
+ if (items.length > 0) await x2(manager, ["install", ...items]);
477
+ if (devItems.length > 0) await x2(manager, ["install", ...devItems, "-D"]);
901
478
  spin.stop("Dependencies installed.");
902
479
  }
903
480
  }
@@ -905,56 +482,68 @@ ${[...items, ...devItems].map((v) => `- ${v}`).join("\n")}`
905
482
 
906
483
  // src/commands/add.ts
907
484
  async function add(input, resolver, config) {
485
+ const installer = createComponentInstaller({
486
+ resolver,
487
+ config
488
+ });
908
489
  let target = input;
909
490
  if (input.length === 0) {
910
- const spin = spinner3();
491
+ const spin = spinner2();
911
492
  spin.start("fetching registry");
912
- const registry = await resolver("_registry.json");
913
- spin.stop(picocolors3.bold(picocolors3.greenBright("registry fetched")));
914
- if (!registry) {
915
- log5.error(`Failed to fetch '_registry.json' file from registry`);
916
- throw new Error(`Failed to fetch registry`);
917
- }
493
+ const indexes = validateRegistryIndex(
494
+ await resolver("_registry.json").catch((e) => {
495
+ log2.error(String(e));
496
+ process.exit(1);
497
+ })
498
+ );
499
+ spin.stop(picocolors.bold(picocolors.greenBright("registry fetched")));
918
500
  const value = await multiselect({
919
501
  message: "Select components to install",
920
- options: registry.map((item) => ({
921
- label: item.name,
502
+ options: indexes.map((item) => ({
503
+ label: item.title,
922
504
  value: item.name,
923
505
  hint: item.description
924
506
  }))
925
507
  });
926
- if (isCancel4(value)) {
508
+ if (isCancel3(value)) {
927
509
  outro2("Ended");
928
510
  return;
929
511
  }
930
512
  target = value;
931
513
  }
932
- await install(target, resolver, config);
514
+ await install(target, installer);
933
515
  }
934
- async function install(target, resolver, config) {
935
- const outputs = [];
516
+ async function install(target, installer) {
517
+ const dependencies = {};
518
+ const devDependencies = {};
936
519
  for (const name of target) {
937
- intro2(
938
- picocolors3.bold(
939
- picocolors3.inverse(picocolors3.cyanBright(`Add Component: ${name}`))
520
+ intro(
521
+ picocolors.bold(
522
+ picocolors.inverse(picocolors.cyanBright(`Add Component: ${name}`))
940
523
  )
941
524
  );
942
- const output = await installComponent(name, resolver, config);
943
- if (!output) {
944
- log5.error(`Failed to install ${name}: not found`);
945
- continue;
525
+ try {
526
+ const output = await installer.install(name);
527
+ Object.assign(dependencies, output.dependencies);
528
+ Object.assign(devDependencies, output.devDependencies);
529
+ outro2(picocolors.bold(picocolors.greenBright(`${name} installed`)));
530
+ } catch (e) {
531
+ log2.error(String(e));
532
+ throw e;
946
533
  }
947
- outro2(picocolors3.bold(picocolors3.greenBright(`${name} installed`)));
948
- outputs.push(output);
949
534
  }
950
- intro2(picocolors3.bold("New Dependencies"));
951
- await installDeps(outputs);
952
- outro2(picocolors3.bold(picocolors3.greenBright("Successful")));
535
+ intro(picocolors.bold("New Dependencies"));
536
+ await installDeps(dependencies, devDependencies);
537
+ outro2(picocolors.bold(picocolors.greenBright("Successful")));
953
538
  }
954
539
 
955
540
  // src/commands/customise.ts
956
541
  async function customise(resolver, config) {
957
- intro3(picocolors4.bgBlack(picocolors4.whiteBright("Customise Fumadocs UI")));
542
+ intro2(picocolors2.bgBlack(picocolors2.whiteBright("Customise Fumadocs UI")));
543
+ const installer = createComponentInstaller({
544
+ resolver,
545
+ config
546
+ });
958
547
  const result = await group(
959
548
  {
960
549
  target: () => select({
@@ -998,14 +587,14 @@ async function customise(resolver, config) {
998
587
  page: async (v) => {
999
588
  if (v.results.target !== "docs" || v.results.mode === "minimal")
1000
589
  return false;
1001
- return confirm4({
590
+ return confirm3({
1002
591
  message: "Do you want to customise the page component too?"
1003
592
  });
1004
593
  }
1005
594
  },
1006
595
  {
1007
596
  onCancel: () => {
1008
- cancel2("Installation Stopped.");
597
+ cancel("Installation Stopped.");
1009
598
  process.exit(0);
1010
599
  }
1011
600
  }
@@ -1025,72 +614,47 @@ async function customise(resolver, config) {
1025
614
  result.mode === "full-default" ? "layouts/docs" : "layouts/notebook"
1026
615
  );
1027
616
  }
1028
- await install(targets, resolver, config);
1029
- intro3(picocolors4.bold("What is Next?"));
1030
- log6.info(
1031
- [
1032
- "You can check the installed components in `components/layouts`.",
1033
- picocolors4.dim("---"),
1034
- "Open your `layout.tsx` files, replace the imports of components:",
1035
- picocolors4.greenBright(
1036
- "`fumadocs-ui/layouts/docs` -> `@/components/layouts/docs`"
1037
- ),
1038
- pageAdded ? picocolors4.greenBright(
1039
- "`fumadocs-ui/page` -> `@/components/layouts/page`"
1040
- ) : ""
1041
- ].join("\n")
1042
- );
617
+ await install(targets, installer);
618
+ const maps = [
619
+ ["fumadocs-ui/layouts/docs", "@/components/layout/docs"]
620
+ ];
621
+ if (pageAdded) {
622
+ maps.push(["fumadocs-ui/page", "@/components/layout/page"]);
623
+ }
624
+ printNext(...maps);
1043
625
  }
1044
626
  if (result.target === "home") {
1045
- await install(["layouts/home"], resolver, config);
1046
- intro3(picocolors4.bold("What is Next?"));
1047
- log6.info(
1048
- [
1049
- "You can check the installed components in `components/layouts`.",
1050
- picocolors4.dim("---"),
1051
- "Open your `layout.tsx` files, replace the imports of components:",
1052
- picocolors4.greenBright(
1053
- "`fumadocs-ui/layouts/home` -> `@/components/layouts/home`"
1054
- )
1055
- ].join("\n")
1056
- );
1057
- }
1058
- outro3(picocolors4.bold("Have fun!"));
627
+ await install(["layouts/home"], installer);
628
+ printNext(["fumadocs-ui/layouts/home", `@/components/layout/home`]);
629
+ }
630
+ outro3(picocolors2.bold("Have fun!"));
631
+ }
632
+ function printNext(...maps) {
633
+ intro2(picocolors2.bold("What is Next?"));
634
+ log3.info(
635
+ [
636
+ "You can check the installed components in `components`.",
637
+ picocolors2.dim("---"),
638
+ "Open your `layout.tsx` files, replace the imports of components:",
639
+ ...maps.map(
640
+ ([from, to]) => picocolors2.greenBright(`"${from}" -> "${to}"`)
641
+ )
642
+ ].join("\n")
643
+ );
1059
644
  }
1060
645
 
1061
646
  // src/index.ts
1062
647
  var program = new Command().option("--config <string>");
1063
- program.name("fumadocs").description("CLI to setup Fumadocs, init a config ").version(package_default.version).action(async () => {
648
+ program.name("fumadocs").description("CLI to setup Fumadocs, init a config").version(package_default.version).action(async () => {
1064
649
  if (await initConfig()) {
1065
- console.log(picocolors5.green("Initialized a `./cli.json` config file."));
650
+ console.log(picocolors3.green("Initialized a `./cli.json` config file."));
1066
651
  } else {
1067
- console.log(picocolors5.redBright("A config file already exists."));
652
+ console.log(picocolors3.redBright("A config file already exists."));
1068
653
  }
1069
654
  });
1070
655
  program.command("customise").alias("customize").description("simple way to customise layouts with Fumadocs UI").option("--dir <string>", "the root url or directory to resolve registry").action(async (options) => {
1071
656
  const resolver = getResolverFromDir(options.dir);
1072
- await customise(resolver, await loadConfig(options.config));
1073
- });
1074
- program.command("init").description("init a new plugin to your docs").argument("[string]", "plugin name").action(async (str, { config }) => {
1075
- const loadedConfig = await loadConfig(config);
1076
- if (str) {
1077
- const plugin = str in plugins ? plugins[str] : void 0;
1078
- if (!plugin) throw new Error(`Plugin not found: ${str}`);
1079
- await init(plugin, loadedConfig);
1080
- return;
1081
- }
1082
- const value = await select2({
1083
- message: "Select components to install",
1084
- options: Object.keys(plugins).map((c) => ({
1085
- label: c,
1086
- value: c
1087
- }))
1088
- });
1089
- if (isCancel5(value)) {
1090
- outro4("Ended");
1091
- return;
1092
- }
1093
- await init(plugins[value], loadedConfig);
657
+ await customise(resolver, await createOrLoadConfig(options.config));
1094
658
  });
1095
659
  var dirShortcuts = {
1096
660
  ":dev": "https://preview.fumadocs.dev/registry",
@@ -1099,7 +663,7 @@ var dirShortcuts = {
1099
663
  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(
1100
664
  async (input, options) => {
1101
665
  const resolver = getResolverFromDir(options.dir);
1102
- await add(input, resolver, await loadConfig(options.config));
666
+ await add(input, resolver, await createOrLoadConfig(options.config));
1103
667
  }
1104
668
  );
1105
669
  program.command("tree").argument(
@@ -1119,10 +683,10 @@ program.command("tree").argument(
1119
683
  } catch {
1120
684
  nodes = await runTree(str ?? "./");
1121
685
  }
1122
- const out = js || output && jsExtensions.includes(path9.extname(output)) ? treeToJavaScript(nodes, noRoot, importName) : treeToMdx(nodes, noRoot);
686
+ const out = js || output && jsExtensions.includes(path4.extname(output)) ? treeToJavaScript(nodes, noRoot, importName) : treeToMdx(nodes, noRoot);
1123
687
  if (output) {
1124
- await fs10.mkdir(path9.dirname(output), { recursive: true });
1125
- await fs10.writeFile(output, out);
688
+ await fs5.mkdir(path4.dirname(output), { recursive: true });
689
+ await fs5.writeFile(output, out);
1126
690
  } else {
1127
691
  console.log(out);
1128
692
  }