@be-link/cos 1.12.0-beta.4 → 1.12.0-beta.6

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/README.md CHANGED
@@ -16,16 +16,56 @@
16
16
  ## 安装
17
17
 
18
18
  ```bash
19
- npm install @be-link/cos cos-js-sdk-v5 crypto-js
19
+ npm install @be-link/cos
20
+ # 或
21
+ pnpm add @be-link/cos
20
22
  ```
21
23
 
24
+ ### 依赖说明
25
+
26
+ | 依赖 | 用途 | 说明 |
27
+ | ------------------- | -------------- | -------------------------- |
28
+ | `cos-js-sdk-v5` | 浏览器端上传 | 自动打包进产物 |
29
+ | `cos-nodejs-sdk-v5` | CLI 命令行上传 | 安装时自动安装,运行时加载 |
30
+ | `crypto-js` | MD5 计算 | 自动打包进产物 |
31
+
32
+ > **说明**:所有依赖会在 `pnpm install` 时自动安装,无需手动处理。
33
+
22
34
  ---
23
35
 
24
36
  ## 快速开始
25
37
 
26
- ### Vue 项目
38
+ > **💡 提示**:本包提供两种使用方式
39
+ >
40
+ > 1. **全局单例模式**(`beLinkCOS`):向后兼容,适合全局统一配置
41
+ > 2. **类实例模式**(`BeLinkCOS`):推荐方式,支持多实例
42
+
43
+ ### 方式一:全局单例模式(向后兼容)
44
+
45
+ 适合全局统一配置的场景,在 `main.ts/main.tsx` 中初始化:
27
46
 
28
- **方式一:立即初始化**
47
+ ```typescript
48
+ // main.ts
49
+ import { beLinkCOS } from '@be-link/cos';
50
+
51
+ // 初始化全局单例
52
+ beLinkCOS.init({
53
+ mode: 'production',
54
+ projectName: 'be-link-live-h5', // 可选,特定项目使用专用桶
55
+ });
56
+
57
+ // 之后在任何地方都可以直接使用
58
+ // 组件中:
59
+ const result = await beLinkCOS.uploadFile(file);
60
+ ```
61
+
62
+ ### 方式二:类实例模式(推荐)
63
+
64
+ 支持创建多个实例,适合需要不同配置的场景:
65
+
66
+ #### Vue 项目
67
+
68
+ **立即初始化**
29
69
 
30
70
  ```vue
31
71
  <template>
@@ -72,7 +112,7 @@ const handleUpload = async () => {
72
112
  </script>
73
113
  ```
74
114
 
75
- **方式二:延迟初始化(支持异步配置)**
115
+ **延迟初始化(支持异步配置)**
76
116
 
77
117
  ```vue
78
118
  <script setup>
@@ -94,7 +134,7 @@ const handleUpload = async () => {
94
134
  </script>
95
135
  ```
96
136
 
97
- ### React 项目
137
+ #### React 项目
98
138
 
99
139
  ```jsx
100
140
  import { useState } from 'react';
@@ -135,7 +175,7 @@ function FileUploader() {
135
175
  }
136
176
  ```
137
177
 
138
- ### 原生 JS
178
+ #### 原生 JS
139
179
 
