@playcraft/devkit 1.0.26 → 1.0.28

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.
Files changed (56) hide show
  1. package/README.md +262 -0
  2. package/cli/bin/playable-scripts.js +44 -1
  3. package/cli/commands/build.js +221 -22
  4. package/cli/commands/builds.js +13 -2
  5. package/cli/commands/dev.js +7 -1
  6. package/cli/commands/pack.js +308 -39
  7. package/cli/commands/vite-dev.js +7 -1
  8. package/core/batch-build.js +1290 -127
  9. package/core/constants.js +16 -2
  10. package/core/cos-uploader.js +286 -23
  11. package/core/index.d.ts +8 -4
  12. package/core/index.js +76 -7
  13. package/core/loaders/gltf-loader.js +74 -28
  14. package/core/options.js +326 -15
  15. package/core/plugins/AdikteevInjectorPlugin.js +26 -4
  16. package/core/plugins/BigoAdsInjectorPlugin.js +21 -4
  17. package/core/plugins/DAPIInjectorPlugin.js +25 -2
  18. package/core/plugins/DebuggerInjectionPlugin.js +31 -3
  19. package/core/plugins/ExitAPIInjectorPlugin.js +57 -4
  20. package/core/plugins/FflateCompressionPlugin.js +231 -40
  21. package/core/plugins/FontInjectionWebpackPlugin.js +46 -4
  22. package/core/plugins/LiftoffInjectorPlugin.js +26 -4
  23. package/core/plugins/MRAIDInjectorPlugin.js +24 -2
  24. package/core/plugins/MintegralInjectorPlugin.js +25 -2
  25. package/core/plugins/PangleInjectorPlugin.js +24 -2
  26. package/core/plugins/PlaycraftOrientationInjectorPlugin.js +33 -0
  27. package/core/plugins/SnapchatInjectorPlugin.js +26 -4
  28. package/core/plugins/SocialMediaInjectorPlugin.js +119 -7
  29. package/core/plugins/TikTokInjectorPlugin.js +24 -2
  30. package/core/plugins/UnityInjectorPlugin.js +37 -8
  31. package/core/plugins/ZipPlugin.js +138 -19
  32. package/core/plugins/socialMediaVitePlugin.js +108 -2
  33. package/core/utils/buildDefines.js +31 -2
  34. package/core/utils/buildTemplateString.js +39 -5
  35. package/core/utils/date.js +16 -1
  36. package/core/utils/fontRegistry.js +81 -13
  37. package/core/utils/fontResolver.js +335 -37
  38. package/core/utils/generateAdikteevHtmlWebpackPluginConfig.js +42 -7
  39. package/core/utils/generateBigabidHtmlWebpackPluginConfig.js +30 -3
  40. package/core/utils/generateInMobiHtmlWebpackPluginConfig.js +43 -7
  41. package/core/utils/getPlaycraftOrientationSuffix.js +13 -0
  42. package/core/utils/injectSDKPlugins.js +69 -14
  43. package/core/utils/logOptions.js +46 -4
  44. package/core/utils/mergeOptions.js +19 -2
  45. package/core/utils/parseArgvOptions.js +111 -5
  46. package/core/utils/resolveChannelFold2Zip.js +15 -3
  47. package/core/utils/validateThemeData.js +227 -26
  48. package/core/validators/index.d.ts +4 -0
  49. package/core/validators/pre-build-checker.js +297 -21
  50. package/core/validators/tracking-validator.js +358 -40
  51. package/core/vite.dev.js +322 -17
  52. package/core/webpack.build.js +520 -56
  53. package/core/webpack.common.js +177 -11
  54. package/core/webpack.dev.js +88 -5
  55. package/defines.d.ts +1 -1
  56. package/package.json +3 -2
