vite-plugin-deploy-ftp 2.0.0 → 3.0.1
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.js +96 -38
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import { checkbox, select } from "@inquirer/prompts";
|
|
3
|
-
import
|
|
4
|
-
import { Client } from "basic-ftp";
|
|
3
|
+
import { Client, FileType } from "basic-ftp";
|
|
5
4
|
import chalk from "chalk";
|
|
6
5
|
import dayjs from "dayjs";
|
|
7
6
|
import fs from "fs";
|
|
@@ -10,6 +9,7 @@ import os from "os";
|
|
|
10
9
|
import path from "path";
|
|
11
10
|
import ora from "ora";
|
|
12
11
|
import { normalizePath } from "vite";
|
|
12
|
+
import yazl from "yazl";
|
|
13
13
|
var formatBytes = (bytes) => {
|
|
14
14
|
if (!Number.isFinite(bytes) || bytes <= 0) return "0 B";
|
|
15
15
|
const units = ["B", "KB", "MB", "GB", "TB"];
|
|
@@ -54,6 +54,12 @@ var normalizeRemotePath = (targetDir, relativeFilePath) => {
|
|
|
54
54
|
if (targetDir.startsWith("/")) return joined.startsWith("/") ? joined : `/${joined}`;
|
|
55
55
|
return joined.replace(/^\/+/, "");
|
|
56
56
|
};
|
|
57
|
+
var normalizeUploadPath = (targetPath) => {
|
|
58
|
+
const normalized = normalizePath(targetPath).replace(/\/{2,}/g, "/").trim();
|
|
59
|
+
if (!normalized || normalized === "." || normalized === "/") return "/";
|
|
60
|
+
const withoutTrailingSlash = normalized.replace(/\/+$/, "");
|
|
61
|
+
return withoutTrailingSlash.startsWith("/") ? withoutTrailingSlash : `/${withoutTrailingSlash}`;
|
|
62
|
+
};
|
|
57
63
|
var sleep = async (ms) => {
|
|
58
64
|
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
59
65
|
};
|
|
@@ -75,6 +81,7 @@ function vitePluginDeployFtp(option) {
|
|
|
75
81
|
const isMultiFtp = "ftps" in safeOption;
|
|
76
82
|
const ftpConfigs = isMultiFtp ? safeOption.ftps || [] : [{ ...safeOption, name: safeOption.name || safeOption.alias || safeOption.host }];
|
|
77
83
|
const defaultFtp = isMultiFtp ? safeOption.defaultFtp : void 0;
|
|
84
|
+
const normalizedUploadPath = normalizeUploadPath(uploadPath);
|
|
78
85
|
let outDir = normalizePath(path.resolve("dist"));
|
|
79
86
|
let upload = false;
|
|
80
87
|
let buildFailed = false;
|
|
@@ -114,9 +121,8 @@ function vitePluginDeployFtp(option) {
|
|
|
114
121
|
try {
|
|
115
122
|
await context.ensureConnected();
|
|
116
123
|
const remoteDir = normalizePath(path.posix.dirname(task.remotePath));
|
|
117
|
-
if (remoteDir && remoteDir !== "."
|
|
124
|
+
if (remoteDir && remoteDir !== ".") {
|
|
118
125
|
await context.client.ensureDir(remoteDir);
|
|
119
|
-
context.ensuredDirs.add(remoteDir);
|
|
120
126
|
}
|
|
121
127
|
await context.client.uploadFrom(task.filePath, path.posix.basename(task.remotePath));
|
|
122
128
|
return {
|
|
@@ -239,7 +245,6 @@ ${chalk.yellow("\u91CD\u8BD5")}: ${retries} ${chalk.yellow("\u5931\u8D25")}: ${
|
|
|
239
245
|
let currentIndex = 0;
|
|
240
246
|
const worker = async () => {
|
|
241
247
|
const client = new Client();
|
|
242
|
-
const ensuredDirs = /* @__PURE__ */ new Set();
|
|
243
248
|
let connected = false;
|
|
244
249
|
const ensureConnected = async () => {
|
|
245
250
|
if (connected) return;
|
|
@@ -248,7 +253,6 @@ ${chalk.yellow("\u91CD\u8BD5")}: ${retries} ${chalk.yellow("\u5931\u8D25")}: ${
|
|
|
248
253
|
};
|
|
249
254
|
const markDisconnected = () => {
|
|
250
255
|
connected = false;
|
|
251
|
-
ensuredDirs.clear();
|
|
252
256
|
};
|
|
253
257
|
try {
|
|
254
258
|
while (true) {
|
|
@@ -259,7 +263,6 @@ ${chalk.yellow("\u91CD\u8BD5")}: ${retries} ${chalk.yellow("\u5931\u8D25")}: ${
|
|
|
259
263
|
updateProgress();
|
|
260
264
|
const result = await uploadFileWithRetry(task, {
|
|
261
265
|
client,
|
|
262
|
-
ensuredDirs,
|
|
263
266
|
ensureConnected,
|
|
264
267
|
markDisconnected,
|
|
265
268
|
silentLogs,
|
|
@@ -325,7 +328,7 @@ ${buildCapsuleBar(1)} 100% (${totalFiles}/${totalFiles}) ${chalk.gray("|")} \u90
|
|
|
325
328
|
console.log(`${chalk.gray("Server:")} ${chalk.green(displayName)}`);
|
|
326
329
|
console.log(`${chalk.gray("Host:")} ${chalk.green(host)}`);
|
|
327
330
|
console.log(`${chalk.gray("Source:")} ${chalk.yellow(outDir)}`);
|
|
328
|
-
console.log(`${chalk.gray("Target:")} ${chalk.yellow(
|
|
331
|
+
console.log(`${chalk.gray("Target:")} ${chalk.yellow(normalizedUploadPath)}`);
|
|
329
332
|
if (alias) console.log(`${chalk.gray("Alias:")} ${chalk.green(alias)}`);
|
|
330
333
|
console.log(`${chalk.gray("Files:")} ${chalk.blue(totalFiles)}
|
|
331
334
|
`);
|
|
@@ -335,11 +338,18 @@ ${buildCapsuleBar(1)} 100% (${totalFiles}/${totalFiles}) ${chalk.gray("|")} \u90
|
|
|
335
338
|
try {
|
|
336
339
|
await connectWithRetry(preflightClient, connectConfig, maxRetries, retryDelay, Boolean(preflightSpinner));
|
|
337
340
|
if (preflightSpinner) preflightSpinner.succeed("\u8FDE\u63A5\u6210\u529F");
|
|
338
|
-
await preflightClient.ensureDir(
|
|
339
|
-
const fileList = await preflightClient.list(
|
|
341
|
+
await preflightClient.ensureDir(normalizedUploadPath);
|
|
342
|
+
const fileList = await preflightClient.list();
|
|
340
343
|
if (fileList.length) {
|
|
341
344
|
if (singleBack) {
|
|
342
|
-
await createSingleBackup(
|
|
345
|
+
await createSingleBackup(
|
|
346
|
+
preflightClient,
|
|
347
|
+
normalizedUploadPath,
|
|
348
|
+
protocol,
|
|
349
|
+
baseUrl,
|
|
350
|
+
singleBackFiles,
|
|
351
|
+
showBackFile
|
|
352
|
+
);
|
|
343
353
|
} else {
|
|
344
354
|
const shouldBackup = await select({
|
|
345
355
|
message: `\u662F\u5426\u5907\u4EFD ${displayName} \u7684\u8FDC\u7A0B\u6587\u4EF6`,
|
|
@@ -347,11 +357,22 @@ ${buildCapsuleBar(1)} 100% (${totalFiles}/${totalFiles}) ${chalk.gray("|")} \u90
|
|
|
347
357
|
default: "\u5426"
|
|
348
358
|
});
|
|
349
359
|
if (shouldBackup === "\u662F") {
|
|
350
|
-
await createBackupFile(
|
|
360
|
+
await createBackupFile(
|
|
361
|
+
preflightClient,
|
|
362
|
+
normalizedUploadPath,
|
|
363
|
+
protocol,
|
|
364
|
+
baseUrl,
|
|
365
|
+
showBackFile
|
|
366
|
+
);
|
|
351
367
|
}
|
|
352
368
|
}
|
|
353
369
|
}
|
|
354
|
-
const results = await uploadFilesInBatches(
|
|
370
|
+
const results = await uploadFilesInBatches(
|
|
371
|
+
connectConfig,
|
|
372
|
+
allFiles,
|
|
373
|
+
normalizedUploadPath,
|
|
374
|
+
concurrency
|
|
375
|
+
);
|
|
355
376
|
const successCount = results.filter((r) => r.success).length;
|
|
356
377
|
const failedCount = results.length - successCount;
|
|
357
378
|
const durationSeconds = (Date.now() - startTime) / 1e3;
|
|
@@ -377,7 +398,9 @@ ${chalk.gray("\u7EDF\u8BA1:")}`);
|
|
|
377
398
|
console.log(` ${chalk.magenta("\u26A1")} \u5E73\u5747\u901F\u5EA6: ${chalk.bold(`${formatBytes(avgSpeed)}/s`)}`);
|
|
378
399
|
console.log(` ${chalk.blue("\u23F1")} \u8017\u65F6: ${chalk.bold(duration)}s`);
|
|
379
400
|
if (baseUrl) {
|
|
380
|
-
console.log(
|
|
401
|
+
console.log(
|
|
402
|
+
` ${chalk.green("\u{1F517}")} \u8BBF\u95EE\u5730\u5740: ${chalk.bold(buildUrl(protocol, baseUrl, normalizedUploadPath))}`
|
|
403
|
+
);
|
|
381
404
|
}
|
|
382
405
|
console.log("");
|
|
383
406
|
if (failedCount > 0) {
|
|
@@ -541,6 +564,43 @@ function parseAlias(alias = "") {
|
|
|
541
564
|
function buildUrl(protocol, baseUrl, targetPath) {
|
|
542
565
|
return protocol + normalizePath(baseUrl + targetPath);
|
|
543
566
|
}
|
|
567
|
+
var backupArchivePattern = /^backup_\d{8}_\d{6}\.zip$/i;
|
|
568
|
+
async function downloadRemoteFilesForBackup(client, remoteDir, localDir, downloadedFiles = []) {
|
|
569
|
+
if (!fs.existsSync(localDir)) {
|
|
570
|
+
fs.mkdirSync(localDir, { recursive: true });
|
|
571
|
+
}
|
|
572
|
+
const remoteEntries = await client.list(remoteDir);
|
|
573
|
+
for (const entry of remoteEntries) {
|
|
574
|
+
const remotePath = normalizePath(`${remoteDir}/${entry.name}`);
|
|
575
|
+
const localPath = path.join(localDir, entry.name);
|
|
576
|
+
if (entry.type === FileType.Directory) {
|
|
577
|
+
await downloadRemoteFilesForBackup(client, remotePath, localPath, downloadedFiles);
|
|
578
|
+
continue;
|
|
579
|
+
}
|
|
580
|
+
if (entry.type === FileType.SymbolicLink) {
|
|
581
|
+
continue;
|
|
582
|
+
}
|
|
583
|
+
if (backupArchivePattern.test(entry.name)) {
|
|
584
|
+
continue;
|
|
585
|
+
}
|
|
586
|
+
if (entry.type === FileType.File) {
|
|
587
|
+
await client.downloadTo(localPath, remotePath);
|
|
588
|
+
downloadedFiles.push({ remotePath, size: entry.size });
|
|
589
|
+
continue;
|
|
590
|
+
}
|
|
591
|
+
try {
|
|
592
|
+
await client.downloadTo(localPath, remotePath);
|
|
593
|
+
downloadedFiles.push({ remotePath, size: entry.size });
|
|
594
|
+
} catch (downloadError) {
|
|
595
|
+
try {
|
|
596
|
+
await downloadRemoteFilesForBackup(client, remotePath, localPath, downloadedFiles);
|
|
597
|
+
} catch {
|
|
598
|
+
throw downloadError;
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
return downloadedFiles;
|
|
603
|
+
}
|
|
544
604
|
async function connectWithRetry(client, config, maxRetries, retryDelay, silentLogs = false) {
|
|
545
605
|
let lastError;
|
|
546
606
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
@@ -593,22 +653,19 @@ async function createBackupFile(client, dir, protocol, baseUrl, showBackFile = f
|
|
|
593
653
|
if (!fs.existsSync(zipDir)) {
|
|
594
654
|
fs.mkdirSync(zipDir, { recursive: true });
|
|
595
655
|
}
|
|
596
|
-
|
|
597
|
-
const
|
|
598
|
-
|
|
599
|
-
|
|
656
|
+
backupSpinner.text = `\u4E0B\u8F7D\u8FDC\u7A0B\u6587\u4EF6\u4E2D ${chalk.yellow(`==> ${buildUrl(protocol, baseUrl, dir)}`)}`;
|
|
657
|
+
const downloadedFiles = await downloadRemoteFilesForBackup(client, dir, tempDir.path);
|
|
658
|
+
if (downloadedFiles.length === 0) {
|
|
659
|
+
backupSpinner.warn("\u672A\u627E\u5230\u53EF\u5907\u4EFD\u7684\u8FDC\u7A0B\u6587\u4EF6");
|
|
660
|
+
return;
|
|
661
|
+
}
|
|
600
662
|
if (showBackFile) {
|
|
601
663
|
console.log(chalk.cyan(`
|
|
602
|
-
\u5F00\u59CB\u5907\u4EFD\u8FDC\u7A0B\u6587\u4EF6\uFF0C\u5171 ${
|
|
603
|
-
|
|
604
|
-
console.log(chalk.gray(` - ${file.
|
|
664
|
+
\u5F00\u59CB\u5907\u4EFD\u8FDC\u7A0B\u6587\u4EF6\uFF0C\u5171 ${downloadedFiles.length} \u4E2A\u6587\u4EF6:`));
|
|
665
|
+
downloadedFiles.forEach((file) => {
|
|
666
|
+
console.log(chalk.gray(` - ${file.remotePath} (${file.size} bytes)`));
|
|
605
667
|
});
|
|
606
668
|
}
|
|
607
|
-
for (const file of filteredFiles) {
|
|
608
|
-
if (file.type === 1) {
|
|
609
|
-
await client.downloadTo(path.join(tempDir.path, file.name), normalizePath(`${dir}/${file.name}`));
|
|
610
|
-
}
|
|
611
|
-
}
|
|
612
669
|
backupSpinner.text = `\u4E0B\u8F7D\u8FDC\u7A0B\u6587\u4EF6\u6210\u529F ${chalk.yellow(`==> ${buildUrl(protocol, baseUrl, dir)}`)}`;
|
|
613
670
|
await createZipFile(tempDir.path, zipFilePath);
|
|
614
671
|
backupSpinner.text = `\u538B\u7F29\u5B8C\u6210, \u51C6\u5907\u4E0A\u4F20 ${chalk.yellow(
|
|
@@ -637,18 +694,19 @@ async function createBackupFile(client, dir, protocol, baseUrl, showBackFile = f
|
|
|
637
694
|
async function createZipFile(sourceDir, outputPath) {
|
|
638
695
|
return new Promise((resolve, reject) => {
|
|
639
696
|
const output = fs.createWriteStream(outputPath);
|
|
640
|
-
const
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
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();
|
|
652
710
|
});
|
|
653
711
|
}
|
|
654
712
|
async function createSingleBackup(client, dir, protocol, baseUrl, singleBackFiles, showBackFile = false) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vite-plugin-deploy-ftp",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.1",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"types": "./dist/index.d.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
"description": "将dist目录下的文件上传到ftp服务器",
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"@types/node": "^22.15.32",
|
|
35
|
+
"@types/yazl": "^3.3.0",
|
|
35
36
|
"tsup": "^8.5.0",
|
|
36
37
|
"typescript": "^5.8.3"
|
|
37
38
|
},
|
|
@@ -40,12 +41,11 @@
|
|
|
40
41
|
},
|
|
41
42
|
"dependencies": {
|
|
42
43
|
"@inquirer/prompts": "^7.5.3",
|
|
43
|
-
"@types/archiver": "^6.0.3",
|
|
44
|
-
"archiver": "^7.0.1",
|
|
45
44
|
"basic-ftp": "^5.0.5",
|
|
46
45
|
"chalk": "^5.4.1",
|
|
47
46
|
"dayjs": "^1.11.13",
|
|
48
|
-
"ora": "^8.2.0"
|
|
47
|
+
"ora": "^8.2.0",
|
|
48
|
+
"yazl": "^3.3.1"
|
|
49
49
|
},
|
|
50
50
|
"scripts": {
|
|
51
51
|
"build": "tsup",
|