@playcraft/devkit 1.0.19 → 1.0.21

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 (49) hide show
  1. package/cli/bin/playable-scripts.js +1 -44
  2. package/cli/commands/build.js +18 -130
  3. package/cli/commands/builds.js +2 -13
  4. package/cli/commands/dev.js +1 -7
  5. package/cli/commands/pack.js +39 -308
  6. package/cli/commands/vite-dev.js +1 -7
  7. package/core/batch-build.js +125 -1091
  8. package/core/constants.js +6 -0
  9. package/core/cos-uploader.js +20 -264
  10. package/core/index.js +7 -76
  11. package/core/loaders/gltf-loader.js +28 -74
  12. package/core/options.js +15 -310
  13. package/core/plugins/AdikteevInjectorPlugin.js +4 -26
  14. package/core/plugins/BigoAdsInjectorPlugin.js +4 -21
  15. package/core/plugins/DAPIInjectorPlugin.js +2 -25
  16. package/core/plugins/DebuggerInjectionPlugin.js +3 -31
  17. package/core/plugins/ExitAPIInjectorPlugin.js +3 -57
  18. package/core/plugins/FflateCompressionPlugin.js +37 -225
  19. package/core/plugins/FontInjectionWebpackPlugin.js +4 -46
  20. package/core/plugins/LiftoffInjectorPlugin.js +4 -26
  21. package/core/plugins/MRAIDInjectorPlugin.js +2 -24
  22. package/core/plugins/MintegralInjectorPlugin.js +2 -25
  23. package/core/plugins/PangleInjectorPlugin.js +2 -24
  24. package/core/plugins/SnapchatInjectorPlugin.js +4 -26
  25. package/core/plugins/TikTokInjectorPlugin.js +2 -24
  26. package/core/plugins/UnityInjectorPlugin.js +8 -37
  27. package/core/plugins/ZipPlugin.js +19 -138
  28. package/core/utils/buildDefines.js +2 -29
  29. package/core/utils/buildTemplateString.js +5 -40
  30. package/core/utils/date.js +1 -16
  31. package/core/utils/fontRegistry.js +13 -81
  32. package/core/utils/fontResolver.js +37 -335
  33. package/core/utils/generateAdikteevHtmlWebpackPluginConfig.js +7 -42
  34. package/core/utils/generateBigabidHtmlWebpackPluginConfig.js +3 -30
  35. package/core/utils/generateInMobiHtmlWebpackPluginConfig.js +7 -43
  36. package/core/utils/injectSDKPlugins.js +11 -58
  37. package/core/utils/logOptions.js +4 -37
  38. package/core/utils/mergeOptions.js +2 -19
  39. package/core/utils/parseArgvOptions.js +5 -110
  40. package/core/utils/resolveChannelFold2Zip.js +3 -15
  41. package/core/utils/validateThemeData.js +25 -220
  42. package/core/validators/pre-build-checker.js +21 -201
  43. package/core/validators/tracking-validator.js +40 -358
  44. package/core/vite.dev.js +17 -321
  45. package/core/webpack.build.js +56 -489
  46. package/core/webpack.common.js +11 -177
  47. package/core/webpack.dev.js +5 -82
  48. package/package.json +2 -3
  49. package/README.md +0 -259
@@ -1,10 +1,6 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
-
4
- // ============================================================
1
+ "use strict";const fs=require("fs");const path=require("path");// ============================================================
5
2
  // 埋点方法配置
6
3
  // ============================================================
