@playcraft/devkit 1.0.28 → 1.0.30
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/cli/bin/playable-scripts.js +6 -44
- package/cli/commands/build.js +40 -220
- package/cli/commands/builds.js +2 -13
- package/cli/commands/dev.js +1 -7
- package/cli/commands/pack.js +39 -308
- package/cli/commands/vite-dev.js +1 -7
- package/core/batch-build.js +149 -1255
- package/core/constants.js +2 -16
- package/core/cos-uploader.js +24 -286
- package/core/index.d.ts +4 -1
- package/core/index.js +7 -76
- package/core/loaders/gltf-loader.js +28 -74
- package/core/options.js +15 -326
- package/core/plugins/AdikteevInjectorPlugin.js +4 -26
- package/core/plugins/BigoAdsInjectorPlugin.js +4 -21
- package/core/plugins/DAPIInjectorPlugin.js +2 -25
- package/core/plugins/DebuggerInjectionPlugin.js +3 -31
- package/core/plugins/ExitAPIInjectorPlugin.js +3 -56
- package/core/plugins/FflateCompressionPlugin.js +40 -231
- package/core/plugins/FontInjectionWebpackPlugin.js +4 -46
- package/core/plugins/LiftoffInjectorPlugin.js +4 -26
- package/core/plugins/MRAIDInjectorPlugin.js +2 -24
- package/core/plugins/MintegralInjectorPlugin.js +2 -25
- package/core/plugins/PangleInjectorPlugin.js +2 -24
- package/core/plugins/PlaycraftOrientationInjectorPlugin.js +3 -29
- package/core/plugins/SnapchatInjectorPlugin.js +4 -26
- package/core/plugins/SocialMediaInjectorPlugin.js +7 -119
- package/core/plugins/TikTokInjectorPlugin.js +2 -24
- package/core/plugins/UnityInjectorPlugin.js +8 -37
- package/core/plugins/ZipPlugin.js +20 -138
- package/core/plugins/socialMediaVitePlugin.js +2 -108
- package/core/utils/buildDefines.js +2 -30
- package/core/utils/buildTemplateString.js +5 -39
- package/core/utils/date.js +1 -16
- package/core/utils/fontRegistry.js +13 -81
- package/core/utils/fontResolver.js +37 -335
- package/core/utils/generateAdikteevHtmlWebpackPluginConfig.js +7 -42
- package/core/utils/generateBigabidHtmlWebpackPluginConfig.js +3 -30
- package/core/utils/generateInMobiHtmlWebpackPluginConfig.js +7 -43
- package/core/utils/getPlaycraftOrientationSuffix.js +2 -9
- package/core/utils/injectSDKPlugins.js +13 -68
- package/core/utils/logOptions.js +5 -46
- package/core/utils/mergeOptions.js +2 -19
- package/core/utils/parseArgvOptions.js +5 -111
- package/core/utils/resolveChannelFold2Zip.js +5 -17
- package/core/utils/validateThemeData.js +26 -227
- package/core/validators/pre-build-checker.js +26 -286
- package/core/validators/tracking-validator.js +40 -358
- package/core/vite.dev.js +17 -322
- package/core/webpack.build.js +66 -520
- package/core/webpack.common.js +11 -177
- package/core/webpack.dev.js +6 -88
- package/defines.d.ts +1 -0
- package/package.json +3 -4
- package/README.md +0 -262
|
@@ -1,10 +1,6 @@
|
|
|
1
|
-
const 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
|
-
|
|
113
|
-
|
|
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
|
-
|
|
169
|
-
|
|
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
|
-
|
|
191
|
-
|
|
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
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
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
|
-
|
|
361
|
-
console.log(
|
|
362
|
-
|
|
363
|
-
|
|
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
|
-
|
|
417
|
-
|
|
418
|
-
|
|
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};
|