dsh-remote 0.8.46 → 0.8.48

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 (391) hide show
  1. package/LICENSE +21 -21
  2. package/README.en.md +361 -361
  3. package/README.md +368 -368
  4. package/cordis.patch.yml +9 -9
  5. package/lib/binding.js +172 -172
  6. package/lib/client.js +3637 -3637
  7. package/lib/credential.js +177 -177
  8. package/lib/deploy-tasks.js +142 -142
  9. package/lib/errors.js +87 -87
  10. package/lib/file-reference.js +457 -457
  11. package/lib/forwards.js +258 -258
  12. package/lib/hostkey.js +108 -108
  13. package/lib/http-transport.js +196 -196
  14. package/lib/ignore.js +202 -156
  15. package/lib/index.js +3779 -3747
  16. package/lib/openssh.js +838 -838
  17. package/lib/paths.js +225 -225
  18. package/lib/pool.js +757 -757
  19. package/lib/registry.js +95 -95
  20. package/lib/remote-fs.js +148 -143
  21. package/lib/routes-fs.js +233 -233
  22. package/lib/search.js +235 -230
  23. package/lib/sftp-surface.js +76 -76
  24. package/lib/sshconfig.js +454 -454
  25. package/lib/sync.js +275 -275
  26. package/lib/tasks.js +95 -95
  27. package/lib/telemetry.js +213 -213
  28. package/lib/update.js +472 -472
  29. package/lib/web-attach.js +850 -850
  30. package/lib/web-deploy-skill.js +205 -205
  31. package/lib/web-deploy.js +694 -694
  32. package/node_modules/asn1/Jenkinsfile +65 -0
  33. package/node_modules/asn1/LICENSE +19 -0
  34. package/node_modules/asn1/README.md +50 -0
  35. package/node_modules/asn1/lib/ber/errors.js +13 -0
  36. package/node_modules/asn1/lib/ber/index.js +27 -0
  37. package/node_modules/asn1/lib/ber/reader.js +262 -0
  38. package/node_modules/asn1/lib/ber/types.js +36 -0
  39. package/node_modules/asn1/lib/ber/writer.js +317 -0
  40. package/node_modules/asn1/lib/index.js +20 -0
  41. package/node_modules/asn1/package.json +31 -0
  42. package/node_modules/bcrypt-pbkdf/CONTRIBUTING.md +13 -0
  43. package/node_modules/bcrypt-pbkdf/LICENSE +66 -0
  44. package/node_modules/bcrypt-pbkdf/README.md +45 -0
  45. package/node_modules/bcrypt-pbkdf/index.js +556 -0
  46. package/node_modules/bcrypt-pbkdf/package.json +15 -0
  47. package/node_modules/buildcheck/.eslintrc.js +5 -0
  48. package/node_modules/buildcheck/.github/workflows/ci.yml +88 -0
  49. package/node_modules/buildcheck/.github/workflows/lint.yml +27 -0
  50. package/node_modules/buildcheck/.npmignore +3 -0
  51. package/node_modules/buildcheck/LICENSE +19 -0
  52. package/node_modules/buildcheck/README.md +106 -0
  53. package/node_modules/buildcheck/deps/Find-VisualStudio.cs +250 -0
  54. package/node_modules/buildcheck/lib/findvs.js +361 -0
  55. package/node_modules/buildcheck/lib/index.js +738 -0
  56. package/node_modules/buildcheck/package.json +36 -0
  57. package/node_modules/buildcheck/test/test.js +8 -0
  58. package/node_modules/cpu-features/.eslintrc.js +5 -0
  59. package/node_modules/cpu-features/.github/workflows/ci.yml +74 -0
  60. package/node_modules/cpu-features/.github/workflows/lint.yml +27 -0
  61. package/node_modules/cpu-features/LICENSE +19 -0
  62. package/node_modules/cpu-features/README.md +59 -0
  63. package/node_modules/cpu-features/binding.gyp +16 -0
  64. package/node_modules/cpu-features/buildcheck.js +32 -0
  65. package/node_modules/cpu-features/deps/cpu_features/.clang-format +4 -0
  66. package/node_modules/cpu-features/deps/cpu_features/.dockerignore +31 -0
  67. package/node_modules/cpu-features/deps/cpu_features/.github/workflows/Dockerfile +5 -0
  68. package/node_modules/cpu-features/deps/cpu_features/.github/workflows/aarch64_linux_cmake.yml +30 -0
  69. package/node_modules/cpu-features/deps/cpu_features/.github/workflows/amd64_freebsd_cmake.yml +22 -0
  70. package/node_modules/cpu-features/deps/cpu_features/.github/workflows/amd64_linux_bazel.yml +26 -0
  71. package/node_modules/cpu-features/deps/cpu_features/.github/workflows/amd64_linux_cmake.yml +31 -0
  72. package/node_modules/cpu-features/deps/cpu_features/.github/workflows/amd64_macos_cmake.yml +43 -0
  73. package/node_modules/cpu-features/deps/cpu_features/.github/workflows/amd64_windows_cmake.yml +25 -0
  74. package/node_modules/cpu-features/deps/cpu_features/.github/workflows/arm_linux_cmake.yml +31 -0
  75. package/node_modules/cpu-features/deps/cpu_features/.github/workflows/clang_format.yml +24 -0
  76. package/node_modules/cpu-features/deps/cpu_features/.github/workflows/mips_linux_cmake.yml +30 -0
  77. package/node_modules/cpu-features/deps/cpu_features/.github/workflows/power_linux_cmake.yml +29 -0
  78. package/node_modules/cpu-features/deps/cpu_features/.github/workflows/riscv_linux_cmake.yml +28 -0
  79. package/node_modules/cpu-features/deps/cpu_features/.github/workflows/s390x_linux_cmake.yml +27 -0
  80. package/node_modules/cpu-features/deps/cpu_features/.grenrc.yml +21 -0
  81. package/node_modules/cpu-features/deps/cpu_features/BUILD.bazel +329 -0
  82. package/node_modules/cpu-features/deps/cpu_features/CMakeLists.txt +261 -0
  83. package/node_modules/cpu-features/deps/cpu_features/CONTRIBUTING.md +23 -0
  84. package/node_modules/cpu-features/deps/cpu_features/LICENSE +230 -0
  85. package/node_modules/cpu-features/deps/cpu_features/README.md +272 -0
  86. package/node_modules/cpu-features/deps/cpu_features/WORKSPACE +19 -0
  87. package/node_modules/cpu-features/deps/cpu_features/bazel/ci/README.md +5 -0
  88. package/node_modules/cpu-features/deps/cpu_features/bazel/platforms.bzl +11 -0
  89. package/node_modules/cpu-features/deps/cpu_features/cmake/CpuFeaturesConfig.cmake.in +3 -0
  90. package/node_modules/cpu-features/deps/cpu_features/cmake/CpuFeaturesNdkCompatConfig.cmake.in +3 -0
  91. package/node_modules/cpu-features/deps/cpu_features/cmake/README.md +30 -0
  92. package/node_modules/cpu-features/deps/cpu_features/cmake/ci/Makefile +252 -0
  93. package/node_modules/cpu-features/deps/cpu_features/cmake/ci/README.md +40 -0
  94. package/node_modules/cpu-features/deps/cpu_features/cmake/ci/doc/docker.dot +64 -0
  95. package/node_modules/cpu-features/deps/cpu_features/cmake/ci/doc/docker.svg +312 -0
  96. package/node_modules/cpu-features/deps/cpu_features/cmake/ci/doc/generate_image.sh +7 -0
  97. package/node_modules/cpu-features/deps/cpu_features/cmake/ci/docker/amd64/Dockerfile +48 -0
  98. package/node_modules/cpu-features/deps/cpu_features/cmake/ci/docker/toolchain/Dockerfile +34 -0
  99. package/node_modules/cpu-features/deps/cpu_features/cmake/ci/sample/CMakeLists.txt +22 -0
  100. package/node_modules/cpu-features/deps/cpu_features/cmake/ci/sample/main.cpp +11 -0
  101. package/node_modules/cpu-features/deps/cpu_features/cmake/ci/vagrant/freebsd/Vagrantfile +107 -0
  102. package/node_modules/cpu-features/deps/cpu_features/cmake/googletest.CMakeLists.txt.in +15 -0
  103. package/node_modules/cpu-features/deps/cpu_features/cpu_features.gyp +105 -0
  104. package/node_modules/cpu-features/deps/cpu_features/include/cpu_features_cache_info.h +54 -0
  105. package/node_modules/cpu-features/deps/cpu_features/include/cpu_features_macros.h +384 -0
  106. package/node_modules/cpu-features/deps/cpu_features/include/cpuinfo_aarch64.h +259 -0
  107. package/node_modules/cpu-features/deps/cpu_features/include/cpuinfo_arm.h +121 -0
  108. package/node_modules/cpu-features/deps/cpu_features/include/cpuinfo_mips.h +74 -0
  109. package/node_modules/cpu-features/deps/cpu_features/include/cpuinfo_ppc.h +149 -0
  110. package/node_modules/cpu-features/deps/cpu_features/include/cpuinfo_riscv.h +72 -0
  111. package/node_modules/cpu-features/deps/cpu_features/include/cpuinfo_s390x.h +108 -0
  112. package/node_modules/cpu-features/deps/cpu_features/include/cpuinfo_x86.h +288 -0
  113. package/node_modules/cpu-features/deps/cpu_features/include/internal/bit_utils.h +40 -0
  114. package/node_modules/cpu-features/deps/cpu_features/include/internal/cpuid_x86.h +37 -0
  115. package/node_modules/cpu-features/deps/cpu_features/include/internal/filesystem.h +39 -0
  116. package/node_modules/cpu-features/deps/cpu_features/include/internal/hwcaps.h +240 -0
  117. package/node_modules/cpu-features/deps/cpu_features/include/internal/stack_line_reader.h +49 -0
  118. package/node_modules/cpu-features/deps/cpu_features/include/internal/string_view.h +110 -0
  119. package/node_modules/cpu-features/deps/cpu_features/include/internal/windows_utils.h +70 -0
  120. package/node_modules/cpu-features/deps/cpu_features/ndk_compat/CMakeLists.txt +60 -0
  121. package/node_modules/cpu-features/deps/cpu_features/ndk_compat/README.md +4 -0
  122. package/node_modules/cpu-features/deps/cpu_features/ndk_compat/cpu-features.c +205 -0
  123. package/node_modules/cpu-features/deps/cpu_features/ndk_compat/cpu-features.h +320 -0
  124. package/node_modules/cpu-features/deps/cpu_features/ndk_compat/ndk-compat-test.c +12 -0
  125. package/node_modules/cpu-features/deps/cpu_features/patches/0001-Add-Apple-Silicon-Support.patch +456 -0
  126. package/node_modules/cpu-features/deps/cpu_features/scripts/generate_badges.d +165 -0
  127. package/node_modules/cpu-features/deps/cpu_features/scripts/make_release.sh +75 -0
  128. package/node_modules/cpu-features/deps/cpu_features/scripts/run_integration.sh +490 -0
  129. package/node_modules/cpu-features/deps/cpu_features/scripts/test_integration.sh +79 -0
  130. package/node_modules/cpu-features/deps/cpu_features/src/copy.inl +19 -0
  131. package/node_modules/cpu-features/deps/cpu_features/src/define_introspection.inl +86 -0
  132. package/node_modules/cpu-features/deps/cpu_features/src/define_introspection_and_hwcaps.inl +26 -0
  133. package/node_modules/cpu-features/deps/cpu_features/src/equals.inl +22 -0
  134. package/node_modules/cpu-features/deps/cpu_features/src/filesystem.c +62 -0
  135. package/node_modules/cpu-features/deps/cpu_features/src/hwcaps.c +169 -0
  136. package/node_modules/cpu-features/deps/cpu_features/src/impl_aarch64__base_implementation.inl +88 -0
  137. package/node_modules/cpu-features/deps/cpu_features/src/impl_aarch64_linux_or_android.c +79 -0
  138. package/node_modules/cpu-features/deps/cpu_features/src/impl_aarch64_macos_or_iphone.c +82 -0
  139. package/node_modules/cpu-features/deps/cpu_features/src/impl_aarch64_windows.c +138 -0
  140. package/node_modules/cpu-features/deps/cpu_features/src/impl_arm_linux_or_android.c +212 -0
  141. package/node_modules/cpu-features/deps/cpu_features/src/impl_mips_linux_or_android.c +93 -0
  142. package/node_modules/cpu-features/deps/cpu_features/src/impl_ppc_linux.c +163 -0
  143. package/node_modules/cpu-features/deps/cpu_features/src/impl_riscv_linux.c +111 -0
  144. package/node_modules/cpu-features/deps/cpu_features/src/impl_s390x_linux.c +120 -0
  145. package/node_modules/cpu-features/deps/cpu_features/src/impl_x86__base_implementation.inl +2055 -0
  146. package/node_modules/cpu-features/deps/cpu_features/src/impl_x86_freebsd.c +68 -0
  147. package/node_modules/cpu-features/deps/cpu_features/src/impl_x86_linux_or_android.c +58 -0
  148. package/node_modules/cpu-features/deps/cpu_features/src/impl_x86_macos.c +57 -0
  149. package/node_modules/cpu-features/deps/cpu_features/src/impl_x86_windows.c +58 -0
  150. package/node_modules/cpu-features/deps/cpu_features/src/stack_line_reader.c +132 -0
  151. package/node_modules/cpu-features/deps/cpu_features/src/string_view.c +192 -0
  152. package/node_modules/cpu-features/deps/cpu_features/src/utils/list_cpu_features.c +461 -0
  153. package/node_modules/cpu-features/deps/cpu_features/test/CMakeLists.txt +114 -0
  154. package/node_modules/cpu-features/deps/cpu_features/test/bit_utils_test.cc +53 -0
  155. package/node_modules/cpu-features/deps/cpu_features/test/cpuinfo_aarch64_test.cc +396 -0
  156. package/node_modules/cpu-features/deps/cpu_features/test/cpuinfo_arm_test.cc +384 -0
  157. package/node_modules/cpu-features/deps/cpu_features/test/cpuinfo_mips_test.cc +176 -0
  158. package/node_modules/cpu-features/deps/cpu_features/test/cpuinfo_ppc_test.cc +131 -0
  159. package/node_modules/cpu-features/deps/cpu_features/test/cpuinfo_riscv_test.cc +180 -0
  160. package/node_modules/cpu-features/deps/cpu_features/test/cpuinfo_s390x_test.cc +82 -0
  161. package/node_modules/cpu-features/deps/cpu_features/test/cpuinfo_x86_test.cc +1729 -0
  162. package/node_modules/cpu-features/deps/cpu_features/test/filesystem_for_testing.cc +103 -0
  163. package/node_modules/cpu-features/deps/cpu_features/test/filesystem_for_testing.h +61 -0
  164. package/node_modules/cpu-features/deps/cpu_features/test/hwcaps_for_testing.cc +52 -0
  165. package/node_modules/cpu-features/deps/cpu_features/test/hwcaps_for_testing.h +31 -0
  166. package/node_modules/cpu-features/deps/cpu_features/test/stack_line_reader_test.cc +132 -0
  167. package/node_modules/cpu-features/deps/cpu_features/test/string_view_test.cc +202 -0
  168. package/node_modules/cpu-features/lib/index.js +5 -0
  169. package/node_modules/cpu-features/package.json +40 -0
  170. package/node_modules/cpu-features/src/binding.cc +151 -0
  171. package/node_modules/cpu-features/test/test.js +12 -0
  172. package/node_modules/nan/.github/workflows/ci.yml +46 -0
  173. package/node_modules/nan/.pre-commit-config.yaml +8 -0
  174. package/node_modules/nan/CHANGELOG.md +605 -0
  175. package/node_modules/nan/CMakeLists.txt +138 -0
  176. package/node_modules/nan/LICENSE.md +9 -0
  177. package/node_modules/nan/README.md +457 -0
  178. package/node_modules/nan/doc/asyncworker.md +146 -0
  179. package/node_modules/nan/doc/buffers.md +54 -0
  180. package/node_modules/nan/doc/callback.md +76 -0
  181. package/node_modules/nan/doc/converters.md +41 -0
  182. package/node_modules/nan/doc/errors.md +226 -0
  183. package/node_modules/nan/doc/json.md +62 -0
  184. package/node_modules/nan/doc/maybe_types.md +583 -0
  185. package/node_modules/nan/doc/methods.md +689 -0
  186. package/node_modules/nan/doc/new.md +147 -0
  187. package/node_modules/nan/doc/node_misc.md +123 -0
  188. package/node_modules/nan/doc/object_wrappers.md +263 -0
  189. package/node_modules/nan/doc/persistent.md +296 -0
  190. package/node_modules/nan/doc/scopes.md +73 -0
  191. package/node_modules/nan/doc/script.md +58 -0
  192. package/node_modules/nan/doc/string_bytes.md +81 -0
  193. package/node_modules/nan/doc/v8_internals.md +211 -0
  194. package/node_modules/nan/doc/v8_misc.md +85 -0
  195. package/node_modules/nan/include_dirs.js +1 -0
  196. package/node_modules/nan/nan.h +3202 -0
  197. package/node_modules/nan/nan_callbacks.h +141 -0
  198. package/node_modules/nan/nan_callbacks_12_inl.h +730 -0
  199. package/node_modules/nan/nan_callbacks_pre_12_inl.h +528 -0
  200. package/node_modules/nan/nan_converters.h +72 -0
  201. package/node_modules/nan/nan_converters_43_inl.h +68 -0
  202. package/node_modules/nan/nan_converters_pre_43_inl.h +42 -0
  203. package/node_modules/nan/nan_define_own_property_helper.h +29 -0
  204. package/node_modules/nan/nan_implementation_12_inl.h +446 -0
  205. package/node_modules/nan/nan_implementation_pre_12_inl.h +263 -0
  206. package/node_modules/nan/nan_json.h +166 -0
  207. package/node_modules/nan/nan_maybe_43_inl.h +360 -0
  208. package/node_modules/nan/nan_maybe_pre_43_inl.h +268 -0
  209. package/node_modules/nan/nan_new.h +340 -0
  210. package/node_modules/nan/nan_object_wrap.h +156 -0
  211. package/node_modules/nan/nan_persistent_12_inl.h +132 -0
  212. package/node_modules/nan/nan_persistent_pre_12_inl.h +242 -0
  213. package/node_modules/nan/nan_private.h +73 -0
  214. package/node_modules/nan/nan_scriptorigin.h +97 -0
  215. package/node_modules/nan/nan_string_bytes.h +305 -0
  216. package/node_modules/nan/nan_typedarray_contents.h +96 -0
  217. package/node_modules/nan/nan_weak.h +453 -0
  218. package/node_modules/nan/package.json +38 -0
  219. package/node_modules/nan/tools/1to2.js +412 -0
  220. package/node_modules/nan/tools/README.md +14 -0
  221. package/node_modules/nan/tools/package.json +19 -0
  222. package/node_modules/safer-buffer/LICENSE +21 -0
  223. package/node_modules/safer-buffer/Porting-Buffer.md +268 -0
  224. package/node_modules/safer-buffer/Readme.md +156 -0
  225. package/node_modules/safer-buffer/dangerous.js +58 -0
  226. package/node_modules/safer-buffer/package.json +34 -0
  227. package/node_modules/safer-buffer/safer.js +77 -0
  228. package/node_modules/safer-buffer/tests.js +406 -0
  229. package/node_modules/ssh2/.eslintignore +4 -0
  230. package/node_modules/ssh2/.eslintrc.js +5 -0
  231. package/node_modules/ssh2/.github/workflows/ci.yml +110 -0
  232. package/node_modules/ssh2/.github/workflows/lint.yml +27 -0
  233. package/node_modules/ssh2/LICENSE +19 -0
  234. package/node_modules/ssh2/README.md +1529 -0
  235. package/node_modules/ssh2/SFTP.md +413 -0
  236. package/node_modules/ssh2/examples/server-chat.js +238 -0
  237. package/node_modules/ssh2/examples/sftp-server-download-only.js +134 -0
  238. package/node_modules/ssh2/install.js +27 -0
  239. package/node_modules/ssh2/lib/Channel.js +295 -0
  240. package/node_modules/ssh2/lib/agent.js +1123 -0
  241. package/node_modules/ssh2/lib/client.js +2176 -0
  242. package/node_modules/ssh2/lib/http-agents.js +84 -0
  243. package/node_modules/ssh2/lib/index.js +44 -0
  244. package/node_modules/ssh2/lib/keygen.js +582 -0
  245. package/node_modules/ssh2/lib/protocol/Protocol.js +2136 -0
  246. package/node_modules/ssh2/lib/protocol/SFTP.js +4052 -0
  247. package/node_modules/ssh2/lib/protocol/constants.js +356 -0
  248. package/node_modules/ssh2/lib/protocol/crypto/binding.gyp +23 -0
  249. package/node_modules/ssh2/lib/protocol/crypto/poly1305.js +43 -0
  250. package/node_modules/ssh2/lib/protocol/crypto/src/binding.cc +2300 -0
  251. package/node_modules/ssh2/lib/protocol/crypto.js +1602 -0
  252. package/node_modules/ssh2/lib/protocol/handlers.js +16 -0
  253. package/node_modules/ssh2/lib/protocol/handlers.misc.js +1285 -0
  254. package/node_modules/ssh2/lib/protocol/kex.js +1908 -0
  255. package/node_modules/ssh2/lib/protocol/keyParser.js +1484 -0
  256. package/node_modules/ssh2/lib/protocol/node-fs-compat.js +115 -0
  257. package/node_modules/ssh2/lib/protocol/utils.js +356 -0
  258. package/node_modules/ssh2/lib/protocol/zlib.js +255 -0
  259. package/node_modules/ssh2/lib/server.js +1380 -0
  260. package/node_modules/ssh2/lib/utils.js +336 -0
  261. package/node_modules/ssh2/package.json +49 -0
  262. package/node_modules/ssh2/test/common.js +316 -0
  263. package/node_modules/ssh2/test/fixtures/bad_rsa_private_key +26 -0
  264. package/node_modules/ssh2/test/fixtures/https_cert.pem +33 -0
  265. package/node_modules/ssh2/test/fixtures/https_key.pem +52 -0
  266. package/node_modules/ssh2/test/fixtures/id_dsa +12 -0
  267. package/node_modules/ssh2/test/fixtures/id_ecdsa +5 -0
  268. package/node_modules/ssh2/test/fixtures/id_rsa +15 -0
  269. package/node_modules/ssh2/test/fixtures/id_rsa.ppk +26 -0
  270. package/node_modules/ssh2/test/fixtures/id_rsa_enc +30 -0
  271. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa +21 -0
  272. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa.pub +1 -0
  273. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa.pub.result +7 -0
  274. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa.result +7 -0
  275. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa_enc +22 -0
  276. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa_enc.pub +1 -0
  277. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa_enc.pub.result +7 -0
  278. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa_enc.result +7 -0
  279. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa_enc_gcm +23 -0
  280. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa_enc_gcm.pub +1 -0
  281. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa_enc_gcm.pub.result +7 -0
  282. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa_enc_gcm.result +7 -0
  283. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa +9 -0
  284. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa.pub +1 -0
  285. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa.pub.result +7 -0
  286. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa.result +7 -0
  287. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa_enc +10 -0
  288. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa_enc.pub +1 -0
  289. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa_enc.pub.result +8 -0
  290. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa_enc.result +7 -0
  291. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa_enc_gcm +10 -0
  292. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa_enc_gcm.pub +1 -0
  293. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa_enc_gcm.pub.result +8 -0
  294. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa_enc_gcm.result +7 -0
  295. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ed25519 +7 -0
  296. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ed25519.pub +1 -0
  297. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ed25519.pub.result +7 -0
  298. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ed25519.result +7 -0
  299. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa +27 -0
  300. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa.pub +1 -0
  301. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa.pub.result +7 -0
  302. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa.result +7 -0
  303. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa_enc +28 -0
  304. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa_enc.pub +1 -0
  305. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa_enc.pub.result +8 -0
  306. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa_enc.result +7 -0
  307. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa_enc_gcm +29 -0
  308. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa_enc_gcm.pub +1 -0
  309. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa_enc_gcm.pub.result +8 -0
  310. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa_enc_gcm.result +7 -0
  311. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_dsa +12 -0
  312. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_dsa.pub +1 -0
  313. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_dsa.pub.result +7 -0
  314. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_dsa.result +7 -0
  315. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_dsa_enc +15 -0
  316. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_dsa_enc.pub +1 -0
  317. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_dsa_enc.pub.result +8 -0
  318. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_dsa_enc.result +7 -0
  319. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_ecdsa +5 -0
  320. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_ecdsa.pub +1 -0
  321. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_ecdsa.pub.result +7 -0
  322. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_ecdsa.result +7 -0
  323. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_ecdsa_enc +8 -0
  324. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_ecdsa_enc.pub +1 -0
  325. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_ecdsa_enc.pub.result +8 -0
  326. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_ecdsa_enc.result +7 -0
  327. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa +27 -0
  328. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa.pub +1 -0
  329. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa.pub.result +7 -0
  330. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa.result +7 -0
  331. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa_enc +30 -0
  332. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa_enc.pub +1 -0
  333. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa_enc.pub.result +8 -0
  334. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa_enc.result +7 -0
  335. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa_enc_aes256 +54 -0
  336. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa_enc_aes256.pub +1 -0
  337. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa_enc_aes256.pub.result +6 -0
  338. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa_enc_aes256.result +6 -0
  339. package/node_modules/ssh2/test/fixtures/keyParser/ppk_dsa_enc +17 -0
  340. package/node_modules/ssh2/test/fixtures/keyParser/ppk_dsa_enc.result +7 -0
  341. package/node_modules/ssh2/test/fixtures/keyParser/ppk_rsa +26 -0
  342. package/node_modules/ssh2/test/fixtures/keyParser/ppk_rsa.result +7 -0
  343. package/node_modules/ssh2/test/fixtures/keyParser/ppk_rsa_enc +18 -0
  344. package/node_modules/ssh2/test/fixtures/keyParser/ppk_rsa_enc.result +7 -0
  345. package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa.pub +9 -0
  346. package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa.pub.result +7 -0
  347. package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa2.pub +10 -0
  348. package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa2.pub.result +7 -0
  349. package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa3.pub +11 -0
  350. package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa3.pub.result +7 -0
  351. package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa4.pub +11 -0
  352. package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa4.pub.result +7 -0
  353. package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa5.pub +8 -0
  354. package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa5.pub.result +7 -0
  355. package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa6.pub +13 -0
  356. package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa6.pub.result +7 -0
  357. package/node_modules/ssh2/test/fixtures/openssh_new_rsa +27 -0
  358. package/node_modules/ssh2/test/fixtures/ssh_host_dsa_key +12 -0
  359. package/node_modules/ssh2/test/fixtures/ssh_host_ecdsa_key +5 -0
  360. package/node_modules/ssh2/test/fixtures/ssh_host_rsa_key +15 -0
  361. package/node_modules/ssh2/test/test-exec.js +578 -0
  362. package/node_modules/ssh2/test/test-integration-openssh.js +486 -0
  363. package/node_modules/ssh2/test/test-keygen.js +60 -0
  364. package/node_modules/ssh2/test/test-misc-client-server.js +1460 -0
  365. package/node_modules/ssh2/test/test-openssh.js +261 -0
  366. package/node_modules/ssh2/test/test-protocol-crypto.js +631 -0
  367. package/node_modules/ssh2/test/test-protocol-keyparser.js +177 -0
  368. package/node_modules/ssh2/test/test-server-hostkeys.js +138 -0
  369. package/node_modules/ssh2/test/test-sftp.js +842 -0
  370. package/node_modules/ssh2/test/test-shell.js +109 -0
  371. package/node_modules/ssh2/test/test-userauth-agent-openssh.js +110 -0
  372. package/node_modules/ssh2/test/test-userauth-agent.js +171 -0
  373. package/node_modules/ssh2/test/test-userauth.js +611 -0
  374. package/node_modules/ssh2/test/test-worker-imports.js +25 -0
  375. package/node_modules/ssh2/test/test.js +20 -0
  376. package/node_modules/ssh2/util/build_pagent.bat +2 -0
  377. package/node_modules/ssh2/util/pagent.c +88 -0
  378. package/node_modules/ssh2/util/pagent.exe +0 -0
  379. package/node_modules/tweetnacl/.npmignore +4 -0
  380. package/node_modules/tweetnacl/AUTHORS.md +28 -0
  381. package/node_modules/tweetnacl/CHANGELOG.md +221 -0
  382. package/node_modules/tweetnacl/LICENSE +24 -0
  383. package/node_modules/tweetnacl/PULL_REQUEST_TEMPLATE.md +20 -0
  384. package/node_modules/tweetnacl/README.md +459 -0
  385. package/node_modules/tweetnacl/nacl-fast.js +2388 -0
  386. package/node_modules/tweetnacl/nacl-fast.min.js +2 -0
  387. package/node_modules/tweetnacl/nacl.d.ts +98 -0
  388. package/node_modules/tweetnacl/nacl.js +1175 -0
  389. package/node_modules/tweetnacl/nacl.min.js +1 -0
  390. package/node_modules/tweetnacl/package.json +58 -0
  391. package/package.json +96 -89