7
-
8
4
  /**
9
5
  * 埋点方法配置
10
6
  * @typedef {Object} TrackingMethod
@@ -12,150 +8,30 @@ const path = require('path');
12
8
  * @property {boolean} required - 是否必须调用
13
9
  * @property {string} description - 方法描述
14
10
  * @property {string[]} channels - 对应的渠道埋点
15
- */
16
-
17
- /**
11
+ *//**
18
12
  * 所有埋点方法配置
19
13
  * @type {TrackingMethod[]}
20
- */
21
- const TRACKING_METHODS = [
22
- {
23
- name: 'onPlaycraftTrackingInit',
24
- required: true,
25
- description: 'SDK 初始化开始',
26
- channels: ['Bigabid:mraid_viewable', 'InMobi:Ad_Load_Start', 'AppLovin:LOADING']
27
- },
28
- {
29
- name: 'onPlaycraftTrackingLoading',
30
- required: true,
31
- description: '游戏资源加载中',
32
- channels: ['Bigabid:game_viewable', 'InMobi:Ad_Viewable', 'AppLovin:LOADED']
33
- },
34
- {
35
- name: 'onPlaycraftTrackingLoaded',
36
- required: true,
37
- description: '主场景就绪,用户可交互',
38
- channels: ['AppLovin:DISPLAYED']
39
- },
40
- {
41
- name: 'onPlaycraftChallengeStart',
42
- required: true,
43
- description: '挑战开始(首次用户交互)',
44
- channels: ['Bigabid:engagement', 'InMobi:First_Engagement', 'AppLovin:CHALLENGE_STARTED']
45
- },
46
- {
47
- name: 'onPlaycraftChallengeProgress',
48
- required: false,
49
- description: '进度百分比追踪(可选)',
50
- channels: ['AppLovin:CHALLENGE_PASS_*']
51
- },
52
- {
53
- name: 'onPlaycraftChallengeSuccess',
54
- required: true,
55
- description: '游戏挑战成功',
56
- channels: ['AppLovin:CHALLENGE_SOLVED', 'Bigabid:complete', 'InMobi:Gameplay_Complete']
57
- },
58
- {
59
- name: 'onPlaycraftChallengeFinish',
60
- required: false,
61
- description: '游戏结束(可选)',
62
- channels: ['Bigabid:complete', 'InMobi:Gameplay_Complete', 'AppLovin:ENDCARD_SHOWN']
63
- },
64
- {
65
- name: 'onPlaycraftInstall',
66
- required: true,
67
- description: '点击安装',
68
- channels: ['Bigabid:click', 'InMobi:DSP_Click', 'AppLovin:CTA_CLICKED']
69
- },
70
- {
71
- name: 'onPlaycraftRetry',
72
- required: false,
73
- description: '重试游戏(可选)',
74
- channels: ['AppLovin:CHALLENGE_RETRY']
75
- },
76
- {
77
- name: 'onPlaycraftChallengeFailed',
78
- required: true,
79
- description: '游戏挑战失败',
80
- channels: ['AppLovin:CHALLENGE_FAILED']
81
- }
82
- ];
83
-
84
- /**
14
+ */const TRACKING_METHODS=[{name:"onPlaycraftTrackingInit",required:true,description:"SDK \u521D\u59CB\u5316\u5F00\u59CB",channels:["Bigabid:mraid_viewable","InMobi:Ad_Load_Start","AppLovin:LOADING"]},{name:"onPlaycraftTrackingLoading",required:true,description:"\u6E38\u620F\u8D44\u6E90\u52A0\u8F7D\u4E2D",channels:["Bigabid:game_viewable","InMobi:Ad_Viewable","AppLovin:LOADED"]},{name:"onPlaycraftTrackingLoaded",required:true,description:"\u4E3B\u573A\u666F\u5C31\u7EEA\uFF0C\u7528\u6237\u53EF\u4EA4\u4E92",channels:["AppLovin:DISPLAYED"]},{name:"onPlaycraftChallengeStart",required:true,description:"\u6311\u6218\u5F00\u59CB\uFF08\u9996\u6B21\u7528\u6237\u4EA4\u4E92\uFF09",channels:["Bigabid:engagement","InMobi:First_Engagement","AppLovin:CHALLENGE_STARTED"]},{name:"onPlaycraftChallengeProgress",required:false,description:"\u8FDB\u5EA6\u767E\u5206\u6BD4\u8FFD\u8E2A\uFF08\u53EF\u9009\uFF09",channels:["AppLovin:CHALLENGE_PASS_*"]},{name:"onPlaycraftChallengeSuccess",required:true,description:"\u6E38\u620F\u6311\u6218\u6210\u529F",channels:["AppLovin:CHALLENGE_SOLVED","Bigabid:complete","InMobi:Gameplay_Complete"]},{name:"onPlaycraftChallengeFinish",required:false,description:"\u6E38\u620F\u7ED3\u675F\uFF08\u53EF\u9009\uFF09",channels:["Bigabid:complete","InMobi:Gameplay_Complete","AppLovin:ENDCARD_SHOWN"]},{name:"onPlaycraftInstall",required:true,description:"\u70B9\u51FB\u5B89\u88C5",channels:["Bigabid:click","InMobi:DSP_Click","AppLovin:CTA_CLICKED"]},{name:"onPlaycraftRetry",required:false,description:"\u91CD\u8BD5\u6E38\u620F\uFF08\u53EF\u9009\uFF09",channels:["AppLovin:CHALLENGE_RETRY"]},{name:"onPlaycraftChallengeFailed",required:true,description:"\u6E38\u620F\u6311\u6218\u5931\u8D25",channels:["AppLovin:CHALLENGE_FAILED"]}];/**
85
15
  * SDK 静态方法映射到 tracking 方法
86
16
  * 这些 SDK 方法内部会调用对应的 tracking 方法
87
- */
88
- const SDK_TO_TRACKING_MAP = {
89
- 'playcraftInit': 'onPlaycraftTrackingInit',
90
- 'playcraftStart': 'onPlaycraftTrackingLoading',
91
- 'playcraftInteractive': 'onPlaycraftTrackingLoaded',
92
- 'playcraftChallengeStart': 'onPlaycraftChallengeStart',
93
- 'recordPlaycraftChallengeProgress': 'onPlaycraftChallengeProgress',
94
- 'playcraftRecordSuccess': 'onPlaycraftChallengeSuccess',
95
- 'playcraftFinish': 'onPlaycraftChallengeFinish',
96
- 'playcraftInstall': 'onPlaycraftInstall',
97
- 'playcraftRetry': 'onPlaycraftRetry',
98
- 'playcraftRecordFailed': 'onPlaycraftChallengeFailed'
99
- };
100
-
101
- // ============================================================
17
+ */const SDK_TO_TRACKING_MAP={"playcraftInit":"onPlaycraftTrackingInit","playcraftStart":"onPlaycraftTrackingLoading","playcraftInteractive":"onPlaycraftTrackingLoaded","playcraftChallengeStart":"onPlaycraftChallengeStart","recordPlaycraftChallengeProgress":"onPlaycraftChallengeProgress","playcraftRecordSuccess":"onPlaycraftChallengeSuccess","playcraftFinish":"onPlaycraftChallengeFinish","playcraftInstall":"onPlaycraftInstall","playcraftRetry":"onPlaycraftRetry","playcraftRecordFailed":"onPlaycraftChallengeFailed"};// ============================================================
102
18
  // 文件扫描工具
