@playcraft/devkit 1.0.16 → 1.0.17
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 +259 -0
- package/cli/bin/playable-scripts.js +44 -1
- package/cli/commands/build.js +122 -17
- package/cli/commands/builds.js +13 -2
- package/cli/commands/dev.js +7 -1
- package/cli/commands/pack.js +308 -39
- package/cli/commands/vite-dev.js +7 -1
- package/core/batch-build.js +1091 -124
- package/core/cos-uploader.js +264 -20
- package/core/index.js +58 -4
- package/core/loaders/gltf-loader.js +74 -28
- package/core/options.js +310 -15
- package/core/plugins/AdikteevInjectorPlugin.js +26 -4
- package/core/plugins/BigoAdsInjectorPlugin.js +21 -4
- package/core/plugins/DAPIInjectorPlugin.js +25 -2
- package/core/plugins/DebuggerInjectionPlugin.js +31 -3
- package/core/plugins/ExitAPIInjectorPlugin.js +57 -3
- package/core/plugins/FflateCompressionPlugin.js +225 -37
- package/core/plugins/LiftoffInjectorPlugin.js +26 -4
- package/core/plugins/MRAIDInjectorPlugin.js +24 -2
- package/core/plugins/MintegralInjectorPlugin.js +25 -2
- package/core/plugins/PangleInjectorPlugin.js +24 -2
- package/core/plugins/SnapchatInjectorPlugin.js +26 -4
- package/core/plugins/TikTokInjectorPlugin.js +24 -2
- package/core/plugins/UnityInjectorPlugin.js +37 -8
- package/core/plugins/ZipPlugin.js +138 -19
- package/core/utils/buildDefines.js +29 -2
- package/core/utils/buildTemplateString.js +40 -5
- package/core/utils/date.js +16 -1
- package/core/utils/generateAdikteevHtmlWebpackPluginConfig.js +42 -7
- package/core/utils/generateBigabidHtmlWebpackPluginConfig.js +30 -3
- package/core/utils/generateInMobiHtmlWebpackPluginConfig.js +43 -7
- package/core/utils/injectSDKPlugins.js +58 -11
- package/core/utils/logOptions.js +37 -4
- package/core/utils/mergeOptions.js +19 -2
- package/core/utils/parseArgvOptions.js +110 -5
- package/core/utils/resolveChannelFold2Zip.js +15 -3
- package/core/utils/validateThemeData.js +220 -25
- package/core/validators/pre-build-checker.js +201 -21
- package/core/validators/tracking-validator.js +358 -40
- package/core/vite.dev.js +181 -15
- package/core/webpack.build.js +446 -52
- package/core/webpack.common.js +177 -11
- package/core/webpack.dev.js +82 -5
- package/package.json +3 -2
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
// ============================================================
|
|
2
5
|
// 埋点方法配置
|
|
3
6
|
// ============================================================
|
|
7
|
+
|
|
4
8
|
/**
|
|
5
9
|
* 埋点方法配置
|
|
6
10
|
* @typedef {Object} TrackingMethod
|
|
@@ -8,30 +12,150 @@
|
|
|
8
12
|
* @property {boolean} required - 是否必须调用
|
|
9
13
|
* @property {string} description - 方法描述
|
|
10
14
|
* @property {string[]} channels - 对应的渠道埋点
|
|
11
|
-
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
12
18
|
* 所有埋点方法配置
|
|
13
19
|
* @type {TrackingMethod[]}
|
|
14
|
-
*/
|
|
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
|
+
/**
|
|
15
85
|
* SDK 静态方法映射到 tracking 方法
|
|
16
86
|
* 这些 SDK 方法内部会调用对应的 tracking 方法
|
|
17
|
-
*/
|
|
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
|
+
// ============================================================
|
|
18
102
|
// 文件扫描工具
|
|
19
103
|
// ============================================================
|
|
104
|
+
|
|
20
105
|
/**
|
|
21
106
|
* 递归扫描目录获取所有源码文件
|
|
22
107
|
* @param {string} dir - 目录路径
|
|
23
108
|
* @param {string[]} extensions - 文件扩展名列表
|
|
24
109
|
* @param {string[]} excludeDirs - 排除的目录名列表
|
|
25
110
|
* @returns {string[]} 文件路径列表
|
|
26
|
-
*/
|
|
27
|
-
|
|
28
|
-
const
|
|
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
|
+
/**
|
|
29
142
|
* 读取文件内容(带错误处理)
|
|
30
143
|
* @param {string} filePath - 文件路径
|
|
31
144
|
* @returns {string|null} 文件内容或 null
|
|
32
|
-
*/
|
|
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
|
+
// ============================================================
|
|
33
156
|
// 方法调用检测
|
|
34
157
|
// ============================================================
|
|
158
|
+
|
|
35
159
|
/**
|
|
36
160
|
* 检测代码中是否包含 tracking 方法的直接调用
|
|
37
161
|
* @param {string} content - 文件内容
|
|
@@ -40,10 +164,21 @@ const ext=path.extname(entry.name);if(extensions.includes(ext)){files.push(fullP
|
|
|
40
164
|
*
|
|
41
165
|
* 注意:不依赖对象名称,只检测方法调用模式
|
|
42
166
|
* 匹配:xxx.onPlaycraftXxx( 或 xxx['onPlaycraftXxx']( 或 xxx["onPlaycraftXxx"](
|
|
43
|
-
*/
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
+
/**
|
|
47
182
|
* 检测代码中是否包含 SDK 方法调用(会间接触发 tracking)
|
|
48
183
|
* @param {string} content - 文件内容
|
|
49
184
|
* @param {string} sdkMethod - SDK 方法名
|
|
@@ -51,20 +186,44 @@ new RegExp(`\\["${methodName}"\\]\\s*\\(`)];return patterns.some(pattern=>patter
|
|
|
51
186
|
*
|
|
52
187
|
* 注意:不依赖对象名称,只检测方法调用模式
|
|
53
188
|
* 匹配:xxx.playcraftXxx( 或 xxx['playcraftXxx']( 或 xxx["playcraftXxx"](
|
|
54
|
-
*/
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
+
/**
|
|
58
204
|
* 检测是否导入了 @playcraft/ads-tracking
|
|
59
205
|
* @param {string} content - 文件内容
|
|
60
206
|
* @returns {boolean} 是否导入了 tracking
|
|
61
|
-
*/
|
|
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
|
+
/**
|
|
62
214
|
* 检测是否导入了 @playcraft/adsdk
|
|
63
215
|
* @param {string} content - 文件内容
|
|
64
216
|
* @returns {boolean} 是否导入了 SDK
|
|
65
|
-
*/
|
|
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
|
+
// ============================================================
|
|
66
224
|
// 验证核心逻辑
|
|
67
225
|
// ============================================================
|
|
226
|
+
|
|
68
227
|
/**
|
|
69
228
|
* 验证结果
|
|
70
229
|
* @typedef {Object} ValidationResult
|
|
@@ -74,7 +233,9 @@ new RegExp(`\\["${sdkMethod}"\\]\\s*\\(`)];return patterns.some(pattern=>pattern
|
|
|
74
233
|
* @property {string[]} errors - 错误信息列表
|
|
75
234
|
* @property {string[]} warnings - 警告信息列表
|
|
76
235
|
* @property {Object} stats - 统计信息
|
|
77
|
-
|
|
236
|
+
*/
|
|
237
|
+
|
|
238
|
+
/**
|
|
78
239
|
* 验证项目中的埋点调用
|
|
79
240
|
* @param {string} projectRoot - 项目根目录
|
|
80
241
|
* @param {Object} [options] - 验证选项
|
|
@@ -82,32 +243,189 @@ new RegExp(`\\["${sdkMethod}"\\]\\s*\\(`)];return patterns.some(pattern=>pattern
|
|
|
82
243
|
* @param {string[]} [options.excludeDirs] - 排除的目录
|
|
83
244
|
* @param {boolean} [options.verbose] - 是否输出详细信息
|
|
84
245
|
* @returns {ValidationResult} 验证结果
|
|
85
|
-
*/
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
+
// ============================================================
|
|
96
352
|
// 格式化输出
|
|
97
353
|
// ============================================================
|
|
354
|
+
|
|
98
355
|
/**
|
|
99
356
|
* 格式化验证结果并输出到控制台
|
|
100
357
|
* @param {ValidationResult} result - 验证结果
|
|
101
358
|
* @param {boolean} [showDetails=true] - 是否显示详细信息
|
|
102
|
-
*/
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
+
// ============================================================
|
|
108
413
|
// 导出
|
|
109
414
|
// ============================================================
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
+
};
|