vite-plugin-deploy-ftp 0.0.8 → 0.0.9

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.mts CHANGED
@@ -6,8 +6,12 @@ type vitePluginDeployFtpOption = {
6
6
  user: string;
7
7
  password: string;
8
8
  uploadPath: string;
9
+ singleBackFiles?: string[];
10
+ singleBack?: boolean;
9
11
  alias?: string;
10
12
  open?: boolean;
13
+ maxRetries?: number;
14
+ retryDelay?: number;
11
15
  };
12
16
  declare function vitePluginDeployFtp(option: vitePluginDeployFtpOption): Plugin;
13
17
 
package/dist/index.d.ts CHANGED
@@ -6,8 +6,12 @@ type vitePluginDeployFtpOption = {
6
6
  user: string;
7
7
  password: string;
8
8
  uploadPath: string;
9
+ singleBackFiles?: string[];
10
+ singleBack?: boolean;
9
11
  alias?: string;
10
12
  open?: boolean;
13
+ maxRetries?: number;
14
+ retryDelay?: number;
11
15
  };
12
16
  declare function vitePluginDeployFtp(option: vitePluginDeployFtpOption): Plugin;
13
17
 
package/dist/index.js CHANGED
@@ -39,11 +39,27 @@ var import_basic_ftp = require("basic-ftp");
39
39
  var import_chalk = __toESM(require("chalk"));
40
40
  var import_dayjs = __toESM(require("dayjs"));
41
41
  var import_node_fs = __toESM(require("fs"));
42
+ var import_node_os = __toESM(require("os"));
42
43
  var import_node_path = __toESM(require("path"));
43
44
  var import_ora = __toESM(require("ora"));
44
45
  var import_vite = require("vite");
