ai-yuca 1.0.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.
Files changed (62) hide show
  1. package/.eslintrc.js +25 -0
  2. package/CONFIG_UPLOAD.md +154 -0
  3. package/CONTRIBUTING.md +58 -0
  4. package/INSTALLATION.md +192 -0
  5. package/README.md +80 -0
  6. package/bin/cli.js +85 -0
  7. package/bin/cli.ts +302 -0
  8. package/dist/bin/cli.d.ts +2 -0
  9. package/dist/bin/cli.js +297 -0
  10. package/dist/package.json +51 -0
  11. package/dist/src/config.d.ts +56 -0
  12. package/dist/src/config.js +101 -0
  13. package/dist/src/download.d.ts +30 -0
  14. package/dist/src/download.js +214 -0
  15. package/dist/src/index.d.ts +18 -0
  16. package/dist/src/index.js +126 -0
  17. package/dist/src/types/analyze.d.ts +33 -0
  18. package/dist/src/types/analyze.js +5 -0
  19. package/dist/src/types/download.d.ts +60 -0
  20. package/dist/src/types/download.js +2 -0
  21. package/dist/src/types/index.d.ts +8 -0
  22. package/dist/src/types/index.js +28 -0
  23. package/dist/src/types/upload.d.ts +89 -0
  24. package/dist/src/types/upload.js +2 -0
  25. package/dist/src/upload.d.ts +24 -0
  26. package/dist/src/upload.js +252 -0
  27. package/dist/src/uploadWithConfig.d.ts +34 -0
  28. package/dist/src/uploadWithConfig.js +82 -0
  29. package/dist/src/utils/compression.d.ts +16 -0
  30. package/dist/src/utils/compression.js +85 -0
  31. package/dist/test/compression.test.d.ts +1 -0
  32. package/dist/test/compression.test.js +109 -0
  33. package/dist/test/download.test.d.ts +1 -0
  34. package/dist/test/download.test.js +168 -0
  35. package/dist/test/index.test.d.ts +1 -0
  36. package/dist/test/index.test.js +33 -0
  37. package/dist/test/upload.test.d.ts +1 -0
  38. package/dist/test/upload.test.js +140 -0
  39. package/docs/usage.md +223 -0
  40. package/examples/sample.txt +7 -0
  41. package/examples/upload-example.js +53 -0
  42. package/out/test.txt +1 -0
  43. package/package.json +51 -0
  44. package/src/config.ts +104 -0
  45. package/src/download.ts +216 -0
  46. package/src/index.js +88 -0
  47. package/src/index.ts +98 -0
  48. package/src/types/analyze.ts +37 -0
  49. package/src/types/download.ts +67 -0
  50. package/src/types/index.ts +16 -0
  51. package/src/types/upload.ts +97 -0
  52. package/src/upload.js +197 -0
  53. package/src/upload.ts +254 -0
  54. package/src/uploadWithConfig.ts +122 -0
  55. package/src/utils/compression.ts +61 -0
  56. package/test/compression.test.ts +88 -0
  57. package/test/download.test.ts +162 -0
  58. package/test/index.test.js +38 -0
  59. package/test/index.test.ts +39 -0
  60. package/test/upload.test.ts +131 -0
  61. package/tsconfig.json +17 -0
  62. package/vs.config.json +42 -0