package/lib/sync.js CHANGED
@@ -1,275 +1,275 @@
1
- // dsh-remote — three-way conflict-aware mirror sync (remote ⇄ local mirror).
2
- //
3
- // Why three-way: plain rsync-style pull/push silently clobbers whichever side
4
- // changed while the other side was edited. We keep a per-mirror snapshot of the
5
- // last-synced remote state (`.dsh-remote-sync-state.json`: relPath →
6
- // {mtime,size}) and compare remote (R), local (L) and last-synced (S):
7
- //
8
- // R==S && L==S → unchanged, skip
9
- // R!=S && L==S → remote changed → pull it (or push when pushing)
10
- // R==S && L!=S → local changed, remote untouched
11
- // • pull (rw_sync): would clobber local edit → conflict
12
- // • push (rw_push): normal upload
13
- // R!=S && L!=S && R==L → both changed to the same bytes → skip
14
- // R!=S && L!=S && R!=L → BOTH changed differently → conflict (never clobber)
15
- //
16
- // `force: true` downgrades every conflict to a plain overwrite (rsync behavior).
17
- // `dryRun: true` computes the full plan without writing anything.
18
- // After a successful pull the local mtime is aligned to the remote mtime, and
19
- // after a successful push the local mtime is aligned to the remote mtime the
20
- // server assigned — so the invariant L==R==S holds and the next run is cheap.
21
- import { readdirSync, statSync, mkdirSync, existsSync, readFileSync, writeFileSync, utimesSync, renameSync } from 'node:fs'
22
- import path from 'node:path'
23
- import { joinRemotePath, relPathUnder } from './paths.js'
24
-
25
- function mapLimit(items, limit, fn) {
26
- const limitN = Math.max(1, Math.min(Math.floor(limit) || 1, items.length))
27
- let next = 0
28
- const worker = async () => {
29
- while (next < items.length) {
30
- const i = next++
31
- try { await fn(items[i], i) } catch { /* per-item errors are the fn's concern */ }
32
- }
33
- }
34
- const workers = []
35
- for (let w = 0; w < limitN; w++) workers.push(worker())
36
- return Promise.all(workers)
37
- }
38
-
39
- const keyOf = (size, mtime) => `${size}:${Math.floor(mtime || 0)}`
40
- const localKey = (st) => (st ? keyOf(st.size, st.mtimeMs / 1000) : null)
41
- const remoteKey = (st) => (st ? keyOf(st.size, st.mtime) : null)
42
-
43
- /** Recursively pull remote → local mirror (three-way, ignore-aware). */
44
- export async function syncTree(sftp, remoteDir, localDir, opts = {}) {
45
- const {
46
- maxDepth = 8,
47
- maxFiles = 2000,
48
- maxFileBytes = 0,
49
- isIgnored = () => false,
50
- dryRun = false,
51
- force = false,
52
- state = {},
53
- } = opts
54
- const stats = { files: 0, dirs: 0, skippedUnchanged: 0, skippedLarge: 0, conflicts: [], staleRemote: 0, touched: [], truncated: false }
55
- const nextState = { ...state }
56
-
57
- const walk = async (rDir, lDir, depth) => {
58
- const entries = await sftp.readdir(rDir).then((l) => l || [], () => [])
59
- // Directories first (sequential, depth-bounded) so every file has a home.
60
- for (const e of entries) {
61
- const name = String(e.filename)
62
- if (name === '.' || name === '..') continue
63
- const isDir = !!(e.attrs && e.attrs.isDirectory && e.attrs.isDirectory())
64
- if (!isDir) continue
65
- if (depth <= 0 || stats.files >= maxFiles) {
66
- stats.truncated = true
67
- continue
68
- }
69
- const rp = joinRemotePath(rDir, name)
70
- const lp = path.join(lDir, name)
71
- const rel = relPathUnder(remoteDir, rp) || name
72
- if (isIgnored(rel, true)) continue
73
- if (!existsSync(lp)) mkdirSync(lp, { recursive: true })
74
- await walk(rp, lp, depth - 1)
75
- stats.dirs++
76
- if (stats.files >= maxFiles) break
77
- }
78
- if (stats.files >= maxFiles) { stats.truncated = true; return }
79
-
80
- const files = entries.filter(
81
- (e) => !(e.attrs && e.attrs.isDirectory && e.attrs.isDirectory()) &&
82
- String(e.filename) !== '.' && String(e.filename) !== '..',
83
- )
84
- await mapLimit(files, 4, async (e) => {
85
- if (stats.files >= maxFiles) { stats.truncated = true; return }
86
- const name = String(e.filename)
87
- const rp = joinRemotePath(rDir, name)
88
- const lp = path.join(lDir, name)
89
- const rel = relPathUnder(remoteDir, rp) || name
90
- if (isIgnored(rel, false)) return
91
- let r
92
- try { r = await sftp.stat(rp) } catch { return }
93
- if (maxFileBytes > 0 && r.size > maxFileBytes) { stats.skippedLarge++; return }
94
- const rk = remoteKey(r)
95
- const sk = state[rel] ? keyOf(state[rel].size, state[rel].mtime) : null
96
- let lk = null
97
- if (existsSync(lp)) {
98
- try { lk = localKey(statSync(lp)) } catch { lk = null }
99
- }
100
- if (sk && rk === sk && lk && lk === sk) { stats.skippedUnchanged++; return }
101
- if (!force && sk && rk !== sk && lk && lk !== sk && rk !== lk) {
102
- stats.conflicts.push({ path: rp, reason: 'both-modified(远端与本地都改过,跳过不覆盖)' })
103
- return
104
- }
105
- if (!force && sk && rk === sk && lk && lk !== sk) {
106
- // Pull would clobber a local edit; the remote never changed.
107
- stats.conflicts.push({ path: rp, reason: 'local-modified(本地改过而远端未变;如需覆盖请用 force / rw_push)' })
108
- return
109
- }
110
- if (dryRun) {
111
- stats.files++
112
- stats.touched.push(rp)
113
- nextState[rel] = { size: r.size, mtime: r.mtime }
114
- return
115
- }
116
- try {
117
- const buf = await sftp.readFile(rp)
118
- writeFileSync(lp, buf)
119
- try { utimesSync(lp, new Date(r.mtime * 1000), new Date(r.mtime * 1000)) } catch {}
120
- stats.files++
121
- stats.touched.push(rp)
122
- nextState[rel] = { size: r.size, mtime: r.mtime }
123
- } catch { /* skip unreadable */ }
124
- })
125
- }
126
-
127
- await walk(remoteDir, localDir, maxDepth)
128
- if (stats.files >= maxFiles) stats.truncated = true
129
- // Informational: remote entries in the snapshot that no longer exist remotely.
130
- for (const rel of Object.keys(state)) {
131
- if (state[rel] && !nextState[rel]) stats.staleRemote++
132
- }
133
- return { stats, nextState }
134
- }
135
-
136
- /** Recursively push local mirror → remote (three-way, ignore-aware). */
137
- export async function pushTree(sftp, localDir, remoteDir, opts = {}) {
138
- const {
139
- maxFiles = 2000,
140
- maxFileBytes = 0,
141
- isIgnored = () => false,
142
- dryRun = false,
143
- force = false,
144
- state = {},
145
- } = opts
146
- const stats = { files: 0, dirs: 0, skippedUnchanged: 0, skippedLarge: 0, conflicts: [], staleLocal: 0, pushed: [], truncated: false }
147
- const nextState = { ...state }
148
-
149
- const walk = async (lDir, rDir) => {
150
- const entries = readdirSync(lDir, { withFileTypes: true }).filter(
151
- (e) => e.name !== '.dsh-remote-meta.json' && e.name !== '.dsh-remote-sync-state.json',
152
- )
153
- for (const e of entries) {
154
- if (!e.isDirectory()) continue
155
- if (stats.files >= maxFiles) break
156
- const rp = joinRemotePath(rDir, e.name)
157
- const rel = relPathUnder(remoteDir, rp) || e.name
158
- if (isIgnored(rel, true)) continue
159
- try { await sftp.mkdir(rp) } catch { /* already exists */ }
160
- stats.dirs++
161
- await walk(path.join(lDir, e.name), rp)
162
- if (stats.files >= maxFiles) break
163
- }
164
- if (stats.files >= maxFiles) { stats.truncated = true; return }
165
-
166
- const files = entries.filter((e) => !e.isDirectory())
167
- await mapLimit(files, 4, async (e) => {
168
- if (stats.files >= maxFiles) { stats.truncated = true; return }
169
- const lp = path.join(lDir, e.name)
170
- const rp = joinRemotePath(rDir, e.name)
171
- const rel = relPathUnder(remoteDir, rp) || e.name
172
- if (isIgnored(rel, false)) return
173
- let l
174
- try { l = statSync(lp) } catch { return }
175
- if (maxFileBytes > 0 && l.size > maxFileBytes) { stats.skippedLarge++; return }
176
- const lk = localKey(l)
177
- let r = null
178
- try { r = await sftp.stat(rp) } catch { /* absent → upload */ }
179
- const rk = remoteKey(r)
180
- const sk = state[rel] ? keyOf(state[rel].size, state[rel].mtime) : null
181
- if (r && sk && lk === sk && rk === sk) { stats.skippedUnchanged++; return }
182
- if (r && !force && sk && rk !== sk && lk !== sk && rk !== lk) {
183
- stats.conflicts.push({ path: rp, reason: 'both-modified(远端与本地都改过,跳过不覆盖)' })
184
- return
185
- }
186
- if (r && !force && sk && rk !== sk && lk === sk) {
187
- stats.conflicts.push({ path: rp, reason: 'remote-modified(远端改过而本地未变;如需覆盖请用 force / 先 rw_sync)' })
188
- return
189
- }
190
- if (r && !force && sk && rk === sk && lk && lk !== sk) {
191
- // Normal case: local edited since sync, remote untouched → push.
192
- }
193
- if (dryRun) {
194
- stats.files++
195
- stats.pushed.push(rp)
196
- return
197
- }
198
- try {
199
- const buf = readFileSync(lp)
200
- await sftp.writeFile(rp, buf)
201
- // Align local mtime to whatever the remote server assigned, so the
202
- // L==R==S invariant holds for the next run.
203
- let r2 = null
204
- try { r2 = await sftp.stat(rp) } catch {}
205
- const rmt = r2 ? r2.mtime : Math.floor(Date.now() / 1000)
206
- try { utimesSync(lp, new Date(rmt * 1000), new Date(rmt * 1000)) } catch {}
207
- stats.files++
208
- stats.pushed.push(rp)
209
- nextState[rel] = { size: l.size, mtime: rmt }
210
- } catch { /* skip unwritable */ }
211
- })
212
- }
213
-
214
- await walk(localDir, remoteDir)
215
- if (stats.files >= maxFiles) stats.truncated = true
216
- for (const rel of Object.keys(state)) {
217
- if (state[rel] && !nextState[rel]) stats.staleLocal++
218
- }
219
- return { stats, nextState }
220
- }
221
-
222
- /** Load the sync-state snapshot from a mirror dir (or {}). */
223
- export function loadSyncState(localDir) {
224
- try {
225
- const j = JSON.parse(readFileSync(path.join(localDir, '.dsh-remote-sync-state.json'), 'utf8'))
226
- if (j && typeof j === 'object' && !Array.isArray(j)) return j
227
- } catch {}
228
- return {}
229
- }
230
-
231
- /** Persist the snapshot (atomic-ish: tmp + rename). */
232
- export function saveSyncState(localDir, state) {
233
- try {
234
- mkdirSync(localDir, { recursive: true })
235
- const tmp = path.join(localDir, '.dsh-remote-sync-state.json.tmp')
236
- writeFileSync(tmp, JSON.stringify(state, null, 1))
237
- renameSync(tmp, path.join(localDir, '.dsh-remote-sync-state.json'))
238
- } catch { /* best effort */ }
239
- }
240
-
241
- /** Push ONE local mirror file back to the remote (used by auto-push).
242
- * Same three-way guard as pushTree: never clobbers a remote change. Returns
243
- * { status: 'pushed'|'unchanged'|'conflict'|'skipped'|'missing' } and the
244
- * updated state delta ({rel: {size, mtime}} when pushed). */
245
- export async function pushOneFile(sftp, localDir, remoteDir, rel, opts = {}) {
246
- const { maxFileBytes = 0, isIgnored = () => false, force = false, state = {} } = opts
247
- if (isIgnored(rel, false)) return { status: 'skipped' }
248
- const lp = path.join(localDir, rel.replace(/\//g, path.sep))
249
- const rp = joinRemotePath(remoteDir, rel)
250
- let l
251
- try { l = statSync(lp) } catch { return { status: 'missing' } }
252
- if (maxFileBytes > 0 && l.size > maxFileBytes) return { status: 'skipped' }
253
- const lk = localKey(l)
254
- let r = null
255
- try { r = await sftp.stat(rp) } catch { /* absent → upload */ }
256
- const rk = remoteKey(r)
257
- const sk = state[rel] ? keyOf(state[rel].size, state[rel].mtime) : null
258
- if (r && sk && lk === sk && rk === sk) return { status: 'unchanged' }
259
- if (r && !force && sk && rk !== sk && lk !== sk && rk !== lk) {
260
- return { status: 'conflict', reason: 'both-modified' }
261
- }
262
- if (r && !force && sk && rk !== sk && lk === sk) {
263
- return { status: 'conflict', reason: 'remote-modified' }
264
- }
265
- try {
266
- await sftp.writeFile(rp, readFileSync(lp))
267
- let r2 = null
268
- try { r2 = await sftp.stat(rp) } catch {}
269
- const rmt = r2 ? r2.mtime : Math.floor(Date.now() / 1000)
270
- try { utimesSync(lp, new Date(rmt * 1000), new Date(rmt * 1000)) } catch {}
271
- return { status: 'pushed', state: { [rel]: { size: l.size, mtime: rmt } } }
272
- } catch (e) {
273
- return { status: 'skipped', error: String((e && e.message) || e) }
274
- }
275
- }
1
+ // dsh-remote — three-way conflict-aware mirror sync (remote ⇄ local mirror).
2
+ //
3
+ // Why three-way: plain rsync-style pull/push silently clobbers whichever side
4
+ // changed while the other side was edited. We keep a per-mirror snapshot of the
5
+ // last-synced remote state (`.dsh-remote-sync-state.json`: relPath →
6
+ // {mtime,size}) and compare remote (R), local (L) and last-synced (S):
7
+ //
8
+ // R==S && L==S → unchanged, skip
9
+ // R!=S && L==S → remote changed → pull it (or push when pushing)
10
+ // R==S && L!=S → local changed, remote untouched
11
+ // • pull (rw_sync): would clobber local edit → conflict
12
+ // • push (rw_push): normal upload
13
+ // R!=S && L!=S && R==L → both changed to the same bytes → skip
14
+ // R!=S && L!=S && R!=L → BOTH changed differently → conflict (never clobber)
15
+ //
16
+ // `force: true` downgrades every conflict to a plain overwrite (rsync behavior).
17
+ // `dryRun: true` computes the full plan without writing anything.
18
+ // After a successful pull the local mtime is aligned to the remote mtime, and
19
+ // after a successful push the local mtime is aligned to the remote mtime the
20
+ // server assigned — so the invariant L==R==S holds and the next run is cheap.
21
+ import { readdirSync, statSync, mkdirSync, existsSync, readFileSync, writeFileSync, utimesSync, renameSync } from 'node:fs'
22
+ import path from 'node:path'
23
+ import { joinRemotePath, relPathUnder } from './paths.js'
24
+
25
+ function mapLimit(items, limit, fn) {
26
+ const limitN = Math.max(1, Math.min(Math.floor(limit) || 1, items.length))
27
+ let next = 0
28
+ const worker = async () => {
29
+ while (next < items.length) {
30
+ const i = next++
31
+ try { await fn(items[i], i) } catch { /* per-item errors are the fn's concern */ }
32
+ }
33
+ }
34
+ const workers = []
35
+ for (let w = 0; w < limitN; w++) workers.push(worker())
36
+ return Promise.all(workers)
37
+ }
38
+
39
+ const keyOf = (size, mtime) => `${size}:${Math.floor(mtime || 0)}`
40
+ const localKey = (st) => (st ? keyOf(st.size, st.mtimeMs / 1000) : null)
41
+ const remoteKey = (st) => (st ? keyOf(st.size, st.mtime) : null)
42
+
43
+ /** Recursively pull remote → local mirror (three-way, ignore-aware). */
44
+ export async function syncTree(sftp, remoteDir, localDir, opts = {}) {
45
+ const {
46
+ maxDepth = 8,
47
+ maxFiles = 2000,
48
+ maxFileBytes = 0,
49
+ isIgnored = () => false,
50
+ dryRun = false,
51
+ force = false,
52
+ state = {},
53
+ } = opts
54
+ const stats = { files: 0, dirs: 0, skippedUnchanged: 0, skippedLarge: 0, conflicts: [], staleRemote: 0, touched: [], truncated: false }
55
+ const nextState = { ...state }
56
+
57
+ const walk = async (rDir, lDir, depth) => {
58
+ const entries = await sftp.readdir(rDir).then((l) => l || [], () => [])
59
+ // Directories first (sequential, depth-bounded) so every file has a home.
60
+ for (const e of entries) {
61
+ const name = String(e.filename)
62
+ if (name === '.' || name === '..') continue
63
+ const isDir = !!(e.attrs && e.attrs.isDirectory && e.attrs.isDirectory())
64
+ if (!isDir) continue
65
+ if (depth <= 0 || stats.files >= maxFiles) {
66
+ stats.truncated = true
67
+ continue
68
+ }
69
+ const rp = joinRemotePath(rDir, name)
70
+ const lp = path.join(lDir, name)
71
+ const rel = relPathUnder(remoteDir, rp) || name
72
+ if (isIgnored(rel, true)) continue
73
+ if (!existsSync(lp)) mkdirSync(lp, { recursive: true })
74
+ await walk(rp, lp, depth - 1)
75
+ stats.dirs++
76
+ if (stats.files >= maxFiles) break
77
+ }
78
+ if (stats.files >= maxFiles) { stats.truncated = true; return }
79
+
80
+ const files = entries.filter(
81
+ (e) => !(e.attrs && e.attrs.isDirectory && e.attrs.isDirectory()) &&
82
+ String(e.filename) !== '.' && String(e.filename) !== '..',
83
+ )
84
+ await mapLimit(files, 4, async (e) => {
85
+ if (stats.files >= maxFiles) { stats.truncated = true; return }
86
+ const name = String(e.filename)
87
+ const rp = joinRemotePath(rDir, name)
88
+ const lp = path.join(lDir, name)
89
+ const rel = relPathUnder(remoteDir, rp) || name
90
+ if (isIgnored(rel, false)) return
91
+ let r
92
+ try { r = await sftp.stat(rp) } catch { return }
93
+ if (maxFileBytes > 0 && r.size > maxFileBytes) { stats.skippedLarge++; return }
94
+ const rk = remoteKey(r)
95
+ const sk = state[rel] ? keyOf(state[rel].size, state[rel].mtime) : null
96
+ let lk = null
97
+ if (existsSync(lp)) {
98
+ try { lk = localKey(statSync(lp)) } catch { lk = null }
99
+ }
100
+ if (sk && rk === sk && lk && lk === sk) { stats.skippedUnchanged++; return }
101
+ if (!force && sk && rk !== sk && lk && lk !== sk && rk !== lk) {
102
+ stats.conflicts.push({ path: rp, reason: 'both-modified(远端与本地都改过,跳过不覆盖)' })
103
+ return
104
+ }
105
+ if (!force && sk && rk === sk && lk && lk !== sk) {
106
+ // Pull would clobber a local edit; the remote never changed.
107
+ stats.conflicts.push({ path: rp, reason: 'local-modified(本地改过而远端未变;如需覆盖请用 force / rw_push)' })
108
+ return
109
+ }
110
+ if (dryRun) {
111
+ stats.files++
112
+ stats.touched.push(rp)
113
+ nextState[rel] = { size: r.size, mtime: r.mtime }
114
+ return
115
+ }
116
+ try {
117
+ const buf = await sftp.readFile(rp)
118
+ writeFileSync(lp, buf)
119
+ try { utimesSync(lp, new Date(r.mtime * 1000), new Date(r.mtime * 1000)) } catch {}
120
+ stats.files++
121
+ stats.touched.push(rp)
122
+ nextState[rel] = { size: r.size, mtime: r.mtime }
123
+ } catch { /* skip unreadable */ }
124
+ })
125
+ }
126
+
127
+ await walk(remoteDir, localDir, maxDepth)
128
+ if (stats.files >= maxFiles) stats.truncated = true
129
+ // Informational: remote entries in the snapshot that no longer exist remotely.
130
+ for (const rel of Object.keys(state)) {
131
+ if (state[rel] && !nextState[rel]) stats.staleRemote++
132
+ }
133
+ return { stats, nextState }
134
+ }
135
+
136
+ /** Recursively push local mirror → remote (three-way, ignore-aware). */
137
+ export async function pushTree(sftp, localDir, remoteDir, opts = {}) {
138
+ const {
139
+ maxFiles = 2000,
140
+ maxFileBytes = 0,
141
+ isIgnored = () => false,
142
+ dryRun = false,
143
+ force = false,
144
+ state = {},
145
+ } = opts
146
+ const stats = { files: 0, dirs: 0, skippedUnchanged: 0, skippedLarge: 0, conflicts: [], staleLocal: 0, pushed: [], truncated: false }
147
+ const nextState = { ...state }
148
+
149
+ const walk = async (lDir, rDir) => {
150
+ const entries = readdirSync(lDir, { withFileTypes: true }).filter(
151
+ (e) => e.name !== '.dsh-remote-meta.json' && e.name !== '.dsh-remote-sync-state.json',
152
+ )
153
+ for (const e of entries) {
154
+ if (!e.isDirectory()) continue
155
+ if (stats.files >= maxFiles) break
156
+ const rp = joinRemotePath(rDir, e.name)
157
+ const rel = relPathUnder(remoteDir, rp) || e.name
158
+ if (isIgnored(rel, true)) continue
159
+ try { await sftp.mkdir(rp) } catch { /* already exists */ }
160
+ stats.dirs++
161
+ await walk(path.join(lDir, e.name), rp)
162
+ if (stats.files >= maxFiles) break
163
+ }
164
+ if (stats.files >= maxFiles) { stats.truncated = true; return }
165
+
166
+ const files = entries.filter((e) => !e.isDirectory())
167
+ await mapLimit(files, 4, async (e) => {
168
+ if (stats.files >= maxFiles) { stats.truncated = true; return }
169
+ const lp = path.join(lDir, e.name)
170
+ const rp = joinRemotePath(rDir, e.name)
171
+ const rel = relPathUnder(remoteDir, rp) || e.name
172
+ if (isIgnored(rel, false)) return
173
+ let l
174
+ try { l = statSync(lp) } catch { return }
175
+ if (maxFileBytes > 0 && l.size > maxFileBytes) { stats.skippedLarge++; return }
176
+ const lk = localKey(l)
177
+ let r = null
178
+ try { r = await sftp.stat(rp) } catch { /* absent → upload */ }
179
+ const rk = remoteKey(r)
180
+ const sk = state[rel] ? keyOf(state[rel].size, state[rel].mtime) : null
181
+ if (r && sk && lk === sk && rk === sk) { stats.skippedUnchanged++; return }
182
+ if (r && !force && sk && rk !== sk && lk !== sk && rk !== lk) {
183
+ stats.conflicts.push({ path: rp, reason: 'both-modified(远端与本地都改过,跳过不覆盖)' })
184
+ return
185
+ }
186
+ if (r && !force && sk && rk !== sk && lk === sk) {
187
+ stats.conflicts.push({ path: rp, reason: 'remote-modified(远端改过而本地未变;如需覆盖请用 force / 先 rw_sync)' })
188
+ return
189
+ }
190
+ if (r && !force && sk && rk === sk && lk && lk !== sk) {
191
+ // Normal case: local edited since sync, remote untouched → push.
192
+ }
193
+ if (dryRun) {
194
+ stats.files++
195
+ stats.pushed.push(rp)
196
+ return
197
+ }
198
+ try {
199
+ const buf = readFileSync(lp)
200
+ await sftp.writeFile(rp, buf)
201
+ // Align local mtime to whatever the remote server assigned, so the
202
+ // L==R==S invariant holds for the next run.
203
+ let r2 = null
204
+ try { r2 = await sftp.stat(rp) } catch {}
205
+ const rmt = r2 ? r2.mtime : Math.floor(Date.now() / 1000)
206
+ try { utimesSync(lp, new Date(rmt * 1000), new Date(rmt * 1000)) } catch {}
207
+ stats.files++
208
+ stats.pushed.push(rp)
209
+ nextState[rel] = { size: l.size, mtime: rmt }
210
+ } catch { /* skip unwritable */ }
211
+ })
212
+ }
213
+
214
+ await walk(localDir, remoteDir)
215
+ if (stats.files >= maxFiles) stats.truncated = true
216
+ for (const rel of Object.keys(state)) {
217
+ if (state[rel] && !nextState[rel]) stats.staleLocal++
218
+ }
219
+ return { stats, nextState }
220
+ }
221
+
222
+ /** Load the sync-state snapshot from a mirror dir (or {}). */
223
+ export function loadSyncState(localDir) {
224
+ try {
225
+ const j = JSON.parse(readFileSync(path.join(localDir, '.dsh-remote-sync-state.json'), 'utf8'))
226
+ if (j && typeof j === 'object' && !Array.isArray(j)) return j
227
+ } catch {}
228
+ return {}
229
+ }
230
+
231
+ /** Persist the snapshot (atomic-ish: tmp + rename). */
232
+ export function saveSyncState(localDir, state) {
233
+ try {
234
+ mkdirSync(localDir, { recursive: true })
235
+ const tmp = path.join(localDir, '.dsh-remote-sync-state.json.tmp')
236
+ writeFileSync(tmp, JSON.stringify(state, null, 1))
237
+ renameSync(tmp, path.join(localDir, '.dsh-remote-sync-state.json'))
238
+ } catch { /* best effort */ }
239
+ }
240
+
241
+ /** Push ONE local mirror file back to the remote (used by auto-push).
242
+ * Same three-way guard as pushTree: never clobbers a remote change. Returns
243
+ * { status: 'pushed'|'unchanged'|'conflict'|'skipped'|'missing' } and the
244
+ * updated state delta ({rel: {size, mtime}} when pushed). */
245
+ export async function pushOneFile(sftp, localDir, remoteDir, rel, opts = {}) {
246
+ const { maxFileBytes = 0, isIgnored = () => false, force = false, state = {} } = opts
247
+ if (isIgnored(rel, false)) return { status: 'skipped' }
248
+ const lp = path.join(localDir, rel.replace(/\//g, path.sep))
249
+ const rp = joinRemotePath(remoteDir, rel)
250
+ let l
251
+ try { l = statSync(lp) } catch { return { status: 'missing' } }
252
+ if (maxFileBytes > 0 && l.size > maxFileBytes) return { status: 'skipped' }
253
+ const lk = localKey(l)
254
+ let r = null
255
+ try { r = await sftp.stat(rp) } catch { /* absent → upload */ }
256
+ const rk = remoteKey(r)
257
+ const sk = state[rel] ? keyOf(state[rel].size, state[rel].mtime) : null
258
+ if (r && sk && lk === sk && rk === sk) return { status: 'unchanged' }
259
+ if (r && !force && sk && rk !== sk && lk !== sk && rk !== lk) {
260
+ return { status: 'conflict', reason: 'both-modified' }
261
+ }
262
+ if (r && !force && sk && rk !== sk && lk === sk) {
263
+ return { status: 'conflict', reason: 'remote-modified' }
264
+ }
265
+ try {
266
+ await sftp.writeFile(rp, readFileSync(lp))
267
+ let r2 = null
268
+ try { r2 = await sftp.stat(rp) } catch {}
269
+ const rmt = r2 ? r2.mtime : Math.floor(Date.now() / 1000)
270
+ try { utimesSync(lp, new Date(rmt * 1000), new Date(rmt * 1000)) } catch {}
271
+ return { status: 'pushed', state: { [rel]: { size: l.size, mtime: rmt } } }
272
+ } catch (e) {
273
+ return { status: 'skipped', error: String((e && e.message) || e) }
274
+ }
275
+ }