103
19
  // ============================================================
104
-
105
20
  /**
106
21
  * 递归扫描目录获取所有源码文件
107
22
  * @param {string} dir - 目录路径
108
23
  * @param {string[]} extensions - 文件扩展名列表
109
24
  * @param {string[]} excludeDirs - 排除的目录名列表
110
25
  * @returns {string[]} 文件路径列表
111
- */
112
- function scanSourceFiles(dir, extensions = ['.ts', '.tsx', '.js', '.jsx'], excludeDirs = ['node_modules', 'dist', 'build', '.git', '.claude']) {
113
- const files = [];
114
-
115
- function scan(currentDir) {
116
- if (!fs.existsSync(currentDir)) return;
117
-
118
- const entries = fs.readdirSync(currentDir, { withFileTypes: true });
119
-
120
- for (const entry of entries) {
121
- const fullPath = path.join(currentDir, entry.name);
122
-
123
- if (entry.isDirectory()) {
124
- // 跳过排除的目录
125
- if (excludeDirs.includes(entry.name)) continue;
126
- scan(fullPath);
127
- } else if (entry.isFile()) {
128
- // 检查文件扩展名
129
- const ext = path.extname(entry.name);
130
- if (extensions.includes(ext)) {
131
- files.push(fullPath);
132
- }
133
- }
134
- }
135
- }
136
-
137
- scan(dir);
138
- return files;
139
- }
140
-
141
- /**
26
+ */function scanSourceFiles(dir,extensions=[".ts",".tsx",".js",".jsx"],excludeDirs=["node_modules","dist","build",".git",".claude"]){const files=[];function scan(currentDir){if(!fs.existsSync(currentDir))return;const entries=fs.readdirSync(currentDir,{withFileTypes:true});for(const entry of entries){const fullPath=path.join(currentDir,entry.name);if(entry.isDirectory()){// 跳过排除的目录
27
+ if(excludeDirs.includes(entry.name))continue;scan(fullPath)}else if(entry.isFile()){// 检查文件扩展名
28
+ const ext=path.extname(entry.name);if(extensions.includes(ext)){files.push(fullPath)}}}}scan(dir);return files}/**
142
29
  * 读取文件内容(带错误处理)
143
30
  * @param {string} filePath - 文件路径
144
31
  * @returns {string|null} 文件内容或 null
145
- */
146
- function readFileContent(filePath) {
147
- try {
148
- return fs.readFileSync(filePath, 'utf-8');
149
- } catch (err) {
150
- console.warn(`[警告] 无法读取文件: ${filePath}`, err.message);
151
- return null;
152
- }
153
- }
154
-
155
- // ============================================================
32
+ */function readFileContent(filePath){try{return fs.readFileSync(filePath,"utf-8")}catch(err){console.warn(`[警告] 无法读取文件: ${filePath}`,err.message);return null}}// ============================================================
156
33
  // 方法调用检测
