ai-yuca 1.5.1 → 1.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/cli.js +22 -2
- package/dist/package.json +2 -1
- package/dist/src/gKeys.d.ts +2 -0
- package/dist/src/gKeys.js +235 -0
- package/dist/src/svgFix.d.ts +2 -0
- package/dist/src/svgFix.js +120 -0
- package/dist/src/tinify.js +49 -5
- package/dist/src/transKey.js +11 -43
- package/dist/src/types/gKeys.d.ts +6 -0
- package/dist/src/types/gKeys.js +2 -0
- package/dist/src/types/index.d.ts +2 -0
- package/dist/src/types/index.js +2 -0
- package/dist/src/types/svgFix.d.ts +3 -0
- package/dist/src/types/svgFix.js +2 -0
- package/package.json +2 -1
package/dist/bin/cli.js
CHANGED
|
@@ -54,6 +54,8 @@ const tinify_1 = require("../src/tinify");
|
|
|
54
54
|
const zipAnim_1 = require("../src/zipAnim");
|
|
55
55
|
const pullCrowdin_1 = require("../src/pullCrowdin");
|
|
56
56
|
const sharp_1 = require("../src/sharp");
|
|
57
|
+
const svgFix_1 = require("../src/svgFix");
|
|
58
|
+
const gKeys_1 = require("../src/gKeys");
|
|
57
59
|
const program = new commander_1.Command();
|
|
58
60
|
// 设置版本和描述
|
|
59
61
|
program
|
|
@@ -79,12 +81,30 @@ program
|
|
|
79
81
|
.action(async (options) => {
|
|
80
82
|
await (0, sharp_1.compressImages)(options);
|
|
81
83
|
});
|
|
84
|
+
// 添加 svg-fix 命令
|
|
85
|
+
program
|
|
86
|
+
.command('svg-fix')
|
|
87
|
+
.description('修复SVG路径并去色 (生成到 fixed 子目录)')
|
|
88
|
+
.option('-s, --source <path>', 'SVG文件目录路径 (默认为当前目录)', '.')
|
|
89
|
+
.action(async (options) => {
|
|
90
|
+
await (0, svgFix_1.svgFix)(options);
|
|
91
|
+
});
|
|
92
|
+
// 添加 gkeys 命令
|
|
93
|
+
program
|
|
94
|
+
.command('gkeys')
|
|
95
|
+
.description('提取 pages 目录下的翻译 key 并生成 gkeys.json')
|
|
96
|
+
.requiredOption('-s, --source <path>', 'pages 文件夹路径')
|
|
97
|
+
.option('-r, --regex <regex>', '提取 Key 的正则表达式 (默认为 {#(.+?)#})')
|
|
98
|
+
.option('-o, --output <path>', '输出文件路径 (默认为 source/gkeys.json)')
|
|
99
|
+
.action(async (options) => {
|
|
100
|
+
await (0, gKeys_1.gKeys)(options);
|
|
101
|
+
});
|
|
82
102
|
// 添加 tinify 命令
|
|
83
103
|
program
|
|
84
104
|
.command('tinify')
|
|
85
105
|
.description('使用 Tinify API 压缩图片 (jpg, png, webp)')
|
|
86
|
-
.
|
|
87
|
-
.
|
|
106
|
+
.option('-s, --source <path>', '资源文件夹 (默认为当前目录)', '.')
|
|
107
|
+
.option('-o, --output <path>', '导出的文件夹地址 (默认为 source/zip)', '')
|
|
88
108
|
.option('-k, --key <string>', 'Tinify API Key (如果不提供则尝试从配置文件读取)')
|
|
89
109
|
.option('-c, --config <path>', '指定配置文件路径(默认为项目根目录下的vs.config.json)')
|
|
90
110
|
.action(async (options) => {
|
package/dist/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-yuca",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.2",
|
|
4
4
|
"description": "一个用AI生成的开发辅助工具",
|
|
5
5
|
"main": "dist/src/index.js",
|
|
6
6
|
"types": "dist/src/index.d.ts",
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
"md5-file": "^5.0.0",
|
|
42
42
|
"mime-types": "^3.0.1",
|
|
43
43
|
"sharp": "^0.34.5",
|
|
44
|
+
"svgo": "^4.0.1",
|
|
44
45
|
"tinify": "^1.8.2"
|
|
45
46
|
},
|
|
46
47
|
"devDependencies": {
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.gKeys = gKeys;
|
|
40
|
+
const fs = __importStar(require("fs"));
|
|
41
|
+
const path = __importStar(require("path"));
|
|
42
|
+
const fast_glob_1 = __importDefault(require("fast-glob"));
|
|
43
|
+
async function gKeys(options) {
|
|
44
|
+
const { source, regex, config: configPath } = options;
|
|
45
|
+
if (!source) {
|
|
46
|
+
console.error('❌ 请提供源文件夹路径 (-s)');
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const sourceAbs = path.resolve(process.cwd(), source);
|
|
50
|
+
if (!fs.existsSync(sourceAbs)) {
|
|
51
|
+
console.error(`❌ 源路径不存在: ${sourceAbs}`);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
// 读取配置文件
|
|
55
|
+
const configFile = configPath || path.join(process.cwd(), 'vs.config.json');
|
|
56
|
+
if (!fs.existsSync(configFile)) {
|
|
57
|
+
console.error(`❌ 配置文件不存在: ${configFile}`);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
let langFilePath = '';
|
|
61
|
+
let keysDirAbs = '';
|
|
62
|
+
try {
|
|
63
|
+
const configData = JSON.parse(fs.readFileSync(configFile, 'utf8'));
|
|
64
|
+
if (configData.crowdin && configData.crowdin.keysDir) {
|
|
65
|
+
keysDirAbs = path.resolve(process.cwd(), configData.crowdin.keysDir);
|
|
66
|
+
langFilePath = path.join(keysDirAbs, 'lang.ts');
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
console.warn('⚠️ 配置文件中未找到 crowdin.keysDir,将跳过生成 lang.ts');
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
catch (e) {
|
|
73
|
+
console.error(`❌ 读取配置文件失败: ${e}`);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
// 默认正则匹配 {#...#}
|
|
77
|
+
const regexPattern = regex ? new RegExp(regex, 'g') : /\{#(.+?)#\}/g;
|
|
78
|
+
console.log(`🚀 开始扫描 Pages: ${sourceAbs}`);
|
|
79
|
+
console.log(`🔍 正则表达式: ${regexPattern}`);
|
|
80
|
+
// 1. 找到所有 .tsx 入口文件
|
|
81
|
+
const entryFiles = await (0, fast_glob_1.default)('**/*.tsx', {
|
|
82
|
+
cwd: sourceAbs,
|
|
83
|
+
absolute: true
|
|
84
|
+
});
|
|
85
|
+
if (entryFiles.length === 0) {
|
|
86
|
+
console.log('⚠️ 未找到 .tsx 入口文件');
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
console.log(`📄 找到 ${entryFiles.length} 个页面入口文件`);
|
|
90
|
+
// 用于收集所有的 pageKey 定义,以便生成 lang.ts
|
|
91
|
+
const pageKeyDefinitions = {};
|
|
92
|
+
let processedCount = 0;
|
|
93
|
+
let skippedCount = 0;
|
|
94
|
+
for (const entryFile of entryFiles) {
|
|
95
|
+
try {
|
|
96
|
+
// 生成基于文件路径的驼峰命名 Key
|
|
97
|
+
const relativePath = path.relative(sourceAbs, entryFile);
|
|
98
|
+
const pathWithoutExt = relativePath.replace(/\.[^/.]+$/, "");
|
|
99
|
+
const camelCaseKey = pathWithoutExt.split(path.sep).map(part => {
|
|
100
|
+
const parts = part.split(/[-_]/);
|
|
101
|
+
const camelParts = parts.map(p => {
|
|
102
|
+
if (!p)
|
|
103
|
+
return '';
|
|
104
|
+
if (p.startsWith('['))
|
|
105
|
+
return p;
|
|
106
|
+
return p.charAt(0).toUpperCase() + p.slice(1);
|
|
107
|
+
});
|
|
108
|
+
return camelParts.join('').replace(/[\[\]]/g, '');
|
|
109
|
+
}).join('');
|
|
110
|
+
const keys = new Set();
|
|
111
|
+
const processedFiles = new Set();
|
|
112
|
+
// 递归处理文件,传入 keysDirAbs 用于忽略
|
|
113
|
+
await processFile(entryFile, regexPattern, keys, processedFiles, sourceAbs, keysDirAbs);
|
|
114
|
+
if (keys.size > 0) {
|
|
115
|
+
const sortedKeys = Array.from(keys).sort();
|
|
116
|
+
// 收集结果
|
|
117
|
+
pageKeyDefinitions[camelCaseKey] = sortedKeys;
|
|
118
|
+
console.log(` ✅ 提取到 ${keys.size} 个 Key`);
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
console.log(` ⚠️ 未提取到 Key`);
|
|
122
|
+
pageKeyDefinitions[camelCaseKey] = [];
|
|
123
|
+
}
|
|
124
|
+
processedCount++;
|
|
125
|
+
}
|
|
126
|
+
catch (err) {
|
|
127
|
+
console.error(`❌ 处理文件出错: ${entryFile}`, err);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
console.log(`\n----------------------------------------`);
|
|
131
|
+
console.log(`处理完成: ${processedCount} 个, 跳过: ${skippedCount} 个 (未找到 PAGE_KEY)`);
|
|
132
|
+
// 生成 lang.ts
|
|
133
|
+
if (langFilePath && Object.keys(pageKeyDefinitions).length > 0) {
|
|
134
|
+
generateLangFile(langFilePath, pageKeyDefinitions);
|
|
135
|
+
}
|
|
136
|
+
else if (Object.keys(pageKeyDefinitions).length === 0) {
|
|
137
|
+
console.log(`⚠️ 没有提取到任何 Key,跳过生成 lang.ts`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
function generateLangFile(filePath, data) {
|
|
141
|
+
let content = '// Auto generated by ai-yuca gkeys\n\n';
|
|
142
|
+
for (const [key, keys] of Object.entries(data)) {
|
|
143
|
+
content += `export const ${key}Keys = ${JSON.stringify(keys)};\n`;
|
|
144
|
+
}
|
|
145
|
+
// 确保目录存在
|
|
146
|
+
const dir = path.dirname(filePath);
|
|
147
|
+
if (!fs.existsSync(dir)) {
|
|
148
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
149
|
+
}
|
|
150
|
+
fs.writeFileSync(filePath, content, 'utf8');
|
|
151
|
+
console.log(`\n🎉 已生成全局语言文件: ${filePath}`);
|
|
152
|
+
}
|
|
153
|
+
async function processFile(filePath, regex, keys, processedFiles, rootContext, ignoreDir // 需要忽略的目录路径 (config.crowdin.keysDir)
|
|
154
|
+
) {
|
|
155
|
+
// 忽略 keysDir 目录下的文件,防止循环引用
|
|
156
|
+
if (ignoreDir && path.resolve(filePath).startsWith(ignoreDir)) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
if (processedFiles.has(filePath))
|
|
160
|
+
return;
|
|
161
|
+
processedFiles.add(filePath);
|
|
162
|
+
if (!fs.existsSync(filePath))
|
|
163
|
+
return;
|
|
164
|
+
let content = fs.readFileSync(filePath, 'utf8');
|
|
165
|
+
// 不再需要剔除 PAGE_KEY 定义,因为我们不再往回写,而且正则提取的是 {#...#},
|
|
166
|
+
// 只要 lang.ts 里的定义不包含 {#...#} 且我们忽略了 lang.ts 文件,就不会有问题。
|
|
167
|
+
// 1. 提取当前文件中的 Key
|
|
168
|
+
let match;
|
|
169
|
+
regex.lastIndex = 0;
|
|
170
|
+
while ((match = regex.exec(content)) !== null) {
|
|
171
|
+
if (match[0]) {
|
|
172
|
+
keys.add(match[0]);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
// 2. 分析 import 引用,递归处理
|
|
176
|
+
const importRegex = /import\s+(?:(?:[\w{}\s,*]+)\s+from\s+)?['"]([^'"]+)['"]/g;
|
|
177
|
+
let importMatch;
|
|
178
|
+
while ((importMatch = importRegex.exec(content)) !== null) {
|
|
179
|
+
let importPath = importMatch[1];
|
|
180
|
+
let targetPath = '';
|
|
181
|
+
// 处理 @ 别名
|
|
182
|
+
if (importPath.startsWith('@/')) {
|
|
183
|
+
// 假设 @ 指向 src 目录
|
|
184
|
+
// 我们需要找到项目根目录下的 src
|
|
185
|
+
// rootContext 是 sourceAbs,通常是 pages 目录
|
|
186
|
+
// 假设项目结构是 root/src/pages
|
|
187
|
+
// 所以 rootContext 往上两级可能是 root?或者 rootContext 本身就在 src 下?
|
|
188
|
+
// 更稳妥的方式是查找 tsconfig.json 或 jsconfig.json 中的 paths 配置
|
|
189
|
+
// 但为了简单,我们假设 @ -> process.cwd() + /src
|
|
190
|
+
targetPath = path.join(process.cwd(), 'src', importPath.substring(2));
|
|
191
|
+
}
|
|
192
|
+
else if (importPath.startsWith('.')) {
|
|
193
|
+
// 解析相对路径
|
|
194
|
+
const currentDir = path.dirname(filePath);
|
|
195
|
+
targetPath = path.resolve(currentDir, importPath);
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
// 忽略非相对路径 (node_modules)
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
// 忽略样式/图片文件
|
|
202
|
+
if (importPath.match(/\.(css|less|scss|sass|png|jpg|jpeg|gif|svg)$/))
|
|
203
|
+
continue;
|
|
204
|
+
// 尝试添加扩展名
|
|
205
|
+
const extensions = ['.tsx', '.ts', '.jsx', '.js'];
|
|
206
|
+
let resolvedPath = '';
|
|
207
|
+
// 1. 尝试直接加扩展名
|
|
208
|
+
for (const ext of extensions) {
|
|
209
|
+
if (fs.existsSync(targetPath + ext)) {
|
|
210
|
+
resolvedPath = targetPath + ext;
|
|
211
|
+
break;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
// 2. 尝试作为目录找 index
|
|
215
|
+
if (!resolvedPath) {
|
|
216
|
+
for (const ext of extensions) {
|
|
217
|
+
const indexPath = path.join(targetPath, `index${ext}`);
|
|
218
|
+
if (fs.existsSync(indexPath)) {
|
|
219
|
+
resolvedPath = indexPath;
|
|
220
|
+
break;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
// 3. 如果原本就有扩展名
|
|
225
|
+
if (!resolvedPath && fs.existsSync(targetPath)) {
|
|
226
|
+
const stat = fs.statSync(targetPath);
|
|
227
|
+
if (stat.isFile()) {
|
|
228
|
+
resolvedPath = targetPath;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
if (resolvedPath) {
|
|
232
|
+
await processFile(resolvedPath, regex, keys, processedFiles, rootContext, ignoreDir);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.svgFix = svgFix;
|
|
40
|
+
const fs = __importStar(require("fs"));
|
|
41
|
+
const path = __importStar(require("path"));
|
|
42
|
+
const fast_glob_1 = __importDefault(require("fast-glob"));
|
|
43
|
+
const svgo_1 = require("svgo");
|
|
44
|
+
async function svgFix(options) {
|
|
45
|
+
const sourcePath = path.resolve(process.cwd(), options.source || '.');
|
|
46
|
+
const destinationPath = path.join(sourcePath, 'fixed');
|
|
47
|
+
if (!fs.existsSync(sourcePath)) {
|
|
48
|
+
console.error(`❌ 源路径不存在: ${sourcePath}`);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
console.log(`🚀 开始处理 SVG 文件...`);
|
|
52
|
+
console.log(`📂 源目录: ${sourcePath}`);
|
|
53
|
+
console.log(`📂 输出目录: ${destinationPath}`);
|
|
54
|
+
try {
|
|
55
|
+
// 1. 查找所有SVG文件
|
|
56
|
+
const svgFiles = await (0, fast_glob_1.default)('**/*.svg', {
|
|
57
|
+
cwd: sourcePath,
|
|
58
|
+
absolute: true,
|
|
59
|
+
ignore: ['**/fixed/**'] // 避免重复处理输出目录
|
|
60
|
+
});
|
|
61
|
+
if (svgFiles.length === 0) {
|
|
62
|
+
console.log('⚠️ 未找到 SVG 文件');
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
console.log(`🔄 找到 ${svgFiles.length} 个 SVG 文件,开始处理...`);
|
|
66
|
+
let processedCount = 0;
|
|
67
|
+
let failedCount = 0;
|
|
68
|
+
for (const file of svgFiles) {
|
|
69
|
+
try {
|
|
70
|
+
const content = fs.readFileSync(file, 'utf8');
|
|
71
|
+
// 计算输出路径,保持原有目录结构
|
|
72
|
+
const relativePath = path.relative(sourcePath, file);
|
|
73
|
+
const outputPath = path.join(destinationPath, relativePath);
|
|
74
|
+
const outputDir = path.dirname(outputPath);
|
|
75
|
+
// 确保子目录存在
|
|
76
|
+
if (!fs.existsSync(outputDir)) {
|
|
77
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
78
|
+
}
|
|
79
|
+
// 使用 SVGO 进行优化和去色
|
|
80
|
+
const result = (0, svgo_1.optimize)(content, {
|
|
81
|
+
path: file,
|
|
82
|
+
multipass: true, // 多次遍历以获得更好效果
|
|
83
|
+
plugins: [
|
|
84
|
+
'preset-default', // 包含 mergePaths, convertStyleToAttrs 等默认优化
|
|
85
|
+
'removeDimensions', // 移除 width/height,方便 CSS 控制
|
|
86
|
+
{
|
|
87
|
+
name: 'removeAttrs',
|
|
88
|
+
params: {
|
|
89
|
+
attrs: '(fill|stroke|style)' // 移除颜色相关属性
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
name: 'addAttributesToSVGElement',
|
|
94
|
+
params: {
|
|
95
|
+
attributes: [
|
|
96
|
+
{ fill: 'currentColor' } // 设置默认填充色为当前字体颜色
|
|
97
|
+
]
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
]
|
|
101
|
+
});
|
|
102
|
+
fs.writeFileSync(outputPath, result.data, 'utf8');
|
|
103
|
+
processedCount++;
|
|
104
|
+
}
|
|
105
|
+
catch (err) {
|
|
106
|
+
console.error(`✗ 处理出错: ${path.relative(sourcePath, file)}`, err);
|
|
107
|
+
failedCount++;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
console.log(`\n✅ 处理完成: ${processedCount} 个文件`);
|
|
111
|
+
if (failedCount > 0) {
|
|
112
|
+
console.log(`❌ 失败: ${failedCount} 个文件`);
|
|
113
|
+
}
|
|
114
|
+
console.log(`🎉 输出目录: ${destinationPath}`);
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
console.error(`❌ 执行失败: ${err instanceof Error ? err.message : String(err)}`);
|
|
118
|
+
process.exit(1);
|
|
119
|
+
}
|
|
120
|
+
}
|
package/dist/src/tinify.js
CHANGED
|
@@ -40,13 +40,17 @@ exports.compressImagesWithTinify = compressImagesWithTinify;
|
|
|
40
40
|
const tinify_1 = __importDefault(require("tinify"));
|
|
41
41
|
const fs = __importStar(require("fs"));
|
|
42
42
|
const path = __importStar(require("path"));
|
|
43
|
+
const os = __importStar(require("os"));
|
|
43
44
|
const fast_glob_1 = __importDefault(require("fast-glob"));
|
|
44
45
|
const chalk_1 = __importDefault(require("chalk"));
|
|
45
46
|
async function compressImagesWithTinify(options) {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
let { source, output, config, key } = options;
|
|
48
|
+
// 处理默认参数
|
|
49
|
+
if (!source) {
|
|
50
|
+
source = '.';
|
|
51
|
+
}
|
|
52
|
+
if (!output) {
|
|
53
|
+
output = path.join(source, 'zip');
|
|
50
54
|
}
|
|
51
55
|
let apiKey = key;
|
|
52
56
|
// 如果命令行没有提供key,尝试从配置文件读取
|
|
@@ -73,6 +77,42 @@ async function compressImagesWithTinify(options) {
|
|
|
73
77
|
console.error(chalk_1.default.red(`错误: 配置文件不存在: ${configPath}`));
|
|
74
78
|
process.exit(1);
|
|
75
79
|
}
|
|
80
|
+
// 如果还没找到,尝试从用户主目录下的 .yuca/.env 获取
|
|
81
|
+
if (!apiKey) {
|
|
82
|
+
try {
|
|
83
|
+
const homeDir = os.homedir();
|
|
84
|
+
const envPath = path.join(homeDir, '.yuca', '.env');
|
|
85
|
+
if (fs.existsSync(envPath)) {
|
|
86
|
+
// console.log(chalk.gray(`尝试读取 ${envPath}...`));
|
|
87
|
+
const envContent = fs.readFileSync(envPath, 'utf8');
|
|
88
|
+
const lines = envContent.split('\n');
|
|
89
|
+
for (const line of lines) {
|
|
90
|
+
const trimmedLine = line.trim();
|
|
91
|
+
// 跳过注释和空行
|
|
92
|
+
if (!trimmedLine || trimmedLine.startsWith('#'))
|
|
93
|
+
continue;
|
|
94
|
+
// 解析 KEY=VALUE
|
|
95
|
+
const parts = trimmedLine.split('=');
|
|
96
|
+
if (parts.length >= 2) {
|
|
97
|
+
const key = parts[0].trim();
|
|
98
|
+
// 获取值部分(可能包含=号,所以重新组合)
|
|
99
|
+
const value = parts.slice(1).join('=').trim();
|
|
100
|
+
// 移除可能的引号
|
|
101
|
+
const cleanValue = value.replace(/^['"]|['"]$/g, '');
|
|
102
|
+
// 检查可能的键名: tinify, TINIFY_KEY, TINIFY_API_KEY, apiKeys.tinify
|
|
103
|
+
if (['tinify', 'TINIFY_KEY', 'TINIFY_API_KEY', 'apiKeys.tinify'].includes(key)) {
|
|
104
|
+
apiKey = cleanValue;
|
|
105
|
+
// console.log(chalk.green(`成功从 ${envPath} 获取到 API Key`));
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
catch (e) {
|
|
113
|
+
console.warn(chalk_1.default.yellow(`警告: 读取用户环境配置失败: ${e}`));
|
|
114
|
+
}
|
|
115
|
+
}
|
|
76
116
|
}
|
|
77
117
|
if (!apiKey) {
|
|
78
118
|
console.error(chalk_1.default.red('错误: 未提供 API Key。请通过 -k 参数或配置文件 (apiKeys.tinify) 提供。'));
|
|
@@ -93,8 +133,12 @@ async function compressImagesWithTinify(options) {
|
|
|
93
133
|
console.log(chalk_1.default.blue(`源目录: ${sourceAbs}`));
|
|
94
134
|
console.log(chalk_1.default.blue(`导出目录: ${outputAbs}`));
|
|
95
135
|
// 查找图片文件
|
|
136
|
+
// 排除输出目录,防止循环压缩
|
|
96
137
|
const patterns = ['**/*.{jpg,jpeg,png,webp}'];
|
|
97
|
-
const files = await (0, fast_glob_1.default)(patterns, {
|
|
138
|
+
const files = await (0, fast_glob_1.default)(patterns, {
|
|
139
|
+
cwd: sourceAbs,
|
|
140
|
+
ignore: [path.relative(sourceAbs, outputAbs) + '/**']
|
|
141
|
+
});
|
|
98
142
|
if (files.length === 0) {
|
|
99
143
|
console.log(chalk_1.default.yellow('未找到任何图片文件。'));
|
|
100
144
|
return;
|
package/dist/src/transKey.js
CHANGED
|
@@ -171,55 +171,23 @@ async function transKey(options) {
|
|
|
171
171
|
console.log(`✅ 生成 ${fileName}: 总 Key 数 ${sortedKeys.length}, 已翻译 ${matchedCount}`);
|
|
172
172
|
}
|
|
173
173
|
// 5. 生成未翻译词条文件 (un-trans.json)
|
|
174
|
-
// 根据需求 "un-trans.json 存放在 source 中"
|
|
175
174
|
const untransPath = path.join(outputDir, 'un-trans.json');
|
|
176
|
-
let
|
|
177
|
-
//
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
catch (e) {
|
|
183
|
-
console.warn(`无法解析 un-trans.json: ${e}`);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
// 合并新旧未翻译数据(保留旧数据)
|
|
187
|
-
// 逻辑:以新的 untranslatedKeys 为主,如果旧数据里有该语言该key且有值(虽然理论上未翻译文件里的值应该是空的,但也许用户手动填了?),
|
|
188
|
-
// 还是保留空值,因为既然在 source 里没值,说明还是未翻译。
|
|
189
|
-
// 但用户说 "每次执行这个操作前这个文件的存量数据",意思是把新生成的未翻译数据和旧的合并?
|
|
190
|
-
// 通常 un-trans.json 只是一个报告文件。
|
|
191
|
-
// 如果用户想保留 un-trans.json 里的数据,可能是想看累计的未翻译?
|
|
192
|
-
// 但如果某个 key 在本次运行中已经翻译了(在 oldData 中有值),它就不应该出现在 un-trans.json 中了。
|
|
193
|
-
// 所以,un-trans.json 应该只包含当前真正未翻译的 key。
|
|
194
|
-
// "存量数据" 可能指的是:如果 un-trans.json 里有一些 key 是本次扫描没扫到的(比如代码删了),是否要保留?
|
|
195
|
-
// 或者,用户只是想把新发现的未翻译 key 合并进去?
|
|
196
|
-
// 根据需求 "每次执行这个操作前这个文件的存量数据" -> 可能是指读取它,然后做某种合并。
|
|
197
|
-
// 但最合理的逻辑是:un-trans.json 应该反映当前的未翻译状态。
|
|
198
|
-
// 如果 key 在代码中存在且未翻译 -> 写入 un-trans.json
|
|
199
|
-
// 如果 key 在代码中不存在 -> 不写入
|
|
200
|
-
// 如果 key 已翻译 -> 不写入
|
|
201
|
-
// 但既然提到了 "存量数据",我将采用以下策略:
|
|
202
|
-
// 1. 读取旧 un-trans.json
|
|
203
|
-
// 2. 将本次检测到的未翻译 key 合并进去 (如果旧文件里有额外的内容,保留之?不,这会导致废弃 key 堆积)
|
|
204
|
-
// 让我们假设 "存量数据" 是指保留那些在本次扫描中依然未翻译的条目,或者仅仅是做一次全量覆盖?
|
|
205
|
-
// 如果是全量覆盖,那就不需要读取存量数据了。
|
|
206
|
-
// 既然明确要求 "每次执行这个操作前这个文件的存量数据",那我就把旧数据读出来,
|
|
207
|
-
// 然后把本次的未翻译数据合并进去(覆盖旧的)。
|
|
208
|
-
// 修正:按照 "un-trans.json" 的通常用途,它应该是“当前未翻译词条的快照”。
|
|
209
|
-
// 但为了满足 "保留存量数据" 的字面意思,我会把旧数据 merge 进去。
|
|
210
|
-
// 比如:旧数据有 { "en": { "A": "" } },本次扫描发现 { "en": { "B": "" } }
|
|
211
|
-
// 结果是 { "en": { "A": "", "B": "" } }
|
|
212
|
-
// 风险:如果 "A" 已经在代码中删除了,它还会留在 un-trans.json 中。
|
|
213
|
-
const finalUntransData = { ...existingUntransData };
|
|
175
|
+
let finalUntransData = {};
|
|
176
|
+
// 只需要判断文件是否存在,如果不存在则创建(实际上这里我们每次都会重新生成内容)
|
|
177
|
+
// 如果存在,我们不需要读取它的旧值来做逻辑判断,而是直接覆盖写入最新的未翻译条目
|
|
178
|
+
// 或者,如果用户的意图是保留旧的 un-trans.json 中的某些手动编辑过的值(虽然通常它是空的)?
|
|
179
|
+
// 根据最新指示 "transKey 不需要读取旧值",我们直接忽略读取步骤。
|
|
180
|
+
// 初始化结构
|
|
214
181
|
for (const lang of langMap) {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
}
|
|
218
|
-
// 合并当前未翻译的
|
|
182
|
+
finalUntransData[lang] = {};
|
|
183
|
+
// 写入当前扫描到的未翻译 key
|
|
219
184
|
for (const key in untranslatedKeys[lang]) {
|
|
220
185
|
finalUntransData[lang][key] = "";
|
|
221
186
|
}
|
|
222
187
|
}
|
|
188
|
+
// 如果文件不存在,或者即使存在,我们也覆盖它(因为不需要读取旧值)
|
|
189
|
+
// 但为了稳妥起见,如果文件不存在,我们肯定要创建。
|
|
190
|
+
// fs.writeFileSync 会自动创建文件(如果目录存在,目录在上面已经创建了)
|
|
223
191
|
fs.writeFileSync(untransPath, JSON.stringify(finalUntransData, null, 2), 'utf8');
|
|
224
192
|
console.log(`\n📋 未翻译词条已导出至: ${untransPath}`);
|
|
225
193
|
console.log('\n所有文件生成完毕!');
|
package/dist/src/types/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-yuca",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.2",
|
|
4
4
|
"description": "一个用AI生成的开发辅助工具",
|
|
5
5
|
"main": "dist/src/index.js",
|
|
6
6
|
"types": "dist/src/index.d.ts",
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
"md5-file": "^5.0.0",
|
|
42
42
|
"mime-types": "^3.0.1",
|
|
43
43
|
"sharp": "^0.34.5",
|
|
44
|
+
"svgo": "^4.0.1",
|
|
44
45
|
"tinify": "^1.8.2"
|
|
45
46
|
},
|
|
46
47
|
"devDependencies": {
|