dsh-remote 0.8.49 → 0.8.51
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/README.en.md +1 -0
- package/README.md +2 -0
- package/lib/client.js +11 -3
- package/lib/index.js +173 -27
- package/lib/sshconfig.js +256 -15
- 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/package.json +1 -1
package/lib/sshconfig.js
CHANGED
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
// The module is pure (I/O is injectable), so it is unit-testable without a real
|
|
29
29
|
// home directory — see test/sshconfig.test.js.
|
|
30
30
|
|
|
31
|
-
import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs'
|
|
31
|
+
import { existsSync, readFileSync, readdirSync, statSync, mkdirSync, writeFileSync, chmodSync } from 'node:fs'
|
|
32
32
|
import { homedir } from 'node:os'
|
|
33
33
|
import path from 'node:path'
|
|
34
34
|
|
|
@@ -362,23 +362,14 @@ export function parseProxyJump(value) {
|
|
|
362
362
|
}
|
|
363
363
|
|
|
364
364
|
/**
|
|
365
|
-
*
|
|
365
|
+
* Shared proxy/warning shaping for alias and literal resolutions (issue #50).
|
|
366
366
|
*
|
|
367
367
|
* `ProxyJump` maps onto the plugin's single jump host. A multi-hop chain cannot
|
|
368
368
|
* be represented by one `proxy`, so the FIRST hop is used and `warnings` says so
|
|
369
369
|
* — silently connecting through the wrong path would be worse. `ProxyCommand`
|
|
370
370
|
* (an arbitrary local command) is reported but never executed.
|
|
371
|
-
*
|
|
372
|
-
* @param {string} alias
|
|
373
|
-
* @param {object} [opts]
|
|
374
|
-
* @param {string} [opts.text] - config text (else the real file is read).
|
|
375
|
-
* @param {string} [opts.file]
|
|
376
|
-
* @param {object} [opts.io]
|
|
377
|
-
* @returns {{alias: string, matched: boolean, host: string, port: number, username: string, privateKeyPath: string, proxy: object|undefined, proxyJump: string, warnings: string[]}}
|
|
378
371
|
*/
|
|
379
|
-
|
|
380
|
-
const text = opts.text !== undefined ? String(opts.text) : loadSshConfigText(opts.file || sshConfigPath(), opts.io || {})
|
|
381
|
-
const resolved = resolveSshHost(alias, text)
|
|
372
|
+
function targetFromResolved(resolved, text, { defaultPort }) {
|
|
382
373
|
const warnings = []
|
|
383
374
|
let proxy
|
|
384
375
|
const hops = parseProxyJump(resolved.proxyJump)
|
|
@@ -400,10 +391,10 @@ export function sshAliasTarget(alias, opts = {}) {
|
|
|
400
391
|
warnings.push('ProxyCommand is not supported by dsh-remote (no jump host was applied).')
|
|
401
392
|
}
|
|
402
393
|
return {
|
|
403
|
-
alias,
|
|
394
|
+
alias: resolved.alias,
|
|
404
395
|
matched: resolved.matched,
|
|
405
|
-
host: resolved.hostName || alias,
|
|
406
|
-
port: resolved.port ||
|
|
396
|
+
host: resolved.hostName || resolved.alias,
|
|
397
|
+
port: resolved.port || defaultPort,
|
|
407
398
|
username: resolved.user || '',
|
|
408
399
|
privateKeyPath: resolved.identityFile ? expandHome(resolved.identityFile) : '',
|
|
409
400
|
proxy,
|
|
@@ -412,6 +403,72 @@ export function sshAliasTarget(alias, opts = {}) {
|
|
|
412
403
|
}
|
|
413
404
|
}
|
|
414
405
|
|
|
406
|
+
/**
|
|
407
|
+
* Connect target for an ssh-config alias: the shape the plugin's pool expects.
|
|
408
|
+
*
|
|
409
|
+
* @param {string} alias
|
|
410
|
+
* @param {object} [opts]
|
|
411
|
+
* @param {string} [opts.text] - config text (else the real file is read).
|
|
412
|
+
* @param {string} [opts.file]
|
|
413
|
+
* @param {object} [opts.io]
|
|
414
|
+
* @returns {{alias: string, matched: boolean, host: string, port: number, username: string, privateKeyPath: string, proxy: object|undefined, proxyJump: string, warnings: string[]}}
|
|
415
|
+
*/
|
|
416
|
+
export function sshAliasTarget(alias, opts = {}) {
|
|
417
|
+
const text = opts.text !== undefined ? String(opts.text) : loadSshConfigText(opts.file || sshConfigPath(), opts.io || {})
|
|
418
|
+
return targetFromResolved(resolveSshHost(alias, text), text, { defaultPort: 22 })
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Effective connect identity for a LITERAL machine (issue #50).
|
|
423
|
+
*
|
|
424
|
+
* A machine saved with literal host/user/port/key holds a COPY of what may also
|
|
425
|
+
* be written in ~/.ssh/config, and the copy goes stale the moment the file is
|
|
426
|
+
* edited. The file is the single source for any host it NAMES: when a Host
|
|
427
|
+
* block matches the machine's literal host — with at least one positive pattern
|
|
428
|
+
* that is not the bare `*` — the resolved values win per field and the record
|
|
429
|
+
* fills the gaps. A bare `Host *` block deliberately does NOT name a host:
|
|
430
|
+
* treating universal defaults as an override would hijack every saved machine
|
|
431
|
+
* at once. Group patterns (`*.corp`, `web-?`) DO name hosts, exactly as ssh
|
|
432
|
+
* itself applies them to a literal dial.
|
|
433
|
+
*
|
|
434
|
+
* `port` is 0 (not 22) when the file says nothing, so the caller falls back to
|
|
435
|
+
* the record instead of the default.
|
|
436
|
+
*
|
|
437
|
+
* @param {object} machine - a registry record with a literal `host`.
|
|
438
|
+
* @param {{text?: string, file?: string, io?: object}} [opts]
|
|
439
|
+
* @returns {null | {alias: string, matched: true, host: string, port: number, username: string, privateKeyPath: string, proxy: object|undefined, proxyJump: string, warnings: string[]}}
|
|
440
|
+
* null when no Host block names the machine's host.
|
|
441
|
+
*/
|
|
442
|
+
export function resolveLiteralSshConfig(machine, opts = {}) {
|
|
443
|
+
if (!machine || machine.useSshConfig) return null
|
|
444
|
+
const name = String(machine.host || '').trim()
|
|
445
|
+
if (!name) return null
|
|
446
|
+
const text = opts.text !== undefined ? String(opts.text) : loadSshConfigText(opts.file || sshConfigPath(), opts.io || {})
|
|
447
|
+
const resolved = {
|
|
448
|
+
alias: name, matched: false, hostName: '', user: '', port: 0,
|
|
449
|
+
identityFile: '', proxyJump: '', proxyCommand: '', identitiesOnly: '',
|
|
450
|
+
}
|
|
451
|
+
for (const block of parseHostBlocks(text)) {
|
|
452
|
+
if (!hostBlockMatches(block.patterns, name)) continue
|
|
453
|
+
const positive = block.patterns.filter((p) => !p.startsWith('!'))
|
|
454
|
+
// A block whose only positive pattern is the bare `*` is a defaults block,
|
|
455
|
+
// not a block that names this host.
|
|
456
|
+
if (positive.length && positive.every((p) => p === '*')) continue
|
|
457
|
+
resolved.matched = true
|
|
458
|
+
for (const [key, value] of Object.entries(block.values)) {
|
|
459
|
+
if (key === 'hostname') { if (!resolved.hostName) resolved.hostName = String(value) }
|
|
460
|
+
else if (key === 'user') { if (!resolved.user) resolved.user = String(value) }
|
|
461
|
+
else if (key === 'port') { if (!resolved.port) resolved.port = Number(value) || 0 }
|
|
462
|
+
else if (key === 'identityfile') { if (!resolved.identityFile) resolved.identityFile = String(value) }
|
|
463
|
+
else if (key === 'proxyjump') { if (!resolved.proxyJump) resolved.proxyJump = String(value) }
|
|
464
|
+
else if (key === 'proxycommand') { if (!resolved.proxyCommand) resolved.proxyCommand = String(value) }
|
|
465
|
+
else if (key === 'identitiesonly') { if (!resolved.identitiesOnly) resolved.identitiesOnly = String(value) }
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
if (!resolved.matched) return null
|
|
469
|
+
return targetFromResolved(resolved, text, { defaultPort: 0 })
|
|
470
|
+
}
|
|
471
|
+
|
|
415
472
|
/**
|
|
416
473
|
* Effective connect identity for a saved machine.
|
|
417
474
|
*
|
|
@@ -452,3 +509,187 @@ export function resolveMachineSshConfig(machine, opts = {}) {
|
|
|
452
509
|
function compareText(left, right) {
|
|
453
510
|
return left < right ? -1 : left > right ? 1 : 0
|
|
454
511
|
}
|
|
512
|
+
|
|
513
|
+
// ── Writing: the managed Host block (issue #50) ─────────────────────────────
|
|
514
|
+
//
|
|
515
|
+
// The four connection fields (host/user/port/key) have ONE home: ~/.ssh/config.
|
|
516
|
+
// When the plugin saves a literal machine it upserts a Host block tagged with
|
|
517
|
+
// MANAGED_MARK, so the file and the registry can never drift — and so the
|
|
518
|
+
// plugin can tell ITS OWN block apart from one the user wrote. Two rules keep
|
|
519
|
+
// the file safe:
|
|
520
|
+
// • a Host block without the marker is never touched — a same-named user
|
|
521
|
+
// block is a CONFLICT, reported to the caller instead of overwritten;
|
|
522
|
+
// • inside a managed block, lines the plugin does not own (a user-added
|
|
523
|
+
// `ForwardAgent yes`, a comment) survive an update — only the four known
|
|
524
|
+
// parameters are rewritten.
|
|
525
|
+
|
|
526
|
+
/** Marker comment that tags a Host block as owned by dsh-remote. */
|
|
527
|
+
export const MANAGED_MARK = '# dsh-remote: managed'
|
|
528
|
+
|
|
529
|
+
/** Parameters the managed block rewrites (lowercase). */
|
|
530
|
+
const MANAGED_PARAMS = new Set(['hostname', 'user', 'port', 'identityfile'])
|
|
531
|
+
|
|
532
|
+
/** Read the RAW config text — no Include expansion (a write targets ONE file). */
|
|
533
|
+
export function readRawSshConfig(filePath = sshConfigPath()) {
|
|
534
|
+
try {
|
|
535
|
+
const text = readFileSync(filePath, 'utf8')
|
|
536
|
+
return typeof text === 'string' ? text : ''
|
|
537
|
+
} catch {
|
|
538
|
+
return ''
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/** Quote one value for a config line when it contains whitespace. */
|
|
543
|
+
function quoteCfg(value) {
|
|
544
|
+
const s = String(value)
|
|
545
|
+
return /[\s#]/.test(s) ? `"${s.replace(/"/g, '\\"')}"` : s
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
/** The values a managed block must carry for these fields ('' = omit). */
|
|
549
|
+
function desiredValues(fields) {
|
|
550
|
+
const alias = String(fields.alias || '').trim()
|
|
551
|
+
const hostName = String(fields.hostName || '').trim()
|
|
552
|
+
const port = Number(fields.port) || 0
|
|
553
|
+
return {
|
|
554
|
+
hostname: hostName && hostName !== alias ? hostName : '',
|
|
555
|
+
user: String(fields.user || '').trim(),
|
|
556
|
+
port: port && port !== 22 ? port : '',
|
|
557
|
+
identityfile: String(fields.identityFile || '').trim(),
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
/** Render the owned body lines of a managed block. */
|
|
562
|
+
function desiredLines(fields) {
|
|
563
|
+
const want = desiredValues(fields)
|
|
564
|
+
const out = []
|
|
565
|
+
if (want.hostname) out.push(` HostName ${quoteCfg(want.hostname)}`)
|
|
566
|
+
if (want.user) out.push(` User ${quoteCfg(want.user)}`)
|
|
567
|
+
if (want.port) out.push(` Port ${want.port}`)
|
|
568
|
+
if (want.identityfile) out.push(` IdentityFile ${quoteCfg(want.identityfile)}`)
|
|
569
|
+
return out
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/**
|
|
573
|
+
* Locate the MANAGED Host block for `alias` in raw config lines.
|
|
574
|
+
* The marker must be the nearest non-blank line above the Host line; a block
|
|
575
|
+
* that names the alias without the marker is the user's, not ours.
|
|
576
|
+
*/
|
|
577
|
+
function locateManagedBlock(lines, alias) {
|
|
578
|
+
const wanted = alias.toLowerCase()
|
|
579
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
580
|
+
const m = lines[i].match(/^\s*host\s+(.+?)\s*$/i)
|
|
581
|
+
if (!m) continue
|
|
582
|
+
const names = splitValue(m[1]).map((p) => p.toLowerCase())
|
|
583
|
+
if (!names.includes(wanted)) continue
|
|
584
|
+
let j = i - 1
|
|
585
|
+
while (j >= 0 && lines[j].trim() === '') j -= 1
|
|
586
|
+
if (j < 0 || lines[j].trim().toLowerCase() !== MANAGED_MARK) continue
|
|
587
|
+
let end = lines.length
|
|
588
|
+
for (let k = i + 1; k < lines.length; k += 1) {
|
|
589
|
+
if (/^\s*(host|match)\s/i.test(lines[k])) { end = k; break }
|
|
590
|
+
}
|
|
591
|
+
return { markerLine: j, hostLine: i, end }
|
|
592
|
+
}
|
|
593
|
+
return null
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
/** Whether an UNMANAGED Host block names `alias` (exact token, case-insensitive). */
|
|
597
|
+
function hasUnmanagedConflict(lines, alias) {
|
|
598
|
+
const wanted = alias.toLowerCase()
|
|
599
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
600
|
+
const m = lines[i].match(/^\s*host\s+(.+?)\s*$/i)
|
|
601
|
+
if (!m) continue
|
|
602
|
+
const names = splitValue(m[1]).map((p) => p.toLowerCase())
|
|
603
|
+
if (!names.includes(wanted)) continue
|
|
604
|
+
let j = i - 1
|
|
605
|
+
while (j >= 0 && lines[j].trim() === '') j -= 1
|
|
606
|
+
if (j >= 0 && lines[j].trim().toLowerCase() === MANAGED_MARK) continue // ours
|
|
607
|
+
return true
|
|
608
|
+
}
|
|
609
|
+
return false
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
/**
|
|
613
|
+
* Insert or refresh the managed Host block for a saved machine.
|
|
614
|
+
*
|
|
615
|
+
* @param {string} rawText - the RAW ~/.ssh/config text ('' when absent).
|
|
616
|
+
* @param {{alias: string, hostName?: string, user?: string, port?: number|string, identityFile?: string}} fields
|
|
617
|
+
* @returns {{status: 'appended'|'updated'|'unchanged'|'conflict'|'invalid', text: string}}
|
|
618
|
+
* 'conflict' — an unmanaged block already names the alias (text untouched);
|
|
619
|
+
* 'invalid' — the alias can never be a safe Host token (space/wildcard).
|
|
620
|
+
*/
|
|
621
|
+
export function upsertManagedHostBlock(rawText, fields) {
|
|
622
|
+
const text = String(rawText || '')
|
|
623
|
+
const alias = String((fields && fields.alias) || '').trim()
|
|
624
|
+
if (!alias || /[\s*?!,]/.test(alias)) return { status: 'invalid', text }
|
|
625
|
+
const lines = text.split('\n')
|
|
626
|
+
if (hasUnmanagedConflict(lines, alias)) return { status: 'conflict', text }
|
|
627
|
+
|
|
628
|
+
const want = desiredValues({ ...fields, alias })
|
|
629
|
+
const found = locateManagedBlock(lines, alias)
|
|
630
|
+
if (!found) {
|
|
631
|
+
const block = [MANAGED_MARK, `Host ${alias}`, ...desiredLines({ ...fields, alias })]
|
|
632
|
+
const trimmedEnd = text.replace(/\s+$/, '')
|
|
633
|
+
const prefix = trimmedEnd === '' ? '' : trimmedEnd + '\n\n'
|
|
634
|
+
return { status: 'appended', text: prefix + block.join('\n') + '\n' }
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
// Compare what the block HAS with what it MUST have; a rewrite that changes
|
|
638
|
+
// nothing would only cost the file's mtime.
|
|
639
|
+
const current = parseHostBlocks(lines.slice(found.hostLine, found.end).join('\n'))[0]
|
|
640
|
+
const cur = (current && current.values) || {}
|
|
641
|
+
const same = ['hostname', 'user', 'port', 'identityfile']
|
|
642
|
+
.every((k) => String(cur[k] ?? '') === String(want[k] ?? ''))
|
|
643
|
+
if (same) return { status: 'unchanged', text }
|
|
644
|
+
|
|
645
|
+
// Rewrite only the four owned parameters; user lines inside OUR block stay.
|
|
646
|
+
const body = lines.slice(found.hostLine + 1, found.end)
|
|
647
|
+
const kept = body.filter((l) => {
|
|
648
|
+
const m = l.match(/^\s*(\S+)\s/)
|
|
649
|
+
return !m || !MANAGED_PARAMS.has(m[1].toLowerCase())
|
|
650
|
+
})
|
|
651
|
+
const next = [
|
|
652
|
+
...lines.slice(0, found.hostLine + 1),
|
|
653
|
+
...desiredLines({ ...fields, alias }),
|
|
654
|
+
...kept,
|
|
655
|
+
...lines.slice(found.end),
|
|
656
|
+
]
|
|
657
|
+
return { status: 'updated', text: next.join('\n') }
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
/**
|
|
661
|
+
* Remove the managed block for `alias` (used when a machine is RENAMED away:
|
|
662
|
+
* the old block is the plugin's own artifact of that edit). Deleting a machine
|
|
663
|
+
* deliberately does NOT call this — the Host block is the user's ssh access,
|
|
664
|
+
* not plugin state. Unmanaged blocks are never removed.
|
|
665
|
+
*
|
|
666
|
+
* @returns {{status: 'removed'|'absent', text: string}}
|
|
667
|
+
*/
|
|
668
|
+
export function removeManagedHostBlock(rawText, alias) {
|
|
669
|
+
const text = String(rawText || '')
|
|
670
|
+
const name = String(alias || '').trim()
|
|
671
|
+
if (!name) return { status: 'absent', text }
|
|
672
|
+
const lines = text.split('\n')
|
|
673
|
+
const found = locateManagedBlock(lines, name)
|
|
674
|
+
if (!found) return { status: 'absent', text }
|
|
675
|
+
let start = found.markerLine
|
|
676
|
+
while (start > 0 && lines[start - 1].trim() === '') start -= 1
|
|
677
|
+
const next = [...lines.slice(0, start), ...lines.slice(found.end)]
|
|
678
|
+
return { status: 'removed', text: next.join('\n') }
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
/**
|
|
682
|
+
* Persist a config text, creating ~/.ssh first. Permissions follow OpenSSH's
|
|
683
|
+
* expectations on POSIX (dir 0700, file 0600) and are a no-op elsewhere.
|
|
684
|
+
*/
|
|
685
|
+
export function writeSshConfig(text, filePath = sshConfigPath(), io = {}) {
|
|
686
|
+
const mkdir = io.mkdir || ((p) => mkdirSync(p, { recursive: true }))
|
|
687
|
+
const write = io.write || ((p, data) => writeFileSync(p, data, 'utf8'))
|
|
688
|
+
const chmod = io.chmod || ((p, mode) => { try { chmodSync(p, mode) } catch { /* no modes here */ } })
|
|
689
|
+
mkdir(path.dirname(filePath))
|
|
690
|
+
write(filePath, String(text))
|
|
691
|
+
if (process.platform !== 'win32') {
|
|
692
|
+
chmod(path.dirname(filePath), 0o700)
|
|
693
|
+
chmod(filePath, 0o600)
|
|
694
|
+
}
|
|
695
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
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
|
|
@@ -0,0 +1,27 @@
|
|
|
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
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Copyright Brian White. All rights reserved.
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
of this software and associated documentation files (the "Software"), to
|
|
5
|
+
deal in the Software without restriction, including without limitation the
|
|
6
|
+
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
7
|
+
sell copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
furnished to do so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
|
11
|
+
all copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
18
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
19
|
+
IN THE SOFTWARE.
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# Description
|
|
2
|
+
|
|
3
|
+
Build environment checking for [node.js](http://nodejs.org/).
|
|
4
|
+
|
|
5
|
+
This allows for autoconf-like functionality for node addons/build scripts.
|
|
6
|
+
|
|
7
|
+
**Note:** Obsolete and/or exotic build environments or platforms not supported
|
|
8
|
+
by node.js are not supported.
|
|
9
|
+
|
|
10
|
+
## Requirements
|
|
11
|
+
|
|
12
|
+
* [node.js](http://nodejs.org/) -- v10.0.0 or newer
|
|
13
|
+
* Supported compilers:
|
|
14
|
+
* gcc
|
|
15
|
+
* clang
|
|
16
|
+
* MSVC 2013+ and Windows SDK 8.1+
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
npm install buildcheck
|
|
21
|
+
|
|
22
|
+
## Examples
|
|
23
|
+
|
|
24
|
+
### Check if a C function exists
|
|
25
|
+
|
|
26
|
+
```js
|
|
27
|
+
'use strict';
|
|
28
|
+
|
|
29
|
+
const BuildEnvironment = require('buildcheck');
|
|
30
|
+
|
|
31
|
+
const buildEnv = new BuildEnvironment();
|
|
32
|
+
|
|
33
|
+
console.log(buildEnv.checkFunction('c', 'preadv2'));
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Check if a C header is usable
|
|
37
|
+
|
|
38
|
+
```js
|
|
39
|
+
'use strict';
|
|
40
|
+
|
|
41
|
+
const BuildEnvironment = require('buildcheck');
|
|
42
|
+
|
|
43
|
+
const buildEnv = new BuildEnvironment();
|
|
44
|
+
|
|
45
|
+
console.log(buildEnv.checkHeader('c', 'linux/io_uring.h'));
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Try to compile some C code
|
|
49
|
+
|
|
50
|
+
```js
|
|
51
|
+
'use strict';
|
|
52
|
+
|
|
53
|
+
const BuildEnvironment = require('buildcheck');
|
|
54
|
+
|
|
55
|
+
const buildEnv = new BuildEnvironment();
|
|
56
|
+
|
|
57
|
+
// Should be a successful compile
|
|
58
|
+
console.log(buildEnv.tryCompile('c', 'int main() { return 0; }'));
|
|
59
|
+
|
|
60
|
+
// Should be a failed compile
|
|
61
|
+
console.log(buildEnv.tryCompile('c', 'int main() { return z; }'));
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## API
|
|
65
|
+
|
|
66
|
+
### Exports
|
|
67
|
+
|
|
68
|
+
The exported value is `BuildEnvironment`, the main class for dealing with a build environment.
|
|
69
|
+
|
|
70
|
+
### BuildEnvironment
|
|
71
|
+
|
|
72
|
+
#### Methods
|
|
73
|
+
|
|
74
|
+
* **(constructor)**([< _object_ >config]) - Creates and returns a new BuildEnvironment instance. `config` may contain:
|
|
75
|
+
|
|
76
|
+
* **compilerC** - _string_ - C compiler command to use. *Note: this is ignored on Windows.* **Default:** `process.env.CC` or `'cc'`
|
|
77
|
+
|
|
78
|
+
* **compilerCXX** - _string_ - C++ compiler command to use. *Note: this is ignored on Windows.* **Default:** `process.env.CXX` or `'c++'`
|
|
79
|
+
|
|
80
|
+
* **msvs_version** - _mixed_ - A string or number containing the year of the Visual Studio compiler to use. *Note: this is for Windows only.* **Default:** newest version installed
|
|
81
|
+
|
|
82
|
+
* **checkDeclared**(< _string_ >lang, < _string_ >symbolName[, < _object_ >options]) - _boolean_ - Checks if a symbol `symbolName` is declared where `lang` is either `'c'` or `'c++'`. Returns `true` if symbol exists, `false` otherwise. `options` may contain:
|
|
83
|
+
|
|
84
|
+
* **headers** - _array_ - A list of headers to try when checking if the symbol is declared. `checkFunction()` will always first try without a library. If not supplied, a default list of common (platform-specific) headers will be used.
|
|
85
|
+
|
|
86
|
+
* **searchLibs** - _array_ - A list of library names (without the `'-l'` prefix) to try when checking if the symbol is declared. `checkDeclared()` will always first try without a library.
|
|
87
|
+
|
|
88
|
+
* **checkFunction**(< _string_ >lang, < _string_ >functionName[, < _object_ >options]) - _boolean_ - Checks if a function `functionName` exists and is linkable where `lang` is either `'c'` or `'c++'`. Returns `true` if function exists, `false` otherwise. `options` may contain:
|
|
89
|
+
|
|
90
|
+
* **searchLibs** - _array_ - A list of library names (without the `'-l'` prefix) to try when checking for this function. `checkFunction()` will always first try without a library.
|
|
91
|
+
|
|
92
|
+
* **checkFeature**(< _string_ >featureName) - _mixed_ - Executes a special test for a "feature" and returns the result. Supported values for `featureName`:
|
|
93
|
+
|
|
94
|
+
* `'strerror_r'` - Returns an object containing:
|
|
95
|
+
|
|
96
|
+
* `declared` - _boolean_ - Whether `strerror_r()` is declared
|
|
97
|
+
|
|
98
|
+
* `returnsCharPtr` - _boolean_ - If `strerror_r()` is declared, whether it returns `char*` (a GNU extension) or not.
|
|
99
|
+
|
|
100
|
+
* **checkHeader**(< _string_ >lang, < _string_ >headerName) - _boolean_ - Checks if the header `headerName` exists and is usable where `lang` is either `'c'` or `'c++'`. Returns `true` if the header exists and is usable, `false` otherwise.
|
|
101
|
+
|
|
102
|
+
* **defines**([< _string_ >lang[, < _boolean_ >rendered]]) - _array_ - Returns a list of features, functions, headers, and symbols known to be defined by this build environment instance. `lang` is either `'c'` or `'c++'` If `lang` is not set, defines for both `'c'` and `'c++'` will be returned. If `rendered` is `true` (defaults to `false`), autoconf-style defines (e.g. "HAVE_FOO=1") will be returned instead. Defines coming from features utilize base strings/names from autoconf for better compatibility.
|
|
103
|
+
|
|
104
|
+
* **libs**([< _string_ >lang]) - _array_ - Returns a list of (`'-l'`-prefixed) libraries known to be required for features and functions defined by this build environment instance. `lang` is either `'c'` or `'c++'` If `lang` is not set, defines for both `'c'` and `'c++'` will be returned.
|
|
105
|
+
|
|
106
|
+
* **tryCompile**(< _string_ >lang, < _string_ >code[, < _array_ >compilerParams]) - _mixed_ - Attempts to compile `code` where `lang` is either `'c'` or `'c++'`. `compilerParams` is an optional array of compiler/linker flags to include. Returns `true` on successful compilation, or an _Error_ instance with an `output` property containing the compiler error output.
|