@@ -1,6 +1,9 @@
1
- "use strict";const{validateTracking,printValidationResult}=require("./tracking-validator.js");// ============================================================
1
+ const { validateTracking, printValidationResult } = require('./tracking-validator.js');
2
+
3
+ // ============================================================
2
4
  // 检查器注册表
3
5
  // ============================================================
6
+
4
7
  /**
5
8
  * 检查器定义
6
9
  * @typedef {Object} Checker
@@ -8,21 +11,145 @@
8
11
  * @property {string} description - 检查器描述
9
12
  * @property {boolean} enabled - 是否启用
10
13
  * @property {(projectRoot: string, options: any) => Promise<CheckResult>|CheckResult} validator - 验证函数
11
- *//**
14
+ */
15
+
16
+ /**
12
17
  * 检查结果
13
18
  * @typedef {Object} CheckResult
14
19
  * @property {boolean} success - 是否通过
15
20
  * @property {string} message - 结果消息
16
21
  * @property {any} [data] - 额外数据
17
- *//**
22
+ */
23
+
24
+ /**
18
25
  * 所有注册的检查器
19
26
  * @type {Map<string, Checker>}
20
- */const checkers=new Map;// ============================================================
27
+ */
28
+ const checkers = new Map();
29
+
30
+ // ============================================================
21
31
  // 内置检查器
22
32
  // ============================================================
