@playcraft/cli 0.0.9 → 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/README.md +60 -4
- package/dist/commands/build.js +204 -15
- package/dist/index.js +9 -1
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -11,8 +11,10 @@ PlayCraft CLI 是一个功能强大的命令行工具,支持本地代码与云
|
|
|
11
11
|
|
|
12
12
|
- 🚀 **本地开发隧道** - 在本地 IDE 编写代码,云端编辑器实时预览与热更新
|
|
13
13
|
- 📦 **Playable Ads 打包** - 支持 10 个主流广告平台的一键打包
|
|
14
|
+
- 🎬 **智能场景选择** - 交互式场景选择,可减小 30-60% 文件大小(**新功能**)
|
|
14
15
|
- 🎯 **两阶段构建** - Base Build + Channel Build,灵活可控
|
|
15
16
|
- ⚡ **Vite 构建** - 使用 Vite 进行资源内联与压缩,性能优异
|
|
17
|
+
- 🖼️ **资源优化** - 自动图片压缩(PNG → WebP)、模型压缩(Draco)
|
|
16
18
|
- 🔧 **交互式配置** - 友好的命令行交互,快速上手
|
|
17
19
|
- 📊 **构建分析** - 生成详细的打包分析报告
|
|
18
20
|
|
|
@@ -140,6 +142,16 @@ done
|
|
|
140
142
|
### 构建优化
|
|
141
143
|
|
|
142
144
|
```bash
|
|
145
|
+
# 🎬 场景选择(推荐!可减小 30-60% 文件大小)
|
|
146
|
+
# 交互式选择(自动检测多个场景并提示)
|
|
147
|
+
playcraft build --platform facebook
|
|
148
|
+
|
|
149
|
+
# 指定特定场景
|
|
150
|
+
playcraft build --platform facebook --scenes BallGame
|
|
151
|
+
|
|
152
|
+
# 指定多个场景
|
|
153
|
+
playcraft build --platform facebook --scenes "MainMenu,Gameplay,Settings"
|
|
154
|
+
|
|
143
155
|
# 启用图片压缩
|
|
144
156
|
playcraft build --platform facebook --compress-images --image-quality 75
|
|
145
157
|
|
|
@@ -148,6 +160,9 @@ playcraft build --platform facebook --compress-models --model-compression draco
|
|
|
148
160
|
|
|
149
161
|
# 生成分析报告
|
|
150
162
|
playcraft build --platform facebook --analyze
|
|
163
|
+
|
|
164
|
+
# 组合使用(场景选择 + 图片压缩 + 分析)
|
|
165
|
+
playcraft build --platform facebook --scenes MainScene --compress-images --analyze
|
|
151
166
|
```
|
|
152
167
|
|
|
153
168
|
## ⚙️ 配置
|
|
@@ -190,13 +205,54 @@ CLI 会在 `~/.playcraft/` 目录下存储:
|
|
|
190
205
|
- `pids/` - 进程文件
|
|
191
206
|
- `logs/` - 日志文件
|
|
192
207
|
|
|
208
|
+
## 🆕 最新功能
|
|
209
|
+
|
|
210
|
+
### v0.0.9 - 场景选择优化 (2026-01-26)
|
|
211
|
+
|
|
212
|
+
当项目包含多个场景时,CLI 会自动提供交互式场景选择:
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
playcraft build --platform facebook
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
**交互界面**:
|
|
219
|
+
```
|
|
220
|
+
🎬 检测到 20 个场景
|
|
221
|
+
💡 提示: 只打包选中的场景可以显著减小文件大小(通常可减小 30-60%)
|
|
222
|
+
|
|
223
|
+
? 选择要打包的场景(使用空格选择,回车确认):
|
|
224
|
+
❯◉ MainScene
|
|
225
|
+
◉ Gameplay
|
|
226
|
+
◯ TestScene
|
|
227
|
+
◯ DebugScene
|
|
228
|
+
...
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
**优化效果**:
|
|
232
|
+
- 🎯 单场景:减小 40-60%
|
|
233
|
+
- 🎯 2-3 个场景:减小 30-50%
|
|
234
|
+
- 🎯 自动过滤未使用的资源
|
|
235
|
+
|
|
236
|
+
**命令行模式**:
|
|
237
|
+
```bash
|
|
238
|
+
# 指定单个场景
|
|
239
|
+
playcraft build --platform facebook --scenes MainScene
|
|
240
|
+
|
|
241
|
+
# 指定多个场景
|
|
242
|
+
playcraft build --platform facebook --scenes "MainScene,Gameplay"
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
详见:[场景选择功能文档](../../docs/cli/scene-selection-feature.md)
|
|
246
|
+
|
|
193
247
|
## 🔗 相关链接
|
|
194
248
|
|
|
195
249
|
- [PlayCraft 项目](https://github.com/your-org/playcraft)
|
|
196
|
-
- [完整文档](
|
|
197
|
-
- [快速开始指南](
|
|
198
|
-
- [构建命令详解](
|
|
199
|
-
- [
|
|
250
|
+
- [完整文档](../../docs/cli/README.md)
|
|
251
|
+
- [快速开始指南](../../docs/cli/quick-start.md)
|
|
252
|
+
- [构建命令详解](../../docs/cli/build.md)
|
|
253
|
+
- [场景选择功能](../../docs/cli/scene-selection-feature.md)
|
|
254
|
+
- [配置文件说明](../../docs/cli/config.md)
|
|
255
|
+
- [平台规格对照](../../docs/cli/各渠道Playable规格对照表.md)
|
|
200
256
|
|
|
201
257
|
## 📝 许可证
|
|
202
258
|
|
package/dist/commands/build.js
CHANGED
|
@@ -25,6 +25,55 @@ async function detectBaseBuild(dir) {
|
|
|
25
25
|
return false;
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* 检测项目场景列表
|
|
30
|
+
*/
|
|
31
|
+
async function detectProjectScenes(projectPath) {
|
|
32
|
+
try {
|
|
33
|
+
// 尝试读取 scenes.json
|
|
34
|
+
const scenesJsonPath = path.join(projectPath, 'scenes.json');
|
|
35
|
+
try {
|
|
36
|
+
const scenesContent = await fs.readFile(scenesJsonPath, 'utf-8');
|
|
37
|
+
const scenesData = JSON.parse(scenesContent);
|
|
38
|
+
// scenes.json 可能是对象格式(key 是场景 ID)
|
|
39
|
+
if (typeof scenesData === 'object' && !Array.isArray(scenesData)) {
|
|
40
|
+
return Object.entries(scenesData).map(([id, scene]) => ({
|
|
41
|
+
id: scene.id || scene.scene || id,
|
|
42
|
+
name: scene.name || `Scene ${id}`
|
|
43
|
+
}));
|
|
44
|
+
}
|
|
45
|
+
// 或者是数组格式
|
|
46
|
+
if (Array.isArray(scenesData)) {
|
|
47
|
+
return scenesData.map(scene => ({
|
|
48
|
+
id: scene.id || scene.scene || scene.uniqueId,
|
|
49
|
+
name: scene.name || `Scene ${scene.id}`
|
|
50
|
+
}));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
// scenes.json 不存在或解析失败,尝试其他方法
|
|
55
|
+
}
|
|
56
|
+
// 尝试从 project.json 读取
|
|
57
|
+
const projectJsonPath = path.join(projectPath, 'project.json');
|
|
58
|
+
try {
|
|
59
|
+
const projectContent = await fs.readFile(projectJsonPath, 'utf-8');
|
|
60
|
+
const projectData = JSON.parse(projectContent);
|
|
61
|
+
if (projectData.scenes && Array.isArray(projectData.scenes)) {
|
|
62
|
+
return projectData.scenes.map((scene) => ({
|
|
63
|
+
id: scene.id || scene.scene || scene.uniqueId,
|
|
64
|
+
name: scene.name || `Scene ${scene.id}`
|
|
65
|
+
}));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
// project.json 不存在或解析失败
|
|
70
|
+
}
|
|
71
|
+
return [];
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
return [];
|
|
75
|
+
}
|
|
76
|
+
}
|
|
28
77
|
function resolveSuggestedPhysicsEngine(use3dPhysics) {
|
|
29
78
|
return use3dPhysics ? 'cannon' : 'p2';
|
|
30
79
|
}
|
|
@@ -35,7 +84,36 @@ async function detectAmmoUsage(baseBuildDir) {
|
|
|
35
84
|
try {
|
|
36
85
|
const configContent = await fs.readFile(configPath, 'utf-8');
|
|
37
86
|
const configJson = JSON.parse(configContent);
|
|
87
|
+
// 检查 application_properties.use3dPhysics
|
|
38
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
|
+
}
|
|
39
117
|
if (configJson.assets) {
|
|
40
118
|
for (const asset of Object.values(configJson.assets)) {
|
|
41
119
|
const assetData = asset;
|
|
@@ -70,6 +148,7 @@ async function detectAmmoUsage(baseBuildDir) {
|
|
|
70
148
|
}
|
|
71
149
|
async function analyzeBaseBuild(baseBuildDir, ammoReplacement) {
|
|
72
150
|
const assetSizes = [];
|
|
151
|
+
const assetPaths = new Set(); // 用于去重
|
|
73
152
|
let engineSize = 0;
|
|
74
153
|
const ammoCheck = await detectAmmoUsage(baseBuildDir);
|
|
75
154
|
const engineCandidates = [
|
|
@@ -102,10 +181,15 @@ async function analyzeBaseBuild(baseBuildDir, ammoReplacement) {
|
|
|
102
181
|
if (ammoReplacement && lowerUrl.includes('ammo')) {
|
|
103
182
|
continue;
|
|
104
183
|
}
|
|
184
|
+
// 跳过已经处理过的资源
|
|
185
|
+
if (assetPaths.has(cleanUrl)) {
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
105
188
|
const assetPath = path.join(baseBuildDir, cleanUrl);
|
|
106
189
|
try {
|
|
107
190
|
const stats = await fs.stat(assetPath);
|
|
108
191
|
assetSizes.push({ path: cleanUrl, size: stats.size });
|
|
192
|
+
assetPaths.add(cleanUrl); // 标记为已处理
|
|
109
193
|
}
|
|
110
194
|
catch (error) {
|
|
111
195
|
// ignore missing assets
|
|
@@ -162,11 +246,64 @@ export async function buildCommand(projectPath, options) {
|
|
|
162
246
|
options.output = fileConfig.outputDir;
|
|
163
247
|
}
|
|
164
248
|
}
|
|
249
|
+
// 解析场景选择参数(需要在 baseOnly 之前)
|
|
250
|
+
let selectedScenes;
|
|
251
|
+
if (options.scenes) {
|
|
252
|
+
// 命令行指定了场景参数
|
|
253
|
+
selectedScenes = options.scenes.split(',').map(s => s.trim()).filter(s => s);
|
|
254
|
+
if (selectedScenes.length > 0) {
|
|
255
|
+
console.log(`\n🎬 选中场景: ${selectedScenes.join(', ')}`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
else {
|
|
259
|
+
// 没有指定场景参数,检测项目场景并提供交互选择
|
|
260
|
+
spinner.stop();
|
|
261
|
+
const projectScenes = await detectProjectScenes(resolvedProjectPath);
|
|
262
|
+
if (projectScenes.length > 1) {
|
|
263
|
+
// 有多个场景,提示用户选择
|
|
264
|
+
console.log(pc.cyan(`\n🎬 检测到 ${projectScenes.length} 个场景`));
|
|
265
|
+
console.log(pc.dim('💡 提示: 只打包选中的场景可以显著减小文件大小(通常可减小 30-60%)\n'));
|
|
266
|
+
const sceneAnswer = await inquirer.prompt([
|
|
267
|
+
{
|
|
268
|
+
type: 'checkbox',
|
|
269
|
+
name: 'selectedScenes',
|
|
270
|
+
message: '选择要打包的场景(使用空格选择,回车确认):',
|
|
271
|
+
choices: projectScenes.map(scene => ({
|
|
272
|
+
name: scene.name,
|
|
273
|
+
value: scene.name,
|
|
274
|
+
checked: true // 默认全选
|
|
275
|
+
})),
|
|
276
|
+
validate: (answer) => {
|
|
277
|
+
if (answer.length === 0) {
|
|
278
|
+
return '请至少选择一个场景';
|
|
279
|
+
}
|
|
280
|
+
return true;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
]);
|
|
284
|
+
selectedScenes = sceneAnswer.selectedScenes;
|
|
285
|
+
if (selectedScenes && selectedScenes.length > 0) {
|
|
286
|
+
if (selectedScenes.length === projectScenes.length) {
|
|
287
|
+
console.log(pc.green(`✅ 已选择所有场景 (${selectedScenes.length} 个)`));
|
|
288
|
+
// 全选则不传递参数(向后兼容)
|
|
289
|
+
selectedScenes = undefined;
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
console.log(pc.green(`✅ 已选择 ${selectedScenes.length} / ${projectScenes.length} 个场景: ${selectedScenes.join(', ')}`));
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
else if (projectScenes.length === 1) {
|
|
297
|
+
console.log(pc.dim(`ℹ️ 项目只有 1 个场景: ${projectScenes[0].name}`));
|
|
298
|
+
}
|
|
299
|
+
spinner.start(pc.cyan('🔨 开始打包 Playable Ad...'));
|
|
300
|
+
}
|
|
165
301
|
// 如果只执行基础构建
|
|
166
302
|
if (options.baseOnly || options.mode === 'base') {
|
|
167
303
|
spinner.text = pc.cyan('执行阶段1: 基础构建...');
|
|
168
304
|
const baseBuilder = new BaseBuilder(resolvedProjectPath, {
|
|
169
305
|
outputDir: path.resolve(options.output || './build'),
|
|
306
|
+
selectedScenes,
|
|
170
307
|
});
|
|
171
308
|
const baseBuild = await baseBuilder.build();
|
|
172
309
|
spinner.succeed(pc.green('✅ 基础构建完成!'));
|
|
@@ -188,6 +325,9 @@ export async function buildCommand(projectPath, options) {
|
|
|
188
325
|
'liftoff',
|
|
189
326
|
'moloco',
|
|
190
327
|
'bigo',
|
|
328
|
+
'inmobi',
|
|
329
|
+
'adikteev',
|
|
330
|
+
'remerge',
|
|
191
331
|
];
|
|
192
332
|
if (!options.platform || !validPlatforms.includes(options.platform)) {
|
|
193
333
|
spinner.stop();
|
|
@@ -243,6 +383,8 @@ export async function buildCommand(projectPath, options) {
|
|
|
243
383
|
convertToWebP: options.convertToWebP,
|
|
244
384
|
compressModels: options.compressModels,
|
|
245
385
|
modelCompression: options.modelCompression,
|
|
386
|
+
// 场景选择
|
|
387
|
+
selectedScenes,
|
|
246
388
|
};
|
|
247
389
|
if (fileConfig) {
|
|
248
390
|
Object.assign(buildOptions, fileConfig);
|
|
@@ -269,6 +411,9 @@ export async function buildCommand(projectPath, options) {
|
|
|
269
411
|
const tempDir = path.join(os.tmpdir(), `playcraft-base-build-${Date.now()}`);
|
|
270
412
|
const baseBuilder = new BaseBuilder(resolvedProjectPath, {
|
|
271
413
|
outputDir: tempDir,
|
|
414
|
+
selectedScenes,
|
|
415
|
+
analyze: buildOptions.analyze,
|
|
416
|
+
analyzeReportPath: buildOptions.analyze ? 'base-bundle-report.html' : undefined,
|
|
272
417
|
});
|
|
273
418
|
const baseBuild = await baseBuilder.build();
|
|
274
419
|
baseBuildDir = baseBuild.outputDir;
|
|
@@ -282,18 +427,48 @@ export async function buildCommand(projectPath, options) {
|
|
|
282
427
|
else if (options.replaceAmmo && ammoCheck.hasAmmo && ammoCheck.suggestedEngine) {
|
|
283
428
|
buildOptions.ammoReplacement = ammoCheck.suggestedEngine;
|
|
284
429
|
}
|
|
285
|
-
else if (ammoCheck.hasAmmo &&
|
|
430
|
+
else if (ammoCheck.hasAmmo && !buildOptions.ammoReplacement) {
|
|
286
431
|
spinner.stop();
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
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
|
+
}
|
|
297
472
|
}
|
|
298
473
|
spinner.start(pc.cyan('🔨 开始打包 Playable Ad...'));
|
|
299
474
|
}
|
|
@@ -326,12 +501,21 @@ export async function buildCommand(projectPath, options) {
|
|
|
326
501
|
console.log(` - 总计: ${totalMB} MB ${status} (限制: ${limitMB} MB)`);
|
|
327
502
|
console.log('\n' + pc.green(`输出: ${outputPath}`));
|
|
328
503
|
if (buildOptions.analyze) {
|
|
329
|
-
|
|
504
|
+
// 从 Base Build 复制分析报告到最终输出目录
|
|
505
|
+
const baseReportPath = path.join(baseBuildDir, 'base-bundle-report.html');
|
|
506
|
+
const finalReportPath = buildOptions.analyzeReportPath
|
|
330
507
|
? buildOptions.analyzeReportPath
|
|
331
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
|
+
}
|
|
332
516
|
const analysis = await analyzeBaseBuild(baseBuildDir, buildOptions.ammoReplacement);
|
|
333
517
|
console.log('\n' + pc.bold('分析报告:'));
|
|
334
|
-
console.log(` - 可视化报告: ${
|
|
518
|
+
console.log(` - 可视化报告: ${finalReportPath}`);
|
|
335
519
|
if (analysis.engineSize > 0) {
|
|
336
520
|
const engineMB = (analysis.engineSize / 1024 / 1024).toFixed(2);
|
|
337
521
|
console.log(` - 引擎大小: ${engineMB} MB`);
|
|
@@ -339,8 +523,13 @@ export async function buildCommand(projectPath, options) {
|
|
|
339
523
|
if (analysis.topAssets.length > 0) {
|
|
340
524
|
console.log(' - 资源 Top:');
|
|
341
525
|
analysis.topAssets.forEach((asset) => {
|
|
342
|
-
const
|
|
343
|
-
|
|
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}`);
|
|
344
533
|
});
|
|
345
534
|
}
|
|
346
535
|
if (analysis.hasAmmo) {
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command } from 'commander';
|
|
3
|
+
import { readFileSync } from 'fs';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import { dirname, join } from 'path';
|
|
3
6
|
import { initCommand } from './commands/init.js';
|
|
4
7
|
import { startCommand, startInternal } from './commands/start.js';
|
|
5
8
|
import { stopCommand } from './commands/stop.js';
|
|
@@ -7,11 +10,14 @@ import { statusCommand } from './commands/status.js';
|
|
|
7
10
|
import { logsCommand } from './commands/logs.js';
|
|
8
11
|
import { configCommand } from './commands/config.js';
|
|
9
12
|
import { buildCommand } from './commands/build.js';
|
|
13
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
14
|
+
const __dirname = dirname(__filename);
|
|
15
|
+
const packageJson = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf-8'));
|
|
10
16
|
const program = new Command();
|
|
11
17
|
program
|
|
12
18
|
.name('playcraft')
|
|
13
19
|
.description('PlayCraft Local Dev Agent - 本地开发助手')
|
|
14
|
-
.version(
|
|
20
|
+
.version(packageJson.version);
|
|
15
21
|
// init 命令
|
|
16
22
|
program
|
|
17
23
|
.command('init')
|
|
@@ -85,6 +91,7 @@ program
|
|
|
85
91
|
.option('-f, --format <format>', '输出格式 (html|zip)', 'html')
|
|
86
92
|
.option('-o, --output <path>', '输出目录', './dist')
|
|
87
93
|
.option('-c, --config <file>', '配置文件路径')
|
|
94
|
+
.option('-s, --scenes <scenes>', '选择场景(逗号分隔),例如: MainMenu,Gameplay')
|
|
88
95
|
.option('--compress', '压缩引擎代码')
|
|
89
96
|
.option('--analyze', '生成打包分析报告')
|
|
90
97
|
.option('--replace-ammo', '检测到 Ammo 时自动替换为 p2 或 cannon')
|
|
@@ -115,6 +122,7 @@ program
|
|
|
115
122
|
.description('生成可运行的多文件构建产物(阶段1)')
|
|
116
123
|
.argument('<project-path>', '项目路径')
|
|
117
124
|
.option('-o, --output <path>', '输出目录', './build')
|
|
125
|
+
.option('-s, --scenes <scenes>', '选择场景(逗号分隔),例如: MainMenu,Gameplay')
|
|
118
126
|
.action(async (projectPath, options) => {
|
|
119
127
|
await buildCommand(projectPath, {
|
|
120
128
|
...options,
|
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": {
|
|
@@ -15,10 +15,12 @@
|
|
|
15
15
|
"dev": "tsc -w",
|
|
16
16
|
"build": "tsc",
|
|
17
17
|
"start": "node dist/index.js",
|
|
18
|
+
"link": "pnpm build && npm link",
|
|
19
|
+
"unlink": "npm unlink -g @playcraft/cli",
|
|
18
20
|
"release": "node scripts/release.js"
|
|
19
21
|
},
|
|
20
22
|
"dependencies": {
|
|
21
|
-
"@playcraft/build": "^0.0.
|
|
23
|
+
"@playcraft/build": "^0.0.4",
|
|
22
24
|
"chokidar": "^4.0.3",
|
|
23
25
|
"commander": "^13.1.0",
|
|
24
26
|
"cors": "^2.8.5",
|