@playcraft/cli 0.0.10 → 0.0.11

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.
@@ -84,7 +84,36 @@ async function detectAmmoUsage(baseBuildDir) {
84
84
  try {
85
85
  const configContent = await fs.readFile(configPath, 'utf-8');
86
86
  const configJson = JSON.parse(configContent);
87
+ // 检查 application_properties.use3dPhysics
87
88
  use3dPhysics = Boolean(configJson.application_properties?.use3dPhysics);
89
+ // 如果 use3dPhysics 未设置,通过检查场景内容来推断
90
+ // 检查场景是否包含 3D 物理组件(rigidbody, collision 等)
91
+ if (!use3dPhysics) {
92
+ const scenesDir = baseBuildDir;
93
+ try {
94
+ const files = await fs.readdir(scenesDir);
95
+ for (const file of files) {
96
+ if (file.endsWith('.json') && !['config.json', 'manifest.json'].includes(file)) {
97
+ try {
98
+ const sceneContent = await fs.readFile(path.join(scenesDir, file), 'utf-8');
99
+ // 检查场景是否包含 3D 物理组件
100
+ if (sceneContent.includes('"rigidbody"') ||
101
+ sceneContent.includes('"collision"') ||
102
+ sceneContent.includes('"joint"')) {
103
+ use3dPhysics = true;
104
+ break;
105
+ }
106
+ }
107
+ catch {
108
+ // 忽略单个场景读取失败
109
+ }
110
+ }
111
+ }
112
+ }
113
+ catch {
114
+ // 忽略目录读取失败
115
+ }
116
+ }
88
117
  if (configJson.assets) {
89
118
  for (const asset of Object.values(configJson.assets)) {
90
119
  const assetData = asset;
@@ -119,6 +148,7 @@ async function detectAmmoUsage(baseBuildDir) {
119
148
  }
120
149
  async function analyzeBaseBuild(baseBuildDir, ammoReplacement) {
121
150
  const assetSizes = [];
151
+ const assetPaths = new Set(); // 用于去重
122
152
  let engineSize = 0;
123
153
  const ammoCheck = await detectAmmoUsage(baseBuildDir);
124
154
  const engineCandidates = [
@@ -151,10 +181,15 @@ async function analyzeBaseBuild(baseBuildDir, ammoReplacement) {
151
181
  if (ammoReplacement && lowerUrl.includes('ammo')) {
152
182
  continue;
153
183
  }
184
+ // 跳过已经处理过的资源
185
+ if (assetPaths.has(cleanUrl)) {
186
+ continue;
187
+ }
154
188
  const assetPath = path.join(baseBuildDir, cleanUrl);
155
189
  try {
156
190
  const stats = await fs.stat(assetPath);
157
191
  assetSizes.push({ path: cleanUrl, size: stats.size });
192
+ assetPaths.add(cleanUrl); // 标记为已处理
158
193
  }
159
194
  catch (error) {
160
195
  // ignore missing assets
@@ -377,6 +412,8 @@ export async function buildCommand(projectPath, options) {
377
412
  const baseBuilder = new BaseBuilder(resolvedProjectPath, {
378
413
  outputDir: tempDir,
379
414
  selectedScenes,
415
+ analyze: buildOptions.analyze,
416
+ analyzeReportPath: buildOptions.analyze ? 'base-bundle-report.html' : undefined,
380
417
  });
381
418
  const baseBuild = await baseBuilder.build();
382
419
  baseBuildDir = baseBuild.outputDir;
@@ -390,18 +427,48 @@ export async function buildCommand(projectPath, options) {
390
427
  else if (options.replaceAmmo && ammoCheck.hasAmmo && ammoCheck.suggestedEngine) {
391
428
  buildOptions.ammoReplacement = ammoCheck.suggestedEngine;
392
429
  }
393
- else if (ammoCheck.hasAmmo && ammoCheck.suggestedEngine && !buildOptions.ammoReplacement) {
430
+ else if (ammoCheck.hasAmmo && !buildOptions.ammoReplacement) {
394
431
  spinner.stop();
395
- const replaceAnswer = await inquirer.prompt([
396
- {
397
- type: 'confirm',
398
- name: 'replaceAmmo',
399
- message: `检测到 Ammo.js,是否替换为 ${ammoCheck.suggestedEngine} 并打包内置?`,
400
- default: false,
401
- },
402
- ]);
403
- if (replaceAnswer.replaceAmmo) {
404
- buildOptions.ammoReplacement = ammoCheck.suggestedEngine;
432
+ // 根据是否使用 3D 物理提供不同的选项
433
+ const physicsType = ammoCheck.use3dPhysics ? '3D' : '2D';
434
+ if (ammoCheck.use3dPhysics) {
435
+ // 3D 项目 - 可以使用 Cannon.js 替换
436
+ console.log(pc.yellow(`⚠️ 检测到 3D 物理项目,Ammo.js 约 1.8MB`));
437
+ console.log(pc.gray(` 提示: Cannon.js (~400KB) 可以减小 75% 的体积`));
438
+ const replaceAnswer = await inquirer.prompt([
439
+ {
440
+ type: 'list',
441
+ name: 'ammoAction',
442
+ message: `如何处理 Ammo.js?`,
443
+ choices: [
444
+ { name: '保留 Ammo.js(完整物理功能,文件较大)', value: 'keep' },
445
+ { name: '替换为 Cannon.js(推荐 3D 项目,~400KB,自动适配)', value: 'cannon' },
446
+ ],
447
+ default: 'cannon',
448
+ },
449
+ ]);
450
+ if (replaceAnswer.ammoAction === 'cannon') {
451
+ buildOptions.ammoReplacement = 'cannon';
452
+ console.log(pc.green(' ✓ 将使用 Cannon.js 替换 Ammo.js(自动兼容 rigidbody API)'));
453
+ }
454
+ }
455
+ else {
456
+ // 2D 项目 - 可以使用 p2.js 替换
457
+ const replaceAnswer = await inquirer.prompt([
458
+ {
459
+ type: 'list',
460
+ name: 'ammoAction',
461
+ message: `检测到 Ammo.js(项目使用 ${physicsType} 物理),如何处理?`,
462
+ choices: [
463
+ { name: '不替换(保留 Ammo.js,文件较大)', value: 'keep' },
464
+ { name: '替换为 p2.js(推荐 2D 项目,~60KB)', value: 'p2' },
465
+ ],
466
+ default: 'p2',
467
+ },
468
+ ]);
469
+ if (replaceAnswer.ammoAction === 'p2') {
470
+ buildOptions.ammoReplacement = 'p2';
471
+ }
405
472
  }
406
473
  spinner.start(pc.cyan('🔨 开始打包 Playable Ad...'));
407
474
  }
@@ -434,12 +501,21 @@ export async function buildCommand(projectPath, options) {
434
501
  console.log(` - 总计: ${totalMB} MB ${status} (限制: ${limitMB} MB)`);
435
502
  console.log('\n' + pc.green(`输出: ${outputPath}`));
436
503
  if (buildOptions.analyze) {
437
- const reportPath = buildOptions.analyzeReportPath
504
+ // Base Build 复制分析报告到最终输出目录
505
+ const baseReportPath = path.join(baseBuildDir, 'base-bundle-report.html');
506
+ const finalReportPath = buildOptions.analyzeReportPath
438
507
  ? buildOptions.analyzeReportPath
439
508
  : path.join(buildOptions.outputDir || './dist', 'bundle-report.html');
509
+ try {
510
+ await fs.access(baseReportPath);
511
+ await fs.copyFile(baseReportPath, finalReportPath);
512
+ }
513
+ catch (error) {
514
+ console.warn(pc.yellow(' ⚠️ 无法找到或复制分析报告'));
515
+ }
440
516
  const analysis = await analyzeBaseBuild(baseBuildDir, buildOptions.ammoReplacement);
441
517
  console.log('\n' + pc.bold('分析报告:'));
442
- console.log(` - 可视化报告: ${reportPath}`);
518
+ console.log(` - 可视化报告: ${finalReportPath}`);
443
519
  if (analysis.engineSize > 0) {
444
520
  const engineMB = (analysis.engineSize / 1024 / 1024).toFixed(2);
445
521
  console.log(` - 引擎大小: ${engineMB} MB`);
@@ -447,8 +523,13 @@ export async function buildCommand(projectPath, options) {
447
523
  if (analysis.topAssets.length > 0) {
448
524
  console.log(' - 资源 Top:');
449
525
  analysis.topAssets.forEach((asset) => {
450
- const sizeMB = (asset.size / 1024 / 1024).toFixed(2);
451
- console.log(` • ${asset.path}: ${sizeMB} MB`);
526
+ const sizeKB = asset.size / 1024;
527
+ const sizeMB = sizeKB / 1024;
528
+ // 自动选择合适的单位
529
+ const sizeStr = sizeMB >= 0.01
530
+ ? `${sizeMB.toFixed(2)} MB`
531
+ : `${sizeKB.toFixed(2)} KB`;
532
+ console.log(` • ${asset.path}: ${sizeStr}`);
452
533
  });
453
534
  }
454
535
  if (analysis.hasAmmo) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playcraft/cli",
3
- "version": "0.0.10",
3
+ "version": "0.0.11",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "bin": {
@@ -20,7 +20,7 @@
20
20
  "release": "node scripts/release.js"
21
21
  },
22
22
  "dependencies": {
23
- "@playcraft/build": "^0.0.3",
23
+ "@playcraft/build": "^0.0.4",
24
24
  "chokidar": "^4.0.3",
25
25
  "commander": "^13.1.0",
26
26
  "cors": "^2.8.5",