@fumadocs/cli 0.2.1 → 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.1",
346
+ version: "1.0.0",
775
347
  description: "The CLI tool for Fumadocs",
776
348
  keywords: [
777
349
  "NextJs",
@@ -810,13 +382,15 @@ var package_default = {
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": "24.0.1",
389
+ shadcn: "2.9.3-canary.0",
390
+ "@types/node": "24.2.1",
817
391
  "eslint-config-custom": "workspace:*",
818
392
  tsconfig: "workspace:*",
819
- tsx: "^4.20.2"
393
+ tsx: "^4.20.3"
820
394
  },
821
395
  publishConfig: {
822
396
  access: "public"
@@ -825,37 +399,44 @@ var package_default = {
825
399
 
826
400
  // src/commands/customise.ts
827
401
  import {
828
- cancel as cancel2,
829
- confirm as confirm4,
402
+ cancel,
403
+ confirm as confirm3,
830
404
  group,
831
- intro as intro3,
832
- log as log6,
405
+ intro as intro2,
406
+ log as log3,
833
407
  outro as outro3,
834
408
  select
835
409
  } from "@clack/prompts";
836
- import picocolors4 from "picocolors";
410
+ import picocolors2 from "picocolors";
837
411
 
838
412
  // src/commands/add.ts
839
413
  import {
840
- intro as intro2,
841
- isCancel as isCancel4,
842
- log as log5,
414
+ intro,
415
+ isCancel as isCancel3,
416
+ log as log2,
843
417
  multiselect,
844
418
  outro as outro2,
845
- spinner as spinner3
419
+ spinner as spinner2
846
420
  } from "@clack/prompts";
847
- 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
+ }
848
429
 
849
430
  // src/utils/add/install-deps.ts
850
- import { confirm as confirm3, isCancel as isCancel3, spinner as spinner2 } from "@clack/prompts";
851
- 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";
852
433
 
853
434
  // src/utils/add/get-deps.ts
854
- import fs9 from "fs/promises";
435
+ import fs4 from "fs/promises";
855
436
  async function getDeps() {
856
437
  const dependencies = /* @__PURE__ */ new Map();
857
438
  if (!await exists("package.json")) return dependencies;
858
- const content = await fs9.readFile("package.json");
439
+ const content = await fs4.readFile("package.json");
859
440
  const parsed = JSON.parse(content.toString());
860
441
  if ("dependencies" in parsed && typeof parsed.dependencies === "object") {
861
442
  const records = parsed.dependencies;
@@ -873,30 +454,27 @@ async function getDeps() {
873
454
  }
874
455
 
875
456
  // src/utils/add/install-deps.ts
876
- async function installDeps(results) {
457
+ async function installDeps(deps, devDeps) {
877
458
  const installed = await getDeps();
878
- const deps = {};
879
- const devDeps = {};
880
- for (const result of results) {
881
- Object.assign(deps, result.dependencies);
882
- 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}`);
883
461
  }
884
- const items = Object.entries(deps).filter(([k]) => !installed.has(k)).map(([k, v]) => v.length === 0 ? k : `${k}@${v}`);
885
- 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);
886
464
  if (items.length > 0 || devItems.length > 0) {
887
465
  const manager = await getPackageManager();
888
- const value = await confirm3({
466
+ const value = await confirm2({
889
467
  message: `Do you want to install with ${manager}?
890
468
  ${[...items, ...devItems].map((v) => `- ${v}`).join("\n")}`
891
469
  });
892
- if (isCancel3(value)) {
470
+ if (isCancel2(value)) {
893
471
  return;
894
472
  }
895
473
  if (value) {
896
- const spin = spinner2();
474
+ const spin = spinner();
897
475
  spin.start("Installing dependencies...");
898
- if (items.length > 0) await x3(manager, ["install", ...items]);
899
- 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"]);
900
478
  spin.stop("Dependencies installed.");
901
479
  }
902
480
  }
@@ -904,56 +482,68 @@ ${[...items, ...devItems].map((v) => `- ${v}`).join("\n")}`
904
482
 
905
483
  // src/commands/add.ts
906
484
  async function add(input, resolver, config) {
485
+ const installer = createComponentInstaller({
486
+ resolver,
487
+ config
488
+ });
907
489
  let target = input;
908
490
  if (input.length === 0) {
909
- const spin = spinner3();
491
+ const spin = spinner2();
910
492
  spin.start("fetching registry");
911
- const registry = await resolver("_registry.json");
912
- spin.stop(picocolors3.bold(picocolors3.greenBright("registry fetched")));
913
- if (!registry) {
914
- log5.error(`Failed to fetch '_registry.json' file from registry`);
915
- throw new Error(`Failed to fetch registry`);
916
- }
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")));
917
500
  const value = await multiselect({
918
501
  message: "Select components to install",
919
- options: registry.map((item) => ({
920
- label: item.name,
502
+ options: indexes.map((item) => ({
503
+ label: item.title,
921
504
  value: item.name,
922
505
  hint: item.description
923
506
  }))
924
507
  });
925
- if (isCancel4(value)) {
508
+ if (isCancel3(value)) {
926
509
  outro2("Ended");
927
510
  return;
928
511
  }
929
512
  target = value;
930
513
  }
931
- await install(target, resolver, config);
514
+ await install(target, installer);
932
515
  }
933
- async function install(target, resolver, config) {
934
- const outputs = [];
516
+ async function install(target, installer) {
517
+ const dependencies = {};
518
+ const devDependencies = {};
935
519
  for (const name of target) {
936
- intro2(
937
- picocolors3.bold(
938
- picocolors3.inverse(picocolors3.cyanBright(`Add Component: ${name}`))
520
+ intro(
521
+ picocolors.bold(
522
+ picocolors.inverse(picocolors.cyanBright(`Add Component: ${name}`))
939
523
  )
940
524
  );
941
- const output = await installComponent(name, resolver, config);
942
- if (!output) {
943
- log5.error(`Failed to install ${name}: not found`);
944
- 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;
945
533
  }
946
- outro2(picocolors3.bold(picocolors3.greenBright(`${name} installed`)));
947
- outputs.push(output);
948
534
  }
949
- intro2(picocolors3.bold("New Dependencies"));
950
- await installDeps(outputs);
951
- outro2(picocolors3.bold(picocolors3.greenBright("Successful")));
535
+ intro(picocolors.bold("New Dependencies"));
536
+ await installDeps(dependencies, devDependencies);
537
+ outro2(picocolors.bold(picocolors.greenBright("Successful")));
952
538
  }
953
539
 
954
540
  // src/commands/customise.ts
955
541
  async function customise(resolver, config) {
956
- 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
+ });
957
547
  const result = await group(
958
548
  {
959
549
  target: () => select({
@@ -997,14 +587,14 @@ async function customise(resolver, config) {
997
587
  page: async (v) => {
998
588
  if (v.results.target !== "docs" || v.results.mode === "minimal")
999
589
  return false;
1000
- return confirm4({
590
+ return confirm3({
1001
591
  message: "Do you want to customise the page component too?"
1002
592
  });
1003
593
  }
1004
594
  },
1005
595
  {
1006
596
  onCancel: () => {
1007
- cancel2("Installation Stopped.");
597
+ cancel("Installation Stopped.");
1008
598
  process.exit(0);
1009
599
  }
1010
600
  }
@@ -1024,72 +614,47 @@ async function customise(resolver, config) {
1024
614
  result.mode === "full-default" ? "layouts/docs" : "layouts/notebook"
1025
615
  );
1026
616
  }
1027
- await install(targets, resolver, config);
1028
- intro3(picocolors4.bold("What is Next?"));
1029
- log6.info(
1030
- [
1031
- "You can check the installed components in `components/layouts`.",
1032
- picocolors4.dim("---"),
1033
- "Open your `layout.tsx` files, replace the imports of components:",
1034
- picocolors4.greenBright(
1035
- "`fumadocs-ui/layouts/docs` -> `@/components/layouts/docs`"
1036
- ),
1037
- pageAdded ? picocolors4.greenBright(
1038
- "`fumadocs-ui/page` -> `@/components/layouts/page`"
1039
- ) : ""
1040
- ].join("\n")
1041
- );
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);
1042
625
  }
1043
626
  if (result.target === "home") {
1044
- await install(["layouts/home"], resolver, config);
1045
- intro3(picocolors4.bold("What is Next?"));
1046
- log6.info(
1047
- [
1048
- "You can check the installed components in `components/layouts`.",
1049
- picocolors4.dim("---"),
1050
- "Open your `layout.tsx` files, replace the imports of components:",
1051
- picocolors4.greenBright(
1052
- "`fumadocs-ui/layouts/home` -> `@/components/layouts/home`"
1053
- )
1054
- ].join("\n")
1055
- );
1056
- }
1057
- 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
+ );
1058
644
  }
1059
645
 
1060
646
  // src/index.ts
1061
647
  var program = new Command().option("--config <string>");
1062
- 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 () => {
1063
649
  if (await initConfig()) {
1064
- console.log(picocolors5.green("Initialized a `./cli.json` config file."));
650
+ console.log(picocolors3.green("Initialized a `./cli.json` config file."));
1065
651
  } else {
1066
- console.log(picocolors5.redBright("A config file already exists."));
652
+ console.log(picocolors3.redBright("A config file already exists."));
1067
653
  }
1068
654
  });
1069
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) => {
1070
656
  const resolver = getResolverFromDir(options.dir);
1071
- await customise(resolver, await loadConfig(options.config));
1072
- });
1073
- program.command("init").description("init a new plugin to your docs").argument("[string]", "plugin name").action(async (str, { config }) => {
1074
- const loadedConfig = await loadConfig(config);
1075
- if (str) {
1076
- const plugin = str in plugins ? plugins[str] : void 0;
1077
- if (!plugin) throw new Error(`Plugin not found: ${str}`);
1078
- await init(plugin, loadedConfig);
1079
- return;
1080
- }
1081
- const value = await select2({
1082
- message: "Select components to install",
1083
- options: Object.keys(plugins).map((c) => ({
1084
- label: c,
1085
- value: c
1086
- }))
1087
- });
1088
- if (isCancel5(value)) {
1089
- outro4("Ended");
1090
- return;
1091
- }
1092
- await init(plugins[value], loadedConfig);
657
+ await customise(resolver, await createOrLoadConfig(options.config));
1093
658
  });
1094
659
  var dirShortcuts = {
1095
660
  ":dev": "https://preview.fumadocs.dev/registry",
@@ -1098,7 +663,7 @@ var dirShortcuts = {
1098
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(
1099
664
  async (input, options) => {
1100
665
  const resolver = getResolverFromDir(options.dir);
1101
- await add(input, resolver, await loadConfig(options.config));
666
+ await add(input, resolver, await createOrLoadConfig(options.config));
1102
667
  }
1103
668
  );
1104
669
  program.command("tree").argument(
@@ -1118,10 +683,10 @@ program.command("tree").argument(
1118
683
  } catch {
1119
684
  nodes = await runTree(str ?? "./");
1120
685
  }
1121
- 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);
1122
687
  if (output) {
1123
- await fs10.mkdir(path9.dirname(output), { recursive: true });
1124
- await fs10.writeFile(output, out);
688
+ await fs5.mkdir(path4.dirname(output), { recursive: true });
689
+ await fs5.writeFile(output, out);
1125
690
  } else {
1126
691
  console.log(out);
1127
692
  }