vite-plugin-deploy-ftp 0.0.8 → 0.0.10

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