33
+
23
34
  /**
24
35
  * 埋点验证检查器
25
- */checkers.set("tracking",{name:"\u57CB\u70B9\u9A8C\u8BC1",description:"\u9A8C\u8BC1\u9879\u76EE\u4E2D\u662F\u5426\u6B63\u786E\u8C03\u7528\u4E86\u6240\u6709\u5FC5\u987B\u7684\u57CB\u70B9\u65B9\u6CD5",enabled:true,validator:(projectRoot,options={})=>{const result=validateTracking(projectRoot,{verbose:options.verbose||false,extensions:options.extensions,excludeDirs:options.excludeDirs});return{success:result.success,message:result.success?"\u2705 \u57CB\u70B9\u9A8C\u8BC1\u901A\u8FC7":`❌ 埋点验证失败:缺少 ${result.errors.length} 个必须的埋点方法`,data:result}}});// 预留:未来可添加更多检查器
36
+ */
37
+ checkers.set('tracking', {
38
+ name: '埋点验证',
39
+ description: '验证项目中是否正确调用了所有必须的埋点方法',
40
+ enabled: true,
41
+ validator: (projectRoot, options = {}) => {
42
+ const result = validateTracking(projectRoot, {
43
+ verbose: options.verbose || false,
44
+ extensions: options.extensions,
45
+ excludeDirs: options.excludeDirs
46
+ });
47
+
48
+ return {
49
+ success: result.success,
50
+ message: result.success
51
+ ? '✅ 埋点验证通过'
52
+ : `❌ 埋点验证失败:缺少 ${result.errors.length} 个必须的埋点方法`,
53
+ data: result
54
+ };
55
+ }
56
+ });
57
+
58
+ /**
59
+ * 主题名称检查器
60
+ * 校验本次构建涉及的主题名称长度是否超过限制(不超过 50 个字符)。
61
+ * 主题名称过长可能导致下游文件名 / 目录名 / COS Key 超出系统或第三方平台限制。
62
+ *
63
+ * 分两种情况校验:
64
+ * 1. 主题名称自身长度(无论是否拼接渠道名都会超限的情况)。
65
+ * 2. 「主题名_渠道名」组合后的长度(对应批量构建产物的第二级目录名 / zip 文件名)。
66
+ * 渠道名可能带有锁定 UI 等后缀变体(如 google_L / google_P),
67
+ * 因此必须以传入的 options.channels 实际渠道列表为准逐一比对,
68
+ * 而不能只校验主题名称本身。
69
+ */
70
+ checkers.set('themeName', {
71
+ name: '主题名称检查',
72
+ description: '验证主题名称、以及「主题名_渠道名」组合后的长度是否超过限制(不超过 50 个字符)',
73
+ enabled: true,
74
+ validator: (projectRoot, options = {}) => {
75
+ const MAX_LENGTH = 50;
76
+ const themeNames = Array.isArray(options.themes)
77
+ ? options.themes.filter(name => typeof name === 'string' && name.length > 0)
78
+ : [];
79
+ const channelNames = Array.isArray(options.channels)
80
+ ? options.channels.filter(name => typeof name === 'string' && name.length > 0)
81
+ : [];
82
+
83
+ if (themeNames.length === 0) {
84
+ return {
85
+ success: true,
86
+ message: 'ℹ️ 未提供主题名称,跳过主题名称检查'
87
+ };
88
+ }
89
+
90
+ // 情况一:主题名称自身已超过限制(拼不拼渠道名都会超)
91
+ const bareInvalidThemes = themeNames.filter(name => name.length > MAX_LENGTH);
92
+ const bareInvalidThemeSet = new Set(bareInvalidThemes);
93
+
94
+ // 情况二:主题名称自身未超限,但拼接渠道名(含锁定 UI 后缀,如 google_L / google_P)后超限
95
+ const combinedViolations = [];
96
+ if (channelNames.length > 0) {
97
+ themeNames.forEach(theme => {
98
+ if (bareInvalidThemeSet.has(theme)) return; // 已在情况一中报告,避免重复
99
+
100
+ channelNames.forEach(channel => {
101
+ const combined = `${theme}_${channel}`;
102
+ if (combined.length > MAX_LENGTH) {
103
+ combinedViolations.push({
104
+ theme,
105
+ channel,
106
+ combined,
107
+ length: combined.length,
108
+ exceededBy: combined.length - MAX_LENGTH
109
+ });
110
+ }
111
+ });
112
+ });
113
+ }
114
+
115
+ if (bareInvalidThemes.length === 0 && combinedViolations.length === 0) {
116
+ return {
117
+ success: true,
118
+ message: channelNames.length > 0
119
+ ? `✅ 主题名称检查通过(共 ${themeNames.length} 个主题 × ${channelNames.length} 个渠道,「主题名_渠道名」组合均未超过 ${MAX_LENGTH} 个字符)`
120
+ : `✅ 主题名称检查通过(共 ${themeNames.length} 个主题,均未超过 ${MAX_LENGTH} 个字符)`
121
+ };
122
+ }
123
+
124
+ const lines = [];
125
+
126
+ if (bareInvalidThemes.length > 0) {
127
+ lines.push('主题名称自身已超过长度限制:');
128
+ bareInvalidThemes.forEach(name => {
129
+ lines.push(` - "${name}" (${name.length} 个字符,超出 ${name.length - MAX_LENGTH} 个字符)`);
130
+ });
131
+ }
132
+
133
+ if (combinedViolations.length > 0) {
134
+ lines.push('主题名称本身未超限,但与渠道名拼接后(第二级输出目录名 / zip 文件名)超过长度限制:');
135
+ combinedViolations.forEach(v => {
136
+ lines.push(
137
+ ` - 主题 "${v.theme}" + 渠道 "${v.channel}" → "${v.combined}" ` +
138
+ `(${v.length} 个字符,超出 ${v.exceededBy} 个字符)`
139
+ );
140
+ });
141
+ lines.push(' 提示:部分渠道可能带有锁定 UI 后缀(如 google_L / google_P),拼接后长度会更长,请一并纳入考量。');
142
+ }
143
+
144
+ return {
145
+ success: false,
146
+ message: `❌ 主题名称检查失败:\n${lines.join('\n')}`,
147
+ data: { bareInvalidThemes, combinedViolations, maxLength: MAX_LENGTH }
148
+ };
149
+ }
150
+ });
151
+
152
+ // 预留:未来可添加更多检查器
26
153
  // checkers.set('assets', {
