i18-fe-automator-beta 2.0.7 → 2.1.1

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 (3) hide show
  1. package/dist/commonjs/index.js +2035 -1513
  2. package/dist/esm/index.mjs +2035 -1513
  3. package/package.json +50 -50
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
  'use strict';
3
3
 
4
4
  var fs = require('fs');
@@ -25,7 +25,6 @@ var stripAnsi = require('strip-ansi');
25
25
  require('shelljs');
26
26
  var process$1 = require('child_process');
27
27
  var _ = require('lodash');
28
- require('nanoid');
29
28
  var request$1 = require('request-promise');
30
29
  require('os');
31
30
  require('console');
@@ -291,267 +290,367 @@ function readCache(key) {
291
290
  return fs.readFileSync(getRunCliPath({ root: true }) + "/.cache/" + key);
292
291
  }
293
292
 
294
- const sleep = (time) =>
295
- new Promise((resolve = 2000) => {
296
- setTimeout(() => resolve, time);
297
- });
298
- async function upload$1() {
299
- const SECRET_NAME = "sass";
300
- const SECRET_NAME_PRO = "sass_pro";
301
- let cacheSecret = {};
302
- let cacheSecretPro = {};
303
- if (!isExistPath(getRunCliPath({ root: true }) + "/.cache")) {
304
- fs.mkdirSync(getRunCliPath({ root: true }) + "/.cache");
305
- }
306
- const cacheFilePath =
307
- getRunCliPath({ root: true }) + "/.cache/" + SECRET_NAME;
308
- const cacheFilePathPro =
309
- getRunCliPath({ root: true }) + "/.cache/" + SECRET_NAME_PRO;
310
- if (isExistPath(cacheFilePath)) {
311
- cacheSecret = JSON.parse(readCache(SECRET_NAME));
312
- }
313
- if (isExistPath(cacheFilePathPro)) {
314
- cacheSecretPro = JSON.parse(readCache(SECRET_NAME_PRO));
315
- }
316
- // 是否上传
317
- inquirer
318
- .prompt([
319
- {
320
- message: "是否上传到sass平台",
321
- name: "upload",
322
- type: "confirm",
323
- default: true,
324
- },
325
- {
326
- // 选择环境
327
- message: "请选择环境",
328
- name: "env",
329
- type: "rawlist",
330
- choices: [
331
- {
332
- name: "mit",
333
- value: "mit",
334
- },
335
- {
336
- name: "sit",
337
- value: "sit",
338
- },
339
- {
340
- name: "uat",
341
- value: "uat",
342
- },
343
- {
344
- name: "pro",
345
- value: "pro",
346
- },
347
- // {
348
- // name: "一键发布pro(mit->sit->uat->pro)",
349
- // value: "topro",
350
- // },
351
- {
352
- name: "一键发布uat(mit->sit->uat)",
353
- value: "to-mit,sit,uat",
354
- },
355
- {
356
- name: "一键发布sit(mit->sit)",
357
- value: "to-mit,sit",
358
- },
359
- ],
360
- when: function (res) {
361
- return res.upload;
362
- },
363
- validate: function (val) {
364
- if (val.length > 0) {
365
- return true;
366
- }
367
- return "请选择环境";
368
- },
369
- },
370
- {
371
- message: "请输入域账号",
372
- name: "username",
373
- // 当upload为true时,展示
374
- when: function (res) {
375
- return res.upload && res.env !== "pro";
376
- },
377
- default: cacheSecret.username,
378
- // 必填
379
- validate: function (val) {
380
- if (val) {
381
- return true;
382
- }
383
- return "请输入域账号";
384
- },
385
- },
386
- {
387
- message: "请输入域密码",
388
- name: "password",
389
- default: cacheSecret.password,
390
- when: function (res) {
391
- return res.upload && res.env !== "pro";
392
- },
393
- // 必填
394
- validate: function (val) {
395
- if (val) {
396
- return true;
397
- }
398
- return "请输入域密码";
399
- },
400
- },
401
- {
402
- message: "请输入生产域账号",
403
- name: "username",
404
- // 当upload为true时,展示
405
- when: function (res) {
406
- return res.upload && res.env === "pro";
407
- },
408
- default: cacheSecretPro.username,
409
- // 必填
410
- validate: function (val) {
411
- if (val) {
412
- return true;
413
- }
414
- return "请输入生产域账号";
415
- },
416
- },
417
- {
418
- message: "请输入生产域密码",
419
- name: "password",
420
- default: cacheSecretPro.password,
421
- when: function (res) {
422
- return res.upload && res.env === "pro";
423
- },
424
- // 必填
425
- validate: function (val) {
426
- if (val) {
427
- return true;
428
- }
429
- return "请输入生产域密码";
430
- },
431
- },
432
- ])
433
- .then(async (res) => {
434
- const { upload, env, username, password } = res;
435
- // 不上传直接退出,避免后续env为undefined报错
436
- if (!upload) {
437
- return;
438
- }
439
- let envList = [];
440
- if (env.indexOf("to") > -1) {
441
- envList = env.replace("to-", "").split(",");
442
- } else {
443
- envList = [env];
444
- }
445
- if (upload) {
446
- writeCache(
447
- env === "pro" ? "sass_pro" : "sass",
448
- JSON.stringify({
449
- username,
450
- password,
451
- })
452
- );
453
- // 上传逻辑
454
- // 1.获取上传文件路径
455
- const choices = fs
456
- .readdirSync("./")
457
- .filter((item) => item.indexOf(".xlsx") > -1 || item.indexOf(".xls") > -1);
458
- // 目录下无excel文件时提前退出
459
- if (choices.length === 0) {
460
- console.log(chalk.red("当前目录下没有excel文件(.xlsx/.xls)"));
461
- return;
462
- }
463
- // 2. 选择上传文件(带全选联动)
464
- inquirer.registerPrompt("checkbox-all", CheckboxAllPrompt);
465
- // 选项拼接序号,与数字键选择对应(按1选全选,按2选第一个文件...)
466
- const promptChoices = [
467
- { name: "全选", value: SELECT_ALL, short: "全选" },
468
- ...choices.map((file) => ({ name: file, value: file, short: file })),
469
- ].map((choice, index) => ({
470
- ...choice,
471
- name: `${index + 1}. ${choice.name}`,
472
- }));
473
- inquirer
474
- .prompt([
475
- {
476
- type: "checkbox-all",
477
- name: "excelFileNames",
478
- message: "请选择要上传的excel文件(可多选)",
479
- choices: promptChoices,
480
- validate: function (val) {
481
- if (val.length > 0) {
482
- return true;
483
- }
484
- return "请选择要上传的excel文件";
485
- },
486
- },
487
- ])
488
- .then(async (res) => {
489
- let { excelFileNames } = res;
490
- // 勾选了全选则上传所有文件
491
- if (excelFileNames.includes(SELECT_ALL)) {
492
- excelFileNames = choices;
493
- }
494
- // 环境外层循环(每个环境只登录一次),文件内层循环批量上传
495
- const failList = [];
496
- for (const env of envList) {
497
- // 1.登录
498
- let token;
499
- try {
500
- token = await login({
501
- env,
502
- username,
503
- password,
504
- });
505
- } catch (e) {
506
- console.log(chalk.red(`${env} 登录失败: ${e.message || e}`));
507
- continue;
508
- }
509
- for (const excelFileName of excelFileNames) {
510
- const uploadFilePath = "./" + excelFileName;
511
- try {
512
- // 2.读取excel
513
- const projectList = await readExcel({ uploadFilePath });
514
- // 3.导入excel
515
- await importExcel({
516
- uploadFilePath,
517
- token,
518
- env,
519
- });
520
- await sleep(2000);
521
- // 4.上传
522
- await uploadExcel({
523
- data: {
524
- projectList,
525
- },
526
- token,
527
- env,
528
- });
529
- console.log(
530
- `🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀${env} ${excelFileName} 成功`
531
- );
532
- } catch (e) {
533
- console.log(
534
- chalk.red(`${env} ${excelFileName} 失败: ${e.message || e}`)
535
- );
536
- failList.push(`${env} ${excelFileName}`);
537
- }
538
- }
539
- }
540
- // 汇总失败项
541
- if (failList.length > 0) {
542
- console.log(
543
- chalk.red(`\n以下 ${failList.length} 项上传失败,请检查:\n- ${failList.join("\n- ")}`)
544
- );
545
- }
546
- // // 4.给export.xls文件名拼接上 已上传环境, 便于知道上传过的环境
547
- // const newUploadFilePath = uploadFilePath.replace(".xlsx", `${uploadFilePath.indexOf('-') > -1 ? ',' : '-'}${envList.join()}.xlsx`)
548
- // fs.renameSync(
549
- // uploadFilePath,
550
- // newUploadFilePath
551
- // );
552
- });
553
- }
554
- });
293
+ // 确保 .cache 目录存在
294
+ function ensureCacheDir() {
295
+ const cacheDir = getRunCliPath({ root: true }) + "/.cache";
296
+ if (!isExistPath(cacheDir)) {
297
+ fs.mkdirSync(cacheDir);
298
+ }
299
+ }
300
+
301
+ // 读取凭据缓存
302
+ function readSecretCache(cacheKey) {
303
+ const cacheFilePath = getRunCliPath({ root: true }) + "/.cache/" + cacheKey;
304
+ if (isExistPath(cacheFilePath)) {
305
+ try {
306
+ return JSON.parse(readCache(cacheKey));
307
+ } catch (error) {
308
+ return {};
309
+ }
310
+ }
311
+ return {};
312
+ }
313
+
314
+ // 校验参数值, 不合法则红字提示合法值并退出
315
+ function validateChoice(value, validList, optionName) {
316
+ if (!validList.includes(value)) {
317
+ console.log(
318
+ chalk.red(
319
+ `参数 ${optionName} 的值 "${value}" 不合法, 可选值: ${validList.join(
320
+ " | "
321
+ )}`
322
+ )
323
+ );
324
+ process.exit(1);
325
+ }
326
+ return value;
327
+ }
328
+
329
+ // CLI 参数 > 交互询问
330
+ async function askOrUse(cliValue, question) {
331
+ if (cliValue !== undefined && cliValue !== "") {
332
+ return cliValue;
333
+ }
334
+ const answers = await inquirer.prompt([question]);
335
+ return answers[question.name];
336
+ }
337
+
338
+ /**
339
+ * 凭据解析: CLI 参数 > --cache 缓存 > 交互(展示缓存默认值)
340
+ * 解析成功后统一回写缓存(与现状"输入即缓存"一致)
341
+ * @param questions inquirer 问题数组(含 default/validate), default 会被缓存值覆盖
342
+ * @param cliValues CLI 参数值 { 字段名: 值 }
343
+ * @param useCache 是否走 --cache
344
+ * @param cacheKey 缓存键(baidu/sass/sass_pro)
345
+ * @param extraCache 回写缓存时额外携带的字段(如 baidu 的 type)
346
+ */
347
+ async function resolveSecretQuestions({
348
+ questions,
349
+ cliValues = {},
350
+ useCache = false,
351
+ cacheKey,
352
+ extraCache = {},
353
+ }) {
354
+ ensureCacheDir();
355
+ const cacheSecret = readSecretCache(cacheKey);
356
+ const resolved = {};
357
+ const pendingQuestions = [];
358
+ questions.forEach((question) => {
359
+ const { name } = question;
360
+ const cliValue = cliValues[name];
361
+ if (cliValue !== undefined && cliValue !== "") {
362
+ resolved[name] = cliValue;
363
+ return;
364
+ }
365
+ if (useCache && cacheSecret[name]) {
366
+ resolved[name] = cacheSecret[name];
367
+ return;
368
+ }
369
+ // 未解析的字段进入交互, 默认值优先用缓存
370
+ pendingQuestions.push({
371
+ ...question,
372
+ default: cacheSecret[name] ?? question.default,
373
+ });
374
+ });
375
+ if (pendingQuestions.length) {
376
+ const answers = await inquirer.prompt(pendingQuestions);
377
+ Object.assign(resolved, answers);
378
+ }
379
+ writeCache(
380
+ cacheKey,
381
+ JSON.stringify({
382
+ ...extraCache,
383
+ ...resolved,
384
+ })
385
+ );
386
+ return resolved;
387
+ }
388
+
389
+ const sleep = (time) =>
390
+ new Promise((resolve = 2000) => {
391
+ setTimeout(() => resolve, time);
392
+ });
393
+ // 上传翻译包(支持 --env/--files/--username/--password/--cache 一键, 参数齐全时跳过确认/选择)
394
+ async function upload$1(cliOpts = {}) {
395
+ // 空字符串视为未传(与askOrUse语义一致)
396
+ const cliEnv = cliOpts.env || undefined;
397
+ const cliFiles = cliOpts.files || undefined;
398
+ const cliUsername = cliOpts.username || undefined;
399
+ const cliPassword = cliOpts.password || undefined;
400
+ const cache = Boolean(cliOpts.cache);
401
+ // 传了任一CLI参数即视为一键意图, 跳过"是否上传"确认(传参即意图)
402
+ const oneClick =
403
+ cliEnv !== undefined ||
404
+ cliFiles !== undefined ||
405
+ cliUsername !== undefined ||
406
+ cliPassword !== undefined ||
407
+ cache;
408
+ // CLI参数校验前置(快速失败, 不进交互)
409
+ if (cliEnv !== undefined) {
410
+ const envArr = cliEnv
411
+ .split(",")
412
+ .map((item) => item.trim())
413
+ .filter(Boolean);
414
+ if (envArr.length === 1) {
415
+ if (!["mit", "sit", "uat", "pro"].includes(envArr[0])) {
416
+ console.log(
417
+ chalk.red(
418
+ `参数 --env 的值 "${cliEnv}" 不合法, 可选值: mit | sit | uat | pro, 多环境逗号分隔(仅mit/sit/uat, pro须单独传)`
419
+ )
420
+ );
421
+ process.exit(1);
422
+ }
423
+ } else {
424
+ const invalid = envArr.filter(
425
+ (item) => !["mit", "sit", "uat"].includes(item)
426
+ );
427
+ if (invalid.length) {
428
+ console.log(
429
+ chalk.red(
430
+ `多环境仅支持 mit | sit | uat 逗号分隔, 不支持: ${invalid.join(
431
+ " | "
432
+ )} (pro须单独传)`
433
+ )
434
+ );
435
+ process.exit(1);
436
+ }
437
+ }
438
+ }
439
+ const excelChoices = fs
440
+ .readdirSync("./")
441
+ .filter((item) => item.indexOf(".xlsx") > -1 || item.indexOf(".xls") > -1);
442
+ if (excelChoices.length === 0) {
443
+ console.log(chalk.red("当前目录下没有excel文件(.xlsx/.xls)"));
444
+ process.exit(1);
445
+ }
446
+ let cliFileArr;
447
+ if (cliFiles !== undefined) {
448
+ if (cliFiles === "all") {
449
+ cliFileArr = excelChoices;
450
+ } else {
451
+ cliFileArr = cliFiles
452
+ .split(",")
453
+ .map((item) => item.trim())
454
+ .filter(Boolean);
455
+ const invalid = cliFileArr.filter((item) => !excelChoices.includes(item));
456
+ if (invalid.length) {
457
+ console.log(
458
+ chalk.red(`当前目录不存在excel文件: ${invalid.join(", ")}`)
459
+ );
460
+ process.exit(1);
461
+ }
462
+ }
463
+ }
464
+ // 1.是否上传(一键模式跳过, 选否属用户主动取消, exit 0)
465
+ if (!oneClick) {
466
+ const { isUpload } = await inquirer.prompt([
467
+ {
468
+ message: "是否上传到sass平台",
469
+ name: "isUpload",
470
+ type: "confirm",
471
+ default: true,
472
+ },
473
+ ]);
474
+ if (!isUpload) {
475
+ return;
476
+ }
477
+ }
478
+ // 2.选择环境(CLI参数 > 交互)
479
+ let envValue;
480
+ if (cliEnv !== undefined) {
481
+ envValue = cliEnv
482
+ .split(",")
483
+ .map((item) => item.trim())
484
+ .filter(Boolean)
485
+ .join(",");
486
+ } else {
487
+ const res = await inquirer.prompt([
488
+ {
489
+ message: "请选择环境",
490
+ name: "env",
491
+ type: "rawlist",
492
+ choices: [
493
+ {
494
+ name: "mit",
495
+ value: "mit",
496
+ },
497
+ {
498
+ name: "sit",
499
+ value: "sit",
500
+ },
501
+ {
502
+ name: "uat",
503
+ value: "uat",
504
+ },
505
+ {
506
+ name: "pro",
507
+ value: "pro",
508
+ },
509
+ {
510
+ name: "一键发布uat(mit->sit->uat)",
511
+ value: "to-mit,sit,uat",
512
+ },
513
+ {
514
+ name: "一键发布sit(mit->sit)",
515
+ value: "to-mit,sit",
516
+ },
517
+ ],
518
+ validate: function (val) {
519
+ if (val.length > 0) {
520
+ return true;
521
+ }
522
+ return "请选择环境";
523
+ },
524
+ },
525
+ ]);
526
+ envValue = res.env;
527
+ }
528
+ // 统一解析: 去掉to-前缀(兼容交互选择值) → 按逗号拆分
529
+ const envList = envValue
530
+ .replace(/^to-/, "")
531
+ .split(",")
532
+ .map((item) => item.trim())
533
+ .filter(Boolean);
534
+ const isPro = envList.length === 1 && envList[0] === "pro";
535
+ // 3.凭据: CLI参数 > --cache缓存 > 交互(展示缓存默认值), 解析后回写缓存
536
+ const secret = await resolveSecretQuestions({
537
+ questions: [
538
+ {
539
+ message: isPro ? "请输入生产域账号" : "请输入域账号",
540
+ name: "username",
541
+ // 必填
542
+ validate: function (val) {
543
+ if (val) {
544
+ return true;
545
+ }
546
+ return isPro ? "请输入生产域账号" : "请输入域账号";
547
+ },
548
+ },
549
+ {
550
+ message: isPro ? "请输入生产域密码" : "请输入域密码",
551
+ name: "password",
552
+ // 必填
553
+ validate: function (val) {
554
+ if (val) {
555
+ return true;
556
+ }
557
+ return isPro ? "请输入生产域密码" : "请输入域密码";
558
+ },
559
+ },
560
+ ],
561
+ cliValues: { username: cliUsername, password: cliPassword },
562
+ useCache: cache,
563
+ cacheKey: isPro ? "sass_pro" : "sass",
564
+ });
565
+ const { username, password } = secret;
566
+ // 4.选择上传文件(CLI参数 > 交互, 交互带全选联动)
567
+ let excelFileNames;
568
+ if (cliFileArr !== undefined) {
569
+ excelFileNames = cliFileArr;
570
+ } else {
571
+ inquirer.registerPrompt("checkbox-all", CheckboxAllPrompt);
572
+ // 选项拼接序号,与数字键选择对应(按1选全选,按2选第一个文件...)
573
+ const promptChoices = [
574
+ { name: "全选", value: SELECT_ALL, short: "全选" },
575
+ ...excelChoices.map((file) => ({ name: file, value: file, short: file })),
576
+ ].map((choice, index) => ({
577
+ ...choice,
578
+ name: `${index + 1}. ${choice.name}`,
579
+ }));
580
+ const res = await inquirer.prompt([
581
+ {
582
+ type: "checkbox-all",
583
+ name: "excelFileNames",
584
+ message: "请选择要上传的excel文件(可多选)",
585
+ choices: promptChoices,
586
+ validate: function (val) {
587
+ if (val.length > 0) {
588
+ return true;
589
+ }
590
+ return "请选择要上传的excel文件";
591
+ },
592
+ },
593
+ ]);
594
+ excelFileNames = res.excelFileNames;
595
+ // 勾选了全选则上传所有文件
596
+ if (excelFileNames.includes(SELECT_ALL)) {
597
+ excelFileNames = excelChoices;
598
+ }
599
+ }
600
+ // 环境外层循环(每个环境只登录一次),文件内层循环批量上传
601
+ const failList = [];
602
+ for (const env of envList) {
603
+ // 1.登录
604
+ let token;
605
+ try {
606
+ token = await login({
607
+ env,
608
+ username,
609
+ password,
610
+ });
611
+ } catch (e) {
612
+ console.log(chalk.red(`${env} 登录失败: ${e.message || e}`));
613
+ failList.push(`${env} 登录`);
614
+ continue;
615
+ }
616
+ for (const excelFileName of excelFileNames) {
617
+ const uploadFilePath = "./" + excelFileName;
618
+ try {
619
+ // 2.读取excel
620
+ const projectList = await readExcel({ uploadFilePath });
621
+ // 3.导入excel
622
+ await importExcel({
623
+ uploadFilePath,
624
+ token,
625
+ env,
626
+ });
627
+ await sleep(2000);
628
+ // 4.上传
629
+ await uploadExcel({
630
+ data: {
631
+ projectList,
632
+ },
633
+ token,
634
+ env,
635
+ });
636
+ console.log(
637
+ `🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀${env} ${excelFileName} 成功`
638
+ );
639
+ } catch (e) {
640
+ console.log(
641
+ chalk.red(`${env} ${excelFileName} 失败: ${e.message || e}`)
642
+ );
643
+ failList.push(`${env} ${excelFileName}`);
644
+ }
645
+ }
646
+ }
647
+ // 汇总失败项(登录失败也计入, 统一失败退出码1)
648
+ if (failList.length > 0) {
649
+ console.log(
650
+ chalk.red(`\n以下 ${failList.length} 项上传失败,请检查:\n- ${failList.join("\n- ")}`)
651
+ );
652
+ process.exit(1);
653
+ }
555
654
  }
556
655
 
557
656
  // 百度翻译接口
@@ -757,429 +856,762 @@ function convertHTMLToPlaceholders(html, prefix = "N") {
757
856
  // let str = `<div class="translate">apple<p class="aa test">test</p>word</div>`;
758
857
  // console.log(convertHTMLToPlaceholders(str));
759
858
 
760
- function excelFn () {
761
- // 1.获取上传文件路径
762
- const choices = fs
763
- .readdirSync("./")
764
- .filter((item) => item.indexOf(".xlsx") > -1 || item.indexOf(".xls") > -1);
765
- // 2. 选择上传文件
766
- inquirer
767
- .prompt([
768
- {
769
- type: "rawlist",
770
- name: "excelFileName",
771
- message: "请选择要上传的excel文件",
772
- choices,
773
- validate: function (val) {
774
- if (val) {
775
- return true;
776
- }
777
- return "请选择项目";
778
- },
779
- },
780
- ])
781
- .then(async (res) => {
782
- const { excelFileName } = res;
783
- // 2.读取excel
784
- const uploadFilePath = "./" + excelFileName;
785
- const workSheetsFromFile = xlsx.parse(uploadFilePath);
786
- const data = workSheetsFromFile[0].data;
787
- // 获取第一行
788
- const titleList = data[0];
789
- const zhIndex = titleList.findIndex((item) => item === "zhCn");
790
- const enIndex = titleList.findIndex((item) => item === "en");
791
- const languageList = titleList.slice(enIndex);
792
- const { language } = await inquirer.prompt([
793
- {
794
- type: "rawlist",
795
- name: "language",
796
- message: `请选择要翻译的语言\n${chalk.red("注意:\n")}${chalk.green(
797
- "1. 英文基于中文翻译\n2. 非英文基于英文翻译"
798
- )}`,
799
- choices: languageList,
800
- validate: function (val) {
801
- if (val) {
802
- return true;
803
- }
804
- return "请选择";
805
- },
806
- },
807
- {
808
- type: "confirm", // 获取上一个选中结果
809
- name: "isTranslate",
810
- message: ({ language }) => {
811
- return `是否翻译${chalk.red(language)}`;
812
- },
813
- default: true,
814
- prefix: "Y", // 添加前缀符号
815
- },
816
- ]);
817
- const content = data.slice(1);
818
- const zhArr = content.map((row) => {
819
- return {
820
- from: row[zhIndex],
821
- };
822
- });
823
- const enArr = content.map((row) => {
824
- return {
825
- from: row[enIndex],
826
- };
827
- });
828
- let transArr = language === "en" ? zhArr : enArr;
829
- await inquirer
830
- .prompt([
831
- {
832
- message: "请选择翻译接口",
833
- name: "type",
834
- type: "list",
835
- choices: [
836
- {
837
- name: "百度翻译",
838
- value: "baidu",
839
- },
840
- ],
841
- },
842
- ])
843
- .then((res) => {
844
- const { type } = res;
845
- let cacheSecret = {};
846
- if (!isExistPath(getRunCliPath({ root: true }) + "/.cache")) {
847
- fs.mkdirSync(getRunCliPath({ root: true }) + "/.cache");
848
- }
849
- const cacheFilePath =
850
- getRunCliPath({ root: true }) + "/.cache/" + res.type;
851
- if (isExistPath(cacheFilePath)) {
852
- const cache = JSON.parse(readCache(res.type));
853
- cacheSecret = cache;
854
- }
855
- const question = [
856
- {
857
- message: "请输入百度翻译的appid",
858
- name: "appid",
859
- default: cacheSecret.appid,
860
- // 必填
861
- validate: function (val) {
862
- if (val) {
863
- return true;
864
- }
865
- return "请输入百度翻译的appid";
866
- },
867
- },
868
- {
869
- message: "请输入百度翻译的key",
870
- name: "key",
871
- default: cacheSecret.key,
872
- // 必填
873
- validate: function (val) {
874
- if (val) {
875
- return true;
876
- }
877
- return "请输入百度翻译的key";
878
- },
879
- },
880
- ];
881
- inquirer.prompt(question).then((res) => {
882
- writeCache(
883
- type,
884
- JSON.stringify({
885
- type,
886
- ...res,
887
- })
888
- );
889
- const { appid, key } = res;
890
- const baiduLang = {
891
- vi: "vie", // 越南语
892
- };
893
- translate(transArr, {
894
- originData: data,
895
- excelFileName,
896
- appid,
897
- key,
898
- from: language === "en" ? "zh" : "en",
899
- to: baiduLang[language] || language,
900
- toIndex: titleList.findIndex((item) => item === language),
901
- });
902
- });
903
- });
904
- });
859
+ // 翻译excel指定列语言(支持 --file/--lang/--appid/--key/--cache 一键, file+lang齐全时跳过选择)
860
+ async function excelFn(cliOpts = {}) {
861
+ // 空字符串视为未传(与askOrUse语义一致)
862
+ const cliFile = cliOpts.file || undefined;
863
+ const cliLang = cliOpts.lang || undefined;
864
+ const cliAppid = cliOpts.appid || undefined;
865
+ const cliKey = cliOpts.key || undefined;
866
+ const cache = Boolean(cliOpts.cache);
867
+ // 1.获取excel文件列表, CLI传入file先校验存在性(快速失败, 不进交互)
868
+ const choices = fs
869
+ .readdirSync("./")
870
+ .filter((item) => item.indexOf(".xlsx") > -1 || item.indexOf(".xls") > -1);
871
+ if (choices.length === 0) {
872
+ console.log(chalk.red("当前目录下没有excel文件(.xlsx/.xls)"));
873
+ process.exit(1);
874
+ }
875
+ if (cliFile !== undefined && !choices.includes(cliFile)) {
876
+ console.log(chalk.red(`当前目录不存在excel文件: ${cliFile}`));
877
+ process.exit(1);
878
+ }
879
+ // 2.选择excel文件(CLI参数 > 交互)
880
+ let excelFileName;
881
+ if (cliFile !== undefined) {
882
+ excelFileName = cliFile;
883
+ } else {
884
+ const res = await inquirer.prompt([
885
+ {
886
+ type: "rawlist",
887
+ name: "excelFileName",
888
+ message: "请选择要上传的excel文件",
889
+ choices,
890
+ validate: function (val) {
891
+ if (val) {
892
+ return true;
893
+ }
894
+ return "请选择项目";
895
+ },
896
+ },
897
+ ]);
898
+ excelFileName = res.excelFileName;
899
+ }
900
+ // 3.读取excel
901
+ const uploadFilePath = "./" + excelFileName;
902
+ const workSheetsFromFile = xlsx.parse(uploadFilePath);
903
+ const data = workSheetsFromFile[0].data;
904
+ // 获取第一行
905
+ const titleList = data[0];
906
+ const zhIndex = titleList.findIndex((item) => item === "zhCn");
907
+ const enIndex = titleList.findIndex((item) => item === "en");
908
+ const languageList = titleList.slice(enIndex);
909
+ // 4.选择语言列(CLI参数 > 交互), CLI传入先校验在该excel语言列中(快速失败)
910
+ let language;
911
+ if (cliLang !== undefined) {
912
+ if (!languageList.includes(cliLang)) {
913
+ console.log(
914
+ chalk.red(
915
+ `参数 --lang 的值 "${cliLang}" 不在该excel的语言列中, 可选值: ${languageList.join(
916
+ " | "
917
+ )}`
918
+ )
919
+ );
920
+ process.exit(1);
921
+ }
922
+ language = cliLang;
923
+ } else {
924
+ const res = await inquirer.prompt([
925
+ {
926
+ type: "rawlist",
927
+ name: "language",
928
+ message: `请选择要翻译的语言\n${chalk.red("注意:\n")}${chalk.green(
929
+ "1. 英文基于中文翻译\n2. 非英文基于英文翻译"
930
+ )}`,
931
+ choices: languageList,
932
+ validate: function (val) {
933
+ if (val) {
934
+ return true;
935
+ }
936
+ return "请选择";
937
+ },
938
+ },
939
+ {
940
+ type: "confirm", // 获取上一个选中结果
941
+ name: "isTranslate",
942
+ message: ({ language }) => {
943
+ return `是否翻译${chalk.red(language)}`;
944
+ },
945
+ default: true,
946
+ prefix: "Y", // 添加前缀符号
947
+ },
948
+ ]);
949
+ language = res.language;
950
+ // 用户选择不翻译则直接退出(一键模式传 --lang 视为翻译, 不进入该询问)
951
+ if (!res.isTranslate) {
952
+ console.log(chalk.green(`已跳过${language}列翻译`));
953
+ return;
954
+ }
955
+ }
956
+ const content = data.slice(1);
957
+ const zhArr = content.map((row) => {
958
+ return {
959
+ from: row[zhIndex],
960
+ };
961
+ });
962
+ const enArr = content.map((row) => {
963
+ return {
964
+ from: row[enIndex],
965
+ };
966
+ });
967
+ let transArr = language === "en" ? zhArr : enArr;
968
+ // 5.凭据: CLI参数 > --cache缓存 > 交互(展示缓存默认值), 翻译接口当前仅百度, 一键时自动选择
969
+ const secret = await resolveSecretQuestions({
970
+ questions: [
971
+ {
972
+ message: "请输入百度翻译的appid",
973
+ name: "appid",
974
+ // 必填
975
+ validate: function (val) {
976
+ if (val) {
977
+ return true;
978
+ }
979
+ return "请输入百度翻译的appid";
980
+ },
981
+ },
982
+ {
983
+ message: "请输入百度翻译的key",
984
+ name: "key",
985
+ // 必填
986
+ validate: function (val) {
987
+ if (val) {
988
+ return true;
989
+ }
990
+ return "请输入百度翻译的key";
991
+ },
992
+ },
993
+ ],
994
+ cliValues: { appid: cliAppid, key: cliKey },
995
+ useCache: cache,
996
+ cacheKey: "baidu",
997
+ extraCache: { type: "baidu" },
998
+ });
999
+ const { appid, key } = secret;
1000
+ const baiduLang = {
1001
+ vi: "vie", // 越南语
1002
+ };
1003
+ translate(transArr, {
1004
+ originData: data,
1005
+ excelFileName,
1006
+ appid,
1007
+ key,
1008
+ from: language === "en" ? "zh" : "en",
1009
+ to: baiduLang[language] || language,
1010
+ toIndex: titleList.findIndex((item) => item === language),
1011
+ });
905
1012
  }
906
1013
 
907
- // 获取应用list
908
- function getApplicationList({ appName, env, token }) {
909
- return new Promise((resolve, reject) => {
910
- request(
911
- {
912
- url: `https://${
913
- env === "pro" ? "" : `${env}-`
914
- }hxjf.hongxinshop.com/api-u/api/saas/app/nameList`,
915
- method: "POST",
916
- json: true,
917
- body: { name: appName, pageNo: 1, pageSize: 50 },
918
- headers: {
919
- Authorization: `Bearer ${token}`,
920
- "x-request-vaildate": "open",
921
- },
922
- },
923
- function (error, response, body) {
924
- const res = body;
925
- if (!error && res.code == 200) {
926
- resolve(
927
- res.data.map((item) => {
928
- // return {
929
- // name: item.name,
930
- // value: item.id,
931
- // };
932
- return {
933
- name: item.name,
934
- value: item.name,
935
- id: item.id,
936
- code: item.code,
937
- };
938
- })
939
- );
940
- } else {
941
- console.log(chalk.red(error || JSON.stringify(res.data)));
942
- }
943
- }
944
- );
945
- });
946
- }
947
- // 获取应用按钮权限配置
948
- function getApplicationButtonConfig({ appId, env, token }) {
949
- Loading$1.start(`获取按钮权限配置中...`);
950
- return new Promise((resolve, reject) => {
951
- request(
952
- {
953
- url: `https://${
954
- env === "pro" ? "" : `${env}-`
955
- }hxjf.hongxinshop.com/api-u/api/saas/permission/list`,
956
- method: "POST",
957
- json: true,
958
- body: { id: appId },
959
- headers: {
960
- Authorization: `Bearer ${token}`,
961
- "x-request-vaildate": "open",
962
- },
963
- },
964
- function (error, response, body) {
965
- const res = body;
966
- if (!error && res.code == 200) {
967
- Loading$1.succeed(`获取${env}环境按钮权限配置成功`);
968
- resolve(res.data);
969
- } else {
970
- Loading$1.fail(`获取${env}环境按钮权限配置失败`);
971
- console.log(chalk.red(error || JSON.stringify(res.data)));
972
- }
973
- }
974
- );
975
- });
976
- }
977
- // 删除应用按钮权限
978
- function deleteApplicationButtonConfig({ data, env, token }) {
979
- return new Promise((resolve, reject) => {
980
- request(
981
- {
982
- url: `https://${
983
- env === "pro" ? "" : `${env}-`
984
- }hxjf.hongxinshop.com/api-u/api/saas/permission/del`,
985
- method: "POST",
986
- json: true,
987
- body: data,
988
- headers: {
989
- Authorization: `Bearer ${token}`,
990
- "x-request-vaildate": "open",
991
- },
992
- },
993
- function (error, response, body) {
994
- const res = body;
995
- if (!error && res.code == 200) {
996
- resolve(res.data);
997
- } else {
998
- const errorData = error || JSON.stringify(res.data);
999
- console.log(chalk.red(errorData));
1000
- reject(errorData);
1001
- }
1002
- }
1003
- );
1004
- });
1005
- }
1006
- // 新增应用按钮权限
1007
- function addApplicationButtonConfig({ data, env, token }) {
1008
- Loading$1.start(`新增按钮权限 ${data.code} 中...`);
1009
- return new Promise((resolve, reject) => {
1010
- request(
1011
- {
1012
- url: `https://${
1013
- env === "pro" ? "" : `${env}-`
1014
- }hxjf.hongxinshop.com/api-u/api/saas/permission/add`,
1015
- method: "POST",
1016
- json: true,
1017
- body: data,
1018
- headers: {
1019
- Authorization: `Bearer ${token}`,
1020
- "x-request-vaildate": "open",
1021
- },
1022
- },
1023
- function (error, response, body) {
1024
- const res = body;
1025
- if (!error && res.code == 200) {
1026
- Loading$1.succeed(`${data.code}`);
1027
- resolve(true);
1028
- } else {
1029
- Loading$1.fail(`${data.code}`);
1030
- console.log(chalk.red(error || JSON.stringify(res.data)));
1031
- }
1032
- }
1033
- );
1034
- });
1035
- }
1036
- async function syncSassConfig() {
1037
- inquirer
1038
- .prompt([
1039
- {
1040
- message: `使用须知:
1041
- ${chalk.green(`
1042
- 1.此命令会${chalk.red('强制覆盖')}[from]->[to]环境配置,${chalk.red('不会合并')}!!! \n
1043
- 2.请确保同步前已经在目标环境创建了应用 \n
1044
- 3.从mit->sit/uat,建议先同步sit->mit以保证mit环境的配置是最新的
1045
- `)}
1046
- `,
1047
- name: "notice",
1048
- type: "confirm",
1049
- default: false,
1050
- },
1051
- {
1052
- message: "请选择从哪个环境同步配置",
1053
- name: "from",
1054
- type: "list",
1055
- choices: ["mit", "sit", "uat"],
1056
- when: (answers) => answers.notice,
1057
- },
1058
- {
1059
- message: "请选择同步至哪个环境",
1060
- name: "to",
1061
- type: "list",
1062
- when: (answers) => answers.notice,
1063
- choices: (answers) => {
1064
- const list = ["mit", "sit", "uat"];
1065
- return list.filter((item) => item !== answers.from);
1066
- },
1067
- },
1068
- ])
1069
- .then(async (answers) => {
1070
- const { from, to,notice } = answers;
1071
- if(!notice){
1072
- return
1073
- }
1074
- let token;
1075
- try {
1076
- token = await login({
1077
- env: from,
1078
- username: "superAdmin",
1079
- password: "admin1",
1080
- });
1081
- } catch (e) {
1082
- console.log(chalk.red(`${from} 登录失败: ${e.message || e}`));
1083
- return;
1084
- }
1085
- const { inputAppName } = await inquirer.prompt([
1086
- {
1087
- message: "请输入应用名称关键字",
1088
- name: "inputAppName",
1089
- type: "input",
1090
- validate: function (value) {
1091
- if (value) {
1092
- return true;
1093
- }
1094
- return "请输入应用名称";
1095
- },
1096
- },
1097
- ]);
1098
- const applicationList = await getApplicationList({
1099
- env: from,
1100
- token,
1101
- appName: inputAppName,
1102
- });
1103
- const { appName } = await inquirer.prompt([
1104
- {
1105
- message: "请选择应用名称",
1106
- name: "appName",
1107
- type: "rawlist",
1108
- choices: applicationList,
1109
- },
1110
- ]);
1111
- const appId = applicationList.find((item) => item.name === appName).id;
1112
- // let appConfig = await getApplicationConfig({ appId, env: from, token });
1113
- const fromButtonList = await getApplicationButtonConfig({
1114
- appId,
1115
- env: from,
1116
- token,
1117
- });
1118
- if (!fromButtonList.length) {
1119
- return console.log(chalk.red("按钮权限配置为空,无法同步"));
1120
- }
1121
- console.log(`开始同步到${to}🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀`);
1122
- let toToken;
1123
- try {
1124
- toToken = await login({
1125
- env: to,
1126
- username: "superAdmin",
1127
- password: "admin1",
1128
- });
1129
- } catch (e) {
1130
- console.log(chalk.red(`${to} 登录失败: ${e.message || e}`));
1131
- return;
1132
- }
1133
- const toApplicationList = await getApplicationList({
1134
- env: to,
1135
- token: toToken,
1136
- appName,
1137
- });
1138
- if (!toApplicationList.length) {
1139
- return console.log(chalk.red(`${to}环境不存在该应用`));
1140
- }
1141
- const toAppId = toApplicationList[0].id;
1142
- const toButtonList = await getApplicationButtonConfig({
1143
- appId: toAppId,
1144
- env: to,
1145
- token: toToken,
1146
- });
1147
- try {
1148
- if (toButtonList.length) {
1149
- const deleteButtonPromiseList = toButtonList.map((item) => {
1150
- return deleteApplicationButtonConfig({
1151
- data: {
1152
- id: item.id,
1153
- ver: item.ver,
1154
- },
1155
- env: to,
1156
- token: toToken,
1157
- });
1158
- });
1159
- Loading$1.start(`删除${to}环境按钮权限配置中...`);
1160
- await Promise.all(deleteButtonPromiseList);
1161
- Loading$1.succeed(`删除${to}环境按钮权限配置成功`);
1162
- }
1163
- } catch (error) {
1164
- console.log(chalk.red(`删除${to}环境按钮权限配置失败`));
1165
- } finally {
1166
- for (const item of fromButtonList) {
1167
- const { code, name, visitConf } = item;
1168
- await addApplicationButtonConfig({
1169
- env: to,
1170
- token: toToken,
1171
- data: {
1172
- code1: code.split(":")[0],
1173
- code,
1174
- name,
1175
- visitConf,
1176
- ascriptionApp: toAppId,
1177
- },
1178
- });
1179
- }
1180
- console.log(chalk.green("同步成功"));
1181
- }
1182
- });
1014
+ // 获取应用list
1015
+ function getApplicationList({ appName, env, token }) {
1016
+ return new Promise((resolve, reject) => {
1017
+ request(
1018
+ {
1019
+ url: `https://${
1020
+ env === "pro" ? "" : `${env}-`
1021
+ }hxjf.hongxinshop.com/api-u/api/saas/app/nameList`,
1022
+ method: "POST",
1023
+ json: true,
1024
+ body: { name: appName, pageNo: 1, pageSize: 50 },
1025
+ headers: {
1026
+ Authorization: `Bearer ${token}`,
1027
+ "x-request-vaildate": "open",
1028
+ },
1029
+ },
1030
+ function (error, response, body) {
1031
+ const res = body;
1032
+ if (!error && res.code == 200) {
1033
+ resolve(
1034
+ res.data.map((item) => {
1035
+ // return {
1036
+ // name: item.name,
1037
+ // value: item.id,
1038
+ // };
1039
+ return {
1040
+ name: item.name,
1041
+ value: item.name,
1042
+ id: item.id,
1043
+ code: item.code,
1044
+ };
1045
+ })
1046
+ );
1047
+ } else {
1048
+ const errorData = error || JSON.stringify(res.data);
1049
+ console.log(chalk.red(errorData));
1050
+ reject(errorData);
1051
+ }
1052
+ }
1053
+ );
1054
+ });
1055
+ }
1056
+ // 获取应用配置
1057
+ function getApplicationConfig({ appId, env, token }) {
1058
+ Loading$1.start(`获取应用配置中...`);
1059
+ return new Promise((resolve, reject) => {
1060
+ request(
1061
+ {
1062
+ url: `https://${
1063
+ env === "pro" ? "" : `${env}-`
1064
+ }hxjf.hongxinshop.com/api-u/api/saas/app/info`,
1065
+ method: "POST",
1066
+ json: true,
1067
+ body: { id: appId },
1068
+ headers: {
1069
+ Authorization: `Bearer ${token}`,
1070
+ "x-request-vaildate": "open",
1071
+ },
1072
+ },
1073
+ function (error, response, body) {
1074
+ const res = body;
1075
+ if (!error && res.code == 200) {
1076
+ Loading$1.succeed(`获取应用配置成功`);
1077
+ resolve(res.data);
1078
+ } else {
1079
+ Loading$1.fail(`获取应用配置失败`);
1080
+ const errorData = error || JSON.stringify(res.data);
1081
+ console.log(chalk.red(errorData));
1082
+ reject(errorData);
1083
+ }
1084
+ }
1085
+ );
1086
+ });
1087
+ }
1088
+ // 获取应用按钮权限配置
1089
+ function getApplicationButtonConfig({ appId, env, token }) {
1090
+ Loading$1.start(`获取按钮权限配置中...`);
1091
+ return new Promise((resolve, reject) => {
1092
+ request(
1093
+ {
1094
+ url: `https://${
1095
+ env === "pro" ? "" : `${env}-`
1096
+ }hxjf.hongxinshop.com/api-u/api/saas/permission/list`,
1097
+ method: "POST",
1098
+ json: true,
1099
+ body: { id: appId },
1100
+ headers: {
1101
+ Authorization: `Bearer ${token}`,
1102
+ "x-request-vaildate": "open",
1103
+ },
1104
+ },
1105
+ function (error, response, body) {
1106
+ const res = body;
1107
+ if (!error && res.code == 200) {
1108
+ Loading$1.succeed(`获取${env}环境按钮权限配置成功`);
1109
+ resolve(res.data);
1110
+ } else {
1111
+ Loading$1.fail(`获取${env}环境按钮权限配置失败`);
1112
+ const errorData = error || JSON.stringify(res.data);
1113
+ console.log(chalk.red(errorData));
1114
+ reject(errorData);
1115
+ }
1116
+ }
1117
+ );
1118
+ });
1119
+ }
1120
+ // 删除应用按钮权限
1121
+ function deleteApplicationButtonConfig({ data, env, token }) {
1122
+ return new Promise((resolve, reject) => {
1123
+ request(
1124
+ {
1125
+ url: `https://${
1126
+ env === "pro" ? "" : `${env}-`
1127
+ }hxjf.hongxinshop.com/api-u/api/saas/permission/del`,
1128
+ method: "POST",
1129
+ json: true,
1130
+ body: data,
1131
+ headers: {
1132
+ Authorization: `Bearer ${token}`,
1133
+ "x-request-vaildate": "open",
1134
+ },
1135
+ },
1136
+ function (error, response, body) {
1137
+ const res = body;
1138
+ if (!error && res.code == 200) {
1139
+ resolve(res.data);
1140
+ } else {
1141
+ const errorData = error || JSON.stringify(res.data);
1142
+ console.log(chalk.red(errorData));
1143
+ reject(errorData);
1144
+ }
1145
+ }
1146
+ );
1147
+ });
1148
+ }
1149
+ // 新增应用按钮权限
1150
+ function addApplicationButtonConfig({ data, env, token }) {
1151
+ Loading$1.start(`新增按钮权限 ${data.code} 中...`);
1152
+ return new Promise((resolve, reject) => {
1153
+ request(
1154
+ {
1155
+ url: `https://${
1156
+ env === "pro" ? "" : `${env}-`
1157
+ }hxjf.hongxinshop.com/api-u/api/saas/permission/add`,
1158
+ method: "POST",
1159
+ json: true,
1160
+ body: data,
1161
+ headers: {
1162
+ Authorization: `Bearer ${token}`,
1163
+ "x-request-vaildate": "open",
1164
+ },
1165
+ },
1166
+ function (error, response, body) {
1167
+ const res = body;
1168
+ if (!error && res.code == 200) {
1169
+ Loading$1.succeed(`${data.code}`);
1170
+ resolve(true);
1171
+ } else {
1172
+ Loading$1.fail(`${data.code}`);
1173
+ const errorData = error || JSON.stringify(res.data);
1174
+ console.log(chalk.red(errorData));
1175
+ reject(errorData);
1176
+ }
1177
+ }
1178
+ );
1179
+ });
1180
+ }
1181
+ // -sass 入口: 功能路由(--action 显式指定 > --from/--to 推断同步 > --permission-* 推断新增 > 菜单)
1182
+ async function sassMain(rawCliOpts = {}) {
1183
+ // 空字符串视为未传(与askOrUse语义一致)
1184
+ const sassOpts = {
1185
+ ...rawCliOpts,
1186
+ action: rawCliOpts.action || undefined,
1187
+ from: rawCliOpts.from || undefined,
1188
+ to: rawCliOpts.to || undefined,
1189
+ app: rawCliOpts.app || undefined,
1190
+ env: rawCliOpts.env || undefined,
1191
+ permissionName: rawCliOpts.permissionName || undefined,
1192
+ permissionCode: rawCliOpts.permissionCode || undefined,
1193
+ };
1194
+ const { action, from, to, permissionName, permissionCode } = sassOpts;
1195
+ if (action !== undefined) {
1196
+ validateChoice(action, ["sync", "permission"], "--action");
1197
+ return action === "permission"
1198
+ ? addSassPermission(sassOpts)
1199
+ : syncSassFlow(sassOpts);
1200
+ }
1201
+ if (from !== undefined || to !== undefined) {
1202
+ return syncSassFlow(sassOpts);
1203
+ }
1204
+ if (permissionName !== undefined || permissionCode !== undefined) {
1205
+ return addSassPermission(sassOpts);
1206
+ }
1207
+ const { menuAction } = await inquirer.prompt([
1208
+ {
1209
+ message: "请选择功能",
1210
+ name: "menuAction",
1211
+ type: "list",
1212
+ choices: [
1213
+ { name: "同步按钮权限配置", value: "sync" },
1214
+ { name: "新增按钮权限", value: "permission" },
1215
+ ],
1216
+ },
1217
+ ]);
1218
+ return menuAction === "permission"
1219
+ ? addSassPermission(sassOpts)
1220
+ : syncSassFlow(sassOpts);
1221
+ }
1222
+
1223
+ // 同步流程(支持 --from/--to/--app 一键, 参数齐全时跳过使用须知)
1224
+ async function syncSassFlow(cliOpts) {
1225
+ const { from: cliFrom, to: cliTo, app: cliApp } = cliOpts;
1226
+ const envList = ["mit", "sit", "uat"];
1227
+ // 传了任一同步参数即视为一键意图, 自动同意使用须知(传参即意图)
1228
+ const oneClick =
1229
+ cliFrom !== undefined || cliTo !== undefined || cliApp !== undefined;
1230
+ if (!oneClick) {
1231
+ const { notice } = await inquirer.prompt([
1232
+ {
1233
+ message: `使用须知:
1234
+ ${chalk.green(`
1235
+ 1.此命令会${chalk.red('强制覆盖')}[from]->[to]环境配置,${chalk.red('不会合并')}!!! \n
1236
+ 2.请确保同步前已经在目标环境创建了应用 \n
1237
+ 3.从mit->sit/uat,建议先同步sit->mit以保证mit环境的配置是最新的
1238
+ `)}
1239
+ `,
1240
+ name: "notice",
1241
+ type: "confirm",
1242
+ default: false,
1243
+ },
1244
+ ]);
1245
+ if (!notice) {
1246
+ return;
1247
+ }
1248
+ }
1249
+ if (cliFrom !== undefined) {
1250
+ validateChoice(cliFrom, envList, "--from");
1251
+ }
1252
+ const from = await askOrUse(cliFrom, {
1253
+ message: "请选择从哪个环境同步配置",
1254
+ name: "from",
1255
+ type: "list",
1256
+ choices: envList,
1257
+ });
1258
+ if (cliTo !== undefined) {
1259
+ validateChoice(cliTo, envList, "--to");
1260
+ if (cliTo === from) {
1261
+ console.log(chalk.red("参数 --to 的值须不同于 --from"));
1262
+ process.exit(1);
1263
+ }
1264
+ }
1265
+ const to = await askOrUse(cliTo, {
1266
+ message: "请选择同步至哪个环境",
1267
+ name: "to",
1268
+ type: "list",
1269
+ choices: envList.filter((item) => item !== from),
1270
+ });
1271
+ let token;
1272
+ try {
1273
+ token = await login({
1274
+ env: from,
1275
+ username: "superAdmin",
1276
+ password: "admin1",
1277
+ });
1278
+ } catch (e) {
1279
+ console.log(chalk.red(`${from} 登录失败: ${e.message || e}`));
1280
+ process.exit(1);
1281
+ }
1282
+ const inputAppName = await askOrUse(cliApp, {
1283
+ message: "请输入应用名称关键字",
1284
+ name: "inputAppName",
1285
+ type: "input",
1286
+ validate: function (value) {
1287
+ if (value) {
1288
+ return true;
1289
+ }
1290
+ return "请输入应用名称";
1291
+ },
1292
+ });
1293
+ let applicationList;
1294
+ try {
1295
+ applicationList = await getApplicationList({
1296
+ env: from,
1297
+ token,
1298
+ appName: inputAppName,
1299
+ });
1300
+ } catch (e) {
1301
+ console.log(chalk.red(`查询${from}环境应用列表失败: ${e.message || e}`));
1302
+ process.exit(1);
1303
+ }
1304
+ if (!applicationList.length) {
1305
+ console.log(chalk.red(`${from}环境未找到该应用`));
1306
+ process.exit(1);
1307
+ }
1308
+ // 一键模式: 唯一匹配或精确匹配CLI传入的应用名时自动选中(与新增按钮权限流程一致)
1309
+ let appName;
1310
+ if (applicationList.length === 1) {
1311
+ appName = applicationList[0].name;
1312
+ console.log(chalk.gray(`唯一匹配,已自动选中: ${appName}`));
1313
+ } else if (oneClick) {
1314
+ const exactMatch = applicationList.find((item) => item.name === cliApp);
1315
+ if (!exactMatch) {
1316
+ console.log(
1317
+ chalk.red(`${from}环境未精确匹配到应用: ${cliApp}, 请更换关键字`)
1318
+ );
1319
+ process.exit(1);
1320
+ }
1321
+ appName = exactMatch.name;
1322
+ console.log(chalk.gray(`精确匹配,已自动选中: ${appName}`));
1323
+ } else {
1324
+ const res = await inquirer.prompt([
1325
+ {
1326
+ message: "请选择应用名称",
1327
+ name: "appName",
1328
+ type: "rawlist",
1329
+ choices: applicationList,
1330
+ },
1331
+ ]);
1332
+ appName = res.appName;
1333
+ }
1334
+ const appId = applicationList.find((item) => item.name === appName).id;
1335
+ let fromButtonList;
1336
+ try {
1337
+ fromButtonList = await getApplicationButtonConfig({
1338
+ appId,
1339
+ env: from,
1340
+ token,
1341
+ });
1342
+ } catch (e) {
1343
+ console.log(
1344
+ chalk.red(`获取${from}环境按钮权限配置失败: ${e.message || e}`)
1345
+ );
1346
+ process.exit(1);
1347
+ }
1348
+ if (!fromButtonList.length) {
1349
+ console.log(chalk.red("按钮权限配置为空,无法同步"));
1350
+ process.exit(1);
1351
+ }
1352
+ console.log(`开始同步到${to}🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀`);
1353
+ let toToken;
1354
+ try {
1355
+ toToken = await login({
1356
+ env: to,
1357
+ username: "superAdmin",
1358
+ password: "admin1",
1359
+ });
1360
+ } catch (e) {
1361
+ console.log(chalk.red(`${to} 登录失败: ${e.message || e}`));
1362
+ process.exit(1);
1363
+ }
1364
+ let toApplicationList;
1365
+ try {
1366
+ toApplicationList = await getApplicationList({
1367
+ env: to,
1368
+ token: toToken,
1369
+ appName,
1370
+ });
1371
+ } catch (e) {
1372
+ console.log(chalk.red(`查询${to}环境应用列表失败: ${e.message || e}`));
1373
+ process.exit(1);
1374
+ }
1375
+ if (!toApplicationList.length) {
1376
+ console.log(chalk.red(`${to}环境不存在该应用`));
1377
+ process.exit(1);
1378
+ }
1379
+ const toAppId = toApplicationList[0].id;
1380
+ let toButtonList;
1381
+ try {
1382
+ toButtonList = await getApplicationButtonConfig({
1383
+ appId: toAppId,
1384
+ env: to,
1385
+ token: toToken,
1386
+ });
1387
+ } catch (e) {
1388
+ console.log(chalk.red(`获取${to}环境按钮权限配置失败: ${e.message || e}`));
1389
+ process.exit(1);
1390
+ }
1391
+ try {
1392
+ if (toButtonList.length) {
1393
+ const deleteButtonPromiseList = toButtonList.map((item) => {
1394
+ return deleteApplicationButtonConfig({
1395
+ data: {
1396
+ id: item.id,
1397
+ ver: item.ver,
1398
+ },
1399
+ env: to,
1400
+ token: toToken,
1401
+ });
1402
+ });
1403
+ Loading$1.start(`删除${to}环境按钮权限配置中...`);
1404
+ await Promise.all(deleteButtonPromiseList);
1405
+ Loading$1.succeed(`删除${to}环境按钮权限配置成功`);
1406
+ }
1407
+ } catch (error) {
1408
+ console.log(chalk.red(`删除${to}环境按钮权限配置失败`));
1409
+ } finally {
1410
+ try {
1411
+ for (const item of fromButtonList) {
1412
+ const { code, name, visitConf } = item;
1413
+ await addApplicationButtonConfig({
1414
+ env: to,
1415
+ token: toToken,
1416
+ data: {
1417
+ code1: code.split(":")[0],
1418
+ code,
1419
+ name,
1420
+ visitConf,
1421
+ ascriptionApp: toAppId,
1422
+ },
1423
+ });
1424
+ }
1425
+ console.log(chalk.green("同步成功"));
1426
+ } catch (e) {
1427
+ console.log(chalk.red(`同步失败: ${e.message || e}`));
1428
+ process.exit(1);
1429
+ }
1430
+ }
1431
+ }
1432
+
1433
+ // 新增按钮权限流程(支持 --env/--app/--permission-name/--permission-code 一键, 参数齐全时免确认)
1434
+ async function addSassPermission(cliOpts = {}) {
1435
+ const {
1436
+ env: cliEnv,
1437
+ app: cliApp,
1438
+ permissionName: cliPermissionName,
1439
+ permissionCode: cliPermissionCode,
1440
+ } = cliOpts;
1441
+ const envList = ["mit", "sit", "uat"];
1442
+ const oneClick =
1443
+ cliEnv !== undefined &&
1444
+ cliApp !== undefined &&
1445
+ cliPermissionName !== undefined &&
1446
+ cliPermissionCode !== undefined;
1447
+ // CLI传入的权限编码含:时快速失败(code以:为分隔符,含:会产生歧义,且避免登录查询后才报错)
1448
+ if (cliPermissionCode !== undefined && cliPermissionCode.includes(":")) {
1449
+ console.log(chalk.red("参数 --permission-code 的值不允许包含 :"));
1450
+ process.exit(1);
1451
+ }
1452
+ if (cliEnv !== undefined) {
1453
+ validateChoice(cliEnv, envList, "--env");
1454
+ }
1455
+ const env = await askOrUse(cliEnv, {
1456
+ message: "请选择环境",
1457
+ name: "env",
1458
+ type: "list",
1459
+ choices: envList,
1460
+ });
1461
+ let token;
1462
+ try {
1463
+ token = await login({
1464
+ env,
1465
+ username: "superAdmin",
1466
+ password: "admin1",
1467
+ });
1468
+ } catch (e) {
1469
+ console.log(chalk.red(`${env} 登录失败: ${e.message || e}`));
1470
+ process.exit(1);
1471
+ }
1472
+ const inputAppName = await askOrUse(cliApp, {
1473
+ message: "请输入要新增按钮权限的应用名称",
1474
+ name: "inputAppName",
1475
+ type: "input",
1476
+ validate: function (value) {
1477
+ if (value) {
1478
+ return true;
1479
+ }
1480
+ return "请输入应用名称";
1481
+ },
1482
+ });
1483
+ let applicationList;
1484
+ try {
1485
+ applicationList = await getApplicationList({
1486
+ env,
1487
+ token,
1488
+ appName: inputAppName,
1489
+ });
1490
+ } catch (e) {
1491
+ console.log(chalk.red(`查询${env}环境应用列表失败: ${e.message || e}`));
1492
+ process.exit(1);
1493
+ }
1494
+ if (!applicationList.length) {
1495
+ console.log(chalk.red(`${env}环境未找到该应用`));
1496
+ process.exit(1);
1497
+ }
1498
+ let appId;
1499
+ let appName;
1500
+ if (applicationList.length === 1) {
1501
+ appId = applicationList[0].id;
1502
+ appName = applicationList[0].name;
1503
+ console.log(chalk.gray(`唯一匹配,已自动选中: ${appName}`));
1504
+ } else {
1505
+ // 一键模式下优先精确匹配CLI传入的应用名称(应用列表是关键字模糊查询结果)
1506
+ const exactMatch = oneClick
1507
+ ? applicationList.find((item) => item.name === cliApp)
1508
+ : undefined;
1509
+ if (exactMatch) {
1510
+ appId = exactMatch.id;
1511
+ appName = exactMatch.name;
1512
+ console.log(chalk.gray(`精确匹配,已自动选中: ${appName}`));
1513
+ } else {
1514
+ const res = await inquirer.prompt([
1515
+ {
1516
+ message: "请选择应用名称",
1517
+ name: "appName",
1518
+ type: "rawlist",
1519
+ choices: applicationList,
1520
+ },
1521
+ ]);
1522
+ appId = applicationList.find(
1523
+ (item) => item.name === res.appName
1524
+ ).id;
1525
+ appName = res.appName;
1526
+ }
1527
+ }
1528
+ let appConfig;
1529
+ try {
1530
+ appConfig = await getApplicationConfig({ appId, env, token });
1531
+ } catch (e) {
1532
+ console.log(chalk.red(`获取应用配置失败: ${e.message || e}`));
1533
+ process.exit(1);
1534
+ }
1535
+ // CLI传入的权限名称/编码仅第一圈消费, 后续圈次走交互输入
1536
+ let cliName = cliPermissionName;
1537
+ let cliCode = cliPermissionCode;
1538
+ let goOn = true;
1539
+ while (goOn) {
1540
+ const permissionName = await askOrUse(cliName, {
1541
+ message: "请输入权限名称",
1542
+ name: "permissionName",
1543
+ type: "input",
1544
+ validate: function (value) {
1545
+ if (value) {
1546
+ return true;
1547
+ }
1548
+ return "请输入权限名称";
1549
+ },
1550
+ });
1551
+ const permissionCode = await askOrUse(cliCode, {
1552
+ message: "请输入权限编码",
1553
+ name: "permissionCode",
1554
+ type: "input",
1555
+ validate: function (value) {
1556
+ if (!value) {
1557
+ return "请输入权限编码";
1558
+ }
1559
+ if (value.includes(":")) {
1560
+ return "权限编码不允许包含 :";
1561
+ }
1562
+ return true;
1563
+ },
1564
+ });
1565
+ cliName = undefined;
1566
+ cliCode = undefined;
1567
+ const data = {
1568
+ code1: appConfig.code,
1569
+ name: permissionName,
1570
+ visitConf: permissionCode,
1571
+ ascriptionApp: appConfig.id,
1572
+ code: `${appConfig.code}:${permissionCode}`,
1573
+ };
1574
+ let confirmed = true;
1575
+ if (!oneClick) {
1576
+ const { confirmAdd } = await inquirer.prompt([
1577
+ {
1578
+ message: `确认新增按钮权限? ${chalk.cyan(
1579
+ `${data.name} (${data.code})`
1580
+ )}`,
1581
+ name: "confirmAdd",
1582
+ type: "confirm",
1583
+ default: true,
1584
+ },
1585
+ ]);
1586
+ confirmed = confirmAdd;
1587
+ }
1588
+ if (confirmed) {
1589
+ try {
1590
+ await addApplicationButtonConfig({ data, env, token });
1591
+ console.log(
1592
+ chalk.green(
1593
+ `新增按钮权限成功: ${appName} - ${data.name} (${data.code})`
1594
+ )
1595
+ );
1596
+ } catch (e) {
1597
+ console.log(chalk.red(`新增按钮权限失败: ${e.message || e}`));
1598
+ process.exit(1);
1599
+ }
1600
+ }
1601
+ if (oneClick) {
1602
+ goOn = false;
1603
+ } else {
1604
+ const { continueAdd } = await inquirer.prompt([
1605
+ {
1606
+ message: "是否继续为该应用添加按钮权限?",
1607
+ name: "continueAdd",
1608
+ type: "confirm",
1609
+ default: false,
1610
+ },
1611
+ ]);
1612
+ goOn = continueAdd;
1613
+ }
1614
+ }
1183
1615
  }
1184
1616
 
1185
1617
  /**
@@ -2177,839 +2609,929 @@ function stylish(results) {
2177
2609
  return total > 0 ? chalk.reset(output) : "";
2178
2610
  }
2179
2611
 
2180
- async function lint$1(patterns, fix, rule, hook = {}) {
2181
- const rules$1 = {
2182
- "i18n/no-single-chinese": rule ? "off" : "warn",
2183
- };
2184
- rules[0].choices.forEach((item) => {
2185
- rules$1[`i18n/no-single-${item.value}`] = "off";
2186
- if (item.children) {
2187
- item.children.forEach((child) => {
2188
- child.choices.forEach((item) => {
2189
- rules$1[`i18n/no-single-${item.value}`] = "off";
2190
- });
2191
- });
2192
- }
2193
- });
2194
- if (rule) {
2195
- rules$1[`i18n/no-single-${rule}`] = "warn";
2196
- }
2197
- const eslint$1 = new eslint.ESLint({
2198
- useEslintrc: false, // 不加载项目中的 .eslintrc 文件, 即不检测执行项目里规则,只检测i18n规则, 不设置为false,则会检测项目中的.eslintrc.js文件规则
2199
- // allowInlineConfig: false, // 禁止解析文件内的 /* eslint-disable */ 等注释,否则虽然设置了useEslintrc:false, 它也会检测/* eslint-disable */等注释,然后找不到该规则的定义,从而检测出错误.但⚠️该规则会导致eslint-disable 失效
2200
- // 只检测js,jsx,vue,ts,tsx文件
2201
- extensions: [".js", ".jsx", ".vue",'ts', 'tsx'],
2202
- fix: fix || false, // 是否自动修复
2203
- overrideConfigFile: null, // 所有规则通过下面overrideConfig配置,不使用基于文件的配置方式,如传配指定配置文件路径
2204
- plugins: { [meta.name]: i18n }, // 必须写,下面的extends才能识别, 和配置文件那种不一样,配置文件那种只写extends就行,不用写plugins
2205
- overrideConfig: {
2206
- extends: [
2207
- "plugin:i18n/recommended", // 表示使用test插件的推荐规则, plugin:后不能有空格,否则不生效
2208
- ],
2209
- // 此处增加rules是为了动态判断覆盖extends中的rule级别
2210
- rules: rules$1,
2211
- },
2212
- });
2213
- Loading$1.start(`检测中...`);
2214
- try {
2215
- // 检查文件
2216
- const results = await eslint$1.lintFiles(patterns);
2217
- results.forEach(item=>{
2218
- if(item?.messages?.length){
2219
- // 脚本只会检测脚本自定义的规则,但是在项目中运行发现eslint-disable-line xx规则会报找不到规则的错误,如
2220
- // error Definition for rule '@typescript-eslint/no-unused-vars' was not found @typescript-eslint/no-unused-vars
2221
- // 这种报错没有messageId,忽略掉
2222
- item.messages=item.messages.filter(message=>{
2223
- return message.messageId
2224
- });
2225
- }
2226
- });
2227
- hook.lintFilesAfter && (await hook.lintFilesAfter(results, Loading$1));
2228
- if (!fix) {
2229
- // 判断是否有匹配到错误
2230
- const hasErrors = results.some(
2231
- (result) => result.messages && result.messages.length
2232
- );
2233
- if (!hasErrors) {
2234
- return Loading$1.succeed(`没有要修复的错误`);
2235
- }
2236
- Loading$1.succeed(`检测完成`);
2237
- // 如果没有fix,则直接输出错误
2238
- return console.log(stylish(results));
2239
- }
2240
- // 判断是否有匹配到错误
2241
- const hasErrors1 = results.some(
2242
- (result) => result.output || result.warningCount
2243
- );
2244
- if (!hasErrors1) {
2245
- return Loading$1.succeed(`没有要修复的错误`);
2246
- }
2247
- // 输出回原文件
2248
- await eslint.ESLint.outputFixes(results);
2249
- Loading$1.succeed(`替换空key完成`);
2250
- inquirer
2251
- .prompt([
2252
- {
2253
- message: `是否开始翻译key`,
2254
- name: "isTransKey",
2255
- type: "confirm",
2256
- default: true,
2257
- },
2258
- ])
2259
- .then(({ isTransKey }) => {
2260
- if (isTransKey) {
2261
- console.log(chalk.green(`开始准备翻译...`));
2262
- process$1.execSync(`fe-it-beta --fix -i "${patterns}" `, {
2263
- stdio: "inherit", // 打印子进程的输出到父进程
2264
- });
2265
- }
2266
- });
2267
- } catch (error) {
2268
- Loading$1.fail(`检测失败`);
2269
- console.error(chalk.red(error));
2270
- }
2612
+ async function lint$1(patterns, fix, rule, hook = {}) {
2613
+ const rules$1 = {
2614
+ "i18n/no-single-chinese": rule ? "off" : "warn",
2615
+ };
2616
+ rules[0].choices.forEach((item) => {
2617
+ rules$1[`i18n/no-single-${item.value}`] = "off";
2618
+ if (item.children) {
2619
+ item.children.forEach((child) => {
2620
+ child.choices.forEach((item) => {
2621
+ rules$1[`i18n/no-single-${item.value}`] = "off";
2622
+ });
2623
+ });
2624
+ }
2625
+ });
2626
+ if (rule) {
2627
+ rules$1[`i18n/no-single-${rule}`] = "warn";
2628
+ }
2629
+ const eslint$1 = new eslint.ESLint({
2630
+ useEslintrc: false, // 不加载项目中的 .eslintrc 文件, 即不检测执行项目里规则,只检测i18n规则, 不设置为false,则会检测项目中的.eslintrc.js文件规则
2631
+ // allowInlineConfig: false, // 禁止解析文件内的 /* eslint-disable */ 等注释,否则虽然设置了useEslintrc:false, 它也会检测/* eslint-disable */等注释,然后找不到该规则的定义,从而检测出错误.但⚠️该规则会导致eslint-disable 失效
2632
+ // 只检测js,jsx,vue,ts,tsx文件
2633
+ extensions: [".js", ".jsx", ".vue",'ts', 'tsx'],
2634
+ fix: fix || false, // 是否自动修复
2635
+ overrideConfigFile: null, // 所有规则通过下面overrideConfig配置,不使用基于文件的配置方式,如传配指定配置文件路径
2636
+ plugins: { [meta.name]: i18n }, // 必须写,下面的extends才能识别, 和配置文件那种不一样,配置文件那种只写extends就行,不用写plugins
2637
+ overrideConfig: {
2638
+ extends: [
2639
+ "plugin:i18n/recommended", // 表示使用test插件的推荐规则, plugin:后不能有空格,否则不生效
2640
+ ],
2641
+ // 此处增加rules是为了动态判断覆盖extends中的rule级别
2642
+ rules: rules$1,
2643
+ },
2644
+ });
2645
+ Loading$1.start(`检测中...`);
2646
+ try {
2647
+ // 检查文件
2648
+ const results = await eslint$1.lintFiles(patterns);
2649
+ results.forEach(item=>{
2650
+ if(item?.messages?.length){
2651
+ // 脚本只会检测脚本自定义的规则,但是在项目中运行发现eslint-disable-line xx规则会报找不到规则的错误,如
2652
+ // error Definition for rule '@typescript-eslint/no-unused-vars' was not found @typescript-eslint/no-unused-vars
2653
+ // 这种报错没有messageId,忽略掉
2654
+ item.messages=item.messages.filter(message=>{
2655
+ return message.messageId
2656
+ });
2657
+ }
2658
+ });
2659
+ hook.lintFilesAfter && (await hook.lintFilesAfter(results, Loading$1));
2660
+ if (!fix) {
2661
+ // 判断是否有匹配到错误
2662
+ const hasErrors = results.some(
2663
+ (result) => result.messages && result.messages.length
2664
+ );
2665
+ if (!hasErrors) {
2666
+ return Loading$1.succeed(`没有要修复的错误`);
2667
+ }
2668
+ Loading$1.succeed(`检测完成`);
2669
+ // 如果没有fix,则直接输出错误
2670
+ return console.log(stylish(results));
2671
+ }
2672
+ // 判断是否有匹配到错误
2673
+ const hasErrors1 = results.some(
2674
+ (result) => result.output || result.warningCount
2675
+ );
2676
+ if (!hasErrors1) {
2677
+ return Loading$1.succeed(`没有要修复的错误`);
2678
+ }
2679
+ // 输出回原文件
2680
+ await eslint.ESLint.outputFixes(results);
2681
+ Loading$1.succeed(`替换空key完成`);
2682
+ inquirer
2683
+ .prompt([
2684
+ {
2685
+ message: `是否开始翻译key`,
2686
+ name: "isTransKey",
2687
+ type: "confirm",
2688
+ default: true,
2689
+ },
2690
+ ])
2691
+ .then(({ isTransKey }) => {
2692
+ if (isTransKey) {
2693
+ console.log(chalk.green(`开始准备翻译...`));
2694
+ process$1.execSync(`fe-it-beta --fix -i "${patterns}" `, {
2695
+ stdio: "inherit", // 打印子进程的输出到父进程
2696
+ });
2697
+ }
2698
+ });
2699
+ } catch (error) {
2700
+ Loading$1.fail(`检测失败`);
2701
+ console.error(chalk.red(error));
2702
+ }
2271
2703
  }
2272
2704
 
2273
- const condition = {
2274
- currency: hasCurrencyName,
2275
- };
2276
- async function customColumnModule (lintc, fix, rule, parentRule) {
2277
- const { way } = await inquirer.prompt([
2278
- {
2279
- message: "请选择检测自定义列方式",
2280
- name: "way",
2281
- type: "list",
2282
- choices: [
2283
- {
2284
- name: "自动检测HxSearchListPage、this.$customColumnDialog的自定义列ID",
2285
- value: "normal",
2286
- },
2287
- {
2288
- name: "自定义输入",
2289
- value: "input",
2290
- },
2291
- ],
2292
- },
2293
- ]);
2294
- if (way === "normal") {
2295
- lint$1(lintc, fix, "customColumnModule", {
2296
- lintFilesAfter: (results, Loading) => {
2297
- const arr = getModuleArr();
2298
- const dynamicsArr = arr.filter((item) => item.isDynamics);
2299
- const normalArr = arr.filter((item) => !item.isDynamics);
2300
-
2301
- return new Promise((resolve) => {
2302
- Loading.succeed(`检测完成,${arr.length ? "" : "未"}匹配到规则`);
2303
- if (arr.length) {
2304
- getColumn(normalArr, dynamicsArr, parentRule);
2305
- }
2306
- });
2307
- },
2308
- });
2309
- } else if (way === "input") {
2310
- const { customColumnId } = await inquirer.prompt([
2311
- {
2312
- message: "请输入自定义列ID,格式为xx,xx,xx",
2313
- name: "customColumnId",
2314
- type: "input",
2315
- },
2316
- ]);
2317
- const moduleArr = customColumnId.split(",").map((item) => {
2318
- return {
2319
- module: item,
2320
- path: "NA",
2321
- };
2322
- });
2323
- getColumn(moduleArr, [], parentRule);
2324
- }
2325
- }
2326
-
2327
- async function getColumn(moduleArr = [], dynamicsArr = [], parentRule) {
2328
- let token;
2329
- try {
2330
- token = await login({
2331
- env: "sit",
2332
- username: "superAdmin",
2333
- password: "admin1",
2334
- });
2335
- } catch (e) {
2336
- console.log(chalk.red(`登录失败: ${e.message || e}`));
2337
- return;
2338
- }
2339
- const requestArr = moduleArr.map(({ module, path }) =>
2340
- request$1({
2341
- url: `http://sit-hxjf.hongxinshop.com/api-item/api/customizedColumns/find`,
2342
- method: "POST",
2343
- json: true,
2344
- body: { module },
2345
- headers: {
2346
- Authorization: `Bearer ${token}`,
2347
- "x-request-vaildate": "open",
2348
- },
2349
- })
2350
- );
2351
- Promise.allSettled(requestArr).then(async (res) => {
2352
- const tableData = [];
2353
- res.forEach(({ value, status }, index) => {
2354
- const { path, module } = moduleArr[index];
2355
- if (status === "fulfilled") {
2356
- const { data } = value;
2357
- const { showColumns, hiddenColumns } = data;
2358
- let fieldArr = [];
2359
- showColumns.concat(hiddenColumns).forEach((item) => {
2360
- if (condition[parentRule](item.showField)) {
2361
- fieldArr.push(`${item.showField} ${item.showName}`);
2362
- }
2363
- });
2364
- if (fieldArr.length) {
2365
- fieldArr.forEach((item, index) => {
2366
- let obj = {
2367
- module,
2368
- field: item,
2369
- path,
2370
- };
2371
- if (index !== 0) {
2372
- // obj.module = module;
2373
- // obj.path = path;
2374
- delete obj.module;
2375
- delete obj.path;
2376
- }
2377
- tableData.push(obj);
2378
- });
2379
- } else {
2380
- tableData.push({
2381
- module,
2382
- field: "NA",
2383
- path,
2384
- });
2385
- }
2386
- } else {
2387
- tableData.push({
2388
- module,
2389
- field: "error",
2390
- path,
2391
- });
2392
- }
2393
- });
2394
- // 合并动态模块
2395
- dynamicsArr.forEach((item) => {
2396
- tableData.push({
2397
- module: item.module,
2398
- field: "error",
2399
- path: item.path,
2400
- });
2401
- });
2402
- // 排序, field为NA和error的排在后面
2403
- tableData.sort((a, b) => {
2404
- if (["NA", "error"].includes(b.field)) {
2405
- return -1;
2406
- } else {
2407
- return 1;
2408
- }
2409
- });
2410
- const successModuleArr = new Set();
2411
-
2412
- const naTableData = [];
2413
- const naModuleArr = new Set();
2414
-
2415
- const noNaTableData = [];
2416
- const noNaModuleArr = new Set();
2417
-
2418
- const errorTableData = []; // 失败的数据
2419
- const errorModuleArr = new Set();
2420
-
2421
- tableData.forEach((item) => {
2422
- if (item.field === "error") {
2423
- errorTableData.push(item);
2424
- item.module && errorModuleArr.add(item.module);
2425
- } else {
2426
- if (item.field === "NA") {
2427
- naTableData.push(item);
2428
- item.module &&naModuleArr.add(item.module);
2429
- } else {
2430
- noNaTableData.push(item);
2431
- item.module &&noNaModuleArr.add(item.module);
2432
- }
2433
- item.module &&successModuleArr.add(item.module);
2434
- }
2435
- });
2436
-
2437
- console.log(
2438
- chalk.green(
2439
- `共${moduleArr.length + dynamicsArr.length}个moduleId, 请求成功${
2440
- successModuleArr.size
2441
- }个(其中规则命中${noNaModuleArr.size}个, ${chalk.yellow(
2442
- `未命中${naModuleArr.size}个`
2443
- )}),`
2444
- ) + chalk.red(`请求失败${errorModuleArr.size}个`)
2445
- );
2446
- console.log(chalk.green(`🚀🚀🚀🚀🚀共匹配到${noNaTableData.length+errorTableData.length}个字段🚀🚀🚀🚀🚀`));
2447
-
2448
- if (noNaTableData.length) {
2449
- console.log(chalk.green(`成功命中字段${noNaTableData.length}个`));
2450
- console.table(noNaTableData);
2451
- }
2452
- if (errorTableData.length) {
2453
- console.log(chalk.red(`失败,需手动处理或重试${errorTableData.length}个`));
2454
- console.table(errorTableData);
2455
- }
2456
- if (naTableData.length) {
2457
- console.log(
2458
- chalk.yellow(`成功但未命中字段,无需处理${naTableData.length}个`)
2459
- );
2460
- console.table(naTableData);
2461
- }
2462
- });
2705
+ const condition = {
2706
+ currency: hasCurrencyName,
2707
+ };
2708
+ // 自定义列检测(支持 --way/--column-id 一键, CLI参数 > 交互)
2709
+ async function customColumnModule (lintc, fix, rule, parentRule, cliOpts = {}) {
2710
+ // 空字符串视为未传(与askOrUse语义一致)
2711
+ const cliWay = cliOpts.way || undefined;
2712
+ const cliColumnId = cliOpts.columnId || undefined;
2713
+ // CLI参数校验前置(快速失败, 不进交互)
2714
+ if (cliWay !== undefined) {
2715
+ validateChoice(cliWay, ["normal", "input"], "--way");
2716
+ }
2717
+ if (cliColumnId !== undefined && cliWay !== "input") {
2718
+ console.log(chalk.red("参数 --column-id 须搭配 --way input 使用"));
2719
+ process.exit(1);
2720
+ }
2721
+ const way = await askOrUse(cliWay, {
2722
+ message: "请选择检测自定义列方式",
2723
+ name: "way",
2724
+ type: "list",
2725
+ choices: [
2726
+ {
2727
+ name: "自动检测HxSearchListPage、this.$customColumnDialog的自定义列ID",
2728
+ value: "normal",
2729
+ },
2730
+ {
2731
+ name: "自定义输入",
2732
+ value: "input",
2733
+ },
2734
+ ],
2735
+ });
2736
+ if (way === "normal") {
2737
+ lint$1(lintc, fix, "customColumnModule", {
2738
+ lintFilesAfter: (results, Loading) => {
2739
+ const arr = getModuleArr();
2740
+ const dynamicsArr = arr.filter((item) => item.isDynamics);
2741
+ const normalArr = arr.filter((item) => !item.isDynamics);
2742
+
2743
+ return new Promise((resolve) => {
2744
+ Loading.succeed(`检测完成,${arr.length ? "" : ""}匹配到规则`);
2745
+ if (arr.length) {
2746
+ getColumn(normalArr, dynamicsArr, parentRule);
2747
+ }
2748
+ });
2749
+ },
2750
+ });
2751
+ } else if (way === "input") {
2752
+ // CLI传入直接用, 否则交互输入
2753
+ let customColumnId;
2754
+ if (cliColumnId !== undefined) {
2755
+ customColumnId = cliColumnId;
2756
+ } else {
2757
+ const res = await inquirer.prompt([
2758
+ {
2759
+ message: "请输入自定义列ID,格式为xx,xx,xx",
2760
+ name: "customColumnId",
2761
+ type: "input",
2762
+ },
2763
+ ]);
2764
+ customColumnId = res.customColumnId;
2765
+ }
2766
+ const columnArr = customColumnId
2767
+ .split(",")
2768
+ .map((item) => item.trim())
2769
+ .filter(Boolean);
2770
+ if (!columnArr.length) {
2771
+ console.log(chalk.red("自定义列ID不能为空"));
2772
+ process.exit(1);
2773
+ }
2774
+ const moduleArr = columnArr.map((item) => {
2775
+ return {
2776
+ module: item,
2777
+ path: "NA",
2778
+ };
2779
+ });
2780
+ getColumn(moduleArr, [], parentRule);
2781
+ }
2463
2782
  }
2464
2783
 
2465
- // import packageJson from "../package.json" assert { type: "json" };
2466
- const { version } = getPackageJson();
2467
-
2468
- // 获取git修改的文件以及暂存区的文件(包含未跟踪的新文件)
2469
- function getGitChangedFiles() {
2470
- const cwd = process.cwd();
2471
- try {
2472
- const repoRoot = process$1.execSync("git rev-parse --show-toplevel", {
2473
- cwd,
2474
- encoding: "utf-8",
2475
- }).trim();
2476
- // 1. git status --porcelain 获取已跟踪文件的变更(已暂存staged + 未暂存unstaged)
2477
- // 注意: 不能用 git diff --name-only HEAD, 它不会包含未跟踪(untracked)的新文件
2478
- const statusOutput = process$1.execSync("git status --porcelain", {
2479
- cwd: repoRoot,
2480
- encoding: "utf-8",
2481
- });
2482
- const trackedFiles = statusOutput
2483
- .split("\n")
2484
- .map((line) => line.replace(/\r$/, ""))
2485
- .filter(Boolean)
2486
- .map((line) => {
2487
- // 输出格式: XY filename, X=暂存区状态, Y=工作区状态
2488
- const status = line.slice(0, 2);
2489
- let filePath = line.slice(3);
2490
- // 重命名(R)/复制(C): "old -> new", 取新文件名
2491
- if (status[0] === "R" || status[0] === "C") {
2492
- const idx = filePath.indexOf(" -> ");
2493
- if (idx > -1) filePath = filePath.slice(idx + 4);
2494
- }
2495
- // 含特殊字符的文件名git会加引号, 去除引号
2496
- if (filePath.startsWith('"') && filePath.endsWith('"')) {
2497
- filePath = filePath.slice(1, -1);
2498
- }
2499
- return { status, filePath };
2500
- })
2501
- // 排除删除的文件 (D 在暂存区或工作区)
2502
- .filter(({ status }) => !status.includes("D"))
2503
- // 未跟踪文件(??) git ls-files 单独处理(因为可能只显示目录,不展开)
2504
- .filter(({ status }) => status !== "??")
2505
- .map(({ filePath }) => filePath);
2506
-
2507
- // 2. git ls-files --others --exclude-standard 获取未跟踪的新文件
2508
- // 相比 git status --porcelain, 它会自动展开目录, 列出具体文件
2509
- const untrackedOutput = process$1.execSync(
2510
- "git ls-files --others --exclude-standard",
2511
- { cwd: repoRoot, encoding: "utf-8" }
2512
- );
2513
- const untrackedFiles = untrackedOutput
2514
- .split("\n")
2515
- .map((f) => f.trim())
2516
- .filter(Boolean);
2517
-
2518
- // 合并去重
2519
- const allFiles = Array.from(
2520
- new Set([...trackedFiles, ...untrackedFiles])
2521
- )
2522
- // 只保留lint支持的文件类型
2523
- .filter((f) => /\.(js|jsx|vue|ts|tsx)$/.test(f))
2524
- // git输出的路径相对于仓库根目录, 转为绝对路径
2525
- .map((f) => path.join(repoRoot, f))
2526
- // 过滤掉不存在的文件
2527
- .filter((f) => fs.existsSync(f));
2528
- return allFiles;
2529
- } catch (error) {
2530
- console.log(chalk.red("获取git修改文件失败,请确保在git仓库中运行"));
2531
- return [];
2532
- }
2533
- }
2534
- // const spinner = ora();
2535
- commander.program.version(version);
2536
- commander.program.option("-i, --input <type>", "翻译空key");
2537
- // command('merge')
2538
- // .option("-i, --input <type>" , "翻译空key")
2539
- // .option("-m, --merge" , "输出文件").action((options) => {
2540
- // console.log('merge', options)
2541
- // })
2542
- // 上传翻译
2543
- commander.program.option("-u, --upload", "上传翻译包");
2544
- // 同步sass应用权限配置
2545
- commander.program.option("-sass, --sass", "同步sass按钮权限配置");
2546
- // 检测未$hxt中文
2547
- commander.program.option("-lint, --lint <patterns>", "检测未$hxt的中文");
2548
- // 检测配置项
2549
- commander.program.option("-lintc, --lintc <patterns>", "检测指定配置规则");
2550
- // 检测未$hxt中文自动修复
2551
- commander.program.option("--fix", "修复lint检出的错误");
2552
- // 翻译excel指定列语言
2553
- commander.program.option("-excel, --excel", "翻译excel指定列语言");
2554
- commander.program.parse(process.argv);
2555
-
2556
- // 判断命令参数
2557
- const { input, sass, upload, lint, lintc, fix, excel } = commander.program.opts();
2558
- const rules = [
2559
- {
2560
- message: "请选择检查规则",
2561
- name: "rule",
2562
- type: "list",
2563
- choices: [
2564
- {
2565
- name: "币种",
2566
- value: "currency",
2567
- children: [
2568
- {
2569
- message: "请选择场景",
2570
- name: "scene",
2571
- type: "list",
2572
- choices: [
2573
- {
2574
- name: "匹配属性名含有price、total、amount金额字段",
2575
- value: "currency",
2576
- },
2577
- {
2578
- name: "自定义列",
2579
- value: "customColumnModule",
2580
- },
2581
- ],
2582
- },
2583
- ],
2584
- },
2585
- ],
2586
- },
2587
- ];
2588
- if (sass) {
2589
- syncSassConfig();
2590
- } else if (upload) {
2591
- upload$1();
2592
- } else if (lint !== undefined) {
2593
- // 必须传入路径
2594
- if (!lint || !lint.trim()) {
2595
- console.log(
2596
- chalk.red("请传入要检测的路径, 或使用 -lint git 检测git修改的文件")
2597
- );
2598
- process.exit(1);
2599
- }
2600
- let lintPatterns = lint.trim();
2601
- // 如果路径写死为 git, 则用git检测修改的文件以及暂存区的文件作为路径
2602
- if (lintPatterns === "git") {
2603
- lintPatterns = getGitChangedFiles();
2604
- if (!lintPatterns.length) {
2605
- console.log(chalk.green("没有检测到git修改的文件"));
2606
- process.exit(0);
2607
- }
2608
- console.log(chalk.green(`检测到${lintPatterns.length}个git修改文件:`));
2609
- lintPatterns.forEach((f) => console.log(chalk.gray(` ${f}`)));
2610
- }
2611
- lint$1(lintPatterns, fix);
2612
- } else if (lintc) {
2613
- inquirer.prompt(rules).then(async ({ rule }) => {
2614
- const parentRule = rule;
2615
- if (rule === "currency") {
2616
- const ruleChoice = rules.find((item) => item.name === "rule");
2617
- const currencySubChoice = ruleChoice.choices.find(
2618
- (item) => item.value === "currency"
2619
- ).children;
2620
- const { scene } = await inquirer.prompt(currencySubChoice);
2621
- rule = scene;
2622
- if (rule === "customColumnModule") {
2623
- return customColumnModule(lintc, fix, rule, parentRule);
2624
- }
2625
- }
2626
- lint$1(lintc, fix, rule);
2627
- });
2628
- } else if (excel) {
2629
- excelFn();
2630
- } else {
2631
- let projectName = "请在package.json中配置name字段(项目名称)";
2632
- const projectPath = "./package.json";
2633
- const isExist = isExistPath(projectPath);
2634
- if (isExist) {
2635
- const str = fs.readFileSync(projectPath, "utf-8").toString();
2636
- projectName = JSON.parse(str).name;
2637
- }
2638
- inquirer
2639
- .prompt([
2640
- {
2641
- message: `请确认package.json中项目名称是否是${projectName}`,
2642
- name: "isProjectName",
2643
- type: "confirm",
2644
- default: true,
2645
- },
2646
- ])
2647
- .then((res) => {
2648
- if (res.isProjectName) {
2649
- inquirer
2650
- .prompt([
2651
- {
2652
- message: "请选择翻译接口",
2653
- name: "type",
2654
- type: "list",
2655
- choices: [
2656
- {
2657
- name: "百度翻译",
2658
- value: "baidu",
2659
- },
2660
- ],
2661
- },
2662
- ])
2663
- .then((res) => {
2664
- const { type } = res;
2665
- let cacheSecret = {};
2666
- if (!isExistPath(getRunCliPath({ root: true }) + "/.cache")) {
2667
- fs.mkdirSync(getRunCliPath({ root: true }) + "/.cache");
2668
- }
2669
- const cacheFilePath =
2670
- getRunCliPath({ root: true }) + "/.cache/" + res.type;
2671
- if (isExistPath(cacheFilePath)) {
2672
- const cache = JSON.parse(readCache(res.type));
2673
- cacheSecret = cache;
2674
- }
2675
- const question = [
2676
- {
2677
- message: "请输入百度翻译的appid",
2678
- name: "appid",
2679
- default: cacheSecret.appid,
2680
- // 必填
2681
- validate: function (val) {
2682
- if (val) {
2683
- return true;
2684
- }
2685
- return "请输入百度翻译的appid";
2686
- },
2687
- },
2688
- {
2689
- message: "请输入百度翻译的key",
2690
- name: "key",
2691
- default: cacheSecret.key,
2692
- // 必填
2693
- validate: function (val) {
2694
- if (val) {
2695
- return true;
2696
- }
2697
- return "请输入百度翻译的key";
2698
- },
2699
- },
2700
- ];
2701
- inquirer.prompt(question).then((res) => {
2702
- writeCache(
2703
- type,
2704
- JSON.stringify({
2705
- type,
2706
- ...res,
2707
- })
2708
- );
2709
- const { appid, key } = res;
2710
- const isLintFix = fix;
2711
- translateUtil(appid, key, projectName, isLintFix);
2712
- });
2713
- });
2714
- } else {
2715
- console.log(
2716
- chalk.red("请在package.json中配置正确的name字段(项目名称)")
2717
- );
2718
- }
2719
- });
2720
- }
2721
-
2722
- function translateUtil(appid, key, projectName, isLintFix) {
2723
- const { input } = commander.program.opts();
2724
- // 支持逗号分隔的多个路径(来自 -lint git 的多文件场景)
2725
- const inputPaths = String(input)
2726
- .split(",")
2727
- .map((s) => s.trim())
2728
- .filter(Boolean);
2729
- let src = [];
2730
- inputPaths.forEach((inputPath) => {
2731
- const normalizedInput = inputPath.replace(/\\/g, "/");
2732
- if (fs.statSync(inputPath).isDirectory()) {
2733
- const directory = normalizedInput + "/**/*.{js,vue,jsx,ts,tsx}";
2734
- const files = glob.glob.sync(directory, {
2735
- ignore: directory + `/**/node_modules/**`,
2736
- });
2737
- src = src.concat(files);
2738
- } else {
2739
- src.push(normalizedInput);
2740
- }
2741
- });
2742
- let allArr = []; // 所有文本组成的数组
2743
- let groupArr = []; // 分组后的数组
2744
- let newAllArr = []; // 分组后的数组还原成所有数组
2745
- // 循环遍历文件下的文件
2746
- function readFolder(isOk) {
2747
- // spinner.start();
2748
- src.forEach((item, index) => {
2749
- const fPath = item;
2750
- let file = fs.readFileSync(fPath, "utf-8").toString();
2751
- // 提取file里所有以$hxt({ key: '', desc:为开头 以')}结尾的字符串
2752
- const reg =
2753
- /\$hxt\([\r\n\s]*\{[\r\n\s]*key[\r\n\s]*:[\r\n\s]*['|"][\r\n\s]*['|"],[\r\n\s]*desc[\r\n\s]*:[\r\n\s]*['|"|`](.*?)['|"|`],*[\r\n\s]*\}(\)|,[\r\n\s]*\{)/g;
2754
- file = file.replace(reg, function (match, p1, p2) {
2755
- if (isOk) {
2756
- const reg1 = /key[\r\n\s]*:[\r\n\s]*('|")[\r\n\s]*('|")/g;
2757
- let key = newAllArr.find(
2758
- (item) =>
2759
- item.path === fPath &&
2760
- (item.ZH_CN === p1 || item.ZH_CN === p1.trim()) // 去除前后空格,原[ 执行人: ]翻译变成[执行人:]
2761
- ).key;
2762
- // 去除{ slotxx }
2763
- // key = key.replace(/\{[\r\n\s]*slot\d[\r\n\s]*\}/g, "");
2764
- // 去除空格
2765
- key = key.replace(/\s/g, "");
2766
- return match.replace(reg1, `key: '${key}'`);
2767
- } else {
2768
- allArr.push({
2769
- path: fPath,
2770
- ZH_CN: p1,
2771
- EN: "",
2772
- key: "",
2773
- });
2774
- }
2775
- });
2776
- if (isOk) {
2777
- fs.writeFileSync(fPath, file, "utf-8");
2778
- }
2779
- });
2780
- if (isOk) {
2781
- // spinner.stop();
2782
- return;
2783
- }
2784
- // 数组分割,如果str长度大于2000,就分割成多个数组
2785
- let str = "";
2786
- let cutArr = [];
2787
- console.log(chalk.green(`共有${allArr.length}条数据需要翻译`));
2788
-
2789
- allArr.forEach((item, index) => {
2790
- if (str.length > 2000) {
2791
- groupArr.push(cutArr);
2792
- str = "";
2793
- cutArr = [];
2794
- }
2795
- str += item.ZH_CN + "\n";
2796
- cutArr.push(item);
2797
- });
2798
- if (cutArr.length > 0) {
2799
- Array.prototype.push.apply(groupArr, [cutArr]);
2800
- } else {
2801
- groupArr = allArr;
2802
- }
2803
- console.log(chalk.green(`共有${groupArr.length}组数据需要翻译`));
2804
- if (!groupArr.length) return;
2805
- const task = groupArr.map(async (item, index) => {
2806
- let str = "";
2807
- item.forEach((item1, index1) => {
2808
- // 去除换行空格
2809
- const ZH_CN = item1.ZH_CN.replace(/\n/g, "");
2810
- str += ZH_CN + "\n";
2811
- });
2812
- return translate(item, str, index + 1);
2813
- });
2814
- Promise.allSettled(task).then(async (res) => {
2815
- console.log(chalk.green("任务执行完毕"));
2816
- groupArr.forEach((item, index) => {
2817
- Array.prototype.push.apply(newAllArr, item);
2818
- });
2819
- // 如果中文不同,但翻译的英文相同(比如商品品类和商品类别翻译的英文都是Product Category),对key进行自动+1
2820
- for (let i = 0; i < newAllArr.length; i++) {
2821
- let num = 0;
2822
- for (let j = 0; j < newAllArr.length; j++) {
2823
- // key存在且不能为空
2824
- if (newAllArr[j].key && newAllArr[i].key) {
2825
- if (
2826
- newAllArr[j].key === newAllArr[i].key &&
2827
- newAllArr[j].ZH_CN !== newAllArr[i].ZH_CN
2828
- ) {
2829
- num++;
2830
- newAllArr[j].key = newAllArr[j].key + num;
2831
- }
2832
- }
2833
- }
2834
- }
2835
- readFolder(true);
2836
- // 如果key相等去重
2837
- const uniqueArr = Array.from(
2838
- new Map(newAllArr.map((item) => [item.key, item])).values()
2839
- );
2840
- let exportData = [];
2841
- // key中有debug排后面
2842
- const sortNewAllArr = _.sortBy(uniqueArr, function (item) {
2843
- return item.key.indexOf("debug") > -1;
2844
- });
2845
- sortNewAllArr.forEach((item, index) => {
2846
- const rowArr = [
2847
- "GIT", // 系统
2848
- projectName, // 项目
2849
- "frontInit", // 模块(路由)
2850
- "", // 编码
2851
- 1, // 所属(1:前端 2:后端)
2852
- 1, // 数据类型(1:static,2:enum,3:json)
2853
- "", // 中文值
2854
- "", // 英文
2855
- "", // 泰文(TH)
2856
- "", // 越南(VI)
2857
- "", // 土耳其(TR)
2858
- ];
2859
- const cacheIndex = {
2860
- ZH_CN: 6,
2861
- EN: 7,
2862
- key: 3,
2863
- };
2864
- Object.entries(item).forEach(([key, value], index1) => {
2865
- const insertIndex = cacheIndex[key];
2866
- rowArr[insertIndex] = value;
2867
- });
2868
- exportData.push(rowArr);
2869
- });
2870
- const { isMerge } = await inquirer.prompt([
2871
- {
2872
- message: `是否合并至export.xlsx,默认${chalk.red("否,直接覆盖")}`,
2873
- name: "isMerge",
2874
- type: "confirm",
2875
- default: false,
2876
- },
2877
- ]);
2878
- try {
2879
- if (isMerge) {
2880
- const str = xlsx.parse(fs.readFileSync("./export.xlsx"));
2881
- if (str[0].data.length) {
2882
- exportData = str[0].data.concat(exportData);
2883
- } else {
2884
- throw new Error();
2885
- }
2886
- } else {
2887
- throw new Error();
2888
- }
2889
- } catch (error) {
2890
- // 第一行插入标题
2891
- exportData.unshift([
2892
- "系统",
2893
- "项目",
2894
- "模块",
2895
- "编码",
2896
- "所属(1:前端 2:后端)",
2897
- "数据类型(1:static,2:enum,3:json)",
2898
- "中文值",
2899
- "英文",
2900
- "泰文(TH)",
2901
- "越南(VI)",
2902
- "土耳其(TR)",
2903
- ]);
2904
- }
2905
- const buffer = xlsx.build([{ data: exportData }]);
2906
- fs.writeFileSync(`./export.xlsx`, buffer, "binary");
2907
- console.log(chalk.green(`${isMerge ? "合并" : "覆盖"}文件成功`));
2908
- });
2909
- }
2910
-
2911
- // 百度翻译接口
2912
- async function translate(item, str, groupIndex) {
2913
- const q = str;
2914
- const salt = Math.random();
2915
- const from = "zh";
2916
- const to = "en";
2917
- const sign = md5(appid + q + salt + key);
2918
- const query = querystring.stringify({
2919
- q,
2920
- appid,
2921
- salt,
2922
- from,
2923
- to,
2924
- sign,
2925
- });
2926
- const url = "http://api.fanyi.baidu.com/api/trans/vip/translate?" + query;
2927
- return new Promise((resolve, reject) => {
2928
- // 自动翻译延时,必须大于 1000 ms,否则调用百度翻译 API 会失败
2929
- throttle(() => {
2930
- console.log(chalk.green(`正在翻译第${groupIndex}组数据`));
2931
- return request(url, function (_error, response, body) {
2932
- const resBody = JSON.parse(body);
2933
- if (resBody.trans_result) {
2934
- console.log(`ok`);
2935
- let result = resBody.trans_result || [];
2936
-
2937
- item.forEach((item1, index1) => {
2938
- const id = uuid.v5(item1.path, uuid.v5.URL).slice(0, 6);
2939
- if (
2940
- result[index1] &&
2941
- (result[index1].src === item1.ZH_CN ||
2942
- result[index1].src === item1.ZH_CN.trim()) // 去除前后空格,原[ 执行人: ]翻译变成[执行人:]
2943
- ) {
2944
- // 删除str中的单引号或双引号或斜杠或逗号
2945
- let str = result[index1].dst
2946
- .replace(/\'/g, "")
2947
- .replace(/\"/g, "")
2948
- .replace(/\//g, "")
2949
- .replace(/\,/g, "");
2950
- // 每个单词首首字母转换为大写
2951
- const s = str.split(" ");
2952
- let key = s
2953
- .map((item, index) => {
2954
- return item ? item[0].toUpperCase() + item.slice(1) : item;
2955
- })
2956
- .join("");
2957
- // 去除key中的标点符号
2958
- key = key.replace(/[^a-zA-Z0-9{}]/g, "").slice(0, 73);
2959
- let EN = result[index1].dst;
2960
- EN = EN[0].toUpperCase() + EN.slice(1);
2961
- item1.EN = EN;
2962
- if (isLintFix) {
2963
- // item1.key = nanoid().replace("_", "a");
2964
- item1.key = id + "-" + key;
2965
- } else {
2966
- item1.key = id + "-" + key;
2967
- }
2968
- }
2969
- });
2970
- resolve(true);
2971
- } else {
2972
- item.forEach((item1, index1) => {
2973
- const id = uuid.v5(item1.path, uuid.v5.URL).slice(0, 6);
2974
- item1.key = id + "-" + "debug";
2975
- });
2976
- console.log(chalk.red(`error: ${body}`));
2977
- resolve(true);
2978
- }
2979
- });
2980
- });
2981
- });
2982
- }
2983
- // md5加密
2984
- function md5(str) {
2985
- const md5 = crypto.createHash("md5");
2986
- md5.update(str);
2987
- return md5.digest("hex");
2988
- }
2989
-
2990
- // 节流函数
2991
- const delay = 2000;
2992
- const throttle = (function (delay = 1500) {
2993
- const wait = [];
2994
- let canCall = true;
2995
- return function throttle(callback) {
2996
- if (!canCall) {
2997
- if (callback) wait.push(callback);
2998
- return;
2999
- }
3000
-
3001
- callback();
3002
- canCall = false;
3003
- setTimeout(() => {
3004
- canCall = true;
3005
- if (wait.length) {
3006
- throttle(wait.shift());
3007
- }
3008
- }, delay);
3009
- };
3010
- })(delay);
3011
- // 开始方法
3012
- readFolder();
2784
+ async function getColumn(moduleArr = [], dynamicsArr = [], parentRule) {
2785
+ let token;
2786
+ try {
2787
+ token = await login({
2788
+ env: "sit",
2789
+ username: "superAdmin",
2790
+ password: "admin1",
2791
+ });
2792
+ } catch (e) {
2793
+ console.log(chalk.red(`登录失败: ${e.message || e}`));
2794
+ return;
2795
+ }
2796
+ const requestArr = moduleArr.map(({ module, path }) =>
2797
+ request$1({
2798
+ url: `http://sit-hxjf.hongxinshop.com/api-item/api/customizedColumns/find`,
2799
+ method: "POST",
2800
+ json: true,
2801
+ body: { module },
2802
+ headers: {
2803
+ Authorization: `Bearer ${token}`,
2804
+ "x-request-vaildate": "open",
2805
+ },
2806
+ })
2807
+ );
2808
+ Promise.allSettled(requestArr).then(async (res) => {
2809
+ const tableData = [];
2810
+ res.forEach(({ value, status }, index) => {
2811
+ const { path, module } = moduleArr[index];
2812
+ if (status === "fulfilled") {
2813
+ const { data } = value;
2814
+ const { showColumns, hiddenColumns } = data;
2815
+ let fieldArr = [];
2816
+ showColumns.concat(hiddenColumns).forEach((item) => {
2817
+ if (condition[parentRule](item.showField)) {
2818
+ fieldArr.push(`${item.showField} ${item.showName}`);
2819
+ }
2820
+ });
2821
+ if (fieldArr.length) {
2822
+ fieldArr.forEach((item, index) => {
2823
+ let obj = {
2824
+ module,
2825
+ field: item,
2826
+ path,
2827
+ };
2828
+ if (index !== 0) {
2829
+ // obj.module = module;
2830
+ // obj.path = path;
2831
+ delete obj.module;
2832
+ delete obj.path;
2833
+ }
2834
+ tableData.push(obj);
2835
+ });
2836
+ } else {
2837
+ tableData.push({
2838
+ module,
2839
+ field: "NA",
2840
+ path,
2841
+ });
2842
+ }
2843
+ } else {
2844
+ tableData.push({
2845
+ module,
2846
+ field: "error",
2847
+ path,
2848
+ });
2849
+ }
2850
+ });
2851
+ // 合并动态模块
2852
+ dynamicsArr.forEach((item) => {
2853
+ tableData.push({
2854
+ module: item.module,
2855
+ field: "error",
2856
+ path: item.path,
2857
+ });
2858
+ });
2859
+ // 排序, field为NA和error的排在后面
2860
+ tableData.sort((a, b) => {
2861
+ if (["NA", "error"].includes(b.field)) {
2862
+ return -1;
2863
+ } else {
2864
+ return 1;
2865
+ }
2866
+ });
2867
+ const successModuleArr = new Set();
2868
+
2869
+ const naTableData = [];
2870
+ const naModuleArr = new Set();
2871
+
2872
+ const noNaTableData = [];
2873
+ const noNaModuleArr = new Set();
2874
+
2875
+ const errorTableData = []; // 失败的数据
2876
+ const errorModuleArr = new Set();
2877
+
2878
+ tableData.forEach((item) => {
2879
+ if (item.field === "error") {
2880
+ errorTableData.push(item);
2881
+ item.module && errorModuleArr.add(item.module);
2882
+ } else {
2883
+ if (item.field === "NA") {
2884
+ naTableData.push(item);
2885
+ item.module &&naModuleArr.add(item.module);
2886
+ } else {
2887
+ noNaTableData.push(item);
2888
+ item.module &&noNaModuleArr.add(item.module);
2889
+ }
2890
+ item.module &&successModuleArr.add(item.module);
2891
+ }
2892
+ });
2893
+
2894
+ console.log(
2895
+ chalk.green(
2896
+ `共${moduleArr.length + dynamicsArr.length}个moduleId, 请求成功${
2897
+ successModuleArr.size
2898
+ }个(其中规则命中${noNaModuleArr.size}个, ${chalk.yellow(
2899
+ `未命中${naModuleArr.size}个`
2900
+ )}),`
2901
+ ) + chalk.red(`请求失败${errorModuleArr.size}个`)
2902
+ );
2903
+ console.log(chalk.green(`🚀🚀🚀🚀🚀共匹配到${noNaTableData.length+errorTableData.length}个字段🚀🚀🚀🚀🚀`));
2904
+
2905
+ if (noNaTableData.length) {
2906
+ console.log(chalk.green(`成功命中字段${noNaTableData.length}个`));
2907
+ console.table(noNaTableData);
2908
+ }
2909
+ if (errorTableData.length) {
2910
+ console.log(chalk.red(`失败,需手动处理或重试${errorTableData.length}个`));
2911
+ console.table(errorTableData);
2912
+ }
2913
+ if (naTableData.length) {
2914
+ console.log(
2915
+ chalk.yellow(`成功但未命中字段,无需处理${naTableData.length}个`)
2916
+ );
2917
+ console.table(naTableData);
2918
+ }
2919
+ });
2920
+ }
2921
+
2922
+ // import packageJson from "../package.json" assert { type: "json" };
2923
+ const { version } = getPackageJson();
2924
+
2925
+ // 获取git修改的文件以及暂存区的文件(包含未跟踪的新文件)
2926
+ function getGitChangedFiles() {
2927
+ const cwd = process.cwd();
2928
+ try {
2929
+ const repoRoot = process$1.execSync("git rev-parse --show-toplevel", {
2930
+ cwd,
2931
+ encoding: "utf-8",
2932
+ }).trim();
2933
+ // 1. git status --porcelain 获取已跟踪文件的变更(已暂存staged + 未暂存unstaged)
2934
+ // 注意: 不能用 git diff --name-only HEAD, 它不会包含未跟踪(untracked)的新文件
2935
+ const statusOutput = process$1.execSync("git status --porcelain", {
2936
+ cwd: repoRoot,
2937
+ encoding: "utf-8",
2938
+ });
2939
+ const trackedFiles = statusOutput
2940
+ .split("\n")
2941
+ .map((line) => line.replace(/\r$/, ""))
2942
+ .filter(Boolean)
2943
+ .map((line) => {
2944
+ // 输出格式: XY filename, X=暂存区状态, Y=工作区状态
2945
+ const status = line.slice(0, 2);
2946
+ let filePath = line.slice(3);
2947
+ // 重命名(R)/复制(C): "old -> new", 取新文件名
2948
+ if (status[0] === "R" || status[0] === "C") {
2949
+ const idx = filePath.indexOf(" -> ");
2950
+ if (idx > -1) filePath = filePath.slice(idx + 4);
2951
+ }
2952
+ // 含特殊字符的文件名git会加引号, 去除引号
2953
+ if (filePath.startsWith('"') && filePath.endsWith('"')) {
2954
+ filePath = filePath.slice(1, -1);
2955
+ }
2956
+ return { status, filePath };
2957
+ })
2958
+ // 排除删除的文件 (D 在暂存区或工作区)
2959
+ .filter(({ status }) => !status.includes("D"))
2960
+ // 未跟踪文件(??)用 git ls-files 单独处理(因为可能只显示目录,不展开)
2961
+ .filter(({ status }) => status !== "??")
2962
+ .map(({ filePath }) => filePath);
2963
+
2964
+ // 2. git ls-files --others --exclude-standard 获取未跟踪的新文件
2965
+ // 相比 git status --porcelain, 它会自动展开目录, 列出具体文件
2966
+ const untrackedOutput = process$1.execSync(
2967
+ "git ls-files --others --exclude-standard",
2968
+ { cwd: repoRoot, encoding: "utf-8" }
2969
+ );
2970
+ const untrackedFiles = untrackedOutput
2971
+ .split("\n")
2972
+ .map((f) => f.trim())
2973
+ .filter(Boolean);
2974
+
2975
+ // 合并去重
2976
+ const allFiles = Array.from(
2977
+ new Set([...trackedFiles, ...untrackedFiles])
2978
+ )
2979
+ // 只保留lint支持的文件类型
2980
+ .filter((f) => /\.(js|jsx|vue|ts|tsx)$/.test(f))
2981
+ // git输出的路径相对于仓库根目录, 转为绝对路径
2982
+ .map((f) => path.join(repoRoot, f))
2983
+ // 过滤掉不存在的文件
2984
+ .filter((f) => fs.existsSync(f));
2985
+ return allFiles;
2986
+ } catch (error) {
2987
+ // git命令失败(如不在git仓库中运行)属于使用错误, 以退出码1结束
2988
+ console.log(chalk.red("获取git修改文件失败,请确保在git仓库中运行"));
2989
+ process.exit(1);
2990
+ }
2991
+ }
2992
+ // const spinner = ora();
2993
+ commander.program.version(version);
2994
+ commander.program.option("-i, --input <type>", "翻译空key");
2995
+ // command('merge')
2996
+ // .option("-i, --input <type>" , "翻译空key")
2997
+ // .option("-m, --merge" , "输出文件").action((options) => {
2998
+ // console.log('merge', options)
2999
+ // })
3000
+ // 上传翻译
3001
+ commander.program.option("-u, --upload", "上传翻译包");
3002
+ // 同步sass应用权限配置
3003
+ commander.program.option("-sass, --sass", "sass按钮权限配置(同步/新增)");
3004
+ // 检测未$hxt中文
3005
+ commander.program.option("-lint, --lint <patterns>", "检测未$hxt的中文");
3006
+ // 检测配置项
3007
+ commander.program.option("-lintc, --lintc <patterns>", "检测指定配置规则");
3008
+ // 检测未$hxt中文自动修复
3009
+ commander.program.option("--fix", "修复lint检出的错误");
3010
+ // 翻译excel指定列语言
3011
+ commander.program.option("-excel, --excel", "翻译excel指定列语言");
3012
+ // 一键执行选择参数(取值优先级: CLI参数 > --cache凭据缓存 > 交互询问)
3013
+ commander.program.option("--cache", "凭据静默走缓存(百度appid/key、域账号密码), 缓存缺失时回退交互");
3014
+ commander.program.option("--env <env>", "上传环境: mit|sit|uat|pro, 多环境逗号分隔(仅mit/sit/uat, pro须单独传); sass新增时单选mit|sit|uat");
3015
+ commander.program.option("--username <name>", "域账号(env为pro时为生产域账号)");
3016
+ commander.program.option("--password <pwd>", "域密码(明文会进shell历史, 日常建议用--cache)");
3017
+ commander.program.option("--files <files>", "上传的excel文件, 逗号分隔或all(全部), 如 --files \"a.xlsx,b.xlsx\"");
3018
+ commander.program.option("--appid <appid>", "百度翻译appid");
3019
+ commander.program.option("--key <key>", "百度翻译key");
3020
+ commander.program.option("--merge", "翻译结果合并至export.xlsx(默认覆盖)");
3021
+ commander.program.option("--from <env>", "sass同步源环境: mit|sit|uat");
3022
+ commander.program.option("--to <env>", "sass同步目标环境: mit|sit|uat(须不同于--from)");
3023
+ commander.program.option("--app <name>", "sass应用名称关键字(同步/新增)");
3024
+ commander.program.option("--action <action>", "sass功能: sync(同步按钮权限) | permission(新增按钮权限)");
3025
+ commander.program.option("--permission-name <name>", "sass按钮权限名称(须搭配 --action permission)");
3026
+ commander.program.option("--permission-code <code>", "sass按钮权限编码(须搭配 --action permission)");
3027
+ commander.program.option("--rule <rule>", "lintc检查规则: currency");
3028
+ commander.program.option("--scene <scene>", "lintc检查场景: currency|customColumnModule");
3029
+ commander.program.option("--way <way>", "自定义列检测方式: normal|input");
3030
+ commander.program.option("--column-id <ids>", "自定义列ID, 逗号分隔(way=input时), 如 --column-id \"a,b,c\"");
3031
+ commander.program.option("--file <name>", "excel翻译的文件名");
3032
+ commander.program.option("--lang <lang>", "excel翻译的语言列(如en、vi)");
3033
+ // help追加的一键执行示例(命令绿色, 说明灰色, 按最长命令对齐)
3034
+ const helpExamples = [
3035
+ ["fe-it-beta -u --env sit --files all --cache", "上传全部excel到sit, 账号密码走缓存"],
3036
+ ["fe-it-beta -u --env sit --files all --username xxx --cache", "账号用传的, 其余凭据走缓存(可混合)"],
3037
+ ['fe-it-beta -u --env "mit,sit,uat" --files export.xlsx --cache', ""],
3038
+ ["fe-it-beta -i src --cache", "翻译空key, 凭据走缓存, 覆盖export.xlsx"],
3039
+ ["fe-it-beta -i src --appid x --key y --merge", "翻译并合并至export.xlsx"],
3040
+ ["fe-it-beta -lint src --fix", "检测并自动修复"],
3041
+ [
3042
+ 'fe-it-beta -lintc src --rule currency --scene customColumnModule --way input --column-id "a,b,c"',
3043
+ "",
3044
+ ],
3045
+ ["fe-it-beta -sass --from mit --to sit --app 应用名", "传同步参数直通同步流程"],
3046
+ [
3047
+ "fe-it-beta -sass --action permission --env mit --app 应用名 --permission-name 权限名 --permission-code 编码",
3048
+ "一键新增按钮权限(参数齐全免确认)",
3049
+ ],
3050
+ ["fe-it-beta -excel --file export.xlsx --lang vi --cache", ""],
3051
+ ];
3052
+ commander.program.addHelpText(
3053
+ "after",
3054
+ "\n" +
3055
+ chalk.bold("一键执行示例") +
3056
+ " " +
3057
+ chalk.gray("(注意: PowerShell下含逗号的参数值需加引号)") +
3058
+ ":\n" +
3059
+ helpExamples
3060
+ .map(([cmd, desc]) => {
3061
+ // 说明列固定从62列起, 超长命令后接2空格
3062
+ const pad = " ".repeat(Math.max(2, 62 - cmd.length));
3063
+ return " " + chalk.green(cmd) + (desc ? chalk.gray(pad + desc) : "");
3064
+ })
3065
+ .join("\n")
3066
+ );
3067
+ commander.program.parse(process.argv);
3068
+
3069
+ // 判断命令参数
3070
+ const { input, sass, upload, lint, lintc, fix, excel } = commander.program.opts();
3071
+ const cliOpts = commander.program.opts();
3072
+ const rules = [
3073
+ {
3074
+ message: "请选择检查规则",
3075
+ name: "rule",
3076
+ type: "list",
3077
+ choices: [
3078
+ {
3079
+ name: "币种",
3080
+ value: "currency",
3081
+ children: [
3082
+ {
3083
+ message: "请选择场景",
3084
+ name: "scene",
3085
+ type: "list",
3086
+ choices: [
3087
+ {
3088
+ name: "匹配属性名含有price、total、amount金额字段",
3089
+ value: "currency",
3090
+ },
3091
+ {
3092
+ name: "自定义列",
3093
+ value: "customColumnModule",
3094
+ },
3095
+ ],
3096
+ },
3097
+ ],
3098
+ },
3099
+ ],
3100
+ },
3101
+ ];
3102
+ if (sass) {
3103
+ sassMain(cliOpts);
3104
+ } else if (upload) {
3105
+ upload$1(cliOpts);
3106
+ } else if (lint !== undefined) {
3107
+ // 必须传入路径
3108
+ if (!lint || !lint.trim()) {
3109
+ console.log(
3110
+ chalk.red("请传入要检测的路径, 或使用 -lint git 检测git修改的文件")
3111
+ );
3112
+ process.exit(1);
3113
+ }
3114
+ let lintPatterns = lint.trim();
3115
+ // 如果路径写死为 git, 则用git检测修改的文件以及暂存区的文件作为路径
3116
+ if (lintPatterns === "git") {
3117
+ lintPatterns = getGitChangedFiles();
3118
+ if (!lintPatterns.length) {
3119
+ console.log(chalk.green("没有检测到git修改的文件"));
3120
+ process.exit(0);
3121
+ }
3122
+ console.log(chalk.green(`检测到${lintPatterns.length}个git修改文件:`));
3123
+ lintPatterns.forEach((f) => console.log(chalk.gray(` ${f}`)));
3124
+ }
3125
+ lint$1(lintPatterns, fix);
3126
+ } else if (lintc) {
3127
+ runLintc().catch((error) => console.error(error));
3128
+ } else if (excel) {
3129
+ excelFn(cliOpts);
3130
+ } else {
3131
+ runTranslate().catch((error) => console.error(error));
3132
+ }
3133
+
3134
+ // lintc分支(支持 --rule/--scene 一键执行)
3135
+ async function runLintc() {
3136
+ const ruleQuestion = rules.find((item) => item.name === "rule");
3137
+ const ruleValues = ruleQuestion.choices.map((item) => item.value);
3138
+ if (cliOpts.rule !== undefined) {
3139
+ validateChoice(cliOpts.rule, ruleValues, "--rule");
3140
+ }
3141
+ const parentRule = await askOrUse(cliOpts.rule, ruleQuestion);
3142
+ if (parentRule === "currency") {
3143
+ const currencySubChoice = ruleQuestion.choices.find(
3144
+ (item) => item.value === "currency"
3145
+ ).children;
3146
+ const sceneValues = currencySubChoice[0].choices.map((item) => item.value);
3147
+ if (cliOpts.scene !== undefined) {
3148
+ validateChoice(cliOpts.scene, sceneValues, "--scene");
3149
+ }
3150
+ const scene = await askOrUse(cliOpts.scene, currencySubChoice[0]);
3151
+ if (scene === "customColumnModule") {
3152
+ return customColumnModule(lintc, fix, scene, parentRule, cliOpts);
3153
+ }
3154
+ lint$1(lintc, fix, scene);
3155
+ }
3156
+ }
3157
+
3158
+ // 默认翻译分支(支持 --appid/--key/--merge/--cache 一键执行)
3159
+ async function runTranslate() {
3160
+ // 前置守卫: 必须传入 -i 路径
3161
+ if (!input || !String(input).trim()) {
3162
+ console.log(chalk.red("请传入要翻译的路径 -i <path>"));
3163
+ process.exit(1);
3164
+ }
3165
+ const { cache, appid, key, merge } = cliOpts;
3166
+ const oneClick = Boolean(cache || appid || key || merge);
3167
+ let projectName = "请在package.json中配置name字段(项目名称)";
3168
+ const projectPath = "./package.json";
3169
+ const isExist = isExistPath(projectPath);
3170
+ if (isExist) {
3171
+ const str = fs.readFileSync(projectPath, "utf-8").toString();
3172
+ projectName = JSON.parse(str).name;
3173
+ }
3174
+ if (!oneClick) {
3175
+ const { isProjectName } = await inquirer.prompt([
3176
+ {
3177
+ message: `请确认package.json中项目名称是否是${projectName}`,
3178
+ name: "isProjectName",
3179
+ type: "confirm",
3180
+ default: true,
3181
+ },
3182
+ ]);
3183
+ if (!isProjectName) {
3184
+ console.log(
3185
+ chalk.red("请在package.json中配置正确的name字段(项目名称)")
3186
+ );
3187
+ process.exit(1);
3188
+ }
3189
+ }
3190
+ // 翻译接口(当前仅百度, 一键执行时自动选择)
3191
+ let type = "baidu";
3192
+ if (!oneClick) {
3193
+ const res = await inquirer.prompt([
3194
+ {
3195
+ message: "请选择翻译接口",
3196
+ name: "type",
3197
+ type: "list",
3198
+ choices: [
3199
+ {
3200
+ name: "百度翻译",
3201
+ value: "baidu",
3202
+ },
3203
+ ],
3204
+ },
3205
+ ]);
3206
+ type = res.type;
3207
+ }
3208
+ // 凭据: CLI参数 > --cache缓存 > 交互
3209
+ const secret = await resolveSecretQuestions({
3210
+ questions: [
3211
+ {
3212
+ message: "请输入百度翻译的appid",
3213
+ name: "appid",
3214
+ // 必填
3215
+ validate: function (val) {
3216
+ if (val) {
3217
+ return true;
3218
+ }
3219
+ return "请输入百度翻译的appid";
3220
+ },
3221
+ },
3222
+ {
3223
+ message: "请输入百度翻译的key",
3224
+ name: "key",
3225
+ // 必填
3226
+ validate: function (val) {
3227
+ if (val) {
3228
+ return true;
3229
+ }
3230
+ return "请输入百度翻译的key";
3231
+ },
3232
+ },
3233
+ ],
3234
+ cliValues: { appid, key },
3235
+ useCache: Boolean(cache),
3236
+ cacheKey: type,
3237
+ extraCache: { type },
3238
+ });
3239
+ // isMerge预决定: --merge为true, 一键执行默认false(覆盖), 否则翻译完成后交互询问
3240
+ const isMergeDecision = merge ? true : oneClick ? false : null;
3241
+ translateUtil(secret.appid, secret.key, projectName, fix, isMergeDecision);
3242
+ }
3243
+
3244
+ function translateUtil(appid, key, projectName, isLintFix, isMergeDecision) {
3245
+ const { input } = commander.program.opts();
3246
+ // 支持逗号分隔的多个路径(来自 -lint git 的多文件场景)
3247
+ const inputPaths = String(input)
3248
+ .split(",")
3249
+ .map((s) => s.trim())
3250
+ .filter(Boolean);
3251
+ let src = [];
3252
+ inputPaths.forEach((inputPath) => {
3253
+ const normalizedInput = inputPath.replace(/\\/g, "/");
3254
+ if (fs.statSync(inputPath).isDirectory()) {
3255
+ const directory = normalizedInput + "/**/*.{js,vue,jsx,ts,tsx}";
3256
+ const files = glob.glob.sync(directory, {
3257
+ ignore: directory + `/**/node_modules/**`,
3258
+ });
3259
+ src = src.concat(files);
3260
+ } else {
3261
+ src.push(normalizedInput);
3262
+ }
3263
+ });
3264
+ let allArr = []; // 所有文本组成的数组
3265
+ let groupArr = []; // 分组后的数组
3266
+ let newAllArr = []; // 分组后的数组还原成所有数组
3267
+ // 循环遍历文件下的文件
3268
+ function readFolder(isOk) {
3269
+ // spinner.start();
3270
+ src.forEach((item, index) => {
3271
+ const fPath = item;
3272
+ let file = fs.readFileSync(fPath, "utf-8").toString();
3273
+ // 提取file里所有以$hxt({ key: '', desc:为开头 以')}结尾的字符串
3274
+ const reg =
3275
+ /\$hxt\([\r\n\s]*\{[\r\n\s]*key[\r\n\s]*:[\r\n\s]*['|"][\r\n\s]*['|"],[\r\n\s]*desc[\r\n\s]*:[\r\n\s]*['|"|`](.*?)['|"|`],*[\r\n\s]*\}(\)|,[\r\n\s]*\{)/g;
3276
+ file = file.replace(reg, function (match, p1, p2) {
3277
+ if (isOk) {
3278
+ const reg1 = /key[\r\n\s]*:[\r\n\s]*('|")[\r\n\s]*('|")/g;
3279
+ let key = newAllArr.find(
3280
+ (item) =>
3281
+ item.path === fPath &&
3282
+ (item.ZH_CN === p1 || item.ZH_CN === p1.trim()) // 去除前后空格,原[ 执行人: ]翻译变成[执行人:]
3283
+ ).key;
3284
+ // 去除{ slotxx }
3285
+ // key = key.replace(/\{[\r\n\s]*slot\d[\r\n\s]*\}/g, "");
3286
+ // 去除空格
3287
+ key = key.replace(/\s/g, "");
3288
+ return match.replace(reg1, `key: '${key}'`);
3289
+ } else {
3290
+ allArr.push({
3291
+ path: fPath,
3292
+ ZH_CN: p1,
3293
+ EN: "",
3294
+ key: "",
3295
+ });
3296
+ }
3297
+ });
3298
+ if (isOk) {
3299
+ fs.writeFileSync(fPath, file, "utf-8");
3300
+ }
3301
+ });
3302
+ if (isOk) {
3303
+ // spinner.stop();
3304
+ return;
3305
+ }
3306
+ // 数组分割,如果str长度大于2000,就分割成多个数组
3307
+ let str = "";
3308
+ let cutArr = [];
3309
+ console.log(chalk.green(`共有${allArr.length}条数据需要翻译`));
3310
+
3311
+ allArr.forEach((item, index) => {
3312
+ if (str.length > 2000) {
3313
+ groupArr.push(cutArr);
3314
+ str = "";
3315
+ cutArr = [];
3316
+ }
3317
+ str += item.ZH_CN + "\n";
3318
+ cutArr.push(item);
3319
+ });
3320
+ if (cutArr.length > 0) {
3321
+ Array.prototype.push.apply(groupArr, [cutArr]);
3322
+ } else {
3323
+ groupArr = allArr;
3324
+ }
3325
+ console.log(chalk.green(`共有${groupArr.length}组数据需要翻译`));
3326
+ if (!groupArr.length) return;
3327
+ const task = groupArr.map(async (item, index) => {
3328
+ let str = "";
3329
+ item.forEach((item1, index1) => {
3330
+ // 去除换行空格
3331
+ const ZH_CN = item1.ZH_CN.replace(/\n/g, "");
3332
+ str += ZH_CN + "\n";
3333
+ });
3334
+ return translate(item, str, index + 1);
3335
+ });
3336
+ Promise.allSettled(task).then(async (res) => {
3337
+ console.log(chalk.green("任务执行完毕"));
3338
+ groupArr.forEach((item, index) => {
3339
+ Array.prototype.push.apply(newAllArr, item);
3340
+ });
3341
+ // 如果中文不同,但翻译的英文相同(比如商品品类和商品类别翻译的英文都是Product Category),对key进行自动+1
3342
+ for (let i = 0; i < newAllArr.length; i++) {
3343
+ let num = 0;
3344
+ for (let j = 0; j < newAllArr.length; j++) {
3345
+ // key存在且不能为空
3346
+ if (newAllArr[j].key && newAllArr[i].key) {
3347
+ if (
3348
+ newAllArr[j].key === newAllArr[i].key &&
3349
+ newAllArr[j].ZH_CN !== newAllArr[i].ZH_CN
3350
+ ) {
3351
+ num++;
3352
+ newAllArr[j].key = newAllArr[j].key + num;
3353
+ }
3354
+ }
3355
+ }
3356
+ }
3357
+ readFolder(true);
3358
+ // 如果key相等去重
3359
+ const uniqueArr = Array.from(
3360
+ new Map(newAllArr.map((item) => [item.key, item])).values()
3361
+ );
3362
+ let exportData = [];
3363
+ // key中有debug排后面
3364
+ const sortNewAllArr = _.sortBy(uniqueArr, function (item) {
3365
+ return item.key.indexOf("debug") > -1;
3366
+ });
3367
+ sortNewAllArr.forEach((item, index) => {
3368
+ const rowArr = [
3369
+ "GIT", // 系统
3370
+ projectName, // 项目
3371
+ "frontInit", // 模块(路由)
3372
+ "", // 编码
3373
+ 1, // 所属(1:前端 2:后端)
3374
+ 1, // 数据类型(1:static,2:enum,3:json)
3375
+ "", // 中文值
3376
+ "", // 英文
3377
+ "", // 泰文(TH)
3378
+ "", // 越南(VI)
3379
+ "", // 土耳其(TR)
3380
+ ];
3381
+ const cacheIndex = {
3382
+ ZH_CN: 6,
3383
+ EN: 7,
3384
+ key: 3,
3385
+ };
3386
+ Object.entries(item).forEach(([key, value], index1) => {
3387
+ const insertIndex = cacheIndex[key];
3388
+ rowArr[insertIndex] = value;
3389
+ });
3390
+ exportData.push(rowArr);
3391
+ });
3392
+ // isMergeDecision: true/false为预决定值(一键执行), null表示仍需交互询问
3393
+ let isMerge = isMergeDecision;
3394
+ if (isMerge === null || isMerge === undefined) {
3395
+ const res = await inquirer.prompt([
3396
+ {
3397
+ message: `是否合并至export.xlsx,默认${chalk.red("否,直接覆盖")}`,
3398
+ name: "isMerge",
3399
+ type: "confirm",
3400
+ default: false,
3401
+ },
3402
+ ]);
3403
+ isMerge = res.isMerge;
3404
+ }
3405
+ try {
3406
+ if (isMerge) {
3407
+ const str = xlsx.parse(fs.readFileSync("./export.xlsx"));
3408
+ if (str[0].data.length) {
3409
+ exportData = str[0].data.concat(exportData);
3410
+ } else {
3411
+ throw new Error();
3412
+ }
3413
+ } else {
3414
+ throw new Error();
3415
+ }
3416
+ } catch (error) {
3417
+ // 第一行插入标题
3418
+ exportData.unshift([
3419
+ "系统",
3420
+ "项目",
3421
+ "模块",
3422
+ "编码",
3423
+ "所属(1:前端 2:后端)",
3424
+ "数据类型(1:static,2:enum,3:json)",
3425
+ "中文值",
3426
+ "英文",
3427
+ "泰文(TH)",
3428
+ "越南(VI)",
3429
+ "土耳其(TR)",
3430
+ ]);
3431
+ }
3432
+ const buffer = xlsx.build([{ data: exportData }]);
3433
+ fs.writeFileSync(`./export.xlsx`, buffer, "binary");
3434
+ console.log(chalk.green(`${isMerge ? "合并" : "覆盖"}文件成功`));
3435
+ });
3436
+ }
3437
+
3438
+ // 百度翻译接口
3439
+ async function translate(item, str, groupIndex) {
3440
+ const q = str;
3441
+ const salt = Math.random();
3442
+ const from = "zh";
3443
+ const to = "en";
3444
+ const sign = md5(appid + q + salt + key);
3445
+ const query = querystring.stringify({
3446
+ q,
3447
+ appid,
3448
+ salt,
3449
+ from,
3450
+ to,
3451
+ sign,
3452
+ });
3453
+ const url = "http://api.fanyi.baidu.com/api/trans/vip/translate?" + query;
3454
+ return new Promise((resolve, reject) => {
3455
+ // 自动翻译延时,必须大于 1000 ms,否则调用百度翻译 API 会失败
3456
+ throttle(() => {
3457
+ console.log(chalk.green(`正在翻译第${groupIndex}组数据`));
3458
+ return request(url, function (_error, response, body) {
3459
+ const resBody = JSON.parse(body);
3460
+ if (resBody.trans_result) {
3461
+ console.log(`ok`);
3462
+ let result = resBody.trans_result || [];
3463
+
3464
+ item.forEach((item1, index1) => {
3465
+ const id = uuid.v5(item1.path, uuid.v5.URL).slice(0, 6);
3466
+ if (
3467
+ result[index1] &&
3468
+ (result[index1].src === item1.ZH_CN ||
3469
+ result[index1].src === item1.ZH_CN.trim()) // 去除前后空格,原[ 执行人: ]翻译变成[执行人:]
3470
+ ) {
3471
+ // 删除str中的单引号或双引号或斜杠或逗号
3472
+ let str = result[index1].dst
3473
+ .replace(/\'/g, "")
3474
+ .replace(/\"/g, "")
3475
+ .replace(/\//g, "")
3476
+ .replace(/\,/g, "");
3477
+ // 每个单词首首字母转换为大写
3478
+ const s = str.split(" ");
3479
+ let key = s
3480
+ .map((item, index) => {
3481
+ return item ? item[0].toUpperCase() + item.slice(1) : item;
3482
+ })
3483
+ .join("");
3484
+ // 去除key中的标点符号
3485
+ key = key.replace(/[^a-zA-Z0-9{}]/g, "").slice(0, 73);
3486
+ let EN = result[index1].dst;
3487
+ EN = EN[0].toUpperCase() + EN.slice(1);
3488
+ item1.EN = EN;
3489
+ item1.key = id + "-" + key;
3490
+ }
3491
+ });
3492
+ resolve(true);
3493
+ } else {
3494
+ item.forEach((item1, index1) => {
3495
+ const id = uuid.v5(item1.path, uuid.v5.URL).slice(0, 6);
3496
+ item1.key = id + "-" + "debug";
3497
+ });
3498
+ console.log(chalk.red(`error: ${body}`));
3499
+ resolve(true);
3500
+ }
3501
+ });
3502
+ });
3503
+ });
3504
+ }
3505
+ // md5加密
3506
+ function md5(str) {
3507
+ const md5 = crypto.createHash("md5");
3508
+ md5.update(str);
3509
+ return md5.digest("hex");
3510
+ }
3511
+
3512
+ // 节流函数
3513
+ const delay = 2000;
3514
+ const throttle = (function (delay = 1500) {
3515
+ const wait = [];
3516
+ let canCall = true;
3517
+ return function throttle(callback) {
3518
+ if (!canCall) {
3519
+ if (callback) wait.push(callback);
3520
+ return;
3521
+ }
3522
+
3523
+ callback();
3524
+ canCall = false;
3525
+ setTimeout(() => {
3526
+ canCall = true;
3527
+ if (wait.length) {
3528
+ throttle(wait.shift());
3529
+ }
3530
+ }, delay);
3531
+ };
3532
+ })(delay);
3533
+ // 开始方法
3534
+ readFolder();
3013
3535
  }
3014
3536
 
3015
3537
  exports.rules = rules;