vite-plugin-deploy-oss 1.2.3 → 3.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 CHANGED
@@ -24,15 +24,31 @@ 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
+ - 当前版本仅支持 ESM(`import`),不再提供 CommonJS(`require`)入口。
52
+ - `open` 默认 `true`,建议通过环境变量控制开关(例如 `DEPLOY_OSS=1` 时再上传)。
53
+ - `fancy` 默认 `true`,TTY 终端下会显示实时动效进度(速度、预计剩余、并发、当前文件)。
54
+ - `failOnError` 默认 `true`,上传有失败会抛错,适合 CI 场景保证发布质量。
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
@@ -1,46 +1,52 @@
1
- "use strict";
2
- var __create = Object.create;
3
- var __defProp = Object.defineProperty;
4
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
- var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
- var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __export = (target, all) => {
9
- for (var name in all)
10
- __defProp(target, name, { get: all[name], enumerable: true });
1
+ // src/index.ts
2
+ import oss from "ali-oss";
3
+ import chalk from "chalk";
4
+ import deleteEmpty from "delete-empty";
5
+ import { globSync } from "glob";
6
+ import { stat, unlink } from "fs/promises";
7
+ import { resolve } from "path";
8
+ import ora from "ora";
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`;
11
30
  };
12
- var __copyProps = (to, from, except, desc) => {
13
- if (from && typeof from === "object" || typeof from === "function") {
14
- for (let key of __getOwnPropNames(from))
15
- if (!__hasOwnProp.call(to, key) && key !== except)
16
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
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));
17
43
  }
18
- return to;
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}`;
19
49
  };
20
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
- // If the importer is in node compatibility mode or this is not an ESM
22
- // file that has been converted to a CommonJS file using a Babel-
23
- // compatible transform (i.e. "__esModule" has not been set), then set
24
- // "default" to the CommonJS "module.exports" for node compatibility.
25
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
- mod
27
- ));
28
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
-
30
- // src/index.ts
31
- var index_exports = {};
32
- __export(index_exports, {
33
- default: () => vitePluginDeployOss
34
- });
35
- module.exports = __toCommonJS(index_exports);
36
- var import_ali_oss = __toESM(require("ali-oss"));
37
- var import_chalk = __toESM(require("chalk"));
38
- var import_delete_empty = __toESM(require("delete-empty"));
39
- var import_glob = require("glob");
40
- var import_node_fs = require("fs");
41
- var import_node_path = require("path");
42
- var import_ora = __toESM(require("ora"));
43
- var import_vite = require("vite");
44
50
  function vitePluginDeployOss(option) {
45
51
  const {
46
52
  accessKeyId,
@@ -55,17 +61,23 @@ function vitePluginDeployOss(option) {
55
61
  autoDelete = false,
56
62
  alias,
57
63
  open = true,
64
+ fancy = true,
58
65
  noCache = false,
66
+ failOnError = true,
59
67
  concurrency = 5,
60
68
  retryTimes = 3,
69
+ multipartThreshold = 10 * 1024 * 1024,
61
70
  ...props
62
71
  } = option || {};
63
72
  let buildFailed = false;
64
73
  let upload = false;
65
- let outDir = "";
66
- const maxListeners = Math.max(20, concurrency * 3);
67
- process.stdout?.setMaxListeners?.(maxListeners);
68
- process.stderr?.setMaxListeners?.(maxListeners);
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
+ };
69
81
  const validateOptions = () => {
70
82
  const errors = [];
71
83
  if (!accessKeyId) errors.push("accessKeyId is required");
@@ -73,79 +85,185 @@ function vitePluginDeployOss(option) {
73
85
  if (!bucket) errors.push("bucket is required");
74
86
  if (!region) errors.push("region is required");
75
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");
76
92
  return errors;
77
93
  };
78
- const uploadFileWithRetry = async (client, name, filePath, maxRetries = retryTimes) => {
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
+ };
79
102
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
80
103
  try {
81
- const result = await client.put(name, filePath, {
104
+ const result = shouldUseMultipart ? await client.multipartUpload(task.name, task.filePath, {
82
105
  timeout: 6e5,
83
- headers: {
84
- "x-oss-storage-class": "Standard",
85
- "x-oss-object-acl": "default",
86
- "Cache-Control": noCache ? "no-cache" : "public, max-age=86400, immutable",
87
- ...overwrite && {
88
- "x-oss-forbid-overwrite": "false"
89
- }
90
- }
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
91
112
  });
92
113
  if (result.res.status === 200) {
93
- const url = alias ? alias + name : result.url;
94
114
  if (autoDelete) {
95
115
  try {
96
- (0, import_node_fs.unlinkSync)(filePath);
116
+ await unlink(task.filePath);
97
117
  } catch (error) {
98
- console.warn(`${import_chalk.default.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}`);
99
119
  }
100
120
  }
101
- return { success: true, file: filePath };
121
+ return { success: true, file: task.filePath, name: task.name, size: task.size, retries: attempt - 1 };
102
122
  } else {
103
123
  throw new Error(`Upload failed with status: ${result.res.status}`);
104
124
  }
105
125
  } catch (error) {
106
126
  if (attempt === maxRetries) {
107
- console.log(`${import_chalk.default.red("\u2717")} ${filePath} => ${error instanceof Error ? error.message : String(error)}`);
108
- return { success: false, file: filePath, error };
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
+ };
109
140
  } else {
110
- console.log(`${import_chalk.default.yellow("\u26A0")} ${filePath} \u4E0A\u4F20\u5931\u8D25\uFF0C\u6B63\u5728\u91CD\u8BD5 (${attempt}/${maxRetries})...`);
141
+ if (!silentLogs) {
142
+ console.log(`${chalk.yellow("\u26A0")} ${task.filePath} \u4E0A\u4F20\u5931\u8D25\uFF0C\u6B63\u5728\u91CD\u8BD5 (${attempt}/${maxRetries})...`);
143
+ }
111
144
  await new Promise((resolve2) => setTimeout(resolve2, 1e3 * attempt));
112
145
  }
113
146
  }
114
147
  }
115
- return { success: false, file: filePath, error: new Error("Max retries exceeded") };
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
+ };
116
156
  };
