vite-plugin-deploy-ftp 1.2.2 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/index.d.ts +10 -21
- package/dist/index.js +516 -256
- package/package.json +8 -6
- package/dist/index.d.mts +0 -41
- package/dist/index.mjs +0 -435
package/package.json
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vite-plugin-deploy-ftp",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
|
-
"module": "./dist/index.mjs",
|
|
6
5
|
"types": "./dist/index.d.ts",
|
|
6
|
+
"type": "module",
|
|
7
7
|
"homepage": "https://github.com/yulin96/vite-plugin-deploy-ftp",
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
10
|
-
"url": "https://github.com/yulin96/vite-plugin-deploy-ftp"
|
|
10
|
+
"url": "git+https://github.com/yulin96/vite-plugin-deploy-ftp.git"
|
|
11
11
|
},
|
|
12
12
|
"bugs": {
|
|
13
13
|
"url": "https://github.com/yulin96/vite-plugin-deploy-ftp/issues"
|
|
14
14
|
},
|
|
15
15
|
"exports": {
|
|
16
16
|
".": {
|
|
17
|
-
"
|
|
18
|
-
"
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"default": "./dist/index.js"
|
|
19
19
|
}
|
|
20
20
|
},
|
|
21
21
|
"keywords": [
|
|
@@ -49,7 +49,9 @@
|
|
|
49
49
|
},
|
|
50
50
|
"scripts": {
|
|
51
51
|
"build": "tsup",
|
|
52
|
+
"typecheck": "tsc -p tsconfig.json",
|
|
52
53
|
"pack": "pnpm run build && pnpm pack",
|
|
53
|
-
"build:test": "cd playground && vite build"
|
|
54
|
+
"build:test": "cd playground && vite build",
|
|
55
|
+
"build:test:deploy": "cd playground && vite build --mode deploy"
|
|
54
56
|
}
|
|
55
57
|
}
|
package/dist/index.d.mts
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { Plugin } from 'vite';
|
|
2
|
-
|
|
3
|
-
type vitePluginDeployFtpOption = ({
|
|
4
|
-
uploadPath: string;
|
|
5
|
-
singleBackFiles?: string[];
|
|
6
|
-
singleBack?: boolean;
|
|
7
|
-
open?: boolean;
|
|
8
|
-
maxRetries?: number;
|
|
9
|
-
retryDelay?: number;
|
|
10
|
-
showBackFile?: boolean;
|
|
11
|
-
autoUpload?: boolean;
|
|
12
|
-
} & {
|
|
13
|
-
ftps: {
|
|
14
|
-
name: string;
|
|
15
|
-
host?: string;
|
|
16
|
-
port?: number;
|
|
17
|
-
user?: string;
|
|
18
|
-
password?: string;
|
|
19
|
-
alias?: string;
|
|
20
|
-
}[];
|
|
21
|
-
defaultFtp?: string;
|
|
22
|
-
}) | ({
|
|
23
|
-
uploadPath: string;
|
|
24
|
-
singleBackFiles?: string[];
|
|
25
|
-
singleBack?: boolean;
|
|
26
|
-
open?: boolean;
|
|
27
|
-
maxRetries?: number;
|
|
28
|
-
retryDelay?: number;
|
|
29
|
-
showBackFile?: boolean;
|
|
30
|
-
autoUpload?: boolean;
|
|
31
|
-
} & {
|
|
32
|
-
name?: string;
|
|
33
|
-
host?: string;
|
|
34
|
-
port?: number;
|
|
35
|
-
user?: string;
|
|
36
|
-
password?: string;
|
|
37
|
-
alias?: string;
|
|
38
|
-
});
|
|
39
|
-
declare function vitePluginDeployFtp(option: vitePluginDeployFtpOption): Plugin;
|
|
40
|
-
|
|
41
|
-
export { vitePluginDeployFtp as default, type vitePluginDeployFtpOption };
|
package/dist/index.mjs
DELETED
|
@@ -1,435 +0,0 @@
|
|
|
1
|
-
// src/index.ts
|
|
2
|
-
import { checkbox, select } from "@inquirer/prompts";
|
|
3
|
-
import archiver from "archiver";
|
|
4
|
-
import { Client } from "basic-ftp";
|
|
5
|
-
import chalk from "chalk";
|
|
6
|
-
import dayjs from "dayjs";
|
|
7
|
-
import fs from "fs";
|
|
8
|
-
import os from "os";
|
|
9
|
-
import path from "path";
|
|
10
|
-
import ora from "ora";
|
|
11
|
-
import { normalizePath } from "vite";
|
|
12
|
-
function vitePluginDeployFtp(option) {
|
|
13
|
-
const {
|
|
14
|
-
open = true,
|
|
15
|
-
uploadPath,
|
|
16
|
-
singleBack = false,
|
|
17
|
-
singleBackFiles = ["index.html"],
|
|
18
|
-
showBackFile = false,
|
|
19
|
-
maxRetries = 3,
|
|
20
|
-
retryDelay = 1e3,
|
|
21
|
-
autoUpload = false
|
|
22
|
-
} = option || {};
|
|
23
|
-
const isMultiFtp = "ftps" in option;
|
|
24
|
-
const ftpConfigs = isMultiFtp ? option.ftps : [{ ...option, name: option.name || option.alias || option.host }];
|
|
25
|
-
const defaultFtp = isMultiFtp ? option.defaultFtp : void 0;
|
|
26
|
-
if (!uploadPath || isMultiFtp && (!option.ftps || option.ftps.length === 0)) {
|
|
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
|
-
}
|
|
37
|
-
let outDir = "dist";
|
|
38
|
-
let buildFailed = false;
|
|
39
|
-
return {
|
|
40
|
-
name: "vite-plugin-deploy-ftp",
|
|
41
|
-
apply: "build",
|
|
42
|
-
enforce: "post",
|
|
43
|
-
buildEnd(error) {
|
|
44
|
-
if (error) buildFailed = true;
|
|
45
|
-
},
|
|
46
|
-
configResolved(config) {
|
|
47
|
-
outDir = config.build?.outDir || "dist";
|
|
48
|
-
},
|
|
49
|
-
closeBundle: {
|
|
50
|
-
sequential: true,
|
|
51
|
-
order: "post",
|
|
52
|
-
async handler() {
|
|
53
|
-
if (!open || buildFailed) return;
|
|
54
|
-
try {
|
|
55
|
-
process.stdout.write("\x1B[2J\x1B[0f");
|
|
56
|
-
await deployToFtp();
|
|
57
|
-
} catch (error) {
|
|
58
|
-
console.error(chalk.red("\u274C FTP \u90E8\u7F72\u5931\u8D25:"), error instanceof Error ? error.message : error);
|
|
59
|
-
throw error;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
};
|
|
64
|
-
async function deployToFtp() {
|
|
65
|
-
if (!autoUpload) {
|
|
66
|
-
const ftpUploadChoice = await select({
|
|
67
|
-
message: "\u662F\u5426\u4E0A\u4F20FTP",
|
|
68
|
-
choices: ["\u662F", "\u5426"],
|
|
69
|
-
default: "\u662F"
|
|
70
|
-
});
|
|
71
|
-
if (ftpUploadChoice === "\u5426") return;
|
|
72
|
-
}
|
|
73
|
-
let selectedConfigs = [];
|
|
74
|
-
if (isMultiFtp) {
|
|
75
|
-
if (defaultFtp) {
|
|
76
|
-
const defaultConfig = ftpConfigs.find((ftp) => ftp.name === defaultFtp);
|
|
77
|
-
if (defaultConfig) {
|
|
78
|
-
if (validateFtpConfig(defaultConfig)) {
|
|
79
|
-
console.log(chalk.blue(`\u4F7F\u7528\u9ED8\u8BA4FTP\u914D\u7F6E: ${defaultFtp}`));
|
|
80
|
-
selectedConfigs = [defaultConfig];
|
|
81
|
-
} else {
|
|
82
|
-
console.log(chalk.yellow(`\u26A0\uFE0F \u9ED8\u8BA4FTP\u914D\u7F6E "${defaultFtp}" \u7F3A\u5C11\u5FC5\u9700\u53C2\u6570\uFF0C\u5C06\u8FDB\u884C\u624B\u52A8\u9009\u62E9`));
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
if (selectedConfigs.length === 0) {
|
|
87
|
-
const validConfigs = ftpConfigs.filter(validateFtpConfig);
|
|
88
|
-
const invalidConfigs = ftpConfigs.filter((config) => !validateFtpConfig(config));
|
|
89
|
-
if (invalidConfigs.length > 0) {
|
|
90
|
-
console.log(chalk.yellow("\n \u4EE5\u4E0BFTP\u914D\u7F6E\u7F3A\u5C11\u5FC5\u9700\u53C2\u6570\uFF0C\u5DF2\u4ECE\u9009\u62E9\u5217\u8868\u4E2D\u6392\u9664:"));
|
|
91
|
-
invalidConfigs.forEach((config) => {
|
|
92
|
-
const missing = [];
|
|
93
|
-
if (!config.host) missing.push("host");
|
|
94
|
-
if (!config.user) missing.push("user");
|
|
95
|
-
if (!config.password) missing.push("password");
|
|
96
|
-
console.log(chalk.yellow(` - ${config.name || "\u672A\u547D\u540D"}: \u7F3A\u5C11 ${missing.join(", ")}`));
|
|
97
|
-
});
|
|
98
|
-
console.log();
|
|
99
|
-
}
|
|
100
|
-
if (validConfigs.length === 0) {
|
|
101
|
-
console.error(chalk.red("\u274C \u6CA1\u6709\u53EF\u7528\u7684\u6709\u6548FTP\u914D\u7F6E"));
|
|
102
|
-
return;
|
|
103
|
-
}
|
|
104
|
-
const choices = validConfigs.map((ftp) => ({
|
|
105
|
-
name: ftp.name,
|
|
106
|
-
value: ftp
|
|
107
|
-
}));
|
|
108
|
-
selectedConfigs = await checkbox({
|
|
109
|
-
message: "\u9009\u62E9\u8981\u4E0A\u4F20\u7684FTP\u670D\u52A1\u5668\uFF08\u53EF\u591A\u9009\uFF09",
|
|
110
|
-
choices,
|
|
111
|
-
required: true
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
|
-
} else {
|
|
115
|
-
const singleConfig = ftpConfigs[0];
|
|
116
|
-
if (validateFtpConfig(singleConfig)) {
|
|
117
|
-
selectedConfigs = [{ ...singleConfig, name: singleConfig.name || singleConfig.host }];
|
|
118
|
-
} else {
|
|
119
|
-
const missing = [];
|
|
120
|
-
if (!singleConfig.host) missing.push("host");
|
|
121
|
-
if (!singleConfig.user) missing.push("user");
|
|
122
|
-
if (!singleConfig.password) missing.push("password");
|
|
123
|
-
console.error(chalk.red(`\u274C FTP\u914D\u7F6E\u7F3A\u5C11\u5FC5\u9700\u53C2\u6570: ${missing.join(", ")}`));
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
for (const ftpConfig of selectedConfigs) {
|
|
128
|
-
const { host, port = 21, user, password, alias = "", name } = ftpConfig;
|
|
129
|
-
if (!host || !user || !password) {
|
|
130
|
-
console.error(chalk.red(`\u274C FTP\u914D\u7F6E "${name || host || "\u672A\u77E5"}" \u7F3A\u5C11\u5FC5\u9700\u53C2\u6570:`));
|
|
131
|
-
if (!host) console.error(chalk.red(" - \u7F3A\u5C11 host"));
|
|
132
|
-
if (!user) console.error(chalk.red(" - \u7F3A\u5C11 user"));
|
|
133
|
-
if (!password) console.error(chalk.red(" - \u7F3A\u5C11 password"));
|
|
134
|
-
continue;
|
|
135
|
-
}
|
|
136
|
-
const { protocol, baseUrl } = parseAlias(alias);
|
|
137
|
-
const displayName = name || host;
|
|
138
|
-
const allFiles = getAllFiles(outDir);
|
|
139
|
-
const totalFiles = allFiles.length;
|
|
140
|
-
console.log(chalk.bold(`
|
|
141
|
-
\u{1F680} FTP \u90E8\u7F72\u5F00\u59CB`));
|
|
142
|
-
console.log();
|
|
143
|
-
console.log(`Host: ${chalk.blue(host)}`);
|
|
144
|
-
console.log(`User: ${chalk.blue(user)}`);
|
|
145
|
-
console.log(`Source: ${chalk.blue(outDir)}`);
|
|
146
|
-
console.log(`Target: ${chalk.blue(uploadPath)}`);
|
|
147
|
-
console.log(`Files: ${chalk.blue(totalFiles)}`);
|
|
148
|
-
console.log();
|
|
149
|
-
const client = new Client();
|
|
150
|
-
let uploadSpinner;
|
|
151
|
-
const startTime = Date.now();
|
|
152
|
-
try {
|
|
153
|
-
uploadSpinner = ora(`\u8FDE\u63A5\u5230 ${displayName} \u4E2D...`).start();
|
|
154
|
-
await connectWithRetry(client, { host, port, user, password }, maxRetries, retryDelay);
|
|
155
|
-
uploadSpinner.color = "green";
|
|
156
|
-
uploadSpinner.text = "\u8FDE\u63A5\u6210\u529F";
|
|
157
|
-
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
158
|
-
const fileList = await client.list(uploadPath);
|
|
159
|
-
uploadSpinner.succeed("\u8FDE\u63A5\u6210\u529F!");
|
|
160
|
-
const startDir = await client.pwd();
|
|
161
|
-
if (fileList.length) {
|
|
162
|
-
if (singleBack) {
|
|
163
|
-
await createSingleBackup(client, uploadPath, protocol, baseUrl, singleBackFiles, showBackFile);
|
|
164
|
-
} else {
|
|
165
|
-
const isBackFiles = await select({
|
|
166
|
-
message: `\u662F\u5426\u5907\u4EFD ${displayName} \u7684\u8FDC\u7A0B\u6587\u4EF6`,
|
|
167
|
-
choices: ["\u5426", "\u662F"],
|
|
168
|
-
default: "\u5426"
|
|
169
|
-
});
|
|
170
|
-
if (isBackFiles === "\u662F") {
|
|
171
|
-
await createBackupFile(client, uploadPath, protocol, baseUrl, showBackFile);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
const progressSpinner = ora("\u51C6\u5907\u4E0A\u4F20...").start();
|
|
176
|
-
let uploadedCount = 0;
|
|
177
|
-
const groups = {};
|
|
178
|
-
for (const file of allFiles) {
|
|
179
|
-
const dir = path.dirname(file);
|
|
180
|
-
if (!groups[dir]) groups[dir] = [];
|
|
181
|
-
groups[dir].push(path.basename(file));
|
|
182
|
-
}
|
|
183
|
-
for (const relDir of Object.keys(groups)) {
|
|
184
|
-
await client.cd(startDir);
|
|
185
|
-
const remoteDir = normalizePath(path.join(uploadPath, relDir));
|
|
186
|
-
await client.ensureDir(remoteDir);
|
|
187
|
-
for (const fileName of groups[relDir]) {
|
|
188
|
-
const currentFile = path.join(relDir, fileName);
|
|
189
|
-
const displayPath = normalizePath(currentFile);
|
|
190
|
-
progressSpinner.text = `\u6B63\u5728\u4E0A\u4F20: ${chalk.dim(displayPath)}
|
|
191
|
-
${formatProgressBar(uploadedCount, totalFiles)}`;
|
|
192
|
-
const localFile = path.join(outDir, relDir, fileName);
|
|
193
|
-
await client.uploadFrom(localFile, fileName);
|
|
194
|
-
uploadedCount++;
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
progressSpinner.succeed("\u6240\u6709\u6587\u4EF6\u4E0A\u4F20\u5B8C\u6210!");
|
|
198
|
-
console.log(chalk.green(formatProgressBar(totalFiles, totalFiles)));
|
|
199
|
-
process.stdout.write("\x1B[2J\x1B[0f");
|
|
200
|
-
console.log(chalk.gray("\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"));
|
|
201
|
-
const duration = ((Date.now() - startTime) / 1e3).toFixed(2);
|
|
202
|
-
console.log(`\u{1F389} \u90E8\u7F72\u6210\u529F!`);
|
|
203
|
-
console.log();
|
|
204
|
-
console.log(`\u7EDF\u8BA1:`);
|
|
205
|
-
console.log(` \u2714 \u6210\u529F: ${chalk.green(totalFiles)}`);
|
|
206
|
-
console.log(` \u23F1 \u8017\u65F6: ${chalk.green(duration + "s")}`);
|
|
207
|
-
console.log();
|
|
208
|
-
if (baseUrl) {
|
|
209
|
-
console.log(`\u8BBF\u95EE\u5730\u5740: ${chalk.green(buildUrl(protocol, baseUrl, uploadPath))}`);
|
|
210
|
-
console.log();
|
|
211
|
-
}
|
|
212
|
-
} catch (error) {
|
|
213
|
-
if (uploadSpinner) {
|
|
214
|
-
uploadSpinner.fail(`\u274C \u4E0A\u4F20\u5230 ${displayName} \u5931\u8D25`);
|
|
215
|
-
}
|
|
216
|
-
console.error(chalk.red(`\u274C \u4E0A\u4F20\u5230 ${displayName} \u5931\u8D25:`), error instanceof Error ? error.message : error);
|
|
217
|
-
throw error;
|
|
218
|
-
} finally {
|
|
219
|
-
client.close();
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
function getAllFiles(dirPath, arrayOfFiles = [], relativePath = "") {
|
|
225
|
-
const files = fs.readdirSync(dirPath);
|
|
226
|
-
files.forEach(function(file) {
|
|
227
|
-
const fullPath = path.join(dirPath, file);
|
|
228
|
-
const relPath = path.join(relativePath, file);
|
|
229
|
-
if (fs.statSync(fullPath).isDirectory()) {
|
|
230
|
-
getAllFiles(fullPath, arrayOfFiles, relPath);
|
|
231
|
-
} else {
|
|
232
|
-
arrayOfFiles.push(relPath);
|
|
233
|
-
}
|
|
234
|
-
});
|
|
235
|
-
return arrayOfFiles;
|
|
236
|
-
}
|
|
237
|
-
function formatProgressBar(current, total, width = 30) {
|
|
238
|
-
const percentage = Math.round(current / total * 100);
|
|
239
|
-
const filled = Math.round(width * current / total);
|
|
240
|
-
const empty = width - filled;
|
|
241
|
-
const bar = "\u2588".repeat(filled) + "\u2591".repeat(empty);
|
|
242
|
-
return `${bar} ${percentage}% (${current}/${total})`;
|
|
243
|
-
}
|
|
244
|
-
function validateFtpConfig(config) {
|
|
245
|
-
return !!(config.host && config.user && config.password);
|
|
246
|
-
}
|
|
247
|
-
function parseAlias(alias = "") {
|
|
248
|
-
const [protocol = "", baseUrl = ""] = alias.split("://");
|
|
249
|
-
return {
|
|
250
|
-
protocol: protocol ? `${protocol}://` : "",
|
|
251
|
-
baseUrl: baseUrl || ""
|
|
252
|
-
};
|
|
253
|
-
}
|
|
254
|
-
function buildUrl(protocol, baseUrl, path2) {
|
|
255
|
-
return protocol + normalizePath(baseUrl + path2);
|
|
256
|
-
}
|
|
257
|
-
async function connectWithRetry(client, config, maxRetries, retryDelay) {
|
|
258
|
-
let lastError;
|
|
259
|
-
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
260
|
-
try {
|
|
261
|
-
client.ftp.verbose = false;
|
|
262
|
-
await client.access({
|
|
263
|
-
...config,
|
|
264
|
-
secure: true,
|
|
265
|
-
secureOptions: { rejectUnauthorized: false, timeout: 6e4 }
|
|
266
|
-
});
|
|
267
|
-
return;
|
|
268
|
-
} catch (error) {
|
|
269
|
-
lastError = error instanceof Error ? error : new Error(String(error));
|
|
270
|
-
if (attempt < maxRetries) {
|
|
271
|
-
console.log(chalk.yellow(`\u26A0\uFE0F \u8FDE\u63A5\u5931\u8D25\uFF0C${retryDelay}ms \u540E\u91CD\u8BD5 (${attempt}/${maxRetries})`));
|
|
272
|
-
await new Promise((resolve) => setTimeout(resolve, retryDelay));
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
throw new Error(`\u274C FTP \u8FDE\u63A5\u5931\u8D25\uFF0C\u5DF2\u91CD\u8BD5 ${maxRetries} \u6B21: ${lastError?.message}`);
|
|
277
|
-
}
|
|
278
|
-
function createTempDir(basePath) {
|
|
279
|
-
const tempBaseDir = os.tmpdir();
|
|
280
|
-
const tempPath = path.join(tempBaseDir, "vite-plugin-deploy-ftp", basePath);
|
|
281
|
-
if (!fs.existsSync(tempPath)) {
|
|
282
|
-
fs.mkdirSync(tempPath, { recursive: true });
|
|
283
|
-
}
|
|
284
|
-
return {
|
|
285
|
-
path: tempPath,
|
|
286
|
-
cleanup: () => {
|
|
287
|
-
try {
|
|
288
|
-
if (fs.existsSync(tempPath)) {
|
|
289
|
-
fs.rmSync(tempPath, { recursive: true, force: true });
|
|
290
|
-
}
|
|
291
|
-
} catch (error) {
|
|
292
|
-
console.warn(chalk.yellow(`\u26A0\uFE0F \u6E05\u7406\u4E34\u65F6\u76EE\u5F55\u5931\u8D25: ${tempPath}`), error);
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
};
|
|
296
|
-
}
|
|
297
|
-
async function createBackupFile(client, dir, protocol, baseUrl, showBackFile = false) {
|
|
298
|
-
const backupSpinner = ora(`\u521B\u5EFA\u5907\u4EFD\u6587\u4EF6\u4E2D ${chalk.yellow(`==> ${buildUrl(protocol, baseUrl, dir)}`)}`).start();
|
|
299
|
-
const fileName = `backup_${dayjs().format("YYYYMMDD_HHmmss")}.zip`;
|
|
300
|
-
const tempDir = createTempDir("backup-zip");
|
|
301
|
-
const zipFilePath = path.join(os.tmpdir(), "vite-plugin-deploy-ftp", fileName);
|
|
302
|
-
try {
|
|
303
|
-
const zipDir = path.dirname(zipFilePath);
|
|
304
|
-
if (!fs.existsSync(zipDir)) {
|
|
305
|
-
fs.mkdirSync(zipDir, { recursive: true });
|
|
306
|
-
}
|
|
307
|
-
const remoteFiles = await client.list(dir);
|
|
308
|
-
const filteredFiles = remoteFiles.filter((file) => !file.name.startsWith("backup_") || !file.name.endsWith(".zip"));
|
|
309
|
-
if (showBackFile) {
|
|
310
|
-
console.log(chalk.cyan(`
|
|
311
|
-
\u5F00\u59CB\u5907\u4EFD\u8FDC\u7A0B\u6587\u4EF6\uFF0C\u5171 ${filteredFiles.length} \u4E2A\u6587\u4EF6:`));
|
|
312
|
-
filteredFiles.forEach((file) => {
|
|
313
|
-
console.log(chalk.gray(` - ${file.name} (${file.size} bytes)`));
|
|
314
|
-
});
|
|
315
|
-
}
|
|
316
|
-
for (const file of filteredFiles) {
|
|
317
|
-
if (file.type === 1) {
|
|
318
|
-
await client.downloadTo(path.join(tempDir.path, file.name), normalizePath(`${dir}/${file.name}`));
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
backupSpinner.text = `\u4E0B\u8F7D\u8FDC\u7A0B\u6587\u4EF6\u6210\u529F ${chalk.yellow(`==> ${buildUrl(protocol, baseUrl, dir)}`)}`;
|
|
322
|
-
await createZipFile(tempDir.path, zipFilePath);
|
|
323
|
-
backupSpinner.text = `\u538B\u7F29\u5B8C\u6210, \u51C6\u5907\u4E0A\u4F20 ${chalk.yellow(
|
|
324
|
-
`==> ${buildUrl(protocol, baseUrl, dir + "/" + fileName)}`
|
|
325
|
-
)}`;
|
|
326
|
-
await client.uploadFrom(zipFilePath, normalizePath(`${dir}/${fileName}`));
|
|
327
|
-
const backupUrl = buildUrl(protocol, baseUrl, `${dir}/${fileName}`);
|
|
328
|
-
backupSpinner.succeed("\u5907\u4EFD\u5B8C\u6210");
|
|
329
|
-
console.log(chalk.cyan("\n\u5907\u4EFD\u6587\u4EF6:"));
|
|
330
|
-
console.log(chalk.green(`${backupUrl}`));
|
|
331
|
-
console.log();
|
|
332
|
-
} catch (error) {
|
|
333
|
-
backupSpinner.fail("\u5907\u4EFD\u5931\u8D25");
|
|
334
|
-
throw error;
|
|
335
|
-
} finally {
|
|
336
|
-
tempDir.cleanup();
|
|
337
|
-
try {
|
|
338
|
-
if (fs.existsSync(zipFilePath)) {
|
|
339
|
-
fs.rmSync(zipFilePath);
|
|
340
|
-
}
|
|
341
|
-
} catch (error) {
|
|
342
|
-
console.warn(chalk.yellow("\u26A0\uFE0F \u6E05\u7406zip\u6587\u4EF6\u5931\u8D25"), error);
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
async function createZipFile(sourceDir, outputPath) {
|
|
347
|
-
return new Promise((resolve, reject) => {
|
|
348
|
-
const output = fs.createWriteStream(outputPath);
|
|
349
|
-
const archive = archiver("zip", {
|
|
350
|
-
zlib: { level: 9 }
|
|
351
|
-
});
|
|
352
|
-
output.on("close", () => {
|
|
353
|
-
resolve();
|
|
354
|
-
});
|
|
355
|
-
archive.on("error", (err) => {
|
|
356
|
-
reject(err);
|
|
357
|
-
});
|
|
358
|
-
archive.pipe(output);
|
|
359
|
-
archive.directory(sourceDir, false);
|
|
360
|
-
archive.finalize();
|
|
361
|
-
});
|
|
362
|
-
}
|
|
363
|
-
async function createSingleBackup(client, dir, protocol, baseUrl, singleBackFiles, showBackFile = false) {
|
|
364
|
-
const timestamp = dayjs().format("YYYYMMDD_HHmmss");
|
|
365
|
-
const backupSpinner = ora(`\u5907\u4EFD\u6307\u5B9A\u6587\u4EF6\u4E2D ${chalk.yellow(`==> ${buildUrl(protocol, baseUrl, dir)}`)}`).start();
|
|
366
|
-
const tempDir = createTempDir("single-backup");
|
|
367
|
-
let backupProgressSpinner;
|
|
368
|
-
try {
|
|
369
|
-
const remoteFiles = await client.list(dir);
|
|
370
|
-
const backupTasks = singleBackFiles.map((fileName) => {
|
|
371
|
-
const remoteFile = remoteFiles.find((f) => f.name === fileName);
|
|
372
|
-
return remoteFile ? { fileName, exists: true } : { fileName, exists: false };
|
|
373
|
-
}).filter((task) => task.exists);
|
|
374
|
-
if (backupTasks.length === 0) {
|
|
375
|
-
backupSpinner.warn("\u672A\u627E\u5230\u9700\u8981\u5907\u4EFD\u7684\u6587\u4EF6");
|
|
376
|
-
return;
|
|
377
|
-
}
|
|
378
|
-
backupSpinner.stop();
|
|
379
|
-
if (showBackFile) {
|
|
380
|
-
console.log(chalk.cyan(`
|
|
381
|
-
\u5F00\u59CB\u5355\u6587\u4EF6\u5907\u4EFD\uFF0C\u5171 ${backupTasks.length} \u4E2A\u6587\u4EF6:`));
|
|
382
|
-
backupTasks.forEach((task) => {
|
|
383
|
-
console.log(chalk.gray(` - ${task.fileName}`));
|
|
384
|
-
});
|
|
385
|
-
}
|
|
386
|
-
backupProgressSpinner = ora("\u6B63\u5728\u5907\u4EFD\u6587\u4EF6...").start();
|
|
387
|
-
const concurrencyLimit = 3;
|
|
388
|
-
let backedUpCount = 0;
|
|
389
|
-
const backedUpFiles = [];
|
|
390
|
-
for (let i = 0; i < backupTasks.length; i += concurrencyLimit) {
|
|
391
|
-
const batch = backupTasks.slice(i, i + concurrencyLimit);
|
|
392
|
-
const promises = batch.map(async ({ fileName }) => {
|
|
393
|
-
try {
|
|
394
|
-
const localTempPath = path.join(tempDir.path, fileName);
|
|
395
|
-
const [name, ext] = fileName.split(".");
|
|
396
|
-
const suffix = ext ? `.${ext}` : "";
|
|
397
|
-
const backupFileName = `${name}.${timestamp}${suffix}`;
|
|
398
|
-
const backupRemotePath = normalizePath(`${dir}/${backupFileName}`);
|
|
399
|
-
await client.downloadTo(localTempPath, normalizePath(`${dir}/${fileName}`));
|
|
400
|
-
await client.uploadFrom(localTempPath, backupRemotePath);
|
|
401
|
-
const backupUrl = buildUrl(protocol, baseUrl, backupRemotePath);
|
|
402
|
-
backedUpFiles.push(backupUrl);
|
|
403
|
-
return true;
|
|
404
|
-
} catch (error) {
|
|
405
|
-
console.warn(chalk.yellow(`\u5907\u4EFD\u6587\u4EF6 ${fileName} \u5931\u8D25:`), error instanceof Error ? error.message : error);
|
|
406
|
-
return false;
|
|
407
|
-
}
|
|
408
|
-
});
|
|
409
|
-
const results = await Promise.all(promises);
|
|
410
|
-
backedUpCount += results.filter(Boolean).length;
|
|
411
|
-
}
|
|
412
|
-
if (backedUpCount > 0) {
|
|
413
|
-
backupProgressSpinner.succeed("\u5907\u4EFD\u5B8C\u6210");
|
|
414
|
-
console.log(chalk.cyan("\n\u5907\u4EFD\u6587\u4EF6:"));
|
|
415
|
-
backedUpFiles.forEach((url) => {
|
|
416
|
-
console.log(chalk.green(`\u{1F517} ${url}`));
|
|
417
|
-
});
|
|
418
|
-
console.log();
|
|
419
|
-
} else {
|
|
420
|
-
backupProgressSpinner.fail("\u6240\u6709\u6587\u4EF6\u5907\u4EFD\u5931\u8D25");
|
|
421
|
-
}
|
|
422
|
-
} catch (error) {
|
|
423
|
-
if (backupProgressSpinner) {
|
|
424
|
-
backupProgressSpinner.fail("\u5907\u4EFD\u8FC7\u7A0B\u4E2D\u53D1\u751F\u9519\u8BEF");
|
|
425
|
-
} else {
|
|
426
|
-
backupSpinner.fail("\u5907\u4EFD\u8FC7\u7A0B\u4E2D\u53D1\u751F\u9519\u8BEF");
|
|
427
|
-
}
|
|
428
|
-
throw error;
|
|
429
|
-
} finally {
|
|
430
|
-
tempDir.cleanup();
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
export {
|
|
434
|
-
vitePluginDeployFtp as default
|
|
435
|
-
};
|