@fumadocs/cli 0.2.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/build/index.d.ts +110 -92
- package/dist/build/index.js +250 -244
- package/dist/index.js +348 -784
- package/package.json +7 -6
- 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",
|
|
@@ -805,19 +377,20 @@ var package_default = {
|
|
|
805
377
|
"types:check": "tsc --noEmit"
|
|
806
378
|
},
|
|
807
379
|
dependencies: {
|
|
808
|
-
"@clack/prompts": "^0.
|
|
380
|
+
"@clack/prompts": "^0.11.0",
|
|
809
381
|
commander: "^14.0.0",
|
|
810
382
|
"package-manager-detector": "^1.3.0",
|
|
811
383
|
picocolors: "^1.1.1",
|
|
812
384
|
tinyexec: "^1.0.1",
|
|
813
|
-
"ts-morph": "^26.0.0"
|
|
385
|
+
"ts-morph": "^26.0.0",
|
|
386
|
+
zod: "^4.0.17"
|
|
814
387
|
},
|
|
815
388
|
devDependencies: {
|
|
816
|
-
|
|
817
|
-
"@types/
|
|
389
|
+
shadcn: "2.9.3-canary.0",
|
|
390
|
+
"@types/node": "24.2.1",
|
|
818
391
|
"eslint-config-custom": "workspace:*",
|
|
819
392
|
tsconfig: "workspace:*",
|
|
820
|
-
tsx: "^4.
|
|
393
|
+
tsx: "^4.20.3"
|
|
821
394
|
},
|
|
822
395
|
publishConfig: {
|
|
823
396
|
access: "public"
|
|
@@ -826,37 +399,44 @@ var package_default = {
|
|
|
826
399
|
|
|
827
400
|
// src/commands/customise.ts
|
|
828
401
|
import {
|
|
829
|
-
cancel
|
|
830
|
-
confirm as
|
|
402
|
+
cancel,
|
|
403
|
+
confirm as confirm3,
|
|
831
404
|
group,
|
|
832
|
-
intro as
|
|
833
|
-
log as
|
|
405
|
+
intro as intro2,
|
|
406
|
+
log as log3,
|
|
834
407
|
outro as outro3,
|
|
835
408
|
select
|
|
836
409
|
} from "@clack/prompts";
|
|
837
|
-
import
|
|
410
|
+
import picocolors2 from "picocolors";
|
|
838
411
|
|
|
839
412
|
// src/commands/add.ts
|
|
840
413
|
import {
|
|
841
|
-
intro
|
|
842
|
-
isCancel as
|
|
843
|
-
log as
|
|
414
|
+
intro,
|
|
415
|
+
isCancel as isCancel3,
|
|
416
|
+
log as log2,
|
|
844
417
|
multiselect,
|
|
845
418
|
outro as outro2,
|
|
846
|
-
spinner as
|
|
419
|
+
spinner as spinner2
|
|
847
420
|
} from "@clack/prompts";
|
|
848
|
-
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
|
+
}
|
|
849
429
|
|
|
850
430
|
// src/utils/add/install-deps.ts
|
|
851
|
-
import { confirm as
|
|
852
|
-
import { x as
|
|
431
|
+
import { confirm as confirm2, isCancel as isCancel2, spinner } from "@clack/prompts";
|
|
432
|
+
import { x as x2 } from "tinyexec";
|
|
853
433
|
|
|
854
434
|
// src/utils/add/get-deps.ts
|
|
855
|
-
import
|
|
435
|
+
import fs4 from "fs/promises";
|
|
856
436
|
async function getDeps() {
|
|
857
437
|
const dependencies = /* @__PURE__ */ new Map();
|
|
858
438
|
if (!await exists("package.json")) return dependencies;
|
|
859
|
-
const content = await
|
|
439
|
+
const content = await fs4.readFile("package.json");
|
|
860
440
|
const parsed = JSON.parse(content.toString());
|
|
861
441
|
if ("dependencies" in parsed && typeof parsed.dependencies === "object") {
|
|
862
442
|
const records = parsed.dependencies;
|
|
@@ -874,30 +454,27 @@ async function getDeps() {
|
|
|
874
454
|
}
|
|
875
455
|
|
|
876
456
|
// src/utils/add/install-deps.ts
|
|
877
|
-
async function installDeps(
|
|
457
|
+
async function installDeps(deps, devDeps) {
|
|
878
458
|
const installed = await getDeps();
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
for (const result of results) {
|
|
882
|
-
Object.assign(deps, result.dependencies);
|
|
883
|
-
Object.assign(devDeps, result.devDependencies);
|
|
459
|
+
function toList(deps2) {
|
|
460
|
+
return Object.entries(deps2).filter(([k]) => !installed.has(k)).map(([k, v]) => v === null || v.length === 0 ? k : `${k}@${v}`);
|
|
884
461
|
}
|
|
885
|
-
const items =
|
|
886
|
-
const devItems =
|
|
462
|
+
const items = toList(deps);
|
|
463
|
+
const devItems = toList(devDeps);
|
|
887
464
|
if (items.length > 0 || devItems.length > 0) {
|
|
888
465
|
const manager = await getPackageManager();
|
|
889
|
-
const value = await
|
|
466
|
+
const value = await confirm2({
|
|
890
467
|
message: `Do you want to install with ${manager}?
|
|
891
468
|
${[...items, ...devItems].map((v) => `- ${v}`).join("\n")}`
|
|
892
469
|
});
|
|
893
|
-
if (
|
|
470
|
+
if (isCancel2(value)) {
|
|
894
471
|
return;
|
|
895
472
|
}
|
|
896
473
|
if (value) {
|
|
897
|
-
const spin =
|
|
474
|
+
const spin = spinner();
|
|
898
475
|
spin.start("Installing dependencies...");
|
|
899
|
-
if (items.length > 0) await
|
|
900
|
-
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"]);
|
|
901
478
|
spin.stop("Dependencies installed.");
|
|
902
479
|
}
|
|
903
480
|
}
|
|
@@ -905,56 +482,68 @@ ${[...items, ...devItems].map((v) => `- ${v}`).join("\n")}`
|
|
|
905
482
|
|
|
906
483
|
// src/commands/add.ts
|
|
907
484
|
async function add(input, resolver, config) {
|
|
485
|
+
const installer = createComponentInstaller({
|
|
486
|
+
resolver,
|
|
487
|
+
config
|
|
488
|
+
});
|
|
908
489
|
let target = input;
|
|
909
490
|
if (input.length === 0) {
|
|
910
|
-
const spin =
|
|
491
|
+
const spin = spinner2();
|
|
911
492
|
spin.start("fetching registry");
|
|
912
|
-
const
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
493
|
+
const indexes = validateRegistryIndex(
|
|
494
|
+
await resolver("_registry.json").catch((e) => {
|
|
495
|
+
log2.error(String(e));
|
|
496
|
+
process.exit(1);
|
|
497
|
+
})
|
|
498
|
+
);
|
|
499
|
+
spin.stop(picocolors.bold(picocolors.greenBright("registry fetched")));
|
|
918
500
|
const value = await multiselect({
|
|
919
501
|
message: "Select components to install",
|
|
920
|
-
options:
|
|
921
|
-
label: item.
|
|
502
|
+
options: indexes.map((item) => ({
|
|
503
|
+
label: item.title,
|
|
922
504
|
value: item.name,
|
|
923
505
|
hint: item.description
|
|
924
506
|
}))
|
|
925
507
|
});
|
|
926
|
-
if (
|
|
508
|
+
if (isCancel3(value)) {
|
|
927
509
|
outro2("Ended");
|
|
928
510
|
return;
|
|
929
511
|
}
|
|
930
512
|
target = value;
|
|
931
513
|
}
|
|
932
|
-
await install(target,
|
|
514
|
+
await install(target, installer);
|
|
933
515
|
}
|
|
934
|
-
async function install(target,
|
|
935
|
-
const
|
|
516
|
+
async function install(target, installer) {
|
|
517
|
+
const dependencies = {};
|
|
518
|
+
const devDependencies = {};
|
|
936
519
|
for (const name of target) {
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
520
|
+
intro(
|
|
521
|
+
picocolors.bold(
|
|
522
|
+
picocolors.inverse(picocolors.cyanBright(`Add Component: ${name}`))
|
|
940
523
|
)
|
|
941
524
|
);
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
525
|
+
try {
|
|
526
|
+
const output = await installer.install(name);
|
|
527
|
+
Object.assign(dependencies, output.dependencies);
|
|
528
|
+
Object.assign(devDependencies, output.devDependencies);
|
|
529
|
+
outro2(picocolors.bold(picocolors.greenBright(`${name} installed`)));
|
|
530
|
+
} catch (e) {
|
|
531
|
+
log2.error(String(e));
|
|
532
|
+
throw e;
|
|
946
533
|
}
|
|
947
|
-
outro2(picocolors3.bold(picocolors3.greenBright(`${name} installed`)));
|
|
948
|
-
outputs.push(output);
|
|
949
534
|
}
|
|
950
|
-
|
|
951
|
-
await installDeps(
|
|
952
|
-
outro2(
|
|
535
|
+
intro(picocolors.bold("New Dependencies"));
|
|
536
|
+
await installDeps(dependencies, devDependencies);
|
|
537
|
+
outro2(picocolors.bold(picocolors.greenBright("Successful")));
|
|
953
538
|
}
|
|
954
539
|
|
|
955
540
|
// src/commands/customise.ts
|
|
956
541
|
async function customise(resolver, config) {
|
|
957
|
-
|
|
542
|
+
intro2(picocolors2.bgBlack(picocolors2.whiteBright("Customise Fumadocs UI")));
|
|
543
|
+
const installer = createComponentInstaller({
|
|
544
|
+
resolver,
|
|
545
|
+
config
|
|
546
|
+
});
|
|
958
547
|
const result = await group(
|
|
959
548
|
{
|
|
960
549
|
target: () => select({
|
|
@@ -998,14 +587,14 @@ async function customise(resolver, config) {
|
|
|
998
587
|
page: async (v) => {
|
|
999
588
|
if (v.results.target !== "docs" || v.results.mode === "minimal")
|
|
1000
589
|
return false;
|
|
1001
|
-
return
|
|
590
|
+
return confirm3({
|
|
1002
591
|
message: "Do you want to customise the page component too?"
|
|
1003
592
|
});
|
|
1004
593
|
}
|
|
1005
594
|
},
|
|
1006
595
|
{
|
|
1007
596
|
onCancel: () => {
|
|
1008
|
-
|
|
597
|
+
cancel("Installation Stopped.");
|
|
1009
598
|
process.exit(0);
|
|
1010
599
|
}
|
|
1011
600
|
}
|
|
@@ -1025,72 +614,47 @@ async function customise(resolver, config) {
|
|
|
1025
614
|
result.mode === "full-default" ? "layouts/docs" : "layouts/notebook"
|
|
1026
615
|
);
|
|
1027
616
|
}
|
|
1028
|
-
await install(targets,
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
"`fumadocs-ui/layouts/docs` -> `@/components/layouts/docs`"
|
|
1037
|
-
),
|
|
1038
|
-
pageAdded ? picocolors4.greenBright(
|
|
1039
|
-
"`fumadocs-ui/page` -> `@/components/layouts/page`"
|
|
1040
|
-
) : ""
|
|
1041
|
-
].join("\n")
|
|
1042
|
-
);
|
|
617
|
+
await install(targets, installer);
|
|
618
|
+
const maps = [
|
|
619
|
+
["fumadocs-ui/layouts/docs", "@/components/layout/docs"]
|
|
620
|
+
];
|
|
621
|
+
if (pageAdded) {
|
|
622
|
+
maps.push(["fumadocs-ui/page", "@/components/layout/page"]);
|
|
623
|
+
}
|
|
624
|
+
printNext(...maps);
|
|
1043
625
|
}
|
|
1044
626
|
if (result.target === "home") {
|
|
1045
|
-
await install(["layouts/home"],
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
627
|
+
await install(["layouts/home"], installer);
|
|
628
|
+
printNext(["fumadocs-ui/layouts/home", `@/components/layout/home`]);
|
|
629
|
+
}
|
|
630
|
+
outro3(picocolors2.bold("Have fun!"));
|
|
631
|
+
}
|
|
632
|
+
function printNext(...maps) {
|
|
633
|
+
intro2(picocolors2.bold("What is Next?"));
|
|
634
|
+
log3.info(
|
|
635
|
+
[
|
|
636
|
+
"You can check the installed components in `components`.",
|
|
637
|
+
picocolors2.dim("---"),
|
|
638
|
+
"Open your `layout.tsx` files, replace the imports of components:",
|
|
639
|
+
...maps.map(
|
|
640
|
+
([from, to]) => picocolors2.greenBright(`"${from}" -> "${to}"`)
|
|
641
|
+
)
|
|
642
|
+
].join("\n")
|
|
643
|
+
);
|
|
1059
644
|
}
|
|
1060
645
|
|
|
1061
646
|
// src/index.ts
|
|
1062
647
|
var program = new Command().option("--config <string>");
|
|
1063
|
-
program.name("fumadocs").description("CLI to setup Fumadocs, init a config
|
|
648
|
+
program.name("fumadocs").description("CLI to setup Fumadocs, init a config").version(package_default.version).action(async () => {
|
|
1064
649
|
if (await initConfig()) {
|
|
1065
|
-
console.log(
|
|
650
|
+
console.log(picocolors3.green("Initialized a `./cli.json` config file."));
|
|
1066
651
|
} else {
|
|
1067
|
-
console.log(
|
|
652
|
+
console.log(picocolors3.redBright("A config file already exists."));
|
|
1068
653
|
}
|
|
1069
654
|
});
|
|
1070
655
|
program.command("customise").alias("customize").description("simple way to customise layouts with Fumadocs UI").option("--dir <string>", "the root url or directory to resolve registry").action(async (options) => {
|
|
1071
656
|
const resolver = getResolverFromDir(options.dir);
|
|
1072
|
-
await customise(resolver, await
|
|
1073
|
-
});
|
|
1074
|
-
program.command("init").description("init a new plugin to your docs").argument("[string]", "plugin name").action(async (str, { config }) => {
|
|
1075
|
-
const loadedConfig = await loadConfig(config);
|
|
1076
|
-
if (str) {
|
|
1077
|
-
const plugin = str in plugins ? plugins[str] : void 0;
|
|
1078
|
-
if (!plugin) throw new Error(`Plugin not found: ${str}`);
|
|
1079
|
-
await init(plugin, loadedConfig);
|
|
1080
|
-
return;
|
|
1081
|
-
}
|
|
1082
|
-
const value = await select2({
|
|
1083
|
-
message: "Select components to install",
|
|
1084
|
-
options: Object.keys(plugins).map((c) => ({
|
|
1085
|
-
label: c,
|
|
1086
|
-
value: c
|
|
1087
|
-
}))
|
|
1088
|
-
});
|
|
1089
|
-
if (isCancel5(value)) {
|
|
1090
|
-
outro4("Ended");
|
|
1091
|
-
return;
|
|
1092
|
-
}
|
|
1093
|
-
await init(plugins[value], loadedConfig);
|
|
657
|
+
await customise(resolver, await createOrLoadConfig(options.config));
|
|
1094
658
|
});
|
|
1095
659
|
var dirShortcuts = {
|
|
1096
660
|
":dev": "https://preview.fumadocs.dev/registry",
|
|
@@ -1099,7 +663,7 @@ var dirShortcuts = {
|
|
|
1099
663
|
program.command("add").description("add a new component to your docs").argument("[components...]", "components to download").option("--dir <string>", "the root url or directory to resolve registry").action(
|
|
1100
664
|
async (input, options) => {
|
|
1101
665
|
const resolver = getResolverFromDir(options.dir);
|
|
1102
|
-
await add(input, resolver, await
|
|
666
|
+
await add(input, resolver, await createOrLoadConfig(options.config));
|
|
1103
667
|
}
|
|
1104
668
|
);
|
|
1105
669
|
program.command("tree").argument(
|
|
@@ -1119,10 +683,10 @@ program.command("tree").argument(
|
|
|
1119
683
|
} catch {
|
|
1120
684
|
nodes = await runTree(str ?? "./");
|
|
1121
685
|
}
|
|
1122
|
-
const out = js || output && jsExtensions.includes(
|
|
686
|
+
const out = js || output && jsExtensions.includes(path4.extname(output)) ? treeToJavaScript(nodes, noRoot, importName) : treeToMdx(nodes, noRoot);
|
|
1123
687
|
if (output) {
|
|
1124
|
-
await
|
|
1125
|
-
await
|
|
688
|
+
await fs5.mkdir(path4.dirname(output), { recursive: true });
|
|
689
|
+
await fs5.writeFile(output, out);
|
|
1126
690
|
} else {
|
|
1127
691
|
console.log(out);
|
|
1128
692
|
}
|