@plugin-light/shared 1.0.3 → 1.0.14

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 (45) hide show
  1. package/lib/cdn/index.d.ts +1 -0
  2. package/lib/css/index.d.ts +1 -1
  3. package/lib/email/index.d.ts +1 -0
  4. package/lib/email/watch-unread.d.ts +103 -0
  5. package/lib/font-project/config.d.ts +15 -0
  6. package/lib/font-project/font-project.d.ts +75 -0
  7. package/lib/font-project/index.d.ts +2 -0
  8. package/lib/icon-project/config.d.ts +23 -0
  9. package/lib/icon-project/icon-project.d.ts +83 -0
  10. package/lib/icon-project/index.d.ts +2 -0
  11. package/lib/image/config.d.ts +23 -0
  12. package/lib/image/image.d.ts +67 -0
  13. package/lib/image/index.d.ts +3 -0
  14. package/lib/image/upload-core.d.ts +83 -0
  15. package/lib/index.d.ts +34 -20
  16. package/lib/index.js +2069 -342
  17. package/lib/index.mjs +2506 -0
  18. package/lib/landun/cos-sync-to-git.d.ts +12 -0
  19. package/lib/landun/index.d.ts +2 -0
  20. package/lib/landun/start.d.ts +42 -0
  21. package/lib/mcp/auth.d.ts +24 -0
  22. package/lib/mcp/http.d.ts +34 -0
  23. package/lib/mcp/index.d.ts +3 -0
  24. package/lib/mcp/mcp.d.ts +27 -0
  25. package/lib/mp-ci/config.d.ts +11 -0
  26. package/lib/mp-ci/helper.d.ts +12 -0
  27. package/lib/mp-ci/index.d.ts +5 -0
  28. package/lib/mp-ci/mp-ci.d.ts +73 -0
  29. package/lib/mp-ci/record.d.ts +39 -0
  30. package/lib/mp-ci/start-from-mcp.d.ts +42 -0
  31. package/lib/npm-publish/index.d.ts +1 -0
  32. package/lib/pipeline/index.d.ts +1 -0
  33. package/lib/pipeline/pipeline.d.ts +4 -0
  34. package/lib/pipeline-npm-publish/core.d.ts +53 -0
  35. package/lib/pipeline-npm-publish/helper.d.ts +7 -0
  36. package/lib/pipeline-npm-publish/index.d.ts +2 -0
  37. package/lib/repo/index.d.ts +1 -0
  38. package/lib/repo/repo.d.ts +7 -0
  39. package/lib/tinypng/index.d.ts +1 -0
  40. package/lib/tinypng/tinypng.d.ts +18 -0
  41. package/lib/white-user/config.d.ts +53 -0
  42. package/lib/white-user/cron.d.ts +15 -0
  43. package/lib/white-user/index.d.ts +3 -0
  44. package/lib/white-user/update-cos.d.ts +47 -0
  45. package/package.json +25 -3
package/lib/index.js CHANGED
@@ -2,12 +2,19 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
+ var _const = require('@plugin-light/const');
5
6
  var fs = require('fs');
6
7
  var path = require('path');
7
8
  var tComm = require('t-comm');
8
9
  var path$1 = require('node:path');
10
+ var imapflow = require('imapflow');
11
+ var mailparser = require('mailparser');
12
+ var TurndownService = require('turndown');
13
+ var axios = require('axios');
9
14
  var loaderUtils = require('loader-utils');
10
15
  var fs$1 = require('node:fs');
16
+ var tinify = require('tinify');
17
+ var croner = require('croner');
11
18
 
12
19
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
13
20
 
