vite-plugin-deploy-ftp 0.0.7 → 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",
@@ -56,95 +72,203 @@ function vitePluginDeployFtp(option) {
56
72
  sequential: true,
57
73
  order: "post",
58
74
  async handler() {
59
- if (!host || !port || !user || !password || !uploadPath || !open) {
60
- console.log(import_chalk.default.yellow("\u8BF7\u914D\u7F6E\u6B63\u786E\u7684FTP\u4FE1\u606F"));
61
- return;
62
- }
63
- let [protocol, other] = (alias || "://").split("://");
64
- if (protocol) {
65
- protocol = protocol + "://";
75
+ if (!open) return;
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;
66
81
  }
67
- const ftpUploadChoice = await (0, import_prompts.select)({
68
- message: "\u662F\u5426\u4E0A\u4F20FTP",
69
- choices: ["\u662F", "\u5426"],
70
- default: "\u662F"
71
- });
72
- if (ftpUploadChoice === "\u5426") return;
73
- const uploadSpinner = (0, import_ora.default)("\u51C6\u5907\u81EA\u52A8\u4E0A\u4F20\uFF0C\u521B\u5EFA\u8FDE\u63A5\u4E2D...").start();
74
- const client = new import_basic_ftp.Client();
75
- client.ftp.verbose = false;
76
- await client.access({
77
- host,
78
- port,
79
- user,
80
- password,
81
- secure: true,
82
- secureOptions: { rejectUnauthorized: false, timeout: 6e4 }
83
- });
84
- uploadSpinner.color = "blue";
85
- uploadSpinner.text = "\u8FDE\u63A5\u6210\u529F";
86
- const fileList = await client.list(uploadPath);
87
- uploadSpinner.succeed(
88
- `\u5DF2\u8FDE\u63A5 ${import_chalk.default.green(`\u76EE\u5F55: ==> ${protocol + (0, import_vite.normalizePath)(other + uploadPath)}`)}`
89
- );
90
- 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 {
91
106
  const isBackFiles = await (0, import_prompts.select)({
92
107
  message: "\u662F\u5426\u5907\u4EFD\u8FDC\u7A0B\u6587\u4EF6",
93
108
  choices: ["\u5426", "\u662F"],
94
109
  default: "\u5426"
95
110
  });
96
111
  if (isBackFiles === "\u662F") {
97
- await createBackupFile(client, uploadPath, protocol, other);
112
+ await createBackupFile(client, uploadPath, protocol, baseUrl);
98
113
  }
99
114
  }
100
- const uploadFileSpinner = (0, import_ora.default)("\u4E0A\u4F20\u4E2D...").start();
101
- await client.uploadFromDir(outDir, uploadPath);
102
- uploadFileSpinner.succeed(
103
- "\u4E0A\u4F20\u6210\u529F url:" + import_chalk.default.green(`${protocol + (0, import_vite.normalizePath)(other + uploadPath)}`)
104
- );
105
- client.close();
106
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();
107
121
  }
122
+ }
123
+ }
124
+ function parseAlias(alias = "") {
125
+ const [protocol = "", baseUrl = ""] = alias.split("://");
126
+ return {
127
+ protocol: protocol ? `${protocol}://` : "",
128
+ baseUrl: baseUrl || ""
108
129
  };
109
130
  }
110
- async function createBackupFile(client, dir, protocol, other) {
111
- const backupSpinner = (0, import_ora.default)(
112
- `\u521B\u5EFA\u5907\u4EFD\u6587\u4EF6\u4E2D ${import_chalk.default.yellow(`\u76EE\u5F55: ==> ${protocol + (0, import_vite.normalizePath)(other + dir)}`)}`
113
- ).start();
114
- const fileName = `backup_${(0, import_dayjs.default)().format("YYYYMMDD_HHmmss")}.zip`;
115
- const localDir = `./__temp/zip`;
116
- const zipFilePath = `./__temp/${fileName}`;
117
- if (!import_node_fs.default.existsSync(localDir)) {
118
- 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 });
119
160
  }
120
- await client.downloadToDir(localDir, dir);
121
- backupSpinner.text = `\u4E0B\u8F7D\u8FDC\u7A0B\u6587\u4EF6\u6210\u529F ${import_chalk.default.yellow(
122
- `\u76EE\u5F55: ==> ${protocol + (0, import_vite.normalizePath)(other + dir)}`
123
- )}`;
124
- import_node_fs.default.readdirSync(localDir).forEach((i) => {
125
- if (i.startsWith("backup_") && i.endsWith(".zip")) {
126
- 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
+ }
127
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();
128
226
  });
