feng3d-cli 0.1.1 → 0.1.3

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/index.js CHANGED
@@ -93,9 +93,254 @@ function getSrcIndexTemplate(options) {
93
93
  function getVitestConfigTemplate() {
94
94
  return fs.readFileSync(path.join(TEMPLATES_DIR, "vitest.config.ts"), "utf-8");
95
95
  }
96
- async function updateProject(directory = ".") {
96
+ async function createProject(name, options) {
97
+ const projectDir = path.join(options.directory, name);
98
+ if (await fs.pathExists(projectDir)) {
99
+ throw new Error(`目录 ${projectDir} 已存在`);
100
+ }
101
+ await fs.ensureDir(projectDir);
102
+ await fs.ensureDir(path.join(projectDir, "src"));
103
+ console.log(chalk.gray(` 创建目录: ${projectDir}`));
104
+ const packageJson = createPackageJson(name, options);
105
+ await fs.writeJson(path.join(projectDir, "package.json"), packageJson, { spaces: 4 });
106
+ console.log(chalk.gray(" 创建: package.json"));
107
+ await fs.writeJson(path.join(projectDir, "tsconfig.json"), getTsconfigTemplate(), { spaces: 4 });
108
+ console.log(chalk.gray(" 创建: tsconfig.json"));
109
+ await fs.writeFile(path.join(projectDir, ".gitignore"), getGitignoreTemplate());
110
+ console.log(chalk.gray(" 创建: .gitignore"));
111
+ await fs.writeFile(path.join(projectDir, ".cursorrules"), getCursorrrulesTemplate());
112
+ console.log(chalk.gray(" 创建: .cursorrules"));
113
+ await fs.writeFile(path.join(projectDir, "eslint.config.js"), getEslintConfigTemplate());
114
+ console.log(chalk.gray(" 创建: eslint.config.js"));
115
+ const typedocConfig = getTypedocConfig({ repoName: name });
116
+ await fs.writeJson(path.join(projectDir, "typedoc.json"), typedocConfig, { spaces: 4 });
117
+ console.log(chalk.gray(" 创建: typedoc.json"));
118
+ await fs.writeFile(path.join(projectDir, "src/index.ts"), getSrcIndexTemplate({ name: `@feng3d/${name}` }));
119
+ console.log(chalk.gray(" 创建: src/index.ts"));
120
+ await fs.writeFile(path.join(projectDir, "README.md"), `# @feng3d/${name}
121
+ `);
122
+ console.log(chalk.gray(" 创建: README.md"));
123
+ if (options.examples !== false) {
124
+ await fs.ensureDir(path.join(projectDir, "examples"));
125
+ console.log(chalk.gray(" 创建: examples/"));
126
+ }
127
+ if (options.vitest !== false) {
128
+ await fs.ensureDir(path.join(projectDir, "test"));
129
+ console.log(chalk.gray(" 创建: test/"));
130
+ }
131
+ await fs.ensureDir(path.join(projectDir, ".github/workflows"));
132
+ await fs.writeFile(path.join(projectDir, ".github/workflows/publish.yml"), getPublishWorkflowTemplate());
133
+ console.log(chalk.gray(" 创建: .github/workflows/publish.yml"));
134
+ await fs.ensureDir(path.join(projectDir, "scripts"));
135
+ await fs.writeFile(path.join(projectDir, "scripts/prepublish.js"), getPrepublishScriptTemplate());
136
+ await fs.writeFile(path.join(projectDir, "scripts/postpublish.js"), getPostpublishScriptTemplate());
137
+ console.log(chalk.gray(" 创建: scripts/prepublish.js"));
138
+ console.log(chalk.gray(" 创建: scripts/postpublish.js"));
139
+ if (options.examples !== false) {
140
+ await fs.writeFile(path.join(projectDir, "scripts/postdocs.js"), getPostdocsScriptTemplate());
141
+ console.log(chalk.gray(" 创建: scripts/postdocs.js"));
142
+ }
143
+ }
144
+ function createPackageJson(name, options) {
145
+ const scripts = {
146
+ clean: "rimraf lib dist public",
147
+ build: "vite build && tsc",
148
+ types: "tsc",
149
+ watch: "tsc -w",
150
+ lint: "eslint . --ext .js,.ts --max-warnings 0",
151
+ lintfix: "npm run lint -- --fix",
152
+ docs: "typedoc",
153
+ release: "npm run clean && npm run lint && npm test && npm run build && npm run docs && npm publish",
154
+ prepublishOnly: "node scripts/prepublish.js",
155
+ postpublish: "node scripts/postpublish.js"
156
+ };
157
+ if (options.examples !== false) {
158
+ scripts["examples:dev"] = "cd examples && npm run dev";
159
+ scripts.postdocs = "node scripts/postdocs.js && cd examples && vite build --outDir ../public";
160
+ }
161
+ if (options.vitest !== false) {
162
+ scripts.test = "vitest run";
163
+ scripts["test:watch"] = "vitest";
164
+ }
165
+ return {
166
+ name: `@feng3d/${name}`,
167
+ version: "0.0.1",
168
+ description: "",
169
+ homepage: `https://feng3d.com/${name}/`,
170
+ author: "feng",
171
+ type: "module",
172
+ main: "./src/index.ts",
173
+ types: "./src/index.ts",
174
+ module: "./src/index.ts",
175
+ exports: {
176
+ ".": {
177
+ types: "./src/index.ts",
178
+ import: "./src/index.ts",
179
+ require: "./src/index.ts"
180
+ }
181
+ },
182
+ scripts,
183
+ repository: {
184
+ type: "git",
185
+ url: `https://github.com/feng3d-labs/${name}.git`
186
+ },
187
+ publishConfig: {
188
+ access: "public"
189
+ },
190
+ files: ["src", "dist", "lib"],
191
+ devDependencies: getDevDependencies({
192
+ includeVitest: options.vitest !== false,
193
+ includeTypedoc: true
194
+ })
195
+ };
196
+ }
197
+ function deepMerge(target, source) {
198
+ const result = { ...target };
199
+ for (const key in source) {
200
+ if (Object.prototype.hasOwnProperty.call(source, key)) {
201
+ const sourceValue = source[key];
202
+ const targetValue = result[key];
203
+ if (sourceValue && typeof sourceValue === "object" && !Array.isArray(sourceValue) && targetValue && typeof targetValue === "object" && !Array.isArray(targetValue)) {
204
+ result[key] = deepMerge(targetValue, sourceValue);
205
+ } else if (!(key in result)) {
206
+ result[key] = sourceValue;
207
+ }
208
+ }
209
+ }
210
+ return result;
211
+ }
212
+ async function mergeJsonConfig(existingPath, standardContent) {
213
+ const existingContent = await fs.readFile(existingPath, "utf-8");
214
+ const existingJson = JSON.parse(existingContent);
215
+ const standardJson = JSON.parse(standardContent);
216
+ const indent = detectIndent$1(existingContent);
217
+ const hasTrailingNewline = existingContent.endsWith("\n");
218
+ const merged = deepMerge(existingJson, standardJson);
219
+ let result = JSON.stringify(merged, null, indent);
220
+ if (hasTrailingNewline) {
221
+ result += "\n";
222
+ }
223
+ return result;
224
+ }
225
+ function detectIndent$1(content) {
226
+ const match = content.match(/^[ \t]+/m);
227
+ return match ? match[0] : " ";
228
+ }
229
+ async function determineFileAction(filePath, mergeStrategy) {
230
+ const exists = await fs.pathExists(filePath);
231
+ if (!exists) {
232
+ return "overwrite";
233
+ }
234
+ switch (mergeStrategy) {
235
+ case "overwrite":
236
+ return "overwrite";
237
+ case "skip-existing":
238
+ return "skip";
239
+ case "merge":
240
+ default: {
241
+ const ext = path.extname(filePath);
242
+ if (ext === ".json") {
243
+ return "merge";
244
+ }
245
+ return "overwrite";
246
+ }
247
+ }
248
+ }
249
+ async function askFileAction(filePath) {
250
+ const readline = await import("./__vite-browser-external-2Ng8QIWW.js");
251
+ const rl = readline.createInterface({
252
+ input: process.stdin,
253
+ output: process.stdout
254
+ });
255
+ return new Promise((resolve) => {
256
+ console.log(chalk.yellow(`
257
+ 文件已存在: ${filePath}`));
258
+ console.log("请选择处理方式:");
259
+ console.log(" 1. 覆盖 (overwrite) - 完全使用标准配置");
260
+ console.log(" 2. 合并 (merge) - 保留用户配置,添加缺失项");
261
+ console.log(" 3. 跳过 (skip) - 不修改该文件");
262
+ rl.question("请输入选项 (1/2/3,默认 2): ", (answer) => {
263
+ rl.close();
264
+ switch (answer.trim()) {
265
+ case "1":
266
+ resolve("overwrite");
267
+ break;
268
+ case "3":
269
+ resolve("skip");
270
+ break;
271
+ case "2":
272
+ case "":
273
+ default:
274
+ resolve("merge");
275
+ break;
276
+ }
277
+ });
278
+ });
279
+ }
280
+ async function writeFileContent(filePath, content, dryRun = false) {
281
+ if (dryRun) {
282
+ console.log(chalk.gray(` [预览] 将写入: ${filePath}`));
283
+ return;
284
+ }
285
+ await fs.ensureDir(path.dirname(filePath));
286
+ await fs.writeFile(filePath, content);
287
+ }
288
+ async function handleFileUpdate(filePath, standardContent, action, dryRun = false) {
289
+ const exists = await fs.pathExists(filePath);
290
+ const relativePath = path.relative(process.cwd(), filePath);
291
+ switch (action) {
292
+ case "skip":
293
+ console.log(chalk.yellow(` 跳过: ${relativePath}`));
294
+ return false;
295
+ case "merge":
296
+ if (!exists) {
297
+ await writeFileContent(filePath, standardContent, dryRun);
298
+ console.log(chalk.gray(` 创建: ${relativePath}`));
299
+ return true;
300
+ }
301
+ try {
302
+ const ext = path.extname(filePath);
303
+ if (ext === ".json") {
304
+ const merged = await mergeJsonConfig(filePath, standardContent);
305
+ await writeFileContent(filePath, merged, dryRun);
306
+ console.log(chalk.blue(` 合并: ${relativePath}`));
307
+ return true;
308
+ }
309
+ await writeFileContent(filePath, standardContent, dryRun);
310
+ console.log(chalk.gray(` 更新: ${relativePath}`));
311
+ return true;
312
+ } catch (error) {
313
+ console.log(chalk.red(` 合并失败,跳过: ${relativePath}`));
314
+ console.log(chalk.red(` 错误: ${error}`));
315
+ return false;
316
+ }
317
+ case "overwrite":
318
+ default:
319
+ await writeFileContent(filePath, standardContent, dryRun);
320
+ if (exists) {
321
+ console.log(chalk.gray(` 更新: ${relativePath}`));
322
+ } else {
323
+ console.log(chalk.gray(` 创建: ${relativePath}`));
324
+ }
325
+ return true;
326
+ }
327
+ }
328
+ async function updateProject(options = {}) {
329
+ const directory = options.directory || ".";
97
330
  const projectDir = path.resolve(directory);
98
331
  const packageJsonPath = path.join(projectDir, "package.json");
332
+ let mergeStrategy = options.mergeStrategy || "merge";
333
+ if (options.force) {
334
+ mergeStrategy = "overwrite";
335
+ }
336
+ const interactive = options.interactive || false;
337
+ const dryRun = options.dryRun || false;
338
+ if (dryRun) {
339
+ console.log(chalk.yellow(" [预览模式] 不会实际修改文件\n"));
340
+ }
341
+ if (interactive) {
342
+ console.log(chalk.cyan(" [交互模式] 将逐个询问文件处理方式\n"));
343
+ }
99
344
  if (!await fs.pathExists(packageJsonPath)) {
100
345
  await fs.ensureDir(projectDir);
101
346
  const dirName = path.basename(projectDir);
@@ -114,71 +359,181 @@ async function updateProject(directory = ".") {
114
359
  const packageJson = await fs.readJson(packageJsonPath);
115
360
  const name = packageJson.name || path.basename(projectDir);
116
361
  const repoName = name.replace(/^@[^/]+\//, "");
362
+ const isMonorepoRoot = Boolean(packageJson.workspaces);
117
363
  const isFeng3dCli = name === "feng3d-cli";
118
- await fs.writeFile(path.join(projectDir, ".gitignore"), getGitignoreTemplate());
119
- console.log(chalk.gray(" 更新: .gitignore"));
120
- await fs.writeFile(path.join(projectDir, ".cursorrules"), getCursorrrulesTemplate());
121
- console.log(chalk.gray(" 更新: .cursorrules"));
122
- await createEslintConfigFile(projectDir);
123
- console.log(chalk.gray(" 更新: eslint.config.js"));
124
- await fs.ensureDir(path.join(projectDir, ".github/workflows"));
125
- await fs.writeFile(path.join(projectDir, ".github/workflows/publish.yml"), getPublishWorkflowTemplate());
126
- console.log(chalk.gray(" 更新: .github/workflows/publish.yml"));
127
- await fs.writeFile(path.join(projectDir, ".github/workflows/pages.yml"), getPagesWorkflowTemplate());
128
- console.log(chalk.gray(" 更新: .github/workflows/pages.yml"));
129
- await fs.writeFile(path.join(projectDir, ".github/workflows/pull-request.yml"), getPullRequestWorkflowTemplate());
130
- console.log(chalk.gray(" 更新: .github/workflows/pull-request.yml"));
131
- await fs.writeFile(path.join(projectDir, ".github/workflows/upload-oss.yml"), getUploadOssWorkflowTemplate());
132
- console.log(chalk.gray(" 更新: .github/workflows/upload-oss.yml"));
133
- const typedocContent = getTypedocConfigTemplate({ repoName });
134
- await fs.writeFile(path.join(projectDir, "typedoc.json"), typedocContent);
135
- console.log(chalk.gray(" 更新: typedoc.json"));
136
- const testDir = path.join(projectDir, "test");
137
- await fs.ensureDir(testDir);
138
- const testFiles = await fs.readdir(testDir);
139
- if (testFiles.length === 0) {
140
- const testContent = getTestIndexTemplate();
141
- await fs.writeFile(path.join(testDir, "_.test.ts"), testContent);
142
- console.log(chalk.gray(" 创建: test/_.test.ts"));
143
- }
144
- await updateDependencies(projectDir);
145
- console.log(chalk.gray(" 更新: package.json devDependencies"));
146
- await fs.ensureDir(path.join(projectDir, ".husky"));
147
- await fs.writeFile(path.join(projectDir, ".husky/pre-commit"), getHuskyPreCommitTemplate());
148
- console.log(chalk.gray(" 更新: .husky/pre-commit"));
149
- await updateHuskyConfig(projectDir);
150
- await fs.writeFile(path.join(projectDir, "LICENSE"), getLicenseTemplate());
151
- console.log(chalk.gray(" 更新: LICENSE"));
152
- await fs.ensureDir(path.join(projectDir, ".vscode"));
153
- await fs.writeFile(path.join(projectDir, ".vscode/settings.json"), getVscodeSettingsTemplate());
154
- console.log(chalk.gray(" 更新: .vscode/settings.json"));
155
- if (!isFeng3dCli) {
156
- await fs.writeFile(path.join(projectDir, "tsconfig.json"), getTsconfigTemplateString());
157
- console.log(chalk.gray(" 更新: tsconfig.json"));
364
+ await updateSingleFile(
365
+ path.join(projectDir, ".gitignore"),
366
+ getGitignoreTemplate(),
367
+ "overwrite",
368
+ // .gitignore 总是覆盖
369
+ false,
370
+ dryRun
371
+ );
372
+ await updateSingleFile(
373
+ path.join(projectDir, ".cursorrules"),
374
+ getCursorrrulesTemplate(),
375
+ mergeStrategy,
376
+ interactive,
377
+ dryRun
378
+ );
379
+ await updateSingleFile(
380
+ path.join(projectDir, "eslint.config.js"),
381
+ getEslintConfigTemplate(),
382
+ mergeStrategy,
383
+ interactive,
384
+ dryRun
385
+ );
386
+ if (!dryRun) {
387
+ await fs.ensureDir(path.join(projectDir, ".github/workflows"));
388
+ }
389
+ await updateSingleFile(
390
+ path.join(projectDir, ".github/workflows/publish.yml"),
391
+ getPublishWorkflowTemplate(),
392
+ mergeStrategy,
393
+ interactive,
394
+ dryRun
395
+ );
396
+ await updateSingleFile(
397
+ path.join(projectDir, ".github/workflows/pages.yml"),
398
+ getPagesWorkflowTemplate(),
399
+ mergeStrategy,
400
+ interactive,
401
+ dryRun
402
+ );
403
+ await updateSingleFile(
404
+ path.join(projectDir, ".github/workflows/pull-request.yml"),
405
+ getPullRequestWorkflowTemplate(),
406
+ mergeStrategy,
407
+ interactive,
408
+ dryRun
409
+ );
410
+ await updateSingleFile(
411
+ path.join(projectDir, ".github/workflows/upload-oss.yml"),
412
+ getUploadOssWorkflowTemplate(),
413
+ mergeStrategy,
414
+ interactive,
415
+ dryRun
416
+ );
417
+ if (!isMonorepoRoot) {
418
+ await updateSingleFile(
419
+ path.join(projectDir, "typedoc.json"),
420
+ getTypedocConfigTemplate({ repoName }),
421
+ mergeStrategy,
422
+ interactive,
423
+ dryRun
424
+ );
425
+ }
426
+ if (!isMonorepoRoot) {
427
+ const testDir = path.join(projectDir, "test");
428
+ if (!dryRun) {
429
+ await fs.ensureDir(testDir);
430
+ }
431
+ const testFiles = dryRun ? [] : await fs.readdir(testDir);
432
+ if (testFiles.length === 0) {
433
+ await updateSingleFile(
434
+ path.join(testDir, "_.test.ts"),
435
+ getTestIndexTemplate(),
436
+ "overwrite",
437
+ // 新文件,直接创建
438
+ false,
439
+ dryRun
440
+ );
441
+ }
158
442
  }
443
+ if (!dryRun) {
444
+ await updateDependencies(projectDir, isMonorepoRoot);
445
+ console.log(chalk.gray(" 更新: package.json devDependencies"));
446
+ }
447
+ if (!dryRun) {
448
+ await fs.ensureDir(path.join(projectDir, ".husky"));
449
+ }
450
+ await updateSingleFile(
451
+ path.join(projectDir, ".husky/pre-commit"),
452
+ getHuskyPreCommitTemplate(),
453
+ mergeStrategy,
454
+ interactive,
455
+ dryRun
456
+ );
457
+ if (!dryRun) {
458
+ await updateHuskyConfig(projectDir);
459
+ }
460
+ await updateSingleFile(
461
+ path.join(projectDir, "LICENSE"),
462
+ getLicenseTemplate(),
463
+ "overwrite",
464
+ // LICENSE 总是覆盖
465
+ false,
466
+ dryRun
467
+ );
468
+ if (!dryRun) {
469
+ await fs.ensureDir(path.join(projectDir, ".vscode"));
470
+ }
471
+ await updateSingleFile(
472
+ path.join(projectDir, ".vscode/settings.json"),
473
+ getVscodeSettingsTemplate(),
474
+ "overwrite",
475
+ // VS Code 设置文件总是覆盖
476
+ false,
477
+ dryRun
478
+ );
159
479
  if (!isFeng3dCli) {
160
- await fs.writeFile(path.join(projectDir, "vite.config.js"), getViteConfigTemplate());
161
- console.log(chalk.gray(" 更新: vite.config.js"));
480
+ await updateSingleFile(
481
+ path.join(projectDir, "tsconfig.json"),
482
+ getTsconfigTemplateString(),
483
+ "overwrite",
484
+ // tsconfig.json 总是覆盖
485
+ false,
486
+ dryRun
487
+ );
162
488
  }
163
489
  if (!isFeng3dCli) {
164
- await fs.writeFile(path.join(projectDir, "vitest.config.ts"), getVitestConfigTemplate());
165
- console.log(chalk.gray(" 更新: vitest.config.ts"));
490
+ await updateSingleFile(
491
+ path.join(projectDir, "vite.config.js"),
492
+ getViteConfigTemplate(),
493
+ mergeStrategy,
494
+ interactive,
495
+ dryRun
496
+ );
497
+ }
498
+ if (!isFeng3dCli && !isMonorepoRoot) {
499
+ await updateSingleFile(
500
+ path.join(projectDir, "vitest.config.ts"),
501
+ getVitestConfigTemplate(),
502
+ mergeStrategy,
503
+ interactive,
504
+ dryRun
505
+ );
166
506
  }
167
507
  const scriptsDir = path.join(projectDir, "scripts");
168
- await fs.ensureDir(scriptsDir);
169
- await fs.writeFile(path.join(scriptsDir, "prepublish.js"), getPrepublishScriptTemplate());
170
- console.log(chalk.gray(" 更新: scripts/prepublish.js"));
171
- await fs.writeFile(path.join(scriptsDir, "postpublish.js"), getPostpublishScriptTemplate());
172
- console.log(chalk.gray(" 更新: scripts/postpublish.js"));
508
+ if (!dryRun) {
509
+ await fs.ensureDir(scriptsDir);
510
+ }
511
+ await updateSingleFile(
512
+ path.join(scriptsDir, "prepublish.js"),
513
+ getPrepublishScriptTemplate(),
514
+ mergeStrategy,
515
+ interactive,
516
+ dryRun
517
+ );
518
+ await updateSingleFile(
519
+ path.join(scriptsDir, "postpublish.js"),
520
+ getPostpublishScriptTemplate(),
521
+ mergeStrategy,
522
+ interactive,
523
+ dryRun
524
+ );
173
525
  const examplesDir = path.join(projectDir, "examples");
174
- if (await fs.pathExists(examplesDir)) {
175
- await fs.writeFile(path.join(scriptsDir, "postdocs.js"), getPostdocsScriptTemplate());
176
- console.log(chalk.gray(" 更新: scripts/postdocs.js"));
526
+ const hasExamples = dryRun ? false : await fs.pathExists(examplesDir);
527
+ if (hasExamples) {
528
+ await updateSingleFile(
529
+ path.join(scriptsDir, "postdocs.js"),
530
+ getPostdocsScriptTemplate(),
531
+ mergeStrategy,
532
+ interactive,
533
+ dryRun
534
+ );
177
535
  }
178
536
  }
179
- async function createEslintConfigFile(projectDir) {
180
- await fs.writeFile(path.join(projectDir, "eslint.config.js"), getEslintConfigTemplate());
181
- }
182
537
  function detectIndent(content) {
183
538
  const match = content.match(/^[ \t]+/m);
184
539
  return match ? match[0] : " ";
@@ -243,15 +598,15 @@ function reorderPackageJson(packageJson) {
243
598
  }
244
599
  return ordered;
245
600
  }
246
- async function updateDependencies(projectDir) {
601
+ async function updateDependencies(projectDir, isMonorepoRoot = false) {
247
602
  const packageJsonPath = path.join(projectDir, "package.json");
248
603
  const originalContent = await fs.readFile(packageJsonPath, "utf-8");
249
604
  const indent = detectIndent(originalContent);
250
605
  const hasTrailingNewline = originalContent.endsWith("\n");
251
606
  const packageJson = JSON.parse(originalContent);
252
607
  const standardDeps = getDevDependencies({
253
- includeVitest: true,
254
- includeTypedoc: true
608
+ includeVitest: !isMonorepoRoot,
609
+ includeTypedoc: !isMonorepoRoot
255
610
  });
256
611
  let updated = false;
257
612
  if (!packageJson.devDependencies) {
@@ -274,15 +629,19 @@ async function updateDependencies(projectDir) {
274
629
  const standardScripts = {
275
630
  clean: "rimraf lib dist public",
276
631
  build: "vite build && tsc",
277
- watch: 'concurrently "vite build --watch" "tsc -w" "vitest"',
278
- test: "vitest run",
279
632
  lint: "eslint . --ext .js,.ts --max-warnings 0",
280
633
  lintfix: "npm run lint -- --fix",
281
- docs: "typedoc",
282
634
  prepublishOnly: "node scripts/prepublish.js",
283
- release: "npm run clean && npm run lint && npm test && npm run build && npm run docs && npm publish",
284
635
  postpublish: "node scripts/postpublish.js"
285
636
  };
637
+ if (!isMonorepoRoot) {
638
+ standardScripts.watch = 'concurrently "vite build --watch" "tsc -w" "vitest"';
639
+ standardScripts.test = "vitest run";
640
+ standardScripts.docs = "typedoc";
641
+ standardScripts.release = "npm run clean && npm run lint && npm test && npm run build && npm run docs && npm publish";
642
+ } else {
643
+ standardScripts.release = "npm run clean && npm run lint && npm run build && npm publish";
644
+ }
286
645
  const examplesDir = path.join(projectDir, "examples");
287
646
  if (await fs.pathExists(examplesDir)) {
288
647
  standardScripts["examples:dev"] = "cd examples && npm run dev";
@@ -360,7 +719,7 @@ async function updateHuskyConfig(projectDir) {
360
719
  console.log(chalk.gray(` 更新: devDependencies.husky = "${VERSIONS.husky}"`));
361
720
  }
362
721
  if (packageJson.devDependencies["lint-staged"] !== VERSIONS["lint-staged"]) {
363
- packageJson.devDependencies["lint-staged"] = VERSIONS["lint-staged"];
722
+ packageJson["lint-staged"] = VERSIONS["lint-staged"];
364
723
  updated = true;
365
724
  console.log(chalk.gray(` 更新: devDependencies.lint-staged = "${VERSIONS["lint-staged"]}"`));
366
725
  }
@@ -389,106 +748,19 @@ async function updateHuskyConfig(projectDir) {
389
748
  await fs.writeFile(packageJsonPath, newContent);
390
749
  }
391
750
  }
392
- async function createProject(name, options) {
393
- const projectDir = path.join(options.directory, name);
394
- if (await fs.pathExists(projectDir)) {
395
- throw new Error(`目录 ${projectDir} 已存在`);
396
- }
397
- await fs.ensureDir(projectDir);
398
- await fs.ensureDir(path.join(projectDir, "src"));
399
- console.log(chalk.gray(` 创建目录: ${projectDir}`));
400
- const packageJson = createPackageJson(name, options);
401
- await fs.writeJson(path.join(projectDir, "package.json"), packageJson, { spaces: 4 });
402
- console.log(chalk.gray(" 创建: package.json"));
403
- await fs.writeJson(path.join(projectDir, "tsconfig.json"), getTsconfigTemplate(), { spaces: 4 });
404
- console.log(chalk.gray(" 创建: tsconfig.json"));
405
- await fs.writeFile(path.join(projectDir, ".gitignore"), getGitignoreTemplate());
406
- console.log(chalk.gray(" 创建: .gitignore"));
407
- await fs.writeFile(path.join(projectDir, ".cursorrules"), getCursorrrulesTemplate());
408
- console.log(chalk.gray(" 创建: .cursorrules"));
409
- await createEslintConfigFile(projectDir);
410
- console.log(chalk.gray(" 创建: eslint.config.js"));
411
- const typedocConfig = getTypedocConfig({ repoName: name });
412
- await fs.writeJson(path.join(projectDir, "typedoc.json"), typedocConfig, { spaces: 4 });
413
- console.log(chalk.gray(" 创建: typedoc.json"));
414
- await fs.writeFile(path.join(projectDir, "src/index.ts"), getSrcIndexTemplate({ name: `@feng3d/${name}` }));
415
- console.log(chalk.gray(" 创建: src/index.ts"));
416
- await fs.writeFile(path.join(projectDir, "README.md"), `# @feng3d/${name}
417
- `);
418
- console.log(chalk.gray(" 创建: README.md"));
419
- if (options.examples !== false) {
420
- await fs.ensureDir(path.join(projectDir, "examples"));
421
- console.log(chalk.gray(" 创建: examples/"));
422
- }
423
- if (options.vitest !== false) {
424
- await fs.ensureDir(path.join(projectDir, "test"));
425
- console.log(chalk.gray(" 创建: test/"));
426
- }
427
- await fs.ensureDir(path.join(projectDir, ".github/workflows"));
428
- await fs.writeFile(path.join(projectDir, ".github/workflows/publish.yml"), getPublishWorkflowTemplate());
429
- console.log(chalk.gray(" 创建: .github/workflows/publish.yml"));
430
- await fs.ensureDir(path.join(projectDir, "scripts"));
431
- await fs.writeFile(path.join(projectDir, "scripts/prepublish.js"), getPrepublishScriptTemplate());
432
- await fs.writeFile(path.join(projectDir, "scripts/postpublish.js"), getPostpublishScriptTemplate());
433
- console.log(chalk.gray(" 创建: scripts/prepublish.js"));
434
- console.log(chalk.gray(" 创建: scripts/postpublish.js"));
435
- if (options.examples !== false) {
436
- await fs.writeFile(path.join(projectDir, "scripts/postdocs.js"), getPostdocsScriptTemplate());
437
- console.log(chalk.gray(" 创建: scripts/postdocs.js"));
438
- }
439
- }
440
- function createPackageJson(name, options) {
441
- const scripts = {
442
- clean: "rimraf lib dist public",
443
- build: "vite build && tsc",
444
- types: "tsc",
445
- watch: "tsc -w",
446
- lint: "eslint . --ext .js,.ts --max-warnings 0",
447
- lintfix: "npm run lint -- --fix",
448
- docs: "typedoc",
449
- release: "npm run clean && npm run lint && npm test && npm run build && npm run docs && npm publish",
450
- prepublishOnly: "node scripts/prepublish.js",
451
- postpublish: "node scripts/postpublish.js"
452
- };
453
- if (options.examples !== false) {
454
- scripts["examples:dev"] = "cd examples && npm run dev";
455
- scripts.postdocs = "node scripts/postdocs.js && cd examples && vite build --outDir ../public";
456
- }
457
- if (options.vitest !== false) {
458
- scripts.test = "vitest run";
459
- scripts["test:watch"] = "vitest";
751
+ async function updateSingleFile(filePath, content, mergeStrategy, interactive, dryRun) {
752
+ let action;
753
+ if (interactive) {
754
+ const exists = await fs.pathExists(filePath);
755
+ if (exists) {
756
+ action = await askFileAction(path.relative(process.cwd(), filePath));
757
+ } else {
758
+ action = "overwrite";
759
+ }
760
+ } else {
761
+ action = await determineFileAction(filePath, mergeStrategy);
460
762
  }
461
- return {
462
- name: `@feng3d/${name}`,
463
- version: "0.0.1",
464
- description: "",
465
- homepage: `https://feng3d.com/${name}/`,
466
- author: "feng",
467
- type: "module",
468
- main: "./src/index.ts",
469
- types: "./src/index.ts",
470
- module: "./src/index.ts",
471
- exports: {
472
- ".": {
473
- types: "./src/index.ts",
474
- import: "./src/index.ts",
475
- require: "./src/index.ts"
476
- }
477
- },
478
- scripts,
479
- repository: {
480
- type: "git",
481
- url: `https://github.com/feng3d-labs/${name}.git`
482
- },
483
- publishConfig: {
484
- access: "public"
485
- },
486
- files: ["src", "dist", "lib"],
487
- devDependencies: getDevDependencies({
488
- includeVitest: options.vitest !== false,
489
- includeTypedoc: true
490
- })
491
- };
763
+ await handleFileUpdate(filePath, content, action, dryRun);
492
764
  }
493
765
  const __filename$1 = fileURLToPath(import.meta.url);
494
766
  const __dirname$1 = path.dirname(__filename$1);