vite-plugin-deploy-ftp 1.2.2 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,147 +1,459 @@
1
- "use strict";
2
- var __create = Object.create;
3
- var __defProp = Object.defineProperty;
4
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
- var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
- var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __export = (target, all) => {
9
- for (var name in all)
10
- __defProp(target, name, { get: all[name], enumerable: true });
1
+ // src/index.ts
2
+ import { checkbox, select } from "@inquirer/prompts";
3
+ import archiver from "archiver";
4
+ import { Client } from "basic-ftp";
5
+ import chalk from "chalk";
6
+ import dayjs from "dayjs";
7
+ import fs from "fs";
8
+ import { stat } from "fs/promises";
9
+ import os from "os";
10
+ import path from "path";
11
+ import ora from "ora";
12
+ import { normalizePath } from "vite";
13
+ var formatBytes = (bytes) => {
14
+ if (!Number.isFinite(bytes) || bytes <= 0) return "0 B";
15
+ const units = ["B", "KB", "MB", "GB", "TB"];
16
+ let value = bytes;
17
+ let unitIndex = 0;
18
+ while (value >= 1024 && unitIndex < units.length - 1) {
19
+ value /= 1024;
20
+ unitIndex++;
21
+ }
22
+ const digits = value >= 100 || unitIndex === 0 ? 0 : 1;
23
+ return `${value.toFixed(digits)} ${units[unitIndex]}`;
24
+ };
25
+ var formatDuration = (seconds) => {
26
+ if (!Number.isFinite(seconds) || seconds < 0) return "--";
27
+ const rounded = Math.round(seconds);
28
+ const mins = Math.floor(rounded / 60);
29
+ const secs = rounded % 60;
30
+ if (mins === 0) return `${secs}s`;
31
+ return `${mins}m${String(secs).padStart(2, "0")}s`;
32
+ };
33
+ var trimMiddle = (text, maxLength) => {
34
+ if (text.length <= maxLength) return text;
35
+ if (maxLength <= 10) return text.slice(0, maxLength);
36
+ const leftLength = Math.floor((maxLength - 3) / 2);
37
+ const rightLength = maxLength - 3 - leftLength;
38
+ return `${text.slice(0, leftLength)}...${text.slice(-rightLength)}`;
11
39
  };
12
- var __copyProps = (to, from, except, desc) => {
13
- if (from && typeof from === "object" || typeof from === "function") {
14
- for (let key of __getOwnPropNames(from))
15
- if (!__hasOwnProp.call(to, key) && key !== except)
16
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
40
+ var buildCapsuleBar = (ratio, width = 30) => {
41
+ const safeRatio = Math.max(0, Math.min(1, ratio));
42
+ if (width <= 0) return "";
43
+ if (safeRatio >= 1) {
44
+ return chalk.green("\u2588".repeat(width));
17
45
  }
18
- return to;
46
+ const pointerIndex = Math.min(width - 1, Math.floor(width * safeRatio));
47
+ const done = pointerIndex > 0 ? chalk.green("\u2588".repeat(pointerIndex)) : "";
48
+ const pointer = chalk.cyanBright("\u25B8");
49
+ const pending = pointerIndex < width - 1 ? chalk.gray("\u2591".repeat(width - pointerIndex - 1)) : "";
50
+ return `${done}${pointer}${pending}`;
51
+ };
52
+ var normalizeRemotePath = (targetDir, relativeFilePath) => {
53
+ const joined = normalizePath(`${targetDir}/${relativeFilePath}`).replace(/\/{2,}/g, "/");
54
+ if (targetDir.startsWith("/")) return joined.startsWith("/") ? joined : `/${joined}`;
55
+ return joined.replace(/^\/+/, "");
56
+ };
57
+ var sleep = async (ms) => {
58
+ await new Promise((resolve) => setTimeout(resolve, ms));
19
59
  };
20
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
- // If the importer is in node compatibility mode or this is not an ESM
22
- // file that has been converted to a CommonJS file using a Babel-
23
- // compatible transform (i.e. "__esModule" has not been set), then set
24
- // "default" to the CommonJS "module.exports" for node compatibility.
25
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
- mod
27
- ));
28
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
-
30
- // src/index.ts
31
- var index_exports = {};
32
- __export(index_exports, {
33
- default: () => vitePluginDeployFtp
34
- });
35
- module.exports = __toCommonJS(index_exports);
36
- var import_prompts = require("@inquirer/prompts");
37
- var import_archiver = __toESM(require("archiver"));
38
- var import_basic_ftp = require("basic-ftp");
39
- var import_chalk = __toESM(require("chalk"));
40
- var import_dayjs = __toESM(require("dayjs"));
41
- var import_node_fs = __toESM(require("fs"));
42
- var import_node_os = __toESM(require("os"));
43
- var import_node_path = __toESM(require("path"));
44
- var import_ora = __toESM(require("ora"));
45
- var import_vite = require("vite");
46
60
  function vitePluginDeployFtp(option) {
61
+ const safeOption = option || {};
47
62
  const {
48
63
  open = true,
49
- uploadPath,
64
+ uploadPath = "",
50
65
  singleBack = false,
51
66
  singleBackFiles = ["index.html"],
52
67
  showBackFile = false,
53
68
  maxRetries = 3,
54
69
  retryDelay = 1e3,
55
- autoUpload = false
56
- } = option || {};
57
- const isMultiFtp = "ftps" in option;
58
- const ftpConfigs = isMultiFtp ? option.ftps : [{ ...option, name: option.name || option.alias || option.host }];
59
- const defaultFtp = isMultiFtp ? option.defaultFtp : void 0;
60
- if (!uploadPath || isMultiFtp && (!option.ftps || option.ftps.length === 0)) {
70
+ autoUpload = false,
71
+ fancy = true,
72
+ failOnError = true,
73
+ concurrency = 1
74
+ } = safeOption;
75
+ const isMultiFtp = "ftps" in safeOption;
76
+ const ftpConfigs = isMultiFtp ? safeOption.ftps || [] : [{ ...safeOption, name: safeOption.name || safeOption.alias || safeOption.host }];
77
+ const defaultFtp = isMultiFtp ? safeOption.defaultFtp : void 0;
78
+ let outDir = normalizePath(path.resolve("dist"));
79
+ let upload = false;
80
+ let buildFailed = false;
81
+ let resolvedConfig = null;
82
+ const useInteractiveOutput = fancy && Boolean(process.stdout?.isTTY) && Boolean(process.stderr?.isTTY) && !process.env.CI;
83
+ const clearScreen = () => {
84
+ if (!useInteractiveOutput) return;
85
+ process.stdout.write("\x1B[2J\x1B[0f");
86
+ };
87
+ const validateOptions = () => {
88
+ const errors = [];
89
+ if (!uploadPath) errors.push("uploadPath is required");
90
+ if (!Number.isInteger(maxRetries) || maxRetries < 1) errors.push("maxRetries must be >= 1");
91
+ if (!Number.isFinite(retryDelay) || retryDelay < 0) errors.push("retryDelay must be >= 0");
92
+ if (!Number.isInteger(concurrency) || concurrency < 1) errors.push("concurrency must be >= 1");
93
+ if (isMultiFtp) {
94
+ if (!ftpConfigs.length) {
95
+ errors.push("ftps is required and must not be empty");
96
+ }
97
+ if (defaultFtp && !ftpConfigs.some((ftp) => ftp.name === defaultFtp)) {
98
+ errors.push(`defaultFtp "${defaultFtp}" does not match any ftp.name`);
99
+ }
100
+ const validConfigCount = ftpConfigs.filter(validateFtpConfig).length;
101
+ if (validConfigCount === 0) {
102
+ errors.push("at least one ftp config requires host, user and password");
103
+ }
104
+ } else {
105
+ const singleConfig = ftpConfigs[0];
106
+ if (!singleConfig?.host) errors.push("host is required");
107
+ if (!singleConfig?.user) errors.push("user is required");
108
+ if (!singleConfig?.password) errors.push("password is required");
109
+ }
110
+ return errors;
111
+ };
112
+ const uploadFileWithRetry = async (task, context) => {
113
+ for (let attempt = 1; attempt <= context.maxRetries; attempt++) {
114
+ try {
115
+ await context.ensureConnected();
116
+ const remoteDir = normalizePath(path.posix.dirname(task.remotePath));
117
+ if (remoteDir && remoteDir !== "." && !context.ensuredDirs.has(remoteDir)) {
118
+ await context.client.ensureDir(remoteDir);
119
+ context.ensuredDirs.add(remoteDir);
120
+ }
121
+ await context.client.uploadFrom(task.filePath, path.posix.basename(task.remotePath));
122
+ return {
123
+ success: true,
124
+ file: task.filePath,
125
+ name: task.remotePath,
126
+ size: task.size,
127
+ retries: attempt - 1
128
+ };
129
+ } catch (error) {
130
+ context.markDisconnected();
131
+ try {
132
+ context.client.close();
133
+ } catch {
134
+ }
135
+ if (attempt === context.maxRetries) {
136
+ if (!context.silentLogs) {
137
+ console.log(
138
+ `${chalk.red("\u2717")} ${task.filePath} => ${error instanceof Error ? error.message : String(error)}`
139
+ );
140
+ }
141
+ return {
142
+ success: false,
143
+ file: task.filePath,
144
+ name: task.remotePath,
145
+ size: task.size,
146
+ retries: attempt - 1,
147
+ error
148
+ };
149
+ }
150
+ if (!context.silentLogs) {
151
+ console.log(
152
+ `${chalk.yellow("\u26A0")} ${task.filePath} \u4E0A\u4F20\u5931\u8D25\uFF0C\u6B63\u5728\u91CD\u8BD5 (${attempt}/${context.maxRetries})...`
153
+ );
154
+ }
155
+ await sleep(context.retryDelay * attempt);
156
+ }
157
+ }
61
158
  return {
62
- name: "vite-plugin-deploy-ftp",
63
- apply: "build",
64
- enforce: "post",
65
- configResolved() {
66
- },
67
- closeBundle: { sequential: true, order: "post", async handler() {
68
- } }
159
+ success: false,
160
+ file: task.filePath,
161
+ name: task.remotePath,
162
+ size: task.size,
163
+ retries: context.maxRetries,
164
+ error: new Error("Max retries exceeded")
69
165
  };
70
- }
71
- let outDir = "dist";
72
- let buildFailed = false;
73
- return {
74
- name: "vite-plugin-deploy-ftp",
75
- apply: "build",
76
- enforce: "post",
77
- buildEnd(error) {
78
- if (error) buildFailed = true;
79
- },
80
- configResolved(config) {
81
- outDir = config.build?.outDir || "dist";
82
- },
83
- closeBundle: {
84
- sequential: true,
85
- order: "post",
86
- async handler() {
87
- if (!open || buildFailed) return;
166
+ };
167
+ const uploadFilesInBatches = async (connectConfig, files, targetDir, windowSize = concurrency) => {
168
+ const results = [];
169
+ const totalFiles = files.length;
170
+ const tasks = [];
171
+ let completed = 0;
172
+ let failed = 0;
173
+ let uploadedBytes = 0;
174
+ let retries = 0;
175
+ const taskCandidates = await Promise.all(
176
+ files.map(async (relativeFilePath) => {
177
+ const filePath = normalizePath(path.resolve(outDir, relativeFilePath));
178
+ const remotePath = normalizeRemotePath(targetDir, relativeFilePath);
88
179
  try {
89
- process.stdout.write("\x1B[2J\x1B[0f");
90
- await deployToFtp();
180
+ const fileStats = await stat(filePath);
181
+ return { task: { filePath, remotePath, size: fileStats.size } };
91
182
  } catch (error) {
92
- console.error(import_chalk.default.red("\u274C FTP \u90E8\u7F72\u5931\u8D25:"), error instanceof Error ? error.message : error);
93
- throw error;
183
+ return { task: null, error, filePath, remotePath };
184
+ }
185
+ })
186
+ );
187
+ for (const candidate of taskCandidates) {
188
+ if (candidate.task) {
189
+ tasks.push(candidate.task);
190
+ } else {
191
+ failed++;
192
+ completed++;
193
+ results.push({
194
+ success: false,
195
+ file: candidate.filePath,
196
+ name: candidate.remotePath,
197
+ size: 0,
198
+ retries: 0,
199
+ error: candidate.error
200
+ });
201
+ }
202
+ }
203
+ const totalBytes = tasks.reduce((sum, task) => sum + task.size, 0);
204
+ const startAt = Date.now();
205
+ const safeWindowSize = Math.max(1, Math.min(windowSize, tasks.length || 1));
206
+ const activeFiles = /* @__PURE__ */ new Set();
207
+ const silentLogs = Boolean(useInteractiveOutput);
208
+ const spinner = useInteractiveOutput ? ora({ text: "\u51C6\u5907\u4E0A\u4F20...", spinner: "dots12" }).start() : null;
209
+ const reportEvery = Math.max(1, Math.ceil(totalFiles / 10));
210
+ let lastReportedCompleted = -1;
211
+ const updateProgress = () => {
212
+ const progressRatio = totalFiles > 0 ? completed / totalFiles : 1;
213
+ const percentage = Math.round(progressRatio * 100);
214
+ const elapsedSeconds = (Date.now() - startAt) / 1e3;
215
+ const speed = elapsedSeconds > 0 ? uploadedBytes / elapsedSeconds : 0;
216
+ const etaSeconds = speed > 0 ? Math.max(0, (totalBytes - uploadedBytes) / speed) : 0;
217
+ const activeList = Array.from(activeFiles);
218
+ const currentFile = activeList.length > 0 ? trimMiddle(activeList[activeList.length - 1], 86) : "-";
219
+ if (!spinner) {
220
+ if (completed === lastReportedCompleted) return;
221
+ if (completed === totalFiles || completed % reportEvery === 0) {
222
+ console.log(
223
+ `${chalk.gray("\u8FDB\u5EA6:")} ${completed}/${totalFiles} (${percentage}%) | ${chalk.gray("\u6570\u636E:")} ${formatBytes(uploadedBytes)}/${formatBytes(totalBytes)} | ${chalk.gray("\u901F\u5EA6:")} ${formatBytes(speed)}/s`
224
+ );
225
+ lastReportedCompleted = completed;
226
+ }
227
+ return;
228
+ }
229
+ const bar = buildCapsuleBar(progressRatio);
230
+ const warnLine = retries > 0 || failed > 0 ? `
231
+ ${chalk.yellow("\u91CD\u8BD5")}: ${retries} ${chalk.yellow("\u5931\u8D25")}: ${failed}` : "";
232
+ spinner.text = [
233
+ `${chalk.cyan("\u6B63\u5728\u4E0A\u4F20:")} ${chalk.white(currentFile)}`,
234
+ `${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))}`
235
+ ].join("\n");
236
+ spinner.text += warnLine;
237
+ };
238
+ const refreshTimer = spinner ? setInterval(updateProgress, 120) : null;
239
+ let currentIndex = 0;
240
+ const worker = async () => {
241
+ const client = new Client();
242
+ const ensuredDirs = /* @__PURE__ */ new Set();
243
+ let connected = false;
244
+ const ensureConnected = async () => {
245
+ if (connected) return;
246
+ await connectWithRetry(client, connectConfig, maxRetries, retryDelay, true);
247
+ connected = true;
248
+ };
249
+ const markDisconnected = () => {
250
+ connected = false;
251
+ ensuredDirs.clear();
252
+ };
253
+ try {
254
+ while (true) {
255
+ const index = currentIndex++;
256
+ if (index >= tasks.length) return;
257
+ const task = tasks[index];
258
+ activeFiles.add(task.remotePath);
259
+ updateProgress();
260
+ const result = await uploadFileWithRetry(task, {
261
+ client,
262
+ ensuredDirs,
263
+ ensureConnected,
264
+ markDisconnected,
265
+ silentLogs,
266
+ maxRetries,
267
+ retryDelay
268
+ });
269
+ completed++;
270
+ retries += result.retries;
271
+ if (result.success) {
272
+ uploadedBytes += result.size;
273
+ } else {
274
+ failed++;
275
+ }
276
+ results.push(result);
277
+ activeFiles.delete(task.remotePath);
278
+ updateProgress();
279
+ }
280
+ } finally {
281
+ client.close();
282
+ }
283
+ };
284
+ updateProgress();
285
+ try {
286
+ await Promise.all(Array.from({ length: safeWindowSize }, () => worker()));
287
+ } finally {
288
+ if (refreshTimer) clearInterval(refreshTimer);
289
+ }
290
+ if (spinner) {
291
+ const elapsedSeconds = (Date.now() - startAt) / 1e3;
292
+ const successCount = results.filter((item) => item.success).length;
293
+ const speed = elapsedSeconds > 0 ? uploadedBytes / elapsedSeconds : 0;
294
+ spinner.succeed(
295
+ `${chalk.green("\u4E0A\u4F20\u6210\u529F")} ${successCount} \u4E2A\u6587\u4EF6\u3002
296
+ ${buildCapsuleBar(1)} 100% (${totalFiles}/${totalFiles}) ${chalk.gray("|")} \u901F\u5EA6 ${chalk.magenta(`${formatBytes(speed)}/s`)} ${chalk.gray("|")} \u8017\u65F6 ${chalk.yellow(formatDuration(elapsedSeconds))}`
297
+ );
298
+ } else {
299
+ console.log(`${chalk.green("\u2714")} \u6240\u6709\u6587\u4EF6\u4E0A\u4F20\u5B8C\u6210 (${totalFiles}/${totalFiles})`);
300
+ }
301
+ return results;
302
+ };
303
+ const deploySingleTarget = async (ftpConfig) => {
304
+ const { host, port = 21, user, password, alias = "", name } = ftpConfig;
305
+ if (!host || !user || !password) {
306
+ console.error(chalk.red(`\u274C FTP\u914D\u7F6E "${name || host || "\u672A\u77E5"}" \u7F3A\u5C11\u5FC5\u9700\u53C2\u6570:`));
307
+ if (!host) console.error(chalk.red(" - \u7F3A\u5C11 host"));
308
+ if (!user) console.error(chalk.red(" - \u7F3A\u5C11 user"));
309
+ if (!password) console.error(chalk.red(" - \u7F3A\u5C11 password"));
310
+ return { name: name || host || "unknown", totalFiles: 0, failedCount: 1 };
311
+ }
312
+ const allFiles = getAllFiles(outDir);
313
+ const totalFiles = allFiles.length;
314
+ const { protocol, baseUrl } = parseAlias(alias);
315
+ const displayName = name || host;
316
+ const startTime = Date.now();
317
+ if (allFiles.length === 0) {
318
+ console.log(`${chalk.yellow("\u26A0 \u6CA1\u6709\u627E\u5230\u9700\u8981\u4E0A\u4F20\u7684\u6587\u4EF6")}`);
319
+ return { name: displayName, totalFiles: 0, failedCount: 0 };
320
+ }
321
+ clearScreen();
322
+ console.log(chalk.cyan(`
323
+ \u{1F680} FTP \u90E8\u7F72\u5F00\u59CB
324
+ `));
325
+ console.log(`${chalk.gray("Server:")} ${chalk.green(displayName)}`);
326
+ console.log(`${chalk.gray("Host:")} ${chalk.green(host)}`);
327
+ console.log(`${chalk.gray("Source:")} ${chalk.yellow(outDir)}`);
328
+ console.log(`${chalk.gray("Target:")} ${chalk.yellow(uploadPath)}`);
329
+ if (alias) console.log(`${chalk.gray("Alias:")} ${chalk.green(alias)}`);
330
+ console.log(`${chalk.gray("Files:")} ${chalk.blue(totalFiles)}
331
+ `);
332
+ const connectConfig = { host, port, user, password };
333
+ const preflightClient = new Client();
334
+ const preflightSpinner = useInteractiveOutput ? ora(`\u8FDE\u63A5\u5230 ${displayName}...`).start() : null;
335
+ try {
336
+ await connectWithRetry(preflightClient, connectConfig, maxRetries, retryDelay, Boolean(preflightSpinner));
337
+ if (preflightSpinner) preflightSpinner.succeed("\u8FDE\u63A5\u6210\u529F");
338
+ await preflightClient.ensureDir(uploadPath);
339
+ const fileList = await preflightClient.list(uploadPath);
340
+ if (fileList.length) {
341
+ if (singleBack) {
342
+ await createSingleBackup(preflightClient, uploadPath, protocol, baseUrl, singleBackFiles, showBackFile);
343
+ } else {
344
+ const shouldBackup = await select({
345
+ message: `\u662F\u5426\u5907\u4EFD ${displayName} \u7684\u8FDC\u7A0B\u6587\u4EF6`,
346
+ choices: ["\u5426", "\u662F"],
347
+ default: "\u5426"
348
+ });
349
+ if (shouldBackup === "\u662F") {
350
+ await createBackupFile(preflightClient, uploadPath, protocol, baseUrl, showBackFile);
351
+ }
352
+ }
353
+ }
354
+ const results = await uploadFilesInBatches(connectConfig, allFiles, uploadPath, concurrency);
355
+ const successCount = results.filter((r) => r.success).length;
356
+ const failedCount = results.length - successCount;
357
+ const durationSeconds = (Date.now() - startTime) / 1e3;
358
+ const duration = durationSeconds.toFixed(2);
359
+ const uploadedBytes = results.reduce((sum, result) => result.success ? sum + result.size : sum, 0);
360
+ const retryCount = results.reduce((sum, result) => sum + result.retries, 0);
361
+ const avgSpeed = durationSeconds > 0 ? uploadedBytes / durationSeconds : 0;
362
+ clearScreen();
363
+ console.log("\n" + chalk.gray("\u2500".repeat(40)) + "\n");
364
+ if (failedCount === 0) {
365
+ console.log(`${chalk.green("\u{1F389} \u90E8\u7F72\u6210\u529F!")}`);
366
+ } else {
367
+ console.log(`${chalk.yellow("\u26A0 \u90E8\u7F72\u5B8C\u6210\u4F46\u5B58\u5728\u9519\u8BEF")}`);
368
+ }
369
+ console.log(`
370
+ ${chalk.gray("\u7EDF\u8BA1:")}`);
371
+ console.log(` ${chalk.green("\u2714")} \u6210\u529F: ${chalk.bold(successCount)}`);
372
+ if (failedCount > 0) {
373
+ console.log(` ${chalk.red("\u2717")} \u5931\u8D25: ${chalk.bold(failedCount)}`);
374
+ }
375
+ console.log(` ${chalk.cyan("\u21C4")} \u91CD\u8BD5: ${chalk.bold(retryCount)}`);
376
+ console.log(` ${chalk.blue("\u{1F4E6}")} \u6570\u636E: ${chalk.bold(formatBytes(uploadedBytes))}`);
377
+ console.log(` ${chalk.magenta("\u26A1")} \u5E73\u5747\u901F\u5EA6: ${chalk.bold(`${formatBytes(avgSpeed)}/s`)}`);
378
+ console.log(` ${chalk.blue("\u23F1")} \u8017\u65F6: ${chalk.bold(duration)}s`);
379
+ if (baseUrl) {
380
+ console.log(` ${chalk.green("\u{1F517}")} \u8BBF\u95EE\u5730\u5740: ${chalk.bold(buildUrl(protocol, baseUrl, uploadPath))}`);
381
+ }
382
+ console.log("");
383
+ if (failedCount > 0) {
384
+ const failedItems = results.filter((result) => !result.success);
385
+ const previewCount = Math.min(5, failedItems.length);
386
+ console.log(chalk.red("\u5931\u8D25\u660E\u7EC6:"));
387
+ for (let i = 0; i < previewCount; i++) {
388
+ const item = failedItems[i];
389
+ const reason = item.error?.message || "unknown error";
390
+ console.log(` ${chalk.red("\u2022")} ${item.name} => ${reason}`);
391
+ }
392
+ if (failedItems.length > previewCount) {
393
+ console.log(chalk.gray(` ... \u8FD8\u6709 ${failedItems.length - previewCount} \u4E2A\u5931\u8D25\u6587\u4EF6`));
94
394
  }
395
+ console.log("");
95
396
  }
397
+ return { name: displayName, totalFiles: results.length, failedCount };
398
+ } catch (error) {
399
+ if (preflightSpinner) preflightSpinner.fail(`\u274C \u4E0A\u4F20\u5230 ${displayName} \u5931\u8D25`);
400
+ console.log(`
401
+ ${chalk.red("\u274C \u4E0A\u4F20\u8FC7\u7A0B\u4E2D\u53D1\u751F\u9519\u8BEF:")} ${error}
402
+ `);
403
+ return {
404
+ name: displayName,
405
+ totalFiles,
406
+ failedCount: totalFiles > 0 ? totalFiles : 1,
407
+ error: error instanceof Error ? error : new Error(String(error))
408
+ };
409
+ } finally {
410
+ preflightClient.close();
96
411
  }
97
412
  };
98
- async function deployToFtp() {
413
+ const deployToFtp = async () => {
99
414
  if (!autoUpload) {
100
- const ftpUploadChoice = await (0, import_prompts.select)({
415
+ const ftpUploadChoice = await select({
101
416
  message: "\u662F\u5426\u4E0A\u4F20FTP",
102
417
  choices: ["\u662F", "\u5426"],
103
418
  default: "\u662F"
104
419
  });
105
- if (ftpUploadChoice === "\u5426") return;
420
+ if (ftpUploadChoice === "\u5426") return [];
106
421
  }
107
422
  let selectedConfigs = [];
108
423
  if (isMultiFtp) {
109
424
  if (defaultFtp) {
110
425
  const defaultConfig = ftpConfigs.find((ftp) => ftp.name === defaultFtp);
111
- if (defaultConfig) {
112
- if (validateFtpConfig(defaultConfig)) {
113
- console.log(import_chalk.default.blue(`\u4F7F\u7528\u9ED8\u8BA4FTP\u914D\u7F6E: ${defaultFtp}`));
114
- selectedConfigs = [defaultConfig];
115
- } else {
116
- console.log(import_chalk.default.yellow(`\u26A0\uFE0F \u9ED8\u8BA4FTP\u914D\u7F6E "${defaultFtp}" \u7F3A\u5C11\u5FC5\u9700\u53C2\u6570\uFF0C\u5C06\u8FDB\u884C\u624B\u52A8\u9009\u62E9`));
117
- }
426
+ if (defaultConfig && validateFtpConfig(defaultConfig)) {
427
+ console.log(chalk.blue(`\u4F7F\u7528\u9ED8\u8BA4FTP\u914D\u7F6E: ${defaultFtp}`));
428
+ selectedConfigs = [defaultConfig];
429
+ } else if (defaultConfig) {
430
+ console.log(chalk.yellow(`\u26A0 \u9ED8\u8BA4FTP\u914D\u7F6E "${defaultFtp}" \u7F3A\u5C11\u5FC5\u9700\u53C2\u6570\uFF0C\u5C06\u8FDB\u884C\u624B\u52A8\u9009\u62E9`));
118
431
  }
119
432
  }
120
433
  if (selectedConfigs.length === 0) {
121
434
  const validConfigs = ftpConfigs.filter(validateFtpConfig);
122
435
  const invalidConfigs = ftpConfigs.filter((config) => !validateFtpConfig(config));
123
436
  if (invalidConfigs.length > 0) {
124
- console.log(import_chalk.default.yellow("\n \u4EE5\u4E0BFTP\u914D\u7F6E\u7F3A\u5C11\u5FC5\u9700\u53C2\u6570\uFF0C\u5DF2\u4ECE\u9009\u62E9\u5217\u8868\u4E2D\u6392\u9664:"));
437
+ console.log(chalk.yellow("\n\u4EE5\u4E0BFTP\u914D\u7F6E\u7F3A\u5C11\u5FC5\u9700\u53C2\u6570\uFF0C\u5DF2\u4ECE\u9009\u62E9\u5217\u8868\u4E2D\u6392\u9664:"));
125
438
  invalidConfigs.forEach((config) => {
126
439
  const missing = [];
127
440
  if (!config.host) missing.push("host");
128
441
  if (!config.user) missing.push("user");
129
442
  if (!config.password) missing.push("password");
130
- console.log(import_chalk.default.yellow(` - ${config.name || "\u672A\u547D\u540D"}: \u7F3A\u5C11 ${missing.join(", ")}`));
443
+ console.log(chalk.yellow(` - ${config.name || "\u672A\u547D\u540D"}: \u7F3A\u5C11 ${missing.join(", ")}`));
131
444
  });
132
445
  console.log();
133
446
  }
134
447
  if (validConfigs.length === 0) {
135
- console.error(import_chalk.default.red("\u274C \u6CA1\u6709\u53EF\u7528\u7684\u6709\u6548FTP\u914D\u7F6E"));
136
- return;
448
+ console.error(chalk.red("\u274C \u6CA1\u6709\u53EF\u7528\u7684\u6709\u6548FTP\u914D\u7F6E"));
449
+ return [];
137
450
  }
138
- const choices = validConfigs.map((ftp) => ({
139
- name: ftp.name,
140
- value: ftp
141
- }));
142
- selectedConfigs = await (0, import_prompts.checkbox)({
451
+ selectedConfigs = await checkbox({
143
452
  message: "\u9009\u62E9\u8981\u4E0A\u4F20\u7684FTP\u670D\u52A1\u5668\uFF08\u53EF\u591A\u9009\uFF09",
144
- choices,
453
+ choices: validConfigs.map((ftp) => ({
454
+ name: ftp.name || ftp.host || "\u672A\u547D\u540DFTP",
455
+ value: ftp
456
+ })),
145
457
  required: true
146
458
  });
147
459
  }
@@ -151,130 +463,71 @@ function vitePluginDeployFtp(option) {
151
463
  selectedConfigs = [{ ...singleConfig, name: singleConfig.name || singleConfig.host }];
152
464
  } else {
153
465
  const missing = [];
154
- if (!singleConfig.host) missing.push("host");
155
- if (!singleConfig.user) missing.push("user");
156
- if (!singleConfig.password) missing.push("password");
157
- console.error(import_chalk.default.red(`\u274C FTP\u914D\u7F6E\u7F3A\u5C11\u5FC5\u9700\u53C2\u6570: ${missing.join(", ")}`));
158
- return;
466
+ if (!singleConfig?.host) missing.push("host");
467
+ if (!singleConfig?.user) missing.push("user");
468
+ if (!singleConfig?.password) missing.push("password");
469
+ console.error(chalk.red(`\u274C FTP\u914D\u7F6E\u7F3A\u5C11\u5FC5\u9700\u53C2\u6570: ${missing.join(", ")}`));
470
+ return [];
159
471
  }
160
472
  }
473
+ const deployResults = [];
161
474
  for (const ftpConfig of selectedConfigs) {
162
- const { host, port = 21, user, password, alias = "", name } = ftpConfig;
163
- if (!host || !user || !password) {
164
- console.error(import_chalk.default.red(`\u274C FTP\u914D\u7F6E "${name || host || "\u672A\u77E5"}" \u7F3A\u5C11\u5FC5\u9700\u53C2\u6570:`));
165
- if (!host) console.error(import_chalk.default.red(" - \u7F3A\u5C11 host"));
166
- if (!user) console.error(import_chalk.default.red(" - \u7F3A\u5C11 user"));
167
- if (!password) console.error(import_chalk.default.red(" - \u7F3A\u5C11 password"));
168
- continue;
475
+ const targetResult = await deploySingleTarget(ftpConfig);
476
+ deployResults.push(targetResult);
477
+ }
478
+ return deployResults;
479
+ };
480
+ return {
481
+ name: "vite-plugin-deploy-ftp",
482
+ apply: "build",
483
+ enforce: "post",
484
+ buildEnd(error) {
485
+ if (error) buildFailed = true;
486
+ },
487
+ config(config) {
488
+ if (!open || buildFailed) return;
489
+ clearScreen();
490
+ const validationErrors = validateOptions();
491
+ if (validationErrors.length > 0) {
492
+ console.log(`${chalk.red("\u2717 \u914D\u7F6E\u9519\u8BEF:")}
493
+ ${validationErrors.map((err) => ` - ${err}`).join("\n")}`);
494
+ return;
169
495
  }
170
- const { protocol, baseUrl } = parseAlias(alias);
171
- const displayName = name || host;
172
- const allFiles = getAllFiles(outDir);
173
- const totalFiles = allFiles.length;
174
- console.log(import_chalk.default.bold(`
175
- \u{1F680} FTP \u90E8\u7F72\u5F00\u59CB`));
176
- console.log();
177
- console.log(`Host: ${import_chalk.default.blue(host)}`);
178
- console.log(`User: ${import_chalk.default.blue(user)}`);
179
- console.log(`Source: ${import_chalk.default.blue(outDir)}`);
180
- console.log(`Target: ${import_chalk.default.blue(uploadPath)}`);
181
- console.log(`Files: ${import_chalk.default.blue(totalFiles)}`);
182
- console.log();
183
- const client = new import_basic_ftp.Client();
184
- let uploadSpinner;
185
- const startTime = Date.now();
186
- try {
187
- uploadSpinner = (0, import_ora.default)(`\u8FDE\u63A5\u5230 ${displayName} \u4E2D...`).start();
188
- await connectWithRetry(client, { host, port, user, password }, maxRetries, retryDelay);
189
- uploadSpinner.color = "green";
190
- uploadSpinner.text = "\u8FDE\u63A5\u6210\u529F";
191
- await new Promise((resolve) => setTimeout(resolve, 500));
192
- const fileList = await client.list(uploadPath);
193
- uploadSpinner.succeed("\u8FDE\u63A5\u6210\u529F!");
194
- const startDir = await client.pwd();
195
- if (fileList.length) {
196
- if (singleBack) {
197
- await createSingleBackup(client, uploadPath, protocol, baseUrl, singleBackFiles, showBackFile);
198
- } else {
199
- const isBackFiles = await (0, import_prompts.select)({
200
- message: `\u662F\u5426\u5907\u4EFD ${displayName} \u7684\u8FDC\u7A0B\u6587\u4EF6`,
201
- choices: ["\u5426", "\u662F"],
202
- default: "\u5426"
203
- });
204
- if (isBackFiles === "\u662F") {
205
- await createBackupFile(client, uploadPath, protocol, baseUrl, showBackFile);
206
- }
207
- }
208
- }
209
- const progressSpinner = (0, import_ora.default)("\u51C6\u5907\u4E0A\u4F20...").start();
210
- let uploadedCount = 0;
211
- const groups = {};
212
- for (const file of allFiles) {
213
- const dir = import_node_path.default.dirname(file);
214
- if (!groups[dir]) groups[dir] = [];
215
- groups[dir].push(import_node_path.default.basename(file));
216
- }
217
- for (const relDir of Object.keys(groups)) {
218
- await client.cd(startDir);
219
- const remoteDir = (0, import_vite.normalizePath)(import_node_path.default.join(uploadPath, relDir));
220
- await client.ensureDir(remoteDir);
221
- for (const fileName of groups[relDir]) {
222
- const currentFile = import_node_path.default.join(relDir, fileName);
223
- const displayPath = (0, import_vite.normalizePath)(currentFile);
224
- progressSpinner.text = `\u6B63\u5728\u4E0A\u4F20: ${import_chalk.default.dim(displayPath)}
225
- ${formatProgressBar(uploadedCount, totalFiles)}`;
226
- const localFile = import_node_path.default.join(outDir, relDir, fileName);
227
- await client.uploadFrom(localFile, fileName);
228
- uploadedCount++;
229
- }
230
- }
231
- progressSpinner.succeed("\u6240\u6709\u6587\u4EF6\u4E0A\u4F20\u5B8C\u6210!");
232
- console.log(import_chalk.default.green(formatProgressBar(totalFiles, totalFiles)));
233
- process.stdout.write("\x1B[2J\x1B[0f");
234
- console.log(import_chalk.default.gray("\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"));
235
- const duration = ((Date.now() - startTime) / 1e3).toFixed(2);
236
- console.log(`\u{1F389} \u90E8\u7F72\u6210\u529F!`);
237
- console.log();
238
- console.log(`\u7EDF\u8BA1:`);
239
- console.log(` \u2714 \u6210\u529F: ${import_chalk.default.green(totalFiles)}`);
240
- console.log(` \u23F1 \u8017\u65F6: ${import_chalk.default.green(duration + "s")}`);
241
- console.log();
242
- if (baseUrl) {
243
- console.log(`\u8BBF\u95EE\u5730\u5740: ${import_chalk.default.green(buildUrl(protocol, baseUrl, uploadPath))}`);
244
- console.log();
245
- }
246
- } catch (error) {
247
- if (uploadSpinner) {
248
- uploadSpinner.fail(`\u274C \u4E0A\u4F20\u5230 ${displayName} \u5931\u8D25`);
496
+ upload = true;
497
+ return config;
498
+ },
499
+ configResolved(config) {
500
+ resolvedConfig = config;
501
+ outDir = normalizePath(path.resolve(config.root, config.build.outDir));
502
+ },
503
+ closeBundle: {
504
+ sequential: true,
505
+ order: "post",
506
+ async handler() {
507
+ if (!open || !upload || buildFailed || !resolvedConfig) return;
508
+ const deployResults = await deployToFtp();
509
+ if (deployResults.length === 0) return;
510
+ const failedTargets = deployResults.filter((target) => target.failedCount > 0);
511
+ if (failedTargets.length > 0 && failOnError) {
512
+ throw new Error(`Failed to deploy ${failedTargets.length} of ${deployResults.length} FTP targets`);
249
513
  }
250
- console.error(import_chalk.default.red(`\u274C \u4E0A\u4F20\u5230 ${displayName} \u5931\u8D25:`), error instanceof Error ? error.message : error);
251
- throw error;
252
- } finally {
253
- client.close();
254
514
  }
255
515
  }
256
- }
516
+ };
257
517
  }
258
518
  function getAllFiles(dirPath, arrayOfFiles = [], relativePath = "") {
259
- const files = import_node_fs.default.readdirSync(dirPath);
260
- files.forEach(function(file) {
261
- const fullPath = import_node_path.default.join(dirPath, file);
262
- const relPath = import_node_path.default.join(relativePath, file);
263
- if (import_node_fs.default.statSync(fullPath).isDirectory()) {
519
+ const files = fs.readdirSync(dirPath);
520
+ files.forEach((file) => {
521
+ const fullPath = path.join(dirPath, file);
522
+ const relPath = path.join(relativePath, file);
523
+ if (fs.statSync(fullPath).isDirectory()) {
264
524
  getAllFiles(fullPath, arrayOfFiles, relPath);
265
525
  } else {
266
- arrayOfFiles.push(relPath);
526
+ arrayOfFiles.push(normalizePath(relPath));
267
527
  }
268
528
  });
269
529
  return arrayOfFiles;
270
530
  }
271
- function formatProgressBar(current, total, width = 30) {
272
- const percentage = Math.round(current / total * 100);
273
- const filled = Math.round(width * current / total);
274
- const empty = width - filled;
275
- const bar = "\u2588".repeat(filled) + "\u2591".repeat(empty);
276
- return `${bar} ${percentage}% (${current}/${total})`;
277
- }
278
531
  function validateFtpConfig(config) {
279
532
  return !!(config.host && config.user && config.password);
280
533
  }
@@ -285,10 +538,10 @@ function parseAlias(alias = "") {
285
538
  baseUrl: baseUrl || ""
286
539
  };
287
540
  }
288
- function buildUrl(protocol, baseUrl, path2) {
289
- return protocol + (0, import_vite.normalizePath)(baseUrl + path2);
541
+ function buildUrl(protocol, baseUrl, targetPath) {
542
+ return protocol + normalizePath(baseUrl + targetPath);
290
543
  }
291
- async function connectWithRetry(client, config, maxRetries, retryDelay) {
544
+ async function connectWithRetry(client, config, maxRetries, retryDelay, silentLogs = false) {
292
545
  let lastError;
293
546
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
294
547
  try {
@@ -302,66 +555,70 @@ async function connectWithRetry(client, config, maxRetries, retryDelay) {
302
555
  } catch (error) {
303
556
  lastError = error instanceof Error ? error : new Error(String(error));
304
557
  if (attempt < maxRetries) {
305
- console.log(import_chalk.default.yellow(`\u26A0\uFE0F \u8FDE\u63A5\u5931\u8D25\uFF0C${retryDelay}ms \u540E\u91CD\u8BD5 (${attempt}/${maxRetries})`));
306
- await new Promise((resolve) => setTimeout(resolve, retryDelay));
558
+ if (!silentLogs) {
559
+ console.log(chalk.yellow(`\u26A0 \u8FDE\u63A5\u5931\u8D25\uFF0C${retryDelay}ms \u540E\u91CD\u8BD5 (${attempt}/${maxRetries})`));
560
+ }
561
+ await sleep(retryDelay * attempt);
307
562
  }
308
563
  }
309
564
  }
310
565
  throw new Error(`\u274C FTP \u8FDE\u63A5\u5931\u8D25\uFF0C\u5DF2\u91CD\u8BD5 ${maxRetries} \u6B21: ${lastError?.message}`);
311
566
  }
312
567
  function createTempDir(basePath) {
313
- const tempBaseDir = import_node_os.default.tmpdir();
314
- const tempPath = import_node_path.default.join(tempBaseDir, "vite-plugin-deploy-ftp", basePath);
315
- if (!import_node_fs.default.existsSync(tempPath)) {
316
- import_node_fs.default.mkdirSync(tempPath, { recursive: true });
568
+ const tempBaseDir = os.tmpdir();
569
+ const tempPath = path.join(tempBaseDir, "vite-plugin-deploy-ftp", basePath);
570
+ if (!fs.existsSync(tempPath)) {
571
+ fs.mkdirSync(tempPath, { recursive: true });
317
572
  }
318
573
  return {
319
574
  path: tempPath,
320
575
  cleanup: () => {
321
576
  try {
322
- if (import_node_fs.default.existsSync(tempPath)) {
323
- import_node_fs.default.rmSync(tempPath, { recursive: true, force: true });
577
+ if (fs.existsSync(tempPath)) {
578
+ fs.rmSync(tempPath, { recursive: true, force: true });
324
579
  }
325
580
  } catch (error) {
326
- console.warn(import_chalk.default.yellow(`\u26A0\uFE0F \u6E05\u7406\u4E34\u65F6\u76EE\u5F55\u5931\u8D25: ${tempPath}`), error);
581
+ console.warn(chalk.yellow(`\u26A0 \u6E05\u7406\u4E34\u65F6\u76EE\u5F55\u5931\u8D25: ${tempPath}`), error);
327
582
  }
328
583
  }
329
584
  };
330
585
  }
331
586
  async function createBackupFile(client, dir, protocol, baseUrl, showBackFile = false) {
332
- const backupSpinner = (0, import_ora.default)(`\u521B\u5EFA\u5907\u4EFD\u6587\u4EF6\u4E2D ${import_chalk.default.yellow(`==> ${buildUrl(protocol, baseUrl, dir)}`)}`).start();
333
- const fileName = `backup_${(0, import_dayjs.default)().format("YYYYMMDD_HHmmss")}.zip`;
587
+ const backupSpinner = ora(`\u521B\u5EFA\u5907\u4EFD\u6587\u4EF6\u4E2D ${chalk.yellow(`==> ${buildUrl(protocol, baseUrl, dir)}`)}`).start();
588
+ const fileName = `backup_${dayjs().format("YYYYMMDD_HHmmss")}.zip`;
334
589
  const tempDir = createTempDir("backup-zip");
335
- const zipFilePath = import_node_path.default.join(import_node_os.default.tmpdir(), "vite-plugin-deploy-ftp", fileName);
590
+ const zipFilePath = path.join(os.tmpdir(), "vite-plugin-deploy-ftp", fileName);
336
591
  try {
337
- const zipDir = import_node_path.default.dirname(zipFilePath);
338
- if (!import_node_fs.default.existsSync(zipDir)) {
339
- import_node_fs.default.mkdirSync(zipDir, { recursive: true });
592
+ const zipDir = path.dirname(zipFilePath);
593
+ if (!fs.existsSync(zipDir)) {
594
+ fs.mkdirSync(zipDir, { recursive: true });
340
595
  }
341
596
  const remoteFiles = await client.list(dir);
342
- const filteredFiles = remoteFiles.filter((file) => !file.name.startsWith("backup_") || !file.name.endsWith(".zip"));
597
+ const filteredFiles = remoteFiles.filter(
598
+ (file) => !(file.name.startsWith("backup_") && file.name.endsWith(".zip"))
599
+ );
343
600
  if (showBackFile) {
344
- console.log(import_chalk.default.cyan(`
601
+ console.log(chalk.cyan(`
345
602
  \u5F00\u59CB\u5907\u4EFD\u8FDC\u7A0B\u6587\u4EF6\uFF0C\u5171 ${filteredFiles.length} \u4E2A\u6587\u4EF6:`));
346
603
  filteredFiles.forEach((file) => {
347
- console.log(import_chalk.default.gray(` - ${file.name} (${file.size} bytes)`));
604
+ console.log(chalk.gray(` - ${file.name} (${file.size} bytes)`));
348
605
  });
349
606
  }
350
607
  for (const file of filteredFiles) {
351
608
  if (file.type === 1) {
352
- await client.downloadTo(import_node_path.default.join(tempDir.path, file.name), (0, import_vite.normalizePath)(`${dir}/${file.name}`));
609
+ await client.downloadTo(path.join(tempDir.path, file.name), normalizePath(`${dir}/${file.name}`));
353
610
  }
354
611
  }
355
- backupSpinner.text = `\u4E0B\u8F7D\u8FDC\u7A0B\u6587\u4EF6\u6210\u529F ${import_chalk.default.yellow(`==> ${buildUrl(protocol, baseUrl, dir)}`)}`;
612
+ backupSpinner.text = `\u4E0B\u8F7D\u8FDC\u7A0B\u6587\u4EF6\u6210\u529F ${chalk.yellow(`==> ${buildUrl(protocol, baseUrl, dir)}`)}`;
356
613
  await createZipFile(tempDir.path, zipFilePath);
357
- backupSpinner.text = `\u538B\u7F29\u5B8C\u6210, \u51C6\u5907\u4E0A\u4F20 ${import_chalk.default.yellow(
358
- `==> ${buildUrl(protocol, baseUrl, dir + "/" + fileName)}`
614
+ backupSpinner.text = `\u538B\u7F29\u5B8C\u6210, \u51C6\u5907\u4E0A\u4F20 ${chalk.yellow(
615
+ `==> ${buildUrl(protocol, baseUrl, `${dir}/${fileName}`)}`
359
616
  )}`;
360
- await client.uploadFrom(zipFilePath, (0, import_vite.normalizePath)(`${dir}/${fileName}`));
617
+ await client.uploadFrom(zipFilePath, normalizePath(`${dir}/${fileName}`));
361
618
  const backupUrl = buildUrl(protocol, baseUrl, `${dir}/${fileName}`);
362
619
  backupSpinner.succeed("\u5907\u4EFD\u5B8C\u6210");
363
- console.log(import_chalk.default.cyan("\n\u5907\u4EFD\u6587\u4EF6:"));
364
- console.log(import_chalk.default.green(`${backupUrl}`));
620
+ console.log(chalk.cyan("\n\u5907\u4EFD\u6587\u4EF6:"));
621
+ console.log(chalk.green(`${backupUrl}`));
365
622
  console.log();
366
623
  } catch (error) {
367
624
  backupSpinner.fail("\u5907\u4EFD\u5931\u8D25");
@@ -369,18 +626,18 @@ async function createBackupFile(client, dir, protocol, baseUrl, showBackFile = f
369
626
  } finally {
370
627
  tempDir.cleanup();
371
628
  try {
372
- if (import_node_fs.default.existsSync(zipFilePath)) {
373
- import_node_fs.default.rmSync(zipFilePath);
629
+ if (fs.existsSync(zipFilePath)) {
630
+ fs.rmSync(zipFilePath);
374
631
  }
375
632
  } catch (error) {
376
- console.warn(import_chalk.default.yellow("\u26A0\uFE0F \u6E05\u7406zip\u6587\u4EF6\u5931\u8D25"), error);
633
+ console.warn(chalk.yellow("\u26A0 \u6E05\u7406zip\u6587\u4EF6\u5931\u8D25"), error);
377
634
  }
378
635
  }
379
636
  }
380
637
  async function createZipFile(sourceDir, outputPath) {
381
638
  return new Promise((resolve, reject) => {
382
- const output = import_node_fs.default.createWriteStream(outputPath);
383
- const archive = (0, import_archiver.default)("zip", {
639
+ const output = fs.createWriteStream(outputPath);
640
+ const archive = archiver("zip", {
384
641
  zlib: { level: 9 }
385
642
  });
386
643
  output.on("close", () => {
@@ -395,8 +652,8 @@ async function createZipFile(sourceDir, outputPath) {
395
652
  });
396
653
  }
397
654
  async function createSingleBackup(client, dir, protocol, baseUrl, singleBackFiles, showBackFile = false) {
398
- const timestamp = (0, import_dayjs.default)().format("YYYYMMDD_HHmmss");
399
- const backupSpinner = (0, import_ora.default)(`\u5907\u4EFD\u6307\u5B9A\u6587\u4EF6\u4E2D ${import_chalk.default.yellow(`==> ${buildUrl(protocol, baseUrl, dir)}`)}`).start();
655
+ const timestamp = dayjs().format("YYYYMMDD_HHmmss");
656
+ const backupSpinner = ora(`\u5907\u4EFD\u6307\u5B9A\u6587\u4EF6\u4E2D ${chalk.yellow(`==> ${buildUrl(protocol, baseUrl, dir)}`)}`).start();
400
657
  const tempDir = createTempDir("single-backup");
401
658
  let backupProgressSpinner;
402
659
  try {
@@ -411,13 +668,13 @@ async function createSingleBackup(client, dir, protocol, baseUrl, singleBackFile
411
668
  }
412
669
  backupSpinner.stop();
413
670
  if (showBackFile) {
414
- console.log(import_chalk.default.cyan(`
671
+ console.log(chalk.cyan(`
415
672
  \u5F00\u59CB\u5355\u6587\u4EF6\u5907\u4EFD\uFF0C\u5171 ${backupTasks.length} \u4E2A\u6587\u4EF6:`));
416
673
  backupTasks.forEach((task) => {
417
- console.log(import_chalk.default.gray(` - ${task.fileName}`));
674
+ console.log(chalk.gray(` - ${task.fileName}`));
418
675
  });
419
676
  }
420
- backupProgressSpinner = (0, import_ora.default)("\u6B63\u5728\u5907\u4EFD\u6587\u4EF6...").start();
677
+ backupProgressSpinner = ora("\u6B63\u5728\u5907\u4EFD\u6587\u4EF6...").start();
421
678
  const concurrencyLimit = 3;
422
679
  let backedUpCount = 0;
423
680
  const backedUpFiles = [];
@@ -425,18 +682,18 @@ async function createSingleBackup(client, dir, protocol, baseUrl, singleBackFile
425
682
  const batch = backupTasks.slice(i, i + concurrencyLimit);
426
683
  const promises = batch.map(async ({ fileName }) => {
427
684
  try {
428
- const localTempPath = import_node_path.default.join(tempDir.path, fileName);
429
- const [name, ext] = fileName.split(".");
430
- const suffix = ext ? `.${ext}` : "";
431
- const backupFileName = `${name}.${timestamp}${suffix}`;
432
- const backupRemotePath = (0, import_vite.normalizePath)(`${dir}/${backupFileName}`);
433
- await client.downloadTo(localTempPath, (0, import_vite.normalizePath)(`${dir}/${fileName}`));
685
+ const localTempPath = path.join(tempDir.path, fileName);
686
+ const extIndex = fileName.lastIndexOf(".");
687
+ const name = extIndex > -1 ? fileName.slice(0, extIndex) : fileName;
688
+ const ext = extIndex > -1 ? fileName.slice(extIndex) : "";
689
+ const backupFileName = `${name}.${timestamp}${ext}`;
690
+ const backupRemotePath = normalizePath(`${dir}/${backupFileName}`);
691
+ await client.downloadTo(localTempPath, normalizePath(`${dir}/${fileName}`));
434
692
  await client.uploadFrom(localTempPath, backupRemotePath);
435
- const backupUrl = buildUrl(protocol, baseUrl, backupRemotePath);
436
- backedUpFiles.push(backupUrl);
693
+ backedUpFiles.push(buildUrl(protocol, baseUrl, backupRemotePath));
437
694
  return true;
438
695
  } catch (error) {
439
- console.warn(import_chalk.default.yellow(`\u5907\u4EFD\u6587\u4EF6 ${fileName} \u5931\u8D25:`), error instanceof Error ? error.message : error);
696
+ console.warn(chalk.yellow(`\u5907\u4EFD\u6587\u4EF6 ${fileName} \u5931\u8D25:`), error instanceof Error ? error.message : error);
440
697
  return false;
441
698
  }
442
699
  });
@@ -445,9 +702,9 @@ async function createSingleBackup(client, dir, protocol, baseUrl, singleBackFile
445
702
  }
446
703
  if (backedUpCount > 0) {
447
704
  backupProgressSpinner.succeed("\u5907\u4EFD\u5B8C\u6210");
448
- console.log(import_chalk.default.cyan("\n\u5907\u4EFD\u6587\u4EF6:"));
705
+ console.log(chalk.cyan("\n\u5907\u4EFD\u6587\u4EF6:"));
449
706
  backedUpFiles.forEach((url) => {
450
- console.log(import_chalk.default.green(`\u{1F517} ${url}`));
707
+ console.log(chalk.green(`\u{1F517} ${url}`));
451
708
  });
452
709
  console.log();
453
710
  } else {
@@ -464,3 +721,6 @@ async function createSingleBackup(client, dir, protocol, baseUrl, singleBackFile
464
721
  tempDir.cleanup();
465
722
  }
466
723
  }
724
+ export {
725
+ vitePluginDeployFtp as default
726
+ };