@be-link/cos 1.12.0-beta.2 → 1.12.0-beta.3
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/bin/cos.js +9 -0
- package/bin/upload.js +330 -0
- package/dist/beLinkCos.d.ts.map +1 -1
- package/package.json +15 -10
package/bin/cos.js
ADDED
package/bin/upload.js
ADDED
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const https = require('https');
|
|
7
|
+
const COS = require('cos-nodejs-sdk-v5');
|
|
8
|
+
const { BUCKETS_CONFIG, REGION } = require('../dist/index.cjs.js');
|
|
9
|
+
|
|
10
|
+
const argv = process.argv.slice(2);
|
|
11
|
+
const [mode] = argv;
|
|
12
|
+
console.log('🚀 上传模式:', mode || 'development');
|
|
13
|
+
|
|
14
|
+
// 获取项目名称
|
|
15
|
+
const baseUrl = mode === 'production' ? 'project/prod/' : 'project/dev/';
|
|
16
|
+
const packageUrl = path.resolve(process.cwd(), './package.json');
|
|
17
|
+
const projectPackage = fs.readFileSync(packageUrl, { encoding: 'utf-8' });
|
|
18
|
+
const projectName = JSON.parse(projectPackage).name;
|
|
19
|
+
const projectRemoteUrl = baseUrl + projectName;
|
|
20
|
+
|
|
21
|
+
// 构建参数输出的地址
|
|
22
|
+
const distDirUrl = process.cwd() + '/dist';
|
|
23
|
+
|
|
24
|
+
// COS 实例(延迟初始化)
|
|
25
|
+
let cos = null;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* 通过云函数获取临时密钥(参照浏览器端实现)
|
|
29
|
+
*/
|
|
30
|
+
function getTempCredentials(mode) {
|
|
31
|
+
return new Promise((resolve, reject) => {
|
|
32
|
+
const url =
|
|
33
|
+
mode === 'production'
|
|
34
|
+
? 'https://shield-60660-10-1304510571.sh.run.tcloudbase.com/config/get-cos-temp-secret'
|
|
35
|
+
: 'https://shield-74680-5-1304510571.sh.run.tcloudbase.com/config/get-cos-temp-secret';
|
|
36
|
+
|
|
37
|
+
const urlObj = new URL(url);
|
|
38
|
+
const options = {
|
|
39
|
+
hostname: urlObj.hostname,
|
|
40
|
+
path: urlObj.pathname,
|
|
41
|
+
method: 'POST',
|
|
42
|
+
headers: {
|
|
43
|
+
'Content-Type': 'application/json',
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const req = https.request(options, (res) => {
|
|
48
|
+
let data = '';
|
|
49
|
+
|
|
50
|
+
res.on('data', (chunk) => {
|
|
51
|
+
data += chunk;
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
res.on('end', () => {
|
|
55
|
+
try {
|
|
56
|
+
const result = JSON.parse(data);
|
|
57
|
+
const credentials = result?.data?.credentials;
|
|
58
|
+
|
|
59
|
+
if (!credentials) {
|
|
60
|
+
reject(new Error('获取临时密钥失败:返回数据格式错误'));
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
resolve({
|
|
65
|
+
TmpSecretId: credentials.tmpSecretId,
|
|
66
|
+
TmpSecretKey: credentials.tmpSecretKey,
|
|
67
|
+
SecurityToken: credentials.sessionToken,
|
|
68
|
+
StartTime: result.data.startTime,
|
|
69
|
+
ExpiredTime: result.data.expiredTime,
|
|
70
|
+
});
|
|
71
|
+
} catch (err) {
|
|
72
|
+
reject(new Error('解析临时密钥响应失败:' + err.message));
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
req.on('error', (err) => {
|
|
78
|
+
reject(new Error('请求临时密钥失败:' + err.message));
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
req.end();
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* 初始化 COS 实例
|
|
87
|
+
* 优先使用云函数临时密钥,降级到环境变量
|
|
88
|
+
*/
|
|
89
|
+
async function initCOS() {
|
|
90
|
+
if (cos) {
|
|
91
|
+
return cos;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
// 方式1:尝试通过云函数获取临时密钥(推荐)
|
|
96
|
+
console.log('🔐 正在获取临时密钥...');
|
|
97
|
+
const credentials = await getTempCredentials(mode || 'development');
|
|
98
|
+
console.log('✅ 临时密钥获取成功,有效期至:', new Date(credentials.ExpiredTime * 1000).toLocaleString());
|
|
99
|
+
|
|
100
|
+
cos = new COS({
|
|
101
|
+
getAuthorization: (options, callback) => {
|
|
102
|
+
callback({
|
|
103
|
+
TmpSecretId: credentials.TmpSecretId,
|
|
104
|
+
TmpSecretKey: credentials.TmpSecretKey,
|
|
105
|
+
SecurityToken: credentials.SecurityToken,
|
|
106
|
+
StartTime: credentials.StartTime,
|
|
107
|
+
ExpiredTime: credentials.ExpiredTime,
|
|
108
|
+
});
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
return cos;
|
|
113
|
+
} catch (err) {
|
|
114
|
+
// 方式2:降级到环境变量(用于本地开发或特殊情况)
|
|
115
|
+
console.warn('⚠️ 临时密钥获取失败:', err.message);
|
|
116
|
+
console.warn('⚠️ 尝试使用环境变量 COS_SECRET_ID 和 COS_SECRET_KEY');
|
|
117
|
+
|
|
118
|
+
const secretId = process.env.COS_SECRET_ID;
|
|
119
|
+
const secretKey = process.env.COS_SECRET_KEY;
|
|
120
|
+
|
|
121
|
+
if (!secretId || !secretKey) {
|
|
122
|
+
console.error('❌ 错误:未找到 COS 密钥配置');
|
|
123
|
+
console.error(' 请设置环境变量:');
|
|
124
|
+
console.error(' - COS_SECRET_ID');
|
|
125
|
+
console.error(' - COS_SECRET_KEY');
|
|
126
|
+
process.exit(1);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
console.log('✅ 使用环境变量中的密钥');
|
|
130
|
+
cos = new COS({
|
|
131
|
+
SecretId: secretId,
|
|
132
|
+
SecretKey: secretKey,
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
return cos;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* 上传单个文件(支持 Promise 和重试)
|
|
141
|
+
*/
|
|
142
|
+
function upload(url, filename, retryCount = 0, maxRetries = 3) {
|
|
143
|
+
return new Promise((resolve, reject) => {
|
|
144
|
+
if (!url || !filename) {
|
|
145
|
+
reject(new Error('上传参数错误:url 和 filename 不能为空'));
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
cos.putObject(
|
|
150
|
+
{
|
|
151
|
+
Bucket: BUCKETS_CONFIG[mode || 'development'].name,
|
|
152
|
+
Region: REGION,
|
|
153
|
+
Key: projectRemoteUrl + filename /* 必须 */,
|
|
154
|
+
StorageClass: 'STANDARD',
|
|
155
|
+
ACL: 'public-read', // 设置为公开读,确保文件可访问
|
|
156
|
+
Body: fs.createReadStream(url), // 上传文件对象
|
|
157
|
+
// onProgress: function (progressData) {
|
|
158
|
+
// // 可选:显示上传进度
|
|
159
|
+
// // console.log('上传进度:', JSON.stringify(progressData))
|
|
160
|
+
// },
|
|
161
|
+
},
|
|
162
|
+
function (err, data) {
|
|
163
|
+
if (err) {
|
|
164
|
+
// 重试逻辑
|
|
165
|
+
if (retryCount < maxRetries) {
|
|
166
|
+
console.log(` ⚠️ 上传失败,正在重试 (${retryCount + 1}/${maxRetries}): ${filename}`);
|
|
167
|
+
setTimeout(
|
|
168
|
+
() => {
|
|
169
|
+
upload(url, filename, retryCount + 1, maxRetries)
|
|
170
|
+
.then(resolve)
|
|
171
|
+
.catch(reject);
|
|
172
|
+
},
|
|
173
|
+
1000 * (retryCount + 1),
|
|
174
|
+
); // 递增延迟
|
|
175
|
+
} else {
|
|
176
|
+
console.error(` ❌ 上传失败(已重试 ${maxRetries} 次): ${filename}`, err.message);
|
|
177
|
+
reject(err);
|
|
178
|
+
}
|
|
179
|
+
} else {
|
|
180
|
+
console.log(` ✅ 上传成功: ${filename}`);
|
|
181
|
+
resolve(data);
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
);
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function getAllFiles(directoryPath) {
|
|
189
|
+
const files = fs.readdirSync(directoryPath, { withFileTypes: true });
|
|
190
|
+
const fileNamesAndPaths = [];
|
|
191
|
+
|
|
192
|
+
files.forEach((file) => {
|
|
193
|
+
const filePath = path.join(directoryPath, file.name);
|
|
194
|
+
|
|
195
|
+
if (file.isFile()) {
|
|
196
|
+
fileNamesAndPaths.push({
|
|
197
|
+
fileName: file.name,
|
|
198
|
+
filePath: filePath,
|
|
199
|
+
});
|
|
200
|
+
} else if (file.isDirectory()) {
|
|
201
|
+
const subDirectoryFiles = getAllFiles(filePath);
|
|
202
|
+
fileNamesAndPaths.push(...subDirectoryFiles);
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
return fileNamesAndPaths;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* 删除远程项目目录
|
|
211
|
+
* 注意:deleteObject 只能删除单个文件,不能删除目录
|
|
212
|
+
* 如需删除整个目录,需要先列出所有文件再逐个删除
|
|
213
|
+
*/
|
|
214
|
+
function deleteProject() {
|
|
215
|
+
return new Promise((resolve, reject) => {
|
|
216
|
+
// 先获取目录下的所有文件
|
|
217
|
+
cos.getBucket(
|
|
218
|
+
{
|
|
219
|
+
Bucket: BUCKETS_CONFIG[mode || 'development'].name,
|
|
220
|
+
Region: REGION,
|
|
221
|
+
Prefix: projectRemoteUrl,
|
|
222
|
+
},
|
|
223
|
+
function (err, data) {
|
|
224
|
+
if (err) {
|
|
225
|
+
console.log('⚠️ 获取远程文件列表失败(可能目录不存在,继续上传)');
|
|
226
|
+
resolve([]); // 目录不存在时继续执行
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const files = data.Contents || [];
|
|
231
|
+
if (files.length === 0) {
|
|
232
|
+
console.log('📁 远程目录为空,无需删除');
|
|
233
|
+
resolve([]);
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
console.log(`🗑️ 正在删除 ${files.length} 个旧文件...`);
|
|
238
|
+
|
|
239
|
+
// 批量删除文件
|
|
240
|
+
cos.deleteMultipleObject(
|
|
241
|
+
{
|
|
242
|
+
Bucket: BUCKETS_CONFIG[mode || 'development'].name,
|
|
243
|
+
Region: REGION,
|
|
244
|
+
Objects: files.map((file) => ({ Key: file.Key })),
|
|
245
|
+
},
|
|
246
|
+
function (err, data) {
|
|
247
|
+
if (err) {
|
|
248
|
+
console.error('❌ 删除失败:', err.message);
|
|
249
|
+
reject(err);
|
|
250
|
+
} else {
|
|
251
|
+
console.log(`✅ 成功删除 ${files.length} 个旧文件`);
|
|
252
|
+
resolve(data);
|
|
253
|
+
}
|
|
254
|
+
},
|
|
255
|
+
);
|
|
256
|
+
},
|
|
257
|
+
);
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* 主函数:上传静态资源到 COS
|
|
263
|
+
*/
|
|
264
|
+
async function staticUploadToCos() {
|
|
265
|
+
const startTime = Date.now();
|
|
266
|
+
console.log('\n' + '='.repeat(60));
|
|
267
|
+
console.log('📦 开始上传静态资源到 COS');
|
|
268
|
+
console.log('='.repeat(60));
|
|
269
|
+
console.log(`📂 项目名称: ${projectName}`);
|
|
270
|
+
console.log(`🌍 环境模式: ${mode || 'development'}`);
|
|
271
|
+
console.log(`📍 远程路径: ${projectRemoteUrl}`);
|
|
272
|
+
console.log(`📁 本地目录: ${distDirUrl}`);
|
|
273
|
+
console.log('='.repeat(60) + '\n');
|
|
274
|
+
|
|
275
|
+
try {
|
|
276
|
+
// 1. 初始化 COS
|
|
277
|
+
await initCOS();
|
|
278
|
+
|
|
279
|
+
// 2. 检查本地文件
|
|
280
|
+
if (!fs.existsSync(distDirUrl)) {
|
|
281
|
+
throw new Error(`本地目录不存在: ${distDirUrl}`);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const files = getAllFiles(distDirUrl);
|
|
285
|
+
if (files.length === 0) {
|
|
286
|
+
console.log('⚠️ 警告:没有找到需要上传的文件');
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
console.log(`📋 找到 ${files.length} 个文件待上传\n`);
|
|
291
|
+
|
|
292
|
+
// 3. 删除远程旧文件
|
|
293
|
+
await deleteProject();
|
|
294
|
+
|
|
295
|
+
// 4. 上传所有文件
|
|
296
|
+
console.log('\n📤 开始上传文件...\n');
|
|
297
|
+
const uploadPromises = files.map((file) => {
|
|
298
|
+
const filename = file.filePath.replace(distDirUrl, '');
|
|
299
|
+
return upload(file.filePath, filename);
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
// 等待所有文件上传完成
|
|
303
|
+
const results = await Promise.all(uploadPromises);
|
|
304
|
+
|
|
305
|
+
// 5. 输出结果
|
|
306
|
+
const endTime = Date.now();
|
|
307
|
+
const duration = ((endTime - startTime) / 1000).toFixed(2);
|
|
308
|
+
|
|
309
|
+
console.log('\n' + '='.repeat(60));
|
|
310
|
+
console.log('🎉 上传完成!');
|
|
311
|
+
console.log('='.repeat(60));
|
|
312
|
+
console.log(`✅ 成功上传: ${results.length} 个文件`);
|
|
313
|
+
console.log(`⏱️ 耗时: ${duration} 秒`);
|
|
314
|
+
console.log(`🔗 访问地址: https://${BUCKETS_CONFIG[mode || 'development'].host}/${projectRemoteUrl}`);
|
|
315
|
+
console.log('='.repeat(60) + '\n');
|
|
316
|
+
|
|
317
|
+
process.exit(0);
|
|
318
|
+
} catch (err) {
|
|
319
|
+
console.error('\n' + '='.repeat(60));
|
|
320
|
+
console.error('💥 上传失败!');
|
|
321
|
+
console.error('='.repeat(60));
|
|
322
|
+
console.error('❌ 错误信息:', err.message);
|
|
323
|
+
console.error('='.repeat(60) + '\n');
|
|
324
|
+
process.exit(1);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
module.exports = {
|
|
329
|
+
staticUploadToCos,
|
|
330
|
+
};
|
package/dist/beLinkCos.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"beLinkCos.d.ts","sourceRoot":"","sources":["../src/beLinkCos.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"beLinkCos.d.ts","sourceRoot":"","sources":["../src/beLinkCos.ts"],"names":[],"mappings":"AAGA,OAAO,EAA0B,KAAK,OAAO,EAAE,MAAM,UAAU,CAAC;AAChE,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEtE;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,SAAS;IACpB,iBAAiB;IACjB,OAAO,CAAC,GAAG,CAAa;IACxB,kBAAkB;IAClB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAsC;IAC/D,aAAa;IACN,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAM;IACzC,aAAa;IACN,IAAI,EAAE,OAAO,GAAG,IAAI,CAAQ;IACnC,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;IAE/B;;;;;;;;;;;;;;;;;;;;OAoBG;gBACS,MAAM,CAAC,EAAE,UAAU;IAO/B;;;;OAIG;IACH,IAAI,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI;IAyB9B;;;;;;;OAOG;YACW,OAAO;IAqCrB;;OAEG;IACH,SAAS,CAAC,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO;IAYjD;;OAEG;IACH,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;;OAEG;IACH,SAAS,CAAC,cAAc,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO;IAmBrD;;OAEG;IACH,SAAS,IAAI;QAAE,IAAI,EAAE,OAAO,GAAG,IAAI,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAAC,UAAU,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI;IAY/F;;;;;;;;;;;;;;OAcG;IACI,aAAa,CAAC,IAAI,EAAE,IAAI,EAAE,SAAS,GAAE,MAAwB,GAAG,OAAO,CAAC,MAAM,CAAC;IA+DtF;;;;;;;;OAQG;IACH,OAAO,CAAC,WAAW;IASnB;;;;;;;;;;;OAWG;IACU,YAAY,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAS7D;;;;;;;;;;;;;;;;;;;;OAoBG;IACU,UAAU,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC;IAsCjF;;OAEG;IACH,OAAO,IAAI,IAAI;CAsBhB"}
|
package/package.json
CHANGED
|
@@ -1,16 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@be-link/cos",
|
|
3
|
-
"version": "1.12.0-beta.
|
|
3
|
+
"version": "1.12.0-beta.3",
|
|
4
4
|
"description": "前端项目产物上传cos",
|
|
5
|
-
"author": "zhuifeng <yangyiboys@163.com>",
|
|
6
5
|
"homepage": "https://github.com/snowmountain-top/be-link#readme",
|
|
6
|
+
"author": "zhuiyi",
|
|
7
7
|
"license": "ISC",
|
|
8
|
-
"main": "dist/index.cjs.js",
|
|
9
|
-
"module": "dist/index.esm.js",
|
|
10
|
-
"types": "dist/index.d.ts",
|
|
11
|
-
"files": [
|
|
12
|
-
"dist"
|
|
13
|
-
],
|
|
14
8
|
"repository": {
|
|
15
9
|
"type": "git",
|
|
16
10
|
"url": "git+https://github.com/snowmountain-top/be-link.git"
|
|
@@ -18,17 +12,28 @@
|
|
|
18
12
|
"bugs": {
|
|
19
13
|
"url": "https://github.com/snowmountain-top/be-link/issues"
|
|
20
14
|
},
|
|
15
|
+
"main": "dist/index.cjs.js",
|
|
16
|
+
"module": "dist/index.esm.js",
|
|
17
|
+
"types": "dist/index.d.ts",
|
|
18
|
+
"files": [
|
|
19
|
+
"dist",
|
|
20
|
+
"bin"
|
|
21
|
+
],
|
|
22
|
+
"bin": {
|
|
23
|
+
"cos": "./bin/cos.js"
|
|
24
|
+
},
|
|
21
25
|
"peerDependencies": {
|
|
22
26
|
"cos-js-sdk-v5": "^1.8.6",
|
|
23
27
|
"crypto-js": "^4.2.0"
|
|
24
28
|
},
|
|
25
29
|
"devDependencies": {
|
|
26
|
-
"@types/crypto-js": "^4.2.2"
|
|
30
|
+
"@types/crypto-js": "^4.2.2",
|
|
31
|
+
"cos-js-sdk-v5": "^1.8.6",
|
|
32
|
+
"crypto-js": "^4.2.0"
|
|
27
33
|
},
|
|
28
34
|
"publishConfig": {
|
|
29
35
|
"access": "public"
|
|
30
36
|
},
|
|
31
|
-
"gitHead": "76b71bb6850e5f57f1a1ba4bd17f2ae0f603b67e",
|
|
32
37
|
"scripts": {
|
|
33
38
|
"clean": "rimraf ./dist && rimraf .rollup.cache",
|
|
34
39
|
"build:js": "rollup --config rollup.config.ts --configPlugin typescript",
|