ai-yuca 1.3.7 → 1.3.9

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 CHANGED
@@ -45,6 +45,8 @@ const download_1 = require("../src/download");
45
45
  const uploadWithConfig_1 = require("../src/uploadWithConfig");
46
46
  const deploy_1 = require("../src/deploy");
47
47
  const pullCrowdin_1 = require("../src/pullCrowdin");
48
+ const transKey_1 = require("../src/transKey");
49
+ const sharp_1 = require("../src/sharp");
48
50
  const program = new commander_1.Command();
49
51
  // 设置版本和描述
50
52
  program
@@ -59,6 +61,17 @@ program
59
61
  .action((options) => {
60
62
  (0, index_1.analyze)(options);
61
63
  });
64
+ // 添加sharp命令
65
+ program
66
+ .command('sharp')
67
+ .description('压缩图片文件 (jpg, png, webp)')
68
+ .requiredOption('-s, --source <path>', '资源文件夹')
69
+ .requiredOption('-o, --output <path>', '导出的文件夹地址')
70
+ .option('-q, --quality <number>', '压缩质量 (1-100)', '80')
71
+ .option('-f, --force', '强制压缩,即使目标文件已存在')
72
+ .action(async (options) => {
73
+ await (0, sharp_1.compressImages)(options);
74
+ });
62
75
  // 添加deploy命令
63
76
  program
64
77
  .command('deploy')
@@ -71,7 +84,7 @@ program
71
84
  .option('--no-recursive', '禁用递归上传目录中的文件')
72
85
  .option('--no-compression', '禁用GZIP压缩')
73
86
  .option('--no-cache', '禁用文件缓存功能')
74
- .option('--cache-file <path>', '指定缓存文件路径(默认为.cdn.cache.json)')
87
+ .option('--cache-file <path>', '指定缓存文件路径(默认为 .cdn.cache.[branch].json)')
75
88
  .option('--show-config', '仅显示配置信息,不执行部署')
76
89
  .option('-f, --force', '强制执行,跳过交互式确认')