129
- const output = import_node_fs.default.createWriteStream(zipFilePath);
130
- const archive = (0, import_archiver.default)("zip", {
131
- zlib: { level: 9 }
132
- });
133
- output.on("close", function() {
134
- });
135
- archive.on("error", function(err) {
136
- backupSpinner.fail("\u538B\u7F29\u5931\u8D25");
137
- throw err;
138
- });
139
- archive.pipe(output);
140
- archive.directory(localDir, false);
141
- await archive.finalize();
142
- backupSpinner.text = `\u538B\u7F29\u5B8C\u6210, \u51C6\u5907\u4E0A\u4F20 ${import_chalk.default.yellow(
143
- `\u76EE\u5F55: ==> ${protocol + (0, import_vite.normalizePath)(other + dir + "/" + fileName)}`
144
- )}`;
145
- await client.uploadFrom(zipFilePath, (0, import_vite.normalizePath)(`${dir}/${fileName}`));
146
- backupSpinner.succeed(
147
- `\u5907\u4EFD\u6210\u529F ${import_chalk.default.green(`\u76EE\u5F55: ==> ${protocol + (0, import_vite.normalizePath)(other + dir + "/" + fileName)}`)}`
148
- );
149
- 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
+ }
150
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",
@@ -22,97 +38,205 @@ function vitePluginDeployFtp(option) {
22
38
  sequential: true,
23
39
  order: "post",
24
40
  async handler() {
25
- if (!host || !port || !user || !password || !uploadPath || !open) {
26
- console.log(chalk.yellow("\u8BF7\u914D\u7F6E\u6B63\u786E\u7684FTP\u4FE1\u606F"));
27
- return;
28
- }
29
- let [protocol, other] = (alias || "://").split("://");
30
- if (protocol) {
31
- protocol = protocol + "://";
41
+ if (!open) return;
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;
32
47
  }
33
- const ftpUploadChoice = await select({
34
- message: "\u662F\u5426\u4E0A\u4F20FTP",
35
- choices: ["\u662F", "\u5426"],
36
- default: "\u662F"
37
- });
38
- if (ftpUploadChoice === "\u5426") return;
39
- const uploadSpinner = ora("\u51C6\u5907\u81EA\u52A8\u4E0A\u4F20\uFF0C\u521B\u5EFA\u8FDE\u63A5\u4E2D...").start();
40
- const client = new Client();
41
- client.ftp.verbose = false;
42
- await client.access({
43
- host,
44
- port,
45
- user,
46
- password,
47
- secure: true,
48
- secureOptions: { rejectUnauthorized: false, timeout: 6e4 }
49
- });
50
- uploadSpinner.color = "blue";
51
- uploadSpinner.text = "\u8FDE\u63A5\u6210\u529F";
52
- const fileList = await client.list(uploadPath);
53
- uploadSpinner.succeed(
54
- `\u5DF2\u8FDE\u63A5 ${chalk.green(`\u76EE\u5F55: ==> ${protocol + normalizePath(other + uploadPath)}`)}`
55
- );
56
- 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 {
57
72
  const isBackFiles = await select({
58
73
  message: "\u662F\u5426\u5907\u4EFD\u8FDC\u7A0B\u6587\u4EF6",
59
74
  choices: ["\u5426", "\u662F"],
60
75
  default: "\u5426"
61
76
  });
62
77
  if (isBackFiles === "\u662F") {
63
- await createBackupFile(client, uploadPath, protocol, other);
78
+ await createBackupFile(client, uploadPath, protocol, baseUrl);
64
79
  }
65
80
  }
66
- const uploadFileSpinner = ora("\u4E0A\u4F20\u4E2D...").start();
67
- await client.uploadFromDir(outDir, uploadPath);
68
- uploadFileSpinner.succeed(
69
- "\u4E0A\u4F20\u6210\u529F url:" + chalk.green(`${protocol + normalizePath(other + uploadPath)}`)
70
- );
71
- client.close();
72
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();
73
87
  }
88
+ }
89
+ }
90
+ function parseAlias(alias = "") {
91
+ const [protocol = "", baseUrl = ""] = alias.split("://");
92
+ return {
93
+ protocol: protocol ? `${protocol}://` : "",
94
+ baseUrl: baseUrl || ""
74
95
  };
75
96
  }
76
- async function createBackupFile(client, dir, protocol, other) {
77
- const backupSpinner = ora(
78
- `\u521B\u5EFA\u5907\u4EFD\u6587\u4EF6\u4E2D ${chalk.yellow(`\u76EE\u5F55: ==> ${protocol + normalizePath(other + dir)}`)}`
79
- ).start();
80
- const fileName = `backup_${dayjs().format("YYYYMMDD_HHmmss")}.zip`;
81
- const localDir = `./__temp/zip`;
82
- const zipFilePath = `./__temp/${fileName}`;
83
- if (!fs.existsSync(localDir)) {
84
- 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 });
85
126
  }
86
- await client.downloadToDir(localDir, dir);
87
- backupSpinner.text = `\u4E0B\u8F7D\u8FDC\u7A0B\u6587\u4EF6\u6210\u529F ${chalk.yellow(
88
- `\u76EE\u5F55: ==> ${protocol + normalizePath(other + dir)}`
89
- )}`;
90
- fs.readdirSync(localDir).forEach((i) => {
91
- if (i.startsWith("backup_") && i.endsWith(".zip")) {
92
- 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
+ }
93
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();
94
192
  });
95
- const output = fs.createWriteStream(zipFilePath);
96
- const archive = archiver("zip", {
97
- zlib: { level: 9 }
98
- });
99
- output.on("close", function() {
100
- });
101
- archive.on("error", function(err) {
102
- backupSpinner.fail("\u538B\u7F29\u5931\u8D25");
103
- throw err;
104
- });
105
- archive.pipe(output);
106
- archive.directory(localDir, false);
107
- await archive.finalize();
108
- backupSpinner.text = `\u538B\u7F29\u5B8C\u6210, \u51C6\u5907\u4E0A\u4F20 ${chalk.yellow(
109
- `\u76EE\u5F55: ==> ${protocol + normalizePath(other + dir + "/" + fileName)}`
110
- )}`;
111
- await client.uploadFrom(zipFilePath, normalizePath(`${dir}/${fileName}`));
112
- backupSpinner.succeed(
113
- `\u5907\u4EFD\u6210\u529F ${chalk.green(`\u76EE\u5F55: ==> ${protocol + normalizePath(other + dir + "/" + fileName)}`)}`
114
- );
115
- 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
+ }
116
240
  }
117
241
  export {
118
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.7",
3
+ "version": "0.0.9",
4
4
  "main": "./dist/index.js",
5
5
  "module": "./dist/index.mjs",
6
6
  "types": "./dist/index.d.ts",