vite-plugin-deploy-ftp 3.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.
Files changed (2) hide show
  1. package/dist/index.js +96 -38
  2. 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 archiver from "archiver";
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 !== "." && !context.ensuredDirs.has(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(uploadPath)}`);
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(uploadPath);
339
- const fileList = await preflightClient.list(uploadPath);
341
+ await preflightClient.ensureDir(normalizedUploadPath);
342
+ const fileList = await preflightClient.list();
340
343
  if (fileList.length) {
341
344
  if (singleBack) {
342
- await createSingleBackup(preflightClient, uploadPath, protocol, baseUrl, singleBackFiles, showBackFile);
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(preflightClient, uploadPath, protocol, baseUrl, showBackFile);
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(connectConfig, allFiles, uploadPath, concurrency);
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(` ${chalk.green("\u{1F517}")} \u8BBF\u95EE\u5730\u5740: ${chalk.bold(buildUrl(protocol, baseUrl, uploadPath))}`);
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
- const remoteFiles = await client.list(dir);
597
- const filteredFiles = remoteFiles.filter(
598
- (file) => !(file.name.startsWith("backup_") && file.name.endsWith(".zip"))
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 ${filteredFiles.length} \u4E2A\u6587\u4EF6:`));
603
- filteredFiles.forEach((file) => {
604
- console.log(chalk.gray(` - ${file.name} (${file.size} bytes)`));
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 archive = archiver("zip", {
641
- zlib: { level: 9 }
642
- });
643
- output.on("close", () => {
644
- resolve();
645
- });
646
- archive.on("error", (err) => {
647
- reject(err);
648
- });
649
- archive.pipe(output);
650
- archive.directory(sourceDir, false);
651
- archive.finalize();
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.0.0",
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",