@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/build/index.d.ts +110 -92
- package/dist/build/index.js +250 -244
- package/dist/index.js +347 -782
- package/package.json +6 -4
- package/dist/chunk-DG6SFM2G.js +0 -19
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
|
|
9
|
-
import
|
|
4
|
+
import fs5 from "fs/promises";
|
|
5
|
+
import path4 from "path";
|
|
10
6
|
import { Command } from "commander";
|
|
11
|
-
import
|
|
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/
|
|
9
|
+
// src/utils/add/install-component.ts
|
|
10
|
+
import path2 from "path";
|
|
46
11
|
import fs from "fs/promises";
|
|
47
|
-
|
|
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/
|
|
73
|
-
import
|
|
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
|
|
87
|
-
const extname =
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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/
|
|
129
|
-
import {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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/
|
|
137
|
-
|
|
138
|
-
|
|
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
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|
-
|
|
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
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
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
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
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
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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
|
-
|
|
281
|
-
|
|
282
|
-
|
|
191
|
+
return sourceFile.getFullText();
|
|
192
|
+
},
|
|
193
|
+
resolveOutputPath(ref) {
|
|
194
|
+
if (ref.target) {
|
|
195
|
+
return path2.join(config.baseDir, ref.target);
|
|
283
196
|
}
|
|
284
|
-
|
|
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
|
-
|
|
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)
|
|
332
|
-
|
|
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
|
-
|
|
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/
|
|
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/
|
|
430
|
-
import
|
|
431
|
-
import
|
|
432
|
-
|
|
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
|
-
|
|
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/
|
|
465
|
-
|
|
466
|
-
|
|
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
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
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
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
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
|
|
620
|
-
|
|
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
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
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
|
|
331
|
+
import { x } from "tinyexec";
|
|
760
332
|
async function runTree(args) {
|
|
761
|
-
const out = await
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|
|
829
|
-
confirm as
|
|
402
|
+
cancel,
|
|
403
|
+
confirm as confirm3,
|
|
830
404
|
group,
|
|
831
|
-
intro as
|
|
832
|
-
log as
|
|
405
|
+
intro as intro2,
|
|
406
|
+
log as log3,
|
|
833
407
|
outro as outro3,
|
|
834
408
|
select
|
|
835
409
|
} from "@clack/prompts";
|
|
836
|
-
import
|
|
410
|
+
import picocolors2 from "picocolors";
|
|
837
411
|
|
|
838
412
|
// src/commands/add.ts
|
|
839
413
|
import {
|
|
840
|
-
intro
|
|
841
|
-
isCancel as
|
|
842
|
-
log as
|
|
414
|
+
intro,
|
|
415
|
+
isCancel as isCancel3,
|
|
416
|
+
log as log2,
|
|
843
417
|
multiselect,
|
|
844
418
|
outro as outro2,
|
|
845
|
-
spinner as
|
|
419
|
+
spinner as spinner2
|
|
846
420
|
} from "@clack/prompts";
|
|
847
|
-
import
|
|
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
|
|
851
|
-
import { x as
|
|
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
|
|
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
|
|
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(
|
|
457
|
+
async function installDeps(deps, devDeps) {
|
|
877
458
|
const installed = await getDeps();
|
|
878
|
-
|
|
879
|
-
|
|
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 =
|
|
885
|
-
const devItems =
|
|
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
|
|
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 (
|
|
470
|
+
if (isCancel2(value)) {
|
|
893
471
|
return;
|
|
894
472
|
}
|
|
895
473
|
if (value) {
|
|
896
|
-
const spin =
|
|
474
|
+
const spin = spinner();
|
|
897
475
|
spin.start("Installing dependencies...");
|
|
898
|
-
if (items.length > 0) await
|
|
899
|
-
if (devItems.length > 0) await
|
|
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 =
|
|
491
|
+
const spin = spinner2();
|
|
910
492
|
spin.start("fetching registry");
|
|
911
|
-
const
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
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:
|
|
920
|
-
label: item.
|
|
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 (
|
|
508
|
+
if (isCancel3(value)) {
|
|
926
509
|
outro2("Ended");
|
|
927
510
|
return;
|
|
928
511
|
}
|
|
929
512
|
target = value;
|
|
930
513
|
}
|
|
931
|
-
await install(target,
|
|
514
|
+
await install(target, installer);
|
|
932
515
|
}
|
|
933
|
-
async function install(target,
|
|
934
|
-
const
|
|
516
|
+
async function install(target, installer) {
|
|
517
|
+
const dependencies = {};
|
|
518
|
+
const devDependencies = {};
|
|
935
519
|
for (const name of target) {
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
520
|
+
intro(
|
|
521
|
+
picocolors.bold(
|
|
522
|
+
picocolors.inverse(picocolors.cyanBright(`Add Component: ${name}`))
|
|
939
523
|
)
|
|
940
524
|
);
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
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
|
-
|
|
950
|
-
await installDeps(
|
|
951
|
-
outro2(
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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,
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
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"],
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
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
|
|
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(
|
|
650
|
+
console.log(picocolors3.green("Initialized a `./cli.json` config file."));
|
|
1065
651
|
} else {
|
|
1066
|
-
console.log(
|
|
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
|
|
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
|
|
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(
|
|
686
|
+
const out = js || output && jsExtensions.includes(path4.extname(output)) ? treeToJavaScript(nodes, noRoot, importName) : treeToMdx(nodes, noRoot);
|
|
1122
687
|
if (output) {
|
|
1123
|
-
await
|
|
1124
|
-
await
|
|
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
|
}
|