@playcraft/cli 0.0.8 → 0.0.10
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 +108 -0
- 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
|
}
|
|
@@ -162,11 +211,64 @@ export async function buildCommand(projectPath, options) {
|
|
|
162
211
|
options.output = fileConfig.outputDir;
|
|
163
212
|
}
|
|
164
213
|
}
|
|
214
|
+
// 解析场景选择参数(需要在 baseOnly 之前)
|
|
215
|
+
let selectedScenes;
|
|
216
|
+
if (options.scenes) {
|
|
217
|
+
// 命令行指定了场景参数
|
|
218
|
+
selectedScenes = options.scenes.split(',').map(s => s.trim()).filter(s => s);
|
|
219
|
+
if (selectedScenes.length > 0) {
|
|
220
|
+
console.log(`\n🎬 选中场景: ${selectedScenes.join(', ')}`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
// 没有指定场景参数,检测项目场景并提供交互选择
|
|
225
|
+
spinner.stop();
|
|
226
|
+
const projectScenes = await detectProjectScenes(resolvedProjectPath);
|
|
227
|
+
if (projectScenes.length > 1) {
|
|
228
|
+
// 有多个场景,提示用户选择
|
|
229
|
+
console.log(pc.cyan(`\n🎬 检测到 ${projectScenes.length} 个场景`));
|
|
230
|
+
console.log(pc.dim('💡 提示: 只打包选中的场景可以显著减小文件大小(通常可减小 30-60%)\n'));
|
|
231
|
+
const sceneAnswer = await inquirer.prompt([
|
|
232
|
+
{
|
|
233
|
+
type: 'checkbox',
|
|
234
|
+
name: 'selectedScenes',
|
|
235
|
+
message: '选择要打包的场景(使用空格选择,回车确认):',
|
|
236
|
+
choices: projectScenes.map(scene => ({
|
|
237
|
+
name: scene.name,
|
|
238
|
+
value: scene.name,
|
|
239
|
+
checked: true // 默认全选
|
|
240
|
+
})),
|
|
241
|
+
validate: (answer) => {
|
|
242
|
+
if (answer.length === 0) {
|
|
243
|
+
return '请至少选择一个场景';
|
|
244
|
+
}
|
|
245
|
+
return true;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
]);
|
|
249
|
+
selectedScenes = sceneAnswer.selectedScenes;
|
|
250
|
+
if (selectedScenes && selectedScenes.length > 0) {
|
|
251
|
+
if (selectedScenes.length === projectScenes.length) {
|
|
252
|
+
console.log(pc.green(`✅ 已选择所有场景 (${selectedScenes.length} 个)`));
|
|
253
|
+
// 全选则不传递参数(向后兼容)
|
|
254
|
+
selectedScenes = undefined;
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
console.log(pc.green(`✅ 已选择 ${selectedScenes.length} / ${projectScenes.length} 个场景: ${selectedScenes.join(', ')}`));
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
else if (projectScenes.length === 1) {
|
|
262
|
+
console.log(pc.dim(`ℹ️ 项目只有 1 个场景: ${projectScenes[0].name}`));
|
|
263
|
+
}
|
|
264
|
+
spinner.start(pc.cyan('🔨 开始打包 Playable Ad...'));
|
|
265
|
+
}
|
|
165
266
|
// 如果只执行基础构建
|
|
166
267
|
if (options.baseOnly || options.mode === 'base') {
|
|
167
268
|
spinner.text = pc.cyan('执行阶段1: 基础构建...');
|
|
168
269
|
const baseBuilder = new BaseBuilder(resolvedProjectPath, {
|
|
169
270
|
outputDir: path.resolve(options.output || './build'),
|
|
271
|
+
selectedScenes,
|
|
170
272
|
});
|
|
171
273
|
const baseBuild = await baseBuilder.build();
|
|
172
274
|
spinner.succeed(pc.green('✅ 基础构建完成!'));
|
|
@@ -188,6 +290,9 @@ export async function buildCommand(projectPath, options) {
|
|
|
188
290
|
'liftoff',
|
|
189
291
|
'moloco',
|
|
190
292
|
'bigo',
|
|
293
|
+
'inmobi',
|
|
294
|
+
'adikteev',
|
|
295
|
+
'remerge',
|
|
191
296
|
];
|
|
192
297
|
if (!options.platform || !validPlatforms.includes(options.platform)) {
|
|
193
298
|
spinner.stop();
|
|
@@ -243,6 +348,8 @@ export async function buildCommand(projectPath, options) {
|
|
|
243
348
|
convertToWebP: options.convertToWebP,
|
|
244
349
|
compressModels: options.compressModels,
|
|
245
350
|
modelCompression: options.modelCompression,
|
|
351
|
+
// 场景选择
|
|
352
|
+
selectedScenes,
|
|
246
353
|
};
|
|
247
354
|
if (fileConfig) {
|
|
248
355
|
Object.assign(buildOptions, fileConfig);
|
|
@@ -269,6 +376,7 @@ export async function buildCommand(projectPath, options) {
|
|
|
269
376
|
const tempDir = path.join(os.tmpdir(), `playcraft-base-build-${Date.now()}`);
|
|
270
377
|
const baseBuilder = new BaseBuilder(resolvedProjectPath, {
|
|
271
378
|
outputDir: tempDir,
|
|
379
|
+
selectedScenes,
|
|
272
380
|
});
|
|
273
381
|
const baseBuild = await baseBuilder.build();
|
|
274
382
|
baseBuildDir = baseBuild.outputDir;
|
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.10",
|
|
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.3",
|
|
22
24
|
"chokidar": "^4.0.3",
|
|
23
25
|
"commander": "^13.1.0",
|
|
24
26
|
"cors": "^2.8.5",
|