sftp-push-sync 1.0.15 → 1.0.16

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.
@@ -48,16 +48,13 @@ const pkg = require("../package.json");
48
48
  // Colors for the State (works on dark + light background)
49
49
  const ADD = pc.green("+"); // Added
50
50
  const CHA = pc.yellow("~"); // Changed
51
- const DEL = pc.red("-"); // Deleted
51
+ const DEL = pc.red("-"); // Deleted
52
52
  const EXC = pc.redBright("-"); // Excluded
53
53
 
54
54
  const hr1 = () => "─".repeat(65); // horizontal line -
55
55
  const hr2 = () => "=".repeat(65); // horizontal line =
56
56
  const tab_a = () => " ".repeat(3); // indentation for formatting the terminal output.
57
57
  const tab_b = () => " ".repeat(6);
58
- const cr_a = () => "\n".repeat(1);
59
- const cr_b = () => "\n".repeat(2);
60
-
61
58
 
62
59
  // ---------------------------------------------------------------------------
63
60
  // CLI arguments
@@ -195,7 +192,7 @@ const CACHE_PATH = path.resolve(syncCacheName);
195
192
 
196
193
  let CACHE = {
197
194
  version: 1,
198
- local: {}, // key: "<TARGET>:<relPath>" -> { size, mtimeMs, hash }
195
+ local: {}, // key: "<TARGET>:<relPath>" -> { size, mtimeMs, hash }
199
196
  remote: {}, // key: "<TARGET>:<relPath>" -> { size, modifyTime, hash }
200
197
  };
201
198
 
@@ -245,11 +242,14 @@ let progressActive = false;
245
242
 
246
243
  function clearProgressLine() {
247
244
  if (!process.stdout.isTTY || !progressActive) return;
248
- const width = process.stdout.columns || 80;
249
- const blank = " ".repeat(width);
250
245
 
251
- // Beide Progress-Zeilen leeren
252
- process.stdout.write("\r" + blank + cr_a() + blank + "\r");
246
+ // Zwei Progress-Zeilen ohne zusätzliche Newlines leeren:
247
+ // Cursor steht nach updateProgress2() auf der ersten Zeile.
248
+ process.stdout.write("\r"); // an Zeilenanfang
249
+ process.stdout.write("\x1b[2K"); // erste Zeile löschen
250
+ process.stdout.write("\x1b[1B"); // eine Zeile nach unten
251
+ process.stdout.write("\x1b[2K"); // zweite Zeile löschen
252
+ process.stdout.write("\x1b[1A"); // wieder nach oben
253
253
 
254
254
  progressActive = false;
255
255
  }
@@ -353,7 +353,7 @@ function updateProgress2(prefix, current, total, rel = "") {
353
353
  if (line2.length > width) line2 = line2.slice(0, width - 1);
354
354
 
355
355
  // zwei Zeilen überschreiben
356
- process.stdout.write("\r" + line1.padEnd(width) + cr_a());
356
+ process.stdout.write("\r" + line1.padEnd(width) + "\n");
357
357
  process.stdout.write(line2.padEnd(width));
358
358
 
359
359
  // Cursor wieder nach oben (auf die Fortschrittszeile)
@@ -384,7 +384,7 @@ async function runTasks(items, workerCount, handler, label = "Tasks") {
384
384
  }
385
385
  done += 1;
386
386
  if (done % 10 === 0 || done === total) {
387
- updateProgress2(`${tab_a()}${label}: `, done, total);
387
+ updateProgress2(`${label}: `, done, total);
388
388
  }
389
389
  }
390
390
  }
@@ -429,7 +429,7 @@ async function walkLocal(root) {
429
429
  const chunk = IS_VERBOSE ? 1 : SCAN_CHUNK;
430
430
  if (scanned === 1 || scanned % chunk === 0) {
431
431
  // totally unknown → total = 0 → no automatic \n
432
- updateProgress2(`${tab_a()}Scan local: `, scanned, 0, rel);
432
+ updateProgress2("Scan local: ", scanned, 0, rel);
433
433
  }
434
434
  }
