dsh-remote 0.8.48 → 0.8.49
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/lib/client.js +89 -16
- package/lib/index.js +37 -0
- package/lib/remote-shell-child.cjs +305 -0
- package/lib/remote-shell.js +518 -0
- package/package.json +1 -2
- package/node_modules/buildcheck/.eslintrc.js +0 -5
- package/node_modules/buildcheck/.github/workflows/ci.yml +0 -88
- package/node_modules/buildcheck/.github/workflows/lint.yml +0 -27
- package/node_modules/buildcheck/.npmignore +0 -3
- package/node_modules/buildcheck/LICENSE +0 -19
- package/node_modules/buildcheck/README.md +0 -106
- package/node_modules/buildcheck/deps/Find-VisualStudio.cs +0 -250
- package/node_modules/buildcheck/lib/findvs.js +0 -361
- package/node_modules/buildcheck/lib/index.js +0 -738
- package/node_modules/buildcheck/package.json +0 -36
- package/node_modules/buildcheck/test/test.js +0 -8
- package/node_modules/cpu-features/.eslintrc.js +0 -5
- package/node_modules/cpu-features/.github/workflows/ci.yml +0 -74
- package/node_modules/cpu-features/.github/workflows/lint.yml +0 -27
- package/node_modules/cpu-features/LICENSE +0 -19
- package/node_modules/cpu-features/README.md +0 -59
- package/node_modules/cpu-features/binding.gyp +0 -16
- package/node_modules/cpu-features/buildcheck.js +0 -32
- package/node_modules/cpu-features/deps/cpu_features/.clang-format +0 -4
- package/node_modules/cpu-features/deps/cpu_features/.dockerignore +0 -31
- package/node_modules/cpu-features/deps/cpu_features/.github/workflows/Dockerfile +0 -5
- package/node_modules/cpu-features/deps/cpu_features/.github/workflows/aarch64_linux_cmake.yml +0 -30
- package/node_modules/cpu-features/deps/cpu_features/.github/workflows/amd64_freebsd_cmake.yml +0 -22
- package/node_modules/cpu-features/deps/cpu_features/.github/workflows/amd64_linux_bazel.yml +0 -26
- package/node_modules/cpu-features/deps/cpu_features/.github/workflows/amd64_linux_cmake.yml +0 -31
- package/node_modules/cpu-features/deps/cpu_features/.github/workflows/amd64_macos_cmake.yml +0 -43
- package/node_modules/cpu-features/deps/cpu_features/.github/workflows/amd64_windows_cmake.yml +0 -25
- package/node_modules/cpu-features/deps/cpu_features/.github/workflows/arm_linux_cmake.yml +0 -31
- package/node_modules/cpu-features/deps/cpu_features/.github/workflows/clang_format.yml +0 -24
- package/node_modules/cpu-features/deps/cpu_features/.github/workflows/mips_linux_cmake.yml +0 -30
- package/node_modules/cpu-features/deps/cpu_features/.github/workflows/power_linux_cmake.yml +0 -29
- package/node_modules/cpu-features/deps/cpu_features/.github/workflows/riscv_linux_cmake.yml +0 -28
- package/node_modules/cpu-features/deps/cpu_features/.github/workflows/s390x_linux_cmake.yml +0 -27
- package/node_modules/cpu-features/deps/cpu_features/.grenrc.yml +0 -21
- package/node_modules/cpu-features/deps/cpu_features/BUILD.bazel +0 -329
- package/node_modules/cpu-features/deps/cpu_features/CMakeLists.txt +0 -261
- package/node_modules/cpu-features/deps/cpu_features/CONTRIBUTING.md +0 -23
- package/node_modules/cpu-features/deps/cpu_features/LICENSE +0 -230
- package/node_modules/cpu-features/deps/cpu_features/README.md +0 -272
- package/node_modules/cpu-features/deps/cpu_features/WORKSPACE +0 -19
- package/node_modules/cpu-features/deps/cpu_features/bazel/ci/README.md +0 -5
- package/node_modules/cpu-features/deps/cpu_features/bazel/platforms.bzl +0 -11
- package/node_modules/cpu-features/deps/cpu_features/cmake/CpuFeaturesConfig.cmake.in +0 -3
- package/node_modules/cpu-features/deps/cpu_features/cmake/CpuFeaturesNdkCompatConfig.cmake.in +0 -3
- package/node_modules/cpu-features/deps/cpu_features/cmake/README.md +0 -30
- package/node_modules/cpu-features/deps/cpu_features/cmake/ci/Makefile +0 -252
- package/node_modules/cpu-features/deps/cpu_features/cmake/ci/README.md +0 -40
- package/node_modules/cpu-features/deps/cpu_features/cmake/ci/doc/docker.dot +0 -64
- package/node_modules/cpu-features/deps/cpu_features/cmake/ci/doc/docker.svg +0 -312
- package/node_modules/cpu-features/deps/cpu_features/cmake/ci/doc/generate_image.sh +0 -7
- package/node_modules/cpu-features/deps/cpu_features/cmake/ci/docker/amd64/Dockerfile +0 -48
- package/node_modules/cpu-features/deps/cpu_features/cmake/ci/docker/toolchain/Dockerfile +0 -34
- package/node_modules/cpu-features/deps/cpu_features/cmake/ci/sample/CMakeLists.txt +0 -22
- package/node_modules/cpu-features/deps/cpu_features/cmake/ci/sample/main.cpp +0 -11
- package/node_modules/cpu-features/deps/cpu_features/cmake/ci/vagrant/freebsd/Vagrantfile +0 -107
- package/node_modules/cpu-features/deps/cpu_features/cmake/googletest.CMakeLists.txt.in +0 -15
- package/node_modules/cpu-features/deps/cpu_features/cpu_features.gyp +0 -105
- package/node_modules/cpu-features/deps/cpu_features/include/cpu_features_cache_info.h +0 -54
- package/node_modules/cpu-features/deps/cpu_features/include/cpu_features_macros.h +0 -384
- package/node_modules/cpu-features/deps/cpu_features/include/cpuinfo_aarch64.h +0 -259
- package/node_modules/cpu-features/deps/cpu_features/include/cpuinfo_arm.h +0 -121
- package/node_modules/cpu-features/deps/cpu_features/include/cpuinfo_mips.h +0 -74
- package/node_modules/cpu-features/deps/cpu_features/include/cpuinfo_ppc.h +0 -149
- package/node_modules/cpu-features/deps/cpu_features/include/cpuinfo_riscv.h +0 -72
- package/node_modules/cpu-features/deps/cpu_features/include/cpuinfo_s390x.h +0 -108
- package/node_modules/cpu-features/deps/cpu_features/include/cpuinfo_x86.h +0 -288
- package/node_modules/cpu-features/deps/cpu_features/include/internal/bit_utils.h +0 -40
- package/node_modules/cpu-features/deps/cpu_features/include/internal/cpuid_x86.h +0 -37
- package/node_modules/cpu-features/deps/cpu_features/include/internal/filesystem.h +0 -39
- package/node_modules/cpu-features/deps/cpu_features/include/internal/hwcaps.h +0 -240
- package/node_modules/cpu-features/deps/cpu_features/include/internal/stack_line_reader.h +0 -49
- package/node_modules/cpu-features/deps/cpu_features/include/internal/string_view.h +0 -110
- package/node_modules/cpu-features/deps/cpu_features/include/internal/windows_utils.h +0 -70
- package/node_modules/cpu-features/deps/cpu_features/ndk_compat/CMakeLists.txt +0 -60
- package/node_modules/cpu-features/deps/cpu_features/ndk_compat/README.md +0 -4
- package/node_modules/cpu-features/deps/cpu_features/ndk_compat/cpu-features.c +0 -205
- package/node_modules/cpu-features/deps/cpu_features/ndk_compat/cpu-features.h +0 -320
- package/node_modules/cpu-features/deps/cpu_features/ndk_compat/ndk-compat-test.c +0 -12
- package/node_modules/cpu-features/deps/cpu_features/patches/0001-Add-Apple-Silicon-Support.patch +0 -456
- package/node_modules/cpu-features/deps/cpu_features/scripts/generate_badges.d +0 -165
- package/node_modules/cpu-features/deps/cpu_features/scripts/make_release.sh +0 -75
- package/node_modules/cpu-features/deps/cpu_features/scripts/run_integration.sh +0 -490
- package/node_modules/cpu-features/deps/cpu_features/scripts/test_integration.sh +0 -79
- package/node_modules/cpu-features/deps/cpu_features/src/copy.inl +0 -19
- package/node_modules/cpu-features/deps/cpu_features/src/define_introspection.inl +0 -86
- package/node_modules/cpu-features/deps/cpu_features/src/define_introspection_and_hwcaps.inl +0 -26
- package/node_modules/cpu-features/deps/cpu_features/src/equals.inl +0 -22
- package/node_modules/cpu-features/deps/cpu_features/src/filesystem.c +0 -62
- package/node_modules/cpu-features/deps/cpu_features/src/hwcaps.c +0 -169
- package/node_modules/cpu-features/deps/cpu_features/src/impl_aarch64__base_implementation.inl +0 -88
- package/node_modules/cpu-features/deps/cpu_features/src/impl_aarch64_linux_or_android.c +0 -79
- package/node_modules/cpu-features/deps/cpu_features/src/impl_aarch64_macos_or_iphone.c +0 -82
- package/node_modules/cpu-features/deps/cpu_features/src/impl_aarch64_windows.c +0 -138
- package/node_modules/cpu-features/deps/cpu_features/src/impl_arm_linux_or_android.c +0 -212
- package/node_modules/cpu-features/deps/cpu_features/src/impl_mips_linux_or_android.c +0 -93
- package/node_modules/cpu-features/deps/cpu_features/src/impl_ppc_linux.c +0 -163
- package/node_modules/cpu-features/deps/cpu_features/src/impl_riscv_linux.c +0 -111
- package/node_modules/cpu-features/deps/cpu_features/src/impl_s390x_linux.c +0 -120
- package/node_modules/cpu-features/deps/cpu_features/src/impl_x86__base_implementation.inl +0 -2055
- package/node_modules/cpu-features/deps/cpu_features/src/impl_x86_freebsd.c +0 -68
- package/node_modules/cpu-features/deps/cpu_features/src/impl_x86_linux_or_android.c +0 -58
- package/node_modules/cpu-features/deps/cpu_features/src/impl_x86_macos.c +0 -57
- package/node_modules/cpu-features/deps/cpu_features/src/impl_x86_windows.c +0 -58
- package/node_modules/cpu-features/deps/cpu_features/src/stack_line_reader.c +0 -132
- package/node_modules/cpu-features/deps/cpu_features/src/string_view.c +0 -192
- package/node_modules/cpu-features/deps/cpu_features/src/utils/list_cpu_features.c +0 -461
- package/node_modules/cpu-features/deps/cpu_features/test/CMakeLists.txt +0 -114
- package/node_modules/cpu-features/deps/cpu_features/test/bit_utils_test.cc +0 -53
- package/node_modules/cpu-features/deps/cpu_features/test/cpuinfo_aarch64_test.cc +0 -396
- package/node_modules/cpu-features/deps/cpu_features/test/cpuinfo_arm_test.cc +0 -384
- package/node_modules/cpu-features/deps/cpu_features/test/cpuinfo_mips_test.cc +0 -176
- package/node_modules/cpu-features/deps/cpu_features/test/cpuinfo_ppc_test.cc +0 -131
- package/node_modules/cpu-features/deps/cpu_features/test/cpuinfo_riscv_test.cc +0 -180
- package/node_modules/cpu-features/deps/cpu_features/test/cpuinfo_s390x_test.cc +0 -82
- package/node_modules/cpu-features/deps/cpu_features/test/cpuinfo_x86_test.cc +0 -1729
- package/node_modules/cpu-features/deps/cpu_features/test/filesystem_for_testing.cc +0 -103
- package/node_modules/cpu-features/deps/cpu_features/test/filesystem_for_testing.h +0 -61
- package/node_modules/cpu-features/deps/cpu_features/test/hwcaps_for_testing.cc +0 -52
- package/node_modules/cpu-features/deps/cpu_features/test/hwcaps_for_testing.h +0 -31
- package/node_modules/cpu-features/deps/cpu_features/test/stack_line_reader_test.cc +0 -132
- package/node_modules/cpu-features/deps/cpu_features/test/string_view_test.cc +0 -202
- package/node_modules/cpu-features/lib/index.js +0 -5
- package/node_modules/cpu-features/package.json +0 -40
- package/node_modules/cpu-features/src/binding.cc +0 -151
- package/node_modules/cpu-features/test/test.js +0 -12
package/lib/client.js
CHANGED
|
@@ -2382,8 +2382,9 @@ window.__ModuleLoader__.load({
|
|
|
2382
2382
|
}
|
|
2383
2383
|
|
|
2384
2384
|
// ── better-sidebar integration (optional) ────────────────────────────────
|
|
2385
|
+
// One descriptor only: the explorer page. The per-file viewer is a native
|
|
2386
|
+
// resource type, never a better-sidebar card (see registerSidebarIntegration).
|
|
2385
2387
|
const SIDEBAR_EXPLORER_ID = 'dsh-remote:explorer'
|
|
2386
|
-
const SIDEBAR_FILE_ID = 'dsh-remote:file'
|
|
2387
2388
|
|
|
2388
2389
|
function fetchRemoteStatus(sessionId) {
|
|
2389
2390
|
return api('GET', withSessionQuery('/dsh-remote/status', sessionId)).catch(() => null)
|
|
@@ -2989,10 +2990,27 @@ window.__ModuleLoader__.load({
|
|
|
2989
2990
|
return () => { clearTimeout(tid); document.removeEventListener('mousedown', close); host.remove() }
|
|
2990
2991
|
}, [menu])
|
|
2991
2992
|
|
|
2993
|
+
// Open one remote file. The viewer is a RESOURCE tab type: the address
|
|
2994
|
+
// names the file, and the HOST's tab registry picks the type that claims
|
|
2995
|
+
// it (ours, `dsh-resource://dsh-remote/**`). That is the same call the
|
|
2996
|
+
// native explorer makes through `onOpenFile`, so one file behaves
|
|
2997
|
+
// identically in both compositions — and it needs no better-sidebar
|
|
2998
|
+
// descriptor, whose only effect would have been a switch that unregisters
|
|
2999
|
+
// the viewer.
|
|
2992
3000
|
const openFile = (p) => {
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
3001
|
+
if (props.onOpenFile) { props.onOpenFile(p); return }
|
|
3002
|
+
const sessionId = (props.scope && props.scope.sessionId) || ''
|
|
3003
|
+
try {
|
|
3004
|
+
const right = props.ctx && props.ctx.get && props.ctx.get('sidebarRight')
|
|
3005
|
+
if (right && typeof right.openResource === 'function') {
|
|
3006
|
+
right.openResource(remoteFileAddress(sessionId, p))
|
|
3007
|
+
return
|
|
3008
|
+
}
|
|
3009
|
+
} catch (e) {
|
|
3010
|
+
console.warn('[dsh-remote] remote file open failed:', e)
|
|
3011
|
+
return
|
|
3012
|
+
}
|
|
3013
|
+
console.warn('[dsh-remote] no right-sidebar resource opener; remote file not opened')
|
|
2996
3014
|
}
|
|
2997
3015
|
|
|
2998
3016
|
const doFs = (op, payload, refreshParent) => {
|
|
@@ -3302,13 +3320,14 @@ window.__ModuleLoader__.load({
|
|
|
3302
3320
|
// Issue #32: own the expansion state (see RemoteExplorerTabOwned).
|
|
3303
3321
|
component: RemoteExplorerTabOwned,
|
|
3304
3322
|
}))
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3323
|
+
// No descriptor for the FILE viewer on purpose. It is a resource tab
|
|
3324
|
+
// type (`dsh-remote/file`, registered with `patterns` on the native
|
|
3325
|
+
// registry), not a page: it only means something once an address names
|
|
3326
|
+
// the file, so it has no card to draw and no page to open. A
|
|
3327
|
+
// descriptor here would put a card in better-sidebar's 侧边栏内容
|
|
3328
|
+
// inventory whose switch could only ever unregister the viewer — the
|
|
3329
|
+
// "switch does nothing" report this replaced. The tree hands the HOST
|
|
3330
|
+
// an address instead and the host routes it to that type (openFile).
|
|
3312
3331
|
} catch (e) {
|
|
3313
3332
|
console.warn('[dsh-remote] better-sidebar integration skipped:', e)
|
|
3314
3333
|
disposers.forEach((d) => { try { d() } catch {} })
|
|
@@ -3522,11 +3541,65 @@ window.__ModuleLoader__.load({
|
|
|
3522
3541
|
// built-in local preview. This is a warm-up, not a dependency: a failure
|
|
3523
3542
|
// just leaves the conservative behaviour in place.
|
|
3524
3543
|
try { ensureMirrors() } catch { /* the registry is best-effort */ }
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3544
|
+
// ── Who owns the 开始 card ────────────────────────────────────────────
|
|
3545
|
+
// The 远程文件 card on the right sidebar's start page (开始) is the
|
|
3546
|
+
// `guide` entry of a native tab type, and TWO providers can publish one:
|
|
3547
|
+
// this plugin (below) and dsh-better-sidebar, which registers a native
|
|
3548
|
+
// type per tab descriptor it hosts — our `dsh-remote:explorer` gets a
|
|
3549
|
+
// guide row of its own there, governed by its side-card settings switch.
|
|
3550
|
+
//
|
|
3551
|
+
// Publishing both is what made that switch look broken: turning the type
|
|
3552
|
+
// off removed better-sidebar's row and left ours, so the card never went
|
|
3553
|
+
// away. The card therefore belongs to exactly ONE provider:
|
|
3554
|
+
// · better-sidebar in the composition → ITS descriptor owns the card
|
|
3555
|
+
// (its switch IS the switch for it), and this type is registered
|
|
3556
|
+
// without a guide entry. The type and its body stay: a tab opened by
|
|
3557
|
+
// kind still renders, and nothing else about the panel changes.
|
|
3558
|
+
// · plain install → the card is ours, always, exactly as before.
|
|
3559
|
+
//
|
|
3560
|
+
// `guide` is captured at registration time and an id may not be
|
|
3561
|
+
// registered twice, so a handover means dispose-then-register inside one
|
|
3562
|
+
// synchronous block: no render can observe the gap.
|
|
3563
|
+
let betterSidebar = false
|
|
3564
|
+
let disposed = false
|
|
3565
|
+
let disposeExplorer = null
|
|
3566
|
+
let guideListed = null
|
|
3567
|
+
const syncExplorerType = () => {
|
|
3568
|
+
const withGuide = !betterSidebar
|
|
3569
|
+
if (guideListed === withGuide && disposeExplorer) return
|
|
3570
|
+
const previous = disposeExplorer
|
|
3571
|
+
disposeExplorer = null
|
|
3572
|
+
if (previous) { try { previous() } catch { /* already released */ } }
|
|
3573
|
+
disposeExplorer = tabs.register({
|
|
3574
|
+
id: NATIVE_EXPLORER, kind: NATIVE_EXPLORER,
|
|
3575
|
+
title: () => tr('explorer.tabTitle'),
|
|
3576
|
+
...(withGuide ? { guide: [{ order: 55, title: () => tr('explorer.tabTitle') }] } : {}),
|
|
3577
|
+
})
|
|
3578
|
+
guideListed = withGuide
|
|
3579
|
+
}
|
|
3580
|
+
inner.effect(() => {
|
|
3581
|
+
syncExplorerType()
|
|
3582
|
+
return () => {
|
|
3583
|
+
disposed = true
|
|
3584
|
+
const dispose = disposeExplorer
|
|
3585
|
+
disposeExplorer = null
|
|
3586
|
+
guideListed = null
|
|
3587
|
+
if (dispose) { try { dispose() } catch { /* already released */ } }
|
|
3588
|
+
}
|
|
3589
|
+
}, 'dsh-remote.native-explorer-type')
|
|
3590
|
+
// The two plugins may activate in EITHER order, so the service has to be
|
|
3591
|
+
// followed rather than read once: the card moves to better-sidebar when
|
|
3592
|
+
// it arrives, and comes back here if it goes away.
|
|
3593
|
+
inner.inject(['betterSidebar'], (withSidebar) => {
|
|
3594
|
+
betterSidebar = true
|
|
3595
|
+
try { syncExplorerType() } catch (e) { console.warn('[dsh-remote] start-page card handover skipped:', e) }
|
|
3596
|
+
if (!withSidebar || typeof withSidebar.effect !== 'function') return
|
|
3597
|
+
withSidebar.effect(() => () => {
|
|
3598
|
+
betterSidebar = false
|
|
3599
|
+
if (disposed) return
|
|
3600
|
+
try { syncExplorerType() } catch (e) { console.warn('[dsh-remote] start-page card handover skipped:', e) }
|
|
3601
|
+
}, 'dsh-remote.start-card-owner')
|
|
3602
|
+
})
|
|
3530
3603
|
inner.effect(() => tabs.register({
|
|
3531
3604
|
id: NATIVE_FILE, kind: NATIVE_FILE,
|
|
3532
3605
|
patterns: ['dsh-resource://dsh-remote/**'],
|
package/lib/index.js
CHANGED
|
@@ -24,6 +24,7 @@ import { execFile } from 'node:child_process'
|
|
|
24
24
|
import { readFileSync, mkdirSync, writeFileSync, existsSync, readdirSync, statSync, renameSync, copyFileSync, appendFileSync, watch, rmSync } from 'node:fs'
|
|
25
25
|
import { homedir } from 'node:os'
|
|
26
26
|
import path from 'node:path'
|
|
27
|
+
import { fileURLToPath } from 'node:url'
|
|
27
28
|
import iconv from 'iconv-lite'
|
|
28
29
|
|
|
29
30
|
import {
|
|
@@ -38,6 +39,7 @@ import { createRemoteFileReference, installFileReferenceOverlay, DEFAULT_EXCLUDE
|
|
|
38
39
|
import { getSecret, deleteSecret, platformBackend, persistPassword } from './credential.js'
|
|
39
40
|
import { syncTree, pushTree, loadSyncState, saveSyncState, pushOneFile } from './sync.js'
|
|
40
41
|
import { searchRemote } from './search.js'
|
|
42
|
+
import { installTerminalShell } from './remote-shell.js'
|
|
41
43
|
import { resolveMirror, poolKey, lookupSessionCwd, encodeSegmentSafe, readSessionHeaderCwd, requestSessionHint } from './binding.js'
|
|
42
44
|
import { TaskManager } from './tasks.js'
|
|
43
45
|
import { DeployTasks } from './deploy-tasks.js'
|
|
@@ -256,6 +258,30 @@ function mirrorRootFor(host, user, port) {
|
|
|
256
258
|
return path.join(remoteWorkspacesRoot(), tag)
|
|
257
259
|
}
|
|
258
260
|
|
|
261
|
+
/** Generated terminal launchers. Deliberately NOT under `remote-workspaces`: a
|
|
262
|
+
* launcher there would sit next to the mirrors, where anything that scans the
|
|
263
|
+
* root for checkouts could pick it up as one. */
|
|
264
|
+
function remoteTerminalRoot() {
|
|
265
|
+
return path.join(dshBase(), 'remote-terminal')
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Every path the remote-shell contribution needs, resolved for this install.
|
|
270
|
+
*
|
|
271
|
+
* Split out from the registration itself so the wiring at the bottom of the
|
|
272
|
+
* plugin stays one readable line, and so the launcher path is a pure function
|
|
273
|
+
* of where the plugin lives.
|
|
274
|
+
*
|
|
275
|
+
* @returns {{ dir: string, childPath: string, machinesFile: string }}
|
|
276
|
+
*/
|
|
277
|
+
function remoteShellSpec() {
|
|
278
|
+
return {
|
|
279
|
+
dir: remoteTerminalRoot(),
|
|
280
|
+
childPath: fileURLToPath(new URL('./remote-shell-child.cjs', import.meta.url)),
|
|
281
|
+
machinesFile: machinesFile(),
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
259
285
|
/** Local mirror dir for a specific remote path (idempotent → returns same dir). */
|
|
260
286
|
function mirrorDirFor(remotePath, host, user, port) {
|
|
261
287
|
const base = remotePathBase(remotePath)
|
|
@@ -3731,6 +3757,17 @@ export async function apply(ctx, config) {
|
|
|
3731
3757
|
registerDeploySkill(skillCtx)
|
|
3732
3758
|
})
|
|
3733
3759
|
|
|
3760
|
+
// ── remote terminal: one extra shell in the host terminal's own list ──────
|
|
3761
|
+
// Additive and optional: a composition without `terminalController` simply
|
|
3762
|
+
// keeps the terminal it already had.
|
|
3763
|
+
//
|
|
3764
|
+
// The callback argument is a *new context*, not the service — reading the
|
|
3765
|
+
// service off it (as if it were the controller) is a silent no-op, because a
|
|
3766
|
+
// context has no `config.shellCandidates`.
|
|
3767
|
+
ctx.inject(['terminalController'], (inner) => {
|
|
3768
|
+
installTerminalShell(ctx, inner.get('terminalController'), remoteShellSpec())
|
|
3769
|
+
})
|
|
3770
|
+
|
|
3734
3771
|
// ── auto-update: check on load + on an interval; apply silently ───────────
|
|
3735
3772
|
// Failures are swallowed (never break the plugin). The persisted override
|
|
3736
3773
|
// (settings UI) wins over the profile-config default, which wins over
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* The program the host terminal runs when the user picks the "Remote SSH" shell.
|
|
4
|
+
*
|
|
5
|
+
* It is started by a generated launcher (see `remote-shell.js`) inside the
|
|
6
|
+
* session's working directory, resolves which remote checkout that directory
|
|
7
|
+
* belongs to, and then replaces itself with `ssh`. Inheriting stdio is the whole
|
|
8
|
+
* point: the local pty stays in charge of the screen, so resize and key handling
|
|
9
|
+
* keep working without this script knowing anything about terminals.
|
|
10
|
+
*
|
|
11
|
+
* Plain CommonJS on purpose: it is spawned as a standalone script, and it must
|
|
12
|
+
* not depend on how the plugin package itself is loaded.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
'use strict'
|
|
16
|
+
|
|
17
|
+
const { spawn } = require('node:child_process')
|
|
18
|
+
const { existsSync, readFileSync } = require('node:fs')
|
|
19
|
+
const path = require('node:path')
|
|
20
|
+
|
|
21
|
+
/** Name of the marker the host half writes into every local mirror root. */
|
|
22
|
+
const META_FILE = '.dsh-remote-meta.json'
|
|
23
|
+
/** How far up from the working directory a marker may be found. */
|
|
24
|
+
const MAX_WALK_UP = 12
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Read and parse a JSON file, returning undefined for anything unusable.
|
|
28
|
+
* @param {string} file
|
|
29
|
+
* @returns {any}
|
|
30
|
+
*/
|
|
31
|
+
function readJson(file) {
|
|
32
|
+
try {
|
|
33
|
+
return JSON.parse(readFileSync(file, 'utf8'))
|
|
34
|
+
} catch {
|
|
35
|
+
return undefined
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Find the mirror root that contains `cwd` by walking upwards for the marker.
|
|
41
|
+
* @param {string} cwd - session working directory
|
|
42
|
+
* @param {{ exists?: (p: string) => boolean, readJsonFile?: (p: string) => any, maxUp?: number }} deps
|
|
43
|
+
* @returns {{ dir: string, meta: object } | null}
|
|
44
|
+
*/
|
|
45
|
+
function findMirror(cwd, deps = {}) {
|
|
46
|
+
const exists = deps.exists || ((p) => existsSync(p))
|
|
47
|
+
const read = deps.readJsonFile || readJson
|
|
48
|
+
const maxUp = deps.maxUp || MAX_WALK_UP
|
|
49
|
+
let dir = path.resolve(cwd || '.')
|
|
50
|
+
for (let depth = 0; depth <= maxUp; depth += 1) {
|
|
51
|
+
const marker = path.join(dir, META_FILE)
|
|
52
|
+
if (exists(marker)) {
|
|
53
|
+
const meta = read(marker)
|
|
54
|
+
if (meta && typeof meta === 'object' && meta.host) return { dir, meta }
|
|
55
|
+
}
|
|
56
|
+
const parent = path.dirname(dir)
|
|
57
|
+
if (parent === dir) break
|
|
58
|
+
dir = parent
|
|
59
|
+
}
|
|
60
|
+
return null
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Pick the saved connection that owns a mirror.
|
|
65
|
+
*
|
|
66
|
+
* The marker records the SSH alias when the machine was saved from
|
|
67
|
+
* `~/.ssh/config` (issue #38), so the alias is the most precise key; otherwise
|
|
68
|
+
* host + user + port identify it. A missing entry is not fatal — the marker
|
|
69
|
+
* alone is enough to connect, and ssh will use its own defaults for the key.
|
|
70
|
+
*
|
|
71
|
+
* @param {object} meta - mirror marker
|
|
72
|
+
* @param {{ list?: object[] }} registry - parsed `machines.json`
|
|
73
|
+
* @returns {object | undefined}
|
|
74
|
+
*/
|
|
75
|
+
function matchMachine(meta, registry) {
|
|
76
|
+
const list = registry && Array.isArray(registry.list) ? registry.list : []
|
|
77
|
+
const port = Number(meta.port || 22)
|
|
78
|
+
if (meta.alias) {
|
|
79
|
+
const byAlias = list.find((m) => m && m.host === meta.alias)
|
|
80
|
+
if (byAlias) return byAlias
|
|
81
|
+
}
|
|
82
|
+
return list.find((m) => m && m.host === meta.host
|
|
83
|
+
&& String(m.username || '') === String(meta.username || '')
|
|
84
|
+
&& Number(m.port || 22) === port)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Quote one path for a POSIX shell, single quotes only.
|
|
89
|
+
* @param {string} value
|
|
90
|
+
* @returns {string}
|
|
91
|
+
*/
|
|
92
|
+
function quotePosix(value) {
|
|
93
|
+
return "'" + String(value).replace(/'/g, "'\\''") + "'"
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Build the `cd` half of the remote command, keeping `~` for the *remote* shell
|
|
98
|
+
* to expand: the local home is not the remote home, so expanding it here would
|
|
99
|
+
* silently land in the wrong directory.
|
|
100
|
+
* @param {string} remotePath
|
|
101
|
+
* @returns {string}
|
|
102
|
+
*/
|
|
103
|
+
function remoteCd(remotePath) {
|
|
104
|
+
const raw = String(remotePath || '').trim()
|
|
105
|
+
if (raw === '' || raw === '~') return 'cd "$HOME"'
|
|
106
|
+
if (raw === '~/') return 'cd "$HOME"'
|
|
107
|
+
if (raw.startsWith('~/')) return 'cd "$HOME"/' + quotePosix(raw.slice(2))
|
|
108
|
+
return 'cd ' + quotePosix(raw)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* The remote command: land in the workspace, then hand over to a login shell.
|
|
113
|
+
*
|
|
114
|
+
* `exec` keeps the pty attached to a single interactive shell, so the exit code
|
|
115
|
+
* the terminal sees is the shell's own, and a deleted workspace falls back to
|
|
116
|
+
* `$HOME` instead of leaving a broken terminal.
|
|
117
|
+
*
|
|
118
|
+
* @param {string} remotePath
|
|
119
|
+
* @returns {string}
|
|
120
|
+
*/
|
|
121
|
+
function remoteCommand(remotePath) {
|
|
122
|
+
return `${remoteCd(remotePath)} 2>/dev/null || cd "$HOME"; exec "\${SHELL:-/bin/bash}" -l`
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Absolute Windows path: `D:\x`, `D:/x`, or a `\\server\share` UNC path.
|
|
126
|
+
* These cannot be the operand of a POSIX `cd`. */
|
|
127
|
+
const WINDOWS_REMOTE_PATH = /^(?:[a-zA-Z]:[\\/]|\\\\)/
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Whether a remote workspace path is a Windows one.
|
|
131
|
+
*
|
|
132
|
+
* A Windows remote speaks cmd or PowerShell in its default shell, so the POSIX
|
|
133
|
+
* `cd … || …; exec "$SHELL"` line would fail outright there — `2>/dev/null`,
|
|
134
|
+
* `||` and `exec` are not cmd syntax at all. The marker records no remote
|
|
135
|
+
* platform, but the path form is unambiguous: `/srv/x` and `~/x` are POSIX,
|
|
136
|
+
* while `D:\x` and `\\server\share` are not valid POSIX paths.
|
|
137
|
+
*
|
|
138
|
+
* @param {string} p - `remotePath` from the mirror marker
|
|
139
|
+
* @returns {boolean}
|
|
140
|
+
*/
|
|
141
|
+
function isWindowsRemotePath(p) {
|
|
142
|
+
return WINDOWS_REMOTE_PATH.test(String(p || '').trim())
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Argument list (without the ssh binary) for one connection.
|
|
147
|
+
*
|
|
148
|
+
* `-tt` is mandatory: the terminal needs a tty even though a command is given,
|
|
149
|
+
* otherwise the shell comes up non-interactive and every key press is ignored.
|
|
150
|
+
* Options are pushed in order and never spliced between an `-o` and its value —
|
|
151
|
+
* an inserted `-J` there once reached ssh as the value of `-o`, which aborted
|
|
152
|
+
* every connection that used a jump host (issue #21).
|
|
153
|
+
*
|
|
154
|
+
* @param {object} target - `{ host, port, username, alias, remotePath }`
|
|
155
|
+
* @param {object} [machine] - saved connection, for the key and transport hints
|
|
156
|
+
* @returns {string[]}
|
|
157
|
+
*/
|
|
158
|
+
function buildSshArgs(target, machine) {
|
|
159
|
+
const args = ['-tt']
|
|
160
|
+
const alias = target.alias || (machine && machine.useSshConfig ? machine.host : '')
|
|
161
|
+
if (alias) {
|
|
162
|
+
args.push(String(alias))
|
|
163
|
+
} else {
|
|
164
|
+
const port = Number(target.port || (machine && machine.port) || 22)
|
|
165
|
+
if (port !== 22) args.push('-p', String(port))
|
|
166
|
+
const key = machine && machine.privateKeyPath
|
|
167
|
+
if (key && !machine.useAgent) args.push('-i', String(key))
|
|
168
|
+
const user = target.username || (machine && machine.username) || ''
|
|
169
|
+
args.push(user ? `${user}@${target.host}` : String(target.host))
|
|
170
|
+
}
|
|
171
|
+
// A Windows workspace cannot be reached with the POSIX line: the remote's
|
|
172
|
+
// default shell may be cmd or PowerShell, and picking one would be a guess.
|
|
173
|
+
// Opening ssh with no command leaves the remote's own default shell in charge,
|
|
174
|
+
// so the terminal still works; only the automatic `cd` is given up.
|
|
175
|
+
if (!isWindowsRemotePath(target.remotePath)) args.push(remoteCommand(target.remotePath))
|
|
176
|
+
return args
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Prefer the OS OpenSSH binary on Windows. A PATH lookup there can reach an
|
|
181
|
+
* MSYS2/git-bash `ssh`, which resolves `~/.ssh` against a different home and
|
|
182
|
+
* makes keys saved by the host half look missing.
|
|
183
|
+
* @param {string} [platform]
|
|
184
|
+
* @param {object} [env]
|
|
185
|
+
* @param {(p: string) => boolean} [exists]
|
|
186
|
+
* @returns {string}
|
|
187
|
+
*/
|
|
188
|
+
function sshBinary(platform = process.platform, env = process.env, exists = existsSync) {
|
|
189
|
+
if (platform === 'win32') {
|
|
190
|
+
const system = env.SystemRoot || env.windir || 'C:\\Windows'
|
|
191
|
+
const native = path.join(system, 'System32', 'OpenSSH', 'ssh.exe')
|
|
192
|
+
if (exists(native)) return native
|
|
193
|
+
}
|
|
194
|
+
return 'ssh'
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* The shell to fall back to when this directory is not a remote checkout.
|
|
199
|
+
* @param {string} [platform]
|
|
200
|
+
* @param {object} [env]
|
|
201
|
+
* @returns {string}
|
|
202
|
+
*/
|
|
203
|
+
function localShell(platform = process.platform, env = process.env) {
|
|
204
|
+
if (platform === 'win32') return env.ComSpec || 'cmd.exe'
|
|
205
|
+
return env.SHELL || '/bin/sh'
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Parse the arguments the launcher passes.
|
|
210
|
+
* @param {string[]} argv
|
|
211
|
+
* @returns {{ machines?: string }}
|
|
212
|
+
*/
|
|
213
|
+
function parseArgs(argv) {
|
|
214
|
+
const out = {}
|
|
215
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
216
|
+
if (argv[i] === '--machines' && argv[i + 1]) {
|
|
217
|
+
out.machines = argv[i + 1]
|
|
218
|
+
i += 1
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return out
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Resolve everything needed to connect from a working directory.
|
|
226
|
+
* @param {{ cwd?: string, machines?: string, exists?: Function, readJsonFile?: Function }} input
|
|
227
|
+
* @returns {{ target?: object, machine?: object, found: boolean }}
|
|
228
|
+
*/
|
|
229
|
+
function resolveTarget(input = {}) {
|
|
230
|
+
const cwd = input.cwd || process.cwd()
|
|
231
|
+
const mirror = findMirror(cwd, input)
|
|
232
|
+
if (!mirror) return { found: false }
|
|
233
|
+
const registry = input.machines ? (input.readJsonFile || readJson)(input.machines) : undefined
|
|
234
|
+
const machine = matchMachine(mirror.meta, registry)
|
|
235
|
+
const target = {
|
|
236
|
+
host: mirror.meta.host,
|
|
237
|
+
port: mirror.meta.port || (machine && machine.port) || 22,
|
|
238
|
+
username: mirror.meta.username || (machine && machine.username) || '',
|
|
239
|
+
alias: mirror.meta.alias || '',
|
|
240
|
+
remotePath: mirror.meta.remotePath || (machine && machine.workspace) || '',
|
|
241
|
+
}
|
|
242
|
+
return { found: true, target, machine, dir: mirror.dir }
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Hand the current process over to `file args…`, propagating the exit code.
|
|
247
|
+
* @param {string} file
|
|
248
|
+
* @param {string[]} args
|
|
249
|
+
* @returns {import('node:child_process').ChildProcess}
|
|
250
|
+
*/
|
|
251
|
+
function handover(file, args) {
|
|
252
|
+
const child = spawn(file, args, { stdio: 'inherit' })
|
|
253
|
+
child.on('error', (error) => {
|
|
254
|
+
process.stderr.write(`[dsh-remote] cannot run ${file}: ${error && error.message}\n`)
|
|
255
|
+
process.exit(127)
|
|
256
|
+
})
|
|
257
|
+
child.on('exit', (code, signal) => {
|
|
258
|
+
if (signal) process.kill(process.pid, signal)
|
|
259
|
+
else process.exit(code == null ? 0 : code)
|
|
260
|
+
})
|
|
261
|
+
return child
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Entry point: connect, or explain why a local shell was used instead.
|
|
266
|
+
* @param {string[]} [argv]
|
|
267
|
+
* @param {{ cwd?: string, platform?: string, env?: object }} [deps]
|
|
268
|
+
*/
|
|
269
|
+
function main(argv = process.argv.slice(2), deps = {}) {
|
|
270
|
+
const platform = deps.platform || process.platform
|
|
271
|
+
const env = deps.env || process.env
|
|
272
|
+
const options = parseArgs(argv)
|
|
273
|
+
const resolved = resolveTarget({ cwd: deps.cwd || process.cwd(), machines: options.machines })
|
|
274
|
+
if (!resolved.found) {
|
|
275
|
+
process.stderr.write('[dsh-remote] this directory is not a remote workspace; opening a local shell.\n')
|
|
276
|
+
process.stderr.write('[dsh-remote] 当前目录不是远程工作区,已打开本地终端。\n')
|
|
277
|
+
return handover(localShell(platform, env), platform === 'win32' ? [] : ['-l'])
|
|
278
|
+
}
|
|
279
|
+
const ssh = sshBinary(platform, env)
|
|
280
|
+
const args = buildSshArgs(resolved.target, resolved.machine)
|
|
281
|
+
const user = resolved.target.username || (resolved.machine && resolved.machine.username) || ''
|
|
282
|
+
process.stderr.write(`[dsh-remote] connecting to ${user ? user + '@' : ''}${resolved.target.host}…\n`)
|
|
283
|
+
if (isWindowsRemotePath(resolved.target.remotePath)) {
|
|
284
|
+
process.stderr.write('[dsh-remote] the remote workspace is a Windows path; opening the remote default shell without changing directory.\n')
|
|
285
|
+
process.stderr.write('[dsh-remote] 远端工作区是 Windows 路径,已打开默认 shell(未自动切换目录)。\n')
|
|
286
|
+
}
|
|
287
|
+
return handover(ssh, args)
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
module.exports = {
|
|
291
|
+
META_FILE,
|
|
292
|
+
buildSshArgs,
|
|
293
|
+
findMirror,
|
|
294
|
+
isWindowsRemotePath,
|
|
295
|
+
localShell,
|
|
296
|
+
main,
|
|
297
|
+
matchMachine,
|
|
298
|
+
parseArgs,
|
|
299
|
+
remoteCd,
|
|
300
|
+
remoteCommand,
|
|
301
|
+
resolveTarget,
|
|
302
|
+
sshBinary,
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (require.main === module) main()
|