27
154
  // name: '资源检查',
28
155
  // description: '验证项目资源文件的完整性和规范性',
@@ -32,6 +159,7 @@
32
159
  // return { success: true, message: '资源检查通过' };
33
160
  // }
34
161
  // });
162
+
35
163
  // checkers.set('config', {
36
164
  // name: '配置检查',
37
165
  // description: '验证项目配置文件的正确性',
@@ -41,23 +169,54 @@
41
169
  // return { success: true, message: '配置检查通过' };
42
170
  // }
43
171
  // });
172
+
44
173
  // ============================================================
45
174
  // 检查器管理
46
175
  // ============================================================
176
+
47
177
  /**
48
178
  * 注册自定义检查器
49
179
  * @param {string} id - 检查器 ID
50
180
  * @param {Checker} checker - 检查器配置
51
- */function registerChecker(id,checker){if(checkers.has(id)){console.warn(`[警告] 检查器 "${id}" 已存在,将被覆盖`)}checkers.set(id,checker)}/**
181
+ */
182
+ function registerChecker(id, checker) {
183
+ if (checkers.has(id)) {
184
+ console.warn(`[警告] 检查器 "${id}" 已存在,将被覆盖`);
185
+ }
186
+ checkers.set(id, checker);
187
+ }
188
+
189
+ /**
52
190
  * 启用/禁用检查器
53
191
  * @param {string} id - 检查器 ID
54
192
  * @param {boolean} enabled - 是否启用
55
- */function toggleChecker(id,enabled){const checker=checkers.get(id);if(checker){checker.enabled=enabled}else{console.warn(`[警告] 未找到检查器: ${id}`)}}/**
193
+ */
194
+ function toggleChecker(id, enabled) {
195
+ const checker = checkers.get(id);
196
+ if (checker) {
197
+ checker.enabled = enabled;
198
+ } else {
199
+ console.warn(`[警告] 未找到检查器: ${id}`);
200
+ }
201
+ }
202
+
203
+ /**
56
204
  * 获取所有检查器列表
57
205
  * @returns {Array<{id: string, name: string, description: string, enabled: boolean}>}
58
- */function listCheckers(){return Array.from(checkers.entries()).map(([id,checker])=>({id,name:checker.name,description:checker.description,enabled:checker.enabled}))}// ============================================================
206
+ */
207
+ function listCheckers() {
208
+ return Array.from(checkers.entries()).map(([id, checker]) => ({
209
+ id,
210
+ name: checker.name,
211
+ description: checker.description,
212
+ enabled: checker.enabled
213
+ }));
214
+ }
215
+
216
+ // ============================================================
59
217
  // 执行检查
60
218
  // ============================================================