117
- const uploadFilesInBatches = async (client, files, batchSize = concurrency) => {
157
+ const uploadFilesInBatches = async (client, files, windowSize = concurrency) => {
118
158
  const results = [];
119
159
  const totalFiles = files.length;
160
+ const tasks = [];
120
161
  let completed = 0;
121
- const spinner = (0, import_ora.default)("\u51C6\u5907\u4E0A\u4F20...").start();
122
- const updateSpinner = (currentFile) => {
123
- const percentage = Math.round(completed / totalFiles * 100);
124
- const width2 = 30;
125
- const filled = Math.round(width2 * completed / totalFiles);
126
- const empty = width2 - filled;
127
- const bar2 = import_chalk.default.green("\u2588".repeat(filled)) + import_chalk.default.gray("\u2591".repeat(empty));
128
- spinner.text = `\u6B63\u5728\u4E0A\u4F20: ${import_chalk.default.cyan(currentFile)}
129
- ${bar2} ${percentage}% (${completed}/${totalFiles})`;
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;
130
227
  };
131
- for (let i = 0; i < files.length; i += batchSize) {
132
- const batch = files.slice(i, i + batchSize);
133
- const batchPromises = batch.map(async (file) => {
134
- const filePath = (0, import_vite.normalizePath)(file);
135
- const name = filePath.replace(outDir, uploadDir).replace(/\/\//g, "/");
136
- updateSpinner(name);
137
- const result = await uploadFileWithRetry(client, name, filePath);
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);
138
238
  completed++;
139
- updateSpinner(name);
140
- return result;
141
- });
142
- const batchResults = await Promise.all(batchPromises);
143
- results.push(...batchResults);
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})`);
144
266
  }
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
267
  return results;
150
268
  };
151
269
  return {
@@ -157,74 +275,109 @@ ${bar} 100% (${totalFiles}/${totalFiles})`);
157
275
  },
