diff --git a/src/ssh-config.ts b/src/ssh-config.ts index 1bd809d..a7fc3b4 100644 --- a/src/ssh-config.ts +++ b/src/ssh-config.ts @@ -7,7 +7,8 @@ import os from 'node:os' const RE_SPACE = /\s/ const RE_LINE_BREAK = /\r|\n/ const RE_SECTION_DIRECTIVE = /^(Host|Match)$/i -const RE_MULTI_VALUE_DIRECTIVE = /^(GlobalKnownHostsFile|Host|IPQoS|SendEnv|UserKnownHostsFile|ProxyCommand|Match|CanonicalDomains)$/i +const RE_MULTI_VALUE_DIRECTIVE = /^(GlobalKnownHostsFile|Host|IPQoS|SendEnv|UserKnownHostsFile|ProxyCommand|KnownHostsCommand|Match|CanonicalDomains)$/i +const RE_COMMAND_DIRECTIVE = /^(ProxyCommand|KnownHostsCommand)$/i const RE_QUOTE_DIRECTIVE = /^(?:CertificateFile|IdentityFile|IdentityAgent|User)$/i const RE_SINGLE_LINE_DIRECTIVE = /^(Include|IdentityFile)$/i @@ -303,7 +304,7 @@ export default class SSHConfig extends Array { const key = computeOpts?.ignoreCase ? name.toLowerCase() : name let val: string | string[] if (Array.isArray(value)) { - if (/ProxyCommand/i.test(key)) { + if (RE_COMMAND_DIRECTIVE.test(key)) { val = value.map(({ val, separator, quoted }) => { return `${separator}${quoted ? `"${val.replace(/"/g, '\\"')}"` : val}` }).join('').trim() diff --git a/test/legacy/compute.test.ts b/test/legacy/compute.test.ts index 973fc59..a5d19f4 100644 --- a/test/legacy/compute.test.ts +++ b/test/legacy/compute.test.ts @@ -300,4 +300,14 @@ describe('compute', function() { assert.equal(result.ProxyCommand, '"/foo/bar - baz/proxylauncher.sh" "/some/param with space"') }) + it('.compute should preserve KnownHostsCommand arguments', async () => { + const config = SSHConfig.parse(` + Host *.sbx + KnownHostsCommand "/opt/homebrew/bin/sbx" ssh known-hosts %H + `) + + const result = config.compute({ Host: 'example.sbx' }) + assert.equal(result.KnownHostsCommand, '"/opt/homebrew/bin/sbx" ssh known-hosts %H') + }) + }) diff --git a/test/legacy/stringify.test.ts b/test/legacy/stringify.test.ts index 7ab73a0..e0d3218 100644 --- a/test/legacy/stringify.test.ts +++ b/test/legacy/stringify.test.ts @@ -167,6 +167,18 @@ describe('stringify', function() { `) }) + it('.stringify KnownHostsCommand with a quoted executable', function() { + const config = parse(` + Host *.sbx + KnownHostsCommand "/opt/homebrew/bin/sbx" ssh known-hosts %H + `) + + assert.equal(stringify(config), ` + Host *.sbx + KnownHostsCommand "/opt/homebrew/bin/sbx" ssh known-hosts %H + `) + }) + it('.stringify Match with criteria', function() { const config = parse(` Match host foo final exec "return 0" diff --git a/test/unit/compute.test.ts b/test/unit/compute.test.ts index 77f8038..e41160e 100644 --- a/test/unit/compute.test.ts +++ b/test/unit/compute.test.ts @@ -318,6 +318,16 @@ describe('compute', function() { assert.equal(result.ProxyCommand, '"/foo/bar - baz/proxylauncher.sh" "/some/param with space"') }) + it('.compute should preserve KnownHostsCommand arguments', async () => { + const config = SSHConfig.parse(` + Host *.sbx + KnownHostsCommand "/opt/homebrew/bin/sbx" ssh known-hosts %H + `) + + const result = config.compute({ Host: 'example.sbx' }) + assert.equal(result.KnownHostsCommand, '"/opt/homebrew/bin/sbx" ssh known-hosts %H') + }) + describe('compute with ignoreCase', function() { it('.compute with ignoreCase: true should normalize directive names to lowercase', async function() { const config = SSHConfig.parse(` diff --git a/test/unit/stringify.test.ts b/test/unit/stringify.test.ts index 3a9e062..36b0643 100644 --- a/test/unit/stringify.test.ts +++ b/test/unit/stringify.test.ts @@ -167,6 +167,18 @@ describe('stringify', function() { `) }) + it('.stringify KnownHostsCommand with a quoted executable', function() { + const config = parse(` + Host *.sbx + KnownHostsCommand "/opt/homebrew/bin/sbx" ssh known-hosts %H + `) + + assert.equal(stringify(config), ` + Host *.sbx + KnownHostsCommand "/opt/homebrew/bin/sbx" ssh known-hosts %H + `) + }) + it('.stringify Match with criteria', function() { const config = parse(` Match host foo final exec "return 0"