sftp-push-sync 1.0.14 → 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.
- package/bin/sftp-push-sync.mjs +46 -95
- package/package.json +1 -1
package/bin/sftp-push-sync.mjs
CHANGED
|
@@ -48,7 +48,7 @@ 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("-");
|
|
51
|
+
const DEL = pc.red("-"); // Deleted
|
|
52
52
|
const EXC = pc.redBright("-"); // Excluded
|
|
53
53
|
|
|
54
54
|
const hr1 = () => "─".repeat(65); // horizontal line -
|
|
@@ -192,7 +192,7 @@ const CACHE_PATH = path.resolve(syncCacheName);
|
|
|
192
192
|
|
|
193
193
|
let CACHE = {
|
|
194
194
|
version: 1,
|
|
195
|
-
local: {},
|
|
195
|
+
local: {}, // key: "<TARGET>:<relPath>" -> { size, mtimeMs, hash }
|
|
196
196
|
remote: {}, // key: "<TARGET>:<relPath>" -> { size, modifyTime, hash }
|
|
197
197
|
};
|
|
198
198
|
|
|
@@ -242,11 +242,14 @@ let progressActive = false;
|
|
|
242
242
|
|
|
243
243
|
function clearProgressLine() {
|
|
244
244
|
if (!process.stdout.isTTY || !progressActive) return;
|
|
245
|
-
const width = process.stdout.columns || 80;
|
|
246
|
-
const blank = " ".repeat(width);
|
|
247
245
|
|
|
248
|
-
//
|
|
249
|
-
|
|
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
|
|
250
253
|
|
|
251
254
|
progressActive = false;
|
|
252
255
|
}
|
|
@@ -381,7 +384,7 @@ async function runTasks(items, workerCount, handler, label = "Tasks") {
|
|
|
381
384
|
}
|
|
382
385
|
done += 1;
|
|
383
386
|
if (done % 10 === 0 || done === total) {
|
|
384
|
-
updateProgress2(`${
|
|
387
|
+
updateProgress2(`${label}: `, done, total);
|
|
385
388
|
}
|
|
386
389
|
}
|
|
387
390
|
}
|
|
@@ -426,7 +429,7 @@ async function walkLocal(root) {
|
|
|
426
429
|
const chunk = IS_VERBOSE ? 1 : SCAN_CHUNK;
|
|
427
430
|
if (scanned === 1 || scanned % chunk === 0) {
|
|
428
431
|
// totally unknown → total = 0 → no automatic \n
|
|
429
|
-
updateProgress2(
|
|
432
|
+
updateProgress2("Scan local: ", scanned, 0, rel);
|
|
430
433
|
}
|
|
431
434
|
}
|
|
432
435
|
}
|
|
@@ -436,7 +439,7 @@ async function walkLocal(root) {
|
|
|
436
439
|
|
|
437
440
|
if (scanned > 0) {
|
|
438
441
|
// last line + neat finish
|
|
439
|
-
updateProgress2(
|
|
442
|
+
updateProgress2("Scan local: ", scanned, 0, "fertig");
|
|
440
443
|
process.stdout.write("\n");
|
|
441
444
|
progressActive = false;
|
|
442
445
|
}
|
|
@@ -477,16 +480,16 @@ async function walkRemote(sftp, remoteRoot) {
|
|
|
477
480
|
scanned += 1;
|
|
478
481
|
const chunk = IS_VERBOSE ? 1 : SCAN_CHUNK;
|
|
479
482
|
if (scanned === 1 || scanned % chunk === 0) {
|
|
480
|
-
updateProgress2(
|
|
483
|
+
updateProgress2("Scan remote: ", scanned, 0, rel);
|
|
481
484
|
}
|
|
482
485
|
}
|
|
483
486
|
}
|
|
484
487
|
}
|
|
485
488
|
|
|
486
|
-
await recurse(remoteRoot);
|
|
489
|
+
await recurse(remoteRoot, "");
|
|
487
490
|
|
|
488
491
|
if (scanned > 0) {
|
|
489
|
-
updateProgress2(
|
|
492
|
+
updateProgress2("Scan remote: ", scanned, 0, "fertig");
|
|
490
493
|
process.stdout.write("\n");
|
|
491
494
|
progressActive = false;
|
|
492
495
|
}
|
|
@@ -613,7 +616,8 @@ function describeSftpError(err) {
|
|
|
613
616
|
async function main() {
|
|
614
617
|
const start = Date.now();
|
|
615
618
|
|
|
616
|
-
|
|
619
|
+
// Header-Abstand wie gehabt: zwei Leerzeilen davor
|
|
620
|
+
log("\n\n" + hr2());
|
|
617
621
|
log(
|
|
618
622
|
pc.bold(
|
|
619
623
|
`🔐 SFTP Push-Synchronisation: sftp-push-sync v${pkg.version} [logLevel=${LOG_LEVEL}]`
|
|
@@ -634,7 +638,7 @@ async function main() {
|
|
|
634
638
|
)
|
|
635
639
|
);
|
|
636
640
|
}
|
|
637
|
-
log(
|
|
641
|
+
log(hr1());
|
|
638
642
|
|
|
639
643
|
const sftp = new SftpClient();
|
|
640
644
|
let connected = false;
|
|
@@ -644,6 +648,7 @@ async function main() {
|
|
|
644
648
|
const toDelete = [];
|
|
645
649
|
|
|
646
650
|
try {
|
|
651
|
+
log("");
|
|
647
652
|
log(pc.cyan("🔌 Connecting to SFTP server …"));
|
|
648
653
|
await sftp.connect({
|
|
649
654
|
host: CONNECTION.host,
|
|
@@ -662,6 +667,8 @@ async function main() {
|
|
|
662
667
|
process.exit(1);
|
|
663
668
|
}
|
|
664
669
|
|
|
670
|
+
// Phase 1 – mit exakt einer Leerzeile davor
|
|
671
|
+
log("");
|
|
665
672
|
log(pc.bold(pc.cyan("📥 Phase 1: Scan local files …")));
|
|
666
673
|
const local = await walkLocal(CONNECTION.localRoot);
|
|
667
674
|
log(`${tab_a()}→ ${local.size} local files`);
|
|
@@ -675,9 +682,12 @@ async function main() {
|
|
|
675
682
|
log("");
|
|
676
683
|
}
|
|
677
684
|
|
|
685
|
+
// Phase 2 – auch mit einer Leerzeile davor
|
|
686
|
+
log("");
|
|
678
687
|
log(pc.bold(pc.cyan("📤 Phase 2: Scan remote files …")));
|
|
679
688
|
const remote = await walkRemote(sftp, CONNECTION.remoteRoot);
|
|
680
|
-
log(`${tab_a()}→ ${remote.size} remote files
|
|
689
|
+
log(`${tab_a()}→ ${remote.size} remote files`);
|
|
690
|
+
log("");
|
|
681
691
|
|
|
682
692
|
const localKeys = new Set(local.keys());
|
|
683
693
|
const remoteKeys = new Set(remote.keys());
|
|
@@ -696,7 +706,7 @@ async function main() {
|
|
|
696
706
|
checkedCount % chunk === 0 ||
|
|
697
707
|
checkedCount === totalToCheck
|
|
698
708
|
) {
|
|
699
|
-
updateProgress2("
|
|
709
|
+
updateProgress2("Analyse: ", checkedCount, totalToCheck, rel);
|
|
700
710
|
}
|
|
701
711
|
|
|
702
712
|
const l = local.get(rel);
|
|
@@ -778,12 +788,12 @@ async function main() {
|
|
|
778
788
|
|
|
779
789
|
// Wenn Phase 3 nichts gefunden hat, explizit sagen
|
|
780
790
|
if (toAdd.length === 0 && toUpdate.length === 0) {
|
|
791
|
+
log("");
|
|
781
792
|
log(`${tab_a()}No differences found. Everything is up to date.`);
|
|
782
793
|
}
|
|
783
794
|
|
|
784
|
-
log(
|
|
785
|
-
|
|
786
|
-
);
|
|
795
|
+
log("");
|
|
796
|
+
log(pc.bold(pc.cyan("🧹 Phase 4: Removing orphaned remote files …")));
|
|
787
797
|
for (const rel of remoteKeys) {
|
|
788
798
|
if (!localKeys.has(rel)) {
|
|
789
799
|
const r = remote.get(rel);
|
|
@@ -804,7 +814,8 @@ async function main() {
|
|
|
804
814
|
// -------------------------------------------------------------------
|
|
805
815
|
|
|
806
816
|
if (!DRY_RUN) {
|
|
807
|
-
log("
|
|
817
|
+
log("");
|
|
818
|
+
log(pc.bold(pc.cyan("🚚 Phase 5: Apply changes …")));
|
|
808
819
|
|
|
809
820
|
// Upload new files
|
|
810
821
|
await runTasks(
|
|
@@ -856,89 +867,23 @@ async function main() {
|
|
|
856
867
|
"Deletes"
|
|
857
868
|
);
|
|
858
869
|
} else {
|
|
870
|
+
log("");
|
|
859
871
|
log(
|
|
860
872
|
pc.yellow(
|
|
861
|
-
"
|
|
873
|
+
"💡 DRY-RUN: Connection tested, no files transferred or deleted."
|
|
862
874
|
)
|
|
863
875
|
);
|
|
864
876
|
}
|
|
865
877
|
|
|
866
|
-
// -------------------------------------------------------------------
|
|
867
|
-
// Phase 6: optional uploadList / downloadList
|
|
868
|
-
// -------------------------------------------------------------------
|
|
869
|
-
|
|
870
|
-
if (RUN_UPLOAD_LIST && UPLOAD_LIST.length > 0) {
|
|
871
|
-
log(
|
|
872
|
-
"\n" +
|
|
873
|
-
pc.bold(pc.cyan("⬆️ Extra Phase: Upload-List (explicit files) …"))
|
|
874
|
-
);
|
|
875
|
-
|
|
876
|
-
const tasks = UPLOAD_LIST.map((rel) => ({
|
|
877
|
-
rel,
|
|
878
|
-
localPath: path.join(CONNECTION.localRoot, rel),
|
|
879
|
-
remotePath: path.posix.join(CONNECTION.remoteRoot, toPosix(rel)),
|
|
880
|
-
}));
|
|
881
|
-
|
|
882
|
-
if (DRY_RUN) {
|
|
883
|
-
for (const t of tasks) {
|
|
884
|
-
log(`${tab_a()}${ADD} would upload (uploadList): ${t.rel}`);
|
|
885
|
-
}
|
|
886
|
-
} else {
|
|
887
|
-
await runTasks(
|
|
888
|
-
tasks,
|
|
889
|
-
CONNECTION.workers,
|
|
890
|
-
async ({ localPath, remotePath, rel }) => {
|
|
891
|
-
const remoteDir = path.posix.dirname(remotePath);
|
|
892
|
-
try {
|
|
893
|
-
await sftp.mkdir(remoteDir, true);
|
|
894
|
-
} catch {
|
|
895
|
-
// ignore
|
|
896
|
-
}
|
|
897
|
-
await sftp.put(localPath, remotePath);
|
|
898
|
-
log(`${tab_a()}${ADD} uploadList: ${rel}`);
|
|
899
|
-
},
|
|
900
|
-
"Upload-List"
|
|
901
|
-
);
|
|
902
|
-
}
|
|
903
|
-
}
|
|
904
|
-
|
|
905
|
-
if (RUN_DOWNLOAD_LIST && DOWNLOAD_LIST.length > 0) {
|
|
906
|
-
log(
|
|
907
|
-
"\n" +
|
|
908
|
-
pc.bold(pc.cyan("⬇️ Extra Phase: Download-List (explicit files) …"))
|
|
909
|
-
);
|
|
910
|
-
|
|
911
|
-
const tasks = DOWNLOAD_LIST.map((rel) => ({
|
|
912
|
-
rel,
|
|
913
|
-
remotePath: path.posix.join(CONNECTION.remoteRoot, toPosix(rel)),
|
|
914
|
-
localPath: path.join(CONNECTION.localRoot, rel),
|
|
915
|
-
}));
|
|
916
|
-
|
|
917
|
-
if (DRY_RUN) {
|
|
918
|
-
for (const t of tasks) {
|
|
919
|
-
log(`${tab_a()}${ADD} would download (downloadList): ${t.rel}`);
|
|
920
|
-
}
|
|
921
|
-
} else {
|
|
922
|
-
await runTasks(
|
|
923
|
-
tasks,
|
|
924
|
-
CONNECTION.workers,
|
|
925
|
-
async ({ remotePath, localPath, rel }) => {
|
|
926
|
-
await fsp.mkdir(path.dirname(localPath), { recursive: true });
|
|
927
|
-
await sftp.fastGet(remotePath, localPath);
|
|
928
|
-
log(`${tab_a()}${ADD} downloadList: ${rel}`);
|
|
929
|
-
},
|
|
930
|
-
"Download-List"
|
|
931
|
-
);
|
|
932
|
-
}
|
|
933
|
-
}
|
|
934
|
-
|
|
935
878
|
const duration = ((Date.now() - start) / 1000).toFixed(2);
|
|
936
879
|
|
|
937
880
|
// Write cache safely at the end
|
|
938
881
|
await saveCache(true);
|
|
939
882
|
|
|
940
883
|
// Summary
|
|
941
|
-
log(
|
|
884
|
+
log(hr1());
|
|
885
|
+
log("");
|
|
886
|
+
log(pc.bold(pc.cyan("📊 Summary:")));
|
|
942
887
|
log(`${tab_a()}Duration: ${pc.green(duration + " s")}`);
|
|
943
888
|
log(`${tab_a()}${ADD} Added : ${toAdd.length}`);
|
|
944
889
|
log(`${tab_a()}${CHA} Changed: ${toUpdate.length}`);
|
|
@@ -949,7 +894,8 @@ async function main() {
|
|
|
949
894
|
);
|
|
950
895
|
}
|
|
951
896
|
if (toAdd.length || toUpdate.length || toDelete.length) {
|
|
952
|
-
log("
|
|
897
|
+
log("");
|
|
898
|
+
log("📄 Changes:");
|
|
953
899
|
[...toAdd.map((t) => t.rel)]
|
|
954
900
|
.sort()
|
|
955
901
|
.forEach((f) => console.log(`${tab_a()}${ADD} ${f}`));
|
|
@@ -960,10 +906,12 @@ async function main() {
|
|
|
960
906
|
.sort()
|
|
961
907
|
.forEach((f) => console.log(`${tab_a()}${DEL} ${f}`));
|
|
962
908
|
} else {
|
|
963
|
-
log("
|
|
909
|
+
log("");
|
|
910
|
+
log("No changes.");
|
|
964
911
|
}
|
|
965
912
|
|
|
966
|
-
log("
|
|
913
|
+
log("");
|
|
914
|
+
log(pc.bold(pc.green("✅ Sync complete.")));
|
|
967
915
|
} catch (err) {
|
|
968
916
|
const hint = describeSftpError(err);
|
|
969
917
|
elog(pc.red("❌ Synchronisation error:"), err.message || err);
|
|
@@ -993,7 +941,10 @@ async function main() {
|
|
|
993
941
|
);
|
|
994
942
|
}
|
|
995
943
|
}
|
|
996
|
-
|
|
944
|
+
|
|
945
|
+
// Abschlusslinie + eine Leerzeile dahinter
|
|
946
|
+
log(hr2());
|
|
947
|
+
log("");
|
|
997
948
|
}
|
|
998
949
|
|
|
999
950
|
main();
|