435
435
  }
@@ -439,8 +439,8 @@ async function walkLocal(root) {
439
439
 
440
440
  if (scanned > 0) {
441
441
  // last line + neat finish
442
- updateProgress2(`${tab_a()}Scan local: `, scanned, 0, "fertig");
443
- process.stdout.write(cr_a());
442
+ updateProgress2("Scan local: ", scanned, 0, "fertig");
443
+ process.stdout.write("\n");
444
444
  progressActive = false;
445
445
  }
446
446
 
@@ -480,17 +480,17 @@ async function walkRemote(sftp, remoteRoot) {
480
480
  scanned += 1;
481
481
  const chunk = IS_VERBOSE ? 1 : SCAN_CHUNK;
482
482
  if (scanned === 1 || scanned % chunk === 0) {
483
- updateProgress2(`${tab_a()}Scan remote: `, scanned, 0, rel);
483
+ updateProgress2("Scan remote: ", scanned, 0, rel);
484
484
  }
485
485
  }
486
486
  }
487
487
  }
488
488
 
489
- await recurse(remoteRoot);
489
+ await recurse(remoteRoot, "");
490
490
 
491
491
  if (scanned > 0) {
492
- updateProgress2(`${tab_a()}Scan remote: `, scanned, 0, "fertig");
493
- process.stdout.write(cr_a());
492
+ updateProgress2("Scan remote: ", scanned, 0, "fertig");
493
+ process.stdout.write("\n");
494
494
  progressActive = false;
495
495
  }
496
496
 
