@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.
- package/dist/commands/build.js +96 -15
- package/package.json +2 -2
package/dist/commands/build.js
CHANGED
|
@@ -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 &&
|
|
430
|
+
else if (ammoCheck.hasAmmo && !buildOptions.ammoReplacement) {
|
|
394
431
|
spinner.stop();
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
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
|
-
|
|
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(` - 可视化报告: ${
|
|
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
|
|
451
|
-
|
|
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.
|
|
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.
|
|
23
|
+
"@playcraft/build": "^0.0.4",
|
|
24
24
|
"chokidar": "^4.0.3",
|
|
25
25
|
"commander": "^13.1.0",
|
|
26
26
|
"cors": "^2.8.5",
|