@lightfish/cli 0.0.4 → 0.0.6

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.
Files changed (30) hide show
  1. package/dist/create-lightfish.js +126 -60
  2. package/dist/lightfish.js +527 -63
  3. package/dist/templates/react-vite/.agents/skills/lightfish-server/SKILL.md +447 -0
  4. package/dist/templates/react-vite/.vscode/settings.json +42 -0
  5. package/dist/templates/react-vite/AGENTS.md +254 -0
  6. package/dist/templates/react-vite/README.md +150 -0
  7. package/dist/templates/react-vite/eslint.config.js +25 -0
  8. package/dist/templates/react-vite/index.html +13 -0
  9. package/dist/templates/react-vite/lightfish-server.config.js +6 -0
  10. package/dist/templates/react-vite/package.json +41 -0
  11. package/dist/templates/react-vite/pnpm-lock.yaml +3649 -0
  12. package/dist/templates/react-vite/public/favicon.svg +1 -0
  13. package/dist/templates/react-vite/public/icons.svg +24 -0
  14. package/dist/templates/react-vite/server/router/hello/index.ts +20 -0
  15. package/dist/templates/react-vite/server/schema/index.ts +18 -0
  16. package/dist/templates/react-vite/src/assets/hero.png +0 -0
  17. package/dist/templates/react-vite/src/assets/react.svg +1 -0
  18. package/dist/templates/react-vite/src/assets/vite.svg +1 -0
  19. package/dist/templates/react-vite/src/main.css +1 -0
  20. package/dist/templates/react-vite/src/main.tsx +43 -0
  21. package/dist/templates/react-vite/src/pages/example/index.tsx +26 -0
  22. package/dist/templates/react-vite/src/pages/example/layout.tsx +31 -0
  23. package/dist/templates/react-vite/src/pages/example/model.ts +20 -0
  24. package/dist/templates/react-vite/src/pages/index.tsx +17 -0
  25. package/dist/templates/react-vite/src/pages/layout.tsx +15 -0
  26. package/dist/templates/react-vite/tsconfig.app.json +30 -0
  27. package/dist/templates/react-vite/tsconfig.json +7 -0
  28. package/dist/templates/react-vite/tsconfig.node.json +24 -0
  29. package/dist/templates/react-vite/vite.config.ts +61 -0
  30. package/package.json +6 -2
@@ -4,17 +4,39 @@
4
4
  import cac from "cac";
5
5
 
6
6
  // src/commands/create.ts
7
- import path4 from "path";
7
+ import path6 from "path";
8
8
  import { execa as execa2 } from "execa";
9
- import fs4 from "fs-extra";
9
+ import fs6 from "fs-extra";
10
10
  import prompts3 from "prompts";
11
11
 
12
- // src/utils/download-template.ts
12
+ // src/utils/copy-template.ts
13
13
  import path from "path";
14
+ import fs from "fs-extra";
15
+ var SKIP_NAMES = /* @__PURE__ */ new Set([".git", "node_modules", "dist"]);
16
+ async function copyLocalTemplate(srcDir, targetDir) {
17
+ await fs.ensureDir(targetDir);
18
+ const entries = await fs.readdir(srcDir, { withFileTypes: true });
19
+ for (const entry of entries) {
20
+ if (SKIP_NAMES.has(entry.name)) continue;
21
+ const src = path.join(srcDir, entry.name);
22
+ const dest = path.join(targetDir, entry.name);
23
+ if (entry.isDirectory()) {
24
+ await copyLocalTemplate(src, dest);
25
+ } else if (entry.isSymbolicLink()) {
26
+ const linkTarget = await fs.readlink(src);
27
+ await fs.symlink(linkTarget, dest);
28
+ } else {
29
+ await fs.copy(src, dest);
30
+ }
31
+ }
32
+ }
33
+
34
+ // src/utils/download-template.ts
35
+ import path2 from "path";
14
36
  import { tmpdir } from "os";
15
37
  import { randomUUID } from "crypto";
16
38
  import { execa } from "execa";
