sftp-push-sync 1.0.20 → 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 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
- # Consider normal synchronisation + upload list
88
- node bin/sftp-push-sync.mjs staging --upload-list
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-list
92
- node bin/sftp-push-sync.mjs staging --skip-sync --download-list
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-list --dry-run
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
- - or with subfolders: "data/downloads.json"
134
- - `downloadList`
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-list
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-list --dry-run # view first
147
- sftp-push-sync prod --download-list # then do
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 `sidecar` is always executed together with `sync` when you use the `--download-list` or `--upload-list` option.
151
- - However, with `--skip-sync`, you can exclude the sync process and only process the sidecar:
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-list --skip-sync
174
+ sftp-push-sync prod --sidecar-download --skip-sync
155
175
  ```
156
176
 
157
177
  ### Logging Progress
@@ -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-list");
67
- const RUN_DOWNLOAD_LIST = args.includes("--download-list");
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-list or --download-list.")
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
- localRoot: path.resolve(TARGET_CONFIG.localRoot),
214
- remoteRoot: TARGET_CONFIG.remoteRoot,
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
- const UPLOAD_LIST = normalizeList(CONFIG_RAW.uploadList ?? []);
291
- const DOWNLOAD_LIST = normalizeList(CONFIG_RAW.downloadList ?? []);
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.localRoot);
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.remoteRoot, rel);
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.remoteRoot);
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.localRoot, rel);
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sftp-push-sync",
3
- "version": "1.0.20",
3
+ "version": "2.0.0",
4
4
  "description": "SFTP sync tool for Hugo projects (local to remote, with hash cache)",
5
5
  "type": "module",
6
6
  "bin": {
@@ -10,7 +10,9 @@
10
10
  "release:patch": "npm run build:noop && npm version patch && git push && git push --tags && npm publish --access public",
11
11
  "release:minor": "npm run build:noop && npm version minor && git push && git push --tags && npm publish --access public",
12
12
  "release:major": "npm run build:noop && npm version major && git push && git push --tags && npm publish --access public",
13
- "build:noop": "echo \"nothing to build\""
13
+ "build:noop": "echo \"nothing to build\"",
14
+ "ncu-me": "ncu -i --format group",
15
+ "tree": "tree -a -L 3 ./ > directory structure.txt"
14
16
  },
15
17
  "keywords": [
16
18
  "hugo",
@@ -21,9 +23,9 @@
21
23
  "author": "Carsten Nichte",
22
24
  "license": "GPL-3.0-or-later",
23
25
  "dependencies": {
24
- "ssh2-sftp-client": "^10.0.0",
25
- "minimatch": "^9.0.3",
26
- "diff": "^5.2.0",
27
- "picocolors": "^1.0.0"
26
+ "ssh2-sftp-client": "^12.0.1",
27
+ "minimatch": "^10.1.1",
28
+ "diff": "^8.0.2",
29
+ "picocolors": "^1.1.1"
28
30
  }
29
31
  }