vite-plugin-deploy-ftp 3.1.1 → 3.2.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/.prettierignore +5 -0
- package/.prettierrc.json +12 -0
- package/dist/index.d.ts +46 -5
- package/dist/index.js +747 -292
- package/package.json +11 -2
package/dist/index.js
CHANGED
|
@@ -1,15 +1,242 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import { checkbox, select } from "@inquirer/prompts";
|
|
3
3
|
import { Client, FileType } from "basic-ftp";
|
|
4
|
-
import
|
|
4
|
+
import chalk5 from "chalk";
|
|
5
|
+
import cliProgress from "cli-progress";
|
|
5
6
|
import dayjs from "dayjs";
|
|
6
|
-
import
|
|
7
|
+
import fs2 from "fs";
|
|
7
8
|
import { stat } from "fs/promises";
|
|
9
|
+
import os2 from "os";
|
|
10
|
+
import path2 from "path";
|
|
11
|
+
import ora from "ora";
|
|
12
|
+
import { normalizePath as normalizePath2 } from "vite";
|
|
13
|
+
|
|
14
|
+
// src/utils/file.ts
|
|
15
|
+
import chalk from "chalk";
|
|
16
|
+
import fs from "fs";
|
|
8
17
|
import os from "os";
|
|
9
18
|
import path from "path";
|
|
10
|
-
import ora from "ora";
|
|
11
19
|
import { normalizePath } from "vite";
|
|
12
20
|
import yazl from "yazl";
|
|
21
|
+
function getAllFiles(dirPath, arrayOfFiles = [], relativePath = "") {
|
|
22
|
+
const files = fs.readdirSync(dirPath);
|
|
23
|
+
files.forEach((file) => {
|
|
24
|
+
const fullPath = path.join(dirPath, file);
|
|
25
|
+
const relPath = path.join(relativePath, file);
|
|
26
|
+
if (fs.statSync(fullPath).isDirectory()) {
|
|
27
|
+
getAllFiles(fullPath, arrayOfFiles, relPath);
|
|
28
|
+
} else {
|
|
29
|
+
arrayOfFiles.push(normalizePath(relPath));
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
return arrayOfFiles;
|
|
33
|
+
}
|
|
34
|
+
function createTempDir(basePath) {
|
|
35
|
+
const tempBaseDir = os.tmpdir();
|
|
36
|
+
const tempPath = path.join(tempBaseDir, "vite-plugin-deploy-ftp", basePath);
|
|
37
|
+
if (!fs.existsSync(tempPath)) {
|
|
38
|
+
fs.mkdirSync(tempPath, { recursive: true });
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
path: tempPath,
|
|
42
|
+
cleanup: () => {
|
|
43
|
+
try {
|
|
44
|
+
if (fs.existsSync(tempPath)) {
|
|
45
|
+
fs.rmSync(tempPath, { recursive: true, force: true });
|
|
46
|
+
}
|
|
47
|
+
} catch (error) {
|
|
48
|
+
console.warn(chalk.yellow(`\u26A0 \u6E05\u7406\u4E34\u65F6\u76EE\u5F55\u5931\u8D25: ${tempPath}`), error);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
async function createZipFile(sourceDir, outputPath) {
|
|
54
|
+
return new Promise((resolve, reject) => {
|
|
55
|
+
const output = fs.createWriteStream(outputPath);
|
|
56
|
+
const zipFile = new yazl.ZipFile();
|
|
57
|
+
const handleError = (error) => {
|
|
58
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
59
|
+
};
|
|
60
|
+
output.on("close", resolve);
|
|
61
|
+
output.on("error", handleError);
|
|
62
|
+
zipFile.outputStream.on("error", handleError);
|
|
63
|
+
zipFile.outputStream.pipe(output);
|
|
64
|
+
for (const relativePath of getAllFiles(sourceDir)) {
|
|
65
|
+
const filePath = path.join(sourceDir, relativePath);
|
|
66
|
+
zipFile.addFile(filePath, normalizePath(relativePath));
|
|
67
|
+
}
|
|
68
|
+
zipFile.end();
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// src/utils/ftp.ts
|
|
73
|
+
import chalk2 from "chalk";
|
|
74
|
+
var sleep = async (ms) => {
|
|
75
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
76
|
+
};
|
|
77
|
+
function validateFtpConfig(config) {
|
|
78
|
+
return !!(config.host && config.user && config.password);
|
|
79
|
+
}
|
|
80
|
+
async function connectWithRetry(client, config, maxRetries, retryDelay, silentLogs = false) {
|
|
81
|
+
let lastError;
|
|
82
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
83
|
+
try {
|
|
84
|
+
client.ftp.verbose = false;
|
|
85
|
+
await client.access({
|
|
86
|
+
...config,
|
|
87
|
+
secure: true,
|
|
88
|
+
secureOptions: { rejectUnauthorized: false, timeout: 6e4 }
|
|
89
|
+
});
|
|
90
|
+
return;
|
|
91
|
+
} catch (error) {
|
|
92
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
93
|
+
if (attempt < maxRetries) {
|
|
94
|
+
if (!silentLogs) {
|
|
95
|
+
console.log(chalk2.yellow(`\u26A0 \u8FDE\u63A5\u5931\u8D25\uFF0C${retryDelay}ms \u540E\u91CD\u8BD5 (${attempt}/${maxRetries})`));
|
|
96
|
+
}
|
|
97
|
+
await sleep(retryDelay * attempt);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
throw new Error(`\u274C FTP \u8FDE\u63A5\u5931\u8D25\uFF0C\u5DF2\u91CD\u8BD5 ${maxRetries} \u6B21: ${lastError?.message}`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// src/utils/output.ts
|
|
105
|
+
import chalk3 from "chalk";
|
|
106
|
+
import cliTruncate from "cli-truncate";
|
|
107
|
+
import logSymbols from "log-symbols";
|
|
108
|
+
import stringWidth from "string-width";
|
|
109
|
+
var panelBorderColor = {
|
|
110
|
+
info: "cyan",
|
|
111
|
+
success: "green",
|
|
112
|
+
warning: "yellow",
|
|
113
|
+
danger: "red"
|
|
114
|
+
};
|
|
115
|
+
var getTerminalWidth = () => process.stdout?.columns || 100;
|
|
116
|
+
var getPanelInnerWidth = () => Math.max(46, Math.min(84, getTerminalWidth() - 4));
|
|
117
|
+
var padVisual = (text, width) => `${text}${" ".repeat(Math.max(0, width - stringWidth(text)))}`;
|
|
118
|
+
var fitVisual = (text, width) => {
|
|
119
|
+
if (width <= 0) return "";
|
|
120
|
+
return padVisual(cliTruncate(text, width, { position: "middle" }), width);
|
|
121
|
+
};
|
|
122
|
+
var truncateTerminalText = (text, reservedWidth = 26) => {
|
|
123
|
+
const maxWidth = Math.max(24, Math.min(88, getTerminalWidth() - reservedWidth));
|
|
124
|
+
return cliTruncate(text, maxWidth, { position: "middle" });
|
|
125
|
+
};
|
|
126
|
+
var renderPanel = (title, rows, tone = "info", footer) => {
|
|
127
|
+
const color = chalk3[panelBorderColor[tone]];
|
|
128
|
+
const innerWidth = getPanelInnerWidth();
|
|
129
|
+
const labelWidth = rows.length > 0 ? Math.max(...rows.map((row) => stringWidth(row.label))) : 0;
|
|
130
|
+
const contentLines = [chalk3.bold(cliTruncate(title, innerWidth, { position: "end" }))];
|
|
131
|
+
if (rows.length > 0) {
|
|
132
|
+
contentLines.push("");
|
|
133
|
+
for (const row of rows) {
|
|
134
|
+
const paddedLabel = padVisual(row.label, labelWidth);
|
|
135
|
+
const prefix = `${paddedLabel} `;
|
|
136
|
+
const availableValueWidth = Math.max(8, innerWidth - stringWidth(prefix));
|
|
137
|
+
contentLines.push(`${chalk3.gray(prefix)}${fitVisual(row.value, availableValueWidth)}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (footer) {
|
|
141
|
+
contentLines.push("");
|
|
142
|
+
contentLines.push(chalk3.gray(cliTruncate(footer, innerWidth, { position: "middle" })));
|
|
143
|
+
}
|
|
144
|
+
const top = color(`\u256D${"\u2500".repeat(innerWidth + 2)}\u256E`);
|
|
145
|
+
const bottom = color(`\u2570${"\u2500".repeat(innerWidth + 2)}\u256F`);
|
|
146
|
+
const body = contentLines.map((line) => `${color("\u2502")} ${fitVisual(line, innerWidth)} ${color("\u2502")}`).join("\n");
|
|
147
|
+
return `${top}
|
|
148
|
+
${body}
|
|
149
|
+
${bottom}`;
|
|
150
|
+
};
|
|
151
|
+
var renderInlineStats = (items) => items.filter(Boolean).join(chalk3.gray(" \xB7 "));
|
|
152
|
+
var getPanelDot = (tone = "success") => {
|
|
153
|
+
switch (tone) {
|
|
154
|
+
case "info":
|
|
155
|
+
return chalk3.green("\u25CF");
|
|
156
|
+
case "success":
|
|
157
|
+
return chalk3.green("\u25CF");
|
|
158
|
+
case "warning":
|
|
159
|
+
return chalk3.yellow("\u25CF");
|
|
160
|
+
case "danger":
|
|
161
|
+
return chalk3.red("\u25CF");
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
var getLogSymbol = (tone) => {
|
|
165
|
+
switch (tone) {
|
|
166
|
+
case "success":
|
|
167
|
+
return logSymbols.success;
|
|
168
|
+
case "warning":
|
|
169
|
+
return logSymbols.warning;
|
|
170
|
+
case "danger":
|
|
171
|
+
return logSymbols.error;
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
// src/utils/path.ts
|
|
176
|
+
var normalizeSlash = (value) => value.replace(/\\/g, "/").trim();
|
|
177
|
+
var normalizePathSegments = (...values) => values.filter((value) => Boolean(value)).flatMap((value) => normalizeSlash(value).split("/")).filter(Boolean).join("/");
|
|
178
|
+
var normalizeFtpUploadPath = (targetPath) => {
|
|
179
|
+
const normalized = normalizePathSegments(targetPath);
|
|
180
|
+
return normalized ? `/${normalized}` : "/";
|
|
181
|
+
};
|
|
182
|
+
var normalizeRemotePath = (targetDir, relativeFilePath) => {
|
|
183
|
+
const normalizedTargetDir = normalizeFtpUploadPath(targetDir);
|
|
184
|
+
const normalizedRelativePath = normalizePathSegments(relativeFilePath);
|
|
185
|
+
if (!normalizedRelativePath) return normalizedTargetDir;
|
|
186
|
+
if (normalizedTargetDir === "/") return `/${normalizedRelativePath}`;
|
|
187
|
+
return `${normalizedTargetDir}/${normalizedRelativePath}`;
|
|
188
|
+
};
|
|
189
|
+
var splitUrlLikeBase = (value) => {
|
|
190
|
+
const normalized = normalizeSlash(value);
|
|
191
|
+
const protocolMatch = normalized.match(/^([a-zA-Z][a-zA-Z\d+.-]*:\/\/[^/]+)(.*)$/);
|
|
192
|
+
if (protocolMatch) {
|
|
193
|
+
return {
|
|
194
|
+
prefix: protocolMatch[1],
|
|
195
|
+
path: protocolMatch[2] || ""
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
const protocolRelativeMatch = normalized.match(/^(\/\/[^/]+)(.*)$/);
|
|
199
|
+
if (protocolRelativeMatch) {
|
|
200
|
+
return {
|
|
201
|
+
prefix: protocolRelativeMatch[1],
|
|
202
|
+
path: protocolRelativeMatch[2] || ""
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
if (normalized.startsWith("/")) {
|
|
206
|
+
return {
|
|
207
|
+
prefix: "/",
|
|
208
|
+
path: normalized
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
return {
|
|
212
|
+
prefix: "",
|
|
213
|
+
path: normalized
|
|
214
|
+
};
|
|
215
|
+
};
|
|
216
|
+
var normalizeUrlLikeBase = (base) => {
|
|
217
|
+
const { prefix, path: path3 } = splitUrlLikeBase(base);
|
|
218
|
+
const normalizedPath = normalizePathSegments(path3);
|
|
219
|
+
if (!prefix) return normalizedPath;
|
|
220
|
+
if (!normalizedPath) return prefix;
|
|
221
|
+
if (prefix === "/") return `/${normalizedPath}`;
|
|
222
|
+
return `${prefix}/${normalizedPath}`;
|
|
223
|
+
};
|
|
224
|
+
var normalizeSelectionPath = (value) => normalizePathSegments(value);
|
|
225
|
+
var joinUrlLikePath = (base, targetPath) => {
|
|
226
|
+
const normalizedBase = normalizeUrlLikeBase(base).replace(/\/+$/, "");
|
|
227
|
+
const normalizedTargetPath = normalizePathSegments(targetPath);
|
|
228
|
+
if (!normalizedTargetPath) return normalizedBase;
|
|
229
|
+
if (!normalizedBase) return `/${normalizedTargetPath}`;
|
|
230
|
+
return `${normalizedBase}/${normalizedTargetPath}`;
|
|
231
|
+
};
|
|
232
|
+
var resolveDisplayUrl = (alias, targetPath) => {
|
|
233
|
+
const normalizedTargetPath = normalizeFtpUploadPath(targetPath);
|
|
234
|
+
if (!alias) return normalizedTargetPath;
|
|
235
|
+
return joinUrlLikePath(alias, normalizedTargetPath);
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
// src/utils/progress.ts
|
|
239
|
+
import chalk4 from "chalk";
|
|
13
240
|
var formatBytes = (bytes) => {
|
|
14
241
|
if (!Number.isFinite(bytes) || bytes <= 0) return "0 B";
|
|
15
242
|
const units = ["B", "KB", "MB", "GB", "TB"];
|
|
@@ -30,38 +257,39 @@ var formatDuration = (seconds) => {
|
|
|
30
257
|
if (mins === 0) return `${secs}s`;
|
|
31
258
|
return `${mins}m${String(secs).padStart(2, "0")}s`;
|
|
32
259
|
};
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
260
|
+
|
|
261
|
+
// src/index.ts
|
|
262
|
+
var backupArchivePattern = /^backup_\d{8}_\d{6}\.zip$/i;
|
|
263
|
+
var formatTimingDuration = (durationMs) => {
|
|
264
|
+
if (durationMs < 1e3) return `${durationMs}ms`;
|
|
265
|
+
const seconds = durationMs / 1e3;
|
|
266
|
+
return `${seconds.toFixed(seconds >= 10 ? 1 : 2)}s`;
|
|
39
267
|
};
|
|
40
|
-
var
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
268
|
+
var renderBackupPanel = (summary) => {
|
|
269
|
+
const previewItems = summary.items.slice(0, 2);
|
|
270
|
+
const rows = [
|
|
271
|
+
{ label: "\u7ED3\u679C:", value: chalk5.green(`${summary.items.length} \u4E2A\u5907\u4EFD\u6587\u4EF6`) },
|
|
272
|
+
...previewItems.map((item, index) => ({
|
|
273
|
+
label: `\u6587\u4EF6 ${index + 1}:`,
|
|
274
|
+
value: chalk5.cyan(truncateTerminalText(item, 22))
|
|
275
|
+
}))
|
|
276
|
+
];
|
|
277
|
+
if (summary.items.length > previewItems.length) {
|
|
278
|
+
rows.push({
|
|
279
|
+
label: "\u5176\u4F59:",
|
|
280
|
+
value: chalk5.gray(`\u8FD8\u6709 ${summary.items.length - previewItems.length} \u4E2A\u5907\u4EFD\u9879\u672A\u5C55\u5F00`)
|
|
281
|
+
});
|
|
45
282
|
}
|
|
46
|
-
|
|
47
|
-
const done = pointerIndex > 0 ? chalk.green("\u2588".repeat(pointerIndex)) : "";
|
|
48
|
-
const pointer = chalk.cyanBright("\u25B8");
|
|
49
|
-
const pending = pointerIndex < width - 1 ? chalk.gray("\u2591".repeat(width - pointerIndex - 1)) : "";
|
|
50
|
-
return `${done}${pointer}${pending}`;
|
|
283
|
+
return renderPanel(`${getPanelDot("success")} ${summary.title}`, rows, "success");
|
|
51
284
|
};
|
|
52
|
-
var
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const withoutTrailingSlash = normalized.replace(/\/+$/, "");
|
|
61
|
-
return withoutTrailingSlash.startsWith("/") ? withoutTrailingSlash : `/${withoutTrailingSlash}`;
|
|
62
|
-
};
|
|
63
|
-
var sleep = async (ms) => {
|
|
64
|
-
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
285
|
+
var renderDebugPanel = (entries) => {
|
|
286
|
+
const rows = entries.map((entry) => ({
|
|
287
|
+
label: `${entry.label}:`,
|
|
288
|
+
value: chalk5.cyan(
|
|
289
|
+
entry.detail ? `${formatTimingDuration(entry.durationMs)} \xB7 ${truncateTerminalText(entry.detail, 24)}` : formatTimingDuration(entry.durationMs)
|
|
290
|
+
)
|
|
291
|
+
}));
|
|
292
|
+
return renderPanel(`${getPanelDot("success")} \u8C03\u8BD5\u8017\u65F6`, rows, "info");
|
|
65
293
|
};
|
|
66
294
|
function vitePluginDeployFtp(option) {
|
|
67
295
|
const safeOption = option || {};
|
|
@@ -71,6 +299,7 @@ function vitePluginDeployFtp(option) {
|
|
|
71
299
|
singleBack = false,
|
|
72
300
|
singleBackFiles = ["index.html"],
|
|
73
301
|
showBackFile = false,
|
|
302
|
+
debug = false,
|
|
74
303
|
maxRetries = 3,
|
|
75
304
|
retryDelay = 1e3,
|
|
76
305
|
autoUpload = false,
|
|
@@ -81,8 +310,8 @@ function vitePluginDeployFtp(option) {
|
|
|
81
310
|
const isMultiFtp = "ftps" in safeOption;
|
|
82
311
|
const ftpConfigs = isMultiFtp ? safeOption.ftps || [] : [{ ...safeOption, name: safeOption.name || safeOption.alias || safeOption.host }];
|
|
83
312
|
const defaultFtp = isMultiFtp ? safeOption.defaultFtp : void 0;
|
|
84
|
-
const normalizedUploadPath =
|
|
85
|
-
let outDir =
|
|
313
|
+
const normalizedUploadPath = normalizeFtpUploadPath(uploadPath);
|
|
314
|
+
let outDir = normalizePath2(path2.resolve("dist"));
|
|
86
315
|
let upload = false;
|
|
87
316
|
let buildFailed = false;
|
|
88
317
|
let resolvedConfig = null;
|
|
@@ -120,11 +349,15 @@ function vitePluginDeployFtp(option) {
|
|
|
120
349
|
for (let attempt = 1; attempt <= context.maxRetries; attempt++) {
|
|
121
350
|
try {
|
|
122
351
|
await context.ensureConnected();
|
|
123
|
-
const remoteDir =
|
|
352
|
+
const remoteDir = normalizePath2(path2.posix.dirname(task.remotePath));
|
|
124
353
|
if (remoteDir && remoteDir !== ".") {
|
|
125
|
-
await context.
|
|
354
|
+
await context.ensureRemoteDir(remoteDir);
|
|
355
|
+
}
|
|
356
|
+
const uploadStartedAt = Date.now();
|
|
357
|
+
await context.client.uploadFrom(task.filePath, path2.posix.basename(task.remotePath));
|
|
358
|
+
if (context.debugMetrics) {
|
|
359
|
+
context.debugMetrics.uploadMs += Date.now() - uploadStartedAt;
|
|
126
360
|
}
|
|
127
|
-
await context.client.uploadFrom(task.filePath, path.posix.basename(task.remotePath));
|
|
128
361
|
return {
|
|
129
362
|
success: true,
|
|
130
363
|
file: task.filePath,
|
|
@@ -141,7 +374,7 @@ function vitePluginDeployFtp(option) {
|
|
|
141
374
|
if (attempt === context.maxRetries) {
|
|
142
375
|
if (!context.silentLogs) {
|
|
143
376
|
console.log(
|
|
144
|
-
`${
|
|
377
|
+
`${chalk5.red("\u2717")} ${task.filePath} => ${error instanceof Error ? error.message : String(error)}`
|
|
145
378
|
);
|
|
146
379
|
}
|
|
147
380
|
return {
|
|
@@ -154,9 +387,7 @@ function vitePluginDeployFtp(option) {
|
|
|
154
387
|
};
|
|
155
388
|
}
|
|
156
389
|
if (!context.silentLogs) {
|
|
157
|
-
console.log(
|
|
158
|
-
`${chalk.yellow("\u26A0")} ${task.filePath} \u4E0A\u4F20\u5931\u8D25\uFF0C\u6B63\u5728\u91CD\u8BD5 (${attempt}/${context.maxRetries})...`
|
|
159
|
-
);
|
|
390
|
+
console.log(`${chalk5.yellow("\u26A0")} ${task.filePath} \u4E0A\u4F20\u5931\u8D25\uFF0C\u6B63\u5728\u91CD\u8BD5 (${attempt}/${context.maxRetries})...`);
|
|
160
391
|
}
|
|
161
392
|
await sleep(context.retryDelay * attempt);
|
|
162
393
|
}
|
|
@@ -170,17 +401,21 @@ function vitePluginDeployFtp(option) {
|
|
|
170
401
|
error: new Error("Max retries exceeded")
|
|
171
402
|
};
|
|
172
403
|
};
|
|
173
|
-
const uploadFilesInBatches = async (
|
|
404
|
+
const uploadFilesInBatches = async (context) => {
|
|
405
|
+
const { connectConfig, files, targetDir, windowSize = concurrency, reusableClient } = context;
|
|
174
406
|
const results = [];
|
|
407
|
+
const debugEntries = [];
|
|
175
408
|
const totalFiles = files.length;
|
|
176
409
|
const tasks = [];
|
|
410
|
+
const taskGroups = [];
|
|
177
411
|
let completed = 0;
|
|
178
412
|
let failed = 0;
|
|
179
413
|
let uploadedBytes = 0;
|
|
180
414
|
let retries = 0;
|
|
415
|
+
const taskPrepareStartedAt = Date.now();
|
|
181
416
|
const taskCandidates = await Promise.all(
|
|
182
417
|
files.map(async (relativeFilePath) => {
|
|
183
|
-
const filePath =
|
|
418
|
+
const filePath = normalizePath2(path2.resolve(outDir, relativeFilePath));
|
|
184
419
|
const remotePath = normalizeRemotePath(targetDir, relativeFilePath);
|
|
185
420
|
try {
|
|
186
421
|
const fileStats = await stat(filePath);
|
|
@@ -206,68 +441,240 @@ function vitePluginDeployFtp(option) {
|
|
|
206
441
|
});
|
|
207
442
|
}
|
|
208
443
|
}
|
|
444
|
+
debugEntries.push({
|
|
445
|
+
label: "\u751F\u6210\u4E0A\u4F20\u4EFB\u52A1",
|
|
446
|
+
durationMs: Date.now() - taskPrepareStartedAt,
|
|
447
|
+
detail: `${tasks.length} \u4E2A\u6587\u4EF6`
|
|
448
|
+
});
|
|
449
|
+
const normalizedTargetDir = normalizeFtpUploadPath(targetDir);
|
|
450
|
+
const groupStartedAt = Date.now();
|
|
451
|
+
const groupsByRelativeDir = /* @__PURE__ */ new Map();
|
|
452
|
+
for (const task of tasks) {
|
|
453
|
+
const remoteDir = normalizePath2(path2.posix.dirname(task.remotePath));
|
|
454
|
+
const normalizedRemoteDir = remoteDir && remoteDir !== "." ? remoteDir : normalizedTargetDir;
|
|
455
|
+
const relativeDir = normalizedRemoteDir === normalizedTargetDir ? "" : normalizedRemoteDir.slice(normalizedTargetDir.length).replace(/^\/+/, "");
|
|
456
|
+
const currentTasks = groupsByRelativeDir.get(relativeDir);
|
|
457
|
+
if (currentTasks) {
|
|
458
|
+
currentTasks.push(task);
|
|
459
|
+
} else {
|
|
460
|
+
groupsByRelativeDir.set(relativeDir, [task]);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
for (const [relativeDir, groupedTasks] of groupsByRelativeDir) {
|
|
464
|
+
const remoteDir = relativeDir ? normalizeRemotePath(normalizedTargetDir, relativeDir) : normalizedTargetDir;
|
|
465
|
+
taskGroups.push({ relativeDir, remoteDir, tasks: groupedTasks });
|
|
466
|
+
}
|
|
467
|
+
taskGroups.sort((left, right) => left.remoteDir.localeCompare(right.remoteDir));
|
|
468
|
+
debugEntries.push({
|
|
469
|
+
label: "\u76EE\u5F55\u5206\u7EC4",
|
|
470
|
+
durationMs: Date.now() - groupStartedAt,
|
|
471
|
+
detail: `${taskGroups.length} \u7EC4`
|
|
472
|
+
});
|
|
209
473
|
const totalBytes = tasks.reduce((sum, task) => sum + task.size, 0);
|
|
210
474
|
const startAt = Date.now();
|
|
211
|
-
const safeWindowSize = Math.max(1, Math.min(windowSize,
|
|
212
|
-
const
|
|
475
|
+
const safeWindowSize = Math.max(1, Math.min(windowSize, taskGroups.length || 1));
|
|
476
|
+
const extraWorkerCount = reusableClient ? Math.max(0, safeWindowSize - 1) : safeWindowSize;
|
|
213
477
|
const silentLogs = Boolean(useInteractiveOutput);
|
|
214
|
-
const
|
|
215
|
-
|
|
478
|
+
const progressBar = useInteractiveOutput ? new cliProgress.SingleBar({
|
|
479
|
+
hideCursor: true,
|
|
480
|
+
clearOnComplete: true,
|
|
481
|
+
stopOnComplete: true,
|
|
482
|
+
barsize: 18,
|
|
483
|
+
barCompleteChar: "\u2588",
|
|
484
|
+
barIncompleteChar: "\u2591",
|
|
485
|
+
format: `${chalk5.gray("\u4E0A\u4F20")} ${chalk5.bold("{percentage}%")} ${chalk5.cyan("{bar}")} ${chalk5.gray("\xB7")} ${chalk5.magenta("{speed}/s")} ${chalk5.gray("\xB7")} ${chalk5.gray("{elapsed}")}s`
|
|
486
|
+
}) : null;
|
|
487
|
+
const reportEvery = Math.max(1, Math.ceil(totalFiles / 6));
|
|
216
488
|
let lastReportedCompleted = -1;
|
|
489
|
+
const debugMetrics = {
|
|
490
|
+
connectMs: 0,
|
|
491
|
+
rootDirMs: 0,
|
|
492
|
+
switchDirMs: 0,
|
|
493
|
+
uploadMs: 0
|
|
494
|
+
};
|
|
495
|
+
if (progressBar) {
|
|
496
|
+
progressBar.start(totalFiles, 0, {
|
|
497
|
+
speed: formatBytes(0),
|
|
498
|
+
elapsed: "0"
|
|
499
|
+
});
|
|
500
|
+
}
|
|
217
501
|
const updateProgress = () => {
|
|
218
|
-
const progressRatio = totalFiles > 0 ? completed / totalFiles : 1;
|
|
219
|
-
const percentage = Math.round(progressRatio * 100);
|
|
220
502
|
const elapsedSeconds = (Date.now() - startAt) / 1e3;
|
|
221
503
|
const speed = elapsedSeconds > 0 ? uploadedBytes / elapsedSeconds : 0;
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
504
|
+
if (!progressBar) {
|
|
505
|
+
const progressRatio = totalFiles > 0 ? completed / totalFiles : 1;
|
|
506
|
+
const percentage = Math.round(progressRatio * 100);
|
|
507
|
+
if (completed === 0 && totalFiles > 0) return;
|
|
226
508
|
if (completed === lastReportedCompleted) return;
|
|
227
509
|
if (completed === totalFiles || completed % reportEvery === 0) {
|
|
228
510
|
console.log(
|
|
229
|
-
`${
|
|
511
|
+
`${chalk5.gray("\u4E0A\u4F20\u8FDB\u5EA6")} ${renderInlineStats([
|
|
512
|
+
chalk5.bold(`${completed}/${totalFiles}`),
|
|
513
|
+
`${percentage}%`,
|
|
514
|
+
`${formatBytes(uploadedBytes)}/${formatBytes(totalBytes)}`,
|
|
515
|
+
`${formatBytes(speed)}/s`
|
|
516
|
+
])}`
|
|
230
517
|
);
|
|
231
518
|
lastReportedCompleted = completed;
|
|
232
519
|
}
|
|
233
520
|
return;
|
|
234
521
|
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
`${chalk.cyan("\u6B63\u5728\u4E0A\u4F20:")} ${chalk.white(currentFile)}`,
|
|
240
|
-
`${bar} ${chalk.bold(`${percentage}%`)} ${chalk.gray(`(${completed}/${totalFiles})`)} ${chalk.gray("|")} ${chalk.blue(formatBytes(uploadedBytes))}/${chalk.blue(formatBytes(totalBytes))} ${chalk.gray("|")} ${chalk.magenta(`${formatBytes(speed)}/s`)} ${chalk.gray("|")} \u9884\u8BA1 ${chalk.yellow(formatDuration(etaSeconds))}`
|
|
241
|
-
].join("\n");
|
|
242
|
-
spinner.text += warnLine;
|
|
522
|
+
progressBar.update(completed, {
|
|
523
|
+
speed: chalk5.magenta(formatBytes(speed)),
|
|
524
|
+
elapsed: formatDuration(elapsedSeconds).replace(/s$/, "")
|
|
525
|
+
});
|
|
243
526
|
};
|
|
244
|
-
const refreshTimer =
|
|
245
|
-
let
|
|
527
|
+
const refreshTimer = progressBar ? setInterval(updateProgress, 120) : null;
|
|
528
|
+
let currentGroupIndex = 0;
|
|
246
529
|
const worker = async () => {
|
|
247
530
|
const client = new Client();
|
|
248
531
|
let connected = false;
|
|
532
|
+
let currentRelativeDir = "";
|
|
533
|
+
let rooted = false;
|
|
534
|
+
const ensuredRelativeDirs = /* @__PURE__ */ new Set();
|
|
249
535
|
const ensureConnected = async () => {
|
|
250
536
|
if (connected) return;
|
|
537
|
+
const connectStartedAt = Date.now();
|
|
251
538
|
await connectWithRetry(client, connectConfig, maxRetries, retryDelay, true);
|
|
252
539
|
connected = true;
|
|
540
|
+
rooted = false;
|
|
541
|
+
currentRelativeDir = "";
|
|
542
|
+
debugMetrics.connectMs += Date.now() - connectStartedAt;
|
|
543
|
+
};
|
|
544
|
+
const ensureRootDir = async () => {
|
|
545
|
+
if (rooted) return;
|
|
546
|
+
const rootStartedAt = Date.now();
|
|
547
|
+
await client.ensureDir(normalizedTargetDir);
|
|
548
|
+
rooted = true;
|
|
549
|
+
currentRelativeDir = "";
|
|
550
|
+
debugMetrics.rootDirMs += Date.now() - rootStartedAt;
|
|
551
|
+
};
|
|
552
|
+
const ensureRemoteDir = async (remoteDir) => {
|
|
553
|
+
await ensureRootDir();
|
|
554
|
+
const relativeDir = remoteDir === normalizedTargetDir ? "" : remoteDir.slice(normalizedTargetDir.length).replace(/^\/+/, "");
|
|
555
|
+
if (currentRelativeDir === relativeDir) return;
|
|
556
|
+
if (!relativeDir) {
|
|
557
|
+
const switchStartedAt2 = Date.now();
|
|
558
|
+
await client.cd(normalizedTargetDir);
|
|
559
|
+
currentRelativeDir = "";
|
|
560
|
+
debugMetrics.switchDirMs += Date.now() - switchStartedAt2;
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
const switchStartedAt = Date.now();
|
|
564
|
+
await client.cd(normalizedTargetDir);
|
|
565
|
+
if (!ensuredRelativeDirs.has(relativeDir)) {
|
|
566
|
+
await client.ensureDir(relativeDir);
|
|
567
|
+
ensuredRelativeDirs.add(relativeDir);
|
|
568
|
+
} else {
|
|
569
|
+
await client.cd(relativeDir);
|
|
570
|
+
}
|
|
571
|
+
currentRelativeDir = relativeDir;
|
|
572
|
+
debugMetrics.switchDirMs += Date.now() - switchStartedAt;
|
|
253
573
|
};
|
|
254
574
|
const markDisconnected = () => {
|
|
255
575
|
connected = false;
|
|
576
|
+
rooted = false;
|
|
577
|
+
currentRelativeDir = "";
|
|
578
|
+
ensuredRelativeDirs.clear();
|
|
256
579
|
};
|
|
257
580
|
try {
|
|
258
581
|
while (true) {
|
|
259
|
-
const
|
|
260
|
-
if (
|
|
261
|
-
const
|
|
262
|
-
|
|
582
|
+
const groupIndex = currentGroupIndex++;
|
|
583
|
+
if (groupIndex >= taskGroups.length) return;
|
|
584
|
+
const taskGroup = taskGroups[groupIndex];
|
|
585
|
+
for (const task of taskGroup.tasks) {
|
|
586
|
+
updateProgress();
|
|
587
|
+
const result = await uploadFileWithRetry(task, {
|
|
588
|
+
client,
|
|
589
|
+
ensureConnected,
|
|
590
|
+
ensureRemoteDir,
|
|
591
|
+
markDisconnected,
|
|
592
|
+
silentLogs,
|
|
593
|
+
maxRetries,
|
|
594
|
+
retryDelay,
|
|
595
|
+
debugMetrics
|
|
596
|
+
});
|
|
597
|
+
completed++;
|
|
598
|
+
retries += result.retries;
|
|
599
|
+
if (result.success) {
|
|
600
|
+
uploadedBytes += result.size;
|
|
601
|
+
} else {
|
|
602
|
+
failed++;
|
|
603
|
+
}
|
|
604
|
+
results.push(result);
|
|
605
|
+
updateProgress();
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
} finally {
|
|
609
|
+
client.close();
|
|
610
|
+
}
|
|
611
|
+
};
|
|
612
|
+
const runReusableWorker = async (seed) => {
|
|
613
|
+
const client = seed.client;
|
|
614
|
+
let connected = true;
|
|
615
|
+
let currentRelativeDir = "";
|
|
616
|
+
let rooted = false;
|
|
617
|
+
const ensuredRelativeDirs = /* @__PURE__ */ new Set();
|
|
618
|
+
const ensureConnected = async () => {
|
|
619
|
+
if (connected) return;
|
|
620
|
+
const connectStartedAt = Date.now();
|
|
621
|
+
await connectWithRetry(client, connectConfig, maxRetries, retryDelay, true);
|
|
622
|
+
connected = true;
|
|
623
|
+
rooted = false;
|
|
624
|
+
currentRelativeDir = "";
|
|
625
|
+
debugMetrics.connectMs += Date.now() - connectStartedAt;
|
|
626
|
+
};
|
|
627
|
+
const ensureRootDir = async () => {
|
|
628
|
+
if (rooted) return;
|
|
629
|
+
const rootStartedAt = Date.now();
|
|
630
|
+
await client.ensureDir(normalizedTargetDir);
|
|
631
|
+
rooted = true;
|
|
632
|
+
currentRelativeDir = "";
|
|
633
|
+
debugMetrics.rootDirMs += Date.now() - rootStartedAt;
|
|
634
|
+
};
|
|
635
|
+
const ensureRemoteDir = async (remoteDir) => {
|
|
636
|
+
await ensureRootDir();
|
|
637
|
+
const relativeDir = remoteDir === normalizedTargetDir ? "" : remoteDir.slice(normalizedTargetDir.length).replace(/^\/+/, "");
|
|
638
|
+
if (currentRelativeDir === relativeDir) return;
|
|
639
|
+
if (!relativeDir) {
|
|
640
|
+
const switchStartedAt2 = Date.now();
|
|
641
|
+
await client.cd(normalizedTargetDir);
|
|
642
|
+
currentRelativeDir = "";
|
|
643
|
+
debugMetrics.switchDirMs += Date.now() - switchStartedAt2;
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
646
|
+
const switchStartedAt = Date.now();
|
|
647
|
+
await client.cd(normalizedTargetDir);
|
|
648
|
+
if (!ensuredRelativeDirs.has(relativeDir)) {
|
|
649
|
+
await client.ensureDir(relativeDir);
|
|
650
|
+
ensuredRelativeDirs.add(relativeDir);
|
|
651
|
+
} else {
|
|
652
|
+
await client.cd(relativeDir);
|
|
653
|
+
}
|
|
654
|
+
currentRelativeDir = relativeDir;
|
|
655
|
+
debugMetrics.switchDirMs += Date.now() - switchStartedAt;
|
|
656
|
+
};
|
|
657
|
+
const markDisconnected = () => {
|
|
658
|
+
connected = false;
|
|
659
|
+
rooted = false;
|
|
660
|
+
currentRelativeDir = "";
|
|
661
|
+
ensuredRelativeDirs.clear();
|
|
662
|
+
};
|
|
663
|
+
while (true) {
|
|
664
|
+
const groupIndex = currentGroupIndex++;
|
|
665
|
+
if (groupIndex >= taskGroups.length) return;
|
|
666
|
+
const taskGroup = taskGroups[groupIndex];
|
|
667
|
+
for (const task of taskGroup.tasks) {
|
|
263
668
|
updateProgress();
|
|
264
669
|
const result = await uploadFileWithRetry(task, {
|
|
265
670
|
client,
|
|
266
671
|
ensureConnected,
|
|
672
|
+
ensureRemoteDir,
|
|
267
673
|
markDisconnected,
|
|
268
674
|
silentLogs,
|
|
269
675
|
maxRetries,
|
|
270
|
-
retryDelay
|
|
676
|
+
retryDelay,
|
|
677
|
+
debugMetrics
|
|
271
678
|
});
|
|
272
679
|
completed++;
|
|
273
680
|
retries += result.retries;
|
|
@@ -277,79 +684,149 @@ ${chalk.yellow("\u91CD\u8BD5")}: ${retries} ${chalk.yellow("\u5931\u8D25")}: ${
|
|
|
277
684
|
failed++;
|
|
278
685
|
}
|
|
279
686
|
results.push(result);
|
|
280
|
-
activeFiles.delete(task.remotePath);
|
|
281
687
|
updateProgress();
|
|
282
688
|
}
|
|
283
|
-
} finally {
|
|
284
|
-
client.close();
|
|
285
689
|
}
|
|
286
690
|
};
|
|
287
691
|
updateProgress();
|
|
288
692
|
try {
|
|
289
|
-
|
|
693
|
+
const workers = Array.from({ length: extraWorkerCount }, () => worker());
|
|
694
|
+
if (reusableClient) {
|
|
695
|
+
workers.unshift(runReusableWorker(reusableClient));
|
|
696
|
+
}
|
|
697
|
+
await Promise.all(workers);
|
|
290
698
|
} finally {
|
|
291
699
|
if (refreshTimer) clearInterval(refreshTimer);
|
|
292
700
|
}
|
|
293
|
-
if (
|
|
701
|
+
if (progressBar) {
|
|
294
702
|
const elapsedSeconds = (Date.now() - startAt) / 1e3;
|
|
295
|
-
const successCount = results.filter((item) => item.success).length;
|
|
296
703
|
const speed = elapsedSeconds > 0 ? uploadedBytes / elapsedSeconds : 0;
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
);
|
|
704
|
+
progressBar.update(totalFiles, {
|
|
705
|
+
speed: chalk5.magenta(formatBytes(speed)),
|
|
706
|
+
elapsed: formatDuration(elapsedSeconds).replace(/s$/, "")
|
|
707
|
+
});
|
|
708
|
+
progressBar.stop();
|
|
301
709
|
} else {
|
|
302
|
-
console.log(`${
|
|
710
|
+
console.log(`${getLogSymbol("success")} \u6240\u6709\u6587\u4EF6\u4E0A\u4F20\u5B8C\u6210 (${totalFiles}/${totalFiles})`);
|
|
303
711
|
}
|
|
304
|
-
|
|
712
|
+
debugEntries.push(
|
|
713
|
+
{
|
|
714
|
+
label: "\u8FDE\u63A5\u670D\u52A1\u5668",
|
|
715
|
+
durationMs: debugMetrics.connectMs
|
|
716
|
+
},
|
|
717
|
+
{
|
|
718
|
+
label: "\u51C6\u5907\u6839\u76EE\u5F55",
|
|
719
|
+
durationMs: debugMetrics.rootDirMs
|
|
720
|
+
},
|
|
721
|
+
{
|
|
722
|
+
label: "\u5207\u6362\u5B50\u76EE\u5F55",
|
|
723
|
+
durationMs: debugMetrics.switchDirMs
|
|
724
|
+
},
|
|
725
|
+
{
|
|
726
|
+
label: "\u6587\u4EF6\u4F20\u8F93",
|
|
727
|
+
durationMs: debugMetrics.uploadMs,
|
|
728
|
+
detail: `${tasks.length} \u4E2A\u6587\u4EF6`
|
|
729
|
+
},
|
|
730
|
+
{
|
|
731
|
+
label: "\u4E0A\u4F20\u9636\u6BB5",
|
|
732
|
+
durationMs: Date.now() - startAt
|
|
733
|
+
}
|
|
734
|
+
);
|
|
735
|
+
return { results, debugEntries };
|
|
305
736
|
};
|
|
306
737
|
const deploySingleTarget = async (ftpConfig) => {
|
|
307
738
|
const { host, port = 21, user, password, alias = "", name } = ftpConfig;
|
|
739
|
+
const normalizedAlias = alias ? normalizeUrlLikeBase(alias) : "";
|
|
308
740
|
if (!host || !user || !password) {
|
|
309
|
-
console.error(
|
|
310
|
-
if (!host) console.error(
|
|
311
|
-
if (!user) console.error(
|
|
312
|
-
if (!password) console.error(
|
|
741
|
+
console.error(chalk5.red(`\u274C FTP\u914D\u7F6E "${name || host || "\u672A\u77E5"}" \u7F3A\u5C11\u5FC5\u9700\u53C2\u6570:`));
|
|
742
|
+
if (!host) console.error(chalk5.red(" - \u7F3A\u5C11 host"));
|
|
743
|
+
if (!user) console.error(chalk5.red(" - \u7F3A\u5C11 user"));
|
|
744
|
+
if (!password) console.error(chalk5.red(" - \u7F3A\u5C11 password"));
|
|
313
745
|
return { name: name || host || "unknown", totalFiles: 0, failedCount: 1 };
|
|
314
746
|
}
|
|
747
|
+
const debugEntries = [];
|
|
748
|
+
const collectFilesStartedAt = Date.now();
|
|
315
749
|
const allFiles = getAllFiles(outDir);
|
|
750
|
+
debugEntries.push({
|
|
751
|
+
label: "\u626B\u63CF\u672C\u5730\u6587\u4EF6",
|
|
752
|
+
durationMs: Date.now() - collectFilesStartedAt,
|
|
753
|
+
detail: `${allFiles.length} \u4E2A\u6587\u4EF6`
|
|
754
|
+
});
|
|
316
755
|
const totalFiles = allFiles.length;
|
|
317
|
-
const { protocol, baseUrl } = parseAlias(alias);
|
|
318
756
|
const displayName = name || host;
|
|
319
757
|
const startTime = Date.now();
|
|
320
758
|
if (allFiles.length === 0) {
|
|
321
|
-
console.log(`${
|
|
759
|
+
console.log(`${getLogSymbol("warning")} \u6CA1\u6709\u627E\u5230\u9700\u8981\u4E0A\u4F20\u7684\u6587\u4EF6`);
|
|
322
760
|
return { name: displayName, totalFiles: 0, failedCount: 0 };
|
|
323
761
|
}
|
|
324
762
|
clearScreen();
|
|
325
|
-
console.log(
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
763
|
+
console.log(
|
|
764
|
+
renderPanel(
|
|
765
|
+
`${getPanelDot("success")} \u51C6\u5907\u90E8\u7F72`,
|
|
766
|
+
[
|
|
767
|
+
{
|
|
768
|
+
label: "\u4F4D\u7F6E:",
|
|
769
|
+
value: chalk5.green(`${displayName} \xB7 ${port === 21 ? host : `${host}:${port}`}`)
|
|
770
|
+
},
|
|
771
|
+
{
|
|
772
|
+
label: "\u76EE\u6807:",
|
|
773
|
+
value: chalk5.yellow(
|
|
774
|
+
truncateTerminalText(
|
|
775
|
+
normalizedAlias ? `${normalizedUploadPath} \xB7 ${normalizedAlias}` : normalizedUploadPath,
|
|
776
|
+
18
|
|
777
|
+
)
|
|
778
|
+
)
|
|
779
|
+
},
|
|
780
|
+
{
|
|
781
|
+
label: "\u6587\u4EF6:",
|
|
782
|
+
value: chalk5.blue(`${totalFiles} \u4E2A \xB7 ${truncateTerminalText(outDir, 30)}`)
|
|
783
|
+
}
|
|
784
|
+
],
|
|
785
|
+
"info"
|
|
786
|
+
)
|
|
787
|
+
);
|
|
335
788
|
const connectConfig = { host, port, user, password };
|
|
336
789
|
const preflightClient = new Client();
|
|
337
790
|
const preflightSpinner = useInteractiveOutput ? ora(`\u8FDE\u63A5\u5230 ${displayName}...`).start() : null;
|
|
338
791
|
try {
|
|
792
|
+
const preflightConnectStartedAt = Date.now();
|
|
339
793
|
await connectWithRetry(preflightClient, connectConfig, maxRetries, retryDelay, Boolean(preflightSpinner));
|
|
340
|
-
|
|
794
|
+
debugEntries.push({
|
|
795
|
+
label: "\u9884\u68C0\u8FDE\u63A5",
|
|
796
|
+
durationMs: Date.now() - preflightConnectStartedAt
|
|
797
|
+
});
|
|
798
|
+
if (preflightSpinner) preflightSpinner.stop();
|
|
799
|
+
const ensureTargetStartedAt = Date.now();
|
|
341
800
|
await preflightClient.ensureDir(normalizedUploadPath);
|
|
801
|
+
debugEntries.push({
|
|
802
|
+
label: "\u786E\u8BA4\u76EE\u6807\u76EE\u5F55",
|
|
803
|
+
durationMs: Date.now() - ensureTargetStartedAt,
|
|
804
|
+
detail: normalizedUploadPath
|
|
805
|
+
});
|
|
806
|
+
const listRemoteStartedAt = Date.now();
|
|
342
807
|
const fileList = await preflightClient.list();
|
|
808
|
+
debugEntries.push({
|
|
809
|
+
label: "\u8BFB\u53D6\u8FDC\u7AEF\u6587\u4EF6",
|
|
810
|
+
durationMs: Date.now() - listRemoteStartedAt,
|
|
811
|
+
detail: `${fileList.length} \u4E2A`
|
|
812
|
+
});
|
|
813
|
+
let backupSummary = null;
|
|
343
814
|
if (fileList.length) {
|
|
344
815
|
if (singleBack) {
|
|
345
|
-
|
|
816
|
+
const backupStartedAt = Date.now();
|
|
817
|
+
backupSummary = await createSingleBackup(
|
|
346
818
|
preflightClient,
|
|
347
819
|
normalizedUploadPath,
|
|
348
|
-
|
|
349
|
-
baseUrl,
|
|
820
|
+
normalizedAlias,
|
|
350
821
|
singleBackFiles,
|
|
351
|
-
showBackFile
|
|
822
|
+
showBackFile,
|
|
823
|
+
useInteractiveOutput
|
|
352
824
|
);
|
|
825
|
+
debugEntries.push({
|
|
826
|
+
label: "\u6267\u884C\u5907\u4EFD",
|
|
827
|
+
durationMs: Date.now() - backupStartedAt,
|
|
828
|
+
detail: backupSummary ? `${backupSummary.items.length} \u4E2A\u5907\u4EFD\u6587\u4EF6` : "\u672A\u751F\u6210\u5907\u4EFD"
|
|
829
|
+
});
|
|
353
830
|
} else {
|
|
354
831
|
const shouldBackup = await select({
|
|
355
832
|
message: `\u662F\u5426\u5907\u4EFD ${displayName} \u7684\u8FDC\u7A0B\u6587\u4EF6`,
|
|
@@ -357,72 +834,118 @@ ${buildCapsuleBar(1)} 100% (${totalFiles}/${totalFiles}) ${chalk.gray("|")} \u90
|
|
|
357
834
|
default: "\u5426"
|
|
358
835
|
});
|
|
359
836
|
if (shouldBackup === "\u662F") {
|
|
360
|
-
|
|
837
|
+
const backupStartedAt = Date.now();
|
|
838
|
+
backupSummary = await createBackupFile(
|
|
361
839
|
preflightClient,
|
|
362
840
|
normalizedUploadPath,
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
841
|
+
normalizedAlias,
|
|
842
|
+
showBackFile,
|
|
843
|
+
useInteractiveOutput
|
|
366
844
|
);
|
|
845
|
+
debugEntries.push({
|
|
846
|
+
label: "\u6267\u884C\u5907\u4EFD",
|
|
847
|
+
durationMs: Date.now() - backupStartedAt,
|
|
848
|
+
detail: backupSummary ? `${backupSummary.items.length} \u4E2A\u5907\u4EFD\u6587\u4EF6` : "\u672A\u751F\u6210\u5907\u4EFD"
|
|
849
|
+
});
|
|
850
|
+
} else if (debug) {
|
|
851
|
+
debugEntries.push({
|
|
852
|
+
label: "\u6267\u884C\u5907\u4EFD",
|
|
853
|
+
durationMs: 0,
|
|
854
|
+
detail: "\u624B\u52A8\u8DF3\u8FC7"
|
|
855
|
+
});
|
|
367
856
|
}
|
|
368
857
|
}
|
|
858
|
+
} else if (debug) {
|
|
859
|
+
debugEntries.push({
|
|
860
|
+
label: "\u6267\u884C\u5907\u4EFD",
|
|
861
|
+
durationMs: 0,
|
|
862
|
+
detail: "\u8FDC\u7AEF\u4E3A\u7A7A\uFF0C\u8DF3\u8FC7"
|
|
863
|
+
});
|
|
864
|
+
}
|
|
865
|
+
if (backupSummary) {
|
|
866
|
+
console.log(renderBackupPanel(backupSummary));
|
|
369
867
|
}
|
|
370
|
-
const
|
|
868
|
+
const uploadExecution = await uploadFilesInBatches({
|
|
371
869
|
connectConfig,
|
|
372
|
-
allFiles,
|
|
373
|
-
normalizedUploadPath,
|
|
374
|
-
concurrency
|
|
375
|
-
|
|
870
|
+
files: allFiles,
|
|
871
|
+
targetDir: normalizedUploadPath,
|
|
872
|
+
windowSize: concurrency,
|
|
873
|
+
reusableClient: { client: preflightClient }
|
|
874
|
+
});
|
|
875
|
+
const { results, debugEntries: uploadDebugEntries } = uploadExecution;
|
|
876
|
+
if (debug) {
|
|
877
|
+
debugEntries.push(...uploadDebugEntries);
|
|
878
|
+
}
|
|
376
879
|
const successCount = results.filter((r) => r.success).length;
|
|
377
880
|
const failedCount = results.length - successCount;
|
|
378
881
|
const durationSeconds = (Date.now() - startTime) / 1e3;
|
|
379
|
-
const duration = durationSeconds.toFixed(2);
|
|
380
882
|
const uploadedBytes = results.reduce((sum, result) => result.success ? sum + result.size : sum, 0);
|
|
381
883
|
const retryCount = results.reduce((sum, result) => sum + result.retries, 0);
|
|
382
884
|
const avgSpeed = durationSeconds > 0 ? uploadedBytes / durationSeconds : 0;
|
|
885
|
+
const accessUrl = normalizedAlias ? resolveDisplayUrl(normalizedAlias, normalizedUploadPath) : "";
|
|
383
886
|
clearScreen();
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
887
|
+
const resultRows = [
|
|
888
|
+
{
|
|
889
|
+
label: "\u7ED3\u679C:",
|
|
890
|
+
value: failedCount === 0 ? chalk5.green(`${successCount}/${results.length} \u5168\u90E8\u6210\u529F`) : chalk5.yellow(`\u6210\u529F ${successCount} \u4E2A\uFF0C\u5931\u8D25 ${failedCount} \u4E2A`)
|
|
891
|
+
},
|
|
892
|
+
{
|
|
893
|
+
label: "\u7EDF\u8BA1:",
|
|
894
|
+
value: renderInlineStats([
|
|
895
|
+
`${retryCount} \u6B21\u91CD\u8BD5`,
|
|
896
|
+
formatBytes(uploadedBytes),
|
|
897
|
+
`${formatBytes(avgSpeed)}/s`,
|
|
898
|
+
formatDuration(durationSeconds)
|
|
899
|
+
])
|
|
900
|
+
}
|
|
901
|
+
];
|
|
902
|
+
if (accessUrl) {
|
|
903
|
+
resultRows.push({ label: "\u8BBF\u95EE:", value: chalk5.cyan(truncateTerminalText(accessUrl, 20)) });
|
|
389
904
|
}
|
|
390
|
-
console.log(`
|
|
391
|
-
${chalk.gray("\u7EDF\u8BA1:")}`);
|
|
392
|
-
console.log(` ${chalk.green("\u2714")} \u6210\u529F: ${chalk.bold(successCount)}`);
|
|
393
905
|
if (failedCount > 0) {
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
` ${chalk.green("\u{1F517}")} \u8BBF\u95EE\u5730\u5740: ${chalk.bold(buildUrl(protocol, baseUrl, normalizedUploadPath))}`
|
|
906
|
+
const failedItems = results.filter((result) => !result.success).slice(0, 2);
|
|
907
|
+
resultRows.push(
|
|
908
|
+
...failedItems.map((item, index) => ({
|
|
909
|
+
label: `\u5931\u8D25 ${index + 1}:`,
|
|
910
|
+
value: chalk5.red(
|
|
911
|
+
`${truncateTerminalText(item.name, 26)} \xB7 ${truncateTerminalText(item.error?.message || "unknown error", 22)}`
|
|
912
|
+
)
|
|
913
|
+
}))
|
|
403
914
|
);
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
console.log(chalk.red("\u5931\u8D25\u660E\u7EC6:"));
|
|
410
|
-
for (let i = 0; i < previewCount; i++) {
|
|
411
|
-
const item = failedItems[i];
|
|
412
|
-
const reason = item.error?.message || "unknown error";
|
|
413
|
-
console.log(` ${chalk.red("\u2022")} ${item.name} => ${reason}`);
|
|
414
|
-
}
|
|
415
|
-
if (failedItems.length > previewCount) {
|
|
416
|
-
console.log(chalk.gray(` ... \u8FD8\u6709 ${failedItems.length - previewCount} \u4E2A\u5931\u8D25\u6587\u4EF6`));
|
|
915
|
+
if (failedCount > failedItems.length) {
|
|
916
|
+
resultRows.push({
|
|
917
|
+
label: "\u5176\u4F59:",
|
|
918
|
+
value: chalk5.gray(`\u8FD8\u6709 ${failedCount - failedItems.length} \u4E2A\u5931\u8D25\u9879\u672A\u5C55\u5F00`)
|
|
919
|
+
});
|
|
417
920
|
}
|
|
418
|
-
|
|
921
|
+
}
|
|
922
|
+
console.log(
|
|
923
|
+
renderPanel(
|
|
924
|
+
failedCount === 0 ? `${getPanelDot("success")} \u90E8\u7F72\u5B8C\u6210` : `${getPanelDot("warning")} \u90E8\u7F72\u5B8C\u6210`,
|
|
925
|
+
resultRows,
|
|
926
|
+
failedCount === 0 ? "success" : "warning"
|
|
927
|
+
)
|
|
928
|
+
);
|
|
929
|
+
if (debug) {
|
|
930
|
+
debugEntries.push({
|
|
931
|
+
label: "\u603B\u8017\u65F6",
|
|
932
|
+
durationMs: Date.now() - startTime
|
|
933
|
+
});
|
|
934
|
+
console.log(renderDebugPanel(debugEntries));
|
|
419
935
|
}
|
|
420
936
|
return { name: displayName, totalFiles: results.length, failedCount };
|
|
421
937
|
} catch (error) {
|
|
422
|
-
if (preflightSpinner) preflightSpinner.
|
|
938
|
+
if (preflightSpinner) preflightSpinner.stop();
|
|
423
939
|
console.log(`
|
|
424
|
-
${
|
|
940
|
+
${getLogSymbol("danger")} \u4E0A\u4F20\u8FC7\u7A0B\u4E2D\u53D1\u751F\u9519\u8BEF: ${error}
|
|
425
941
|
`);
|
|
942
|
+
if (debug && debugEntries.length > 0) {
|
|
943
|
+
debugEntries.push({
|
|
944
|
+
label: "\u5931\u8D25\u524D\u8017\u65F6",
|
|
945
|
+
durationMs: Date.now() - startTime
|
|
946
|
+
});
|
|
947
|
+
console.log(renderDebugPanel(debugEntries));
|
|
948
|
+
}
|
|
426
949
|
return {
|
|
427
950
|
name: displayName,
|
|
428
951
|
totalFiles,
|
|
@@ -447,28 +970,28 @@ ${chalk.red("\u274C \u4E0A\u4F20\u8FC7\u7A0B\u4E2D\u53D1\u751F\u9519\u8BEF:")} $
|
|
|
447
970
|
if (defaultFtp) {
|
|
448
971
|
const defaultConfig = ftpConfigs.find((ftp) => ftp.name === defaultFtp);
|
|
449
972
|
if (defaultConfig && validateFtpConfig(defaultConfig)) {
|
|
450
|
-
console.log(
|
|
973
|
+
console.log(chalk5.blue(`\u4F7F\u7528\u9ED8\u8BA4FTP\u914D\u7F6E: ${defaultFtp}`));
|
|
451
974
|
selectedConfigs = [defaultConfig];
|
|
452
975
|
} else if (defaultConfig) {
|
|
453
|
-
console.log(
|
|
976
|
+
console.log(chalk5.yellow(`\u26A0 \u9ED8\u8BA4FTP\u914D\u7F6E "${defaultFtp}" \u7F3A\u5C11\u5FC5\u9700\u53C2\u6570\uFF0C\u5C06\u8FDB\u884C\u624B\u52A8\u9009\u62E9`));
|
|
454
977
|
}
|
|
455
978
|
}
|
|
456
979
|
if (selectedConfigs.length === 0) {
|
|
457
980
|
const validConfigs = ftpConfigs.filter(validateFtpConfig);
|
|
458
981
|
const invalidConfigs = ftpConfigs.filter((config) => !validateFtpConfig(config));
|
|
459
982
|
if (invalidConfigs.length > 0) {
|
|
460
|
-
console.log(
|
|
983
|
+
console.log(chalk5.yellow("\n\u4EE5\u4E0BFTP\u914D\u7F6E\u7F3A\u5C11\u5FC5\u9700\u53C2\u6570\uFF0C\u5DF2\u4ECE\u9009\u62E9\u5217\u8868\u4E2D\u6392\u9664:"));
|
|
461
984
|
invalidConfigs.forEach((config) => {
|
|
462
985
|
const missing = [];
|
|
463
986
|
if (!config.host) missing.push("host");
|
|
464
987
|
if (!config.user) missing.push("user");
|
|
465
988
|
if (!config.password) missing.push("password");
|
|
466
|
-
console.log(
|
|
989
|
+
console.log(chalk5.yellow(` - ${config.name || "\u672A\u547D\u540D"}: \u7F3A\u5C11 ${missing.join(", ")}`));
|
|
467
990
|
});
|
|
468
991
|
console.log();
|
|
469
992
|
}
|
|
470
993
|
if (validConfigs.length === 0) {
|
|
471
|
-
console.error(
|
|
994
|
+
console.error(chalk5.red("\u274C \u6CA1\u6709\u53EF\u7528\u7684\u6709\u6548FTP\u914D\u7F6E"));
|
|
472
995
|
return [];
|
|
473
996
|
}
|
|
474
997
|
selectedConfigs = await checkbox({
|
|
@@ -489,7 +1012,7 @@ ${chalk.red("\u274C \u4E0A\u4F20\u8FC7\u7A0B\u4E2D\u53D1\u751F\u9519\u8BEF:")} $
|
|
|
489
1012
|
if (!singleConfig?.host) missing.push("host");
|
|
490
1013
|
if (!singleConfig?.user) missing.push("user");
|
|
491
1014
|
if (!singleConfig?.password) missing.push("password");
|
|
492
|
-
console.error(
|
|
1015
|
+
console.error(chalk5.red(`\u274C FTP\u914D\u7F6E\u7F3A\u5C11\u5FC5\u9700\u53C2\u6570: ${missing.join(", ")}`));
|
|
493
1016
|
return [];
|
|
494
1017
|
}
|
|
495
1018
|
}
|
|
@@ -512,7 +1035,7 @@ ${chalk.red("\u274C \u4E0A\u4F20\u8FC7\u7A0B\u4E2D\u53D1\u751F\u9519\u8BEF:")} $
|
|
|
512
1035
|
clearScreen();
|
|
513
1036
|
const validationErrors = validateOptions();
|
|
514
1037
|
if (validationErrors.length > 0) {
|
|
515
|
-
console.log(`${
|
|
1038
|
+
console.log(`${chalk5.red("\u2717 \u914D\u7F6E\u9519\u8BEF:")}
|
|
516
1039
|
${validationErrors.map((err) => ` - ${err}`).join("\n")}`);
|
|
517
1040
|
return;
|
|
518
1041
|
}
|
|
@@ -521,7 +1044,7 @@ ${validationErrors.map((err) => ` - ${err}`).join("\n")}`);
|
|
|
521
1044
|
},
|
|
522
1045
|
configResolved(config) {
|
|
523
1046
|
resolvedConfig = config;
|
|
524
|
-
outDir =
|
|
1047
|
+
outDir = normalizePath2(path2.resolve(config.root, config.build.outDir));
|
|
525
1048
|
},
|
|
526
1049
|
closeBundle: {
|
|
527
1050
|
sequential: true,
|
|
@@ -538,41 +1061,14 @@ ${validationErrors.map((err) => ` - ${err}`).join("\n")}`);
|
|
|
538
1061
|
}
|
|
539
1062
|
};
|
|
540
1063
|
}
|
|
541
|
-
function getAllFiles(dirPath, arrayOfFiles = [], relativePath = "") {
|
|
542
|
-
const files = fs.readdirSync(dirPath);
|
|
543
|
-
files.forEach((file) => {
|
|
544
|
-
const fullPath = path.join(dirPath, file);
|
|
545
|
-
const relPath = path.join(relativePath, file);
|
|
546
|
-
if (fs.statSync(fullPath).isDirectory()) {
|
|
547
|
-
getAllFiles(fullPath, arrayOfFiles, relPath);
|
|
548
|
-
} else {
|
|
549
|
-
arrayOfFiles.push(normalizePath(relPath));
|
|
550
|
-
}
|
|
551
|
-
});
|
|
552
|
-
return arrayOfFiles;
|
|
553
|
-
}
|
|
554
|
-
function validateFtpConfig(config) {
|
|
555
|
-
return !!(config.host && config.user && config.password);
|
|
556
|
-
}
|
|
557
|
-
function parseAlias(alias = "") {
|
|
558
|
-
const [protocol = "", baseUrl = ""] = alias.split("://");
|
|
559
|
-
return {
|
|
560
|
-
protocol: protocol ? `${protocol}://` : "",
|
|
561
|
-
baseUrl: baseUrl || ""
|
|
562
|
-
};
|
|
563
|
-
}
|
|
564
|
-
function buildUrl(protocol, baseUrl, targetPath) {
|
|
565
|
-
return protocol + normalizePath(baseUrl + targetPath);
|
|
566
|
-
}
|
|
567
|
-
var backupArchivePattern = /^backup_\d{8}_\d{6}\.zip$/i;
|
|
568
1064
|
async function downloadRemoteFilesForBackup(client, remoteDir, localDir, downloadedFiles = []) {
|
|
569
|
-
if (!
|
|
570
|
-
|
|
1065
|
+
if (!fs2.existsSync(localDir)) {
|
|
1066
|
+
fs2.mkdirSync(localDir, { recursive: true });
|
|
571
1067
|
}
|
|
572
1068
|
const remoteEntries = await client.list(remoteDir);
|
|
573
1069
|
for (const entry of remoteEntries) {
|
|
574
|
-
const remotePath =
|
|
575
|
-
const localPath =
|
|
1070
|
+
const remotePath = normalizeRemotePath(remoteDir, entry.name);
|
|
1071
|
+
const localPath = path2.join(localDir, entry.name);
|
|
576
1072
|
if (entry.type === FileType.Directory) {
|
|
577
1073
|
await downloadRemoteFilesForBackup(client, remotePath, localPath, downloadedFiles);
|
|
578
1074
|
continue;
|
|
@@ -601,138 +1097,94 @@ async function downloadRemoteFilesForBackup(client, remoteDir, localDir, downloa
|
|
|
601
1097
|
}
|
|
602
1098
|
return downloadedFiles;
|
|
603
1099
|
}
|
|
604
|
-
async function
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
try {
|
|
608
|
-
client.ftp.verbose = false;
|
|
609
|
-
await client.access({
|
|
610
|
-
...config,
|
|
611
|
-
secure: true,
|
|
612
|
-
secureOptions: { rejectUnauthorized: false, timeout: 6e4 }
|
|
613
|
-
});
|
|
614
|
-
return;
|
|
615
|
-
} catch (error) {
|
|
616
|
-
lastError = error instanceof Error ? error : new Error(String(error));
|
|
617
|
-
if (attempt < maxRetries) {
|
|
618
|
-
if (!silentLogs) {
|
|
619
|
-
console.log(chalk.yellow(`\u26A0 \u8FDE\u63A5\u5931\u8D25\uFF0C${retryDelay}ms \u540E\u91CD\u8BD5 (${attempt}/${maxRetries})`));
|
|
620
|
-
}
|
|
621
|
-
await sleep(retryDelay * attempt);
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
|
-
throw new Error(`\u274C FTP \u8FDE\u63A5\u5931\u8D25\uFF0C\u5DF2\u91CD\u8BD5 ${maxRetries} \u6B21: ${lastError?.message}`);
|
|
626
|
-
}
|
|
627
|
-
function createTempDir(basePath) {
|
|
628
|
-
const tempBaseDir = os.tmpdir();
|
|
629
|
-
const tempPath = path.join(tempBaseDir, "vite-plugin-deploy-ftp", basePath);
|
|
630
|
-
if (!fs.existsSync(tempPath)) {
|
|
631
|
-
fs.mkdirSync(tempPath, { recursive: true });
|
|
632
|
-
}
|
|
633
|
-
return {
|
|
634
|
-
path: tempPath,
|
|
635
|
-
cleanup: () => {
|
|
636
|
-
try {
|
|
637
|
-
if (fs.existsSync(tempPath)) {
|
|
638
|
-
fs.rmSync(tempPath, { recursive: true, force: true });
|
|
639
|
-
}
|
|
640
|
-
} catch (error) {
|
|
641
|
-
console.warn(chalk.yellow(`\u26A0 \u6E05\u7406\u4E34\u65F6\u76EE\u5F55\u5931\u8D25: ${tempPath}`), error);
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
|
-
};
|
|
645
|
-
}
|
|
646
|
-
async function createBackupFile(client, dir, protocol, baseUrl, showBackFile = false) {
|
|
647
|
-
const backupSpinner = ora(`\u521B\u5EFA\u5907\u4EFD\u6587\u4EF6\u4E2D ${chalk.yellow(`==> ${buildUrl(protocol, baseUrl, dir)}`)}`).start();
|
|
1100
|
+
async function createBackupFile(client, dir, alias, showBackFile = false, useSpinner = true) {
|
|
1101
|
+
const targetUrl = resolveDisplayUrl(alias, dir);
|
|
1102
|
+
const backupSpinner = useSpinner ? ora(`\u521B\u5EFA\u5907\u4EFD\u6587\u4EF6\u4E2D ${chalk5.yellow(`==> ${targetUrl}`)}`).start() : null;
|
|
648
1103
|
const fileName = `backup_${dayjs().format("YYYYMMDD_HHmmss")}.zip`;
|
|
649
1104
|
const tempDir = createTempDir("backup-zip");
|
|
650
|
-
const zipFilePath =
|
|
1105
|
+
const zipFilePath = path2.join(os2.tmpdir(), "vite-plugin-deploy-ftp", fileName);
|
|
651
1106
|
try {
|
|
652
|
-
const zipDir =
|
|
653
|
-
if (!
|
|
654
|
-
|
|
1107
|
+
const zipDir = path2.dirname(zipFilePath);
|
|
1108
|
+
if (!fs2.existsSync(zipDir)) {
|
|
1109
|
+
fs2.mkdirSync(zipDir, { recursive: true });
|
|
1110
|
+
}
|
|
1111
|
+
if (backupSpinner) {
|
|
1112
|
+
backupSpinner.text = `\u4E0B\u8F7D\u8FDC\u7A0B\u6587\u4EF6\u4E2D ${chalk5.yellow(`==> ${targetUrl}`)}`;
|
|
655
1113
|
}
|
|
656
|
-
backupSpinner.text = `\u4E0B\u8F7D\u8FDC\u7A0B\u6587\u4EF6\u4E2D ${chalk.yellow(`==> ${buildUrl(protocol, baseUrl, dir)}`)}`;
|
|
657
1114
|
const downloadedFiles = await downloadRemoteFilesForBackup(client, dir, tempDir.path);
|
|
658
1115
|
if (downloadedFiles.length === 0) {
|
|
659
|
-
backupSpinner
|
|
660
|
-
|
|
1116
|
+
if (backupSpinner) {
|
|
1117
|
+
backupSpinner.warn("\u672A\u627E\u5230\u53EF\u5907\u4EFD\u7684\u8FDC\u7A0B\u6587\u4EF6");
|
|
1118
|
+
}
|
|
1119
|
+
return null;
|
|
661
1120
|
}
|
|
662
1121
|
if (showBackFile) {
|
|
663
|
-
console.log(
|
|
1122
|
+
console.log(chalk5.cyan(`
|
|
664
1123
|
\u5F00\u59CB\u5907\u4EFD\u8FDC\u7A0B\u6587\u4EF6\uFF0C\u5171 ${downloadedFiles.length} \u4E2A\u6587\u4EF6:`));
|
|
665
1124
|
downloadedFiles.forEach((file) => {
|
|
666
|
-
console.log(
|
|
1125
|
+
console.log(chalk5.gray(` - ${file.remotePath} (${file.size} bytes)`));
|
|
667
1126
|
});
|
|
668
1127
|
}
|
|
669
|
-
|
|
1128
|
+
if (backupSpinner) {
|
|
1129
|
+
backupSpinner.text = `\u4E0B\u8F7D\u8FDC\u7A0B\u6587\u4EF6\u6210\u529F ${chalk5.yellow(`==> ${targetUrl}`)}`;
|
|
1130
|
+
}
|
|
670
1131
|
await createZipFile(tempDir.path, zipFilePath);
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
1132
|
+
const backupRemotePath = normalizeRemotePath(dir, fileName);
|
|
1133
|
+
if (backupSpinner) {
|
|
1134
|
+
backupSpinner.text = `\u538B\u7F29\u5B8C\u6210, \u51C6\u5907\u4E0A\u4F20 ${chalk5.yellow(`==> ${resolveDisplayUrl(alias, backupRemotePath)}`)}`;
|
|
1135
|
+
}
|
|
1136
|
+
await client.uploadFrom(zipFilePath, backupRemotePath);
|
|
1137
|
+
const backupUrl = resolveDisplayUrl(alias, backupRemotePath);
|
|
1138
|
+
backupSpinner?.stop();
|
|
1139
|
+
return {
|
|
1140
|
+
title: "\u5907\u4EFD\u5B8C\u6210",
|
|
1141
|
+
items: [backupUrl]
|
|
1142
|
+
};
|
|
680
1143
|
} catch (error) {
|
|
681
|
-
backupSpinner
|
|
1144
|
+
if (backupSpinner) {
|
|
1145
|
+
backupSpinner.fail("\u5907\u4EFD\u5931\u8D25");
|
|
1146
|
+
}
|
|
682
1147
|
throw error;
|
|
683
1148
|
} finally {
|
|
684
1149
|
tempDir.cleanup();
|
|
685
1150
|
try {
|
|
686
|
-
if (
|
|
687
|
-
|
|
1151
|
+
if (fs2.existsSync(zipFilePath)) {
|
|
1152
|
+
fs2.rmSync(zipFilePath);
|
|
688
1153
|
}
|
|
689
1154
|
} catch (error) {
|
|
690
|
-
console.warn(
|
|
1155
|
+
console.warn(chalk5.yellow("\u26A0 \u6E05\u7406zip\u6587\u4EF6\u5931\u8D25"), error);
|
|
691
1156
|
}
|
|
692
1157
|
}
|
|
693
1158
|
}
|
|
694
|
-
async function
|
|
695
|
-
return new Promise((resolve, reject) => {
|
|
696
|
-
const output = fs.createWriteStream(outputPath);
|
|
697
|
-
const zipFile = new yazl.ZipFile();
|
|
698
|
-
const handleError = (error) => {
|
|
699
|
-
reject(error instanceof Error ? error : new Error(String(error)));
|
|
700
|
-
};
|
|
701
|
-
output.on("close", resolve);
|
|
702
|
-
output.on("error", handleError);
|
|
703
|
-
zipFile.outputStream.on("error", handleError);
|
|
704
|
-
zipFile.outputStream.pipe(output);
|
|
705
|
-
for (const relativePath of getAllFiles(sourceDir)) {
|
|
706
|
-
const filePath = path.join(sourceDir, relativePath);
|
|
707
|
-
zipFile.addFile(filePath, normalizePath(relativePath));
|
|
708
|
-
}
|
|
709
|
-
zipFile.end();
|
|
710
|
-
});
|
|
711
|
-
}
|
|
712
|
-
async function createSingleBackup(client, dir, protocol, baseUrl, singleBackFiles, showBackFile = false) {
|
|
1159
|
+
async function createSingleBackup(client, dir, alias, singleBackFiles, showBackFile = false, useSpinner = true) {
|
|
713
1160
|
const timestamp = dayjs().format("YYYYMMDD_HHmmss");
|
|
714
|
-
const backupSpinner = ora(`\u5907\u4EFD\u6307\u5B9A\u6587\u4EF6\u4E2D ${
|
|
1161
|
+
const backupSpinner = useSpinner ? ora(`\u5907\u4EFD\u6307\u5B9A\u6587\u4EF6\u4E2D ${chalk5.yellow(`==> ${resolveDisplayUrl(alias, dir)}`)}`).start() : null;
|
|
715
1162
|
const tempDir = createTempDir("single-backup");
|
|
716
1163
|
let backupProgressSpinner;
|
|
717
1164
|
try {
|
|
718
1165
|
const remoteFiles = await client.list(dir);
|
|
719
|
-
const
|
|
720
|
-
|
|
1166
|
+
const normalizedSingleBackFiles = singleBackFiles.map((fileName) => normalizeSelectionPath(fileName)).filter(Boolean);
|
|
1167
|
+
const backupTasks = normalizedSingleBackFiles.map((fileName) => {
|
|
1168
|
+
const remoteFile = remoteFiles.find((file) => file.name === fileName);
|
|
721
1169
|
return remoteFile ? { fileName, exists: true } : { fileName, exists: false };
|
|
722
1170
|
}).filter((task) => task.exists);
|
|
723
1171
|
if (backupTasks.length === 0) {
|
|
724
|
-
backupSpinner
|
|
725
|
-
|
|
1172
|
+
if (backupSpinner) {
|
|
1173
|
+
backupSpinner.warn("\u672A\u627E\u5230\u9700\u8981\u5907\u4EFD\u7684\u6587\u4EF6");
|
|
1174
|
+
}
|
|
1175
|
+
return null;
|
|
726
1176
|
}
|
|
727
|
-
backupSpinner
|
|
1177
|
+
backupSpinner?.stop();
|
|
728
1178
|
if (showBackFile) {
|
|
729
|
-
console.log(
|
|
1179
|
+
console.log(chalk5.cyan(`
|
|
730
1180
|
\u5F00\u59CB\u5355\u6587\u4EF6\u5907\u4EFD\uFF0C\u5171 ${backupTasks.length} \u4E2A\u6587\u4EF6:`));
|
|
731
1181
|
backupTasks.forEach((task) => {
|
|
732
|
-
console.log(
|
|
1182
|
+
console.log(chalk5.gray(` - ${task.fileName}`));
|
|
733
1183
|
});
|
|
734
1184
|
}
|
|
735
|
-
|
|
1185
|
+
if (useSpinner) {
|
|
1186
|
+
backupProgressSpinner = ora("\u6B63\u5728\u5907\u4EFD\u6587\u4EF6...").start();
|
|
1187
|
+
}
|
|
736
1188
|
const concurrencyLimit = 3;
|
|
737
1189
|
let backedUpCount = 0;
|
|
738
1190
|
const backedUpFiles = [];
|
|
@@ -740,18 +1192,19 @@ async function createSingleBackup(client, dir, protocol, baseUrl, singleBackFile
|
|
|
740
1192
|
const batch = backupTasks.slice(i, i + concurrencyLimit);
|
|
741
1193
|
const promises = batch.map(async ({ fileName }) => {
|
|
742
1194
|
try {
|
|
743
|
-
const localTempPath =
|
|
1195
|
+
const localTempPath = path2.join(tempDir.path, fileName);
|
|
744
1196
|
const extIndex = fileName.lastIndexOf(".");
|
|
745
1197
|
const name = extIndex > -1 ? fileName.slice(0, extIndex) : fileName;
|
|
746
1198
|
const ext = extIndex > -1 ? fileName.slice(extIndex) : "";
|
|
747
1199
|
const backupFileName = `${name}.${timestamp}${ext}`;
|
|
748
|
-
const
|
|
749
|
-
|
|
1200
|
+
const sourceRemotePath = normalizeRemotePath(dir, fileName);
|
|
1201
|
+
const backupRemotePath = normalizeRemotePath(dir, backupFileName);
|
|
1202
|
+
await client.downloadTo(localTempPath, sourceRemotePath);
|
|
750
1203
|
await client.uploadFrom(localTempPath, backupRemotePath);
|
|
751
|
-
backedUpFiles.push(
|
|
1204
|
+
backedUpFiles.push(resolveDisplayUrl(alias, backupRemotePath));
|
|
752
1205
|
return true;
|
|
753
1206
|
} catch (error) {
|
|
754
|
-
console.warn(
|
|
1207
|
+
console.warn(chalk5.yellow(`\u5907\u4EFD\u6587\u4EF6 ${fileName} \u5931\u8D25:`), error instanceof Error ? error.message : error);
|
|
755
1208
|
return false;
|
|
756
1209
|
}
|
|
757
1210
|
});
|
|
@@ -759,19 +1212,21 @@ async function createSingleBackup(client, dir, protocol, baseUrl, singleBackFile
|
|
|
759
1212
|
backedUpCount += results.filter(Boolean).length;
|
|
760
1213
|
}
|
|
761
1214
|
if (backedUpCount > 0) {
|
|
762
|
-
backupProgressSpinner
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
}
|
|
767
|
-
console.log();
|
|
1215
|
+
backupProgressSpinner?.stop();
|
|
1216
|
+
return {
|
|
1217
|
+
title: "\u5907\u4EFD\u5B8C\u6210",
|
|
1218
|
+
items: backedUpFiles
|
|
1219
|
+
};
|
|
768
1220
|
} else {
|
|
769
|
-
backupProgressSpinner
|
|
1221
|
+
if (backupProgressSpinner) {
|
|
1222
|
+
backupProgressSpinner.fail("\u6240\u6709\u6587\u4EF6\u5907\u4EFD\u5931\u8D25");
|
|
1223
|
+
}
|
|
1224
|
+
return null;
|
|
770
1225
|
}
|
|
771
1226
|
} catch (error) {
|
|
772
1227
|
if (backupProgressSpinner) {
|
|
773
1228
|
backupProgressSpinner.fail("\u5907\u4EFD\u8FC7\u7A0B\u4E2D\u53D1\u751F\u9519\u8BEF");
|
|
774
|
-
} else {
|
|
1229
|
+
} else if (backupSpinner) {
|
|
775
1230
|
backupSpinner.fail("\u5907\u4EFD\u8FC7\u7A0B\u4E2D\u53D1\u751F\u9519\u8BEF");
|
|
776
1231
|
}
|
|
777
1232
|
throw error;
|