157
34
  // ============================================================
158
-
159
35
  /**
160
36
  * 检测代码中是否包含 tracking 方法的直接调用
161
37
  * @param {string} content - 文件内容
@@ -164,21 +40,10 @@ function readFileContent(filePath) {
164
40
  *
165
41
  * 注意:不依赖对象名称,只检测方法调用模式
166
42
  * 匹配:xxx.onPlaycraftXxx( 或 xxx['onPlaycraftXxx']( 或 xxx["onPlaycraftXxx"](
167
- */
168
- function detectDirectTrackingCall(content, methodName) {
169
- const patterns = [
170
- // 匹配 .方法名(
171
- new RegExp(`\\.${methodName}\\s*\\(`),
172
- // 匹配 ['方法名'](
173
- new RegExp(`\\['${methodName}'\\]\\s*\\(`),
174
- // 匹配 ["方法名"](
175
- new RegExp(`\\["${methodName}"\\]\\s*\\(`)
176
- ];
177
-
178
- return patterns.some(pattern => pattern.test(content));
179
- }
180
-
181
- /**
43
+ */function detectDirectTrackingCall(content,methodName){const patterns=[// 匹配 .方法名(
44
+ new RegExp(`\\.${methodName}\\s*\\(`),// 匹配 ['方法名'](
45
+ new RegExp(`\\['${methodName}'\\]\\s*\\(`),// 匹配 ["方法名"](
46
+ new RegExp(`\\["${methodName}"\\]\\s*\\(`)];return patterns.some(pattern=>pattern.test(content))}/**
182
47
  * 检测代码中是否包含 SDK 方法调用(会间接触发 tracking)
183
48
  * @param {string} content - 文件内容
184
49
  * @param {string} sdkMethod - SDK 方法名
@@ -186,44 +51,20 @@ function detectDirectTrackingCall(content, methodName) {
186
51
  *
187
52
  * 注意:不依赖对象名称,只检测方法调用模式
188
53
  * 匹配:xxx.playcraftXxx( 或 xxx['playcraftXxx']( 或 xxx["playcraftXxx"](
189
- */
190
- function detectSDKCall(content, sdkMethod) {
191
- const patterns = [
192
- // 匹配 .方法名(
193
- new RegExp(`\\.${sdkMethod}\\s*\\(`),
194
- // 匹配 ['方法名'](
195
- new RegExp(`\\['${sdkMethod}'\\]\\s*\\(`),
196
- // 匹配 ["方法名"](
197
- new RegExp(`\\["${sdkMethod}"\\]\\s*\\(`)
198
- ];
199
-
200
- return patterns.some(pattern => pattern.test(content));
201
- }
202
-
203
- /**
54
+ */function detectSDKCall(content,sdkMethod){const patterns=[// 匹配 .方法名(
55
+ new RegExp(`\\.${sdkMethod}\\s*\\(`),// 匹配 ['方法名'](
56
+ new RegExp(`\\['${sdkMethod}'\\]\\s*\\(`),// 匹配 ["方法名"](
57
+ new RegExp(`\\["${sdkMethod}"\\]\\s*\\(`)];return patterns.some(pattern=>pattern.test(content))}/**
204
58
  * 检测是否导入了 @playcraft/ads-tracking
205
59
  * @param {string} content - 文件内容
206
60
  * @returns {boolean} 是否导入了 tracking
207
- */
208
- function hasTrackingImport(content) {
209
- return /from\s+['"]@playcraft\/ads-tracking['"]/.test(content) ||
210
- /require\s*\(\s*['"]@playcraft\/ads-tracking['"]\s*\)/.test(content);
211
- }
212
-
213
- /**
61
+ */function hasTrackingImport(content){return /from\s+['"]@playcraft\/ads-tracking['"]/.test(content)||/require\s*\(\s*['"]@playcraft\/ads-tracking['"]\s*\)/.test(content)}/**
214
62
  * 检测是否导入了 @playcraft/adsdk
215
63
  * @param {string} content - 文件内容
216
64
  * @returns {boolean} 是否导入了 SDK
217
- */
218
- function hasSDKImport(content) {
219
- return /from\s+['"]@playcraft\/adsdk['"]/.test(content) ||
220
- /require\s*\(\s*['"]@playcraft\/adsdk['"]\s*\)/.test(content);
221
- }
222
-
223
- // ============================================================
65
+ */function hasSDKImport(content){return /from\s+['"]@playcraft\/adsdk['"]/.test(content)||/require\s*\(\s*['"]@playcraft\/adsdk['"]\s*\)/.test(content)}// ============================================================
224
66
  // 验证核心逻辑
225
67
  // ============================================================
226
-
227
68
  /**
228
69
  * 验证结果
229
70
  * @typedef {Object} ValidationResult
@@ -233,9 +74,7 @@ function hasSDKImport(content) {
233
74
  * @property {string[]} errors - 错误信息列表
234
75
  * @property {string[]} warnings - 警告信息列表
235
76
  * @property {Object} stats - 统计信息
236
- */
237
-
238
- /**
77
+ *//**
239
78
  * 验证项目中的埋点调用
240
79
  * @param {string} projectRoot - 项目根目录
241
80
  * @param {Object} [options] - 验证选项
@@ -243,189 +82,32 @@ function hasSDKImport(content) {
243
82
  * @param {string[]} [options.excludeDirs] - 排除的目录
244
83
  * @param {boolean} [options.verbose] - 是否输出详细信息
245
84
  * @returns {ValidationResult} 验证结果
246
- */
247
- function validateTracking(projectRoot, options = {}) {
248
- const {
249
- extensions = ['.ts', '.tsx', '.js', '.jsx'],
250
- excludeDirs = ['node_modules', 'dist', 'build', '.git'],
251
- verbose = false
252
- } = options;
253
-
254
- const result = {
255
- success: false,
256
- integrationType: 'none',
257
- methods: [],
258
- errors: [],
259
- warnings: [],
260
- stats: {
261
- totalFiles: 0,
262
- scannedFiles: 0,
263
- hasTrackingImport: false,
264
- hasSDKImport: false
265
- }
266
- };
267
-
268
- // 1. 扫描所有源码文件
269
- if (verbose) console.log(`\n[扫描] 开始扫描项目: ${projectRoot}`);
270
- const files = scanSourceFiles(projectRoot, extensions, excludeDirs);
271
- result.stats.totalFiles = files.length;
272
-
273
- if (files.length === 0) {
274
- result.errors.push('未找到任何源码文件');
275
- return result;
276
- }
277
-
278
- if (verbose) console.log(`[扫描] 找到 ${files.length} 个源码文件\n`);
279
-
280
- // 2. 读取所有文件内容并合并(用于全局检测)
281
- let allContent = '';
282
- for (const file of files) {
283
- const content = readFileContent(file);
284
- if (content) {
285
- allContent += content + '\n';
286
- result.stats.scannedFiles++;
287
- }
288
- }
289
-
290
- // 3. 判断集成类型
291
- result.stats.hasTrackingImport = hasTrackingImport(allContent);
292
- result.stats.hasSDKImport = hasSDKImport(allContent);
293
-
294
- if (result.stats.hasTrackingImport && result.stats.hasSDKImport) {
295
- result.integrationType = 'sdk'; // SDK 优先
296
- } else if (result.stats.hasSDKImport) {
297
- result.integrationType = 'sdk';
298
- } else if (result.stats.hasTrackingImport) {
299
- result.integrationType = 'tracking';
300
- } else {
301
- result.errors.push('未检测到 @playcraft/adsdk 或 @playcraft/ads-tracking 的导入');
302
- return result;
303
- }
304
-
305
- if (verbose) {
306
- console.log(`[集成类型] ${result.integrationType === 'sdk' ? 'SDK 集成(通过 sdk 静态方法调用)' : '直接 tracking 集成'}`);
307
- console.log(`[导入检测] SDK: ${result.stats.hasSDKImport ? '✓' : '✗'} | Tracking: ${result.stats.hasTrackingImport ? '✓' : '✗'}\n`);
308
- }
309
-
310
- // 4. 检测每个埋点方法
311
- for (const method of TRACKING_METHODS) {
312
- let found = false;
313
-
314
- if (result.integrationType === 'sdk') {
315
- // SDK 集成:检测 SDK 方法调用
316
- const sdkMethod = Object.keys(SDK_TO_TRACKING_MAP).find(
317
- key => SDK_TO_TRACKING_MAP[key] === method.name
318
- );
319
- if (sdkMethod) {
320
- found = detectSDKCall(allContent, sdkMethod);
321
- }
322
- } else if (result.integrationType === 'tracking') {
323
- // 直接 tracking 集成:检测 tracking 方法调用
324
- found = detectDirectTrackingCall(allContent, method.name);
325
- }
326
-
327
- result.methods.push({
328
- method: method.name,
329
- found,
330
- required: method.required,
331
- description: method.description
332
- });
333
-
334
- // 必须方法未找到 -> 错误
335
- if (method.required && !found) {
336
- result.errors.push(`❌ 缺少必须的埋点方法: ${method.name} (${method.description})`);
337
- }
338
-
339
- // 可选方法未找到 -> 警告
340
- if (!method.required && !found) {
341
- result.warnings.push(`⚠️ 未找到可选埋点方法: ${method.name} (${method.description})`);
342
- }
343
- }
344
-
345
- // 5. 判断验证结果
346
- result.success = result.errors.length === 0;
347
-
348
- return result;
349
- }
350
-
351
- // ============================================================
85
+ */function validateTracking(projectRoot,options={}){const{extensions=[".ts",".tsx",".js",".jsx"],excludeDirs=["node_modules","dist","build",".git"],verbose=false}=options;const result={success:false,integrationType:"none",methods:[],errors:[],warnings:[],stats:{totalFiles:0,scannedFiles:0,hasTrackingImport:false,hasSDKImport:false}};// 1. 扫描所有源码文件
86
+ if(verbose)console.log(`\n[扫描] 开始扫描项目: ${projectRoot}`);const files=scanSourceFiles(projectRoot,extensions,excludeDirs);result.stats.totalFiles=files.length;if(files.length===0){result.errors.push("\u672A\u627E\u5230\u4EFB\u4F55\u6E90\u7801\u6587\u4EF6");return result}if(verbose)console.log(`[扫描] 找到 ${files.length} 个源码文件\n`);// 2. 读取所有文件内容并合并(用于全局检测)
87
+ let allContent="";for(const file of files){const content=readFileContent(file);if(content){allContent+=content+"\n";result.stats.scannedFiles++}}// 3. 判断集成类型
88
+ result.stats.hasTrackingImport=hasTrackingImport(allContent);result.stats.hasSDKImport=hasSDKImport(allContent);if(result.stats.hasTrackingImport&&result.stats.hasSDKImport){result.integrationType="sdk";// SDK 优先
89
+ }else if(result.stats.hasSDKImport){result.integrationType="sdk"}else if(result.stats.hasTrackingImport){result.integrationType="tracking"}else{result.errors.push("\u672A\u68C0\u6D4B\u5230 @playcraft/adsdk \u6216 @playcraft/ads-tracking \u7684\u5BFC\u5165");return result}if(verbose){console.log(`[集成类型] ${result.integrationType==="sdk"?"SDK \u96C6\u6210\uFF08\u901A\u8FC7 sdk \u9759\u6001\u65B9\u6CD5\u8C03\u7528\uFF09":"\u76F4\u63A5 tracking \u96C6\u6210"}`);console.log(`[导入检测] SDK: ${result.stats.hasSDKImport?"\u2713":"\u2717"} | Tracking: ${result.stats.hasTrackingImport?"\u2713":"\u2717"}\n`)}// 4. 检测每个埋点方法
90
+ for(const method of TRACKING_METHODS){let found=false;if(result.integrationType==="sdk"){// SDK 集成:检测 SDK 方法调用
91
+ const sdkMethod=Object.keys(SDK_TO_TRACKING_MAP).find(key=>SDK_TO_TRACKING_MAP[key]===method.name);if(sdkMethod){found=detectSDKCall(allContent,sdkMethod)}}else if(result.integrationType==="tracking"){// 直接 tracking 集成:检测 tracking 方法调用
92
+ found=detectDirectTrackingCall(allContent,method.name)}result.methods.push({method:method.name,found,required:method.required,description:method.description});// 必须方法未找到 -> 错误
93
+ if(method.required&&!found){result.errors.push(`❌ 缺少必须的埋点方法: ${method.name} (${method.description})`)}// 可选方法未找到 -> 警告
94
+ if(!method.required&&!found){result.warnings.push(`⚠️ 未找到可选埋点方法: ${method.name} (${method.description})`)}}// 5. 判断验证结果
95
+ result.success=result.errors.length===0;return result}// ============================================================
352
96
  // 格式化输出
353
97
  // ============================================================
354
-
355
98
  /**
356
99
  * 格式化验证结果并输出到控制台
357
100
  * @param {ValidationResult} result - 验证结果
358
101
  * @param {boolean} [showDetails=true] - 是否显示详细信息
359
- */
360
- function printValidationResult(result, showDetails = true) {
361
- console.log('\n' + '='.repeat(60));
362
- console.log('📊 埋点验证报告');
363
- console.log('='.repeat(60));
364
-
365
- // 统计信息
366
- console.log(`\n📁 文件统计: 扫描 ${result.stats.scannedFiles}/${result.stats.totalFiles} 个文件`);
367
- console.log(`🔗 集成类型: ${result.integrationType === 'sdk' ? 'SDK 集成' : result.integrationType === 'tracking' ? 'Tracking 直接集成' : '未检测到'}`);
368
-
369
- // 方法检测结果
370
- if (showDetails) {
371
- console.log('\n📋 方法检测明细:');
372
- console.log('-'.repeat(60));
373
-
374
- const requiredMethods = result.methods.filter(m => m.required);
375
- const optionalMethods = result.methods.filter(m => !m.required);
376
-
377
- console.log('\n必须方法:');
378
- for (const m of requiredMethods) {
379
- const status = m.found ? '✅' : '❌';
380
- console.log(` ${status} ${m.method.padEnd(35)} ${m.found ? '已调用' : '未调用'}`);
381
- }
382
-
383
- console.log('\n可选方法:');
384
- for (const m of optionalMethods) {
385
- const status = m.found ? '✅' : '⚠️ ';
386
- console.log(` ${status} ${m.method.padEnd(35)} ${m.found ? '已调用' : '未调用'}`);
387
- }
388
- }
389
-
390
- // 错误信息
391
- if (result.errors.length > 0) {
392
- console.log('\n❌ 错误:');
393
- result.errors.forEach(err => console.log(` ${err}`));
394
- }
395
-
396
- // 警告信息
397
- if (result.warnings.length > 0) {
398
- console.log('\n⚠️ 警告:');
399
- result.warnings.forEach(warn => console.log(` ${warn}`));
400
- }
401
-
402
- // 最终结果
403
- console.log('\n' + '='.repeat(60));
404
- if (result.success) {
405
- console.log('✅ 验证通过!所有必须的埋点方法都已正确调用。');
406
- } else {
407
- console.log('❌ 验证失败!存在缺失的必须埋点方法,请检查代码。');
408
- }
409
- console.log('='.repeat(60) + '\n');
410
- }
411
-
412
- // ============================================================
102
+ */function printValidationResult(result,showDetails=true){console.log("\n"+"=".repeat(60));console.log("\uD83D\uDCCA \u57CB\u70B9\u9A8C\u8BC1\u62A5\u544A");console.log("=".repeat(60));// 统计信息
103
+ console.log(`\n📁 文件统计: 扫描 ${result.stats.scannedFiles}/${result.stats.totalFiles} 个文件`);console.log(`🔗 集成类型: ${result.integrationType==="sdk"?"SDK \u96C6\u6210":result.integrationType==="tracking"?"Tracking \u76F4\u63A5\u96C6\u6210":"\u672A\u68C0\u6D4B\u5230"}`);// 方法检测结果
104
+ if(showDetails){console.log("\n\uD83D\uDCCB \u65B9\u6CD5\u68C0\u6D4B\u660E\u7EC6:");console.log("-".repeat(60));const requiredMethods=result.methods.filter(m=>m.required);const optionalMethods=result.methods.filter(m=>!m.required);console.log("\n\u5FC5\u987B\u65B9\u6CD5:");for(const m of requiredMethods){const status=m.found?"\u2705":"\u274C";console.log(` ${status} ${m.method.padEnd(35)} ${m.found?"\u5DF2\u8C03\u7528":"\u672A\u8C03\u7528"}`)}console.log("\n\u53EF\u9009\u65B9\u6CD5:");for(const m of optionalMethods){const status=m.found?"\u2705":"\u26A0\uFE0F ";console.log(` ${status} ${m.method.padEnd(35)} ${m.found?"\u5DF2\u8C03\u7528":"\u672A\u8C03\u7528"}`)}}// 错误信息
105
+ if(result.errors.length>0){console.log("\n\u274C \u9519\u8BEF:");result.errors.forEach(err=>console.log(` ${err}`))}// 警告信息
106
+ if(result.warnings.length>0){console.log("\n\u26A0\uFE0F \u8B66\u544A:");result.warnings.forEach(warn=>console.log(` ${warn}`))}// 最终结果
107
+ console.log("\n"+"=".repeat(60));if(result.success){console.log("\u2705 \u9A8C\u8BC1\u901A\u8FC7\uFF01\u6240\u6709\u5FC5\u987B\u7684\u57CB\u70B9\u65B9\u6CD5\u90FD\u5DF2\u6B63\u786E\u8C03\u7528\u3002")}else{console.log("\u274C \u9A8C\u8BC1\u5931\u8D25\uFF01\u5B58\u5728\u7F3A\u5931\u7684\u5FC5\u987B\u57CB\u70B9\u65B9\u6CD5\uFF0C\u8BF7\u68C0\u67E5\u4EE3\u7801\u3002")}console.log("=".repeat(60)+"\n")}// ============================================================
413
108
  // 导出
414
109
  // ============================================================
415
-
416
- module.exports = {
417
- // 核心验证函数
418
- validateTracking,
419
- printValidationResult,
420
-
421
- // 配置数据(供外部使用)
422
- TRACKING_METHODS,
423
- SDK_TO_TRACKING_MAP,
424
-
425
- // 工具函数(供外部使用)
426
- scanSourceFiles,
427
- detectDirectTrackingCall,
428
- detectSDKCall,
429
- hasTrackingImport,
430
- hasSDKImport
431
- };
110
+ module.exports={// 核心验证函数
111
+ validateTracking,printValidationResult,// 配置数据(供外部使用)
112
+ TRACKING_METHODS,SDK_TO_TRACKING_MAP,// 工具函数(供外部使用)
113
+ scanSourceFiles,detectDirectTrackingCall,detectSDKCall,hasTrackingImport,hasSDKImport};