45
46
  function vitePluginDeployFtp(option) {
46
- const { open = true, host, port = 21, user, password, uploadPath, alias = "" } = option || {};
47
+ const {
48
+ open = true,
49
+ host,
50
+ port = 21,
51
+ user,
52
+ password,
53
+ uploadPath,
54
+ alias = "",
55
+ singleBack = false,
56
+ singleBackFiles = ["index.html"],
57
+ maxRetries = 3,
58
+ retryDelay = 1e3
59
+ } = option || {};
60
+ if (!host || !user || !password || !uploadPath) {
61
+ throw new Error("Missing required FTP configuration: host, user, password, uploadPath");
62
+ }
47
63
  let outDir = "dist";
48
64
  return {
49
65
  name: "vite-plugin-deploy-ftp",
@@ -57,89 +73,202 @@ function vitePluginDeployFtp(option) {
57
73
  order: "post",
58
74
  async handler() {
59
75
  if (!open) return;
60
- if (!host || !port || !user || !password || !uploadPath) {
61
- console.log(import_chalk.default.yellow("\u8BF7\u914D\u7F6E\u6B63\u786E\u7684FTP\u4FE1\u606F"));
62
- return;
63
- }
64
- let [protocol, other] = (alias || "://").split("://");
65
- if (protocol) {
66
- protocol = protocol + "://";
76
+ try {
77
+ await deployToFtp();
78
+ } catch (error) {
79
+ console.error(import_chalk.default.red("FTP \u90E8\u7F72\u5931\u8D25:"), error instanceof Error ? error.message : error);
80
+ throw error;
67
81
  }
68
- const ftpUploadChoice = await (0, import_prompts.select)({
69
- message: "\u662F\u5426\u4E0A\u4F20FTP",
70
- choices: ["\u662F", "\u5426"],
71
- default: "\u662F"
72
- });
73
- if (ftpUploadChoice === "\u5426") return;
74
- const uploadSpinner = (0, import_ora.default)("\u51C6\u5907\u81EA\u52A8\u4E0A\u4F20\uFF0C\u521B\u5EFA\u8FDE\u63A5\u4E2D...").start();
75
- const client = new import_basic_ftp.Client();
76
- client.ftp.verbose = false;
77
- await client.access({
78
- host,
79
- port,
80
- user,
81
- password,
82
- secure: true,
83
- secureOptions: { rejectUnauthorized: false, timeout: 6e4 }
84
- });
85
- uploadSpinner.color = "blue";
86
- uploadSpinner.text = "\u8FDE\u63A5\u6210\u529F";
87
- const fileList = await client.list(uploadPath);
88
- uploadSpinner.succeed(`\u5DF2\u8FDE\u63A5 ${import_chalk.default.green(`\u76EE\u5F55: ==> ${protocol + (0, import_vite.normalizePath)(other + uploadPath)}`)}`);
89
- if (fileList.length) {
82
+ }
83
+ }
84
+ };
85
+ async function deployToFtp() {
86
+ const { protocol, baseUrl } = parseAlias(alias);
87
+ const ftpUploadChoice = await (0, import_prompts.select)({
88
+ message: "\u662F\u5426\u4E0A\u4F20FTP",
89
+ choices: ["\u662F", "\u5426"],
90
+ default: "\u662F"
91
+ });
92
+ if (ftpUploadChoice === "\u5426") return;
93
+ const client = new import_basic_ftp.Client();
94
+ let uploadSpinner;
95
+ try {
96
+ uploadSpinner = (0, import_ora.default)("\u51C6\u5907\u81EA\u52A8\u4E0A\u4F20\uFF0C\u521B\u5EFA\u8FDE\u63A5\u4E2D...").start();
97
+ await connectWithRetry(client, { host, port, user, password }, maxRetries, retryDelay);
98
+ uploadSpinner.color = "blue";
99
+ uploadSpinner.text = "\u8FDE\u63A5\u6210\u529F";
100
+ const fileList = await client.list(uploadPath);
101
+ uploadSpinner.succeed(`\u5DF2\u8FDE\u63A5 ${import_chalk.default.green(`\u76EE\u5F55: ==> ${buildUrl(protocol, baseUrl, uploadPath)}`)}`);
102
+ if (fileList.length) {
103
+ if (singleBack) {
104
+ await createSingleBackup(client, uploadPath, protocol, baseUrl, singleBackFiles);
105
+ } else {
90
106
  const isBackFiles = await (0, import_prompts.select)({
91
107
  message: "\u662F\u5426\u5907\u4EFD\u8FDC\u7A0B\u6587\u4EF6",
92
108
  choices: ["\u5426", "\u662F"],
93
109
  default: "\u5426"
94
110
  });
95
111
  if (isBackFiles === "\u662F") {
96
- await createBackupFile(client, uploadPath, protocol, other);
112
+ await createBackupFile(client, uploadPath, protocol, baseUrl);
97
113
  }
98
114
  }
99
- const uploadFileSpinner = (0, import_ora.default)("\u4E0A\u4F20\u4E2D...").start();
100
- await client.uploadFromDir(outDir, uploadPath);
101
- uploadFileSpinner.succeed("\u4E0A\u4F20\u6210\u529F url:" + import_chalk.default.green(`${protocol + (0, import_vite.normalizePath)(other + uploadPath)}`));
102
- client.close();
103
115
  }
116
+ const uploadFileSpinner = (0, import_ora.default)("\u4E0A\u4F20\u4E2D...").start();
117
+ await client.uploadFromDir(outDir, uploadPath);
118
+ uploadFileSpinner.succeed("\u4E0A\u4F20\u6210\u529F url:" + import_chalk.default.green(buildUrl(protocol, baseUrl, uploadPath)));
119
+ } finally {
120
+ client.close();
104
121
  }
122
+ }
123
+ }
124
+ function parseAlias(alias = "") {
125
+ const [protocol = "", baseUrl = ""] = alias.split("://");
126
+ return {
127
+ protocol: protocol ? `${protocol}://` : "",
128
+ baseUrl: baseUrl || ""
105
129
  };
106
130
  }
107
- async function createBackupFile(client, dir, protocol, other) {
108
- const backupSpinner = (0, import_ora.default)(
109
- `\u521B\u5EFA\u5907\u4EFD\u6587\u4EF6\u4E2D ${import_chalk.default.yellow(`\u76EE\u5F55: ==> ${protocol + (0, import_vite.normalizePath)(other + dir)}`)}`
110
- ).start();
111
- const fileName = `backup_${(0, import_dayjs.default)().format("YYYYMMDD_HHmmss")}.zip`;
112
- const localDir = `./__temp/zip`;
113
- const zipFilePath = `./__temp/${fileName}`;
114
- if (!import_node_fs.default.existsSync(localDir)) {
115
- import_node_fs.default.mkdirSync(localDir, { recursive: true });
131
+ function buildUrl(protocol, baseUrl, path2) {
132
+ return protocol + (0, import_vite.normalizePath)(baseUrl + path2);
133
+ }
134
+ async function connectWithRetry(client, config, maxRetries, retryDelay) {
135
+ let lastError;
136
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
137
+ try {
138
+ client.ftp.verbose = false;
139
+ await client.access({
140
+ ...config,
141
+ secure: true,
142
+ secureOptions: { rejectUnauthorized: false, timeout: 6e4 }
143
+ });
144
+ return;
145
+ } catch (error) {
146
+ lastError = error instanceof Error ? error : new Error(String(error));
147
+ if (attempt < maxRetries) {
148
+ console.log(import_chalk.default.yellow(`\u8FDE\u63A5\u5931\u8D25\uFF0C${retryDelay}ms \u540E\u91CD\u8BD5 (${attempt}/${maxRetries})`));
149
+ await new Promise((resolve) => setTimeout(resolve, retryDelay));
150
+ }
151
+ }
152
+ }
153
+ throw new Error(`FTP \u8FDE\u63A5\u5931\u8D25\uFF0C\u5DF2\u91CD\u8BD5 ${maxRetries} \u6B21: ${lastError?.message}`);
154
+ }
155
+ function createTempDir(basePath) {
156
+ const tempBaseDir = import_node_os.default.tmpdir();
157
+ const tempPath = import_node_path.default.join(tempBaseDir, "vite-plugin-deploy-ftp", basePath);
158
+ if (!import_node_fs.default.existsSync(tempPath)) {
159
+ import_node_fs.default.mkdirSync(tempPath, { recursive: true });
116
160
  }
117
- await client.downloadToDir(localDir, dir);
118
- backupSpinner.text = `\u4E0B\u8F7D\u8FDC\u7A0B\u6587\u4EF6\u6210\u529F ${import_chalk.default.yellow(`\u76EE\u5F55: ==> ${protocol + (0, import_vite.normalizePath)(other + dir)}`)}`;
119
- import_node_fs.default.readdirSync(localDir).forEach((i) => {
120
- if (i.startsWith("backup_") && i.endsWith(".zip")) {
121
- import_node_fs.default.rmSync(import_node_path.default.join(localDir, i));
161
+ return {
162
+ path: tempPath,
163
+ cleanup: () => {
164
+ try {
165
+ if (import_node_fs.default.existsSync(tempPath)) {
166
+ import_node_fs.default.rmSync(tempPath, { recursive: true, force: true });
167
+ }
168
+ } catch (error) {
169
+ console.warn(import_chalk.default.yellow(`\u6E05\u7406\u4E34\u65F6\u76EE\u5F55\u5931\u8D25: ${tempPath}`), error);
170
+ }
122
171
  }
172
+ };
173
+ }
174
+ async function createBackupFile(client, dir, protocol, baseUrl) {
175
+ const backupSpinner = (0, import_ora.default)(`\u521B\u5EFA\u5907\u4EFD\u6587\u4EF6\u4E2D ${import_chalk.default.yellow(`\u76EE\u5F55: ==> ${buildUrl(protocol, baseUrl, dir)}`)}`).start();
176
+ const fileName = `backup_${(0, import_dayjs.default)().format("YYYYMMDD_HHmmss")}.zip`;
177
+ const tempDir = createTempDir("backup-zip");
178
+ const zipFilePath = import_node_path.default.join(import_node_os.default.tmpdir(), "vite-plugin-deploy-ftp", fileName);
179
+ try {
180
+ const zipDir = import_node_path.default.dirname(zipFilePath);
181
+ if (!import_node_fs.default.existsSync(zipDir)) {
182
+ import_node_fs.default.mkdirSync(zipDir, { recursive: true });
183
+ }
184
+ await client.downloadToDir(tempDir.path, dir);
185
+ backupSpinner.text = `\u4E0B\u8F7D\u8FDC\u7A0B\u6587\u4EF6\u6210\u529F ${import_chalk.default.yellow(`\u76EE\u5F55: ==> ${buildUrl(protocol, baseUrl, dir)}`)}`;
186
+ import_node_fs.default.readdirSync(tempDir.path).forEach((fileName2) => {
187
+ if (fileName2.startsWith("backup_") && fileName2.endsWith(".zip")) {
188
+ import_node_fs.default.rmSync(import_node_path.default.join(tempDir.path, fileName2));
189
+ }
190
+ });
191
+ await createZipFile(tempDir.path, zipFilePath);
192
+ backupSpinner.text = `\u538B\u7F29\u5B8C\u6210, \u51C6\u5907\u4E0A\u4F20 ${import_chalk.default.yellow(
193
+ `\u76EE\u5F55: ==> ${buildUrl(protocol, baseUrl, dir + "/" + fileName)}`
194
+ )}`;
195
+ await client.uploadFrom(zipFilePath, (0, import_vite.normalizePath)(`${dir}/${fileName}`));
196
+ backupSpinner.succeed(`\u5907\u4EFD\u6210\u529F ${import_chalk.default.green(`\u76EE\u5F55: ==> ${buildUrl(protocol, baseUrl, dir + "/" + fileName)}`)}`);
197
+ } catch (error) {
198
+ backupSpinner.fail("\u5907\u4EFD\u5931\u8D25");
199
+ throw error;
200
+ } finally {
201
+ tempDir.cleanup();
202
+ try {
203
+ if (import_node_fs.default.existsSync(zipFilePath)) {
204
+ import_node_fs.default.rmSync(zipFilePath);
205
+ }
206
+ } catch (error) {
207
+ console.warn(import_chalk.default.yellow("\u6E05\u7406zip\u6587\u4EF6\u5931\u8D25"), error);
208
+ }
209
+ }
210
+ }
211
+ async function createZipFile(sourceDir, outputPath) {
212
+ return new Promise((resolve, reject) => {
213
+ const output = import_node_fs.default.createWriteStream(outputPath);
214
+ const archive = (0, import_archiver.default)("zip", {
215
+ zlib: { level: 9 }
216
+ });
217
+ output.on("close", () => {
218
+ resolve();
219
+ });
220
+ archive.on("error", (err) => {
221
+ reject(err);
222
+ });
223
+ archive.pipe(output);
224
+ archive.directory(sourceDir, false);
225
+ archive.finalize();
123
226
  });
124
- const output = import_node_fs.default.createWriteStream(zipFilePath);
125
- const archive = (0, import_archiver.default)("zip", {
126
- zlib: { level: 9 }
127
- });
128
- output.on("close", function() {
129
- });
130
- archive.on("error", function(err) {
131
- backupSpinner.fail("\u538B\u7F29\u5931\u8D25");
132
- throw err;
133
- });
134
- archive.pipe(output);
135
- archive.directory(localDir, false);
136
- await archive.finalize();
137
- backupSpinner.text = `\u538B\u7F29\u5B8C\u6210, \u51C6\u5907\u4E0A\u4F20 ${import_chalk.default.yellow(
138
- `\u76EE\u5F55: ==> ${protocol + (0, import_vite.normalizePath)(other + dir + "/" + fileName)}`
139
- )}`;
140
- await client.uploadFrom(zipFilePath, (0, import_vite.normalizePath)(`${dir}/${fileName}`));
141
- backupSpinner.succeed(
142
- `\u5907\u4EFD\u6210\u529F ${import_chalk.default.green(`\u76EE\u5F55: ==> ${protocol + (0, import_vite.normalizePath)(other + dir + "/" + fileName)}`)}`
143
- );
144
- import_node_fs.default.rmSync(`./__temp`, { recursive: true });
227
+ }
228
+ async function createSingleBackup(client, dir, protocol, baseUrl, singleBackFiles) {
229
+ const timestamp = (0, import_dayjs.default)().format("YYYYMMDD_HHmmss");
230
+ const backupSpinner = (0, import_ora.default)(`\u5907\u4EFD\u6307\u5B9A\u6587\u4EF6\u4E2D ${import_chalk.default.yellow(`\u76EE\u5F55: ==> ${buildUrl(protocol, baseUrl, dir)}`)}`).start();
231
+ const tempDir = createTempDir("single-backup");
232
+ try {
233
+ const remoteFiles = await client.list(dir);
234
+ const backupTasks = singleBackFiles.map((fileName) => {
235
+ const remoteFile = remoteFiles.find((f) => f.name === fileName);
236
+ return remoteFile ? { fileName, exists: true } : { fileName, exists: false };
237
+ }).filter((task) => task.exists);
238
+ if (backupTasks.length === 0) {
239
+ backupSpinner.warn("\u672A\u627E\u5230\u9700\u8981\u5907\u4EFD\u7684\u6587\u4EF6");
240
+ return;
241
+ }
242
+ const concurrencyLimit = 3;
243
+ let backedUpCount = 0;
244
+ for (let i = 0; i < backupTasks.length; i += concurrencyLimit) {
245
+ const batch = backupTasks.slice(i, i + concurrencyLimit);
246
+ const promises = batch.map(async ({ fileName }) => {
247
+ try {
248
+ const localTempPath = import_node_path.default.join(tempDir.path, fileName);
249
+ const [name, ext] = fileName.split(".");
250
+ const suffix = ext ? `.${ext}` : "";
251
+ const backupRemotePath = (0, import_vite.normalizePath)(`${dir}/${name}.${timestamp}${suffix}`);
252
+ await client.downloadTo(localTempPath, (0, import_vite.normalizePath)(`${dir}/${fileName}`));
253
+ await client.uploadFrom(localTempPath, backupRemotePath);
254
+ return true;
255
+ } catch (error) {
256
+ console.warn(import_chalk.default.yellow(`\u5907\u4EFD\u6587\u4EF6 ${fileName} \u5931\u8D25:`), error instanceof Error ? error.message : error);
257
+ return false;
258
+ }
259
+ });
260
+ const results = await Promise.all(promises);
261
+ backedUpCount += results.filter(Boolean).length;
262
+ }
263
+ if (backedUpCount > 0) {
264
+ backupSpinner.succeed(`\u5DF2\u5907\u4EFD ${backedUpCount} \u4E2A\u6587\u4EF6\u5230 ${import_chalk.default.green(buildUrl(protocol, baseUrl, dir))}`);
265
+ } else {
266
+ backupSpinner.fail("\u6240\u6709\u6587\u4EF6\u5907\u4EFD\u5931\u8D25");
267
+ }
268
+ } catch (error) {
269
+ backupSpinner.fail("\u5907\u4EFD\u8FC7\u7A0B\u4E2D\u53D1\u751F\u9519\u8BEF");
270
+ throw error;
271
+ } finally {
272
+ tempDir.cleanup();
273
+ }
145
274
  }
package/dist/index.mjs CHANGED
@@ -5,11 +5,27 @@ import { Client } from "basic-ftp";
5
5
  import chalk from "chalk";
6
6
  import dayjs from "dayjs";
7
7
  import fs from "node:fs";
8
+ import os from "node:os";
8
9
  import path from "node:path";
9
10
  import ora from "ora";
10
11
  import { normalizePath } from "vite";
11
12
  function vitePluginDeployFtp(option) {
12
- const { open = true, host, port = 21, user, password, uploadPath, alias = "" } = option || {};
13
+ const {
14
+ open = true,
15
+ host,
16
+ port = 21,
17
+ user,
18
+ password,
19
+ uploadPath,
20
+ alias = "",
21
+ singleBack = false,
22
+ singleBackFiles = ["index.html"],
23
+ maxRetries = 3,
24
+ retryDelay = 1e3
25
+ } = option || {};
26
+ if (!host || !user || !password || !uploadPath) {
27
+ throw new Error("Missing required FTP configuration: host, user, password, uploadPath");
28
+ }
13
29
  let outDir = "dist";
14
30
  return {
15
31
  name: "vite-plugin-deploy-ftp",
@@ -23,91 +39,204 @@ function vitePluginDeployFtp(option) {
23
39
  order: "post",
24
40
  async handler() {
25
41
  if (!open) return;
26
- if (!host || !port || !user || !password || !uploadPath) {
27
- console.log(chalk.yellow("\u8BF7\u914D\u7F6E\u6B63\u786E\u7684FTP\u4FE1\u606F"));
28
- return;
29
- }
30
- let [protocol, other] = (alias || "://").split("://");
31
- if (protocol) {
32
- protocol = protocol + "://";
42
+ try {
43
+ await deployToFtp();
44
+ } catch (error) {
45
+ console.error(chalk.red("FTP \u90E8\u7F72\u5931\u8D25:"), error instanceof Error ? error.message : error);
46
+ throw error;
33
47
  }
34
- const ftpUploadChoice = await select({
35
- message: "\u662F\u5426\u4E0A\u4F20FTP",
36
- choices: ["\u662F", "\u5426"],
37
- default: "\u662F"
38
- });
39
- if (ftpUploadChoice === "\u5426") return;
40
- const uploadSpinner = ora("\u51C6\u5907\u81EA\u52A8\u4E0A\u4F20\uFF0C\u521B\u5EFA\u8FDE\u63A5\u4E2D...").start();
41
- const client = new Client();
42
- client.ftp.verbose = false;
43
- await client.access({
44
- host,
45
- port,
46
- user,
47
- password,
48
- secure: true,
49
- secureOptions: { rejectUnauthorized: false, timeout: 6e4 }
50
- });
51
- uploadSpinner.color = "blue";
52
- uploadSpinner.text = "\u8FDE\u63A5\u6210\u529F";
53
- const fileList = await client.list(uploadPath);
54
- uploadSpinner.succeed(`\u5DF2\u8FDE\u63A5 ${chalk.green(`\u76EE\u5F55: ==> ${protocol + normalizePath(other + uploadPath)}`)}`);
55
- if (fileList.length) {
48
+ }
49
+ }
50
+ };
51
+ async function deployToFtp() {
52
+ const { protocol, baseUrl } = parseAlias(alias);
53
+ const ftpUploadChoice = await select({
54
+ message: "\u662F\u5426\u4E0A\u4F20FTP",
55
+ choices: ["\u662F", "\u5426"],
56
+ default: "\u662F"
57
+ });
58
+ if (ftpUploadChoice === "\u5426") return;
59
+ const client = new Client();
60
+ let uploadSpinner;
61
+ try {
62
+ uploadSpinner = ora("\u51C6\u5907\u81EA\u52A8\u4E0A\u4F20\uFF0C\u521B\u5EFA\u8FDE\u63A5\u4E2D...").start();
63
+ await connectWithRetry(client, { host, port, user, password }, maxRetries, retryDelay);
64
+ uploadSpinner.color = "blue";
65
+ uploadSpinner.text = "\u8FDE\u63A5\u6210\u529F";
66
+ const fileList = await client.list(uploadPath);
67
+ uploadSpinner.succeed(`\u5DF2\u8FDE\u63A5 ${chalk.green(`\u76EE\u5F55: ==> ${buildUrl(protocol, baseUrl, uploadPath)}`)}`);
68
+ if (fileList.length) {
69
+ if (singleBack) {
70
+ await createSingleBackup(client, uploadPath, protocol, baseUrl, singleBackFiles);
71
+ } else {
56
72
  const isBackFiles = await select({
57
73
  message: "\u662F\u5426\u5907\u4EFD\u8FDC\u7A0B\u6587\u4EF6",
58
74
  choices: ["\u5426", "\u662F"],
59
75
  default: "\u5426"
60
76
  });
61
77
  if (isBackFiles === "\u662F") {
62
- await createBackupFile(client, uploadPath, protocol, other);
78
+ await createBackupFile(client, uploadPath, protocol, baseUrl);
63
79
  }
64
80
  }
65
- const uploadFileSpinner = ora("\u4E0A\u4F20\u4E2D...").start();
66
- await client.uploadFromDir(outDir, uploadPath);
67
- uploadFileSpinner.succeed("\u4E0A\u4F20\u6210\u529F url:" + chalk.green(`${protocol + normalizePath(other + uploadPath)}`));
68
- client.close();
69
81
  }
82
+ const uploadFileSpinner = ora("\u4E0A\u4F20\u4E2D...").start();
83
+ await client.uploadFromDir(outDir, uploadPath);
84
+ uploadFileSpinner.succeed("\u4E0A\u4F20\u6210\u529F url:" + chalk.green(buildUrl(protocol, baseUrl, uploadPath)));
85
+ } finally {
86
+ client.close();
70
87
  }
88
+ }
89
+ }
90
+ function parseAlias(alias = "") {
91
+ const [protocol = "", baseUrl = ""] = alias.split("://");
92
+ return {
93
+ protocol: protocol ? `${protocol}://` : "",
94
+ baseUrl: baseUrl || ""
71
95
  };
72
96
  }
73
- async function createBackupFile(client, dir, protocol, other) {
74
- const backupSpinner = ora(
75
- `\u521B\u5EFA\u5907\u4EFD\u6587\u4EF6\u4E2D ${chalk.yellow(`\u76EE\u5F55: ==> ${protocol + normalizePath(other + dir)}`)}`
76
- ).start();
77
- const fileName = `backup_${dayjs().format("YYYYMMDD_HHmmss")}.zip`;
78
- const localDir = `./__temp/zip`;
79
- const zipFilePath = `./__temp/${fileName}`;
80
- if (!fs.existsSync(localDir)) {
81
- fs.mkdirSync(localDir, { recursive: true });
97
+ function buildUrl(protocol, baseUrl, path2) {
98
+ return protocol + normalizePath(baseUrl + path2);
99
+ }
100
+ async function connectWithRetry(client, config, maxRetries, retryDelay) {
101
+ let lastError;
102
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
103
+ try {
104
+ client.ftp.verbose = false;
105
+ await client.access({
106
+ ...config,
107
+ secure: true,
108
+ secureOptions: { rejectUnauthorized: false, timeout: 6e4 }
109
+ });
110
+ return;
111
+ } catch (error) {
112
+ lastError = error instanceof Error ? error : new Error(String(error));
113
+ if (attempt < maxRetries) {
114
+ console.log(chalk.yellow(`\u8FDE\u63A5\u5931\u8D25\uFF0C${retryDelay}ms \u540E\u91CD\u8BD5 (${attempt}/${maxRetries})`));
115
+ await new Promise((resolve) => setTimeout(resolve, retryDelay));
116
+ }
117
+ }
118
+ }
119
+ throw new Error(`FTP \u8FDE\u63A5\u5931\u8D25\uFF0C\u5DF2\u91CD\u8BD5 ${maxRetries} \u6B21: ${lastError?.message}`);
120
+ }
121
+ function createTempDir(basePath) {
122
+ const tempBaseDir = os.tmpdir();
123
+ const tempPath = path.join(tempBaseDir, "vite-plugin-deploy-ftp", basePath);
124
+ if (!fs.existsSync(tempPath)) {
125
+ fs.mkdirSync(tempPath, { recursive: true });
82
126
  }
83
- await client.downloadToDir(localDir, dir);
84
- backupSpinner.text = `\u4E0B\u8F7D\u8FDC\u7A0B\u6587\u4EF6\u6210\u529F ${chalk.yellow(`\u76EE\u5F55: ==> ${protocol + normalizePath(other + dir)}`)}`;
85
- fs.readdirSync(localDir).forEach((i) => {
86
- if (i.startsWith("backup_") && i.endsWith(".zip")) {
87
- fs.rmSync(path.join(localDir, i));
127
+ return {
128
+ path: tempPath,
129
+ cleanup: () => {
130
+ try {
131
+ if (fs.existsSync(tempPath)) {
132
+ fs.rmSync(tempPath, { recursive: true, force: true });
133
+ }
134
+ } catch (error) {
135
+ console.warn(chalk.yellow(`\u6E05\u7406\u4E34\u65F6\u76EE\u5F55\u5931\u8D25: ${tempPath}`), error);
136
+ }
88
137
  }
138
+ };
139
+ }
140
+ async function createBackupFile(client, dir, protocol, baseUrl) {
141
+ const backupSpinner = ora(`\u521B\u5EFA\u5907\u4EFD\u6587\u4EF6\u4E2D ${chalk.yellow(`\u76EE\u5F55: ==> ${buildUrl(protocol, baseUrl, dir)}`)}`).start();
142
+ const fileName = `backup_${dayjs().format("YYYYMMDD_HHmmss")}.zip`;
143
+ const tempDir = createTempDir("backup-zip");
144
+ const zipFilePath = path.join(os.tmpdir(), "vite-plugin-deploy-ftp", fileName);
145
+ try {
146
+ const zipDir = path.dirname(zipFilePath);
147
+ if (!fs.existsSync(zipDir)) {
148
+ fs.mkdirSync(zipDir, { recursive: true });
149
+ }
150
+ await client.downloadToDir(tempDir.path, dir);
151
+ backupSpinner.text = `\u4E0B\u8F7D\u8FDC\u7A0B\u6587\u4EF6\u6210\u529F ${chalk.yellow(`\u76EE\u5F55: ==> ${buildUrl(protocol, baseUrl, dir)}`)}`;
152
+ fs.readdirSync(tempDir.path).forEach((fileName2) => {
153
+ if (fileName2.startsWith("backup_") && fileName2.endsWith(".zip")) {
154
+ fs.rmSync(path.join(tempDir.path, fileName2));
155
+ }
156
+ });
157
+ await createZipFile(tempDir.path, zipFilePath);
158
+ backupSpinner.text = `\u538B\u7F29\u5B8C\u6210, \u51C6\u5907\u4E0A\u4F20 ${chalk.yellow(
159
+ `\u76EE\u5F55: ==> ${buildUrl(protocol, baseUrl, dir + "/" + fileName)}`
160
+ )}`;
161
+ await client.uploadFrom(zipFilePath, normalizePath(`${dir}/${fileName}`));
162
+ backupSpinner.succeed(`\u5907\u4EFD\u6210\u529F ${chalk.green(`\u76EE\u5F55: ==> ${buildUrl(protocol, baseUrl, dir + "/" + fileName)}`)}`);
163
+ } catch (error) {
164
+ backupSpinner.fail("\u5907\u4EFD\u5931\u8D25");
165
+ throw error;
166
+ } finally {
167
+ tempDir.cleanup();
168
+ try {
169
+ if (fs.existsSync(zipFilePath)) {
170
+ fs.rmSync(zipFilePath);
171
+ }
172
+ } catch (error) {
173
+ console.warn(chalk.yellow("\u6E05\u7406zip\u6587\u4EF6\u5931\u8D25"), error);
174
+ }
175
+ }
176
+ }
177
+ async function createZipFile(sourceDir, outputPath) {
178
+ return new Promise((resolve, reject) => {
179
+ const output = fs.createWriteStream(outputPath);
180
+ const archive = archiver("zip", {
181
+ zlib: { level: 9 }
182
+ });
183
+ output.on("close", () => {
184
+ resolve();
185
+ });
186
+ archive.on("error", (err) => {
187
+ reject(err);
188
+ });
189
+ archive.pipe(output);
190
+ archive.directory(sourceDir, false);
191
+ archive.finalize();
89
192
  });
90
- const output = fs.createWriteStream(zipFilePath);
91
- const archive = archiver("zip", {
92
- zlib: { level: 9 }
93
- });
94
- output.on("close", function() {
95
- });
96
- archive.on("error", function(err) {
97
- backupSpinner.fail("\u538B\u7F29\u5931\u8D25");
98
- throw err;
99
- });
100
- archive.pipe(output);
101
- archive.directory(localDir, false);
102
- await archive.finalize();
103
- backupSpinner.text = `\u538B\u7F29\u5B8C\u6210, \u51C6\u5907\u4E0A\u4F20 ${chalk.yellow(
104
- `\u76EE\u5F55: ==> ${protocol + normalizePath(other + dir + "/" + fileName)}`
105
- )}`;
106
- await client.uploadFrom(zipFilePath, normalizePath(`${dir}/${fileName}`));
107
- backupSpinner.succeed(
108
- `\u5907\u4EFD\u6210\u529F ${chalk.green(`\u76EE\u5F55: ==> ${protocol + normalizePath(other + dir + "/" + fileName)}`)}`
109
- );
110
- fs.rmSync(`./__temp`, { recursive: true });
193
+ }
194
+ async function createSingleBackup(client, dir, protocol, baseUrl, singleBackFiles) {
195
+ const timestamp = dayjs().format("YYYYMMDD_HHmmss");
196
+ const backupSpinner = ora(`\u5907\u4EFD\u6307\u5B9A\u6587\u4EF6\u4E2D ${chalk.yellow(`\u76EE\u5F55: ==> ${buildUrl(protocol, baseUrl, dir)}`)}`).start();
197
+ const tempDir = createTempDir("single-backup");
198
+ try {
199
+ const remoteFiles = await client.list(dir);
200
+ const backupTasks = singleBackFiles.map((fileName) => {
201
+ const remoteFile = remoteFiles.find((f) => f.name === fileName);
202
+ return remoteFile ? { fileName, exists: true } : { fileName, exists: false };
203
+ }).filter((task) => task.exists);
204
+ if (backupTasks.length === 0) {
205
+ backupSpinner.warn("\u672A\u627E\u5230\u9700\u8981\u5907\u4EFD\u7684\u6587\u4EF6");
206
+ return;
207
+ }
208
+ const concurrencyLimit = 3;
209
+ let backedUpCount = 0;
210
+ for (let i = 0; i < backupTasks.length; i += concurrencyLimit) {
211
+ const batch = backupTasks.slice(i, i + concurrencyLimit);
212
+ const promises = batch.map(async ({ fileName }) => {
213
+ try {
214
+ const localTempPath = path.join(tempDir.path, fileName);
215
+ const [name, ext] = fileName.split(".");
216
+ const suffix = ext ? `.${ext}` : "";
217
+ const backupRemotePath = normalizePath(`${dir}/${name}.${timestamp}${suffix}`);
218
+ await client.downloadTo(localTempPath, normalizePath(`${dir}/${fileName}`));
219
+ await client.uploadFrom(localTempPath, backupRemotePath);
220
+ return true;
221
+ } catch (error) {
222
+ console.warn(chalk.yellow(`\u5907\u4EFD\u6587\u4EF6 ${fileName} \u5931\u8D25:`), error instanceof Error ? error.message : error);
223
+ return false;
224
+ }
225
+ });
226
+ const results = await Promise.all(promises);
227
+ backedUpCount += results.filter(Boolean).length;
228
+ }
229
+ if (backedUpCount > 0) {
230
+ backupSpinner.succeed(`\u5DF2\u5907\u4EFD ${backedUpCount} \u4E2A\u6587\u4EF6\u5230 ${chalk.green(buildUrl(protocol, baseUrl, dir))}`);
231
+ } else {
232
+ backupSpinner.fail("\u6240\u6709\u6587\u4EF6\u5907\u4EFD\u5931\u8D25");
233
+ }
234
+ } catch (error) {
235
+ backupSpinner.fail("\u5907\u4EFD\u8FC7\u7A0B\u4E2D\u53D1\u751F\u9519\u8BEF");
236
+ throw error;
237
+ } finally {
238
+ tempDir.cleanup();
239
+ }
111
240
  }
112
241
  export {
113
242
  vitePluginDeployFtp as default
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vite-plugin-deploy-ftp",
3
- "version": "0.0.8",
3
+ "version": "0.0.9",
4
4
  "main": "./dist/index.js",
5
5
  "module": "./dist/index.mjs",
6
6
  "types": "./dist/index.d.ts",