17
- import fs from "fs-extra";
39
+ import fs2 from "fs-extra";
18
40
  function parseRepo(repo) {
19
41
  const [repoPart, branch] = repo.split("#");
20
42
  let url;
@@ -28,8 +50,8 @@ function parseRepo(repo) {
28
50
  return { url, branch: branch || void 0 };
29
51
  }
30
52
  async function gitClone(url, branch, targetDir) {
31
- const tmpDir = path.join(tmpdir(), `lightfish-template-${randomUUID()}`);
32
- await fs.ensureDir(tmpDir);
53
+ const tmpDir = path2.join(tmpdir(), `lightfish-template-${randomUUID()}`);
54
+ await fs2.ensureDir(tmpDir);
33
55
  const args = ["clone", "--depth", "1"];
34
56
  if (branch) {
35
57
  args.push("--branch", branch);
@@ -38,30 +60,30 @@ async function gitClone(url, branch, targetDir) {
38
60
  try {
39
61
  await execa("git", args, { stdio: "inherit" });
40
62
  } catch (error) {
41
- await fs.remove(tmpDir).catch(() => {
63
+ await fs2.remove(tmpDir).catch(() => {
42
64
  });
43
65
  const detail = error instanceof Error ? error.message : String(error);
44
66
  throw new Error(`Git \u514B\u9686\u5931\u8D25\uFF08${url}\uFF09\uFF1A${detail}`);
45
67
  }
46
- const entries = await fs.readdir(tmpDir);
68
+ const entries = await fs2.readdir(tmpDir);
47
69
  for (const entry of entries) {
48
70
  if (entry === ".git") continue;
49
- const src = path.join(tmpDir, entry);
50
- const dest = path.join(targetDir, entry);
51
- await fs.move(src, dest, { overwrite: true });
71
+ const src = path2.join(tmpDir, entry);
72
+ const dest = path2.join(targetDir, entry);
73
+ await fs2.move(src, dest, { overwrite: true });
52
74
  }
53
- await fs.remove(tmpDir).catch(() => {
75
+ await fs2.remove(tmpDir).catch(() => {
54
76
  });
55
77
  }
56
78
  async function downloadTemplate(repo, targetDir) {
57
- await fs.ensureDir(targetDir);
79
+ await fs2.ensureDir(targetDir);
58
80
  const { url, branch } = parseRepo(repo);
59
81
  console.log(`\u6B63\u5728\u62C9\u53D6\u6A21\u677F: ${url}${branch ? ` (\u5206\u652F: ${branch})` : ""}`);
60
82
  await gitClone(url, branch, targetDir);
61
83
  console.log("\u6A21\u677F\u62C9\u53D6\u5B8C\u6210");
62
- const gitDir = path.join(targetDir, ".git");
63
- if (await fs.pathExists(gitDir)) {
64
- await fs.remove(gitDir);
84
+ const gitDir = path2.join(targetDir, ".git");
85
+ if (await fs2.pathExists(gitDir)) {
86
+ await fs2.remove(gitDir);
65
87
  console.log("\u5DF2\u5220\u9664 .git \u76EE\u5F55");
66
88
  }
67
89
  }
@@ -85,26 +107,26 @@ function installCommand(packageManager) {
85
107
  }
86
108
 
87
109
  // src/utils/project.ts
88
- import path2 from "path";
89
- import fs2 from "fs-extra";
110
+ import path3 from "path";
111
+ import fs3 from "fs-extra";
90
112
  import prompts from "prompts";
91
113
  function normalizeProjectName(name) {
92
114
  return name.trim();
93
115
  }
94
116
  function resolveTargetDir(cwd, projectName) {
95
- return path2.resolve(cwd, projectName);
117
+ return path3.resolve(cwd, projectName);
96
118
  }
97
119
  async function assertCanCreateProject(targetDir, yes) {
98
- if (!await fs2.pathExists(targetDir)) {
120
+ if (!await fs3.pathExists(targetDir)) {
99
121
  return true;
100
122
  }
101
- const files = await fs2.readdir(targetDir);
123
+ const files = await fs3.readdir(targetDir);
102
124
  if (files.length === 0) {
103
125
  return true;
104
126
  }
105
127
  if (yes) {
106
- await fs2.remove(targetDir);
107
- await fs2.ensureDir(targetDir);
128
+ await fs3.remove(targetDir);
129
+ await fs3.ensureDir(targetDir);
108
130
  return true;
109
131
  }
110
132
  const { overwrite } = await prompts(
@@ -123,8 +145,8 @@ async function assertCanCreateProject(targetDir, yes) {
123
145
  if (!overwrite) {
124
146
  return false;
125
147
  }
126
- await fs2.remove(targetDir);
127
- await fs2.ensureDir(targetDir);
148
+ await fs3.remove(targetDir);
149
+ await fs3.ensureDir(targetDir);
128
150
  return true;
129
151
  }
130
152
  function toPackageName(projectName) {
@@ -132,8 +154,8 @@ function toPackageName(projectName) {
132
154
  }
133
155
 
134
156
  // src/utils/replace-token.ts
135
- import path3 from "path";
136
- import fs3 from "fs-extra";
157
+ import path4 from "path";
158
+ import fs4 from "fs-extra";
137
159
  var SKIP_DIRS = /* @__PURE__ */ new Set([
138
160
  ".git",
139
161
  "node_modules",
@@ -165,22 +187,22 @@ var SKIP_EXTS = /* @__PURE__ */ new Set([
165
187
  ".lock"
166
188
  ]);
167
189
  async function replaceToken(targetDir, token, value) {
168
- const entries = await fs3.readdir(targetDir);
190
+ const entries = await fs4.readdir(targetDir);
169
191
  for (const entry of entries) {
170
- const fullPath = path3.join(targetDir, entry);
171
- const stat = await fs3.stat(fullPath);
192
+ const fullPath = path4.join(targetDir, entry);
193
+ const stat = await fs4.stat(fullPath);
172
194
  if (stat.isDirectory()) {
173
195
  if (SKIP_DIRS.has(entry)) continue;
174
196
  await replaceToken(fullPath, token, value);
175
197
  continue;
176
198
  }
177
- const ext = path3.extname(entry).toLowerCase();
199
+ const ext = path4.extname(entry).toLowerCase();
178
200
  if (SKIP_EXTS.has(ext)) continue;
179
201
  try {
180
- const content = await fs3.readFile(fullPath, "utf8");
202
+ const content = await fs4.readFile(fullPath, "utf8");
181
203
  if (content.includes(token)) {
182
204
  const replaced = content.replaceAll(token, value);
183
- await fs3.writeFile(fullPath, replaced, "utf8");
205
+ await fs4.writeFile(fullPath, replaced, "utf8");
184
206
  console.log(` [replace] ${entry}`);
185
207
  }
186
208
  } catch {
@@ -196,6 +218,7 @@ var BUILTIN_TEMPLATES = [
196
218
  {
197
219
  id: "react-vite",
198
220
  label: "React + Vite",
221
+ localDir: "react-vite",
199
222
  repo: "QGtiger/template-vite-server#main"
200
223
  }
201
224
  ];
@@ -207,6 +230,21 @@ function listTemplateIds() {
207
230
  return BUILTIN_TEMPLATES.map((entry) => entry.id);
208
231
  }
209
232
 
233
+ // src/utils/builtin-template-dir.ts
234
+ import path5 from "path";
235
+ import fs5 from "fs";
236
+ import { fileURLToPath } from "url";
237
+ var dirname = path5.dirname(fileURLToPath(import.meta.url));
238
+ function resolveBuiltinTemplateDir(localDir) {
239
+ const candidates = [
240
+ // 产物模式:dirname=dist -> dist/templates/<localDir>
241
+ path5.resolve(dirname, "templates", localDir),
242
+ // 源码模式:dirname=src/utils -> packages/cli/templates/<localDir>
243
+ path5.resolve(dirname, "..", "..", "templates", localDir)
244
+ ];
245
+ return candidates.find((candidate) => fs5.existsSync(candidate));
246
+ }
247
+
210
248
  // src/utils/resolve-template.ts
211
249
  async function promptTemplateId() {
212
250
  const { templateId } = await prompts2(
@@ -230,41 +268,59 @@ async function promptTemplateId() {
230
268
  }
231
269
  return templateId;
232
270
  }
233
- async function resolveTemplateRepo(options) {
234
- if (options.templateRepo) {
235
- return options.templateRepo;
236
- }
271
+ async function selectEntry(options) {
237
272
  if (options.template) {
238
273
  const entry2 = getTemplateById(options.template);
239
274
  if (!entry2) {
240
275
  throw new Error(`\u672A\u77E5\u6A21\u677F\uFF1A${options.template}\u3002\u53EF\u9009\uFF1A${listTemplateIds().join("\u3001")}`);
241
276
  }
242
- return entry2.repo;
277
+ return entry2;
278
+ }
279
+ if (BUILTIN_TEMPLATES.length === 1) {
280
+ const entry2 = getTemplateById(DEFAULT_TEMPLATE_ID ?? BUILTIN_TEMPLATES[0].id);
281
+ if (!entry2) {
282
+ throw new Error("\u5185\u7F6E\u6A21\u677F\u5217\u8868\u4E3A\u7A7A");
283
+ }
284
+ return entry2;
243
285
  }
244
286
  if (options.yes) {
245
287
  const entry2 = getTemplateById(DEFAULT_TEMPLATE_ID ?? BUILTIN_TEMPLATES[0].id);
246
288
  if (!entry2) {
247
289
  throw new Error("\u5185\u7F6E\u6A21\u677F\u5217\u8868\u4E3A\u7A7A");
248
290
  }
249
- return entry2.repo;
291
+ return entry2;
250
292
  }
251
293
  const templateId = await promptTemplateId();
252
294
  const entry = getTemplateById(templateId);
253
295
  if (!entry) {
254
296
  throw new Error(`\u672A\u77E5\u6A21\u677F\uFF1A${templateId}`);
255
297
  }
256
- return entry.repo;
298
+ return entry;
299
+ }
300
+ async function resolveTemplate(options) {
301
+ const entry = await selectEntry(options);
302
+ if (!options.remote && entry.localDir) {
303
+ const dir = resolveBuiltinTemplateDir(entry.localDir);
304
+ if (dir) {
305
+ return { type: "local", dir, id: entry.id };
306
+ }
307
+ console.warn(`[template] \u5185\u7F6E\u6A21\u677F\u526F\u672C\u7F3A\u5931\uFF08${entry.localDir}\uFF09\uFF0C\u56DE\u9000\u5230 git \u62C9\u53D6\u3002`);
308
+ }
309
+ if (!entry.repo) {
310
+ throw new Error(`\u6A21\u677F ${entry.id} \u672A\u914D\u7F6E git \u6E90\uFF0C\u4E14\u672C\u5730\u526F\u672C\u4E0D\u53EF\u7528`);
311
+ }
312
+ return { type: "git", repo: entry.repo, id: entry.id };
257
313
  }
258
314
 
259
315
  // src/commands/create.ts
260
316
  async function patchPackageJsonName(targetDir, projectName) {
261
- const packageJsonPath = path4.join(targetDir, "package.json");
262
- if (!await fs4.pathExists(packageJsonPath)) {
317
+ const packageJsonPath = path6.join(targetDir, "package.json");
318
+ if (!await fs6.pathExists(packageJsonPath)) {
263
319
  throw new Error("\u6A21\u677F\u7F3A\u5C11 package.json");
264
320
  }
265
- const packageJson = await fs4.readJson(packageJsonPath);
321
+ const packageJson = await fs6.readJson(packageJsonPath);
266
322
  packageJson.name = toPackageName(projectName);
267
- await fs4.writeJson(packageJsonPath, packageJson, { spaces: 2 });
323
+ await fs6.writeJson(packageJsonPath, packageJson, { spaces: 2 });
268
324
  }
269
325
  async function resolveCreateOptions(options) {
270
326
  const cwd = options.cwd ?? process.cwd();
@@ -304,9 +360,9 @@ async function resolveCreateOptions(options) {
304
360
  if (!isPackageManager(packageManager)) {
305
361
  throw new Error(`\u4E0D\u652F\u6301\u7684\u5305\u7BA1\u7406\u5668\uFF1A${String(packageManager)}`);
306
362
  }
307
- const templateRepo = await resolveTemplateRepo({
363
+ const template = await resolveTemplate({
308
364
  template: options.template,
309
- templateRepo: options.templateRepo,
365
+ remote: options.remote,
310
366
  yes: options.yes
311
367
  });
312
368
  const yesMode = options.yes === true;
@@ -315,7 +371,7 @@ async function resolveCreateOptions(options) {
315
371
  projectName,
316
372
  targetDir: resolveTargetDir(cwd, projectName),
317
373
  packageManager,
318
- templateRepo,
374
+ template,
319
375
  install: options.skipInstall ? false : options.install ?? (yesMode ? true : answers.install ?? false),
320
376
  git: options.git ?? (yesMode ? true : answers.git ?? false)
321
377
  };
@@ -339,15 +395,25 @@ async function maybeInitGit(options) {
339
395
  stdio: "inherit"
340
396
  });
341
397
  }
398
+ async function writeTemplate(template, targetDir) {
399
+ if (template.type === "local") {
400
+ console.log(`[create] \u4F7F\u7528\u5185\u7F6E\u6A21\u677F\uFF1A${template.id}`);
401
+ await copyLocalTemplate(template.dir, targetDir);
402
+ console.log("[create] \u5185\u7F6E\u6A21\u677F\u62F7\u8D1D\u5B8C\u6210");
403
+ return;
404
+ }
405
+ console.log(`[create] \u4F7F\u7528 git \u6A21\u677F\uFF1A${template.id}`);
406
+ await downloadTemplate(template.repo, targetDir);
407
+ }
342
408
  async function createProject(options = {}) {
343
409
  const resolvedOptions = await resolveCreateOptions(options);
344
410
  const canCreate = await assertCanCreateProject(resolvedOptions.targetDir, options.yes);
345
411
  if (!canCreate) {
346
412
  throw new Error("\u5DF2\u53D6\u6D88\u521B\u5EFA\u9879\u76EE");
347
413
  }
348
- console.log("[create] \u5F00\u59CB\u4E0B\u8F7D\u6A21\u677F...");
349
- await downloadTemplate(resolvedOptions.templateRepo, resolvedOptions.targetDir);
350
- console.log("[create] \u6A21\u677F\u4E0B\u8F7D\u5B8C\u6210");
414
+ console.log("[create] \u5F00\u59CB\u5199\u5165\u6A21\u677F...");
415
+ await writeTemplate(resolvedOptions.template, resolvedOptions.targetDir);
416
+ console.log("[create] \u6A21\u677F\u5199\u5165\u5B8C\u6210");
351
417
  console.log("[create] \u5F00\u59CB\u4FEE\u6539 package.json...");
352
418
  await patchPackageJsonName(resolvedOptions.targetDir, resolvedOptions.projectName);
353
419
  console.log("[create] package.json \u4FEE\u6539\u5B8C\u6210");
@@ -364,28 +430,28 @@ async function createProject(options = {}) {
364
430
  }
365
431
 
366
432
  // src/utils/package-info.ts
367
- import fs5 from "fs";
368
- import path5 from "path";
369
- import { fileURLToPath } from "url";
370
- var dirname = path5.dirname(fileURLToPath(import.meta.url));
433
+ import fs7 from "fs";
434
+ import path7 from "path";
435
+ import { fileURLToPath as fileURLToPath2 } from "url";
436
+ var dirname2 = path7.dirname(fileURLToPath2(import.meta.url));
371
437
  function readPackageVersion() {
372
438
  const packageJsonPathCandidates = [
373
439
  // 源码模式:src/utils/package-info.ts -> packages/cli/package.json
374
- path5.resolve(dirname, "../../package.json"),
440
+ path7.resolve(dirname2, "../../package.json"),
375
441
  // 构建产物模式:dist/lightfish.js -> packages/cli/package.json
376
- path5.resolve(dirname, "../package.json")
442
+ path7.resolve(dirname2, "../package.json")
377
443
  ];
378
- const packageJsonPath = packageJsonPathCandidates.find((candidate) => fs5.existsSync(candidate));
444
+ const packageJsonPath = packageJsonPathCandidates.find((candidate) => fs7.existsSync(candidate));
379
445
  if (!packageJsonPath) {
380
446
  return "0.0.0";
381
447
  }
382
- const packageJson = JSON.parse(fs5.readFileSync(packageJsonPath, "utf8"));
448
+ const packageJson = JSON.parse(fs7.readFileSync(packageJsonPath, "utf8"));
383
449
  return packageJson.version ?? "0.0.0";
384
450
  }
385
451
 
386
452
  // src/create-lightfish.ts
387
453
  var cli = cac("create-lightfish");
388
- 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 git \u4ED3\u5E93\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("-y, --yes", "\u4F7F\u7528\u9ED8\u8BA4\u9009\u9879\u5E76\u8DF3\u8FC7\u786E\u8BA4").action(async (projectName, options) => {
454
+ 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("--remote", "\u5F3A\u5236\u8D70 git \u62C9\u53D6\u5185\u7F6E\u6A21\u677F\uFF08\u5FFD\u7565\u672C\u5730\u526F\u672C\uFF09").option("--skip-install", "\u8DF3\u8FC7\u4F9D\u8D56\u5B89\u88C5").option("--git", "\u521B\u5EFA\u540E\u521D\u59CB\u5316 git").option("-y, --yes", "\u4F7F\u7528\u9ED8\u8BA4\u9009\u9879\u5E76\u8DF3\u8FC7\u786E\u8BA4").action(async (projectName, options) => {
389
455
  try {
390
456
  const packageManager = options.pm ?? "pnpm";
391
457
  if (!isPackageManager(packageManager)) {
@@ -395,7 +461,7 @@ cli.command("[projectName]", "\u521B\u5EFA Lightfish React Vite \u9879\u76EE").o
395
461
  projectName,
396
462
  packageManager,
397
463
  template: options.template,
398
- templateRepo: options.templateRepo,
464
+ remote: options.remote,
399
465
  skipInstall: options.skipInstall,
400
466
  git: options.git,
401
467
  yes: options.yes