140
180
  ```html
141
181
  <!DOCTYPE html>
@@ -187,6 +227,49 @@ await Promise.all([devUploader.uploadFile(file), prodUploader.uploadFile(file)])
187
227
 
188
228
  ---
189
229
 
230
+ ## 特定项目桶配置
231
+
232
+ 某些项目需要使用专用的存储桶,通过 `projectName` 参数指定:
233
+
234
+ ### 浏览器端使用
235
+
236
+ ```typescript
237
+ import { BeLinkCOS } from '@be-link/cos';
238
+
239
+ // be-link-live-h5 项目使用专用桶
240
+ const uploader = new BeLinkCOS({
241
+ mode: 'production',
242
+ projectName: 'be-link-live-h5',
243
+ });
244
+
245
+ // 文件会自动上传到专用桶:
246
+ // - 桶名称: release-belink-1304510571
247
+ // - 桶地址: release-belink-1304510571.cos.ap-shanghai.myqcloud.com
248
+ // - 地域: ap-shanghai
249
+ await uploader.uploadFile(file);
250
+ ```
251
+
252
+ ### CLI 命令行使用
253
+
254
+ ```bash
255
+ # be-link-live-h5 项目上传
256
+ node_modules/.bin/cos production --project be-link-live-h5
257
+
258
+ # 或使用简写
259
+ node_modules/.bin/cos production -p be-link-live-h5
260
+
261
+ # 其他项目使用默认桶
262
+ node_modules/.bin/cos production
263
+ ```
264
+
265
+ ### 当前支持的特定项目配置
266
+
267
+ | 项目名称 | 环境 | 桶名称 | 地域 |
268
+ | ----------------- | ---------- | --------------------------- | ----------- |
269
+ | `be-link-live-h5` | production | `release-belink-1304510571` | ap-shanghai |
270
+
271
+ ---
272
+
190
273
  ## API 文档
191
274
 
192
275
  ### `new BeLinkCOS(config?)`
@@ -201,6 +284,7 @@ await Promise.all([devUploader.uploadFile(file), prodUploader.uploadFile(file)])
201
284
  **参数:**
202
285
 
203
286
  - `config.mode` - **必填** 环境模式:`'development'` | `'test'` | `'production'`
287
+ - `config.projectName` - 可选,项目标识,用于特定项目的专用桶配置(如 `'be-link-live-h5'`)
204
288
  - `config.headers` - 可选,自定义请求头
205
289
  - `config.ScopeLimit` - 可选,是否限制临时密钥范围(默认 `false`)
206
290
  - `config.debug` - 可选,是否开启调试模式(默认 `false`)
package/bin/cos.js CHANGED
@@ -1,9 +1,5 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
3
 
4
- const { staticUploadToCos } = require('./upload');
5
- function main() {
6
- staticUploadToCos();
7
- }
8
-
9
- main();
4
+ const { staticUploadToCos } = require('../dist/cli.cjs.js');
5
+ staticUploadToCos();
@@ -0,0 +1,67 @@
1
+ import { type EnvMode, InitConfig, UploadConfig, UploadResult } from '../shared';
2
+ /**
3
+ * BeLinkCOS 类 - 腾讯云 COS 文件上传工具(浏览器端)
4
+ *
5
+ * @example
6
+ * import { BeLinkCOS } from '@be-link/cos';
7
+ *
8
+ * const uploader = new BeLinkCOS({ mode: 'production' });
9
+ * await uploader.uploadFile(file);
10
+ */
11
+ export declare class BeLinkCOS {
12
+ /** COS SDK 实例 */
13
+ private cos;
14
+ /** COS 存储的基础路径 */
15
+ private basePath;
16
+ /** 自定义请求头 */
17
+ headers: Record<string, any>;
18
+ /** 当前环境模式 */
19
+ mode: EnvMode | null;
20
+ /** 项目名称 */
21
+ projectName: string | undefined;
22
+ /** 是否限制临时密钥的使用范围 */
23
+ private scopeLimit;
24
+ /** 生命周期状态 */
25
+ protected isInitialized: boolean;
26
+ protected isDestroyed: boolean;
27
+ /** 调试模式 */
28
+ private debug;
29
+ constructor(config?: InitConfig);
30
+ /**
31
+ * 初始化 COS 实例
32
+ */
33
+ init(config: InitConfig): void;
34
+ /**
35
+ * 更新 COS 配置
36
+ * @param config 要更新的配置项(可部分更新)
37
+ */
38
+ updateConfig(config: Partial<InitConfig>): void;
39
+ /**
40
+ * 初始化 COS SDK 实例
41
+ */
42
+ private initCOS;
43
+ protected checkState(methodName: string): boolean;
44
+ protected log(...args: unknown[]): void;
45
+ protected warn(...args: unknown[]): void;
46
+ protected error(...args: unknown[]): void;
47
+ protected validateConfig(config: InitConfig): boolean;
48
+ private getCurrentBucketConfig;
49
+ getConfig(): {
50
+ mode: EnvMode | null;
51
+ projectName: string | undefined;
52
+ headers: Record<string, any>;
53
+ scopeLimit: boolean;
54
+ } | null;
55
+ /**
56
+ * 计算文件的 MD5 哈希值
57
+ */
58
+ createFileMd5(file: File, chunkSize?: number): Promise<string>;
59
+ private getFilePath;
60
+ getSourceUrl(file: File, basePath?: string): Promise<string | null>;
61
+ /**
62
+ * 上传文件到腾讯云 COS
63
+ */
64
+ uploadFile(file: File, config?: UploadConfig): Promise<UploadResult>;
65
+ destroy(): void;
66
+ }
67
+ //# sourceMappingURL=BeLinkCOS.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BeLinkCOS.d.ts","sourceRoot":"","sources":["../../src/browser/BeLinkCOS.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,KAAK,OAAO,EAIZ,UAAU,EACV,YAAY,EACZ,YAAY,EACb,MAAM,WAAW,CAAC;AAEnB;;;;;;;;GAQG;AACH,qBAAa,SAAS;IACpB,iBAAiB;IACjB,OAAO,CAAC,GAAG,CAAyC;IACpD,kBAAkB;IAClB,OAAO,CAAC,QAAQ,CAAsC;IACtD,aAAa;IACN,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAM;IACzC,aAAa;IACN,IAAI,EAAE,OAAO,GAAG,IAAI,CAAQ;IACnC,WAAW;IACJ,WAAW,EAAE,MAAM,GAAG,SAAS,CAAa;IACnD,oBAAoB;IACpB,OAAO,CAAC,UAAU,CAAkB;IACpC,aAAa;IACb,SAAS,CAAC,aAAa,EAAE,OAAO,CAAS;IACzC,SAAS,CAAC,WAAW,EAAE,OAAO,CAAS;IACvC,WAAW;IACX,OAAO,CAAC,KAAK,CAAkB;gBAEnB,MAAM,CAAC,EAAE,UAAU;IAM/B;;OAEG;IACH,IAAI,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI;IA0B9B;;;OAGG;IACH,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,UAAU,CAAC,GAAG,IAAI;IAsD/C;;OAEG;YACW,OAAO;IAiCrB,SAAS,CAAC,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO;IAYjD,SAAS,CAAC,GAAG,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI;IAMvC,SAAS,CAAC,IAAI,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI;IAMxC,SAAS,CAAC,KAAK,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI;IAMzC,SAAS,CAAC,cAAc,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO;IAmBrD,OAAO,CAAC,sBAAsB;IAO9B,SAAS,IAAI;QACX,IAAI,EAAE,OAAO,GAAG,IAAI,CAAC;QACrB,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;QAChC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAC7B,UAAU,EAAE,OAAO,CAAC;KACrB,GAAG,IAAI;IAaR;;OAEG;IACI,aAAa,CAAC,IAAI,EAAE,IAAI,EAAE,SAAS,GAAE,MAAwB,GAAG,OAAO,CAAC,MAAM,CAAC;IA6DtF,OAAO,CAAC,WAAW;IAUN,YAAY,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAUhF;;OAEG;IACU,UAAU,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC;IAwCjF,OAAO,IAAI,IAAI;CAoBhB"}
@@ -0,0 +1,2 @@
1
+ export { BeLinkCOS } from './BeLinkCOS';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/browser/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { COSUploader, staticUploadToCos } from './upload';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC"}
@@ -0,0 +1,44 @@
1
+ import { type EnvMode } from '../shared';
2
+ interface UploadOptions {
3
+ mode: EnvMode;
4
+ projectName?: string;
5
+ }
6
+ /**
7
+ * CLI 上传工具类
8
+ */
9
+ export declare class COSUploader {
10
+ private mode;
11
+ private projectName?;
12
+ private cos;
13
+ private bucketConfig;
14
+ private region;
15
+ private projectRemoteUrl;
16
+ private distDirUrl;
17
+ constructor(options: UploadOptions);
18
+ /**
19
+ * 初始化 COS 实例
20
+ */
21
+ private initCOS;
22
+ /**
23
+ * 上传单个文件
24
+ */
25
+ private upload;
26
+ /**
27
+ * 递归获取所有文件
28
+ */
29
+ private getAllFiles;
30
+ /**
31
+ * 删除远程项目目录
32
+ */
33
+ private deleteProject;
34
+ /**
35
+ * 执行上传
36
+ */
37
+ run(): Promise<void>;
38
+ }
39
+ /**
40
+ * CLI 入口函数
41
+ */
42
+ export declare function staticUploadToCos(): Promise<void>;
43
+ export {};
44
+ //# sourceMappingURL=upload.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"upload.d.ts","sourceRoot":"","sources":["../../src/cli/upload.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,OAAO,EAAwE,MAAM,WAAW,CAAC;AAE/G,UAAU,aAAa;IACrB,IAAI,EAAE,OAAO,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAOD;;GAEG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,IAAI,CAAU;IACtB,OAAO,CAAC,WAAW,CAAC,CAAS;IAC7B,OAAO,CAAC,GAAG,CAAoB;IAC/B,OAAO,CAAC,YAAY,CAAqC;IACzD,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,gBAAgB,CAAS;IACjC,OAAO,CAAC,UAAU,CAAS;gBAEf,OAAO,EAAE,aAAa;IAelC;;OAEG;YACW,OAAO;IAuCrB;;OAEG;IACH,OAAO,CAAC,MAAM;IAyCd;;OAEG;IACH,OAAO,CAAC,WAAW;IAqBnB;;OAEG;IACH,OAAO,CAAC,aAAa;IA6CrB;;OAEG;IACG,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;CAiE3B;AA+BD;;GAEG;AACH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC,CAIvD"}
@@ -0,0 +1,342 @@
1
+ 'use strict';
2
+
3
+ var fs = require('fs');
4
+ var path = require('path');
5
+ var COS = require('cos-nodejs-sdk-v5');
6
+
7
+ /**
8
+ * 各环境的 COS 存储桶配置
9
+ * 根据环境自动切换到对应的存储桶
10
+ */
11
+ const BUCKETS_CONFIG = {
12
+ development: {
13
+ name: 'dev-1304510571',
14
+ host: 'dev-1304510571.cos.ap-nanjing.myqcloud.com',
15
+ protocol: 'https',
16
+ },
17
+ test: {
18
+ name: 'dev-1304510571',
19
+ host: 'dev-1304510571.cos.ap-nanjing.myqcloud.com',
20
+ protocol: 'https',
21
+ },
22
+ production: {
23
+ name: 'release-1304510571',
24
+ host: 'release-1304510571.file.myqcloud.com',
25
+ protocol: 'https',
26
+ },
27
+ };
28
+ /**
29
+ * 特定项目的自定义存储桶配置
30
+ * 优先级高于默认配置
31
+ */
32
+ const PROJECT_BUCKETS_CONFIG = {
33
+ 'be-link-live-h5': {
34
+ production: {
35
+ name: 'release-belink-ec-1304510571',
36
+ host: 'release-belink-ec-1304510571.cos.ap-nanjing.myqcloud.com',
37
+ protocol: 'https',
38
+ },
39
+ },
40
+ };
41
+ /**
42
+ * 获取存储桶配置
43
+ * 优先使用项目特定配置,如果没有则使用默认配置
44
+ */
45
+ function getBucketConfig(mode, projectName) {
46
+ if (projectName && PROJECT_BUCKETS_CONFIG[projectName]?.[mode]) {
47
+ return PROJECT_BUCKETS_CONFIG[projectName][mode];
48
+ }
49
+ return BUCKETS_CONFIG[mode];
50
+ }
51
+ /**
52
+ * 获取存储桶所在地域
53
+ * 根据桶配置动态判断地域
54
+ */
55
+ function getRegion(bucketConfig) {
56
+ if (bucketConfig.host.includes('ap-shanghai')) {
57
+ return 'ap-shanghai';
58
+ }
59
+ return 'ap-nanjing';
60
+ }
61
+ /**
62
+ * 云函数 URL 配置
63
+ * 用于获取 COS 临时密钥
64
+ */
65
+ const CLOUD_FUNCTION_URLS = {
66
+ production: 'https://shield-60660-10-1304510571.sh.run.tcloudbase.com/config/get-cos-temp-secret',
67
+ development: 'https://shield-74680-5-1304510571.sh.run.tcloudbase.com/config/get-cos-temp-secret',
68
+ test: 'https://shield-74680-5-1304510571.sh.run.tcloudbase.com/config/get-cos-temp-secret',
69
+ };
70
+
71
+ /**
72
+ * 通过云函数获取 COS 临时密钥
73
+ * 浏览器端和 Node CLI 共用
74
+ */
75
+ async function getTempCredentials(mode) {
76
+ const url = CLOUD_FUNCTION_URLS[mode];
77
+ const response = await fetch(url, { method: 'POST' });
78
+ const result = await response.json();
79
+ const data = result?.data || {};
80
+ const credentials = data?.credentials;
81
+ if (!credentials) {
82
+ throw new Error('获取临时密钥失败:返回数据格式错误');
83
+ }
84
+ return {
85
+ TmpSecretId: credentials.tmpSecretId,
86
+ TmpSecretKey: credentials.tmpSecretKey,
87
+ SecurityToken: credentials.sessionToken,
88
+ StartTime: data.startTime,
89
+ ExpiredTime: data.expiredTime,
90
+ };
91
+ }
92
+
93
+ /**
94
+ * CLI 上传工具类
95
+ */
96
+ class COSUploader {
97
+ constructor(options) {
98
+ this.cos = null;
99
+ this.mode = options.mode;
100
+ this.projectName = options.projectName;
101
+ this.bucketConfig = getBucketConfig(this.mode, this.projectName);
102
+ this.region = getRegion(this.bucketConfig);
103
+ // 获取项目名称和远程路径
104
+ const baseUrl = this.mode === 'production' ? 'project/prod/' : 'project/dev/';
105
+ const packageUrl = path.resolve(process.cwd(), './package.json');
106
+ const projectPackage = fs.readFileSync(packageUrl, { encoding: 'utf-8' });
107
+ const packageProjectName = JSON.parse(projectPackage).name;
108
+ this.projectRemoteUrl = baseUrl + packageProjectName;
109
+ this.distDirUrl = process.cwd() + '/dist';
110
+ }
111
+ /**
112
+ * 初始化 COS 实例
113
+ */
114
+ async initCOS() {
115
+ if (this.cos) {
116
+ return;
117
+ }
118
+ try {
119
+ console.log('🔐 正在获取临时密钥...');
120
+ const credentials = await getTempCredentials(this.mode);
121
+ console.log('✅ 临时密钥获取成功');
122
+ console.log(' 有效期至:', new Date(credentials.ExpiredTime * 1000).toLocaleString());
123
+ this.cos = new COS({
124
+ getAuthorization: (_options, callback) => {
125
+ callback(credentials);
126
+ },
127
+ });
128
+ }
129
+ catch (err) {
130
+ console.warn('⚠️ 临时密钥获取失败:', err.message);
131
+ console.warn('⚠️ 尝试使用环境变量 COS_SECRET_ID 和 COS_SECRET_KEY');
132
+ const secretId = process.env.COS_SECRET_ID;
133
+ const secretKey = process.env.COS_SECRET_KEY;
134
+ if (!secretId || !secretKey) {
135
+ console.error('❌ 错误:未找到 COS 密钥配置');
136
+ console.error(' 请设置环境变量:');
137
+ console.error(' - COS_SECRET_ID');
138
+ console.error(' - COS_SECRET_KEY');
139
+ process.exit(1);
140
+ }
141
+ console.log('✅ 使用环境变量中的密钥');
142
+ this.cos = new COS({
143
+ SecretId: secretId,
144
+ SecretKey: secretKey,
145
+ });
146
+ }
147
+ }
148
+ /**
149
+ * 上传单个文件
150
+ */
151
+ upload(url, filename, retryCount = 0, maxRetries = 3) {
152
+ return new Promise((resolve, reject) => {
153
+ if (!url || !filename) {
154
+ reject(new Error('上传参数错误:url 和 filename 不能为空'));
155
+ return;
156
+ }
157
+ this.cos.putObject({
158
+ Bucket: this.bucketConfig.name,
159
+ Region: this.region,
160
+ Key: this.projectRemoteUrl + filename,
161
+ StorageClass: 'STANDARD',
162
+ ACL: 'public-read',
163
+ Body: fs.createReadStream(url),
164
+ }, (err, data) => {
165
+ if (err) {
166
+ console.log(`❕ 上传失败: ${JSON.stringify(err)}`);
167
+ if (retryCount < maxRetries) {
168
+ console.log(` ⚠️ 上传失败,正在重试 (${retryCount + 1}/${maxRetries}): ${filename}`);
169
+ setTimeout(() => {
170
+ this.upload(url, filename, retryCount + 1, maxRetries)
171
+ .then(resolve)
172
+ .catch(reject);
173
+ }, 1000 * (retryCount + 1));
174
+ }
175
+ else {
176
+ console.error(` ❌ 上传失败(已重试 ${maxRetries} 次): ${filename}`, err.message);
177
+ reject(err);
178
+ }
179
+ }
180
+ else {
181
+ resolve(data);
182
+ }
183
+ });
184
+ });
185
+ }
186
+ /**
187
+ * 递归获取所有文件
188
+ */
189
+ getAllFiles(directoryPath) {
190
+ const files = fs.readdirSync(directoryPath, { withFileTypes: true });
191
+ const fileNamesAndPaths = [];
192
+ files.forEach((file) => {
193
+ const filePath = path.join(directoryPath, file.name);
194
+ if (file.isFile()) {
195
+ fileNamesAndPaths.push({
196
+ fileName: file.name,
197
+ filePath: filePath,
198
+ });
199
+ }
200
+ else if (file.isDirectory()) {
201
+ const subDirectoryFiles = this.getAllFiles(filePath);
202
+ fileNamesAndPaths.push(...subDirectoryFiles);
203
+ }
204
+ });
205
+ return fileNamesAndPaths;
206
+ }
207
+ /**
208
+ * 删除远程项目目录
209
+ */
210
+ deleteProject() {
211
+ return new Promise((resolve, reject) => {
212
+ this.cos.getBucket({
213
+ Bucket: this.bucketConfig.name,
214
+ Region: this.region,
215
+ Prefix: this.projectRemoteUrl,
216
+ }, (err, data) => {
217
+ if (err) {
218
+ console.log('⚠️ 获取远程文件列表失败(可能目录不存在,继续上传)');
219
+ resolve(null);
220
+ return;
221
+ }
222
+ const files = data.Contents || [];
223
+ if (files.length === 0) {
224
+ console.log('📁 远程目录为空,无需删除');
225
+ resolve(null);
226
+ return;
227
+ }
228
+ console.log(`🗑️ 正在删除 ${files.length} 个旧文件...`);
229
+ this.cos.deleteMultipleObject({
230
+ Bucket: this.bucketConfig.name,
231
+ Region: this.region,
232
+ Objects: files.map((file) => ({ Key: file.Key })),
233
+ }, (err, data) => {
234
+ if (err) {
235
+ console.error('❌ 删除失败:', err.message);
236
+ reject(err);
237
+ }
238
+ else {
239
+ console.log(`✅ 成功删除 ${files.length} 个旧文件`);
240
+ resolve(data);
241
+ }
242
+ });
243
+ });
244
+ });
245
+ }
246
+ /**
247
+ * 执行上传
248
+ */
249
+ async run() {
250
+ const startTime = Date.now();
251
+ const packageUrl = path.resolve(process.cwd(), './package.json');
252
+ const projectPackage = fs.readFileSync(packageUrl, { encoding: 'utf-8' });
253
+ const packageProjectName = JSON.parse(projectPackage).name;
254
+ console.log('\n' + '='.repeat(60));
255
+ console.log('📦 开始上传静态资源到 COS');
256
+ console.log('='.repeat(60));
257
+ console.log(`📂 项目名称: ${packageProjectName}`);
258
+ console.log(`🌍 环境模式: ${this.mode}`);
259
+ console.log(`🪣 存储桶: ${this.bucketConfig.name} (${this.region})`);
260
+ if (this.projectName) {
261
+ console.log(`🎯 项目标识: ${this.projectName}`);
262
+ }
263
+ console.log(`📍 远程路径: ${this.projectRemoteUrl}`);
264
+ console.log(`📁 本地目录: ${this.distDirUrl}`);
265
+ console.log('='.repeat(60) + '\n');
266
+ try {
267
+ await this.initCOS();
268
+ if (!fs.existsSync(this.distDirUrl)) {
269
+ throw new Error(`本地目录不存在: ${this.distDirUrl}`);
270
+ }
271
+ const files = this.getAllFiles(this.distDirUrl);
272
+ if (files.length === 0) {
273
+ console.log('⚠️ 警告:没有找到需要上传的文件');
274
+ return;
275
+ }
276
+ console.log(`📋 找到 ${files.length} 个文件待上传\n`);
277
+ await this.deleteProject();
278
+ console.log('\n📤 开始上传文件...\n');
279
+ const uploadPromises = files.map((file) => {
280
+ const filename = file.filePath.replace(this.distDirUrl, '');
281
+ return this.upload(file.filePath, filename);
282
+ });
283
+ const results = await Promise.all(uploadPromises);
284
+ const endTime = Date.now();
285
+ const duration = ((endTime - startTime) / 1000).toFixed(2);
286
+ console.log('\n' + '='.repeat(60));
287
+ console.log('🎉 上传完成!');
288
+ console.log('='.repeat(60));
289
+ console.log(`✅ 成功上传: ${results.length} 个文件`);
290
+ console.log(`⏱️ 耗时: ${duration} 秒`);
291
+ console.log(`🔗 访问地址: https://${this.bucketConfig.host}/${this.projectRemoteUrl}`);
292
+ console.log('='.repeat(60) + '\n');
293
+ process.exit(0);
294
+ }
295
+ catch (err) {
296
+ console.error('\n' + '='.repeat(60));
297
+ console.error('💥 上传失败!');
298
+ console.error('='.repeat(60));
299
+ console.error('❌ 错误信息:', err.message);
300
+ console.error('='.repeat(60) + '\n');
301
+ process.exit(1);
302
+ }
303
+ }
304
+ }
305
+ /**
306
+ * 解析命令行参数
307
+ */
308
+ function parseArgs() {
309
+ const argv = process.argv.slice(2);
310
+ let mode = 'development';
311
+ let projectName = undefined;
312
+ for (let i = 0; i < argv.length; i++) {
313
+ const arg = argv[i];
314
+ if (arg === '--project' || arg === '-p') {
315
+ projectName = argv[i + 1];
316
+ i++;
317
+ }
318
+ else if (arg === '--mode' || arg === '-m') {
319
+ mode = argv[i + 1];
320
+ i++;
321
+ }
322
+ else if (!arg.startsWith('-') && ['development', 'test', 'production'].includes(arg)) {
323
+ mode = arg;
324
+ }
325
+ }
326
+ console.log('🚀 上传模式:', mode);
327
+ if (projectName) {
328
+ console.log('📦 项目标识:', projectName);
329
+ }
330
+ return { mode, projectName };
331
+ }
332
+ /**
333
+ * CLI 入口函数
334
+ */
335
+ async function staticUploadToCos() {
336
+ const options = parseArgs();
337
+ const uploader = new COSUploader(options);
338
+ await uploader.run();
339
+ }
340
+
341
+ exports.COSUploader = COSUploader;
342
+ exports.staticUploadToCos = staticUploadToCos;