td-web-cli 0.1.34 → 0.1.35
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.
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/modules/i18n/jsonInsert/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/modules/i18n/jsonInsert/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA0HpC;;GAEG;AACH,wBAAsB,UAAU,CAAC,OAAO,EAAE,OAAO,iBAoHhD"}
|
|
@@ -2,90 +2,111 @@ import fs from 'fs';
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { input, select, Separator } from '@inquirer/prompts';
|
|
4
4
|
import { logger, loggerError, normalizeGitBashPath, readJsonFile, writeJsonFile, getJsonFilesInLangDir, validatePathInput, } from '../../../utils/index.js';
|
|
5
|
+
/** 获取目录下的一级子文件夹名称 */
|
|
6
|
+
function getSubDirectories(dirPath) {
|
|
7
|
+
return fs.readdirSync(dirPath).filter((name) => fs.statSync(path.join(dirPath, name)).isDirectory());
|
|
8
|
+
}
|
|
9
|
+
/** 校验 JSON 文件路径输入 */
|
|
10
|
+
function validateJsonFileInput(value) {
|
|
11
|
+
const cleaned = value.trim().replace(/^['"]|['"]$/g, '');
|
|
12
|
+
if (!cleaned)
|
|
13
|
+
return '路径不能为空';
|
|
14
|
+
const normalized = normalizeGitBashPath(cleaned);
|
|
15
|
+
if (!fs.existsSync(normalized))
|
|
16
|
+
return '文件不存在,请输入有效路径';
|
|
17
|
+
if (!fs.statSync(normalized).isFile())
|
|
18
|
+
return '请输入 JSON 文件路径';
|
|
19
|
+
if (!normalized.endsWith('.json'))
|
|
20
|
+
return '请输入 .json 文件路径';
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
5
23
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* @param insertObj 待插入的 JSON 对象
|
|
9
|
-
* @param keys 需要插入的键数组
|
|
10
|
-
* @param langKey 当前语言标识(用于日志)
|
|
11
|
-
* @param strategy 冲突处理策略:直接覆盖或手动选择
|
|
12
|
-
* @returns 返回修改后的源对象(与 baseObj 同一引用)
|
|
24
|
+
* 从 JSON 文件中提取待插入的 key 列表
|
|
25
|
+
* 支持对象(取顶层 key)或字符串数组两种格式
|
|
13
26
|
*/
|
|
14
|
-
|
|
27
|
+
function extractKeysFromJsonFile(filePath) {
|
|
28
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
29
|
+
let parsed;
|
|
30
|
+
try {
|
|
31
|
+
parsed = JSON.parse(content);
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
throw new Error(`JSON 文件解析失败: ${filePath}`);
|
|
35
|
+
}
|
|
36
|
+
if (Array.isArray(parsed)) {
|
|
37
|
+
const keys = parsed
|
|
38
|
+
.filter((item) => typeof item === 'string')
|
|
39
|
+
.map((key) => key.trim())
|
|
40
|
+
.filter((key) => key.length > 0);
|
|
41
|
+
return [...new Set(keys)];
|
|
42
|
+
}
|
|
43
|
+
if (parsed && typeof parsed === 'object') {
|
|
44
|
+
return Object.keys(parsed);
|
|
45
|
+
}
|
|
46
|
+
throw new Error('JSON 文件格式无效,需为对象或字符串数组');
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* 将指定 keys 从插入对象合并到被插入对象
|
|
50
|
+
*/
|
|
51
|
+
async function insertKeysIntoObject(baseObj, insertObj, keys, strategy) {
|
|
15
52
|
for (const key of keys) {
|
|
16
|
-
// 插入文件中不存在该键 → 跳过
|
|
17
53
|
if (!(key in insertObj)) {
|
|
18
54
|
logger.warn(`键 "${key}" 在插入文件中不存在,已跳过`, true);
|
|
19
55
|
continue;
|
|
20
56
|
}
|
|
21
57
|
const insertVal = insertObj[key];
|
|
22
|
-
// 源文件中不存在该键 → 直接新增
|
|
23
58
|
if (!(key in baseObj)) {
|
|
24
59
|
baseObj[key] = insertVal;
|
|
25
60
|
logger.info(`新增键: ${key}`, true);
|
|
26
61
|
continue;
|
|
27
62
|
}
|
|
28
|
-
// 键已存在且值相同 → 跳过
|
|
29
63
|
if (baseObj[key] === insertVal) {
|
|
30
64
|
logger.info(`键 "${key}" 值相同,已跳过`, true);
|
|
31
65
|
continue;
|
|
32
66
|
}
|
|
33
|
-
// 键已存在且值不同 → 根据策略处理
|
|
34
67
|
logger.info(`键 "${key}" 已存在且值不同`, true);
|
|
35
68
|
if (strategy === 'overwrite') {
|
|
36
|
-
|
|
69
|
+
baseObj[key] = insertVal;
|
|
70
|
+
logger.info(`已用插入值覆盖键 "${key}"`, true);
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
const choice = await select({
|
|
74
|
+
message: `请选择要保留的值:`,
|
|
75
|
+
choices: [
|
|
76
|
+
{ name: `被插入文件值: ${baseObj[key]}`, value: 'base' },
|
|
77
|
+
{ name: `插入文件值: ${insertVal}`, value: 'insert' },
|
|
78
|
+
new Separator(),
|
|
79
|
+
],
|
|
80
|
+
default: 'base',
|
|
81
|
+
loop: true,
|
|
82
|
+
});
|
|
83
|
+
if (choice === 'insert') {
|
|
37
84
|
baseObj[key] = insertVal;
|
|
38
85
|
logger.info(`已用插入值覆盖键 "${key}"`, true);
|
|
39
86
|
}
|
|
40
87
|
else {
|
|
41
|
-
|
|
42
|
-
const choice = await select({
|
|
43
|
-
message: `请选择要保留的值:`,
|
|
44
|
-
choices: [
|
|
45
|
-
{ name: `源文件值: ${baseObj[key]}`, value: 'base' },
|
|
46
|
-
{ name: `插入文件值: ${insertVal}`, value: 'insert' },
|
|
47
|
-
new Separator(), // 分割线,方便未来扩展更多功能
|
|
48
|
-
],
|
|
49
|
-
default: 'base',
|
|
50
|
-
loop: true,
|
|
51
|
-
});
|
|
52
|
-
if (choice === 'insert') {
|
|
53
|
-
baseObj[key] = insertVal;
|
|
54
|
-
logger.info(`已用插入值覆盖键 "${key}"`, true);
|
|
55
|
-
}
|
|
56
|
-
else {
|
|
57
|
-
logger.info(`保留源文件值,键 "${key}" 未更改`, true);
|
|
58
|
-
}
|
|
88
|
+
logger.info(`保留被插入文件值,键 "${key}" 未更改`, true);
|
|
59
89
|
}
|
|
60
90
|
}
|
|
61
91
|
return baseObj;
|
|
62
92
|
}
|
|
63
93
|
/**
|
|
64
|
-
* 主函数:从插入目录按指定 keys
|
|
65
|
-
* @param program Commander 实例(保留扩展可能)
|
|
94
|
+
* 主函数:从插入目录按指定 keys 批量插入到被插入目录
|
|
66
95
|
*/
|
|
67
96
|
export async function jsonInsert(program) {
|
|
68
97
|
try {
|
|
69
|
-
// 获取源目录路径
|
|
70
98
|
const srcDir = await input({
|
|
71
|
-
message: '
|
|
99
|
+
message: '请输入被插入 JSON 文件夹路径(含语言子文件夹,如 cn/translate.json):',
|
|
72
100
|
validate: validatePathInput,
|
|
73
101
|
});
|
|
74
|
-
// 获取待插入目录路径
|
|
75
102
|
const insertDir = await input({
|
|
76
103
|
message: '请输入待插入 JSON 文件夹路径(含语言子文件夹,如 cn/translate.json):',
|
|
77
104
|
validate: validatePathInput,
|
|
78
105
|
});
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
validate: (value) => {
|
|
83
|
-
if (!value.trim())
|
|
84
|
-
return '键不能为空';
|
|
85
|
-
return true;
|
|
86
|
-
},
|
|
106
|
+
const keysFileInput = await input({
|
|
107
|
+
message: '请输入 key 来源 JSON 文件路径(从中读取需要插入的 key):',
|
|
108
|
+
validate: validateJsonFileInput,
|
|
87
109
|
});
|
|
88
|
-
// 冲突处理策略选择
|
|
89
110
|
const conflictStrategy = await select({
|
|
90
111
|
message: '当目标键已存在且值不同时,请选择处理方式:',
|
|
91
112
|
choices: [
|
|
@@ -96,69 +117,46 @@ export async function jsonInsert(program) {
|
|
|
96
117
|
default: 'manual',
|
|
97
118
|
loop: true,
|
|
98
119
|
});
|
|
99
|
-
// 路径标准化
|
|
100
120
|
const srcPath = normalizeGitBashPath(srcDir);
|
|
101
121
|
const insertPath = normalizeGitBashPath(insertDir);
|
|
102
|
-
|
|
103
|
-
const keys =
|
|
104
|
-
.split(',')
|
|
105
|
-
.map((k) => k.trim())
|
|
106
|
-
.filter((k) => k.length > 0);
|
|
122
|
+
const keysFilePath = normalizeGitBashPath(keysFileInput);
|
|
123
|
+
const keys = extractKeysFromJsonFile(keysFilePath);
|
|
107
124
|
if (keys.length === 0) {
|
|
108
|
-
logger.info('
|
|
125
|
+
logger.info('key 来源文件中未找到有效 key,操作取消');
|
|
109
126
|
return;
|
|
110
127
|
}
|
|
111
|
-
logger.info(
|
|
128
|
+
logger.info(`被插入目录: ${srcPath}`);
|
|
112
129
|
logger.info(`插入目录: ${insertPath}`);
|
|
130
|
+
logger.info(`key 来源文件: ${keysFilePath}`);
|
|
113
131
|
logger.info(`待插入 key: ${keys.join(', ')}`);
|
|
114
132
|
logger.info(`冲突处理策略: ${conflictStrategy === 'overwrite' ? '直接覆盖' : '手动选择'}`);
|
|
115
|
-
|
|
116
|
-
const srcLangDirs = fs
|
|
117
|
-
.readdirSync(srcPath)
|
|
118
|
-
.filter((f) => fs.statSync(path.join(srcPath, f)).isDirectory());
|
|
119
|
-
const insertLangDirs = fs
|
|
120
|
-
.readdirSync(insertPath)
|
|
121
|
-
.filter((f) => fs.statSync(path.join(insertPath, f)).isDirectory());
|
|
122
|
-
const commonLangDirs = srcLangDirs.filter((lang) => insertLangDirs.includes(lang));
|
|
133
|
+
const commonLangDirs = getSubDirectories(srcPath).filter((lang) => getSubDirectories(insertPath).includes(lang));
|
|
123
134
|
if (commonLangDirs.length === 0) {
|
|
124
135
|
logger.info('没有发现相同语言文件夹,操作取消');
|
|
125
136
|
return;
|
|
126
137
|
}
|
|
127
138
|
logger.info(`发现 ${commonLangDirs.length} 个共同语言文件夹: ${commonLangDirs.join(', ')}`, true);
|
|
128
|
-
// 逐语言文件夹处理
|
|
129
139
|
for (const langKey of commonLangDirs) {
|
|
130
140
|
logger.info(`${'='.repeat(60)}`, true);
|
|
131
141
|
logger.info(`处理语言: ${langKey}`, true);
|
|
132
142
|
const srcLangPath = path.join(srcPath, langKey);
|
|
133
143
|
const insertLangPath = path.join(insertPath, langKey);
|
|
134
|
-
const srcJsonFiles = getJsonFilesInLangDir(srcLangPath);
|
|
135
144
|
const insertJsonFiles = getJsonFilesInLangDir(insertLangPath);
|
|
136
|
-
|
|
137
|
-
const commonJsonFiles = srcJsonFiles.filter((file) => insertJsonFiles.includes(file));
|
|
145
|
+
const commonJsonFiles = getJsonFilesInLangDir(srcLangPath).filter((file) => insertJsonFiles.includes(file));
|
|
138
146
|
if (commonJsonFiles.length === 0) {
|
|
139
147
|
logger.info(`语言【${langKey}】下没有共同的 JSON 文件,跳过`, true);
|
|
140
148
|
continue;
|
|
141
149
|
}
|
|
142
150
|
logger.info(`发现 ${commonJsonFiles.length} 个共同 JSON 文件: ${commonJsonFiles.join(', ')}`, true);
|
|
143
|
-
// 逐文件插入
|
|
144
151
|
for (const jsonFile of commonJsonFiles) {
|
|
145
152
|
logger.info(`处理文件: ${jsonFile}`, true);
|
|
146
153
|
const srcFile = path.join(srcLangPath, jsonFile);
|
|
147
154
|
const insertFile = path.join(insertLangPath, jsonFile);
|
|
148
|
-
if (!fs.existsSync(srcFile) || !fs.existsSync(insertFile)) {
|
|
149
|
-
logger.warn(`文件缺失,跳过: ${jsonFile}`, true);
|
|
150
|
-
continue;
|
|
151
|
-
}
|
|
152
|
-
// 读取 JSON
|
|
153
155
|
const srcJson = readJsonFile(srcFile);
|
|
154
156
|
const insertJson = readJsonFile(insertFile);
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
const updated = await insertKeysIntoObject(srcJson, insertJson, keys, langKey, conflictStrategy);
|
|
159
|
-
const afterCount = Object.keys(updated).length;
|
|
160
|
-
logger.info(`插入后键数: ${afterCount}`, true);
|
|
161
|
-
// 写回源文件
|
|
157
|
+
logger.info(`被插入文件原有键数: ${Object.keys(srcJson).length}`, true);
|
|
158
|
+
const updated = await insertKeysIntoObject(srcJson, insertJson, keys, conflictStrategy);
|
|
159
|
+
logger.info(`插入后键数: ${Object.keys(updated).length}`, true);
|
|
162
160
|
writeJsonFile(srcFile, updated);
|
|
163
161
|
logger.info(`文件 ${jsonFile} 已更新`, true);
|
|
164
162
|
}
|
|
@@ -166,7 +164,7 @@ export async function jsonInsert(program) {
|
|
|
166
164
|
}
|
|
167
165
|
logger.info(`${'='.repeat(60)}`, true);
|
|
168
166
|
logger.info(`所有插入操作完成!`, true);
|
|
169
|
-
logger.info(
|
|
167
|
+
logger.info(`被插入目录已更新: ${srcPath}`, true);
|
|
170
168
|
}
|
|
171
169
|
catch (error) {
|
|
172
170
|
loggerError(error, logger);
|