@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.
- package/cli/bin/playable-scripts.js +1 -44
- package/cli/commands/build.js +18 -130
- 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 +125 -1091
- package/core/constants.js +6 -0
- package/core/cos-uploader.js +20 -264
- package/core/index.js +7 -76
- package/core/loaders/gltf-loader.js +28 -74
- package/core/options.js +15 -310
- 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 -57
- package/core/plugins/FflateCompressionPlugin.js +37 -225
- 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/SnapchatInjectorPlugin.js +4 -26
- package/core/plugins/TikTokInjectorPlugin.js +2 -24
- package/core/plugins/UnityInjectorPlugin.js +8 -37
- package/core/plugins/ZipPlugin.js +19 -138
- package/core/utils/buildDefines.js +2 -29
- package/core/utils/buildTemplateString.js +5 -40
- 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/injectSDKPlugins.js +11 -58
- package/core/utils/logOptions.js +4 -37
- package/core/utils/mergeOptions.js +2 -19
- package/core/utils/parseArgvOptions.js +5 -110
- package/core/utils/resolveChannelFold2Zip.js +3 -15
- package/core/utils/validateThemeData.js +25 -220
- package/core/validators/pre-build-checker.js +21 -201
- package/core/validators/tracking-validator.js +40 -358
- package/core/vite.dev.js +17 -321
- package/core/webpack.build.js +56 -489
- package/core/webpack.common.js +11 -177
- package/core/webpack.dev.js +5 -82
- package/package.json +2 -3
- package/README.md +0 -259
package/core/cos-uploader.js
CHANGED
|
@@ -1,86 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
'use strict';
|
|
4
|
-
|
|
5
|
-
/**
|
|
2
|
+
"use strict";/**
|
|
6
3
|
* COS 上传模块
|
|
7
4
|
*
|
|
8
5
|
* 功能:上传文件到腾讯云 COS,并刷新 CDN 缓存
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
const COS = require('cos-nodejs-sdk-v5');
|
|
12
|
-
const fs = require('fs');
|
|
13
|
-
const path = require('path');
|
|
14
|
-
|
|
15
|
-
/**
|
|
6
|
+
*/const COS=require("cos-nodejs-sdk-v5");const fs=require("fs");const path=require("path");const http=require("http");const{API_BASE_URL}=require("./constants");/**
|
|
16
7
|
* 从远程 API 获取临时凭证
|
|
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
|
-
/**
|
|
8
|
+
*/async function fetchTemporaryCredentials(){const credentialsUrl=`${API_BASE_URL}/aix/playable/api/cos/credentials`;return new Promise((resolve,reject)=>{http.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"))})})}/**
|
|
52
9
|
* 创建 COS 客户端(使用临时凭证)
|
|
53
10
|
* @param {Object} cosConfig - COS 配置
|
|
54
11
|
* @param {Object} credentials - 已获取的临时凭证(可选,避免重复请求)
|
|
55
|
-
*/
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
/**
|
|
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})}})}/**
|
|
84
15
|
* 刷新 CDN 缓存
|
|
85
16
|
* 通过服务端 API 代理执行(STS 临时凭证无法携带 CDN 权限,需由服务端使用主账号凭证刷新)
|
|
86
17
|
*
|
|
@@ -88,82 +19,9 @@ async function createCOSClient(cosConfig, credentials = null) {
|
|
|
88
19
|
* @param {Array<string>} urls - 需要刷新的 URL 列表
|
|
89
20
|
* @param {Object} credentials - 未使用,保留参数兼容性
|
|
90
21
|
* @returns {Promise<void>}
|
|
91
|
-
*/
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
/**
|
|
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 purgeUrl=`${API_BASE_URL}/aix/playable/api/cdn/purge`;const postData=JSON.stringify({urls,area});return new Promise((resolve,reject)=>{const req=http.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()})}/**
|
|
167
25
|
* 上传单个文件到 COS
|
|
168
26
|
*
|
|
169
27
|
* @param {Object} cosConfig - COS 配置
|
|
@@ -171,58 +29,12 @@ async function purgeCDNCache(cosConfig, urls, credentials = null) {
|
|
|
171
29
|
* @param {string} cosKey - COS 对象键(远程路径)
|
|
172
30
|
* @param {Object} credentials - 可选的凭证对象
|
|
173
31
|
* @returns {Promise<string>} - 上传成功返回访问链接
|
|
174
|
-
*/
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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
|
-
/**
|
|
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)}})})}/**
|
|
226
38
|
* 上传目录下的所有文件到 COS
|
|
227
39
|
*
|
|
228
40
|
* @param {Object} cosConfig - COS 配置
|
|
@@ -230,64 +42,8 @@ async function uploadFileToCOS(cosConfig, filePath, cosKey, credentials = null)
|
|
|
230
42
|
* @param {string} cosPrefix - COS 路径前缀
|
|
231
43
|
* @param {Object} credentials - 可选的凭证对象
|
|
232
44
|
* @returns {Promise<Array>} - 上传成功返回文件列表
|
|
233
|
-
*/
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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
|
-
};
|
|
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};
|
package/core/index.js
CHANGED
|
@@ -1,76 +1,7 @@
|
|
|
1
|
-
const
|
|
2
|
-
const
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const { runBuild, makeWebpackBuildConfig } = require('./webpack.build.js');
|
|
9
|
-
const { runDev, makeWebpackDevConfig } = require('./webpack.dev.js');
|
|
10
|
-
const { runViteDev, makeViteDevConfig, json5Plugin, assetInlinePlugin, fontFamilyPlugin } = 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
|
-
// 公共 Vite 插件(供项目 vite.config.ts 直接引用)
|
|
53
|
-
exports.json5Plugin = json5Plugin;
|
|
54
|
-
exports.assetInlinePlugin = assetInlinePlugin;
|
|
55
|
-
exports.fontFamilyPlugin = fontFamilyPlugin;
|
|
56
|
-
|
|
57
|
-
// 编译前检查器导出
|
|
58
|
-
exports.runPreBuildChecks = runPreBuildChecks;
|
|
59
|
-
exports.runTrackingCheck = runTrackingCheck;
|
|
60
|
-
exports.registerChecker = registerChecker;
|
|
61
|
-
exports.toggleChecker = toggleChecker;
|
|
62
|
-
exports.listCheckers = listCheckers;
|
|
63
|
-
|
|
64
|
-
// 埋点验证器导出
|
|
65
|
-
exports.validateTracking = validateTracking;
|
|
66
|
-
exports.printValidationResult = printValidationResult;
|
|
67
|
-
exports.TRACKING_METHODS = TRACKING_METHODS;
|
|
68
|
-
exports.SDK_TO_TRACKING_MAP = SDK_TO_TRACKING_MAP;
|
|
69
|
-
|
|
70
|
-
// 字体注入导出
|
|
71
|
-
exports.FontInjectionWebpackPlugin = FontInjectionWebpackPlugin;
|
|
72
|
-
exports.FONT_REGISTRY = FONT_REGISTRY;
|
|
73
|
-
exports.getRegisteredFontKeys = getRegisteredFontKeys;
|
|
74
|
-
exports.isBuiltinFont = isBuiltinFont;
|
|
75
|
-
exports.getFontEntry = getFontEntry;
|
|
76
|
-
exports.getOrGenerateFontCSS = getOrGenerateFontCSS;
|
|
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,json5Plugin,assetInlinePlugin,fontFamilyPlugin}=require("./vite.dev.js");// 字体注入
|
|
2
|
+
const{FontInjectionWebpackPlugin}=require("./plugins/FontInjectionWebpackPlugin.js");const{FONT_REGISTRY,getRegisteredFontKeys,isBuiltin:isBuiltinFont,getFontEntry}=require("./utils/fontRegistry.js");const{getOrGenerateFontCSS}=require("./utils/fontResolver.js");// 验证器
|
|
3
|
+
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;// 公共 Vite 插件(供项目 vite.config.ts 直接引用)
|
|
4
|
+
exports.json5Plugin=json5Plugin;exports.assetInlinePlugin=assetInlinePlugin;exports.fontFamilyPlugin=fontFamilyPlugin;// 编译前检查器导出
|
|
5
|
+
exports.runPreBuildChecks=runPreBuildChecks;exports.runTrackingCheck=runTrackingCheck;exports.registerChecker=registerChecker;exports.toggleChecker=toggleChecker;exports.listCheckers=listCheckers;// 埋点验证器导出
|
|
6
|
+
exports.validateTracking=validateTracking;exports.printValidationResult=printValidationResult;exports.TRACKING_METHODS=TRACKING_METHODS;exports.SDK_TO_TRACKING_MAP=SDK_TO_TRACKING_MAP;// 字体注入导出
|
|
7
|
+
exports.FontInjectionWebpackPlugin=FontInjectionWebpackPlugin;exports.FONT_REGISTRY=FONT_REGISTRY;exports.getRegisteredFontKeys=getRegisteredFontKeys;exports.isBuiltinFont=isBuiltinFont;exports.getFontEntry=getFontEntry;exports.getOrGenerateFontCSS=getOrGenerateFontCSS;
|
|
@@ -1,11 +1,4 @@
|
|
|
1
|
-
const path = require(
|
|
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
|
-
/**
|
|
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);/**
|
|
9
2
|
* A utility function that allows mapping the value for a given key in an object
|
|
10
3
|
* [in place](https://en.wikipedia.org/wiki/In-place_algorithm).
|
|
11
4
|
*
|
|
@@ -20,72 +13,33 @@ const isDataURI = (value) => /^data:/i.test(value);
|
|
|
20
13
|
* @param {(value: unknown) => Promise<any>} callback
|
|
21
14
|
* The callback that either returns the mapped value, or returns a Promise
|
|
22
15
|
* that resolves to the new value.
|
|
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
|
-
/**
|
|
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)}}}}/**
|
|
41
17
|
* @typedef {import(".").GLTFLoaderDefinition} GLTFLoader
|
|
42
18
|
* @this {ThisParameterType<GLTFLoader>}
|
|
43
19
|
* @type {GLTFLoader}
|
|
44
|
-
*/
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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;
|
|
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;
|