@@ -616,7 +616,8 @@ function describeSftpError(err) {
616
616
  async function main() {
617
617
  const start = Date.now();
618
618
 
619
- log(`${cr_b()}${hr2()}`);
619
+ // Header-Abstand wie gehabt: zwei Leerzeilen davor
620
+ log("\n\n" + hr2());
620
621
  log(
621
622
  pc.bold(
622
623
  `🔐 SFTP Push-Synchronisation: sftp-push-sync v${pkg.version} [logLevel=${LOG_LEVEL}]`
@@ -637,7 +638,7 @@ async function main() {
637
638
  )
638
639
  );
639
640
  }
640
- log(`${hr1()}${cr_a()}`);
641
+ log(hr1());
641
642
 
642
643
  const sftp = new SftpClient();
643
644
  let connected = false;
@@ -647,6 +648,7 @@ async function main() {
647
648
  const toDelete = [];
648
649
 
649
650
  try {
651
+ log("");
650
652
  log(pc.cyan("🔌 Connecting to SFTP server …"));
651
653
  await sftp.connect({
652
654
  host: CONNECTION.host,
@@ -665,7 +667,9 @@ async function main() {
665
667
  process.exit(1);
666
668
  }
667
669
 
668
- log(cr_a() + pc.bold(pc.cyan("📥 Phase 1: Scan local files …")));
670
+ // Phase 1 mit exakt einer Leerzeile davor
671
+ log("");
672
+ log(pc.bold(pc.cyan("📥 Phase 1: Scan local files …")));
669
673
  const local = await walkLocal(CONNECTION.localRoot);
670
674
  log(`${tab_a()}→ ${local.size} local files`);
671
675
 
@@ -678,14 +682,17 @@ async function main() {
678
682
  log("");
679
683
  }
680
684
 
681
- log(cr_a() + pc.bold(pc.cyan("📤 Phase 2: Scan remote files …")));
685
+ // Phase 2 auch mit einer Leerzeile davor
686
+ log("");
687
+ log(pc.bold(pc.cyan("📤 Phase 2: Scan remote files …")));
682
688
  const remote = await walkRemote(sftp, CONNECTION.remoteRoot);
683
- log(`${tab_a()}→ ${remote.size} remote files${cr_a()}`);
689
+ log(`${tab_a()}→ ${remote.size} remote files`);
690
+ log("");
684
691
 
685
692
  const localKeys = new Set(local.keys());
686
693
  const remoteKeys = new Set(remote.keys());
687
694
 
688
- log(cr_a() + pc.bold(pc.cyan("🔎 Phase 3: Compare & decide …")));
695
+ log(pc.bold(pc.cyan("🔎 Phase 3: Compare & decide …")));
689
696
  const totalToCheck = localKeys.size;
690
697
  let checkedCount = 0;
691
698
 
@@ -699,7 +706,7 @@ async function main() {
699
706
  checkedCount % chunk === 0 ||
700
707
  checkedCount === totalToCheck
701
708
  ) {
702
- updateProgress2(" Analyse: ", checkedCount, totalToCheck, rel);
709
+ updateProgress2("Analyse: ", checkedCount, totalToCheck, rel);
703
710
  }
704
711
 
705
712
  const l = local.get(rel);
@@ -781,12 +788,12 @@ async function main() {
781
788
 
782
789
  // Wenn Phase 3 nichts gefunden hat, explizit sagen
783
790
  if (toAdd.length === 0 && toUpdate.length === 0) {
791
+ log("");
784
792
  log(`${tab_a()}No differences found. Everything is up to date.`);
785
793
  }
786
794
 
787
- log(
788
- cr_a() + pc.bold(pc.cyan("🧹 Phase 4: Removing orphaned remote files …"))
789
- );
795
+ log("");
796
+ log(pc.bold(pc.cyan("🧹 Phase 4: Removing orphaned remote files …")));
790
797
  for (const rel of remoteKeys) {
791
798
  if (!localKeys.has(rel)) {
792
799
  const r = remote.get(rel);
@@ -807,7 +814,8 @@ async function main() {
807
814
  // -------------------------------------------------------------------
808
815
 
809
816
  if (!DRY_RUN) {
810
- log(cr_a() + pc.bold(pc.cyan("🚚 Phase 5: Apply changes …")));
817
+ log("");
818
+ log(pc.bold(pc.cyan("🚚 Phase 5: Apply changes …")));
811
819
 
812
820
  // Upload new files
813
821
  await runTasks(
@@ -859,82 +867,14 @@ async function main() {
859
867
  "Deletes"
860
868
  );
861
869
  } else {
870
+ log("");
862
871
  log(
863
872
  pc.yellow(
864
- `${cr_a()}💡 DRY-RUN: Connection tested, no files transferred or deleted.`
873
+ "💡 DRY-RUN: Connection tested, no files transferred or deleted."
865
874
  )
866
875
  );
867
876
  }
868
877
 
869
- // -------------------------------------------------------------------
870
- // Phase 6: optional uploadList / downloadList
871
- // -------------------------------------------------------------------
872
-
873
- if (RUN_UPLOAD_LIST && UPLOAD_LIST.length > 0) {
874
- log(
875
- cr_a() +
876
- pc.bold(pc.cyan("⬆️ Extra Phase: Upload-List (explicit files) …"))
877
- );
878
-
879
- const tasks = UPLOAD_LIST.map((rel) => ({
880
- rel,
881
- localPath: path.join(CONNECTION.localRoot, rel),
882
- remotePath: path.posix.join(CONNECTION.remoteRoot, toPosix(rel)),
883
- }));
884
-
885
- if (DRY_RUN) {
886
- for (const t of tasks) {
887
- log(`${tab_a()}${ADD} would upload (uploadList): ${t.rel}`);
888
- }
889
- } else {
890
- await runTasks(
891
- tasks,
892
- CONNECTION.workers,
893
- async ({ localPath, remotePath, rel }) => {
894
- const remoteDir = path.posix.dirname(remotePath);
895
- try {
896
- await sftp.mkdir(remoteDir, true);
897
- } catch {
898
- // ignore
899
- }
900
- await sftp.put(localPath, remotePath);
901
- log(`${tab_a()}${ADD} uploadList: ${rel}`);
902
- },
903
- "Upload-List"
904
- );
905
- }
906
- }
907
-
908
- if (RUN_DOWNLOAD_LIST && DOWNLOAD_LIST.length > 0) {
909
- log(
910
- cr_a() +
911
- pc.bold(pc.cyan("⬇️ Extra Phase: Download-List (explicit files) …"))
912
- );
913
-
914
- const tasks = DOWNLOAD_LIST.map((rel) => ({
915
- rel,
916
- remotePath: path.posix.join(CONNECTION.remoteRoot, toPosix(rel)),
917
- localPath: path.join(CONNECTION.localRoot, rel),
918
- }));
919
-
920
- if (DRY_RUN) {
921
- for (const t of tasks) {
922
- log(`${tab_a()}${ADD} would download (downloadList): ${t.rel}`);
923
- }
924
- } else {
925
- await runTasks(
926
- tasks,
927
- CONNECTION.workers,
928
- async ({ remotePath, localPath, rel }) => {
929
- await fsp.mkdir(path.dirname(localPath), { recursive: true });
930
- await sftp.fastGet(remotePath, localPath);
931
- log(`${tab_a()}${ADD} downloadList: ${rel}`);
932
- },
933
- "Download-List"
934
- );
935
- }
936
- }
937
-
938
878
  const duration = ((Date.now() - start) / 1000).toFixed(2);
939
879
 
940
880
  // Write cache safely at the end
@@ -942,7 +882,8 @@ async function main() {
942
882
 
943
883
  // Summary
944
884
  log(hr1());
945
- log(cr_a() + pc.bold(pc.cyan("📊 Summary:")));
885
+ log("");
886
+ log(pc.bold(pc.cyan("📊 Summary:")));
946
887
  log(`${tab_a()}Duration: ${pc.green(duration + " s")}`);
947
888
  log(`${tab_a()}${ADD} Added : ${toAdd.length}`);
948
889
  log(`${tab_a()}${CHA} Changed: ${toUpdate.length}`);
@@ -953,7 +894,8 @@ async function main() {
953
894
  );
954
895
  }
955
896
  if (toAdd.length || toUpdate.length || toDelete.length) {
956
- log(`${cr_a()}📄 Changes:`);
897
+ log("");
898
+ log("📄 Changes:");
957
899
  [...toAdd.map((t) => t.rel)]
958
900
  .sort()
959
901
  .forEach((f) => console.log(`${tab_a()}${ADD} ${f}`));
@@ -964,10 +906,12 @@ async function main() {
964
906
  .sort()
965
907
  .forEach((f) => console.log(`${tab_a()}${DEL} ${f}`));
966
908
  } else {
967
- log(`${cr_a()}No changes.`);
909
+ log("");
910
+ log("No changes.");
968
911
  }
969
912
 
970
- log(cr_a() + pc.bold(pc.green("✅ Sync complete.")));
913
+ log("");
914
+ log(pc.bold(pc.green("✅ Sync complete.")));
971
915
  } catch (err) {
972
916
  const hint = describeSftpError(err);
973
917
  elog(pc.red("❌ Synchronisation error:"), err.message || err);
@@ -997,7 +941,10 @@ async function main() {
997
941
  );
998
942
  }
999
943
  }
1000
- log(`${hr2()}${cr_b()}`);
944
+
945
+ // Abschlusslinie + eine Leerzeile dahinter
946
+ log(hr2());
947
+ log("");
1001
948
  }
1002
949
 
1003
950
  main();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sftp-push-sync",
3
- "version": "1.0.15",
3
+ "version": "1.0.16",
4
4
  "description": "SFTP sync tool for Hugo projects (local to remote, with hash cache)",
5
5
  "type": "module",
6
6
  "bin": {