vite-plugin-deploy-oss 3.2.0 → 3.3.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
@@ -67,7 +67,8 @@ export default {
67
67
  {
68
68
  "file": "assets/index-abc123.js",
69
69
  "key": "H5/zz/test/assets/index-abc123.js",
70
- "url": "https://oss.eventnet.cn/H5/zz/test/assets/index-abc123.js"
70
+ "url": "https://oss.eventnet.cn/H5/zz/test/assets/index-abc123.js",
71
+ "md5": "d41d8cd98f00b204e9800998ecf8427e"
71
72
  }
72
73
  ]
73
74
  }
package/dist/index.d.ts CHANGED
@@ -1,9 +1,10 @@
1
- import oss from 'ali-oss';
2
1
  import { Plugin } from 'vite';
2
+ import oss from 'ali-oss';
3
3
 
4
4
  interface ManifestOption {
5
5
  fileName?: string;
6
6
  }
7
+ type ManifestConfig = boolean | ManifestOption | undefined;
7
8
  interface vitePluginDeployOssOption extends Omit<oss.Options, 'accessKeyId' | 'accessKeySecret' | 'bucket' | 'region'> {
8
9
  configBase?: string;
9
10
  accessKeyId: string;
@@ -23,8 +24,35 @@ interface vitePluginDeployOssOption extends Omit<oss.Options, 'accessKeyId' | 'a
23
24
  concurrency?: number;
24
25
  retryTimes?: number;
25
26
  multipartThreshold?: number;
26
- manifest?: boolean | ManifestOption;
27
+ manifest?: ManifestConfig;
28
+ }
29
+ interface UploadResult {
30
+ success: boolean;
31
+ file: string;
32
+ relativeFilePath: string;
33
+ name: string;
34
+ size: number;
35
+ retries: number;
36
+ error?: Error;
37
+ }
38
+ interface UploadTask {
39
+ filePath: string;
40
+ relativeFilePath: string;
41
+ name: string;
42
+ size: number;
43
+ cacheControl?: string;
27
44
  }
45
+ interface ManifestFileItem {
46
+ file: string;
47
+ key: string;
48
+ url: string;
49
+ md5: string;
50
+ }
51
+ interface ManifestPayload {
52
+ version: number;
53
+ files: ManifestFileItem[];
54
+ }
55
+
28
56
  declare function vitePluginDeployOss(option: vitePluginDeployOssOption): Plugin;
29
57
 
30
- export { vitePluginDeployOss as default, type vitePluginDeployOssOption };
58
+ export { type ManifestConfig, type ManifestFileItem, type ManifestOption, type ManifestPayload, type UploadResult, type UploadTask, vitePluginDeployOss as default, type vitePluginDeployOssOption };
package/dist/index.js CHANGED
@@ -1,13 +1,27 @@
1
1
  // src/index.ts
2
2
  import oss from "ali-oss";
3
- import chalk from "chalk";
3
+ import chalk3 from "chalk";
4
+ import cliProgress from "cli-progress";
4
5
  import { globSync } from "glob";
5
- import { mkdir, readdir, rm, stat, unlink, writeFile } from "fs/promises";
6
- import { dirname, resolve } from "path";
7
- import ora from "ora";
6
+ import { mkdir, stat, unlink, writeFile } from "fs/promises";
7
+ import { dirname, resolve as resolve2 } from "path";
8
8
  import { normalizePath } from "vite";
9
+
10
+ // src/utils/file.ts
11
+ import { createHash } from "crypto";
12
+ import { createReadStream } from "fs";
13
+ import { readdir, rm } from "fs/promises";
14
+ import { resolve } from "path";
9
15
  var GARBAGE_FILE_REGEX = /(?:Thumbs\.db|\.DS_Store)$/i;
