vite-plugin-deploy-oss 3.2.1 → 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/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,24 +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";
6
+ import { mkdir, stat, unlink, writeFile } from "fs/promises";
7
+ import { dirname, resolve as resolve2 } from "path";
8
+ import { normalizePath } from "vite";
9
+
10
+ // src/utils/file.ts
5
11
  import { createHash } from "crypto";
6
12
  import { createReadStream } from "fs";
7
- import { mkdir, readdir, rm, stat, unlink, writeFile } from "fs/promises";
8
- import { dirname, resolve } from "path";
9
- import ora from "ora";
10
- import { normalizePath } from "vite";
13
+ import { readdir, rm } from "fs/promises";
14
+ import { resolve } from "path";
15
+ var GARBAGE_FILE_REGEX = /(?:Thumbs\.db|\.DS_Store)$/i;
11
16
  var getFileMd5 = (filePath) => {
12
- return new Promise((resolve2, reject) => {
17
+ return new Promise((resolvePromise, reject) => {
13
18
  const hash = createHash("md5");
14
19
  const stream = createReadStream(filePath);
15
20
  stream.on("error", (err) => reject(err));
16
21
  stream.on("data", (chunk) => hash.update(chunk));
17
- stream.on("end", () => resolve2(hash.digest("hex")));
22
+ stream.on("end", () => resolvePromise(hash.digest("hex")));
18
23
  });
19
24
  };
20
- var GARBAGE_FILE_REGEX = /(?:Thumbs\.db|\.DS_Store)$/i;
21
- var DEFAULT_MANIFEST_FILE_NAME = "oss-manifest.json";
22
25
  var removeEmptyDirectories = async (rootDir) => {
23
26
  const deletedDirectories = [];
24
27
  const visit = async (dirPath) => {
@@ -43,9 +46,53 @@ var removeEmptyDirectories = async (rootDir) => {
43
46
  await visit(resolve(rootDir));
44
47
  return deletedDirectories;
45
48
  };
46
- 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);
47
94
  var normalizeManifestFileName = (fileName) => {
48
- const normalized = normalizePath(fileName || DEFAULT_MANIFEST_FILE_NAME).replace(/^\/+/, "").replace(/\/{2,}/g, "/");
95
+ const normalized = normalizePathSegments(fileName || DEFAULT_MANIFEST_FILE_NAME);
49
96
  return normalized || DEFAULT_MANIFEST_FILE_NAME;
50
97
  };
51
98
  var resolveManifestFileName = (manifest) => {
@@ -53,46 +100,16 @@ var resolveManifestFileName = (manifest) => {
53
100
  if (manifest === true) return DEFAULT_MANIFEST_FILE_NAME;
54
101
  return normalizeManifestFileName(manifest.fileName);
55
102
  };
56
- var normalizeUrlBase = (base) => {
57
- const normalized = base.replace(/\\/g, "/");
58
- const protocolSeparatorIndex = normalized.indexOf("://");
59
- if (protocolSeparatorIndex >= 0) {
60
- const pathIndex = normalized.indexOf("/", protocolSeparatorIndex + 3);
61
- if (pathIndex < 0) return normalized;
62
- return `${normalized.slice(0, pathIndex)}${normalized.slice(pathIndex).replace(/\/{2,}/g, "/")}`;
63
- }
64
- if (normalized.startsWith("//")) {
65
- const pathIndex = normalized.indexOf("/", 2);
66
- if (pathIndex < 0) return normalized;
67
- return `${normalized.slice(0, pathIndex)}${normalized.slice(pathIndex).replace(/\/{2,}/g, "/")}`;
68
- }
69
- return normalized.replace(/\/{2,}/g, "/");
70
- };
71
- var encodeUrlPath = (path) => encodeURI(path.replace(/^\/+/, ""));
72
- 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)}`;
73
105
  var resolveUploadedFileUrl = (relativeFilePath, objectKey, configBase, alias) => {
74
106
  if (configBase) return joinUrlPath(configBase, relativeFilePath);
75
107
  if (alias) return joinUrlPath(alias, objectKey);
76
108
  return objectKey;
77
109
  };
78
- var createManifestPayload = async (results, configBase, alias) => {
79
- const successfulResults = results.filter((result) => result.success);
80
- const files = await Promise.all(
81
- successfulResults.map(async (result) => {
82
- const md5 = await getFileMd5(result.file);
83
- return {
84
- file: result.relativeFilePath,
85
- key: result.name,
86
- url: resolveUploadedFileUrl(result.relativeFilePath, result.name, configBase, alias),
87
- md5
88
- };
89
- })
90
- );
91
- return {
92
- version: Date.now(),
93
- files
94
- };
95
- };
110
+
111
+ // src/utils/progress.ts
112
+ import chalk from "chalk";
96
113
  var formatBytes = (bytes) => {
97
114
  if (!Number.isFinite(bytes) || bytes <= 0) return "0 B";
98
115
  const units = ["B", "KB", "MB", "GB", "TB"];
@@ -113,24 +130,84 @@ var formatDuration = (seconds) => {
113
130
  if (mins === 0) return `${secs}s`;
114
131
  return `${mins}m${String(secs).padStart(2, "0")}s`;
115
132
  };
116
- var trimMiddle = (text, maxLength) => {
117
- if (text.length <= maxLength) return text;
118
- if (maxLength <= 10) return text.slice(0, maxLength);
119
- const leftLength = Math.floor((maxLength - 3) / 2);
120
- const rightLength = maxLength - 3 - leftLength;
121
- 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"
122
144
  };
123
- var buildCapsuleBar = (ratio, width = 30) => {
124
- 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) => {
125
149
  if (width <= 0) return "";
126
- if (safeRatio >= 1) {
127
- 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" })));
128
173
  }
129
- const pointerIndex = Math.min(width - 1, Math.floor(width * safeRatio));
130
- const done = pointerIndex > 0 ? chalk.green("\u2588".repeat(pointerIndex)) : "";
131
- const pointer = chalk.cyanBright("\u25B8");
132
- const pending = pointerIndex < width - 1 ? chalk.gray("\u2591".repeat(width - pointerIndex - 1)) : "";
133
- 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
+ };
134
211
  };
135
212
  function vitePluginDeployOss(option) {
136
213
  const {
@@ -155,12 +232,15 @@ function vitePluginDeployOss(option) {
155
232
  manifest = false,
156
233
  ...props
157
234
  } = option || {};
235
+ const normalizedUploadDir = normalizePathSegments(uploadDir);
236
+ const normalizedConfigBase = configBase ? ensureTrailingSlash(normalizeUrlLikeBase(configBase)) : void 0;
237
+ const normalizedAlias = alias ? normalizeUrlLikeBase(alias) : void 0;
158
238
  let buildFailed = false;
159
239
  let upload = false;
160
- let outDir = normalizePath(resolve("dist"));
240
+ let outDir = normalizePath(resolve2("dist"));
161
241
  let resolvedConfig = null;
162
242
  const useInteractiveOutput = fancy && Boolean(process.stdout?.isTTY) && Boolean(process.stderr?.isTTY) && !process.env.CI;
163
- const clearScreen = () => {
243
+ const clearViewport = () => {
164
244
  if (!useInteractiveOutput) return;
165
245
  process.stdout.write("\x1B[2J\x1B[0f");
166
246
  };
@@ -173,8 +253,7 @@ function vitePluginDeployOss(option) {
173
253
  if (!uploadDir) errors.push("uploadDir is required");
174
254
  if (!Number.isInteger(retryTimes) || retryTimes < 1) errors.push("retryTimes must be >= 1");
175
255
  if (!Number.isInteger(concurrency) || concurrency < 1) errors.push("concurrency must be >= 1");
176
- if (!Number.isFinite(multipartThreshold) || multipartThreshold <= 0)
177
- errors.push("multipartThreshold must be > 0");
256
+ if (!Number.isFinite(multipartThreshold) || multipartThreshold <= 0) errors.push("multipartThreshold must be > 0");
178
257
  return errors;
179
258
  };
180
259
  const uploadSingleTask = async (client, task) => uploadFileWithRetry(client, task, false);
@@ -202,7 +281,9 @@ function vitePluginDeployOss(option) {
202
281
  try {
203
282
  await unlink(task.filePath);
204
283
  } catch (error) {
205
- 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
+ );
206
287
  }
207
288
  }
208
289
  return {
@@ -219,9 +300,8 @@ function vitePluginDeployOss(option) {
219
300
  } catch (error) {
220
301
  if (attempt === maxRetries) {
221
302
  if (!silentLogs) {
222
- console.log(
223
- `${chalk.red("\u2717")} ${task.filePath} => ${error instanceof Error ? error.message : String(error)}`
224
- );
303
+ const reason = error instanceof Error ? error.message : String(error);
304
+ console.log(`${getLogSymbol("danger")} ${truncateTerminalText(task.relativeFilePath, 18)} ${reason}`);
225
305
  }
226
306
  return {
227
307
  success: false,
@@ -234,9 +314,11 @@ function vitePluginDeployOss(option) {
234
314
  };
235
315
  } else {
236
316
  if (!silentLogs) {
237
- 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
+ );
238
320
  }
239
- await new Promise((resolve2) => setTimeout(resolve2, 1e3 * attempt));
321
+ await new Promise((resolve3) => setTimeout(resolve3, 1e3 * attempt));
240
322
  }
241
323
  }
242
324
  }
@@ -260,8 +342,8 @@ function vitePluginDeployOss(option) {
260
342
  let retries = 0;
261
343
  const taskCandidates = await Promise.all(
262
344
  files.map(async (relativeFilePath) => {
263
- const filePath = normalizePath(resolve(outDir, relativeFilePath));
264
- const name = normalizeObjectKey(uploadDir, relativeFilePath);
345
+ const filePath = normalizePath(resolve2(outDir, relativeFilePath));
346
+ const name = normalizeObjectKey(normalizedUploadDir, relativeFilePath);
265
347
  try {
266
348
  const fileStats = await stat(filePath);
267
349
  return { task: { filePath, relativeFilePath, name, size: fileStats.size } };
@@ -289,47 +371,58 @@ function vitePluginDeployOss(option) {
289
371
  }
290
372
  const totalBytes = tasks.reduce((sum, task) => sum + task.size, 0);
291
373
  const startAt = Date.now();
292
- const activeFiles = /* @__PURE__ */ new Set();
293
374
  const safeWindowSize = Math.max(1, Math.min(windowSize, tasks.length || 1));
294
375
  const silentLogs = Boolean(useInteractiveOutput);
295
- const spinner = useInteractiveOutput ? ora({ text: "\u51C6\u5907\u4E0A\u4F20...", spinner: "dots12" }).start() : null;
296
- 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));
297
386
  let lastReportedCompleted = -1;
387
+ if (progressBar) {
388
+ progressBar.start(totalFiles, 0, {
389
+ speed: formatBytes(0),
390
+ elapsed: "0"
391
+ });
392
+ }
298
393
  const updateProgress = () => {
299
- const progressRatio = totalFiles > 0 ? completed / totalFiles : 1;
300
- const percentage = Math.round(progressRatio * 100);
301
394
  const elapsedSeconds = (Date.now() - startAt) / 1e3;
302
395
  const speed = elapsedSeconds > 0 ? uploadedBytes / elapsedSeconds : 0;
303
- const etaSeconds = speed > 0 ? Math.max(0, (totalBytes - uploadedBytes) / speed) : 0;
304
- const activeList = Array.from(activeFiles);
305
- const currentFile = activeList.length > 0 ? trimMiddle(activeList[activeList.length - 1], 86) : "-";
306
- 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;
307
400
  if (completed === lastReportedCompleted) return;
308
401
  if (completed === totalFiles || completed % reportEvery === 0) {
309
402
  console.log(
310
- `${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
+ ])}`
311
409
  );
312
410
  lastReportedCompleted = completed;
313
411
  }
314
412
  return;
315
413
  }
316
- const bar = buildCapsuleBar(progressRatio);
317
- const warnLine = retries > 0 || failed > 0 ? `
318
- ${chalk.yellow("\u91CD\u8BD5")}: ${retries} ${chalk.yellow("\u5931\u8D25")}: ${failed}` : "";
319
- spinner.text = [
320
- `${chalk.cyan("\u6B63\u5728\u4E0A\u4F20:")} ${chalk.white(currentFile)}`,
321
- `${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))}`
322
- ].join("\n");
323
- spinner.text += warnLine;
414
+ progressBar.update(completed, {
415
+ speed: chalk3.magenta(formatBytes(speed)),
416
+ elapsed: formatDuration(elapsedSeconds).replace(/s$/, "")
417
+ });
324
418
  };
325
- const refreshTimer = spinner ? setInterval(updateProgress, 120) : null;
419
+ const refreshTimer = progressBar ? setInterval(updateProgress, 120) : null;
326
420
  let currentIndex = 0;
327
421
  const worker = async () => {
328
422
  while (true) {
329
423
  const index = currentIndex++;
330
424
  if (index >= tasks.length) return;
331
425
  const task = tasks[index];
332
- activeFiles.add(task.name);
333
426
  updateProgress();
334
427
  const result = await uploadFileWithRetry(client, task, silentLogs);
335
428
  completed++;
@@ -340,7 +433,6 @@ ${chalk.yellow("\u91CD\u8BD5")}: ${retries} ${chalk.yellow("\u5931\u8D25")}: ${
340
433
  failed++;
341
434
  }
342
435
  results.push(result);
343
- activeFiles.delete(task.name);
344
436
  updateProgress();
345
437
  }
346
438
  };
@@ -350,16 +442,16 @@ ${chalk.yellow("\u91CD\u8BD5")}: ${retries} ${chalk.yellow("\u5931\u8D25")}: ${
350
442
  } finally {
351
443
  if (refreshTimer) clearInterval(refreshTimer);
352
444
  }
353
- if (spinner) {
445
+ if (progressBar) {
354
446
  const elapsedSeconds = (Date.now() - startAt) / 1e3;
355
- const successCount = results.filter((item) => item.success).length;
356
447
  const speed = elapsedSeconds > 0 ? uploadedBytes / elapsedSeconds : 0;
357
- spinner.succeed(
358
- `${chalk.green("\u4E0A\u4F20\u6210\u529F")} ${successCount} \u4E2A\u6587\u4EF6\u3002
359
- ${buildCapsuleBar(1)} 100% (${totalFiles}/${totalFiles}) ${chalk.gray("|")} \u901F\u5EA6 ${chalk.magenta(`${formatBytes(speed)}/s`)} ${chalk.gray("|")} \u8017\u65F6 ${chalk.yellow(formatDuration(elapsedSeconds))}`
360
- );
448
+ progressBar.update(totalFiles, {
449
+ speed: chalk3.magenta(formatBytes(speed)),
450
+ elapsed: formatDuration(elapsedSeconds).replace(/s$/, "")
451
+ });
452
+ progressBar.stop();
361
453
  } else {
362
- 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})`);
363
455
  }
