diff --git a/src/ssh-config.ts b/src/ssh-config.ts index fde5b89..ebe68d4 100644 --- a/src/ssh-config.ts +++ b/src/ssh-config.ts @@ -69,7 +69,7 @@ interface ComputeContext { inFinalPass: boolean; } -const MULTIPLE_VALUE_PROPS = [ +const REPEATABLE_DIRECTIVES = [ 'IdentityFile', 'LocalForward', 'RemoteForward', @@ -186,10 +186,19 @@ export default class SSHConfig extends Array { } const obj: Record = {} - const setProperty = (name: string, value: string | { val: string }[]) => { - const val = Array.isArray(value) ? value.map(({ val }) => val) : value + const setProperty = (name: string, value: string | { val: string, separator: string }[]) => { + let val: string | string[] + if (Array.isArray(value)) { + if (/ProxyCommand/i.test(name)) { + val = value.map(({ val, separator }) => `${separator}${val}`).join('').trim() + } else { + val = value.map(({ val }) => val) + } + } else { + val = value + } const val0 = Array.isArray(val) ? val[0] : val - if (MULTIPLE_VALUE_PROPS.includes(name)) { + if (REPEATABLE_DIRECTIVES.includes(name)) { const list = (obj[name] || (obj[name] = [])) as string[] list.push(...([] as string[]).concat(val)) } else if (obj[name] == null) { @@ -695,7 +704,7 @@ export function stringify(config: SSHConfig): string { if (line.type === LineType.COMMENT) { str += line.content } - else if (line.type === LineType.DIRECTIVE && MULTIPLE_VALUE_PROPS.includes(line.param)) { + else if (line.type === LineType.DIRECTIVE && REPEATABLE_DIRECTIVES.includes(line.param)) { (Array.isArray(line.value) ? line.value : [line.value]).forEach((value, i, values) => { str += formatDirective({ ...line, value: typeof value !== 'string' ? value.val : value }) if (i < values.length - 1) str += `\n${line.before}` diff --git a/test/unit/ssh-config.test.ts b/test/unit/ssh-config.test.ts index 1983c02..1a21c15 100644 --- a/test/unit/ssh-config.test.ts +++ b/test/unit/ssh-config.test.ts @@ -39,13 +39,7 @@ describe('SSHConfig', function() { IdentityFile: [ '~/.ssh/id_rsa' ], - ProxyCommand: [ - 'ssh', - '-q', - 'gateway', - '-W', - '%h:%p', - ], + ProxyCommand: 'ssh -q gateway -W %h:%p', ServerAliveInterval: '80', User: 'nil', ForwardAgent: 'true', @@ -267,6 +261,21 @@ describe('SSHConfig', function() { assert.ok(result) }) + it('.compute should preserve separators in multi-value directives', async () => { + const config = SSHConfig.parse(` + Host YYYY + HostName YYYY + IdentityFile ~/.ssh/id_rsa + StrictHostKeyChecking no + UserKnownHostsFile /dev/null + ProxyCommand ssh -i ~/.ssh/id_rsa -W %h:%p -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null XXX@ZZZ + User XXX + `) + + const result = config.compute({ Host: 'YYYY' }) + assert.equal(result.ProxyCommand, 'ssh -i ~/.ssh/id_rsa -W %h:%p -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null XXX@ZZZ') + }) + it('.find with nothing shall yield error', async function() { const config = SSHConfig.parse(readFile('fixture/config')) assert.throws(function() { config.find({}) })