sftp-push-sync 1.0.21 → 2.0.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/CHANGELOG.md +18 -0
- package/README.md +50 -30
- package/bin/sftp-push-sync.mjs +54 -13
- package/package.json +1 -1
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [2.0.0] - 2025-11-18
|
|
4
|
+
|
|
5
|
+
### Breaking
|
|
6
|
+
|
|
7
|
+
- CLI flags renamed:
|
|
8
|
+
- `--upload-list` → `--sidecar-upload`
|
|
9
|
+
- `--download-list` → `--sidecar-download`
|
|
10
|
+
- Configuration per connection restructured:
|
|
11
|
+
- `localRoot` / `remoteRoot` now under `sync`
|
|
12
|
+
- `sidecar` block for sidecar uploads/downloads
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- Separate `sidecar.localRoot` / `sidecar.remoteRoot` für Upload-/Download-Listen.
|
|
17
|
+
|
|
18
|
+
---
|
package/README.md
CHANGED
|
@@ -23,6 +23,12 @@ Features:
|
|
|
23
23
|
|
|
24
24
|
The file `sftp-push-sync.mjs` is pure JavaScript (ESM), not TypeScript. Node.js can execute it directly as long as "type": "module" is specified in package.json or the file has the extension .mjs.
|
|
25
25
|
|
|
26
|
+
## Breaking changes in 2.0.0
|
|
27
|
+
|
|
28
|
+
- The flags `--upload-list` / `--download-list` have been replaced by
|
|
29
|
+
`--sidecar-upload` / `--sidecar-download`.
|
|
30
|
+
- The settings for sidecars are now located in the `sidecar` block of the connection.
|
|
31
|
+
|
|
26
32
|
## Install
|
|
27
33
|
|
|
28
34
|
```bash
|
|
@@ -47,20 +53,36 @@ Create a `sync.config.json` in the root folder of your project:
|
|
|
47
53
|
"port": 23,
|
|
48
54
|
"user": "ftpuser",
|
|
49
55
|
"password": "mypassword",
|
|
50
|
-
"remoteRoot": "/folder/",
|
|
51
|
-
"localRoot": "public",
|
|
52
56
|
"syncCache": ".sync-cache.prod.json",
|
|
53
|
-
"worker": 3
|
|
57
|
+
"worker": 3,
|
|
58
|
+
"sync": {
|
|
59
|
+
"localRoot": "public",
|
|
60
|
+
"remoteRoot": "/folder/"
|
|
61
|
+
},
|
|
62
|
+
"sidecar": {
|
|
63
|
+
"localRoot": "sidecar-local",
|
|
64
|
+
"remoteRoot": "/sidecar-remote/",
|
|
65
|
+
"uploadList": [],
|
|
66
|
+
"downloadList": []
|
|
67
|
+
}
|
|
54
68
|
},
|
|
55
69
|
"staging": {
|
|
56
70
|
"host": "ftpserver02",
|
|
57
71
|
"port": 22,
|
|
58
72
|
"user": "ftp_user",
|
|
59
73
|
"password": "total_secret",
|
|
60
|
-
"remoteRoot": "/web/my-page/",
|
|
61
|
-
"localRoot": "public",
|
|
62
74
|
"syncCache": ".sync-cache.staging.json",
|
|
63
|
-
"worker": 1
|
|
75
|
+
"worker": 1,
|
|
76
|
+
"sync": {
|
|
77
|
+
"localRoot": "public",
|
|
78
|
+
"remoteRoot": "/web/my-page/"
|
|
79
|
+
},
|
|
80
|
+
"sidecar": {
|
|
81
|
+
"localRoot": "sidecar-local",
|
|
82
|
+
"remoteRoot": "/sidecar-remote/",
|
|
83
|
+
"uploadList": [],
|
|
84
|
+
"downloadList": []
|
|
85
|
+
}
|
|
64
86
|
}
|
|
65
87
|
},
|
|
66
88
|
"include": [],
|
|
@@ -72,9 +94,7 @@ Create a `sync.config.json` in the root folder of your project:
|
|
|
72
94
|
"analyzeChunk": 1
|
|
73
95
|
},
|
|
74
96
|
"logLevel": "normal",
|
|
75
|
-
"logFile": ".sftp-push-sync.{target}.log"
|
|
76
|
-
"uploadList": [],
|
|
77
|
-
"downloadList": ["download-counter.json"]
|
|
97
|
+
"logFile": ".sftp-push-sync.{target}.log"
|
|
78
98
|
}
|
|
79
99
|
```
|
|
80
100
|
|
|
@@ -84,16 +104,18 @@ Create a `sync.config.json` in the root folder of your project:
|
|
|
84
104
|
# Normal synchronisation
|
|
85
105
|
node bin/sftp-push-sync.mjs staging
|
|
86
106
|
|
|
87
|
-
#
|
|
88
|
-
node bin/sftp-push-sync.mjs staging --upload
|
|
107
|
+
# Normal synchronisation + sidecar upload list
|
|
108
|
+
node bin/sftp-push-sync.mjs staging --sidecar-upload
|
|
109
|
+
|
|
110
|
+
# Normal synchronisation + sidecar download list
|
|
111
|
+
node bin/sftp-push-sync.mjs staging --sidecar-download
|
|
89
112
|
|
|
90
|
-
# Only lists, no standard synchronisation
|
|
91
|
-
node bin/sftp-push-sync.mjs staging --skip-sync --upload
|
|
92
|
-
node bin/sftp-push-sync.mjs staging --skip-sync --download
|
|
93
|
-
node bin/sftp-push-sync.mjs staging --skip-sync --upload-list --download-list
|
|
113
|
+
# Only sidecar lists, no standard synchronisation
|
|
114
|
+
node bin/sftp-push-sync.mjs staging --skip-sync --sidecar-upload
|
|
115
|
+
node bin/sftp-push-sync.mjs staging --skip-sync --sidecar-download
|
|
94
116
|
|
|
95
117
|
# (optional) only run lists dry
|
|
96
|
-
node bin/sftp-push-sync.mjs staging --skip-sync --upload
|
|
118
|
+
node bin/sftp-push-sync.mjs staging --skip-sync --sidecar-upload --dry-run
|
|
97
119
|
```
|
|
98
120
|
|
|
99
121
|
- Can be conveniently started via the scripts in `package.json`:
|
|
@@ -128,30 +150,28 @@ The dry run is a great way to compare files and fill the cache.
|
|
|
128
150
|
|
|
129
151
|
A list of files that are excluded from the sync comparison and can be downloaded or uploaded separately.
|
|
130
152
|
|
|
131
|
-
- `uploadList`
|
|
132
|
-
- Relative to localRoot "downloads.json"
|
|
133
|
-
|
|
134
|
-
-
|
|
135
|
-
- Relative to remoteRoot "download-counter.json"
|
|
136
|
-
- or e.g. "logs/download-counter.json"
|
|
153
|
+
- `sidecar.uploadList`
|
|
154
|
+
- Relative to sidecar.localRoot, e.g. "downloads.json" or "data/downloads.json"
|
|
155
|
+
- `sidecar.downloadList`
|
|
156
|
+
- Relative to sidecar.remoteRoot, e.g. "download-counter.json" or "logs/download-counter.json"
|
|
137
157
|
|
|
138
158
|
```bash
|
|
139
159
|
# normal synchronisation
|
|
140
160
|
sftp-push-sync staging
|
|
141
161
|
|
|
142
|
-
# Normal synchronisation + explicitly transfer upload list
|
|
143
|
-
sftp-push-sync staging --upload
|
|
162
|
+
# Normal synchronisation + explicitly transfer sidecar upload list
|
|
163
|
+
sftp-push-sync staging --sidecar-upload
|
|
144
164
|
|
|
145
|
-
# just fetch the download list from the server (combined with normal synchronisation)
|
|
146
|
-
sftp-push-sync prod --download
|
|
147
|
-
sftp-push-sync prod --download
|
|
165
|
+
# just fetch the sidecar download list from the server (combined with normal synchronisation)
|
|
166
|
+
sftp-push-sync prod --sidecar-download --dry-run # view first
|
|
167
|
+
sftp-push-sync prod --sidecar-download # then do
|
|
148
168
|
```
|
|
149
169
|
|
|
150
|
-
- The
|
|
151
|
-
-
|
|
170
|
+
- The sidecar is always executed together with sync when using `--sidecar-download` or `--sidecar-upload`.
|
|
171
|
+
- With `--skip-sync`, you can exclude the sync process and only process the sidecar:
|
|
152
172
|
|
|
153
173
|
```bash
|
|
154
|
-
sftp-push-sync prod --download
|
|
174
|
+
sftp-push-sync prod --sidecar-download --skip-sync
|
|
155
175
|
```
|
|
156
176
|
|
|
157
177
|
### Logging Progress
|
package/bin/sftp-push-sync.mjs
CHANGED
|
@@ -63,8 +63,8 @@ const tab_b = () => " ".repeat(6);
|
|
|
63
63
|
const args = process.argv.slice(2);
|
|
64
64
|
const TARGET = args[0];
|
|
65
65
|
const DRY_RUN = args.includes("--dry-run");
|
|
66
|
-
const RUN_UPLOAD_LIST = args.includes("--upload
|
|
67
|
-
const RUN_DOWNLOAD_LIST = args.includes("--download
|
|
66
|
+
const RUN_UPLOAD_LIST = args.includes("--sidecar-upload");
|
|
67
|
+
const RUN_DOWNLOAD_LIST = args.includes("--sidecar-download");
|
|
68
68
|
const SKIP_SYNC = args.includes("--skip-sync");
|
|
69
69
|
|
|
70
70
|
// logLevel override via CLI (optional)
|
|
@@ -81,7 +81,7 @@ if (!TARGET) {
|
|
|
81
81
|
// Wenn jemand --skip-sync ohne Listen benutzt → sinnlos, also abbrechen
|
|
82
82
|
if (SKIP_SYNC && !RUN_UPLOAD_LIST && !RUN_DOWNLOAD_LIST) {
|
|
83
83
|
console.error(
|
|
84
|
-
pc.red("❌ --skip-sync requires at least --upload
|
|
84
|
+
pc.red("❌ --skip-sync requires at least --sidecar-upload or --sidecar-download.")
|
|
85
85
|
);
|
|
86
86
|
process.exit(1);
|
|
87
87
|
}
|
|
@@ -205,13 +205,31 @@ if (!TARGET_CONFIG) {
|
|
|
205
205
|
process.exit(1);
|
|
206
206
|
}
|
|
207
207
|
|
|
208
|
+
const SYNC_CFG = TARGET_CONFIG.sync ?? TARGET_CONFIG;
|
|
209
|
+
const SIDECAR_CFG = TARGET_CONFIG.sidecar ?? {};
|
|
210
|
+
|
|
211
|
+
if (!SYNC_CFG.localRoot || !SYNC_CFG.remoteRoot) {
|
|
212
|
+
console.error(
|
|
213
|
+
pc.red(
|
|
214
|
+
`❌ Connection '${TARGET}' is missing sync.localRoot or sync.remoteRoot.`
|
|
215
|
+
)
|
|
216
|
+
);
|
|
217
|
+
process.exit(1);
|
|
218
|
+
}
|
|
219
|
+
|
|
208
220
|
const CONNECTION = {
|
|
209
221
|
host: TARGET_CONFIG.host,
|
|
210
222
|
port: TARGET_CONFIG.port ?? 22,
|
|
211
223
|
user: TARGET_CONFIG.user,
|
|
212
224
|
password: TARGET_CONFIG.password,
|
|
213
|
-
|
|
214
|
-
|
|
225
|
+
// Main sync roots
|
|
226
|
+
localRoot: path.resolve(SYNC_CFG.localRoot),
|
|
227
|
+
remoteRoot: SYNC_CFG.remoteRoot,
|
|
228
|
+
// Sidecar roots (for uploadList/downloadList)
|
|
229
|
+
sidecarLocalRoot: path.resolve(
|
|
230
|
+
SIDECAR_CFG.localRoot ?? SYNC_CFG.localRoot
|
|
231
|
+
),
|
|
232
|
+
sidecarRemoteRoot: SIDECAR_CFG.remoteRoot ?? SYNC_CFG.remoteRoot,
|
|
215
233
|
workers: TARGET_CONFIG.worker ?? 2,
|
|
216
234
|
};
|
|
217
235
|
|
|
@@ -274,7 +292,7 @@ const MEDIA_EXT = CONFIG_RAW.mediaExtensions ?? [
|
|
|
274
292
|
".pdf",
|
|
275
293
|
];
|
|
276
294
|
|
|
277
|
-
// Special: Lists for targeted uploads/downloads
|
|
295
|
+
// Special: Lists for targeted uploads/downloads (per-connection sidecar)
|
|
278
296
|
function normalizeList(list) {
|
|
279
297
|
if (!Array.isArray(list)) return [];
|
|
280
298
|
return list.flatMap((item) =>
|
|
@@ -287,8 +305,9 @@ function normalizeList(list) {
|
|
|
287
305
|
);
|
|
288
306
|
}
|
|
289
307
|
|
|
290
|
-
|
|
291
|
-
const
|
|
308
|
+
// Lists from sidecar config (relative to sidecar.localRoot / sidecar.remoteRoot)
|
|
309
|
+
const UPLOAD_LIST = normalizeList(SIDECAR_CFG.uploadList ?? []);
|
|
310
|
+
const DOWNLOAD_LIST = normalizeList(SIDECAR_CFG.downloadList ?? []);
|
|
292
311
|
|
|
293
312
|
// Effektive Exclude-Liste: explizites exclude + Upload/Download-Listen
|
|
294
313
|
// → diese Dateien werden im „normalen“ Sync nicht angerührt,
|
|
@@ -775,12 +794,12 @@ function describeSftpError(err) {
|
|
|
775
794
|
// ---------------------------------------------------------------------------
|
|
776
795
|
|
|
777
796
|
async function collectUploadTargets() {
|
|
778
|
-
const all = await walkLocalPlain(CONNECTION.
|
|
797
|
+
const all = await walkLocalPlain(CONNECTION.sidecarLocalRoot);
|
|
779
798
|
const results = [];
|
|
780
799
|
|
|
781
800
|
for (const [rel, meta] of all.entries()) {
|
|
782
801
|
if (matchesAny(UPLOAD_LIST, rel)) {
|
|
783
|
-
const remotePath = path.posix.join(CONNECTION.
|
|
802
|
+
const remotePath = path.posix.join(CONNECTION.sidecarRemoteRoot, rel);
|
|
784
803
|
results.push({
|
|
785
804
|
rel,
|
|
786
805
|
localPath: meta.localPath,
|
|
@@ -793,12 +812,12 @@ async function collectUploadTargets() {
|
|
|
793
812
|
}
|
|
794
813
|
|
|
795
814
|
async function collectDownloadTargets(sftp) {
|
|
796
|
-
const all = await walkRemotePlain(sftp, CONNECTION.
|
|
815
|
+
const all = await walkRemotePlain(sftp, CONNECTION.sidecarRemoteRoot);
|
|
797
816
|
const results = [];
|
|
798
817
|
|
|
799
818
|
for (const [rel, meta] of all.entries()) {
|
|
800
819
|
if (matchesAny(DOWNLOAD_LIST, rel)) {
|
|
801
|
-
const localPath = path.join(CONNECTION.
|
|
820
|
+
const localPath = path.join(CONNECTION.sidecarLocalRoot, rel);
|
|
802
821
|
results.push({
|
|
803
822
|
rel,
|
|
804
823
|
remotePath: meta.remotePath,
|
|
@@ -813,6 +832,20 @@ async function collectDownloadTargets(sftp) {
|
|
|
813
832
|
async function performBypassOnly(sftp) {
|
|
814
833
|
log("");
|
|
815
834
|
log(pc.bold(pc.cyan("🚀 Bypass-Only Mode (skip-sync)")));
|
|
835
|
+
log(
|
|
836
|
+
`${tab_a()}Sidecar Local: ${pc.green(CONNECTION.sidecarLocalRoot)}`
|
|
837
|
+
);
|
|
838
|
+
log(
|
|
839
|
+
`${tab_a()}Sidecar Remote: ${pc.green(CONNECTION.sidecarRemoteRoot)}`
|
|
840
|
+
);
|
|
841
|
+
|
|
842
|
+
if (RUN_UPLOAD_LIST && !fs.existsSync(CONNECTION.sidecarLocalRoot)) {
|
|
843
|
+
elog(
|
|
844
|
+
pc.red("❌ Sidecar local root does not exist:"),
|
|
845
|
+
CONNECTION.sidecarLocalRoot
|
|
846
|
+
);
|
|
847
|
+
process.exit(1);
|
|
848
|
+
}
|
|
816
849
|
|
|
817
850
|
if (RUN_UPLOAD_LIST) {
|
|
818
851
|
log("");
|
|
@@ -902,6 +935,14 @@ async function main() {
|
|
|
902
935
|
);
|
|
903
936
|
log(`${tab_a()}Local: ${pc.green(CONNECTION.localRoot)}`);
|
|
904
937
|
log(`${tab_a()}Remote: ${pc.green(CONNECTION.remoteRoot)}`);
|
|
938
|
+
if (RUN_UPLOAD_LIST || RUN_DOWNLOAD_LIST || SKIP_SYNC) {
|
|
939
|
+
log(
|
|
940
|
+
`${tab_a()}Sidecar Local: ${pc.green(CONNECTION.sidecarLocalRoot)}`
|
|
941
|
+
);
|
|
942
|
+
log(
|
|
943
|
+
`${tab_a()}Sidecar Remote: ${pc.green(CONNECTION.sidecarRemoteRoot)}`
|
|
944
|
+
);
|
|
945
|
+
}
|
|
905
946
|
if (DRY_RUN) log(pc.yellow(`${tab_a()}Mode: DRY-RUN (no changes)`));
|
|
906
947
|
if (SKIP_SYNC) log(pc.yellow(`${tab_a()}Mode: SKIP-SYNC (bypass only)`));
|
|
907
948
|
if (RUN_UPLOAD_LIST || RUN_DOWNLOAD_LIST) {
|
|
@@ -937,7 +978,7 @@ async function main() {
|
|
|
937
978
|
connected = true;
|
|
938
979
|
log(pc.green(`${tab_a()}✔ Connected to SFTP.`));
|
|
939
980
|
|
|
940
|
-
if (!fs.existsSync(CONNECTION.localRoot)) {
|
|
981
|
+
if (!SKIP_SYNC && !fs.existsSync(CONNECTION.localRoot)) {
|
|
941
982
|
console.error(
|
|
942
983
|
pc.red("❌ Local root does not exist:"),
|
|
943
984
|
CONNECTION.localRoot
|