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,67 @@
1
+ /**
2
+ * 下载功能相关类型定义
3
+ */
4
+ import { Storage } from '@google-cloud/storage';
5
+
6
+ /**
7
+ * 下载选项接口
8
+ */
9
+ export interface DownloadFileOptions {
10
+ bucketName: string;
11
+ sourcePath: string;
12
+ destinationPath: string;
13
+ storageClient: Storage;
14
+ }
15
+
16
+ /**
17
+ * 批量下载选项接口
18
+ */
19
+ export interface DownloadFilesOptions {
20
+ bucketName: string;
21
+ sourcePath: string;
22
+ destinationPath: string;
23
+ storageClient: Storage;
24
+ recursive?: boolean;
25
+ }
26
+
27
+ /**
28
+ * 下载成功结果接口
29
+ */
30
+ export interface DownloadSuccessResult {
31
+ success: true;
32
+ file: string;
33
+ localPath: string;
34
+ }
35
+
36
+ /**
37
+ * 下载失败结果接口
38
+ */
39
+ export interface DownloadFailedResult {
40
+ success: false;
41
+ file: string;
42
+ error: string;
43
+ }
44
+
45
+ /**
46
+ * 下载结果类型
47
+ */
48
+ export type DownloadResult = DownloadSuccessResult | DownloadFailedResult;
49
+
50
+ /**
51
+ * 批量下载结果接口
52
+ */
53
+ export interface DownloadFilesResult {
54
+ success: DownloadSuccessResult[];
55
+ failed: DownloadFailedResult[];
56
+ }
57
+
58
+ /**
59
+ * CLI下载命令选项接口
60
+ */
61
+ export interface DownloadCommandOptions {
62
+ source: string;
63
+ bucket: string;
64
+ destination: string;
65
+ keyFile?: string;
66
+ recursive: boolean;
67
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * 类型定义索引文件
3
+ */
4
+
5
+ // 导出所有上传相关类型
6
+ export * from './upload';
7
+
8
+ // 导出所有下载相关类型
9
+ export * from './download';
10
+
11
+ // 导出所有配置相关类型
12
+ export * from '../config';
13
+ export * from '../uploadWithConfig';
14
+
15
+ // 导出所有分析相关类型
16
+ export * from './analyze';
@@ -0,0 +1,97 @@
1
+ /**
2
+ * 上传功能相关类型定义
3
+ */
4
+ import { Storage, Bucket } from '@google-cloud/storage';
5
+
6
+ /**
7
+ * 存储客户端选项接口
8
+ */
9
+ export interface StorageClientOptions {
10
+ keyFilename?: string;
11
+ projectId?: string;
12
+ credentials?: {
13
+ client_email?: string;
14
+ private_key?: string;
15
+ };
16
+ }
17
+
18
+ /**
19
+ * 文件上传选项接口
20
+ */
21
+ export interface UploadFileOptions {
22
+ bucketName: string;
23
+ filePath: string;
24
+ destination?: string;
25
+ storageClient: Storage;
26
+ /**
27
+ * 是否启用GZIP压缩(对js、css、json、html、woff文件类型)
28
+ * @default true
29
+ */
30
+ enableCompression?: boolean;
31
+ }
32
+
33
+ /**
34
+ * 批量上传选项接口
35
+ */
36
+ export interface UploadFilesOptions {
37
+ bucketName: string;
38
+ sourcePath: string | string[];
39
+ destination?: string;
40
+ storageClient: Storage;
41
+ recursive?: boolean;
42
+ /**
43
+ * 是否启用GZIP压缩(对js、css、json、html、woff文件类型)
44
+ * @default true
45
+ */
46
+ enableCompression?: boolean;
47
+ }
48
+
49
+ /**
50
+ * 上传成功结果接口
51
+ */
52
+ export interface UploadSuccessResult {
53
+ success: true;
54
+ file: string;
55
+ size: string | number;
56
+ contentType: string;
57
+ timeCreated: string;
58
+ url: string;
59
+ }
60
+
61
+ /**
62
+ * 上传失败结果接口
63
+ */
64
+ export interface UploadFailedResult {
65
+ success: false;
66
+ file: string;
67
+ error: string;
68
+ }
69
+
70
+ /**
71
+ * 上传结果类型
72
+ */
73
+ export type UploadResult = UploadSuccessResult | UploadFailedResult;
74
+
75
+ /**
76
+ * 批量上传结果接口
77
+ */
78
+ export interface UploadFilesResult {
79
+ success: UploadSuccessResult[];
80
+ failed: UploadFailedResult[];
81
+ }
82
+
83
+ /**
84
+ * CLI上传命令选项接口
85
+ */
86
+ export interface UploadCommandOptions {
87
+ source: string[];
88
+ bucket: string;
89
+ destination?: string;
90
+ keyFile?: string;
91
+ recursive: boolean;
92
+ /**
93
+ * 是否启用GZIP压缩(对js、css、json、html、woff文件类型)
94
+ * @default true
95
+ */
96
+ compression?: boolean;
97
+ }
package/src/upload.js ADDED
@@ -0,0 +1,197 @@
1
+ /**
2
+ * GCP上传功能模块
3
+ */
4
+ const {Storage} = require('@google-cloud/storage');
5
+ const fs = require('fs');
6
+ const path = require('path');
7
+ const util = require('util');
8
+ const readdir = util.promisify(fs.readdir);
9
+ const stat = util.promisify(fs.stat);
10
+
11
+ /**
12
+ * 创建GCP存储客户端
13
+ * @param {Object} options - 配置选项
14
+ * @param {string} options.keyFilename - 服务账号密钥文件路径
15
+ * @returns {Storage} GCP存储客户端实例
16
+ */
17
+ function createStorageClient(options = {}) {
18
+ const { keyFilename } = options;
19
+
20
+ if (!keyFilename) {
21
+ throw new Error('必须提供GCP服务账号密钥文件路径');
22
+ }
23
+
24
+ return new Storage({
25
+ keyFilename
26
+ });
27
+ }
28
+
29
+ /**
30
+ * 上传单个文件到GCP存储桶
31
+ * @param {Object} options - 上传选项
32
+ * @param {string} options.bucketName - GCP存储桶名称
33
+ * @param {string} options.filePath - 要上传的本地文件路径
34
+ * @param {string} options.destination - 目标路径(存储桶中的路径)
35
+ * @param {Storage} options.storageClient - GCP存储客户端实例
36
+ * @returns {Promise<Object>} 上传结果
37
+ */
38
+ async function uploadFile(options) {
39
+ const { bucketName, filePath, destination, storageClient } = options;
40
+
41
+ if (!bucketName || !filePath || !storageClient) {
42
+ throw new Error('缺少必要的上传参数');
43
+ }
44
+
45
+ try {
46
+ // 检查文件是否存在
47
+ if (!fs.existsSync(filePath)) {
48
+ throw new Error(`文件不存在: ${filePath}`);
49
+ }
50
+
51
+ // 获取存储桶
52
+ const bucket = storageClient.bucket(bucketName);
53
+
54
+ // 确定目标路径
55
+ const destPath = destination || path.basename(filePath);
56
+
57
+ // 上传文件
58
+ const [file] = await bucket.upload(filePath, {
59
+ destination: destPath,
60
+ // 可选:设置元数据
61
+ metadata: {
62
+ cacheControl: 'public, max-age=31536000',
63
+ },
64
+ });
65
+
66
+ // 获取文件公共URL
67
+ const [metadata] = await file.getMetadata();
68
+
69
+ return {
70
+ success: true,
71
+ file: metadata.name,
72
+ size: metadata.size,
73
+ contentType: metadata.contentType,
74
+ timeCreated: metadata.timeCreated,
75
+ url: `https://storage.googleapis.com/${bucketName}/${metadata.name}`
76
+ };
77
+ } catch (error) {
78
+ return {
79
+ success: false,
80
+ file: filePath,
81
+ error: error.message
82
+ };
83
+ }
84
+ }
85
+
86
+ /**
87
+ * 批量上传文件到GCP存储桶
88
+ * @param {Object} options - 上传选项
89
+ * @param {string} options.bucketName - GCP存储桶名称
90
+ * @param {string|string[]} options.sourcePath - 要上传的本地文件路径或目录
91
+ * @param {string} options.destination - 目标路径(存储桶中的路径)
92
+ * @param {Storage} options.storageClient - GCP存储客户端实例
93
+ * @param {boolean} options.recursive - 是否递归上传目录中的文件
94
+ * @returns {Promise<Object>} 上传结果
95
+ */
96
+ async function uploadFiles(options) {
97
+ const { bucketName, sourcePath, destination, storageClient, recursive = false } = options;
98
+
99
+ if (!bucketName || !sourcePath || !storageClient) {
100
+ throw new Error('缺少必要的上传参数');
101
+ }
102
+
103
+ const results = {
104
+ success: [],
105
+ failed: []
106
+ };
107
+
108
+ try {
109
+ // 如果sourcePath是数组,批量上传多个文件
110
+ if (Array.isArray(sourcePath)) {
111
+ for (const filePath of sourcePath) {
112
+ const result = await uploadFile({
113
+ bucketName,
114
+ filePath,
115
+ destination: destination ? path.join(destination, path.basename(filePath)) : undefined,
116
+ storageClient
117
+ });
118
+
119
+ if (result.success) {
120
+ results.success.push(result);
121
+ } else {
122
+ results.failed.push(result);
123
+ }
124
+ }
125
+ return results;
126
+ }
127
+
128
+ // 检查sourcePath是文件还是目录
129
+ const stats = await stat(sourcePath);
130
+
131
+ if (stats.isFile()) {
132
+ // 上传单个文件
133
+ const result = await uploadFile({
134
+ bucketName,
135
+ filePath: sourcePath,
136
+ destination,
137
+ storageClient
138
+ });
139
+
140
+ if (result.success) {
141
+ results.success.push(result);
142
+ } else {
143
+ results.failed.push(result);
144
+ }
145
+ } else if (stats.isDirectory()) {
146
+ // 上传目录中的文件
147
+ const files = await readdir(sourcePath);
148
+
149
+ for (const file of files) {
150
+ const filePath = path.join(sourcePath, file);
151
+ const fileStats = await stat(filePath);
152
+
153
+ if (fileStats.isFile()) {
154
+ // 上传文件
155
+ const destPath = destination ? path.join(destination, file) : file;
156
+
157
+ const result = await uploadFile({
158
+ bucketName,
159
+ filePath,
160
+ destination: destPath,
161
+ storageClient
162
+ });
163
+
164
+ if (result.success) {
165
+ results.success.push(result);
166
+ } else {
167
+ results.failed.push(result);
168
+ }
169
+ } else if (recursive && fileStats.isDirectory()) {
170
+ // 递归上传子目录
171
+ const subDestination = destination ? path.join(destination, file) : file;
172
+
173
+ const subResults = await uploadFiles({
174
+ bucketName,
175
+ sourcePath: filePath,
176
+ destination: subDestination,
177
+ storageClient,
178
+ recursive
179
+ });
180
+
181
+ results.success = results.success.concat(subResults.success);
182
+ results.failed = results.failed.concat(subResults.failed);
183
+ }
184
+ }
185
+ }
186
+
187
+ return results;
188
+ } catch (error) {
189
+ throw new Error(`上传文件失败: ${error.message}`);
190
+ }
191
+ }
192
+
193
+ module.exports = {
194
+ createStorageClient,
195
+ uploadFile,
196
+ uploadFiles
197
+ };
package/src/upload.ts ADDED
@@ -0,0 +1,254 @@
1
+ /**
2
+ * GCP上传功能模块
3
+ */
4
+ import { Storage, Bucket, File } from '@google-cloud/storage';
5
+ import * as fs from 'fs';
6
+ import * as path from 'path';
7
+ import * as util from 'util';
8
+ import {
9
+ StorageClientOptions,
10
+ UploadFileOptions,
11
+ UploadFilesOptions,
12
+ UploadSuccessResult,
13
+ UploadFailedResult,
14
+ UploadResult,
15
+ UploadFilesResult
16
+ } from './types/upload';
17
+ import { shouldCompressFile, compressFile } from './utils/compression';
18
+
19
+ const readdir = util.promisify(fs.readdir);
20
+ const stat = util.promisify(fs.stat);
21
+
22
+ /**
23
+ * 创建GCP存储客户端
24
+ * @param options - 配置选项
25
+ * @returns GCP存储客户端实例
26
+ */
27
+ function createStorageClient(options: Partial<StorageClientOptions> = {}): Storage {
28
+ const { keyFilename, projectId, credentials } = options;
29
+
30
+ // 如果提供了keyFilename,使用密钥文件认证
31
+ if (keyFilename) {
32
+ return new Storage({
33
+ keyFilename
34
+ });
35
+ }
36
+
37
+ // 如果提供了credentials,使用凭证对象认证
38
+ if (credentials) {
39
+ return new Storage({
40
+ projectId,
41
+ credentials
42
+ });
43
+ }
44
+
45
+ // 使用应用默认凭证(ADC)进行免密认证
46
+ // 这将使用环境变量GOOGLE_APPLICATION_CREDENTIALS或默认服务账号
47
+ return new Storage();
48
+ }
49
+
50
+ /**
51
+ * 上传单个文件到GCP存储桶
52
+ * @param options - 上传选项
53
+ * @returns 上传结果
54
+ */
55
+ async function uploadFile(options: UploadFileOptions): Promise<UploadResult> {
56
+ const { bucketName, filePath, destination, storageClient, enableCompression = true } = options;
57
+
58
+ if (!bucketName || !filePath || !storageClient) {
59
+ throw new Error('缺少必要的上传参数');
60
+ }
61
+
62
+ try {
63
+ // 检查文件是否存在
64
+ if (!fs.existsSync(filePath)) {
65
+ throw new Error(`文件不存在: ${filePath}`);
66
+ }
67
+
68
+ // 获取存储桶
69
+ const bucket: Bucket = storageClient.bucket(bucketName);
70
+
71
+ // 确定目标路径
72
+ const destPath = destination || path.basename(filePath);
73
+
74
+ // 检查是否需要压缩
75
+ let fileToUpload = filePath;
76
+ let contentEncoding: string | undefined;
77
+ let contentType: string | undefined;
78
+
79
+ if (enableCompression && shouldCompressFile(filePath)) {
80
+ // 压缩文件
81
+ fileToUpload = await compressFile(filePath);
82
+ contentEncoding = 'gzip';
83
+
84
+ // 根据文件扩展名设置正确的Content-Type
85
+ const ext = path.extname(filePath).toLowerCase();
86
+ switch (ext) {
87
+ case '.js':
88
+ contentType = 'application/javascript';
89
+ break;
90
+ case '.css':
91
+ contentType = 'text/css';
92
+ break;
93
+ case '.json':
94
+ contentType = 'application/json';
95
+ break;
96
+ case '.html':
97
+ contentType = 'text/html';
98
+ break;
99
+ case '.woff':
100
+ contentType = 'font/woff';
101
+ break;
102
+ default:
103
+ contentType = 'application/octet-stream';
104
+ }
105
+ }
106
+
107
+ // 上传文件
108
+ const [file] = await bucket.upload(fileToUpload, {
109
+ destination: destPath,
110
+ // 设置元数据
111
+ metadata: {
112
+ // 为.json文件设置缓存时间为0,其他文件保持一年缓存
113
+ cacheControl: path.extname(filePath).toLowerCase() === '.json' ? 'public, max-age=0' : 'public, max-age=31536000',
114
+ contentEncoding,
115
+ contentType
116
+ },
117
+ });
118
+
119
+ // 如果上传了压缩文件,删除临时文件
120
+ if (fileToUpload !== filePath && fs.existsSync(fileToUpload)) {
121
+ fs.unlinkSync(fileToUpload);
122
+ }
123
+
124
+ // 获取文件公共URL
125
+ const [metadata] = await file.getMetadata();
126
+
127
+ return {
128
+ success: true,
129
+ file: metadata.name || path.basename(filePath),
130
+ size: metadata.size || 0,
131
+ contentType: metadata.contentType || 'application/octet-stream',
132
+ timeCreated: metadata.timeCreated || new Date().toISOString(),
133
+ url: `https://storage.googleapis.com/${bucketName}/${metadata.name || path.basename(filePath)}`
134
+ };
135
+ } catch (error) {
136
+ return {
137
+ success: false,
138
+ file: filePath,
139
+ error: error instanceof Error ? error.message : String(error)
140
+ };
141
+ }
142
+ }
143
+
144
+ /**
145
+ * 批量上传文件到GCP存储桶
146
+ * @param options - 上传选项
147
+ * @returns 上传结果
148
+ */
149
+ async function uploadFiles(options: UploadFilesOptions): Promise<UploadFilesResult> {
150
+ const { bucketName, sourcePath, destination, storageClient, recursive = false, enableCompression = true } = options;
151
+
152
+ if (!bucketName || !sourcePath || !storageClient) {
153
+ throw new Error('缺少必要的上传参数');
154
+ }
155
+
156
+ const results: UploadFilesResult = {
157
+ success: [],
158
+ failed: []
159
+ };
160
+
161
+ try {
162
+ // 如果sourcePath是数组,批量上传多个文件
163
+ if (Array.isArray(sourcePath)) {
164
+ for (const filePath of sourcePath) {
165
+ const result = await uploadFile({
166
+ bucketName,
167
+ filePath,
168
+ destination: destination ? `${destination}/${path.basename(filePath)}` : undefined,
169
+ storageClient,
170
+ enableCompression
171
+ });
172
+
173
+ if (result.success) {
174
+ results.success.push(result);
175
+ } else {
176
+ results.failed.push(result);
177
+ }
178
+ }
179
+ return results;
180
+ }
181
+
182
+ // 检查sourcePath是文件还是目录
183
+ const stats = await stat(sourcePath);
184
+
185
+ if (stats.isFile()) {
186
+ // 上传单个文件
187
+ const result = await uploadFile({
188
+ bucketName,
189
+ filePath: sourcePath,
190
+ destination,
191
+ storageClient,
192
+ enableCompression
193
+ });
194
+
195
+ if (result.success) {
196
+ results.success.push(result);
197
+ } else {
198
+ results.failed.push(result);
199
+ }
200
+ } else if (stats.isDirectory()) {
201
+ // 上传目录中的文件
202
+ const files = await readdir(sourcePath);
203
+
204
+ for (const file of files) {
205
+ const filePath = path.join(sourcePath, file);
206
+ const fileStats = await stat(filePath);
207
+
208
+ if (fileStats.isFile()) {
209
+ // 上传文件
210
+ const destPath = destination ? `${destination}/${file}` : file;
211
+
212
+ const result = await uploadFile({
213
+ bucketName,
214
+ filePath,
215
+ destination: destPath,
216
+ storageClient,
217
+ enableCompression
218
+ });
219
+
220
+ if (result.success) {
221
+ results.success.push(result);
222
+ } else {
223
+ results.failed.push(result);
224
+ }
225
+ } else if (recursive && fileStats.isDirectory()) {
226
+ // 递归上传子目录
227
+ const subDestination = destination ? `${destination}/${file}` : file;
228
+
229
+ const subResults = await uploadFiles({
230
+ bucketName,
231
+ sourcePath: filePath,
232
+ destination: subDestination,
233
+ storageClient,
234
+ recursive,
235
+ enableCompression
236
+ });
237
+
238
+ results.success = results.success.concat(subResults.success);
239
+ results.failed = results.failed.concat(subResults.failed);
240
+ }
241
+ }
242
+ }
243
+
244
+ return results;
245
+ } catch (error) {
246
+ throw new Error(`上传文件失败: ${error instanceof Error ? error.message : String(error)}`);
247
+ }
248
+ }
249
+
250
+ export {
251
+ createStorageClient,
252
+ uploadFile,
253
+ uploadFiles
254
+ };