vite-plugin-deploy-oss 3.2.1 → 3.3.1

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.
@@ -0,0 +1,5 @@
1
+ dist
2
+ node_modules
3
+ playground/__dist__
4
+ coverage
5
+ *.log
@@ -0,0 +1,12 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/prettierrc",
3
+ "arrowParens": "always",
4
+ "bracketSameLine": false,
5
+ "jsxSingleQuote": true,
6
+ "printWidth": 120,
7
+ "quoteProps": "as-needed",
8
+ "semi": false,
9
+ "singleQuote": true,
10
+ "tabWidth": 2,
11
+ "endOfLine": "lf"
12
+ }
package/README.md CHANGED
@@ -12,6 +12,14 @@
12
12
  pnpm add vite-plugin-deploy-oss -D
13
13
  ```
14
14
 
15
+ ## 调试模式
16
+
17
+ ```bash
18
+ pnpm run build:test:debug
19
+ ```
20
+
21
+ 这会进入带调试信息的上传模式,构建结束后额外输出每个关键步骤花了多久。
22
+
15
23
  ## 使用
16
24
 
17
25
  ```ts
@@ -26,6 +34,8 @@ export default {
26
34
  vitePluginDeployOss({
27
35
  // 建议按环境变量开关上传,避免本地/CI误上传
28
36
  open: process.env.DEPLOY_OSS === '1',
37
+ // 输出调试耗时信息,方便排查慢在哪里
38
+ debug: process.env.DEPLOY_OSS_DEBUG === '1',
29
39
  // 终端实时动效进度面板(默认 true)
30
40
  fancy: true,
31
41
 
@@ -53,6 +63,7 @@ export default {
53
63
 
54
64
  - 当前版本仅支持 ESM(`import`),不再提供 CommonJS(`require`)入口。
55
65
  - `open` 默认 `true`,建议通过环境变量控制开关(例如 `DEPLOY_OSS=1` 时再上传)。
66
+ - `debug` 默认关闭。开启后会在结束时额外输出每个关键步骤花了多久,方便排查慢点和卡点。
56
67
  - `fancy` 默认 `true`,TTY 终端下会显示实时动效进度(速度、预计剩余、并发、当前文件)。
57
68
  - `failOnError` 默认 `true`,上传有失败会抛错,适合 CI 场景保证发布质量。
58
69
  - `manifest` 默认关闭。开启后会在构建目录生成并上传 `oss-manifest.json`。
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;
@@ -17,14 +18,42 @@ interface vitePluginDeployOssOption extends Omit<oss.Options, 'accessKeyId' | 'a
17
18
  autoDelete?: boolean;
18
19
  skip?: string | string[];
19
20
  open?: boolean;
21
+ debug?: boolean;
20
22
  fancy?: boolean;
21
23
  noCache?: boolean;
22
24
  failOnError?: boolean;
23
25
  concurrency?: number;
24
26
  retryTimes?: number;
25
27
  multipartThreshold?: number;
26
- manifest?: boolean | ManifestOption;
28
+ manifest?: ManifestConfig;
29
+ }
30
+ interface UploadResult {
31
+ success: boolean;
32
+ file: string;
33
+ relativeFilePath: string;
34
+ name: string;
35
+ size: number;
36
+ retries: number;
37
+ error?: Error;
38
+ }
39
+ interface UploadTask {
40
+ filePath: string;
41
+ relativeFilePath: string;
42
+ name: string;
43
+ size: number;
44
+ cacheControl?: string;
27
45
  }
46
+ interface ManifestFileItem {
47
+ file: string;
48
+ key: string;
49
+ url: string;
50
+ md5: string;
51
+ }
52
+ interface ManifestPayload {
53
+ version: number;
54
+ files: ManifestFileItem[];
55
+ }
56
+
28
57
  declare function vitePluginDeployOss(option: vitePluginDeployOssOption): Plugin;
29
58
 
30
- export { vitePluginDeployOss as default, type vitePluginDeployOssOption };
59
+ 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,110 @@ 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" })));
173
+ }
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 getPanelDot = (tone = "success") => {
183
+ switch (tone) {
184
+ case "info":
185
+ return chalk2.green("\u25CF");
186
+ case "success":
187
+ return chalk2.green("\u25CF");
188
+ case "warning":
189
+ return chalk2.yellow("\u25CF");
190
+ case "danger":
191
+ return chalk2.red("\u25CF");
192
+ }
193
+ };
194
+ var getLogSymbol = (tone) => {
195
+ switch (tone) {
196
+ case "success":
197
+ return logSymbols.success;
198
+ case "warning":
199
+ return logSymbols.warning;
200
+ case "danger":
201
+ return logSymbols.error;
128
202
  }
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}`;
203
+ };
204
+
205
+ // src/index.ts
206
+ var formatTimingDuration = (durationMs) => {
207
+ if (durationMs < 1e3) return `${durationMs}ms`;
208
+ const seconds = durationMs / 1e3;
209
+ return `${seconds.toFixed(seconds >= 10 ? 1 : 2)}s`;
210
+ };
211
+ var renderDebugPanel = (entries) => {
212
+ const rows = entries.map((entry) => ({
213
+ label: `${entry.label}:`,
214
+ value: chalk3.cyan(
215
+ entry.detail ? `${formatTimingDuration(entry.durationMs)} \xB7 ${truncateTerminalText(entry.detail, 24)}` : formatTimingDuration(entry.durationMs)
216
+ )
217
+ }));
218
+ return renderPanel(`${getPanelDot("success")} \u8C03\u8BD5\u8017\u65F6`, rows, "info");
219
+ };
220
+ var createManifestPayload = async (results, configBase, alias) => {
221
+ const successfulResults = results.filter((result) => result.success);
222
+ const files = await Promise.all(
223
+ successfulResults.map(async (result) => {
224
+ const md5 = await getFileMd5(result.file);
225
+ return {
226
+ file: result.relativeFilePath,
227
+ key: result.name,
228
+ url: resolveUploadedFileUrl(result.relativeFilePath, result.name, configBase, alias),
229
+ md5
230
+ };
231
+ })
232
+ );
233
+ return {
234
+ version: Date.now(),
235
+ files
236
+ };
134
237
  };
135
238
  function vitePluginDeployOss(option) {
136
239
  const {
@@ -146,6 +249,7 @@ function vitePluginDeployOss(option) {
146
249
  autoDelete = false,
147
250
  alias,
148
251
  open = true,
252
+ debug = false,
149
253
  fancy = true,
150
254
  noCache = false,
151
255
  failOnError = true,
@@ -155,12 +259,15 @@ function vitePluginDeployOss(option) {
155
259
  manifest = false,
156
260
  ...props
157
261
  } = option || {};
262
+ const normalizedUploadDir = normalizePathSegments(uploadDir);
263
+ const normalizedConfigBase = configBase ? ensureTrailingSlash(normalizeUrlLikeBase(configBase)) : void 0;
264
+ const normalizedAlias = alias ? normalizeUrlLikeBase(alias) : void 0;
158
265
  let buildFailed = false;
159
266
  let upload = false;
160
- let outDir = normalizePath(resolve("dist"));
267
+ let outDir = normalizePath(resolve2("dist"));
161
268
  let resolvedConfig = null;
162
269
  const useInteractiveOutput = fancy && Boolean(process.stdout?.isTTY) && Boolean(process.stderr?.isTTY) && !process.env.CI;
163
- const clearScreen = () => {
270
+ const clearViewport = () => {
164
271
  if (!useInteractiveOutput) return;
165
272
  process.stdout.write("\x1B[2J\x1B[0f");
166
273
  };
@@ -173,8 +280,7 @@ function vitePluginDeployOss(option) {
173
280
  if (!uploadDir) errors.push("uploadDir is required");
174
281
  if (!Number.isInteger(retryTimes) || retryTimes < 1) errors.push("retryTimes must be >= 1");
175
282
  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");
283
+ if (!Number.isFinite(multipartThreshold) || multipartThreshold <= 0) errors.push("multipartThreshold must be > 0");
178
284
  return errors;
179
285
  };
180
286
  const uploadSingleTask = async (client, task) => uploadFileWithRetry(client, task, false);
@@ -202,7 +308,9 @@ function vitePluginDeployOss(option) {
202
308
  try {
203
309
  await unlink(task.filePath);
204
310
  } catch (error) {
205
- console.warn(`${chalk.yellow("\u26A0")} \u5220\u9664\u672C\u5730\u6587\u4EF6\u5931\u8D25: ${task.filePath}`);
311
+ console.warn(
312
+ `${getLogSymbol("warning")} \u5220\u9664\u672C\u5730\u6587\u4EF6\u5931\u8D25: ${truncateTerminalText(task.relativeFilePath, 18)}`
313
+ );
206
314
  }
207
315
  }
208
316
  return {
@@ -219,9 +327,8 @@ function vitePluginDeployOss(option) {
219
327
  } catch (error) {
220
328
  if (attempt === maxRetries) {
221
329
  if (!silentLogs) {
222
- console.log(
223
- `${chalk.red("\u2717")} ${task.filePath} => ${error instanceof Error ? error.message : String(error)}`
224
- );
330
+ const reason = error instanceof Error ? error.message : String(error);
331
+ console.log(`${getLogSymbol("danger")} ${truncateTerminalText(task.relativeFilePath, 18)} ${reason}`);
225
332
  }
226
333
  return {
227
334
  success: false,
@@ -234,9 +341,11 @@ function vitePluginDeployOss(option) {
234
341
  };
235
342
  } else {
236
343
  if (!silentLogs) {
237
- console.log(`${chalk.yellow("\u26A0")} ${task.filePath} \u4E0A\u4F20\u5931\u8D25\uFF0C\u6B63\u5728\u91CD\u8BD5 (${attempt}/${maxRetries})...`);
344
+ console.log(
345
+ `${getLogSymbol("warning")} ${truncateTerminalText(task.relativeFilePath, 18)} \u6B63\u5728\u91CD\u8BD5 (${attempt}/${maxRetries})`
346
+ );
238
347
  }
239
- await new Promise((resolve2) => setTimeout(resolve2, 1e3 * attempt));
348
+ await new Promise((resolve3) => setTimeout(resolve3, 1e3 * attempt));
240
349
  }
241
350
  }
242
351
  }
@@ -252,16 +361,18 @@ function vitePluginDeployOss(option) {
252
361
  };
253
362
  const uploadFilesInBatches = async (client, files, windowSize = concurrency) => {
254
363
  const results = [];
364
+ const debugEntries = [];
255
365
  const totalFiles = files.length;
256
366
  const tasks = [];
257
367
  let completed = 0;
258
368
  let failed = 0;
259
369
  let uploadedBytes = 0;
260
370
  let retries = 0;
371
+ const taskPrepareStartedAt = Date.now();
261
372
  const taskCandidates = await Promise.all(
262
373
  files.map(async (relativeFilePath) => {
263
- const filePath = normalizePath(resolve(outDir, relativeFilePath));
264
- const name = normalizeObjectKey(uploadDir, relativeFilePath);
374
+ const filePath = normalizePath(resolve2(outDir, relativeFilePath));
375
+ const name = normalizeObjectKey(normalizedUploadDir, relativeFilePath);
265
376
  try {
266
377
  const fileStats = await stat(filePath);
267
378
  return { task: { filePath, relativeFilePath, name, size: fileStats.size } };
@@ -270,6 +381,11 @@ function vitePluginDeployOss(option) {
270
381
  }
271
382
  })
272
383
  );
384
+ debugEntries.push({
385
+ label: "\u751F\u6210\u4E0A\u4F20\u4EFB\u52A1",
386
+ durationMs: Date.now() - taskPrepareStartedAt,
387
+ detail: `${files.length} \u4E2A\u6587\u4EF6`
388
+ });
273
389
  for (const candidate of taskCandidates) {
274
390
  if (candidate.task) {
275
391
  tasks.push(candidate.task);
@@ -289,47 +405,58 @@ function vitePluginDeployOss(option) {
289
405
  }
290
406
  const totalBytes = tasks.reduce((sum, task) => sum + task.size, 0);
291
407
  const startAt = Date.now();
292
- const activeFiles = /* @__PURE__ */ new Set();
293
408
  const safeWindowSize = Math.max(1, Math.min(windowSize, tasks.length || 1));
294
409
  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));
410
+ const progressBar = useInteractiveOutput ? new cliProgress.SingleBar({
411
+ hideCursor: true,
412
+ clearOnComplete: true,
413
+ stopOnComplete: true,
414
+ barsize: 18,
415
+ barCompleteChar: "\u2588",
416
+ barIncompleteChar: "\u2591",
417
+ 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`
418
+ }) : null;
419
+ const reportEvery = Math.max(1, Math.ceil(totalFiles / 6));
297
420
  let lastReportedCompleted = -1;
