@fumadocs/cli 1.1.0 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/build/index.d.ts +163 -139
- package/dist/build/index.d.ts.map +1 -0
- package/dist/build/index.js +265 -405
- package/dist/build/index.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +592 -628
- package/dist/index.js.map +1 -0
- package/dist/schema/default.json +1 -0
- package/dist/schema/src.json +1 -0
- package/package.json +20 -20
package/dist/index.js
CHANGED
|
@@ -1,680 +1,644 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import fs5 from "fs/promises";
|
|
5
|
-
import path4 from "path";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
6
4
|
import { Command } from "commander";
|
|
7
|
-
import
|
|
8
|
-
|
|
9
|
-
// src/utils/add/install-component.ts
|
|
10
|
-
import path2 from "path";
|
|
11
|
-
import fs from "fs/promises";
|
|
12
|
-
import { confirm, isCancel, log, outro } from "@clack/prompts";
|
|
13
|
-
|
|
14
|
-
// src/utils/typescript.ts
|
|
15
|
-
import { Project } from "ts-morph";
|
|
16
|
-
function createEmptyProject() {
|
|
17
|
-
return new Project({
|
|
18
|
-
compilerOptions: {}
|
|
19
|
-
});
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// src/constants.ts
|
|
23
|
-
var typescriptExtensions = [".ts", ".tsx", ".js", ".jsx"];
|
|
24
|
-
|
|
25
|
-
// src/utils/transform-references.ts
|
|
26
|
-
import path from "path";
|
|
27
|
-
function transformReferences(file, transform) {
|
|
28
|
-
for (const specifier of file.getImportStringLiterals()) {
|
|
29
|
-
const result = transform(specifier.getLiteralValue());
|
|
30
|
-
if (!result) continue;
|
|
31
|
-
specifier.setLiteralValue(result);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
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, "/");
|
|
41
|
-
return importPath.startsWith("../") ? importPath : `./${importPath}`;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// src/registry/schema.ts
|
|
5
|
+
import picocolors from "picocolors";
|
|
45
6
|
import { z } from "zod";
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// src/utils/add/install-component.ts
|
|
90
|
-
function createComponentInstaller(options) {
|
|
91
|
-
const { config, resolver } = options;
|
|
92
|
-
const project = createEmptyProject();
|
|
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);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
return Array.from(map.values());
|
|
110
|
-
}
|
|
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
|
-
})
|
|
164
|
-
);
|
|
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
|
|
175
|
-
});
|
|
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
|
-
}
|
|
190
|
-
});
|
|
191
|
-
return sourceFile.getFullText();
|
|
192
|
-
},
|
|
193
|
-
resolveOutputPath(ref) {
|
|
194
|
-
if (ref.target) {
|
|
195
|
-
return path2.join(config.baseDir, ref.target);
|
|
196
|
-
}
|
|
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);
|
|
207
|
-
}
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
function remoteResolver(url) {
|
|
211
|
-
return async (file) => {
|
|
212
|
-
const res = await fetch(`${url}/${file}`);
|
|
213
|
-
if (!res.ok) {
|
|
214
|
-
throw new Error(`failed to fetch ${url}/${file}: ${res.statusText}`);
|
|
215
|
-
}
|
|
216
|
-
return await res.json();
|
|
217
|
-
};
|
|
218
|
-
}
|
|
219
|
-
function localResolver(dir) {
|
|
220
|
-
return async (file) => {
|
|
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
|
-
});
|
|
227
|
-
};
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
// src/config.ts
|
|
231
|
-
import fs3 from "fs/promises";
|
|
7
|
+
import { x } from "tinyexec";
|
|
8
|
+
import { cancel, confirm, group, intro, isCancel, log, multiselect, outro, select, spinner } from "@clack/prompts";
|
|
9
|
+
import { Project } from "ts-morph";
|
|
10
|
+
import { detect } from "package-manager-detector";
|
|
232
11
|
|
|
233
|
-
|
|
234
|
-
import fs2 from "fs/promises";
|
|
235
|
-
import path3 from "path";
|
|
12
|
+
//#region src/utils/fs.ts
|
|
236
13
|
async function exists(pathLike) {
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
14
|
+
try {
|
|
15
|
+
await fs.access(pathLike);
|
|
16
|
+
return true;
|
|
17
|
+
} catch {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
243
20
|
}
|
|
244
21
|
|
|
245
|
-
|
|
22
|
+
//#endregion
|
|
23
|
+
//#region src/utils/is-src.ts
|
|
246
24
|
async function isSrc() {
|
|
247
|
-
|
|
25
|
+
return exists("./src");
|
|
248
26
|
}
|
|
249
27
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
function createConfigSchema(
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
format: z3.string().optional()
|
|
274
|
-
}).default({})
|
|
275
|
-
});
|
|
28
|
+
//#endregion
|
|
29
|
+
//#region src/config.ts
|
|
30
|
+
function createConfigSchema(isSrc$1) {
|
|
31
|
+
const defaultAliases = {
|
|
32
|
+
uiDir: "./components/ui",
|
|
33
|
+
componentsDir: "./components",
|
|
34
|
+
blockDir: "./components",
|
|
35
|
+
cssDir: "./styles",
|
|
36
|
+
libDir: "./lib"
|
|
37
|
+
};
|
|
38
|
+
return z.object({
|
|
39
|
+
$schema: z.string().default(isSrc$1 ? "node_modules/@fumadocs/cli/dist/schema/src.json" : "node_modules/@fumadocs/cli/dist/schema/default.json").optional(),
|
|
40
|
+
aliases: z.object({
|
|
41
|
+
uiDir: z.string().default(defaultAliases.uiDir),
|
|
42
|
+
componentsDir: z.string().default(defaultAliases.uiDir),
|
|
43
|
+
blockDir: z.string().default(defaultAliases.blockDir),
|
|
44
|
+
cssDir: z.string().default(defaultAliases.componentsDir),
|
|
45
|
+
libDir: z.string().default(defaultAliases.libDir)
|
|
46
|
+
}).default(defaultAliases),
|
|
47
|
+
baseDir: z.string().default(isSrc$1 ? "src" : ""),
|
|
48
|
+
uiLibrary: z.enum(["radix-ui", "base-ui"]).default("radix-ui"),
|
|
49
|
+
commands: z.object({ format: z.string().optional() }).default({})
|
|
50
|
+
});
|
|
276
51
|
}
|
|
277
52
|
async function createOrLoadConfig(file = "./cli.json") {
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
const configSchema = createConfigSchema(src);
|
|
283
|
-
return configSchema.parse(JSON.parse(content));
|
|
53
|
+
const inited = await initConfig(file);
|
|
54
|
+
if (inited) return inited;
|
|
55
|
+
const content = (await fs.readFile(file)).toString();
|
|
56
|
+
return createConfigSchema(await isSrc()).parse(JSON.parse(content));
|
|
284
57
|
}
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
58
|
+
/**
|
|
59
|
+
* Write new config, skip if a config already exists
|
|
60
|
+
*
|
|
61
|
+
* @returns the created config, `undefined` if not created
|
|
62
|
+
*/
|
|
63
|
+
async function initConfig(file = "./cli.json", src) {
|
|
64
|
+
if (await fs.stat(file).then(() => true).catch(() => false)) return;
|
|
65
|
+
const defaultConfig = createConfigSchema(src ?? await isSrc()).parse({});
|
|
66
|
+
await fs.writeFile(file, JSON.stringify(defaultConfig, null, 2));
|
|
67
|
+
return defaultConfig;
|
|
293
68
|
}
|
|
294
69
|
|
|
295
|
-
|
|
296
|
-
|
|
70
|
+
//#endregion
|
|
71
|
+
//#region src/commands/file-tree.ts
|
|
72
|
+
const scanned = [
|
|
73
|
+
"file",
|
|
74
|
+
"directory",
|
|
75
|
+
"link"
|
|
76
|
+
];
|
|
297
77
|
function treeToMdx(input, noRoot = false) {
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
}
|
|
310
|
-
return `<Folder name=${JSON.stringify(item.name)}>
|
|
78
|
+
function toNode(item) {
|
|
79
|
+
if (item.type === "file" || item.type === "link") return `<File name=${JSON.stringify(item.name)} />`;
|
|
80
|
+
if (item.type === "directory") {
|
|
81
|
+
if (item.contents.length === 1 && "name" in item.contents[0]) {
|
|
82
|
+
const child = item.contents[0];
|
|
83
|
+
return toNode({
|
|
84
|
+
...child,
|
|
85
|
+
name: `${item.name}/${child.name}`
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
return `<Folder name=${JSON.stringify(item.name)}>
|
|
311
89
|
${item.contents.map(toNode).filter(Boolean).join("\n")}
|
|
312
90
|
</Folder>`;
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
}
|
|
320
|
-
return `<Files>
|
|
91
|
+
}
|
|
92
|
+
return "";
|
|
93
|
+
}
|
|
94
|
+
let children = input.filter((v) => scanned.includes(v.type));
|
|
95
|
+
if (noRoot && children.length === 1 && input[0].type === "directory") children = input[0].contents;
|
|
96
|
+
return `<Files>
|
|
321
97
|
${children.map(toNode).filter(Boolean).join("\n")}
|
|
322
98
|
</Files>`;
|
|
323
99
|
}
|
|
324
100
|
function treeToJavaScript(input, noRoot, importName = "fumadocs-ui/components/files") {
|
|
325
|
-
|
|
101
|
+
return `import { File, Files, Folder } from ${JSON.stringify(importName)}
|
|
326
102
|
|
|
327
103
|
export default (${treeToMdx(input, noRoot)})`;
|
|
328
104
|
}
|
|
329
105
|
|
|
330
|
-
|
|
331
|
-
|
|
106
|
+
//#endregion
|
|
107
|
+
//#region src/utils/file-tree/run-tree.ts
|
|
332
108
|
async function runTree(args) {
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
109
|
+
const out = await x("tree", [
|
|
110
|
+
args,
|
|
111
|
+
"--gitignore",
|
|
112
|
+
"--prune",
|
|
113
|
+
"-J"
|
|
114
|
+
]);
|
|
115
|
+
try {
|
|
116
|
+
return JSON.parse(out.stdout);
|
|
117
|
+
} catch (e) {
|
|
118
|
+
throw new Error("failed to run `tree` command", { cause: e });
|
|
119
|
+
}
|
|
341
120
|
}
|
|
342
121
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
version: "1.1.0",
|
|
347
|
-
description: "The CLI tool for Fumadocs",
|
|
348
|
-
keywords: [
|
|
349
|
-
"NextJs",
|
|
350
|
-
"Docs",
|
|
351
|
-
"Fumadocs"
|
|
352
|
-
],
|
|
353
|
-
homepage: "https://fumadocs.dev",
|
|
354
|
-
repository: "github:fuma-nama/fumadocs",
|
|
355
|
-
license: "MIT",
|
|
356
|
-
author: "Fuma Nama",
|
|
357
|
-
type: "module",
|
|
358
|
-
exports: {
|
|
359
|
-
"./build": {
|
|
360
|
-
import: "./dist/build/index.js",
|
|
361
|
-
types: "./dist/build/index.d.ts"
|
|
362
|
-
}
|
|
363
|
-
},
|
|
364
|
-
main: "./dist/index.js",
|
|
365
|
-
bin: {
|
|
366
|
-
fumadocs: "./dist/index.js"
|
|
367
|
-
},
|
|
368
|
-
files: [
|
|
369
|
-
"dist/*"
|
|
370
|
-
],
|
|
371
|
-
scripts: {
|
|
372
|
-
build: "tsup",
|
|
373
|
-
clean: "rimraf dist",
|
|
374
|
-
dev: "tsup --watch",
|
|
375
|
-
lint: "eslint .",
|
|
376
|
-
"types:check": "tsc --noEmit"
|
|
377
|
-
},
|
|
378
|
-
dependencies: {
|
|
379
|
-
"@clack/prompts": "^0.11.0",
|
|
380
|
-
commander: "^14.0.2",
|
|
381
|
-
"package-manager-detector": "^1.5.0",
|
|
382
|
-
picocolors: "^1.1.1",
|
|
383
|
-
tinyexec: "^1.0.2",
|
|
384
|
-
"ts-morph": "^27.0.2",
|
|
385
|
-
zod: "^4.1.12"
|
|
386
|
-
},
|
|
387
|
-
devDependencies: {
|
|
388
|
-
"@types/node": "24.10.1",
|
|
389
|
-
"eslint-config-custom": "workspace:*",
|
|
390
|
-
shadcn: "3.5.0",
|
|
391
|
-
tsconfig: "workspace:*"
|
|
392
|
-
},
|
|
393
|
-
publishConfig: {
|
|
394
|
-
access: "public"
|
|
395
|
-
}
|
|
396
|
-
};
|
|
122
|
+
//#endregion
|
|
123
|
+
//#region package.json
|
|
124
|
+
var version = "1.2.1";
|
|
397
125
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
126
|
+
//#endregion
|
|
127
|
+
//#region src/utils/typescript.ts
|
|
128
|
+
function createEmptyProject() {
|
|
129
|
+
return new Project({ compilerOptions: {} });
|
|
130
|
+
}
|
|
401
131
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
} from "@clack/prompts";
|
|
411
|
-
import picocolors from "picocolors";
|
|
132
|
+
//#endregion
|
|
133
|
+
//#region src/constants.ts
|
|
134
|
+
const typescriptExtensions = [
|
|
135
|
+
".ts",
|
|
136
|
+
".tsx",
|
|
137
|
+
".js",
|
|
138
|
+
".jsx"
|
|
139
|
+
];
|
|
412
140
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
141
|
+
//#endregion
|
|
142
|
+
//#region src/utils/ast.ts
|
|
143
|
+
/**
|
|
144
|
+
* Return the import modifier for `sourceFile` to import `referenceFile`
|
|
145
|
+
*
|
|
146
|
+
* @example
|
|
147
|
+
* ```ts
|
|
148
|
+
* toReferencePath('index.ts', 'dir/hello.ts')
|
|
149
|
+
* // should output './dir/hello'
|
|
150
|
+
* ```
|
|
151
|
+
*/
|
|
152
|
+
function toImportSpecifier(sourceFile, referenceFile) {
|
|
153
|
+
const extname = path.extname(referenceFile);
|
|
154
|
+
const removeExt = typescriptExtensions.includes(extname);
|
|
155
|
+
let importPath = path.relative(path.dirname(sourceFile), removeExt ? referenceFile.substring(0, referenceFile.length - extname.length) : referenceFile).replaceAll(path.sep, "/");
|
|
156
|
+
if (removeExt && importPath.endsWith("/index")) importPath = importPath.slice(0, -6);
|
|
157
|
+
return importPath.startsWith("../") ? importPath : `./${importPath}`;
|
|
418
158
|
}
|
|
419
159
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
160
|
+
//#endregion
|
|
161
|
+
//#region src/registry/schema.ts
|
|
162
|
+
const namespaces = [
|
|
163
|
+
"components",
|
|
164
|
+
"lib",
|
|
165
|
+
"css",
|
|
166
|
+
"route",
|
|
167
|
+
"ui",
|
|
168
|
+
"block"
|
|
169
|
+
];
|
|
170
|
+
const indexSchema = z.object({
|
|
171
|
+
name: z.string(),
|
|
172
|
+
title: z.string().optional(),
|
|
173
|
+
description: z.string().optional()
|
|
174
|
+
});
|
|
175
|
+
const fileSchema = z.object({
|
|
176
|
+
type: z.literal(namespaces),
|
|
177
|
+
path: z.string(),
|
|
178
|
+
target: z.string().optional(),
|
|
179
|
+
content: z.string()
|
|
180
|
+
});
|
|
181
|
+
const httpSubComponent = z.object({
|
|
182
|
+
type: z.literal("http"),
|
|
183
|
+
baseUrl: z.string(),
|
|
184
|
+
component: z.string()
|
|
185
|
+
});
|
|
186
|
+
const componentSchema = z.object({
|
|
187
|
+
name: z.string(),
|
|
188
|
+
title: z.string().optional(),
|
|
189
|
+
description: z.string().optional(),
|
|
190
|
+
files: z.array(fileSchema),
|
|
191
|
+
dependencies: z.record(z.string(), z.string().or(z.null())),
|
|
192
|
+
devDependencies: z.record(z.string(), z.string().or(z.null())),
|
|
193
|
+
subComponents: z.array(z.string().or(httpSubComponent)).default([])
|
|
194
|
+
});
|
|
195
|
+
const registryInfoSchema = z.object({
|
|
196
|
+
variables: z.record(z.string(), z.object({
|
|
197
|
+
description: z.string().optional(),
|
|
198
|
+
default: z.unknown().optional()
|
|
199
|
+
})).optional(),
|
|
200
|
+
env: z.record(z.string(), z.unknown()).optional(),
|
|
201
|
+
indexes: z.array(indexSchema).default([]),
|
|
202
|
+
registries: z.array(z.string()).optional()
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
//#endregion
|
|
206
|
+
//#region src/utils/cache.ts
|
|
207
|
+
var AsyncCache = class {
|
|
208
|
+
constructor() {
|
|
209
|
+
this.store = /* @__PURE__ */ new Map();
|
|
210
|
+
}
|
|
211
|
+
cached(key, fn) {
|
|
212
|
+
let cached = this.store.get(key);
|
|
213
|
+
if (cached !== void 0) return cached;
|
|
214
|
+
cached = fn();
|
|
215
|
+
this.store.set(key, cached);
|
|
216
|
+
return cached;
|
|
217
|
+
}
|
|
218
|
+
};
|
|
423
219
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
}
|
|
220
|
+
//#endregion
|
|
221
|
+
//#region src/registry/client.ts
|
|
222
|
+
const fetchCache = new AsyncCache();
|
|
223
|
+
var HttpRegistryClient = class HttpRegistryClient {
|
|
224
|
+
constructor(baseUrl, config) {
|
|
225
|
+
this.baseUrl = baseUrl;
|
|
226
|
+
this.config = config;
|
|
227
|
+
this.registryId = baseUrl;
|
|
228
|
+
}
|
|
229
|
+
async fetchRegistryInfo(baseUrl = this.baseUrl) {
|
|
230
|
+
const url = new URL("_registry.json", `${baseUrl}/`);
|
|
231
|
+
return fetchCache.cached(url.href, async () => {
|
|
232
|
+
const res = await fetch(url);
|
|
233
|
+
if (!res.ok) throw new Error(`failed to fetch ${url.href}: ${res.statusText}`);
|
|
234
|
+
return registryInfoSchema.parse(await res.json());
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
async fetchComponent(name) {
|
|
238
|
+
const url = new URL(`${name}.json`, `${this.baseUrl}/`);
|
|
239
|
+
return fetchCache.cached(url.href, async () => {
|
|
240
|
+
const res = await fetch(`${this.baseUrl}/${name}.json`);
|
|
241
|
+
if (!res.ok) {
|
|
242
|
+
log.error(`component ${name} not found at ${url.href}`);
|
|
243
|
+
throw new Error(await res.text());
|
|
244
|
+
}
|
|
245
|
+
return componentSchema.parse(await res.json());
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
async hasComponent(name) {
|
|
249
|
+
const url = new URL(`${name}.json`, `${this.baseUrl}/`);
|
|
250
|
+
return (await fetch(url, { method: "HEAD" })).ok;
|
|
251
|
+
}
|
|
252
|
+
createLinkedRegistryClient(name) {
|
|
253
|
+
return new HttpRegistryClient(`${this.baseUrl}/${name}`, this.config);
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
var LocalRegistryClient = class LocalRegistryClient {
|
|
257
|
+
constructor(dir, config) {
|
|
258
|
+
this.dir = dir;
|
|
259
|
+
this.config = config;
|
|
260
|
+
this.registryId = dir;
|
|
261
|
+
}
|
|
262
|
+
async fetchRegistryInfo(dir = this.dir) {
|
|
263
|
+
if (this.registryInfo) return this.registryInfo;
|
|
264
|
+
const filePath = path.join(dir, "_registry.json");
|
|
265
|
+
const out = await fs.readFile(filePath).then((res) => JSON.parse(res.toString())).catch((e) => {
|
|
266
|
+
throw new Error(`failed to resolve local file "${filePath}"`, { cause: e });
|
|
267
|
+
});
|
|
268
|
+
return this.registryInfo = registryInfoSchema.parse(out);
|
|
269
|
+
}
|
|
270
|
+
async fetchComponent(name) {
|
|
271
|
+
const filePath = path.join(this.dir, `${name}.json`);
|
|
272
|
+
const out = await fs.readFile(filePath).then((res) => JSON.parse(res.toString())).catch((e) => {
|
|
273
|
+
log.error(`component ${name} not found at ${filePath}`);
|
|
274
|
+
throw e;
|
|
275
|
+
});
|
|
276
|
+
return componentSchema.parse(out);
|
|
277
|
+
}
|
|
278
|
+
async hasComponent(name) {
|
|
279
|
+
const filePath = path.join(this.dir, `${name}.json`);
|
|
280
|
+
try {
|
|
281
|
+
await fs.stat(filePath);
|
|
282
|
+
return true;
|
|
283
|
+
} catch {
|
|
284
|
+
return false;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
createLinkedRegistryClient(name) {
|
|
288
|
+
return new LocalRegistryClient(path.join(this.dir, name), this.config);
|
|
289
|
+
}
|
|
290
|
+
};
|
|
445
291
|
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
return Object.entries(deps2).filter(([k]) => !installed.has(k)).map(([k, v]) => v === null || v.length === 0 ? k : `${k}@${v}`);
|
|
451
|
-
}
|
|
452
|
-
const items = toList(deps);
|
|
453
|
-
const devItems = toList(devDeps);
|
|
454
|
-
if (items.length > 0 || devItems.length > 0) {
|
|
455
|
-
const manager = await getPackageManager();
|
|
456
|
-
const value = await confirm2({
|
|
457
|
-
message: `Do you want to install with ${manager}?
|
|
458
|
-
${[...items, ...devItems].map((v) => `- ${v}`).join("\n")}`
|
|
459
|
-
});
|
|
460
|
-
if (isCancel2(value)) {
|
|
461
|
-
return;
|
|
462
|
-
}
|
|
463
|
-
if (value) {
|
|
464
|
-
const spin = spinner();
|
|
465
|
-
spin.start("Installing dependencies...");
|
|
466
|
-
if (items.length > 0) await x2(manager, ["install", ...items]);
|
|
467
|
-
if (devItems.length > 0) await x2(manager, ["install", ...devItems, "-D"]);
|
|
468
|
-
spin.stop("Dependencies installed.");
|
|
469
|
-
}
|
|
470
|
-
}
|
|
292
|
+
//#endregion
|
|
293
|
+
//#region src/utils/get-package-manager.ts
|
|
294
|
+
async function getPackageManager() {
|
|
295
|
+
return (await detect())?.name ?? "npm";
|
|
471
296
|
}
|
|
472
297
|
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
298
|
+
//#endregion
|
|
299
|
+
//#region src/registry/installer/dep-manager.ts
|
|
300
|
+
var DependencyManager = class {
|
|
301
|
+
/**
|
|
302
|
+
* Get dependencies from `package.json`
|
|
303
|
+
*/
|
|
304
|
+
async getDeps() {
|
|
305
|
+
if (this.cachedInstalledDeps) return this.cachedInstalledDeps;
|
|
306
|
+
const dependencies = /* @__PURE__ */ new Map();
|
|
307
|
+
if (!await exists("package.json")) return dependencies;
|
|
308
|
+
const content = await fs.readFile("package.json");
|
|
309
|
+
const parsed = JSON.parse(content.toString());
|
|
310
|
+
if ("dependencies" in parsed && typeof parsed.dependencies === "object") {
|
|
311
|
+
const records = parsed.dependencies;
|
|
312
|
+
for (const [k, v] of Object.entries(records)) dependencies.set(k, v);
|
|
313
|
+
}
|
|
314
|
+
if ("devDependencies" in parsed && typeof parsed.devDependencies === "object") {
|
|
315
|
+
const records = parsed.devDependencies;
|
|
316
|
+
for (const [k, v] of Object.entries(records)) dependencies.set(k, v);
|
|
317
|
+
}
|
|
318
|
+
return this.cachedInstalledDeps = dependencies;
|
|
319
|
+
}
|
|
320
|
+
async resolveInstallDependencies(deps) {
|
|
321
|
+
const cachedInstalledDeps = await this.getDeps();
|
|
322
|
+
return Object.entries(deps).filter(([k]) => !cachedInstalledDeps.has(k)).map(([k, v]) => v === null || v.length === 0 ? k : `${k}@${v}`);
|
|
323
|
+
}
|
|
324
|
+
async installDeps(deps, devDeps) {
|
|
325
|
+
const items = await this.resolveInstallDependencies(deps);
|
|
326
|
+
const devItems = await this.resolveInstallDependencies(devDeps);
|
|
327
|
+
if (items.length === 0 && devItems.length === 0) return;
|
|
328
|
+
const manager = await getPackageManager();
|
|
329
|
+
const value = await confirm({ message: `Do you want to install with ${manager}?
|
|
330
|
+
${[...items, ...devItems].map((v) => `- ${v}`).join("\n")}` });
|
|
331
|
+
if (isCancel(value) || !value) return;
|
|
332
|
+
const spin = spinner();
|
|
333
|
+
spin.start("Installing dependencies...");
|
|
334
|
+
if (items.length > 0) await x(manager, ["install", ...items]);
|
|
335
|
+
if (devItems.length > 0) await x(manager, [
|
|
336
|
+
"install",
|
|
337
|
+
...devItems,
|
|
338
|
+
"-D"
|
|
339
|
+
]);
|
|
340
|
+
spin.stop("Dependencies installed.");
|
|
341
|
+
}
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
//#endregion
|
|
345
|
+
//#region src/registry/installer/index.ts
|
|
346
|
+
var ComponentInstaller = class {
|
|
347
|
+
constructor(rootClient, plugins = []) {
|
|
348
|
+
this.rootClient = rootClient;
|
|
349
|
+
this.plugins = plugins;
|
|
350
|
+
this.project = createEmptyProject();
|
|
351
|
+
this.installedFiles = /* @__PURE__ */ new Set();
|
|
352
|
+
this.downloadCache = new AsyncCache();
|
|
353
|
+
this.dependencies = {};
|
|
354
|
+
this.devDependencies = {};
|
|
355
|
+
this.pathToFileCache = new AsyncCache();
|
|
356
|
+
}
|
|
357
|
+
async install(name) {
|
|
358
|
+
let downloaded;
|
|
359
|
+
const info = await this.rootClient.fetchRegistryInfo();
|
|
360
|
+
for (const registry of info.registries ?? []) if (name.startsWith(`${registry}/`)) {
|
|
361
|
+
downloaded = await this.download(name.slice(registry.length + 1), this.rootClient.createLinkedRegistryClient(registry));
|
|
362
|
+
break;
|
|
363
|
+
}
|
|
364
|
+
downloaded ??= await this.download(name, this.rootClient);
|
|
365
|
+
for (const item of downloaded) {
|
|
366
|
+
Object.assign(this.dependencies, item.dependencies);
|
|
367
|
+
Object.assign(this.devDependencies, item.devDependencies);
|
|
368
|
+
}
|
|
369
|
+
for (const comp of downloaded) for (const file of comp.files) {
|
|
370
|
+
const outPath = this.resolveOutputPath(file);
|
|
371
|
+
if (this.installedFiles.has(outPath)) continue;
|
|
372
|
+
this.installedFiles.add(outPath);
|
|
373
|
+
const output = typescriptExtensions.includes(path.extname(outPath)) ? await this.transform(name, file, comp, downloaded) : file.content;
|
|
374
|
+
const status = await fs.readFile(outPath).then((res) => {
|
|
375
|
+
if (res.toString() === output) return "ignore";
|
|
376
|
+
return "need-update";
|
|
377
|
+
}).catch(() => "write");
|
|
378
|
+
if (status === "ignore") continue;
|
|
379
|
+
if (status === "need-update") {
|
|
380
|
+
const override = await confirm({
|
|
381
|
+
message: `Do you want to override ${outPath}?`,
|
|
382
|
+
initialValue: false
|
|
383
|
+
});
|
|
384
|
+
if (isCancel(override)) {
|
|
385
|
+
outro("Ended");
|
|
386
|
+
process.exit(0);
|
|
387
|
+
}
|
|
388
|
+
if (!override) continue;
|
|
389
|
+
}
|
|
390
|
+
await fs.mkdir(path.dirname(outPath), { recursive: true });
|
|
391
|
+
await fs.writeFile(outPath, output);
|
|
392
|
+
log.step(`downloaded ${outPath}`);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
async installDeps() {
|
|
396
|
+
await new DependencyManager().installDeps(this.dependencies, this.devDependencies);
|
|
397
|
+
}
|
|
398
|
+
async onEnd() {
|
|
399
|
+
const config = this.rootClient.config;
|
|
400
|
+
if (config.commands.format) await x(config.commands.format);
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* return a list of components, merged with child components & variables.
|
|
404
|
+
*/
|
|
405
|
+
async download(name, client, contextVariables) {
|
|
406
|
+
const hash = `${client.registryId} ${name}`;
|
|
407
|
+
const info = await client.fetchRegistryInfo();
|
|
408
|
+
const variables = {
|
|
409
|
+
...contextVariables,
|
|
410
|
+
...info.env
|
|
411
|
+
};
|
|
412
|
+
for (const [k, v] of Object.entries(info.variables ?? {})) variables[k] ??= v.default;
|
|
413
|
+
return (await this.downloadCache.cached(hash, async () => {
|
|
414
|
+
const comp = await client.fetchComponent(name);
|
|
415
|
+
const result = [comp];
|
|
416
|
+
this.downloadCache.store.set(hash, result);
|
|
417
|
+
const child = await Promise.all(comp.subComponents.map((sub) => {
|
|
418
|
+
if (typeof sub === "string") return this.download(sub, client);
|
|
419
|
+
const baseUrl = this.rootClient instanceof HttpRegistryClient ? new URL(sub.baseUrl, `${this.rootClient.baseUrl}/`).href : sub.baseUrl;
|
|
420
|
+
return this.download(sub.component, new HttpRegistryClient(baseUrl, client.config), variables);
|
|
421
|
+
}));
|
|
422
|
+
for (const sub of child) result.push(...sub);
|
|
423
|
+
return result;
|
|
424
|
+
})).map((file) => ({
|
|
425
|
+
...file,
|
|
426
|
+
variables
|
|
427
|
+
}));
|
|
428
|
+
}
|
|
429
|
+
async transform(taskId, file, component, allComponents) {
|
|
430
|
+
const filePath = this.resolveOutputPath(file);
|
|
431
|
+
const sourceFile = this.project.createSourceFile(filePath, file.content, { overwrite: true });
|
|
432
|
+
const prefix = "@/";
|
|
433
|
+
const variables = Object.entries(component.variables ?? {});
|
|
434
|
+
const pathToFile = await this.pathToFileCache.cached(taskId, () => {
|
|
435
|
+
const map = /* @__PURE__ */ new Map();
|
|
436
|
+
for (const comp of allComponents) for (const file$1 of comp.files) map.set(file$1.target ?? file$1.path, file$1);
|
|
437
|
+
return map;
|
|
438
|
+
});
|
|
439
|
+
for (const specifier of sourceFile.getImportStringLiterals()) {
|
|
440
|
+
for (const [k, v] of variables) specifier.setLiteralValue(specifier.getLiteralValue().replaceAll(`<${k}>`, v));
|
|
441
|
+
if (specifier.getLiteralValue().startsWith(prefix)) {
|
|
442
|
+
const lookup = specifier.getLiteralValue().substring(2);
|
|
443
|
+
const target = pathToFile.get(lookup);
|
|
444
|
+
if (target) specifier.setLiteralValue(toImportSpecifier(filePath, this.resolveOutputPath(target)));
|
|
445
|
+
else console.warn(`cannot find the referenced file of ${specifier}`);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
for (const plugin of this.plugins) await plugin.transform?.({
|
|
449
|
+
file: sourceFile,
|
|
450
|
+
componentFile: file,
|
|
451
|
+
component
|
|
452
|
+
});
|
|
453
|
+
return sourceFile.getFullText();
|
|
454
|
+
}
|
|
455
|
+
resolveOutputPath(file) {
|
|
456
|
+
const config = this.rootClient.config;
|
|
457
|
+
const dir = {
|
|
458
|
+
components: config.aliases.componentsDir,
|
|
459
|
+
block: config.aliases.blockDir,
|
|
460
|
+
ui: config.aliases.uiDir,
|
|
461
|
+
css: config.aliases.cssDir,
|
|
462
|
+
lib: config.aliases.libDir,
|
|
463
|
+
route: "./"
|
|
464
|
+
}[file.type];
|
|
465
|
+
if (file.target) return path.join(config.baseDir, file.target.replace("<dir>", dir));
|
|
466
|
+
return path.join(config.baseDir, dir, path.basename(file.path));
|
|
467
|
+
}
|
|
468
|
+
};
|
|
469
|
+
|
|
470
|
+
//#endregion
|
|
471
|
+
//#region src/commands/shared.ts
|
|
472
|
+
const UIRegistries = {
|
|
473
|
+
"base-ui": "fumadocs/base-ui",
|
|
474
|
+
"radix-ui": "fumadocs/radix-ui"
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
//#endregion
|
|
478
|
+
//#region src/commands/add.ts
|
|
479
|
+
async function add(input, client) {
|
|
480
|
+
const config = client.config;
|
|
481
|
+
let target;
|
|
482
|
+
const installer = new ComponentInstaller(client);
|
|
483
|
+
const registry = UIRegistries[config.uiLibrary];
|
|
484
|
+
if (input.length === 0) {
|
|
485
|
+
const spin = spinner();
|
|
486
|
+
spin.start("fetching registry");
|
|
487
|
+
const info = await client.fetchRegistryInfo();
|
|
488
|
+
const options = [];
|
|
489
|
+
for (const item of info.indexes) options.push({
|
|
490
|
+
label: item.title ?? item.name,
|
|
491
|
+
value: item.name,
|
|
492
|
+
hint: item.description
|
|
493
|
+
});
|
|
494
|
+
const { indexes } = await client.createLinkedRegistryClient(registry).fetchRegistryInfo();
|
|
495
|
+
for (const item of indexes) options.push({
|
|
496
|
+
label: item.title ?? item.name,
|
|
497
|
+
value: `${registry}/${item.name}`,
|
|
498
|
+
hint: item.description
|
|
499
|
+
});
|
|
500
|
+
spin.stop(picocolors.bold(picocolors.greenBright("registry fetched")));
|
|
501
|
+
const value = await multiselect({
|
|
502
|
+
message: "Select components to install",
|
|
503
|
+
options
|
|
504
|
+
});
|
|
505
|
+
if (isCancel(value)) {
|
|
506
|
+
outro("Ended");
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
target = value;
|
|
510
|
+
} else target = await Promise.all(input.map(async (item) => await client.hasComponent(item) ? item : `${registry}/${item}`));
|
|
511
|
+
await install(target, installer);
|
|
505
512
|
}
|
|
506
513
|
async function install(target, installer) {
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
log2.error(String(e));
|
|
522
|
-
throw e;
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
intro(picocolors.bold("New Dependencies"));
|
|
526
|
-
await installDeps(dependencies, devDependencies);
|
|
527
|
-
outro2(picocolors.bold(picocolors.greenBright("Successful")));
|
|
514
|
+
for (const name of target) {
|
|
515
|
+
intro(picocolors.bold(picocolors.inverse(picocolors.cyanBright(`Add Component: ${name}`))));
|
|
516
|
+
try {
|
|
517
|
+
await installer.install(name);
|
|
518
|
+
outro(picocolors.bold(picocolors.greenBright(`${name} installed`)));
|
|
519
|
+
} catch (e) {
|
|
520
|
+
log.error(String(e));
|
|
521
|
+
throw e;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
intro(picocolors.bold("New Dependencies"));
|
|
525
|
+
await installer.installDeps();
|
|
526
|
+
await installer.onEnd();
|
|
527
|
+
outro(picocolors.bold(picocolors.greenBright("Successful")));
|
|
528
528
|
}
|
|
529
529
|
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
} else {
|
|
590
|
-
targets.push(
|
|
591
|
-
result.mode === "full-default" ? "layouts/docs" : "layouts/notebook"
|
|
592
|
-
);
|
|
593
|
-
}
|
|
594
|
-
await install(targets, installer);
|
|
595
|
-
const maps = result.mode === "full-notebook" ? [
|
|
596
|
-
["fumadocs-ui/layouts/notebook", "@/components/layout/notebook"],
|
|
597
|
-
[
|
|
598
|
-
"fumadocs-ui/layouts/notebook/page",
|
|
599
|
-
"@/components/layout/notebook/page"
|
|
600
|
-
]
|
|
601
|
-
] : [
|
|
602
|
-
["fumadocs-ui/layouts/docs", "@/components/layout/docs"],
|
|
603
|
-
["fumadocs-ui/layouts/docs/page", "@/components/layout/docs/page"]
|
|
604
|
-
];
|
|
605
|
-
printNext(...maps);
|
|
606
|
-
}
|
|
607
|
-
if (result.target === "home") {
|
|
608
|
-
await install(["layouts/home"], installer);
|
|
609
|
-
printNext(["fumadocs-ui/layouts/home", `@/components/layout/home`]);
|
|
610
|
-
}
|
|
611
|
-
outro3(picocolors2.bold("Have fun!"));
|
|
530
|
+
//#endregion
|
|
531
|
+
//#region src/commands/customise.ts
|
|
532
|
+
async function customise(client) {
|
|
533
|
+
intro(picocolors.bgBlack(picocolors.whiteBright("Customise Fumadocs UI")));
|
|
534
|
+
const config = client.config;
|
|
535
|
+
const installer = new ComponentInstaller(client);
|
|
536
|
+
const result = await group({
|
|
537
|
+
target: () => select({
|
|
538
|
+
message: "What do you want to customise?",
|
|
539
|
+
options: [{
|
|
540
|
+
label: "Docs Layout",
|
|
541
|
+
value: "docs",
|
|
542
|
+
hint: "main UI of your docs"
|
|
543
|
+
}, {
|
|
544
|
+
label: "Home Layout",
|
|
545
|
+
value: "home",
|
|
546
|
+
hint: "the navbar for your other pages"
|
|
547
|
+
}]
|
|
548
|
+
}),
|
|
549
|
+
mode: (v) => {
|
|
550
|
+
if (v.results.target !== "docs") return;
|
|
551
|
+
return select({
|
|
552
|
+
message: "Which variant do you want to start from?",
|
|
553
|
+
options: [
|
|
554
|
+
{
|
|
555
|
+
label: "Start from minimal styles",
|
|
556
|
+
value: "minimal",
|
|
557
|
+
hint: "for those who want to build their own variant from ground up."
|
|
558
|
+
},
|
|
559
|
+
{
|
|
560
|
+
label: "Start from default layout",
|
|
561
|
+
value: "full-default",
|
|
562
|
+
hint: "useful for adjusting small details."
|
|
563
|
+
},
|
|
564
|
+
{
|
|
565
|
+
label: "Start from Notebook layout",
|
|
566
|
+
value: "full-notebook",
|
|
567
|
+
hint: "useful for adjusting small details."
|
|
568
|
+
}
|
|
569
|
+
]
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
}, { onCancel: () => {
|
|
573
|
+
cancel("Installation Stopped.");
|
|
574
|
+
process.exit(0);
|
|
575
|
+
} });
|
|
576
|
+
const registry = UIRegistries[config.uiLibrary];
|
|
577
|
+
if (result.target === "docs") {
|
|
578
|
+
const targets = [];
|
|
579
|
+
if (result.mode === "minimal") targets.push("fumadocs/ui/layouts/docs-min");
|
|
580
|
+
else targets.push(result.mode === "full-default" ? `${registry}/layouts/docs` : `${registry}/layouts/notebook`);
|
|
581
|
+
await install(targets, installer);
|
|
582
|
+
printNext(...result.mode === "full-notebook" ? [["fumadocs-ui/layouts/notebook", "@/components/layout/notebook"], ["fumadocs-ui/layouts/notebook/page", "@/components/layout/notebook/page"]] : [["fumadocs-ui/layouts/docs", "@/components/layout/docs"], ["fumadocs-ui/layouts/docs/page", "@/components/layout/docs/page"]]);
|
|
583
|
+
}
|
|
584
|
+
if (result.target === "home") {
|
|
585
|
+
await install([`${registry}/layouts/home`], installer);
|
|
586
|
+
printNext(["fumadocs-ui/layouts/home", `@/components/layout/home`]);
|
|
587
|
+
}
|
|
588
|
+
outro(picocolors.bold("Have fun!"));
|
|
612
589
|
}
|
|
613
590
|
function printNext(...maps) {
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
([from, to]) => picocolors2.greenBright(`"${from}" -> "${to}"`)
|
|
622
|
-
)
|
|
623
|
-
].join("\n")
|
|
624
|
-
);
|
|
591
|
+
intro(picocolors.bold("What is Next?"));
|
|
592
|
+
log.info([
|
|
593
|
+
"You can check the installed components in `components`.",
|
|
594
|
+
picocolors.dim("---"),
|
|
595
|
+
"Open your `layout.tsx` files, replace the imports of components:",
|
|
596
|
+
...maps.map(([from, to]) => picocolors.greenBright(`"${from}" -> "${to}"`))
|
|
597
|
+
].join("\n"));
|
|
625
598
|
}
|
|
626
599
|
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
program
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
console.log(picocolors3.redBright("A config file already exists."));
|
|
634
|
-
}
|
|
600
|
+
//#endregion
|
|
601
|
+
//#region src/index.ts
|
|
602
|
+
const program = new Command().option("--config <string>");
|
|
603
|
+
program.name("fumadocs").description("CLI to setup Fumadocs, init a config").version(version).action(async () => {
|
|
604
|
+
if (await initConfig()) console.log(picocolors.green("Initialized a `./cli.json` config file."));
|
|
605
|
+
else console.log(picocolors.redBright("A config file already exists."));
|
|
635
606
|
});
|
|
636
607
|
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) => {
|
|
637
|
-
|
|
638
|
-
await customise(resolver, await createOrLoadConfig(options.config));
|
|
608
|
+
await customise(createClientFromDir(options.dir, await createOrLoadConfig(options.config)));
|
|
639
609
|
});
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
610
|
+
const dirShortcuts = {
|
|
611
|
+
":preview": "https://preview.fumadocs.dev/registry",
|
|
612
|
+
":dev": "http://localhost:3000/registry"
|
|
643
613
|
};
|
|
644
|
-
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(
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
await fs5.mkdir(path4.dirname(output), { recursive: true });
|
|
670
|
-
await fs5.writeFile(output, out);
|
|
671
|
-
} else {
|
|
672
|
-
console.log(out);
|
|
673
|
-
}
|
|
674
|
-
}
|
|
675
|
-
);
|
|
676
|
-
function getResolverFromDir(dir = "https://fumadocs.dev/registry") {
|
|
677
|
-
if (dir in dirShortcuts) dir = dirShortcuts[dir];
|
|
678
|
-
return dir.startsWith("http://") || dir.startsWith("https://") ? remoteResolver(dir) : localResolver(dir);
|
|
614
|
+
program.command("add").description("add a new component to your docs").argument("[components...]", "components to download").option("--dir <string>", "the root url or directory to resolve registry").action(async (input, options) => {
|
|
615
|
+
await add(input, createClientFromDir(options.dir, await createOrLoadConfig(options.config)));
|
|
616
|
+
});
|
|
617
|
+
program.command("tree").argument("[json_or_args]", "JSON output of `tree` command or arguments for the `tree` command").argument("[output]", "output path of file").option("--js", "output as JavaScript file").option("--no-root", "remove the root node").option("--import-name <name>", "where to import components (JS only)").action(async (str, output, { js, root, importName }) => {
|
|
618
|
+
const jsExtensions = [
|
|
619
|
+
".js",
|
|
620
|
+
".tsx",
|
|
621
|
+
".jsx"
|
|
622
|
+
];
|
|
623
|
+
const noRoot = !root;
|
|
624
|
+
let nodes;
|
|
625
|
+
try {
|
|
626
|
+
nodes = JSON.parse(str ?? "");
|
|
627
|
+
} catch {
|
|
628
|
+
nodes = await runTree(str ?? "./");
|
|
629
|
+
}
|
|
630
|
+
const out = js || output && jsExtensions.includes(path.extname(output)) ? treeToJavaScript(nodes, noRoot, importName) : treeToMdx(nodes, noRoot);
|
|
631
|
+
if (output) {
|
|
632
|
+
await fs.mkdir(path.dirname(output), { recursive: true });
|
|
633
|
+
await fs.writeFile(output, out);
|
|
634
|
+
} else console.log(out);
|
|
635
|
+
});
|
|
636
|
+
function createClientFromDir(dir = "https://fumadocs.dev/registry", config) {
|
|
637
|
+
if (dir in dirShortcuts) dir = dirShortcuts[dir];
|
|
638
|
+
return dir.startsWith("http://") || dir.startsWith("https://") ? new HttpRegistryClient(dir, config) : new LocalRegistryClient(dir, config);
|
|
679
639
|
}
|
|
680
640
|
program.parse();
|
|
641
|
+
|
|
642
|
+
//#endregion
|
|
643
|
+
export { };
|
|
644
|
+
//# sourceMappingURL=index.js.map
|