77
90
  .action(async (options) => {
@@ -250,7 +263,7 @@ program
250
263
  .option('--no-recursive', '禁用递归上传目录中的文件')
251
264
  .option('--no-compression', '禁用GZIP压缩')
252
265
  .option('--no-cache', '禁用文件缓存功能')
253
- .option('--cache-file <path>', '指定缓存文件路径(默认为.cdn.cache.json)')
266
+ .option('--cache-file <path>', '指定缓存文件路径(默认为 .cdn.cache.[branch].json)')
254
267
  .option('--show-config', '仅显示配置信息,不执行上传')
255
268
  .action(async (options) => {
256
269
  try {
@@ -363,6 +376,14 @@ program
363
376
  process.exit(1);
364
377
  }
365
378
  });
379
+ // 添加 transKey 命令
380
+ program
381
+ .command('transKey')
382
+ .description('提取项目中的翻译key并生成源文件')
383
+ .option('-c, --config <path>', '指定配置文件路径(默认为项目根目录下的vs.config.json)')
384
+ .action(async (options) => {
385
+ await (0, transKey_1.transKey)(options);
386
+ });
366
387
  // 添加pull-crowdin命令
367
388
  program
368
389
  .command('cr-pull')
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-yuca",
3
- "version": "1.3.7",
3
+ "version": "1.3.9",
4
4
  "description": "一个用AI生成的开发辅助工具",
5
5
  "main": "dist/src/index.js",
6
6
  "types": "dist/src/index.d.ts",
@@ -31,13 +31,15 @@
31
31
  },
32
32
  "dependencies": {
33
33
  "@google-cloud/storage": "^7.17.1",
34
+ "@types/sharp": "^0.32.0",
34
35
  "axios": "^1.12.1",
35
36
  "chalk": "^4.1.2",
36
37
  "commander": "^10.0.0",
37
38
  "fast-glob": "^3.3.3",
38
39
  "inquirer": "^12.9.4",
39
40
  "md5-file": "^5.0.0",
40
- "mime-types": "^3.0.1"
41
+ "mime-types": "^3.0.1",
42
+ "sharp": "^0.34.5"
41
43
  },
42
44
  "devDependencies": {
43
45
  "@types/chai": "^5.2.2",
package/dist/src/cache.js CHANGED
@@ -40,12 +40,37 @@ exports.writeLastDeployVersion = writeLastDeployVersion;
40
40
  const fs = __importStar(require("fs"));
41
41
  const path = __importStar(require("path"));
42
42
  const md5File = __importStar(require("md5-file"));
43
+ const child_process_1 = require("child_process");
44
+ /**
45
+ * 获取当前Git分支名称(经过清理,适合作为文件名)
46
+ */
47
+ function getGitBranchName() {
48
+ try {
49
+ const branch = (0, child_process_1.execSync)('git branch --show-current', { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
50
+ if (branch) {
51
+ // 将非法字符替换为下划线
52
+ return branch.replace(/[^a-zA-Z0-9.\-_]/g, '_');
53
+ }
54
+ }
55
+ catch (e) {
56
+ // 忽略错误,可能是非git仓库
57
+ }
58
+ return '';
59
+ }
60
+ /**
61
+ * 获取默认缓存文件路径
62
+ */
63
+ function getDefaultCacheFilePath() {
64
+ const branchName = getGitBranchName();
65
+ const fileName = branchName ? `.cdn.cache.${branchName}.json` : '.cdn.cache.json';
66
+ return fileName;
67
+ }
43
68
  /**
44
69
  * 缓存管理类
45
70
  */
46
71
  class CacheManager {
47
- constructor(cacheFilePath = '.cdn.cache.json') {
48
- this.cacheFilePath = path.resolve(cacheFilePath);
72
+ constructor(cacheFilePath) {
73
+ this.cacheFilePath = path.resolve(cacheFilePath || getDefaultCacheFilePath());
49
74
  this.cache = this.loadCache();
50
75
  // 确保缓存文件存在,如果不存在则创建空文件
51
76
  if (!fs.existsSync(this.cacheFilePath)) {
@@ -155,7 +180,7 @@ function createCacheManager(cacheFilePath) {
155
180
  * 读取最后上传的缓存文件版本号
156
181
  */
157
182
  function readLastUploadCacheFile(cacheFile) {
158
- const cacheFilePath = cacheFile || path.join(process.cwd(), '.cdn.cache.json');
183
+ const cacheFilePath = cacheFile || path.join(process.cwd(), getDefaultCacheFilePath());
159
184
  if (!fs.existsSync(cacheFilePath)) {
160
185
  return null;
161
186
  }
@@ -171,7 +196,7 @@ function readLastUploadCacheFile(cacheFile) {
171
196
  * 写入最后部署的版本号
172
197
  */
173
198
  function writeLastDeployVersion(cdnKey, cacheFile) {
174
- const cacheFilePath = cacheFile || path.join(process.cwd(), '.cdn.cache.json');
199
+ const cacheFilePath = cacheFile || path.join(process.cwd(), getDefaultCacheFilePath());
175
200
  let cache = {};
176
201
  if (fs.existsSync(cacheFilePath)) {
177
202
  try {
@@ -0,0 +1,2 @@
1
+ import { SharpCommandOptions } from './types';
2
+ export declare function compressImages(options: SharpCommandOptions): Promise<void>;
@@ -0,0 +1,134 @@
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.compressImages = compressImages;
40
+ const sharp_1 = __importDefault(require("sharp"));
41
+ const fs = __importStar(require("fs"));
42
+ const path = __importStar(require("path"));
43
+ const fast_glob_1 = __importDefault(require("fast-glob"));
44
+ const chalk_1 = __importDefault(require("chalk"));
45
+ async function compressImages(options) {
46
+ const { source, output, quality, force } = options;
47
+ const qualityNum = quality ? parseInt(quality, 10) : 80;
48
+ if (!source) {
49
+ console.error(chalk_1.default.red('错误: 必须提供源文件夹路径 (-s)'));
50
+ return;
51
+ }
52
+ if (!output) {
53
+ console.error(chalk_1.default.red('错误: 必须提供导出文件夹路径 (-o)'));
54
+ return;
55
+ }
56
+ const sourceAbs = path.resolve(process.cwd(), source);
57
+ const outputAbs = path.resolve(process.cwd(), output);
58
+ if (!fs.existsSync(sourceAbs)) {
59
+ console.error(chalk_1.default.red(`源文件夹不存在: ${sourceAbs}`));
60
+ return;
61
+ }
62
+ if (!fs.existsSync(outputAbs)) {
63
+ try {
64
+ fs.mkdirSync(outputAbs, { recursive: true });
65
+ }
66
+ catch (err) {
67
+ console.error(chalk_1.default.red(`无法创建导出文件夹: ${err}`));
68
+ return;
69
+ }
70
+ }
71
+ console.log(chalk_1.default.blue(`源目录: ${sourceAbs}`));
72
+ console.log(chalk_1.default.blue(`导出目录: ${outputAbs}`));
73
+ console.log(chalk_1.default.blue(`压缩质量: ${qualityNum}`));
74
+ // 支持的图片格式
75
+ const patterns = ['**/*.{jpg,jpeg,png,webp,JPG,JPEG,PNG,WEBP}'];
76
+ try {
77
+ const files = await (0, fast_glob_1.default)(patterns, { cwd: sourceAbs, absolute: false });
78
+ if (files.length === 0) {
79
+ console.log(chalk_1.default.yellow('未找到任何图片文件。'));
80
+ return;
81
+ }
82
+ console.log(chalk_1.default.blue(`找到 ${files.length} 个图片文件,开始压缩...`));
83
+ let successCount = 0;
84
+ let failCount = 0;
85
+ for (const file of files) {
86
+ const sourcePath = path.join(sourceAbs, file);
87
+ const outputPath = path.join(outputAbs, file);
88
+ const outputDir = path.dirname(outputPath);
89
+ if (!fs.existsSync(outputDir)) {
90
+ fs.mkdirSync(outputDir, { recursive: true });
91
+ }
92
+ if (fs.existsSync(outputPath) && !force) {
93
+ console.log(chalk_1.default.yellow(`跳过 (已存在): ${path.basename(sourcePath)}`));
94
+ continue;
95
+ }
96
+ try {
97
+ const ext = path.extname(file).toLowerCase();
98
+ const image = (0, sharp_1.default)(sourcePath);
99
+ // 获取图片元数据
100
+ const metadata = await image.metadata();
101
+ if (ext === '.jpg' || ext === '.jpeg') {
102
+ await image.jpeg({ quality: qualityNum }).toFile(outputPath);
103
+ }
104
+ else if (ext === '.png') {
105
+ // PNG压缩策略:如果质量小于100,尝试使用palette量化以减少体积
106
+ // 注意:sharp默认的png压缩是无损的,除非指定palette或quality
107
+ await image.png({ quality: qualityNum, compressionLevel: 9 }).toFile(outputPath);
108
+ }
109
+ else if (ext === '.webp') {
110
+ await image.webp({ quality: qualityNum }).toFile(outputPath);
111
+ }
112
+ else {
113
+ // 其他格式如果不特别处理,可以尝试直接输出或复制
114
+ // 这里我们假设 fastGlob 只匹配了我们支持的格式,所以应该不会走到这里
115
+ // 但为了健壮性,直接复制
116
+ await fs.promises.copyFile(sourcePath, outputPath);
117
+ }
118
+ const srcStats = fs.statSync(sourcePath);
119
+ const destStats = fs.statSync(outputPath);
120
+ const saved = ((srcStats.size - destStats.size) / srcStats.size * 100).toFixed(2);
121
+ console.log(chalk_1.default.green(`已处理: ${path.basename(sourcePath)} | 原始: ${(srcStats.size / 1024).toFixed(2)}KB -> 压缩: ${(destStats.size / 1024).toFixed(2)}KB | 节省: ${saved}% | 图片尺寸: ${metadata === null || metadata === void 0 ? void 0 : metadata.width} * ${metadata.height}`));
122
+ successCount++;
123
+ }
124
+ catch (error) {
125
+ console.error(chalk_1.default.red(`✗ ${file}: ${error}`));
126
+ failCount++;
127
+ }
128
+ }
129
+ console.log(chalk_1.default.blue(`\n压缩完成! 成功: ${successCount}, 失败: ${failCount}`));
130
+ }
131
+ catch (err) {
132
+ console.error(chalk_1.default.red(`发生错误: ${err}`));
133
+ }
134
+ }
@@ -0,0 +1,5 @@
1
+ interface TransKeyOptions {
2
+ config?: string;
3
+ }
4
+ export declare function transKey(options: TransKeyOptions): Promise<void>;
5
+ export {};
@@ -0,0 +1,149 @@
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.transKey = transKey;
40
+ const fs = __importStar(require("fs"));
41
+ const path = __importStar(require("path"));
42
+ const fast_glob_1 = __importDefault(require("fast-glob"));
43
+ async function transKey(options) {
44
+ try {
45
+ // 1. 读取配置文件
46
+ const configPath = options.config || path.join(process.cwd(), 'vs.config.json');
47
+ if (!fs.existsSync(configPath)) {
48
+ throw new Error(`配置文件不存在: ${configPath}`);
49
+ }
50
+ console.log(`读取配置文件: ${configPath}`);
51
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
52
+ const crowdinConfig = config.crowdin;
53
+ if (!crowdinConfig) {
54
+ throw new Error('配置文件中缺少 crowdin 配置');
55
+ }
56
+ const { workDir, keysDir, reg, langMap } = crowdinConfig;
57
+ if (!workDir || !keysDir || !reg || !langMap) {
58
+ throw new Error('crowdin 配置缺少必要字段: workDir, keysDir, reg, langMap');
59
+ }
60
+ // 2. 扫描文件并提取 key
61
+ console.log(`开始扫描文件,工作目录: ${workDir}, 正则: ${reg}`);
62
+ // 将 workDir 转换为相对于 cwd 的路径,或者如果它是相对路径,就直接用
63
+ const searchPattern = path.join(workDir, '**/*.{ts,tsx,js,jsx,html,vue}');
64
+ // normalized pattern for glob (glob expects forward slashes)
65
+ const normalizedPattern = searchPattern.replace(/\\/g, '/');
66
+ const files = await (0, fast_glob_1.default)(normalizedPattern, {
67
+ ignore: ['**/node_modules/**', '**/dist/**', '**/.git/**'],
68
+ cwd: process.cwd(),
69
+ absolute: true
70
+ });
71
+ console.log(`找到 ${files.length} 个文件`);
72
+ const keys = new Set();
73
+ // 移除正则表达式两端的斜杠(如果是字符串形式的正则)
74
+ // 配置文件中的 "reg": "{#(.+?)#}" 是一个字符串,不是 /.../ 格式
75
+ // 所以直接用 new RegExp(reg, 'g') 即可
76
+ const regex = new RegExp(reg, 'g');
77
+ for (const file of files) {
78
+ const content = fs.readFileSync(file, 'utf8');
79
+ let match;
80
+ // 重置 lastIndex,虽然对于局部变量 regex 没必要,但如果是全局变量就需要
81
+ // 这里每次循环都是同一个 regex 实例,必须重置或者重新创建,或者因为是 'g' 模式,
82
+ // exec 会从上次位置继续。但我们是针对不同文件内容,
83
+ // 实际上对于新内容,regex.exec(newContent) 会从头开始吗?
84
+ // 不,同一个正则实例在不同字符串上使用时,如果之前的字符串没匹配完,lastIndex 可能会保留?
85
+ // 不,exec 方法在新的字符串上调用时,如果它是全局匹配,lastIndex 属性是关键。
86
+ // 最安全的做法是每次循环都重置 lastIndex = 0
87
+ regex.lastIndex = 0;
88
+ while ((match = regex.exec(content)) !== null) {
89
+ if (match[1]) {
90
+ keys.add(match[1]);
91
+ }
92
+ }
93
+ }
94
+ console.log(`提取到 ${keys.size} 个唯一的 Key`);
95
+ // 3. 准备目录
96
+ // 目标目录: Obj.crowdin.workDir/Obj.crowdin.keysDir/source
97
+ const outputDir = path.join(process.cwd(), workDir, keysDir, 'source');
98
+ if (!fs.existsSync(outputDir)) {
99
+ console.log(`创建输出目录: ${outputDir}`);
100
+ fs.mkdirSync(outputDir, { recursive: true });
101
+ }
102
+ // 旧文件目录:Obj.crowdin.workDir/Obj.crowdin.keysDir
103
+ // 注意:用户描述是 "读取 Obj.crowdin.wordDir/Obj.crowdin.keysDir 目录中对应名字的.json文件"
104
+ // 这里 wordDir 应该是 workDir 的笔误
105
+ const oldFilesDir = path.join(process.cwd(), workDir, keysDir);
106
+ // 4. 遍历语言生成文件
107
+ for (const lang of langMap) {
108
+ const fileName = `${lang}.json`;
109
+ const outputPath = path.join(outputDir, fileName);
110
+ // 读取旧文件
111
+ const oldFilePath = path.join(oldFilesDir, fileName);
112
+ let oldData = {};
113
+ if (fs.existsSync(oldFilePath)) {
114
+ try {
115
+ oldData = JSON.parse(fs.readFileSync(oldFilePath, 'utf8'));
116
+ // console.log(`读取旧文件: ${oldFilePath}`);
117
+ }
118
+ catch (e) {
119
+ console.warn(`无法解析旧文件: ${oldFilePath}`);
120
+ }
121
+ }
122
+ else {
123
+ // console.log(`旧文件不存在: ${oldFilePath} (这是新语言?)`);
124
+ }
125
+ // 生成新数据
126
+ const newData = {};
127
+ const sortedKeys = Array.from(keys).sort();
128
+ let matchedCount = 0;
129
+ for (const key of sortedKeys) {
130
+ // 如果旧文件有值,就用旧文件的值,否则为空字符串
131
+ if (oldData[key]) {
132
+ newData[key] = oldData[key];
133
+ matchedCount++;
134
+ }
135
+ else {
136
+ newData[key] = '';
137
+ }
138
+ }
139
+ // 写入文件
140
+ fs.writeFileSync(outputPath, JSON.stringify(newData, null, 2), 'utf8');
141
+ console.log(`✅ 生成 ${fileName}: 总 Key 数 ${sortedKeys.length}, 已翻译 ${matchedCount}`);
142
+ }
143
+ console.log('\n所有文件生成完毕!');
144
+ }
145
+ catch (err) {
146
+ console.error(`TransKey 操作失败: ${err instanceof Error ? err.message : String(err)}`);
147
+ process.exit(1);
148
+ }
149
+ }
@@ -8,3 +8,4 @@ export * from '../uploadWithConfig';
8
8
  export * from './analyze';
9
9
  export * from './deploy';
10
10
  export * from './pullCrowdin';
11
+ export * from './sharp';
@@ -30,3 +30,5 @@ __exportStar(require("./analyze"), exports);
30
30
  __exportStar(require("./deploy"), exports);
31
31
  // 导出所有pull-crowdin相关类型
32
32
  __exportStar(require("./pullCrowdin"), exports);
33
+ // 导出所有sharp相关类型
34
+ __exportStar(require("./sharp"), exports);
@@ -0,0 +1,6 @@
1
+ export interface SharpCommandOptions {
2
+ source: string;
3
+ output: string;
4
+ quality?: string;
5
+ force?: boolean;
6
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-yuca",
3
- "version": "1.3.7",
3
+ "version": "1.3.9",
4
4
  "description": "一个用AI生成的开发辅助工具",
5
5
  "main": "dist/src/index.js",
6
6
  "types": "dist/src/index.d.ts",
@@ -31,13 +31,15 @@
31
31
  },
32
32
  "dependencies": {
33
33
  "@google-cloud/storage": "^7.17.1",
34
+ "@types/sharp": "^0.32.0",
34
35
  "axios": "^1.12.1",
35
36
  "chalk": "^4.1.2",
36
37
  "commander": "^10.0.0",
37
38
  "fast-glob": "^3.3.3",
38
39
  "inquirer": "^12.9.4",
39
40
  "md5-file": "^5.0.0",
40
- "mime-types": "^3.0.1"
41
+ "mime-types": "^3.0.1",
42
+ "sharp": "^0.34.5"
41
43
  },
42
44
  "devDependencies": {
43
45
  "@types/chai": "^5.2.2",
package/vs.config.json CHANGED
@@ -31,7 +31,7 @@
31
31
  "it-it",
32
32
  "ru-ru"
33
33
  ],
34
- "workDir": "src",
34
+ "workDir": ".",
35
35
  "reg": "{#(.+?)#}",
36
36
  "keysDir": "i18n",
37
37
  "Bucket": "decom-cdn-test",
@@ -1 +0,0 @@
1
- export {};
@@ -1,109 +0,0 @@
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
- Object.defineProperty(exports, "__esModule", { value: true });
36
- const chai_1 = require("chai");
37
- const fs = __importStar(require("fs"));
38
- const path = __importStar(require("path"));
39
- const os = __importStar(require("os"));
40
- const compression_1 = require("../src/utils/compression");
41
- describe('压缩工具测试', function () {
42
- describe('shouldCompressFile', function () {
43
- it('应该正确识别可压缩的文件类型', function () {
44
- // 测试所有可压缩的扩展名
45
- compression_1.COMPRESSIBLE_EXTENSIONS.forEach(ext => {
46
- const filePath = `/path/to/file${ext}`;
47
- (0, chai_1.expect)((0, compression_1.shouldCompressFile)(filePath)).to.be.true;
48
- });
49
- });
50
- it('应该正确识别不可压缩的文件类型', function () {
51
- const nonCompressibleExtensions = [
52
- '.jpg', '.png', '.gif', '.mp4', '.pdf', '.txt'
53
- ];
54
- nonCompressibleExtensions.forEach(ext => {
55
- const filePath = `/path/to/file${ext}`;
56
- (0, chai_1.expect)((0, compression_1.shouldCompressFile)(filePath)).to.be.false;
57
- });
58
- });
59
- it('应该处理大写扩展名', function () {
60
- (0, chai_1.expect)((0, compression_1.shouldCompressFile)('/path/to/file.JS')).to.be.true;
61
- (0, chai_1.expect)((0, compression_1.shouldCompressFile)('/path/to/file.CSS')).to.be.true;
62
- (0, chai_1.expect)((0, compression_1.shouldCompressFile)('/path/to/file.JSON')).to.be.true;
63
- });
64
- });
65
- describe('compressFile', function () {
66
- let tempDir;
67
- let testFilePath;
68
- beforeEach(function () {
69
- // 创建临时目录和测试文件
70
- tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'compression-test-'));
71
- testFilePath = path.join(tempDir, 'test.js');
72
- fs.writeFileSync(testFilePath, 'console.log("Hello, World!");');
73
- });
74
- afterEach(function () {
75
- // 清理临时文件和目录
76
- if (fs.existsSync(tempDir)) {
77
- const files = fs.readdirSync(tempDir);
78
- files.forEach(file => {
79
- fs.unlinkSync(path.join(tempDir, file));
80
- });
81
- fs.rmdirSync(tempDir);
82
- }
83
- });
84
- it('应该成功压缩文件并返回压缩后的文件路径', async function () {
85
- const compressedFilePath = await (0, compression_1.compressFile)(testFilePath);
86
- // 验证压缩文件存在
87
- (0, chai_1.expect)(fs.existsSync(compressedFilePath)).to.be.true;
88
- // 验证压缩文件大小小于原文件
89
- const originalSize = fs.statSync(testFilePath).size;
90
- const compressedSize = fs.statSync(compressedFilePath).size;
91
- // 清理压缩文件
92
- fs.unlinkSync(compressedFilePath);
93
- // 由于GZIP头部信息,对于非常小的文件,压缩后可能会更大
94
- // 但对于实际应用中的JS/CSS文件,压缩效果会很明显
95
- // 这里我们只验证压缩文件已创建
96
- (0, chai_1.expect)(compressedFilePath.endsWith('.gz')).to.be.true;
97
- });
98
- it('应该在文件不存在时抛出错误', async function () {
99
- const nonExistentFile = path.join(tempDir, 'non-existent.js');
100
- try {
101
- await (0, compression_1.compressFile)(nonExistentFile);
102
- chai_1.expect.fail('应该抛出错误');
103
- }
104
- catch (error) {
105
- (0, chai_1.expect)(error).to.exist;
106
- }
107
- });
108
- });
109
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,168 +0,0 @@
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
- Object.defineProperty(exports, "__esModule", { value: true });
36
- const chai_1 = require("chai");
37
- const sinon = __importStar(require("sinon"));
38
- const storage_1 = require("@google-cloud/storage");
39
- const download_1 = require("../src/download");
40
- const fs = __importStar(require("fs"));
41
- const util = __importStar(require("util"));
42
- describe('下载功能测试', function () {
43
- let storageStub;
44
- let fsExistsStub;
45
- let fsMkdirStub;
46
- let bucketStub;
47
- let fileStub;
48
- beforeEach(function () {
49
- // 创建存储客户端的存根
50
- storageStub = sinon.createStubInstance(storage_1.Storage);
51
- // 创建文件系统存根
52
- fsExistsStub = sinon.stub(fs, 'existsSync');
53
- fsMkdirStub = sinon.stub().resolves();
54
- sinon.stub(util, 'promisify').returns(fsMkdirStub);
55
- // 创建存储桶和文件存根
56
- bucketStub = {
57
- file: sinon.stub(),
58
- getFiles: sinon.stub()
59
- };
60
- fileStub = {
61
- exists: sinon.stub(),
62
- download: sinon.stub(),
63
- getMetadata: sinon.stub()
64
- };
65
- // 设置存根行为
66
- storageStub.bucket.returns(bucketStub);
67
- bucketStub.file.returns(fileStub);
68
- fileStub.exists.resolves([true]);
69
- fileStub.download.resolves([{}]);
70
- fileStub.getMetadata.resolves([{ name: 'test.txt' }]);
71
- // 默认目录存在
72
- fsExistsStub.returns(true);
73
- // 设置getFiles的默认行为
74
- bucketStub.getFiles.resolves([[
75
- { name: 'test.txt' },
76
- { name: 'folder/test2.txt' }
77
- ], null, { prefixes: ['folder/'] }]);
78
- });
79
- afterEach(function () {
80
- sinon.restore();
81
- });
82
- describe('createStorageClient()', function () {
83
- it('应该使用应用默认凭证创建存储客户端', function () {
84
- const storageClient = (0, download_1.createStorageClient)();
85
- (0, chai_1.expect)(storageClient).to.be.instanceOf(storage_1.Storage);
86
- });
87
- it('应该使用密钥文件创建存储客户端', function () {
88
- const storageClient = (0, download_1.createStorageClient)({ keyFilename: 'key.json' });
89
- (0, chai_1.expect)(storageClient).to.be.instanceOf(storage_1.Storage);
90
- });
91
- it('应该使用凭证对象创建存储客户端', function () {
92
- const storageClient = (0, download_1.createStorageClient)({
93
- projectId: 'test-project',
94
- credentials: {
95
- client_email: 'test@example.com',
96
- private_key: 'private-key'
97
- }
98
- });
99
- (0, chai_1.expect)(storageClient).to.be.instanceOf(storage_1.Storage);
100
- });
101
- });
102
- describe('ensureDirectoryExists()', function () {
103
- it('应该在目录不存在时创建目录', async function () {
104
- fsExistsStub.returns(false);
105
- await (0, download_1.ensureDirectoryExists)('/test/dir');
106
- (0, chai_1.expect)(fsExistsStub.calledWith('/test/dir')).to.be.true;
107
- (0, chai_1.expect)(fsMkdirStub.calledWith('/test/dir', { recursive: true })).to.be.true;
108
- });
109
- it('应该在目录已存在时不创建目录', async function () {
110
- fsExistsStub.returns(true);
111
- await (0, download_1.ensureDirectoryExists)('/test/dir');
112
- (0, chai_1.expect)(fsExistsStub.calledWith('/test/dir')).to.be.true;
113
- (0, chai_1.expect)(fsMkdirStub.called).to.be.false;
114
- });
115
- });
116
- describe('downloadFile()', function () {
117
- it('应该成功下载文件', async function () {
118
- const result = await (0, download_1.downloadFile)({
119
- bucketName: 'test-bucket',
120
- sourcePath: 'test.txt',
121
- destinationPath: '/test/test.txt',
122
- storageClient: storageStub
123
- });
124
- (0, chai_1.expect)(result.success).to.be.true;
125
- (0, chai_1.expect)(result.file).to.equal('test.txt');
126
- (0, chai_1.expect)(result.localPath).to.equal('/test/test.txt');
127
- (0, chai_1.expect)(storageStub.bucket.calledWith('test-bucket')).to.be.true;
128
- (0, chai_1.expect)(bucketStub.file.calledWith('test.txt')).to.be.true;
129
- (0, chai_1.expect)(fileStub.download.calledWith({ destination: '/test/test.txt' })).to.be.true;
130
- });
131
- it('应该在文件不存在时返回失败结果', async function () {
132
- fileStub.exists.resolves([false]);
133
- const result = await (0, download_1.downloadFile)({
134
- bucketName: 'test-bucket',
135
- sourcePath: 'nonexistent.txt',
136
- destinationPath: '/test/nonexistent.txt',
137
- storageClient: storageStub
138
- });
139
- (0, chai_1.expect)(result.success).to.be.false;
140
- (0, chai_1.expect)(result.file).to.equal('nonexistent.txt');
141
- (0, chai_1.expect)(result.error).to.include('文件不存在');
142
- });
143
- });
144
- describe('downloadFiles()', function () {
145
- it('应该成功下载多个文件', async function () {
146
- const result = await (0, download_1.downloadFiles)({
147
- bucketName: 'test-bucket',
148
- sourcePath: '',
149
- destinationPath: '/test',
150
- storageClient: storageStub
151
- });
152
- (0, chai_1.expect)(result.success.length).to.equal(2);
153
- (0, chai_1.expect)(result.failed.length).to.equal(0);
154
- (0, chai_1.expect)(bucketStub.getFiles.called).to.be.true;
155
- });
156
- it('应该在递归模式下下载子目录', async function () {
157
- const result = await (0, download_1.downloadFiles)({
158
- bucketName: 'test-bucket',
159
- sourcePath: '',
160
- destinationPath: '/test',
161
- storageClient: storageStub,
162
- recursive: true
163
- });
164
- (0, chai_1.expect)(result.success.length).to.equal(2);
165
- (0, chai_1.expect)(bucketStub.getFiles.called).to.be.true;
166
- });
167
- });
168
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,33 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const chai_1 = require("chai");
4
- const index_1 = require("../src/index");
5
- describe('AI-Yuca 核心功能测试', function () {
6
- describe('analyzeContent()', function () {
7
- it('应该正确计算文本统计信息', function () {
8
- const testText = 'Hello world!\nThis is a test.\nAI-Yuca is awesome.';
9
- const result = (0, index_1.analyzeContent)(testText);
10
- (0, chai_1.expect)(result).to.have.property('statistics');
11
- (0, chai_1.expect)(result.statistics).to.have.property('charCount').that.equals(testText.length);
12
- (0, chai_1.expect)(result.statistics).to.have.property('wordCount').that.equals(9);
13
- (0, chai_1.expect)(result.statistics).to.have.property('lineCount').that.equals(3);
14
- });
15
- it('应该提取关键词', function () {
16
- const testText = 'The quick brown fox jumps over the lazy dog. The fox is quick and brown.';
17
- const result = (0, index_1.analyzeContent)(testText);
18
- (0, chai_1.expect)(result).to.have.property('keywords');
19
- (0, chai_1.expect)(result.keywords).to.be.an('array');
20
- // 检查是否包含预期的关键词
21
- const keywords = result.keywords.map((k) => k.word);
22
- (0, chai_1.expect)(keywords).to.include('quick');
23
- (0, chai_1.expect)(keywords).to.include('brown');
24
- });
25
- it('应该处理空文本', function () {
26
- const result = (0, index_1.analyzeContent)('');
27
- (0, chai_1.expect)(result.statistics.charCount).to.equal(0);
28
- (0, chai_1.expect)(result.statistics.wordCount).to.equal(0);
29
- (0, chai_1.expect)(result.statistics.lineCount).to.equal(1); // 空字符串也算一行
30
- (0, chai_1.expect)(result.keywords).to.be.an('array').that.is.empty;
31
- });
32
- });
33
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,140 +0,0 @@
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
- Object.defineProperty(exports, "__esModule", { value: true });
36
- const chai_1 = require("chai");
37
- const sinon = __importStar(require("sinon"));
38
- const storage_1 = require("@google-cloud/storage");
39
- const upload_1 = require("../src/upload");
40
- const compression_1 = require("../src/utils/compression");
41
- // 在测试中使用sinon替代jest进行模拟
42
- const fs = __importStar(require("fs"));
43
- describe('上传功能测试', function () {
44
- let storageStub;
45
- let fsExistsStub;
46
- let bucketStub;
47
- beforeEach(function () {
48
- // 创建存储客户端的存根
49
- storageStub = sinon.createStubInstance(storage_1.Storage);
50
- // 创建文件系统存根
51
- fsExistsStub = sinon.stub(fs, 'existsSync');
52
- // 创建存储桶存根
53
- bucketStub = {
54
- upload: sinon.stub().resolves([{
55
- getMetadata: sinon.stub().resolves([{
56
- name: 'test-file.txt',
57
- size: '1024',
58
- contentType: 'text/plain',
59
- timeCreated: '2023-01-01T00:00:00.000Z'
60
- }])
61
- }])
62
- };
63
- storageStub.bucket.returns(bucketStub);
64
- });
65
- afterEach(function () {
66
- sinon.restore();
67
- });
68
- describe('createStorageClient()', function () {
69
- it('应该使用应用默认凭证创建存储客户端', function () {
70
- const client = (0, upload_1.createStorageClient)({});
71
- (0, chai_1.expect)(client).to.be.an.instanceof(storage_1.Storage);
72
- });
73
- it('应该使用密钥文件创建存储客户端', function () {
74
- const client = (0, upload_1.createStorageClient)({ keyFilename: 'test-key.json' });
75
- (0, chai_1.expect)(client).to.be.an.instanceof(storage_1.Storage);
76
- });
77
- it('应该使用凭证对象创建存储客户端', function () {
78
- const client = (0, upload_1.createStorageClient)({
79
- credentials: { client_email: 'test@example.com', private_key: 'test-key' }
80
- });
81
- (0, chai_1.expect)(client).to.be.an.instanceof(storage_1.Storage);
82
- });
83
- });
84
- describe('uploadFile()', function () {
85
- beforeEach(function () {
86
- // 模拟压缩函数
87
- compression_1.shouldCompressFile = sinon.stub().returns(false);
88
- compression_1.compressFile = sinon.stub().resolves('test-file.txt.gz');
89
- // 模拟文件系统
90
- fsExistsStub.withArgs('test-file.txt.gz').returns(true);
91
- });
92
- it('应该在文件不存在时返回失败结果', async function () {
93
- fsExistsStub.withArgs('non-existent-file.txt').returns(false);
94
- const result = await (0, upload_1.uploadFile)({
95
- bucketName: 'test-bucket',
96
- filePath: 'non-existent-file.txt',
97
- storageClient: storageStub
98
- });
99
- (0, chai_1.expect)(result.success).to.be.false;
100
- (0, chai_1.expect)(result).to.have.property('error').that.includes('文件不存在');
101
- });
102
- it('应该成功上传文件(不压缩)', async function () {
103
- fsExistsStub.withArgs('test-file.txt').returns(true);
104
- const result = await (0, upload_1.uploadFile)({
105
- bucketName: 'test-bucket',
106
- filePath: 'test-file.txt',
107
- storageClient: storageStub
108
- });
109
- (0, chai_1.expect)(result.success).to.be.true;
110
- (0, chai_1.expect)(result).to.have.property('file').that.equals('test-file.txt');
111
- (0, chai_1.expect)(result).to.have.property('url').that.includes('test-bucket/test-file.txt');
112
- });
113
- it('应该成功上传压缩文件', async function () {
114
- fsExistsStub.withArgs('test-file.js').returns(true);
115
- compression_1.shouldCompressFile.returns(true);
116
- const result = await (0, upload_1.uploadFile)({
117
- bucketName: 'test-bucket',
118
- filePath: 'test-file.js',
119
- storageClient: storageStub,
120
- enableCompression: true
121
- });
122
- (0, chai_1.expect)(compression_1.shouldCompressFile.calledWith('test-file.js')).to.be.true;
123
- (0, chai_1.expect)(compression_1.compressFile.calledWith('test-file.js')).to.be.true;
124
- (0, chai_1.expect)(result.success).to.be.true;
125
- });
126
- it('应该在禁用压缩时不压缩文件', async function () {
127
- fsExistsStub.withArgs('test-file.js').returns(true);
128
- compression_1.shouldCompressFile.returns(true);
129
- const result = await (0, upload_1.uploadFile)({
130
- bucketName: 'test-bucket',
131
- filePath: 'test-file.js',
132
- storageClient: storageStub,
133
- enableCompression: false
134
- });
135
- // 不应该调用压缩函数
136
- (0, chai_1.expect)(compression_1.compressFile.called).to.be.false;
137
- (0, chai_1.expect)(result.success).to.be.true;
138
- });
139
- });
140
- });