364
456
  return results;
365
457
  };
@@ -372,20 +464,19 @@ ${buildCapsuleBar(1)} 100% (${totalFiles}/${totalFiles}) ${chalk.gray("|")} \u90
372
464
  },
373
465
  config(config) {
374
466
  if (!open || buildFailed) return;
375
- clearScreen();
376
467
  const validationErrors = validateOptions();
377
468
  if (validationErrors.length > 0) {
378
- console.log(`${chalk.red("\u2717 \u914D\u7F6E\u9519\u8BEF:")}
469
+ console.log(`${chalk3.red("\u2717 \u914D\u7F6E\u9519\u8BEF:")}
379
470
  ${validationErrors.map((err) => ` - ${err}`).join("\n")}`);
380
471
  return;
381
472
  }
382
473
  upload = true;
383
- config.base = configBase || config.base;
474
+ config.base = normalizedConfigBase || config.base;
384
475
  return config;
385
476
  },
386
477
  configResolved(config) {
387
478
  resolvedConfig = config;
388
- outDir = normalizePath(resolve(config.root, config.build.outDir));
479
+ outDir = normalizePath(resolve2(config.root, config.build.outDir));
389
480
  },
390
481
  closeBundle: {
391
482
  sequential: true,
@@ -401,69 +492,49 @@ ${validationErrors.map((err) => ` - ${err}`).join("\n")}`);
401
492
  ignore: Array.isArray(skip) ? skip : [skip]
402
493
  }).map((file) => normalizePath(file)).filter((file) => file !== manifestFileName);
