@playcraft/cli 0.0.52 → 0.0.55
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/create.js +164 -98
- package/dist/index.js +4 -1
- package/dist/scaffold/templates.js +35 -0
- package/package.json +3 -3
- package/project-templates/playable-template-phaser/.playcraftignore +10 -0
- package/project-templates/playable-template-phaser/CLAUDE.md +178 -0
- package/project-templates/playable-template-phaser/PLAYCRAFT-MIGRATION.md +328 -0
- package/project-templates/playable-template-phaser/README.md +381 -0
- package/project-templates/playable-template-phaser/assets/brain.png +0 -0
- package/project-templates/playable-template-phaser/assets/button.png +0 -0
- package/project-templates/playable-template-phaser/assets/interact.png +0 -0
- package/project-templates/playable-template-phaser/assets/logo.webp +0 -0
- package/project-templates/playable-template-phaser/assets/warn_brain.png +0 -0
- package/project-templates/playable-template-phaser/builds.config.js +12 -0
- package/project-templates/playable-template-phaser/globals.d.ts +2 -0
- package/project-templates/playable-template-phaser/index.html +12 -0
- package/project-templates/playable-template-phaser/package.json +45 -0
- package/project-templates/playable-template-phaser/scripts/base64-to-mp3.js +340 -0
- package/project-templates/playable-template-phaser/scripts/convert-to-webp.js +370 -0
- package/project-templates/playable-template-phaser/src/Game.ts +154 -0
- package/project-templates/playable-template-phaser/src/config/LoadingConfig.ts +40 -0
- package/project-templates/playable-template-phaser/src/config/ThemeConfig.ts +81 -0
- package/project-templates/playable-template-phaser/src/index.css +18 -0
- package/project-templates/playable-template-phaser/src/index.html +9 -0
- package/project-templates/playable-template-phaser/src/index.ts +30 -0
- package/project-templates/playable-template-phaser/src/json5.d.ts +4 -0
- package/project-templates/playable-template-phaser/src/managers/AudioManager.ts +559 -0
- package/project-templates/playable-template-phaser/src/scenes/MainScene.ts +301 -0
- package/project-templates/playable-template-phaser/src/scenes/PreloadScene.ts +256 -0
- package/project-templates/playable-template-phaser/src/theme/README.md +194 -0
- package/project-templates/playable-template-phaser/src/theme/blue-theme/assets/.gitkeep +24 -0
- package/project-templates/playable-template-phaser/src/theme/blue-theme/index.ts +27 -0
- package/project-templates/playable-template-phaser/src/theme/blue-theme/theme.data.json5 +19 -0
- package/project-templates/playable-template-phaser/src/theme/green-theme/assets/.gitkeep +3 -0
- package/project-templates/playable-template-phaser/src/theme/green-theme/index.ts +20 -0
- package/project-templates/playable-template-phaser/src/theme/green-theme/theme.data.json5 +19 -0
- package/project-templates/playable-template-phaser/src/theme/index.ts +1 -0
- package/project-templates/playable-template-phaser/src/theme/red-theme/assets/.gitkeep +3 -0
- package/project-templates/playable-template-phaser/src/theme/red-theme/index.ts +20 -0
- package/project-templates/playable-template-phaser/src/theme/red-theme/theme.data.json5 +19 -0
- package/project-templates/playable-template-phaser/src/theme/theme.metadata.md +60 -0
- package/project-templates/playable-template-phaser/src/theme/theme.schema.json5 +149 -0
- package/project-templates/playable-template-phaser/src/ui/BrainAgeUI.ts +681 -0
- package/project-templates/playable-template-phaser/src/ui/CountdownUI.ts +565 -0
- package/project-templates/playable-template-phaser/src/ui/PlayButton.ts +51 -0
- package/project-templates/playable-template-phaser/src/ui/ScoreUI.ts +207 -0
- package/project-templates/playable-template-phaser/src/ui/SuccessUI.ts +43 -0
- package/project-templates/playable-template-phaser/src/ui/index.ts +15 -0
- package/project-templates/playable-template-phaser/tsconfig.json +26 -0
- package/project-templates/playable-template-phaser/vite.config.ts +64 -0
- package/project-templates/playable-template-phaser//346/226/260/346/211/213/345/274/200/345/217/221/346/214/207/345/215/227.md +209 -0
- package/project-templates/playable-template-three/README.md +795 -0
- package/project-templates/playable-template-three/assets/Stork.glb +0 -0
- package/project-templates/playable-template-three/assets/button.png +0 -0
- package/project-templates/playable-template-three/assets/logo.webp +0 -0
- package/project-templates/playable-template-three/build.json +7 -0
- package/project-templates/playable-template-three/builds.config.example.js +151 -0
- package/project-templates/playable-template-three/builds.config.js +158 -0
- package/project-templates/playable-template-three/package.json +36 -0
- package/project-templates/playable-template-three/scripts/convert-to-webp.js +370 -0
- package/project-templates/playable-template-three/scripts/theme-builds.js +514 -0
- package/project-templates/playable-template-three/src/Game.ts +215 -0
- package/project-templates/playable-template-three/src/config/LoadingConfig.ts +27 -0
- package/project-templates/playable-template-three/src/config/ThemeConfig.ts +36 -0
- package/project-templates/playable-template-three/src/global.d.ts +108 -0
- package/project-templates/playable-template-three/src/index.css +18 -0
- package/project-templates/playable-template-three/src/index.html +9 -0
- package/project-templates/playable-template-three/src/index.ts +20 -0
- package/project-templates/playable-template-three/src/managers/AudioManager.ts +538 -0
- package/project-templates/playable-template-three/src/scenes/PreloadScene.ts +305 -0
- package/project-templates/playable-template-three/src/theme/README.md +225 -0
- package/project-templates/playable-template-three/src/theme/blue-theme/assets/.gitkeep +28 -0
- package/project-templates/playable-template-three/src/theme/blue-theme/index.ts +45 -0
- package/project-templates/playable-template-three/src/theme/current.ts +12 -0
- package/project-templates/playable-template-three/src/theme/green-theme/assets/.gitkeep +3 -0
- package/project-templates/playable-template-three/src/theme/green-theme/index.ts +34 -0
- package/project-templates/playable-template-three/src/theme/red-theme/assets/.gitkeep +3 -0
- package/project-templates/playable-template-three/src/theme/red-theme/index.ts +34 -0
- package/project-templates/playable-template-three/tsconfig.json +24 -0
|
@@ -0,0 +1,514 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 批量构建脚本 - Three.js Template
|
|
5
|
+
*
|
|
6
|
+
* 功能:
|
|
7
|
+
* - 支持多主题自动切换构建(可选)
|
|
8
|
+
* - 支持多渠道批量构建
|
|
9
|
+
* - 自动生成规范文件名
|
|
10
|
+
* - 灵活的配置系统
|
|
11
|
+
*
|
|
12
|
+
* 使用方法:
|
|
13
|
+
* npm run build:all # 构建所有(使用默认配置)
|
|
14
|
+
* npm run builds # 同上(别名)
|
|
15
|
+
* npm run builds -- -t "theme1,theme2" # 构建指定主题
|
|
16
|
+
* npm run builds -- -c "google,facebook" # 构建指定渠道
|
|
17
|
+
* npm run builds -- -t "theme1" -c "google" # 构建指定主题和渠道
|
|
18
|
+
*
|
|
19
|
+
* 配置文件(可选):
|
|
20
|
+
* 在项目根目录创建 builds.config.js 来自定义配置
|
|
21
|
+
* 如果不存在配置文件,将使用默认配置(无主题切换,多渠道构建)
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
const { execSync } = require('child_process');
|
|
25
|
+
const fs = require('fs');
|
|
26
|
+
const path = require('path');
|
|
27
|
+
|
|
28
|
+
// ==================== 默认配置 ====================
|
|
29
|
+
|
|
30
|
+
const DEFAULT_CONFIG = {
|
|
31
|
+
// 项目基本信息
|
|
32
|
+
projectName: 'Three Game',
|
|
33
|
+
projectCode: '0001',
|
|
34
|
+
|
|
35
|
+
// 渠道列表
|
|
36
|
+
channels: [
|
|
37
|
+
'google',
|
|
38
|
+
'facebook',
|
|
39
|
+
'tiktok',
|
|
40
|
+
'applovin',
|
|
41
|
+
'ironSource',
|
|
42
|
+
'unity',
|
|
43
|
+
'moloco',
|
|
44
|
+
'liftoff',
|
|
45
|
+
'bigoads'
|
|
46
|
+
],
|
|
47
|
+
|
|
48
|
+
// 主题配置
|
|
49
|
+
themes: {
|
|
50
|
+
enabled: false, // 是否启用主题切换
|
|
51
|
+
sourceDir: 'src/theme', // 主题目录
|
|
52
|
+
entryFile: 'src/index.ts', // 入口文件
|
|
53
|
+
whitelist: null, // 主题白名单函数(返回允许的主题列表)
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
// 文件命名配置
|
|
57
|
+
naming: {
|
|
58
|
+
language: 'EN',
|
|
59
|
+
format: '无',
|
|
60
|
+
supplier: 'AIX',
|
|
61
|
+
follower: 'ZQL',
|
|
62
|
+
// 自定义文件名生成函数
|
|
63
|
+
// generator: (themeName, channel, date, month) => { ... }
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
// 构建配置
|
|
67
|
+
build: {
|
|
68
|
+
outputDir: 'dist-all', // 批量构建输出目录
|
|
69
|
+
tempDir: 'dist', // 单次构建临时目录
|
|
70
|
+
continueOnError: true, // 单个渠道失败后是否继续
|
|
71
|
+
cleanTemp: true, // 构建后是否清理临时目录
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// ==================== 工具函数 ====================
|
|
76
|
+
|
|
77
|
+
let isExiting = false;
|
|
78
|
+
let backupContent = null;
|
|
79
|
+
let backupPath = null;
|
|
80
|
+
|
|
81
|
+
function log(message, level = 'info') {
|
|
82
|
+
const timestamp = new Date().toLocaleTimeString();
|
|
83
|
+
const colors = {
|
|
84
|
+
info: '\x1b[36m', // cyan
|
|
85
|
+
success: '\x1b[32m', // green
|
|
86
|
+
error: '\x1b[31m', // red
|
|
87
|
+
warn: '\x1b[33m', // yellow
|
|
88
|
+
debug: '\x1b[90m' // gray
|
|
89
|
+
};
|
|
90
|
+
const reset = '\x1b[0m';
|
|
91
|
+
const prefix = {
|
|
92
|
+
info: '📦',
|
|
93
|
+
success: '✅',
|
|
94
|
+
error: '❌',
|
|
95
|
+
warn: '⚠️',
|
|
96
|
+
debug: '🔍'
|
|
97
|
+
}[level] || 'ℹ️';
|
|
98
|
+
|
|
99
|
+
console.log(`${colors[level]}[${timestamp}] ${prefix} ${message}${reset}`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function getCurrentMonth() {
|
|
103
|
+
const month = new Date().getMonth() + 1;
|
|
104
|
+
return `${month}月`;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function getCurrentDate() {
|
|
108
|
+
const date = new Date();
|
|
109
|
+
const year = date.getFullYear();
|
|
110
|
+
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
111
|
+
const day = String(date.getDate()).padStart(2, '0');
|
|
112
|
+
return `${year}${month}${day}`;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* 默认文件名生成器
|
|
117
|
+
*/
|
|
118
|
+
function defaultFileNameGenerator(config, themeName, channel) {
|
|
119
|
+
const parts = [
|
|
120
|
+
themeName,
|
|
121
|
+
config.naming.language,
|
|
122
|
+
config.naming.format,
|
|
123
|
+
getCurrentMonth(),
|
|
124
|
+
getCurrentDate(),
|
|
125
|
+
config.naming.supplier,
|
|
126
|
+
config.naming.follower,
|
|
127
|
+
channel.toLowerCase()
|
|
128
|
+
];
|
|
129
|
+
|
|
130
|
+
return parts.join('-');
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* 获取所有主题
|
|
135
|
+
*/
|
|
136
|
+
function getAllThemes(config) {
|
|
137
|
+
const themesDir = path.join(PROJECT_ROOT, config.themes.sourceDir);
|
|
138
|
+
|
|
139
|
+
if (!fs.existsSync(themesDir)) {
|
|
140
|
+
log(`主题目录不存在: ${themesDir}`, 'warn');
|
|
141
|
+
return [];
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const entries = fs.readdirSync(themesDir, { withFileTypes: true });
|
|
145
|
+
let themes = entries
|
|
146
|
+
.filter(entry => entry.isDirectory())
|
|
147
|
+
.map(entry => entry.name)
|
|
148
|
+
.sort();
|
|
149
|
+
|
|
150
|
+
// 应用白名单过滤
|
|
151
|
+
if (config.themes.whitelist && typeof config.themes.whitelist === 'function') {
|
|
152
|
+
themes = themes.filter(config.themes.whitelist);
|
|
153
|
+
log(`应用主题白名单过滤: ${themes.length} 个主题`, 'debug');
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return themes;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* 备份入口文件
|
|
161
|
+
*/
|
|
162
|
+
function backupEntryFile(config) {
|
|
163
|
+
const entryPath = path.join(PROJECT_ROOT, config.themes.entryFile);
|
|
164
|
+
|
|
165
|
+
if (!fs.existsSync(entryPath)) {
|
|
166
|
+
throw new Error(`入口文件不存在: ${entryPath}`);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
backupPath = entryPath;
|
|
170
|
+
backupContent = fs.readFileSync(entryPath, 'utf8');
|
|
171
|
+
log(`已备份入口文件: ${config.themes.entryFile}`, 'debug');
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* 恢复入口文件
|
|
176
|
+
*/
|
|
177
|
+
function restoreEntryFile() {
|
|
178
|
+
if (backupPath && backupContent) {
|
|
179
|
+
fs.writeFileSync(backupPath, backupContent, 'utf8');
|
|
180
|
+
log(`已恢复入口文件`, 'debug');
|
|
181
|
+
backupPath = null;
|
|
182
|
+
backupContent = null;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* 切换主题
|
|
188
|
+
*/
|
|
189
|
+
function switchTheme(config, themeName) {
|
|
190
|
+
const entryPath = path.join(PROJECT_ROOT, config.themes.entryFile);
|
|
191
|
+
const themePath = `./${config.themes.sourceDir}/${themeName}`;
|
|
192
|
+
|
|
193
|
+
const content = `export { default } from '${themePath}';\n`;
|
|
194
|
+
fs.writeFileSync(entryPath, content, 'utf8');
|
|
195
|
+
|
|
196
|
+
log(`已切换主题: ${themeName}`, 'debug');
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* 构建单个渠道
|
|
201
|
+
*/
|
|
202
|
+
function buildChannel(config, themeName, channel, outputDir) {
|
|
203
|
+
log(`开始构建: 主题=${themeName}, 渠道=${channel}`);
|
|
204
|
+
|
|
205
|
+
try {
|
|
206
|
+
// 生成文件名
|
|
207
|
+
const fileName = config.naming.generator
|
|
208
|
+
? config.naming.generator(themeName, channel, getCurrentDate(), getCurrentMonth())
|
|
209
|
+
: defaultFileNameGenerator(config, themeName, channel);
|
|
210
|
+
|
|
211
|
+
log(`文件名: ${fileName}`, 'debug');
|
|
212
|
+
|
|
213
|
+
// 更新 build.json
|
|
214
|
+
const buildJsonPath = path.join(PROJECT_ROOT, 'build.json');
|
|
215
|
+
const buildConfig = JSON.parse(fs.readFileSync(buildJsonPath, 'utf8'));
|
|
216
|
+
const originalFileName = buildConfig.filename;
|
|
217
|
+
buildConfig.filename = fileName;
|
|
218
|
+
fs.writeFileSync(buildJsonPath, JSON.stringify(buildConfig, null, 2));
|
|
219
|
+
|
|
220
|
+
// 执行构建
|
|
221
|
+
execSync(`npm run build -- ${channel}`, {
|
|
222
|
+
cwd: PROJECT_ROOT,
|
|
223
|
+
stdio: 'inherit'
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
// 恢复 build.json
|
|
227
|
+
buildConfig.filename = originalFileName;
|
|
228
|
+
fs.writeFileSync(buildJsonPath, JSON.stringify(buildConfig, null, 2));
|
|
229
|
+
|
|
230
|
+
// 移动构建产物
|
|
231
|
+
const tempDistDir = path.join(PROJECT_ROOT, config.build.tempDir);
|
|
232
|
+
const targetDir = path.join(outputDir, themeName, channel);
|
|
233
|
+
|
|
234
|
+
if (fs.existsSync(tempDistDir)) {
|
|
235
|
+
if (!fs.existsSync(targetDir)) {
|
|
236
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const files = fs.readdirSync(tempDistDir);
|
|
240
|
+
files.forEach(file => {
|
|
241
|
+
const srcPath = path.join(tempDistDir, file);
|
|
242
|
+
const destPath = path.join(targetDir, file);
|
|
243
|
+
|
|
244
|
+
if (fs.statSync(srcPath).isDirectory()) {
|
|
245
|
+
copyDirRecursive(srcPath, destPath);
|
|
246
|
+
} else {
|
|
247
|
+
fs.copyFileSync(srcPath, destPath);
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
log(`✓ 构建成功: ${themeName}/${channel}`, 'success');
|
|
252
|
+
}
|
|
253
|
+
} catch (error) {
|
|
254
|
+
log(`构建失败: ${themeName}/${channel} - ${error.message}`, 'error');
|
|
255
|
+
|
|
256
|
+
if (!config.build.continueOnError) {
|
|
257
|
+
throw error;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* 递归复制目录
|
|
264
|
+
*/
|
|
265
|
+
function copyDirRecursive(src, dest) {
|
|
266
|
+
if (!fs.existsSync(dest)) {
|
|
267
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
271
|
+
|
|
272
|
+
for (const entry of entries) {
|
|
273
|
+
const srcPath = path.join(src, entry.name);
|
|
274
|
+
const destPath = path.join(dest, entry.name);
|
|
275
|
+
|
|
276
|
+
if (entry.isDirectory()) {
|
|
277
|
+
copyDirRecursive(srcPath, destPath);
|
|
278
|
+
} else {
|
|
279
|
+
fs.copyFileSync(srcPath, destPath);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* 清理临时目录
|
|
286
|
+
*/
|
|
287
|
+
function cleanTempDir(config) {
|
|
288
|
+
const tempDir = path.join(PROJECT_ROOT, config.build.tempDir);
|
|
289
|
+
if (fs.existsSync(tempDir)) {
|
|
290
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* 解析命令行参数
|
|
296
|
+
*/
|
|
297
|
+
function parseArgs() {
|
|
298
|
+
const args = process.argv.slice(2);
|
|
299
|
+
const result = {
|
|
300
|
+
themes: null,
|
|
301
|
+
channels: null
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
for (let i = 0; i < args.length; i++) {
|
|
305
|
+
if (args[i] === '-t' || args[i] === '--themes') {
|
|
306
|
+
result.themes = args[i + 1]?.split(',').map(t => t.trim()).filter(Boolean);
|
|
307
|
+
i++;
|
|
308
|
+
} else if (args[i] === '-c' || args[i] === '--channels') {
|
|
309
|
+
result.channels = args[i + 1]?.split(',').map(c => c.trim()).filter(Boolean);
|
|
310
|
+
i++;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return result;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* 加载配置文件
|
|
319
|
+
*/
|
|
320
|
+
function loadConfig() {
|
|
321
|
+
const configPath = path.join(PROJECT_ROOT, 'builds.config.js');
|
|
322
|
+
|
|
323
|
+
if (fs.existsSync(configPath)) {
|
|
324
|
+
log('加载配置文件: builds.config.js', 'debug');
|
|
325
|
+
const userConfig = require(configPath);
|
|
326
|
+
return mergeConfig(DEFAULT_CONFIG, userConfig);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
log('使用默认配置', 'debug');
|
|
330
|
+
return { ...DEFAULT_CONFIG };
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* 深度合并配置
|
|
335
|
+
*/
|
|
336
|
+
function mergeConfig(defaultConfig, userConfig) {
|
|
337
|
+
const result = { ...defaultConfig };
|
|
338
|
+
|
|
339
|
+
for (const key in userConfig) {
|
|
340
|
+
if (typeof userConfig[key] === 'object' && !Array.isArray(userConfig[key])) {
|
|
341
|
+
result[key] = { ...defaultConfig[key], ...userConfig[key] };
|
|
342
|
+
} else {
|
|
343
|
+
result[key] = userConfig[key];
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
return result;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* 打印配置摘要
|
|
352
|
+
*/
|
|
353
|
+
function printSummary(config, themes, channels) {
|
|
354
|
+
const totalBuilds = themes.length * channels.length;
|
|
355
|
+
|
|
356
|
+
log('========================================');
|
|
357
|
+
log('开始批量构建任务');
|
|
358
|
+
log(`项目: ${config.projectName}`);
|
|
359
|
+
log(`主题数: ${themes.length}`);
|
|
360
|
+
log(`渠道数: ${channels.length}`);
|
|
361
|
+
log(`总构建数: ${totalBuilds}`);
|
|
362
|
+
log('========================================\n');
|
|
363
|
+
|
|
364
|
+
if (themes.length <= 10) {
|
|
365
|
+
log(`主题列表: ${themes.join(', ')}`, 'debug');
|
|
366
|
+
}
|
|
367
|
+
log(`渠道列表: ${channels.join(', ')}`, 'debug');
|
|
368
|
+
log('');
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// ==================== 主函数 ====================
|
|
372
|
+
|
|
373
|
+
const PROJECT_ROOT = path.resolve(__dirname, '..');
|
|
374
|
+
|
|
375
|
+
async function main() {
|
|
376
|
+
const startTime = Date.now();
|
|
377
|
+
|
|
378
|
+
try {
|
|
379
|
+
// 加载配置
|
|
380
|
+
const config = loadConfig();
|
|
381
|
+
|
|
382
|
+
// 解析命令行参数
|
|
383
|
+
const args = parseArgs();
|
|
384
|
+
|
|
385
|
+
// 确定主题列表
|
|
386
|
+
let themes;
|
|
387
|
+
if (!config.themes.enabled) {
|
|
388
|
+
// 不启用主题切换,使用项目名称作为单一"主题"
|
|
389
|
+
themes = [config.projectName.replace(/\s+/g, '-')];
|
|
390
|
+
} else if (args.themes) {
|
|
391
|
+
themes = args.themes;
|
|
392
|
+
log(`使用命令行指定的主题: ${themes.join(', ')}`, 'info');
|
|
393
|
+
} else {
|
|
394
|
+
themes = getAllThemes(config);
|
|
395
|
+
if (themes.length === 0) {
|
|
396
|
+
throw new Error('未找到任何主题');
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// 确定渠道列表
|
|
401
|
+
const channels = args.channels || config.channels;
|
|
402
|
+
|
|
403
|
+
// 打印摘要
|
|
404
|
+
printSummary(config, themes, channels);
|
|
405
|
+
|
|
406
|
+
// 备份入口文件(如果启用主题切换)
|
|
407
|
+
if (config.themes.enabled) {
|
|
408
|
+
backupEntryFile(config);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// 批量构建
|
|
412
|
+
const outputDir = path.join(PROJECT_ROOT, config.build.outputDir);
|
|
413
|
+
let completedBuilds = 0;
|
|
414
|
+
const totalBuilds = themes.length * channels.length;
|
|
415
|
+
const failedBuilds = [];
|
|
416
|
+
|
|
417
|
+
for (const theme of themes) {
|
|
418
|
+
// 切换主题
|
|
419
|
+
if (config.themes.enabled) {
|
|
420
|
+
switchTheme(config, theme);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
for (const channel of channels) {
|
|
424
|
+
try {
|
|
425
|
+
// 清理临时目录
|
|
426
|
+
if (config.build.cleanTemp) {
|
|
427
|
+
cleanTempDir(config);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// 构建
|
|
431
|
+
buildChannel(config, theme, channel, outputDir);
|
|
432
|
+
|
|
433
|
+
completedBuilds++;
|
|
434
|
+
const progress = Math.round(completedBuilds / totalBuilds * 100);
|
|
435
|
+
log(`进度: ${completedBuilds}/${totalBuilds} (${progress}%)`, 'success');
|
|
436
|
+
} catch (error) {
|
|
437
|
+
failedBuilds.push({ theme, channel, error: error.message });
|
|
438
|
+
|
|
439
|
+
if (!config.build.continueOnError) {
|
|
440
|
+
throw error;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
log(`跳过失败的构建: ${theme}/${channel}`, 'warn');
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// 恢复入口文件
|
|
449
|
+
if (config.themes.enabled) {
|
|
450
|
+
restoreEntryFile();
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// 清理临时目录
|
|
454
|
+
if (config.build.cleanTemp) {
|
|
455
|
+
cleanTempDir(config);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// 打印结果
|
|
459
|
+
const duration = ((Date.now() - startTime) / 1000 / 60).toFixed(2);
|
|
460
|
+
log('\n========================================');
|
|
461
|
+
log('🎉 所有构建任务完成!', 'success');
|
|
462
|
+
log(`✓ 成功构建: ${completedBuilds}/${totalBuilds}`);
|
|
463
|
+
|
|
464
|
+
if (failedBuilds.length > 0) {
|
|
465
|
+
log(`✗ 失败构建: ${failedBuilds.length}`, 'warn');
|
|
466
|
+
failedBuilds.forEach(({ theme, channel, error }) => {
|
|
467
|
+
log(` - ${theme}/${channel}: ${error}`, 'error');
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
log(`⏱️ 耗时: ${duration} 分钟`);
|
|
472
|
+
log(`📁 输出目录: ${outputDir}`);
|
|
473
|
+
log('========================================');
|
|
474
|
+
|
|
475
|
+
} catch (error) {
|
|
476
|
+
log(`严重错误: ${error.message}`, 'error');
|
|
477
|
+
console.error(error);
|
|
478
|
+
|
|
479
|
+
// 确保恢复入口文件
|
|
480
|
+
restoreEntryFile();
|
|
481
|
+
|
|
482
|
+
process.exit(1);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// ==================== 信号处理 ====================
|
|
487
|
+
|
|
488
|
+
function cleanup() {
|
|
489
|
+
if (isExiting) return;
|
|
490
|
+
isExiting = true;
|
|
491
|
+
|
|
492
|
+
log('\n收到中断信号,正在清理...', 'warn');
|
|
493
|
+
|
|
494
|
+
restoreEntryFile();
|
|
495
|
+
|
|
496
|
+
const config = loadConfig();
|
|
497
|
+
if (config.build.cleanTemp) {
|
|
498
|
+
cleanTempDir(config);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
process.exit(1);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
process.on('SIGINT', cleanup);
|
|
505
|
+
process.on('SIGTERM', cleanup);
|
|
506
|
+
|
|
507
|
+
// ==================== 执行 ====================
|
|
508
|
+
|
|
509
|
+
main().catch(error => {
|
|
510
|
+
log(`未捕获的错误: ${error.message}`, 'error');
|
|
511
|
+
console.error(error);
|
|
512
|
+
restoreEntryFile();
|
|
513
|
+
process.exit(1);
|
|
514
|
+
});
|