@playcraft/devkit 1.0.16 → 1.0.18

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 (57) hide show
  1. package/README.md +259 -0
  2. package/cli/bin/playable-scripts.js +44 -1
  3. package/cli/commands/build.js +130 -17
  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 +1091 -124
  9. package/core/cos-uploader.js +264 -20
  10. package/core/index.js +71 -4
  11. package/core/loaders/gltf-loader.js +74 -28
  12. package/core/options.js +310 -15
  13. package/core/plugins/AdikteevInjectorPlugin.js +26 -4
  14. package/core/plugins/BigoAdsInjectorPlugin.js +21 -4
  15. package/core/plugins/DAPIInjectorPlugin.js +25 -2
  16. package/core/plugins/DebuggerInjectionPlugin.js +31 -3
  17. package/core/plugins/ExitAPIInjectorPlugin.js +57 -3
  18. package/core/plugins/FflateCompressionPlugin.js +225 -37
  19. package/core/plugins/FontInjectionWebpackPlugin.js +59 -0
  20. package/core/plugins/LiftoffInjectorPlugin.js +26 -4
  21. package/core/plugins/MRAIDInjectorPlugin.js +24 -2
  22. package/core/plugins/MintegralInjectorPlugin.js +25 -2
  23. package/core/plugins/PangleInjectorPlugin.js +24 -2
  24. package/core/plugins/SnapchatInjectorPlugin.js +26 -4
  25. package/core/plugins/TikTokInjectorPlugin.js +24 -2
  26. package/core/plugins/UnityInjectorPlugin.js +37 -8
  27. package/core/plugins/ZipPlugin.js +138 -19
  28. package/core/resources/fonts/README.md +31 -0
  29. package/core/resources/fonts/maoken-regular.woff2 +0 -0
  30. package/core/resources/fonts/maoken-regular.woff2.chars +1 -0
  31. package/core/resources/fonts/maoken-zhuyuan.ttf +0 -0
  32. package/core/resources/fonts/noto-sans-sc-regular.woff2 +0 -0
  33. package/core/resources/fonts/pangmen-biaoti.ttf +0 -0
  34. package/core/resources/fonts/pangmen-regular.woff2 +0 -0
  35. package/core/resources/fonts/pangmen-regular.woff2.chars +1 -0
  36. package/core/resources/fonts/poppins-regular.woff2 +0 -0
  37. package/core/utils/buildDefines.js +29 -2
  38. package/core/utils/buildTemplateString.js +40 -5
  39. package/core/utils/date.js +16 -1
  40. package/core/utils/fontRegistry.js +97 -0
  41. package/core/utils/fontResolver.js +398 -0
  42. package/core/utils/generateAdikteevHtmlWebpackPluginConfig.js +42 -7
  43. package/core/utils/generateBigabidHtmlWebpackPluginConfig.js +30 -3
  44. package/core/utils/generateInMobiHtmlWebpackPluginConfig.js +43 -7
  45. package/core/utils/injectSDKPlugins.js +58 -11
  46. package/core/utils/logOptions.js +37 -4
  47. package/core/utils/mergeOptions.js +19 -2
  48. package/core/utils/parseArgvOptions.js +110 -5
  49. package/core/utils/resolveChannelFold2Zip.js +15 -3
  50. package/core/utils/validateThemeData.js +220 -25
  51. package/core/validators/pre-build-checker.js +201 -21
  52. package/core/validators/tracking-validator.js +358 -40
  53. package/core/vite.dev.js +332 -16
  54. package/core/webpack.build.js +494 -52
  55. package/core/webpack.common.js +177 -11
  56. package/core/webpack.dev.js +82 -5
  57. package/package.json +3 -2
@@ -1,17 +1,86 @@
1
1
  #!/usr/bin/env node