403
494
  if (files.length === 0) {
404
- 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`);
405
496
  return;
406
497
  }
407
- clearScreen();
408
- console.log(chalk.cyan(`
409
- \u{1F680} OSS \u90E8\u7F72\u5F00\u59CB
410
- `));
411
- console.log(`${chalk.gray("Bucket:")} ${chalk.green(bucket)}`);
412
- console.log(`${chalk.gray("Region:")} ${chalk.green(region)}`);
413
- console.log(`${chalk.gray("Source:")} ${chalk.yellow(outDir)}`);
414
- console.log(`${chalk.gray("Target:")} ${chalk.yellow(uploadDir)}`);
415
- if (alias) console.log(`${chalk.gray("Alias:")} ${chalk.green(alias)}`);
416
- console.log(`${chalk.gray("Files:")} ${chalk.blue(files.length)}
417
- `);
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
+ );
418
521
  try {
419
522
  const results = await uploadFilesInBatches(client, files, concurrency);
420
523
  const successCount = results.filter((r) => r.success).length;
421
524
  const failedCount = results.length - successCount;
422
525
  const durationSeconds = (Date.now() - startTime) / 1e3;
423
- const duration = durationSeconds.toFixed(2);
424
526
  const uploadedBytes = results.reduce((sum, result) => result.success ? sum + result.size : sum, 0);
425
527
  const retryCount = results.reduce((sum, result) => sum + result.retries, 0);
426
528
  const avgSpeed = durationSeconds > 0 ? uploadedBytes / durationSeconds : 0;
427
- clearScreen();
428
- console.log("\n" + chalk.gray("\u2500".repeat(40)) + "\n");
429
- if (failedCount === 0) {
430
- console.log(`${chalk.green("\u{1F389} \u90E8\u7F72\u6210\u529F!")}`);
431
- } else {
432
- console.log(`${chalk.yellow("\u26A0 \u90E8\u7F72\u5B8C\u6210\u4F46\u5B58\u5728\u9519\u8BEF")}`);
433
- }
434
- console.log(`
435
- ${chalk.gray("\u7EDF\u8BA1:")}`);
436
- console.log(` ${chalk.green("\u2714")} \u6210\u529F: ${chalk.bold(successCount)}`);
437
- if (failedCount > 0) {
438
- console.log(` ${chalk.red("\u2717")} \u5931\u8D25: ${chalk.bold(failedCount)}`);
439
- }
440
- console.log(` ${chalk.cyan("\u21C4")} \u91CD\u8BD5: ${chalk.bold(retryCount)}`);
441
- console.log(` ${chalk.blue("\u{1F4E6}")} \u6570\u636E: ${chalk.bold(formatBytes(uploadedBytes))}`);
442
- console.log(` ${chalk.magenta("\u26A1")} \u5E73\u5747\u901F\u5EA6: ${chalk.bold(`${formatBytes(avgSpeed)}/s`)}`);
443
- console.log(` ${chalk.blue("\u23F1")} \u8017\u65F6: ${chalk.bold(duration)}s`);
444
- console.log("");
445
- if (failedCount > 0) {
446
- const failedItems = results.filter((result) => !result.success);
447
- const previewCount = Math.min(5, failedItems.length);
448
- console.log(chalk.red("\u5931\u8D25\u660E\u7EC6:"));
449
- for (let i = 0; i < previewCount; i++) {
450
- const item = failedItems[i];
451
- const reason = item.error?.message || "unknown error";
452
- console.log(` ${chalk.red("\u2022")} ${item.name} => ${reason}`);
453
- }
454
- if (failedItems.length > previewCount) {
455
- console.log(chalk.gray(` ... \u8FD8\u6709 ${failedItems.length - previewCount} \u4E2A\u5931\u8D25\u6587\u4EF6`));
456
- }
457
- console.log("");
458
- }
529
+ let manifestSummary = null;
459
530
  if (manifestFileName) {
460
531
  const manifestRelativeFilePath = manifestFileName;
461
- const manifestFilePath = normalizePath(resolve(outDir, manifestRelativeFilePath));
462
- const manifestObjectKey = normalizeObjectKey(uploadDir, manifestRelativeFilePath);
532
+ const manifestFilePath = normalizePath(resolve2(outDir, manifestRelativeFilePath));
533
+ const manifestObjectKey = normalizeObjectKey(normalizedUploadDir, manifestRelativeFilePath);
463
534
  await mkdir(dirname(manifestFilePath), { recursive: true });
464
535
  await writeFile(
465
536
  manifestFilePath,
466
- JSON.stringify(await createManifestPayload(results, configBase, alias), null, 2),
537
+ JSON.stringify(await createManifestPayload(results, normalizedConfigBase, normalizedAlias), null, 2),
467
538
  "utf8"
468
539
  );
469
540
  const manifestStats = await stat(manifestFilePath);
@@ -480,26 +551,62 @@ ${chalk.gray("\u7EDF\u8BA1:")}`);
480
551
  const manifestUrl = resolveUploadedFileUrl(
481
552
  manifestRelativeFilePath,
482
553
  manifestObjectKey,
483
- configBase,
484
- alias
554
+ normalizedConfigBase,
555
+ normalizedAlias
485
556
  );
486
- console.log(chalk.cyan("Manifest:"));
487
- console.log(` ${chalk.gray("File:")} ${chalk.yellow(manifestFilePath)}`);
488
- console.log(` ${chalk.gray("Target:")} ${chalk.yellow(manifestObjectKey)}`);
489
- console.log(` ${chalk.gray("URL:")} ${chalk.green(manifestUrl)}`);
490
- console.log("");
557
+ manifestSummary = truncateTerminalText(manifestUrl || manifestObjectKey, 20);
491
558
  }
492
559
  try {
493
560
  await removeEmptyDirectories(outDir);
494
561
  } catch (error) {
495
- 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}`);
496
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
+ );
497
604
  if (failedCount > 0 && failOnError) {
498
605
  throw new Error(`Failed to upload ${failedCount} of ${results.length} files`);
499
606
  }
500
607
  } catch (error) {
501
608
  console.log(`
502
- ${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}
503
610
  `);
504
611
  if (failOnError) {
505
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.1",
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",