@@ -32,7 +39,10 @@ function _interopNamespace(e) {
32
39
  var fs__namespace = /*#__PURE__*/_interopNamespace(fs);
33
40
  var path__namespace = /*#__PURE__*/_interopNamespace(path);
34
41
  var path__default = /*#__PURE__*/_interopDefaultLegacy(path$1);
42
+ var TurndownService__default = /*#__PURE__*/_interopDefaultLegacy(TurndownService);
43
+ var axios__default = /*#__PURE__*/_interopDefaultLegacy(axios);
35
44
  var fs__default = /*#__PURE__*/_interopDefaultLegacy(fs$1);
45
+ var tinify__default = /*#__PURE__*/_interopDefaultLegacy(tinify);
36
46
 
37
47
  function isInBlackList(filePath, blackList) {
38
48
  if (!blackList) {
@@ -156,81 +166,6 @@ const EXTERNAL_LINK_MAP = {
156
166
  UNI_SIMPLE_ROUTER: UNI_SIMPLE_ROUTER_SCRIPT_LINK,
157
167
  };
158
168
 
159
- /* eslint-disable @typescript-eslint/no-require-imports */
160
- const ROOT_NAME = 'MAIN';
161
- function saveJsonToLog(content, file, needLog = true) {
162
- if (!needLog)
163
- return;
164
- createLogDir();
165
- const filePath = `./log/${file}`;
166
- let beforeContent = [];
167
- let newContent = [{
168
- logTime: tComm.timeStampFormat(Date.now(), 'yyyy-MM-dd hh:mm:ss'),
169
- data: content,
170
- }];
171
- if (fs__namespace.existsSync(filePath)) {
172
- try {
173
- beforeContent = tComm.readFileSync(filePath, true).logList || [];
174
- }
175
- catch (err) {
176
- beforeContent = [];
177
- }
178
- }
179
- if (beforeContent && Array.isArray(beforeContent)) {
180
- newContent.push(...beforeContent);
181
- }
182
- newContent = newContent.slice(0, 10);
183
- try {
184
- fs__namespace.writeFile(filePath, JSON.stringify({ logList: newContent }, null, 2), {
185
- encoding: 'utf-8',
186
- }, () => { });
187
- }
188
- catch (err) {
189
- }
190
- }
191
- function createLogDir() {
192
- if (!fs__namespace.existsSync('./log')) {
193
- fs__namespace.mkdirSync('./log');
194
- }
195
- }
196
- const normalizePath = (path) => (tComm.isWindows() ? path.replace(/\\/g, '/') : path);
197
- function updateAssetSource(assets, key, source) {
198
- assets[key] = {
199
- source() {
200
- return source;
201
- },
202
- size() {
203
- return source.length;
204
- },
205
- };
206
- }
207
- function removeFirstSlash(key) {
208
- if (key.startsWith('/')) {
209
- return key.slice(1);
210
- }
211
- return key;
212
- }
213
- function sortStringList(list) {
214
- list.sort((a, b) => {
215
- if (a > b)
216
- return 1;
217
- if (a < b)
218
- return -1;
219
- return 0;
220
- });
221
- return list;
222
- }
223
- function parseSetDeps(deps) {
224
- return Object.keys(deps).reduce((acc, item) => {
225
- acc[item] = Array.from(deps[item]);
226
- sortStringList(acc[item]);
227
- return acc;
228
- }, {});
229
- }
230
- function getRelativePath(filePath) {
231
- return path__namespace.relative(process.cwd(), path__namespace.resolve(filePath));
232
- }
233
-
234
169
  const DEFAULT_KEYS = [
235
170
  'UNI_APP_X',
236
171
  'APP',
@@ -267,10 +202,105 @@ const DEFAULT_CONTEXT_OBJECT = DEFAULT_KEYS.reduce((acc, key) => ({
267
202
  [key]: false,
268
203
  }), {});
269
204
 
205
+ const TIP_STYLE_NAME = '@TIP_STYLE_NAME';
206
+ const DEFAULT_EXT_LIST = [
207
+ 'scss',
208
+ 'less',
209
+ ];
210
+
211
+ function getRootDir() {
212
+ return process.cwd();
213
+ }
214
+
215
+ function getAppDir() {
216
+ if (process.env.VUE_APP_DIR) {
217
+ return process.env.VUE_APP_DIR;
218
+ }
219
+ if (process.env.UNI_INPUT_DIR) {
220
+ return process.env.UNI_INPUT_DIR;
221
+ }
222
+ const dir = tComm.readEnvVariable('VUE_APP_DIR', path__namespace.join(getRootDir(), '.env.local'));
223
+ if (dir) {
224
+ return dir;
225
+ }
226
+ return '';
227
+ }
228
+ function getStyleName() {
229
+ const configPath = path__namespace.resolve(getRootDir(), 'src', getAppDir(), 'config.js');
230
+ let config = { styleName: '' };
231
+ if (fs__namespace.existsSync(configPath)) {
232
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
233
+ config = require(configPath);
234
+ }
235
+ const { styleName } = config;
236
+ return styleName;
237
+ }
238
+
239
+ function tryRemoveImport(source, removeImport = false) {
240
+ let res = source;
241
+ if (removeImport) {
242
+ res = res.replace(`src="${TIP_STYLE_NAME}"`, '').replace(`src='${TIP_STYLE_NAME}'`, '');
243
+ }
244
+ else {
245
+ res = res.replace(TIP_STYLE_NAME, '');
246
+ }
247
+ return res;
248
+ }
249
+ function findValidFile(fileName, extList) {
250
+ for (const ext of extList) {
251
+ const file = `${fileName}.${ext}`;
252
+ if (fs__namespace.existsSync(file)) {
253
+ return [ext, file];
254
+ }
255
+ }
256
+ return [];
257
+ }
258
+ function crossGameStyle({ source, options, dir, removeImport = false, }) {
259
+ if (!source.includes(TIP_STYLE_NAME)) {
260
+ return source;
261
+ }
262
+ const extList = options?.extList || DEFAULT_EXT_LIST;
263
+ let styleName = '';
264
+ // 使用 env.local 的样式 VUE_APP_DIR = module/ingame-nba,即为 nba
265
+ if (options?.styleName) {
266
+ styleName = options.styleName;
267
+ }
268
+ else if (getStyleName()) {
269
+ styleName = getStyleName();
270
+ }
271
+ if (Array.isArray(styleName)) {
272
+ if (styleName.length > 1) {
273
+ styleName = styleName.filter((item) => {
274
+ const cssAbsolutePath = `${dir}/css/${item}.scss`;
275
+ return fs__namespace.existsSync(cssAbsolutePath);
276
+ });
277
+ if (styleName.length > 1) {
278
+ const styleTags = styleName
279
+ .filter((item) => {
280
+ const cssAbsolutePath = `${dir}/css/${item}.scss`;
281
+ return removeImport ? fs__namespace.existsSync(cssAbsolutePath) : true;
282
+ })
283
+ .map(item => `.${item} {@import './css/${item}.scss';}`);
284
+ const res = tryRemoveImport(source, removeImport);
285
+ return res.replace(/<\/style>/, `</style>${['<style scoped lang="scss">', ...styleTags, '</style>'].join('')}`);
286
+ }
287
+ }
288
+ styleName = styleName[0] || '';
289
+ }
290
+ const cssPath = `./css/${styleName}`;
291
+ const cssAbsolutePath = `${dir}/css/${styleName}`;
292
+ const found = findValidFile(cssAbsolutePath, extList);
293
+ if (found.length) {
294
+ const [ext] = found;
295
+ return source.replace(TIP_STYLE_NAME, `${cssPath}.${ext}`);
296
+ }
297
+ return tryRemoveImport(source, removeImport);
298
+ }
299
+
270
300
  const scssLogger = {
271
- warn(message, options) {
301
+ warn(message) {
272
302
  // Mute "Mixed Declarations" warning
273
- if (options.deprecation && message.includes('mixed-decls')) {
303
+ if (message.includes('mixed-decls')) {
274
304
  return;
275
305
  }
276
306
  // List all other warnings
@@ -285,6 +315,216 @@ function getDeps(dir) {
285
315
  });
286
316
  }
287
317
 
318
+ /**
319
+ * 方案二:IMAP IDLE 实时监听新邮件
320
+ *
321
+ * 通过 IMAP IDLE 命令保持长连接,实时监听收件箱中的新邮件到达。
322
+ * 适用于需要实时感知新邮件的场景,如:监听邮件后自动触发业务逻辑。
323
+ *
324
+ * 依赖安装:npm install imapflow mailparser
325
+ * 类型安装:npm install -D @types/mailparser
326
+ *
327
+ * 注意事项:
328
+ * 1. IDLE 连接可能因网络问题断开,内置自动重连机制
329
+ * 2. 部分邮箱服务器对 IDLE 连接有超时限制(通常 29 分钟),imapflow 会自动处理
330
+ * 3. 生产环境建议配合进程管理工具(如 pm2)使用
331
+ */
332
+ // 初始化 HTML -> Markdown 转换器
333
+ const turndown = new TurndownService__default["default"]({
334
+ headingStyle: 'atx',
335
+ codeBlockStyle: 'fenced',
336
+ bulletListMarker: '-',
337
+ });
338
+ // ==================== 核心方法 ====================
339
+ /**
340
+ * 启动邮件监听(基于 IMAP IDLE)
341
+ *
342
+ * @example
343
+ * ```ts
344
+ * const watcher = await watchEmails({
345
+ * host: 'imap.exmail.qq.com',
346
+ * user: 'yourname@company.com',
347
+ * password: 'your-app-password',
348
+ * onNewEmail: (email) => {
349
+ * console.log('📬 收到新邮件:', email.subject);
350
+ * // 触发你的业务逻辑,比如推送到企微群
351
+ * },
352
+ * onError: (err) => {
353
+ * console.error('监听出错:', err.message);
354
+ * },
355
+ * });
356
+ *
357
+ * // 需要停止时
358
+ * // await watcher.stop();
359
+ * ```
360
+ */
361
+ async function watchEmails(options) {
362
+ const { host, port = 143, user, password, mailbox = 'INBOX', onNewEmail, onError, onConnected, onDisconnected, autoReconnect = true, reconnectInterval = 5000, maxReconnectAttempts = Infinity, debug = false, } = options;
363
+ let client = null;
364
+ let connected = false;
365
+ let stopped = false;
366
+ let reconnectAttempts = 0;
367
+ let reconnectTimer = null;
368
+ /**
369
+ * 创建 IMAP 客户端并开始监听
370
+ */
371
+ async function connect() {
372
+ if (stopped)
373
+ return;
374
+ const secure = options.secure ?? (port === 993);
375
+ client = new imapflow.ImapFlow({
376
+ host,
377
+ port,
378
+ secure,
379
+ auth: { user, pass: password },
380
+ logger: debug ? console : false,
381
+ tls: {
382
+ rejectUnauthorized: false, // 内网邮箱服务器可能使用自签名证书
383
+ },
384
+ });
385
+ try {
386
+ await client.connect();
387
+ connected = true;
388
+ reconnectAttempts = 0;
389
+ console.log(`[watchEmails] 已连接到 ${host}:${port},用户: ${user}`);
390
+ onConnected?.();
391
+ // 打开邮箱
392
+ await client.mailboxOpen(mailbox);
393
+ console.log(`[watchEmails] 已打开 ${mailbox},开始监听新邮件...`);
394
+ // 监听新邮件事件
395
+ client.on('exists', (data) => {
396
+ console.log(`[watchEmails] 📬 检测到新邮件! 当前共 ${data.count} 封 (之前 ${data.prevCount} 封)`);
397
+ // 计算新增邮件数量
398
+ const newCount = data.count - data.prevCount;
399
+ if (newCount <= 0)
400
+ return;
401
+ // 使用 void 包装异步操作,避免 Promise 返回到 void 回调中
402
+ void (async () => {
403
+ try {
404
+ // 获取最新的邮件(按 seq 号获取)
405
+ const startSeq = data.prevCount + 1;
406
+ const range = newCount === 1 ? `${startSeq}` : `${startSeq}:${data.count}`;
407
+ for await (const msg of client.fetch(range, { source: true })) {
408
+ try {
409
+ if (!msg.source) {
410
+ console.warn(`[watchEmails] 邮件 UID=${msg.uid} 的 source 为空,跳过`);
411
+ continue;
412
+ }
413
+ const parsed = await mailparser.simpleParser(msg.source);
414
+ // 将 HTML 转换为 Markdown,如果没有 HTML 则使用纯文本
415
+ let markdown = '';
416
+ if (parsed.html) {
417
+ markdown = turndown.turndown(parsed.html);
418
+ }
419
+ else if (parsed.text) {
420
+ markdown = parsed.text;
421
+ }
422
+ const email = {
423
+ uid: msg.uid,
424
+ subject: parsed.subject || '(无主题)',
425
+ from: parsed.from?.text || '',
426
+ to: Array.isArray(parsed.to) ? parsed.to.map(addr => addr.text).join(', ') : (parsed.to?.text || ''),
427
+ date: parsed.date,
428
+ text: parsed.text,
429
+ html: parsed.html || false,
430
+ markdown,
431
+ attachments: (parsed.attachments || []).map(att => ({
432
+ filename: att.filename || '未命名',
433
+ contentType: att.contentType || 'application/octet-stream',
434
+ size: att.size,
435
+ })),
436
+ };
437
+ // 调用用户回调(支持异步)
438
+ await onNewEmail(email);
439
+ }
440
+ catch (parseErr) {
441
+ console.error('[watchEmails] 解析邮件失败:', parseErr);
442
+ }
443
+ }
444
+ }
445
+ catch (fetchErr) {
446
+ console.error('[watchEmails] 获取新邮件失败:', fetchErr);
447
+ onError?.(fetchErr);
448
+ }
449
+ })();
450
+ });
451
+ // 监听连接关闭事件(用于自动重连)
452
+ client.on('close', () => {
453
+ connected = false;
454
+ console.log('[watchEmails] 连接已关闭');
455
+ onDisconnected?.();
456
+ if (!stopped && autoReconnect) {
457
+ scheduleReconnect();
458
+ }
459
+ });
460
+ client.on('error', (err) => {
461
+ console.error('[watchEmails] 连接错误:', err.message);
462
+ onError?.(err);
463
+ });
464
+ }
465
+ catch (error) {
466
+ connected = false;
467
+ console.error('[watchEmails] 连接失败:', error.message);
468
+ onError?.(error);
469
+ if (!stopped && autoReconnect) {
470
+ scheduleReconnect();
471
+ }
472
+ }
473
+ }
474
+ /**
475
+ * 安排自动重连
476
+ */
477
+ function scheduleReconnect() {
478
+ if (stopped)
479
+ return;
480
+ reconnectAttempts += 1;
481
+ if (reconnectAttempts > maxReconnectAttempts) {
482
+ console.error(`[watchEmails] 已达最大重连次数 (${maxReconnectAttempts}),停止重连`);
483
+ return;
484
+ }
485
+ const delay = Math.min(reconnectInterval * reconnectAttempts, 60000); // 最大 60s
486
+ console.log(`[watchEmails] 将在 ${delay}ms 后重连 (第 ${reconnectAttempts} 次)...`);
487
+ reconnectTimer = setTimeout(() => {
488
+ reconnectTimer = null;
489
+ void (async () => {
490
+ try {
491
+ await connect();
492
+ }
493
+ catch (err) {
494
+ console.error('[watchEmails] 重连失败:', err);
495
+ }
496
+ })();
497
+ }, delay);
498
+ }
499
+ /**
500
+ * 停止监听
501
+ */
502
+ async function stop() {
503
+ stopped = true;
504
+ if (reconnectTimer) {
505
+ clearTimeout(reconnectTimer);
506
+ reconnectTimer = null;
507
+ }
508
+ if (client) {
509
+ try {
510
+ await client.logout();
511
+ }
512
+ catch {
513
+ // 忽略断开时的错误
514
+ }
515
+ client = null;
516
+ }
517
+ connected = false;
518
+ console.log('[watchEmails] 监听已停止');
519
+ }
520
+ // 启动初始连接
521
+ await connect();
522
+ return {
523
+ stop,
524
+ isConnected: () => connected,
525
+ };
526
+ }
527
+
288
528
  function getImportOrderRule() {
289
529
  return {
290
530
  'import/order': [
@@ -355,251 +595,298 @@ function findDependencies(content) {
355
595
  return sourceList;
356
596
  }
357
597
 
358
- function checkH5() {
359
- return process.env.VUE_APP_PLATFORM === 'h5';
360
- }
598
+ /**
599
+ * 字体项目 CDN 地址
600
+ */
601
+ const FONT_PROJECT_CDN = 'https://image-1251917893.cos.ap-guangzhou.myqcloud.com/pmd-font/font-project.json';
602
+ /**
603
+ * 字体项目 COS 基础配置
604
+ */
605
+ const FONT_PROJECT_COS_BASE = {
606
+ bucket: 'image-1251917893',
607
+ region: 'ap-guangzhou',
608
+ };
609
+ /**
610
+ * 字体项目 Webhook URL
611
+ */
612
+ const FONT_PROJECT_WEBHOOK_URL = '9f673531-788b-4780-be72-93b16a62e3eb';
361
613
 
362
- const LOADER_PROD = 'loader.prod.js';
363
- function getLoaderFile(dir = '', isProd = false) {
364
- if (isProd) {
365
- return path__default["default"].resolve(dir, LOADER_PROD);
366
- }
367
- return path__default["default"].resolve(dir, 'loader.js');
614
+ /**
615
+ * 获取上传图片的文件路径
616
+ * @param cdn - CDN 域名
617
+ * @returns 格式化的文件路径,包含年月信息
618
+ */
619
+ function getUploadImageFilePath(cdn) {
620
+ const { cosPrefix } = getUploadCosConfig(cdn);
621
+ const date = new Date();
622
+ const year = date.getFullYear();
623
+ const month = date.getMonth() + 1;
624
+ return `${cosPrefix}/${year}/${month}`;
368
625
  }
369
- function getLoaderProdFile(dir = '') {
370
- return path__default["default"].resolve(dir, LOADER_PROD);
626
+ /**
627
+ * 从 COS 基础域名中提取 region
628
+ * @param cosBaseDomain - COS 基础域名
629
+ * @returns region 字符串
630
+ */
631
+ function getCosRegion(cosBaseDomain = '') {
632
+ return cosBaseDomain.split('.')[1];
371
633
  }
372
-
373
- const LOG_KEY = 'LOADER_LOGS';
374
- function saveLoaderLog() {
375
- const loaderLogs = global[LOG_KEY];
376
- if (!loaderLogs)
377
- return;
378
- Object.keys(loaderLogs).forEach((file) => {
379
- saveJsonToLog(loaderLogs[file], file);
380
- });
634
+ /**
635
+ * 获取上传 COS 配置
636
+ * @param cdn - CDN 域名
637
+ * @returns COS 配置对象,如果 CDN 无效则返回空对象
638
+ */
639
+ function getUploadCosConfig(cdn) {
640
+ if (!cdn) {
641
+ return {};
642
+ }
643
+ const cdnMap = _const.getCdnList().reduce((acc, item) => ({
644
+ ...acc,
645
+ [item.cdnDomain]: item,
646
+ }), {});
647
+ const cur = cdnMap[cdn];
648
+ if (!cur) {
649
+ return {};
650
+ }
651
+ const { secretId, secretKey, cosBaseDomain, cosBucketName, cosAppId } = cur;
652
+ const region = getCosRegion(cosBaseDomain);
653
+ return {
654
+ secretId,
655
+ secretKey,
656
+ bucket: `${cosBucketName}-${cosAppId}`,
657
+ region,
658
+ cosPrefix: 'next-svr/images',
659
+ maxSize: 1 * 1024 * 1024, // 1M
660
+ };
381
661
  }
382
- function recordLoaderLog(file, content) {
383
- if (!global[LOG_KEY]) {
384
- global[LOG_KEY] = {};
662
+ /**
663
+ * 解析图片记录,将 COS URL 转换为 CDN URL
664
+ * @param image - 图片记录对象
665
+ * @returns 解析后的图片记录,包含 parsedUrl
666
+ */
667
+ const parseImage = (image) => ({
668
+ ...image,
669
+ parsedUrl: toCdnUrl(image.url),
670
+ });
671
+ const cdnMap = new Map(_const.getCdnList().map(item => [item.cosDomain, item.cdnDomain]));
672
+ /**
673
+ * 从 URL 中提取域名
674
+ * @param url - 输入的 URL
675
+ * @returns 域名字符串,如果无法提取则返回空字符串
676
+ */
677
+ function extractDomain(url) {
678
+ if (!url) {
679
+ return '';
385
680
  }
386
- if (!global[LOG_KEY][file]) {
387
- global[LOG_KEY][file] = [];
681
+ // 处理 https:// 或 http:// 开头的链接
682
+ const protocolMatch = url.match(/^https?:\/\/([^/]+)/);
683
+ if (protocolMatch) {
684
+ return protocolMatch[1];
388
685
  }
389
- global[LOG_KEY][file].push(content);
686
+ // 处理 // 开头的协议相对链接
687
+ const relativeMatch = url.match(/^\/\/([^/]+)/);
688
+ if (relativeMatch) {
689
+ return relativeMatch[1];
690
+ }
691
+ // 处理纯域名格式(不带协议前缀),如 a.cos.ap-guangzhou.myqcloud.com 或 a.cos.ap-guangzhou.myqcloud.com/path
692
+ // 域名特征:包含至少一个点,且第一段不以 / 开头
693
+ const pureDomainMatch = url.match(/^([a-zA-Z0-9][\w.-]*\.[a-zA-Z]{2,}(?:\.[a-zA-Z]{2,})*)/);
694
+ if (pureDomainMatch) {
695
+ return pureDomainMatch[1];
696
+ }
697
+ return '';
698
+ }
699
+ /**
700
+ * 将 COS URL 转换为 CDN URL
701
+ * 支持 https://、http://、//、纯域名 等多种链接格式
702
+ * @param inUrl - 输入的 URL
703
+ * @returns 转换后的 CDN URL
704
+ * @example
705
+ * toCdnUrl('https://xxx.cos.ap-guangzhou.myqcloud.com/path/to/file')
706
+ * toCdnUrl('//xxx.cos.ap-guangzhou.myqcloud.com/path/to/file')
707
+ * toCdnUrl('http://xxx.cos.ap-guangzhou.myqcloud.com/path/to/file')
708
+ * toCdnUrl('xxx.cos.ap-guangzhou.myqcloud.com/path/to/file')
709
+ * toCdnUrl('xxx.cos.ap-guangzhou.myqcloud.com')
710
+ */
711
+ function toCdnUrl(inUrl) {
712
+ if (!inUrl) {
713
+ return inUrl;
714
+ }
715
+ const domain = extractDomain(inUrl);
716
+ if (!domain) {
717
+ return inUrl;
718
+ }
719
+ if (cdnMap.has(domain)) {
720
+ return inUrl.replace(domain, cdnMap.get(domain));
721
+ }
722
+ return inUrl;
390
723
  }
724
+ /**
725
+ * 将 COS URL 转换为 CDN URL
726
+ * @deprecated 请使用 toCdnUrl 代替
727
+ * @param inUrl - 输入的 URL
728
+ * @returns 转换后的 CDN URL
729
+ */
730
+ const getOneCdnUrl = toCdnUrl;
731
+ /**
732
+ * 中国大陆 CDN 列表
733
+ */
734
+ const MAINLAND_CDN_LIST = [
735
+ 'image-1251917893.file.myqcloud.com',
736
+ ];
737
+ /**
738
+ * 根据 CDN 获取推送 URL 缓存区域
739
+ * @param cdn - CDN 域名
740
+ * @returns 缓存区域,中国大陆返回 'mainland',否则返回 'overseas'
741
+ */
742
+ const getPushUrlCacheArea = (cdn) => (MAINLAND_CDN_LIST.includes(cdn) ? 'mainland' : 'overseas');
391
743
 
392
- function shouldUseLoader(defaultPlatforms = []) {
393
- const options = loaderUtils.getOptions(this) || {};
394
- const { platforms = defaultPlatforms } = options;
395
- const platform = process.env.UNI_PLATFORM || '';
396
- if (platforms === ALL_PLATFORM
397
- || platforms.indexOf(ALL_PLATFORM) > -1) {
398
- return true;
744
+ /**
745
+ * 文件上传核心处理函数
746
+ * @param options - 上传选项
747
+ * @returns 上传结果
748
+ */
749
+ async function uploadFilesCore({ file, originName, useOriginFilename, newFileKey, staffName, parsedDir, uploadFile, cdn, cos, uploadCallBack, fromMcp = false, mcpDB, mcpName, mcpVersion, imageDB, operationTool, }) {
750
+ const uploadError = await uploadCallBack();
751
+ if (uploadError) {
752
+ return uploadError;
399
753
  }
400
- return platforms.includes(platform);
754
+ const url = `https://${cos.bucket}.cos.${cos.region}.myqcloud.com/${newFileKey}`;
755
+ await imageDB.insert({
756
+ fileKey: newFileKey,
757
+ url,
758
+ size: file.size,
759
+ mimetype: file.mimetype,
760
+ originalname: originName,
761
+ createTime: Date.now(),
762
+ staffName,
763
+ dir: parsedDir,
764
+ uploadFile,
765
+ cdn,
766
+ });
767
+ try {
768
+ tComm.purgeUrlCache({
769
+ secretId: cos.secretId,
770
+ secretKey: cos.secretKey,
771
+ urls: [toCdnUrl(url)],
772
+ area: getPushUrlCacheArea(cdn),
773
+ })
774
+ .then((res) => {
775
+ console.log('[purgeUrlCache] res: ', res?.data);
776
+ })
777
+ .catch((err) => {
778
+ console.log('[purgeUrlCache] err: ', err);
779
+ });
780
+ }
781
+ catch (err) {
782
+ // 忽略错误
783
+ }
784
+ const recordList = [
785
+ `原始名称:${originName}`,
786
+ `目录:${parsedDir}`,
787
+ `是否使用原始文件名:${useOriginFilename ? '是' : '否'}`,
788
+ `CDN:${cdn}`,
789
+ ];
790
+ await operationTool.addByMapParam({
791
+ staffName,
792
+ type: uploadFile ? 'UPLOAD_FILE' : 'UPLOAD_IMAGE',
793
+ desc: [
794
+ ...recordList,
795
+ `MCP:${fromMcp ? '是' : '否'}`,
796
+ ].join(','),
797
+ status: 'success',
798
+ extra: {
799
+ fileKey: newFileKey,
800
+ url,
801
+ size: file.size,
802
+ mimetype: file.mimetype,
803
+ originalname: originName,
804
+ dir: parsedDir,
805
+ useOriginFilename,
806
+ uploadFile,
807
+ cdn,
808
+ fromMcp,
809
+ },
810
+ });
811
+ if (typeof mcpDB?.add === 'function') {
812
+ const mcpContent = recordList.join(',');
813
+ await mcpDB.add(mcpName || 'upload-mcp', staffName, mcpContent, mcpVersion);
814
+ }
815
+ return { r: 0, msg: '上传成功', url, cdnUrl: toCdnUrl(url) };
401
816
  }
402
817
 
403
- function getWxmlAndWxssPostfix() {
404
- const map = Object.keys(PLATFORM_MAP).reduce((acc, item) => {
405
- acc[PLATFORM_MAP[item]] = item;
406
- return acc;
407
- }, {});
408
- const key = map[process.env.UNI_PLATFORM || ''];
409
- return [
410
- HTML_MAP[key],
411
- CSS_MAP[key],
412
- ];
413
- }
414
-
415
- function getProjectName() {
416
- let result = '';
417
- try {
418
- const json = tComm.readFileSync('package.json', true) || {};
419
- result = json.name || '';
420
- }
421
- catch (err) { }
422
- return result;
423
- }
424
- function getSubProjectName() {
425
- const name = process.env.VUE_APP_DIR?.split('/')?.[1] || '';
426
- return name;
427
- }
428
-
429
- function updateManifestCore(_a) {
430
- var path = _a.path,
431
- value = _a.value,
432
- manifest = _a.manifest;
433
- var arr = path.split('.');
434
- var len = arr.length;
435
- var lastItem = arr[len - 1];
436
- var i = 0;
437
- var manifestArr = manifest.split(/\n/);
438
- for (var index = 0; index < manifestArr.length; index++) {
439
- var item = manifestArr[index];
440
- if (new RegExp("\"".concat(arr[i], "\"")).test(item)) i = i + 1;
441
- if (i === len) {
442
- var hasComma = /,/.test(item);
443
- manifestArr[index] = item.replace(new RegExp("\"".concat(lastItem, "\"[\\s\\S]*:[\\s\\S]*")), "\"".concat(lastItem, "\": ").concat(value).concat(hasComma ? ',' : ''));
444
- break;
445
- }
446
- }
447
- return manifestArr.join('\n');
448
- }
449
-
450
- // 读取 manifest.json ,修改后重新写入
451
- const manifestPath = `${process.env.UNI_INPUT_DIR}/manifest.json`;
452
- let originManifest = '';
453
- try {
454
- originManifest = fs__namespace.readFileSync(manifestPath, { encoding: 'utf-8' });
455
- }
456
- catch (err) {
457
- }
458
- let Manifest = originManifest;
459
- function updateManifest(path, value) {
460
- Manifest = updateManifestCore({
461
- path,
462
- value,
463
- manifest: Manifest,
464
- });
465
- fs__namespace.writeFileSync(manifestPath, Manifest, {
466
- flag: 'w',
467
- });
468
- }
469
- function revertManifest() {
470
- fs__namespace.writeFileSync(manifestPath, originManifest, {
471
- flag: 'w',
472
- });
473
- }
818
+ /**
819
+ * COS 同步到 Git 的远程流水线 ID
820
+ */
821
+ const COS_TO_GIT_REMOTE_PIPELINE_ID = 'df18b1dc954e49c98271c531a0f40812';
474
822
 
475
- function getRootDir() {
476
- return process.cwd();
823
+ /**
824
+ * 获取所有字体项目配置
825
+ * @returns 包含字体项目数据的 Promise
826
+ */
827
+ function genFontProjects() {
828
+ return axios__default["default"].get(`${FONT_PROJECT_CDN}?v=${Date.now()}`);
477
829
  }
478
-
479
- function getSubProjectRoot({ root, appDir, }) {
480
- let subProjectRoot = `${path__default["default"].resolve(root, `./src/${appDir}`)}/`;
481
- if (!appDir) {
482
- subProjectRoot = path__default["default"].resolve(root, './src/');
830
+ /**
831
+ * 创建或更新字体项目
832
+ * @param options - 创建/更新选项
833
+ * @returns 操作结果
834
+ */
835
+ async function createFontProjectOrUpdate({ projectNameZN = '', projectName = '', fontFileName = '', validationText = '', cdn = '', cos, staffName = '', isCreate, tags = [], }) {
836
+ const { data } = await genFontProjects();
837
+ if (data[projectName] && isCreate) {
838
+ return {
839
+ error: 'project already exists',
840
+ };
483
841
  }
484
- return subProjectRoot;
485
- }
486
- function getSubProjectConfig(subProjectRoot) {
487
- let res = {};
842
+ const createInfo = isCreate || !data[projectName]?.creator ? {
843
+ creator: staffName,
844
+ createTime: Date.now(),
845
+ } : {};
846
+ data[projectName] = {
847
+ ...data[projectName],
848
+ projectNameZN,
849
+ projectName,
850
+ fontFileName,
851
+ validationText,
852
+ cdn,
853
+ tags,
854
+ ...createInfo,
855
+ updateTime: Date.now(),
856
+ };
857
+ const key = FONT_PROJECT_CDN.replace(/^https:\/\/[^/]+\//, '');
858
+ console.log('[createFontProjectOrUpdate] data: ', { data, key });
488
859
  try {
489
- // eslint-disable-next-line @typescript-eslint/no-require-imports
490
- res = require(path__default["default"].resolve(subProjectRoot, 'config.js'));
491
- }
492
- catch (err) { }
493
- return res;
494
- }
495
-
496
- const getPlatform = () => process.env.UNI_PLATFORM || '';
497
- const getUtsPlatform = () => process.env.UNI_UTS_PLATFORM || '';
498
- const getAppPlatform = () => process.env.UNI_APP_PLATFORM || '';
499
- const isH5 = () => getPlatform() === 'h5';
500
- const isApp = () => getPlatform() === 'app';
501
- const isAppAndroid = () => getAppPlatform() === 'android' || getUtsPlatform() === 'app-android';
502
- const isAppIOS = () => getAppPlatform() === 'ios' || getUtsPlatform() === 'app-ios';
503
- const isMp = () => /^mp-/i.test(getPlatform());
504
- const isMpWeixin = () => getPlatform() === 'mp-weixin';
505
- const isMpAlipay = () => getPlatform() === 'mp-alipay';
506
- const isMpBaidu = () => getPlatform() === 'mp-baidu';
507
- const isMpKuaishou = () => getPlatform() === 'mp-kuaishou';
508
- const isMpQQ = () => getPlatform() === 'mp-qq';
509
- const isMpToutiao = () => getPlatform() === 'mp-toutiao';
510
- const isQuickapp = () => /^quickapp-webview/i.test(getPlatform());
511
- const isQuickappUnion = () => getPlatform() === 'quickapp-webview-union';
512
- const isQuickappHuawei = () => getPlatform() === 'quickapp-webview-huawei';
513
-
514
- const TIP_STYLE_NAME = '@TIP_STYLE_NAME';
515
- const DEFAULT_EXT_LIST = [
516
- 'scss',
517
- 'less',
518
- ];
519
-
520
- function getAppDir() {
521
- if (process.env.VUE_APP_DIR) {
522
- return process.env.VUE_APP_DIR;
523
- }
524
- if (process.env.UNI_INPUT_DIR) {
525
- return process.env.UNI_INPUT_DIR;
526
- }
527
- const dir = tComm.readEnvVariable('VUE_APP_DIR', path__namespace.join(getRootDir(), '.env.local'));
528
- if (dir) {
529
- return dir;
530
- }
531
- return '';
532
- }
533
- function getStyleName() {
534
- const configPath = path__namespace.resolve(getRootDir(), 'src', getAppDir(), 'config.js');
535
- let config = { styleName: '' };
536
- if (fs__namespace.existsSync(configPath)) {
537
- // eslint-disable-next-line @typescript-eslint/no-require-imports
538
- config = require(configPath);
539
- }
540
- const { styleName } = config;
541
- return styleName;
542
- }
543
-
544
- function tryRemoveImport(source, removeImport = false) {
545
- let res = source;
546
- if (removeImport) {
547
- res = res.replace(`src="${TIP_STYLE_NAME}"`, '').replace(`src='${TIP_STYLE_NAME}'`, '');
548
- }
549
- else {
550
- res = res.replace(TIP_STYLE_NAME, '');
551
- }
552
- return res;
553
- }
554
- function findValidFile(fileName, extList) {
555
- for (const ext of extList) {
556
- const file = `${fileName}.${ext}`;
557
- if (fs__namespace.existsSync(file)) {
558
- return [ext, file];
559
- }
560
- }
561
- return [];
562
- }
563
- function crossGameStyle({ source, options, dir, removeImport = false, }) {
564
- if (!source.includes(TIP_STYLE_NAME)) {
565
- return source;
566
- }
567
- const extList = options?.extList || DEFAULT_EXT_LIST;
568
- let styleName = '';
569
- // 使用 env.local 的样式 VUE_APP_DIR = module/ingame-nba,即为 nba
570
- if (options?.styleName) {
571
- styleName = options.styleName;
572
- }
573
- else if (getStyleName()) {
574
- styleName = getStyleName();
575
- }
576
- if (Array.isArray(styleName)) {
577
- if (styleName.length > 1) {
578
- styleName = styleName.filter((item) => {
579
- const cssAbsolutePath = `${dir}/css/${item}.scss`;
580
- return fs__namespace.existsSync(cssAbsolutePath);
581
- });
582
- if (styleName.length > 1) {
583
- const styleTags = styleName
584
- .filter((item) => {
585
- const cssAbsolutePath = `${dir}/css/${item}.scss`;
586
- return removeImport ? fs__namespace.existsSync(cssAbsolutePath) : true;
587
- })
588
- .map(item => `.${item} {@import './css/${item}.scss';}`);
589
- const res = tryRemoveImport(source, removeImport);
590
- return res.replace(/<\/style>/, `</style>${['<style scoped lang="scss">', ...styleTags, '</style>'].join('')}`);
591
- }
592
- }
593
- styleName = styleName[0] || '';
860
+ await tComm.uploadCOSStreamFile({
861
+ secretId: cos.secretId,
862
+ secretKey: cos.secretKey,
863
+ bucket: cos.bucket,
864
+ region: cos.region,
865
+ key,
866
+ file: {
867
+ buffer: Buffer.from(JSON.stringify(data, null, 2)),
868
+ },
869
+ });
870
+ await tComm.purgeUrlCache({
871
+ secretId: cos.secretId,
872
+ secretKey: cos.secretKey,
873
+ urls: [toCdnUrl(FONT_PROJECT_CDN)],
874
+ })
875
+ .then((res) => {
876
+ console.log('[purgeUrlCache] res: ', res?.data);
877
+ })
878
+ .catch((err) => {
879
+ console.log('[purgeUrlCache] err: ', err);
880
+ });
594
881
  }
595
- const cssPath = `./css/${styleName}`;
596
- const cssAbsolutePath = `${dir}/css/${styleName}`;
597
- const found = findValidFile(cssAbsolutePath, extList);
598
- if (found.length) {
599
- const [ext] = found;
600
- return source.replace(TIP_STYLE_NAME, `${cssPath}.${ext}`);
882
+ catch (e) {
883
+ return {
884
+ error: e.message,
885
+ };
601
886
  }
602
- return tryRemoveImport(source, removeImport);
887
+ return {
888
+ isCreate,
889
+ };
603
890
  }
604
891
 
605
892
  function parseQuote(str = '') {
@@ -741,46 +1028,802 @@ try {
741
1028
  `;
742
1029
  }
743
1030
 
744
- const BASE_SCSS = 'base.scss';
745
- function getStyleList(dir) {
746
- const cssList = fs__namespace.readdirSync(dir);
747
- const filtered = cssList
748
- .filter(item => item.endsWith('scss') && !item.startsWith(BASE_SCSS))
749
- .map(item => item.replace(/\.scss$/, ''));
750
- return filtered;
751
- }
752
- function genInjectContent({ styleList, componentName, topElement, dir = '', }) {
753
- const styleStr = styleList.map((item) => `
754
- &--type-${item} {
755
- @import './${dir}${item}.scss';
756
- }`).join('\n');
757
- return `
758
- ${topElement}.${componentName} {
759
- ${styleStr}
760
- }
761
- `;
762
- }
763
- function getComponentName(dir) {
764
- const tPath = tComm.normalizePath(dir);
765
- const reg = /\/([^/]+)\/css/;
766
- const match = tPath.match(reg);
767
- return match?.[1] || '';
1031
+ function checkH5() {
1032
+ return process.env.VUE_APP_PLATFORM === 'h5';
768
1033
  }
769
1034
 
770
- function findNodeModuleFile({ name, target, filePath, root, }) {
771
- const iRoot = root ?? process.cwd();
772
- const NODE_MODULES = 'node_modules';
773
- const PNPM = `${NODE_MODULES}/.pnpm`;
774
- const nodeModulesTargetFile = path__default["default"].resolve(iRoot, NODE_MODULES, name, filePath);
775
- const exist = fs__default["default"].existsSync(nodeModulesTargetFile);
776
- if (exist) {
777
- return [
778
- nodeModulesTargetFile,
779
- ];
1035
+ /* eslint-disable @typescript-eslint/no-require-imports */
1036
+ const ROOT_NAME = 'MAIN';
1037
+ function saveJsonToLog(content, file, needLog = true) {
1038
+ if (!needLog)
1039
+ return;
1040
+ createLogDir();
1041
+ const filePath = `./log/${file}`;
1042
+ let beforeContent = [];
1043
+ let newContent = [{
1044
+ logTime: tComm.timeStampFormat(Date.now(), 'yyyy-MM-dd hh:mm:ss'),
1045
+ data: content,
1046
+ }];
1047
+ if (fs__namespace.existsSync(filePath)) {
1048
+ try {
1049
+ beforeContent = tComm.readFileSync(filePath, true).logList || [];
1050
+ }
1051
+ catch (err) {
1052
+ beforeContent = [];
1053
+ }
780
1054
  }
781
- const pnpmRoot = path__default["default"].resolve(iRoot, PNPM);
782
- if (!fs__default["default"].existsSync(pnpmRoot)) {
783
- return [];
1055
+ if (beforeContent && Array.isArray(beforeContent)) {
1056
+ newContent.push(...beforeContent);
1057
+ }
1058
+ newContent = newContent.slice(0, 10);
1059
+ try {
1060
+ fs__namespace.writeFile(filePath, JSON.stringify({ logList: newContent }, null, 2), {
1061
+ encoding: 'utf-8',
1062
+ }, () => { });
1063
+ }
1064
+ catch (err) {
1065
+ }
1066
+ }
1067
+ function createLogDir() {
1068
+ if (!fs__namespace.existsSync('./log')) {
1069
+ fs__namespace.mkdirSync('./log');
1070
+ }
1071
+ }
1072
+ const normalizePath = (path) => (tComm.isWindows() ? path.replace(/\\/g, '/') : path);
1073
+ function updateAssetSource(assets, key, source) {
1074
+ assets[key] = {
1075
+ source() {
1076
+ return source;
1077
+ },
1078
+ size() {
1079
+ return source.length;
1080
+ },
1081
+ };
1082
+ }
1083
+ function removeFirstSlash(key) {
1084
+ if (key.startsWith('/')) {
1085
+ return key.slice(1);
1086
+ }
1087
+ return key;
1088
+ }
1089
+ function sortStringList(list) {
1090
+ list.sort((a, b) => {
1091
+ if (a > b)
1092
+ return 1;
1093
+ if (a < b)
1094
+ return -1;
1095
+ return 0;
1096
+ });
1097
+ return list;
1098
+ }
1099
+ function parseSetDeps(deps) {
1100
+ return Object.keys(deps).reduce((acc, item) => {
1101
+ acc[item] = Array.from(deps[item]);
1102
+ sortStringList(acc[item]);
1103
+ return acc;
1104
+ }, {});
1105
+ }
1106
+ function getRelativePath(filePath) {
1107
+ return path__namespace.relative(process.cwd(), path__namespace.resolve(filePath));
1108
+ }
1109
+
1110
+ /**
1111
+ * 图标项目 CDN 地址
1112
+ */
1113
+ const ICON_PROJECT_CDN = 'https://image-1251917893.cos.ap-guangzhou.myqcloud.com/pmd-icon/icon-project.json';
1114
+ /**
1115
+ * COS 基础配置
1116
+ */
1117
+ const COS_BASE = {
1118
+ bucket: 'image-1251917893',
1119
+ region: 'ap-guangzhou',
1120
+ };
1121
+ /**
1122
+ * 图标项目流水线配置
1123
+ */
1124
+ const ICON_PROJECT_PIPELINE_CONFIG = {
1125
+ mainRemotePipelineId: 'ea55dae018e14f3ab7cac84a76858621',
1126
+ mainPipelineId: 'p-c988624124334c24b6872b10053c763e',
1127
+ createDirRemotePipelineId: '1209c293e0024a6cb9fe6e5e188239cf',
1128
+ };
1129
+ /**
1130
+ * 图标项目 Webhook URL
1131
+ */
1132
+ const ICON_PROJECT_WEBHOOK_URL = '331402f8-c274-4b99-a302-4404759ca25a';
1133
+
1134
+ /**
1135
+ * 通过原始 API 启动蓝盾流水线
1136
+ * @param params - 启动参数
1137
+ * @returns 流水线执行结果
1138
+ */
1139
+ async function startPipelineByRawApi({ data, pipelineId, userName, projectId: pId = 'tip-h5', }) {
1140
+ const result = await axios__default["default"]({
1141
+ method: 'POST',
1142
+ url: `https://devops.aow.com/ms/process/api/external/pipelines/${pipelineId}/build`,
1143
+ headers: {
1144
+ 'X-DEVOPS-PROJECT-ID': pId,
1145
+ 'X-DEVOPS-UID': userName || 'novlan1',
1146
+ },
1147
+ data,
1148
+ });
1149
+ return result.data;
1150
+ }
1151
+
1152
+ /**
1153
+ * 获取所有图标项目配置
1154
+ * @returns 包含图标项目数据的 Promise
1155
+ */
1156
+ function genIconProjects() {
1157
+ return axios__default["default"].get(`${ICON_PROJECT_CDN}?v=${Date.now()}`);
1158
+ }
1159
+ /**
1160
+ * 创建或更新图标项目
1161
+ * @param options - 创建/更新选项
1162
+ * @returns 操作结果
1163
+ */
1164
+ async function createIconProjectOrUpdate({ projectNameZN = '', projectName = '', figmaFileId = '', figmaNodeId = '', iconfontPrefix = '', iconfontFamily = '', cdn, cos, staffName = '', isCreate, tags, }) {
1165
+ const { data } = await genIconProjects();
1166
+ if (data[projectName] && isCreate) {
1167
+ return {
1168
+ error: 'project already exists',
1169
+ };
1170
+ }
1171
+ const createInfo = isCreate || !data[projectName]?.creator ? {
1172
+ creator: staffName,
1173
+ createTime: Date.now(),
1174
+ } : {};
1175
+ data[projectName] = {
1176
+ ...data[projectName],
1177
+ projectNameZN,
1178
+ projectName,
1179
+ figmaFileId,
1180
+ figmaNodeId,
1181
+ iconfontPrefix,
1182
+ iconfontFamily,
1183
+ cdn: cdn || '',
1184
+ tags,
1185
+ ...createInfo,
1186
+ updateTime: Date.now(),
1187
+ };
1188
+ const key = ICON_PROJECT_CDN.replace(/^https:\/\/[^/]+\//, '');
1189
+ console.log('[createIconProjectOrUpdate] data: ', { data, key });
1190
+ // if (isCreate) {
1191
+ await startPipelineByRawApi({
1192
+ data: {
1193
+ creator: staffName,
1194
+ projectName,
1195
+ toCreateDir: projectName,
1196
+ },
1197
+ pipelineId: ICON_PROJECT_PIPELINE_CONFIG.createDirRemotePipelineId,
1198
+ userName: staffName,
1199
+ });
1200
+ // }
1201
+ try {
1202
+ await tComm.uploadCOSStreamFile({
1203
+ secretId: cos.secretId,
1204
+ secretKey: cos.secretKey,
1205
+ bucket: cos.bucket,
1206
+ region: cos.region,
1207
+ key,
1208
+ file: {
1209
+ buffer: Buffer.from(JSON.stringify(data, null, 2)),
1210
+ },
1211
+ });
1212
+ await tComm.purgeUrlCache({
1213
+ secretId: cos.secretId,
1214
+ secretKey: cos.secretKey,
1215
+ urls: [toCdnUrl(ICON_PROJECT_CDN)],
1216
+ })
1217
+ .then((res) => {
1218
+ console.log('[purgeUrlCache] res: ', res?.data);
1219
+ })
1220
+ .catch((err) => {
1221
+ console.log('[purgeUrlCache] err: ', err);
1222
+ });
1223
+ }
1224
+ catch (e) {
1225
+ return {
1226
+ error: e.message,
1227
+ };
1228
+ }
1229
+ return {
1230
+ isCreate,
1231
+ };
1232
+ }
1233
+
1234
+ const BASE_SCSS = 'base.scss';
1235
+ function getStyleList(dir) {
1236
+ const cssList = fs__namespace.readdirSync(dir);
1237
+ const filtered = cssList
1238
+ .filter(item => item.endsWith('scss') && !item.startsWith(BASE_SCSS))
1239
+ .map(item => item.replace(/\.scss$/, ''));
1240
+ return filtered;
1241
+ }
1242
+ function genInjectContent({ styleList, componentName, topElement, dir = '', }) {
1243
+ const styleStr = styleList.map((item) => `
1244
+ &--type-${item} {
1245
+ @import './${dir}${item}.scss';
1246
+ }`).join('\n');
1247
+ return `
1248
+ ${topElement}.${componentName} {
1249
+ ${styleStr}
1250
+ }
1251
+ `;
1252
+ }
1253
+ function getComponentName(dir) {
1254
+ const tPath = tComm.normalizePath(dir);
1255
+ const reg = /\/([^/]+)\/css/;
1256
+ const match = tPath.match(reg);
1257
+ return match?.[1] || '';
1258
+ }
1259
+
1260
+ /**
1261
+ * 将 COS 文件同步到 Git 仓库
1262
+ * @param options - 同步选项
1263
+ */
1264
+ async function cosSyncToGit({ staffName, }) {
1265
+ await startPipelineByRawApi({
1266
+ data: {
1267
+ creator: staffName || 'novlan1',
1268
+ },
1269
+ pipelineId: COS_TO_GIT_REMOTE_PIPELINE_ID,
1270
+ userName: staffName || 'novlan1',
1271
+ });
1272
+ }
1273
+
1274
+ const LOADER_PROD = 'loader.prod.js';
1275
+ function getLoaderFile(dir = '', isProd = false) {
1276
+ if (isProd) {
1277
+ return path__default["default"].resolve(dir, LOADER_PROD);
1278
+ }
1279
+ return path__default["default"].resolve(dir, 'loader.js');
1280
+ }
1281
+ function getLoaderProdFile(dir = '') {
1282
+ return path__default["default"].resolve(dir, LOADER_PROD);
1283
+ }
1284
+
1285
+ const LOG_KEY = 'LOADER_LOGS';
1286
+ function saveLoaderLog() {
1287
+ const loaderLogs = global[LOG_KEY];
1288
+ if (!loaderLogs)
1289
+ return;
1290
+ Object.keys(loaderLogs).forEach((file) => {
1291
+ saveJsonToLog(loaderLogs[file], file);
1292
+ });
1293
+ }
1294
+ function recordLoaderLog(file, content) {
1295
+ if (!global[LOG_KEY]) {
1296
+ global[LOG_KEY] = {};
1297
+ }
1298
+ if (!global[LOG_KEY][file]) {
1299
+ global[LOG_KEY][file] = [];
1300
+ }
1301
+ global[LOG_KEY][file].push(content);
1302
+ }
1303
+
1304
+ function shouldUseLoader(defaultPlatforms = []) {
1305
+ const options = loaderUtils.getOptions(this) || {};
1306
+ const { platforms = defaultPlatforms } = options;
1307
+ const platform = process.env.UNI_PLATFORM || '';
1308
+ if (platforms === ALL_PLATFORM
1309
+ || platforms.indexOf(ALL_PLATFORM) > -1) {
1310
+ return true;
1311
+ }
1312
+ return platforms.includes(platform);
1313
+ }
1314
+
1315
+ /**
1316
+ * 研发平台 MCP 公共认证模块
1317
+ */
1318
+ /**
1319
+ * 解析命令行参数
1320
+ * @param defaultApiUrl 默认的 API URL
1321
+ * @returns 包含 rid, rtoken, apiUrl 和所有其他参数的对象
1322
+ */
1323
+ function parseMCPCommandLineArgs(defaultApiUrl) {
1324
+ const args = process.argv.slice(2).reduce((acc, arg) => {
1325
+ const parts = arg.split('=');
1326
+ // 只处理格式正确的参数(包含等号且有值)
1327
+ if (parts.length >= 2) {
1328
+ const key = parts[0].replace('--', '');
1329
+ const value = parts.slice(1).join('='); // 处理值中包含等号的情况
1330
+ if (key && value) {
1331
+ acc[key] = value;
1332
+ }
1333
+ }
1334
+ return acc;
1335
+ }, {});
1336
+ const rid = args.rid || '';
1337
+ const rtoken = args.rtoken || '';
1338
+ const apiUrl = args.apiUrl || defaultApiUrl;
1339
+ if (!rid) {
1340
+ console.error('错误:缺少必需的参数 --rid');
1341
+ }
1342
+ if (!rtoken) {
1343
+ console.error('错误:缺少必需的参数 --rtoken');
1344
+ }
1345
+ // 返回所有参数,但确保 rid、rtoken、apiUrl 使用处理后的值
1346
+ return { ...args, rid, rtoken, apiUrl };
1347
+ }
1348
+ /**
1349
+ * 验证凭证
1350
+ */
1351
+ function validateMCPCredentials(rid, rtoken) {
1352
+ if (!rid || !rtoken) {
1353
+ return {
1354
+ content: [
1355
+ {
1356
+ type: 'text',
1357
+ text: '❌ 错误:缺少必需的凭证参数\n\n请确保启动 MCP 服务时提供了 --rid 和 --rtoken 参数(https://mobile.aow.com/rd-platform-web/#/management/token)。\n\n示例:\nnode dist/index.js --rid=YOUR_RID --rtoken=YOUR_RTOKEN',
1358
+ },
1359
+ ],
1360
+ isError: true,
1361
+ };
1362
+ }
1363
+ return null;
1364
+ }
1365
+
1366
+ /**
1367
+ * 获取 MCP 名称(去掉 前缀)
1368
+ * @param pkg package.json 对象
1369
+ * @returns MCP 名称
1370
+ * @example
1371
+ * import pkg from '../package.json';
1372
+ * const mcpName = getMcpName(pkg);
1373
+ */
1374
+ function getMcpName(pkg) {
1375
+ if (!pkg.name) {
1376
+ throw new Error('package.json 中未找到 name 字段');
1377
+ }
1378
+ // 去掉 前缀
1379
+ return pkg.name.replace(/^@tencent\//, '');
1380
+ }
1381
+ /**
1382
+ * 获取 MCP 版本
1383
+ * @param pkg package.json 对象
1384
+ * @returns 版本号
1385
+ * @example
1386
+ * import pkg from '../package.json';
1387
+ * const mcpVersion = getMcpVersion(pkg);
1388
+ */
1389
+ function getMcpVersion(pkg) {
1390
+ if (!pkg.version) {
1391
+ throw new Error('package.json 中未找到 version 字段');
1392
+ }
1393
+ return pkg.version;
1394
+ }
1395
+
1396
+ /**
1397
+ * 研发平台 MCP 公共 HTTP 模块
1398
+ */
1399
+ /**
1400
+ * 通用错误处理
1401
+ */
1402
+ function handleAxiosError(error, actionName) {
1403
+ console.error(`❌ ${actionName}失败\n`);
1404
+ // 处理网络错误
1405
+ if (error.code === 'ENOTFOUND' || error.code === 'ECONNREFUSED') {
1406
+ console.error(` 网络错误: ${error.message}\n`);
1407
+ return { success: false, message: '网络连接失败,无法访问研发平台服务' };
1408
+ }
1409
+ // 处理超时错误
1410
+ if (error.code === 'ETIMEDOUT' || error.code === 'ECONNABORTED') {
1411
+ console.error(` 请求超时: ${error.message}\n`);
1412
+ return { success: false, message: '请求超时,请检查网络连接后重试' };
1413
+ }
1414
+ // 处理 HTTP 错误响应
1415
+ if (error.response) {
1416
+ const { status } = error.response;
1417
+ const { data } = error.response;
1418
+ console.error(` HTTP 错误: ${status}\n`);
1419
+ console.error(` 响应详情: ${JSON.stringify(data, null, 2)}\n`);
1420
+ if (status === 401 || status === 403) {
1421
+ return { success: false, message: '认证失败,rid 或 rtoken 无效或已过期' };
1422
+ }
1423
+ return {
1424
+ success: false,
1425
+ message: `服务器返回错误 (${status}): ${data?.msg || data?.message || '未知错误'}`,
1426
+ };
1427
+ }
1428
+ // 其他未知错误
1429
+ console.error(` 未知错误: ${error.message}\n`);
1430
+ return { success: false, message: `调用${actionName}时发生未知错误: ${error.message}` };
1431
+ }
1432
+ /**
1433
+ * 调用研发平台 API
1434
+ * @param apiUrl API 地址
1435
+ * @param requestBody 请求体
1436
+ * @param rid 用户 rid
1437
+ * @param rtoken 用户 token
1438
+ * @param pkg package.json 对象(用于获取 MCP 名称和版本)
1439
+ * @example
1440
+ * import pkg from '../package.json';
1441
+ * await callRdApi(apiUrl, requestBody, rid, rtoken, pkg);
1442
+ */
1443
+ async function callRdMCPApi(apiUrl, requestBody, rid, rtoken, pkg) {
1444
+ // 获取 MCP 名称和版本
1445
+ const mcpName = getMcpName(pkg);
1446
+ const mcpVersion = getMcpVersion(pkg);
1447
+ const logPrefix = `[${mcpName}]`;
1448
+ console.log(`${logPrefix} ⏳ 正在调用接口...`);
1449
+ console.log(`${logPrefix} API: ${apiUrl}`);
1450
+ console.log(`${logPrefix} 请求参数: ${JSON.stringify(requestBody, null, 2)}`);
1451
+ console.log('');
1452
+ try {
1453
+ const response = await axios__default["default"].post(apiUrl, {
1454
+ mcpName,
1455
+ mcpVersion,
1456
+ ...requestBody,
1457
+ }, {
1458
+ headers: {
1459
+ 'Content-Type': 'application/json',
1460
+ 'User-Agent': mcpName,
1461
+ rid,
1462
+ rtoken,
1463
+ },
1464
+ });
1465
+ // 检查业务状态码(兼容 code 和 r 两种字段)
1466
+ const statusCode = response.data.code ?? response.data.r ?? 0;
1467
+ const errMsg = response.data.msg || response.data.message || '未知错误';
1468
+ if (statusCode < 0) {
1469
+ console.log(`${logPrefix} ❌ 接口调用失败: ${errMsg}\n`);
1470
+ console.log(`${logPrefix} 响应详情: ${JSON.stringify(response.data, null, 2)}\n`);
1471
+ const errorMessage = `${errMsg}\n\n请求参数:\n${JSON.stringify(requestBody, null, 2)}`;
1472
+ return { success: false, message: errorMessage, code: statusCode };
1473
+ }
1474
+ console.log(`${logPrefix} ✅ 接口调用成功\n`);
1475
+ console.log(`${logPrefix} 响应详情: ${JSON.stringify(response.data, null, 2)}\n`);
1476
+ return { success: true, data: response.data.data };
1477
+ }
1478
+ catch (error) {
1479
+ return handleAxiosError(error, `${logPrefix} 接口`);
1480
+ }
1481
+ }
1482
+
1483
+ /**
1484
+ * 小程序 CI 模板 ID 映射
1485
+ */
1486
+ const TEMPLATE_ID_MAP = {
1487
+ /** 微信小程序 CI 模板 ID */
1488
+ WX_MP_CI: '864ecf9343fb4748adfdecde92712401',
1489
+ /** QQ 小程序 CI 模板 ID */
1490
+ QQ_MP_CI: '6c13205d64fe4a42889db131b5e47d03',
1491
+ /** Cypress 自动化测试模板 ID */
1492
+ CYPRESS_AUTO_TEST: 'f7b13cf8929343a1bd56949e5463dfc4',
1493
+ };
1494
+
1495
+ /* eslint-disable @typescript-eslint/no-require-imports */
1496
+ /**
1497
+ * Rainbow 配置常量
1498
+ */
1499
+ const RAINBOW_CONFIG$1 = {
1500
+ valueType: 4,
1501
+ creator: 'novlan1',
1502
+ approvers: 'novlan1;Rainbow_ygw',
1503
+ envName: 'Default',
1504
+ groupName: 'devops_mp_ci',
1505
+ };
1506
+ /**
1507
+ * 获取小程序 CI 通用配置
1508
+ * @returns 通用配置对象
1509
+ */
1510
+ async function getMpCICommonConfig() {
1511
+ const { RAINBOW_APP_ID, } = process.env;
1512
+ const { envName, groupName, } = RAINBOW_CONFIG$1;
1513
+ let res = await tComm.fetchRainbowConfigFromSdk({
1514
+ secretInfo: {
1515
+ appId: RAINBOW_APP_ID,
1516
+ userId: process.env.RAINBOW_USER_ID,
1517
+ secretKey: process.env.RAINBOW_SECRET_KEY || '',
1518
+ envName,
1519
+ groupName,
1520
+ },
1521
+ key: 'common_config',
1522
+ sdk: require('@tencent/rainbow-node-sdk'),
1523
+ rainbow: global.rainbowInstance,
1524
+ tryJsonParse: false,
1525
+ });
1526
+ try {
1527
+ res = JSON.parse(res);
1528
+ }
1529
+ catch (err) { }
1530
+ return res;
1531
+ }
1532
+ /**
1533
+ * 获取 Rainbow 配置
1534
+ * @param params - 查询参数
1535
+ * @returns Rainbow 配置项列表
1536
+ */
1537
+ async function getRainbowConfig({ rdProjectId, projectName, subProject, }) {
1538
+ const { RAINBOW_APP_ID, RAINBOW_USER_ID, RAINBOW_SECRET_KEY, } = process.env;
1539
+ const resp = await tComm.queryGroupInfo({
1540
+ secretInfo: {
1541
+ appId: RAINBOW_APP_ID,
1542
+ envName: 'Default',
1543
+ groupName: RAINBOW_CONFIG$1.groupName,
1544
+ userId: RAINBOW_USER_ID,
1545
+ secretKey: RAINBOW_SECRET_KEY || '',
1546
+ },
1547
+ });
1548
+ const result = resp
1549
+ .filter((item) => item.key.endsWith('_mp_ci'))
1550
+ .map((item) => {
1551
+ let data = {};
1552
+ try {
1553
+ data = JSON.parse(item.value);
1554
+ }
1555
+ catch (err) { }
1556
+ return {
1557
+ rainbowKey: item.key,
1558
+ ...data,
1559
+ };
1560
+ })
1561
+ .filter((item) => ((rdProjectId && item.rdProjectId === rdProjectId)
1562
+ || (projectName && item.ci?.repo === projectName))
1563
+ && item.subProject === subProject);
1564
+ return result;
1565
+ }
1566
+ /**
1567
+ * 更新小程序 CI 配置
1568
+ * @param value - 配置值
1569
+ * @param key - 配置 Key
1570
+ */
1571
+ async function updateMpCiConfig(value, key) {
1572
+ const { RAINBOW_APP_ID, RAINBOW_USER_ID, RAINBOW_SECRET_KEY, } = process.env;
1573
+ const { valueType, creator, approvers, envName, groupName, } = RAINBOW_CONFIG$1;
1574
+ await tComm.updateRainbowKVAndPublish({
1575
+ key,
1576
+ value: JSON.stringify(value, null, 2),
1577
+ valueType,
1578
+ creator,
1579
+ approvers,
1580
+ secretInfo: {
1581
+ appId: RAINBOW_APP_ID,
1582
+ userId: RAINBOW_USER_ID,
1583
+ secretKey: RAINBOW_SECRET_KEY || '',
1584
+ envName,
1585
+ groupName,
1586
+ },
1587
+ }).then((res) => {
1588
+ console.log('[updateMpCiConfig] res', res);
1589
+ })
1590
+ .catch((err) => {
1591
+ console.log('[updateMpCiConfig] err', err);
1592
+ });
1593
+ }
1594
+ /**
1595
+ * 获取所有小程序流水线实例
1596
+ * @returns 微信和 QQ 小程序流水线实例
1597
+ */
1598
+ async function getAllMpPipeline() {
1599
+ const param = {
1600
+ projectId: 'tip-h5',
1601
+ host: 'http://devops.apigw.o.aow.com',
1602
+ secretInfo: {
1603
+ appCode: 'tip-tool',
1604
+ appSecret: process.env.DEVOPS_APP_SECRET || '',
1605
+ devopsUid: 'novlan1',
1606
+ },
1607
+ };
1608
+ const wxInstances = await tComm.getAllDevopsTemplateInstances({
1609
+ ...param,
1610
+ templateId: TEMPLATE_ID_MAP.WX_MP_CI,
1611
+ });
1612
+ const qqInstances = await tComm.getAllDevopsTemplateInstances({
1613
+ ...param,
1614
+ templateId: TEMPLATE_ID_MAP.QQ_MP_CI,
1615
+ });
1616
+ return {
1617
+ wxInstances,
1618
+ qqInstances,
1619
+ };
1620
+ }
1621
+ /**
1622
+ * 获取指定 Key 的小程序 CI 配置
1623
+ * @param key - 配置 Key
1624
+ * @returns 配置值
1625
+ */
1626
+ async function getMpCIKeyConfig(key) {
1627
+ const { RAINBOW_APP_ID, } = process.env;
1628
+ const { envName, groupName, } = RAINBOW_CONFIG$1;
1629
+ let res = await tComm.fetchRainbowConfigFromSdk({
1630
+ secretInfo: {
1631
+ appId: RAINBOW_APP_ID,
1632
+ userId: process.env.RAINBOW_USER_ID,
1633
+ secretKey: process.env.RAINBOW_SECRET_KEY || '',
1634
+ envName,
1635
+ groupName,
1636
+ },
1637
+ key,
1638
+ sdk: require('@tencent/rainbow-node-sdk'),
1639
+ rainbow: global.rainbowInstanceNoCache,
1640
+ tryJsonParse: false,
1641
+ });
1642
+ try {
1643
+ res = JSON.parse(res);
1644
+ }
1645
+ catch (err) { }
1646
+ return res;
1647
+ }
1648
+
1649
+ /**
1650
+ * 从 repo 地址中提取仓库路径
1651
+ * 支持 git@git.aow.com:pmd-mobile/pmd-h5/plugin-light.git 和 https://github.com/novlan1/plugin-light 两种格式
1652
+ * @param repo 仓库地址
1653
+ * @returns 仓库路径,如 pmd-mobile/pmd-h5/plugin-light
1654
+ */
1655
+ function extractRepoPath(repo) {
1656
+ if (!repo)
1657
+ return '';
1658
+ // 去除末尾的 .git
1659
+ const cleanRepo = repo.replace(/\.git$/, '');
1660
+ // SSH 格式: git@git.aow.com:pmd-mobile/pmd-h5/plugin-light
1661
+ if (cleanRepo.includes('@') && cleanRepo.includes(':')) {
1662
+ const match = cleanRepo.match(/:(.+)$/);
1663
+ return match ? match[1] : '';
1664
+ }
1665
+ // HTTPS 格式: https://github.com/novlan1/plugin-light
1666
+ if (cleanRepo.includes('://')) {
1667
+ const match = cleanRepo.match(/git\.aow\.com\/(.+)$/);
1668
+ return match ? match[1] : '';
1669
+ }
1670
+ // 已经是路径格式
1671
+ return cleanRepo;
1672
+ }
1673
+
1674
+ /**
1675
+ * 根据分支获取环境
1676
+ * @param branch - 分支名称
1677
+ * @returns 环境名称,release 分支返回 'release',其他返回 'test'
1678
+ */
1679
+ function getEnvByBranch(branch) {
1680
+ if (branch === 'release') {
1681
+ return 'release';
1682
+ }
1683
+ return 'test';
1684
+ }
1685
+ /**
1686
+ * 解析发布原因,替换特殊字符
1687
+ * @param str - 原始发布原因字符串
1688
+ * @returns 处理后的发布原因字符串
1689
+ */
1690
+ function parsePublishReason(str = '') {
1691
+ return str.replace(/[`'"]/g, '·').replace(/\n/g, ';');
1692
+ }
1693
+
1694
+ /**
1695
+ * 添加小程序发布记录
1696
+ * @param params - 记录参数
1697
+ */
1698
+ async function addMPPublishRecord({ staffName, repo, subProjectName, branch, env, isWeixin, pipelineId, pipelineRunId, publishReason, fromMcp, mcpName, version, operationTool, mcpDB, }) {
1699
+ const recordList = [
1700
+ isWeixin ? '微信小程序' : 'QQ小程序',
1701
+ `工程:${repo}`,
1702
+ `子工程:${subProjectName}`,
1703
+ `分支:${branch}`,
1704
+ `环境:${env}`,
1705
+ publishReason ? `发布原因:${publishReason}` : '',
1706
+ ].filter(Boolean);
1707
+ const desc = [
1708
+ ...recordList,
1709
+ `MCP:${fromMcp ? '是' : '否'}`,
1710
+ ].filter(Boolean).join(',');
1711
+ const list = repo.split('/');
1712
+ const groupName = list.slice(0, list.length - 1).join('/');
1713
+ const operationData = [
1714
+ // req.header('staffname') || creator || 'novlan1',
1715
+ staffName,
1716
+ // operationTool.TYPE.MP_BUILD,
1717
+ '小程序构建',
1718
+ desc,
1719
+ // operationTool.STATUS.START,
1720
+ '已启动',
1721
+ pipelineId,
1722
+ pipelineRunId,
1723
+ '',
1724
+ '',
1725
+ groupName,
1726
+ repo,
1727
+ subProjectName,
1728
+ ];
1729
+ if (fromMcp && typeof mcpDB?.add === 'function') {
1730
+ const mcpContent = recordList.join(',');
1731
+ await mcpDB.add(mcpName || 'mp-publish-mcp', staffName, mcpContent, version);
1732
+ }
1733
+ operationTool.add(...operationData);
1734
+ }
1735
+
1736
+ /**
1737
+ * 启动小程序 CI 流水线
1738
+ * @param params - 启动参数
1739
+ * @returns 执行结果
1740
+ */
1741
+ async function startMpCIPipeline({ repo: rawRepo, subProject, branch, staffName, isWeixin, publishReason = '', mcpName, mcpVersion, startPipeline, operationTool, mcpDB, }) {
1742
+ const env = getEnvByBranch(branch);
1743
+ const repo = extractRepoPath(rawRepo);
1744
+ const rainbowConfigs = await getRainbowConfig({
1745
+ projectName: repo,
1746
+ subProject,
1747
+ });
1748
+ if (!rainbowConfigs?.length) {
1749
+ return {
1750
+ r: -1,
1751
+ msg: '未找到对应的七彩石配置',
1752
+ };
1753
+ }
1754
+ const rainbowConfig = rainbowConfigs[0];
1755
+ const robotMap = isWeixin
1756
+ ? rainbowConfig.robotMap
1757
+ : rainbowConfig.qqRobotMap;
1758
+ const curRobot = robotMap?.[branch]?.[env];
1759
+ if (!curRobot) {
1760
+ // TODO: 新增机器人配置
1761
+ return {
1762
+ r: -1,
1763
+ msg: '未找到对应的机器人配置',
1764
+ };
1765
+ }
1766
+ const { wxInstances, qqInstances, } = await getAllMpPipeline();
1767
+ const instances = isWeixin ? wxInstances : qqInstances;
1768
+ const projectCiName = rainbowConfig?.ci?.name;
1769
+ const prefix = isWeixin ? 'wxci' : 'qqci';
1770
+ const foundInstance = instances.find((item) => item.pipelineName.startsWith(`${prefix}__${projectCiName}__${curRobot}__`));
1771
+ if (!foundInstance) {
1772
+ return {
1773
+ r: -1,
1774
+ msg: '未找到对应的流水线',
1775
+ };
1776
+ }
1777
+ const { pipelineId } = foundInstance;
1778
+ const parsedPublishReason = parsePublishReason(publishReason || '');
1779
+ const result = await startPipeline(pipelineId, {
1780
+ creator: staffName,
1781
+ rainbowConfigKey: rainbowConfig.rainbowKey,
1782
+ publishReason: parsedPublishReason,
1783
+ isWeixin: isWeixin ? 1 : 0,
1784
+ repo,
1785
+ subProjectName: subProject,
1786
+ branch,
1787
+ env,
1788
+ useDevopsWXCIPlugin: rainbowConfig?.devopsParams?.useDevopsWXCIPlugin ?? '1',
1789
+ });
1790
+ await addMPPublishRecord({
1791
+ staffName,
1792
+ repo,
1793
+ subProjectName: subProject,
1794
+ branch,
1795
+ env,
1796
+ isWeixin,
1797
+ pipelineId,
1798
+ pipelineRunId: result.data?.id,
1799
+ publishReason: parsedPublishReason,
1800
+ fromMcp: true,
1801
+ mcpName,
1802
+ version: mcpVersion,
1803
+ operationTool,
1804
+ mcpDB,
1805
+ });
1806
+ return {
1807
+ r: result.status ?? 0,
1808
+ msg: result.message ?? result.data?.id,
1809
+ result: result.data,
1810
+ };
1811
+ }
1812
+
1813
+ function findNodeModuleFile({ name, target, filePath, root, }) {
1814
+ const iRoot = root ?? process.cwd();
1815
+ const NODE_MODULES = 'node_modules';
1816
+ const PNPM = `${NODE_MODULES}/.pnpm`;
1817
+ const nodeModulesTargetFile = path__default["default"].resolve(iRoot, NODE_MODULES, name, filePath);
1818
+ const exist = fs__default["default"].existsSync(nodeModulesTargetFile);
1819
+ if (exist) {
1820
+ return [
1821
+ nodeModulesTargetFile,
1822
+ ];
1823
+ }
1824
+ const pnpmRoot = path__default["default"].resolve(iRoot, PNPM);
1825
+ if (!fs__default["default"].existsSync(pnpmRoot)) {
1826
+ return [];
784
1827
  }
785
1828
  const pnpmList = fs__default["default"].readdirSync(pnpmRoot);
786
1829
  const list = pnpmList.filter(item => item.startsWith(target));
@@ -791,6 +1834,169 @@ function findNodeModuleFile({ name, target, filePath, root, }) {
791
1834
  return innerFileList;
792
1835
  }
793
1836
 
1837
+ function getPipelineRunLink({ pipelineId, pipelineRunId, }) {
1838
+ return `https://devops.aow.com/console/pipeline/tip-h5/${pipelineId}/detail/${pipelineRunId}/executeDetail`;
1839
+ }
1840
+
1841
+ /**
1842
+ * 启动 NPM 发布流水线核心函数
1843
+ * @param params - 启动参数
1844
+ * @returns 执行结果
1845
+ */
1846
+ async function startNPMPublishPipelineCore({ form, staffName, remotePipelineId, componentTitle, mcpDB, fromMcp, mcpName, mcpVersion, operationTool, }) {
1847
+ const { branch, publishReason, versionType, publishExternalNPM, publishPackage, ...args } = form || {};
1848
+ if (!branch) {
1849
+ return { r: -1, msg: '缺少分支名称' };
1850
+ }
1851
+ if (!versionType) {
1852
+ return { r: -1, msg: '缺少发布版本类型' };
1853
+ }
1854
+ if (!publishReason) {
1855
+ return { r: -1, msg: '缺少发布原因' };
1856
+ }
1857
+ try {
1858
+ const result = await startPipelineByRawApi({
1859
+ data: {
1860
+ branch,
1861
+ versionType,
1862
+ publishReason,
1863
+ publishExternalNPM,
1864
+ creator: staffName,
1865
+ publishPackage,
1866
+ ...args,
1867
+ },
1868
+ pipelineId: remotePipelineId,
1869
+ userName: staffName,
1870
+ });
1871
+ if (!result?.status) {
1872
+ const recordList = [
1873
+ `包名:${componentTitle}`,
1874
+ `分支:${branch}`,
1875
+ `版本类型:${versionType}`,
1876
+ `发布原因:${publishReason}`,
1877
+ `是否发布外网:${publishExternalNPM ? '是' : '否'}`,
1878
+ publishPackage ? `发布子包:${publishPackage}` : '',
1879
+ ].filter(Boolean);
1880
+ await operationTool.addByMapParam({
1881
+ staffName,
1882
+ type: 'NPM_PUBLISH',
1883
+ desc: [
1884
+ ...recordList,
1885
+ `MCP:${fromMcp ? '是' : '否'}`,
1886
+ mcpVersion ? `MCP版本:${mcpVersion}` : '',
1887
+ ].filter(Boolean).join(','),
1888
+ status: 'success',
1889
+ extra: {
1890
+ pipelineId: result.data?.pipelineId,
1891
+ pipelineRunId: result.data?.id,
1892
+ },
1893
+ });
1894
+ if (typeof mcpDB?.add === 'function') {
1895
+ const mcpContent = recordList.join(',');
1896
+ await mcpDB.add(mcpName || 'npm-publish-mcp', staffName, mcpContent, mcpVersion);
1897
+ }
1898
+ return { r: 0, result };
1899
+ }
1900
+ return { r: -1, msg: result.message };
1901
+ }
1902
+ catch (err) {
1903
+ return { r: -1, msg: err.message };
1904
+ }
1905
+ }
1906
+
1907
+ /**
1908
+ * 根据 repo 获取组件配置
1909
+ * @param repo 仓库地址,支持 SSH 和 HTTPS 格式
1910
+ * @returns 组件配置,未找到则返回 null
1911
+ */
1912
+ function getComponentByRepo(repo) {
1913
+ if (!repo)
1914
+ return null;
1915
+ const repoPath = extractRepoPath(repo);
1916
+ if (!repoPath)
1917
+ return null;
1918
+ // 遍历二维数组查找匹配的组件
1919
+ for (const component of _const.NPM_PACKAGES_LIST) {
1920
+ const componentRepoPath = extractRepoPath(component.git);
1921
+ if (componentRepoPath === repoPath) {
1922
+ return component;
1923
+ }
1924
+ }
1925
+ return null;
1926
+ }
1927
+
1928
+ function getWxmlAndWxssPostfix() {
1929
+ const map = Object.keys(PLATFORM_MAP).reduce((acc, item) => {
1930
+ acc[PLATFORM_MAP[item]] = item;
1931
+ return acc;
1932
+ }, {});
1933
+ const key = map[process.env.UNI_PLATFORM || ''];
1934
+ return [
1935
+ HTML_MAP[key],
1936
+ CSS_MAP[key],
1937
+ ];
1938
+ }
1939
+
1940
+ function getProjectName() {
1941
+ let result = '';
1942
+ try {
1943
+ const json = tComm.readFileSync('package.json', true) || {};
1944
+ result = json.name || '';
1945
+ }
1946
+ catch (err) { }
1947
+ return result;
1948
+ }
1949
+ function getSubProjectName() {
1950
+ const name = process.env.VUE_APP_DIR?.split('/')?.[1] || '';
1951
+ return name;
1952
+ }
1953
+
1954
+ function updateManifestCore(_a) {
1955
+ var path = _a.path,
1956
+ value = _a.value,
1957
+ manifest = _a.manifest;
1958
+ var arr = path.split('.');
1959
+ var len = arr.length;
1960
+ var lastItem = arr[len - 1];
1961
+ var i = 0;
1962
+ var manifestArr = manifest.split(/\n/);
1963
+ for (var index = 0; index < manifestArr.length; index++) {
1964
+ var item = manifestArr[index];
1965
+ if (new RegExp("\"".concat(arr[i], "\"")).test(item)) i = i + 1;
1966
+ if (i === len) {
1967
+ var hasComma = /,/.test(item);
1968
+ manifestArr[index] = item.replace(new RegExp("\"".concat(lastItem, "\"[\\s\\S]*:[\\s\\S]*")), "\"".concat(lastItem, "\": ").concat(value).concat(hasComma ? ',' : ''));
1969
+ break;
1970
+ }
1971
+ }
1972
+ return manifestArr.join('\n');
1973
+ }
1974
+
1975
+ // 读取 manifest.json ,修改后重新写入
1976
+ const manifestPath = `${process.env.UNI_INPUT_DIR}/manifest.json`;
1977
+ let originManifest = '';
1978
+ try {
1979
+ originManifest = fs__namespace.readFileSync(manifestPath, { encoding: 'utf-8' });
1980
+ }
1981
+ catch (err) {
1982
+ }
1983
+ let Manifest = originManifest;
1984
+ function updateManifest(path, value) {
1985
+ Manifest = updateManifestCore({
1986
+ path,
1987
+ value,
1988
+ manifest: Manifest,
1989
+ });
1990
+ fs__namespace.writeFileSync(manifestPath, Manifest, {
1991
+ flag: 'w',
1992
+ });
1993
+ }
1994
+ function revertManifest() {
1995
+ fs__namespace.writeFileSync(manifestPath, originManifest, {
1996
+ flag: 'w',
1997
+ });
1998
+ }
1999
+
794
2000
  function replaceDirective(source, list) {
795
2001
  if (!list.length)
796
2002
  return source;
@@ -799,6 +2005,125 @@ function replaceDirective(source, list) {
799
2005
  return newSource;
800
2006
  }
801
2007
 
2008
+ function getSubProjectRoot({ root, appDir, }) {
2009
+ let subProjectRoot = `${path__default["default"].resolve(root, `./src/${appDir}`)}/`;
2010
+ if (!appDir) {
2011
+ subProjectRoot = path__default["default"].resolve(root, './src/');
2012
+ }
2013
+ return subProjectRoot;
2014
+ }
2015
+ function getSubProjectConfig(subProjectRoot) {
2016
+ let res = {};
2017
+ try {
2018
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
2019
+ res = require(path__default["default"].resolve(subProjectRoot, 'config.js'));
2020
+ }
2021
+ catch (err) { }
2022
+ return res;
2023
+ }
2024
+
2025
+ const apikey = 'c0zWN3rC0FT4vlvhZYynvy689NxB3kq6';
2026
+ tinify__default["default"].key = apikey;
2027
+ /**
2028
+ * 从 Rainbow 获取 TinyPNG API Keys
2029
+ * @returns API Keys 列表
2030
+ */
2031
+ async function fetchApiKeys() {
2032
+ const result = await tComm.fetchRainbowConfigFromSdk({
2033
+ secretInfo: {
2034
+ appId: process.env.RAINBOW_APP_ID,
2035
+ userId: process.env.RAINBOW_USER_ID || '',
2036
+ secretKey: process.env.RAINBOW_SECRET_KEY || '',
2037
+ envName: 'Default',
2038
+ groupName: 'devops',
2039
+ },
2040
+ key: 'tiny_png_api_keys',
2041
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
2042
+ sdk: require(' '),
2043
+ tryJsonParse: true,
2044
+ });
2045
+ const list = result.data.map((item) => item.key);
2046
+ return list;
2047
+ }
2048
+ /**
2049
+ * 使用指定 Key 尝试压缩文件
2050
+ * @param file - 输入文件
2051
+ * @param key - TinyPNG API Key
2052
+ * @returns 压缩后的文件路径或缓冲区
2053
+ */
2054
+ async function tryCompressUseEachKey(file, key) {
2055
+ if (file.path && !file.buffer) {
2056
+ tinify__default["default"].key = key;
2057
+ const list = file.path.split(path__namespace.sep);
2058
+ const newPath = `${list.slice(0, list.length - 1).join(path__namespace.sep) + path__namespace.sep}tiny_${list[list.length - 1]}`;
2059
+ console.log('newPath', newPath);
2060
+ await tinify__default["default"].fromFile(file.path).toFile(newPath);
2061
+ return Promise.resolve(newPath);
2062
+ }
2063
+ return new Promise((resolve, reject) => {
2064
+ tinify__default["default"].key = key;
2065
+ tinify__default["default"]
2066
+ .fromBuffer(file.buffer)
2067
+ .toBuffer((err, resultData) => {
2068
+ console.log('resultData', { resultData, err }, resultData);
2069
+ if (err) {
2070
+ reject(err);
2071
+ }
2072
+ else if (resultData) {
2073
+ resolve(Buffer.from(resultData));
2074
+ }
2075
+ else {
2076
+ reject(new Error('tinypng 压缩结果为空'));
2077
+ }
2078
+ });
2079
+ });
2080
+ }
2081
+ /**
2082
+ * 使用 TinyPNG 压缩文件
2083
+ * @param file - 输入文件
2084
+ * @returns 压缩后的文件路径或缓冲区
2085
+ * @throws 如果所有 API Key 都失败则抛出错误
2086
+ */
2087
+ async function compressFileUseTinypng(file) {
2088
+ const apikeys = await fetchApiKeys();
2089
+ console.log('apikeysMap', apikeys);
2090
+ let err;
2091
+ let result;
2092
+ for (const key of apikeys) {
2093
+ try {
2094
+ result = await tryCompressUseEachKey(file, key);
2095
+ if (result) {
2096
+ return result;
2097
+ }
2098
+ }
2099
+ catch (e) {
2100
+ err = e;
2101
+ }
2102
+ }
2103
+ if (!result && err) {
2104
+ throw new Error(err.message);
2105
+ }
2106
+ return result;
2107
+ }
2108
+
2109
+ const getPlatform = () => process.env.UNI_PLATFORM || '';
2110
+ const getUtsPlatform = () => process.env.UNI_UTS_PLATFORM || '';
2111
+ const getAppPlatform = () => process.env.UNI_APP_PLATFORM || '';
2112
+ const isH5 = () => getPlatform() === 'h5';
2113
+ const isApp = () => getPlatform() === 'app';
2114
+ const isAppAndroid = () => getAppPlatform() === 'android' || getUtsPlatform() === 'app-android';
2115
+ const isAppIOS = () => getAppPlatform() === 'ios' || getUtsPlatform() === 'app-ios';
2116
+ const isMp = () => /^mp-/i.test(getPlatform());
2117
+ const isMpWeixin = () => getPlatform() === 'mp-weixin';
2118
+ const isMpAlipay = () => getPlatform() === 'mp-alipay';
2119
+ const isMpBaidu = () => getPlatform() === 'mp-baidu';
2120
+ const isMpKuaishou = () => getPlatform() === 'mp-kuaishou';
2121
+ const isMpQQ = () => getPlatform() === 'mp-qq';
2122
+ const isMpToutiao = () => getPlatform() === 'mp-toutiao';
2123
+ const isQuickapp = () => /^quickapp-webview/i.test(getPlatform());
2124
+ const isQuickappUnion = () => getPlatform() === 'quickapp-webview-union';
2125
+ const isQuickappHuawei = () => getPlatform() === 'quickapp-webview-huawei';
2126
+
802
2127
  const htmlReg = /(?<=<template>)([\s\S]+)(?=<\/template>)/;
803
2128
  const imgReg = /(<img[\s\S]+?)v-lazy=(?:"|')(.*?)(?:"|')([\s\S]*?>)/g;
804
2129
  const sizeReg = /(?<=[\s\n]+(?:data-)?)size=(?:"|')(\d+)(?:"|')/;
@@ -866,52 +2191,442 @@ function handleImg(str = '', urlHandler = '') {
866
2191
  return res;
867
2192
  }
868
2193
 
2194
+ /**
2195
+ * 白名单用户状态映射
2196
+ */
2197
+ const WHITE_USER_STATUS_MAP = {
2198
+ /** 待处理 */
2199
+ PENDING: 0,
2200
+ /** 成功 */
2201
+ SUCCESS: 1,
2202
+ /** 失败 */
2203
+ FAIL: 2,
2204
+ };
2205
+ /**
2206
+ * 白名单用户流水线 ID
2207
+ */
2208
+ const WHITE_USER_PIPELINE_ID = 'p-cd935542953b4057884920da801d9feb';
2209
+ /**
2210
+ * 白名单用户 CDN 刷新流水线 ID
2211
+ */
2212
+ const WHITE_USER_CDN_REFRESH_PIPELINE_ID = 'p-24d52115511f4fb8bacdd95114ff044d';
2213
+ /**
2214
+ * Rainbow 配置常量
2215
+ */
2216
+ const RAINBOW_CONFIG = {
2217
+ configKey: 'frontend_white_config',
2218
+ envName: 'Default',
2219
+ groupName: 'devops_open',
2220
+ };
2221
+ /**
2222
+ * 获取白名单 Rainbow 配置
2223
+ * @returns 白名单配置对象
2224
+ */
2225
+ async function getWhiteRainbowConfig() {
2226
+ const { RAINBOW_APP_ID, } = process.env;
2227
+ const configListStr = await tComm.fetchRainbowConfigFromSdk({
2228
+ secretInfo: {
2229
+ appID: RAINBOW_APP_ID,
2230
+ userId: process.env.RAINBOW_USER_ID || '',
2231
+ secretKey: process.env.RAINBOW_SECRET_KEY || '',
2232
+ envName: RAINBOW_CONFIG.envName,
2233
+ groupName: RAINBOW_CONFIG.groupName,
2234
+ },
2235
+ key: RAINBOW_CONFIG.configKey,
2236
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
2237
+ sdk: require(' '),
2238
+ rainbow: global.rainbowInstance,
2239
+ tryJsonParse: false,
2240
+ initOptions: {
2241
+ isUsingFileCache: false,
2242
+ isUsingLocalCache: false,
2243
+ },
2244
+ });
2245
+ const config = tComm.safeJsonParse(configListStr || '{}', {
2246
+ whiteTypeList: [],
2247
+ whiteTypeMap: {},
2248
+ });
2249
+ const whiteTypeList = config?.whiteTypeList || [];
2250
+ const whiteTypeMap = whiteTypeList.reduce((acc, item) => ({
2251
+ ...acc,
2252
+ [item.value]: item,
2253
+ }), {});
2254
+ return {
2255
+ ...(config || {}),
2256
+ whiteTypeMap,
2257
+ };
2258
+ }
2259
+ /**
2260
+ * 获取白名单类型描述
2261
+ * @param whiteType - 白名单类型
2262
+ * @returns 白名单类型的显示标签
2263
+ */
2264
+ async function getWhiteTypeDesc(whiteType) {
2265
+ const config = await getWhiteRainbowConfig();
2266
+ const whiteTypeMap = config?.whiteTypeMap || {};
2267
+ return whiteTypeMap[whiteType]?.label;
2268
+ }
2269
+
2270
+ /**
2271
+ * 获取两个列表的差异项
2272
+ * @param config - 新配置列表
2273
+ * @param originConfig - 原配置列表
2274
+ * @returns 差异项列表
2275
+ */
2276
+ function getEachDiff(config = [], originConfig = []) {
2277
+ return config.filter(item => !originConfig.includes(item));
2278
+ }
2279
+ /**
2280
+ * 获取文件链接
2281
+ * @param params - 文件参数
2282
+ * @returns 完整的文件 URL
2283
+ */
2284
+ function getFileLink({ fileName, cdn, }) {
2285
+ if (cdn) {
2286
+ const fullName = `https://${cdn}/white/${fileName}.json?v=${Date.now()}`;
2287
+ return fullName;
2288
+ }
2289
+ const fullName = `https://tip-components-1251917893.cos.ap-guangzhou.myqcloud.com/white/${fileName}.json`;
2290
+ return fullName;
2291
+ }
2292
+ /**
2293
+ * 获取文件差异
2294
+ * @param params - 文件参数
2295
+ * @returns 差异结果
2296
+ */
2297
+ async function getDiff({ fileName, fileContent, cdn, }) {
2298
+ const fullName = getFileLink({
2299
+ fileName,
2300
+ cdn,
2301
+ });
2302
+ let originList = [];
2303
+ try {
2304
+ const origin = await axios__default["default"].get(fullName);
2305
+ originList = origin?.data?.data || [];
2306
+ }
2307
+ catch (err) {
2308
+ // 忽略错误
2309
+ }
2310
+ const addedList = getEachDiff(fileContent, originList);
2311
+ const deletedList = getEachDiff(originList, fileContent);
2312
+ return {
2313
+ deletedList,
2314
+ addedList,
2315
+ };
2316
+ }
2317
+ /**
2318
+ * 发送企业微信机器人消息
2319
+ * @param options - 消息选项
2320
+ */
2321
+ async function sendRobotMessage({ addedList, deletedList, fileName, label, fromCron, fromWebhook, found, }) {
2322
+ if (!addedList?.length && !deletedList?.length) {
2323
+ return;
2324
+ }
2325
+ const beauty = (list) => list.map(item => `\`${item}\``).join(',');
2326
+ const getTrigger = () => {
2327
+ if (fromCron) {
2328
+ return '定时任务';
2329
+ }
2330
+ if (fromWebhook) {
2331
+ return '回调';
2332
+ }
2333
+ return '未知';
2334
+ };
2335
+ const applyUserStr = fromWebhook && found?.length ? `,申请者<@${found[0].applyUser}>` : '';
2336
+ const content = [
2337
+ `【前端白名单更新】\`${label || fileName}\`,触发自\`Next SVR - ${getTrigger()}\`,[自助申请地址](https://mobile.aow.com/rd-platform-web/#/management/white/new/)${applyUserStr}`,
2338
+ addedList.length ? `- 新增:${beauty(addedList)}` : '',
2339
+ deletedList.length ? `- 删除:${beauty(deletedList)}` : '',
2340
+ ]
2341
+ .filter(item => item)
2342
+ .join('\n');
2343
+ await tComm.batchSendWxRobotMarkdown({
2344
+ content,
2345
+ chatId: 'ALL',
2346
+ webhookUrl: '8d47cdb4-6852-4305-aefd-17b20478ae4e',
2347
+ // only me
2348
+ // webhookUrl: 'd7ac7b67-0960-4b15-a407-6d682ba77247',
2349
+ });
2350
+ }
2351
+ /**
2352
+ * 根据数据库更新白名单用户 COS 文件
2353
+ * @param options - 更新选项
2354
+ */
2355
+ async function updateWhiteUserCosByDb(options) {
2356
+ const { buildId, fromCron = false, WhiteUserDB, startPipeline, } = options || {};
2357
+ const where = {
2358
+ status: 1,
2359
+ };
2360
+ let found = [];
2361
+ if (buildId) {
2362
+ found = await WhiteUserDB.getList({
2363
+ start: 0,
2364
+ limit: 999999,
2365
+ where: {
2366
+ pipelineRunId: buildId,
2367
+ },
2368
+ });
2369
+ if (found?.[0]) {
2370
+ where.whiteType = found[0].whiteType;
2371
+ }
2372
+ }
2373
+ const list = await WhiteUserDB.getList({
2374
+ start: 0,
2375
+ limit: 999999,
2376
+ where,
2377
+ });
2378
+ const config = await getWhiteRainbowConfig();
2379
+ const files = await generateCosFile(list, config);
2380
+ for (const item of files) {
2381
+ const { addedList, deletedList, } = await getDiff(item);
2382
+ if (!addedList?.length && !deletedList.length) {
2383
+ continue;
2384
+ }
2385
+ await sendRobotMessage({
2386
+ addedList,
2387
+ deletedList,
2388
+ fileName: item.fileName,
2389
+ label: item.label,
2390
+ fromCron,
2391
+ fromWebhook: !!buildId,
2392
+ found,
2393
+ });
2394
+ await uploadToCos(item);
2395
+ await startPipeline(WHITE_USER_CDN_REFRESH_PIPELINE_ID, {});
2396
+ }
2397
+ }
2398
+ /**
2399
+ * 保存文件到本地
2400
+ * @param fileName - 文件名
2401
+ * @param fileContent - 文件内容
2402
+ * @returns 保存后的文件路径
2403
+ */
2404
+ function saveFile(fileName, fileContent) {
2405
+ const targetDir = path__namespace.resolve(__dirname, '../../../log');
2406
+ if (!fs__namespace.existsSync(targetDir)) {
2407
+ fs__namespace.mkdirSync(targetDir);
2408
+ }
2409
+ const targetFile = path__namespace.resolve(targetDir, fileName);
2410
+ tComm.writeFileSync(targetFile, { data: fileContent }, true);
2411
+ return targetFile;
2412
+ }
2413
+ /**
2414
+ * 上传文件到 COS
2415
+ * @param file - 文件信息
2416
+ * @returns 上传结果
2417
+ */
2418
+ async function uploadToCos(file) {
2419
+ const logFilename = `${file.fileName}.json`;
2420
+ const targetFile = await saveFile(logFilename, file.fileContent);
2421
+ const newFileKey = `white/${logFilename}`;
2422
+ if (file.cdn) {
2423
+ const cos = getUploadCosConfig(file.cdn);
2424
+ if (!cos) {
2425
+ throw new Error(`cdn not found: ${file.cdn}`);
2426
+ }
2427
+ await tComm.uploadCOSFile({
2428
+ secretId: cos.secretId,
2429
+ secretKey: cos.secretKey,
2430
+ bucket: cos.bucket,
2431
+ region: cos.region,
2432
+ files: [
2433
+ {
2434
+ key: newFileKey,
2435
+ path: targetFile,
2436
+ },
2437
+ ],
2438
+ }).then((res) => {
2439
+ console.log('[uploadCOSFile] res: ', res);
2440
+ })
2441
+ .catch((err) => {
2442
+ console.log('[uploadCOSFile] err: ', err);
2443
+ });
2444
+ const url = `https://${cos.bucket}.cos.${cos.region}.myqcloud.com/${newFileKey}`;
2445
+ tComm.purgeUrlCache({
2446
+ secretId: cos.secretId,
2447
+ secretKey: cos.secretKey,
2448
+ urls: [toCdnUrl(url)],
2449
+ area: getPushUrlCacheArea(file.cdn),
2450
+ })
2451
+ .then((res) => {
2452
+ console.log('[purgeUrlCache] res: ', res?.data);
2453
+ })
2454
+ .catch((err) => {
2455
+ console.log('[purgeUrlCache] err: ', err);
2456
+ });
2457
+ return;
2458
+ }
2459
+ return await tComm.uploadCOSFile({
2460
+ secretId: process.env.CLOUD_API_SECRET_ID || '',
2461
+ secretKey: process.env.CLOUD_API_SECRET_KEY || '',
2462
+ bucket: 'tip-components-1251917893',
2463
+ region: 'ap-guangzhou',
2464
+ files: [
2465
+ {
2466
+ key: newFileKey,
2467
+ path: targetFile,
2468
+ },
2469
+ ],
2470
+ });
2471
+ }
2472
+ /**
2473
+ * 生成 COS 文件内容
2474
+ * @param list - 白名单用户列表
2475
+ * @param config - 白名单配置
2476
+ * @returns 文件信息列表
2477
+ */
2478
+ function generateCosFile(list, config) {
2479
+ const targetMap = list
2480
+ .filter((item) => {
2481
+ const valid = item.startTime + item.validTime * 1000 > Date.now();
2482
+ return valid;
2483
+ })
2484
+ .reduce((acc, item) => ({
2485
+ ...acc,
2486
+ [item.whiteType]: [
2487
+ ...(acc[item.whiteType] || []),
2488
+ item,
2489
+ ],
2490
+ }), {});
2491
+ const files = Object.keys(targetMap)
2492
+ .map((key) => {
2493
+ const fileName = config?.whiteTypeMap?.[key]?.file;
2494
+ const fileContent = targetMap[key].map(item => item.openId);
2495
+ const label = config?.whiteTypeMap?.[key]?.label;
2496
+ const cdn = config?.whiteTypeMap?.[key]?.cdn;
2497
+ return {
2498
+ fileName: fileName || '',
2499
+ fileContent,
2500
+ label,
2501
+ cdn,
2502
+ };
2503
+ })
2504
+ .filter(item => item.fileName);
2505
+ return files || [];
2506
+ }
2507
+
2508
+ /**
2509
+ * 定时任务执行时间(每 10 分钟执行一次)
2510
+ */
2511
+ const CRON_TIME = '0 */10 * * * *';
2512
+ /**
2513
+ * 创建更新白名单用户 COS 的定时任务
2514
+ * @param params - 定时任务参数
2515
+ */
2516
+ function createUpdateWhiteUserCosCron({ WhiteUserDB, startPipeline, }) {
2517
+ if (process.env.NODE_ENV === 'development') {
2518
+ return;
2519
+ }
2520
+ try {
2521
+ new croner.Cron(CRON_TIME, {
2522
+ timezone: 'Asia/Shanghai',
2523
+ }, () => {
2524
+ console.log('[CRON] updateCosByDb', tComm.timeStampFormat(Date.now(), 'yyyy-MM-dd hh:mm:ss'));
2525
+ updateWhiteUserCosByDb({
2526
+ fromCron: true,
2527
+ WhiteUserDB,
2528
+ startPipeline,
2529
+ });
2530
+ });
2531
+ }
2532
+ catch (err) {
2533
+ // 忽略错误
2534
+ }
2535
+ }
2536
+
2537
+ Object.defineProperty(exports, 'NPM_PACKAGES_LIST', {
2538
+ enumerable: true,
2539
+ get: function () { return _const.NPM_PACKAGES_LIST; }
2540
+ });
2541
+ Object.defineProperty(exports, 'getCdnList', {
2542
+ enumerable: true,
2543
+ get: function () { return _const.getCdnList; }
2544
+ });
869
2545
  exports.AEGIS_EXTERNAL_SCRIPT_LINK = AEGIS_EXTERNAL_SCRIPT_LINK;
870
2546
  exports.ALL_PLATFORM = ALL_PLATFORM;
871
2547
  exports.BASE_SCSS = BASE_SCSS;
872
2548
  exports.CDN_MAP = CDN_MAP;
2549
+ exports.COS_BASE = COS_BASE;
2550
+ exports.COS_TO_GIT_REMOTE_PIPELINE_ID = COS_TO_GIT_REMOTE_PIPELINE_ID;
873
2551
  exports.CSS_MAP = CSS_MAP;
874
2552
  exports.CSS_POSTFIX_MAP = CSS_POSTFIX_MAP;
875
2553
  exports.DEFAULT_ADAPTER_DIRS = DEFAULT_ADAPTER_DIRS;
876
2554
  exports.DEFAULT_CONTEXT_OBJECT = DEFAULT_CONTEXT_OBJECT;
877
2555
  exports.DEFAULT_TRANSPILE_DEPENDENCIES = DEFAULT_TRANSPILE_DEPENDENCIES;
878
2556
  exports.EXTERNAL_LINK_MAP = EXTERNAL_LINK_MAP;
2557
+ exports.FONT_PROJECT_CDN = FONT_PROJECT_CDN;
2558
+ exports.FONT_PROJECT_COS_BASE = FONT_PROJECT_COS_BASE;
2559
+ exports.FONT_PROJECT_WEBHOOK_URL = FONT_PROJECT_WEBHOOK_URL;
879
2560
  exports.HTML_MAP = HTML_MAP;
2561
+ exports.ICON_PROJECT_CDN = ICON_PROJECT_CDN;
2562
+ exports.ICON_PROJECT_PIPELINE_CONFIG = ICON_PROJECT_PIPELINE_CONFIG;
2563
+ exports.ICON_PROJECT_WEBHOOK_URL = ICON_PROJECT_WEBHOOK_URL;
2564
+ exports.MAINLAND_CDN_LIST = MAINLAND_CDN_LIST;
880
2565
  exports.PLATFORMS_ALL = PLATFORMS_ALL;
881
2566
  exports.PLATFORMS_MP = PLATFORMS_MP;
882
2567
  exports.PLATFORM_MAP = PLATFORM_MAP;
883
2568
  exports.ROOT_NAME = ROOT_NAME;
2569
+ exports.TEMPLATE_ID_MAP = TEMPLATE_ID_MAP;
884
2570
  exports.UNI_SIMPLE_ROUTER_SCRIPT_LINK = UNI_SIMPLE_ROUTER_SCRIPT_LINK;
2571
+ exports.WHITE_USER_CDN_REFRESH_PIPELINE_ID = WHITE_USER_CDN_REFRESH_PIPELINE_ID;
2572
+ exports.WHITE_USER_PIPELINE_ID = WHITE_USER_PIPELINE_ID;
2573
+ exports.WHITE_USER_STATUS_MAP = WHITE_USER_STATUS_MAP;
2574
+ exports.addMPPublishRecord = addMPPublishRecord;
2575
+ exports.callRdMCPApi = callRdMCPApi;
885
2576
  exports.checkBundleAnalyze = checkBundleAnalyze;
886
2577
  exports.checkDebugMode = checkDebugMode;
887
2578
  exports.checkH5 = checkH5;
2579
+ exports.compressFileUseTinypng = compressFileUseTinypng;
2580
+ exports.cosSyncToGit = cosSyncToGit;
2581
+ exports.createFontProjectOrUpdate = createFontProjectOrUpdate;
2582
+ exports.createIconProjectOrUpdate = createIconProjectOrUpdate;
888
2583
  exports.createLogDir = createLogDir;
2584
+ exports.createUpdateWhiteUserCosCron = createUpdateWhiteUserCosCron;
889
2585
  exports.crossGameStyle = crossGameStyle;
2586
+ exports.extractRepoPath = extractRepoPath;
890
2587
  exports.findDependencies = findDependencies;
891
2588
  exports.findNodeModuleFile = findNodeModuleFile;
2589
+ exports.genFontProjects = genFontProjects;
2590
+ exports.genIconProjects = genIconProjects;
892
2591
  exports.genInjectContent = genInjectContent;
2592
+ exports.getAllMpPipeline = getAllMpPipeline;
893
2593
  exports.getAppPlatform = getAppPlatform;
894
2594
  exports.getCommitCode = getCommitCode;
2595
+ exports.getComponentByRepo = getComponentByRepo;
895
2596
  exports.getComponentName = getComponentName;
896
2597
  exports.getDeps = getDeps;
2598
+ exports.getEnvByBranch = getEnvByBranch;
897
2599
  exports.getGenVersionPluginOptions = getGenVersionPluginOptions;
898
2600
  exports.getImportOrderRule = getImportOrderRule;
899
2601
  exports.getLoaderFile = getLoaderFile;
900
2602
  exports.getLoaderProdFile = getLoaderProdFile;
2603
+ exports.getMcpName = getMcpName;
2604
+ exports.getMcpVersion = getMcpVersion;
2605
+ exports.getMpCICommonConfig = getMpCICommonConfig;
2606
+ exports.getMpCIKeyConfig = getMpCIKeyConfig;
901
2607
  exports.getMpInsertCode = getMpInsertCode;
902
2608
  exports.getMpVersionCode = getMpVersionCode;
2609
+ exports.getOneCdnUrl = getOneCdnUrl;
2610
+ exports.getPipelineRunLink = getPipelineRunLink;
903
2611
  exports.getPlatform = getPlatform;
904
2612
  exports.getProjectName = getProjectName;
2613
+ exports.getPushUrlCacheArea = getPushUrlCacheArea;
2614
+ exports.getRainbowConfig = getRainbowConfig;
905
2615
  exports.getRelativePath = getRelativePath;
906
2616
  exports.getRootDir = getRootDir;
907
2617
  exports.getStyleList = getStyleList;
908
2618
  exports.getSubProjectConfig = getSubProjectConfig;
909
2619
  exports.getSubProjectName = getSubProjectName;
910
2620
  exports.getSubProjectRoot = getSubProjectRoot;
2621
+ exports.getUploadCosConfig = getUploadCosConfig;
2622
+ exports.getUploadImageFilePath = getUploadImageFilePath;
911
2623
  exports.getUtsPlatform = getUtsPlatform;
912
2624
  exports.getVersionCode = getVersionCode;
913
2625
  exports.getWebInsertCode = getWebInsertCode;
2626
+ exports.getWhiteRainbowConfig = getWhiteRainbowConfig;
2627
+ exports.getWhiteTypeDesc = getWhiteTypeDesc;
914
2628
  exports.getWxmlAndWxssPostfix = getWxmlAndWxssPostfix;
2629
+ exports.handleAxiosError = handleAxiosError;
915
2630
  exports.isApp = isApp;
916
2631
  exports.isAppAndroid = isAppAndroid;
917
2632
  exports.isAppIOS = isAppIOS;
@@ -928,6 +2643,9 @@ exports.isQuickapp = isQuickapp;
928
2643
  exports.isQuickappHuawei = isQuickappHuawei;
929
2644
  exports.isQuickappUnion = isQuickappUnion;
930
2645
  exports.normalizePath = normalizePath;
2646
+ exports.parseImage = parseImage;
2647
+ exports.parseMCPCommandLineArgs = parseMCPCommandLineArgs;
2648
+ exports.parsePublishReason = parsePublishReason;
931
2649
  exports.parseSetDeps = parseSetDeps;
932
2650
  exports.recordLoaderLog = recordLoaderLog;
933
2651
  exports.removeFirstSlash = removeFirstSlash;
@@ -937,6 +2655,15 @@ exports.saveJsonToLog = saveJsonToLog;
937
2655
  exports.saveLoaderLog = saveLoaderLog;
938
2656
  exports.scssLogger = scssLogger;
939
2657
  exports.shouldUseLoader = shouldUseLoader;
2658
+ exports.startMpCIPipeline = startMpCIPipeline;
2659
+ exports.startNPMPublishPipelineCore = startNPMPublishPipelineCore;
2660
+ exports.startPipelineByRawApi = startPipelineByRawApi;
2661
+ exports.toCdnUrl = toCdnUrl;
940
2662
  exports.updateAssetSource = updateAssetSource;
941
2663
  exports.updateManifest = updateManifest;
2664
+ exports.updateMpCiConfig = updateMpCiConfig;
2665
+ exports.updateWhiteUserCosByDb = updateWhiteUserCosByDb;
2666
+ exports.uploadFilesCore = uploadFilesCore;
942
2667
  exports.vLazyCore = vLazyCore;
2668
+ exports.validateMCPCredentials = validateMCPCredentials;
2669
+ exports.watchEmails = watchEmails;