@@ -0,0 +1,109 @@
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
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,168 @@
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
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,33 @@
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
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,140 @@
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
+ });
package/docs/usage.md ADDED
@@ -0,0 +1,223 @@
1
+ # AI-Yuca 使用指南
2
+
3
+ > 注意:AI-Yuca 现已使用 TypeScript 重构,提供更好的类型安全和开发体验。
4
+
5
+ ## 安装
6
+
7
+ ### 全局安装
8
+
9
+ ```bash
10
+ npm install -g ai-yuca
11
+ ```
12
+
13
+ ### 本地安装
14
+
15
+ ```bash
16
+ npm install ai-yuca
17
+ ```
18
+
19
+ ## 命令行使用
20
+
21
+ ### 帮助信息
22
+
23
+ ```bash
24
+ ai-yuca --help
25
+ ```
26
+
27
+ ### 分析文件
28
+
29
+ ```bash
30
+ ai-yuca analyze --file path/to/your/file.txt
31
+ ```
32
+
33
+ ### 分析文本
34
+
35
+ ```bash
36
+ ai-yuca analyze --text "这是一段需要分析的文本内容"
37
+ ```
38
+
39
+ ### 上传文件到GCP
40
+
41
+ ```bash
42
+ ai-yuca upload --source path/to/your/file.txt --bucket your-bucket-name --key-file path/to/keyfile.json
43
+ ```
44
+
45
+ #### 上传选项
46
+
47
+ - `--source, -s`: 指定要上传的文件或目录路径(支持多个)
48
+ - `--bucket, -b`: GCP存储桶名称
49
+ - `--destination, -d`: 目标路径(存储桶中的路径,可选)
50
+ - `--key-file, -k`: GCP服务账号密钥文件路径(可选,不提供则使用应用默认凭证)
51
+ - `--recursive, -r`: 递归上传目录中的文件(默认为false)
52
+
53
+ #### 认证方式
54
+
55
+ ##### 1. 使用密钥文件(显式认证)
56
+
57
+ ```bash
58
+ ai-yuca upload --source file.txt --bucket your-bucket-name --key-file path/to/keyfile.json
59
+ ```
60
+
61
+ ##### 2. 使用应用默认凭证(免密上传)
62
+
63
+ ```bash
64
+ # 先设置环境变量
65
+ export GOOGLE_APPLICATION_CREDENTIALS=/path/to/keyfile.json
66
+
67
+ # 然后无需提供--key-file参数
68
+ ai-yuca upload --source file.txt --bucket your-bucket-name
69
+ ```
70
+
71
+ 也可以使用GCP实例的默认服务账号(如在GCE、GKE或Cloud Run中运行时)。
72
+
73
+ ```bash
74
+ # 批量上传示例
75
+ ai-yuca upload --source file1.txt file2.txt --bucket your-bucket-name --key-file path/to/keyfile.json
76
+
77
+ # 递归上传目录示例
78
+ ai-yuca upload --source path/to/directory --bucket your-bucket-name --key-file path/to/keyfile.json --recursive
79
+ ```
80
+
81
+ ## 开发者指南
82
+
83
+ ### 项目结构
84
+
85
+ AI-Yuca 使用 TypeScript 开发,项目结构如下:
86
+
87
+ ```
88
+ ├── bin/ # 命令行入口
89
+ ├── src/ # 源代码
90
+ ├── dist/ # 编译后的代码
91
+ ├── test/ # 测试文件
92
+ └── docs/ # 文档
93
+ ```
94
+
95
+ ### 构建项目
96
+
97
+ ```bash
98
+ # 安装依赖
99
+ npm install
100
+
101
+ # 构建项目
102
+ npm run build
103
+
104
+ # 开发模式运行
105
+ npm run dev
106
+
107
+ # 运行测试
108
+ npm test
109
+ ```
110
+
111
+ ### 类型定义
112
+
113
+ AI-Yuca 提供了完整的 TypeScript 类型定义,可以在开发时获得更好的代码提示和类型检查。
114
+
115
+ ```typescript
116
+ import { analyze, AnalyzeOptions, AnalysisResult } from 'ai-yuca';
117
+
118
+ // 使用类型定义
119
+ const options: AnalyzeOptions = {
120
+ text: '这是一段需要分析的文本'
121
+ };
122
+
123
+ const result: AnalysisResult = analyze(options);
124
+ ```
125
+
126
+ ### 在代码中使用免密上传
127
+
128
+ AI-Yuca 支持多种认证方式上传文件到 GCP 存储桶:
129
+
130
+ ```typescript
131
+ import { createStorageClient, uploadFile, StorageClientOptions } from 'ai-yuca';
132
+
133
+ // 1. 使用密钥文件认证
134
+ const client1 = createStorageClient({ keyFilename: '/path/to/keyfile.json' });
135
+
136
+ // 2. 使用凭证对象认证
137
+ const client2 = createStorageClient({
138
+ projectId: 'your-project-id',
139
+ credentials: {
140
+ client_email: 'service-account@project.iam.gserviceaccount.com',
141
+ private_key: '-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n'
142
+ }
143
+ });
144
+
145
+ // 3. 使用应用默认凭证(免密上传)
146
+ // 这将使用环境变量GOOGLE_APPLICATION_CREDENTIALS或默认服务账号
147
+ const client3 = createStorageClient();
148
+
149
+ // 使用客户端上传文件
150
+ await uploadFile({
151
+ bucketName: 'your-bucket-name',
152
+ filePath: '/path/to/local/file.txt',
153
+ destination: 'path/in/bucket/file.txt',
154
+ storageClient: client3
155
+ });
156
+ ```
157
+
158
+ ## 分析结果说明
159
+
160
+ 分析结果将以JSON格式输出,包含以下信息:
161
+
162
+ - **statistics**: 文本统计信息
163
+ - **charCount**: 字符数量
164
+ - **wordCount**: 单词数量
165
+ - **lineCount**: 行数
166
+
167
+ - **keywords**: 关键词列表,包含最常见的5个词及其出现次数
168
+
169
+ - **summary**: 文本摘要,简要描述文本的基本信息
170
+
171
+ ## 上传结果说明
172
+
173
+ 上传结果将以JSON格式输出,包含以下信息:
174
+
175
+ - **success**: 成功上传的文件列表
176
+ - **file**: 文件名
177
+ - **size**: 文件大小
178
+ - **contentType**: 文件类型
179
+ - **timeCreated**: 创建时间
180
+ - **url**: 文件公共访问URL
181
+
182
+ - **failed**: 上传失败的文件列表
183
+ - **file**: 文件名
184
+ - **error**: 错误信息
185
+
186
+ ## 示例
187
+
188
+ ```bash
189
+ $ ai-yuca analyze --text "AI-Yuca is a simple text analysis tool. It can count words and extract keywords from your text."
190
+ ```
191
+
192
+ 输出:
193
+
194
+ ```json
195
+ {
196
+ "statistics": {
197
+ "charCount": 85,
198
+ "wordCount": 16,
199
+ "lineCount": 1
200
+ },
201
+ "keywords": [
202
+ { "word": "text", "count": 2 },
203
+ { "word": "yuca", "count": 1 },
204
+ { "word": "simple", "count": 1 },
205
+ { "word": "analysis", "count": 1 },
206
+ { "word": "tool", "count": 1 }
207
+ ],
208
+ "summary": "文本包含 85 个字符, 16 个单词, 1 行。"
209
+ }
210
+ ```
211
+
212
+ ## 在代码中使用
213
+
214
+ 您也可以在Node.js项目中直接使用AI-Yuca的API:
215
+
216
+ ```javascript
217
+ const { analyzeContent } = require('ai-yuca');
218
+
219
+ const text = '这是一段需要分析的文本内容';
220
+ const result = analyzeContent(text);
221
+
222
+ console.log(result);
223
+ ```