i18-fe-automator-beta 2.0.6 → 2.1.0

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.
@@ -6,10 +6,11 @@ var inquirer = require('inquirer');
6
6
  var commander = require('commander');
7
7
  var request = require('request');
8
8
  var glob = require('glob');
9
+ var chalk = require('chalk');
10
+ var CheckboxPrompt = require('inquirer/lib/prompts/checkbox.js');
9
11
  var md5$1 = require('js-md5');
10
12
  var CryptoJS = require('crypto-js');
11
13
  var ora = require('ora');
12
- var chalk = require('chalk');
13
14
  var xlsx = require('node-xlsx');
14
15
  var url = require('url');
15
16
  var path = require('path');
@@ -49,215 +50,437 @@ function _interopNamespaceDefault(e) {
49
50
 
50
51
  var espree__namespace = /*#__PURE__*/_interopNamespaceDefault(espree);
51
52
 
52
- function Loading() {
53
- this.spinner = ora();
53
+ // "全选"选项的value,与普通文件选项区分
54
+ const SELECT_ALL = "__all__";
55
+
56
+ /**
57
+ * 带全选联动的checkbox prompt:
58
+ * - 勾选/取消"全选",其他选项全部同步勾选/取消
59
+ * - 取消任一普通选项,"全选"自动取消;所有普通选项勾满后,"全选"自动勾上
60
+ */
61
+ class CheckboxAllPrompt extends CheckboxPrompt {
62
+ getAllChoice() {
63
+ return this.opt.choices.find(
64
+ (choice) => choice.type !== "separator" && choice.value === SELECT_ALL
65
+ );
66
+ }
67
+
68
+ getItemChoices() {
69
+ return this.opt.choices.filter(
70
+ (choice) =>
71
+ choice.type !== "separator" &&
72
+ choice.value !== SELECT_ALL &&
73
+ !choice.disabled
74
+ );
75
+ }
76
+
77
+ // 根据普通选项的勾选情况同步"全选"状态
78
+ syncAllChoice() {
79
+ const allChoice = this.getAllChoice();
80
+ if (!allChoice) return;
81
+ const items = this.getItemChoices();
82
+ allChoice.checked = items.length > 0 && items.every((item) => item.checked);
83
+ }
84
+
85
+ // 覆写toggleChoice: 空格键和数字键选择都会经过这里,统一处理联动
86
+ toggleChoice(index) {
87
+ const choice = this.opt.choices.getChoice(index);
88
+ if (choice && choice.value === SELECT_ALL) {
89
+ // 切换"全选":所有普通选项与全选状态保持一致
90
+ const checked = !choice.checked;
91
+ choice.checked = checked;
92
+ this.getItemChoices().forEach((item) => {
93
+ item.checked = checked;
94
+ });
95
+ } else {
96
+ super.toggleChoice(index);
97
+ this.syncAllChoice();
98
+ }
99
+ }
100
+
101
+ // 按a全选/按i反选后,同步"全选"勾选状态
102
+ onAllKey() {
103
+ super.onAllKey();
104
+ this.syncAllChoice();
105
+ }
106
+
107
+ onInverseKey() {
108
+ super.onInverseKey();
109
+ this.syncAllChoice();
110
+ }
54
111
  }
112
+
113
+ function Loading() {
114
+ this.spinner = ora();
115
+ }
55
116
  var Loading$1 = new Loading().spinner;
56
117
 
57
- //加密
58
- function encrypt(word, keyStr) {
59
- keyStr = keyStr ? keyStr : "abcdefgabcdefg12";
60
- var key = CryptoJS.enc.Utf8.parse(keyStr); //Latin1 w8m31+Yy/Nw6thPsMpO5fg==
61
- var srcs = CryptoJS.enc.Utf8.parse(word);
62
- var encrypted = CryptoJS.AES.encrypt(srcs, key, {
63
- mode: CryptoJS.mode.ECB,
64
- padding: CryptoJS.pad.Pkcs7,
65
- });
66
- return encrypted.toString();
118
+ //加密
119
+ function encrypt(word, keyStr) {
120
+ keyStr = keyStr ? keyStr : "abcdefgabcdefg12";
121
+ var key = CryptoJS.enc.Utf8.parse(keyStr); //Latin1 w8m31+Yy/Nw6thPsMpO5fg==
122
+ var srcs = CryptoJS.enc.Utf8.parse(word);
123
+ var encrypted = CryptoJS.AES.encrypt(srcs, key, {
124
+ mode: CryptoJS.mode.ECB,
125
+ padding: CryptoJS.pad.Pkcs7,
126
+ });
127
+ return encrypted.toString();
128
+ }
129
+
130
+ function login(data) {
131
+ data.password = encrypt(md5$1(data.password));
132
+ const env=data.env;
133
+ Loading$1.start(`${env}: 登录中...`);
134
+ return new Promise((resolve, reject) => {
135
+ request(
136
+ {
137
+ url: `https://${
138
+ env === "pro" ? "" : `${env}-`
139
+ }hxjf.hongxinshop.com/sys/login`,
140
+ method: "POST",
141
+ json: true,
142
+ body: data,
143
+ },
144
+ function (error, response, body) {
145
+ const res = body || {};
146
+ if (!error && res.code == 200) {
147
+ Loading$1.succeed(`${env}: 登录成功`);
148
+ const access_token = res.data.access_token;
149
+ resolve(access_token);
150
+ } else {
151
+ Loading$1.fail(`${env}: 登录失败`);
152
+ reject(error || res.msg || "未知错误");
153
+ }
154
+ }
155
+ );
156
+ });
67
157
  }
68
158
 
69
- function login(data) {
70
- data.password = encrypt(md5$1(data.password));
71
- const env=data.env;
72
- Loading$1.start(`${env}: 登录中...`);
73
- return new Promise((resolve, reject) => {
74
- request(
75
- {
76
- url: `https://${
77
- env === "pro" ? "" : `${env}-`
78
- }hxjf.hongxinshop.com/sys/login`,
79
- method: "POST",
80
- json: true,
81
- body: data,
82
- },
83
- function (error, response, body) {
84
- const res = body;
85
- if (!error && res.code == 200) {
86
- Loading$1.succeed(`${env}: 登录成功`);
87
- const access_token = res.data.access_token;
88
- resolve(access_token);
89
- } else {
90
- Loading$1.fail(`${env}: 登录失败`);
91
- console.log(chalk.red(error || res.msg));
92
- }
93
- }
94
- );
95
- });
159
+ function importExcel({ uploadFilePath, token, env }) {
160
+ Loading$1.start(`${env}: 导入文件中...`);
161
+ const stream = fs.createReadStream(uploadFilePath);
162
+ return new Promise((resolve, reject) => {
163
+ request(
164
+ {
165
+ url: `https://${
166
+ env === "pro" ? "" : `${env}-`
167
+ }hxjf.hongxinshop.com/vue-api/api-globalization/api/i18n/importLangExcel`,
168
+ method: "POST",
169
+ headers: {
170
+ contentType: "multipart/form-data",
171
+ Authorization: `Bearer ${token}`,
172
+ "x-request-vaildate": "open",
173
+ },
174
+ formData: {
175
+ file: stream,
176
+ },
177
+ },
178
+ function (error, response, body) {
179
+ let res = {};
180
+ try {
181
+ res = JSON.parse(body);
182
+ } catch (e) {
183
+ // body非json(如网络错误)
184
+ }
185
+ if (!error && res.code == 200) {
186
+ Loading$1.succeed(`${env}: 导入文件成功`);
187
+ resolve(true);
188
+ } else {
189
+ Loading$1.fail(`${env}: 导入文件失败`);
190
+ reject(
191
+ error || (res.data ? JSON.stringify(res.data) : res.msg) || "未知错误"
192
+ );
193
+ }
194
+ }
195
+ );
196
+ });
197
+ }
198
+ function readExcel({ uploadFilePath }) {
199
+ return new Promise((resolve, reject) => {
200
+ // 读取文件
201
+ const workSheetsFromFile = xlsx.parse(uploadFilePath);
202
+ const data = workSheetsFromFile[0].data;
203
+ // 获取第一行
204
+ const titleList = data[0];
205
+ // 获取项目所在的列
206
+ let projectIndex;
207
+ if (titleList.indexOf("项目") > -1) {
208
+ projectIndex = titleList.indexOf("项目");
209
+ } else if (titleList.indexOf("project") > -1) {
210
+ projectIndex = titleList.indexOf("project");
211
+ }
212
+ if (!projectIndex && projectIndex !== 0) {
213
+ reject(new Error(`${uploadFilePath} 请检查文件是否包含项目/project列`));
214
+ return;
215
+ }
216
+ // 获取数据
217
+ const list = data.slice(1);
218
+ const set = new Set();
219
+ list.forEach((item) => {
220
+ if (item[projectIndex]) {
221
+ set.add(item[projectIndex]);
222
+ }
223
+ });
224
+ // 判断set是否为空
225
+ if (set.size === 0) {
226
+ reject(new Error(`${uploadFilePath} 请检查文件是否包含项目数据`));
227
+ return;
228
+ }
229
+ resolve([...set]);
230
+ });
231
+ }
232
+
233
+ function uploadExcel({ data, token, env }) {
234
+ Loading$1.start(`${env}: 导入项目 ${data.projectList.join()}`);
235
+ return new Promise((resolve, reject) => {
236
+ request(
237
+ {
238
+ url: `https://${
239
+ env === "pro" ? "" : `${env}-`
240
+ }hxjf.hongxinshop.com/vue-api/api-globalization/api/i18n/uploadI18nLangForFront`,
241
+ method: "POST",
242
+ json: true,
243
+ headers: {
244
+ Authorization: `Bearer ${token}`,
245
+ "x-request-vaildate": "open",
246
+ },
247
+ body: data,
248
+ },
249
+ function (error, response, body) {
250
+ const res = body || {};
251
+ if (!error && res.code == 200) {
252
+ Loading$1.succeed(`${env}: 导入项目成功`);
253
+ resolve(true);
254
+ } else {
255
+ Loading$1.fail(`${env}: 导入项目失败`);
256
+ reject(error || res.msg || res.message || "未知错误");
257
+ }
258
+ }
259
+ );
260
+ });
96
261
  }
97
262
 
98
- function importExcel({ uploadFilePath, token, env }) {
99
- Loading$1.start(`${env}: 导入文件中...`);
100
- const stream = fs.createReadStream(uploadFilePath);
101
- return new Promise((resolve, reject) => {
102
- request(
103
- {
104
- url: `https://${
105
- env === "pro" ? "" : `${env}-`
106
- }hxjf.hongxinshop.com/vue-api/api-globalization/api/i18n/importLangExcel`,
107
- method: "POST",
108
- headers: {
109
- contentType: "multipart/form-data",
110
- Authorization: `Bearer ${token}`,
111
- "x-request-vaildate": "open",
112
- },
113
- formData: {
114
- file: stream,
115
- },
116
- },
117
- function (error, response, body) {
118
- const res = JSON.parse(body);
119
- if (!error && res.code == 200) {
120
- Loading$1.succeed(`${env}: 导入文件成功`);
121
- resolve(true);
122
- } else {
123
- Loading$1.fail(`${env}: 导入文件失败`);
124
- console.log(
125
- chalk.red(error || (res.data ? JSON.stringify(res.data) : res.msg))
126
- );
127
- }
128
- }
129
- );
130
- });
263
+ // 获取命令行所在的目录
264
+ function getRunCliPath({ directory = false, root = false } = {}) {
265
+ const __filename = url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.js', document.baseURI).href)));
266
+ const __dirname = path.dirname(__filename);
267
+ if (root) {
268
+ return path.resolve(__dirname, "../");
269
+ }
270
+ return directory ? __dirname : __filename;
271
+ }
272
+
273
+ // 获取package.json
274
+ function getPackageJson() {
275
+ const packageJson = JSON.parse(
276
+ fs.readFileSync(path.resolve(getRunCliPath({ root: true }), "../package.json"))
277
+ );
278
+ return packageJson;
279
+ }
280
+ // 判断是否存在该路径
281
+ function isExistPath(path) {
282
+ return fs.existsSync(path);
283
+ }
284
+
285
+ // 写入缓存
286
+ function writeCache(key, value) {
287
+ fs.writeFileSync(getRunCliPath({ root: true }) + "/.cache/" + key, value);
288
+ }
289
+ // 读取缓存
290
+ function readCache(key) {
291
+ return fs.readFileSync(getRunCliPath({ root: true }) + "/.cache/" + key);
131
292
  }
132
- function readExcel({ uploadFilePath }) {
133
- return new Promise((resolve, reject) => {
134
- // 读取文件
135
- const workSheetsFromFile = xlsx.parse(uploadFilePath);
136
- const data = workSheetsFromFile[0].data;
137
- // 获取第一行
138
- const titleList = data[0];
139
- // 获取项目所在的列
140
- let projectIndex;
141
- if (titleList.indexOf("项目") > -1) {
142
- projectIndex = titleList.indexOf("项目");
143
- } else if (titleList.indexOf("project") > -1) {
144
- projectIndex = titleList.indexOf("project");
145
- }
146
- if (!projectIndex && projectIndex !== 0) {
147
- console.log(chalk.red("请检查文件是否包含项目/project列"));
148
- return;
149
- }
150
- // 获取数据
151
- const list = data.slice(1);
152
- const set = new Set();
153
- list.forEach((item) => {
154
- if (item[projectIndex]) {
155
- set.add(item[projectIndex]);
156
- }
157
- });
158
- // 判断set是否为空
159
- if (set.size === 0) {
160
- console.log(chalk.red("请检查文件是否包含项目数据"));
161
- return;
293
+
294
+ // 确保 .cache 目录存在
295
+ function ensureCacheDir() {
296
+ const cacheDir = getRunCliPath({ root: true }) + "/.cache";
297
+ if (!isExistPath(cacheDir)) {
298
+ fs.mkdirSync(cacheDir);
299
+ }
300
+ }
301
+
302
+ // 读取凭据缓存
303
+ function readSecretCache(cacheKey) {
304
+ const cacheFilePath = getRunCliPath({ root: true }) + "/.cache/" + cacheKey;
305
+ if (isExistPath(cacheFilePath)) {
306
+ try {
307
+ return JSON.parse(readCache(cacheKey));
308
+ } catch (error) {
309
+ return {};
162
310
  }
163
- resolve([...set]);
164
- });
311
+ }
312
+ return {};
165
313
  }
166
314
 
167
- function uploadExcel({ data, token, env }) {
168
- Loading$1.start(`${env}: 导入项目 ${data.projectList.join()}`);
169
- return new Promise((resolve, reject) => {
170
- request(
171
- {
172
- url: `https://${
173
- env === "pro" ? "" : `${env}-`
174
- }hxjf.hongxinshop.com/vue-api/api-globalization/api/i18n/uploadI18nLangForFront`,
175
- method: "POST",
176
- json: true,
177
- headers: {
178
- Authorization: `Bearer ${token}`,
179
- "x-request-vaildate": "open",
180
- },
181
- body: data,
182
- },
183
- function (error, response, body) {
184
- const res = body;
185
- if (!error && res.code == 200) {
186
- Loading$1.succeed(`${env}: 导入项目成功`);
187
- resolve(true);
188
- } else {
189
- Loading$1.fail(`${env}: 导入项目失败`);
190
- console.log(chalk.red(error || res.msg || res.message));
191
- }
192
- }
315
+ // 校验参数值, 不合法则红字提示合法值并退出
316
+ function validateChoice(value, validList, optionName) {
317
+ if (!validList.includes(value)) {
318
+ console.log(
319
+ chalk.red(
320
+ `参数 ${optionName} 的值 "${value}" 不合法, 可选值: ${validList.join(
321
+ " | "
322
+ )}`
323
+ )
193
324
  );
194
- });
325
+ process.exit(1);
326
+ }
327
+ return value;
195
328
  }
196
329
 
197
- // 获取命令行所在的目录
198
- function getRunCliPath({ directory = false, root = false } = {}) {
199
- const __filename = url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.js', document.baseURI).href)));
200
- const __dirname = path.dirname(__filename);
201
- if (root) {
202
- return path.resolve(__dirname, "../");
330
+ // CLI 参数 > 交互询问
331
+ async function askOrUse(cliValue, question) {
332
+ if (cliValue !== undefined && cliValue !== "") {
333
+ return cliValue;
203
334
  }
204
- return directory ? __dirname : __filename;
335
+ const answers = await inquirer.prompt([question]);
336
+ return answers[question.name];
205
337
  }
206
338
 
207
- // 获取package.json
208
- function getPackageJson() {
209
- const packageJson = JSON.parse(
210
- fs.readFileSync(path.resolve(getRunCliPath({ root: true }), "../package.json"))
339
+ /**
340
+ * 凭据解析: CLI 参数 > --cache 缓存 > 交互(展示缓存默认值)
341
+ * 解析成功后统一回写缓存(与现状"输入即缓存"一致)
342
+ * @param questions inquirer 问题数组(含 default/validate), default 会被缓存值覆盖
343
+ * @param cliValues CLI 参数值 { 字段名: 值 }
344
+ * @param useCache 是否走 --cache
345
+ * @param cacheKey 缓存键(baidu/sass/sass_pro)
346
+ * @param extraCache 回写缓存时额外携带的字段(如 baidu 的 type)
347
+ */
348
+ async function resolveSecretQuestions({
349
+ questions,
350
+ cliValues = {},
351
+ useCache = false,
352
+ cacheKey,
353
+ extraCache = {},
354
+ }) {
355
+ ensureCacheDir();
356
+ const cacheSecret = readSecretCache(cacheKey);
357
+ const resolved = {};
358
+ const pendingQuestions = [];
359
+ questions.forEach((question) => {
360
+ const { name } = question;
361
+ const cliValue = cliValues[name];
362
+ if (cliValue !== undefined && cliValue !== "") {
363
+ resolved[name] = cliValue;
364
+ return;
365
+ }
366
+ if (useCache && cacheSecret[name]) {
367
+ resolved[name] = cacheSecret[name];
368
+ return;
369
+ }
370
+ // 未解析的字段进入交互, 默认值优先用缓存
371
+ pendingQuestions.push({
372
+ ...question,
373
+ default: cacheSecret[name] ?? question.default,
374
+ });
375
+ });
376
+ if (pendingQuestions.length) {
377
+ const answers = await inquirer.prompt(pendingQuestions);
378
+ Object.assign(resolved, answers);
379
+ }
380
+ writeCache(
381
+ cacheKey,
382
+ JSON.stringify({
383
+ ...extraCache,
384
+ ...resolved,
385
+ })
211
386
  );
212
- return packageJson;
213
- }
214
- // 判断是否存在该路径
215
- function isExistPath(path) {
216
- return fs.existsSync(path);
217
- }
218
-
219
- // 写入缓存
220
- function writeCache(key, value) {
221
- fs.writeFileSync(getRunCliPath({ root: true }) + "/.cache/" + key, value);
222
- }
223
- // 读取缓存
224
- function readCache(key) {
225
- return fs.readFileSync(getRunCliPath({ root: true }) + "/.cache/" + key);
387
+ return resolved;
226
388
  }
227
389
 
228
390
  const sleep = (time) =>
229
391
  new Promise((resolve = 2000) => {
230
392
  setTimeout(() => resolve, time);
231
393
  });
232
- async function upload$1() {
233
- const SECRET_NAME = "sass";
234
- const SECRET_NAME_PRO = "sass_pro";
235
- let cacheSecret = {};
236
- let cacheSecretPro = {};
237
- if (!isExistPath(getRunCliPath({ root: true }) + "/.cache")) {
238
- fs.mkdirSync(getRunCliPath({ root: true }) + "/.cache");
394
+ // 上传翻译包(支持 --env/--files/--username/--password/--cache 一键, 参数齐全时跳过确认/选择)
395
+ async function upload$1(cliOpts = {}) {
396
+ // 空字符串视为未传(与askOrUse语义一致)
397
+ const cliEnv = cliOpts.env || undefined;
398
+ const cliFiles = cliOpts.files || undefined;
399
+ const cliUsername = cliOpts.username || undefined;
400
+ const cliPassword = cliOpts.password || undefined;
401
+ const cache = Boolean(cliOpts.cache);
402
+ const oneClick = cliEnv !== undefined && cliFiles !== undefined;
403
+ // CLI参数校验前置(快速失败, 不进交互)
404
+ if (cliEnv !== undefined) {
405
+ const envArr = cliEnv
406
+ .split(",")
407
+ .map((item) => item.trim())
408
+ .filter(Boolean);
409
+ if (envArr.length === 1) {
410
+ if (!["mit", "sit", "uat", "pro"].includes(envArr[0])) {
411
+ console.log(
412
+ chalk.red(
413
+ `参数 --env 的值 "${cliEnv}" 不合法, 可选值: mit | sit | uat | pro, 多环境逗号分隔(仅mit/sit/uat, pro须单独传)`
414
+ )
415
+ );
416
+ process.exit(1);
417
+ }
418
+ } else {
419
+ const invalid = envArr.filter(
420
+ (item) => !["mit", "sit", "uat"].includes(item)
421
+ );
422
+ if (invalid.length) {
423
+ console.log(
424
+ chalk.red(
425
+ `多环境仅支持 mit | sit | uat 逗号分隔, 不支持: ${invalid.join(
426
+ " | "
427
+ )} (pro须单独传)`
428
+ )
429
+ );
430
+ process.exit(1);
431
+ }
432
+ }
239
433
  }
240
- const cacheFilePath =
241
- getRunCliPath({ root: true }) + "/.cache/" + SECRET_NAME;
242
- const cacheFilePathPro =
243
- getRunCliPath({ root: true }) + "/.cache/" + SECRET_NAME_PRO;
244
- if (isExistPath(cacheFilePath)) {
245
- cacheSecret = JSON.parse(readCache(SECRET_NAME));
434
+ const excelChoices = fs
435
+ .readdirSync("./")
436
+ .filter((item) => item.indexOf(".xlsx") > -1 || item.indexOf(".xls") > -1);
437
+ if (excelChoices.length === 0) {
438
+ console.log(chalk.red("当前目录下没有excel文件(.xlsx/.xls)"));
439
+ process.exit(1);
246
440
  }
247
- if (isExistPath(cacheFilePathPro)) {
248
- cacheSecretPro = JSON.parse(readCache(SECRET_NAME_PRO));
441
+ let cliFileArr;
442
+ if (cliFiles !== undefined) {
443
+ if (cliFiles === "all") {
444
+ cliFileArr = excelChoices;
445
+ } else {
446
+ cliFileArr = cliFiles
447
+ .split(",")
448
+ .map((item) => item.trim())
449
+ .filter(Boolean);
450
+ const invalid = cliFileArr.filter((item) => !excelChoices.includes(item));
451
+ if (invalid.length) {
452
+ console.log(
453
+ chalk.red(`当前目录不存在excel文件: ${invalid.join(", ")}`)
454
+ );
455
+ process.exit(1);
456
+ }
457
+ }
249
458
  }
250
- // 是否上传
251
- inquirer
252
- .prompt([
459
+ // 1.是否上传(一键模式跳过, 选否属用户主动取消, exit 0)
460
+ if (!oneClick) {
461
+ const { isUpload } = await inquirer.prompt([
253
462
  {
254
463
  message: "是否上传到sass平台",
255
- name: "upload",
464
+ name: "isUpload",
256
465
  type: "confirm",
257
466
  default: true,
258
467
  },
468
+ ]);
469
+ if (!isUpload) {
470
+ return;
471
+ }
472
+ }
473
+ // 2.选择环境(CLI参数 > 交互)
474
+ let envValue;
475
+ if (cliEnv !== undefined) {
476
+ envValue = cliEnv
477
+ .split(",")
478
+ .map((item) => item.trim())
479
+ .filter(Boolean)
480
+ .join(",");
481
+ } else {
482
+ const res = await inquirer.prompt([
259
483
  {
260
- // 选择环境
261
484
  message: "请选择环境",
262
485
  name: "env",
263
486
  type: "rawlist",
@@ -278,10 +501,6 @@ async function upload$1() {
278
501
  name: "pro",
279
502
  value: "pro",
280
503
  },
281
- // {
282
- // name: "一键发布pro(mit->sit->uat->pro)",
283
- // value: "topro",
284
- // },
285
504
  {
286
505
  name: "一键发布uat(mit->sit->uat)",
287
506
  value: "to-mit,sit,uat",
@@ -291,9 +510,6 @@ async function upload$1() {
291
510
  value: "to-mit,sit",
292
511
  },
293
512
  ],
294
- when: function (res) {
295
- return res.upload;
296
- },
297
513
  validate: function (val) {
298
514
  if (val.length > 0) {
299
515
  return true;
@@ -301,357 +517,366 @@ async function upload$1() {
301
517
  return "请选择环境";
302
518
  },
303
519
  },
520
+ ]);
521
+ envValue = res.env;
522
+ }
523
+ let envList = [];
524
+ if (envValue.indexOf("to") > -1) {
525
+ envList = envValue.replace("to-", "").split(",");
526
+ } else {
527
+ envList = [envValue];
528
+ }
529
+ const isPro = envList.length === 1 && envList[0] === "pro";
530
+ // 3.凭据: CLI参数 > --cache缓存 > 交互(展示缓存默认值), 解析后回写缓存
531
+ const secret = await resolveSecretQuestions({
532
+ questions: [
304
533
  {
305
- message: "请输入域账号",
534
+ message: isPro ? "请输入生产域账号" : "请输入域账号",
306
535
  name: "username",
307
- // 当upload为true时,展示
308
- when: function (res) {
309
- return res.upload && res.env !== "pro";
310
- },
311
- default: cacheSecret.username,
312
536
  // 必填
313
537
  validate: function (val) {
314
538
  if (val) {
315
539
  return true;
316
540
  }
317
- return "请输入域账号";
541
+ return isPro ? "请输入生产域账号" : "请输入域账号";
318
542
  },
319
543
  },
320
544
  {
321
- message: "请输入域密码",
545
+ message: isPro ? "请输入生产域密码" : "请输入域密码",
322
546
  name: "password",
323
- default: cacheSecret.password,
324
- when: function (res) {
325
- return res.upload && res.env !== "pro";
326
- },
327
- // 必填
328
- validate: function (val) {
329
- if (val) {
330
- return true;
331
- }
332
- return "请输入域密码";
333
- },
334
- },
335
- {
336
- message: "请输入生产域账号",
337
- name: "username",
338
- // 当upload为true时,展示
339
- when: function (res) {
340
- return res.upload && res.env === "pro";
341
- },
342
- default: cacheSecretPro.username,
343
547
  // 必填
344
548
  validate: function (val) {
345
549
  if (val) {
346
550
  return true;
347
551
  }
348
- return "请输入生产域账号";
552
+ return isPro ? "请输入生产域密码" : "请输入域密码";
349
553
  },
350
554
  },
555
+ ],
556
+ cliValues: { username: cliUsername, password: cliPassword },
557
+ useCache: cache,
558
+ cacheKey: isPro ? "sass_pro" : "sass",
559
+ });
560
+ const { username, password } = secret;
561
+ // 4.选择上传文件(CLI参数 > 交互, 交互带全选联动)
562
+ let excelFileNames;
563
+ if (cliFileArr !== undefined) {
564
+ excelFileNames = cliFileArr;
565
+ } else {
566
+ inquirer.registerPrompt("checkbox-all", CheckboxAllPrompt);
567
+ // 选项拼接序号,与数字键选择对应(按1选全选,按2选第一个文件...)
568
+ const promptChoices = [
569
+ { name: "全选", value: SELECT_ALL, short: "全选" },
570
+ ...excelChoices.map((file) => ({ name: file, value: file, short: file })),
571
+ ].map((choice, index) => ({
572
+ ...choice,
573
+ name: `${index + 1}. ${choice.name}`,
574
+ }));
575
+ const res = await inquirer.prompt([
351
576
  {
352
- message: "请输入生产域密码",
353
- name: "password",
354
- default: cacheSecretPro.password,
355
- when: function (res) {
356
- return res.upload && res.env === "pro";
357
- },
358
- // 必填
577
+ type: "checkbox-all",
578
+ name: "excelFileNames",
579
+ message: "请选择要上传的excel文件(可多选)",
580
+ choices: promptChoices,
359
581
  validate: function (val) {
360
- if (val) {
582
+ if (val.length > 0) {
361
583
  return true;
362
584
  }
363
- return "请输入生产域密码";
585
+ return "请选择要上传的excel文件";
364
586
  },
365
587
  },
366
- ])
367
- .then(async (res) => {
368
- const { upload, env, username, password } = res;
369
- let envList = [];
370
- if (env.indexOf("to") > -1) {
371
- envList = env.replace("to-", "").split(",");
372
- } else {
373
- envList = [env];
374
- }
375
- if (upload) {
376
- writeCache(
377
- env === "pro" ? "sass_pro" : "sass",
378
- JSON.stringify({
379
- username,
380
- password,
381
- })
382
- );
383
- // 上传逻辑
384
- // 1.获取上传文件路径
385
- const choices = fs
386
- .readdirSync("./")
387
- .filter((item) => item.indexOf(".xlsx") > -1 || item.indexOf(".xls") > -1);
388
- // 2. 选择上传文件
389
- inquirer
390
- .prompt([
391
- {
392
- type: "rawlist",
393
- name: "excelFileName",
394
- message: "请选择要上传的excel文件",
395
- choices,
396
- validate: function (val) {
397
- if (val) {
398
- return true;
399
- }
400
- return "请选择项目";
401
- },
402
- },
403
- ])
404
- .then(async (res) => {
405
- const { excelFileName } = res;
406
- // 2.读取excel
407
- const uploadFilePath = "./" + excelFileName;
408
- const projectList = await readExcel({ uploadFilePath });
409
- for (const env of envList) {
410
- // 1.登录
411
- const token = await login({
412
- env,
413
- username,
414
- password,
415
- });
416
- // 2.导入excel
417
- await importExcel({
418
- uploadFilePath,
419
- token,
420
- env,
421
- });
422
- sleep(2000);
423
- // 3.上传
424
- await uploadExcel({
425
- data: {
426
- projectList,
427
- },
428
- token,
429
- env,
430
- });
431
- console.log(`🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀${env} 成功`);
432
- }
433
- // // 4.给export.xls文件名拼接上 已上传环境, 便于知道上传过的环境
434
- // const newUploadFilePath = uploadFilePath.replace(".xlsx", `${uploadFilePath.indexOf('-') > -1 ? ',' : '-'}${envList.join()}.xlsx`)
435
- // fs.renameSync(
436
- // uploadFilePath,
437
- // newUploadFilePath
438
- // );
439
- });
440
- }
441
- });
442
- }
443
-
444
- // 百度翻译接口
445
- async function translateFn(item, str, groupIndex, config) {
446
- const { appid, key, from, to } = config;
447
- const q = str;
448
- const salt = Math.random();
449
- const sign = md5(appid + q + salt + key);
450
- const query = querystring.stringify({
451
- q,
452
- appid,
453
- salt,
454
- from,
455
- to,
456
- sign,
457
- });
458
- const url = "http://api.fanyi.baidu.com/api/trans/vip/translate?" + query;
459
- return new Promise((resolve, reject) => {
460
- // 自动翻译延时,必须大于 1000 ms,否则调用百度翻译 API 会失败
461
- throttle(() => {
462
- console.log(chalk.green(`正在翻译第${groupIndex}组数据`));
463
- return request(url, function (_error, response, body) {
464
- const resBody = JSON.parse(body);
465
- if (resBody.trans_result) {
466
- console.log(`ok`);
467
- let result = resBody.trans_result || [];
468
- item.forEach((item1, index1) => {
469
- if (
470
- result[index1] &&
471
- result[index1].src === item1.from // 去除前后空格,原[ 执行人: ]翻译变成[执行人:]
472
- ) {
473
- item1.to = result[index1].dst;
474
- }
475
- });
476
- resolve(true);
477
- } else {
478
- console.log(chalk.red(`error: ${body}`));
479
- resolve(true);
480
- }
481
- });
482
- });
483
- });
484
- }
485
-
486
- function translate(allArr, config) {
487
- let groupArr = []; // 分组后的数组
488
- let newAllArr = []; // 分组后的数组还原成所有数组
489
- let str = "";
490
- let cutArr = [];
491
- console.log(chalk.green(`共有${allArr.length}条数据需要翻译`));
492
- replaceHtmlTag(allArr);
493
- allArr.forEach((item, index) => {
494
- if (str.length > 2000) {
495
- groupArr.push(cutArr);
496
- str = "";
497
- cutArr = [];
588
+ ]);
589
+ excelFileNames = res.excelFileNames;
590
+ // 勾选了全选则上传所有文件
591
+ if (excelFileNames.includes(SELECT_ALL)) {
592
+ excelFileNames = excelChoices;
498
593
  }
499
- str += item.from + "\n";
500
- cutArr.push(item);
501
- });
502
- if (cutArr.length > 0) {
503
- Array.prototype.push.apply(groupArr, [cutArr]);
504
- } else {
505
- groupArr = allArr;
506
594
  }
507
- console.log(chalk.green(`共有${groupArr.length}组数据需要翻译`));
508
- const task = groupArr.map(async (item, index) => {
509
- let str = "";
510
- item.forEach((item1, index1) => {
511
- // 去除换行空格
512
- str += item1.from.replace(/\n/g, "") + "\n";
513
- });
514
- return translateFn(item, str, index + 1, config);
515
- });
516
- Promise.allSettled(task).then(async (res) => {
517
- console.log(chalk.green("任务执行完毕"));
518
- groupArr.forEach((item, index) => {
519
- Array.prototype.push.apply(newAllArr, item);
520
- });
521
- config.originData.forEach((row, index) => {
522
- if (index > 0) {
523
- let {tagMap,to}= newAllArr[index - 1];
524
- // 用占位符还原
525
- // const toStr=
526
- for (const key in tagMap) {
527
- const value = tagMap[key];
528
- to=to.replace(`<${key}>`, value);
529
- }
530
- row[config.toIndex] = to;
531
- }
532
- });
533
- const buffer = xlsx.build([{ data: config.originData }]);
534
- fs.writeFileSync(`./${config.excelFileName}`, buffer, "binary");
535
- console.log(chalk.green(`更新文件成功`));
536
- });
537
- }
538
- // 节流函数
539
- const delay = 2000;
540
- const throttle = (function (delay = 1500) {
541
- const wait = [];
542
- let canCall = true;
543
- return function throttle(callback) {
544
- if (!canCall) {
545
- if (callback) wait.push(callback);
546
- return;
595
+ // 环境外层循环(每个环境只登录一次),文件内层循环批量上传
596
+ const failList = [];
597
+ for (const env of envList) {
598
+ // 1.登录
599
+ let token;
600
+ try {
601
+ token = await login({
602
+ env,
603
+ username,
604
+ password,
605
+ });
606
+ } catch (e) {
607
+ console.log(chalk.red(`${env} 登录失败: ${e.message || e}`));
608
+ failList.push(`${env} 登录`);
609
+ continue;
547
610
  }
548
-
549
- callback();
550
- canCall = false;
551
- setTimeout(() => {
552
- canCall = true;
553
- if (wait.length) {
554
- throttle(wait.shift());
611
+ for (const excelFileName of excelFileNames) {
612
+ const uploadFilePath = "./" + excelFileName;
613
+ try {
614
+ // 2.读取excel
615
+ const projectList = await readExcel({ uploadFilePath });
616
+ // 3.导入excel
617
+ await importExcel({
618
+ uploadFilePath,
619
+ token,
620
+ env,
621
+ });
622
+ await sleep(2000);
623
+ // 4.上传
624
+ await uploadExcel({
625
+ data: {
626
+ projectList,
627
+ },
628
+ token,
629
+ env,
630
+ });
631
+ console.log(
632
+ `🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀${env} ${excelFileName} 成功`
633
+ );
634
+ } catch (e) {
635
+ console.log(
636
+ chalk.red(`${env} ${excelFileName} 失败: ${e.message || e}`)
637
+ );
638
+ failList.push(`${env} ${excelFileName}`);
555
639
  }
556
- }, delay);
557
- };
558
- })(delay);
559
- // md5加密
560
- function md5(str) {
561
- const md5 = crypto.createHash("md5");
562
- md5.update(str);
563
- return md5.digest("hex");
564
- }
565
- function replaceHtmlTag(arr) {
566
- arr.forEach((item) => {
567
- const str = item.from;
568
- const { convertedText, tagMap } = convertHTMLToPlaceholders(str);
569
- item.origin = str;
570
- item.from = convertedText;
571
- item.tagMap = tagMap;
572
- });
573
- }
574
- /**
575
- * 将HTML字符串转换为占位符格式,并存储标签映射
576
- * @param {string} html - 输入的HTML字符串
577
- * @param {string} prefix - 占位符前缀,默认为'N'
578
- * @returns {Object} 包含转换后文本和标签映射的对象
579
- */
580
- function convertHTMLToPlaceholders(html, prefix = "N") {
581
- // 存储结果的标签映射对象
582
- const tagMap = {};
583
- let counter = 1;
584
-
585
- // 使用正则表达式匹配所有HTML标签
586
- // 包括自闭和标签和普通标签
587
- const tagRegex = /<(\/)?([a-zA-Z][a-zA-Z0-9]*)(\s[^>]*)?(\/)?>/g;
588
-
589
- // 先找出所有标签
590
- const tags = [];
591
- let match;
592
- while ((match = tagRegex.exec(html)) !== null) {
593
- tags.push({
594
- fullMatch: match[0],
595
- isClosing: match[1] === "/",
596
- tagName: match[2],
597
- attributes: match[3] || "",
598
- isSelfClosing: match[4] === "/",
599
- startIndex: match.index,
600
- endIndex: match.index + match[0].length,
601
- });
602
- }
603
-
604
- // 按出现顺序排序
605
- tags.sort((a, b) => a.startIndex - b.startIndex);
606
-
607
- // 处理标签和文本
608
- let currentIndex = 0;
609
- let resultText = "";
610
-
611
- tags.forEach((tag) => {
612
- // 添加标签前的文本
613
- const textBeforeTag = html.substring(currentIndex, tag.startIndex);
614
- if (textBeforeTag) {
615
- resultText += textBeforeTag;
616
640
  }
617
-
618
- // 生成占位符
619
- const placeholder = `<${prefix}${counter}>`;
620
-
621
- // 存储到映射对象
622
- const key = `${prefix}${counter}`;
623
- tagMap[key] = tag.fullMatch;
624
-
625
- // 添加到结果文本
626
- resultText += placeholder;
627
-
628
- // 更新计数器
629
- counter++;
630
- currentIndex = tag.endIndex;
631
- });
632
-
633
- // 添加最后一个标签后的文本
634
- const remainingText = html.substring(currentIndex);
635
- if (remainingText) {
636
- resultText += remainingText;
637
641
  }
638
-
639
- return {
640
- convertedText: resultText,
641
- tagMap,
642
- };
642
+ // 汇总失败项(登录失败也计入, 统一失败退出码1)
643
+ if (failList.length > 0) {
644
+ console.log(
645
+ chalk.red(`\n以下 ${failList.length} 项上传失败,请检查:\n- ${failList.join("\n- ")}`)
646
+ );
647
+ process.exit(1);
648
+ }
643
649
  }
644
- // let str = `<div class="translate">apple<p class="aa test">test</p>word</div>`;
650
+
651
+ // 百度翻译接口
652
+ async function translateFn(item, str, groupIndex, config) {
653
+ const { appid, key, from, to } = config;
654
+ const q = str;
655
+ const salt = Math.random();
656
+ const sign = md5(appid + q + salt + key);
657
+ const query = querystring.stringify({
658
+ q,
659
+ appid,
660
+ salt,
661
+ from,
662
+ to,
663
+ sign,
664
+ });
665
+ const url = "http://api.fanyi.baidu.com/api/trans/vip/translate?" + query;
666
+ return new Promise((resolve, reject) => {
667
+ // 自动翻译延时,必须大于 1000 ms,否则调用百度翻译 API 会失败
668
+ throttle(() => {
669
+ console.log(chalk.green(`正在翻译第${groupIndex}组数据`));
670
+ return request(url, function (_error, response, body) {
671
+ const resBody = JSON.parse(body);
672
+ if (resBody.trans_result) {
673
+ console.log(`ok`);
674
+ let result = resBody.trans_result || [];
675
+ item.forEach((item1, index1) => {
676
+ if (
677
+ result[index1] &&
678
+ result[index1].src === item1.from // 去除前后空格,原[ 执行人: ]翻译变成[执行人:]
679
+ ) {
680
+ item1.to = result[index1].dst;
681
+ }
682
+ });
683
+ resolve(true);
684
+ } else {
685
+ console.log(chalk.red(`error: ${body}`));
686
+ resolve(true);
687
+ }
688
+ });
689
+ });
690
+ });
691
+ }
692
+
693
+ function translate(allArr, config) {
694
+ let groupArr = []; // 分组后的数组
695
+ let newAllArr = []; // 分组后的数组还原成所有数组
696
+ let str = "";
697
+ let cutArr = [];
698
+ console.log(chalk.green(`共有${allArr.length}条数据需要翻译`));
699
+ replaceHtmlTag(allArr);
700
+ allArr.forEach((item, index) => {
701
+ if (str.length > 2000) {
702
+ groupArr.push(cutArr);
703
+ str = "";
704
+ cutArr = [];
705
+ }
706
+ str += item.from + "\n";
707
+ cutArr.push(item);
708
+ });
709
+ if (cutArr.length > 0) {
710
+ Array.prototype.push.apply(groupArr, [cutArr]);
711
+ } else {
712
+ groupArr = allArr;
713
+ }
714
+ console.log(chalk.green(`共有${groupArr.length}组数据需要翻译`));
715
+ const task = groupArr.map(async (item, index) => {
716
+ let str = "";
717
+ item.forEach((item1, index1) => {
718
+ // 去除换行空格
719
+ str += item1.from.replace(/\n/g, "") + "\n";
720
+ });
721
+ return translateFn(item, str, index + 1, config);
722
+ });
723
+ Promise.allSettled(task).then(async (res) => {
724
+ console.log(chalk.green("任务执行完毕"));
725
+ groupArr.forEach((item, index) => {
726
+ Array.prototype.push.apply(newAllArr, item);
727
+ });
728
+ config.originData.forEach((row, index) => {
729
+ if (index > 0) {
730
+ let {tagMap,to}= newAllArr[index - 1];
731
+ // 用占位符还原
732
+ // const toStr=
733
+ for (const key in tagMap) {
734
+ const value = tagMap[key];
735
+ to=to.replace(`<${key}>`, value);
736
+ }
737
+ row[config.toIndex] = to;
738
+ }
739
+ });
740
+ const buffer = xlsx.build([{ data: config.originData }]);
741
+ fs.writeFileSync(`./${config.excelFileName}`, buffer, "binary");
742
+ console.log(chalk.green(`更新文件成功`));
743
+ });
744
+ }
745
+ // 节流函数
746
+ const delay = 2000;
747
+ const throttle = (function (delay = 1500) {
748
+ const wait = [];
749
+ let canCall = true;
750
+ return function throttle(callback) {
751
+ if (!canCall) {
752
+ if (callback) wait.push(callback);
753
+ return;
754
+ }
755
+
756
+ callback();
757
+ canCall = false;
758
+ setTimeout(() => {
759
+ canCall = true;
760
+ if (wait.length) {
761
+ throttle(wait.shift());
762
+ }
763
+ }, delay);
764
+ };
765
+ })(delay);
766
+ // md5加密
767
+ function md5(str) {
768
+ const md5 = crypto.createHash("md5");
769
+ md5.update(str);
770
+ return md5.digest("hex");
771
+ }
772
+ function replaceHtmlTag(arr) {
773
+ arr.forEach((item) => {
774
+ const str = item.from;
775
+ const { convertedText, tagMap } = convertHTMLToPlaceholders(str);
776
+ item.origin = str;
777
+ item.from = convertedText;
778
+ item.tagMap = tagMap;
779
+ });
780
+ }
781
+ /**
782
+ * 将HTML字符串转换为占位符格式,并存储标签映射
783
+ * @param {string} html - 输入的HTML字符串
784
+ * @param {string} prefix - 占位符前缀,默认为'N'
785
+ * @returns {Object} 包含转换后文本和标签映射的对象
786
+ */
787
+ function convertHTMLToPlaceholders(html, prefix = "N") {
788
+ // 存储结果的标签映射对象
789
+ const tagMap = {};
790
+ let counter = 1;
791
+
792
+ // 使用正则表达式匹配所有HTML标签
793
+ // 包括自闭和标签和普通标签
794
+ const tagRegex = /<(\/)?([a-zA-Z][a-zA-Z0-9]*)(\s[^>]*)?(\/)?>/g;
795
+
796
+ // 先找出所有标签
797
+ const tags = [];
798
+ let match;
799
+ while ((match = tagRegex.exec(html)) !== null) {
800
+ tags.push({
801
+ fullMatch: match[0],
802
+ isClosing: match[1] === "/",
803
+ tagName: match[2],
804
+ attributes: match[3] || "",
805
+ isSelfClosing: match[4] === "/",
806
+ startIndex: match.index,
807
+ endIndex: match.index + match[0].length,
808
+ });
809
+ }
810
+
811
+ // 按出现顺序排序
812
+ tags.sort((a, b) => a.startIndex - b.startIndex);
813
+
814
+ // 处理标签和文本
815
+ let currentIndex = 0;
816
+ let resultText = "";
817
+
818
+ tags.forEach((tag) => {
819
+ // 添加标签前的文本
820
+ const textBeforeTag = html.substring(currentIndex, tag.startIndex);
821
+ if (textBeforeTag) {
822
+ resultText += textBeforeTag;
823
+ }
824
+
825
+ // 生成占位符
826
+ const placeholder = `<${prefix}${counter}>`;
827
+
828
+ // 存储到映射对象
829
+ const key = `${prefix}${counter}`;
830
+ tagMap[key] = tag.fullMatch;
831
+
832
+ // 添加到结果文本
833
+ resultText += placeholder;
834
+
835
+ // 更新计数器
836
+ counter++;
837
+ currentIndex = tag.endIndex;
838
+ });
839
+
840
+ // 添加最后一个标签后的文本
841
+ const remainingText = html.substring(currentIndex);
842
+ if (remainingText) {
843
+ resultText += remainingText;
844
+ }
845
+
846
+ return {
847
+ convertedText: resultText,
848
+ tagMap,
849
+ };
850
+ }
851
+ // let str = `<div class="translate">apple<p class="aa test">test</p>word</div>`;
645
852
  // console.log(convertHTMLToPlaceholders(str));
646
853
 
647
- function excelFn () {
648
- // 1.获取上传文件路径
854
+ // 翻译excel指定列语言(支持 --file/--lang/--appid/--key/--cache 一键, file+lang齐全时跳过选择)
855
+ async function excelFn(cliOpts = {}) {
856
+ // 空字符串视为未传(与askOrUse语义一致)
857
+ const cliFile = cliOpts.file || undefined;
858
+ const cliLang = cliOpts.lang || undefined;
859
+ const cliAppid = cliOpts.appid || undefined;
860
+ const cliKey = cliOpts.key || undefined;
861
+ const cache = Boolean(cliOpts.cache);
862
+ // 1.获取excel文件列表, CLI传入file先校验存在性(快速失败, 不进交互)
649
863
  const choices = fs
650
864
  .readdirSync("./")
651
865
  .filter((item) => item.indexOf(".xlsx") > -1 || item.indexOf(".xls") > -1);
652
- // 2. 选择上传文件
653
- inquirer
654
- .prompt([
866
+ if (choices.length === 0) {
867
+ console.log(chalk.red("当前目录下没有excel文件(.xlsx/.xls)"));
868
+ process.exit(1);
869
+ }
870
+ if (cliFile !== undefined && !choices.includes(cliFile)) {
871
+ console.log(chalk.red(`当前目录不存在excel文件: ${cliFile}`));
872
+ process.exit(1);
873
+ }
874
+ // 2.选择excel文件(CLI参数 > 交互)
875
+ let excelFileName;
876
+ if (cliFile !== undefined) {
877
+ excelFileName = cliFile;
878
+ } else {
879
+ const res = await inquirer.prompt([
655
880
  {
656
881
  type: "rawlist",
657
882
  name: "excelFileName",
@@ -664,131 +889,116 @@ function excelFn () {
664
889
  return "请选择项目";
665
890
  },
666
891
  },
667
- ])
668
- .then(async (res) => {
669
- const { excelFileName } = res;
670
- // 2.读取excel
671
- const uploadFilePath = "./" + excelFileName;
672
- const workSheetsFromFile = xlsx.parse(uploadFilePath);
673
- const data = workSheetsFromFile[0].data;
674
- // 获取第一行
675
- const titleList = data[0];
676
- const zhIndex = titleList.findIndex((item) => item === "zhCn");
677
- const enIndex = titleList.findIndex((item) => item === "en");
678
- const languageList = titleList.slice(enIndex);
679
- const { language } = await inquirer.prompt([
680
- {
681
- type: "rawlist",
682
- name: "language",
683
- message: `请选择要翻译的语言\n${chalk.red("注意:\n")}${chalk.green(
684
- "1. 英文基于中文翻译\n2. 非英文基于英文翻译"
685
- )}`,
686
- choices: languageList,
687
- validate: function (val) {
688
- if (val) {
689
- return true;
690
- }
691
- return "请选择";
692
- },
892
+ ]);
893
+ excelFileName = res.excelFileName;
894
+ }
895
+ // 3.读取excel
896
+ const uploadFilePath = "./" + excelFileName;
897
+ const workSheetsFromFile = xlsx.parse(uploadFilePath);
898
+ const data = workSheetsFromFile[0].data;
899
+ // 获取第一行
900
+ const titleList = data[0];
901
+ const zhIndex = titleList.findIndex((item) => item === "zhCn");
902
+ const enIndex = titleList.findIndex((item) => item === "en");
903
+ const languageList = titleList.slice(enIndex);
904
+ // 4.选择语言列(CLI参数 > 交互), CLI传入先校验在该excel语言列中(快速失败)
905
+ let language;
906
+ if (cliLang !== undefined) {
907
+ if (!languageList.includes(cliLang)) {
908
+ console.log(
909
+ chalk.red(
910
+ `参数 --lang 的值 "${cliLang}" 不在该excel的语言列中, 可选值: ${languageList.join(
911
+ " | "
912
+ )}`
913
+ )
914
+ );
915
+ process.exit(1);
916
+ }
917
+ language = cliLang;
918
+ } else {
919
+ const res = await inquirer.prompt([
920
+ {
921
+ type: "rawlist",
922
+ name: "language",
923
+ message: `请选择要翻译的语言\n${chalk.red("注意:\n")}${chalk.green(
924
+ "1. 英文基于中文翻译\n2. 非英文基于英文翻译"
925
+ )}`,
926
+ choices: languageList,
927
+ validate: function (val) {
928
+ if (val) {
929
+ return true;
930
+ }
931
+ return "请选择";
693
932
  },
694
- {
695
- type: "confirm", // 获取上一个选中结果
696
- name: "isTranslate",
697
- message: ({ language }) => {
698
- return `是否翻译${chalk.red(language)}`;
699
- },
700
- default: true,
701
- prefix: "Y", // 添加前缀符号
933
+ },
934
+ {
935
+ type: "confirm", // 获取上一个选中结果
936
+ name: "isTranslate",
937
+ message: ({ language }) => {
938
+ return `是否翻译${chalk.red(language)}`;
702
939
  },
703
- ]);
704
- const content = data.slice(1);
705
- const zhArr = content.map((row) => {
706
- return {
707
- from: row[zhIndex],
708
- };
709
- });
710
- const enArr = content.map((row) => {
711
- return {
712
- from: row[enIndex],
713
- };
714
- });
715
- let transArr = language === "en" ? zhArr : enArr;
716
- await inquirer
717
- .prompt([
718
- {
719
- message: "请选择翻译接口",
720
- name: "type",
721
- type: "list",
722
- choices: [
723
- {
724
- name: "百度翻译",
725
- value: "baidu",
726
- },
727
- ],
728
- },
729
- ])
730
- .then((res) => {
731
- const { type } = res;
732
- let cacheSecret = {};
733
- if (!isExistPath(getRunCliPath({ root: true }) + "/.cache")) {
734
- fs.mkdirSync(getRunCliPath({ root: true }) + "/.cache");
940
+ default: true,
941
+ prefix: "Y", // 添加前缀符号
942
+ },
943
+ ]);
944
+ language = res.language;
945
+ }
946
+ const content = data.slice(1);
947
+ const zhArr = content.map((row) => {
948
+ return {
949
+ from: row[zhIndex],
950
+ };
951
+ });
952
+ const enArr = content.map((row) => {
953
+ return {
954
+ from: row[enIndex],
955
+ };
956
+ });
957
+ let transArr = language === "en" ? zhArr : enArr;
958
+ // 5.凭据: CLI参数 > --cache缓存 > 交互(展示缓存默认值), 翻译接口当前仅百度, 一键时自动选择
959
+ const secret = await resolveSecretQuestions({
960
+ questions: [
961
+ {
962
+ message: "请输入百度翻译的appid",
963
+ name: "appid",
964
+ // 必填
965
+ validate: function (val) {
966
+ if (val) {
967
+ return true;
735
968
  }
736
- const cacheFilePath =
737
- getRunCliPath({ root: true }) + "/.cache/" + res.type;
738
- if (isExistPath(cacheFilePath)) {
739
- const cache = JSON.parse(readCache(res.type));
740
- cacheSecret = cache;
969
+ return "请输入百度翻译的appid";
970
+ },
971
+ },
972
+ {
973
+ message: "请输入百度翻译的key",
974
+ name: "key",
975
+ // 必填
976
+ validate: function (val) {
977
+ if (val) {
978
+ return true;
741
979
  }
742
- const question = [
743
- {
744
- message: "请输入百度翻译的appid",
745
- name: "appid",
746
- default: cacheSecret.appid,
747
- // 必填
748
- validate: function (val) {
749
- if (val) {
750
- return true;
751
- }
752
- return "请输入百度翻译的appid";
753
- },
754
- },
755
- {
756
- message: "请输入百度翻译的key",
757
- name: "key",
758
- default: cacheSecret.key,
759
- // 必填
760
- validate: function (val) {
761
- if (val) {
762
- return true;
763
- }
764
- return "请输入百度翻译的key";
765
- },
766
- },
767
- ];
768
- inquirer.prompt(question).then((res) => {
769
- writeCache(
770
- type,
771
- JSON.stringify({
772
- type,
773
- ...res,
774
- })
775
- );
776
- const { appid, key } = res;
777
- const baiduLang = {
778
- vi: "vie", // 越南语
779
- };
780
- translate(transArr, {
781
- originData: data,
782
- excelFileName,
783
- appid,
784
- key,
785
- from: language === "en" ? "zh" : "en",
786
- to: baiduLang[language] || language,
787
- toIndex: titleList.findIndex((item) => item === language),
788
- });
789
- });
790
- });
791
- });
980
+ return "请输入百度翻译的key";
981
+ },
982
+ },
983
+ ],
984
+ cliValues: { appid: cliAppid, key: cliKey },
985
+ useCache: cache,
986
+ cacheKey: "baidu",
987
+ extraCache: { type: "baidu" },
988
+ });
989
+ const { appid, key } = secret;
990
+ const baiduLang = {
991
+ vi: "vie", // 越南语
992
+ };
993
+ translate(transArr, {
994
+ originData: data,
995
+ excelFileName,
996
+ appid,
997
+ key,
998
+ from: language === "en" ? "zh" : "en",
999
+ to: baiduLang[language] || language,
1000
+ toIndex: titleList.findIndex((item) => item === language),
1001
+ });
792
1002
  }
793
1003
 
794
1004
  // 获取应用list
@@ -825,7 +1035,41 @@ function getApplicationList({ appName, env, token }) {
825
1035
  })
826
1036
  );
827
1037
  } else {
828
- console.log(chalk.red(error || JSON.stringify(res.data)));
1038
+ const errorData = error || JSON.stringify(res.data);
1039
+ console.log(chalk.red(errorData));
1040
+ reject(errorData);
1041
+ }
1042
+ }
1043
+ );
1044
+ });
1045
+ }
1046
+ // 获取应用配置
1047
+ function getApplicationConfig({ appId, env, token }) {
1048
+ Loading$1.start(`获取应用配置中...`);
1049
+ return new Promise((resolve, reject) => {
1050
+ request(
1051
+ {
1052
+ url: `https://${
1053
+ env === "pro" ? "" : `${env}-`
1054
+ }hxjf.hongxinshop.com/api-u/api/saas/app/info`,
1055
+ method: "POST",
1056
+ json: true,
1057
+ body: { id: appId },
1058
+ headers: {
1059
+ Authorization: `Bearer ${token}`,
1060
+ "x-request-vaildate": "open",
1061
+ },
1062
+ },
1063
+ function (error, response, body) {
1064
+ const res = body;
1065
+ if (!error && res.code == 200) {
1066
+ Loading$1.succeed(`获取应用配置成功`);
1067
+ resolve(res.data);
1068
+ } else {
1069
+ Loading$1.fail(`获取应用配置失败`);
1070
+ const errorData = error || JSON.stringify(res.data);
1071
+ console.log(chalk.red(errorData));
1072
+ reject(errorData);
829
1073
  }
830
1074
  }
831
1075
  );
@@ -855,7 +1099,9 @@ function getApplicationButtonConfig({ appId, env, token }) {
855
1099
  resolve(res.data);
856
1100
  } else {
857
1101
  Loading$1.fail(`获取${env}环境按钮权限配置失败`);
858
- console.log(chalk.red(error || JSON.stringify(res.data)));
1102
+ const errorData = error || JSON.stringify(res.data);
1103
+ console.log(chalk.red(errorData));
1104
+ reject(errorData);
859
1105
  }
860
1106
  }
861
1107
  );
@@ -914,15 +1160,64 @@ function addApplicationButtonConfig({ data, env, token }) {
914
1160
  resolve(true);
915
1161
  } else {
916
1162
  Loading$1.fail(`${data.code}`);
917
- console.log(chalk.red(error || JSON.stringify(res.data)));
1163
+ const errorData = error || JSON.stringify(res.data);
1164
+ console.log(chalk.red(errorData));
1165
+ reject(errorData);
918
1166
  }
919
1167
  }
920
1168
  );
921
1169
  });
922
1170
  }
923
- async function syncSassConfig() {
924
- inquirer
925
- .prompt([
1171
+ // -sass 入口: 功能路由(--action 显式指定 > --from/--to 推断同步 > --permission-* 推断新增 > 菜单)
1172
+ async function sassMain(rawCliOpts = {}) {
1173
+ // 空字符串视为未传(与askOrUse语义一致)
1174
+ const sassOpts = {
1175
+ ...rawCliOpts,
1176
+ action: rawCliOpts.action || undefined,
1177
+ from: rawCliOpts.from || undefined,
1178
+ to: rawCliOpts.to || undefined,
1179
+ app: rawCliOpts.app || undefined,
1180
+ env: rawCliOpts.env || undefined,
1181
+ permissionName: rawCliOpts.permissionName || undefined,
1182
+ permissionCode: rawCliOpts.permissionCode || undefined,
1183
+ };
1184
+ const { action, from, to, permissionName, permissionCode } = sassOpts;
1185
+ if (action !== undefined) {
1186
+ validateChoice(action, ["sync", "permission"], "--action");
1187
+ return action === "permission"
1188
+ ? addSassPermission(sassOpts)
1189
+ : syncSassFlow(sassOpts);
1190
+ }
1191
+ if (from !== undefined || to !== undefined) {
1192
+ return syncSassFlow(sassOpts);
1193
+ }
1194
+ if (permissionName !== undefined || permissionCode !== undefined) {
1195
+ return addSassPermission(sassOpts);
1196
+ }
1197
+ const { menuAction } = await inquirer.prompt([
1198
+ {
1199
+ message: "请选择功能",
1200
+ name: "menuAction",
1201
+ type: "list",
1202
+ choices: [
1203
+ { name: "同步按钮权限配置", value: "sync" },
1204
+ { name: "新增按钮权限", value: "permission" },
1205
+ ],
1206
+ },
1207
+ ]);
1208
+ return menuAction === "permission"
1209
+ ? addSassPermission(sassOpts)
1210
+ : syncSassFlow(sassOpts);
1211
+ }
1212
+
1213
+ // 同步流程(支持 --from/--to/--app 一键, 参数齐全时跳过使用须知)
1214
+ async function syncSassFlow(cliOpts) {
1215
+ const { from: cliFrom, to: cliTo, app: cliApp } = cliOpts;
1216
+ const envList = ["mit", "sit", "uat"];
1217
+ const oneClick =
1218
+ cliFrom !== undefined && cliTo !== undefined && cliApp !== undefined;
1219
+ if (!oneClick) {
1220
+ const { notice } = await inquirer.prompt([
926
1221
  {
927
1222
  message: `使用须知:
928
1223
  ${chalk.green(`
@@ -935,1121 +1230,1350 @@ async function syncSassConfig() {
935
1230
  type: "confirm",
936
1231
  default: false,
937
1232
  },
938
- {
939
- message: "请选择从哪个环境同步配置",
940
- name: "from",
941
- type: "list",
942
- choices: ["mit", "sit", "uat"],
943
- when: (answers) => answers.notice,
944
- },
945
- {
946
- message: "请选择同步至哪个环境",
947
- name: "to",
948
- type: "list",
949
- when: (answers) => answers.notice,
950
- choices: (answers) => {
951
- const list = ["mit", "sit", "uat"];
952
- return list.filter((item) => item !== answers.from);
953
- },
954
- },
955
- ])
956
- .then(async (answers) => {
957
- const { from, to,notice } = answers;
958
- if(!notice){
959
- return
960
- }
961
- const token = await login({
962
- env: from,
963
- username: "superAdmin",
964
- password: "admin1",
965
- });
966
- const { inputAppName } = await inquirer.prompt([
967
- {
968
- message: "请输入应用名称关键字",
969
- name: "inputAppName",
970
- type: "input",
971
- validate: function (value) {
972
- if (value) {
973
- return true;
974
- }
975
- return "请输入应用名称";
976
- },
977
- },
978
- ]);
979
- const applicationList = await getApplicationList({
980
- env: from,
981
- token,
982
- appName: inputAppName,
983
- });
984
- const { appName } = await inquirer.prompt([
985
- {
986
- message: "请选择应用名称",
987
- name: "appName",
988
- type: "rawlist",
989
- choices: applicationList,
990
- },
991
- ]);
992
- const appId = applicationList.find((item) => item.name === appName).id;
993
- // let appConfig = await getApplicationConfig({ appId, env: from, token });
994
- const fromButtonList = await getApplicationButtonConfig({
995
- appId,
996
- env: from,
997
- token,
998
- });
999
- if (!fromButtonList.length) {
1000
- return console.log(chalk.red("按钮权限配置为空,无法同步"));
1001
- }
1002
- console.log(`开始同步到${to}🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀`);
1003
- const toToken = await login({
1004
- env: to,
1005
- username: "superAdmin",
1006
- password: "admin1",
1007
- });
1008
- const toApplicationList = await getApplicationList({
1009
- env: to,
1010
- token: toToken,
1011
- appName,
1012
- });
1013
- if (!toApplicationList.length) {
1014
- return console.log(chalk.red(`${to}环境不存在该应用`));
1015
- }
1016
- const toAppId = toApplicationList[0].id;
1017
- const toButtonList = await getApplicationButtonConfig({
1018
- appId: toAppId,
1019
- env: to,
1020
- token: toToken,
1021
- });
1022
- try {
1023
- if (toButtonList.length) {
1024
- const deleteButtonPromiseList = toButtonList.map((item) => {
1025
- return deleteApplicationButtonConfig({
1026
- data: {
1027
- id: item.id,
1028
- ver: item.ver,
1029
- },
1030
- env: to,
1031
- token: toToken,
1032
- });
1033
- });
1034
- Loading$1.start(`删除${to}环境按钮权限配置中...`);
1035
- await Promise.all(deleteButtonPromiseList);
1036
- Loading$1.succeed(`删除${to}环境按钮权限配置成功`);
1037
- }
1038
- } catch (error) {
1039
- console.log(chalk.red(`删除${to}环境按钮权限配置失败`));
1040
- } finally {
1041
- for (const item of fromButtonList) {
1042
- const { code, name, visitConf } = item;
1043
- await addApplicationButtonConfig({
1044
- env: to,
1045
- token: toToken,
1046
- data: {
1047
- code1: code.split(":")[0],
1048
- code,
1049
- name,
1050
- visitConf,
1051
- ascriptionApp: toAppId,
1052
- },
1053
- });
1054
- }
1055
- console.log(chalk.green("同步成功"));
1056
- }
1057
- });
1058
- }
1059
-
1060
- /**
1061
- * @fileoverview 不要单独中文
1062
- * @author ypf
1063
- */
1064
- //------------------------------------------------------------------------------
1065
- // Rule Definition
1066
- //------------------------------------------------------------------------------
1067
-
1068
- /** @type {import('eslint').Rule.RuleModule} */
1069
-
1070
- // 判断字符串是否是中文
1071
- const isChinese = (str) => {
1072
- return /[\u4e00-\u9fa5]+/.test(str);
1073
- };
1074
- //去除特殊字符,包含空格
1075
- function trimSpecial(string = "", formatter) {
1076
- // const pattern =
1077
- // /[`~!@#$^\-&*()=|{}':;',\\\[\]\.<>\/?~!@#¥……&*()——|{}【】';:""'。,、?\s]/g;
1078
- // return string.replace(pattern, "");
1079
- // console.log(string);
1080
- // 获取开头空白符的位置
1081
- const startIdx = string.search(/\S/) - 1;
1082
- // 获取结尾空白符的位置
1083
- const endIdx = string.search(/\S\s*$/) + 1;
1084
- // 获取开头和结尾的字符串
1085
- const startStr = string.slice(0, startIdx + 1);
1086
- const endStr = string.slice(endIdx);
1087
- // 获取中间的字符串
1088
- const middle = string.slice(startIdx + 1, endIdx);
1089
- // 取出中间字符串的换行符
1090
- const middleStr = middle.replace(/\n/g, "");
1091
- return startStr + formatter(middleStr) + endStr;
1092
- }
1093
- // 判断当前节点是否已经翻译过
1094
- function isTranslate(node) {
1095
- if (
1096
- node.parent?.parent?.parent?.type === "CallExpression" &&
1097
- node.parent?.parent?.parent?.callee?.name === "$hxt"
1098
- ) {
1099
- return true;
1233
+ ]);
1234
+ if (!notice) {
1235
+ return;
1236
+ }
1100
1237
  }
1101
- // console不翻译
1102
- if (
1103
- node.parent.type === "CallExpression" &&
1104
- node.parent.callee?.object?.name === "console"
1105
- ) {
1106
- return true;
1238
+ if (cliFrom !== undefined) {
1239
+ validateChoice(cliFrom, envList, "--from");
1107
1240
  }
1108
-
1109
- return false;
1110
- }
1111
-
1112
- // 空key
1113
- const emptyKeyRules = (context) => {
1114
- return {
1115
- CallExpression(node) {
1116
- if (node.callee.name === "$hxt") {
1117
- const properties = node.arguments[0]?.properties || [];
1118
- // 如果属性是key且值为空
1119
- const result = properties.some((item) => {
1120
- // 去除key空格
1121
- const key = item.key.name.replace(/\s/g, "");
1122
- if (key === "key") {
1123
- // 去除value空格
1124
- // value是模版字符串
1125
- let value = "";
1126
- if (item.value.type === "TemplateLiteral") {
1127
- value = item.value.quasis[0].value.raw.replace(/\s/g, "");
1128
- } else if (item.value.type === "Literal") {
1129
- value = item.value.value.replace(/\s/g, "");
1130
- }
1131
- if (value === "") {
1132
- return true;
1133
- }
1134
- }
1135
- });
1136
- if (result) {
1137
- context.report({
1138
- node: node,
1139
- messageId: "noSingleChinese",
1140
- data: {
1141
- raw: chalk.green("key为空"),
1142
- },
1143
- fix: (fixer) => {},
1144
- });
1145
- }
1241
+ const from = await askOrUse(cliFrom, {
1242
+ message: "请选择从哪个环境同步配置",
1243
+ name: "from",
1244
+ type: "list",
1245
+ choices: envList,
1246
+ });
1247
+ if (cliTo !== undefined) {
1248
+ validateChoice(cliTo, envList, "--to");
1249
+ if (cliTo === from) {
1250
+ console.log(chalk.red("参数 --to 的值须不同于 --from"));
1251
+ process.exit(1);
1252
+ }
1253
+ }
1254
+ const to = await askOrUse(cliTo, {
1255
+ message: "请选择同步至哪个环境",
1256
+ name: "to",
1257
+ type: "list",
1258
+ choices: envList.filter((item) => item !== from),
1259
+ });
1260
+ let token;
1261
+ try {
1262
+ token = await login({
1263
+ env: from,
1264
+ username: "superAdmin",
1265
+ password: "admin1",
1266
+ });
1267
+ } catch (e) {
1268
+ console.log(chalk.red(`${from} 登录失败: ${e.message || e}`));
1269
+ process.exit(1);
1270
+ }
1271
+ const inputAppName = await askOrUse(cliApp, {
1272
+ message: "请输入应用名称关键字",
1273
+ name: "inputAppName",
1274
+ type: "input",
1275
+ validate: function (value) {
1276
+ if (value) {
1277
+ return true;
1146
1278
  }
1279
+ return "请输入应用名称";
1147
1280
  },
1148
- };
1149
- };
1150
- var noSingleChineseRule = {
1151
- meta: {
1152
- type: "suggestion", // `problem`, `suggestion`, or `layout`
1153
- docs: {
1154
- description: "不要单独中文",
1155
- recommended: false,
1156
- url: null, // URL to the documentation page for this rule
1281
+ });
1282
+ let applicationList;
1283
+ try {
1284
+ applicationList = await getApplicationList({
1285
+ env: from,
1286
+ token,
1287
+ appName: inputAppName,
1288
+ });
1289
+ } catch (e) {
1290
+ console.log(chalk.red(`查询${from}环境应用列表失败: ${e.message || e}`));
1291
+ process.exit(1);
1292
+ }
1293
+ const { appName } = await inquirer.prompt([
1294
+ {
1295
+ message: "请选择应用名称",
1296
+ name: "appName",
1297
+ type: "rawlist",
1298
+ choices: applicationList,
1157
1299
  },
1158
- fixable: "code", // Or `code` or `whitespace`
1159
- schema: [], // Add a schema if the rule has options
1160
- messages: {
1161
- noSingleChinese: "不要单独中文: {{raw}}",
1162
- }, // Add messageId and message
1163
- },
1164
- create(context) {
1165
- const sourceCode = context.sourceCode;
1166
- context.filename;
1167
- // console.log(filename,999999)
1168
- return context.parserServices.defineTemplateBodyVisitor(
1169
- // Event handlers for <template>.
1170
- {
1171
- // 纯字符串,如 测试
1172
- VText(node) {
1173
- // 如果是中文,替换为 {{ $hxt({key:'',desc:'中文'})}}
1174
- if (!isTranslate(node)) {
1175
- if (isChinese(node.value)) {
1176
- context.report({
1177
- node,
1178
- messageId: "noSingleChinese",
1179
- data: {
1180
- raw: chalk.green(node.value),
1181
- },
1182
- // message: `VText`,
1183
- fix: (fixer) => {
1184
- return fixer.replaceText(
1185
- node,
1186
- trimSpecial(node.value, (middle) => {
1187
- return "{{ $hxt({key:'',desc:'" + middle + "'})}}";
1188
- })
1189
- );
1190
- },
1191
- });
1192
- }
1193
- }
1194
- },
1195
- // 纯字符串,如 {{ mini ? '测试' : `开启` }}中的测试
1196
- // 纯字符串,如 {{test('测试')}}中的测试
1197
- Literal(node) {
1198
- if (!isTranslate(node)) {
1199
- if (isChinese(node.value)) {
1200
- context.report({
1201
- node,
1202
- messageId: "noSingleChinese",
1203
- data: {
1204
- raw: chalk.green(node.value),
1205
- },
1206
- // message: `Literal`,
1207
- fix: (fixer) => {
1208
- // console.log(1111, node);
1209
- return fixer.replaceText(
1210
- node,
1211
- trimSpecial(node.value, (middle) => {
1212
- return "$hxt({key:'',desc:'" + middle + "'})";
1213
- })
1214
- );
1215
- },
1216
- });
1217
- }
1218
- }
1219
- },
1220
- // 模版字符串,
1221
- // 如{{ mini ? '测试' : `开启` }}中的开启
1222
- // 如{{ mini ? `${a}测试` : "开启" }}中的${a}测试
1223
- // 如{{`111`}}
1224
- // 如<p :a="`${a}册书`">
1225
- TemplateLiteral(node) {
1226
- if (!isTranslate(node)) {
1227
- const textSource = sourceCode.getText(node);
1228
- let text = textSource;
1229
- // 删除$符号
1230
- text = text.replace(/\${/g, "{");
1231
- if (isChinese(text)) {
1232
- // 获取expressions的文本
1233
- const expressionsText = node.expressions.map((item) => {
1234
- return sourceCode.getText(item);
1235
- });
1236
- let expressionStr = "";
1237
- if (expressionsText.length) {
1238
- expressionsText.forEach((item, index) => {
1239
- const key = `slot${index + 1}`;
1240
- text = text.replace(item, `${key}`);
1241
- expressionStr += `${key}:${item},`;
1242
- });
1243
- // 删除最后一个逗号
1244
- expressionStr = `{${expressionStr.slice(0, -1)}}`;
1245
- }
1246
- if (!isChinese(text)) {
1247
- return;
1248
- }
1249
- context.report({
1250
- node,
1251
- data: {
1252
- raw: chalk.green(textSource),
1253
- },
1254
- messageId: "noSingleChinese",
1255
- // message: `TemplateLiteral`,
1256
- fix: (fixer) => {
1257
- return fixer.replaceText(
1258
- node,
1259
- trimSpecial(text, (middle) => {
1260
- return `$hxt({key:'',desc:${middle}}${
1261
- expressionStr ? `,${expressionStr}` : ""
1262
- })`;
1263
- })
1264
- );
1265
- },
1266
- });
1267
- }
1268
- }
1269
- },
1270
- // 如<p title="1">
1271
- VLiteral(node) {
1272
- if (!isTranslate(node)) {
1273
- if (isChinese(node.value)) {
1274
- context.report({
1275
- node,
1276
- messageId: "noSingleChinese",
1277
- // message: `VLiteral`,
1278
- data: {
1279
- raw: chalk.green(node.value),
1280
- },
1281
-
1282
- fix: (fixer) => {
1283
- // 父节点需要改为冒号方式
1284
- const parentNode = node.parent;
1285
- if (parentNode.type === "VAttribute") {
1286
- const key = parentNode.key;
1287
- return fixer.replaceText(
1288
- parentNode,
1289
- ":" +
1290
- key.name +
1291
- "=" +
1292
- '"' +
1293
- trimSpecial(node.value, (middle) => {
1294
- return "$hxt({key:'',desc:'" + middle + "'})";
1295
- }) +
1296
- '"'
1297
- );
1298
- }
1299
- },
1300
- });
1301
- }
1302
- }
1303
- },
1304
- // 如 <p v-permission="测试">中的测试
1305
- Identifier(node) {
1306
- if (
1307
- !isTranslate(node) &&
1308
- node.parent.type === "VExpressionContainer"
1309
- ) {
1310
- if (isChinese(node.name)) {
1311
- context.report({
1312
- node,
1313
- messageId: "noSingleChinese",
1314
- // message: `Identifier`,
1315
- data: {
1316
- raw: chalk.green(node.name),
1317
- },
1318
- fix: (fixer) => {
1319
- return fixer.replaceText(
1320
- node,
1321
- trimSpecial(node.name, (middle) => {
1322
- return "$hxt({key:'',desc:'" + middle + "'})";
1323
- })
1324
- );
1325
- },
1326
- });
1327
- }
1328
- }
1329
- },
1330
- ...emptyKeyRules(context),
1331
- },
1332
- // Event handlers for <script> or scripts. (optional)
1333
- {
1334
- JSXText(node) {
1335
- if (!isTranslate(node)) {
1336
- if (isChinese(node.value)) {
1337
- context.report({
1338
- node,
1339
- messageId: "noSingleChinese",
1340
- data: {
1341
- raw: chalk.green(node.value),
1342
- },
1343
- fix: (fixer) => {
1344
- return fixer.replaceText(
1345
- node,
1346
- trimSpecial(node.value, (middle) => {
1347
- return "{ $hxt({key:'',desc:'" + middle + "'})}";
1348
- })
1349
- );
1350
- },
1351
- });
1352
- }
1353
- }
1354
- },
1355
- TemplateLiteral(node) {
1356
- if (!isTranslate(node)) {
1357
- const textSource = sourceCode.getText(node);
1358
- let text = textSource;
1359
- // 删除$符号
1360
- text = text.replace(/\${/g, "{");
1361
- if (isChinese(text)) {
1362
- // 获取expressions的文本
1363
- const expressionsText = node.expressions.map((item) => {
1364
- return sourceCode.getText(item);
1365
- });
1366
- // console.log(expressionsText,111)
1367
- let expressionStr = "";
1368
- if (expressionsText.length) {
1369
- expressionsText.forEach((item, index) => {
1370
- const key = `slot${index + 1}`;
1371
- text = text.replace(item, `${key}`);
1372
- expressionStr += `${key}:${item},`;
1373
- });
1374
- // 删除最后一个逗号
1375
- expressionStr = `{${expressionStr.slice(0, -1)}}`;
1376
- }
1377
- if (!isChinese(text)) {
1378
- return;
1379
- }
1380
- context.report({
1381
- node,
1382
- messageId: "noSingleChinese",
1383
- data: {
1384
- raw: chalk.green(textSource),
1385
- },
1386
- // message: `TemplateLiteral`,
1387
- fix: (fixer) => {
1388
- // return console.log(text,expressionStr)
1389
- // trimSpecial(text, (middle) => {
1390
- // return `$hxt({key:'',desc:${middle}}${
1391
- // expressionStr ? `,${expressionStr}` : ""
1392
- // })`;
1393
- // })
1394
- // return
1395
- return fixer.replaceText(
1396
- node,
1397
- trimSpecial(text, (middle) => {
1398
- return `$hxt({key:'',desc:${middle}}${
1399
- expressionStr ? `,${expressionStr}` : ""
1400
- })`;
1401
- })
1402
- );
1403
- },
1404
- });
1405
- }
1406
- }
1407
- },
1408
- Literal(node) {
1409
- // 如果正则忽略
1410
- if (node.regex) return;
1411
- if (!isTranslate(node)) {
1412
- if (isChinese(node.value)) {
1413
- // 增加{}包裹 render(){return <p title={'测试'} title="测试" title={`${1}测试`}>内容 {`测试`}</p>} 中的内容
1414
- if (node.parent.type === "JSXAttribute") {
1415
- context.report({
1416
- node,
1417
- messageId: "noSingleChinese",
1418
- data: {
1419
- raw: chalk.green(node.value),
1420
- },
1421
- fix: (fixer) => {
1422
- return fixer.replaceText(
1423
- node,
1424
- trimSpecial(node.value, (middle) => {
1425
- return "{$hxt({key:'',desc:'" + middle + "'})}";
1426
- })
1427
- );
1428
- },
1429
- });
1430
- } else {
1431
- context.report({
1432
- node,
1433
- messageId: "noSingleChinese",
1434
- data: {
1435
- raw: chalk.green(node.value),
1436
- },
1437
- fix: (fixer) => {
1438
- return fixer.replaceText(
1439
- node,
1440
- trimSpecial(node.value, (middle) => {
1441
- return "$hxt({key:'',desc:'" + middle + "'})";
1442
- })
1443
- );
1444
- },
1445
- });
1446
- }
1447
- }
1448
- }
1449
- },
1450
- ...emptyKeyRules(context),
1451
- }
1300
+ ]);
1301
+ const appId = applicationList.find((item) => item.name === appName).id;
1302
+ let fromButtonList;
1303
+ try {
1304
+ fromButtonList = await getApplicationButtonConfig({
1305
+ appId,
1306
+ env: from,
1307
+ token,
1308
+ });
1309
+ } catch (e) {
1310
+ console.log(
1311
+ chalk.red(`获取${from}环境按钮权限配置失败: ${e.message || e}`)
1452
1312
  );
1453
- },
1454
- };
1455
-
1456
- /**
1457
- * @fileoverview 不能单独金额
1458
- * @author ypf
1459
- */
1460
-
1461
- //------------------------------------------------------------------------------
1462
- // Rule Definition
1463
- //------------------------------------------------------------------------------
1464
-
1465
- /** @type {import('eslint').Rule.RuleModule} */
1466
- const inputTagNameList = [
1467
- "el-input",
1468
- "ElInput",
1469
- "hx-input",
1470
- "HxInput",
1471
- "van-field",
1472
- "VanField",
1473
- "hxmb-field",
1474
- "HxmbField",
1475
- ]; // input标签
1476
- const formTagNameList = ["hx-form", "HxForm", "HxmForm", "hxmb-form"]; // form标签
1477
- // 匹配金额名
1478
- const currencyList = [
1479
- // "currency",
1480
- "total",
1481
- "price",
1482
- "amount",
1483
- ];
1484
- // 价格后缀过滤
1485
- const notIncludeCurrencySuffixList = [
1486
- "code",
1487
- "type",
1488
- "no",
1489
- "status",
1490
- "name",
1491
- "sort",
1492
- "source",
1493
- "qty",
1494
- ];
1495
- // 关键字属性
1496
- const keyPropList = ["prop", "name", "id", "key"];
1497
- const hasCurrencyName = (str) => {
1498
- return currencyList.some((item) => {
1499
- str = str.toLowerCase();
1500
- const suffix = str.split(item)[1]?.toLowerCase();
1501
- if (str.includes(item)) {
1502
- if(suffix){
1503
- return !notIncludeCurrencySuffixList.some((item) =>
1504
- suffix.includes(item)
1505
- );
1506
- }
1507
- return true
1313
+ process.exit(1);
1314
+ }
1315
+ if (!fromButtonList.length) {
1316
+ console.log(chalk.red("按钮权限配置为空,无法同步"));
1317
+ process.exit(1);
1318
+ }
1319
+ console.log(`开始同步到${to}🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀`);
1320
+ let toToken;
1321
+ try {
1322
+ toToken = await login({
1323
+ env: to,
1324
+ username: "superAdmin",
1325
+ password: "admin1",
1326
+ });
1327
+ } catch (e) {
1328
+ console.log(chalk.red(`${to} 登录失败: ${e.message || e}`));
1329
+ process.exit(1);
1330
+ }
1331
+ let toApplicationList;
1332
+ try {
1333
+ toApplicationList = await getApplicationList({
1334
+ env: to,
1335
+ token: toToken,
1336
+ appName,
1337
+ });
1338
+ } catch (e) {
1339
+ console.log(chalk.red(`查询${to}环境应用列表失败: ${e.message || e}`));
1340
+ process.exit(1);
1341
+ }
1342
+ if (!toApplicationList.length) {
1343
+ console.log(chalk.red(`${to}环境不存在该应用`));
1344
+ process.exit(1);
1345
+ }
1346
+ const toAppId = toApplicationList[0].id;
1347
+ let toButtonList;
1348
+ try {
1349
+ toButtonList = await getApplicationButtonConfig({
1350
+ appId: toAppId,
1351
+ env: to,
1352
+ token: toToken,
1353
+ });
1354
+ } catch (e) {
1355
+ console.log(chalk.red(`获取${to}环境按钮权限配置失败: ${e.message || e}`));
1356
+ process.exit(1);
1357
+ }
1358
+ try {
1359
+ if (toButtonList.length) {
1360
+ const deleteButtonPromiseList = toButtonList.map((item) => {
1361
+ return deleteApplicationButtonConfig({
1362
+ data: {
1363
+ id: item.id,
1364
+ ver: item.ver,
1365
+ },
1366
+ env: to,
1367
+ token: toToken,
1368
+ });
1369
+ });
1370
+ Loading$1.start(`删除${to}环境按钮权限配置中...`);
1371
+ await Promise.all(deleteButtonPromiseList);
1372
+ Loading$1.succeed(`删除${to}环境按钮权限配置成功`);
1508
1373
  }
1509
- return false;
1510
- });
1511
- };
1512
- var noSingleCurrencyRule = {
1513
- meta: {
1514
- type: "suggestion", // `problem`, `suggestion`, or `layout`
1515
- docs: {
1516
- description: "不能单独金额",
1517
- recommended: false,
1518
- url: null, // URL to the documentation page for this rule
1519
- },
1520
- fixable: null, // Or `code` or `whitespace`
1521
- schema: [], // Add a schema if the rule has options
1522
- messages: {
1523
- noSingleCurrency: "不要单独币种: {{raw}}",
1524
- }, // Add messageId and message
1525
- },
1526
-
1527
- create(context) {
1528
- const sourceCode = context.sourceCode;
1529
- // 获取路径
1530
- const filename = context.filename;
1531
- return context.parserServices.defineTemplateBodyVisitor(
1532
- // Event handlers for <template>.
1533
- {
1534
- // 模版标签, 如<el-input >
1535
- VElement(node) {
1536
- const tagName = node.rawName;
1537
- if (inputTagNameList.indexOf(tagName) > -1) {
1538
- const attrs = node.startTag.attributes;
1539
- // 遍历找到v-model 属性
1540
- attrs.some((attr) => {
1541
- const attrTextSource = sourceCode.getText(attr);
1542
- if (attr.key?.name?.name === "model") {
1543
- const textSource = sourceCode.getText(attr.value);
1544
- // 如果包含currency、price、amount,则报错
1545
- if (hasCurrencyName(textSource)) {
1546
- context.report({
1547
- node,
1548
- data: {
1549
- raw:
1550
- chalk.red(`Template|<${tagName}> `) +
1551
- chalk.green(attrTextSource),
1552
- },
1553
- messageId: "noSingleCurrency",
1554
- // message: `TemplateLiteral`,
1555
- fix: (fixer) => {},
1556
- });
1557
- }
1558
- }
1559
- return false;
1560
- });
1561
- }
1562
- },
1563
- },
1564
- // Event handlers for <script> or scripts. (optional)
1565
- {
1566
- // jsx 标签,如 render() { return <el-input /> } 中的<el-input />
1567
- // <el-form formItemList=[]>中的formItemList
1568
- JSXElement(node) {
1569
- // console.log(node,'node')
1570
- const tagName = node?.openingElement?.name?.name;
1571
- // input
1572
- if (inputTagNameList.indexOf(tagName) > -1) {
1573
- // 遍历找到vModel 属性
1574
- const attrs = node.openingElement.attributes;
1575
- attrs.some((attr) => {
1576
- const attrTextSource = sourceCode.getText(attr);
1577
- // console.log(attrTextSource,'attrTextSource')
1578
- if (attr.name.name === "vModel") {
1579
- const textSource = sourceCode.getText(attr.value);
1580
- // 如果包含币种
1581
- if (hasCurrencyName(textSource)) {
1582
- context.report({
1583
- node,
1584
- data: {
1585
- raw:
1586
- chalk.red(`JSX|<${tagName}> `) +
1587
- chalk.green(attrTextSource),
1588
- },
1589
- messageId: "noSingleCurrency",
1590
- // message: `TemplateLiteral`,
1591
- fix: (fixer) => {},
1592
- });
1593
- }
1594
- }
1595
- });
1596
- }
1597
- // form
1598
- if (formTagNameList.indexOf(tagName)) {
1599
- const attrs = node.openingElement.attributes;
1600
- // 遍历找到formItemList 属性
1601
- attrs.some((attr) => {
1602
- sourceCode.getText(attr);
1603
- // 是formItemList
1604
- if (attr?.name?.name === "formItemList") {
1605
- // 且值是数组
1606
- if (attr?.value?.expression?.type === "ArrayExpression") {
1607
- // 遍历数组
1608
- attr.value.expression.elements.forEach((item) => {
1609
- (item.properties || []).some((item) => {
1610
- const attrTextSource = sourceCode.getText(item);
1611
- if (keyPropList.indexOf(item.key.name) > -1) {
1612
- const textSource = sourceCode.getText(item.value);
1613
- if (item?.value?.type === "CallExpression") return;
1614
- // 如果包含币种
1615
- if (hasCurrencyName(textSource)) {
1616
- context.report({
1617
- node,
1618
- data: {
1619
- raw:
1620
- chalk.red(`JSX|${tagName}|${attr.name.name} `) +
1621
- chalk.green(`${attrTextSource}`),
1622
- },
1623
- messageId: "noSingleCurrency",
1624
- // message: `TemplateLiteral`,
1625
- fix: (fixer) => {},
1626
- });
1627
- }
1628
- }
1629
- });
1630
- });
1631
- }
1632
- }
1633
- });
1634
- }
1635
- },
1636
- // [{prop:'price'}]
1637
- ArrayExpression(node) {
1638
- // 不检测路由文件
1639
- if (filename.includes("src/router")) return;
1640
- // 是数组且不是jsx属性上的数组(上面已经检测过了,否则会出现2条错误)
1641
- if (node.parent?.parent?.type === "JSXAttribute") return;
1642
- (node.elements || []).forEach((item) => {
1643
- // 数组里面是对象
1644
- if (item?.type === "ObjectExpression") {
1645
- (item.properties || []).forEach((item) => {
1646
- const attrTextSource = sourceCode.getText(item);
1647
- if (keyPropList.indexOf(item?.key?.name) > -1) {
1648
- if (item?.value?.type === "CallExpression") return;
1649
- const textSource = sourceCode.getText(item.value);
1650
- // 如果包含币种
1651
- if (hasCurrencyName(textSource)) {
1652
- context.report({
1653
- node,
1654
- data: {
1655
- raw:
1656
- chalk.red(`JSX|[{}] `) +
1657
- chalk.green(`${attrTextSource}`),
1658
- },
1659
- messageId: "noSingleCurrency",
1660
- });
1661
- }
1662
- }
1663
- });
1664
- }
1665
- });
1666
- },
1374
+ } catch (error) {
1375
+ console.log(chalk.red(`删除${to}环境按钮权限配置失败`));
1376
+ } finally {
1377
+ try {
1378
+ for (const item of fromButtonList) {
1379
+ const { code, name, visitConf } = item;
1380
+ await addApplicationButtonConfig({
1381
+ env: to,
1382
+ token: toToken,
1383
+ data: {
1384
+ code1: code.split(":")[0],
1385
+ code,
1386
+ name,
1387
+ visitConf,
1388
+ ascriptionApp: toAppId,
1389
+ },
1390
+ });
1667
1391
  }
1668
- );
1669
- },
1670
- };
1671
-
1672
- /**
1673
- * @fileoverview 不能单独金额
1674
- * @author ypf
1675
- */
1676
-
1677
- //------------------------------------------------------------------------------
1678
- // Rule Definition
1679
- //------------------------------------------------------------------------------
1392
+ console.log(chalk.green("同步成功"));
1393
+ } catch (e) {
1394
+ console.log(chalk.red(`同步失败: ${e.message || e}`));
1395
+ process.exit(1);
1396
+ }
1397
+ }
1398
+ }
1680
1399
 
1681
- /** @type {import('eslint').Rule.RuleModule} */
1682
- const tagNameList = ["hx-search-list-page", "HxSearchListPage"];
1683
- const attrNameList = ["custom-column-module", "customColumnModule"];
1684
- const moduleArr = [];
1685
- const getModuleArr = () => moduleArr;
1686
- const addModule = (moduleObj) => {
1687
- const isExist= moduleArr.some((item) => {
1688
- return item.module === moduleObj.module
1400
+ // 新增按钮权限流程(支持 --env/--app/--permission-name/--permission-code 一键, 参数齐全时免确认)
1401
+ async function addSassPermission(cliOpts = {}) {
1402
+ const {
1403
+ env: cliEnv,
1404
+ app: cliApp,
1405
+ permissionName: cliPermissionName,
1406
+ permissionCode: cliPermissionCode,
1407
+ } = cliOpts;
1408
+ const envList = ["mit", "sit", "uat"];
1409
+ const oneClick =
1410
+ cliEnv !== undefined &&
1411
+ cliApp !== undefined &&
1412
+ cliPermissionName !== undefined &&
1413
+ cliPermissionCode !== undefined;
1414
+ // CLI传入的权限编码含:时快速失败(code以:为分隔符,含:会产生歧义,且避免登录查询后才报错)
1415
+ if (cliPermissionCode !== undefined && cliPermissionCode.includes(":")) {
1416
+ console.log(chalk.red("参数 --permission-code 的值不允许包含 :"));
1417
+ process.exit(1);
1418
+ }
1419
+ if (cliEnv !== undefined) {
1420
+ validateChoice(cliEnv, envList, "--env");
1421
+ }
1422
+ const env = await askOrUse(cliEnv, {
1423
+ message: "请选择环境",
1424
+ name: "env",
1425
+ type: "list",
1426
+ choices: envList,
1689
1427
  });
1690
- if(!isExist){
1691
- moduleArr.push(moduleObj);
1428
+ let token;
1429
+ try {
1430
+ token = await login({
1431
+ env,
1432
+ username: "superAdmin",
1433
+ password: "admin1",
1434
+ });
1435
+ } catch (e) {
1436
+ console.log(chalk.red(`${env} 登录失败: ${e.message || e}`));
1437
+ process.exit(1);
1692
1438
  }
1693
- };
1694
-
1695
- var noSingleCustomColumnModule = {
1696
- meta: {
1697
- type: "suggestion", // `problem`, `suggestion`, or `layout`
1698
- docs: {
1699
- description: "不能单独自定义列",
1700
- recommended: false,
1701
- url: null, // URL to the documentation page for this rule
1702
- },
1703
- fixable: null, // Or `code` or `whitespace`
1704
- schema: [], // Add a schema if the rule has options
1705
- messages: {}, // Add messageId and message
1706
- },
1707
-
1708
- create(context) {
1709
- const sourceCode = context.sourceCode;
1710
- // 获取路径
1711
- const fullPathName = context.filename;
1712
- const cwd = context.cwd;
1713
- const getPath = (loc = {}) => {
1714
- const location = `${loc.start?.line}:${loc.start?.column} `;
1715
- return location + fullPathName.replace(cwd, "");
1716
- };
1717
- return context.parserServices.defineTemplateBodyVisitor(
1718
- // Event handlers for <template>.
1719
- {
1720
- // 模版标签,
1721
- VElement(node) {
1722
- const tagName = node.rawName;
1723
- if (tagNameList.indexOf(tagName) > -1) {
1724
- const attrs = node.startTag.attributes;
1725
- attrs.some((attr) => {
1726
- sourceCode.getText(attr);
1727
- if (
1728
- attrNameList.includes(attr.key?.argument?.rawName) &&
1729
- attr.value?.expression?.type === "Literal"
1730
- ) {
1731
- const value = attr.value.expression.value;
1732
- addModule({
1733
- module: value,
1734
- path: getPath(attr.loc),
1735
- });
1736
- } else if (
1737
- attrNameList.includes(attr.key?.rawName) &&
1738
- attr.value.type === "VLiteral"
1739
- ) {
1740
- const value = attr.value.value;
1741
- addModule({
1742
- module: +value,
1743
- path: getPath(attr.loc),
1744
- });
1745
- } else if (
1746
- attrNameList.includes(attr.key?.argument?.rawName) ||
1747
- attrNameList.includes(attr.key?.rawName)
1748
- ) {
1749
- addModule({
1750
- module: sourceCode.getText(attr.value.expression),
1751
- path: getPath(attr.loc),
1752
- isDynamics: true, // 变量
1753
- });
1754
- }
1755
- return false;
1756
- });
1757
- }
1758
- },
1759
- },
1760
- // Event handlers for <script> or scripts. (optional)
1761
- {
1762
- JSXElement(node) {
1763
- const tagName = node?.openingElement?.name?.name;
1764
- if (tagNameList.indexOf(tagName) > -1) {
1765
- // 遍历找到customColumnModule 属性
1766
- const attrs = node.openingElement.attributes;
1767
- attrs.some((attr) => {
1768
- sourceCode.getText(attr);
1769
- if (attrNameList.includes(attr?.name?.name)) {
1770
- if (attr.value?.expression?.type === "Literal") {
1771
- const value = attr.value.expression.value;
1772
- addModule({
1773
- module: value,
1774
- path: getPath(attr.loc),
1775
- });
1776
- } else {
1777
- addModule({
1778
- module: sourceCode.getText(attr.value.expression),
1779
- path: getPath(attr.loc),
1780
- isDynamics: true, // 变量
1781
- });
1782
- }
1783
- }
1784
- });
1785
- }
1786
- },
1787
- // $customColumnDialog
1788
- CallExpression(node) {
1789
- if (node.callee?.property?.name === "$customColumnDialog") {
1790
- const property = node.arguments[0]?.properties[0];
1791
- if (property.key.name === "module") {
1792
- if (property.value.type === "Literal") {
1793
- addModule({
1794
- module: property.value.value,
1795
- path: getPath(property.loc),
1796
- });
1797
- } else if (property.value.type === "ConditionalExpression") {
1798
- getConditionValue(property.value.consequent, moduleArr,getPath);
1799
- getConditionValue(property.value.alternate, moduleArr,getPath);
1800
- }
1801
- }
1802
- }
1803
- },
1439
+ const inputAppName = await askOrUse(cliApp, {
1440
+ message: "请输入要新增按钮权限的应用名称",
1441
+ name: "inputAppName",
1442
+ type: "input",
1443
+ validate: function (value) {
1444
+ if (value) {
1445
+ return true;
1804
1446
  }
1805
- );
1806
- },
1807
- };
1808
-
1809
- function getConditionValue(node, moduleArr,getPath) {
1810
- if (node.type === "Literal") {
1811
- addModule({
1812
- module: node.value,
1813
- path: getPath(node.loc),
1447
+ return "请输入应用名称";
1448
+ },
1449
+ });
1450
+ let applicationList;
1451
+ try {
1452
+ applicationList = await getApplicationList({
1453
+ env,
1454
+ token,
1455
+ appName: inputAppName,
1814
1456
  });
1815
- }else if (node.type === "ConditionalExpression") {
1816
- getConditionValue(node.consequent, moduleArr,getPath);
1817
- getConditionValue(node.alternate, moduleArr,getPath);
1457
+ } catch (e) {
1458
+ console.log(chalk.red(`查询${env}环境应用列表失败: ${e.message || e}`));
1459
+ process.exit(1);
1818
1460
  }
1819
- }
1820
-
1821
- /**
1822
- * @fileoverview 国际化
1823
- * @author ypf
1824
- */
1825
- const require$1 = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.js', document.baseURI).href)));
1826
- const VueESlintParserPath = require$1.resolve('vue-eslint-parser');
1827
- const meta = {
1828
- name: "eslint-plugin-i18n"};
1829
- var i18n = {
1830
- rules: {
1831
- "no-single-chinese": noSingleChineseRule,
1832
- "no-single-currency": noSingleCurrencyRule,
1833
- "no-single-customColumnModule": noSingleCustomColumnModule,
1834
- },
1835
- processors: {},
1836
- configs: {
1837
- recommended: {
1838
- // 插件
1839
- plugins: [
1840
- "i18n", // 可以省略eslint-plugin-
1841
- ],
1842
- rules: {
1843
- "i18n/no-single-chinese": "warn",
1844
- "i18n/no-single-currency": "warn",
1845
- "i18n/no-single-customColumnModule": "warn",
1846
- },
1847
- // parser: "vue-eslint-parser",
1848
- parser: VueESlintParserPath, // 文档中是只能字符串
1849
- parserOptions: {
1850
- ecmaVersion: "latest", // 指定你想要使用的 ECMAScript 版本
1851
- sourceType: "module", // 支持脚本类型为模块,否则不支持import/export
1852
- ecmaFeatures: {
1853
- jsx: true,
1854
- },
1855
- parser: {
1856
- js: espree__namespace,
1857
- jsx: espree__namespace, // 支持jsx语法
1858
- ts: tsParser,
1859
- tsx: tsParser,
1461
+ if (!applicationList.length) {
1462
+ console.log(chalk.red(`${env}环境未找到该应用`));
1463
+ process.exit(1);
1464
+ }
1465
+ let appId;
1466
+ let appName;
1467
+ if (applicationList.length === 1) {
1468
+ appId = applicationList[0].id;
1469
+ appName = applicationList[0].name;
1470
+ console.log(chalk.gray(`唯一匹配,已自动选中: ${appName}`));
1471
+ } else {
1472
+ // 一键模式下优先精确匹配CLI传入的应用名称(应用列表是关键字模糊查询结果)
1473
+ const exactMatch = oneClick
1474
+ ? applicationList.find((item) => item.name === cliApp)
1475
+ : undefined;
1476
+ if (exactMatch) {
1477
+ appId = exactMatch.id;
1478
+ appName = exactMatch.name;
1479
+ console.log(chalk.gray(`精确匹配,已自动选中: ${appName}`));
1480
+ } else {
1481
+ const res = await inquirer.prompt([
1482
+ {
1483
+ message: "请选择应用名称",
1484
+ name: "appName",
1485
+ type: "rawlist",
1486
+ choices: applicationList,
1860
1487
  },
1488
+ ]);
1489
+ appId = applicationList.find(
1490
+ (item) => item.name === res.appName
1491
+ ).id;
1492
+ appName = res.appName;
1493
+ }
1494
+ }
1495
+ let appConfig;
1496
+ try {
1497
+ appConfig = await getApplicationConfig({ appId, env, token });
1498
+ } catch (e) {
1499
+ console.log(chalk.red(`获取应用配置失败: ${e.message || e}`));
1500
+ process.exit(1);
1501
+ }
1502
+ // CLI传入的权限名称/编码仅第一圈消费, 后续圈次走交互输入
1503
+ let cliName = cliPermissionName;
1504
+ let cliCode = cliPermissionCode;
1505
+ let goOn = true;
1506
+ while (goOn) {
1507
+ const permissionName = await askOrUse(cliName, {
1508
+ message: "请输入权限名称",
1509
+ name: "permissionName",
1510
+ type: "input",
1511
+ validate: function (value) {
1512
+ if (value) {
1513
+ return true;
1514
+ }
1515
+ return "请输入权限名称";
1861
1516
  },
1862
- },
1863
- },
1864
- };
1865
-
1866
- // https://www.npmjs.com/package/text-table?activeTab=readme
1867
-
1868
- function table (rows_, opts) {
1869
- if (!opts) opts = {};
1870
- var hsep = opts.hsep === undefined ? " " : opts.hsep;
1871
- var align = opts.align || [];
1872
- var stringLength =
1873
- opts.stringLength ||
1874
- function (s) {
1875
- return String(s).length;
1876
- };
1877
- var dotsizes = reduce(
1878
- rows_,
1879
- function (acc, row) {
1880
- forEach(row, function (c, ix) {
1881
- var n = dotindex(c);
1882
- if (!acc[ix] || n > acc[ix]) acc[ix] = n;
1883
- });
1884
- return acc;
1885
- },
1886
- []
1887
- );
1888
-
1889
- var rows = map(rows_, function (row) {
1890
- return map(row, function (c_, ix) {
1891
- var c = String(c_);
1892
- if (align[ix] === ".") {
1893
- var index = dotindex(c);
1894
- var size =
1895
- dotsizes[ix] + (/\./.test(c) ? 1 : 2) - (stringLength(c) - index);
1896
- return c + Array(size).join(" ");
1897
- } else return c;
1898
1517
  });
1899
- });
1900
-
1901
- var sizes = reduce(
1902
- rows,
1903
- function (acc, row) {
1904
- forEach(row, function (c, ix) {
1905
- var n = stringLength(c);
1906
- if (!acc[ix] || n > acc[ix]) acc[ix] = n;
1907
- });
1908
- return acc;
1909
- },
1910
- []
1911
- );
1912
-
1913
- return map(rows, function (row) {
1914
- return map(row, function (c, ix) {
1915
- var n = sizes[ix] - stringLength(c) || 0;
1916
- var s = Array(Math.max(n + 1, 1)).join(" ");
1917
- if (align[ix] === "r" || align[ix] === ".") {
1918
- return s + c;
1919
- }
1920
- if (align[ix] === "c") {
1921
- return (
1922
- Array(Math.ceil(n / 2 + 1)).join(" ") +
1923
- c +
1924
- Array(Math.floor(n / 2 + 1)).join(" ")
1518
+ const permissionCode = await askOrUse(cliCode, {
1519
+ message: "请输入权限编码",
1520
+ name: "permissionCode",
1521
+ type: "input",
1522
+ validate: function (value) {
1523
+ if (!value) {
1524
+ return "请输入权限编码";
1525
+ }
1526
+ if (value.includes(":")) {
1527
+ return "权限编码不允许包含 :";
1528
+ }
1529
+ return true;
1530
+ },
1531
+ });
1532
+ cliName = undefined;
1533
+ cliCode = undefined;
1534
+ const data = {
1535
+ code1: appConfig.code,
1536
+ name: permissionName,
1537
+ visitConf: permissionCode,
1538
+ ascriptionApp: appConfig.id,
1539
+ code: `${appConfig.code}:${permissionCode}`,
1540
+ };
1541
+ let confirmed = true;
1542
+ if (!oneClick) {
1543
+ const { confirmAdd } = await inquirer.prompt([
1544
+ {
1545
+ message: `确认新增按钮权限? ${chalk.cyan(
1546
+ `${data.name} (${data.code})`
1547
+ )}`,
1548
+ name: "confirmAdd",
1549
+ type: "confirm",
1550
+ default: true,
1551
+ },
1552
+ ]);
1553
+ confirmed = confirmAdd;
1554
+ }
1555
+ if (confirmed) {
1556
+ try {
1557
+ await addApplicationButtonConfig({ data, env, token });
1558
+ console.log(
1559
+ chalk.green(
1560
+ `新增按钮权限成功: ${appName} - ${data.name} (${data.code})`
1561
+ )
1925
1562
  );
1563
+ } catch (e) {
1564
+ console.log(chalk.red(`新增按钮权限失败: ${e.message || e}`));
1565
+ process.exit(1);
1926
1566
  }
1927
-
1928
- return c + s;
1929
- })
1930
- .join(hsep)
1931
- .replace(/\s+$/, "");
1932
- }).join("\n");
1933
- }
1934
-
1935
- function dotindex(c) {
1936
- var m = /\.[^.]*$/.exec(c);
1937
- return m ? m.index + 1 : c.length;
1938
- }
1939
-
1940
- function reduce(xs, f, init) {
1941
- if (xs.reduce) return xs.reduce(f, init);
1942
- var i = 0;
1943
- var acc = arguments.length >= 3 ? init : xs[i++];
1944
- for (; i < xs.length; i++) {
1945
- f(acc, xs[i], i);
1946
- }
1947
- return acc;
1948
- }
1949
-
1950
- function forEach(xs, f) {
1951
- if (xs.forEach) return xs.forEach(f);
1952
- for (var i = 0; i < xs.length; i++) {
1953
- f.call(xs, xs[i], i);
1567
+ }
1568
+ if (oneClick) {
1569
+ goOn = false;
1570
+ } else {
1571
+ const { continueAdd } = await inquirer.prompt([
1572
+ {
1573
+ message: "是否继续为该应用添加按钮权限?",
1574
+ name: "continueAdd",
1575
+ type: "confirm",
1576
+ default: false,
1577
+ },
1578
+ ]);
1579
+ goOn = continueAdd;
1580
+ }
1954
1581
  }
1955
1582
  }
1956
1583
 
1957
- function map(xs, f) {
1958
- if (xs.map) return xs.map(f);
1959
- var res = [];
1960
- for (var i = 0; i < xs.length; i++) {
1961
- res.push(f.call(xs, xs[i], i));
1962
- }
1963
- return res;
1964
- }
1584
+ /**
1585
+ * @fileoverview 不要单独中文
1586
+ * @author ypf
1587
+ */
1588
+ //------------------------------------------------------------------------------
1589
+ // Rule Definition
1590
+ //------------------------------------------------------------------------------
1591
+
1592
+ /** @type {import('eslint').Rule.RuleModule} */
1593
+
1594
+ // 判断字符串是否是中文
1595
+ const isChinese = (str) => {
1596
+ return /[\u4e00-\u9fa5]+/.test(str);
1597
+ };
1598
+ //去除特殊字符,包含空格
1599
+ function trimSpecial(string = "", formatter) {
1600
+ // const pattern =
1601
+ // /[`~!@#$^\-&*()=|{}':;',\\\[\]\.<>\/?~!@#¥……&*()——|{}【】';:""'。,、?\s]/g;
1602
+ // return string.replace(pattern, "");
1603
+ // console.log(string);
1604
+ // 获取开头空白符的位置
1605
+ const startIdx = string.search(/\S/) - 1;
1606
+ // 获取结尾空白符的位置
1607
+ const endIdx = string.search(/\S\s*$/) + 1;
1608
+ // 获取开头和结尾的字符串
1609
+ const startStr = string.slice(0, startIdx + 1);
1610
+ const endStr = string.slice(endIdx);
1611
+ // 获取中间的字符串
1612
+ const middle = string.slice(startIdx + 1, endIdx);
1613
+ // 取出中间字符串的换行符
1614
+ const middleStr = middle.replace(/\n/g, "");
1615
+ return startStr + formatter(middleStr) + endStr;
1616
+ }
1617
+ // 判断当前节点是否已经翻译过
1618
+ function isTranslate(node) {
1619
+ if (
1620
+ node.parent?.parent?.parent?.type === "CallExpression" &&
1621
+ node.parent?.parent?.parent?.callee?.name === "$hxt"
1622
+ ) {
1623
+ return true;
1624
+ }
1625
+ // console不翻译
1626
+ if (
1627
+ node.parent.type === "CallExpression" &&
1628
+ node.parent.callee?.object?.name === "console"
1629
+ ) {
1630
+ return true;
1631
+ }
1632
+
1633
+ return false;
1634
+ }
1635
+
1636
+ // 空key
1637
+ const emptyKeyRules = (context) => {
1638
+ return {
1639
+ CallExpression(node) {
1640
+ if (node.callee.name === "$hxt") {
1641
+ const properties = node.arguments[0]?.properties || [];
1642
+ // 如果属性是key且值为空
1643
+ const result = properties.some((item) => {
1644
+ // 去除key空格
1645
+ const key = item.key.name.replace(/\s/g, "");
1646
+ if (key === "key") {
1647
+ // 去除value空格
1648
+ // value是模版字符串
1649
+ let value = "";
1650
+ if (item.value.type === "TemplateLiteral") {
1651
+ value = item.value.quasis[0].value.raw.replace(/\s/g, "");
1652
+ } else if (item.value.type === "Literal") {
1653
+ value = item.value.value.replace(/\s/g, "");
1654
+ }
1655
+ if (value === "") {
1656
+ return true;
1657
+ }
1658
+ }
1659
+ });
1660
+ if (result) {
1661
+ context.report({
1662
+ node: node,
1663
+ messageId: "noSingleChinese",
1664
+ data: {
1665
+ raw: chalk.green("key为空"),
1666
+ },
1667
+ fix: (fixer) => {},
1668
+ });
1669
+ }
1670
+ }
1671
+ },
1672
+ };
1673
+ };
1674
+ var noSingleChineseRule = {
1675
+ meta: {
1676
+ type: "suggestion", // `problem`, `suggestion`, or `layout`
1677
+ docs: {
1678
+ description: "不要单独中文",
1679
+ recommended: false,
1680
+ url: null, // URL to the documentation page for this rule
1681
+ },
1682
+ fixable: "code", // Or `code` or `whitespace`
1683
+ schema: [], // Add a schema if the rule has options
1684
+ messages: {
1685
+ noSingleChinese: "不要单独中文: {{raw}}",
1686
+ }, // Add messageId and message
1687
+ },
1688
+ create(context) {
1689
+ const sourceCode = context.sourceCode;
1690
+ context.filename;
1691
+ // console.log(filename,999999)
1692
+ return context.parserServices.defineTemplateBodyVisitor(
1693
+ // Event handlers for <template>.
1694
+ {
1695
+ // 纯字符串,如 测试
1696
+ VText(node) {
1697
+ // 如果是中文,替换为 {{ $hxt({key:'',desc:'中文'})}}
1698
+ if (!isTranslate(node)) {
1699
+ if (isChinese(node.value)) {
1700
+ context.report({
1701
+ node,
1702
+ messageId: "noSingleChinese",
1703
+ data: {
1704
+ raw: chalk.green(node.value),
1705
+ },
1706
+ // message: `VText`,
1707
+ fix: (fixer) => {
1708
+ return fixer.replaceText(
1709
+ node,
1710
+ trimSpecial(node.value, (middle) => {
1711
+ return "{{ $hxt({key:'',desc:'" + middle + "'})}}";
1712
+ })
1713
+ );
1714
+ },
1715
+ });
1716
+ }
1717
+ }
1718
+ },
1719
+ // 纯字符串,如 {{ mini ? '测试' : `开启` }}中的测试
1720
+ // 纯字符串,如 {{test('测试')}}中的测试
1721
+ Literal(node) {
1722
+ if (!isTranslate(node)) {
1723
+ if (isChinese(node.value)) {
1724
+ context.report({
1725
+ node,
1726
+ messageId: "noSingleChinese",
1727
+ data: {
1728
+ raw: chalk.green(node.value),
1729
+ },
1730
+ // message: `Literal`,
1731
+ fix: (fixer) => {
1732
+ // console.log(1111, node);
1733
+ return fixer.replaceText(
1734
+ node,
1735
+ trimSpecial(node.value, (middle) => {
1736
+ return "$hxt({key:'',desc:'" + middle + "'})";
1737
+ })
1738
+ );
1739
+ },
1740
+ });
1741
+ }
1742
+ }
1743
+ },
1744
+ // 模版字符串,
1745
+ // 如{{ mini ? '测试' : `开启` }}中的开启
1746
+ // 如{{ mini ? `${a}测试` : "开启" }}中的${a}测试
1747
+ // 如{{`111`}}
1748
+ // 如<p :a="`${a}册书`">
1749
+ TemplateLiteral(node) {
1750
+ if (!isTranslate(node)) {
1751
+ const textSource = sourceCode.getText(node);
1752
+ let text = textSource;
1753
+ // 删除$符号
1754
+ text = text.replace(/\${/g, "{");
1755
+ if (isChinese(text)) {
1756
+ // 获取expressions的文本
1757
+ const expressionsText = node.expressions.map((item) => {
1758
+ return sourceCode.getText(item);
1759
+ });
1760
+ let expressionStr = "";
1761
+ if (expressionsText.length) {
1762
+ expressionsText.forEach((item, index) => {
1763
+ const key = `slot${index + 1}`;
1764
+ text = text.replace(item, `${key}`);
1765
+ expressionStr += `${key}:${item},`;
1766
+ });
1767
+ // 删除最后一个逗号
1768
+ expressionStr = `{${expressionStr.slice(0, -1)}}`;
1769
+ }
1770
+ if (!isChinese(text)) {
1771
+ return;
1772
+ }
1773
+ context.report({
1774
+ node,
1775
+ data: {
1776
+ raw: chalk.green(textSource),
1777
+ },
1778
+ messageId: "noSingleChinese",
1779
+ // message: `TemplateLiteral`,
1780
+ fix: (fixer) => {
1781
+ return fixer.replaceText(
1782
+ node,
1783
+ trimSpecial(text, (middle) => {
1784
+ return `$hxt({key:'',desc:${middle}}${
1785
+ expressionStr ? `,${expressionStr}` : ""
1786
+ })`;
1787
+ })
1788
+ );
1789
+ },
1790
+ });
1791
+ }
1792
+ }
1793
+ },
1794
+ // 如<p title="1">
1795
+ VLiteral(node) {
1796
+ if (!isTranslate(node)) {
1797
+ if (isChinese(node.value)) {
1798
+ context.report({
1799
+ node,
1800
+ messageId: "noSingleChinese",
1801
+ // message: `VLiteral`,
1802
+ data: {
1803
+ raw: chalk.green(node.value),
1804
+ },
1805
+
1806
+ fix: (fixer) => {
1807
+ // 父节点需要改为冒号方式
1808
+ const parentNode = node.parent;
1809
+ if (parentNode.type === "VAttribute") {
1810
+ const key = parentNode.key;
1811
+ return fixer.replaceText(
1812
+ parentNode,
1813
+ ":" +
1814
+ key.name +
1815
+ "=" +
1816
+ '"' +
1817
+ trimSpecial(node.value, (middle) => {
1818
+ return "$hxt({key:'',desc:'" + middle + "'})";
1819
+ }) +
1820
+ '"'
1821
+ );
1822
+ }
1823
+ },
1824
+ });
1825
+ }
1826
+ }
1827
+ },
1828
+ // 如 <p v-permission="测试">中的测试
1829
+ Identifier(node) {
1830
+ if (
1831
+ !isTranslate(node) &&
1832
+ node.parent.type === "VExpressionContainer"
1833
+ ) {
1834
+ if (isChinese(node.name)) {
1835
+ context.report({
1836
+ node,
1837
+ messageId: "noSingleChinese",
1838
+ // message: `Identifier`,
1839
+ data: {
1840
+ raw: chalk.green(node.name),
1841
+ },
1842
+ fix: (fixer) => {
1843
+ return fixer.replaceText(
1844
+ node,
1845
+ trimSpecial(node.name, (middle) => {
1846
+ return "$hxt({key:'',desc:'" + middle + "'})";
1847
+ })
1848
+ );
1849
+ },
1850
+ });
1851
+ }
1852
+ }
1853
+ },
1854
+ ...emptyKeyRules(context),
1855
+ },
1856
+ // Event handlers for <script> or scripts. (optional)
1857
+ {
1858
+ JSXText(node) {
1859
+ if (!isTranslate(node)) {
1860
+ if (isChinese(node.value)) {
1861
+ context.report({
1862
+ node,
1863
+ messageId: "noSingleChinese",
1864
+ data: {
1865
+ raw: chalk.green(node.value),
1866
+ },
1867
+ fix: (fixer) => {
1868
+ return fixer.replaceText(
1869
+ node,
1870
+ trimSpecial(node.value, (middle) => {
1871
+ return "{ $hxt({key:'',desc:'" + middle + "'})}";
1872
+ })
1873
+ );
1874
+ },
1875
+ });
1876
+ }
1877
+ }
1878
+ },
1879
+ TemplateLiteral(node) {
1880
+ if (!isTranslate(node)) {
1881
+ const textSource = sourceCode.getText(node);
1882
+ let text = textSource;
1883
+ // 删除$符号
1884
+ text = text.replace(/\${/g, "{");
1885
+ if (isChinese(text)) {
1886
+ // 获取expressions的文本
1887
+ const expressionsText = node.expressions.map((item) => {
1888
+ return sourceCode.getText(item);
1889
+ });
1890
+ // console.log(expressionsText,111)
1891
+ let expressionStr = "";
1892
+ if (expressionsText.length) {
1893
+ expressionsText.forEach((item, index) => {
1894
+ const key = `slot${index + 1}`;
1895
+ text = text.replace(item, `${key}`);
1896
+ expressionStr += `${key}:${item},`;
1897
+ });
1898
+ // 删除最后一个逗号
1899
+ expressionStr = `{${expressionStr.slice(0, -1)}}`;
1900
+ }
1901
+ if (!isChinese(text)) {
1902
+ return;
1903
+ }
1904
+ context.report({
1905
+ node,
1906
+ messageId: "noSingleChinese",
1907
+ data: {
1908
+ raw: chalk.green(textSource),
1909
+ },
1910
+ // message: `TemplateLiteral`,
1911
+ fix: (fixer) => {
1912
+ // return console.log(text,expressionStr)
1913
+ // trimSpecial(text, (middle) => {
1914
+ // return `$hxt({key:'',desc:${middle}}${
1915
+ // expressionStr ? `,${expressionStr}` : ""
1916
+ // })`;
1917
+ // })
1918
+ // return
1919
+ return fixer.replaceText(
1920
+ node,
1921
+ trimSpecial(text, (middle) => {
1922
+ return `$hxt({key:'',desc:${middle}}${
1923
+ expressionStr ? `,${expressionStr}` : ""
1924
+ })`;
1925
+ })
1926
+ );
1927
+ },
1928
+ });
1929
+ }
1930
+ }
1931
+ },
1932
+ Literal(node) {
1933
+ // 如果正则忽略
1934
+ if (node.regex) return;
1935
+ if (!isTranslate(node)) {
1936
+ if (isChinese(node.value)) {
1937
+ // 增加{}包裹 render(){return <p title={'测试'} title="测试" title={`${1}测试`}>内容 {`测试`}</p>} 中的内容
1938
+ if (node.parent.type === "JSXAttribute") {
1939
+ context.report({
1940
+ node,
1941
+ messageId: "noSingleChinese",
1942
+ data: {
1943
+ raw: chalk.green(node.value),
1944
+ },
1945
+ fix: (fixer) => {
1946
+ return fixer.replaceText(
1947
+ node,
1948
+ trimSpecial(node.value, (middle) => {
1949
+ return "{$hxt({key:'',desc:'" + middle + "'})}";
1950
+ })
1951
+ );
1952
+ },
1953
+ });
1954
+ } else {
1955
+ context.report({
1956
+ node,
1957
+ messageId: "noSingleChinese",
1958
+ data: {
1959
+ raw: chalk.green(node.value),
1960
+ },
1961
+ fix: (fixer) => {
1962
+ return fixer.replaceText(
1963
+ node,
1964
+ trimSpecial(node.value, (middle) => {
1965
+ return "$hxt({key:'',desc:'" + middle + "'})";
1966
+ })
1967
+ );
1968
+ },
1969
+ });
1970
+ }
1971
+ }
1972
+ }
1973
+ },
1974
+ ...emptyKeyRules(context),
1975
+ }
1976
+ );
1977
+ },
1978
+ };
1965
1979
 
1966
- //------------------------------------------------------------------------------
1980
+ /**
1981
+ * @fileoverview 不能单独金额
1982
+ * @author ypf
1983
+ */
1984
+
1985
+ //------------------------------------------------------------------------------
1986
+ // Rule Definition
1987
+ //------------------------------------------------------------------------------
1988
+
1989
+ /** @type {import('eslint').Rule.RuleModule} */
1990
+ const inputTagNameList = [
1991
+ "el-input",
1992
+ "ElInput",
1993
+ "hx-input",
1994
+ "HxInput",
1995
+ "van-field",
1996
+ "VanField",
1997
+ "hxmb-field",
1998
+ "HxmbField",
1999
+ ]; // input标签
2000
+ const formTagNameList = ["hx-form", "HxForm", "HxmForm", "hxmb-form"]; // form标签
2001
+ // 匹配金额名
2002
+ const currencyList = [
2003
+ // "currency",
2004
+ "total",
2005
+ "price",
2006
+ "amount",
2007
+ ];
2008
+ // 价格后缀过滤
2009
+ const notIncludeCurrencySuffixList = [
2010
+ "code",
2011
+ "type",
2012
+ "no",
2013
+ "status",
2014
+ "name",
2015
+ "sort",
2016
+ "source",
2017
+ "qty",
2018
+ ];
2019
+ // 关键字属性
2020
+ const keyPropList = ["prop", "name", "id", "key"];
2021
+ const hasCurrencyName = (str) => {
2022
+ return currencyList.some((item) => {
2023
+ str = str.toLowerCase();
2024
+ const suffix = str.split(item)[1]?.toLowerCase();
2025
+ if (str.includes(item)) {
2026
+ if(suffix){
2027
+ return !notIncludeCurrencySuffixList.some((item) =>
2028
+ suffix.includes(item)
2029
+ );
2030
+ }
2031
+ return true
2032
+ }
2033
+ return false;
2034
+ });
2035
+ };
2036
+ var noSingleCurrencyRule = {
2037
+ meta: {
2038
+ type: "suggestion", // `problem`, `suggestion`, or `layout`
2039
+ docs: {
2040
+ description: "不能单独金额",
2041
+ recommended: false,
2042
+ url: null, // URL to the documentation page for this rule
2043
+ },
2044
+ fixable: null, // Or `code` or `whitespace`
2045
+ schema: [], // Add a schema if the rule has options
2046
+ messages: {
2047
+ noSingleCurrency: "不要单独币种: {{raw}}",
2048
+ }, // Add messageId and message
2049
+ },
2050
+
2051
+ create(context) {
2052
+ const sourceCode = context.sourceCode;
2053
+ // 获取路径
2054
+ const filename = context.filename;
2055
+ return context.parserServices.defineTemplateBodyVisitor(
2056
+ // Event handlers for <template>.
2057
+ {
2058
+ // 模版标签, 如<el-input >
2059
+ VElement(node) {
2060
+ const tagName = node.rawName;
2061
+ if (inputTagNameList.indexOf(tagName) > -1) {
2062
+ const attrs = node.startTag.attributes;
2063
+ // 遍历找到v-model 属性
2064
+ attrs.some((attr) => {
2065
+ const attrTextSource = sourceCode.getText(attr);
2066
+ if (attr.key?.name?.name === "model") {
2067
+ const textSource = sourceCode.getText(attr.value);
2068
+ // 如果包含currency、price、amount,则报错
2069
+ if (hasCurrencyName(textSource)) {
2070
+ context.report({
2071
+ node,
2072
+ data: {
2073
+ raw:
2074
+ chalk.red(`Template|<${tagName}> `) +
2075
+ chalk.green(attrTextSource),
2076
+ },
2077
+ messageId: "noSingleCurrency",
2078
+ // message: `TemplateLiteral`,
2079
+ fix: (fixer) => {},
2080
+ });
2081
+ }
2082
+ }
2083
+ return false;
2084
+ });
2085
+ }
2086
+ },
2087
+ },
2088
+ // Event handlers for <script> or scripts. (optional)
2089
+ {
2090
+ // jsx 标签,如 render() { return <el-input /> } 中的<el-input />
2091
+ // <el-form formItemList=[]>中的formItemList
2092
+ JSXElement(node) {
2093
+ // console.log(node,'node')
2094
+ const tagName = node?.openingElement?.name?.name;
2095
+ // input
2096
+ if (inputTagNameList.indexOf(tagName) > -1) {
2097
+ // 遍历找到vModel 属性
2098
+ const attrs = node.openingElement.attributes;
2099
+ attrs.some((attr) => {
2100
+ const attrTextSource = sourceCode.getText(attr);
2101
+ // console.log(attrTextSource,'attrTextSource')
2102
+ if (attr.name.name === "vModel") {
2103
+ const textSource = sourceCode.getText(attr.value);
2104
+ // 如果包含币种
2105
+ if (hasCurrencyName(textSource)) {
2106
+ context.report({
2107
+ node,
2108
+ data: {
2109
+ raw:
2110
+ chalk.red(`JSX|<${tagName}> `) +
2111
+ chalk.green(attrTextSource),
2112
+ },
2113
+ messageId: "noSingleCurrency",
2114
+ // message: `TemplateLiteral`,
2115
+ fix: (fixer) => {},
2116
+ });
2117
+ }
2118
+ }
2119
+ });
2120
+ }
2121
+ // form
2122
+ if (formTagNameList.indexOf(tagName)) {
2123
+ const attrs = node.openingElement.attributes;
2124
+ // 遍历找到formItemList 属性
2125
+ attrs.some((attr) => {
2126
+ sourceCode.getText(attr);
2127
+ // 是formItemList
2128
+ if (attr?.name?.name === "formItemList") {
2129
+ // 且值是数组
2130
+ if (attr?.value?.expression?.type === "ArrayExpression") {
2131
+ // 遍历数组
2132
+ attr.value.expression.elements.forEach((item) => {
2133
+ (item.properties || []).some((item) => {
2134
+ const attrTextSource = sourceCode.getText(item);
2135
+ if (keyPropList.indexOf(item.key.name) > -1) {
2136
+ const textSource = sourceCode.getText(item.value);
2137
+ if (item?.value?.type === "CallExpression") return;
2138
+ // 如果包含币种
2139
+ if (hasCurrencyName(textSource)) {
2140
+ context.report({
2141
+ node,
2142
+ data: {
2143
+ raw:
2144
+ chalk.red(`JSX|${tagName}|${attr.name.name} `) +
2145
+ chalk.green(`${attrTextSource}`),
2146
+ },
2147
+ messageId: "noSingleCurrency",
2148
+ // message: `TemplateLiteral`,
2149
+ fix: (fixer) => {},
2150
+ });
2151
+ }
2152
+ }
2153
+ });
2154
+ });
2155
+ }
2156
+ }
2157
+ });
2158
+ }
2159
+ },
2160
+ // [{prop:'price'}]
2161
+ ArrayExpression(node) {
2162
+ // 不检测路由文件
2163
+ if (filename.includes("src/router")) return;
2164
+ // 是数组且不是jsx属性上的数组(上面已经检测过了,否则会出现2条错误)
2165
+ if (node.parent?.parent?.type === "JSXAttribute") return;
2166
+ (node.elements || []).forEach((item) => {
2167
+ // 数组里面是对象
2168
+ if (item?.type === "ObjectExpression") {
2169
+ (item.properties || []).forEach((item) => {
2170
+ const attrTextSource = sourceCode.getText(item);
2171
+ if (keyPropList.indexOf(item?.key?.name) > -1) {
2172
+ if (item?.value?.type === "CallExpression") return;
2173
+ const textSource = sourceCode.getText(item.value);
2174
+ // 如果包含币种
2175
+ if (hasCurrencyName(textSource)) {
2176
+ context.report({
2177
+ node,
2178
+ data: {
2179
+ raw:
2180
+ chalk.red(`JSX|[{}] `) +
2181
+ chalk.green(`${attrTextSource}`),
2182
+ },
2183
+ messageId: "noSingleCurrency",
2184
+ });
2185
+ }
2186
+ }
2187
+ });
2188
+ }
2189
+ });
2190
+ },
2191
+ }
2192
+ );
2193
+ },
2194
+ };
1967
2195
 
1968
- /**
1969
- * Given a word and a count, append an s if count is not one.
1970
- * @param {string} word A word in its singular form.
1971
- * @param {int} count A number controlling whether word should be pluralized.
1972
- * @returns {string} The original word with an s on the end if count is not one.
1973
- */
1974
- function pluralize(word, count) {
1975
- return (count === 1 ? word : `${word}s`);
2196
+ /**
2197
+ * @fileoverview 不能单独金额
2198
+ * @author ypf
2199
+ */
2200
+
2201
+ //------------------------------------------------------------------------------
2202
+ // Rule Definition
2203
+ //------------------------------------------------------------------------------
2204
+
2205
+ /** @type {import('eslint').Rule.RuleModule} */
2206
+ const tagNameList = ["hx-search-list-page", "HxSearchListPage"];
2207
+ const attrNameList = ["custom-column-module", "customColumnModule"];
2208
+ const moduleArr = [];
2209
+ const getModuleArr = () => moduleArr;
2210
+ const addModule = (moduleObj) => {
2211
+ const isExist= moduleArr.some((item) => {
2212
+ return item.module === moduleObj.module
2213
+ });
2214
+ if(!isExist){
2215
+ moduleArr.push(moduleObj);
2216
+ }
2217
+ };
2218
+
2219
+ var noSingleCustomColumnModule = {
2220
+ meta: {
2221
+ type: "suggestion", // `problem`, `suggestion`, or `layout`
2222
+ docs: {
2223
+ description: "不能单独自定义列",
2224
+ recommended: false,
2225
+ url: null, // URL to the documentation page for this rule
2226
+ },
2227
+ fixable: null, // Or `code` or `whitespace`
2228
+ schema: [], // Add a schema if the rule has options
2229
+ messages: {}, // Add messageId and message
2230
+ },
2231
+
2232
+ create(context) {
2233
+ const sourceCode = context.sourceCode;
2234
+ // 获取路径
2235
+ const fullPathName = context.filename;
2236
+ const cwd = context.cwd;
2237
+ const getPath = (loc = {}) => {
2238
+ const location = `${loc.start?.line}:${loc.start?.column} `;
2239
+ return location + fullPathName.replace(cwd, "");
2240
+ };
2241
+ return context.parserServices.defineTemplateBodyVisitor(
2242
+ // Event handlers for <template>.
2243
+ {
2244
+ // 模版标签,
2245
+ VElement(node) {
2246
+ const tagName = node.rawName;
2247
+ if (tagNameList.indexOf(tagName) > -1) {
2248
+ const attrs = node.startTag.attributes;
2249
+ attrs.some((attr) => {
2250
+ sourceCode.getText(attr);
2251
+ if (
2252
+ attrNameList.includes(attr.key?.argument?.rawName) &&
2253
+ attr.value?.expression?.type === "Literal"
2254
+ ) {
2255
+ const value = attr.value.expression.value;
2256
+ addModule({
2257
+ module: value,
2258
+ path: getPath(attr.loc),
2259
+ });
2260
+ } else if (
2261
+ attrNameList.includes(attr.key?.rawName) &&
2262
+ attr.value.type === "VLiteral"
2263
+ ) {
2264
+ const value = attr.value.value;
2265
+ addModule({
2266
+ module: +value,
2267
+ path: getPath(attr.loc),
2268
+ });
2269
+ } else if (
2270
+ attrNameList.includes(attr.key?.argument?.rawName) ||
2271
+ attrNameList.includes(attr.key?.rawName)
2272
+ ) {
2273
+ addModule({
2274
+ module: sourceCode.getText(attr.value.expression),
2275
+ path: getPath(attr.loc),
2276
+ isDynamics: true, // 变量
2277
+ });
2278
+ }
2279
+ return false;
2280
+ });
2281
+ }
2282
+ },
2283
+ },
2284
+ // Event handlers for <script> or scripts. (optional)
2285
+ {
2286
+ JSXElement(node) {
2287
+ const tagName = node?.openingElement?.name?.name;
2288
+ if (tagNameList.indexOf(tagName) > -1) {
2289
+ // 遍历找到customColumnModule 属性
2290
+ const attrs = node.openingElement.attributes;
2291
+ attrs.some((attr) => {
2292
+ sourceCode.getText(attr);
2293
+ if (attrNameList.includes(attr?.name?.name)) {
2294
+ if (attr.value?.expression?.type === "Literal") {
2295
+ const value = attr.value.expression.value;
2296
+ addModule({
2297
+ module: value,
2298
+ path: getPath(attr.loc),
2299
+ });
2300
+ } else {
2301
+ addModule({
2302
+ module: sourceCode.getText(attr.value.expression),
2303
+ path: getPath(attr.loc),
2304
+ isDynamics: true, // 变量
2305
+ });
2306
+ }
2307
+ }
2308
+ });
2309
+ }
2310
+ },
2311
+ // $customColumnDialog
2312
+ CallExpression(node) {
2313
+ if (node.callee?.property?.name === "$customColumnDialog") {
2314
+ const property = node.arguments[0]?.properties[0];
2315
+ if (property.key.name === "module") {
2316
+ if (property.value.type === "Literal") {
2317
+ addModule({
2318
+ module: property.value.value,
2319
+ path: getPath(property.loc),
2320
+ });
2321
+ } else if (property.value.type === "ConditionalExpression") {
2322
+ getConditionValue(property.value.consequent, moduleArr,getPath);
2323
+ getConditionValue(property.value.alternate, moduleArr,getPath);
2324
+ }
2325
+ }
2326
+ }
2327
+ },
2328
+ }
2329
+ );
2330
+ },
2331
+ };
2332
+
2333
+ function getConditionValue(node, moduleArr,getPath) {
2334
+ if (node.type === "Literal") {
2335
+ addModule({
2336
+ module: node.value,
2337
+ path: getPath(node.loc),
2338
+ });
2339
+ }else if (node.type === "ConditionalExpression") {
2340
+ getConditionValue(node.consequent, moduleArr,getPath);
2341
+ getConditionValue(node.alternate, moduleArr,getPath);
2342
+ }
1976
2343
  }
1977
2344
 
1978
- //------------------------------------------------------------------------------
1979
- // Public Interface
1980
- //------------------------------------------------------------------------------
1981
-
1982
- function stylish(results) {
1983
-
1984
- let output = "\n",
1985
- errorCount = 0,
1986
- warningCount = 0,
1987
- fixableErrorCount = 0,
1988
- fixableWarningCount = 0,
1989
- summaryColor = "yellow";
1990
- results.forEach(result => {
1991
- const messages = result.messages;
1992
-
1993
- if (messages.length === 0) {
1994
- return;
1995
- }
1996
-
1997
- errorCount += result.errorCount;
1998
- warningCount += result.warningCount;
1999
- fixableErrorCount += result.fixableErrorCount;
2000
- fixableWarningCount += result.fixableWarningCount;
2001
-
2002
- output += `${chalk.underline(result.filePath)}\n`;
2003
-
2004
- output += `${table(
2005
- messages.map(message => {
2006
- let messageType;
2007
-
2008
- if (message.fatal || message.severity === 2) {
2009
- messageType = chalk.red("error");
2010
- summaryColor = "red";
2011
- } else {
2012
- messageType = chalk.yellow("warning");
2013
- }
2014
-
2015
- return [
2016
- "",
2017
- message.line || 0,
2018
- message.column || 0,
2019
- messageType,
2020
- message.message.replace(/([^ ])\.$/u, "$1"),
2021
- chalk.dim(message.ruleId || "")
2022
- ];
2023
- }),
2024
- {
2025
- align: ["", "r", "l"],
2026
- stringLength(str) {
2027
- return stripAnsi(str).length;
2028
- }
2029
- }
2030
- ).split("\n").map(el => el.replace(/(\d+)\s+(\d+)/u, (m, p1, p2) => chalk.dim(`${p1}:${p2}`))).join("\n")}\n\n`;
2031
- });
2032
-
2033
- const total = errorCount + warningCount;
2034
-
2035
- if (total > 0) {
2036
- output += chalk[summaryColor].bold([
2037
- "\u2716 ", total, pluralize(" problem", total),
2038
- " (", errorCount, pluralize(" error", errorCount), ", ",
2039
- warningCount, pluralize(" warning", warningCount), ")\n"
2040
- ].join(""));
2345
+ /**
2346
+ * @fileoverview 国际化
2347
+ * @author ypf
2348
+ */
2349
+ const require$1 = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.js', document.baseURI).href)));
2350
+ const VueESlintParserPath = require$1.resolve('vue-eslint-parser');
2351
+ const meta = {
2352
+ name: "eslint-plugin-i18n"};
2353
+ var i18n = {
2354
+ rules: {
2355
+ "no-single-chinese": noSingleChineseRule,
2356
+ "no-single-currency": noSingleCurrencyRule,
2357
+ "no-single-customColumnModule": noSingleCustomColumnModule,
2358
+ },
2359
+ processors: {},
2360
+ configs: {
2361
+ recommended: {
2362
+ // 插件
2363
+ plugins: [
2364
+ "i18n", // 可以省略eslint-plugin-
2365
+ ],
2366
+ rules: {
2367
+ "i18n/no-single-chinese": "warn",
2368
+ "i18n/no-single-currency": "warn",
2369
+ "i18n/no-single-customColumnModule": "warn",
2370
+ },
2371
+ // parser: "vue-eslint-parser",
2372
+ parser: VueESlintParserPath, // 文档中是只能字符串
2373
+ parserOptions: {
2374
+ ecmaVersion: "latest", // 指定你想要使用的 ECMAScript 版本
2375
+ sourceType: "module", // 支持脚本类型为模块,否则不支持import/export
2376
+ ecmaFeatures: {
2377
+ jsx: true,
2378
+ },
2379
+ parser: {
2380
+ js: espree__namespace,
2381
+ jsx: espree__namespace, // 支持jsx语法
2382
+ ts: tsParser,
2383
+ tsx: tsParser,
2384
+ },
2385
+ },
2386
+ },
2387
+ },
2388
+ };
2041
2389
 
2042
- if (fixableErrorCount > 0 || fixableWarningCount > 0) {
2043
- output += chalk[summaryColor].bold([
2044
- " ", fixableErrorCount, pluralize(" error", fixableErrorCount), " and ",
2045
- fixableWarningCount, pluralize(" warning", fixableWarningCount),
2046
- " potentially fixable with the `--fix` option.\n"
2047
- ].join(""));
2048
- }
2049
- }
2390
+ // https://www.npmjs.com/package/text-table?activeTab=readme
2391
+
2392
+ function table (rows_, opts) {
2393
+ if (!opts) opts = {};
2394
+ var hsep = opts.hsep === undefined ? " " : opts.hsep;
2395
+ var align = opts.align || [];
2396
+ var stringLength =
2397
+ opts.stringLength ||
2398
+ function (s) {
2399
+ return String(s).length;
2400
+ };
2401
+ var dotsizes = reduce(
2402
+ rows_,
2403
+ function (acc, row) {
2404
+ forEach(row, function (c, ix) {
2405
+ var n = dotindex(c);
2406
+ if (!acc[ix] || n > acc[ix]) acc[ix] = n;
2407
+ });
2408
+ return acc;
2409
+ },
2410
+ []
2411
+ );
2412
+
2413
+ var rows = map(rows_, function (row) {
2414
+ return map(row, function (c_, ix) {
2415
+ var c = String(c_);
2416
+ if (align[ix] === ".") {
2417
+ var index = dotindex(c);
2418
+ var size =
2419
+ dotsizes[ix] + (/\./.test(c) ? 1 : 2) - (stringLength(c) - index);
2420
+ return c + Array(size).join(" ");
2421
+ } else return c;
2422
+ });
2423
+ });
2424
+
2425
+ var sizes = reduce(
2426
+ rows,
2427
+ function (acc, row) {
2428
+ forEach(row, function (c, ix) {
2429
+ var n = stringLength(c);
2430
+ if (!acc[ix] || n > acc[ix]) acc[ix] = n;
2431
+ });
2432
+ return acc;
2433
+ },
2434
+ []
2435
+ );
2436
+
2437
+ return map(rows, function (row) {
2438
+ return map(row, function (c, ix) {
2439
+ var n = sizes[ix] - stringLength(c) || 0;
2440
+ var s = Array(Math.max(n + 1, 1)).join(" ");
2441
+ if (align[ix] === "r" || align[ix] === ".") {
2442
+ return s + c;
2443
+ }
2444
+ if (align[ix] === "c") {
2445
+ return (
2446
+ Array(Math.ceil(n / 2 + 1)).join(" ") +
2447
+ c +
2448
+ Array(Math.floor(n / 2 + 1)).join(" ")
2449
+ );
2450
+ }
2451
+
2452
+ return c + s;
2453
+ })
2454
+ .join(hsep)
2455
+ .replace(/\s+$/, "");
2456
+ }).join("\n");
2457
+ }
2458
+
2459
+ function dotindex(c) {
2460
+ var m = /\.[^.]*$/.exec(c);
2461
+ return m ? m.index + 1 : c.length;
2462
+ }
2463
+
2464
+ function reduce(xs, f, init) {
2465
+ if (xs.reduce) return xs.reduce(f, init);
2466
+ var i = 0;
2467
+ var acc = arguments.length >= 3 ? init : xs[i++];
2468
+ for (; i < xs.length; i++) {
2469
+ f(acc, xs[i], i);
2470
+ }
2471
+ return acc;
2472
+ }
2473
+
2474
+ function forEach(xs, f) {
2475
+ if (xs.forEach) return xs.forEach(f);
2476
+ for (var i = 0; i < xs.length; i++) {
2477
+ f.call(xs, xs[i], i);
2478
+ }
2479
+ }
2480
+
2481
+ function map(xs, f) {
2482
+ if (xs.map) return xs.map(f);
2483
+ var res = [];
2484
+ for (var i = 0; i < xs.length; i++) {
2485
+ res.push(f.call(xs, xs[i], i));
2486
+ }
2487
+ return res;
2488
+ }
2050
2489
 
2051
- // Resets output color, for prevent change on top level
2052
- return total > 0 ? chalk.reset(output) : "";
2490
+ //------------------------------------------------------------------------------
2491
+
2492
+ /**
2493
+ * Given a word and a count, append an s if count is not one.
2494
+ * @param {string} word A word in its singular form.
2495
+ * @param {int} count A number controlling whether word should be pluralized.
2496
+ * @returns {string} The original word with an s on the end if count is not one.
2497
+ */
2498
+ function pluralize(word, count) {
2499
+ return (count === 1 ? word : `${word}s`);
2500
+ }
2501
+
2502
+ //------------------------------------------------------------------------------
2503
+ // Public Interface
2504
+ //------------------------------------------------------------------------------
2505
+
2506
+ function stylish(results) {
2507
+
2508
+ let output = "\n",
2509
+ errorCount = 0,
2510
+ warningCount = 0,
2511
+ fixableErrorCount = 0,
2512
+ fixableWarningCount = 0,
2513
+ summaryColor = "yellow";
2514
+ results.forEach(result => {
2515
+ const messages = result.messages;
2516
+
2517
+ if (messages.length === 0) {
2518
+ return;
2519
+ }
2520
+
2521
+ errorCount += result.errorCount;
2522
+ warningCount += result.warningCount;
2523
+ fixableErrorCount += result.fixableErrorCount;
2524
+ fixableWarningCount += result.fixableWarningCount;
2525
+
2526
+ output += `${chalk.underline(result.filePath)}\n`;
2527
+
2528
+ output += `${table(
2529
+ messages.map(message => {
2530
+ let messageType;
2531
+
2532
+ if (message.fatal || message.severity === 2) {
2533
+ messageType = chalk.red("error");
2534
+ summaryColor = "red";
2535
+ } else {
2536
+ messageType = chalk.yellow("warning");
2537
+ }
2538
+
2539
+ return [
2540
+ "",
2541
+ message.line || 0,
2542
+ message.column || 0,
2543
+ messageType,
2544
+ message.message.replace(/([^ ])\.$/u, "$1"),
2545
+ chalk.dim(message.ruleId || "")
2546
+ ];
2547
+ }),
2548
+ {
2549
+ align: ["", "r", "l"],
2550
+ stringLength(str) {
2551
+ return stripAnsi(str).length;
2552
+ }
2553
+ }
2554
+ ).split("\n").map(el => el.replace(/(\d+)\s+(\d+)/u, (m, p1, p2) => chalk.dim(`${p1}:${p2}`))).join("\n")}\n\n`;
2555
+ });
2556
+
2557
+ const total = errorCount + warningCount;
2558
+
2559
+ if (total > 0) {
2560
+ output += chalk[summaryColor].bold([
2561
+ "\u2716 ", total, pluralize(" problem", total),
2562
+ " (", errorCount, pluralize(" error", errorCount), ", ",
2563
+ warningCount, pluralize(" warning", warningCount), ")\n"
2564
+ ].join(""));
2565
+
2566
+ if (fixableErrorCount > 0 || fixableWarningCount > 0) {
2567
+ output += chalk[summaryColor].bold([
2568
+ " ", fixableErrorCount, pluralize(" error", fixableErrorCount), " and ",
2569
+ fixableWarningCount, pluralize(" warning", fixableWarningCount),
2570
+ " potentially fixable with the `--fix` option.\n"
2571
+ ].join(""));
2572
+ }
2573
+ }
2574
+
2575
+ // Resets output color, for prevent change on top level
2576
+ return total > 0 ? chalk.reset(output) : "";
2053
2577
  }
2054
2578
 
2055
2579
  async function lint$1(patterns, fix, rule, hook = {}) {
@@ -2148,24 +2672,34 @@ async function lint$1(patterns, fix, rule, hook = {}) {
2148
2672
  const condition = {
2149
2673
  currency: hasCurrencyName,
2150
2674
  };
2151
- async function customColumnModule (lintc, fix, rule, parentRule) {
2152
- const { way } = await inquirer.prompt([
2153
- {
2154
- message: "请选择检测自定义列方式",
2155
- name: "way",
2156
- type: "list",
2157
- choices: [
2158
- {
2159
- name: "自动检测HxSearchListPage、this.$customColumnDialog的自定义列ID",
2160
- value: "normal",
2161
- },
2162
- {
2163
- name: "自定义输入",
2164
- value: "input",
2165
- },
2166
- ],
2167
- },
2168
- ]);
2675
+ // 自定义列检测(支持 --way/--column-id 一键, CLI参数 > 交互)
2676
+ async function customColumnModule (lintc, fix, rule, parentRule, cliOpts = {}) {
2677
+ // 空字符串视为未传(与askOrUse语义一致)
2678
+ const cliWay = cliOpts.way || undefined;
2679
+ const cliColumnId = cliOpts.columnId || undefined;
2680
+ // CLI参数校验前置(快速失败, 不进交互)
2681
+ if (cliWay !== undefined) {
2682
+ validateChoice(cliWay, ["normal", "input"], "--way");
2683
+ }
2684
+ if (cliColumnId !== undefined && cliWay !== "input") {
2685
+ console.log(chalk.red("参数 --column-id 须搭配 --way input 使用"));
2686
+ process.exit(1);
2687
+ }
2688
+ const way = await askOrUse(cliWay, {
2689
+ message: "请选择检测自定义列方式",
2690
+ name: "way",
2691
+ type: "list",
2692
+ choices: [
2693
+ {
2694
+ name: "自动检测HxSearchListPage、this.$customColumnDialog的自定义列ID",
2695
+ value: "normal",
2696
+ },
2697
+ {
2698
+ name: "自定义输入",
2699
+ value: "input",
2700
+ },
2701
+ ],
2702
+ });
2169
2703
  if (way === "normal") {
2170
2704
  lint$1(lintc, fix, "customColumnModule", {
2171
2705
  lintFilesAfter: (results, Loading) => {
@@ -2182,14 +2716,29 @@ async function customColumnModule (lintc, fix, rule, parentRule) {
2182
2716
  },
2183
2717
  });
2184
2718
  } else if (way === "input") {
2185
- const { customColumnId } = await inquirer.prompt([
2186
- {
2187
- message: "请输入自定义列ID,格式为xx,xx,xx",
2188
- name: "customColumnId",
2189
- type: "input",
2190
- },
2191
- ]);
2192
- const moduleArr = customColumnId.split(",").map((item) => {
2719
+ // CLI传入直接用, 否则交互输入
2720
+ let customColumnId;
2721
+ if (cliColumnId !== undefined) {
2722
+ customColumnId = cliColumnId;
2723
+ } else {
2724
+ const res = await inquirer.prompt([
2725
+ {
2726
+ message: "请输入自定义列ID,格式为xx,xx,xx",
2727
+ name: "customColumnId",
2728
+ type: "input",
2729
+ },
2730
+ ]);
2731
+ customColumnId = res.customColumnId;
2732
+ }
2733
+ const columnArr = customColumnId
2734
+ .split(",")
2735
+ .map((item) => item.trim())
2736
+ .filter(Boolean);
2737
+ if (!columnArr.length) {
2738
+ console.log(chalk.red("自定义列ID不能为空"));
2739
+ process.exit(1);
2740
+ }
2741
+ const moduleArr = columnArr.map((item) => {
2193
2742
  return {
2194
2743
  module: item,
2195
2744
  path: "NA",
@@ -2200,11 +2749,17 @@ async function customColumnModule (lintc, fix, rule, parentRule) {
2200
2749
  }
2201
2750
 
2202
2751
  async function getColumn(moduleArr = [], dynamicsArr = [], parentRule) {
2203
- const token = await login({
2204
- env: "sit",
2205
- username: "superAdmin",
2206
- password: "admin1",
2207
- });
2752
+ let token;
2753
+ try {
2754
+ token = await login({
2755
+ env: "sit",
2756
+ username: "superAdmin",
2757
+ password: "admin1",
2758
+ });
2759
+ } catch (e) {
2760
+ console.log(chalk.red(`登录失败: ${e.message || e}`));
2761
+ return;
2762
+ }
2208
2763
  const requestArr = moduleArr.map(({ module, path }) =>
2209
2764
  request$1({
2210
2765
  url: `http://sit-hxjf.hongxinshop.com/api-item/api/customizedColumns/find`,
@@ -2396,8 +2951,8 @@ function getGitChangedFiles() {
2396
2951
  .filter((f) => fs.existsSync(f));
2397
2952
  return allFiles;
2398
2953
  } catch (error) {
2399
- console.log(chalk.red("获取git修改文件失败,请确保在git仓库中运行"));
2400
- return [];
2954
+ // git命令失败(如不在git仓库中运行)属于使用错误, 以退出码1结束
2955
+ exitError("获取git修改文件失败,请确保在git仓库中运行");
2401
2956
  }
2402
2957
  }
2403
2958
  // const spinner = ora();
@@ -2411,7 +2966,7 @@ commander.program.option("-i, --input <type>", "翻译空key");
2411
2966
  // 上传翻译
2412
2967
  commander.program.option("-u, --upload", "上传翻译包");
2413
2968
  // 同步sass应用权限配置
2414
- commander.program.option("-sass, --sass", "同步sass按钮权限配置");
2969
+ commander.program.option("-sass, --sass", "sass按钮权限配置(同步/新增)");
2415
2970
  // 检测未$hxt中文
2416
2971
  commander.program.option("-lint, --lint <patterns>", "检测未$hxt的中文");
2417
2972
  // 检测配置项
@@ -2420,10 +2975,66 @@ commander.program.option("-lintc, --lintc <patterns>", "检测指定配置规则
2420
2975
  commander.program.option("--fix", "修复lint检出的错误");
2421
2976
  // 翻译excel指定列语言
2422
2977
  commander.program.option("-excel, --excel", "翻译excel指定列语言");
2978
+ // 一键执行选择参数(取值优先级: CLI参数 > --cache凭据缓存 > 交互询问)
2979
+ commander.program.option("--cache", "凭据静默走缓存(百度appid/key、域账号密码), 缓存缺失时回退交互");
2980
+ commander.program.option("--env <env>", "上传环境: mit|sit|uat|pro, 多环境逗号分隔(仅mit/sit/uat, pro须单独传); sass新增时单选mit|sit|uat");
2981
+ commander.program.option("--username <name>", "域账号(env为pro时为生产域账号)");
2982
+ commander.program.option("--password <pwd>", "域密码(明文会进shell历史, 日常建议用--cache)");
2983
+ commander.program.option("--files <files>", "上传的excel文件, 逗号分隔或all(全部), 如 --files \"a.xlsx,b.xlsx\"");
2984
+ commander.program.option("--appid <appid>", "百度翻译appid");
2985
+ commander.program.option("--key <key>", "百度翻译key");
2986
+ commander.program.option("--merge", "翻译结果合并至export.xlsx(默认覆盖)");
2987
+ commander.program.option("--from <env>", "sass同步源环境: mit|sit|uat");
2988
+ commander.program.option("--to <env>", "sass同步目标环境: mit|sit|uat(须不同于--from)");
2989
+ commander.program.option("--app <name>", "sass应用名称关键字(同步/新增)");
2990
+ commander.program.option("--action <action>", "sass功能: sync(同步按钮权限) | permission(新增按钮权限)");
2991
+ commander.program.option("--permission-name <name>", "sass按钮权限名称(须搭配 --action permission)");
2992
+ commander.program.option("--permission-code <code>", "sass按钮权限编码(须搭配 --action permission)");
2993
+ commander.program.option("--rule <rule>", "lintc检查规则: currency");
2994
+ commander.program.option("--scene <scene>", "lintc检查场景: currency|customColumnModule");
2995
+ commander.program.option("--way <way>", "自定义列检测方式: normal|input");
2996
+ commander.program.option("--column-id <ids>", "自定义列ID, 逗号分隔(way=input时), 如 --column-id \"a,b,c\"");
2997
+ commander.program.option("--file <name>", "excel翻译的文件名");
2998
+ commander.program.option("--lang <lang>", "excel翻译的语言列(如en、vi)");
2999
+ // help追加的一键执行示例(命令绿色, 说明灰色, 按最长命令对齐)
3000
+ const helpExamples = [
3001
+ ["fe-it-beta -u --env sit --files all --cache", "上传全部excel到sit, 账号密码走缓存"],
3002
+ ["fe-it-beta -u --env sit --files all --username xxx --cache", "账号用传的, 其余凭据走缓存(可混合)"],
3003
+ ['fe-it-beta -u --env "mit,sit,uat" --files export.xlsx --cache', ""],
3004
+ ["fe-it-beta -i src --cache", "翻译空key, 凭据走缓存, 覆盖export.xlsx"],
3005
+ ["fe-it-beta -i src --appid x --key y --merge", "翻译并合并至export.xlsx"],
3006
+ ["fe-it-beta -lint src --fix", "检测并自动修复"],
3007
+ [
3008
+ 'fe-it-beta -lintc src --rule currency --scene customColumnModule --way input --column-id "a,b,c"',
3009
+ "",
3010
+ ],
3011
+ ["fe-it-beta -sass --from mit --to sit --app 应用名", "传同步参数直通同步流程"],
3012
+ [
3013
+ "fe-it-beta -sass --action permission --env mit --app 应用名 --permission-name 权限名 --permission-code 编码",
3014
+ "一键新增按钮权限(参数齐全免确认)",
3015
+ ],
3016
+ ["fe-it-beta -excel --file export.xlsx --lang vi --cache", ""],
3017
+ ];
3018
+ commander.program.addHelpText(
3019
+ "after",
3020
+ "\n" +
3021
+ chalk.bold("一键执行示例") +
3022
+ " " +
3023
+ chalk.gray("(注意: PowerShell下含逗号的参数值需加引号)") +
3024
+ ":\n" +
3025
+ helpExamples
3026
+ .map(([cmd, desc]) => {
3027
+ // 说明列固定从62列起, 超长命令后接2空格
3028
+ const pad = " ".repeat(Math.max(2, 62 - cmd.length));
3029
+ return " " + chalk.green(cmd) + (desc ? chalk.gray(pad + desc) : "");
3030
+ })
3031
+ .join("\n")
3032
+ );
2423
3033
  commander.program.parse(process.argv);
2424
3034
 
2425
3035
  // 判断命令参数
2426
3036
  const { input, sass, upload, lint, lintc, fix, excel } = commander.program.opts();
3037
+ const cliOpts = commander.program.opts();
2427
3038
  const rules = [
2428
3039
  {
2429
3040
  message: "请选择检查规则",
@@ -2455,9 +3066,9 @@ const rules = [
2455
3066
  },
2456
3067
  ];
2457
3068
  if (sass) {
2458
- syncSassConfig();
3069
+ sassMain(cliOpts);
2459
3070
  } else if (upload) {
2460
- upload$1();
3071
+ upload$1(cliOpts);
2461
3072
  } else if (lint !== undefined) {
2462
3073
  // 必须传入路径
2463
3074
  if (!lint || !lint.trim()) {
@@ -2479,24 +3090,46 @@ if (sass) {
2479
3090
  }
2480
3091
  lint$1(lintPatterns, fix);
2481
3092
  } else if (lintc) {
2482
- inquirer.prompt(rules).then(async ({ rule }) => {
2483
- const parentRule = rule;
2484
- if (rule === "currency") {
2485
- const ruleChoice = rules.find((item) => item.name === "rule");
2486
- const currencySubChoice = ruleChoice.choices.find(
2487
- (item) => item.value === "currency"
2488
- ).children;
2489
- const { scene } = await inquirer.prompt(currencySubChoice);
2490
- rule = scene;
2491
- if (rule === "customColumnModule") {
2492
- return customColumnModule(lintc, fix, rule, parentRule);
2493
- }
2494
- }
2495
- lint$1(lintc, fix, rule);
2496
- });
3093
+ runLintc().catch((error) => console.error(error));
2497
3094
  } else if (excel) {
2498
- excelFn();
3095
+ excelFn(cliOpts);
2499
3096
  } else {
3097
+ runTranslate().catch((error) => console.error(error));
3098
+ }
3099
+
3100
+ // lintc分支(支持 --rule/--scene 一键执行)
3101
+ async function runLintc() {
3102
+ const ruleQuestion = rules.find((item) => item.name === "rule");
3103
+ const ruleValues = ruleQuestion.choices.map((item) => item.value);
3104
+ if (cliOpts.rule !== undefined) {
3105
+ validateChoice(cliOpts.rule, ruleValues, "--rule");
3106
+ }
3107
+ const parentRule = await askOrUse(cliOpts.rule, ruleQuestion);
3108
+ if (parentRule === "currency") {
3109
+ const currencySubChoice = ruleQuestion.choices.find(
3110
+ (item) => item.value === "currency"
3111
+ ).children;
3112
+ const sceneValues = currencySubChoice[0].choices.map((item) => item.value);
3113
+ if (cliOpts.scene !== undefined) {
3114
+ validateChoice(cliOpts.scene, sceneValues, "--scene");
3115
+ }
3116
+ const scene = await askOrUse(cliOpts.scene, currencySubChoice[0]);
3117
+ if (scene === "customColumnModule") {
3118
+ return customColumnModule(lintc, fix, scene, parentRule, cliOpts);
3119
+ }
3120
+ lint$1(lintc, fix, scene);
3121
+ }
3122
+ }
3123
+
3124
+ // 默认翻译分支(支持 --appid/--key/--merge/--cache 一键执行)
3125
+ async function runTranslate() {
3126
+ // 前置守卫: 必须传入 -i 路径
3127
+ if (!input || !String(input).trim()) {
3128
+ console.log(chalk.red("请传入要翻译的路径 -i <path>"));
3129
+ process.exit(1);
3130
+ }
3131
+ const { cache, appid, key, merge } = cliOpts;
3132
+ const oneClick = Boolean(cache || appid || key || merge);
2500
3133
  let projectName = "请在package.json中配置name字段(项目名称)";
2501
3134
  const projectPath = "./package.json";
2502
3135
  const isExist = isExistPath(projectPath);
@@ -2504,91 +3137,77 @@ if (sass) {
2504
3137
  const str = fs.readFileSync(projectPath, "utf-8").toString();
2505
3138
  projectName = JSON.parse(str).name;
2506
3139
  }
2507
- inquirer
2508
- .prompt([
3140
+ if (!oneClick) {
3141
+ const { isProjectName } = await inquirer.prompt([
2509
3142
  {
2510
3143
  message: `请确认package.json中项目名称是否是${projectName}`,
2511
3144
  name: "isProjectName",
2512
3145
  type: "confirm",
2513
3146
  default: true,
2514
3147
  },
2515
- ])
2516
- .then((res) => {
2517
- if (res.isProjectName) {
2518
- inquirer
2519
- .prompt([
2520
- {
2521
- message: "请选择翻译接口",
2522
- name: "type",
2523
- type: "list",
2524
- choices: [
2525
- {
2526
- name: "百度翻译",
2527
- value: "baidu",
2528
- },
2529
- ],
2530
- },
2531
- ])
2532
- .then((res) => {
2533
- const { type } = res;
2534
- let cacheSecret = {};
2535
- if (!isExistPath(getRunCliPath({ root: true }) + "/.cache")) {
2536
- fs.mkdirSync(getRunCliPath({ root: true }) + "/.cache");
2537
- }
2538
- const cacheFilePath =
2539
- getRunCliPath({ root: true }) + "/.cache/" + res.type;
2540
- if (isExistPath(cacheFilePath)) {
2541
- const cache = JSON.parse(readCache(res.type));
2542
- cacheSecret = cache;
2543
- }
2544
- const question = [
2545
- {
2546
- message: "请输入百度翻译的appid",
2547
- name: "appid",
2548
- default: cacheSecret.appid,
2549
- // 必填
2550
- validate: function (val) {
2551
- if (val) {
2552
- return true;
2553
- }
2554
- return "请输入百度翻译的appid";
2555
- },
2556
- },
2557
- {
2558
- message: "请输入百度翻译的key",
2559
- name: "key",
2560
- default: cacheSecret.key,
2561
- // 必填
2562
- validate: function (val) {
2563
- if (val) {
2564
- return true;
2565
- }
2566
- return "请输入百度翻译的key";
2567
- },
2568
- },
2569
- ];
2570
- inquirer.prompt(question).then((res) => {
2571
- writeCache(
2572
- type,
2573
- JSON.stringify({
2574
- type,
2575
- ...res,
2576
- })
2577
- );
2578
- const { appid, key } = res;
2579
- const isLintFix = fix;
2580
- translateUtil(appid, key, projectName, isLintFix);
2581
- });
2582
- });
2583
- } else {
2584
- console.log(
2585
- chalk.red("请在package.json中配置正确的name字段(项目名称)")
2586
- );
2587
- }
2588
- });
3148
+ ]);
3149
+ if (!isProjectName) {
3150
+ console.log(
3151
+ chalk.red("请在package.json中配置正确的name字段(项目名称)")
3152
+ );
3153
+ process.exit(1);
3154
+ }
3155
+ }
3156
+ // 翻译接口(当前仅百度, 一键执行时自动选择)
3157
+ let type = "baidu";
3158
+ if (!oneClick) {
3159
+ const res = await inquirer.prompt([
3160
+ {
3161
+ message: "请选择翻译接口",
3162
+ name: "type",
3163
+ type: "list",
3164
+ choices: [
3165
+ {
3166
+ name: "百度翻译",
3167
+ value: "baidu",
3168
+ },
3169
+ ],
3170
+ },
3171
+ ]);
3172
+ type = res.type;
3173
+ }
3174
+ // 凭据: CLI参数 > --cache缓存 > 交互
3175
+ const secret = await resolveSecretQuestions({
3176
+ questions: [
3177
+ {
3178
+ message: "请输入百度翻译的appid",
3179
+ name: "appid",
3180
+ // 必填
3181
+ validate: function (val) {
3182
+ if (val) {
3183
+ return true;
3184
+ }
3185
+ return "请输入百度翻译的appid";
3186
+ },
3187
+ },
3188
+ {
3189
+ message: "请输入百度翻译的key",
3190
+ name: "key",
3191
+ // 必填
3192
+ validate: function (val) {
3193
+ if (val) {
3194
+ return true;
3195
+ }
3196
+ return "请输入百度翻译的key";
3197
+ },
3198
+ },
3199
+ ],
3200
+ cliValues: { appid, key },
3201
+ useCache: Boolean(cache),
3202
+ cacheKey: type,
3203
+ extraCache: { type },
3204
+ });
3205
+ // isMerge预决定: --merge为true, 一键执行默认false(覆盖), 否则翻译完成后交互询问
3206
+ const isMergeDecision = merge ? true : oneClick ? false : null;
3207
+ translateUtil(secret.appid, secret.key, projectName, fix, isMergeDecision);
2589
3208
  }
2590
3209
 
2591
- function translateUtil(appid, key, projectName, isLintFix) {
3210
+ function translateUtil(appid, key, projectName, isLintFix, isMergeDecision) {
2592
3211
  const { input } = commander.program.opts();
2593
3212
  // 支持逗号分隔的多个路径(来自 -lint git 的多文件场景)
2594
3213
  const inputPaths = String(input)
@@ -2736,14 +3355,19 @@ function translateUtil(appid, key, projectName, isLintFix) {
2736
3355
  });
2737
3356
  exportData.push(rowArr);
2738
3357
  });
2739
- const { isMerge } = await inquirer.prompt([
2740
- {
2741
- message: `是否合并至export.xlsx,默认${chalk.red("否,直接覆盖")}`,
2742
- name: "isMerge",
2743
- type: "confirm",
2744
- default: false,
2745
- },
2746
- ]);
3358
+ // isMergeDecision: true/false为预决定值(一键执行), null表示仍需交互询问
3359
+ let isMerge = isMergeDecision;
3360
+ if (isMerge === null || isMerge === undefined) {
3361
+ const res = await inquirer.prompt([
3362
+ {
3363
+ message: `是否合并至export.xlsx,默认${chalk.red("否,直接覆盖")}`,
3364
+ name: "isMerge",
3365
+ type: "confirm",
3366
+ default: false,
3367
+ },
3368
+ ]);
3369
+ isMerge = res.isMerge;
3370
+ }
2747
3371
  try {
2748
3372
  if (isMerge) {
2749
3373
  const str = xlsx.parse(fs.readFileSync("./export.xlsx"));