421
+ if (progressBar) {
422
+ progressBar.start(totalFiles, 0, {
423
+ speed: formatBytes(0),
424
+ elapsed: "0"
425
+ });
426
+ }
298
427
  const updateProgress = () => {
299
- const progressRatio = totalFiles > 0 ? completed / totalFiles : 1;
300
- const percentage = Math.round(progressRatio * 100);
301
428
  const elapsedSeconds = (Date.now() - startAt) / 1e3;
302
429
  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) {
430
+ if (!progressBar) {
431
+ const progressRatio = totalFiles > 0 ? completed / totalFiles : 1;
432
+ const percentage = Math.round(progressRatio * 100);
433
+ if (completed === 0 && totalFiles > 0) return;
307
434
  if (completed === lastReportedCompleted) return;
308
435
  if (completed === totalFiles || completed % reportEvery === 0) {
309
436
  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`
437
+ `${chalk3.gray("\u4E0A\u4F20\u8FDB\u5EA6")} ${renderInlineStats([
438
+ chalk3.bold(`${completed}/${totalFiles}`),
439
+ `${percentage}%`,
440
+ `${formatBytes(uploadedBytes)}/${formatBytes(totalBytes)}`,
441
+ `${formatBytes(speed)}/s`
442
+ ])}`
311
443
  );
312
444
  lastReportedCompleted = completed;
313
445
  }
314
446
  return;
315
447
  }
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;
448
+ progressBar.update(completed, {
449
+ speed: chalk3.magenta(formatBytes(speed)),
450
+ elapsed: formatDuration(elapsedSeconds).replace(/s$/, "")
451
+ });
324
452
  };
325
- const refreshTimer = spinner ? setInterval(updateProgress, 120) : null;
453
+ const refreshTimer = progressBar ? setInterval(updateProgress, 120) : null;
326
454
  let currentIndex = 0;
327
455
  const worker = async () => {
328
456
  while (true) {
329
457
  const index = currentIndex++;
330
458
  if (index >= tasks.length) return;
331
459
  const task = tasks[index];
332
- activeFiles.add(task.name);
333
460
  updateProgress();
334
461
  const result = await uploadFileWithRetry(client, task, silentLogs);
335
462
  completed++;
@@ -340,7 +467,6 @@ ${chalk.yellow("\u91CD\u8BD5")}: ${retries} ${chalk.yellow("\u5931\u8D25")}: ${
340
467
  failed++;
341
468
  }
342
469
  results.push(result);
343
- activeFiles.delete(task.name);
344
470
  updateProgress();
345
471
  }
346
472
  };
@@ -350,18 +476,23 @@ ${chalk.yellow("\u91CD\u8BD5")}: ${retries} ${chalk.yellow("\u5931\u8D25")}: ${
350
476
  } finally {
351
477
  if (refreshTimer) clearInterval(refreshTimer);
352
478
  }
353
- if (spinner) {
479
+ if (progressBar) {
354
480
  const elapsedSeconds = (Date.now() - startAt) / 1e3;
355
- const successCount = results.filter((item) => item.success).length;
356
481
  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
- );
482
+ progressBar.update(totalFiles, {
483
+ speed: chalk3.magenta(formatBytes(speed)),
484
+ elapsed: formatDuration(elapsedSeconds).replace(/s$/, "")
485
+ });
486
+ progressBar.stop();
361
487
  } else {
362
- console.log(`${chalk.green("\u2714")} \u6240\u6709\u6587\u4EF6\u4E0A\u4F20\u5B8C\u6210 (${totalFiles}/${totalFiles})`);
488
+ console.log(`${getLogSymbol("success")} \u6240\u6709\u6587\u4EF6\u4E0A\u4F20\u5B8C\u6210 (${totalFiles}/${totalFiles})`);
363
489
  }
364
- return results;
490
+ debugEntries.push({
491
+ label: "\u4E0A\u4F20\u6587\u4EF6",
492
+ durationMs: Date.now() - startAt,
493
+ detail: `${tasks.length} \u4E2A\u6210\u529F\u5019\u9009 \xB7 \u5E76\u53D1 ${safeWindowSize}`
494
+ });
495
+ return { results, debugEntries };
365
496
  };
366
497
  return {
367
498
  name: "vite-plugin-deploy-oss",
@@ -372,20 +503,19 @@ ${buildCapsuleBar(1)} 100% (${totalFiles}/${totalFiles}) ${chalk.gray("|")} \u90
372
503
  },
373
504
  config(config) {
374
505
  if (!open || buildFailed) return;
375
- clearScreen();
376
506
  const validationErrors = validateOptions();
377
507
  if (validationErrors.length > 0) {
378
- console.log(`${chalk.red("\u2717 \u914D\u7F6E\u9519\u8BEF:")}
508
+ console.log(`${chalk3.red("\u2717 \u914D\u7F6E\u9519\u8BEF:")}
379
509
  ${validationErrors.map((err) => ` - ${err}`).join("\n")}`);
380
510
  return;
381
511
  }
382
512
  upload = true;
383
- config.base = configBase || config.base;
513
+ config.base = normalizedConfigBase || config.base;
384
514
  return config;
385
515
  },
386
516
  configResolved(config) {
387
517
  resolvedConfig = config;
388
- outDir = normalizePath(resolve(config.root, config.build.outDir));
518
+ outDir = normalizePath(resolve2(config.root, config.build.outDir));
389
519
  },
390
520
  closeBundle: {
391
521
  sequential: true,
@@ -393,80 +523,80 @@ ${validationErrors.map((err) => ` - ${err}`).join("\n")}`);
393
523
  async handler() {
394
524
  if (!open || !upload || buildFailed || !resolvedConfig) return;
395
525
  const startTime = Date.now();
526
+ const debugEntries = [];
396
527
  const client = new oss({ region, accessKeyId, accessKeySecret, secure, bucket, ...props });
397
528
  const manifestFileName = resolveManifestFileName(manifest);
529
+ const collectFilesStartedAt = Date.now();
398
530
  const files = globSync("**/*", {
399
531
  cwd: outDir,
400
532
  nodir: true,
401
533
  ignore: Array.isArray(skip) ? skip : [skip]
402
534
  }).map((file) => normalizePath(file)).filter((file) => file !== manifestFileName);
535
+ debugEntries.push({
536
+ label: "\u626B\u63CF\u672C\u5730\u6587\u4EF6",
537
+ durationMs: Date.now() - collectFilesStartedAt,
538
+ detail: `${files.length} \u4E2A\u6587\u4EF6`
539
+ });
403
540
  if (files.length === 0) {
404
- console.log(`${chalk.yellow("\u26A0 \u6CA1\u6709\u627E\u5230\u9700\u8981\u4E0A\u4F20\u7684\u6587\u4EF6")}`);
541
+ console.log(`${getLogSymbol("warning")} \u6CA1\u6709\u627E\u5230\u9700\u8981\u4E0A\u4F20\u7684\u6587\u4EF6`);
405
542
  return;
406
543
  }
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
- `);
544
+ clearViewport();
545
+ console.log(
546
+ renderPanel(
547
+ `${getPanelDot("success")} \u51C6\u5907\u90E8\u7F72`,
548
+ [
549
+ { label: "\u4F4D\u7F6E:", value: chalk3.green(`${bucket} \xB7 ${region}`) },
550
+ {
551
+ label: "\u76EE\u6807:",
552
+ value: chalk3.yellow(
553
+ truncateTerminalText(
554
+ normalizedAlias ? `${normalizedUploadDir || "/"} \xB7 ${normalizedAlias}` : normalizedUploadDir || "/",
555
+ 18
556
+ )
557
+ )
558
+ },
559
+ {
560
+ label: "\u6587\u4EF6:",
561
+ value: chalk3.blue(`${files.length} \u4E2A \xB7 ${truncateTerminalText(outDir, 30)}`)
562
+ }
563
+ ],
564
+ "info"
565
+ )
566
+ );
418
567
  try {
419
- const results = await uploadFilesInBatches(client, files, concurrency);
568
+ const uploadExecution = await uploadFilesInBatches(client, files, concurrency);
569
+ const { results, debugEntries: uploadDebugEntries } = uploadExecution;
570
+ if (debug) {
571
+ debugEntries.push(...uploadDebugEntries);
572
+ }
420
573
  const successCount = results.filter((r) => r.success).length;
421
574
  const failedCount = results.length - successCount;
422
575
  const durationSeconds = (Date.now() - startTime) / 1e3;
423
- const duration = durationSeconds.toFixed(2);
424
576
  const uploadedBytes = results.reduce((sum, result) => result.success ? sum + result.size : sum, 0);
425
577
  const retryCount = results.reduce((sum, result) => sum + result.retries, 0);
426
578
  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
- }
579
+ let manifestSummary = null;
459
580
  if (manifestFileName) {
460
581
  const manifestRelativeFilePath = manifestFileName;
461
- const manifestFilePath = normalizePath(resolve(outDir, manifestRelativeFilePath));
462
- const manifestObjectKey = normalizeObjectKey(uploadDir, manifestRelativeFilePath);
582
+ const manifestFilePath = normalizePath(resolve2(outDir, manifestRelativeFilePath));
583
+ const manifestObjectKey = normalizeObjectKey(normalizedUploadDir, manifestRelativeFilePath);
584
+ const manifestStartedAt = Date.now();
463
585
  await mkdir(dirname(manifestFilePath), { recursive: true });
464
586
  await writeFile(
465
587
  manifestFilePath,
466
- JSON.stringify(await createManifestPayload(results, configBase, alias), null, 2),
588
+ JSON.stringify(await createManifestPayload(results, normalizedConfigBase, normalizedAlias), null, 2),
467
589
  "utf8"
468
590
  );
591
+ if (debug) {
592
+ debugEntries.push({
593
+ label: "\u751F\u6210\u6E05\u5355\u6587\u4EF6",
594
+ durationMs: Date.now() - manifestStartedAt,
595
+ detail: manifestRelativeFilePath
596
+ });
597
+ }
469
598
  const manifestStats = await stat(manifestFilePath);
599
+ const manifestUploadStartedAt = Date.now();
470
600
  const manifestResult = await uploadSingleTask(client, {
471
601
  filePath: manifestFilePath,
472
602
  relativeFilePath: manifestRelativeFilePath,
@@ -477,30 +607,94 @@ ${chalk.gray("\u7EDF\u8BA1:")}`);
477
607
  if (!manifestResult.success) {
478
608
  throw manifestResult.error || new Error(`Failed to upload manifest: ${manifestRelativeFilePath}`);
479
609
  }
610
+ if (debug) {
611
+ debugEntries.push({
612
+ label: "\u4E0A\u4F20\u6E05\u5355\u6587\u4EF6",
613
+ durationMs: Date.now() - manifestUploadStartedAt,
614
+ detail: manifestRelativeFilePath
615
+ });
616
+ }
480
617
  const manifestUrl = resolveUploadedFileUrl(
481
618
  manifestRelativeFilePath,
482
619
  manifestObjectKey,
483
- configBase,
484
- alias
620
+ normalizedConfigBase,
621
+ normalizedAlias
485
622
  );
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("");
623
+ manifestSummary = truncateTerminalText(manifestUrl || manifestObjectKey, 20);
491
624
  }
492
625
  try {
626
+ const cleanupStartedAt = Date.now();
493
627
  await removeEmptyDirectories(outDir);
628
+ if (debug) {
629
+ debugEntries.push({
630
+ label: "\u6E05\u7406\u7A7A\u76EE\u5F55",
631
+ durationMs: Date.now() - cleanupStartedAt
632
+ });
633
+ }
494
634
  } catch (error) {
495
- console.warn(`${chalk.yellow("\u26A0 \u6E05\u7406\u7A7A\u76EE\u5F55\u5931\u8D25:")} ${error}`);
635
+ console.warn(`${getLogSymbol("warning")} \u6E05\u7406\u7A7A\u76EE\u5F55\u5931\u8D25: ${error}`);
636
+ }
637
+ const resultRows = [
638
+ {
639
+ label: "\u7ED3\u679C:",
640
+ value: failedCount === 0 ? chalk3.green(`${successCount}/${results.length} \u5168\u90E8\u6210\u529F`) : chalk3.yellow(`\u6210\u529F ${successCount} \u4E2A\uFF0C\u5931\u8D25 ${failedCount} \u4E2A`)
641
+ },
642
+ {
643
+ label: "\u7EDF\u8BA1:",
644
+ value: renderInlineStats([
645
+ `${retryCount} \u6B21\u91CD\u8BD5`,
646
+ formatBytes(uploadedBytes),
647
+ `${formatBytes(avgSpeed)}/s`,
648
+ formatDuration(durationSeconds)
649
+ ])
650
+ },
651
+ ...manifestSummary ? [{ label: "\u6E05\u5355:", value: chalk3.cyan(manifestSummary) }] : []
652
+ ];
653
+ if (failedCount > 0) {
654
+ const failedItems = results.filter((result) => !result.success).slice(0, 2);
655
+ resultRows.push(
656
+ ...failedItems.map((item, index) => ({
657
+ label: `\u5931\u8D25 ${index + 1}`,
658
+ value: chalk3.red(
659
+ `${truncateTerminalText(item.name, 26)} \xB7 ${truncateTerminalText(item.error?.message || "unknown error", 22)}`
660
+ )
661
+ }))
662
+ );
663
+ if (failedCount > failedItems.length) {
664
+ resultRows.push({
665
+ label: "\u5176\u4F59",
666
+ value: chalk3.gray(`\u8FD8\u6709 ${failedCount - failedItems.length} \u4E2A\u5931\u8D25\u9879\u672A\u5C55\u5F00`)
667
+ });
668
+ }
669
+ }
670
+ console.log(
671
+ renderPanel(
672
+ failedCount === 0 ? `${getPanelDot("success")} \u90E8\u7F72\u5B8C\u6210` : `${getPanelDot("warning")} \u90E8\u7F72\u5B8C\u6210`,
673
+ resultRows,
674
+ failedCount === 0 ? "success" : "warning"
675
+ )
676
+ );
677
+ if (debug) {
678
+ debugEntries.push({
679
+ label: "\u603B\u8017\u65F6",
680
+ durationMs: Date.now() - startTime
681
+ });
682
+ console.log(renderDebugPanel(debugEntries));
496
683
  }
497
684
  if (failedCount > 0 && failOnError) {
498
685
  throw new Error(`Failed to upload ${failedCount} of ${results.length} files`);
499
686
  }
500
687
  } catch (error) {
501
688
  console.log(`
502
- ${chalk.red("\u274C \u4E0A\u4F20\u8FC7\u7A0B\u4E2D\u53D1\u751F\u9519\u8BEF:")} ${error}
689
+ ${getLogSymbol("danger")} \u4E0A\u4F20\u8FC7\u7A0B\u4E2D\u53D1\u751F\u9519\u8BEF: ${error}
503
690
  `);
691
+ if (debug && debugEntries.length > 0) {
692
+ debugEntries.push({
693
+ label: "\u5931\u8D25\u524D\u8017\u65F6",
694
+ durationMs: Date.now() - startTime
695
+ });
696
+ console.log(renderDebugPanel(debugEntries));
697
+ }
504
698
  if (failOnError) {
505
699
  throw error instanceof Error ? error : new Error(String(error));
506
700
  }
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.1",
4
4
  "main": "./dist/index.js",
5
5
  "types": "./dist/index.d.ts",
6
6
  "type": "module",
@@ -33,6 +33,7 @@
33
33
  "devDependencies": {
34
34
  "@types/ali-oss": "^6.23.3",
35
35
  "@types/node": "^22.19.13",
36
+ "prettier": "3.8.1",
36
37
  "tsup": "^8.5.1",
37
38
  "typescript": "^5.9.3"
38
39
  },
@@ -40,16 +41,23 @@
40
41
  "vite": "^6.0.3 || ^7 || ^8"
41
42
  },
42
43
  "dependencies": {
44
+ "@types/cli-progress": "^3.11.6",
43
45
  "ali-oss": "^6.23.0",
44
46
  "chalk": "^5.6.2",
47
+ "cli-progress": "^3.12.0",
48
+ "cli-truncate": "^5.2.0",
45
49
  "glob": "^13.0.6",
46
- "ora": "^9.3.0"
50
+ "log-symbols": "^7.0.1",
51
+ "string-width": "^8.2.0"
47
52
  },
48
53
  "scripts": {
49
54
  "build": "tsup",
50
55
  "typecheck": "tsc -p tsconfig.json",
51
56
  "pack": "pnpm run build && pnpm pack",
57
+ "format": "prettier --write .",
58
+ "format:check": "prettier --check .",
52
59
  "build:test": "cd playground && vite build",
53
- "build:test:deploy": "cd playground && vite build --mode deploy"
60
+ "build:test:deploy": "cd playground && vite build --mode deploy",
61
+ "build:test:debug": "cd playground && vite build --mode deploy-debug"
54
62
  }
55
63
  }