@lightfish/cli 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
@@ -0,0 +1,367 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/create-lightfish.ts
4
+ import cac from "cac";
5
+
6
+ // src/commands/create.ts
7
+ import path3 from "path";
8
+ import { execa as execa2 } from "execa";
9
+ import fs3 from "fs-extra";
10
+ import prompts3 from "prompts";
11
+
12
+ // src/utils/download-template.ts
13
+ import path from "path";
14
+ import { tmpdir } from "os";
15
+ import { randomUUID } from "crypto";
16
+ import { execa } from "execa";
17
+ import degit from "degit";
18
+ import fs from "fs-extra";
19
+ function parseDegitRepo(repo) {
20
+ const [repoPart, branch] = repo.split("#");
21
+ let url;
22
+ if (repoPart.startsWith("http://") || repoPart.startsWith("https://")) {
23
+ url = repoPart.replace(/\.git$/, "") + ".git";
24
+ } else if (repoPart.includes("/")) {
25
+ url = `https://github.com/${repoPart}.git`;
26
+ } else {
27
+ throw new Error(`\u65E0\u6CD5\u89E3\u6790\u4ED3\u5E93\u5730\u5740: ${repo}`);
28
+ }
29
+ return { url, branch };
30
+ }
31
+ async function gitClone(url, branch, targetDir) {
32
+ const tmpDir = path.join(tmpdir(), `lightfish-template-${randomUUID()}`);
33
+ await fs.ensureDir(tmpDir);
34
+ const args = ["clone", "--depth", "1"];
35
+ if (branch) {
36
+ args.push("--branch", branch);
37
+ }
38
+ args.push(url, tmpDir);
39
+ try {
40
+ await execa("git", args, { stdio: "inherit" });
41
+ } catch (error) {
42
+ await fs.remove(tmpDir).catch(() => {
43
+ });
44
+ const detail = error instanceof Error ? error.message : String(error);
45
+ throw new Error(`Git \u514B\u9686\u5931\u8D25\uFF08${url}\uFF09\uFF1A${detail}`);
46
+ }
47
+ const entries = await fs.readdir(tmpDir);
48
+ for (const entry of entries) {
49
+ if (entry === ".git") continue;
50
+ const src = path.join(tmpDir, entry);
51
+ const dest = path.join(targetDir, entry);
52
+ await fs.move(src, dest, { overwrite: true });
53
+ }
54
+ await fs.remove(tmpDir).catch(() => {
55
+ });
56
+ }
57
+ async function downloadTemplate(repo, targetDir) {
58
+ await fs.ensureDir(targetDir);
59
+ const emitter = degit(repo, {
60
+ cache: false,
61
+ // 禁用缓存,避免使用损坏的本地缓存
62
+ force: true,
63
+ verbose: true
64
+ // 开启详细日志,方便调试
65
+ });
66
+ try {
67
+ console.log(`\u6B63\u5728\u901A\u8FC7 degit \u62C9\u53D6\u6A21\u677F: ${repo}`);
68
+ await emitter.clone(targetDir);
69
+ console.log("degit \u62C9\u53D6\u5B8C\u6210");
70
+ } catch (error) {
71
+ const detail = error instanceof Error ? error.message : String(error);
72
+ await fs.emptyDir(targetDir);
73
+ const { url, branch } = parseDegitRepo(repo);
74
+ console.log(`\u6B63\u5728\u901A\u8FC7 git clone \u62C9\u53D6\u6A21\u677F: ${url}${branch ? ` (\u5206\u652F: ${branch})` : ""}`);
75
+ await gitClone(url, branch, targetDir);
76
+ console.log("git clone \u62C9\u53D6\u5B8C\u6210");
77
+ }
78
+ const gitDir = path.join(targetDir, ".git");
79
+ if (await fs.pathExists(gitDir)) {
80
+ await fs.remove(gitDir);
81
+ console.log("\u5DF2\u5220\u9664 .git \u76EE\u5F55");
82
+ }
83
+ }
84
+
85
+ // src/utils/package-manager.ts
86
+ function isPackageManager(value) {
87
+ return value === "npm" || value === "yarn" || value === "pnpm" || value === "bun";
88
+ }
89
+ function installCommand(packageManager) {
90
+ switch (packageManager) {
91
+ case "npm":
92
+ return ["npm", ["install"]];
93
+ case "yarn":
94
+ return ["yarn", []];
95
+ case "bun":
96
+ return ["bun", ["install"]];
97
+ case "pnpm":
98
+ default:
99
+ return ["pnpm", ["install"]];
100
+ }
101
+ }
102
+
103
+ // src/utils/project.ts
104
+ import path2 from "path";
105
+ import fs2 from "fs-extra";
106
+ import prompts from "prompts";
107
+ function normalizeProjectName(name) {
108
+ return name.trim();
109
+ }
110
+ function resolveTargetDir(cwd, projectName) {
111
+ return path2.resolve(cwd, projectName);
112
+ }
113
+ async function assertCanCreateProject(targetDir, yes) {
114
+ if (!await fs2.pathExists(targetDir)) {
115
+ return true;
116
+ }
117
+ const files = await fs2.readdir(targetDir);
118
+ if (files.length === 0) {
119
+ return true;
120
+ }
121
+ if (yes) {
122
+ await fs2.remove(targetDir);
123
+ await fs2.ensureDir(targetDir);
124
+ return true;
125
+ }
126
+ const { overwrite } = await prompts(
127
+ {
128
+ type: "confirm",
129
+ name: "overwrite",
130
+ message: `\u76EE\u6807\u76EE\u5F55\u5DF2\u5B58\u5728\u4E14\u4E0D\u4E3A\u7A7A\uFF1A${targetDir}\uFF0C\u662F\u5426\u8986\u76D6\uFF1F`,
131
+ initial: false
132
+ },
133
+ {
134
+ onCancel() {
135
+ throw new Error("\u5DF2\u53D6\u6D88\u521B\u5EFA\u9879\u76EE");
136
+ }
137
+ }
138
+ );
139
+ if (!overwrite) {
140
+ return false;
141
+ }
142
+ await fs2.remove(targetDir);
143
+ await fs2.ensureDir(targetDir);
144
+ return true;
145
+ }
146
+ function toPackageName(projectName) {
147
+ return projectName.trim().toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
148
+ }
149
+
150
+ // src/utils/resolve-template.ts
151
+ import prompts2 from "prompts";
152
+
153
+ // src/templates/registry.ts
154
+ var BUILTIN_TEMPLATES = [
155
+ {
156
+ id: "react-vite",
157
+ label: "React + Vite",
158
+ repo: "QGtiger/template-vite-server#main"
159
+ }
160
+ ];
161
+ var DEFAULT_TEMPLATE_ID = BUILTIN_TEMPLATES[0]?.id;
162
+ function getTemplateById(id) {
163
+ return BUILTIN_TEMPLATES.find((entry) => entry.id === id);
164
+ }
165
+ function listTemplateIds() {
166
+ return BUILTIN_TEMPLATES.map((entry) => entry.id);
167
+ }
168
+
169
+ // src/utils/resolve-template.ts
170
+ async function promptTemplateId() {
171
+ const { templateId } = await prompts2(
172
+ {
173
+ type: "select",
174
+ name: "templateId",
175
+ message: "\u9009\u62E9\u9879\u76EE\u6A21\u677F",
176
+ choices: BUILTIN_TEMPLATES.map((entry) => ({
177
+ title: entry.label,
178
+ value: entry.id
179
+ }))
180
+ },
181
+ {
182
+ onCancel() {
183
+ throw new Error("\u5DF2\u53D6\u6D88\u521B\u5EFA\u9879\u76EE");
184
+ }
185
+ }
186
+ );
187
+ if (!templateId) {
188
+ throw new Error("\u672A\u9009\u62E9\u6A21\u677F");
189
+ }
190
+ return templateId;
191
+ }
192
+ async function resolveTemplateRepo(options) {
193
+ if (options.templateRepo) {
194
+ return options.templateRepo;
195
+ }
196
+ if (options.template) {
197
+ const entry2 = getTemplateById(options.template);
198
+ if (!entry2) {
199
+ throw new Error(`\u672A\u77E5\u6A21\u677F\uFF1A${options.template}\u3002\u53EF\u9009\uFF1A${listTemplateIds().join("\u3001")}`);
200
+ }
201
+ return entry2.repo;
202
+ }
203
+ if (options.yes) {
204
+ const entry2 = getTemplateById(DEFAULT_TEMPLATE_ID ?? BUILTIN_TEMPLATES[0].id);
205
+ if (!entry2) {
206
+ throw new Error("\u5185\u7F6E\u6A21\u677F\u5217\u8868\u4E3A\u7A7A");
207
+ }
208
+ return entry2.repo;
209
+ }
210
+ const templateId = await promptTemplateId();
211
+ const entry = getTemplateById(templateId);
212
+ if (!entry) {
213
+ throw new Error(`\u672A\u77E5\u6A21\u677F\uFF1A${templateId}`);
214
+ }
215
+ return entry.repo;
216
+ }
217
+
218
+ // src/commands/create.ts
219
+ async function patchPackageJsonName(targetDir, projectName) {
220
+ const packageJsonPath = path3.join(targetDir, "package.json");
221
+ if (!await fs3.pathExists(packageJsonPath)) {
222
+ throw new Error("\u6A21\u677F\u7F3A\u5C11 package.json");
223
+ }
224
+ const packageJson = await fs3.readJson(packageJsonPath);
225
+ packageJson.name = toPackageName(projectName);
226
+ await fs3.writeJson(packageJsonPath, packageJson, { spaces: 2 });
227
+ }
228
+ async function resolveCreateOptions(options) {
229
+ const cwd = options.cwd ?? process.cwd();
230
+ const initialProjectName = options.projectName ? normalizeProjectName(options.projectName) : "";
231
+ const answers = await prompts3(
232
+ [
233
+ {
234
+ type: initialProjectName ? null : "text",
235
+ name: "projectName",
236
+ message: "\u9879\u76EE\u540D",
237
+ initial: "lightfish-app"
238
+ },
239
+ {
240
+ type: options.install !== void 0 || options.skipInstall || options.yes ? null : "confirm",
241
+ name: "install",
242
+ message: "\u662F\u5426\u7ACB\u5373\u5B89\u88C5\u4F9D\u8D56\uFF1F",
243
+ initial: false
244
+ },
245
+ {
246
+ type: options.git !== void 0 || options.yes ? null : "confirm",
247
+ name: "git",
248
+ message: "\u662F\u5426\u521D\u59CB\u5316 git\uFF1F",
249
+ initial: false
250
+ }
251
+ ],
252
+ {
253
+ onCancel() {
254
+ throw new Error("\u5DF2\u53D6\u6D88\u521B\u5EFA\u9879\u76EE");
255
+ }
256
+ }
257
+ );
258
+ const projectName = normalizeProjectName(initialProjectName || answers.projectName);
259
+ if (!projectName) {
260
+ throw new Error("\u9879\u76EE\u540D\u4E0D\u80FD\u4E3A\u7A7A");
261
+ }
262
+ const packageManager = options.packageManager ?? "pnpm";
263
+ if (!isPackageManager(packageManager)) {
264
+ throw new Error(`\u4E0D\u652F\u6301\u7684\u5305\u7BA1\u7406\u5668\uFF1A${String(packageManager)}`);
265
+ }
266
+ const templateRepo = await resolveTemplateRepo({
267
+ template: options.template,
268
+ templateRepo: options.templateRepo,
269
+ yes: options.yes
270
+ });
271
+ return {
272
+ cwd,
273
+ projectName,
274
+ targetDir: resolveTargetDir(cwd, projectName),
275
+ packageManager,
276
+ templateRepo,
277
+ install: options.skipInstall ? false : options.install ?? (options.yes ? false : answers.install ?? false),
278
+ git: options.git ?? answers.git ?? false
279
+ };
280
+ }
281
+ async function maybeInstall(options) {
282
+ if (!options.install) {
283
+ return;
284
+ }
285
+ const [command, args] = installCommand(options.packageManager);
286
+ await execa2(command, args, {
287
+ cwd: options.targetDir,
288
+ stdio: "inherit"
289
+ });
290
+ }
291
+ async function maybeInitGit(options) {
292
+ if (!options.git) {
293
+ return;
294
+ }
295
+ await execa2("git", ["init"], {
296
+ cwd: options.targetDir,
297
+ stdio: "inherit"
298
+ });
299
+ }
300
+ async function createProject(options = {}) {
301
+ const resolvedOptions = await resolveCreateOptions(options);
302
+ const canCreate = await assertCanCreateProject(resolvedOptions.targetDir, options.yes);
303
+ if (!canCreate) {
304
+ throw new Error("\u5DF2\u53D6\u6D88\u521B\u5EFA\u9879\u76EE");
305
+ }
306
+ console.log("[create] \u5F00\u59CB\u4E0B\u8F7D\u6A21\u677F...");
307
+ await downloadTemplate(resolvedOptions.templateRepo, resolvedOptions.targetDir);
308
+ console.log("[create] \u6A21\u677F\u4E0B\u8F7D\u5B8C\u6210");
309
+ console.log("[create] \u5F00\u59CB\u4FEE\u6539 package.json...");
310
+ await patchPackageJsonName(resolvedOptions.targetDir, resolvedOptions.projectName);
311
+ console.log("[create] package.json \u4FEE\u6539\u5B8C\u6210");
312
+ console.log("[create] \u5F00\u59CB\u5B89\u88C5\u4F9D\u8D56...");
313
+ await maybeInstall(resolvedOptions);
314
+ console.log("[create] \u4F9D\u8D56\u5B89\u88C5\u5B8C\u6210");
315
+ console.log("[create] \u5F00\u59CB\u521D\u59CB\u5316 git...");
316
+ await maybeInitGit(resolvedOptions);
317
+ console.log("[create] git \u521D\u59CB\u5316\u5B8C\u6210");
318
+ return resolvedOptions;
319
+ }
320
+
321
+ // src/utils/package-info.ts
322
+ import fs4 from "fs";
323
+ import path4 from "path";
324
+ import { fileURLToPath } from "url";
325
+ var dirname = path4.dirname(fileURLToPath(import.meta.url));
326
+ function readPackageVersion() {
327
+ const packageJsonPathCandidates = [
328
+ // 源码模式:src/utils/package-info.ts -> packages/cli/package.json
329
+ path4.resolve(dirname, "../../package.json"),
330
+ // 构建产物模式:dist/lightfish.js -> packages/cli/package.json
331
+ path4.resolve(dirname, "../package.json")
332
+ ];
333
+ const packageJsonPath = packageJsonPathCandidates.find((candidate) => fs4.existsSync(candidate));
334
+ if (!packageJsonPath) {
335
+ return "0.0.0";
336
+ }
337
+ const packageJson = JSON.parse(fs4.readFileSync(packageJsonPath, "utf8"));
338
+ return packageJson.version ?? "0.0.0";
339
+ }
340
+
341
+ // src/create-lightfish.ts
342
+ var cli = cac("create-lightfish");
343
+ cli.command("[projectName]", "\u521B\u5EFA Lightfish React Vite \u9879\u76EE").option("--pm <pm>", "\u5305\u7BA1\u7406\u5668\uFF1Anpm\u3001yarn\u3001pnpm\u3001bun", { default: "pnpm" }).option("--template <id>", "\u5185\u7F6E\u6A21\u677F id\uFF0C\u4F8B\u5982 react-vite").option("--template-repo <repo>", "\u76F4\u63A5\u6307\u5B9A degit \u6E90\uFF0C\u5982 QGtiger/template-vite-server#main\uFF08\u4F18\u5148\u4E8E --template\uFF09").option("--skip-install", "\u8DF3\u8FC7\u4F9D\u8D56\u5B89\u88C5").option("--git", "\u521B\u5EFA\u540E\u521D\u59CB\u5316 git").option("--yes", "\u4F7F\u7528\u9ED8\u8BA4\u9009\u9879\u5E76\u8DF3\u8FC7\u786E\u8BA4").action(async (projectName, options) => {
344
+ try {
345
+ const packageManager = options.pm ?? "pnpm";
346
+ if (!isPackageManager(packageManager)) {
347
+ throw new Error(`\u4E0D\u652F\u6301\u7684\u5305\u7BA1\u7406\u5668\uFF1A${packageManager}`);
348
+ }
349
+ await createProject({
350
+ projectName,
351
+ packageManager,
352
+ template: options.template,
353
+ templateRepo: options.templateRepo,
354
+ skipInstall: options.skipInstall,
355
+ git: options.git,
356
+ yes: options.yes
357
+ });
358
+ process.exit(0);
359
+ } catch (error) {
360
+ const message = error instanceof Error ? error.message : String(error);
361
+ console.error(`\u521B\u5EFA\u5931\u8D25\uFF1A${message}`);
362
+ process.exit(1);
363
+ }
364
+ });
365
+ cli.help();
366
+ cli.version(readPackageVersion());
367
+ cli.parse();
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
@@ -0,0 +1,387 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/lightfish.ts
4
+ import cac from "cac";
5
+ import { execa as execa3 } from "execa";
6
+
7
+ // src/commands/create.ts
8
+ import path3 from "path";
9
+ import { execa as execa2 } from "execa";
10
+ import fs3 from "fs-extra";
11
+ import prompts3 from "prompts";
12
+
13
+ // src/utils/download-template.ts
14
+ import path from "path";
15
+ import { tmpdir } from "os";
16
+ import { randomUUID } from "crypto";
17
+ import { execa } from "execa";
18
+ import degit from "degit";
19
+ import fs from "fs-extra";
20
+ function parseDegitRepo(repo) {
21
+ const [repoPart, branch] = repo.split("#");
22
+ let url;
23
+ if (repoPart.startsWith("http://") || repoPart.startsWith("https://")) {
24
+ url = repoPart.replace(/\.git$/, "") + ".git";
25
+ } else if (repoPart.includes("/")) {
26
+ url = `https://github.com/${repoPart}.git`;
27
+ } else {
28
+ throw new Error(`\u65E0\u6CD5\u89E3\u6790\u4ED3\u5E93\u5730\u5740: ${repo}`);
29
+ }
30
+ return { url, branch };
31
+ }
32
+ async function gitClone(url, branch, targetDir) {
33
+ const tmpDir = path.join(tmpdir(), `lightfish-template-${randomUUID()}`);
34
+ await fs.ensureDir(tmpDir);
35
+ const args = ["clone", "--depth", "1"];
36
+ if (branch) {
37
+ args.push("--branch", branch);
38
+ }
39
+ args.push(url, tmpDir);
40
+ try {
41
+ await execa("git", args, { stdio: "inherit" });
42
+ } catch (error) {
43
+ await fs.remove(tmpDir).catch(() => {
44
+ });
45
+ const detail = error instanceof Error ? error.message : String(error);
46
+ throw new Error(`Git \u514B\u9686\u5931\u8D25\uFF08${url}\uFF09\uFF1A${detail}`);
47
+ }
48
+ const entries = await fs.readdir(tmpDir);
49
+ for (const entry of entries) {
50
+ if (entry === ".git") continue;
51
+ const src = path.join(tmpDir, entry);
52
+ const dest = path.join(targetDir, entry);
53
+ await fs.move(src, dest, { overwrite: true });
54
+ }
55
+ await fs.remove(tmpDir).catch(() => {
56
+ });
57
+ }
58
+ async function downloadTemplate(repo, targetDir) {
59
+ await fs.ensureDir(targetDir);
60
+ const emitter = degit(repo, {
61
+ cache: false,
62
+ // 禁用缓存,避免使用损坏的本地缓存
63
+ force: true,
64
+ verbose: true
65
+ // 开启详细日志,方便调试
66
+ });
67
+ try {
68
+ console.log(`\u6B63\u5728\u901A\u8FC7 degit \u62C9\u53D6\u6A21\u677F: ${repo}`);
69
+ await emitter.clone(targetDir);
70
+ console.log("degit \u62C9\u53D6\u5B8C\u6210");
71
+ } catch (error) {
72
+ const detail = error instanceof Error ? error.message : String(error);
73
+ await fs.emptyDir(targetDir);
74
+ const { url, branch } = parseDegitRepo(repo);
75
+ console.log(`\u6B63\u5728\u901A\u8FC7 git clone \u62C9\u53D6\u6A21\u677F: ${url}${branch ? ` (\u5206\u652F: ${branch})` : ""}`);
76
+ await gitClone(url, branch, targetDir);
77
+ console.log("git clone \u62C9\u53D6\u5B8C\u6210");
78
+ }
79
+ const gitDir = path.join(targetDir, ".git");
80
+ if (await fs.pathExists(gitDir)) {
81
+ await fs.remove(gitDir);
82
+ console.log("\u5DF2\u5220\u9664 .git \u76EE\u5F55");
83
+ }
84
+ }
85
+
86
+ // src/utils/package-manager.ts
87
+ function isPackageManager(value) {
88
+ return value === "npm" || value === "yarn" || value === "pnpm" || value === "bun";
89
+ }
90
+ function installCommand(packageManager) {
91
+ switch (packageManager) {
92
+ case "npm":
93
+ return ["npm", ["install"]];
94
+ case "yarn":
95
+ return ["yarn", []];
96
+ case "bun":
97
+ return ["bun", ["install"]];
98
+ case "pnpm":
99
+ default:
100
+ return ["pnpm", ["install"]];
101
+ }
102
+ }
103
+
104
+ // src/utils/project.ts
105
+ import path2 from "path";
106
+ import fs2 from "fs-extra";
107
+ import prompts from "prompts";
108
+ function normalizeProjectName(name) {
109
+ return name.trim();
110
+ }
111
+ function resolveTargetDir(cwd, projectName) {
112
+ return path2.resolve(cwd, projectName);
113
+ }
114
+ async function assertCanCreateProject(targetDir, yes) {
115
+ if (!await fs2.pathExists(targetDir)) {
116
+ return true;
117
+ }
118
+ const files = await fs2.readdir(targetDir);
119
+ if (files.length === 0) {
120
+ return true;
121
+ }
122
+ if (yes) {
123
+ await fs2.remove(targetDir);
124
+ await fs2.ensureDir(targetDir);
125
+ return true;
126
+ }
127
+ const { overwrite } = await prompts(
128
+ {
129
+ type: "confirm",
130
+ name: "overwrite",
131
+ message: `\u76EE\u6807\u76EE\u5F55\u5DF2\u5B58\u5728\u4E14\u4E0D\u4E3A\u7A7A\uFF1A${targetDir}\uFF0C\u662F\u5426\u8986\u76D6\uFF1F`,
132
+ initial: false
133
+ },
134
+ {
135
+ onCancel() {
136
+ throw new Error("\u5DF2\u53D6\u6D88\u521B\u5EFA\u9879\u76EE");
137
+ }
138
+ }
139
+ );
140
+ if (!overwrite) {
141
+ return false;
142
+ }
143
+ await fs2.remove(targetDir);
144
+ await fs2.ensureDir(targetDir);
145
+ return true;
146
+ }
147
+ function toPackageName(projectName) {
148
+ return projectName.trim().toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
149
+ }
150
+
151
+ // src/utils/resolve-template.ts
152
+ import prompts2 from "prompts";
153
+
154
+ // src/templates/registry.ts
155
+ var BUILTIN_TEMPLATES = [
156
+ {
157
+ id: "react-vite",
158
+ label: "React + Vite",
159
+ repo: "QGtiger/template-vite-server#main"
160
+ }
161
+ ];
162
+ var DEFAULT_TEMPLATE_ID = BUILTIN_TEMPLATES[0]?.id;
163
+ function getTemplateById(id) {
164
+ return BUILTIN_TEMPLATES.find((entry) => entry.id === id);
165
+ }
166
+ function listTemplateIds() {
167
+ return BUILTIN_TEMPLATES.map((entry) => entry.id);
168
+ }
169
+
170
+ // src/utils/resolve-template.ts
171
+ async function promptTemplateId() {
172
+ const { templateId } = await prompts2(
173
+ {
174
+ type: "select",
175
+ name: "templateId",
176
+ message: "\u9009\u62E9\u9879\u76EE\u6A21\u677F",
177
+ choices: BUILTIN_TEMPLATES.map((entry) => ({
178
+ title: entry.label,
179
+ value: entry.id
180
+ }))
181
+ },
182
+ {
183
+ onCancel() {
184
+ throw new Error("\u5DF2\u53D6\u6D88\u521B\u5EFA\u9879\u76EE");
185
+ }
186
+ }
187
+ );
188
+ if (!templateId) {
189
+ throw new Error("\u672A\u9009\u62E9\u6A21\u677F");
190
+ }
191
+ return templateId;
192
+ }
193
+ async function resolveTemplateRepo(options) {
194
+ if (options.templateRepo) {
195
+ return options.templateRepo;
196
+ }
197
+ if (options.template) {
198
+ const entry2 = getTemplateById(options.template);
199
+ if (!entry2) {
200
+ throw new Error(`\u672A\u77E5\u6A21\u677F\uFF1A${options.template}\u3002\u53EF\u9009\uFF1A${listTemplateIds().join("\u3001")}`);
201
+ }
202
+ return entry2.repo;
203
+ }
204
+ if (options.yes) {
205
+ const entry2 = getTemplateById(DEFAULT_TEMPLATE_ID ?? BUILTIN_TEMPLATES[0].id);
206
+ if (!entry2) {
207
+ throw new Error("\u5185\u7F6E\u6A21\u677F\u5217\u8868\u4E3A\u7A7A");
208
+ }
209
+ return entry2.repo;
210
+ }
211
+ const templateId = await promptTemplateId();
212
+ const entry = getTemplateById(templateId);
213
+ if (!entry) {
214
+ throw new Error(`\u672A\u77E5\u6A21\u677F\uFF1A${templateId}`);
215
+ }
216
+ return entry.repo;
217
+ }
218
+
219
+ // src/commands/create.ts
220
+ async function patchPackageJsonName(targetDir, projectName) {
221
+ const packageJsonPath = path3.join(targetDir, "package.json");
222
+ if (!await fs3.pathExists(packageJsonPath)) {
223
+ throw new Error("\u6A21\u677F\u7F3A\u5C11 package.json");
224
+ }
225
+ const packageJson = await fs3.readJson(packageJsonPath);
226
+ packageJson.name = toPackageName(projectName);
227
+ await fs3.writeJson(packageJsonPath, packageJson, { spaces: 2 });
228
+ }
229
+ async function resolveCreateOptions(options) {
230
+ const cwd = options.cwd ?? process.cwd();
231
+ const initialProjectName = options.projectName ? normalizeProjectName(options.projectName) : "";
232
+ const answers = await prompts3(
233
+ [
234
+ {
235
+ type: initialProjectName ? null : "text",
236
+ name: "projectName",
237
+ message: "\u9879\u76EE\u540D",
238
+ initial: "lightfish-app"
239
+ },
240
+ {
241
+ type: options.install !== void 0 || options.skipInstall || options.yes ? null : "confirm",
242
+ name: "install",
243
+ message: "\u662F\u5426\u7ACB\u5373\u5B89\u88C5\u4F9D\u8D56\uFF1F",
244
+ initial: false
245
+ },
246
+ {
247
+ type: options.git !== void 0 || options.yes ? null : "confirm",
248
+ name: "git",
249
+ message: "\u662F\u5426\u521D\u59CB\u5316 git\uFF1F",
250
+ initial: false
251
+ }
252
+ ],
253
+ {
254
+ onCancel() {
255
+ throw new Error("\u5DF2\u53D6\u6D88\u521B\u5EFA\u9879\u76EE");
256
+ }
257
+ }
258
+ );
259
+ const projectName = normalizeProjectName(initialProjectName || answers.projectName);
260
+ if (!projectName) {
261
+ throw new Error("\u9879\u76EE\u540D\u4E0D\u80FD\u4E3A\u7A7A");
262
+ }
263
+ const packageManager = options.packageManager ?? "pnpm";
264
+ if (!isPackageManager(packageManager)) {
265
+ throw new Error(`\u4E0D\u652F\u6301\u7684\u5305\u7BA1\u7406\u5668\uFF1A${String(packageManager)}`);
266
+ }
267
+ const templateRepo = await resolveTemplateRepo({
268
+ template: options.template,
269
+ templateRepo: options.templateRepo,
270
+ yes: options.yes
271
+ });
272
+ return {
273
+ cwd,
274
+ projectName,
275
+ targetDir: resolveTargetDir(cwd, projectName),
276
+ packageManager,
277
+ templateRepo,
278
+ install: options.skipInstall ? false : options.install ?? (options.yes ? false : answers.install ?? false),
279
+ git: options.git ?? answers.git ?? false
280
+ };
281
+ }
282
+ async function maybeInstall(options) {
283
+ if (!options.install) {
284
+ return;
285
+ }
286
+ const [command, args] = installCommand(options.packageManager);
287
+ await execa2(command, args, {
288
+ cwd: options.targetDir,
289
+ stdio: "inherit"
290
+ });
291
+ }
292
+ async function maybeInitGit(options) {
293
+ if (!options.git) {
294
+ return;
295
+ }
296
+ await execa2("git", ["init"], {
297
+ cwd: options.targetDir,
298
+ stdio: "inherit"
299
+ });
300
+ }
301
+ async function createProject(options = {}) {
302
+ const resolvedOptions = await resolveCreateOptions(options);
303
+ const canCreate = await assertCanCreateProject(resolvedOptions.targetDir, options.yes);
304
+ if (!canCreate) {
305
+ throw new Error("\u5DF2\u53D6\u6D88\u521B\u5EFA\u9879\u76EE");
306
+ }
307
+ console.log("[create] \u5F00\u59CB\u4E0B\u8F7D\u6A21\u677F...");
308
+ await downloadTemplate(resolvedOptions.templateRepo, resolvedOptions.targetDir);
309
+ console.log("[create] \u6A21\u677F\u4E0B\u8F7D\u5B8C\u6210");
310
+ console.log("[create] \u5F00\u59CB\u4FEE\u6539 package.json...");
311
+ await patchPackageJsonName(resolvedOptions.targetDir, resolvedOptions.projectName);
312
+ console.log("[create] package.json \u4FEE\u6539\u5B8C\u6210");
313
+ console.log("[create] \u5F00\u59CB\u5B89\u88C5\u4F9D\u8D56...");
314
+ await maybeInstall(resolvedOptions);
315
+ console.log("[create] \u4F9D\u8D56\u5B89\u88C5\u5B8C\u6210");
316
+ console.log("[create] \u5F00\u59CB\u521D\u59CB\u5316 git...");
317
+ await maybeInitGit(resolvedOptions);
318
+ console.log("[create] git \u521D\u59CB\u5316\u5B8C\u6210");
319
+ return resolvedOptions;
320
+ }
321
+
322
+ // src/utils/package-info.ts
323
+ import fs4 from "fs";
324
+ import path4 from "path";
325
+ import { fileURLToPath } from "url";
326
+ var dirname = path4.dirname(fileURLToPath(import.meta.url));
327
+ function readPackageVersion() {
328
+ const packageJsonPathCandidates = [
329
+ // 源码模式:src/utils/package-info.ts -> packages/cli/package.json
330
+ path4.resolve(dirname, "../../package.json"),
331
+ // 构建产物模式:dist/lightfish.js -> packages/cli/package.json
332
+ path4.resolve(dirname, "../package.json")
333
+ ];
334
+ const packageJsonPath = packageJsonPathCandidates.find((candidate) => fs4.existsSync(candidate));
335
+ if (!packageJsonPath) {
336
+ return "0.0.0";
337
+ }
338
+ const packageJson = JSON.parse(fs4.readFileSync(packageJsonPath, "utf8"));
339
+ return packageJson.version ?? "0.0.0";
340
+ }
341
+
342
+ // src/lightfish.ts
343
+ var cli = cac("lightfish");
344
+ async function runBuild() {
345
+ await execa3("vite", ["build"], {
346
+ cwd: process.cwd(),
347
+ preferLocal: true,
348
+ stdio: "inherit"
349
+ });
350
+ }
351
+ cli.command("create [projectName]", "\u521B\u5EFA Lightfish React Vite \u9879\u76EE").option("--pm <pm>", "\u5305\u7BA1\u7406\u5668\uFF1Anpm\u3001yarn\u3001pnpm\u3001bun", { default: "pnpm" }).option("--template <id>", "\u5185\u7F6E\u6A21\u677F id\uFF0C\u4F8B\u5982 react-vite").option("--template-repo <repo>", "\u76F4\u63A5\u6307\u5B9A degit \u6E90\uFF0C\u5982 QGtiger/template-vite-server#main\uFF08\u4F18\u5148\u4E8E --template\uFF09").option("--skip-install", "\u8DF3\u8FC7\u4F9D\u8D56\u5B89\u88C5").option("--git", "\u521B\u5EFA\u540E\u521D\u59CB\u5316 git").option("--yes", "\u4F7F\u7528\u9ED8\u8BA4\u9009\u9879\u5E76\u8DF3\u8FC7\u786E\u8BA4").action(async (projectName, options) => {
352
+ try {
353
+ const packageManager = options.pm ?? "pnpm";
354
+ if (!isPackageManager(packageManager)) {
355
+ throw new Error(`\u4E0D\u652F\u6301\u7684\u5305\u7BA1\u7406\u5668\uFF1A${packageManager}`);
356
+ }
357
+ await createProject({
358
+ projectName,
359
+ packageManager,
360
+ template: options.template,
361
+ templateRepo: options.templateRepo,
362
+ skipInstall: options.skipInstall,
363
+ git: options.git,
364
+ yes: options.yes
365
+ });
366
+ process.exit(0);
367
+ } catch (error) {
368
+ const message = error instanceof Error ? error.message : String(error);
369
+ console.error(`\u521B\u5EFA\u5931\u8D25\uFF1A${message}`);
370
+ process.exit(1);
371
+ }
372
+ });
373
+ cli.command("build", "\u6267\u884C Vite \u6784\u5EFA\uFF0C\u540E\u7EED\u4F1A\u53E0\u52A0 Lightfish \u6784\u5EFA\u903B\u8F91").action(async () => {
374
+ try {
375
+ await runBuild();
376
+ } catch (error) {
377
+ const message = error instanceof Error ? error.message : String(error);
378
+ console.error(`\u6784\u5EFA\u5931\u8D25\uFF1A${message}`);
379
+ process.exitCode = 1;
380
+ }
381
+ });
382
+ cli.command("", "\u663E\u793A\u5E2E\u52A9").action(() => {
383
+ cli.outputHelp();
384
+ });
385
+ cli.help();
386
+ cli.version(readPackageVersion());
387
+ cli.parse();
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@lightfish/cli",
3
+ "version": "0.0.1",
4
+ "type": "module",
5
+ "main": "./dist/index.js",
6
+ "types": "./dist/index.d.ts",
7
+ "bin": {
8
+ "create-lightfish": "./dist/create-lightfish.js",
9
+ "lightfish": "./dist/lightfish.js"
10
+ },
11
+ "files": [
12
+ "dist"
13
+ ],
14
+ "scripts": {
15
+ "build": "tsup src/create-lightfish.ts src/lightfish.ts --format esm --dts --splitting false",
16
+ "dev": "tsx src/create-lightfish.ts",
17
+ "typecheck": "tsc --noEmit",
18
+ "lint": "tsc --noEmit"
19
+ },
20
+ "dependencies": {
21
+ "cac": "^7.0.0",
22
+ "degit": "^2.8.4",
23
+ "execa": "^9.6.1",
24
+ "fs-extra": "^11.3.5",
25
+ "prompts": "^2.4.2"
26
+ },
27
+ "devDependencies": {
28
+ "@types/fs-extra": "^11.0.4",
29
+ "@types/prompts": "^2.4.9"
30
+ }
31
+ }