@playcraft/cli 0.0.14 → 0.0.17
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/agent/agent.js +54 -1
- package/dist/agent/fs-backend.js +312 -8
- package/dist/agent/local-backend.js +249 -18
- package/dist/commands/build-all.js +477 -0
- package/dist/commands/build.js +238 -176
- package/dist/fs-handler.js +117 -0
- package/dist/index.js +59 -14
- package/dist/playable/base-builder.js +265 -0
- package/dist/playable/builder.js +1462 -0
- package/dist/playable/converter.js +150 -0
- package/dist/playable/index.js +3 -0
- package/dist/playable/platforms/base.js +12 -0
- package/dist/playable/platforms/facebook.js +37 -0
- package/dist/playable/platforms/index.js +24 -0
- package/dist/playable/platforms/snapchat.js +59 -0
- package/dist/playable/playable-builder.js +521 -0
- package/dist/playable/types.js +1 -0
- package/dist/playable/vite/config-builder.js +136 -0
- package/dist/playable/vite/platform-configs.js +102 -0
- package/dist/playable/vite/plugin-model-compression.js +63 -0
- package/dist/playable/vite/plugin-platform.js +65 -0
- package/dist/playable/vite/plugin-playcanvas.js +454 -0
- package/dist/playable/vite-builder.js +125 -0
- package/dist/server.js +23 -6
- package/dist/socket.js +7 -2
- package/dist/watcher.js +27 -1
- package/package.json +5 -4
package/dist/commands/build.js
CHANGED
|
@@ -2,7 +2,7 @@ import path from 'path';
|
|
|
2
2
|
import fs from 'fs/promises';
|
|
3
3
|
import pc from 'picocolors';
|
|
4
4
|
import ora from 'ora';
|
|
5
|
-
import { BaseBuilder, ViteBuilder, PlayableBuilder, PlayableAnalyzer } from '@playcraft/build';
|
|
5
|
+
import { BaseBuilder, ViteBuilder, PlayableBuilder, PlayableAnalyzer, EngineDetector, detectAmmoUsage } from '@playcraft/build';
|
|
6
6
|
import { loadBuildConfig } from '../build-config.js';
|
|
7
7
|
import inquirer from 'inquirer';
|
|
8
8
|
/**
|
|
@@ -33,11 +33,59 @@ async function detectBaseBuild(dir) {
|
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
/**
|
|
36
|
-
*
|
|
36
|
+
* 检测项目场景列表(支持 PlayCanvas 和 PlayCraft 两种格式)
|
|
37
37
|
*/
|
|
38
38
|
async function detectProjectScenes(projectPath) {
|
|
39
39
|
try {
|
|
40
|
-
// 尝试读取
|
|
40
|
+
// 1. 尝试读取 PlayCraft 格式的 manifest.json
|
|
41
|
+
const manifestJsonPath = path.join(projectPath, 'manifest.json');
|
|
42
|
+
try {
|
|
43
|
+
const manifestContent = await fs.readFile(manifestJsonPath, 'utf-8');
|
|
44
|
+
const manifestData = JSON.parse(manifestContent);
|
|
45
|
+
if (manifestData.scenes && Array.isArray(manifestData.scenes)) {
|
|
46
|
+
// PlayCraft manifest.json 中的场景列表
|
|
47
|
+
const scenes = manifestData.scenes.map((scene) => ({
|
|
48
|
+
id: String(scene.id || scene.name),
|
|
49
|
+
name: scene.name || `Scene ${scene.id}`,
|
|
50
|
+
isMain: scene.isMain === true
|
|
51
|
+
}));
|
|
52
|
+
// 同时检查 scenes/ 目录中是否有不在 manifest 中的场景文件
|
|
53
|
+
const scenesDir = path.join(projectPath, 'scenes');
|
|
54
|
+
try {
|
|
55
|
+
const sceneFiles = await fs.readdir(scenesDir);
|
|
56
|
+
const manifestIds = new Set(scenes.map((s) => String(s.id)));
|
|
57
|
+
for (const f of sceneFiles) {
|
|
58
|
+
if (!f.endsWith('.json'))
|
|
59
|
+
continue;
|
|
60
|
+
const fileId = f.replace(/\.scene\.json$/, '').replace(/\.json$/, '');
|
|
61
|
+
if (!manifestIds.has(fileId)) {
|
|
62
|
+
// 读取场景文件获取名称
|
|
63
|
+
try {
|
|
64
|
+
const sceneContent = await fs.readFile(path.join(scenesDir, f), 'utf-8');
|
|
65
|
+
const sceneData = JSON.parse(sceneContent);
|
|
66
|
+
scenes.push({
|
|
67
|
+
id: fileId,
|
|
68
|
+
name: sceneData.name || `Scene ${fileId}`,
|
|
69
|
+
isMain: false
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
scenes.push({ id: fileId, name: `Scene ${fileId}`, isMain: false });
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
// scenes 目录不存在
|
|
80
|
+
}
|
|
81
|
+
if (scenes.length > 0)
|
|
82
|
+
return scenes;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
// manifest.json 不存在或解析失败
|
|
87
|
+
}
|
|
88
|
+
// 2. 尝试读取 PlayCanvas 格式的 scenes.json
|
|
41
89
|
const scenesJsonPath = path.join(projectPath, 'scenes.json');
|
|
42
90
|
try {
|
|
43
91
|
const scenesContent = await fs.readFile(scenesJsonPath, 'utf-8');
|
|
@@ -60,7 +108,7 @@ async function detectProjectScenes(projectPath) {
|
|
|
60
108
|
catch (error) {
|
|
61
109
|
// scenes.json 不存在或解析失败,尝试其他方法
|
|
62
110
|
}
|
|
63
|
-
// 尝试从 project.json 读取
|
|
111
|
+
// 3. 尝试从 project.json 读取
|
|
64
112
|
const projectJsonPath = path.join(projectPath, 'project.json');
|
|
65
113
|
try {
|
|
66
114
|
const projectContent = await fs.readFile(projectJsonPath, 'utf-8');
|
|
@@ -81,78 +129,6 @@ async function detectProjectScenes(projectPath) {
|
|
|
81
129
|
return [];
|
|
82
130
|
}
|
|
83
131
|
}
|
|
84
|
-
function resolveSuggestedPhysicsEngine(use3dPhysics) {
|
|
85
|
-
return use3dPhysics ? 'cannon' : 'p2';
|
|
86
|
-
}
|
|
87
|
-
async function detectAmmoUsage(baseBuildDir) {
|
|
88
|
-
const configPath = path.join(baseBuildDir, 'config.json');
|
|
89
|
-
let hasAmmo = false;
|
|
90
|
-
let use3dPhysics = false;
|
|
91
|
-
try {
|
|
92
|
-
const configContent = await fs.readFile(configPath, 'utf-8');
|
|
93
|
-
const configJson = JSON.parse(configContent);
|
|
94
|
-
// 检查 application_properties.use3dPhysics
|
|
95
|
-
use3dPhysics = Boolean(configJson.application_properties?.use3dPhysics);
|
|
96
|
-
// 如果 use3dPhysics 未设置,通过检查场景内容来推断
|
|
97
|
-
// 检查场景是否包含 3D 物理组件(rigidbody, collision 等)
|
|
98
|
-
if (!use3dPhysics) {
|
|
99
|
-
const scenesDir = baseBuildDir;
|
|
100
|
-
try {
|
|
101
|
-
const files = await fs.readdir(scenesDir);
|
|
102
|
-
for (const file of files) {
|
|
103
|
-
if (file.endsWith('.json') && !['config.json', 'manifest.json'].includes(file)) {
|
|
104
|
-
try {
|
|
105
|
-
const sceneContent = await fs.readFile(path.join(scenesDir, file), 'utf-8');
|
|
106
|
-
// 检查场景是否包含 3D 物理组件
|
|
107
|
-
if (sceneContent.includes('"rigidbody"') ||
|
|
108
|
-
sceneContent.includes('"collision"') ||
|
|
109
|
-
sceneContent.includes('"joint"')) {
|
|
110
|
-
use3dPhysics = true;
|
|
111
|
-
break;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
catch {
|
|
115
|
-
// 忽略单个场景读取失败
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
catch {
|
|
121
|
-
// 忽略目录读取失败
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
if (configJson.assets) {
|
|
125
|
-
for (const asset of Object.values(configJson.assets)) {
|
|
126
|
-
const assetData = asset;
|
|
127
|
-
const moduleName = assetData?.data?.moduleName?.toLowerCase?.() || '';
|
|
128
|
-
const fileUrl = assetData?.file?.url?.toLowerCase?.() || '';
|
|
129
|
-
if (moduleName.includes('ammo') || fileUrl.includes('ammo')) {
|
|
130
|
-
hasAmmo = true;
|
|
131
|
-
break;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
catch (error) {
|
|
137
|
-
// 忽略解析失败
|
|
138
|
-
}
|
|
139
|
-
if (!hasAmmo) {
|
|
140
|
-
try {
|
|
141
|
-
const settingsContent = await fs.readFile(path.join(baseBuildDir, '__settings__.js'), 'utf-8');
|
|
142
|
-
if (settingsContent.toLowerCase().includes('ammo')) {
|
|
143
|
-
hasAmmo = true;
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
catch (error) {
|
|
147
|
-
// 忽略读取失败
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
return {
|
|
151
|
-
hasAmmo,
|
|
152
|
-
suggestedEngine: resolveSuggestedPhysicsEngine(use3dPhysics),
|
|
153
|
-
use3dPhysics,
|
|
154
|
-
};
|
|
155
|
-
}
|
|
156
132
|
async function analyzeBaseBuild(baseBuildDir, ammoReplacement) {
|
|
157
133
|
const assetSizes = [];
|
|
158
134
|
const assetPaths = new Set(); // 用于去重
|
|
@@ -225,6 +201,27 @@ export async function buildCommand(projectPath, options) {
|
|
|
225
201
|
// 清理选项(默认为 false,使用覆盖模式)
|
|
226
202
|
const shouldClean = options.clean === true;
|
|
227
203
|
try {
|
|
204
|
+
// ====== 引擎检测 ======
|
|
205
|
+
spinner.text = pc.cyan('检测项目引擎类型...');
|
|
206
|
+
let detectedEngine;
|
|
207
|
+
if (options.engine) {
|
|
208
|
+
// 用户指定了引擎类型
|
|
209
|
+
detectedEngine = options.engine;
|
|
210
|
+
console.log(pc.dim(`\nℹ️ 使用指定引擎: ${detectedEngine}`));
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
// 自动检测引擎类型
|
|
214
|
+
detectedEngine = await EngineDetector.detect(resolvedProjectPath);
|
|
215
|
+
console.log(pc.dim(`\nℹ️ 检测到引擎: ${detectedEngine}`));
|
|
216
|
+
// 对于外部引擎,显示提示
|
|
217
|
+
if (detectedEngine !== 'playcanvas') {
|
|
218
|
+
console.log(pc.cyan(`\n🎮 检测到 ${detectedEngine} 项目`));
|
|
219
|
+
console.log(pc.dim(' 将使用 npm install + npm run build 进行构建'));
|
|
220
|
+
console.log(pc.yellow(' ⚠️ 构建时间可能较长(约 2-5 分钟)\n'));
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
const isPlayCanvas = detectedEngine === 'playcanvas';
|
|
224
|
+
const requiresNpmBuild = detectedEngine !== 'playcanvas';
|
|
228
225
|
if (!options.baseOnly && !options.mode) {
|
|
229
226
|
spinner.stop();
|
|
230
227
|
const modeAnswer = await inquirer.prompt([
|
|
@@ -258,57 +255,60 @@ export async function buildCommand(projectPath, options) {
|
|
|
258
255
|
options.output = fileConfig.outputDir;
|
|
259
256
|
}
|
|
260
257
|
}
|
|
261
|
-
//
|
|
258
|
+
// 解析场景选择参数(支持 PlayCanvas 和 PlayCraft 项目)
|
|
262
259
|
let selectedScenes;
|
|
263
|
-
if (
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
260
|
+
if (isPlayCanvas) {
|
|
261
|
+
if (options.scenes) {
|
|
262
|
+
// 命令行指定了场景参数
|
|
263
|
+
selectedScenes = options.scenes.split(',').map(s => s.trim()).filter(s => s);
|
|
264
|
+
if (selectedScenes.length > 0) {
|
|
265
|
+
console.log(`\n🎬 选中场景: ${selectedScenes.join(', ')}`);
|
|
266
|
+
}
|
|
268
267
|
}
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
268
|
+
else if (options.mode !== 'playable') {
|
|
269
|
+
// 没有指定场景参数,且不是仅做 playable build(playable 不需要选场景),检测项目场景并提供交互选择
|
|
270
|
+
spinner.stop();
|
|
271
|
+
const projectScenes = await detectProjectScenes(resolvedProjectPath);
|
|
272
|
+
if (projectScenes.length > 1) {
|
|
273
|
+
// 有多个场景,提示用户选择
|
|
274
|
+
console.log(pc.cyan(`\n🎬 检测到 ${projectScenes.length} 个场景`));
|
|
275
|
+
console.log(pc.dim('💡 提示: 只打包选中的场景可以显著减小文件大小(通常可减小 30-60%)\n'));
|
|
276
|
+
// 为 PlayCraft 项目标记主场景
|
|
277
|
+
const sceneAnswer = await inquirer.prompt([
|
|
278
|
+
{
|
|
279
|
+
type: 'checkbox',
|
|
280
|
+
name: 'selectedScenes',
|
|
281
|
+
message: '选择要打包的场景(使用空格选择,回车确认):',
|
|
282
|
+
choices: projectScenes.map(scene => ({
|
|
283
|
+
name: scene.isMain ? `${scene.name} (主场景)` : scene.name,
|
|
284
|
+
value: scene.name,
|
|
285
|
+
checked: scene.isMain === true || !projectScenes.some(s => s.isMain) // 有 isMain 标记则只默认选主场景,否则全选
|
|
286
|
+
})),
|
|
287
|
+
validate: (answer) => {
|
|
288
|
+
if (answer.length === 0) {
|
|
289
|
+
return '请至少选择一个场景';
|
|
290
|
+
}
|
|
291
|
+
return true;
|
|
291
292
|
}
|
|
292
|
-
return true;
|
|
293
293
|
}
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
294
|
+
]);
|
|
295
|
+
selectedScenes = sceneAnswer.selectedScenes;
|
|
296
|
+
if (selectedScenes && selectedScenes.length > 0) {
|
|
297
|
+
if (selectedScenes.length === projectScenes.length) {
|
|
298
|
+
console.log(pc.green(`✅ 已选择所有场景 (${selectedScenes.length} 个)`));
|
|
299
|
+
// 全选则不传递参数(向后兼容)
|
|
300
|
+
selectedScenes = undefined;
|
|
301
|
+
}
|
|
302
|
+
else {
|
|
303
|
+
console.log(pc.green(`✅ 已选择 ${selectedScenes.length} / ${projectScenes.length} 个场景: ${selectedScenes.join(', ')}`));
|
|
304
|
+
}
|
|
305
305
|
}
|
|
306
306
|
}
|
|
307
|
+
else if (projectScenes.length === 1) {
|
|
308
|
+
console.log(pc.dim(`ℹ️ 项目只有 1 个场景: ${projectScenes[0].name}`));
|
|
309
|
+
}
|
|
310
|
+
spinner.start(pc.cyan('🔨 开始打包 Playable Ad...'));
|
|
307
311
|
}
|
|
308
|
-
else if (projectScenes.length === 1) {
|
|
309
|
-
console.log(pc.dim(`ℹ️ 项目只有 1 个场景: ${projectScenes[0].name}`));
|
|
310
|
-
}
|
|
311
|
-
spinner.start(pc.cyan('🔨 开始打包 Playable Ad...'));
|
|
312
312
|
}
|
|
313
313
|
// 如果只执行基础构建
|
|
314
314
|
if (options.baseOnly || options.mode === 'base') {
|
|
@@ -329,6 +329,7 @@ export async function buildCommand(projectPath, options) {
|
|
|
329
329
|
selectedScenes,
|
|
330
330
|
clean: shouldClean && !isSameAsInput, // 清理逻辑由 BaseBuilder 内部处理
|
|
331
331
|
analyze: options.analyze, // 传递 analyze 参数
|
|
332
|
+
engine: detectedEngine, // 传递引擎类型
|
|
332
333
|
});
|
|
333
334
|
const baseBuild = await baseBuilder.build();
|
|
334
335
|
spinner.succeed(pc.green('✅ 基础构建完成!'));
|
|
@@ -353,6 +354,7 @@ export async function buildCommand(projectPath, options) {
|
|
|
353
354
|
'inmobi',
|
|
354
355
|
'adikteev',
|
|
355
356
|
'remerge',
|
|
357
|
+
'mintegral',
|
|
356
358
|
];
|
|
357
359
|
// 渠道输出格式配置(根据各渠道Playable规格对照表)
|
|
358
360
|
// 只支持 HTML: applovin, ironsource, unity, moloco, adikteev, remerge
|
|
@@ -376,6 +378,7 @@ export async function buildCommand(projectPath, options) {
|
|
|
376
378
|
bigo: { formats: ['zip'], default: 'zip' },
|
|
377
379
|
snapchat: { formats: ['zip'], default: 'zip' },
|
|
378
380
|
inmobi: { formats: ['zip'], default: 'zip' },
|
|
381
|
+
mintegral: { formats: ['zip'], default: 'zip' },
|
|
379
382
|
};
|
|
380
383
|
// 获取渠道支持的格式
|
|
381
384
|
const getPlatformFormats = (platform) => {
|
|
@@ -426,6 +429,27 @@ export async function buildCommand(projectPath, options) {
|
|
|
426
429
|
]);
|
|
427
430
|
selectedFormat = formatAnswer.format;
|
|
428
431
|
}
|
|
432
|
+
// 输入商店跳转地址(CTA)- 必填
|
|
433
|
+
console.log(pc.cyan('\n🔗 商店跳转地址(CTA 按钮目标)'));
|
|
434
|
+
console.log(pc.dim(' iOS 和 Android 地址均为必填'));
|
|
435
|
+
const storeUrlAnswer = await inquirer.prompt([
|
|
436
|
+
{
|
|
437
|
+
type: 'input',
|
|
438
|
+
name: 'iosStoreUrl',
|
|
439
|
+
message: 'iOS App Store URL:',
|
|
440
|
+
validate: (input) => input.trim() ? true : '请输入 iOS App Store URL',
|
|
441
|
+
},
|
|
442
|
+
{
|
|
443
|
+
type: 'input',
|
|
444
|
+
name: 'androidStoreUrl',
|
|
445
|
+
message: 'Android Google Play URL:',
|
|
446
|
+
validate: (input) => input.trim() ? true : '请输入 Android Google Play URL',
|
|
447
|
+
},
|
|
448
|
+
]);
|
|
449
|
+
options.storeUrls = {
|
|
450
|
+
ios: storeUrlAnswer.iosStoreUrl.trim(),
|
|
451
|
+
android: storeUrlAnswer.androidStoreUrl.trim(),
|
|
452
|
+
};
|
|
429
453
|
// 选择输出目录
|
|
430
454
|
const outputAnswer = await inquirer.prompt([
|
|
431
455
|
{
|
|
@@ -452,20 +476,47 @@ export async function buildCommand(projectPath, options) {
|
|
|
452
476
|
// 未指定格式,使用默认值
|
|
453
477
|
options.format = formatConfig.default;
|
|
454
478
|
}
|
|
479
|
+
// 如果命令行未传入 storeUrls,交互式输入(必填)
|
|
480
|
+
if (!options.storeUrls) {
|
|
481
|
+
spinner.stop();
|
|
482
|
+
console.log(pc.cyan('\n🔗 商店跳转地址(CTA 按钮目标)'));
|
|
483
|
+
console.log(pc.dim(' iOS 和 Android 地址均为必填'));
|
|
484
|
+
const storeUrlAnswer = await inquirer.prompt([
|
|
485
|
+
{
|
|
486
|
+
type: 'input',
|
|
487
|
+
name: 'iosStoreUrl',
|
|
488
|
+
message: 'iOS App Store URL:',
|
|
489
|
+
validate: (input) => input.trim() ? true : '请输入 iOS App Store URL',
|
|
490
|
+
},
|
|
491
|
+
{
|
|
492
|
+
type: 'input',
|
|
493
|
+
name: 'androidStoreUrl',
|
|
494
|
+
message: 'Android Google Play URL:',
|
|
495
|
+
validate: (input) => input.trim() ? true : '请输入 Android Google Play URL',
|
|
496
|
+
},
|
|
497
|
+
]);
|
|
498
|
+
options.storeUrls = {
|
|
499
|
+
ios: storeUrlAnswer.iosStoreUrl.trim(),
|
|
500
|
+
android: storeUrlAnswer.androidStoreUrl.trim(),
|
|
501
|
+
};
|
|
502
|
+
spinner.start(pc.cyan('🔨 开始打包 Playable Ad...'));
|
|
503
|
+
}
|
|
455
504
|
}
|
|
456
505
|
// 解析配置
|
|
457
506
|
const buildOptions = {
|
|
458
507
|
platform: options.platform,
|
|
459
508
|
format: options.format || 'html',
|
|
460
509
|
outputDir: path.resolve(options.output || './dist'),
|
|
461
|
-
//
|
|
462
|
-
compressEngine: options.compress,
|
|
510
|
+
// 压缩选项(默认全部开启)
|
|
511
|
+
compressEngine: options.compress ?? true,
|
|
512
|
+
compressConfigJson: options.compressConfig ?? true,
|
|
513
|
+
compressJS: options.compressJs ?? true,
|
|
463
514
|
analyze: options.analyze || false,
|
|
464
515
|
// Vite 构建选项
|
|
465
516
|
useVite: options.useVite !== false, // 默认使用 Vite
|
|
466
|
-
//
|
|
467
|
-
cssMinify: options.cssMinify,
|
|
468
|
-
jsMinify: options.jsMinify,
|
|
517
|
+
// 压缩选项(默认全部开启)
|
|
518
|
+
cssMinify: options.cssMinify ?? true,
|
|
519
|
+
jsMinify: options.jsMinify ?? true,
|
|
469
520
|
compressImages: options.compressImages,
|
|
470
521
|
imageQuality: options.imageQuality,
|
|
471
522
|
convertToWebP: options.convertToWebP,
|
|
@@ -475,6 +526,8 @@ export async function buildCommand(projectPath, options) {
|
|
|
475
526
|
selectedScenes,
|
|
476
527
|
// ESM 选项
|
|
477
528
|
...(options.esmMode ? { esmMode: options.esmMode } : {}),
|
|
529
|
+
// 商店跳转地址
|
|
530
|
+
...(options.storeUrls ? { storeUrls: options.storeUrls } : {}),
|
|
478
531
|
};
|
|
479
532
|
if (fileConfig) {
|
|
480
533
|
Object.assign(buildOptions, fileConfig);
|
|
@@ -529,77 +582,86 @@ export async function buildCommand(projectPath, options) {
|
|
|
529
582
|
selectedScenes,
|
|
530
583
|
analyze: buildOptions.analyze,
|
|
531
584
|
analyzeReportPath: buildOptions.analyze ? 'base-bundle-report.html' : undefined,
|
|
585
|
+
engine: detectedEngine, // 传递引擎类型
|
|
532
586
|
});
|
|
533
587
|
const baseBuild = await baseBuilder.build();
|
|
534
588
|
baseBuildDir = baseBuild.outputDir;
|
|
535
589
|
spinner.text = pc.cyan('✅ 阶段1完成,开始阶段2...');
|
|
536
590
|
}
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
buildOptions.ammoReplacement = ammoCheck.suggestedEngine;
|
|
544
|
-
}
|
|
545
|
-
else if (ammoCheck.hasAmmo && !buildOptions.ammoReplacement) {
|
|
546
|
-
spinner.stop();
|
|
547
|
-
// 根据是否使用 3D 物理提供不同的选项
|
|
548
|
-
const physicsType = ammoCheck.use3dPhysics ? '3D' : '2D';
|
|
549
|
-
if (ammoCheck.use3dPhysics) {
|
|
550
|
-
// 3D 项目 - 可以使用 Cannon.js 替换
|
|
551
|
-
console.log(pc.yellow(`⚠️ 检测到 3D 物理项目,Ammo.js 约 1.8MB`));
|
|
552
|
-
console.log(pc.gray(` 提示: Cannon.js (~400KB) 可以减小 75% 的体积`));
|
|
553
|
-
const replaceAnswer = await inquirer.prompt([
|
|
554
|
-
{
|
|
555
|
-
type: 'list',
|
|
556
|
-
name: 'ammoAction',
|
|
557
|
-
message: `如何处理 Ammo.js?`,
|
|
558
|
-
choices: [
|
|
559
|
-
{ name: '保留 Ammo.js(完整物理功能,文件较大)', value: 'keep' },
|
|
560
|
-
{ name: '替换为 Cannon.js(推荐 3D 项目,~400KB,自动适配)', value: 'cannon' },
|
|
561
|
-
],
|
|
562
|
-
default: 'cannon',
|
|
563
|
-
},
|
|
564
|
-
]);
|
|
565
|
-
if (replaceAnswer.ammoAction === 'cannon') {
|
|
566
|
-
buildOptions.ammoReplacement = 'cannon';
|
|
567
|
-
console.log(pc.green(' ✓ 将使用 Cannon.js 替换 Ammo.js(自动兼容 rigidbody API)'));
|
|
568
|
-
}
|
|
591
|
+
// Ammo 检测和替换(仅 PlayCanvas 项目)
|
|
592
|
+
if (isPlayCanvas) {
|
|
593
|
+
const ammoCheck = await detectAmmoUsage(baseBuildDir);
|
|
594
|
+
const requestedAmmoEngine = options.ammoEngine;
|
|
595
|
+
if (requestedAmmoEngine === 'p2' || requestedAmmoEngine === 'cannon') {
|
|
596
|
+
buildOptions.ammoReplacement = requestedAmmoEngine;
|
|
569
597
|
}
|
|
570
|
-
else {
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
598
|
+
else if (options.replaceAmmo && ammoCheck.hasAmmo && ammoCheck.suggestedEngine) {
|
|
599
|
+
buildOptions.ammoReplacement = ammoCheck.suggestedEngine;
|
|
600
|
+
}
|
|
601
|
+
else if (ammoCheck.hasAmmo && !buildOptions.ammoReplacement) {
|
|
602
|
+
spinner.stop();
|
|
603
|
+
// 根据是否使用 3D 物理提供不同的选项
|
|
604
|
+
const physicsType = ammoCheck.use3dPhysics ? '3D' : '2D';
|
|
605
|
+
if (ammoCheck.use3dPhysics) {
|
|
606
|
+
// 3D 项目 - 可以使用 Cannon.js 替换
|
|
607
|
+
console.log(pc.yellow(`⚠️ 检测到 3D 物理项目,Ammo.js 约 1.8MB`));
|
|
608
|
+
console.log(pc.gray(` 提示: Cannon.js (~400KB) 可以减小 75% 的体积`));
|
|
609
|
+
const replaceAnswer = await inquirer.prompt([
|
|
610
|
+
{
|
|
611
|
+
type: 'list',
|
|
612
|
+
name: 'ammoAction',
|
|
613
|
+
message: `如何处理 Ammo.js?`,
|
|
614
|
+
choices: [
|
|
615
|
+
{ name: '保留 Ammo.js(完整物理功能,文件较大)', value: 'keep' },
|
|
616
|
+
{ name: '替换为 Cannon.js(推荐 3D 项目,~400KB,自动适配)', value: 'cannon' },
|
|
617
|
+
],
|
|
618
|
+
default: 'cannon',
|
|
619
|
+
},
|
|
620
|
+
]);
|
|
621
|
+
if (replaceAnswer.ammoAction === 'cannon') {
|
|
622
|
+
buildOptions.ammoReplacement = 'cannon';
|
|
623
|
+
console.log(pc.green(' ✓ 将使用 Cannon.js 替换 Ammo.js(自动兼容 rigidbody API)'));
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
else {
|
|
627
|
+
// 2D 项目 - 可以使用 p2.js 替换
|
|
628
|
+
const replaceAnswer = await inquirer.prompt([
|
|
629
|
+
{
|
|
630
|
+
type: 'list',
|
|
631
|
+
name: 'ammoAction',
|
|
632
|
+
message: `检测到 Ammo.js(项目使用 ${physicsType} 物理),如何处理?`,
|
|
633
|
+
choices: [
|
|
634
|
+
{ name: '不替换(保留 Ammo.js,文件较大)', value: 'keep' },
|
|
635
|
+
{ name: '替换为 p2.js(推荐 2D 项目,~60KB)', value: 'p2' },
|
|
636
|
+
],
|
|
637
|
+
default: 'p2',
|
|
638
|
+
},
|
|
639
|
+
]);
|
|
640
|
+
if (replaceAnswer.ammoAction === 'p2') {
|
|
641
|
+
buildOptions.ammoReplacement = 'p2';
|
|
642
|
+
}
|
|
586
643
|
}
|
|
644
|
+
spinner.start(pc.cyan('🔨 开始打包 Playable Ad...'));
|
|
587
645
|
}
|
|
588
|
-
spinner.start(pc.cyan('🔨 开始打包 Playable Ad...'));
|
|
589
646
|
}
|
|
590
647
|
// 2. 执行阶段2:转换为Playable Ads
|
|
591
648
|
spinner.text = pc.cyan('执行阶段2: 转换为单HTML Playable Ads...');
|
|
592
649
|
let outputPath;
|
|
593
650
|
let sizeReport;
|
|
651
|
+
// 构建选项中传递引擎类型
|
|
652
|
+
const viteBuildOptions = {
|
|
653
|
+
...buildOptions,
|
|
654
|
+
engine: detectedEngine,
|
|
655
|
+
};
|
|
594
656
|
if (buildOptions.useVite !== false) {
|
|
595
657
|
// 使用 Vite 构建
|
|
596
|
-
const viteBuilder = new ViteBuilder(baseBuildDir,
|
|
658
|
+
const viteBuilder = new ViteBuilder(baseBuildDir, viteBuildOptions);
|
|
597
659
|
outputPath = await viteBuilder.build();
|
|
598
660
|
sizeReport = viteBuilder.getSizeReport();
|
|
599
661
|
}
|
|
600
662
|
else {
|
|
601
663
|
// 使用旧的 PlayableBuilder(向后兼容)
|
|
602
|
-
const playableBuilder = new PlayableBuilder(baseBuildDir,
|
|
664
|
+
const playableBuilder = new PlayableBuilder(baseBuildDir, viteBuildOptions);
|
|
603
665
|
outputPath = await playableBuilder.build();
|
|
604
666
|
sizeReport = playableBuilder.getSizeReport();
|
|
605
667
|
}
|