dsh-remote 0.8.45 → 0.8.47
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/LICENSE +21 -21
- package/README.en.md +361 -361
- package/README.md +368 -368
- package/cordis.patch.yml +9 -9
- package/lib/binding.js +172 -172
- package/lib/client.js +3637 -3623
- package/lib/credential.js +177 -177
- package/lib/deploy-tasks.js +142 -142
- package/lib/errors.js +87 -87
- package/lib/file-reference.js +457 -457
- package/lib/forwards.js +258 -258
- package/lib/hostkey.js +108 -108
- package/lib/http-transport.js +196 -196
- package/lib/ignore.js +156 -156
- package/lib/index.js +3771 -3747
- package/lib/openssh.js +838 -838
- package/lib/paths.js +225 -225
- package/lib/pool.js +757 -754
- package/lib/registry.js +95 -95
- package/lib/remote-fs.js +148 -143
- package/lib/routes-fs.js +233 -233
- package/lib/search.js +230 -230
- package/lib/sftp-surface.js +76 -76
- package/lib/sshconfig.js +454 -454
- package/lib/sync.js +275 -275
- package/lib/tasks.js +95 -95
- package/lib/telemetry.js +213 -213
- package/lib/update.js +472 -472
- package/lib/web-attach.js +850 -846
- package/lib/web-deploy-skill.js +205 -205
- package/lib/web-deploy.js +694 -694
- package/node_modules/asn1/Jenkinsfile +65 -0
- package/node_modules/asn1/LICENSE +19 -0
- package/node_modules/asn1/README.md +50 -0
- package/node_modules/asn1/lib/ber/errors.js +13 -0
- package/node_modules/asn1/lib/ber/index.js +27 -0
- package/node_modules/asn1/lib/ber/reader.js +262 -0
- package/node_modules/asn1/lib/ber/types.js +36 -0
- package/node_modules/asn1/lib/ber/writer.js +317 -0
- package/node_modules/asn1/lib/index.js +20 -0
- package/node_modules/asn1/package.json +31 -0
- package/node_modules/bcrypt-pbkdf/CONTRIBUTING.md +13 -0
- package/node_modules/bcrypt-pbkdf/LICENSE +66 -0
- package/node_modules/bcrypt-pbkdf/README.md +45 -0
- package/node_modules/bcrypt-pbkdf/index.js +556 -0
- package/node_modules/bcrypt-pbkdf/package.json +15 -0
- package/node_modules/buildcheck/.eslintrc.js +5 -0
- package/node_modules/buildcheck/.github/workflows/ci.yml +88 -0
- package/node_modules/buildcheck/.github/workflows/lint.yml +27 -0
- package/node_modules/buildcheck/.npmignore +3 -0
- package/node_modules/buildcheck/LICENSE +19 -0
- package/node_modules/buildcheck/README.md +106 -0
- package/node_modules/buildcheck/deps/Find-VisualStudio.cs +250 -0
- package/node_modules/buildcheck/lib/findvs.js +361 -0
- package/node_modules/buildcheck/lib/index.js +738 -0
- package/node_modules/buildcheck/package.json +36 -0
- package/node_modules/buildcheck/test/test.js +8 -0
- package/node_modules/cpu-features/.eslintrc.js +5 -0
- package/node_modules/cpu-features/.github/workflows/ci.yml +74 -0
- package/node_modules/cpu-features/.github/workflows/lint.yml +27 -0
- package/node_modules/cpu-features/LICENSE +19 -0
- package/node_modules/cpu-features/README.md +59 -0
- package/node_modules/cpu-features/binding.gyp +16 -0
- package/node_modules/cpu-features/buildcheck.js +32 -0
- package/node_modules/cpu-features/deps/cpu_features/.clang-format +4 -0
- package/node_modules/cpu-features/deps/cpu_features/.dockerignore +31 -0
- package/node_modules/cpu-features/deps/cpu_features/.github/workflows/Dockerfile +5 -0
- package/node_modules/cpu-features/deps/cpu_features/.github/workflows/aarch64_linux_cmake.yml +30 -0
- package/node_modules/cpu-features/deps/cpu_features/.github/workflows/amd64_freebsd_cmake.yml +22 -0
- package/node_modules/cpu-features/deps/cpu_features/.github/workflows/amd64_linux_bazel.yml +26 -0
- package/node_modules/cpu-features/deps/cpu_features/.github/workflows/amd64_linux_cmake.yml +31 -0
- package/node_modules/cpu-features/deps/cpu_features/.github/workflows/amd64_macos_cmake.yml +43 -0
- package/node_modules/cpu-features/deps/cpu_features/.github/workflows/amd64_windows_cmake.yml +25 -0
- package/node_modules/cpu-features/deps/cpu_features/.github/workflows/arm_linux_cmake.yml +31 -0
- package/node_modules/cpu-features/deps/cpu_features/.github/workflows/clang_format.yml +24 -0
- package/node_modules/cpu-features/deps/cpu_features/.github/workflows/mips_linux_cmake.yml +30 -0
- package/node_modules/cpu-features/deps/cpu_features/.github/workflows/power_linux_cmake.yml +29 -0
- package/node_modules/cpu-features/deps/cpu_features/.github/workflows/riscv_linux_cmake.yml +28 -0
- package/node_modules/cpu-features/deps/cpu_features/.github/workflows/s390x_linux_cmake.yml +27 -0
- package/node_modules/cpu-features/deps/cpu_features/.grenrc.yml +21 -0
- package/node_modules/cpu-features/deps/cpu_features/BUILD.bazel +329 -0
- package/node_modules/cpu-features/deps/cpu_features/CMakeLists.txt +261 -0
- package/node_modules/cpu-features/deps/cpu_features/CONTRIBUTING.md +23 -0
- package/node_modules/cpu-features/deps/cpu_features/LICENSE +230 -0
- package/node_modules/cpu-features/deps/cpu_features/README.md +272 -0
- package/node_modules/cpu-features/deps/cpu_features/WORKSPACE +19 -0
- package/node_modules/cpu-features/deps/cpu_features/bazel/ci/README.md +5 -0
- package/node_modules/cpu-features/deps/cpu_features/bazel/platforms.bzl +11 -0
- package/node_modules/cpu-features/deps/cpu_features/cmake/CpuFeaturesConfig.cmake.in +3 -0
- package/node_modules/cpu-features/deps/cpu_features/cmake/CpuFeaturesNdkCompatConfig.cmake.in +3 -0
- package/node_modules/cpu-features/deps/cpu_features/cmake/README.md +30 -0
- package/node_modules/cpu-features/deps/cpu_features/cmake/ci/Makefile +252 -0
- package/node_modules/cpu-features/deps/cpu_features/cmake/ci/README.md +40 -0
- package/node_modules/cpu-features/deps/cpu_features/cmake/ci/doc/docker.dot +64 -0
- package/node_modules/cpu-features/deps/cpu_features/cmake/ci/doc/docker.svg +312 -0
- package/node_modules/cpu-features/deps/cpu_features/cmake/ci/doc/generate_image.sh +7 -0
- package/node_modules/cpu-features/deps/cpu_features/cmake/ci/docker/amd64/Dockerfile +48 -0
- package/node_modules/cpu-features/deps/cpu_features/cmake/ci/docker/toolchain/Dockerfile +34 -0
- package/node_modules/cpu-features/deps/cpu_features/cmake/ci/sample/CMakeLists.txt +22 -0
- package/node_modules/cpu-features/deps/cpu_features/cmake/ci/sample/main.cpp +11 -0
- package/node_modules/cpu-features/deps/cpu_features/cmake/ci/vagrant/freebsd/Vagrantfile +107 -0
- package/node_modules/cpu-features/deps/cpu_features/cmake/googletest.CMakeLists.txt.in +15 -0
- package/node_modules/cpu-features/deps/cpu_features/cpu_features.gyp +105 -0
- package/node_modules/cpu-features/deps/cpu_features/include/cpu_features_cache_info.h +54 -0
- package/node_modules/cpu-features/deps/cpu_features/include/cpu_features_macros.h +384 -0
- package/node_modules/cpu-features/deps/cpu_features/include/cpuinfo_aarch64.h +259 -0
- package/node_modules/cpu-features/deps/cpu_features/include/cpuinfo_arm.h +121 -0
- package/node_modules/cpu-features/deps/cpu_features/include/cpuinfo_mips.h +74 -0
- package/node_modules/cpu-features/deps/cpu_features/include/cpuinfo_ppc.h +149 -0
- package/node_modules/cpu-features/deps/cpu_features/include/cpuinfo_riscv.h +72 -0
- package/node_modules/cpu-features/deps/cpu_features/include/cpuinfo_s390x.h +108 -0
- package/node_modules/cpu-features/deps/cpu_features/include/cpuinfo_x86.h +288 -0
- package/node_modules/cpu-features/deps/cpu_features/include/internal/bit_utils.h +40 -0
- package/node_modules/cpu-features/deps/cpu_features/include/internal/cpuid_x86.h +37 -0
- package/node_modules/cpu-features/deps/cpu_features/include/internal/filesystem.h +39 -0
- package/node_modules/cpu-features/deps/cpu_features/include/internal/hwcaps.h +240 -0
- package/node_modules/cpu-features/deps/cpu_features/include/internal/stack_line_reader.h +49 -0
- package/node_modules/cpu-features/deps/cpu_features/include/internal/string_view.h +110 -0
- package/node_modules/cpu-features/deps/cpu_features/include/internal/windows_utils.h +70 -0
- package/node_modules/cpu-features/deps/cpu_features/ndk_compat/CMakeLists.txt +60 -0
- package/node_modules/cpu-features/deps/cpu_features/ndk_compat/README.md +4 -0
- package/node_modules/cpu-features/deps/cpu_features/ndk_compat/cpu-features.c +205 -0
- package/node_modules/cpu-features/deps/cpu_features/ndk_compat/cpu-features.h +320 -0
- package/node_modules/cpu-features/deps/cpu_features/ndk_compat/ndk-compat-test.c +12 -0
- package/node_modules/cpu-features/deps/cpu_features/patches/0001-Add-Apple-Silicon-Support.patch +456 -0
- package/node_modules/cpu-features/deps/cpu_features/scripts/generate_badges.d +165 -0
- package/node_modules/cpu-features/deps/cpu_features/scripts/make_release.sh +75 -0
- package/node_modules/cpu-features/deps/cpu_features/scripts/run_integration.sh +490 -0
- package/node_modules/cpu-features/deps/cpu_features/scripts/test_integration.sh +79 -0
- package/node_modules/cpu-features/deps/cpu_features/src/copy.inl +19 -0
- package/node_modules/cpu-features/deps/cpu_features/src/define_introspection.inl +86 -0
- package/node_modules/cpu-features/deps/cpu_features/src/define_introspection_and_hwcaps.inl +26 -0
- package/node_modules/cpu-features/deps/cpu_features/src/equals.inl +22 -0
- package/node_modules/cpu-features/deps/cpu_features/src/filesystem.c +62 -0
- package/node_modules/cpu-features/deps/cpu_features/src/hwcaps.c +169 -0
- package/node_modules/cpu-features/deps/cpu_features/src/impl_aarch64__base_implementation.inl +88 -0
- package/node_modules/cpu-features/deps/cpu_features/src/impl_aarch64_linux_or_android.c +79 -0
- package/node_modules/cpu-features/deps/cpu_features/src/impl_aarch64_macos_or_iphone.c +82 -0
- package/node_modules/cpu-features/deps/cpu_features/src/impl_aarch64_windows.c +138 -0
- package/node_modules/cpu-features/deps/cpu_features/src/impl_arm_linux_or_android.c +212 -0
- package/node_modules/cpu-features/deps/cpu_features/src/impl_mips_linux_or_android.c +93 -0
- package/node_modules/cpu-features/deps/cpu_features/src/impl_ppc_linux.c +163 -0
- package/node_modules/cpu-features/deps/cpu_features/src/impl_riscv_linux.c +111 -0
- package/node_modules/cpu-features/deps/cpu_features/src/impl_s390x_linux.c +120 -0
- package/node_modules/cpu-features/deps/cpu_features/src/impl_x86__base_implementation.inl +2055 -0
- package/node_modules/cpu-features/deps/cpu_features/src/impl_x86_freebsd.c +68 -0
- package/node_modules/cpu-features/deps/cpu_features/src/impl_x86_linux_or_android.c +58 -0
- package/node_modules/cpu-features/deps/cpu_features/src/impl_x86_macos.c +57 -0
- package/node_modules/cpu-features/deps/cpu_features/src/impl_x86_windows.c +58 -0
- package/node_modules/cpu-features/deps/cpu_features/src/stack_line_reader.c +132 -0
- package/node_modules/cpu-features/deps/cpu_features/src/string_view.c +192 -0
- package/node_modules/cpu-features/deps/cpu_features/src/utils/list_cpu_features.c +461 -0
- package/node_modules/cpu-features/deps/cpu_features/test/CMakeLists.txt +114 -0
- package/node_modules/cpu-features/deps/cpu_features/test/bit_utils_test.cc +53 -0
- package/node_modules/cpu-features/deps/cpu_features/test/cpuinfo_aarch64_test.cc +396 -0
- package/node_modules/cpu-features/deps/cpu_features/test/cpuinfo_arm_test.cc +384 -0
- package/node_modules/cpu-features/deps/cpu_features/test/cpuinfo_mips_test.cc +176 -0
- package/node_modules/cpu-features/deps/cpu_features/test/cpuinfo_ppc_test.cc +131 -0
- package/node_modules/cpu-features/deps/cpu_features/test/cpuinfo_riscv_test.cc +180 -0
- package/node_modules/cpu-features/deps/cpu_features/test/cpuinfo_s390x_test.cc +82 -0
- package/node_modules/cpu-features/deps/cpu_features/test/cpuinfo_x86_test.cc +1729 -0
- package/node_modules/cpu-features/deps/cpu_features/test/filesystem_for_testing.cc +103 -0
- package/node_modules/cpu-features/deps/cpu_features/test/filesystem_for_testing.h +61 -0
- package/node_modules/cpu-features/deps/cpu_features/test/hwcaps_for_testing.cc +52 -0
- package/node_modules/cpu-features/deps/cpu_features/test/hwcaps_for_testing.h +31 -0
- package/node_modules/cpu-features/deps/cpu_features/test/stack_line_reader_test.cc +132 -0
- package/node_modules/cpu-features/deps/cpu_features/test/string_view_test.cc +202 -0
- package/node_modules/cpu-features/lib/index.js +5 -0
- package/node_modules/cpu-features/package.json +40 -0
- package/node_modules/cpu-features/src/binding.cc +151 -0
- package/node_modules/cpu-features/test/test.js +12 -0
- package/node_modules/nan/.github/workflows/ci.yml +46 -0
- package/node_modules/nan/.pre-commit-config.yaml +8 -0
- package/node_modules/nan/CHANGELOG.md +605 -0
- package/node_modules/nan/CMakeLists.txt +138 -0
- package/node_modules/nan/LICENSE.md +9 -0
- package/node_modules/nan/README.md +457 -0
- package/node_modules/nan/doc/asyncworker.md +146 -0
- package/node_modules/nan/doc/buffers.md +54 -0
- package/node_modules/nan/doc/callback.md +76 -0
- package/node_modules/nan/doc/converters.md +41 -0
- package/node_modules/nan/doc/errors.md +226 -0
- package/node_modules/nan/doc/json.md +62 -0
- package/node_modules/nan/doc/maybe_types.md +583 -0
- package/node_modules/nan/doc/methods.md +689 -0
- package/node_modules/nan/doc/new.md +147 -0
- package/node_modules/nan/doc/node_misc.md +123 -0
- package/node_modules/nan/doc/object_wrappers.md +263 -0
- package/node_modules/nan/doc/persistent.md +296 -0
- package/node_modules/nan/doc/scopes.md +73 -0
- package/node_modules/nan/doc/script.md +58 -0
- package/node_modules/nan/doc/string_bytes.md +81 -0
- package/node_modules/nan/doc/v8_internals.md +211 -0
- package/node_modules/nan/doc/v8_misc.md +85 -0
- package/node_modules/nan/include_dirs.js +1 -0
- package/node_modules/nan/nan.h +3202 -0
- package/node_modules/nan/nan_callbacks.h +141 -0
- package/node_modules/nan/nan_callbacks_12_inl.h +730 -0
- package/node_modules/nan/nan_callbacks_pre_12_inl.h +528 -0
- package/node_modules/nan/nan_converters.h +72 -0
- package/node_modules/nan/nan_converters_43_inl.h +68 -0
- package/node_modules/nan/nan_converters_pre_43_inl.h +42 -0
- package/node_modules/nan/nan_define_own_property_helper.h +29 -0
- package/node_modules/nan/nan_implementation_12_inl.h +446 -0
- package/node_modules/nan/nan_implementation_pre_12_inl.h +263 -0
- package/node_modules/nan/nan_json.h +166 -0
- package/node_modules/nan/nan_maybe_43_inl.h +360 -0
- package/node_modules/nan/nan_maybe_pre_43_inl.h +268 -0
- package/node_modules/nan/nan_new.h +340 -0
- package/node_modules/nan/nan_object_wrap.h +156 -0
- package/node_modules/nan/nan_persistent_12_inl.h +132 -0
- package/node_modules/nan/nan_persistent_pre_12_inl.h +242 -0
- package/node_modules/nan/nan_private.h +73 -0
- package/node_modules/nan/nan_scriptorigin.h +97 -0
- package/node_modules/nan/nan_string_bytes.h +305 -0
- package/node_modules/nan/nan_typedarray_contents.h +96 -0
- package/node_modules/nan/nan_weak.h +453 -0
- package/node_modules/nan/package.json +38 -0
- package/node_modules/nan/tools/1to2.js +412 -0
- package/node_modules/nan/tools/README.md +14 -0
- package/node_modules/nan/tools/package.json +19 -0
- package/node_modules/safer-buffer/LICENSE +21 -0
- package/node_modules/safer-buffer/Porting-Buffer.md +268 -0
- package/node_modules/safer-buffer/Readme.md +156 -0
- package/node_modules/safer-buffer/dangerous.js +58 -0
- package/node_modules/safer-buffer/package.json +34 -0
- package/node_modules/safer-buffer/safer.js +77 -0
- package/node_modules/safer-buffer/tests.js +406 -0
- package/node_modules/ssh2/.eslintignore +4 -0
- package/node_modules/ssh2/.eslintrc.js +5 -0
- package/node_modules/ssh2/.github/workflows/ci.yml +110 -0
- package/node_modules/ssh2/.github/workflows/lint.yml +27 -0
- package/node_modules/ssh2/LICENSE +19 -0
- package/node_modules/ssh2/README.md +1529 -0
- package/node_modules/ssh2/SFTP.md +413 -0
- package/node_modules/ssh2/examples/server-chat.js +238 -0
- package/node_modules/ssh2/examples/sftp-server-download-only.js +134 -0
- package/node_modules/ssh2/install.js +27 -0
- package/node_modules/ssh2/lib/Channel.js +295 -0
- package/node_modules/ssh2/lib/agent.js +1123 -0
- package/node_modules/ssh2/lib/client.js +2176 -0
- package/node_modules/ssh2/lib/http-agents.js +84 -0
- package/node_modules/ssh2/lib/index.js +44 -0
- package/node_modules/ssh2/lib/keygen.js +582 -0
- package/node_modules/ssh2/lib/protocol/Protocol.js +2136 -0
- package/node_modules/ssh2/lib/protocol/SFTP.js +4052 -0
- package/node_modules/ssh2/lib/protocol/constants.js +356 -0
- package/node_modules/ssh2/lib/protocol/crypto/binding.gyp +23 -0
- package/node_modules/ssh2/lib/protocol/crypto/poly1305.js +43 -0
- package/node_modules/ssh2/lib/protocol/crypto/src/binding.cc +2300 -0
- package/node_modules/ssh2/lib/protocol/crypto.js +1602 -0
- package/node_modules/ssh2/lib/protocol/handlers.js +16 -0
- package/node_modules/ssh2/lib/protocol/handlers.misc.js +1285 -0
- package/node_modules/ssh2/lib/protocol/kex.js +1908 -0
- package/node_modules/ssh2/lib/protocol/keyParser.js +1484 -0
- package/node_modules/ssh2/lib/protocol/node-fs-compat.js +115 -0
- package/node_modules/ssh2/lib/protocol/utils.js +356 -0
- package/node_modules/ssh2/lib/protocol/zlib.js +255 -0
- package/node_modules/ssh2/lib/server.js +1380 -0
- package/node_modules/ssh2/lib/utils.js +336 -0
- package/node_modules/ssh2/package.json +49 -0
- package/node_modules/ssh2/test/common.js +316 -0
- package/node_modules/ssh2/test/fixtures/bad_rsa_private_key +26 -0
- package/node_modules/ssh2/test/fixtures/https_cert.pem +33 -0
- package/node_modules/ssh2/test/fixtures/https_key.pem +52 -0
- package/node_modules/ssh2/test/fixtures/id_dsa +12 -0
- package/node_modules/ssh2/test/fixtures/id_ecdsa +5 -0
- package/node_modules/ssh2/test/fixtures/id_rsa +15 -0
- package/node_modules/ssh2/test/fixtures/id_rsa.ppk +26 -0
- package/node_modules/ssh2/test/fixtures/id_rsa_enc +30 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa +21 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa.pub.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa_enc +22 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa_enc.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa_enc.pub.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa_enc.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa_enc_gcm +23 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa_enc_gcm.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa_enc_gcm.pub.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa_enc_gcm.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa +9 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa.pub.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa_enc +10 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa_enc.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa_enc.pub.result +8 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa_enc.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa_enc_gcm +10 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa_enc_gcm.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa_enc_gcm.pub.result +8 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa_enc_gcm.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ed25519 +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ed25519.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ed25519.pub.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ed25519.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa +27 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa.pub.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa_enc +28 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa_enc.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa_enc.pub.result +8 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa_enc.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa_enc_gcm +29 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa_enc_gcm.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa_enc_gcm.pub.result +8 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa_enc_gcm.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_dsa +12 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_dsa.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_dsa.pub.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_dsa.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_dsa_enc +15 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_dsa_enc.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_dsa_enc.pub.result +8 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_dsa_enc.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_ecdsa +5 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_ecdsa.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_ecdsa.pub.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_ecdsa.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_ecdsa_enc +8 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_ecdsa_enc.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_ecdsa_enc.pub.result +8 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_ecdsa_enc.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa +27 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa.pub.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa_enc +30 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa_enc.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa_enc.pub.result +8 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa_enc.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa_enc_aes256 +54 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa_enc_aes256.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa_enc_aes256.pub.result +6 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa_enc_aes256.result +6 -0
- package/node_modules/ssh2/test/fixtures/keyParser/ppk_dsa_enc +17 -0
- package/node_modules/ssh2/test/fixtures/keyParser/ppk_dsa_enc.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/ppk_rsa +26 -0
- package/node_modules/ssh2/test/fixtures/keyParser/ppk_rsa.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/ppk_rsa_enc +18 -0
- package/node_modules/ssh2/test/fixtures/keyParser/ppk_rsa_enc.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa.pub +9 -0
- package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa.pub.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa2.pub +10 -0
- package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa2.pub.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa3.pub +11 -0
- package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa3.pub.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa4.pub +11 -0
- package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa4.pub.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa5.pub +8 -0
- package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa5.pub.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa6.pub +13 -0
- package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa6.pub.result +7 -0
- package/node_modules/ssh2/test/fixtures/openssh_new_rsa +27 -0
- package/node_modules/ssh2/test/fixtures/ssh_host_dsa_key +12 -0
- package/node_modules/ssh2/test/fixtures/ssh_host_ecdsa_key +5 -0
- package/node_modules/ssh2/test/fixtures/ssh_host_rsa_key +15 -0
- package/node_modules/ssh2/test/test-exec.js +578 -0
- package/node_modules/ssh2/test/test-integration-openssh.js +486 -0
- package/node_modules/ssh2/test/test-keygen.js +60 -0
- package/node_modules/ssh2/test/test-misc-client-server.js +1460 -0
- package/node_modules/ssh2/test/test-openssh.js +261 -0
- package/node_modules/ssh2/test/test-protocol-crypto.js +631 -0
- package/node_modules/ssh2/test/test-protocol-keyparser.js +177 -0
- package/node_modules/ssh2/test/test-server-hostkeys.js +138 -0
- package/node_modules/ssh2/test/test-sftp.js +842 -0
- package/node_modules/ssh2/test/test-shell.js +109 -0
- package/node_modules/ssh2/test/test-userauth-agent-openssh.js +110 -0
- package/node_modules/ssh2/test/test-userauth-agent.js +171 -0
- package/node_modules/ssh2/test/test-userauth.js +611 -0
- package/node_modules/ssh2/test/test-worker-imports.js +25 -0
- package/node_modules/ssh2/test/test.js +20 -0
- package/node_modules/ssh2/util/build_pagent.bat +2 -0
- package/node_modules/ssh2/util/pagent.c +88 -0
- package/node_modules/ssh2/util/pagent.exe +0 -0
- package/node_modules/tweetnacl/.npmignore +4 -0
- package/node_modules/tweetnacl/AUTHORS.md +28 -0
- package/node_modules/tweetnacl/CHANGELOG.md +221 -0
- package/node_modules/tweetnacl/LICENSE +24 -0
- package/node_modules/tweetnacl/PULL_REQUEST_TEMPLATE.md +20 -0
- package/node_modules/tweetnacl/README.md +459 -0
- package/node_modules/tweetnacl/nacl-fast.js +2388 -0
- package/node_modules/tweetnacl/nacl-fast.min.js +2 -0
- package/node_modules/tweetnacl/nacl.d.ts +98 -0
- package/node_modules/tweetnacl/nacl.js +1175 -0
- package/node_modules/tweetnacl/nacl.min.js +1 -0
- package/node_modules/tweetnacl/package.json +58 -0
- package/package.json +95 -89
package/lib/web-deploy.js
CHANGED
|
@@ -1,694 +1,694 @@
|
|
|
1
|
-
// dsh-remote — remote DSH deployment probe & installer (issue #46 follow-up).
|
|
2
|
-
//
|
|
3
|
-
// Why this exists: connecting to a remote machine's DSH Web UI fails for
|
|
4
|
-
// environmental reasons far more often than for plugin reasons. Measured on a
|
|
5
|
-
// real host: its global dsh was 0.1.0-rc.6, whose node-pty@1.1.0 publishes
|
|
6
|
-
// darwin/win32 prebuilds but **no linux-x64**, so `dsh web` could not boot at
|
|
7
|
-
// all ("Failed to load native module: pty.node"). The user saw only "the remote
|
|
8
|
-
// DSH did not report a startup token" and had no way to know why.
|
|
9
|
-
//
|
|
10
|
-
// Two design commitments, both deliberate:
|
|
11
|
-
//
|
|
12
|
-
// 1. **Detection and installation are deterministic, so they are code, not a
|
|
13
|
-
// model call.** A probe that needs an LLM is slow, costs money and cannot be
|
|
14
|
-
// unit-tested. Everything in this module is a pure function over shell
|
|
15
|
-
// output: the caller supplies `exec`, tests supply strings.
|
|
16
|
-
// 2. **The probe is strictly read-only.** It never writes, installs or mutates
|
|
17
|
-
// remote state — that is what makes it safe to run before the user has
|
|
18
|
-
// agreed to anything, and safe to run on a machine they are merely looking
|
|
19
|
-
// at.
|
|
20
|
-
//
|
|
21
|
-
// The installer deliberately targets a PRIVATE PREFIX by default rather than a
|
|
22
|
-
// global install: no write permission to the system prefix is required, the
|
|
23
|
-
// user's PATH is untouched, and a dsh they are already using is never replaced.
|
|
24
|
-
// That is the same shape used to get the E2E working by hand.
|
|
25
|
-
|
|
26
|
-
/** Machine-readable markers the probe prints; parsing never guesses. */
|
|
27
|
-
const MARK = {
|
|
28
|
-
platform: 'P_OS',
|
|
29
|
-
arch: 'P_ARCH',
|
|
30
|
-
node: 'P_NODE',
|
|
31
|
-
nodeVersion: 'P_NODE_V',
|
|
32
|
-
npm: 'P_NPM',
|
|
33
|
-
npmVersion: 'P_NPM_V',
|
|
34
|
-
dsh: 'P_DSH',
|
|
35
|
-
dshVersion: 'P_DSH_V',
|
|
36
|
-
dshRoot: 'P_DSH_ROOT',
|
|
37
|
-
noOpen: 'P_NOOPEN',
|
|
38
|
-
ptyPrebuild: 'P_PTY',
|
|
39
|
-
home: 'P_HOME',
|
|
40
|
-
writePrefix: 'P_WRITABLE',
|
|
41
|
-
prefix: 'P_PREFIX',
|
|
42
|
-
installed: 'P_INSTALLED',
|
|
43
|
-
installedVersion: 'P_INSTALLED_V',
|
|
44
|
-
installedPty: 'P_INSTALLED_PTY',
|
|
45
|
-
installedWeb: 'P_INSTALLED_WEB',
|
|
46
|
-
gitBash: 'P_GITBASH',
|
|
47
|
-
proxyVar: 'P_PROXY_NAME',
|
|
48
|
-
registry: 'P_REGISTRY',
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Build the READ-ONLY environment probe.
|
|
53
|
-
*
|
|
54
|
-
* Multiline rather than '; '-joined on purpose: the launch builder once shipped a
|
|
55
|
-
* `do;` POSIX syntax error that only a real shell caught, and newlines remove
|
|
56
|
-
* that entire failure mode. Nothing here writes, installs or deletes.
|
|
57
|
-
*
|
|
58
|
-
* @param {object} [opts] - probe options.
|
|
59
|
-
* @param {string} [opts.prefix] - directory the installer would use; reported
|
|
60
|
-
* only to say whether it is writable (the probe never creates it).
|
|
61
|
-
* @returns {string} a POSIX shell script.
|
|
62
|
-
*/
|
|
63
|
-
export function buildProbeCommand(opts = {}) {
|
|
64
|
-
// `prefix` may be:
|
|
65
|
-
// • an explicit absolute path from configuration, checked verbatim;
|
|
66
|
-
// • empty, meaning "use the default", which MUST be resolved ON THE REMOTE:
|
|
67
|
-
// the caller cannot know $HOME before the probe runs, and guessing (the
|
|
68
|
-
// caller's own /tmp) would report writability for a directory that has
|
|
69
|
-
// nothing to do with the real install location.
|
|
70
|
-
const prefix = String(opts.prefix || '')
|
|
71
|
-
const useDefaultPrefix = !prefix && opts.defaultPrefixFor === 'home'
|
|
72
|
-
// A dsh a previous deployment installed FOR THIS MACHINE. Probing only the
|
|
73
|
-
// PATH dsh would keep reporting the old broken one after a successful install,
|
|
74
|
-
// so the installed binary is probed explicitly and preferred when usable.
|
|
75
|
-
const installed = String(opts.installedCommand || '')
|
|
76
|
-
// Statements are one-per-line. `if ...; then` is legitimate shell, but mixing
|
|
77
|
-
// `;`-joined statements with a `while`/`if` body is exactly how the launch
|
|
78
|
-
// builder once produced a `do;` syntax error — so this stays newline-only and
|
|
79
|
-
// a test asserts that.
|
|
80
|
-
const lines = [
|
|
81
|
-
'echo "P_OS=$(uname -s 2>/dev/null || echo unknown)"',
|
|
82
|
-
'A=$(uname -m 2>/dev/null || echo unknown)',
|
|
83
|
-
'case "$A" in x86_64|amd64) A=x64;; aarch64|arm64) A=arm64;; esac',
|
|
84
|
-
'echo "P_ARCH=$A"',
|
|
85
|
-
'echo "P_HOME=$HOME"',
|
|
86
|
-
'N=$(command -v node 2>/dev/null)',
|
|
87
|
-
'echo "P_NODE=$N"',
|
|
88
|
-
'if [ -n "$N" ]',
|
|
89
|
-
'then',
|
|
90
|
-
' echo "P_NODE_V=$($N -v 2>/dev/null)"',
|
|
91
|
-
'fi',
|
|
92
|
-
'M=$(command -v npm 2>/dev/null)',
|
|
93
|
-
'echo "P_NPM=$M"',
|
|
94
|
-
'if [ -n "$M" ]',
|
|
95
|
-
'then',
|
|
96
|
-
' echo "P_NPM_V=$($M -v 2>/dev/null)"',
|
|
97
|
-
'fi',
|
|
98
|
-
'D=$(command -v dsh 2>/dev/null)',
|
|
99
|
-
'echo "P_DSH=$D"',
|
|
100
|
-
'if [ -n "$D" ]',
|
|
101
|
-
'then',
|
|
102
|
-
' echo "P_DSH_V=$($D --version 2>/dev/null | tail -1)"',
|
|
103
|
-
'fi',
|
|
104
|
-
]
|
|
105
|
-
// A prefix the caller is considering. Reported, never created.
|
|
106
|
-
if (useDefaultPrefix) {
|
|
107
|
-
// Resolve the default on the REMOTE and report it back, so the caller learns
|
|
108
|
-
// the real install path (and its writability) in the same round trip.
|
|
109
|
-
lines.push(
|
|
110
|
-
`DFLT="$HOME/${DEFAULT_PREFIX_SUFFIX}"`,
|
|
111
|
-
'echo "P_PREFIX=$DFLT"',
|
|
112
|
-
'if [ -w "$HOME" ]',
|
|
113
|
-
'then',
|
|
114
|
-
' echo "P_WRITABLE=parent:yes"',
|
|
115
|
-
'else',
|
|
116
|
-
' echo "P_WRITABLE=parent:no"',
|
|
117
|
-
'fi',
|
|
118
|
-
)
|
|
119
|
-
} else if (prefix) {
|
|
120
|
-
lines.push(
|
|
121
|
-
`if [ -d "${prefix}" ]`,
|
|
122
|
-
'then',
|
|
123
|
-
` if [ -w "${prefix}" ]`,
|
|
124
|
-
' then',
|
|
125
|
-
' echo "P_WRITABLE=yes"',
|
|
126
|
-
' else',
|
|
127
|
-
' echo "P_WRITABLE=no"',
|
|
128
|
-
' fi',
|
|
129
|
-
`elif [ -w "$(dirname "${prefix}")" ]`,
|
|
130
|
-
'then',
|
|
131
|
-
' echo "P_WRITABLE=parent:yes"',
|
|
132
|
-
'else',
|
|
133
|
-
' echo "P_WRITABLE=parent:no"',
|
|
134
|
-
'fi',
|
|
135
|
-
)
|
|
136
|
-
} else {
|
|
137
|
-
lines.push('echo "P_WRITABLE=unknown"')
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// Probe whatever a previous deployment installed for THIS machine, and prefer
|
|
141
|
-
// it when it is usable. Without this a successful install is invisible to the
|
|
142
|
-
// next probe, because only the PATH dsh would be examined.
|
|
143
|
-
if (installed) {
|
|
144
|
-
lines.push(
|
|
145
|
-
`INSTALLED="${installed}"`,
|
|
146
|
-
'if [ -x "$INSTALLED" ]',
|
|
147
|
-
'then',
|
|
148
|
-
' echo "P_INSTALLED=yes"',
|
|
149
|
-
' echo "P_INSTALLED_V=$("$INSTALLED" --version 2>/dev/null | tail -1)"',
|
|
150
|
-
...resolveRootLines('INSTALLED', 'RI'),
|
|
151
|
-
' OSI=$(echo "$(uname -s)" | tr "A-Z" "a-z")',
|
|
152
|
-
' PTYI="$RI/node_modules/node-pty/prebuilds/$OSI-$A/pty.node"',
|
|
153
|
-
' ALTI="$RI/node_modules/@deepseek-ai/dsh/node_modules/node-pty/prebuilds/$OSI-$A/pty.node"',
|
|
154
|
-
' if [ -f "$PTYI" ] || [ -f "$ALTI" ]',
|
|
155
|
-
' then',
|
|
156
|
-
' echo "P_INSTALLED_PTY=yes"',
|
|
157
|
-
' else',
|
|
158
|
-
' echo "P_INSTALLED_PTY=no"',
|
|
159
|
-
' fi',
|
|
160
|
-
' if "$INSTALLED" web --help 2>&1 | grep -q -- "--port"',
|
|
161
|
-
' then',
|
|
162
|
-
' echo "P_INSTALLED_WEB=yes"',
|
|
163
|
-
' else',
|
|
164
|
-
' echo "P_INSTALLED_WEB=no"',
|
|
165
|
-
' fi',
|
|
166
|
-
'else',
|
|
167
|
-
' echo "P_INSTALLED=no"',
|
|
168
|
-
'fi',
|
|
169
|
-
)
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// A POSIX shell on a Windows remote. The launch/install commands need one, and
|
|
173
|
-
// this is the single fact that decides whether a Windows host is deployable.
|
|
174
|
-
lines.push(
|
|
175
|
-
'GB=""',
|
|
176
|
-
'for c in /usr/bin/bash.exe /bin/bash.exe bash',
|
|
177
|
-
'do',
|
|
178
|
-
' if command -v "$c" >/dev/null 2>&1',
|
|
179
|
-
' then',
|
|
180
|
-
' GB=$(command -v "$c")',
|
|
181
|
-
' break',
|
|
182
|
-
' fi',
|
|
183
|
-
'done',
|
|
184
|
-
'echo "P_GITBASH=$GB"',
|
|
185
|
-
)
|
|
186
|
-
|
|
187
|
-
// Proxy configuration: REPORTED only. An install that inherits a broken proxy
|
|
188
|
-
// is a plausible cause of a slow or failing npm, and the user is the one who
|
|
189
|
-
// knows the right value. The probe never sets or changes it.
|
|
190
|
-
lines.push(
|
|
191
|
-
'PX=""',
|
|
192
|
-
'for v in HTTPS_PROXY https_proxy HTTP_PROXY http_proxy ALL_PROXY all_proxy',
|
|
193
|
-
'do',
|
|
194
|
-
' EV=$(eval "echo \\$$v")',
|
|
195
|
-
' if [ -n "$EV" ]',
|
|
196
|
-
' then',
|
|
197
|
-
' PX="$v"',
|
|
198
|
-
' break',
|
|
199
|
-
' fi',
|
|
200
|
-
'done',
|
|
201
|
-
'echo "P_PROXY_NAME=$PX"',
|
|
202
|
-
)
|
|
203
|
-
|
|
204
|
-
// Whether npm already points at a non-default registry (an internal mirror).
|
|
205
|
-
// Reported so an install can keep using it rather than overriding it.
|
|
206
|
-
lines.push(
|
|
207
|
-
'if [ -n "$M" ]',
|
|
208
|
-
'then',
|
|
209
|
-
' RG=$($M config get registry 2>/dev/null | head -1)',
|
|
210
|
-
' echo "P_REGISTRY=$RG"',
|
|
211
|
-
'fi',
|
|
212
|
-
)
|
|
213
|
-
|
|
214
|
-
// Facts that need the dsh binary: where it lives and whether it can boot web.
|
|
215
|
-
lines.push(
|
|
216
|
-
'if [ -n "$D" ]',
|
|
217
|
-
'then',
|
|
218
|
-
' if [ -n "$N" ]',
|
|
219
|
-
' then',
|
|
220
|
-
...resolveRootLines('D', 'R').map((l) => ' ' + l),
|
|
221
|
-
' echo "P_DSH_ROOT=$R"',
|
|
222
|
-
' OS=$(echo "$(uname -s)" | tr "A-Z" "a-z")',
|
|
223
|
-
' PTY="$R/node_modules/node-pty/prebuilds/$OS-$A/pty.node"',
|
|
224
|
-
' ALT="$R/node_modules/@deepseek-ai/dsh/node_modules/node-pty/prebuilds/$OS-$A/pty.node"',
|
|
225
|
-
' if [ -f "$PTY" ] || [ -f "$ALT" ]',
|
|
226
|
-
' then',
|
|
227
|
-
' echo "P_PTY=yes"',
|
|
228
|
-
' else',
|
|
229
|
-
' echo "P_PTY=no"',
|
|
230
|
-
' fi',
|
|
231
|
-
' fi',
|
|
232
|
-
// `web --help` is cheap and reveals the flag family; it does NOT prove the
|
|
233
|
-
// surface boots (a broken native module still prints help), which is why
|
|
234
|
-
// P_PTY is probed separately.
|
|
235
|
-
' if $D web --help 2>&1 | grep -q -- "--no-open"',
|
|
236
|
-
' then',
|
|
237
|
-
' echo "P_NOOPEN=yes"',
|
|
238
|
-
' else',
|
|
239
|
-
' echo "P_NOOPEN=no"',
|
|
240
|
-
' fi',
|
|
241
|
-
'fi',
|
|
242
|
-
)
|
|
243
|
-
return lines.join('\n')
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
/** Read one `KEY=value` line out of probe output. */
|
|
247
|
-
function valueOf(text, key) {
|
|
248
|
-
const re = new RegExp('^' + key + '=(.*)$', 'm')
|
|
249
|
-
const m = re.exec(text)
|
|
250
|
-
return m ? m[1].trim() : ''
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
/**
|
|
254
|
-
* Resolve a `dsh` executable to the PREFIX ROOT that owns its `node_modules`,
|
|
255
|
-
* as a POSIX shell fragment (one statement per line).
|
|
256
|
-
*
|
|
257
|
-
* Not `dirname(dirname(bin))`. The bin is typically a symlink like
|
|
258
|
-
* `node_modules/.bin/dsh -> ../@deepseek-ai/dsh/lib/bin.js`, so realpath puts us
|
|
259
|
-
* at `<root>/node_modules/@deepseek-ai/dsh/lib/bin.js` and two dirnames land at
|
|
260
|
-
* `<root>/node_modules/@deepseek-ai/dsh` — INSIDE the package, where a sibling
|
|
261
|
-
* `node-pty` does not exist. Measured on a real host: that produced a false
|
|
262
|
-
* "pty missing" verdict for a perfectly good install, and the same mistake would
|
|
263
|
-
* hide a genuinely broken one.
|
|
264
|
-
*
|
|
265
|
-
* Walking up to the first ancestor that contains a `node_modules` DIRECTORY
|
|
266
|
-
* resolves both layouts: a private prefix (`.../dsh/node_modules/.bin/dsh`) and a
|
|
267
|
-
* global install (`.../lib/node_modules/@deepseek-ai/dsh/lib/bin.js`).
|
|
268
|
-
*
|
|
269
|
-
* @param {string} binVar - shell variable holding the executable path.
|
|
270
|
-
* @param {string} outVar - shell variable to receive the resolved root.
|
|
271
|
-
* @returns {string[]} shell lines.
|
|
272
|
-
*/
|
|
273
|
-
export function resolveRootLines(binVar, outVar) {
|
|
274
|
-
return [
|
|
275
|
-
`${outVar}=""`,
|
|
276
|
-
`RD="$(dirname "$${binVar}")"`,
|
|
277
|
-
'i=0',
|
|
278
|
-
'while [ $i -lt 8 ]',
|
|
279
|
-
'do',
|
|
280
|
-
` if [ -d "$RD/node_modules" ]`,
|
|
281
|
-
' then',
|
|
282
|
-
` ${outVar}="$RD"`,
|
|
283
|
-
' break',
|
|
284
|
-
' fi',
|
|
285
|
-
' NC="$(dirname "$RD")"',
|
|
286
|
-
' if [ "$NC" = "$RD" ]',
|
|
287
|
-
' then',
|
|
288
|
-
' break',
|
|
289
|
-
' fi',
|
|
290
|
-
' RD="$NC"',
|
|
291
|
-
' i=$((i+1))',
|
|
292
|
-
'done',
|
|
293
|
-
]
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
/**
|
|
297
|
-
* Parse probe output into facts. Absent keys stay empty rather than defaulting,
|
|
298
|
-
* so the judge can tell "not installed" from "probe could not tell".
|
|
299
|
-
*
|
|
300
|
-
* @param {string} output - raw stdout of {@link buildProbeCommand}.
|
|
301
|
-
* @returns {object} parsed facts.
|
|
302
|
-
*/
|
|
303
|
-
export function parseProbe(output) {
|
|
304
|
-
const text = String(output || '')
|
|
305
|
-
const platform = valueOf(text, MARK.platform)
|
|
306
|
-
const lower = platform.toLowerCase()
|
|
307
|
-
// Git Bash / MSYS / Cygwin report an emulated uname; treat them as Windows.
|
|
308
|
-
const windows = /mingw|msys|cygwin|windows/i.test(platform)
|
|
309
|
-
return {
|
|
310
|
-
platform: lower === 'unknown' ? '' : platform,
|
|
311
|
-
arch: valueOf(text, MARK.arch),
|
|
312
|
-
windows,
|
|
313
|
-
posix: !windows && lower !== 'unknown' && lower !== '',
|
|
314
|
-
node: valueOf(text, MARK.node),
|
|
315
|
-
nodeVersion: valueOf(text, MARK.nodeVersion),
|
|
316
|
-
npm: valueOf(text, MARK.npm),
|
|
317
|
-
npmVersion: valueOf(text, MARK.npmVersion),
|
|
318
|
-
dsh: valueOf(text, MARK.dsh),
|
|
319
|
-
dshVersion: valueOf(text, MARK.dshVersion),
|
|
320
|
-
dshRoot: valueOf(text, MARK.dshRoot),
|
|
321
|
-
noOpen: valueOf(text, MARK.noOpen),
|
|
322
|
-
ptyPrebuild: valueOf(text, MARK.ptyPrebuild),
|
|
323
|
-
installed: valueOf(text, MARK.installed),
|
|
324
|
-
installedVersion: valueOf(text, MARK.installedVersion),
|
|
325
|
-
installedPty: valueOf(text, MARK.installedPty),
|
|
326
|
-
installedWeb: valueOf(text, MARK.installedWeb),
|
|
327
|
-
gitBash: valueOf(text, MARK.gitBash),
|
|
328
|
-
proxyVar: valueOf(text, MARK.proxyVar),
|
|
329
|
-
registry: valueOf(text, MARK.registry),
|
|
330
|
-
home: valueOf(text, MARK.home),
|
|
331
|
-
writable: valueOf(text, MARK.writePrefix),
|
|
332
|
-
// The prefix the REMOTE resolved for itself (only reported when the caller
|
|
333
|
-
// asked for the default). Absent when an explicit prefix was probed.
|
|
334
|
-
resolvedPrefix: valueOf(text, MARK.prefix),
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
/** Compare dotted versions; returns true when `a` >= `b` (empty `a` is false). */
|
|
339
|
-
export function versionAtLeast(a, b) {
|
|
340
|
-
const parse = (v) => String(v || '').replace(/^v/, '').split(/[.\-+]/).map((x) => Number(x) || 0)
|
|
341
|
-
const [x, y] = [parse(a), parse(b)]
|
|
342
|
-
if (!String(a || '').trim()) return false
|
|
343
|
-
for (let i = 0; i < Math.max(x.length, y.length); i++) {
|
|
344
|
-
const l = x[i] || 0
|
|
345
|
-
const r = y[i] || 0
|
|
346
|
-
if (l !== r) return l > r
|
|
347
|
-
}
|
|
348
|
-
return true
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
/** The first dsh release whose node-pty ships Linux prebuilds (measured). */
|
|
352
|
-
export const PTY_FIXED_VERSION = '0.1.5-rc.2'
|
|
353
|
-
|
|
354
|
-
/**
|
|
355
|
-
* Minimum Node the remote must run, and the reason it is exactly this number.
|
|
356
|
-
*
|
|
357
|
-
* DSH's boot path imports `parseEnv` from `node:util`, and that export only
|
|
358
|
-
* exists from **20.12.0** (Node 21.7.0 and later have it too). Measured on a
|
|
359
|
-
* real host with two nodes installed:
|
|
360
|
-
*
|
|
361
|
-
* /usr/bin/node (v18.19.1) → parseEnv: undefined
|
|
362
|
-
* ~/.nvm/versions/node/v24.22.0/bin/node (v24.22.0) → parseEnv: function
|
|
363
|
-
*
|
|
364
|
-
* The failure mode is what makes this worth a BLOCKER rather than a warning: npm
|
|
365
|
-
* only prints EBADENGINE (it does not refuse), so all ~535 packages install
|
|
366
|
-
* cleanly and the crash arrives afterwards as
|
|
367
|
-
*
|
|
368
|
-
* SyntaxError: The requested module 'node:util' does not provide an export
|
|
369
|
-
* named 'parseEnv' at .../@deepseek-ai/dsh-app-boot/lib/index.js:4
|
|
370
|
-
*
|
|
371
|
-
* which does not read as "your node is too old". Checking the version BEFORE the
|
|
372
|
-
* install is the only way to turn that into an actionable message.
|
|
373
|
-
*
|
|
374
|
-
* Reference: Node.js 20.12.0 release notes, "Loading and parsing environment
|
|
375
|
-
* variables" — https://nodejs.org/en/blog/release/v20.12.0
|
|
376
|
-
*/
|
|
377
|
-
export const MIN_NODE_VERSION = '20.12.0'
|
|
378
|
-
|
|
379
|
-
/**
|
|
380
|
-
* Turn facts into a verdict: what is wrong, why, and what to do.
|
|
381
|
-
*
|
|
382
|
-
* Severity is deliberately coarse — `blocker` means "connecting cannot work
|
|
383
|
-
* until this changes", `warn` means "works, but with a caveat", `info` means
|
|
384
|
-
* "worth knowing", `ok` means "nothing to do". The UI maps these directly.
|
|
385
|
-
*
|
|
386
|
-
* @param {object} facts - parsed probe facts.
|
|
387
|
-
* @param {object} [opts] - judgement options.
|
|
388
|
-
* @param {string} [opts.installedCommand] - command the installer already placed, if any.
|
|
389
|
-
* @returns {{severity: string, ok: boolean, findings: object[], useCommand: string, canAutoInstall: boolean}}
|
|
390
|
-
*/
|
|
391
|
-
export function judgeProbe(facts, opts = {}) {
|
|
392
|
-
const f = facts || {}
|
|
393
|
-
const findings = []
|
|
394
|
-
const add = (code, severity, summary, detail, fix) => {
|
|
395
|
-
findings.push({ code, severity, summary, detail: detail || '', ...(fix ? { fix } : {}) })
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
// ── platform / shell ──────────────────────────────────────────────────────
|
|
399
|
-
if (!f.platform) {
|
|
400
|
-
add('PLATFORM_UNKNOWN', 'warn', '无法识别远端操作系统',
|
|
401
|
-
'探测没有返回可识别的 uname 结果。', '确认该主机是常规 Linux/macOS/Windows。')
|
|
402
|
-
} else if (f.windows) {
|
|
403
|
-
// The launch command is POSIX shell (setsid/nohup). pool.exec wraps commands
|
|
404
|
-
// in Git Bash when it finds one on a Windows remote; without it there is no
|
|
405
|
-
// POSIX shell to run them in, and an install would produce a dsh we could
|
|
406
|
-
// never start.
|
|
407
|
-
if (f.gitBash) {
|
|
408
|
-
add('WINDOWS_REMOTE_GITBASH', 'info', '远端是 Windows 主机,但有 Git Bash',
|
|
409
|
-
'启动命令会经 Git Bash 执行,自动部署与连接都可用。')
|
|
410
|
-
} else {
|
|
411
|
-
add('WINDOWS_REMOTE', 'warn', '远端是 Windows 主机且没有 Git Bash',
|
|
412
|
-
'本功能的启动命令是 POSIX shell(依赖 setsid/nohup),没有 Git Bash 就无法执行。',
|
|
413
|
-
'在该机器安装 Git Bash(随 Git for Windows 一起),或改用 Linux/macOS 远端。')
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
// ── node / npm ────────────────────────────────────────────────────────────
|
|
418
|
-
if (!f.node) {
|
|
419
|
-
add('NO_NODE', 'blocker', '远端没有 node',
|
|
420
|
-
'自动部署需要 node 与 npm。',
|
|
421
|
-
'先在该机器安装 Node.js(nvm 或系统包管理器),再回来重试。')
|
|
422
|
-
} else if (f.nodeVersion && !versionAtLeast(f.nodeVersion, MIN_NODE_VERSION)) {
|
|
423
|
-
// The install itself SUCCEEDS on this node — npm only warns (EBADENGINE) —
|
|
424
|
-
// and the failure surfaces later as a parseEnv import error that does not
|
|
425
|
-
// read as a version problem. So the check has to happen before the install.
|
|
426
|
-
add('NODE_TOO_OLD', 'blocker',
|
|
427
|
-
`远端 node ${f.nodeVersion} 版本过低(需要 ≥ ${MIN_NODE_VERSION})`,
|
|
428
|
-
`dsh 启动时要 import node:util 的 parseEnv,该导出从 Node ${MIN_NODE_VERSION} 起才有。`
|
|
429
|
-
+ '在这个版本上 npm 只会给 EBADENGINE 警告、仍然把包装完,'
|
|
430
|
-
+ '直到启动时才崩:SyntaxError: The requested module \'node:util\' does not provide an export named \'parseEnv\'。',
|
|
431
|
-
`升级该机器的 node 到 ${MIN_NODE_VERSION} 以上(例如 nvm install 22),`
|
|
432
|
-
+ '并把 `webAttachCommand` 指向新 node 的绝对路径(如 ~/.nvm/versions/node/v22.x/bin/node <前缀>/node_modules/@deepseek-ai/dsh/lib/bin.js)。')
|
|
433
|
-
}
|
|
434
|
-
if (!f.npm) {
|
|
435
|
-
add('NO_NPM', 'blocker', '远端没有 npm',
|
|
436
|
-
'自动部署通过 npm 拉取 dsh。',
|
|
437
|
-
'随 Node.js 一起安装 npm(或换用 nvm 安装的 node)。')
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
// ── existing dsh ──────────────────────────────────────────────────────────
|
|
441
|
-
if (!f.dsh) {
|
|
442
|
-
add('NO_DSH', 'info', '远端没有 dsh',
|
|
443
|
-
'没有找到可执行的 dsh。',
|
|
444
|
-
'可以自动装到私有前缀,不影响该机器上已装的任何东西。')
|
|
445
|
-
} else {
|
|
446
|
-
// The exact failure measured on a real host: node-pty has no prebuild for
|
|
447
|
-
// this platform, so the web surface cannot boot at all.
|
|
448
|
-
if (f.ptyPrebuild === 'no' && !f.windows) {
|
|
449
|
-
add('PTY_MISSING', 'blocker',
|
|
450
|
-
`远端 dsh ${f.dshVersion || '(版本未知)'} 无法启动 web 界面`,
|
|
451
|
-
'它依赖的 node-pty 在当前平台没有预编译产物,'
|
|
452
|
-
+ '启动会直接失败(Failed to load native module: pty.node)。',
|
|
453
|
-
`装一个 ${PTY_FIXED_VERSION} 或更新的 dsh 到私有前缀即可(其 node-pty 带该平台产物)。`)
|
|
454
|
-
}
|
|
455
|
-
if (f.noOpen === 'no') {
|
|
456
|
-
// Handled automatically by WebAttach (it retries without the flag), so
|
|
457
|
-
// this is informational rather than a problem.
|
|
458
|
-
add('OLD_FLAG_SET', 'info', `远端 dsh ${f.dshVersion || ''} 不认识 --no-open`,
|
|
459
|
-
'这是较新的 flag;插件会自动去掉它重试一次,无需处理。',
|
|
460
|
-
'升级到较新版本可避免这条重试。')
|
|
461
|
-
}
|
|
462
|
-
if (f.noOpen === 'yes' && f.ptyPrebuild !== 'no') {
|
|
463
|
-
add('DSH_OK', 'ok', `远端 dsh ${f.dshVersion || ''} 可以直接使用`,
|
|
464
|
-
'已装且有当前平台的 node-pty 预编译产物。')
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
// ── prefix writability (only relevant when we would install) ──────────────
|
|
469
|
-
if ((!f.dsh || f.ptyPrebuild === 'no') && f.writable === 'no') {
|
|
470
|
-
add('PREFIX_UNWRITABLE', 'warn', '默认安装位置不可写',
|
|
471
|
-
'自动部署会改用 $HOME 下的私有目录;若 $HOME 也不可写则需要人工处理。',
|
|
472
|
-
'通常无需处理,安装器会回退到 $HOME。')
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
// ── a dsh a previous deployment installed for this machine ─────────────────
|
|
476
|
-
// This must be judged BEFORE the PATH dsh: an installed build is the whole
|
|
477
|
-
// point of a deployment, and reporting the old broken PATH dsh after a
|
|
478
|
-
// successful install would make the feature look like it did nothing.
|
|
479
|
-
const installed = String(opts.installedCommand || '').trim()
|
|
480
|
-
const installedUsable = f.installed === 'yes'
|
|
481
|
-
&& f.installedPty !== 'no'
|
|
482
|
-
&& f.installedWeb !== 'no'
|
|
483
|
-
if (installed && installedUsable) {
|
|
484
|
-
add('INSTALLED_OK', 'ok',
|
|
485
|
-
`已部署的 dsh ${f.installedVersion || ''} 可用`,
|
|
486
|
-
'该机器上之前自动部署的版本已通过校验(native 模块与 web 子命令都在)。')
|
|
487
|
-
} else if (installed && f.installed === 'no') {
|
|
488
|
-
add('INSTALLED_MISSING', 'info', '之前部署的 dsh 已不在',
|
|
489
|
-
`${installed} 不存在(可能被清理或换了机器镜像)。`,
|
|
490
|
-
'重新部署一次即可。')
|
|
491
|
-
} else if (installed && f.installed === 'yes' && !installedUsable) {
|
|
492
|
-
add('INSTALLED_UNUSABLE', 'warn', '已部署的 dsh 校验未通过',
|
|
493
|
-
'它存在但 web 界面或 native 模块有问题。',
|
|
494
|
-
'重新部署一次;仍失败时用「让 AI 排查」。')
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
// ── environment notes that change what an install will do ─────────────────
|
|
498
|
-
// Reported, never "fixed": the user is the one who knows whether the proxy or
|
|
499
|
-
// mirror is correct, and silently overriding either would be worse than slow.
|
|
500
|
-
if (f.proxyVar) {
|
|
501
|
-
add('PROXY_SET', 'info', `远端设置了代理(${f.proxyVar})`,
|
|
502
|
-
'安装会沿用远端已有的代理设置。',
|
|
503
|
-
'若 npm 下载很慢或失败,检查这个代理是否可达。')
|
|
504
|
-
}
|
|
505
|
-
if (f.registry && !/registry\.npmjs\.org/.test(f.registry)) {
|
|
506
|
-
add('CUSTOM_REGISTRY', 'info', `远端 npm 使用了自定义源:${f.registry}`,
|
|
507
|
-
'安装默认沿用远端自己的 npm 配置,不会覆盖它。')
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
const blockers = findings.filter((x) => x.severity === 'blocker')
|
|
511
|
-
// A Windows remote with Git Bash can be installed into: pool.exec pipes every
|
|
512
|
-
// command through `bash -s`, so the POSIX plan works there.
|
|
513
|
-
//
|
|
514
|
-
// A node older than MIN_NODE_VERSION makes an install POINTLESS as well as
|
|
515
|
-
// failing: the packages land, then the binary cannot boot. Promising
|
|
516
|
-
// "can auto-install: yes" there is what sent the reporter of issue #54 through
|
|
517
|
-
// a 535-package install that ended in a parseEnv SyntaxError.
|
|
518
|
-
const nodeUsable = !!f.node && (!f.nodeVersion || versionAtLeast(f.nodeVersion, MIN_NODE_VERSION))
|
|
519
|
-
const canAutoInstall = nodeUsable && !!f.npm && (!f.windows || !!f.gitBash)
|
|
520
|
-
|
|
521
|
-
// Which command should the attach flow use? A verified deployment wins, then a
|
|
522
|
-
// usable dsh already on PATH.
|
|
523
|
-
//
|
|
524
|
-
// Note `noOpen` deliberately does NOT disqualify a PATH dsh: WebAttach already
|
|
525
|
-
// retries without `--no-open`, so an old version is perfectly usable. Treating
|
|
526
|
-
// it as unusable would tell users to reinstall for no reason.
|
|
527
|
-
const usablePath = f.dsh && f.ptyPrebuild !== 'no' ? f.dsh : ''
|
|
528
|
-
const useCommand = installedUsable ? installed : usablePath
|
|
529
|
-
|
|
530
|
-
// If the installed build works, the PATH dsh's shortcomings are no longer
|
|
531
|
-
// blockers — the user can connect right now.
|
|
532
|
-
const effectiveBlockers = installedUsable
|
|
533
|
-
? blockers.filter((x) => x.code !== 'PTY_MISSING')
|
|
534
|
-
: blockers
|
|
535
|
-
|
|
536
|
-
return {
|
|
537
|
-
ok: effectiveBlockers.length === 0,
|
|
538
|
-
severity: effectiveBlockers.length ? 'blocker'
|
|
539
|
-
: findings.some((x) => x.severity === 'warn') ? 'warn' : 'ok',
|
|
540
|
-
findings,
|
|
541
|
-
useCommand,
|
|
542
|
-
canAutoInstall,
|
|
543
|
-
}
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
// ── installation ────────────────────────────────────────────────────────────
|
|
547
|
-
|
|
548
|
-
/** Where the installer puts dsh when the user does not choose. */
|
|
549
|
-
export const DEFAULT_PREFIX_SUFFIX = '.dsh-remote/dsh'
|
|
550
|
-
/** Default version: the first release whose node-pty ships Linux prebuilds. */
|
|
551
|
-
export const DEFAULT_INSTALL_VERSION = PTY_FIXED_VERSION
|
|
552
|
-
|
|
553
|
-
/**
|
|
554
|
-
* Resolve the install prefix from probe facts and configuration.
|
|
555
|
-
*
|
|
556
|
-
* Preference order is deliberate: an explicit user setting, then `$HOME`, and
|
|
557
|
-
* only then a temp directory. `$HOME` keeps the install across reboots, which
|
|
558
|
-
* matters because the resulting `webAttachCommand` is written into the machine
|
|
559
|
-
* record and would otherwise dangle after a reboot wipes `/tmp`.
|
|
560
|
-
*
|
|
561
|
-
* @param {object} facts - parsed probe facts.
|
|
562
|
-
* @param {object} [opts] - overrides.
|
|
563
|
-
* @param {string} [opts.prefix] - explicit prefix.
|
|
564
|
-
* @returns {string} a remote directory path.
|
|
565
|
-
*/
|
|
566
|
-
export function resolvePrefix(facts, opts = {}) {
|
|
567
|
-
const explicit = String(opts.prefix || '').trim()
|
|
568
|
-
if (explicit) return explicit
|
|
569
|
-
const home = String((facts && facts.home) || '').trim()
|
|
570
|
-
if (home) return `${home.replace(/\/+$/, '')}/${DEFAULT_PREFIX_SUFFIX}`
|
|
571
|
-
return `/tmp/${DEFAULT_PREFIX_SUFFIX}`
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
/** Quote one shell argument. */
|
|
575
|
-
function q(s) {
|
|
576
|
-
return `'${String(s).replace(/'/g, `'\\''`)}'`
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
/**
|
|
580
|
-
* Build the ordered install plan.
|
|
581
|
-
*
|
|
582
|
-
* Each step is a separate SSH exec rather than one long script, because the
|
|
583
|
-
* caller wants per-step progress and a failure report that names the step. The
|
|
584
|
-
* plan is pure data so a test can assert the exact commands without a shell.
|
|
585
|
-
*
|
|
586
|
-
* @param {object} spec - install parameters.
|
|
587
|
-
* @param {object} spec.facts - parsed probe facts.
|
|
588
|
-
* @param {string} spec.prefix - remote prefix directory.
|
|
589
|
-
* @param {string} [spec.version] - dsh version to install.
|
|
590
|
-
* @param {string} [spec.registry] - npm registry to use.
|
|
591
|
-
* @param {object} [spec.env] - env vars for the install step (proxy, mirror…).
|
|
592
|
-
* @param {boolean} [spec.forcePosix] - allow the plan for a Windows remote that
|
|
593
|
-
* has Git Bash (pool.exec then pipes commands through `bash -s`).
|
|
594
|
-
* @returns {{steps: object[], command: string}} the plan and the resulting dsh path.
|
|
595
|
-
*/
|
|
596
|
-
export function buildInstallPlan(spec) {
|
|
597
|
-
const facts = spec.facts || {}
|
|
598
|
-
const prefix = String(spec.prefix || '').replace(/\/+$/, '')
|
|
599
|
-
const version = String(spec.version || DEFAULT_INSTALL_VERSION)
|
|
600
|
-
const registry = String(spec.registry || '')
|
|
601
|
-
const env = spec.env && typeof spec.env === 'object' ? spec.env : undefined
|
|
602
|
-
const bin = `${prefix}/node_modules/.bin/dsh`
|
|
603
|
-
|
|
604
|
-
// `--no-audit --no-fund` keeps output small; `--loglevel=error` keeps the
|
|
605
|
-
// captured stdout parseable. `--prefix` installs locally without touching any
|
|
606
|
-
// system or user-global prefix.
|
|
607
|
-
const npmArgs = [
|
|
608
|
-
'install', '--prefix', q(prefix),
|
|
609
|
-
'--no-audit', '--no-fund', '--loglevel=error',
|
|
610
|
-
// `--ignore-scripts=false` is npm's default and is stated explicitly here
|
|
611
|
-
// because a restrictive global config would otherwise silently skip
|
|
612
|
-
// node-pty's build/install step, producing an install that cannot boot web.
|
|
613
|
-
...(registry ? ['--registry', q(registry)] : []),
|
|
614
|
-
q(`@deepseek-ai/dsh@${version}`),
|
|
615
|
-
]
|
|
616
|
-
|
|
617
|
-
const steps = [
|
|
618
|
-
{
|
|
619
|
-
id: 'prepare',
|
|
620
|
-
title: '准备安装目录',
|
|
621
|
-
// A private package.json stops npm from walking up to an unrelated
|
|
622
|
-
// project and mutating its dependency tree.
|
|
623
|
-
command: `mkdir -p ${q(prefix)} && printf '%s\\n' `
|
|
624
|
-
+ `'{"name":"dsh-remote-install","private":true,"version":"0.0.0"}' > ${q(`${prefix}/package.json`)}`
|
|
625
|
-
+ ` && echo OK`,
|
|
626
|
-
},
|
|
627
|
-
{
|
|
628
|
-
id: 'install',
|
|
629
|
-
title: `安装 dsh@${version} 到私有目录`,
|
|
630
|
-
command: `cd ${q(prefix)} && ${facts.npm || 'npm'} ${npmArgs.join(' ')}`,
|
|
631
|
-
// npm can legitimately take minutes on a cold cache.
|
|
632
|
-
timeoutMs: 600000,
|
|
633
|
-
...(env ? { env } : {}),
|
|
634
|
-
},
|
|
635
|
-
{
|
|
636
|
-
id: 'verify-binary',
|
|
637
|
-
title: '校验安装结果',
|
|
638
|
-
command: `test -x ${q(bin)} && ${q(bin)} --version`,
|
|
639
|
-
},
|
|
640
|
-
]
|
|
641
|
-
|
|
642
|
-
// A native-module check is what actually distinguishes "installed" from
|
|
643
|
-
// "installed and able to boot the web surface" — the exact gap that made the
|
|
644
|
-
// original failure so opaque. It uses the walk-up rule from
|
|
645
|
-
// `resolveRootLines` rather than dirname(dirname()), which lands inside the
|
|
646
|
-
// package and yields a FALSE NEGATIVE on a good install (measured).
|
|
647
|
-
steps.push({
|
|
648
|
-
id: 'verify-pty',
|
|
649
|
-
title: '校验 web 界面可启动(native 模块)',
|
|
650
|
-
command: [
|
|
651
|
-
`BIN=${q(bin)}`,
|
|
652
|
-
...resolveRootLines('BIN', 'ROOT'),
|
|
653
|
-
`OS=$(echo "$(uname -s)" | tr 'A-Z' 'a-z')`,
|
|
654
|
-
`A=$(uname -m)`,
|
|
655
|
-
`case "$A" in x86_64|amd64) A=x64;; aarch64|arm64) A=arm64;; esac`,
|
|
656
|
-
`P1="$ROOT/node_modules/node-pty/prebuilds/$OS-$A/pty.node"`,
|
|
657
|
-
`P2="$ROOT/node_modules/@deepseek-ai/dsh/node_modules/node-pty/prebuilds/$OS-$A/pty.node"`,
|
|
658
|
-
// One `if` rather than a `||` continuation: a line beginning with `||`
|
|
659
|
-
// after a newline is a syntax error in POSIX sh (caught by the real-shell
|
|
660
|
-
// gate, not by string assertions).
|
|
661
|
-
`if [ -f "$P1" ] || [ -f "$P2" ]`,
|
|
662
|
-
`then`,
|
|
663
|
-
` echo PTY_OK`,
|
|
664
|
-
`else`,
|
|
665
|
-
` echo "PTY_MISSING: no prebuild at $P1 or $P2" >&2`,
|
|
666
|
-
` exit 1`,
|
|
667
|
-
`fi`,
|
|
668
|
-
].join('\n'),
|
|
669
|
-
})
|
|
670
|
-
|
|
671
|
-
steps.push({
|
|
672
|
-
id: 'verify-web',
|
|
673
|
-
title: '校验 dsh web 可用',
|
|
674
|
-
command: `${q(bin)} web --help 2>&1 | grep -q -- '--port'`,
|
|
675
|
-
})
|
|
676
|
-
|
|
677
|
-
return { steps, command: bin }
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
/** One step's outcome, as returned to the UI. */
|
|
681
|
-
export function stepResult(step, res) {
|
|
682
|
-
const out = String((res && res.stdout) || '')
|
|
683
|
-
const err = String((res && res.stderr) || '')
|
|
684
|
-
const code = res && res.code !== undefined ? res.code : 0
|
|
685
|
-
return {
|
|
686
|
-
id: step.id,
|
|
687
|
-
title: step.title,
|
|
688
|
-
ok: code === 0,
|
|
689
|
-
code,
|
|
690
|
-
// Keep a bounded tail: enough to diagnose, small enough to stream to a UI.
|
|
691
|
-
output: (out + (err ? '\n' + err : '')).trim().slice(-1200),
|
|
692
|
-
}
|
|
693
|
-
}
|
|
694
|
-
|
|
1
|
+
// dsh-remote — remote DSH deployment probe & installer (issue #46 follow-up).
|
|
2
|
+
//
|
|
3
|
+
// Why this exists: connecting to a remote machine's DSH Web UI fails for
|
|
4
|
+
// environmental reasons far more often than for plugin reasons. Measured on a
|
|
5
|
+
// real host: its global dsh was 0.1.0-rc.6, whose node-pty@1.1.0 publishes
|
|
6
|
+
// darwin/win32 prebuilds but **no linux-x64**, so `dsh web` could not boot at
|
|
7
|
+
// all ("Failed to load native module: pty.node"). The user saw only "the remote
|
|
8
|
+
// DSH did not report a startup token" and had no way to know why.
|
|
9
|
+
//
|
|
10
|
+
// Two design commitments, both deliberate:
|
|
11
|
+
//
|
|
12
|
+
// 1. **Detection and installation are deterministic, so they are code, not a
|
|
13
|
+
// model call.** A probe that needs an LLM is slow, costs money and cannot be
|
|
14
|
+
// unit-tested. Everything in this module is a pure function over shell
|
|
15
|
+
// output: the caller supplies `exec`, tests supply strings.
|
|
16
|
+
// 2. **The probe is strictly read-only.** It never writes, installs or mutates
|
|
17
|
+
// remote state — that is what makes it safe to run before the user has
|
|
18
|
+
// agreed to anything, and safe to run on a machine they are merely looking
|
|
19
|
+
// at.
|
|
20
|
+
//
|
|
21
|
+
// The installer deliberately targets a PRIVATE PREFIX by default rather than a
|
|
22
|
+
// global install: no write permission to the system prefix is required, the
|
|
23
|
+
// user's PATH is untouched, and a dsh they are already using is never replaced.
|
|
24
|
+
// That is the same shape used to get the E2E working by hand.
|
|
25
|
+
|
|
26
|
+
/** Machine-readable markers the probe prints; parsing never guesses. */
|
|
27
|
+
const MARK = {
|
|
28
|
+
platform: 'P_OS',
|
|
29
|
+
arch: 'P_ARCH',
|
|
30
|
+
node: 'P_NODE',
|
|
31
|
+
nodeVersion: 'P_NODE_V',
|
|
32
|
+
npm: 'P_NPM',
|
|
33
|
+
npmVersion: 'P_NPM_V',
|
|
34
|
+
dsh: 'P_DSH',
|
|
35
|
+
dshVersion: 'P_DSH_V',
|
|
36
|
+
dshRoot: 'P_DSH_ROOT',
|
|
37
|
+
noOpen: 'P_NOOPEN',
|
|
38
|
+
ptyPrebuild: 'P_PTY',
|
|
39
|
+
home: 'P_HOME',
|
|
40
|
+
writePrefix: 'P_WRITABLE',
|
|
41
|
+
prefix: 'P_PREFIX',
|
|
42
|
+
installed: 'P_INSTALLED',
|
|
43
|
+
installedVersion: 'P_INSTALLED_V',
|
|
44
|
+
installedPty: 'P_INSTALLED_PTY',
|
|
45
|
+
installedWeb: 'P_INSTALLED_WEB',
|
|
46
|
+
gitBash: 'P_GITBASH',
|
|
47
|
+
proxyVar: 'P_PROXY_NAME',
|
|
48
|
+
registry: 'P_REGISTRY',
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Build the READ-ONLY environment probe.
|
|
53
|
+
*
|
|
54
|
+
* Multiline rather than '; '-joined on purpose: the launch builder once shipped a
|
|
55
|
+
* `do;` POSIX syntax error that only a real shell caught, and newlines remove
|
|
56
|
+
* that entire failure mode. Nothing here writes, installs or deletes.
|
|
57
|
+
*
|
|
58
|
+
* @param {object} [opts] - probe options.
|
|
59
|
+
* @param {string} [opts.prefix] - directory the installer would use; reported
|
|
60
|
+
* only to say whether it is writable (the probe never creates it).
|
|
61
|
+
* @returns {string} a POSIX shell script.
|
|
62
|
+
*/
|
|
63
|
+
export function buildProbeCommand(opts = {}) {
|
|
64
|
+
// `prefix` may be:
|
|
65
|
+
// • an explicit absolute path from configuration, checked verbatim;
|
|
66
|
+
// • empty, meaning "use the default", which MUST be resolved ON THE REMOTE:
|
|
67
|
+
// the caller cannot know $HOME before the probe runs, and guessing (the
|
|
68
|
+
// caller's own /tmp) would report writability for a directory that has
|
|
69
|
+
// nothing to do with the real install location.
|
|
70
|
+
const prefix = String(opts.prefix || '')
|
|
71
|
+
const useDefaultPrefix = !prefix && opts.defaultPrefixFor === 'home'
|
|
72
|
+
// A dsh a previous deployment installed FOR THIS MACHINE. Probing only the
|
|
73
|
+
// PATH dsh would keep reporting the old broken one after a successful install,
|
|
74
|
+
// so the installed binary is probed explicitly and preferred when usable.
|
|
75
|
+
const installed = String(opts.installedCommand || '')
|
|
76
|
+
// Statements are one-per-line. `if ...; then` is legitimate shell, but mixing
|
|
77
|
+
// `;`-joined statements with a `while`/`if` body is exactly how the launch
|
|
78
|
+
// builder once produced a `do;` syntax error — so this stays newline-only and
|
|
79
|
+
// a test asserts that.
|
|
80
|
+
const lines = [
|
|
81
|
+
'echo "P_OS=$(uname -s 2>/dev/null || echo unknown)"',
|
|
82
|
+
'A=$(uname -m 2>/dev/null || echo unknown)',
|
|
83
|
+
'case "$A" in x86_64|amd64) A=x64;; aarch64|arm64) A=arm64;; esac',
|
|
84
|
+
'echo "P_ARCH=$A"',
|
|
85
|
+
'echo "P_HOME=$HOME"',
|
|
86
|
+
'N=$(command -v node 2>/dev/null)',
|
|
87
|
+
'echo "P_NODE=$N"',
|
|
88
|
+
'if [ -n "$N" ]',
|
|
89
|
+
'then',
|
|
90
|
+
' echo "P_NODE_V=$($N -v 2>/dev/null)"',
|
|
91
|
+
'fi',
|
|
92
|
+
'M=$(command -v npm 2>/dev/null)',
|
|
93
|
+
'echo "P_NPM=$M"',
|
|
94
|
+
'if [ -n "$M" ]',
|
|
95
|
+
'then',
|
|
96
|
+
' echo "P_NPM_V=$($M -v 2>/dev/null)"',
|
|
97
|
+
'fi',
|
|
98
|
+
'D=$(command -v dsh 2>/dev/null)',
|
|
99
|
+
'echo "P_DSH=$D"',
|
|
100
|
+
'if [ -n "$D" ]',
|
|
101
|
+
'then',
|
|
102
|
+
' echo "P_DSH_V=$($D --version 2>/dev/null | tail -1)"',
|
|
103
|
+
'fi',
|
|
104
|
+
]
|
|
105
|
+
// A prefix the caller is considering. Reported, never created.
|
|
106
|
+
if (useDefaultPrefix) {
|
|
107
|
+
// Resolve the default on the REMOTE and report it back, so the caller learns
|
|
108
|
+
// the real install path (and its writability) in the same round trip.
|
|
109
|
+
lines.push(
|
|
110
|
+
`DFLT="$HOME/${DEFAULT_PREFIX_SUFFIX}"`,
|
|
111
|
+
'echo "P_PREFIX=$DFLT"',
|
|
112
|
+
'if [ -w "$HOME" ]',
|
|
113
|
+
'then',
|
|
114
|
+
' echo "P_WRITABLE=parent:yes"',
|
|
115
|
+
'else',
|
|
116
|
+
' echo "P_WRITABLE=parent:no"',
|
|
117
|
+
'fi',
|
|
118
|
+
)
|
|
119
|
+
} else if (prefix) {
|
|
120
|
+
lines.push(
|
|
121
|
+
`if [ -d "${prefix}" ]`,
|
|
122
|
+
'then',
|
|
123
|
+
` if [ -w "${prefix}" ]`,
|
|
124
|
+
' then',
|
|
125
|
+
' echo "P_WRITABLE=yes"',
|
|
126
|
+
' else',
|
|
127
|
+
' echo "P_WRITABLE=no"',
|
|
128
|
+
' fi',
|
|
129
|
+
`elif [ -w "$(dirname "${prefix}")" ]`,
|
|
130
|
+
'then',
|
|
131
|
+
' echo "P_WRITABLE=parent:yes"',
|
|
132
|
+
'else',
|
|
133
|
+
' echo "P_WRITABLE=parent:no"',
|
|
134
|
+
'fi',
|
|
135
|
+
)
|
|
136
|
+
} else {
|
|
137
|
+
lines.push('echo "P_WRITABLE=unknown"')
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Probe whatever a previous deployment installed for THIS machine, and prefer
|
|
141
|
+
// it when it is usable. Without this a successful install is invisible to the
|
|
142
|
+
// next probe, because only the PATH dsh would be examined.
|
|
143
|
+
if (installed) {
|
|
144
|
+
lines.push(
|
|
145
|
+
`INSTALLED="${installed}"`,
|
|
146
|
+
'if [ -x "$INSTALLED" ]',
|
|
147
|
+
'then',
|
|
148
|
+
' echo "P_INSTALLED=yes"',
|
|
149
|
+
' echo "P_INSTALLED_V=$("$INSTALLED" --version 2>/dev/null | tail -1)"',
|
|
150
|
+
...resolveRootLines('INSTALLED', 'RI'),
|
|
151
|
+
' OSI=$(echo "$(uname -s)" | tr "A-Z" "a-z")',
|
|
152
|
+
' PTYI="$RI/node_modules/node-pty/prebuilds/$OSI-$A/pty.node"',
|
|
153
|
+
' ALTI="$RI/node_modules/@deepseek-ai/dsh/node_modules/node-pty/prebuilds/$OSI-$A/pty.node"',
|
|
154
|
+
' if [ -f "$PTYI" ] || [ -f "$ALTI" ]',
|
|
155
|
+
' then',
|
|
156
|
+
' echo "P_INSTALLED_PTY=yes"',
|
|
157
|
+
' else',
|
|
158
|
+
' echo "P_INSTALLED_PTY=no"',
|
|
159
|
+
' fi',
|
|
160
|
+
' if "$INSTALLED" web --help 2>&1 | grep -q -- "--port"',
|
|
161
|
+
' then',
|
|
162
|
+
' echo "P_INSTALLED_WEB=yes"',
|
|
163
|
+
' else',
|
|
164
|
+
' echo "P_INSTALLED_WEB=no"',
|
|
165
|
+
' fi',
|
|
166
|
+
'else',
|
|
167
|
+
' echo "P_INSTALLED=no"',
|
|
168
|
+
'fi',
|
|
169
|
+
)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// A POSIX shell on a Windows remote. The launch/install commands need one, and
|
|
173
|
+
// this is the single fact that decides whether a Windows host is deployable.
|
|
174
|
+
lines.push(
|
|
175
|
+
'GB=""',
|
|
176
|
+
'for c in /usr/bin/bash.exe /bin/bash.exe bash',
|
|
177
|
+
'do',
|
|
178
|
+
' if command -v "$c" >/dev/null 2>&1',
|
|
179
|
+
' then',
|
|
180
|
+
' GB=$(command -v "$c")',
|
|
181
|
+
' break',
|
|
182
|
+
' fi',
|
|
183
|
+
'done',
|
|
184
|
+
'echo "P_GITBASH=$GB"',
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
// Proxy configuration: REPORTED only. An install that inherits a broken proxy
|
|
188
|
+
// is a plausible cause of a slow or failing npm, and the user is the one who
|
|
189
|
+
// knows the right value. The probe never sets or changes it.
|
|
190
|
+
lines.push(
|
|
191
|
+
'PX=""',
|
|
192
|
+
'for v in HTTPS_PROXY https_proxy HTTP_PROXY http_proxy ALL_PROXY all_proxy',
|
|
193
|
+
'do',
|
|
194
|
+
' EV=$(eval "echo \\$$v")',
|
|
195
|
+
' if [ -n "$EV" ]',
|
|
196
|
+
' then',
|
|
197
|
+
' PX="$v"',
|
|
198
|
+
' break',
|
|
199
|
+
' fi',
|
|
200
|
+
'done',
|
|
201
|
+
'echo "P_PROXY_NAME=$PX"',
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
// Whether npm already points at a non-default registry (an internal mirror).
|
|
205
|
+
// Reported so an install can keep using it rather than overriding it.
|
|
206
|
+
lines.push(
|
|
207
|
+
'if [ -n "$M" ]',
|
|
208
|
+
'then',
|
|
209
|
+
' RG=$($M config get registry 2>/dev/null | head -1)',
|
|
210
|
+
' echo "P_REGISTRY=$RG"',
|
|
211
|
+
'fi',
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
// Facts that need the dsh binary: where it lives and whether it can boot web.
|
|
215
|
+
lines.push(
|
|
216
|
+
'if [ -n "$D" ]',
|
|
217
|
+
'then',
|
|
218
|
+
' if [ -n "$N" ]',
|
|
219
|
+
' then',
|
|
220
|
+
...resolveRootLines('D', 'R').map((l) => ' ' + l),
|
|
221
|
+
' echo "P_DSH_ROOT=$R"',
|
|
222
|
+
' OS=$(echo "$(uname -s)" | tr "A-Z" "a-z")',
|
|
223
|
+
' PTY="$R/node_modules/node-pty/prebuilds/$OS-$A/pty.node"',
|
|
224
|
+
' ALT="$R/node_modules/@deepseek-ai/dsh/node_modules/node-pty/prebuilds/$OS-$A/pty.node"',
|
|
225
|
+
' if [ -f "$PTY" ] || [ -f "$ALT" ]',
|
|
226
|
+
' then',
|
|
227
|
+
' echo "P_PTY=yes"',
|
|
228
|
+
' else',
|
|
229
|
+
' echo "P_PTY=no"',
|
|
230
|
+
' fi',
|
|
231
|
+
' fi',
|
|
232
|
+
// `web --help` is cheap and reveals the flag family; it does NOT prove the
|
|
233
|
+
// surface boots (a broken native module still prints help), which is why
|
|
234
|
+
// P_PTY is probed separately.
|
|
235
|
+
' if $D web --help 2>&1 | grep -q -- "--no-open"',
|
|
236
|
+
' then',
|
|
237
|
+
' echo "P_NOOPEN=yes"',
|
|
238
|
+
' else',
|
|
239
|
+
' echo "P_NOOPEN=no"',
|
|
240
|
+
' fi',
|
|
241
|
+
'fi',
|
|
242
|
+
)
|
|
243
|
+
return lines.join('\n')
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/** Read one `KEY=value` line out of probe output. */
|
|
247
|
+
function valueOf(text, key) {
|
|
248
|
+
const re = new RegExp('^' + key + '=(.*)$', 'm')
|
|
249
|
+
const m = re.exec(text)
|
|
250
|
+
return m ? m[1].trim() : ''
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Resolve a `dsh` executable to the PREFIX ROOT that owns its `node_modules`,
|
|
255
|
+
* as a POSIX shell fragment (one statement per line).
|
|
256
|
+
*
|
|
257
|
+
* Not `dirname(dirname(bin))`. The bin is typically a symlink like
|
|
258
|
+
* `node_modules/.bin/dsh -> ../@deepseek-ai/dsh/lib/bin.js`, so realpath puts us
|
|
259
|
+
* at `<root>/node_modules/@deepseek-ai/dsh/lib/bin.js` and two dirnames land at
|
|
260
|
+
* `<root>/node_modules/@deepseek-ai/dsh` — INSIDE the package, where a sibling
|
|
261
|
+
* `node-pty` does not exist. Measured on a real host: that produced a false
|
|
262
|
+
* "pty missing" verdict for a perfectly good install, and the same mistake would
|
|
263
|
+
* hide a genuinely broken one.
|
|
264
|
+
*
|
|
265
|
+
* Walking up to the first ancestor that contains a `node_modules` DIRECTORY
|
|
266
|
+
* resolves both layouts: a private prefix (`.../dsh/node_modules/.bin/dsh`) and a
|
|
267
|
+
* global install (`.../lib/node_modules/@deepseek-ai/dsh/lib/bin.js`).
|
|
268
|
+
*
|
|
269
|
+
* @param {string} binVar - shell variable holding the executable path.
|
|
270
|
+
* @param {string} outVar - shell variable to receive the resolved root.
|
|
271
|
+
* @returns {string[]} shell lines.
|
|
272
|
+
*/
|
|
273
|
+
export function resolveRootLines(binVar, outVar) {
|
|
274
|
+
return [
|
|
275
|
+
`${outVar}=""`,
|
|
276
|
+
`RD="$(dirname "$${binVar}")"`,
|
|
277
|
+
'i=0',
|
|
278
|
+
'while [ $i -lt 8 ]',
|
|
279
|
+
'do',
|
|
280
|
+
` if [ -d "$RD/node_modules" ]`,
|
|
281
|
+
' then',
|
|
282
|
+
` ${outVar}="$RD"`,
|
|
283
|
+
' break',
|
|
284
|
+
' fi',
|
|
285
|
+
' NC="$(dirname "$RD")"',
|
|
286
|
+
' if [ "$NC" = "$RD" ]',
|
|
287
|
+
' then',
|
|
288
|
+
' break',
|
|
289
|
+
' fi',
|
|
290
|
+
' RD="$NC"',
|
|
291
|
+
' i=$((i+1))',
|
|
292
|
+
'done',
|
|
293
|
+
]
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Parse probe output into facts. Absent keys stay empty rather than defaulting,
|
|
298
|
+
* so the judge can tell "not installed" from "probe could not tell".
|
|
299
|
+
*
|
|
300
|
+
* @param {string} output - raw stdout of {@link buildProbeCommand}.
|
|
301
|
+
* @returns {object} parsed facts.
|
|
302
|
+
*/
|
|
303
|
+
export function parseProbe(output) {
|
|
304
|
+
const text = String(output || '')
|
|
305
|
+
const platform = valueOf(text, MARK.platform)
|
|
306
|
+
const lower = platform.toLowerCase()
|
|
307
|
+
// Git Bash / MSYS / Cygwin report an emulated uname; treat them as Windows.
|
|
308
|
+
const windows = /mingw|msys|cygwin|windows/i.test(platform)
|
|
309
|
+
return {
|
|
310
|
+
platform: lower === 'unknown' ? '' : platform,
|
|
311
|
+
arch: valueOf(text, MARK.arch),
|
|
312
|
+
windows,
|
|
313
|
+
posix: !windows && lower !== 'unknown' && lower !== '',
|
|
314
|
+
node: valueOf(text, MARK.node),
|
|
315
|
+
nodeVersion: valueOf(text, MARK.nodeVersion),
|
|
316
|
+
npm: valueOf(text, MARK.npm),
|
|
317
|
+
npmVersion: valueOf(text, MARK.npmVersion),
|
|
318
|
+
dsh: valueOf(text, MARK.dsh),
|
|
319
|
+
dshVersion: valueOf(text, MARK.dshVersion),
|
|
320
|
+
dshRoot: valueOf(text, MARK.dshRoot),
|
|
321
|
+
noOpen: valueOf(text, MARK.noOpen),
|
|
322
|
+
ptyPrebuild: valueOf(text, MARK.ptyPrebuild),
|
|
323
|
+
installed: valueOf(text, MARK.installed),
|
|
324
|
+
installedVersion: valueOf(text, MARK.installedVersion),
|
|
325
|
+
installedPty: valueOf(text, MARK.installedPty),
|
|
326
|
+
installedWeb: valueOf(text, MARK.installedWeb),
|
|
327
|
+
gitBash: valueOf(text, MARK.gitBash),
|
|
328
|
+
proxyVar: valueOf(text, MARK.proxyVar),
|
|
329
|
+
registry: valueOf(text, MARK.registry),
|
|
330
|
+
home: valueOf(text, MARK.home),
|
|
331
|
+
writable: valueOf(text, MARK.writePrefix),
|
|
332
|
+
// The prefix the REMOTE resolved for itself (only reported when the caller
|
|
333
|
+
// asked for the default). Absent when an explicit prefix was probed.
|
|
334
|
+
resolvedPrefix: valueOf(text, MARK.prefix),
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/** Compare dotted versions; returns true when `a` >= `b` (empty `a` is false). */
|
|
339
|
+
export function versionAtLeast(a, b) {
|
|
340
|
+
const parse = (v) => String(v || '').replace(/^v/, '').split(/[.\-+]/).map((x) => Number(x) || 0)
|
|
341
|
+
const [x, y] = [parse(a), parse(b)]
|
|
342
|
+
if (!String(a || '').trim()) return false
|
|
343
|
+
for (let i = 0; i < Math.max(x.length, y.length); i++) {
|
|
344
|
+
const l = x[i] || 0
|
|
345
|
+
const r = y[i] || 0
|
|
346
|
+
if (l !== r) return l > r
|
|
347
|
+
}
|
|
348
|
+
return true
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/** The first dsh release whose node-pty ships Linux prebuilds (measured). */
|
|
352
|
+
export const PTY_FIXED_VERSION = '0.1.5-rc.2'
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Minimum Node the remote must run, and the reason it is exactly this number.
|
|
356
|
+
*
|
|
357
|
+
* DSH's boot path imports `parseEnv` from `node:util`, and that export only
|
|
358
|
+
* exists from **20.12.0** (Node 21.7.0 and later have it too). Measured on a
|
|
359
|
+
* real host with two nodes installed:
|
|
360
|
+
*
|
|
361
|
+
* /usr/bin/node (v18.19.1) → parseEnv: undefined
|
|
362
|
+
* ~/.nvm/versions/node/v24.22.0/bin/node (v24.22.0) → parseEnv: function
|
|
363
|
+
*
|
|
364
|
+
* The failure mode is what makes this worth a BLOCKER rather than a warning: npm
|
|
365
|
+
* only prints EBADENGINE (it does not refuse), so all ~535 packages install
|
|
366
|
+
* cleanly and the crash arrives afterwards as
|
|
367
|
+
*
|
|
368
|
+
* SyntaxError: The requested module 'node:util' does not provide an export
|
|
369
|
+
* named 'parseEnv' at .../@deepseek-ai/dsh-app-boot/lib/index.js:4
|
|
370
|
+
*
|
|
371
|
+
* which does not read as "your node is too old". Checking the version BEFORE the
|
|
372
|
+
* install is the only way to turn that into an actionable message.
|
|
373
|
+
*
|
|
374
|
+
* Reference: Node.js 20.12.0 release notes, "Loading and parsing environment
|
|
375
|
+
* variables" — https://nodejs.org/en/blog/release/v20.12.0
|
|
376
|
+
*/
|
|
377
|
+
export const MIN_NODE_VERSION = '20.12.0'
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Turn facts into a verdict: what is wrong, why, and what to do.
|
|
381
|
+
*
|
|
382
|
+
* Severity is deliberately coarse — `blocker` means "connecting cannot work
|
|
383
|
+
* until this changes", `warn` means "works, but with a caveat", `info` means
|
|
384
|
+
* "worth knowing", `ok` means "nothing to do". The UI maps these directly.
|
|
385
|
+
*
|
|
386
|
+
* @param {object} facts - parsed probe facts.
|
|
387
|
+
* @param {object} [opts] - judgement options.
|
|
388
|
+
* @param {string} [opts.installedCommand] - command the installer already placed, if any.
|
|
389
|
+
* @returns {{severity: string, ok: boolean, findings: object[], useCommand: string, canAutoInstall: boolean}}
|
|
390
|
+
*/
|
|
391
|
+
export function judgeProbe(facts, opts = {}) {
|
|
392
|
+
const f = facts || {}
|
|
393
|
+
const findings = []
|
|
394
|
+
const add = (code, severity, summary, detail, fix) => {
|
|
395
|
+
findings.push({ code, severity, summary, detail: detail || '', ...(fix ? { fix } : {}) })
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// ── platform / shell ──────────────────────────────────────────────────────
|
|
399
|
+
if (!f.platform) {
|
|
400
|
+
add('PLATFORM_UNKNOWN', 'warn', '无法识别远端操作系统',
|
|
401
|
+
'探测没有返回可识别的 uname 结果。', '确认该主机是常规 Linux/macOS/Windows。')
|
|
402
|
+
} else if (f.windows) {
|
|
403
|
+
// The launch command is POSIX shell (setsid/nohup). pool.exec wraps commands
|
|
404
|
+
// in Git Bash when it finds one on a Windows remote; without it there is no
|
|
405
|
+
// POSIX shell to run them in, and an install would produce a dsh we could
|
|
406
|
+
// never start.
|
|
407
|
+
if (f.gitBash) {
|
|
408
|
+
add('WINDOWS_REMOTE_GITBASH', 'info', '远端是 Windows 主机,但有 Git Bash',
|
|
409
|
+
'启动命令会经 Git Bash 执行,自动部署与连接都可用。')
|
|
410
|
+
} else {
|
|
411
|
+
add('WINDOWS_REMOTE', 'warn', '远端是 Windows 主机且没有 Git Bash',
|
|
412
|
+
'本功能的启动命令是 POSIX shell(依赖 setsid/nohup),没有 Git Bash 就无法执行。',
|
|
413
|
+
'在该机器安装 Git Bash(随 Git for Windows 一起),或改用 Linux/macOS 远端。')
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// ── node / npm ────────────────────────────────────────────────────────────
|
|
418
|
+
if (!f.node) {
|
|
419
|
+
add('NO_NODE', 'blocker', '远端没有 node',
|
|
420
|
+
'自动部署需要 node 与 npm。',
|
|
421
|
+
'先在该机器安装 Node.js(nvm 或系统包管理器),再回来重试。')
|
|
422
|
+
} else if (f.nodeVersion && !versionAtLeast(f.nodeVersion, MIN_NODE_VERSION)) {
|
|
423
|
+
// The install itself SUCCEEDS on this node — npm only warns (EBADENGINE) —
|
|
424
|
+
// and the failure surfaces later as a parseEnv import error that does not
|
|
425
|
+
// read as a version problem. So the check has to happen before the install.
|
|
426
|
+
add('NODE_TOO_OLD', 'blocker',
|
|
427
|
+
`远端 node ${f.nodeVersion} 版本过低(需要 ≥ ${MIN_NODE_VERSION})`,
|
|
428
|
+
`dsh 启动时要 import node:util 的 parseEnv,该导出从 Node ${MIN_NODE_VERSION} 起才有。`
|
|
429
|
+
+ '在这个版本上 npm 只会给 EBADENGINE 警告、仍然把包装完,'
|
|
430
|
+
+ '直到启动时才崩:SyntaxError: The requested module \'node:util\' does not provide an export named \'parseEnv\'。',
|
|
431
|
+
`升级该机器的 node 到 ${MIN_NODE_VERSION} 以上(例如 nvm install 22),`
|
|
432
|
+
+ '并把 `webAttachCommand` 指向新 node 的绝对路径(如 ~/.nvm/versions/node/v22.x/bin/node <前缀>/node_modules/@deepseek-ai/dsh/lib/bin.js)。')
|
|
433
|
+
}
|
|
434
|
+
if (!f.npm) {
|
|
435
|
+
add('NO_NPM', 'blocker', '远端没有 npm',
|
|
436
|
+
'自动部署通过 npm 拉取 dsh。',
|
|
437
|
+
'随 Node.js 一起安装 npm(或换用 nvm 安装的 node)。')
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// ── existing dsh ──────────────────────────────────────────────────────────
|
|
441
|
+
if (!f.dsh) {
|
|
442
|
+
add('NO_DSH', 'info', '远端没有 dsh',
|
|
443
|
+
'没有找到可执行的 dsh。',
|
|
444
|
+
'可以自动装到私有前缀,不影响该机器上已装的任何东西。')
|
|
445
|
+
} else {
|
|
446
|
+
// The exact failure measured on a real host: node-pty has no prebuild for
|
|
447
|
+
// this platform, so the web surface cannot boot at all.
|
|
448
|
+
if (f.ptyPrebuild === 'no' && !f.windows) {
|
|
449
|
+
add('PTY_MISSING', 'blocker',
|
|
450
|
+
`远端 dsh ${f.dshVersion || '(版本未知)'} 无法启动 web 界面`,
|
|
451
|
+
'它依赖的 node-pty 在当前平台没有预编译产物,'
|
|
452
|
+
+ '启动会直接失败(Failed to load native module: pty.node)。',
|
|
453
|
+
`装一个 ${PTY_FIXED_VERSION} 或更新的 dsh 到私有前缀即可(其 node-pty 带该平台产物)。`)
|
|
454
|
+
}
|
|
455
|
+
if (f.noOpen === 'no') {
|
|
456
|
+
// Handled automatically by WebAttach (it retries without the flag), so
|
|
457
|
+
// this is informational rather than a problem.
|
|
458
|
+
add('OLD_FLAG_SET', 'info', `远端 dsh ${f.dshVersion || ''} 不认识 --no-open`,
|
|
459
|
+
'这是较新的 flag;插件会自动去掉它重试一次,无需处理。',
|
|
460
|
+
'升级到较新版本可避免这条重试。')
|
|
461
|
+
}
|
|
462
|
+
if (f.noOpen === 'yes' && f.ptyPrebuild !== 'no') {
|
|
463
|
+
add('DSH_OK', 'ok', `远端 dsh ${f.dshVersion || ''} 可以直接使用`,
|
|
464
|
+
'已装且有当前平台的 node-pty 预编译产物。')
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// ── prefix writability (only relevant when we would install) ──────────────
|
|
469
|
+
if ((!f.dsh || f.ptyPrebuild === 'no') && f.writable === 'no') {
|
|
470
|
+
add('PREFIX_UNWRITABLE', 'warn', '默认安装位置不可写',
|
|
471
|
+
'自动部署会改用 $HOME 下的私有目录;若 $HOME 也不可写则需要人工处理。',
|
|
472
|
+
'通常无需处理,安装器会回退到 $HOME。')
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// ── a dsh a previous deployment installed for this machine ─────────────────
|
|
476
|
+
// This must be judged BEFORE the PATH dsh: an installed build is the whole
|
|
477
|
+
// point of a deployment, and reporting the old broken PATH dsh after a
|
|
478
|
+
// successful install would make the feature look like it did nothing.
|
|
479
|
+
const installed = String(opts.installedCommand || '').trim()
|
|
480
|
+
const installedUsable = f.installed === 'yes'
|
|
481
|
+
&& f.installedPty !== 'no'
|
|
482
|
+
&& f.installedWeb !== 'no'
|
|
483
|
+
if (installed && installedUsable) {
|
|
484
|
+
add('INSTALLED_OK', 'ok',
|
|
485
|
+
`已部署的 dsh ${f.installedVersion || ''} 可用`,
|
|
486
|
+
'该机器上之前自动部署的版本已通过校验(native 模块与 web 子命令都在)。')
|
|
487
|
+
} else if (installed && f.installed === 'no') {
|
|
488
|
+
add('INSTALLED_MISSING', 'info', '之前部署的 dsh 已不在',
|
|
489
|
+
`${installed} 不存在(可能被清理或换了机器镜像)。`,
|
|
490
|
+
'重新部署一次即可。')
|
|
491
|
+
} else if (installed && f.installed === 'yes' && !installedUsable) {
|
|
492
|
+
add('INSTALLED_UNUSABLE', 'warn', '已部署的 dsh 校验未通过',
|
|
493
|
+
'它存在但 web 界面或 native 模块有问题。',
|
|
494
|
+
'重新部署一次;仍失败时用「让 AI 排查」。')
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// ── environment notes that change what an install will do ─────────────────
|
|
498
|
+
// Reported, never "fixed": the user is the one who knows whether the proxy or
|
|
499
|
+
// mirror is correct, and silently overriding either would be worse than slow.
|
|
500
|
+
if (f.proxyVar) {
|
|
501
|
+
add('PROXY_SET', 'info', `远端设置了代理(${f.proxyVar})`,
|
|
502
|
+
'安装会沿用远端已有的代理设置。',
|
|
503
|
+
'若 npm 下载很慢或失败,检查这个代理是否可达。')
|
|
504
|
+
}
|
|
505
|
+
if (f.registry && !/registry\.npmjs\.org/.test(f.registry)) {
|
|
506
|
+
add('CUSTOM_REGISTRY', 'info', `远端 npm 使用了自定义源:${f.registry}`,
|
|
507
|
+
'安装默认沿用远端自己的 npm 配置,不会覆盖它。')
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
const blockers = findings.filter((x) => x.severity === 'blocker')
|
|
511
|
+
// A Windows remote with Git Bash can be installed into: pool.exec pipes every
|
|
512
|
+
// command through `bash -s`, so the POSIX plan works there.
|
|
513
|
+
//
|
|
514
|
+
// A node older than MIN_NODE_VERSION makes an install POINTLESS as well as
|
|
515
|
+
// failing: the packages land, then the binary cannot boot. Promising
|
|
516
|
+
// "can auto-install: yes" there is what sent the reporter of issue #54 through
|
|
517
|
+
// a 535-package install that ended in a parseEnv SyntaxError.
|
|
518
|
+
const nodeUsable = !!f.node && (!f.nodeVersion || versionAtLeast(f.nodeVersion, MIN_NODE_VERSION))
|
|
519
|
+
const canAutoInstall = nodeUsable && !!f.npm && (!f.windows || !!f.gitBash)
|
|
520
|
+
|
|
521
|
+
// Which command should the attach flow use? A verified deployment wins, then a
|
|
522
|
+
// usable dsh already on PATH.
|
|
523
|
+
//
|
|
524
|
+
// Note `noOpen` deliberately does NOT disqualify a PATH dsh: WebAttach already
|
|
525
|
+
// retries without `--no-open`, so an old version is perfectly usable. Treating
|
|
526
|
+
// it as unusable would tell users to reinstall for no reason.
|
|
527
|
+
const usablePath = f.dsh && f.ptyPrebuild !== 'no' ? f.dsh : ''
|
|
528
|
+
const useCommand = installedUsable ? installed : usablePath
|
|
529
|
+
|
|
530
|
+
// If the installed build works, the PATH dsh's shortcomings are no longer
|
|
531
|
+
// blockers — the user can connect right now.
|
|
532
|
+
const effectiveBlockers = installedUsable
|
|
533
|
+
? blockers.filter((x) => x.code !== 'PTY_MISSING')
|
|
534
|
+
: blockers
|
|
535
|
+
|
|
536
|
+
return {
|
|
537
|
+
ok: effectiveBlockers.length === 0,
|
|
538
|
+
severity: effectiveBlockers.length ? 'blocker'
|
|
539
|
+
: findings.some((x) => x.severity === 'warn') ? 'warn' : 'ok',
|
|
540
|
+
findings,
|
|
541
|
+
useCommand,
|
|
542
|
+
canAutoInstall,
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// ── installation ────────────────────────────────────────────────────────────
|
|
547
|
+
|
|
548
|
+
/** Where the installer puts dsh when the user does not choose. */
|
|
549
|
+
export const DEFAULT_PREFIX_SUFFIX = '.dsh-remote/dsh'
|
|
550
|
+
/** Default version: the first release whose node-pty ships Linux prebuilds. */
|
|
551
|
+
export const DEFAULT_INSTALL_VERSION = PTY_FIXED_VERSION
|
|
552
|
+
|
|
553
|
+
/**
|
|
554
|
+
* Resolve the install prefix from probe facts and configuration.
|
|
555
|
+
*
|
|
556
|
+
* Preference order is deliberate: an explicit user setting, then `$HOME`, and
|
|
557
|
+
* only then a temp directory. `$HOME` keeps the install across reboots, which
|
|
558
|
+
* matters because the resulting `webAttachCommand` is written into the machine
|
|
559
|
+
* record and would otherwise dangle after a reboot wipes `/tmp`.
|
|
560
|
+
*
|
|
561
|
+
* @param {object} facts - parsed probe facts.
|
|
562
|
+
* @param {object} [opts] - overrides.
|
|
563
|
+
* @param {string} [opts.prefix] - explicit prefix.
|
|
564
|
+
* @returns {string} a remote directory path.
|
|
565
|
+
*/
|
|
566
|
+
export function resolvePrefix(facts, opts = {}) {
|
|
567
|
+
const explicit = String(opts.prefix || '').trim()
|
|
568
|
+
if (explicit) return explicit
|
|
569
|
+
const home = String((facts && facts.home) || '').trim()
|
|
570
|
+
if (home) return `${home.replace(/\/+$/, '')}/${DEFAULT_PREFIX_SUFFIX}`
|
|
571
|
+
return `/tmp/${DEFAULT_PREFIX_SUFFIX}`
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
/** Quote one shell argument. */
|
|
575
|
+
function q(s) {
|
|
576
|
+
return `'${String(s).replace(/'/g, `'\\''`)}'`
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
/**
|
|
580
|
+
* Build the ordered install plan.
|
|
581
|
+
*
|
|
582
|
+
* Each step is a separate SSH exec rather than one long script, because the
|
|
583
|
+
* caller wants per-step progress and a failure report that names the step. The
|
|
584
|
+
* plan is pure data so a test can assert the exact commands without a shell.
|
|
585
|
+
*
|
|
586
|
+
* @param {object} spec - install parameters.
|
|
587
|
+
* @param {object} spec.facts - parsed probe facts.
|
|
588
|
+
* @param {string} spec.prefix - remote prefix directory.
|
|
589
|
+
* @param {string} [spec.version] - dsh version to install.
|
|
590
|
+
* @param {string} [spec.registry] - npm registry to use.
|
|
591
|
+
* @param {object} [spec.env] - env vars for the install step (proxy, mirror…).
|
|
592
|
+
* @param {boolean} [spec.forcePosix] - allow the plan for a Windows remote that
|
|
593
|
+
* has Git Bash (pool.exec then pipes commands through `bash -s`).
|
|
594
|
+
* @returns {{steps: object[], command: string}} the plan and the resulting dsh path.
|
|
595
|
+
*/
|
|
596
|
+
export function buildInstallPlan(spec) {
|
|
597
|
+
const facts = spec.facts || {}
|
|
598
|
+
const prefix = String(spec.prefix || '').replace(/\/+$/, '')
|
|
599
|
+
const version = String(spec.version || DEFAULT_INSTALL_VERSION)
|
|
600
|
+
const registry = String(spec.registry || '')
|
|
601
|
+
const env = spec.env && typeof spec.env === 'object' ? spec.env : undefined
|
|
602
|
+
const bin = `${prefix}/node_modules/.bin/dsh`
|
|
603
|
+
|
|
604
|
+
// `--no-audit --no-fund` keeps output small; `--loglevel=error` keeps the
|
|
605
|
+
// captured stdout parseable. `--prefix` installs locally without touching any
|
|
606
|
+
// system or user-global prefix.
|
|
607
|
+
const npmArgs = [
|
|
608
|
+
'install', '--prefix', q(prefix),
|
|
609
|
+
'--no-audit', '--no-fund', '--loglevel=error',
|
|
610
|
+
// `--ignore-scripts=false` is npm's default and is stated explicitly here
|
|
611
|
+
// because a restrictive global config would otherwise silently skip
|
|
612
|
+
// node-pty's build/install step, producing an install that cannot boot web.
|
|
613
|
+
...(registry ? ['--registry', q(registry)] : []),
|
|
614
|
+
q(`@deepseek-ai/dsh@${version}`),
|
|
615
|
+
]
|
|
616
|
+
|
|
617
|
+
const steps = [
|
|
618
|
+
{
|
|
619
|
+
id: 'prepare',
|
|
620
|
+
title: '准备安装目录',
|
|
621
|
+
// A private package.json stops npm from walking up to an unrelated
|
|
622
|
+
// project and mutating its dependency tree.
|
|
623
|
+
command: `mkdir -p ${q(prefix)} && printf '%s\\n' `
|
|
624
|
+
+ `'{"name":"dsh-remote-install","private":true,"version":"0.0.0"}' > ${q(`${prefix}/package.json`)}`
|
|
625
|
+
+ ` && echo OK`,
|
|
626
|
+
},
|
|
627
|
+
{
|
|
628
|
+
id: 'install',
|
|
629
|
+
title: `安装 dsh@${version} 到私有目录`,
|
|
630
|
+
command: `cd ${q(prefix)} && ${facts.npm || 'npm'} ${npmArgs.join(' ')}`,
|
|
631
|
+
// npm can legitimately take minutes on a cold cache.
|
|
632
|
+
timeoutMs: 600000,
|
|
633
|
+
...(env ? { env } : {}),
|
|
634
|
+
},
|
|
635
|
+
{
|
|
636
|
+
id: 'verify-binary',
|
|
637
|
+
title: '校验安装结果',
|
|
638
|
+
command: `test -x ${q(bin)} && ${q(bin)} --version`,
|
|
639
|
+
},
|
|
640
|
+
]
|
|
641
|
+
|
|
642
|
+
// A native-module check is what actually distinguishes "installed" from
|
|
643
|
+
// "installed and able to boot the web surface" — the exact gap that made the
|
|
644
|
+
// original failure so opaque. It uses the walk-up rule from
|
|
645
|
+
// `resolveRootLines` rather than dirname(dirname()), which lands inside the
|
|
646
|
+
// package and yields a FALSE NEGATIVE on a good install (measured).
|
|
647
|
+
steps.push({
|
|
648
|
+
id: 'verify-pty',
|
|
649
|
+
title: '校验 web 界面可启动(native 模块)',
|
|
650
|
+
command: [
|
|
651
|
+
`BIN=${q(bin)}`,
|
|
652
|
+
...resolveRootLines('BIN', 'ROOT'),
|
|
653
|
+
`OS=$(echo "$(uname -s)" | tr 'A-Z' 'a-z')`,
|
|
654
|
+
`A=$(uname -m)`,
|
|
655
|
+
`case "$A" in x86_64|amd64) A=x64;; aarch64|arm64) A=arm64;; esac`,
|
|
656
|
+
`P1="$ROOT/node_modules/node-pty/prebuilds/$OS-$A/pty.node"`,
|
|
657
|
+
`P2="$ROOT/node_modules/@deepseek-ai/dsh/node_modules/node-pty/prebuilds/$OS-$A/pty.node"`,
|
|
658
|
+
// One `if` rather than a `||` continuation: a line beginning with `||`
|
|
659
|
+
// after a newline is a syntax error in POSIX sh (caught by the real-shell
|
|
660
|
+
// gate, not by string assertions).
|
|
661
|
+
`if [ -f "$P1" ] || [ -f "$P2" ]`,
|
|
662
|
+
`then`,
|
|
663
|
+
` echo PTY_OK`,
|
|
664
|
+
`else`,
|
|
665
|
+
` echo "PTY_MISSING: no prebuild at $P1 or $P2" >&2`,
|
|
666
|
+
` exit 1`,
|
|
667
|
+
`fi`,
|
|
668
|
+
].join('\n'),
|
|
669
|
+
})
|
|
670
|
+
|
|
671
|
+
steps.push({
|
|
672
|
+
id: 'verify-web',
|
|
673
|
+
title: '校验 dsh web 可用',
|
|
674
|
+
command: `${q(bin)} web --help 2>&1 | grep -q -- '--port'`,
|
|
675
|
+
})
|
|
676
|
+
|
|
677
|
+
return { steps, command: bin }
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
/** One step's outcome, as returned to the UI. */
|
|
681
|
+
export function stepResult(step, res) {
|
|
682
|
+
const out = String((res && res.stdout) || '')
|
|
683
|
+
const err = String((res && res.stderr) || '')
|
|
684
|
+
const code = res && res.code !== undefined ? res.code : 0
|
|
685
|
+
return {
|
|
686
|
+
id: step.id,
|
|
687
|
+
title: step.title,
|
|
688
|
+
ok: code === 0,
|
|
689
|
+
code,
|
|
690
|
+
// Keep a bounded tail: enough to diagnose, small enough to stream to a UI.
|
|
691
|
+
output: (out + (err ? '\n' + err : '')).trim().slice(-1200),
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
|