2
- "use strict";/**
2
+
3
+ 'use strict';
4
+
5
+ /**
3
6
  * COS 上传模块
4
7
  *
5
8
  * 功能:上传文件到腾讯云 COS,并刷新 CDN 缓存
6
- */const COS=require("cos-nodejs-sdk-v5");const fs=require("fs");const path=require("path");/**
9
+ */
10
+
11
+ const COS = require('cos-nodejs-sdk-v5');
12
+ const fs = require('fs');
13
+ const path = require('path');
14
+
15
+ /**
7
16
  * 从远程 API 获取临时凭证
8
- */async function fetchTemporaryCredentials(){const https=require("https");const credentialsUrl="https://wd-act.woa.com/aix/playable/api/cos/credentials";return new Promise((resolve,reject)=>{https.get(credentialsUrl,response=>{if(response.statusCode!==200){reject(new Error(`HTTP ${response.statusCode}: ${response.statusMessage}`));return}let data="";response.on("data",chunk=>data+=chunk);response.on("end",()=>{try{const result=JSON.parse(data);if(result.code==="SUCCESS"){resolve(result.data)}else{reject(new Error(result.message||"Failed to get credentials"))}}catch(error){reject(new Error(`Failed to parse response: ${error.message}`))}})}).on("error",reject).setTimeout(30000,function(){this.destroy();reject(new Error("Request timeout"))})})}/**
17
+ */
18
+ async function fetchTemporaryCredentials() {
19
+ const https = require('https');
20
+ const credentialsUrl = 'https://wd-act.woa.com/aix/playable/api/cos/credentials';
21
+
22
+ return new Promise((resolve, reject) => {
23
+ https.get(credentialsUrl, (response) => {
24
+ if (response.statusCode !== 200) {
25
+ reject(new Error(`HTTP ${response.statusCode}: ${response.statusMessage}`));
26
+ return;
27
+ }
28
+
29
+ let data = '';
30
+ response.on('data', chunk => data += chunk);
31
+ response.on('end', () => {
32
+ try {
33
+ const result = JSON.parse(data);
34
+ if (result.code === 'SUCCESS') {
35
+ resolve(result.data);
36
+ } else {
37
+ reject(new Error(result.message || 'Failed to get credentials'));
38
+ }
39
+ } catch (error) {
40
+ reject(new Error(`Failed to parse response: ${error.message}`));
41
+ }
42
+ });
43
+ }).on('error', reject)
44
+ .setTimeout(30000, function() {
45
+ this.destroy();
46
+ reject(new Error('Request timeout'));
47
+ });
48
+ });
49
+ }
50
+
51
+ /**
9
52
  * 创建 COS 客户端(使用临时凭证)
10
53
  * @param {Object} cosConfig - COS 配置
11
54
  * @param {Object} credentials - 已获取的临时凭证(可选,避免重复请求)
12
- */async function createCOSClient(cosConfig,credentials=null){// 如果配置中提供了 SecretId 和 SecretKey,使用传统方式
13
- if(cosConfig&&cosConfig.SecretId&&cosConfig.SecretKey){return new COS({SecretId:cosConfig.SecretId,SecretKey:cosConfig.SecretKey})}// 使用已传入的凭证,或重新获取
14
- if(!credentials){credentials=await fetchTemporaryCredentials()}return new COS({getAuthorization:(options,callback)=>{callback({TmpSecretId:credentials.credentials.tmpSecretId,TmpSecretKey:credentials.credentials.tmpSecretKey,SecurityToken:credentials.credentials.sessionToken,StartTime:credentials.startTime,ExpiredTime:credentials.expiredTime})}})}/**
55
+ */
56
+ async function createCOSClient(cosConfig, credentials = null) {
57
+ // 如果配置中提供了 SecretId 和 SecretKey,使用传统方式
58
+ if (cosConfig && cosConfig.SecretId && cosConfig.SecretKey) {
59
+ return new COS({
60
+ SecretId: cosConfig.SecretId,
61
+ SecretKey: cosConfig.SecretKey,
62
+ });
63
+ }
64
+
65
+ // 使用已传入的凭证,或重新获取
66
+ if (!credentials) {
67
+ credentials = await fetchTemporaryCredentials();
68
+ }
69
+
70
+ return new COS({
71
+ getAuthorization: (options, callback) => {
72
+ callback({
73
+ TmpSecretId: credentials.credentials.tmpSecretId,
74
+ TmpSecretKey: credentials.credentials.tmpSecretKey,
75
+ SecurityToken: credentials.credentials.sessionToken,
76
+ StartTime: credentials.startTime,
77
+ ExpiredTime: credentials.expiredTime,
78
+ });
79
+ },
80
+ });
81
+ }
82
+
83
+ /**
15
84
  * 刷新 CDN 缓存
16
85
  * 通过服务端 API 代理执行(STS 临时凭证无法携带 CDN 权限,需由服务端使用主账号凭证刷新)
17
86
  *
@@ -19,9 +88,82 @@ if(!credentials){credentials=await fetchTemporaryCredentials()}return new COS({g
19
88
  * @param {Array<string>} urls - 需要刷新的 URL 列表
20
89
  * @param {Object} credentials - 未使用,保留参数兼容性
21
90
  * @returns {Promise<void>}
22
- */async function purgeCDNCache(cosConfig,urls,credentials=null){if(!urls||urls.length===0){return}const area=(cosConfig===null||cosConfig===void 0?void 0:cosConfig.CdnArea)||"overseas";// 如果配置中有 SecretId/SecretKey,本地直接刷新
23
- if(cosConfig&&cosConfig.SecretId&&cosConfig.SecretKey){try{const tencentcloud=require("tencentcloud-sdk-nodejs-cdn");const client=new tencentcloud.cdn.v20180606.Client({credential:{secretId:cosConfig.SecretId,secretKey:cosConfig.SecretKey},region:"",profile:{httpProfile:{endpoint:"cdn.tencentcloudapi.com"}}});const batchSize=1000;for(let i=0;i<urls.length;i+=batchSize){const batch=urls.slice(i,i+batchSize);await client.PurgeUrlsCache({Urls:batch,Area:area});console.log(`✓ CDN 缓存已刷新 ${batch.length} 个 URL (区域: ${area})`)}}catch(error){if(error.code==="MODULE_NOT_FOUND"){console.warn("\u26A0\uFE0F \u672A\u5B89\u88C5 tencentcloud-sdk-nodejs-cdn\uFF0C\u8DF3\u8FC7 CDN \u5237\u65B0")}else{throw error}}return}// 否则通过服务端 API 代理刷新
24
- const https=require("https");const purgeUrl="https://wd-act.woa.com/aix/playable/api/cdn/purge";const postData=JSON.stringify({urls,area});return new Promise((resolve,reject)=>{const req=https.request(purgeUrl,{method:"POST",headers:{"Content-Type":"application/json","Content-Length":Buffer.byteLength(postData)}},response=>{let data="";response.on("data",chunk=>data+=chunk);response.on("end",()=>{try{const result=JSON.parse(data);if(result.code==="SUCCESS"){console.log(`✓ CDN 缓存已刷新 ${urls.length} URL (区域: ${area})`);resolve()}else{reject(new Error(result.message||"CDN purge failed"))}}catch(error){reject(new Error(`Failed to parse CDN purge response: ${error.message}`))}})});req.on("error",reject);req.setTimeout(30000,function(){this.destroy();reject(new Error("CDN purge request timeout"))});req.write(postData);req.end()})}/**
91
+ */
92
+ async function purgeCDNCache(cosConfig, urls, credentials = null) {
93
+ if (!urls || urls.length === 0) {
94
+ return;
95
+ }
96
+
97
+ const area = cosConfig?.CdnArea || 'overseas';
98
+
99
+ // 如果配置中有 SecretId/SecretKey,本地直接刷新
100
+ if (cosConfig && cosConfig.SecretId && cosConfig.SecretKey) {
101
+ try {
102
+ const tencentcloud = require('tencentcloud-sdk-nodejs-cdn');
103
+ const client = new tencentcloud.cdn.v20180606.Client({
104
+ credential: { secretId: cosConfig.SecretId, secretKey: cosConfig.SecretKey },
105
+ region: '',
106
+ profile: { httpProfile: { endpoint: 'cdn.tencentcloudapi.com' } },
107
+ });
108
+ const batchSize = 1000;
109
+ for (let i = 0; i < urls.length; i += batchSize) {
110
+ const batch = urls.slice(i, i + batchSize);
111
+ await client.PurgeUrlsCache({ Urls: batch, Area: area });
112
+ console.log(`✓ CDN 缓存已刷新 ${batch.length} 个 URL (区域: ${area})`);
113
+ }
114
+ } catch (error) {
115
+ if (error.code === 'MODULE_NOT_FOUND') {
116
+ console.warn('⚠️ 未安装 tencentcloud-sdk-nodejs-cdn,跳过 CDN 刷新');
117
+ } else {
118
+ throw error;
119
+ }
120
+ }
121
+ return;
122
+ }
123
+
124
+ // 否则通过服务端 API 代理刷新
125
+ const https = require('https');
126
+ const purgeUrl = 'https://wd-act.woa.com/aix/playable/api/cdn/purge';
127
+
128
+ const postData = JSON.stringify({ urls, area });
129
+
130
+ return new Promise((resolve, reject) => {
131
+ const req = https.request(purgeUrl, {
132
+ method: 'POST',
133
+ headers: {
134
+ 'Content-Type': 'application/json',
135
+ 'Content-Length': Buffer.byteLength(postData),
136
+ },
137
+ }, (response) => {
138
+ let data = '';
139
+ response.on('data', chunk => data += chunk);
140
+ response.on('end', () => {
141
+ try {
142
+ const result = JSON.parse(data);
143
+ if (result.code === 'SUCCESS') {
144
+ console.log(`✓ CDN 缓存已刷新 ${urls.length} 个 URL (区域: ${area})`);
145
+ resolve();
146
+ } else {
147
+ reject(new Error(result.message || 'CDN purge failed'));
148
+ }
149
+ } catch (error) {
150
+ reject(new Error(`Failed to parse CDN purge response: ${error.message}`));
151
+ }
152
+ });
153
+ });
154
+
155
+ req.on('error', reject);
156
+ req.setTimeout(30000, function() {
157
+ this.destroy();
158
+ reject(new Error('CDN purge request timeout'));
159
+ });
160
+
161
+ req.write(postData);
162
+ req.end();
163
+ });
164
+ }
165
+
166
+ /**
25
167
  * 上传单个文件到 COS
26
168
  *
27
169
  * @param {Object} cosConfig - COS 配置
@@ -29,12 +171,58 @@ const https=require("https");const purgeUrl="https://wd-act.woa.com/aix/playable
29
171
  * @param {string} cosKey - COS 对象键(远程路径)
30
172
  * @param {Object} credentials - 可选的凭证对象
31
173
  * @returns {Promise<string>} - 上传成功返回访问链接
32
- */async function uploadFileToCOS(cosConfig,filePath,cosKey,credentials=null){if(!fs.existsSync(filePath)){throw new Error(`文件不存在: ${filePath}`)}// 获取临时凭证和配置信息
33
- let bucket,region,cdnDomain;if(cosConfig&&cosConfig.Bucket&&cosConfig.Region){// 使用配置文件中的信息
34
- bucket=cosConfig.Bucket;region=cosConfig.Region;cdnDomain=cosConfig.CdnDomain||"static.aix.intlgame.com"}else{// 从临时凭证 API 获取配置(优先使用传入的凭证,否则重新获取)
35
- if(!credentials){credentials=await fetchTemporaryCredentials()}bucket=credentials.bucket;region=credentials.region;cdnDomain="static.aix.intlgame.com"}const cos=await createCOSClient(cosConfig||null,credentials);return new Promise((resolve,reject)=>{cos.putObject({Bucket:bucket,Region:region,Key:cosKey,Body:fs.createReadStream(filePath)},(err,data)=>{if(err){reject(new Error(`上传失败: ${err.message}`))}else{// 生成访问链接
36
- // 对路径的每个部分分别进行 URL 编码
37
- const pathParts=cosKey.split("/");const encodedPath=pathParts.map(part=>encodeURIComponent(part)).join("/");const url=`https://${cdnDomain}/${encodedPath}`;resolve(url)}})})}/**
174
+ */
175
+ async function uploadFileToCOS(cosConfig, filePath, cosKey, credentials = null) {
176
+ if (!fs.existsSync(filePath)) {
177
+ throw new Error(`文件不存在: ${filePath}`);
178
+ }
179
+
180
+ // 获取临时凭证和配置信息
181
+ let bucket, region, cdnDomain;
182
+
183
+ if (cosConfig && cosConfig.Bucket && cosConfig.Region) {
184
+ // 使用配置文件中的信息
185
+ bucket = cosConfig.Bucket;
186
+ region = cosConfig.Region;
187
+ cdnDomain = cosConfig.CdnDomain || 'static.aix.intlgame.com';
188
+ } else {
189
+ // 从临时凭证 API 获取配置(优先使用传入的凭证,否则重新获取)
190
+ if (!credentials) {
191
+ credentials = await fetchTemporaryCredentials();
192
+ }
193
+ bucket = credentials.bucket;
194
+ region = credentials.region;
195
+ cdnDomain = 'static.aix.intlgame.com';
196
+ }
197
+
198
+ const cos = await createCOSClient(cosConfig || null, credentials);
199
+
200
+ return new Promise((resolve, reject) => {
201
+ cos.putObject(
202
+ {
203
+ Bucket: bucket,
204
+ Region: region,
205
+ Key: cosKey,
206
+ Body: fs.createReadStream(filePath),
207
+ },
208
+ (err, data) => {
209
+ if (err) {
210
+ reject(new Error(`上传失败: ${err.message}`));
211
+ } else {
212
+ // 生成访问链接
213
+ // 对路径的每个部分分别进行 URL 编码
214
+ const pathParts = cosKey.split('/');
215
+ const encodedPath = pathParts.map(part => encodeURIComponent(part)).join('/');
216
+
217
+ const url = `https://${cdnDomain}/${encodedPath}`;
218
+ resolve(url);
219
+ }
220
+ }
221
+ );
222
+ });
223
+ }
224
+
225
+ /**
38
226
  * 上传目录下的所有文件到 COS
39
227
  *
40
228
  * @param {Object} cosConfig - COS 配置
@@ -42,8 +230,64 @@ const pathParts=cosKey.split("/");const encodedPath=pathParts.map(part=>encodeUR
42
230
  * @param {string} cosPrefix - COS 路径前缀
43
231
  * @param {Object} credentials - 可选的凭证对象
44
232
  * @returns {Promise<Array>} - 上传成功返回文件列表
45
- */async function uploadDirectoryToCOS(cosConfig,localDir,cosPrefix,credentials=null){if(!fs.existsSync(localDir)){throw new Error(`目录不存在: ${localDir}`)}// 如果没有传入凭证且没有配置,获取一次凭证用于所有文件上传
46
- if(!credentials&&(!cosConfig||!cosConfig.Bucket||!cosConfig.Region)){try{credentials=await fetchTemporaryCredentials();console.log("\u2713 \u83B7\u53D6\u4E34\u65F6\u51ED\u8BC1\u6210\u529F")}catch(error){throw new Error(`获取临时凭证失败: ${error.message}`)}}const files=fs.readdirSync(localDir);const results=[];const uploadedUrls=[];for(const file of files){const filePath=path.join(localDir,file);const stat=fs.statSync(filePath);if(stat.isFile()){// 去掉文件名中的 -preview 或 _preview 后缀
47
- let cleanFileName=file;cleanFileName=cleanFileName.replace(/-preview\./i,".");cleanFileName=cleanFileName.replace(/_preview\./i,".");const cosKey=cosPrefix?`${cosPrefix}/${cleanFileName}`:cleanFileName;// 传入凭证,避免重复获取
48
- const url=await uploadFileToCOS(cosConfig,filePath,cosKey,credentials);results.push({file:cleanFileName,url});uploadedUrls.push(url)}}// 上传完成后刷新 CDN 缓存,传入凭证
49
- if(uploadedUrls.length>0){try{await purgeCDNCache(cosConfig,uploadedUrls,credentials);console.log(`✓ CDN 缓存刷新成功 (${uploadedUrls.length} 个文件)`)}catch(error){console.warn(`⚠️ CDN 刷新失败: ${error.message}`)}}return results}module.exports={fetchTemporaryCredentials,createCOSClient,uploadFileToCOS,uploadDirectoryToCOS,purgeCDNCache};
233
+ */
234
+ async function uploadDirectoryToCOS(cosConfig, localDir, cosPrefix, credentials = null) {
235
+ if (!fs.existsSync(localDir)) {
236
+ throw new Error(`目录不存在: ${localDir}`);
237
+ }
238
+
239
+ // 如果没有传入凭证且没有配置,获取一次凭证用于所有文件上传
240
+ if (!credentials && (!cosConfig || !cosConfig.Bucket || !cosConfig.Region)) {
241
+ try {
242
+ credentials = await fetchTemporaryCredentials();
243
+ console.log('✓ 获取临时凭证成功');
244
+ } catch (error) {
245
+ throw new Error(`获取临时凭证失败: ${error.message}`);
246
+ }
247
+ }
248
+
249
+ const files = fs.readdirSync(localDir);
250
+ const results = [];
251
+ const uploadedUrls = [];
252
+
253
+ for (const file of files) {
254
+ const filePath = path.join(localDir, file);
255
+ const stat = fs.statSync(filePath);
256
+
257
+ if (stat.isFile()) {
258
+ // 去掉文件名中的 -preview 或 _preview 后缀
259
+ let cleanFileName = file;
260
+ cleanFileName = cleanFileName.replace(/-preview\./i, '.');
261
+ cleanFileName = cleanFileName.replace(/_preview\./i, '.');
262
+
263
+ const cosKey = cosPrefix ? `${cosPrefix}/${cleanFileName}` : cleanFileName;
264
+ // 传入凭证,避免重复获取
265
+ const url = await uploadFileToCOS(cosConfig, filePath, cosKey, credentials);
266
+ results.push({
267
+ file: cleanFileName,
268
+ url,
269
+ });
270
+ uploadedUrls.push(url);
271
+ }
272
+ }
273
+
274
+ // 上传完成后刷新 CDN 缓存,传入凭证
275
+ if (uploadedUrls.length > 0) {
276
+ try {
277
+ await purgeCDNCache(cosConfig, uploadedUrls, credentials);
278
+ console.log(`✓ CDN 缓存刷新成功 (${uploadedUrls.length} 个文件)`);
279
+ } catch (error) {
280
+ console.warn(`⚠️ CDN 刷新失败: ${error.message}`);
281
+ }
282
+ }
283
+
284
+ return results;
285
+ }
286
+
287
+ module.exports = {
288
+ fetchTemporaryCredentials,
289
+ createCOSClient,
290
+ uploadFileToCOS,
291
+ uploadDirectoryToCOS,
292
+ purgeCDNCache,
293
+ };
package/core/index.js CHANGED
@@ -1,4 +1,71 @@
1
- "use strict";const{allowedExtensions,webpackCommonConfig}=require("./webpack.common.js");const{parseArgvOptions}=require("./utils/parseArgvOptions.js");const{mergeOptions}=require("./utils/mergeOptions.js");const{options}=require("./options.js");const{DAPIInjectorPlugin}=require("./plugins/DAPIInjectorPlugin.js");const{DebuggerInjectionPlugin}=require("./plugins/DebuggerInjectionPlugin.js");const{ExitAPIInjectorPlugin}=require("./plugins/ExitAPIInjectorPlugin.js");const{runBuild,makeWebpackBuildConfig}=require("./webpack.build.js");const{runDev,makeWebpackDevConfig}=require("./webpack.dev.js");const{runViteDev,makeViteDevConfig}=require("./vite.dev.js");// 验证器
2
- const{runPreBuildChecks,runTrackingCheck,registerChecker,toggleChecker,listCheckers}=require("./validators/pre-build-checker.js");const{validateTracking,printValidationResult,TRACKING_METHODS,SDK_TO_TRACKING_MAP}=require("./validators/tracking-validator.js");exports.mergeOptions=mergeOptions;exports.parseArgvOptions=parseArgvOptions;exports.options=options;exports.allowedExtensions=allowedExtensions;exports.webpackCommonConfig=webpackCommonConfig;exports.makeWebpackDevConfig=makeWebpackDevConfig;exports.makeWebpackBuildConfig=makeWebpackBuildConfig;exports.runDev=runDev;exports.runBuild=runBuild;exports.DAPIInjectorPlugin=DAPIInjectorPlugin;exports.ExitAPIInjectorPlugin=ExitAPIInjectorPlugin;exports.DebuggerInjectionPlugin=DebuggerInjectionPlugin;exports.makeViteDevConfig=makeViteDevConfig;exports.runViteDev=runViteDev;// 编译前检查器导出
3
- exports.runPreBuildChecks=runPreBuildChecks;exports.runTrackingCheck=runTrackingCheck;exports.registerChecker=registerChecker;exports.toggleChecker=toggleChecker;exports.listCheckers=listCheckers;// 埋点验证器导出
4
- exports.validateTracking=validateTracking;exports.printValidationResult=printValidationResult;exports.TRACKING_METHODS=TRACKING_METHODS;exports.SDK_TO_TRACKING_MAP=SDK_TO_TRACKING_MAP;
1
+ const { allowedExtensions, webpackCommonConfig } = require('./webpack.common.js');
2
+ const { parseArgvOptions } = require('./utils/parseArgvOptions.js');
3
+ const { mergeOptions } = require('./utils/mergeOptions.js');
4
+ const { options } = require('./options.js');
5
+ const { DAPIInjectorPlugin } = require('./plugins/DAPIInjectorPlugin.js');
6
+ const { DebuggerInjectionPlugin } = require('./plugins/DebuggerInjectionPlugin.js');
7
+ const { ExitAPIInjectorPlugin } = require('./plugins/ExitAPIInjectorPlugin.js');
8
+ const { runBuild, makeWebpackBuildConfig } = require('./webpack.build.js');
9
+ const { runDev, makeWebpackDevConfig } = require('./webpack.dev.js');
10
+ const { runViteDev, makeViteDevConfig } = require('./vite.dev.js');
11
+
12
+ // 字体注入
13
+ const { FontInjectionWebpackPlugin } = require('./plugins/FontInjectionWebpackPlugin.js');
14
+ const { FONT_REGISTRY, getRegisteredFontKeys, isBuiltin: isBuiltinFont, getFontEntry } = require('./utils/fontRegistry.js');
15
+ const { getOrGenerateFontCSS } = require('./utils/fontResolver.js');
16
+
17
+ // 验证器
18
+ const {
19
+ runPreBuildChecks,
20
+ runTrackingCheck,
21
+ registerChecker,
22
+ toggleChecker,
23
+ listCheckers
24
+ } = require('./validators/pre-build-checker.js');
25
+
26
+ const {
27
+ validateTracking,
28
+ printValidationResult,
29
+ TRACKING_METHODS,
30
+ SDK_TO_TRACKING_MAP
31
+ } = require('./validators/tracking-validator.js');
32
+
33
+ exports.mergeOptions = mergeOptions;
34
+ exports.parseArgvOptions = parseArgvOptions;
35
+
36
+ exports.options = options;
37
+
38
+ exports.allowedExtensions = allowedExtensions;
39
+ exports.webpackCommonConfig = webpackCommonConfig;
40
+ exports.makeWebpackDevConfig = makeWebpackDevConfig;
41
+ exports.makeWebpackBuildConfig = makeWebpackBuildConfig;
42
+ exports.runDev = runDev;
43
+ exports.runBuild = runBuild;
44
+
45
+ exports.DAPIInjectorPlugin = DAPIInjectorPlugin;
46
+ exports.ExitAPIInjectorPlugin = ExitAPIInjectorPlugin;
47
+ exports.DebuggerInjectionPlugin = DebuggerInjectionPlugin;
48
+
49
+ exports.makeViteDevConfig = makeViteDevConfig;
50
+ exports.runViteDev = runViteDev;
51
+
52
+ // 编译前检查器导出
53
+ exports.runPreBuildChecks = runPreBuildChecks;
54
+ exports.runTrackingCheck = runTrackingCheck;
55
+ exports.registerChecker = registerChecker;
56
+ exports.toggleChecker = toggleChecker;
57
+ exports.listCheckers = listCheckers;
58
+
59
+ // 埋点验证器导出
60
+ exports.validateTracking = validateTracking;
61
+ exports.printValidationResult = printValidationResult;
62
+ exports.TRACKING_METHODS = TRACKING_METHODS;
63
+ exports.SDK_TO_TRACKING_MAP = SDK_TO_TRACKING_MAP;
64
+
65
+ // 字体注入导出
66
+ exports.FontInjectionWebpackPlugin = FontInjectionWebpackPlugin;
67
+ exports.FONT_REGISTRY = FONT_REGISTRY;
68
+ exports.getRegisteredFontKeys = getRegisteredFontKeys;
69
+ exports.isBuiltinFont = isBuiltinFont;
70
+ exports.getFontEntry = getFontEntry;
71
+ exports.getOrGenerateFontCSS = getOrGenerateFontCSS;
@@ -1,4 +1,11 @@
1
- "use strict";const path=require("path");const loaderUtils=require("loader-utils");const fs=require("fs");const isObject=value=>typeof value==="object"&&value!==null;const isDataURI=value=>/^data:/i.test(value);/**
1
+ const path = require('path');
2
+ const loaderUtils = require('loader-utils');
3
+ const fs = require('fs');
4
+
5
+ const isObject = (value) => typeof value === 'object' && value !== null;
6
+ const isDataURI = (value) => /^data:/i.test(value);
7
+
8
+ /**
2
9
  * A utility function that allows mapping the value for a given key in an object
3
10
  * [in place](https://en.wikipedia.org/wiki/In-place_algorithm).
4
11
  *
@@ -13,33 +20,72 @@
13
20
  * @param {(value: unknown) => Promise<any>} callback
14
21
  * The callback that either returns the mapped value, or returns a Promise
15
22
  * that resolves to the new value.
16
- */async function mapDeep(obj,key,callback){if(Array.isArray(obj)){for(const item of obj){await mapDeep(item,key,callback)}}else if(isObject(obj)){for(const[k,v]of Object.entries(obj)){if(k===key){obj[k]=await callback(v)}else{await mapDeep(v,key,callback)}}}}/**
23
+ */
24
+ async function mapDeep(obj, key, callback) {
25
+ if (Array.isArray(obj)) {
26
+ for (const item of obj) {
27
+ await mapDeep(item, key, callback);
28
+ }
29
+ } else if (isObject(obj)) {
30
+ for (const [k, v] of Object.entries(obj)) {
31
+ if (k === key) {
32
+ obj[k] = await callback(v);
33
+ } else {
34
+ await mapDeep(v, key, callback);
35
+ }
36
+ }
37
+ }
38
+ }
39
+
40
+ /**
17
41
  * @typedef {import(".").GLTFLoaderDefinition} GLTFLoader
18
42
  * @this {ThisParameterType<GLTFLoader>}
19
43
  * @type {GLTFLoader}
20
- */async function gltfLoader(content){// Destructure loader options and set default values:
21
- const{// inline = true,
22
- // useRelativePaths = true,
23
- // uriResolver = (module) => String(module.default ?? module),
24
- // fileName = '[name].[hash:8].[ext]',
25
- // filePath = '/static/media',
26
- // publicPath = this._compilation?.outputOptions.publicPath ?? '/',
27
- context=this.context}=this.getOptions();// Parse the glTF data:
28
- // console.log(content);
29
- const data=JSON.parse(content);// console.log(data);
30
- // Iterate over the object and map any URIs:
31
- await mapDeep(data,"uri",async uri=>{// Resolve early if the URI cannot be imported as a module:
32
- if(!loaderUtils.isUrlRequest(uri))return uri;// If the URI is a data URI, print a warning and resolve with the original value:
33
- if(isDataURI(uri)){return uri}const requestPath=path.join(context,uri);// console.log(requestPath);
34
- const binContent=fs.readFileSync(requestPath,{encoding:"base64"});return`data:application/octet-stream;base64,${binContent}`});// Stringify the updated data:
35
- const updatedContent=JSON.stringify(data);// Interpolate any file name tokens:
36
- // const interpolatedName = loaderUtils.interpolateName(this, fileName, { content: updatedContent });
37
- // Join the file path and interpolated name:
38
- // const interpolatedPath = path.join(filePath, interpolatedName);
39
- // Emit the file:
40
- // this.emitFile(interpolatedPath, updatedContent, null);
41
- // Join all paths together:
42
- // const fullPath = path.join(publicPath, interpolatedPath);
43
- // Lastly, resolve with either the JSON data or the full output path:
44
- // return `export default ${inline ? updatedContent : `"${fullPath}"`}`;
45
- return`export default ${updatedContent}`}module.exports=gltfLoader;
44
+ */
45
+ async function gltfLoader(content) {
46
+ // Destructure loader options and set default values:
47
+ const {
48
+ // inline = true,
49
+ // useRelativePaths = true,
50
+ // uriResolver = (module) => String(module.default ?? module),
51
+ // fileName = '[name].[hash:8].[ext]',
52
+ // filePath = '/static/media',
53
+ // publicPath = this._compilation?.outputOptions.publicPath ?? '/',
54
+ context = this.context
55
+ } = this.getOptions();
56
+
57
+ // Parse the glTF data:
58
+
59
+ // console.log(content);
60
+ const data = JSON.parse(content);
61
+ // console.log(data);
62
+ // Iterate over the object and map any URIs:
63
+ await mapDeep(data, 'uri', async (uri) => {
64
+ // Resolve early if the URI cannot be imported as a module:
65
+ if (!loaderUtils.isUrlRequest(uri)) return uri;
66
+ // If the URI is a data URI, print a warning and resolve with the original value:
67
+ if (isDataURI(uri)) {
68
+ return uri;
69
+ }
70
+
71
+ const requestPath = path.join(context, uri);
72
+ // console.log(requestPath);
73
+ const binContent = fs.readFileSync(requestPath, { encoding: 'base64' });
74
+ return `data:application/octet-stream;base64,${binContent}`;
75
+ });
76
+ // Stringify the updated data:
77
+ const updatedContent = JSON.stringify(data);
78
+ // Interpolate any file name tokens:
79
+ // const interpolatedName = loaderUtils.interpolateName(this, fileName, { content: updatedContent });
80
+ // Join the file path and interpolated name:
81
+ // const interpolatedPath = path.join(filePath, interpolatedName);
82
+ // Emit the file:
83
+ // this.emitFile(interpolatedPath, updatedContent, null);
84
+ // Join all paths together:
85
+ // const fullPath = path.join(publicPath, interpolatedPath);
86
+ // Lastly, resolve with either the JSON data or the full output path:
87
+ // return `export default ${inline ? updatedContent : `"${fullPath}"`}`;
88
+ return `export default ${updatedContent}`;
89
+ }
90
+
91
+ module.exports = gltfLoader;