@lightfish/cli 0.0.5 → 0.0.7

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