vite-plugin-deploy-oss 1.2.3 → 2.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/README.md +15 -0
- package/dist/index.d.mts +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +242 -58
- package/dist/index.mjs +242 -58
- package/package.json +22 -13
package/README.md
CHANGED
|
@@ -24,15 +24,30 @@ export default {
|
|
|
24
24
|
plugins: [
|
|
25
25
|
// 在最后一个插件中使用
|
|
26
26
|
vitePluginDeployOss({
|
|
27
|
+
// 建议按环境变量开关上传,避免本地/CI误上传
|
|
28
|
+
open: process.env.DEPLOY_OSS === '1',
|
|
29
|
+
// 终端实时动效进度面板(默认 true)
|
|
30
|
+
fancy: true,
|
|
31
|
+
|
|
27
32
|
accessKeyId: '***',
|
|
28
33
|
accessKeySecret: '***',
|
|
29
34
|
bucket: '***',
|
|
30
35
|
region: '***',
|
|
31
36
|
uploadDir: `H5/zz/test`,
|
|
32
37
|
skip: ['**/index.html'],
|
|
38
|
+
|
|
39
|
+
// 默认 true:有上传失败时抛错并让构建失败
|
|
40
|
+
failOnError: true,
|
|
41
|
+
|
|
33
42
|
// 修改打包后的资源路径
|
|
34
43
|
configBase: `https://oss.eventnet.cn/H5/zz/test/`,
|
|
35
44
|
}),
|
|
36
45
|
],
|
|
37
46
|
}
|
|
38
47
|
```
|
|
48
|
+
|
|
49
|
+
## 说明
|
|
50
|
+
|
|
51
|
+
- `open` 默认 `true`,建议通过环境变量控制开关(例如 `DEPLOY_OSS=1` 时再上传)。
|
|
52
|
+
- `fancy` 默认 `true`,TTY 终端下会显示实时动效进度(速度、预计剩余、并发、当前文件)。
|
|
53
|
+
- `failOnError` 默认 `true`,上传有失败会抛错,适合 CI 场景保证发布质量。
|
package/dist/index.d.mts
CHANGED
|
@@ -14,9 +14,12 @@ interface vitePluginDeployOssOption extends Omit<oss.Options, 'accessKeyId' | 'a
|
|
|
14
14
|
autoDelete?: boolean;
|
|
15
15
|
skip?: string | string[];
|
|
16
16
|
open?: boolean;
|
|
17
|
+
fancy?: boolean;
|
|
17
18
|
noCache?: boolean;
|
|
19
|
+
failOnError?: boolean;
|
|
18
20
|
concurrency?: number;
|
|
19
21
|
retryTimes?: number;
|
|
22
|
+
multipartThreshold?: number;
|
|
20
23
|
}
|
|
21
24
|
declare function vitePluginDeployOss(option: vitePluginDeployOssOption): Plugin;
|
|
22
25
|
|
package/dist/index.d.ts
CHANGED
|
@@ -14,9 +14,12 @@ interface vitePluginDeployOssOption extends Omit<oss.Options, 'accessKeyId' | 'a
|
|
|
14
14
|
autoDelete?: boolean;
|
|
15
15
|
skip?: string | string[];
|
|
16
16
|
open?: boolean;
|
|
17
|
+
fancy?: boolean;
|
|
17
18
|
noCache?: boolean;
|
|
19
|
+
failOnError?: boolean;
|
|
18
20
|
concurrency?: number;
|
|
19
21
|
retryTimes?: number;
|
|
22
|
+
multipartThreshold?: number;
|
|
20
23
|
}
|
|
21
24
|
declare function vitePluginDeployOss(option: vitePluginDeployOssOption): Plugin;
|
|
22
25
|
|
package/dist/index.js
CHANGED
|
@@ -37,10 +37,50 @@ var import_ali_oss = __toESM(require("ali-oss"));
|
|
|
37
37
|
var import_chalk = __toESM(require("chalk"));
|
|
38
38
|
var import_delete_empty = __toESM(require("delete-empty"));
|
|
39
39
|
var import_glob = require("glob");
|
|
40
|
-
var
|
|
40
|
+
var import_promises = require("fs/promises");
|
|
41
41
|
var import_node_path = require("path");
|
|
42
42
|
var import_ora = __toESM(require("ora"));
|
|
43
43
|
var import_vite = require("vite");
|
|
44
|
+
var normalizeObjectKey = (targetDir, relativeFilePath) => (0, import_vite.normalizePath)(`${targetDir}/${relativeFilePath}`).replace(/\/{2,}/g, "/").replace(/^\/+/, "");
|
|
45
|
+
var formatBytes = (bytes) => {
|
|
46
|
+
if (!Number.isFinite(bytes) || bytes <= 0) return "0 B";
|
|
47
|
+
const units = ["B", "KB", "MB", "GB", "TB"];
|
|
48
|
+
let value = bytes;
|
|
49
|
+
let unitIndex = 0;
|
|
50
|
+
while (value >= 1024 && unitIndex < units.length - 1) {
|
|
51
|
+
value /= 1024;
|
|
52
|
+
unitIndex++;
|
|
53
|
+
}
|
|
54
|
+
const digits = value >= 100 || unitIndex === 0 ? 0 : 1;
|
|
55
|
+
return `${value.toFixed(digits)} ${units[unitIndex]}`;
|
|
56
|
+
};
|
|
57
|
+
var formatDuration = (seconds) => {
|
|
58
|
+
if (!Number.isFinite(seconds) || seconds < 0) return "--";
|
|
59
|
+
const rounded = Math.round(seconds);
|
|
60
|
+
const mins = Math.floor(rounded / 60);
|
|
61
|
+
const secs = rounded % 60;
|
|
62
|
+
if (mins === 0) return `${secs}s`;
|
|
63
|
+
return `${mins}m${String(secs).padStart(2, "0")}s`;
|
|
64
|
+
};
|
|
65
|
+
var trimMiddle = (text, maxLength) => {
|
|
66
|
+
if (text.length <= maxLength) return text;
|
|
67
|
+
if (maxLength <= 10) return text.slice(0, maxLength);
|
|
68
|
+
const leftLength = Math.floor((maxLength - 3) / 2);
|
|
69
|
+
const rightLength = maxLength - 3 - leftLength;
|
|
70
|
+
return `${text.slice(0, leftLength)}...${text.slice(-rightLength)}`;
|
|
71
|
+
};
|
|
72
|
+
var buildCapsuleBar = (ratio, width = 30) => {
|
|
73
|
+
const safeRatio = Math.max(0, Math.min(1, ratio));
|
|
74
|
+
if (width <= 0) return "";
|
|
75
|
+
if (safeRatio >= 1) {
|
|
76
|
+
return import_chalk.default.green("\u2588".repeat(width));
|
|
77
|
+
}
|
|
78
|
+
const pointerIndex = Math.min(width - 1, Math.floor(width * safeRatio));
|
|
79
|
+
const done = pointerIndex > 0 ? import_chalk.default.green("\u2588".repeat(pointerIndex)) : "";
|
|
80
|
+
const pointer = import_chalk.default.cyanBright("\u25B8");
|
|
81
|
+
const pending = pointerIndex < width - 1 ? import_chalk.default.gray("\u2591".repeat(width - pointerIndex - 1)) : "";
|
|
82
|
+
return `${done}${pointer}${pending}`;
|
|
83
|
+
};
|
|
44
84
|
function vitePluginDeployOss(option) {
|
|
45
85
|
const {
|
|
46
86
|
accessKeyId,
|
|
@@ -55,17 +95,23 @@ function vitePluginDeployOss(option) {
|
|
|
55
95
|
autoDelete = false,
|
|
56
96
|
alias,
|
|
57
97
|
open = true,
|
|
98
|
+
fancy = true,
|
|
58
99
|
noCache = false,
|
|
100
|
+
failOnError = true,
|
|
59
101
|
concurrency = 5,
|
|
60
102
|
retryTimes = 3,
|
|
103
|
+
multipartThreshold = 10 * 1024 * 1024,
|
|
61
104
|
...props
|
|
62
105
|
} = option || {};
|
|
63
106
|
let buildFailed = false;
|
|
64
107
|
let upload = false;
|
|
65
|
-
let outDir = "";
|
|
66
|
-
|
|
67
|
-
process.stdout?.
|
|
68
|
-
|
|
108
|
+
let outDir = (0, import_vite.normalizePath)((0, import_node_path.resolve)("dist"));
|
|
109
|
+
let resolvedConfig = null;
|
|
110
|
+
const useInteractiveOutput = fancy && Boolean(process.stdout?.isTTY) && Boolean(process.stderr?.isTTY) && !process.env.CI;
|
|
111
|
+
const clearScreen = () => {
|
|
112
|
+
if (!useInteractiveOutput) return;
|
|
113
|
+
process.stdout.write("\x1B[2J\x1B[0f");
|
|
114
|
+
};
|
|
69
115
|
const validateOptions = () => {
|
|
70
116
|
const errors = [];
|
|
71
117
|
if (!accessKeyId) errors.push("accessKeyId is required");
|
|
@@ -73,79 +119,185 @@ function vitePluginDeployOss(option) {
|
|
|
73
119
|
if (!bucket) errors.push("bucket is required");
|
|
74
120
|
if (!region) errors.push("region is required");
|
|
75
121
|
if (!uploadDir) errors.push("uploadDir is required");
|
|
122
|
+
if (!Number.isInteger(retryTimes) || retryTimes < 1) errors.push("retryTimes must be >= 1");
|
|
123
|
+
if (!Number.isInteger(concurrency) || concurrency < 1) errors.push("concurrency must be >= 1");
|
|
124
|
+
if (!Number.isFinite(multipartThreshold) || multipartThreshold <= 0)
|
|
125
|
+
errors.push("multipartThreshold must be > 0");
|
|
76
126
|
return errors;
|
|
77
127
|
};
|
|
78
|
-
const uploadFileWithRetry = async (client,
|
|
128
|
+
const uploadFileWithRetry = async (client, task, silentLogs, maxRetries = retryTimes) => {
|
|
129
|
+
const shouldUseMultipart = task.size >= multipartThreshold;
|
|
130
|
+
const headers = {
|
|
131
|
+
"x-oss-storage-class": "Standard",
|
|
132
|
+
"x-oss-object-acl": "default",
|
|
133
|
+
"Cache-Control": noCache ? "no-cache" : "public, max-age=86400, immutable",
|
|
134
|
+
"x-oss-forbid-overwrite": overwrite ? "false" : "true"
|
|
135
|
+
};
|
|
79
136
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
80
137
|
try {
|
|
81
|
-
const result = await client.
|
|
138
|
+
const result = shouldUseMultipart ? await client.multipartUpload(task.name, task.filePath, {
|
|
82
139
|
timeout: 6e5,
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}
|
|
90
|
-
}
|
|
140
|
+
partSize: 1024 * 1024,
|
|
141
|
+
parallel: Math.max(1, Math.min(concurrency, 4)),
|
|
142
|
+
headers
|
|
143
|
+
}) : await client.put(task.name, task.filePath, {
|
|
144
|
+
timeout: 6e5,
|
|
145
|
+
headers
|
|
91
146
|
});
|
|
92
147
|
if (result.res.status === 200) {
|
|
93
|
-
const url = alias ? alias + name : result.url;
|
|
94
148
|
if (autoDelete) {
|
|
95
149
|
try {
|
|
96
|
-
(0,
|
|
150
|
+
await (0, import_promises.unlink)(task.filePath);
|
|
97
151
|
} catch (error) {
|
|
98
|
-
console.warn(`${import_chalk.default.yellow("\u26A0")} \u5220\u9664\u672C\u5730\u6587\u4EF6\u5931\u8D25: ${filePath}`);
|
|
152
|
+
console.warn(`${import_chalk.default.yellow("\u26A0")} \u5220\u9664\u672C\u5730\u6587\u4EF6\u5931\u8D25: ${task.filePath}`);
|
|
99
153
|
}
|
|
100
154
|
}
|
|
101
|
-
return { success: true, file: filePath };
|
|
155
|
+
return { success: true, file: task.filePath, name: task.name, size: task.size, retries: attempt - 1 };
|
|
102
156
|
} else {
|
|
103
157
|
throw new Error(`Upload failed with status: ${result.res.status}`);
|
|
104
158
|
}
|
|
105
159
|
} catch (error) {
|
|
106
160
|
if (attempt === maxRetries) {
|
|
107
|
-
|
|
108
|
-
|
|
161
|
+
if (!silentLogs) {
|
|
162
|
+
console.log(
|
|
163
|
+
`${import_chalk.default.red("\u2717")} ${task.filePath} => ${error instanceof Error ? error.message : String(error)}`
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
return {
|
|
167
|
+
success: false,
|
|
168
|
+
file: task.filePath,
|
|
169
|
+
name: task.name,
|
|
170
|
+
size: task.size,
|
|
171
|
+
retries: attempt - 1,
|
|
172
|
+
error
|
|
173
|
+
};
|
|
109
174
|
} else {
|
|
110
|
-
|
|
175
|
+
if (!silentLogs) {
|
|
176
|
+
console.log(`${import_chalk.default.yellow("\u26A0")} ${task.filePath} \u4E0A\u4F20\u5931\u8D25\uFF0C\u6B63\u5728\u91CD\u8BD5 (${attempt}/${maxRetries})...`);
|
|
177
|
+
}
|
|
111
178
|
await new Promise((resolve2) => setTimeout(resolve2, 1e3 * attempt));
|
|
112
179
|
}
|
|
113
180
|
}
|
|
114
181
|
}
|
|
115
|
-
return {
|
|
182
|
+
return {
|
|
183
|
+
success: false,
|
|
184
|
+
file: task.filePath,
|
|
185
|
+
name: task.name,
|
|
186
|
+
size: task.size,
|
|
187
|
+
retries: maxRetries,
|
|
188
|
+
error: new Error("Max retries exceeded")
|
|
189
|
+
};
|
|
116
190
|
};
|
|
117
|
-
const uploadFilesInBatches = async (client, files,
|
|
191
|
+
const uploadFilesInBatches = async (client, files, windowSize = concurrency) => {
|
|
118
192
|
const results = [];
|
|
119
193
|
const totalFiles = files.length;
|
|
194
|
+
const tasks = [];
|
|
120
195
|
let completed = 0;
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
196
|
+
let failed = 0;
|
|
197
|
+
let uploadedBytes = 0;
|
|
198
|
+
let retries = 0;
|
|
199
|
+
const taskCandidates = await Promise.all(
|
|
200
|
+
files.map(async (relativeFilePath) => {
|
|
201
|
+
const filePath = (0, import_vite.normalizePath)((0, import_node_path.resolve)(outDir, relativeFilePath));
|
|
202
|
+
const name = normalizeObjectKey(uploadDir, relativeFilePath);
|
|
203
|
+
try {
|
|
204
|
+
const fileStats = await (0, import_promises.stat)(filePath);
|
|
205
|
+
return { task: { filePath, name, size: fileStats.size } };
|
|
206
|
+
} catch (error) {
|
|
207
|
+
return { task: null, error, filePath, name };
|
|
208
|
+
}
|
|
209
|
+
})
|
|
210
|
+
);
|
|
211
|
+
for (const candidate of taskCandidates) {
|
|
212
|
+
if (candidate.task) {
|
|
213
|
+
tasks.push(candidate.task);
|
|
214
|
+
} else {
|
|
215
|
+
failed++;
|
|
216
|
+
completed++;
|
|
217
|
+
results.push({
|
|
218
|
+
success: false,
|
|
219
|
+
file: candidate.filePath,
|
|
220
|
+
name: candidate.name,
|
|
221
|
+
size: 0,
|
|
222
|
+
retries: 0,
|
|
223
|
+
error: candidate.error
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
const totalBytes = tasks.reduce((sum, task) => sum + task.size, 0);
|
|
228
|
+
const startAt = Date.now();
|
|
229
|
+
const activeFiles = /* @__PURE__ */ new Set();
|
|
230
|
+
const safeWindowSize = Math.max(1, Math.min(windowSize, tasks.length || 1));
|
|
231
|
+
const silentLogs = Boolean(useInteractiveOutput);
|
|
232
|
+
const spinner = useInteractiveOutput ? (0, import_ora.default)({ text: "\u51C6\u5907\u4E0A\u4F20...", spinner: "dots12" }).start() : null;
|
|
233
|
+
const reportEvery = Math.max(1, Math.ceil(totalFiles / 10));
|
|
234
|
+
let lastReportedCompleted = -1;
|
|
235
|
+
const updateProgress = () => {
|
|
236
|
+
const progressRatio = totalFiles > 0 ? completed / totalFiles : 1;
|
|
237
|
+
const percentage = Math.round(progressRatio * 100);
|
|
238
|
+
const elapsedSeconds = (Date.now() - startAt) / 1e3;
|
|
239
|
+
const speed = elapsedSeconds > 0 ? uploadedBytes / elapsedSeconds : 0;
|
|
240
|
+
const etaSeconds = speed > 0 ? Math.max(0, (totalBytes - uploadedBytes) / speed) : 0;
|
|
241
|
+
const activeList = Array.from(activeFiles);
|
|
242
|
+
const currentFile = activeList.length > 0 ? trimMiddle(activeList[activeList.length - 1], 86) : "-";
|
|
243
|
+
if (!spinner) {
|
|
244
|
+
if (completed === lastReportedCompleted) return;
|
|
245
|
+
if (completed === totalFiles || completed % reportEvery === 0) {
|
|
246
|
+
console.log(
|
|
247
|
+
`${import_chalk.default.gray("\u8FDB\u5EA6:")} ${completed}/${totalFiles} (${percentage}%) | ${import_chalk.default.gray("\u6570\u636E:")} ${formatBytes(uploadedBytes)}/${formatBytes(totalBytes)} | ${import_chalk.default.gray("\u901F\u5EA6:")} ${formatBytes(speed)}/s`
|
|
248
|
+
);
|
|
249
|
+
lastReportedCompleted = completed;
|
|
250
|
+
}
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
const bar = buildCapsuleBar(progressRatio);
|
|
254
|
+
const warnLine = retries > 0 || failed > 0 ? `
|
|
255
|
+
${import_chalk.default.yellow("\u91CD\u8BD5")}: ${retries} ${import_chalk.default.yellow("\u5931\u8D25")}: ${failed}` : "";
|
|
256
|
+
spinner.text = [
|
|
257
|
+
`${import_chalk.default.cyan("\u6B63\u5728\u4E0A\u4F20:")} ${import_chalk.default.white(currentFile)}`,
|
|
258
|
+
`${bar} ${import_chalk.default.bold(`${percentage}%`)} ${import_chalk.default.gray(`(${completed}/${totalFiles})`)} ${import_chalk.default.gray("|")} ${import_chalk.default.blue(formatBytes(uploadedBytes))}/${import_chalk.default.blue(formatBytes(totalBytes))} ${import_chalk.default.gray("|")} ${import_chalk.default.magenta(`${formatBytes(speed)}/s`)} ${import_chalk.default.gray("|")} \u9884\u8BA1 ${import_chalk.default.yellow(formatDuration(etaSeconds))}`
|
|
259
|
+
].join("\n");
|
|
260
|
+
spinner.text += warnLine;
|
|
130
261
|
};
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
const
|
|
262
|
+
const refreshTimer = spinner ? setInterval(updateProgress, 120) : null;
|
|
263
|
+
let currentIndex = 0;
|
|
264
|
+
const worker = async () => {
|
|
265
|
+
while (true) {
|
|
266
|
+
const index = currentIndex++;
|
|
267
|
+
if (index >= tasks.length) return;
|
|
268
|
+
const task = tasks[index];
|
|
269
|
+
activeFiles.add(task.name);
|
|
270
|
+
updateProgress();
|
|
271
|
+
const result = await uploadFileWithRetry(client, task, silentLogs);
|
|
138
272
|
completed++;
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
273
|
+
retries += result.retries;
|
|
274
|
+
if (result.success) {
|
|
275
|
+
uploadedBytes += result.size;
|
|
276
|
+
} else {
|
|
277
|
+
failed++;
|
|
278
|
+
}
|
|
279
|
+
results.push(result);
|
|
280
|
+
activeFiles.delete(task.name);
|
|
281
|
+
updateProgress();
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
updateProgress();
|
|
285
|
+
try {
|
|
286
|
+
await Promise.all(Array.from({ length: safeWindowSize }, () => worker()));
|
|
287
|
+
} finally {
|
|
288
|
+
if (refreshTimer) clearInterval(refreshTimer);
|
|
289
|
+
}
|
|
290
|
+
if (spinner) {
|
|
291
|
+
const elapsedSeconds = (Date.now() - startAt) / 1e3;
|
|
292
|
+
const successCount = results.filter((item) => item.success).length;
|
|
293
|
+
const speed = elapsedSeconds > 0 ? uploadedBytes / elapsedSeconds : 0;
|
|
294
|
+
spinner.succeed(
|
|
295
|
+
`${import_chalk.default.green("\u4E0A\u4F20\u6210\u529F")} ${successCount} \u4E2A\u6587\u4EF6\u3002
|
|
296
|
+
${buildCapsuleBar(1)} 100% (${totalFiles}/${totalFiles}) ${import_chalk.default.gray("|")} \u901F\u5EA6 ${import_chalk.default.magenta(`${formatBytes(speed)}/s`)} ${import_chalk.default.gray("|")} \u8017\u65F6 ${import_chalk.default.yellow(formatDuration(elapsedSeconds))}`
|
|
297
|
+
);
|
|
298
|
+
} else {
|
|
299
|
+
console.log(`${import_chalk.default.green("\u2714")} \u6240\u6709\u6587\u4EF6\u4E0A\u4F20\u5B8C\u6210 (${totalFiles}/${totalFiles})`);
|
|
144
300
|
}
|
|
145
|
-
const width = 30;
|
|
146
|
-
const bar = import_chalk.default.green("\u2588".repeat(width));
|
|
147
|
-
spinner.succeed(`\u6240\u6709\u6587\u4EF6\u4E0A\u4F20\u5B8C\u6210!
|
|
148
|
-
${bar} 100% (${totalFiles}/${totalFiles})`);
|
|
149
301
|
return results;
|
|
150
302
|
};
|
|
151
303
|
return {
|
|
@@ -157,7 +309,7 @@ ${bar} 100% (${totalFiles}/${totalFiles})`);
|
|
|
157
309
|
},
|
|
158
310
|
config(config) {
|
|
159
311
|
if (!open || buildFailed) return;
|
|
160
|
-
|
|
312
|
+
clearScreen();
|
|
161
313
|
const validationErrors = validateOptions();
|
|
162
314
|
if (validationErrors.length > 0) {
|
|
163
315
|
console.log(`${import_chalk.default.red("\u2717 \u914D\u7F6E\u9519\u8BEF:")}
|
|
@@ -166,25 +318,29 @@ ${validationErrors.map((err) => ` - ${err}`).join("\n")}`);
|
|
|
166
318
|
}
|
|
167
319
|
upload = true;
|
|
168
320
|
config.base = configBase || config.base;
|
|
169
|
-
outDir = config.build?.outDir || "dist";
|
|
170
321
|
return config;
|
|
171
322
|
},
|
|
323
|
+
configResolved(config) {
|
|
324
|
+
resolvedConfig = config;
|
|
325
|
+
outDir = (0, import_vite.normalizePath)((0, import_node_path.resolve)(config.root, config.build.outDir));
|
|
326
|
+
},
|
|
172
327
|
closeBundle: {
|
|
173
328
|
sequential: true,
|
|
174
329
|
order: "post",
|
|
175
330
|
async handler() {
|
|
176
|
-
if (!open || !upload || buildFailed) return;
|
|
331
|
+
if (!open || !upload || buildFailed || !resolvedConfig) return;
|
|
177
332
|
const startTime = Date.now();
|
|
178
333
|
const client = new import_ali_oss.default({ region, accessKeyId, accessKeySecret, secure, bucket, ...props });
|
|
179
|
-
const files = (0, import_glob.globSync)(
|
|
334
|
+
const files = (0, import_glob.globSync)("**/*", {
|
|
335
|
+
cwd: outDir,
|
|
180
336
|
nodir: true,
|
|
181
337
|
ignore: Array.isArray(skip) ? skip : [skip]
|
|
182
|
-
});
|
|
338
|
+
}).map((file) => (0, import_vite.normalizePath)(file));
|
|
183
339
|
if (files.length === 0) {
|
|
184
340
|
console.log(`${import_chalk.default.yellow("\u26A0 \u6CA1\u6709\u627E\u5230\u9700\u8981\u4E0A\u4F20\u7684\u6587\u4EF6")}`);
|
|
185
341
|
return;
|
|
186
342
|
}
|
|
187
|
-
|
|
343
|
+
clearScreen();
|
|
188
344
|
console.log(import_chalk.default.cyan(`
|
|
189
345
|
\u{1F680} OSS \u90E8\u7F72\u5F00\u59CB
|
|
190
346
|
`));
|
|
@@ -192,14 +348,19 @@ ${validationErrors.map((err) => ` - ${err}`).join("\n")}`);
|
|
|
192
348
|
console.log(`${import_chalk.default.gray("Region:")} ${import_chalk.default.green(region)}`);
|
|
193
349
|
console.log(`${import_chalk.default.gray("Source:")} ${import_chalk.default.yellow(outDir)}`);
|
|
194
350
|
console.log(`${import_chalk.default.gray("Target:")} ${import_chalk.default.yellow(uploadDir)}`);
|
|
351
|
+
if (alias) console.log(`${import_chalk.default.gray("Alias:")} ${import_chalk.default.green(alias)}`);
|
|
195
352
|
console.log(`${import_chalk.default.gray("Files:")} ${import_chalk.default.blue(files.length)}
|
|
196
353
|
`);
|
|
197
354
|
try {
|
|
198
355
|
const results = await uploadFilesInBatches(client, files, concurrency);
|
|
199
356
|
const successCount = results.filter((r) => r.success).length;
|
|
200
357
|
const failedCount = results.length - successCount;
|
|
201
|
-
const
|
|
202
|
-
|
|
358
|
+
const durationSeconds = (Date.now() - startTime) / 1e3;
|
|
359
|
+
const duration = durationSeconds.toFixed(2);
|
|
360
|
+
const uploadedBytes = results.reduce((sum, result) => result.success ? sum + result.size : sum, 0);
|
|
361
|
+
const retryCount = results.reduce((sum, result) => sum + result.retries, 0);
|
|
362
|
+
const avgSpeed = durationSeconds > 0 ? uploadedBytes / durationSeconds : 0;
|
|
363
|
+
clearScreen();
|
|
203
364
|
console.log("\n" + import_chalk.default.gray("\u2500".repeat(40)) + "\n");
|
|
204
365
|
if (failedCount === 0) {
|
|
205
366
|
console.log(`${import_chalk.default.green("\u{1F389} \u90E8\u7F72\u6210\u529F!")}`);
|
|
@@ -212,17 +373,40 @@ ${import_chalk.default.gray("\u7EDF\u8BA1:")}`);
|
|
|
212
373
|
if (failedCount > 0) {
|
|
213
374
|
console.log(` ${import_chalk.default.red("\u2717")} \u5931\u8D25: ${import_chalk.default.bold(failedCount)}`);
|
|
214
375
|
}
|
|
376
|
+
console.log(` ${import_chalk.default.cyan("\u21C4")} \u91CD\u8BD5: ${import_chalk.default.bold(retryCount)}`);
|
|
377
|
+
console.log(` ${import_chalk.default.blue("\u{1F4E6}")} \u6570\u636E: ${import_chalk.default.bold(formatBytes(uploadedBytes))}`);
|
|
378
|
+
console.log(` ${import_chalk.default.magenta("\u26A1")} \u5E73\u5747\u901F\u5EA6: ${import_chalk.default.bold(`${formatBytes(avgSpeed)}/s`)}`);
|
|
215
379
|
console.log(` ${import_chalk.default.blue("\u23F1")} \u8017\u65F6: ${import_chalk.default.bold(duration)}s`);
|
|
216
380
|
console.log("");
|
|
381
|
+
if (failedCount > 0) {
|
|
382
|
+
const failedItems = results.filter((result) => !result.success);
|
|
383
|
+
const previewCount = Math.min(5, failedItems.length);
|
|
384
|
+
console.log(import_chalk.default.red("\u5931\u8D25\u660E\u7EC6:"));
|
|
385
|
+
for (let i = 0; i < previewCount; i++) {
|
|
386
|
+
const item = failedItems[i];
|
|
387
|
+
const reason = item.error?.message || "unknown error";
|
|
388
|
+
console.log(` ${import_chalk.default.red("\u2022")} ${item.name} => ${reason}`);
|
|
389
|
+
}
|
|
390
|
+
if (failedItems.length > previewCount) {
|
|
391
|
+
console.log(import_chalk.default.gray(` ... \u8FD8\u6709 ${failedItems.length - previewCount} \u4E2A\u5931\u8D25\u6587\u4EF6`));
|
|
392
|
+
}
|
|
393
|
+
console.log("");
|
|
394
|
+
}
|
|
217
395
|
try {
|
|
218
|
-
(0, import_delete_empty.default)((0, import_node_path.resolve)(outDir));
|
|
396
|
+
await (0, import_delete_empty.default)((0, import_node_path.resolve)(outDir));
|
|
219
397
|
} catch (error) {
|
|
220
398
|
console.warn(`${import_chalk.default.yellow("\u26A0 \u6E05\u7406\u7A7A\u76EE\u5F55\u5931\u8D25:")} ${error}`);
|
|
221
399
|
}
|
|
400
|
+
if (failedCount > 0 && failOnError) {
|
|
401
|
+
throw new Error(`Failed to upload ${failedCount} of ${results.length} files`);
|
|
402
|
+
}
|
|
222
403
|
} catch (error) {
|
|
223
404
|
console.log(`
|
|
224
405
|
${import_chalk.default.red("\u274C \u4E0A\u4F20\u8FC7\u7A0B\u4E2D\u53D1\u751F\u9519\u8BEF:")} ${error}
|
|
225
406
|
`);
|
|
407
|
+
if (failOnError) {
|
|
408
|
+
throw error instanceof Error ? error : new Error(String(error));
|
|
409
|
+
}
|
|
226
410
|
}
|
|
227
411
|
}
|
|
228
412
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -3,10 +3,50 @@ import oss from "ali-oss";
|
|
|
3
3
|
import chalk from "chalk";
|
|
4
4
|
import deleteEmpty from "delete-empty";
|
|
5
5
|
import { globSync } from "glob";
|
|
6
|
-
import {
|
|
6
|
+
import { stat, unlink } from "fs/promises";
|
|
7
7
|
import { resolve } from "path";
|
|
8
8
|
import ora from "ora";
|
|
9
9
|
import { normalizePath } from "vite";
|
|
10
|
+
var normalizeObjectKey = (targetDir, relativeFilePath) => normalizePath(`${targetDir}/${relativeFilePath}`).replace(/\/{2,}/g, "/").replace(/^\/+/, "");
|
|
11
|
+
var formatBytes = (bytes) => {
|
|
12
|
+
if (!Number.isFinite(bytes) || bytes <= 0) return "0 B";
|
|
13
|
+
const units = ["B", "KB", "MB", "GB", "TB"];
|
|
14
|
+
let value = bytes;
|
|
15
|
+
let unitIndex = 0;
|
|
16
|
+
while (value >= 1024 && unitIndex < units.length - 1) {
|
|
17
|
+
value /= 1024;
|
|
18
|
+
unitIndex++;
|
|
19
|
+
}
|
|
20
|
+
const digits = value >= 100 || unitIndex === 0 ? 0 : 1;
|
|
21
|
+
return `${value.toFixed(digits)} ${units[unitIndex]}`;
|
|
22
|
+
};
|
|
23
|
+
var formatDuration = (seconds) => {
|
|
24
|
+
if (!Number.isFinite(seconds) || seconds < 0) return "--";
|
|
25
|
+
const rounded = Math.round(seconds);
|
|
26
|
+
const mins = Math.floor(rounded / 60);
|
|
27
|
+
const secs = rounded % 60;
|
|
28
|
+
if (mins === 0) return `${secs}s`;
|
|
29
|
+
return `${mins}m${String(secs).padStart(2, "0")}s`;
|
|
30
|
+
};
|
|
31
|
+
var trimMiddle = (text, maxLength) => {
|
|
32
|
+
if (text.length <= maxLength) return text;
|
|
33
|
+
if (maxLength <= 10) return text.slice(0, maxLength);
|
|
34
|
+
const leftLength = Math.floor((maxLength - 3) / 2);
|
|
35
|
+
const rightLength = maxLength - 3 - leftLength;
|
|
36
|
+
return `${text.slice(0, leftLength)}...${text.slice(-rightLength)}`;
|
|
37
|
+
};
|
|
38
|
+
var buildCapsuleBar = (ratio, width = 30) => {
|
|
39
|
+
const safeRatio = Math.max(0, Math.min(1, ratio));
|
|
40
|
+
if (width <= 0) return "";
|
|
41
|
+
if (safeRatio >= 1) {
|
|
42
|
+
return chalk.green("\u2588".repeat(width));
|
|
43
|
+
}
|
|
44
|
+
const pointerIndex = Math.min(width - 1, Math.floor(width * safeRatio));
|
|
45
|
+
const done = pointerIndex > 0 ? chalk.green("\u2588".repeat(pointerIndex)) : "";
|
|
46
|
+
const pointer = chalk.cyanBright("\u25B8");
|
|
47
|
+
const pending = pointerIndex < width - 1 ? chalk.gray("\u2591".repeat(width - pointerIndex - 1)) : "";
|
|
48
|
+
return `${done}${pointer}${pending}`;
|
|
49
|
+
};
|
|
10
50
|
function vitePluginDeployOss(option) {
|
|
11
51
|
const {
|
|
12
52
|
accessKeyId,
|
|
@@ -21,17 +61,23 @@ function vitePluginDeployOss(option) {
|
|
|
21
61
|
autoDelete = false,
|
|
22
62
|
alias,
|
|
23
63
|
open = true,
|
|
64
|
+
fancy = true,
|
|
24
65
|
noCache = false,
|
|
66
|
+
failOnError = true,
|
|
25
67
|
concurrency = 5,
|
|
26
68
|
retryTimes = 3,
|
|
69
|
+
multipartThreshold = 10 * 1024 * 1024,
|
|
27
70
|
...props
|
|
28
71
|
} = option || {};
|
|
29
72
|
let buildFailed = false;
|
|
30
73
|
let upload = false;
|
|
31
|
-
let outDir = "";
|
|
32
|
-
|
|
33
|
-
process.stdout?.
|
|
34
|
-
|
|
74
|
+
let outDir = normalizePath(resolve("dist"));
|
|
75
|
+
let resolvedConfig = null;
|
|
76
|
+
const useInteractiveOutput = fancy && Boolean(process.stdout?.isTTY) && Boolean(process.stderr?.isTTY) && !process.env.CI;
|
|
77
|
+
const clearScreen = () => {
|
|
78
|
+
if (!useInteractiveOutput) return;
|
|
79
|
+
process.stdout.write("\x1B[2J\x1B[0f");
|
|
80
|
+
};
|
|
35
81
|
const validateOptions = () => {
|
|
36
82
|
const errors = [];
|
|
37
83
|
if (!accessKeyId) errors.push("accessKeyId is required");
|
|
@@ -39,79 +85,185 @@ function vitePluginDeployOss(option) {
|
|
|
39
85
|
if (!bucket) errors.push("bucket is required");
|
|
40
86
|
if (!region) errors.push("region is required");
|
|
41
87
|
if (!uploadDir) errors.push("uploadDir is required");
|
|
88
|
+
if (!Number.isInteger(retryTimes) || retryTimes < 1) errors.push("retryTimes must be >= 1");
|
|
89
|
+
if (!Number.isInteger(concurrency) || concurrency < 1) errors.push("concurrency must be >= 1");
|
|
90
|
+
if (!Number.isFinite(multipartThreshold) || multipartThreshold <= 0)
|
|
91
|
+
errors.push("multipartThreshold must be > 0");
|
|
42
92
|
return errors;
|
|
43
93
|
};
|
|
44
|
-
const uploadFileWithRetry = async (client,
|
|
94
|
+
const uploadFileWithRetry = async (client, task, silentLogs, maxRetries = retryTimes) => {
|
|
95
|
+
const shouldUseMultipart = task.size >= multipartThreshold;
|
|
96
|
+
const headers = {
|
|
97
|
+
"x-oss-storage-class": "Standard",
|
|
98
|
+
"x-oss-object-acl": "default",
|
|
99
|
+
"Cache-Control": noCache ? "no-cache" : "public, max-age=86400, immutable",
|
|
100
|
+
"x-oss-forbid-overwrite": overwrite ? "false" : "true"
|
|
101
|
+
};
|
|
45
102
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
46
103
|
try {
|
|
47
|
-
const result = await client.
|
|
104
|
+
const result = shouldUseMultipart ? await client.multipartUpload(task.name, task.filePath, {
|
|
48
105
|
timeout: 6e5,
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
56
|
-
}
|
|
106
|
+
partSize: 1024 * 1024,
|
|
107
|
+
parallel: Math.max(1, Math.min(concurrency, 4)),
|
|
108
|
+
headers
|
|
109
|
+
}) : await client.put(task.name, task.filePath, {
|
|
110
|
+
timeout: 6e5,
|
|
111
|
+
headers
|
|
57
112
|
});
|
|
58
113
|
if (result.res.status === 200) {
|
|
59
|
-
const url = alias ? alias + name : result.url;
|
|
60
114
|
if (autoDelete) {
|
|
61
115
|
try {
|
|
62
|
-
|
|
116
|
+
await unlink(task.filePath);
|
|
63
117
|
} catch (error) {
|
|
64
|
-
console.warn(`${chalk.yellow("\u26A0")} \u5220\u9664\u672C\u5730\u6587\u4EF6\u5931\u8D25: ${filePath}`);
|
|
118
|
+
console.warn(`${chalk.yellow("\u26A0")} \u5220\u9664\u672C\u5730\u6587\u4EF6\u5931\u8D25: ${task.filePath}`);
|
|
65
119
|
}
|
|
66
120
|
}
|
|
67
|
-
return { success: true, file: filePath };
|
|
121
|
+
return { success: true, file: task.filePath, name: task.name, size: task.size, retries: attempt - 1 };
|
|
68
122
|
} else {
|
|
69
123
|
throw new Error(`Upload failed with status: ${result.res.status}`);
|
|
70
124
|
}
|
|
71
125
|
} catch (error) {
|
|
72
126
|
if (attempt === maxRetries) {
|
|
73
|
-
|
|
74
|
-
|
|
127
|
+
if (!silentLogs) {
|
|
128
|
+
console.log(
|
|
129
|
+
`${chalk.red("\u2717")} ${task.filePath} => ${error instanceof Error ? error.message : String(error)}`
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
success: false,
|
|
134
|
+
file: task.filePath,
|
|
135
|
+
name: task.name,
|
|
136
|
+
size: task.size,
|
|
137
|
+
retries: attempt - 1,
|
|
138
|
+
error
|
|
139
|
+
};
|
|
75
140
|
} else {
|
|
76
|
-
|
|
141
|
+
if (!silentLogs) {
|
|
142
|
+
console.log(`${chalk.yellow("\u26A0")} ${task.filePath} \u4E0A\u4F20\u5931\u8D25\uFF0C\u6B63\u5728\u91CD\u8BD5 (${attempt}/${maxRetries})...`);
|
|
143
|
+
}
|
|
77
144
|
await new Promise((resolve2) => setTimeout(resolve2, 1e3 * attempt));
|
|
78
145
|
}
|
|
79
146
|
}
|
|
80
147
|
}
|
|
81
|
-
return {
|
|
148
|
+
return {
|
|
149
|
+
success: false,
|
|
150
|
+
file: task.filePath,
|
|
151
|
+
name: task.name,
|
|
152
|
+
size: task.size,
|
|
153
|
+
retries: maxRetries,
|
|
154
|
+
error: new Error("Max retries exceeded")
|
|
155
|
+
};
|
|
82
156
|
};
|
|
83
|
-
const uploadFilesInBatches = async (client, files,
|
|
157
|
+
const uploadFilesInBatches = async (client, files, windowSize = concurrency) => {
|
|
84
158
|
const results = [];
|
|
85
159
|
const totalFiles = files.length;
|
|
160
|
+
const tasks = [];
|
|
86
161
|
let completed = 0;
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
162
|
+
let failed = 0;
|
|
163
|
+
let uploadedBytes = 0;
|
|
164
|
+
let retries = 0;
|
|
165
|
+
const taskCandidates = await Promise.all(
|
|
166
|
+
files.map(async (relativeFilePath) => {
|
|
167
|
+
const filePath = normalizePath(resolve(outDir, relativeFilePath));
|
|
168
|
+
const name = normalizeObjectKey(uploadDir, relativeFilePath);
|
|
169
|
+
try {
|
|
170
|
+
const fileStats = await stat(filePath);
|
|
171
|
+
return { task: { filePath, name, size: fileStats.size } };
|
|
172
|
+
} catch (error) {
|
|
173
|
+
return { task: null, error, filePath, name };
|
|
174
|
+
}
|
|
175
|
+
})
|
|
176
|
+
);
|
|
177
|
+
for (const candidate of taskCandidates) {
|
|
178
|
+
if (candidate.task) {
|
|
179
|
+
tasks.push(candidate.task);
|
|
180
|
+
} else {
|
|
181
|
+
failed++;
|
|
182
|
+
completed++;
|
|
183
|
+
results.push({
|
|
184
|
+
success: false,
|
|
185
|
+
file: candidate.filePath,
|
|
186
|
+
name: candidate.name,
|
|
187
|
+
size: 0,
|
|
188
|
+
retries: 0,
|
|
189
|
+
error: candidate.error
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
const totalBytes = tasks.reduce((sum, task) => sum + task.size, 0);
|
|
194
|
+
const startAt = Date.now();
|
|
195
|
+
const activeFiles = /* @__PURE__ */ new Set();
|
|
196
|
+
const safeWindowSize = Math.max(1, Math.min(windowSize, tasks.length || 1));
|
|
197
|
+
const silentLogs = Boolean(useInteractiveOutput);
|
|
198
|
+
const spinner = useInteractiveOutput ? ora({ text: "\u51C6\u5907\u4E0A\u4F20...", spinner: "dots12" }).start() : null;
|
|
199
|
+
const reportEvery = Math.max(1, Math.ceil(totalFiles / 10));
|
|
200
|
+
let lastReportedCompleted = -1;
|
|
201
|
+
const updateProgress = () => {
|
|
202
|
+
const progressRatio = totalFiles > 0 ? completed / totalFiles : 1;
|
|
203
|
+
const percentage = Math.round(progressRatio * 100);
|
|
204
|
+
const elapsedSeconds = (Date.now() - startAt) / 1e3;
|
|
205
|
+
const speed = elapsedSeconds > 0 ? uploadedBytes / elapsedSeconds : 0;
|
|
206
|
+
const etaSeconds = speed > 0 ? Math.max(0, (totalBytes - uploadedBytes) / speed) : 0;
|
|
207
|
+
const activeList = Array.from(activeFiles);
|
|
208
|
+
const currentFile = activeList.length > 0 ? trimMiddle(activeList[activeList.length - 1], 86) : "-";
|
|
209
|
+
if (!spinner) {
|
|
210
|
+
if (completed === lastReportedCompleted) return;
|
|
211
|
+
if (completed === totalFiles || completed % reportEvery === 0) {
|
|
212
|
+
console.log(
|
|
213
|
+
`${chalk.gray("\u8FDB\u5EA6:")} ${completed}/${totalFiles} (${percentage}%) | ${chalk.gray("\u6570\u636E:")} ${formatBytes(uploadedBytes)}/${formatBytes(totalBytes)} | ${chalk.gray("\u901F\u5EA6:")} ${formatBytes(speed)}/s`
|
|
214
|
+
);
|
|
215
|
+
lastReportedCompleted = completed;
|
|
216
|
+
}
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
const bar = buildCapsuleBar(progressRatio);
|
|
220
|
+
const warnLine = retries > 0 || failed > 0 ? `
|
|
221
|
+
${chalk.yellow("\u91CD\u8BD5")}: ${retries} ${chalk.yellow("\u5931\u8D25")}: ${failed}` : "";
|
|
222
|
+
spinner.text = [
|
|
223
|
+
`${chalk.cyan("\u6B63\u5728\u4E0A\u4F20:")} ${chalk.white(currentFile)}`,
|
|
224
|
+
`${bar} ${chalk.bold(`${percentage}%`)} ${chalk.gray(`(${completed}/${totalFiles})`)} ${chalk.gray("|")} ${chalk.blue(formatBytes(uploadedBytes))}/${chalk.blue(formatBytes(totalBytes))} ${chalk.gray("|")} ${chalk.magenta(`${formatBytes(speed)}/s`)} ${chalk.gray("|")} \u9884\u8BA1 ${chalk.yellow(formatDuration(etaSeconds))}`
|
|
225
|
+
].join("\n");
|
|
226
|
+
spinner.text += warnLine;
|
|
96
227
|
};
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
const
|
|
228
|
+
const refreshTimer = spinner ? setInterval(updateProgress, 120) : null;
|
|
229
|
+
let currentIndex = 0;
|
|
230
|
+
const worker = async () => {
|
|
231
|
+
while (true) {
|
|
232
|
+
const index = currentIndex++;
|
|
233
|
+
if (index >= tasks.length) return;
|
|
234
|
+
const task = tasks[index];
|
|
235
|
+
activeFiles.add(task.name);
|
|
236
|
+
updateProgress();
|
|
237
|
+
const result = await uploadFileWithRetry(client, task, silentLogs);
|
|
104
238
|
completed++;
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
239
|
+
retries += result.retries;
|
|
240
|
+
if (result.success) {
|
|
241
|
+
uploadedBytes += result.size;
|
|
242
|
+
} else {
|
|
243
|
+
failed++;
|
|
244
|
+
}
|
|
245
|
+
results.push(result);
|
|
246
|
+
activeFiles.delete(task.name);
|
|
247
|
+
updateProgress();
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
updateProgress();
|
|
251
|
+
try {
|
|
252
|
+
await Promise.all(Array.from({ length: safeWindowSize }, () => worker()));
|
|
253
|
+
} finally {
|
|
254
|
+
if (refreshTimer) clearInterval(refreshTimer);
|
|
255
|
+
}
|
|
256
|
+
if (spinner) {
|
|
257
|
+
const elapsedSeconds = (Date.now() - startAt) / 1e3;
|
|
258
|
+
const successCount = results.filter((item) => item.success).length;
|
|
259
|
+
const speed = elapsedSeconds > 0 ? uploadedBytes / elapsedSeconds : 0;
|
|
260
|
+
spinner.succeed(
|
|
261
|
+
`${chalk.green("\u4E0A\u4F20\u6210\u529F")} ${successCount} \u4E2A\u6587\u4EF6\u3002
|
|
262
|
+
${buildCapsuleBar(1)} 100% (${totalFiles}/${totalFiles}) ${chalk.gray("|")} \u901F\u5EA6 ${chalk.magenta(`${formatBytes(speed)}/s`)} ${chalk.gray("|")} \u8017\u65F6 ${chalk.yellow(formatDuration(elapsedSeconds))}`
|
|
263
|
+
);
|
|
264
|
+
} else {
|
|
265
|
+
console.log(`${chalk.green("\u2714")} \u6240\u6709\u6587\u4EF6\u4E0A\u4F20\u5B8C\u6210 (${totalFiles}/${totalFiles})`);
|
|
110
266
|
}
|
|
111
|
-
const width = 30;
|
|
112
|
-
const bar = chalk.green("\u2588".repeat(width));
|
|
113
|
-
spinner.succeed(`\u6240\u6709\u6587\u4EF6\u4E0A\u4F20\u5B8C\u6210!
|
|
114
|
-
${bar} 100% (${totalFiles}/${totalFiles})`);
|
|
115
267
|
return results;
|
|
116
268
|
};
|
|
117
269
|
return {
|
|
@@ -123,7 +275,7 @@ ${bar} 100% (${totalFiles}/${totalFiles})`);
|
|
|
123
275
|
},
|
|
124
276
|
config(config) {
|
|
125
277
|
if (!open || buildFailed) return;
|
|
126
|
-
|
|
278
|
+
clearScreen();
|
|
127
279
|
const validationErrors = validateOptions();
|
|
128
280
|
if (validationErrors.length > 0) {
|
|
129
281
|
console.log(`${chalk.red("\u2717 \u914D\u7F6E\u9519\u8BEF:")}
|
|
@@ -132,25 +284,29 @@ ${validationErrors.map((err) => ` - ${err}`).join("\n")}`);
|
|
|
132
284
|
}
|
|
133
285
|
upload = true;
|
|
134
286
|
config.base = configBase || config.base;
|
|
135
|
-
outDir = config.build?.outDir || "dist";
|
|
136
287
|
return config;
|
|
137
288
|
},
|
|
289
|
+
configResolved(config) {
|
|
290
|
+
resolvedConfig = config;
|
|
291
|
+
outDir = normalizePath(resolve(config.root, config.build.outDir));
|
|
292
|
+
},
|
|
138
293
|
closeBundle: {
|
|
139
294
|
sequential: true,
|
|
140
295
|
order: "post",
|
|
141
296
|
async handler() {
|
|
142
|
-
if (!open || !upload || buildFailed) return;
|
|
297
|
+
if (!open || !upload || buildFailed || !resolvedConfig) return;
|
|
143
298
|
const startTime = Date.now();
|
|
144
299
|
const client = new oss({ region, accessKeyId, accessKeySecret, secure, bucket, ...props });
|
|
145
|
-
const files = globSync(
|
|
300
|
+
const files = globSync("**/*", {
|
|
301
|
+
cwd: outDir,
|
|
146
302
|
nodir: true,
|
|
147
303
|
ignore: Array.isArray(skip) ? skip : [skip]
|
|
148
|
-
});
|
|
304
|
+
}).map((file) => normalizePath(file));
|
|
149
305
|
if (files.length === 0) {
|
|
150
306
|
console.log(`${chalk.yellow("\u26A0 \u6CA1\u6709\u627E\u5230\u9700\u8981\u4E0A\u4F20\u7684\u6587\u4EF6")}`);
|
|
151
307
|
return;
|
|
152
308
|
}
|
|
153
|
-
|
|
309
|
+
clearScreen();
|
|
154
310
|
console.log(chalk.cyan(`
|
|
155
311
|
\u{1F680} OSS \u90E8\u7F72\u5F00\u59CB
|
|
156
312
|
`));
|
|
@@ -158,14 +314,19 @@ ${validationErrors.map((err) => ` - ${err}`).join("\n")}`);
|
|
|
158
314
|
console.log(`${chalk.gray("Region:")} ${chalk.green(region)}`);
|
|
159
315
|
console.log(`${chalk.gray("Source:")} ${chalk.yellow(outDir)}`);
|
|
160
316
|
console.log(`${chalk.gray("Target:")} ${chalk.yellow(uploadDir)}`);
|
|
317
|
+
if (alias) console.log(`${chalk.gray("Alias:")} ${chalk.green(alias)}`);
|
|
161
318
|
console.log(`${chalk.gray("Files:")} ${chalk.blue(files.length)}
|
|
162
319
|
`);
|
|
163
320
|
try {
|
|
164
321
|
const results = await uploadFilesInBatches(client, files, concurrency);
|
|
165
322
|
const successCount = results.filter((r) => r.success).length;
|
|
166
323
|
const failedCount = results.length - successCount;
|
|
167
|
-
const
|
|
168
|
-
|
|
324
|
+
const durationSeconds = (Date.now() - startTime) / 1e3;
|
|
325
|
+
const duration = durationSeconds.toFixed(2);
|
|
326
|
+
const uploadedBytes = results.reduce((sum, result) => result.success ? sum + result.size : sum, 0);
|
|
327
|
+
const retryCount = results.reduce((sum, result) => sum + result.retries, 0);
|
|
328
|
+
const avgSpeed = durationSeconds > 0 ? uploadedBytes / durationSeconds : 0;
|
|
329
|
+
clearScreen();
|
|
169
330
|
console.log("\n" + chalk.gray("\u2500".repeat(40)) + "\n");
|
|
170
331
|
if (failedCount === 0) {
|
|
171
332
|
console.log(`${chalk.green("\u{1F389} \u90E8\u7F72\u6210\u529F!")}`);
|
|
@@ -178,17 +339,40 @@ ${chalk.gray("\u7EDF\u8BA1:")}`);
|
|
|
178
339
|
if (failedCount > 0) {
|
|
179
340
|
console.log(` ${chalk.red("\u2717")} \u5931\u8D25: ${chalk.bold(failedCount)}`);
|
|
180
341
|
}
|
|
342
|
+
console.log(` ${chalk.cyan("\u21C4")} \u91CD\u8BD5: ${chalk.bold(retryCount)}`);
|
|
343
|
+
console.log(` ${chalk.blue("\u{1F4E6}")} \u6570\u636E: ${chalk.bold(formatBytes(uploadedBytes))}`);
|
|
344
|
+
console.log(` ${chalk.magenta("\u26A1")} \u5E73\u5747\u901F\u5EA6: ${chalk.bold(`${formatBytes(avgSpeed)}/s`)}`);
|
|
181
345
|
console.log(` ${chalk.blue("\u23F1")} \u8017\u65F6: ${chalk.bold(duration)}s`);
|
|
182
346
|
console.log("");
|
|
347
|
+
if (failedCount > 0) {
|
|
348
|
+
const failedItems = results.filter((result) => !result.success);
|
|
349
|
+
const previewCount = Math.min(5, failedItems.length);
|
|
350
|
+
console.log(chalk.red("\u5931\u8D25\u660E\u7EC6:"));
|
|
351
|
+
for (let i = 0; i < previewCount; i++) {
|
|
352
|
+
const item = failedItems[i];
|
|
353
|
+
const reason = item.error?.message || "unknown error";
|
|
354
|
+
console.log(` ${chalk.red("\u2022")} ${item.name} => ${reason}`);
|
|
355
|
+
}
|
|
356
|
+
if (failedItems.length > previewCount) {
|
|
357
|
+
console.log(chalk.gray(` ... \u8FD8\u6709 ${failedItems.length - previewCount} \u4E2A\u5931\u8D25\u6587\u4EF6`));
|
|
358
|
+
}
|
|
359
|
+
console.log("");
|
|
360
|
+
}
|
|
183
361
|
try {
|
|
184
|
-
deleteEmpty(resolve(outDir));
|
|
362
|
+
await deleteEmpty(resolve(outDir));
|
|
185
363
|
} catch (error) {
|
|
186
364
|
console.warn(`${chalk.yellow("\u26A0 \u6E05\u7406\u7A7A\u76EE\u5F55\u5931\u8D25:")} ${error}`);
|
|
187
365
|
}
|
|
366
|
+
if (failedCount > 0 && failOnError) {
|
|
367
|
+
throw new Error(`Failed to upload ${failedCount} of ${results.length} files`);
|
|
368
|
+
}
|
|
188
369
|
} catch (error) {
|
|
189
370
|
console.log(`
|
|
190
371
|
${chalk.red("\u274C \u4E0A\u4F20\u8FC7\u7A0B\u4E2D\u53D1\u751F\u9519\u8BEF:")} ${error}
|
|
191
372
|
`);
|
|
373
|
+
if (failOnError) {
|
|
374
|
+
throw error instanceof Error ? error : new Error(String(error));
|
|
375
|
+
}
|
|
192
376
|
}
|
|
193
377
|
}
|
|
194
378
|
}
|
package/package.json
CHANGED
|
@@ -1,21 +1,28 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vite-plugin-deploy-oss",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"module": "./dist/index.mjs",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
7
|
+
"type": "commonjs",
|
|
7
8
|
"homepage": "https://github.com/yulin96/vite-plugin-deploy-oss",
|
|
8
9
|
"repository": {
|
|
9
10
|
"type": "git",
|
|
10
|
-
"url": "https://github.com/yulin96/vite-plugin-deploy-oss"
|
|
11
|
+
"url": "git+https://github.com/yulin96/vite-plugin-deploy-oss.git"
|
|
11
12
|
},
|
|
12
13
|
"bugs": {
|
|
13
14
|
"url": "https://github.com/yulin96/vite-plugin-deploy-oss/issues"
|
|
14
15
|
},
|
|
15
16
|
"exports": {
|
|
16
17
|
".": {
|
|
17
|
-
"import":
|
|
18
|
-
|
|
18
|
+
"import": {
|
|
19
|
+
"types": "./dist/index.d.mts",
|
|
20
|
+
"default": "./dist/index.mjs"
|
|
21
|
+
},
|
|
22
|
+
"require": {
|
|
23
|
+
"types": "./dist/index.d.ts",
|
|
24
|
+
"default": "./dist/index.js"
|
|
25
|
+
}
|
|
19
26
|
}
|
|
20
27
|
},
|
|
21
28
|
"keywords": [
|
|
@@ -31,25 +38,27 @@
|
|
|
31
38
|
"license": "MIT",
|
|
32
39
|
"description": "将dist目录下的文件上传到阿里云oss",
|
|
33
40
|
"devDependencies": {
|
|
34
|
-
"@types/
|
|
35
|
-
"
|
|
36
|
-
"
|
|
41
|
+
"@types/ali-oss": "^6.23.3",
|
|
42
|
+
"@types/delete-empty": "^3.0.5",
|
|
43
|
+
"@types/node": "^22.19.11",
|
|
44
|
+
"tsup": "^8.5.1",
|
|
45
|
+
"typescript": "^5.9.3"
|
|
37
46
|
},
|
|
38
47
|
"peerDependencies": {
|
|
39
48
|
"vite": "^6.0.3 || ^7"
|
|
40
49
|
},
|
|
41
50
|
"dependencies": {
|
|
42
|
-
"@types/ali-oss": "^6.16.11",
|
|
43
|
-
"@types/delete-empty": "^3.0.5",
|
|
44
51
|
"ali-oss": "^6.23.0",
|
|
45
|
-
"chalk": "^5.
|
|
52
|
+
"chalk": "^5.6.2",
|
|
46
53
|
"delete-empty": "^3.0.0",
|
|
47
|
-
"glob": "^13.0.
|
|
48
|
-
"ora": "^9.
|
|
54
|
+
"glob": "^13.0.6",
|
|
55
|
+
"ora": "^9.3.0"
|
|
49
56
|
},
|
|
50
57
|
"scripts": {
|
|
51
58
|
"build": "tsup",
|
|
59
|
+
"typecheck": "tsc -p tsconfig.json",
|
|
52
60
|
"pack": "pnpm run build && pnpm pack",
|
|
53
|
-
"build:test": "cd playground && vite build"
|
|
61
|
+
"build:test": "cd playground && vite build",
|
|
62
|
+
"build:test:deploy": "cd playground && vite build --mode deploy"
|
|
54
63
|
}
|
|
55
64
|
}
|