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.
- package/.eslintrc.js +25 -0
- package/CONFIG_UPLOAD.md +154 -0
- package/CONTRIBUTING.md +58 -0
- package/INSTALLATION.md +192 -0
- package/README.md +80 -0
- package/bin/cli.js +85 -0
- package/bin/cli.ts +302 -0
- package/dist/bin/cli.d.ts +2 -0
- package/dist/bin/cli.js +297 -0
- package/dist/package.json +51 -0
- package/dist/src/config.d.ts +56 -0
- package/dist/src/config.js +101 -0
- package/dist/src/download.d.ts +30 -0
- package/dist/src/download.js +214 -0
- package/dist/src/index.d.ts +18 -0
- package/dist/src/index.js +126 -0
- package/dist/src/types/analyze.d.ts +33 -0
- package/dist/src/types/analyze.js +5 -0
- package/dist/src/types/download.d.ts +60 -0
- package/dist/src/types/download.js +2 -0
- package/dist/src/types/index.d.ts +8 -0
- package/dist/src/types/index.js +28 -0
- package/dist/src/types/upload.d.ts +89 -0
- package/dist/src/types/upload.js +2 -0
- package/dist/src/upload.d.ts +24 -0
- package/dist/src/upload.js +252 -0
- package/dist/src/uploadWithConfig.d.ts +34 -0
- package/dist/src/uploadWithConfig.js +82 -0
- package/dist/src/utils/compression.d.ts +16 -0
- package/dist/src/utils/compression.js +85 -0
- package/dist/test/compression.test.d.ts +1 -0
- package/dist/test/compression.test.js +109 -0
- package/dist/test/download.test.d.ts +1 -0
- package/dist/test/download.test.js +168 -0
- package/dist/test/index.test.d.ts +1 -0
- package/dist/test/index.test.js +33 -0
- package/dist/test/upload.test.d.ts +1 -0
- package/dist/test/upload.test.js +140 -0
- package/docs/usage.md +223 -0
- package/examples/sample.txt +7 -0
- package/examples/upload-example.js +53 -0
- package/out/test.txt +1 -0
- package/package.json +51 -0
- package/src/config.ts +104 -0
- package/src/download.ts +216 -0
- package/src/index.js +88 -0
- package/src/index.ts +98 -0
- package/src/types/analyze.ts +37 -0
- package/src/types/download.ts +67 -0
- package/src/types/index.ts +16 -0
- package/src/types/upload.ts +97 -0
- package/src/upload.js +197 -0
- package/src/upload.ts +254 -0
- package/src/uploadWithConfig.ts +122 -0
- package/src/utils/compression.ts +61 -0
- package/test/compression.test.ts +88 -0
- package/test/download.test.ts +162 -0
- package/test/index.test.js +38 -0
- package/test/index.test.ts +39 -0
- package/test/upload.test.ts +131 -0
- package/tsconfig.json +17 -0
- 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
|
+
```
|