158
276
  config(config) {
159
277
  if (!open || buildFailed) return;
160
- process.stdout.write("\x1B[2J\x1B[0f");
278
+ clearScreen();
161
279
  const validationErrors = validateOptions();
162
280
  if (validationErrors.length > 0) {
163
- console.log(`${import_chalk.default.red("\u2717 \u914D\u7F6E\u9519\u8BEF:")}
281
+ console.log(`${chalk.red("\u2717 \u914D\u7F6E\u9519\u8BEF:")}
164
282
  ${validationErrors.map((err) => ` - ${err}`).join("\n")}`);
165
283
  return;
166
284
  }
167
285
  upload = true;
168
286
  config.base = configBase || config.base;
169
- outDir = config.build?.outDir || "dist";
170
287
  return config;
171
288
  },
289
+ configResolved(config) {
290
+ resolvedConfig = config;
291
+ outDir = normalizePath(resolve(config.root, config.build.outDir));
292
+ },
172
293
  closeBundle: {
173
294
  sequential: true,
174
295
  order: "post",
175
296
  async handler() {
176
- if (!open || !upload || buildFailed) return;
297
+ if (!open || !upload || buildFailed || !resolvedConfig) return;
177
298
  const startTime = Date.now();
178
- const client = new import_ali_oss.default({ region, accessKeyId, accessKeySecret, secure, bucket, ...props });
179
- const files = (0, import_glob.globSync)(outDir + "/**/*", {
299
+ const client = new oss({ region, accessKeyId, accessKeySecret, secure, bucket, ...props });
300
+ const files = globSync("**/*", {
301
+ cwd: outDir,
180
302
  nodir: true,
181
303
  ignore: Array.isArray(skip) ? skip : [skip]
182
- });
304
+ }).map((file) => normalizePath(file));
183
305
  if (files.length === 0) {
184
- console.log(`${import_chalk.default.yellow("\u26A0 \u6CA1\u6709\u627E\u5230\u9700\u8981\u4E0A\u4F20\u7684\u6587\u4EF6")}`);
306
+ console.log(`${chalk.yellow("\u26A0 \u6CA1\u6709\u627E\u5230\u9700\u8981\u4E0A\u4F20\u7684\u6587\u4EF6")}`);
185
307
  return;
186
308
  }
187
- process.stdout.write("\x1B[2J\x1B[0f");
188
- console.log(import_chalk.default.cyan(`
309
+ clearScreen();
310
+ console.log(chalk.cyan(`
189
311
  \u{1F680} OSS \u90E8\u7F72\u5F00\u59CB
190
312
  `));
191
- console.log(`${import_chalk.default.gray("Bucket:")} ${import_chalk.default.green(bucket)}`);
192
- console.log(`${import_chalk.default.gray("Region:")} ${import_chalk.default.green(region)}`);
193
- console.log(`${import_chalk.default.gray("Source:")} ${import_chalk.default.yellow(outDir)}`);
194
- console.log(`${import_chalk.default.gray("Target:")} ${import_chalk.default.yellow(uploadDir)}`);
195
- console.log(`${import_chalk.default.gray("Files:")} ${import_chalk.default.blue(files.length)}
313
+ console.log(`${chalk.gray("Bucket:")} ${chalk.green(bucket)}`);
314
+ console.log(`${chalk.gray("Region:")} ${chalk.green(region)}`);
315
+ console.log(`${chalk.gray("Source:")} ${chalk.yellow(outDir)}`);
316
+ console.log(`${chalk.gray("Target:")} ${chalk.yellow(uploadDir)}`);
317
+ if (alias) console.log(`${chalk.gray("Alias:")} ${chalk.green(alias)}`);
318
+ console.log(`${chalk.gray("Files:")} ${chalk.blue(files.length)}
196
319
  `);
197
320
  try {
198
321
  const results = await uploadFilesInBatches(client, files, concurrency);
199
322
  const successCount = results.filter((r) => r.success).length;
200
323
  const failedCount = results.length - successCount;
201
- const duration = ((Date.now() - startTime) / 1e3).toFixed(2);
202
- process.stdout.write("\x1B[2J\x1B[0f");
203
- console.log("\n" + import_chalk.default.gray("\u2500".repeat(40)) + "\n");
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();
330
+ console.log("\n" + chalk.gray("\u2500".repeat(40)) + "\n");
204
331
  if (failedCount === 0) {
205
- console.log(`${import_chalk.default.green("\u{1F389} \u90E8\u7F72\u6210\u529F!")}`);
332
+ console.log(`${chalk.green("\u{1F389} \u90E8\u7F72\u6210\u529F!")}`);
206
333
  } else {
207
- console.log(`${import_chalk.default.yellow("\u26A0 \u90E8\u7F72\u5B8C\u6210\u4F46\u5B58\u5728\u9519\u8BEF")}`);
334
+ console.log(`${chalk.yellow("\u26A0 \u90E8\u7F72\u5B8C\u6210\u4F46\u5B58\u5728\u9519\u8BEF")}`);
208
335
  }
209
336
  console.log(`
210
- ${import_chalk.default.gray("\u7EDF\u8BA1:")}`);
211
- console.log(` ${import_chalk.default.green("\u2714")} \u6210\u529F: ${import_chalk.default.bold(successCount)}`);
337
+ ${chalk.gray("\u7EDF\u8BA1:")}`);
338
+ console.log(` ${chalk.green("\u2714")} \u6210\u529F: ${chalk.bold(successCount)}`);
212
339
  if (failedCount > 0) {
213
- console.log(` ${import_chalk.default.red("\u2717")} \u5931\u8D25: ${import_chalk.default.bold(failedCount)}`);
340
+ console.log(` ${chalk.red("\u2717")} \u5931\u8D25: ${chalk.bold(failedCount)}`);
214
341
  }
215
- console.log(` ${import_chalk.default.blue("\u23F1")} \u8017\u65F6: ${import_chalk.default.bold(duration)}s`);
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`)}`);
345
+ console.log(` ${chalk.blue("\u23F1")} \u8017\u65F6: ${chalk.bold(duration)}s`);
216
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
+ }
217
361
  try {
218
- (0, import_delete_empty.default)((0, import_node_path.resolve)(outDir));
362
+ await deleteEmpty(resolve(outDir));
219
363
  } catch (error) {
220
- console.warn(`${import_chalk.default.yellow("\u26A0 \u6E05\u7406\u7A7A\u76EE\u5F55\u5931\u8D25:")} ${error}`);
364
+ console.warn(`${chalk.yellow("\u26A0 \u6E05\u7406\u7A7A\u76EE\u5F55\u5931\u8D25:")} ${error}`);
365
+ }
366
+ if (failedCount > 0 && failOnError) {
367
+ throw new Error(`Failed to upload ${failedCount} of ${results.length} files`);
221
368
  }
222
369
  } catch (error) {
223
370
  console.log(`
224
- ${import_chalk.default.red("\u274C \u4E0A\u4F20\u8FC7\u7A0B\u4E2D\u53D1\u751F\u9519\u8BEF:")} ${error}
371
+ ${chalk.red("\u274C \u4E0A\u4F20\u8FC7\u7A0B\u4E2D\u53D1\u751F\u9519\u8BEF:")} ${error}
225
372
  `);
373
+ if (failOnError) {
374
+ throw error instanceof Error ? error : new Error(String(error));
375
+ }
226
376
  }