219
+
61
220
  /**
62
221
  * 执行所有已启用的检查器
63
222
  * @param {string} projectRoot - 项目根目录
@@ -67,23 +226,140 @@
67
226
  * @param {boolean} [options.verbose] - 详细输出
68
227
  * @param {boolean} [options.showDetails] - 显示详细报告
69
228
  * @param {boolean} [options.failFast] - 遇到错误立即停止
229
+ * @param {string[]} [options.themes] - 本次构建涉及的主题名称列表(供 themeName 检查器使用)
230
+ * @param {string[]} [options.channels] - 本次构建涉及的渠道名称列表(供 themeName 检查器校验「主题名_渠道名」组合长度,可包含锁定 UI 后缀如 google_L / google_P)
70
231
  * @returns {Promise<{success: boolean, results: Map<string, CheckResult>}>}
71
- */async function runPreBuildChecks(projectRoot,options={}){const{only=[],skip=[],verbose=false,showDetails=true,failFast=false}=options;const results=new Map;let allSuccess=true;console.log("\n"+"\u2550".repeat(70));console.log("\uD83D\uDD0D \u7F16\u8BD1\u524D\u68C0\u67E5");console.log("\u2550".repeat(70));// 确定要执行的检查器
72
- let checkersToRun=Array.from(checkers.entries()).filter(([id,checker])=>{// 如果指定了 only,只运行指定的
73
- if(only.length>0&&!only.includes(id))return false;// 跳过 skip 列表中的
74
- if(skip.includes(id))return false;// 只运行启用的
75
- return checker.enabled});if(checkersToRun.length===0){console.log("\n\u26A0\uFE0F \u6CA1\u6709\u542F\u7528\u7684\u68C0\u67E5\u5668");console.log("\u2550".repeat(70)+"\n");return{success:true,results}}if(verbose){console.log(`\n📋 将执行 ${checkersToRun.length} 个检查器:`);checkersToRun.forEach(([id,checker])=>{console.log(` - ${checker.name} (${id})`)});console.log("")}// 执行检查器
76
- for(const[id,checker]of checkersToRun){try{console.log(`\n▶️ 执行检查: ${checker.name}`);console.log("-".repeat(70));const result=await Promise.resolve(checker.validator(projectRoot,options));results.set(id,result);console.log(result.message);// 详细输出(针对埋点验证)
77
- if(showDetails&&id==="tracking"&&result.data){printValidationResult(result.data,showDetails)}if(!result.success){allSuccess=false;if(failFast){console.log("\n\u26A0\uFE0F failFast \u6A21\u5F0F\uFF1A\u68C0\u6D4B\u5230\u5931\u8D25\uFF0C\u7EC8\u6B62\u540E\u7EED\u68C0\u67E5");break}}}catch(error){console.error(`\n❌ 检查器 "${checker.name}" 执行失败:`,error.message);results.set(id,{success:false,message:`执行出错: ${error.message}`,error});allSuccess=false;if(failFast)break}}// 总结
78
- console.log("\n"+"\u2550".repeat(70));console.log("\uD83D\uDCCA \u68C0\u67E5\u603B\u7ED3");console.log("\u2550".repeat(70));const successCount=Array.from(results.values()).filter(r=>r.success).length;const totalCount=results.size;console.log(`\n通过: ${successCount}/${totalCount}`);if(allSuccess){console.log("\n\u2705 \u6240\u6709\u68C0\u67E5\u901A\u8FC7\uFF0C\u53EF\u4EE5\u7EE7\u7EED\u7F16\u8BD1\uFF01")}else{console.log("\n\u274C \u90E8\u5206\u68C0\u67E5\u5931\u8D25\uFF0C\u8BF7\u4FEE\u590D\u540E\u518D\u7F16\u8BD1\u3002")}console.log("\u2550".repeat(70)+"\n");return{success:allSuccess,results}}/**
232
+ */
233
+ async function runPreBuildChecks(projectRoot, options = {}) {
234
+ const {
235
+ only = [],
236
+ skip = [],
237
+ verbose = false,
238
+ showDetails = true,
239
+ failFast = false
240
+ } = options;
241
+
242
+ const results = new Map();
243
+ let allSuccess = true;
244
+
245
+ console.log('\n' + '═'.repeat(70));
246
+ console.log('🔍 编译前检查');
247
+ console.log('═'.repeat(70));
248
+
249
+ // 确定要执行的检查器
250
+ let checkersToRun = Array.from(checkers.entries()).filter(([id, checker]) => {
251
+ // 如果指定了 only,只运行指定的
252
+ if (only.length > 0 && !only.includes(id)) return false;
253
+ // 跳过 skip 列表中的
254
+ if (skip.includes(id)) return false;
255
+ // 只运行启用的
256
+ return checker.enabled;
257
+ });
258
+
259
+ if (checkersToRun.length === 0) {
260
+ console.log('\n⚠️ 没有启用的检查器');
261
+ console.log('═'.repeat(70) + '\n');
262
+ return { success: true, results };
263
+ }
264
+
265
+ if (verbose) {
266
+ console.log(`\n📋 将执行 ${checkersToRun.length} 个检查器:`);
267
+ checkersToRun.forEach(([id, checker]) => {
268
+ console.log(` - ${checker.name} (${id})`);
269
+ });
270
+ console.log('');
271
+ }
272
+
273
+ // 执行检查器
274
+ for (const [id, checker] of checkersToRun) {
275
+ try {
276
+ console.log(`\n▶️ 执行检查: ${checker.name}`);
277
+ console.log('-'.repeat(70));
278
+
279
+ const result = await Promise.resolve(checker.validator(projectRoot, options));
280
+ results.set(id, result);
281
+
282
+ console.log(result.message);
283
+
284
+ // 详细输出(针对埋点验证)
285
+ if (showDetails && id === 'tracking' && result.data) {
286
+ printValidationResult(result.data, showDetails);
287
+ }
288
+
289
+ if (!result.success) {
290
+ allSuccess = false;
291
+ if (failFast) {
292
+ console.log('\n⚠️ failFast 模式:检测到失败,终止后续检查');
293
+ break;
294
+ }
295
+ }
296
+
297
+ } catch (error) {
298
+ console.error(`\n❌ 检查器 "${checker.name}" 执行失败:`, error.message);
299
+ results.set(id, {
300
+ success: false,
301
+ message: `执行出错: ${error.message}`,
302
+ error
303
+ });
304
+ allSuccess = false;
305
+
306
+ if (failFast) break;
307
+ }
308
+ }
309
+
310
+ // 总结
311
+ console.log('\n' + '═'.repeat(70));
312
+ console.log('📊 检查总结');
313
+ console.log('═'.repeat(70));
314
+
315
+ const successCount = Array.from(results.values()).filter(r => r.success).length;
316
+ const totalCount = results.size;
317
+
318
+ console.log(`\n通过: ${successCount}/${totalCount}`);
319
+
320
+ if (allSuccess) {
321
+ console.log('\n✅ 所有检查通过,可以继续编译!');
322
+ } else {
323
+ console.log('\n❌ 部分检查失败,请修复后再编译。');
324
+ }
325
+
326
+ console.log('═'.repeat(70) + '\n');
327
+
328
+ return {
329
+ success: allSuccess,
330
+ results
331
+ };
332
+ }
333
+
334
+ /**
79
335
  * 单独运行埋点验证(快捷方法)
80
336
  * @param {string} projectRoot - 项目根目录
81
337
  * @param {Object} [options] - 选项
82
338
  * @returns {Promise<CheckResult>}
83
- */async function runTrackingCheck(projectRoot,options={}){const checker=checkers.get("tracking");if(!checker){throw new Error("\u57CB\u70B9\u68C0\u67E5\u5668\u672A\u6CE8\u518C")}return await Promise.resolve(checker.validator(projectRoot,options))}// ============================================================
339
+ */
340
+ async function runTrackingCheck(projectRoot, options = {}) {
341
+ const checker = checkers.get('tracking');
342
+ if (!checker) {
343
+ throw new Error('埋点检查器未注册');
344
+ }
345
+
346
+ return await Promise.resolve(checker.validator(projectRoot, options));
347
+ }
348
+
349
+ // ============================================================
84
350
  // 导出
85
351
  // ============================================================
86
- module.exports={// 主要函数
87
- runPreBuildChecks,runTrackingCheck,// 检查器管理
88
- registerChecker,toggleChecker,listCheckers,// 内部访问(供高级用户)
89
- checkers};
352
+
353
+ module.exports = {
354
+ // 主要函数
355
+ runPreBuildChecks,
356
+ runTrackingCheck,
357
+
358
+ // 检查器管理
359
+ registerChecker,
360
+ toggleChecker,
361
+ listCheckers,
362
+
363
+ // 内部访问(供高级用户)
364
+ checkers
365
+ };