10
- var DEFAULT_MANIFEST_FILE_NAME = "oss-manifest.json";
16
+ var getFileMd5 = (filePath) => {
17
+ return new Promise((resolvePromise, reject) => {
18
+ const hash = createHash("md5");
19
+ const stream = createReadStream(filePath);
20
+ stream.on("error", (err) => reject(err));
21
+ stream.on("data", (chunk) => hash.update(chunk));
22
+ stream.on("end", () => resolvePromise(hash.digest("hex")));
23
+ });
24
+ };
11
25
  var removeEmptyDirectories = async (rootDir) => {
12
26
  const deletedDirectories = [];
13
27
  const visit = async (dirPath) => {
@@ -32,9 +46,53 @@ var removeEmptyDirectories = async (rootDir) => {
32
46
  await visit(resolve(rootDir));
33
47
  return deletedDirectories;
34
48
  };
35
- var normalizeObjectKey = (targetDir, relativeFilePath) => normalizePath(`${targetDir}/${relativeFilePath}`).replace(/\/{2,}/g, "/").replace(/^\/+/, "");
49
+
50
+ // src/utils/path.ts
51
+ var DEFAULT_MANIFEST_FILE_NAME = "oss-manifest.json";
52
+ var normalizeSlash = (value) => value.replace(/\\/g, "/").trim();
53
+ var normalizePathSegments = (...values) => values.filter((value) => Boolean(value)).flatMap((value) => normalizeSlash(value).split("/")).filter(Boolean).join("/");
54
+ var splitUrlLikeBase = (value) => {
55
+ const normalized = normalizeSlash(value);
56
+ const protocolMatch = normalized.match(/^([a-zA-Z][a-zA-Z\d+.-]*:\/\/[^/]+)(.*)$/);
57
+ if (protocolMatch) {
58
+ return {
59
+ prefix: protocolMatch[1],
60
+ path: protocolMatch[2] || ""
61
+ };
62
+ }
63
+ const protocolRelativeMatch = normalized.match(/^(\/\/[^/]+)(.*)$/);
64
+ if (protocolRelativeMatch) {
65
+ return {
66
+ prefix: protocolRelativeMatch[1],
67
+ path: protocolRelativeMatch[2] || ""
68
+ };
69
+ }
70
+ if (normalized.startsWith("/")) {
71
+ return {
72
+ prefix: "/",
73
+ path: normalized
74
+ };
75
+ }
76
+ return {
77
+ prefix: "",
78
+ path: normalized
79
+ };
80
+ };
81
+ var normalizeUrlLikeBase = (base) => {
82
+ const { prefix, path } = splitUrlLikeBase(base);
83
+ const normalizedPath = normalizePathSegments(path);
84
+ if (!prefix) return normalizedPath;
85
+ if (!normalizedPath) return prefix;
86
+ if (prefix === "/") return `/${normalizedPath}`;
87
+ return `${prefix}/${normalizedPath}`;
88
+ };
89
+ var ensureTrailingSlash = (value) => {
90
+ if (!value || value.endsWith("/")) return value;
91
+ return `${value}/`;
92
+ };
93
+ var normalizeObjectKey = (targetDir, relativeFilePath) => normalizePathSegments(targetDir, relativeFilePath);
36
94
  var normalizeManifestFileName = (fileName) => {
37
- const normalized = normalizePath(fileName || DEFAULT_MANIFEST_FILE_NAME).replace(/^\/+/, "").replace(/\/{2,}/g, "/");
95
+ const normalized = normalizePathSegments(fileName || DEFAULT_MANIFEST_FILE_NAME);
38
96
  return normalized || DEFAULT_MANIFEST_FILE_NAME;
39
97
  };
40
98
  var resolveManifestFileName = (manifest) => {
@@ -42,36 +100,16 @@ var resolveManifestFileName = (manifest) => {
42
100
  if (manifest === true) return DEFAULT_MANIFEST_FILE_NAME;
43
101
  return normalizeManifestFileName(manifest.fileName);
44
102
  };
45
- var normalizeUrlBase = (base) => {
46
- const normalized = base.replace(/\\/g, "/");
47
- const protocolSeparatorIndex = normalized.indexOf("://");
48
- if (protocolSeparatorIndex >= 0) {
49
- const pathIndex = normalized.indexOf("/", protocolSeparatorIndex + 3);
50
- if (pathIndex < 0) return normalized;
51
- return `${normalized.slice(0, pathIndex)}${normalized.slice(pathIndex).replace(/\/{2,}/g, "/")}`;
52
- }
53
- if (normalized.startsWith("//")) {
54
- const pathIndex = normalized.indexOf("/", 2);
55
- if (pathIndex < 0) return normalized;
56
- return `${normalized.slice(0, pathIndex)}${normalized.slice(pathIndex).replace(/\/{2,}/g, "/")}`;
57
- }
58
- return normalized.replace(/\/{2,}/g, "/");
59
- };
60
- var encodeUrlPath = (path) => encodeURI(path.replace(/^\/+/, ""));
61
- var joinUrlPath = (base, path) => `${normalizeUrlBase(base).replace(/\/+$/, "")}/${encodeUrlPath(path)}`;
103
+ var encodeUrlPath = (path) => encodeURI(normalizePathSegments(path));
104
+ var joinUrlPath = (base, path) => `${normalizeUrlLikeBase(base).replace(/\/+$/, "")}/${encodeUrlPath(path)}`;
62
105
  var resolveUploadedFileUrl = (relativeFilePath, objectKey, configBase, alias) => {
63
106
  if (configBase) return joinUrlPath(configBase, relativeFilePath);
64
107
  if (alias) return joinUrlPath(alias, objectKey);
65
108
  return objectKey;
66
109
  };
67
- var createManifestPayload = (results, configBase, alias) => ({
68
- version: Date.now(),
69
- files: results.filter((result) => result.success).map((result) => ({
70
- file: result.relativeFilePath,
71
- key: result.name,
72
- url: resolveUploadedFileUrl(result.relativeFilePath, result.name, configBase, alias)
73
- }))
74
- });
110
+
111
+ // src/utils/progress.ts
112
+ import chalk from "chalk";
75
113
  var formatBytes = (bytes) => {
76
114
  if (!Number.isFinite(bytes) || bytes <= 0) return "0 B";
77
115
  const units = ["B", "KB", "MB", "GB", "TB"];
@@ -92,24 +130,84 @@ var formatDuration = (seconds) => {
92
130
  if (mins === 0) return `${secs}s`;
93
131
  return `${mins}m${String(secs).padStart(2, "0")}s`;
94
132
  };
95
- var trimMiddle = (text, maxLength) => {
96
- if (text.length <= maxLength) return text;
97
- if (maxLength <= 10) return text.slice(0, maxLength);
98
- const leftLength = Math.floor((maxLength - 3) / 2);
99
- const rightLength = maxLength - 3 - leftLength;
100
- return `${text.slice(0, leftLength)}...${text.slice(-rightLength)}`;
133
+
134
+ // src/utils/terminal.ts
135
+ import chalk2 from "chalk";
136
+ import cliTruncate from "cli-truncate";
137
+ import logSymbols from "log-symbols";
138
+ import stringWidth from "string-width";
139
+ var panelBorderColor = {
140
+ info: "cyan",
141
+ success: "green",
142
+ warning: "yellow",
143
+ danger: "red"
101
144
  };
102
- var buildCapsuleBar = (ratio, width = 30) => {
103
- const safeRatio = Math.max(0, Math.min(1, ratio));
145
+ var getTerminalWidth = () => process.stdout?.columns || 100;
146
+ var getPanelInnerWidth = () => Math.max(46, Math.min(84, getTerminalWidth() - 4));
147
+ var padVisual = (text, width) => `${text}${" ".repeat(Math.max(0, width - stringWidth(text)))}`;
148
+ var fitVisual = (text, width) => {
104
149
  if (width <= 0) return "";
105
- if (safeRatio >= 1) {
106
- return chalk.green("\u2588".repeat(width));
150
+ return padVisual(cliTruncate(text, width, { position: "middle" }), width);
151
+ };
152
+ var truncateTerminalText = (text, reservedWidth = 26) => {
153
+ const maxWidth = Math.max(24, Math.min(88, getTerminalWidth() - reservedWidth));
154
+ return cliTruncate(text, maxWidth, { position: "middle" });
155
+ };
156
+ var renderPanel = (title, rows, tone = "info", footer) => {
157
+ const color = chalk2[panelBorderColor[tone]];
158
+ const innerWidth = getPanelInnerWidth();
159
+ const labelWidth = rows.length > 0 ? Math.max(...rows.map((row) => stringWidth(row.label))) : 0;
160
+ const contentLines = [chalk2.bold(cliTruncate(title, innerWidth, { position: "end" }))];
161
+ if (rows.length > 0) {
162
+ contentLines.push("");
163
+ for (const row of rows) {
164
+ const paddedLabel = padVisual(row.label, labelWidth);
165
+ const prefix = `${paddedLabel} `;
166
+ const availableValueWidth = Math.max(8, innerWidth - stringWidth(prefix));
167
+ contentLines.push(`${chalk2.gray(prefix)}${fitVisual(row.value, availableValueWidth)}`);
168
+ }
169
+ }
170
+ if (footer) {
171
+ contentLines.push("");
172
+ contentLines.push(chalk2.gray(cliTruncate(footer, innerWidth, { position: "middle" })));
107
173
  }
108
- const pointerIndex = Math.min(width - 1, Math.floor(width * safeRatio));
109
- const done = pointerIndex > 0 ? chalk.green("\u2588".repeat(pointerIndex)) : "";
110
- const pointer = chalk.cyanBright("\u25B8");
111
- const pending = pointerIndex < width - 1 ? chalk.gray("\u2591".repeat(width - pointerIndex - 1)) : "";
112
- return `${done}${pointer}${pending}`;
174
+ const top = color(`\u256D${"\u2500".repeat(innerWidth + 2)}\u256E`);
175
+ const bottom = color(`\u2570${"\u2500".repeat(innerWidth + 2)}\u256F`);
176
+ const body = contentLines.map((line) => `${color("\u2502")} ${fitVisual(line, innerWidth)} ${color("\u2502")}`).join("\n");
177
+ return `${top}
178
+ ${body}
179
+ ${bottom}`;
180
+ };
181
+ var renderInlineStats = (items) => items.filter(Boolean).join(chalk2.gray(" \xB7 "));
182
+ var getLogSymbol = (tone) => {
183
+ switch (tone) {
184
+ case "success":
185
+ return logSymbols.success;
186
+ case "warning":
187
+ return logSymbols.warning;
188
+ case "danger":
189
+ return logSymbols.error;
190
+ }
191
+ };
192
+
193
+ // src/index.ts
194
+ var createManifestPayload = async (results, configBase, alias) => {
195
+ const successfulResults = results.filter((result) => result.success);
196
+ const files = await Promise.all(
197
+ successfulResults.map(async (result) => {
198
+ const md5 = await getFileMd5(result.file);
199
+ return {
200
+ file: result.relativeFilePath,
201
+ key: result.name,
202
+ url: resolveUploadedFileUrl(result.relativeFilePath, result.name, configBase, alias),
203
+ md5
204
+ };
205
+ })
206
+ );
207
+ return {
208
+ version: Date.now(),
209
+ files
210
+ };
113
211
  };
114
212
  function vitePluginDeployOss(option) {
115
213
  const {
@@ -134,12 +232,15 @@ function vitePluginDeployOss(option) {
134
232
  manifest = false,
135
233
  ...props
136
234
  } = option || {};
235
+ const normalizedUploadDir = normalizePathSegments(uploadDir);
236
+ const normalizedConfigBase = configBase ? ensureTrailingSlash(normalizeUrlLikeBase(configBase)) : void 0;
237
+ const normalizedAlias = alias ? normalizeUrlLikeBase(alias) : void 0;
137
238
  let buildFailed = false;
138
239
  let upload = false;
139
- let outDir = normalizePath(resolve("dist"));
240
+ let outDir = normalizePath(resolve2("dist"));
140
241
  let resolvedConfig = null;
141
242
  const useInteractiveOutput = fancy && Boolean(process.stdout?.isTTY) && Boolean(process.stderr?.isTTY) && !process.env.CI;
142
- const clearScreen = () => {
243
+ const clearViewport = () => {
143
244
  if (!useInteractiveOutput) return;
144
245
  process.stdout.write("\x1B[2J\x1B[0f");
145
246
  };
@@ -152,8 +253,7 @@ function vitePluginDeployOss(option) {
152
253
  if (!uploadDir) errors.push("uploadDir is required");
153
254
  if (!Number.isInteger(retryTimes) || retryTimes < 1) errors.push("retryTimes must be >= 1");
154
255
  if (!Number.isInteger(concurrency) || concurrency < 1) errors.push("concurrency must be >= 1");
155
- if (!Number.isFinite(multipartThreshold) || multipartThreshold <= 0)
156
- errors.push("multipartThreshold must be > 0");
256
+ if (!Number.isFinite(multipartThreshold) || multipartThreshold <= 0) errors.push("multipartThreshold must be > 0");
157
257
  return errors;
158
258
  };
159
259
  const uploadSingleTask = async (client, task) => uploadFileWithRetry(client, task, false);
@@ -162,7 +262,7 @@ function vitePluginDeployOss(option) {
162
262
  const headers = {
163
263
  "x-oss-storage-class": "Standard",
164
264
  "x-oss-object-acl": "default",
165
- "Cache-Control": task.cacheControl || (noCache ? "no-cache" : "public, max-age=86400, immutable"),
265
+ "Cache-Control": task.cacheControl || (noCache || task.name.endsWith(".html") ? "no-cache" : "public, max-age=86400, immutable"),
166
266
  "x-oss-forbid-overwrite": overwrite ? "false" : "true"
167
267
  };
168
268
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
@@ -181,7 +281,9 @@ function vitePluginDeployOss(option) {
181
281
  try {
182
282
  await unlink(task.filePath);
183
283
  } catch (error) {
184
- console.warn(`${chalk.yellow("\u26A0")} \u5220\u9664\u672C\u5730\u6587\u4EF6\u5931\u8D25: ${task.filePath}`);
284
+ console.warn(
285
+ `${getLogSymbol("warning")} \u5220\u9664\u672C\u5730\u6587\u4EF6\u5931\u8D25: ${truncateTerminalText(task.relativeFilePath, 18)}`
286
+ );
185
287
  }
186
288
  }
187
289
  return {
@@ -198,9 +300,8 @@ function vitePluginDeployOss(option) {
198
300
  } catch (error) {
199
301
  if (attempt === maxRetries) {
200
302
  if (!silentLogs) {
201
- console.log(
202
- `${chalk.red("\u2717")} ${task.filePath} => ${error instanceof Error ? error.message : String(error)}`
203
- );
303
+ const reason = error instanceof Error ? error.message : String(error);
304
+ console.log(`${getLogSymbol("danger")} ${truncateTerminalText(task.relativeFilePath, 18)} ${reason}`);
204
305
  }
205
306
  return {
206
307
  success: false,
@@ -213,9 +314,11 @@ function vitePluginDeployOss(option) {
213
314
  };
214
315
  } else {
215
316
  if (!silentLogs) {
216
- console.log(`${chalk.yellow("\u26A0")} ${task.filePath} \u4E0A\u4F20\u5931\u8D25\uFF0C\u6B63\u5728\u91CD\u8BD5 (${attempt}/${maxRetries})...`);
317
+ console.log(
318
+ `${getLogSymbol("warning")} ${truncateTerminalText(task.relativeFilePath, 18)} \u6B63\u5728\u91CD\u8BD5 (${attempt}/${maxRetries})`
319
+ );
217
320
  }
218
- await new Promise((resolve2) => setTimeout(resolve2, 1e3 * attempt));
321
+ await new Promise((resolve3) => setTimeout(resolve3, 1e3 * attempt));
219
322
  }
220
323
  }
221
324
  }
@@ -239,8 +342,8 @@ function vitePluginDeployOss(option) {
239
342
  let retries = 0;
240
343
  const taskCandidates = await Promise.all(
241
344
  files.map(async (relativeFilePath) => {
242
- const filePath = normalizePath(resolve(outDir, relativeFilePath));
243
- const name = normalizeObjectKey(uploadDir, relativeFilePath);
345
+ const filePath = normalizePath(resolve2(outDir, relativeFilePath));
346
+ const name = normalizeObjectKey(normalizedUploadDir, relativeFilePath);
244
347
  try {
245
348
  const fileStats = await stat(filePath);
246
349
  return { task: { filePath, relativeFilePath, name, size: fileStats.size } };
@@ -268,47 +371,58 @@ function vitePluginDeployOss(option) {
268
371
  }
269
372
  const totalBytes = tasks.reduce((sum, task) => sum + task.size, 0);
270
373
  const startAt = Date.now();
271
- const activeFiles = /* @__PURE__ */ new Set();
272
374
  const safeWindowSize = Math.max(1, Math.min(windowSize, tasks.length || 1));
273
375
  const silentLogs = Boolean(useInteractiveOutput);
274
- const spinner = useInteractiveOutput ? ora({ text: "\u51C6\u5907\u4E0A\u4F20...", spinner: "dots12" }).start() : null;
275
- const reportEvery = Math.max(1, Math.ceil(totalFiles / 10));
376
+ const progressBar = useInteractiveOutput ? new cliProgress.SingleBar({
377
+ hideCursor: true,
378
+ clearOnComplete: true,
379
+ stopOnComplete: true,
380
+ barsize: 18,
381
+ barCompleteChar: "\u2588",
382
+ barIncompleteChar: "\u2591",
383
+ format: `${chalk3.gray("\u4E0A\u4F20")} ${chalk3.bold("{percentage}%")} ${chalk3.cyan("{bar}")} ${chalk3.gray("\xB7")} ${chalk3.magenta("{speed}/s")} ${chalk3.gray("\xB7")} ${chalk3.gray("{elapsed}")}s`
384
+ }) : null;
385
+ const reportEvery = Math.max(1, Math.ceil(totalFiles / 6));
276
386
  let lastReportedCompleted = -1;
387
+ if (progressBar) {
388
+ progressBar.start(totalFiles, 0, {
389
+ speed: formatBytes(0),
390
+ elapsed: "0"
391
+ });
392
+ }
277
393
  const updateProgress = () => {
278
- const progressRatio = totalFiles > 0 ? completed / totalFiles : 1;
279
- const percentage = Math.round(progressRatio * 100);
280
394
  const elapsedSeconds = (Date.now() - startAt) / 1e3;
281
395
  const speed = elapsedSeconds > 0 ? uploadedBytes / elapsedSeconds : 0;
282
- const etaSeconds = speed > 0 ? Math.max(0, (totalBytes - uploadedBytes) / speed) : 0;
283
- const activeList = Array.from(activeFiles);
284
- const currentFile = activeList.length > 0 ? trimMiddle(activeList[activeList.length - 1], 86) : "-";
285
- if (!spinner) {
396
+ if (!progressBar) {
397
+ const progressRatio = totalFiles > 0 ? completed / totalFiles : 1;
398
+ const percentage = Math.round(progressRatio * 100);
399
+ if (completed === 0 && totalFiles > 0) return;
286
400
  if (completed === lastReportedCompleted) return;
287
401
  if (completed === totalFiles || completed % reportEvery === 0) {
288
402
  console.log(
289
- `${chalk.gray("\u8FDB\u5EA6:")} ${completed}/${totalFiles} (${percentage}%) | ${chalk.gray("\u6570\u636E:")} ${formatBytes(uploadedBytes)}/${formatBytes(totalBytes)} | ${chalk.gray("\u901F\u5EA6:")} ${formatBytes(speed)}/s`
403
+ `${chalk3.gray("\u4E0A\u4F20\u8FDB\u5EA6")} ${renderInlineStats([
404
+ chalk3.bold(`${completed}/${totalFiles}`),
405
+ `${percentage}%`,
406
+ `${formatBytes(uploadedBytes)}/${formatBytes(totalBytes)}`,
407
+ `${formatBytes(speed)}/s`
408
+ ])}`
290
409
  );
291
410
  lastReportedCompleted = completed;
292
411
  }
293
412
  return;
294
413
  }
295
- const bar = buildCapsuleBar(progressRatio);
296
- const warnLine = retries > 0 || failed > 0 ? `
297
- ${chalk.yellow("\u91CD\u8BD5")}: ${retries} ${chalk.yellow("\u5931\u8D25")}: ${failed}` : "";
298
- spinner.text = [
299
- `${chalk.cyan("\u6B63\u5728\u4E0A\u4F20:")} ${chalk.white(currentFile)}`,
300
- `${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))}`
301
- ].join("\n");
302
- spinner.text += warnLine;
414
+ progressBar.update(completed, {
415
+ speed: chalk3.magenta(formatBytes(speed)),
416
+ elapsed: formatDuration(elapsedSeconds).replace(/s$/, "")
417
+ });
303
418
  };
304
- const refreshTimer = spinner ? setInterval(updateProgress, 120) : null;
419
+ const refreshTimer = progressBar ? setInterval(updateProgress, 120) : null;
305
420
  let currentIndex = 0;
306
421
  const worker = async () => {
307
422
  while (true) {
308
423
  const index = currentIndex++;
309
424
  if (index >= tasks.length) return;
310
425
  const task = tasks[index];
311
- activeFiles.add(task.name);
312
426
  updateProgress();
313
427
  const result = await uploadFileWithRetry(client, task, silentLogs);
314
428
  completed++;
@@ -319,7 +433,6 @@ ${chalk.yellow("\u91CD\u8BD5")}: ${retries} ${chalk.yellow("\u5931\u8D25")}: ${
319
433
  failed++;
320
434
  }
321
435
  results.push(result);
322
- activeFiles.delete(task.name);
323
436
  updateProgress();
324
437
  }
325
438
  };
@@ -329,16 +442,16 @@ ${chalk.yellow("\u91CD\u8BD5")}: ${retries} ${chalk.yellow("\u5931\u8D25")}: ${
329
442
  } finally {
330
443
  if (refreshTimer) clearInterval(refreshTimer);
331
444
  }
332
- if (spinner) {
445
+ if (progressBar) {
333
446
  const elapsedSeconds = (Date.now() - startAt) / 1e3;
334
- const successCount = results.filter((item) => item.success).length;
335
447
  const speed = elapsedSeconds > 0 ? uploadedBytes / elapsedSeconds : 0;
336
- spinner.succeed(
337
- `${chalk.green("\u4E0A\u4F20\u6210\u529F")} ${successCount} \u4E2A\u6587\u4EF6\u3002
338
- ${buildCapsuleBar(1)} 100% (${totalFiles}/${totalFiles}) ${chalk.gray("|")} \u901F\u5EA6 ${chalk.magenta(`${formatBytes(speed)}/s`)} ${chalk.gray("|")} \u8017\u65F6 ${chalk.yellow(formatDuration(elapsedSeconds))}`
339
- );
448
+ progressBar.update(totalFiles, {
449
+ speed: chalk3.magenta(formatBytes(speed)),
450
+ elapsed: formatDuration(elapsedSeconds).replace(/s$/, "")
451
+ });
452
+ progressBar.stop();
340
453
  } else {
341
- console.log(`${chalk.green("\u2714")} \u6240\u6709\u6587\u4EF6\u4E0A\u4F20\u5B8C\u6210 (${totalFiles}/${totalFiles})`);
454
+ console.log(`${getLogSymbol("success")} \u6240\u6709\u6587\u4EF6\u4E0A\u4F20\u5B8C\u6210 (${totalFiles}/${totalFiles})`);
342
455
  }
343
456
  return results;
344
457
  };
@@ -351,20 +464,19 @@ ${buildCapsuleBar(1)} 100% (${totalFiles}/${totalFiles}) ${chalk.gray("|")} \u90
351
464
  },
352
465
  config(config) {
353
466
  if (!open || buildFailed) return;
354
- clearScreen();
355
467
  const validationErrors = validateOptions();
356
468
  if (validationErrors.length > 0) {
357
- console.log(`${chalk.red("\u2717 \u914D\u7F6E\u9519\u8BEF:")}
469
+ console.log(`${chalk3.red("\u2717 \u914D\u7F6E\u9519\u8BEF:")}
358
470
  ${validationErrors.map((err) => ` - ${err}`).join("\n")}`);
359
471
  return;
360
472
  }
361
473
  upload = true;
362
- config.base = configBase || config.base;
474
+ config.base = normalizedConfigBase || config.base;
363
475
  return config;
364
476
  },
365
477
  configResolved(config) {
366
478
  resolvedConfig = config;
367
- outDir = normalizePath(resolve(config.root, config.build.outDir));
479
+ outDir = normalizePath(resolve2(config.root, config.build.outDir));
368
480
  },
369
481
  closeBundle: {
370
482
  sequential: true,
@@ -380,69 +492,49 @@ ${validationErrors.map((err) => ` - ${err}`).join("\n")}`);
380
492
  ignore: Array.isArray(skip) ? skip : [skip]
381
493
  }).map((file) => normalizePath(file)).filter((file) => file !== manifestFileName);
382
494
  if (files.length === 0) {
383
- console.log(`${chalk.yellow("\u26A0 \u6CA1\u6709\u627E\u5230\u9700\u8981\u4E0A\u4F20\u7684\u6587\u4EF6")}`);
495
+ console.log(`${getLogSymbol("warning")} \u6CA1\u6709\u627E\u5230\u9700\u8981\u4E0A\u4F20\u7684\u6587\u4EF6`);
384
496
  return;
385
497
  }
386
- clearScreen();
387
- console.log(chalk.cyan(`
388
- \u{1F680} OSS \u90E8\u7F72\u5F00\u59CB
389
- `));
390
- console.log(`${chalk.gray("Bucket:")} ${chalk.green(bucket)}`);
391
- console.log(`${chalk.gray("Region:")} ${chalk.green(region)}`);
392
- console.log(`${chalk.gray("Source:")} ${chalk.yellow(outDir)}`);
393
- console.log(`${chalk.gray("Target:")} ${chalk.yellow(uploadDir)}`);
394
- if (alias) console.log(`${chalk.gray("Alias:")} ${chalk.green(alias)}`);
395
- console.log(`${chalk.gray("Files:")} ${chalk.blue(files.length)}
396
- `);
498
+ clearViewport();
499
+ console.log(
500
+ renderPanel(
501
+ "\u51C6\u5907\u90E8\u7F72",
502
+ [
503
+ { label: "\u4F4D\u7F6E:", value: chalk3.green(`${bucket} \xB7 ${region}`) },
504
+ {
505
+ label: "\u76EE\u6807:",
506
+ value: chalk3.yellow(
507
+ truncateTerminalText(
508
+ normalizedAlias ? `${normalizedUploadDir || "/"} \xB7 ${normalizedAlias}` : normalizedUploadDir || "/",
509
+ 18
510
+ )
511
+ )
512
+ },
513
+ {
514
+ label: "\u6587\u4EF6:",
515
+ value: chalk3.blue(`${files.length} \u4E2A \xB7 ${truncateTerminalText(outDir, 30)}`)
516
+ }
517
+ ],
518
+ "info"
519
+ )
520
+ );
397
521
  try {
398
522
  const results = await uploadFilesInBatches(client, files, concurrency);
399
523
  const successCount = results.filter((r) => r.success).length;
400
524
  const failedCount = results.length - successCount;
401
525
  const durationSeconds = (Date.now() - startTime) / 1e3;
402
- const duration = durationSeconds.toFixed(2);
403
526
  const uploadedBytes = results.reduce((sum, result) => result.success ? sum + result.size : sum, 0);
404
527
  const retryCount = results.reduce((sum, result) => sum + result.retries, 0);
405
528
  const avgSpeed = durationSeconds > 0 ? uploadedBytes / durationSeconds : 0;
406
- clearScreen();
407
- console.log("\n" + chalk.gray("\u2500".repeat(40)) + "\n");
408
- if (failedCount === 0) {
409
- console.log(`${chalk.green("\u{1F389} \u90E8\u7F72\u6210\u529F!")}`);
410
- } else {
411
- console.log(`${chalk.yellow("\u26A0 \u90E8\u7F72\u5B8C\u6210\u4F46\u5B58\u5728\u9519\u8BEF")}`);
412
- }
413
- console.log(`
414
- ${chalk.gray("\u7EDF\u8BA1:")}`);
415
- console.log(` ${chalk.green("\u2714")} \u6210\u529F: ${chalk.bold(successCount)}`);
416
- if (failedCount > 0) {
417
- console.log(` ${chalk.red("\u2717")} \u5931\u8D25: ${chalk.bold(failedCount)}`);
418
- }
419
- console.log(` ${chalk.cyan("\u21C4")} \u91CD\u8BD5: ${chalk.bold(retryCount)}`);
420
- console.log(` ${chalk.blue("\u{1F4E6}")} \u6570\u636E: ${chalk.bold(formatBytes(uploadedBytes))}`);
421
- console.log(` ${chalk.magenta("\u26A1")} \u5E73\u5747\u901F\u5EA6: ${chalk.bold(`${formatBytes(avgSpeed)}/s`)}`);
422
- console.log(` ${chalk.blue("\u23F1")} \u8017\u65F6: ${chalk.bold(duration)}s`);
423
- console.log("");
424
- if (failedCount > 0) {
425
- const failedItems = results.filter((result) => !result.success);
426
- const previewCount = Math.min(5, failedItems.length);
427
- console.log(chalk.red("\u5931\u8D25\u660E\u7EC6:"));
428
- for (let i = 0; i < previewCount; i++) {
429
- const item = failedItems[i];
430
- const reason = item.error?.message || "unknown error";
431
- console.log(` ${chalk.red("\u2022")} ${item.name} => ${reason}`);
432
- }
433
- if (failedItems.length > previewCount) {
434
- console.log(chalk.gray(` ... \u8FD8\u6709 ${failedItems.length - previewCount} \u4E2A\u5931\u8D25\u6587\u4EF6`));
435
- }
436
- console.log("");
437
- }
529
+ let manifestSummary = null;
438
530
  if (manifestFileName) {
439
531
  const manifestRelativeFilePath = manifestFileName;
440
- const manifestFilePath = normalizePath(resolve(outDir, manifestRelativeFilePath));
441
- const manifestObjectKey = normalizeObjectKey(uploadDir, manifestRelativeFilePath);
532
+ const manifestFilePath = normalizePath(resolve2(outDir, manifestRelativeFilePath));
533
+ const manifestObjectKey = normalizeObjectKey(normalizedUploadDir, manifestRelativeFilePath);
442
534
  await mkdir(dirname(manifestFilePath), { recursive: true });
443
535
  await writeFile(
444
536
  manifestFilePath,
445
- JSON.stringify(createManifestPayload(results, configBase, alias), null, 2),
537
+ JSON.stringify(await createManifestPayload(results, normalizedConfigBase, normalizedAlias), null, 2),
446
538
  "utf8"
447
539
  );
448
540
  const manifestStats = await stat(manifestFilePath);
@@ -451,7 +543,7 @@ ${chalk.gray("\u7EDF\u8BA1:")}`);
451
543
  relativeFilePath: manifestRelativeFilePath,
452
544
  name: manifestObjectKey,
453
545
  size: manifestStats.size,
454
- cacheControl: "no-cache"
546
+ cacheControl: "no-cache, no-store, must-revalidate"
455
547
  });
456
548
  if (!manifestResult.success) {
457
549
  throw manifestResult.error || new Error(`Failed to upload manifest: ${manifestRelativeFilePath}`);
@@ -459,26 +551,62 @@ ${chalk.gray("\u7EDF\u8BA1:")}`);
459
551
  const manifestUrl = resolveUploadedFileUrl(
460
552
  manifestRelativeFilePath,
461
553
  manifestObjectKey,
462
- configBase,
463
- alias
554
+ normalizedConfigBase,
555
+ normalizedAlias
464
556
  );
465
- console.log(chalk.cyan("Manifest:"));
466
- console.log(` ${chalk.gray("File:")} ${chalk.yellow(manifestFilePath)}`);
467
- console.log(` ${chalk.gray("Target:")} ${chalk.yellow(manifestObjectKey)}`);
468
- console.log(` ${chalk.gray("URL:")} ${chalk.green(manifestUrl)}`);
469
- console.log("");
557
+ manifestSummary = truncateTerminalText(manifestUrl || manifestObjectKey, 20);
470
558
  }
471
559
  try {
472
560
  await removeEmptyDirectories(outDir);
473
561
  } catch (error) {
474
- console.warn(`${chalk.yellow("\u26A0 \u6E05\u7406\u7A7A\u76EE\u5F55\u5931\u8D25:")} ${error}`);
562
+ console.warn(`${getLogSymbol("warning")} \u6E05\u7406\u7A7A\u76EE\u5F55\u5931\u8D25: ${error}`);
475
563
  }
564
+ const resultRows = [
565
+ {
566
+ label: "\u7ED3\u679C:",
567
+ value: failedCount === 0 ? chalk3.green(`${successCount}/${results.length} \u5168\u90E8\u6210\u529F`) : chalk3.yellow(`\u6210\u529F ${successCount} \u4E2A\uFF0C\u5931\u8D25 ${failedCount} \u4E2A`)
568
+ },
569
+ {
570
+ label: "\u7EDF\u8BA1:",
571
+ value: renderInlineStats([
572
+ `${retryCount} \u6B21\u91CD\u8BD5`,
573
+ formatBytes(uploadedBytes),
574
+ `${formatBytes(avgSpeed)}/s`,
575
+ formatDuration(durationSeconds)
576
+ ])
577
+ },
578
+ ...manifestSummary ? [{ label: "\u6E05\u5355:", value: chalk3.cyan(manifestSummary) }] : []
579
+ ];
580
+ if (failedCount > 0) {
581
+ const failedItems = results.filter((result) => !result.success).slice(0, 2);
582
+ resultRows.push(
583
+ ...failedItems.map((item, index) => ({
584
+ label: `\u5931\u8D25 ${index + 1}`,
585
+ value: chalk3.red(
586
+ `${truncateTerminalText(item.name, 26)} \xB7 ${truncateTerminalText(item.error?.message || "unknown error", 22)}`
587
+ )
588
+ }))
589
+ );
590
+ if (failedCount > failedItems.length) {
591
+ resultRows.push({
592
+ label: "\u5176\u4F59",
593
+ value: chalk3.gray(`\u8FD8\u6709 ${failedCount - failedItems.length} \u4E2A\u5931\u8D25\u9879\u672A\u5C55\u5F00`)
594
+ });
595
+ }
596
+ }
597
+ console.log(
598
+ renderPanel(
599
+ failedCount === 0 ? `${getLogSymbol("success")} \u90E8\u7F72\u5B8C\u6210` : `${getLogSymbol("warning")} \u90E8\u7F72\u5B8C\u6210`,
600
+ resultRows,
601
+ failedCount === 0 ? "success" : "warning"
602
+ )
603
+ );
476
604
  if (failedCount > 0 && failOnError) {
477
605
  throw new Error(`Failed to upload ${failedCount} of ${results.length} files`);
478
606
  }
479
607
  } catch (error) {
480
608
  console.log(`
481
- ${chalk.red("\u274C \u4E0A\u4F20\u8FC7\u7A0B\u4E2D\u53D1\u751F\u9519\u8BEF:")} ${error}
609
+ ${getLogSymbol("danger")} \u4E0A\u4F20\u8FC7\u7A0B\u4E2D\u53D1\u751F\u9519\u8BEF: ${error}
482
610
  `);
483
611
  if (failOnError) {
484
612
  throw error instanceof Error ? error : new Error(String(error));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vite-plugin-deploy-oss",
3
- "version": "3.2.0",
3
+ "version": "3.3.0",
4
4
  "main": "./dist/index.js",
5
5
  "types": "./dist/index.d.ts",
6
6
  "type": "module",
@@ -40,10 +40,14 @@
40
40
  "vite": "^6.0.3 || ^7 || ^8"
41
41
  },
42
42
  "dependencies": {
43
+ "@types/cli-progress": "^3.11.6",
43
44
  "ali-oss": "^6.23.0",
44
45
  "chalk": "^5.6.2",
46
+ "cli-progress": "^3.12.0",
47
+ "cli-truncate": "^5.2.0",
45
48
  "glob": "^13.0.6",
46
- "ora": "^9.3.0"
49
+ "log-symbols": "^7.0.1",
50
+ "string-width": "^8.2.0"
47
51
  },
48
52
  "scripts": {
49
53
  "build": "tsup",