227
377
  }
228
378
  }
229
379
  };
230
380
  }
381
+ export {
382
+ vitePluginDeployOss as default
383
+ };
package/package.json CHANGED
@@ -1,21 +1,21 @@
1
1
  {
2
2
  "name": "vite-plugin-deploy-oss",
3
- "version": "1.2.3",
3
+ "version": "3.0.0",
4
4
  "main": "./dist/index.js",
5
- "module": "./dist/index.mjs",
6
5
  "types": "./dist/index.d.ts",
6
+ "type": "module",
7
7
  "homepage": "https://github.com/yulin96/vite-plugin-deploy-oss",
8
8
  "repository": {
9
9
  "type": "git",
10
- "url": "https://github.com/yulin96/vite-plugin-deploy-oss"
10
+ "url": "git+https://github.com/yulin96/vite-plugin-deploy-oss.git"
11
11
  },
12
12
  "bugs": {
13
13
  "url": "https://github.com/yulin96/vite-plugin-deploy-oss/issues"
14
14
  },
15
15
  "exports": {
16
16
  ".": {
17
- "import": "./dist/index.mjs",
18
- "require": "./dist/index.js"
17
+ "types": "./dist/index.d.ts",
18
+ "default": "./dist/index.js"
19
19
  }
20
20
  },
21
21
  "keywords": [
@@ -31,25 +31,27 @@
31
31
  "license": "MIT",
32
32
  "description": "将dist目录下的文件上传到阿里云oss",
33
33
  "devDependencies": {
34
- "@types/node": "^22.15.32",
35
- "tsup": "^8.5.0",
36
- "typescript": "^5.8.3"
34
+ "@types/ali-oss": "^6.23.3",
35
+ "@types/delete-empty": "^3.0.5",
36
+ "@types/node": "^22.19.11",
37
+ "tsup": "^8.5.1",
38
+ "typescript": "^5.9.3"
37
39
  },
38
40
  "peerDependencies": {
39
41
  "vite": "^6.0.3 || ^7"
40
42
  },
41
43
  "dependencies": {
42
- "@types/ali-oss": "^6.16.11",
43
- "@types/delete-empty": "^3.0.5",
44
44
  "ali-oss": "^6.23.0",
45
- "chalk": "^5.4.1",
45
+ "chalk": "^5.6.2",
46
46
  "delete-empty": "^3.0.0",
47
- "glob": "^13.0.1",
48
- "ora": "^9.0.0"
47
+ "glob": "^13.0.6",
48
+ "ora": "^9.3.0"
49
49
  },
50
50
  "scripts": {
51
51
  "build": "tsup",
52
+ "typecheck": "tsc -p tsconfig.json",
52
53
  "pack": "pnpm run build && pnpm pack",
53
- "build:test": "cd playground && vite build"
54
+ "build:test": "cd playground && vite build",
55
+ "build:test:deploy": "cd playground && vite build --mode deploy"
54
56
  }
55
57
  }
package/dist/index.d.mts DELETED
@@ -1,23 +0,0 @@
1
- import oss from 'ali-oss';
2
- import { Plugin } from 'vite';
3
-
4
- interface vitePluginDeployOssOption extends Omit<oss.Options, 'accessKeyId' | 'accessKeySecret' | 'bucket' | 'region'> {
5
- configBase?: string;
6
- accessKeyId: string;
7
- accessKeySecret: string;
8
- region: string;
9
- secure?: boolean;
10
- bucket: string;
11
- overwrite?: boolean;
12
- uploadDir: string;
13
- alias?: string;
14
- autoDelete?: boolean;
15
- skip?: string | string[];
16
- open?: boolean;
17
- noCache?: boolean;
18
- concurrency?: number;
19
- retryTimes?: number;
20
- }
21
- declare function vitePluginDeployOss(option: vitePluginDeployOssOption): Plugin;
22
-
23
- export { vitePluginDeployOss as default, type vitePluginDeployOssOption };
package/dist/index.mjs DELETED
@@ -1,199 +0,0 @@
1
- // src/index.ts
2
- import oss from "ali-oss";
3
- import chalk from "chalk";
4
- import deleteEmpty from "delete-empty";
5
- import { globSync } from "glob";
6
- import { unlinkSync } from "fs";
7
- import { resolve } from "path";
8
- import ora from "ora";
9
- import { normalizePath } from "vite";
10
- function vitePluginDeployOss(option) {
11
- const {
12
- accessKeyId,
13
- accessKeySecret,
14
- region,
15
- bucket,
16
- configBase,
17
- skip = "**/index.html",
18
- uploadDir,
19
- overwrite = true,
20
- secure = true,
21
- autoDelete = false,
22
- alias,
23
- open = true,
24
- noCache = false,
25
- concurrency = 5,
26
- retryTimes = 3,
27
- ...props
28
- } = option || {};
29
- let buildFailed = false;
30
- let upload = false;
31
- let outDir = "";
32
- const maxListeners = Math.max(20, concurrency * 3);
33
- process.stdout?.setMaxListeners?.(maxListeners);
34
- process.stderr?.setMaxListeners?.(maxListeners);
35
- const validateOptions = () => {
36
- const errors = [];
37
- if (!accessKeyId) errors.push("accessKeyId is required");
38
- if (!accessKeySecret) errors.push("accessKeySecret is required");
39
- if (!bucket) errors.push("bucket is required");
40
- if (!region) errors.push("region is required");
41
- if (!uploadDir) errors.push("uploadDir is required");
42
- return errors;
43
- };
44
- const uploadFileWithRetry = async (client, name, filePath, maxRetries = retryTimes) => {
45
- for (let attempt = 1; attempt <= maxRetries; attempt++) {
46
- try {
47
- const result = await client.put(name, filePath, {
48
- timeout: 6e5,
49
- headers: {
50
- "x-oss-storage-class": "Standard",
51
- "x-oss-object-acl": "default",
52
- "Cache-Control": noCache ? "no-cache" : "public, max-age=86400, immutable",
53
- ...overwrite && {
54
- "x-oss-forbid-overwrite": "false"
55
- }
56
- }
57
- });
58
- if (result.res.status === 200) {
59
- const url = alias ? alias + name : result.url;
60
- if (autoDelete) {
61
- try {
62
- unlinkSync(filePath);
63
- } catch (error) {
64
- console.warn(`${chalk.yellow("\u26A0")} \u5220\u9664\u672C\u5730\u6587\u4EF6\u5931\u8D25: ${filePath}`);
65
- }
66
- }
67
- return { success: true, file: filePath };
68
- } else {
69
- throw new Error(`Upload failed with status: ${result.res.status}`);
70
- }
71
- } catch (error) {
72
- if (attempt === maxRetries) {
73
- console.log(`${chalk.red("\u2717")} ${filePath} => ${error instanceof Error ? error.message : String(error)}`);
74
- return { success: false, file: filePath, error };
75
- } else {
76
- console.log(`${chalk.yellow("\u26A0")} ${filePath} \u4E0A\u4F20\u5931\u8D25\uFF0C\u6B63\u5728\u91CD\u8BD5 (${attempt}/${maxRetries})...`);
77
- await new Promise((resolve2) => setTimeout(resolve2, 1e3 * attempt));
78
- }
79
- }
80
- }
81
- return { success: false, file: filePath, error: new Error("Max retries exceeded") };
82
- };
83
- const uploadFilesInBatches = async (client, files, batchSize = concurrency) => {
84
- const results = [];
85
- const totalFiles = files.length;
86
- let completed = 0;
87
- const spinner = ora("\u51C6\u5907\u4E0A\u4F20...").start();
88
- const updateSpinner = (currentFile) => {
89
- const percentage = Math.round(completed / totalFiles * 100);
90
- const width2 = 30;
91
- const filled = Math.round(width2 * completed / totalFiles);
92
- const empty = width2 - filled;
93
- const bar2 = chalk.green("\u2588".repeat(filled)) + chalk.gray("\u2591".repeat(empty));
94
- spinner.text = `\u6B63\u5728\u4E0A\u4F20: ${chalk.cyan(currentFile)}
95
- ${bar2} ${percentage}% (${completed}/${totalFiles})`;
96
- };
97
- for (let i = 0; i < files.length; i += batchSize) {
98
- const batch = files.slice(i, i + batchSize);
99
- const batchPromises = batch.map(async (file) => {
100
- const filePath = normalizePath(file);
101
- const name = filePath.replace(outDir, uploadDir).replace(/\/\//g, "/");
102
- updateSpinner(name);
103
- const result = await uploadFileWithRetry(client, name, filePath);
104
- completed++;
105
- updateSpinner(name);
106
- return result;
107
- });
108
- const batchResults = await Promise.all(batchPromises);
109
- results.push(...batchResults);
110
- }
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
- return results;
116
- };
117
- return {
118
- name: "vite-plugin-deploy-oss",
119
- apply: "build",
120
- enforce: "post",
121
- buildEnd(error) {
122
- if (error) buildFailed = true;
123
- },
124
- config(config) {
125
- if (!open || buildFailed) return;
126
- process.stdout.write("\x1B[2J\x1B[0f");
127
- const validationErrors = validateOptions();
128
- if (validationErrors.length > 0) {
129
- console.log(`${chalk.red("\u2717 \u914D\u7F6E\u9519\u8BEF:")}
130
- ${validationErrors.map((err) => ` - ${err}`).join("\n")}`);
131
- return;
132
- }
133
- upload = true;
134
- config.base = configBase || config.base;
135
- outDir = config.build?.outDir || "dist";
136
- return config;
137
- },
138
- closeBundle: {
139
- sequential: true,
140
- order: "post",
141
- async handler() {
142
- if (!open || !upload || buildFailed) return;
143
- const startTime = Date.now();
144
- const client = new oss({ region, accessKeyId, accessKeySecret, secure, bucket, ...props });
145
- const files = globSync(outDir + "/**/*", {
146
- nodir: true,
147
- ignore: Array.isArray(skip) ? skip : [skip]
148
- });
149
- if (files.length === 0) {
150
- console.log(`${chalk.yellow("\u26A0 \u6CA1\u6709\u627E\u5230\u9700\u8981\u4E0A\u4F20\u7684\u6587\u4EF6")}`);
151
- return;
152
- }
153
- process.stdout.write("\x1B[2J\x1B[0f");
154
- console.log(chalk.cyan(`
155
- \u{1F680} OSS \u90E8\u7F72\u5F00\u59CB
156
- `));
157
- console.log(`${chalk.gray("Bucket:")} ${chalk.green(bucket)}`);
158
- console.log(`${chalk.gray("Region:")} ${chalk.green(region)}`);
159
- console.log(`${chalk.gray("Source:")} ${chalk.yellow(outDir)}`);
160
- console.log(`${chalk.gray("Target:")} ${chalk.yellow(uploadDir)}`);
161
- console.log(`${chalk.gray("Files:")} ${chalk.blue(files.length)}
162
- `);
163
- try {
164
- const results = await uploadFilesInBatches(client, files, concurrency);
165
- const successCount = results.filter((r) => r.success).length;
166
- const failedCount = results.length - successCount;
167
- const duration = ((Date.now() - startTime) / 1e3).toFixed(2);
168
- process.stdout.write("\x1B[2J\x1B[0f");
169
- console.log("\n" + chalk.gray("\u2500".repeat(40)) + "\n");
170
- if (failedCount === 0) {
171
- console.log(`${chalk.green("\u{1F389} \u90E8\u7F72\u6210\u529F!")}`);
172
- } else {
173
- console.log(`${chalk.yellow("\u26A0 \u90E8\u7F72\u5B8C\u6210\u4F46\u5B58\u5728\u9519\u8BEF")}`);
174
- }
175
- console.log(`
176
- ${chalk.gray("\u7EDF\u8BA1:")}`);
177
- console.log(` ${chalk.green("\u2714")} \u6210\u529F: ${chalk.bold(successCount)}`);
178
- if (failedCount > 0) {
179
- console.log(` ${chalk.red("\u2717")} \u5931\u8D25: ${chalk.bold(failedCount)}`);
180
- }
181
- console.log(` ${chalk.blue("\u23F1")} \u8017\u65F6: ${chalk.bold(duration)}s`);
182
- console.log("");
183
- try {
184
- deleteEmpty(resolve(outDir));
185
- } catch (error) {
186
- console.warn(`${chalk.yellow("\u26A0 \u6E05\u7406\u7A7A\u76EE\u5F55\u5931\u8D25:")} ${error}`);
187
- }
188
- } catch (error) {
189
- console.log(`
190
- ${chalk.red("\u274C \u4E0A\u4F20\u8FC7\u7A0B\u4E2D\u53D1\u751F\u9519\u8BEF:")} ${error}
191
- `);
192
- }
193
- }
194
- }
195
- };
196
- }
197
- export {
198
- vitePluginDeployOss as default
199
- };