dsh-remote 0.8.47 → 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/ignore.js +46 -0
- package/lib/index.js +68 -23
- package/lib/remote-shell-child.cjs +305 -0
- package/lib/remote-shell.js +518 -0
- package/lib/search.js +8 -3
- package/package.json +1 -1
- 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
|
@@ -0,0 +1,518 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remote terminal as a shell choice — the host terminal's own option list.
|
|
3
|
+
*
|
|
4
|
+
* The built-in terminal is a *local* pty whose program comes from
|
|
5
|
+
* `dsh-api-terminal-controller`'s verified shell list (`shellCandidates`). A
|
|
6
|
+
* plugin cannot hand that pty a command line, but it CAN contribute one entry
|
|
7
|
+
* to that list: an absolute path to a launcher we generate. The pty then runs
|
|
8
|
+
* our launcher as a local process, and the launcher execs `ssh` with inherited
|
|
9
|
+
* stdio — which is exactly how the terminal should behave. Resizing, key
|
|
10
|
+
* binding, scrollback and color all stay the host's job; nothing is re-implemented
|
|
11
|
+
* here.
|
|
12
|
+
*
|
|
13
|
+
* The launcher itself is dumb (find node, run the child script). The child
|
|
14
|
+
* (`remote-shell-child.cjs`) resolves the remote target from the checkout it is
|
|
15
|
+
* started in and replaces itself with `ssh`.
|
|
16
|
+
*
|
|
17
|
+
* @module dsh-remote/remote-shell
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { chmodSync, existsSync, mkdirSync, readdirSync, unlinkSync, writeFileSync } from 'node:fs'
|
|
21
|
+
import path from 'node:path'
|
|
22
|
+
|
|
23
|
+
/** Label shown in the terminal's shell menu.
|
|
24
|
+
*
|
|
25
|
+
* Windows needs a real `.cmd` on disk (ConPTY cannot start an extensionless
|
|
26
|
+
* file), but the label does not have to advertise it: the menu shows the
|
|
27
|
+
* candidate's basename, and `shellCandidate` contributes the bare name so the
|
|
28
|
+
* host resolves it through `PATH` + `PATHEXT`. The suffix is therefore an
|
|
29
|
+
* implementation detail of the file on disk, not of the menu entry. */
|
|
30
|
+
export const SHELL_OPTION_NAME = 'remote ssh'
|
|
31
|
+
|
|
32
|
+
/** Absolute path of the generated launcher for this platform. */
|
|
33
|
+
export function shellLauncherPath(dir, platform = process.platform) {
|
|
34
|
+
return path.join(dir, platform === 'win32' ? SHELL_OPTION_NAME + '.cmd' : SHELL_OPTION_NAME)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Default `PATHEXT`, used when the environment does not carry one. */
|
|
38
|
+
const DEFAULT_PATH_EXT = '.COM;.EXE;.BAT;.CMD'
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The value to contribute to `shellCandidates`.
|
|
42
|
+
*
|
|
43
|
+
* The menu label is `executableName(candidate)` — the candidate's basename, with
|
|
44
|
+
* no extension stripped — while the path that actually runs is whatever the host
|
|
45
|
+
* resolves. Those are two different things, and the gap is the only way to get a
|
|
46
|
+
* clean label on Windows.
|
|
47
|
+
*
|
|
48
|
+
* A file path would be shown as `remote ssh.cmd`, because the terminal has to
|
|
49
|
+
* start a real `.cmd` and ConPTY refuses anything else (a file with no extension
|
|
50
|
+
* dies with 193, `ERROR_BAD_EXE_FORMAT`). So on Windows the *bare* name is
|
|
51
|
+
* contributed instead: the host resolves it through `PATH` + `PATHEXT`, lands on
|
|
52
|
+
* `remote ssh.cmd`, and the label stays `remote ssh`. That is exactly the rule
|
|
53
|
+
* `resolveExecutable` documents — an absolute path is checked as-is with no
|
|
54
|
+
* extension guessing, a bare name is looked up per `PATHEXT`.
|
|
55
|
+
*
|
|
56
|
+
* Two consequences, both handled by the caller: the launcher's directory has to
|
|
57
|
+
* be on `PATH` (see `appendPathEntry`), and the name must contain no dot, since
|
|
58
|
+
* a bare name is only extended when `extname()` is empty.
|
|
59
|
+
*
|
|
60
|
+
* POSIX has no such rule — the file is directly executable — so the absolute
|
|
61
|
+
* path is contributed as-is.
|
|
62
|
+
*
|
|
63
|
+
* @param {string} platform
|
|
64
|
+
* @param {string} launcherPath
|
|
65
|
+
* @returns {string} candidate value for the shell list
|
|
66
|
+
*/
|
|
67
|
+
export function shellCandidate(platform, launcherPath) {
|
|
68
|
+
if (platform !== 'win32') return launcherPath
|
|
69
|
+
return path.basename(launcherPath).replace(/\.cmd$/i, '')
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Resolve a bare command name the way the host does, without spawning anything.
|
|
74
|
+
*
|
|
75
|
+
* A mirror of `subprocess-local`'s `executableCandidates` + `resolveExecutable`:
|
|
76
|
+
* directory-major order over `PATH`, extension-minor over `PATHEXT`, and
|
|
77
|
+
* extensions only when the name has none of its own. Used to decide whether the
|
|
78
|
+
* bare-name contribution will actually resolve; if it will not, the caller falls
|
|
79
|
+
* back to the absolute path and accepts the `.cmd` in the label.
|
|
80
|
+
*
|
|
81
|
+
* @param {string} name
|
|
82
|
+
* @param {{ platform?: string, pathEnv?: string, pathExt?: string, cwd?: string,
|
|
83
|
+
* fs?: { existsSync: (p: string) => boolean } }} [deps]
|
|
84
|
+
* @returns {string} resolved path, or '' when nothing matches
|
|
85
|
+
*/
|
|
86
|
+
export function resolveThroughPath(name, deps = {}) {
|
|
87
|
+
const platform = deps.platform || process.platform
|
|
88
|
+
const fs = deps.fs || { existsSync }
|
|
89
|
+
const pathEnv = deps.pathEnv === undefined ? (process.env.PATH || '') : deps.pathEnv
|
|
90
|
+
const pathExt = deps.pathExt === undefined
|
|
91
|
+
? (process.env.PATHEXT || DEFAULT_PATH_EXT)
|
|
92
|
+
: deps.pathExt
|
|
93
|
+
const cwd = deps.cwd || process.cwd()
|
|
94
|
+
// The platform being MODELED owns the path semantics, not the platform this
|
|
95
|
+
// process runs on: a win32 lookup splits PATH on ';' and understands drive
|
|
96
|
+
// letters, which the runtime `path` only does when the host is Windows. On a
|
|
97
|
+
// real host the two coincide, so this changes nothing outside tests.
|
|
98
|
+
const pathApi = platform === 'win32' ? path.win32 : path.posix
|
|
99
|
+
const extensions = platform === 'win32' && pathApi.extname(name) === ''
|
|
100
|
+
? pathExt.split(';').filter(Boolean)
|
|
101
|
+
: ['']
|
|
102
|
+
const delimiter = platform === 'win32' ? ';' : ':'
|
|
103
|
+
for (const directory of pathEnv.split(delimiter)) {
|
|
104
|
+
if (!directory) continue
|
|
105
|
+
for (const extension of extensions) {
|
|
106
|
+
const candidate = pathApi.resolve(cwd, directory, name + extension)
|
|
107
|
+
try {
|
|
108
|
+
if (fs.existsSync(candidate)) return candidate
|
|
109
|
+
} catch { /* an unreadable directory is simply skipped */ }
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return ''
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Per-environment ownership of the entries this module added. */
|
|
116
|
+
const pathEntries = new WeakMap()
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Append a directory to the process `PATH`, idempotently and ref-counted.
|
|
120
|
+
*
|
|
121
|
+
* Appended, never prepended: a plugin has no business shadowing a program the
|
|
122
|
+
* user already has, and the launcher's name cannot collide by accident.
|
|
123
|
+
*
|
|
124
|
+
* The entry is ref-counted per environment object because the contribution can
|
|
125
|
+
* be installed more than once in a process lifetime — a reload disposes the old
|
|
126
|
+
* effect and installs a new one, and the two callbacks may interleave. A plain
|
|
127
|
+
* "set/clear" pair would let the first disposal strip the entry out from under
|
|
128
|
+
* the still-live registration, which only shows up much later as the menu entry
|
|
129
|
+
* silently vanishing (the host cannot resolve a bare name that is no longer on
|
|
130
|
+
* PATH).
|
|
131
|
+
*
|
|
132
|
+
* A directory that was already on `PATH` before we touched it is marked as
|
|
133
|
+
* externally owned and is never removed by us.
|
|
134
|
+
*
|
|
135
|
+
* @param {string} dir - absolute directory to expose
|
|
136
|
+
* @param {object} [env] - environment object; defaults to `process.env`
|
|
137
|
+
* @returns {() => void} undo, safe to call more than once
|
|
138
|
+
* @throws {Error} when the variable cannot be written
|
|
139
|
+
*/
|
|
140
|
+
export function appendPathEntry(dir, env = process.env) {
|
|
141
|
+
const absolute = path.resolve(dir)
|
|
142
|
+
const key = Object.keys(env).find((name) => name.toUpperCase() === 'PATH') || 'PATH'
|
|
143
|
+
let owned = pathEntries.get(env)
|
|
144
|
+
if (!owned) {
|
|
145
|
+
owned = new Map()
|
|
146
|
+
pathEntries.set(env, owned)
|
|
147
|
+
}
|
|
148
|
+
const count = owned.get(absolute)
|
|
149
|
+
if (count === undefined) {
|
|
150
|
+
const current = env[key] || ''
|
|
151
|
+
const parts = current.split(path.delimiter).filter(Boolean)
|
|
152
|
+
if (parts.some((entry) => path.resolve(entry) === absolute)) {
|
|
153
|
+
owned.set(absolute, 0) // pre-existing: ours to use, not ours to remove
|
|
154
|
+
} else {
|
|
155
|
+
env[key] = current === '' ? dir : current + path.delimiter + dir
|
|
156
|
+
owned.set(absolute, 1)
|
|
157
|
+
}
|
|
158
|
+
} else if (count > 0) {
|
|
159
|
+
owned.set(absolute, count + 1)
|
|
160
|
+
}
|
|
161
|
+
return () => {
|
|
162
|
+
const live = owned.get(absolute)
|
|
163
|
+
if (live === undefined || live <= 0) return
|
|
164
|
+
if (live > 1) {
|
|
165
|
+
owned.set(absolute, live - 1)
|
|
166
|
+
return
|
|
167
|
+
}
|
|
168
|
+
owned.delete(absolute)
|
|
169
|
+
const kept = (env[key] || '')
|
|
170
|
+
.split(path.delimiter)
|
|
171
|
+
.filter((entry) => entry !== '' && path.resolve(entry) !== absolute)
|
|
172
|
+
env[key] = kept.join(path.delimiter)
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Node interpreters worth baking into the launcher, most specific first.
|
|
178
|
+
*
|
|
179
|
+
* The desktop host does not put its bundled node on a child's PATH (by design),
|
|
180
|
+
* so an absolute path is the only reliable option; `where node` is a last
|
|
181
|
+
* resort. When no node is found the launcher falls back to re-entering the
|
|
182
|
+
* Electron binary with `ELECTRON_RUN_AS_NODE`, which needs no second runtime.
|
|
183
|
+
*
|
|
184
|
+
* Electron puts its resources beside the executable on Windows and Linux, but
|
|
185
|
+
* under `Contents/Resources` on macOS, so the path derived from `execPath` has
|
|
186
|
+
* to follow the platform; the `resourcesPath` candidate already does.
|
|
187
|
+
*
|
|
188
|
+
* @param {{ execPath?: string, resourcesPath?: string, platform?: string, env?: object }} deps
|
|
189
|
+
* @returns {string[]} existing interpreter candidates, in preference order
|
|
190
|
+
*/
|
|
191
|
+
export function runtimeNodeCandidates(deps = {}) {
|
|
192
|
+
const platform = deps.platform || process.platform
|
|
193
|
+
const env = deps.env || process.env
|
|
194
|
+
// `undefined` means "ask the process", `''` means "this host has no such
|
|
195
|
+
// executable" — callers that model another platform rely on the difference.
|
|
196
|
+
const execPath = deps.execPath === undefined ? (process.execPath || '') : deps.execPath
|
|
197
|
+
const exe = platform === 'win32' ? 'node.exe' : 'node'
|
|
198
|
+
const bundledNode = ['runtime', 'primary-runtime', 'dependencies', 'node', 'bin', exe]
|
|
199
|
+
const out = []
|
|
200
|
+
const push = (value) => { if (value && !out.includes(value)) out.push(value) }
|
|
201
|
+
push(env.DSH_REMOTE_TERMINAL_NODE)
|
|
202
|
+
if (deps.resourcesPath) push(path.join(deps.resourcesPath, ...bundledNode))
|
|
203
|
+
if (execPath) {
|
|
204
|
+
const here = path.dirname(execPath)
|
|
205
|
+
push(path.join(platform === 'darwin' ? path.join(here, '..', 'Resources') : path.join(here, 'resources'), ...bundledNode))
|
|
206
|
+
// A plain node install: execPath IS the interpreter.
|
|
207
|
+
if (!deps.electron && path.basename(execPath).toLowerCase() === exe) push(execPath)
|
|
208
|
+
}
|
|
209
|
+
return out.filter((candidate) => {
|
|
210
|
+
try { return existsSync(candidate) } catch { return false }
|
|
211
|
+
})
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Body of the generated launcher. Pure so it can be asserted without touching
|
|
216
|
+
* the filesystem.
|
|
217
|
+
*
|
|
218
|
+
* @param {{ platform?: string, nodePath?: string, electronPath?: string, childPath: string, machinesFile: string }} spec
|
|
219
|
+
* @returns {string} launcher source, runnable as-is
|
|
220
|
+
*/
|
|
221
|
+
export function launcherSource(spec) {
|
|
222
|
+
const platform = spec.platform || process.platform
|
|
223
|
+
const child = spec.childPath
|
|
224
|
+
const machines = spec.machinesFile
|
|
225
|
+
if (platform === 'win32') {
|
|
226
|
+
const node = spec.nodePath || ''
|
|
227
|
+
const electron = spec.electronPath || ''
|
|
228
|
+
const lines = [
|
|
229
|
+
'@echo off',
|
|
230
|
+
'rem Generated by dsh-remote - do not edit; rewritten on every load.',
|
|
231
|
+
'setlocal',
|
|
232
|
+
`set "DSH_CHILD=${child}"`,
|
|
233
|
+
`set "DSH_MACHINES=${machines}"`,
|
|
234
|
+
`set "DSH_NODE=${node}"`,
|
|
235
|
+
'if defined DSH_NODE if exist "%DSH_NODE%" goto :run',
|
|
236
|
+
'set "DSH_NODE="',
|
|
237
|
+
'for /f "delims=" %%N in (\'where node 2^>nul\') do if not defined DSH_NODE set "DSH_NODE=%%N"',
|
|
238
|
+
'if defined DSH_NODE goto :run',
|
|
239
|
+
]
|
|
240
|
+
// Re-entering the Electron binary as node only makes sense when this host
|
|
241
|
+
// IS an Electron app; a plain node install has nothing to re-enter.
|
|
242
|
+
if (electron) {
|
|
243
|
+
lines.push(
|
|
244
|
+
'set "ELECTRON_RUN_AS_NODE=1"',
|
|
245
|
+
`"${electron}" "%DSH_CHILD%" --machines "%DSH_MACHINES%"`,
|
|
246
|
+
'exit /b %ERRORLEVEL%',
|
|
247
|
+
)
|
|
248
|
+
}
|
|
249
|
+
lines.push(
|
|
250
|
+
'echo [dsh-remote] no node interpreter found for the remote terminal. 1>&2',
|
|
251
|
+
'echo [dsh-remote] set DSH_REMOTE_TERMINAL_NODE to an absolute node.exe path, or reinstall dsh-remote. 1>&2',
|
|
252
|
+
'exit /b 127',
|
|
253
|
+
':run',
|
|
254
|
+
'"%DSH_NODE%" "%DSH_CHILD%" --machines "%DSH_MACHINES%"',
|
|
255
|
+
'exit /b %ERRORLEVEL%',
|
|
256
|
+
'',
|
|
257
|
+
)
|
|
258
|
+
return lines.join('\r\n')
|
|
259
|
+
}
|
|
260
|
+
const node = spec.nodePath || ''
|
|
261
|
+
return [
|
|
262
|
+
'#!/bin/sh',
|
|
263
|
+
'# Generated by dsh-remote - do not edit; rewritten on every load.',
|
|
264
|
+
`DSH_CHILD=${shqPosix(child)}`,
|
|
265
|
+
`DSH_MACHINES=${shqPosix(machines)}`,
|
|
266
|
+
node ? `DSH_NODE=${shqPosix(node)}` : 'DSH_NODE=',
|
|
267
|
+
'if [ -n "$DSH_NODE" ] && [ -x "$DSH_NODE" ]; then exec "$DSH_NODE" "$DSH_CHILD" --machines "$DSH_MACHINES"; fi',
|
|
268
|
+
'if command -v node >/dev/null 2>&1; then exec node "$DSH_CHILD" --machines "$DSH_MACHINES"; fi',
|
|
269
|
+
'echo "[dsh-remote] no node interpreter found for the remote terminal." >&2',
|
|
270
|
+
'exit 127',
|
|
271
|
+
'',
|
|
272
|
+
].join('\n')
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/** POSIX single-quote, for paths baked into a shell script. */
|
|
276
|
+
export function shqPosix(value) {
|
|
277
|
+
return "'" + String(value).replace(/'/g, "'\\''") + "'"
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Remove a launcher left on disk under another spelling of the same name.
|
|
282
|
+
*
|
|
283
|
+
* The menu label IS the file name, so renaming the option renames the file. On
|
|
284
|
+
* a case-insensitive volume (NTFS, and APFS by default) writing the new spelling
|
|
285
|
+
* lands in the OLD directory entry and the menu keeps showing the previous
|
|
286
|
+
* name; on a case-sensitive one the old file would simply linger and, having
|
|
287
|
+
* been registered before, could still appear. Deleting the stale entry first
|
|
288
|
+
* makes a rename take effect on both.
|
|
289
|
+
*
|
|
290
|
+
* Best effort throughout: a launcher that cannot be cleaned up must not stop
|
|
291
|
+
* the new one from being written.
|
|
292
|
+
*
|
|
293
|
+
* @param {string} dir
|
|
294
|
+
* @param {string} keepName - basename that must survive
|
|
295
|
+
* @param {object} fs
|
|
296
|
+
*/
|
|
297
|
+
function removeStaleLauncher(dir, keepName, fs) {
|
|
298
|
+
if (typeof fs.readdirSync !== 'function' || typeof fs.unlinkSync !== 'function') return
|
|
299
|
+
let entries
|
|
300
|
+
try {
|
|
301
|
+
entries = fs.readdirSync(dir)
|
|
302
|
+
} catch {
|
|
303
|
+
return // first run: the directory does not exist yet
|
|
304
|
+
}
|
|
305
|
+
for (const entry of entries) {
|
|
306
|
+
if (entry === keepName || entry.toLowerCase() !== keepName.toLowerCase()) continue
|
|
307
|
+
try { fs.unlinkSync(path.join(dir, entry)) } catch { /* keep going */ }
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Write (or refresh) the launcher. Idempotent and cheap: the content is a pure
|
|
313
|
+
* function of this install's paths, so a plugin upgrade that moves the package
|
|
314
|
+
* rewrites the same file with the new child path.
|
|
315
|
+
*
|
|
316
|
+
* @param {{ dir: string, platform?: string, childPath: string, machinesFile: string, nodePath?: string, electronPath?: string, fs?: object }} spec
|
|
317
|
+
* @returns {{ path: string, source: string }}
|
|
318
|
+
*/
|
|
319
|
+
export function writeLauncher(spec) {
|
|
320
|
+
const platform = spec.platform || process.platform
|
|
321
|
+
const fs = spec.fs || { mkdirSync, writeFileSync, chmodSync, readdirSync, unlinkSync }
|
|
322
|
+
fs.mkdirSync(spec.dir, { recursive: true })
|
|
323
|
+
const file = shellLauncherPath(spec.dir, platform)
|
|
324
|
+
// Before writing: on a case-insensitive filesystem the new name would
|
|
325
|
+
// otherwise be absorbed into the old entry.
|
|
326
|
+
removeStaleLauncher(spec.dir, path.basename(file), fs)
|
|
327
|
+
const source = launcherSource({ ...spec, platform })
|
|
328
|
+
fs.writeFileSync(file, source, 'utf8')
|
|
329
|
+
if (platform !== 'win32') {
|
|
330
|
+
try { fs.chmodSync(file, 0o755) } catch { /* a no-op on filesystems without modes */ }
|
|
331
|
+
}
|
|
332
|
+
return { path: file, source }
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Add one entry to the host terminal's shell list.
|
|
337
|
+
*
|
|
338
|
+
* `shellCandidates` is the exact array `discoverShells` reads on every `shells()`
|
|
339
|
+
* call, so appending to it is live: the next time the user opens the shell menu
|
|
340
|
+
* the entry is there, and `spawn()` accepts the matching `shellPath` because it
|
|
341
|
+
* looks the path up in that same list. Removing it on dispose keeps the menu
|
|
342
|
+
* truthful when the plugin is unloaded.
|
|
343
|
+
*
|
|
344
|
+
* A host without the field (older line, or a controller rebuilt to hide the
|
|
345
|
+
* array) is not an error: the plugin simply contributes no shell. A host that
|
|
346
|
+
* HAS the field but refuses the write is a different story — that throws, so the
|
|
347
|
+
* caller can say why the option never appeared instead of leaving the user to
|
|
348
|
+
* guess.
|
|
349
|
+
*
|
|
350
|
+
* @param {object} controller - the `terminalController` service
|
|
351
|
+
* @param {string} candidate - absolute launcher path
|
|
352
|
+
* @returns {(() => void) | null} undo, or null when there is nothing to undo
|
|
353
|
+
* @throws {TypeError} when the host exposes a shell list that cannot be written
|
|
354
|
+
*/
|
|
355
|
+
export function addShellCandidate(controller, candidate) {
|
|
356
|
+
const config = controller && controller.config
|
|
357
|
+
const list = config && config.shellCandidates
|
|
358
|
+
if (!Array.isArray(list)) return null
|
|
359
|
+
if (list.includes(candidate)) return null
|
|
360
|
+
list.push(candidate)
|
|
361
|
+
return () => {
|
|
362
|
+
const at = list.indexOf(candidate)
|
|
363
|
+
if (at >= 0) list.splice(at, 1)
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/** Human-readable reason from anything thrown. */
|
|
368
|
+
function reason(err) {
|
|
369
|
+
return String((err && err.message) || err)
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/** Read an environment variable case-insensitively, as Windows does. */
|
|
373
|
+
function envValue(env, name) {
|
|
374
|
+
if (!env) return undefined
|
|
375
|
+
if (env[name] !== undefined) return env[name]
|
|
376
|
+
const match = Object.keys(env).find((key) => key.toUpperCase() === name.toUpperCase())
|
|
377
|
+
return match === undefined ? undefined : env[match]
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Write the launcher and put it in the terminal's shell menu.
|
|
382
|
+
*
|
|
383
|
+
* Kept out of the plugin entry so the whole contribution — write, publish,
|
|
384
|
+
* withdraw — can be exercised without booting the host. Every way this can
|
|
385
|
+
* fail to produce a visible menu entry is reported through `logger.warn`,
|
|
386
|
+
* because the failure mode that matters is silent: a launcher on disk plus no
|
|
387
|
+
* option in the menu is indistinguishable from "the feature was never built".
|
|
388
|
+
*
|
|
389
|
+
* @param {object} ctx - plugin context; only `effect`, `get('logger')` are used
|
|
390
|
+
* @param {object} controller - the resolved `terminalController` *service*
|
|
391
|
+
* @param {{ dir: string, childPath: string, machinesFile: string, platform?: string,
|
|
392
|
+
* nodePath?: string, electronPath?: string, electron?: boolean, logger?: object,
|
|
393
|
+
* env?: object, fs?: { existsSync: (p: string) => boolean } }} spec
|
|
394
|
+
* @returns {boolean} whether the option is (or already was) in the menu
|
|
395
|
+
*/
|
|
396
|
+
export function installTerminalShell(ctx, controller, spec = {}) {
|
|
397
|
+
const logger = spec.logger !== undefined
|
|
398
|
+
? spec.logger
|
|
399
|
+
: (ctx && typeof ctx.get === 'function' ? ctx.get('logger') : undefined)
|
|
400
|
+
const warn = (message) => {
|
|
401
|
+
try {
|
|
402
|
+
if (logger && typeof logger.warn === 'function') logger.warn(`dsh-remote: ${message}`)
|
|
403
|
+
} catch { /* a logger failure must not change the outcome */ }
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
const platform = spec.platform || process.platform
|
|
407
|
+
const electron = spec.electron === undefined
|
|
408
|
+
? Boolean(process.versions && process.versions.electron)
|
|
409
|
+
: spec.electron
|
|
410
|
+
const nodePath = spec.nodePath === undefined
|
|
411
|
+
? (runtimeNodeCandidates({
|
|
412
|
+
platform,
|
|
413
|
+
resourcesPath: process.resourcesPath,
|
|
414
|
+
execPath: process.execPath,
|
|
415
|
+
env: process.env,
|
|
416
|
+
electron,
|
|
417
|
+
})[0] || '')
|
|
418
|
+
: spec.nodePath
|
|
419
|
+
const electronPath = spec.electronPath === undefined
|
|
420
|
+
? (electron ? process.execPath : '')
|
|
421
|
+
: spec.electronPath
|
|
422
|
+
|
|
423
|
+
let launcher
|
|
424
|
+
try {
|
|
425
|
+
launcher = writeLauncher({
|
|
426
|
+
dir: spec.dir,
|
|
427
|
+
platform,
|
|
428
|
+
childPath: spec.childPath,
|
|
429
|
+
machinesFile: spec.machinesFile,
|
|
430
|
+
nodePath,
|
|
431
|
+
electronPath,
|
|
432
|
+
})
|
|
433
|
+
} catch (err) {
|
|
434
|
+
// A read-only DSH_HOME must not take the plugin down: the terminal just
|
|
435
|
+
// keeps offering the shells it already had.
|
|
436
|
+
warn(`cannot write the remote terminal launcher: ${reason(err)}`)
|
|
437
|
+
return false
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
if (controller === undefined || controller === null) {
|
|
441
|
+
warn('the terminal controller is unavailable; the remote ssh option is unavailable')
|
|
442
|
+
return false
|
|
443
|
+
}
|
|
444
|
+
const list = controller.config && controller.config.shellCandidates
|
|
445
|
+
if (!Array.isArray(list)) {
|
|
446
|
+
// Either an older host, or the wrong object was passed in — the service is
|
|
447
|
+
// reached with `ctx.get('terminalController')`, not with the inject callback
|
|
448
|
+
// argument itself. Say which one it looks like.
|
|
449
|
+
const looksLikeContext = typeof controller.get === 'function' || typeof controller.inject === 'function'
|
|
450
|
+
warn(looksLikeContext
|
|
451
|
+
? 'the terminal controller was not resolved (a plugin context was passed instead); the remote ssh option is unavailable'
|
|
452
|
+
: 'the terminal controller exposes no shellCandidates list; the remote ssh option is unavailable')
|
|
453
|
+
return false
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// On Windows the label is the candidate's basename, so contributing a file
|
|
457
|
+
// path would show `remote ssh.cmd`. Contributing the bare name keeps the label
|
|
458
|
+
// clean, at the price of exposing the launcher directory on PATH — and only
|
|
459
|
+
// while the host can actually resolve that name.
|
|
460
|
+
const bare = shellCandidate(platform, launcher.path)
|
|
461
|
+
const needsPath = bare !== launcher.path
|
|
462
|
+
const env = spec.env || process.env
|
|
463
|
+
const fs = spec.fs || { existsSync }
|
|
464
|
+
|
|
465
|
+
let installed = false
|
|
466
|
+
const publish = () => {
|
|
467
|
+
// The PATH entry lives inside the effect, not outside it: if the effect ever
|
|
468
|
+
// re-runs, the new run must put the entry back that the previous disposal
|
|
469
|
+
// took away. `appendPathEntry` ref-counts, so interleaved install/dispose
|
|
470
|
+
// pairs cannot strand a live registration without PATH.
|
|
471
|
+
let restorePath
|
|
472
|
+
let candidate = launcher.path
|
|
473
|
+
if (needsPath) {
|
|
474
|
+
try {
|
|
475
|
+
restorePath = appendPathEntry(path.dirname(launcher.path), env)
|
|
476
|
+
const found = resolveThroughPath(bare, {
|
|
477
|
+
platform,
|
|
478
|
+
pathEnv: envValue(env, 'PATH') || '',
|
|
479
|
+
pathExt: envValue(env, 'PATHEXT'),
|
|
480
|
+
fs,
|
|
481
|
+
})
|
|
482
|
+
if (found) candidate = bare
|
|
483
|
+
else {
|
|
484
|
+
// `PATHEXT` without `.CMD`, or a PATH that refuses the write. The
|
|
485
|
+
// option still works when contributed by absolute path; only its
|
|
486
|
+
// label carries the suffix.
|
|
487
|
+
restorePath()
|
|
488
|
+
restorePath = undefined
|
|
489
|
+
warn('the host cannot resolve the launcher by name; the remote ssh option keeps its .cmd suffix')
|
|
490
|
+
}
|
|
491
|
+
} catch (err) {
|
|
492
|
+
candidate = launcher.path
|
|
493
|
+
restorePath = undefined
|
|
494
|
+
warn(`the launcher directory cannot be added to PATH (${reason(err)}); the remote ssh option keeps its .cmd suffix`)
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
try {
|
|
498
|
+
const undo = addShellCandidate(controller, candidate)
|
|
499
|
+
installed = true
|
|
500
|
+
return () => {
|
|
501
|
+
if (undo) undo()
|
|
502
|
+
if (restorePath) restorePath()
|
|
503
|
+
}
|
|
504
|
+
} catch (err) {
|
|
505
|
+
// The host advertises a shell list but will not take a write.
|
|
506
|
+
if (restorePath) restorePath()
|
|
507
|
+
warn(`the terminal shell list rejected the remote shell (${reason(err)}); the remote ssh option is unavailable`)
|
|
508
|
+
return undefined
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
if (ctx && typeof ctx.effect === 'function') {
|
|
513
|
+
ctx.effect(publish, 'dsh-remote.terminal-shell')
|
|
514
|
+
} else {
|
|
515
|
+
publish()
|
|
516
|
+
}
|
|
517
|
+
return installed
|
|
518
|
+
}
|
package/lib/search.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Replaces `find … -exec grep` (which needs a POSIX shell and dies on
|
|
3
3
|
// Windows remotes). Walks the remote tree over SFTP readdir/stat/readFile with
|
|
4
4
|
// the same bounded-parallelism + ignore rules as the mirror sync.
|
|
5
|
-
import { joinRemotePath, shq } from './paths.js'
|
|
5
|
+
import { joinRemotePath, relPathUnder, shq } from './paths.js'
|
|
6
6
|
|
|
7
7
|
function mapLimit(items, limit, fn) {
|
|
8
8
|
const limitN = Math.max(1, Math.min(Math.floor(limit) || 1, items.length))
|
|
@@ -105,7 +105,12 @@ export async function searchTree(sftp, rootDir, opts = {}) {
|
|
|
105
105
|
if (shouldStop() || scanned >= maxFiles) return
|
|
106
106
|
if (depth <= 0) continue
|
|
107
107
|
const sub = joinRemotePath(dir, name)
|
|
108
|
-
|
|
108
|
+
// isIgnored receives the path RELATIVE to the search root (the same
|
|
109
|
+
// contract sync has), so root-anchored ignore rules — `/proc/` when the
|
|
110
|
+
// workspace is the filesystem root (issue #60), `Library/Caches/` in the
|
|
111
|
+
// default search set — actually match here; a bare basename would never
|
|
112
|
+
// satisfy an anchored pattern and those rules were silently dead.
|
|
113
|
+
if (isIgnored(relPathUnder(rootDir, sub) || name, true)) continue
|
|
109
114
|
await walk(sub, depth - 1)
|
|
110
115
|
}
|
|
111
116
|
if (shouldStop() || scanned >= maxFiles) return
|
|
@@ -118,8 +123,8 @@ export async function searchTree(sftp, rootDir, opts = {}) {
|
|
|
118
123
|
// Checked per entry: this is the loop that used to run for hours.
|
|
119
124
|
if (shouldStop() || scanned >= maxFiles || matches.length >= maxMatches) return
|
|
120
125
|
if (glob && !matchGlob(name, glob)) return
|
|
121
|
-
if (isIgnored(name, false)) return
|
|
122
126
|
const fp = joinRemotePath(dir, name)
|
|
127
|
+
if (isIgnored(relPathUnder(rootDir, fp) || name, false)) return
|
|
123
128
|
scanned++
|
|
124
129
|
let st
|
|
125
130
|
try { st = await sftp.stat(fp) } catch { return }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-remote",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.49",
|
|
4
4
|
"description": "Remote-work assistant for DeepSeek Harness: connect SSH (password/key/agent/keyboard-interactive/proxy jump), pick a remote workspace, operate on it with 22 rw_* tools (incl. rw_edit/rw_stat/rw_mkdir/rw_remove/rw_move/rw_forward), conflict-aware mirror sync, one-click remote dsh deploy + web UI attach, port forwarding, optional side-bar remote file editing, audit log, keychain passwords.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "flymysql <flyphp@outlook.com>",
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
name: CI
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
pull_request:
|
|
5
|
-
push:
|
|
6
|
-
branches: [ master ]
|
|
7
|
-
|
|
8
|
-
jobs:
|
|
9
|
-
tests-linux:
|
|
10
|
-
runs-on: ubuntu-latest
|
|
11
|
-
strategy:
|
|
12
|
-
fail-fast: false
|
|
13
|
-
matrix:
|
|
14
|
-
node-version: [10.x, 12.x, 14.x, 16.x, 18.x, 20.x, 22.x, 24.x]
|
|
15
|
-
steps:
|
|
16
|
-
- uses: actions/checkout@v4
|
|
17
|
-
with:
|
|
18
|
-
persist-credentials: false
|
|
19
|
-
- name: Use Node.js ${{ matrix.node-version }}
|
|
20
|
-
uses: actions/setup-node@v4
|
|
21
|
-
with:
|
|
22
|
-
node-version: ${{ matrix.node-version }}
|
|
23
|
-
- name: Check Node.js version
|
|
24
|
-
run: node -pe process.versions
|
|
25
|
-
- name: Install module
|
|
26
|
-
run: npm install
|
|
27
|
-
- name: Run tests
|
|
28
|
-
run: npm test
|
|
29
|
-
tests-macos-intel:
|
|
30
|
-
runs-on: macos-15-intel
|
|
31
|
-
strategy:
|
|
32
|
-
fail-fast: false
|
|
33
|
-
matrix:
|
|
34
|
-
node-version: [10.x, 12.x, 14.x, 16.x, 18.x, 20.x, 22.x, 24.x]
|
|
35
|
-
steps:
|
|
36
|
-
- uses: actions/checkout@v4
|
|
37
|
-
with:
|
|
38
|
-
persist-credentials: false
|
|
39
|
-
- name: Use Node.js ${{ matrix.node-version }}
|
|
40
|
-
uses: actions/setup-node@v4
|
|
41
|
-
with:
|
|
42
|
-
node-version: ${{ matrix.node-version }}
|
|
43
|
-
- name: Check Node.js version
|
|
44
|
-
run: node -pe process.versions
|
|
45
|
-
- name: Install module
|
|
46
|
-
run: npm install
|
|
47
|
-
- name: Run tests
|
|
48
|
-
run: npm test
|
|
49
|
-
tests-macos-arm:
|
|
50
|
-
runs-on: macos-latest
|
|
51
|
-
strategy:
|
|
52
|
-
fail-fast: false
|
|
53
|
-
matrix:
|
|
54
|
-
node-version: [16.x, 18.x, 20.x, 22.x, 24.x]
|
|
55
|
-
steps:
|
|
56
|
-
- uses: actions/checkout@v4
|
|
57
|
-
with:
|
|
58
|
-
persist-credentials: false
|
|
59
|
-
- name: Use Node.js ${{ matrix.node-version }}
|
|
60
|
-
uses: actions/setup-node@v4
|
|
61
|
-
with:
|
|
62
|
-
node-version: ${{ matrix.node-version }}
|
|
63
|
-
- name: Check Node.js version
|
|
64
|
-
run: node -pe process.versions
|
|
65
|
-
- name: Install module
|
|
66
|
-
run: npm install
|
|
67
|
-
- name: Run tests
|
|
68
|
-
run: npm test
|
|
69
|
-
tests-windows-2022:
|
|
70
|
-
runs-on: windows-2022
|
|
71
|
-
strategy:
|
|
72
|
-
fail-fast: false
|
|
73
|
-
matrix:
|
|
74
|
-
node-version: [24.x]
|
|
75
|
-
steps:
|
|
76
|
-
- uses: actions/checkout@v4
|
|
77
|
-
with:
|
|
78
|
-
persist-credentials: false
|
|
79
|
-
- name: Use Node.js ${{ matrix.node-version }}
|
|
80
|
-
uses: actions/setup-node@v4
|
|
81
|
-
with:
|
|
82
|
-
node-version: ${{ matrix.node-version }}
|
|
83
|
-
- name: Check Node.js version
|
|
84
|
-
run: node -pe process.versions
|
|
85
|
-
- name: Install module
|
|
86
|
-
run: npm install
|
|
87
|
-
- name: Run tests
|
|
88
|
-
run: npm test
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
name: lint
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
pull_request:
|
|
5
|
-
push:
|
|
6
|
-
branches: [ master ]
|
|
7
|
-
|
|
8
|
-
env:
|
|
9
|
-
NODE_VERSION: 22.x
|
|
10
|
-
|
|
11
|
-
jobs:
|
|
12
|
-
lint-js:
|
|
13
|
-
runs-on: ubuntu-latest
|
|
14
|
-
steps:
|
|
15
|
-
- uses: actions/checkout@v4
|
|
16
|
-
with:
|
|
17
|
-
persist-credentials: false
|
|
18
|
-
- name: Use Node.js ${{ env.NODE_VERSION }}
|
|
19
|
-
uses: actions/setup-node@v4
|
|
20
|
-
with:
|
|
21
|
-
node-version: ${{ env.NODE_VERSION }}
|
|
22
|
-
- name: Check Node.js version
|
|
23
|
-
run: node -pe process.versions
|
|
24
|
-
- name: Install ESLint + ESLint configs/plugins
|
|
25
|
-
run: npm install --only=dev
|
|
26
|
-
- name: Lint files
|
|
27
|
-
run: npm run lint
|