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 +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +199 -70
- package/dist/index.mjs +199 -70
- package/package.json +1 -1
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 {
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
if (
|
|
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,
|
|
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
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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 {
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
if (
|
|
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,
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|