ssh-config 4.1.6 → 4.2.0
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/index.js +23 -478
- package/package.json +9 -7
- package/src/glob.js +29 -35
- package/src/glob.js.map +1 -0
- package/src/glob.ts +45 -0
- package/src/ssh-config.js +459 -0
- package/src/ssh-config.js.map +1 -0
- package/src/ssh-config.ts +544 -0
- package/types/index.d.ts +0 -46
|
@@ -0,0 +1,544 @@
|
|
|
1
|
+
|
|
2
|
+
import glob from './glob'
|
|
3
|
+
import { spawnSync } from 'child_process'
|
|
4
|
+
|
|
5
|
+
const RE_SPACE = /\s/
|
|
6
|
+
const RE_LINE_BREAK = /\r|\n/
|
|
7
|
+
const RE_SECTION_DIRECTIVE = /^(Host|Match)$/i
|
|
8
|
+
const RE_MULTI_VALUE_DIRECTIVE = /^(GlobalKnownHostsFile|Host|IPQoS|SendEnv|UserKnownHostsFile|ProxyCommand|Match)$/i
|
|
9
|
+
const RE_QUOTE_DIRECTIVE = /^(?:CertificateFile|IdentityFile|IdentityAgent|User)$/i
|
|
10
|
+
const RE_SINGLE_LINE_DIRECTIVE = /^(Include|IdentityFile)$/i
|
|
11
|
+
|
|
12
|
+
enum LineType {
|
|
13
|
+
DIRECTIVE = 1,
|
|
14
|
+
COMMENT = 2,
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
type Separator = ' ' | '=' | '\t';
|
|
18
|
+
|
|
19
|
+
type Space = ' ' | '\t' | '\n';
|
|
20
|
+
|
|
21
|
+
interface Directive {
|
|
22
|
+
type: LineType.DIRECTIVE;
|
|
23
|
+
before: string;
|
|
24
|
+
after: string;
|
|
25
|
+
param: string;
|
|
26
|
+
separator: Separator;
|
|
27
|
+
value: string | string[];
|
|
28
|
+
quoted?: boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface Section extends Directive {
|
|
32
|
+
config: SSHConfig;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface Match extends Section {
|
|
36
|
+
criteria: Record<string, string | string[]>
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface Comment {
|
|
40
|
+
type: LineType.COMMENT;
|
|
41
|
+
before: string;
|
|
42
|
+
after: string;
|
|
43
|
+
content: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
type Line = Match | Section | Directive | Comment;
|
|
47
|
+
|
|
48
|
+
interface FindOptions {
|
|
49
|
+
Host?: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const MULTIPLE_VALUE_PROPS = [
|
|
53
|
+
'IdentityFile',
|
|
54
|
+
'LocalForward',
|
|
55
|
+
'RemoteForward',
|
|
56
|
+
'DynamicForward',
|
|
57
|
+
'CertificateFile',
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
function compare(line, opts) {
|
|
61
|
+
return opts.hasOwnProperty(line.param) && opts[line.param] === line.value
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function getIndent(config: SSHConfig) {
|
|
65
|
+
for (const line of config) {
|
|
66
|
+
if (line.type === LineType.DIRECTIVE && 'config' in line) {
|
|
67
|
+
for (const subline of line.config) {
|
|
68
|
+
if (subline.before) {
|
|
69
|
+
return subline.before
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return ' '
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function capitalize(str) {
|
|
79
|
+
if (typeof str !== 'string') return str
|
|
80
|
+
return str[0].toUpperCase() + str.slice(1)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function match(criteria, params) {
|
|
84
|
+
for (const key in criteria) {
|
|
85
|
+
const criterion = criteria[key]
|
|
86
|
+
const keyword = key.toLowerCase()
|
|
87
|
+
if (keyword === 'exec') {
|
|
88
|
+
const command = `function main {
|
|
89
|
+
${criterion}
|
|
90
|
+
}
|
|
91
|
+
main`
|
|
92
|
+
const { status } = spawnSync(command, { shell: true })
|
|
93
|
+
if (status != 0) return false
|
|
94
|
+
} else if (!glob(criterion, params[capitalize(keyword)])) {
|
|
95
|
+
return false
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return true
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
class SSHConfig extends Array<Line> {
|
|
102
|
+
static DIRECTIVE: LineType.DIRECTIVE = LineType.DIRECTIVE
|
|
103
|
+
static COMMENT: LineType.COMMENT = LineType.COMMENT
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Query ssh config by host.
|
|
107
|
+
*/
|
|
108
|
+
compute(params): Record<string, string | string[]> {
|
|
109
|
+
if (typeof params === 'string') params = { Host: params }
|
|
110
|
+
const obj = {}
|
|
111
|
+
const setProperty = (name, value) => {
|
|
112
|
+
if (MULTIPLE_VALUE_PROPS.includes(name)) {
|
|
113
|
+
const list = obj[name] || (obj[name] = [])
|
|
114
|
+
list.push(value)
|
|
115
|
+
} else if (obj[name] == null) {
|
|
116
|
+
obj[name] = value
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
for (const line of this) {
|
|
121
|
+
if (line.type !== LineType.DIRECTIVE) continue
|
|
122
|
+
if (line.param === 'Host' && glob(line.value, params.Host)) {
|
|
123
|
+
setProperty(line.param, line.value)
|
|
124
|
+
for (const subline of (line as Section).config) {
|
|
125
|
+
if (subline.type === LineType.DIRECTIVE) {
|
|
126
|
+
setProperty(subline.param, subline.value)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
} else if (line.param === 'Match' && 'criteria' in line && match(line.criteria, params)) {
|
|
130
|
+
for (const subline of (line as Section).config) {
|
|
131
|
+
if (subline.type === LineType.DIRECTIVE) {
|
|
132
|
+
setProperty(subline.param, subline.value)
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
} else {
|
|
136
|
+
setProperty(line.param, line.value)
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return obj
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* find section by Host / Match or function
|
|
145
|
+
*/
|
|
146
|
+
find(opts: ((line: Line, index: number, config: Line[]) => unknown) | FindOptions) {
|
|
147
|
+
if (typeof opts === 'function') return super.find(opts)
|
|
148
|
+
|
|
149
|
+
if (!(opts && ('Host' in opts || 'Match' in opts))) {
|
|
150
|
+
throw new Error('Can only find by Host or Match')
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return super.find(line => compare(line, opts))
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Remove section by Host / Match or function
|
|
158
|
+
*/
|
|
159
|
+
remove(opts: ((line: Line, index: number, config: Line[]) => unknown) | FindOptions) {
|
|
160
|
+
let index: number
|
|
161
|
+
|
|
162
|
+
if (typeof opts === 'function') {
|
|
163
|
+
index = super.findIndex(opts)
|
|
164
|
+
} else if (!(opts && ('Host' in opts || 'Match' in opts))) {
|
|
165
|
+
throw new Error('Can only remove by Host or Match')
|
|
166
|
+
} else {
|
|
167
|
+
index = super.findIndex(line => compare(line, opts))
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (index >= 0) return this.splice(index, 1)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
toString(): string {
|
|
174
|
+
return stringify(this)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Append new section to existing ssh config.
|
|
179
|
+
* @param {Object} opts
|
|
180
|
+
*/
|
|
181
|
+
append(opts: Record<string, string | string[]>) {
|
|
182
|
+
const indent = getIndent(this)
|
|
183
|
+
const lastEntry = this.length > 0 ? this[this.length - 1] : null
|
|
184
|
+
let config = lastEntry && (lastEntry as Section).config || this
|
|
185
|
+
let configWas = this
|
|
186
|
+
|
|
187
|
+
let lastLine = config.length > 0 ? config[config.length - 1] : lastEntry
|
|
188
|
+
if (lastLine && !lastLine.after) lastLine.after = '\n'
|
|
189
|
+
|
|
190
|
+
let sectionLineFound = config !== configWas
|
|
191
|
+
|
|
192
|
+
for (const param in opts) {
|
|
193
|
+
const value = opts[param]
|
|
194
|
+
const line: Directive = {
|
|
195
|
+
type: LineType.DIRECTIVE,
|
|
196
|
+
param,
|
|
197
|
+
separator: ' ',
|
|
198
|
+
value,
|
|
199
|
+
before: sectionLineFound ? indent : indent.replace(/ |\t/, ''),
|
|
200
|
+
after: '\n',
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (RE_SECTION_DIRECTIVE.test(param)) {
|
|
204
|
+
sectionLineFound = true
|
|
205
|
+
line.before = indent.replace(/ |\t/, '')
|
|
206
|
+
config = configWas
|
|
207
|
+
// separate sections with an extra newline
|
|
208
|
+
// https://github.com/cyjake/ssh-config/issues/23#issuecomment-564768248
|
|
209
|
+
if (lastLine && lastLine.after === '\n') lastLine.after += '\n'
|
|
210
|
+
config.push(line)
|
|
211
|
+
config = (line as Section).config = new SSHConfig()
|
|
212
|
+
} else {
|
|
213
|
+
config.push(line)
|
|
214
|
+
}
|
|
215
|
+
lastLine = line
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return configWas
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Prepend new section to existing ssh config.
|
|
223
|
+
* @param {Object} opts
|
|
224
|
+
*/
|
|
225
|
+
prepend(opts: Record<string, string | string[]>, beforeFirstSection = false) {
|
|
226
|
+
const indent = getIndent(this)
|
|
227
|
+
let config: SSHConfig = this
|
|
228
|
+
let i = 0
|
|
229
|
+
|
|
230
|
+
// insert above known sections
|
|
231
|
+
if (beforeFirstSection) {
|
|
232
|
+
while (i < this.length && !('config' in this[i])) {
|
|
233
|
+
i += 1
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (i >= this.length) { // No sections in original config
|
|
237
|
+
return this.append(opts)
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Prepend new section above the first section
|
|
242
|
+
let sectionLineFound = false
|
|
243
|
+
let processedLines = 0
|
|
244
|
+
|
|
245
|
+
for (const param in opts) {
|
|
246
|
+
processedLines += 1
|
|
247
|
+
const value = opts[param]
|
|
248
|
+
const line: Directive = {
|
|
249
|
+
type: LineType.DIRECTIVE,
|
|
250
|
+
param,
|
|
251
|
+
separator: ' ',
|
|
252
|
+
value,
|
|
253
|
+
before: '',
|
|
254
|
+
after: '\n',
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (RE_SECTION_DIRECTIVE.test(param)) {
|
|
258
|
+
line.before = indent.replace(/ |\t/, '')
|
|
259
|
+
config.splice(i, 0, line)
|
|
260
|
+
config = (line as Section).config = new SSHConfig()
|
|
261
|
+
sectionLineFound = true
|
|
262
|
+
continue
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// separate from previous sections with an extra newline
|
|
266
|
+
if (processedLines === Object.keys(opts).length) {
|
|
267
|
+
line.after += '\n'
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (!sectionLineFound) {
|
|
271
|
+
config.splice(i, 0, line)
|
|
272
|
+
i += 1
|
|
273
|
+
|
|
274
|
+
// Add an extra newline if a single line directive like Include
|
|
275
|
+
if (RE_SINGLE_LINE_DIRECTIVE.test(param)) {
|
|
276
|
+
line.after += '\n'
|
|
277
|
+
}
|
|
278
|
+
continue
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
line.before = indent
|
|
282
|
+
config.push(line)
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return config
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Parse ssh config text into structured object.
|
|
291
|
+
*/
|
|
292
|
+
export function parse(text: string): SSHConfig {
|
|
293
|
+
let i = 0
|
|
294
|
+
let chr = next()
|
|
295
|
+
let config: SSHConfig = new SSHConfig()
|
|
296
|
+
let configWas = config
|
|
297
|
+
|
|
298
|
+
function next() {
|
|
299
|
+
return text[i++]
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function space(): Space {
|
|
303
|
+
let spaces = ''
|
|
304
|
+
|
|
305
|
+
while (RE_SPACE.test(chr)) {
|
|
306
|
+
spaces += chr
|
|
307
|
+
chr = next()
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return spaces as Space
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function linebreak() {
|
|
314
|
+
let breaks = ''
|
|
315
|
+
|
|
316
|
+
while (RE_LINE_BREAK.test(chr)) {
|
|
317
|
+
breaks += chr
|
|
318
|
+
chr = next()
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
return breaks
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function parameter() {
|
|
325
|
+
let param = ''
|
|
326
|
+
|
|
327
|
+
while (chr && /[^ \t=]/.test(chr)) {
|
|
328
|
+
param += chr
|
|
329
|
+
chr = next()
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return param
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function separator(): Separator {
|
|
336
|
+
let sep = space()
|
|
337
|
+
|
|
338
|
+
if (chr === '=') {
|
|
339
|
+
sep += chr
|
|
340
|
+
chr = next()
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
return (sep + space()) as Separator
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function value() {
|
|
347
|
+
let val = ''
|
|
348
|
+
let quoted = false
|
|
349
|
+
let escaped = false
|
|
350
|
+
|
|
351
|
+
while (chr && !RE_LINE_BREAK.test(chr)) {
|
|
352
|
+
// backslash escapes only double quotes
|
|
353
|
+
if (escaped) {
|
|
354
|
+
val += chr === '"' ? chr : `\\${chr}`
|
|
355
|
+
escaped = false
|
|
356
|
+
}
|
|
357
|
+
// ProxyCommand ssh -W "%h:%p" firewall.example.org
|
|
358
|
+
else if (chr === '"' && (!val || quoted)) {
|
|
359
|
+
quoted = !quoted
|
|
360
|
+
}
|
|
361
|
+
else if (chr === '\\') {
|
|
362
|
+
escaped = true
|
|
363
|
+
}
|
|
364
|
+
else {
|
|
365
|
+
val += chr
|
|
366
|
+
}
|
|
367
|
+
chr = next()
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
if (quoted || escaped) {
|
|
371
|
+
throw new Error(`Unexpected line break at ${val}`)
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
return val.trim()
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function comment(): Comment {
|
|
378
|
+
const type = LineType.COMMENT
|
|
379
|
+
let content = ''
|
|
380
|
+
|
|
381
|
+
while (chr && !RE_LINE_BREAK.test(chr)) {
|
|
382
|
+
content += chr
|
|
383
|
+
chr = next()
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
return { type, content, before: '', after: '' }
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Host *.co.uk
|
|
390
|
+
// Host * !local.dev
|
|
391
|
+
// Host "foo bar"
|
|
392
|
+
function values() {
|
|
393
|
+
const results: string[] = []
|
|
394
|
+
let val = ''
|
|
395
|
+
let quoted = false
|
|
396
|
+
let escaped = false
|
|
397
|
+
|
|
398
|
+
while (chr && !RE_LINE_BREAK.test(chr)) {
|
|
399
|
+
if (escaped) {
|
|
400
|
+
val += chr === '"' ? chr : `\\${chr}`
|
|
401
|
+
escaped = false
|
|
402
|
+
}
|
|
403
|
+
else if (chr === '"') {
|
|
404
|
+
quoted = !quoted
|
|
405
|
+
}
|
|
406
|
+
else if (chr === '\\') {
|
|
407
|
+
escaped = true
|
|
408
|
+
}
|
|
409
|
+
else if (quoted) {
|
|
410
|
+
val += chr
|
|
411
|
+
}
|
|
412
|
+
else if (/[ \t]/.test(chr)) {
|
|
413
|
+
if (val) {
|
|
414
|
+
results.push(val)
|
|
415
|
+
val = ''
|
|
416
|
+
}
|
|
417
|
+
// otherwise ignore the space
|
|
418
|
+
}
|
|
419
|
+
else {
|
|
420
|
+
val += chr
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
chr = next()
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
if (quoted || escaped) {
|
|
427
|
+
throw new Error(`Unexpected line break at ${results.concat(val).join(' ')}`)
|
|
428
|
+
}
|
|
429
|
+
if (val) results.push(val)
|
|
430
|
+
return results.length > 1 ? results : results[0]
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function directive() {
|
|
434
|
+
const type = LineType.DIRECTIVE
|
|
435
|
+
const param = parameter()
|
|
436
|
+
// Host "foo bar" baz
|
|
437
|
+
const multiple = RE_MULTI_VALUE_DIRECTIVE.test(param)
|
|
438
|
+
const result: Directive = {
|
|
439
|
+
type,
|
|
440
|
+
param,
|
|
441
|
+
separator: separator(),
|
|
442
|
+
quoted: !multiple && chr === '"',
|
|
443
|
+
value: multiple ? values() : value(),
|
|
444
|
+
before: '',
|
|
445
|
+
after: '',
|
|
446
|
+
}
|
|
447
|
+
if (!result.quoted) delete result.quoted
|
|
448
|
+
if (/^Match$/i.test(param)) {
|
|
449
|
+
const criteria = {}
|
|
450
|
+
for (let i = 0; i < result.value.length; i += 2) {
|
|
451
|
+
const keyword = result.value[i]
|
|
452
|
+
const value = result.value[ i + 1]
|
|
453
|
+
criteria[keyword] = value
|
|
454
|
+
}
|
|
455
|
+
(result as Match).criteria = criteria
|
|
456
|
+
}
|
|
457
|
+
return result
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function line() {
|
|
461
|
+
const before = space()
|
|
462
|
+
const node = chr === '#' ? comment() : directive()
|
|
463
|
+
const after = linebreak()
|
|
464
|
+
|
|
465
|
+
node.before = before
|
|
466
|
+
node.after = after
|
|
467
|
+
|
|
468
|
+
return node
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
while (chr) {
|
|
472
|
+
let node = line()
|
|
473
|
+
|
|
474
|
+
if (node.type === LineType.DIRECTIVE && RE_SECTION_DIRECTIVE.test(node.param)) {
|
|
475
|
+
config = configWas
|
|
476
|
+
config.push(node)
|
|
477
|
+
config = (node as Section).config = new SSHConfig()
|
|
478
|
+
}
|
|
479
|
+
else if (node.type === LineType.DIRECTIVE && !node.param) {
|
|
480
|
+
// blank lines at file end
|
|
481
|
+
config[config.length - 1].after += node.before
|
|
482
|
+
}
|
|
483
|
+
else {
|
|
484
|
+
config.push(node)
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
return configWas
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* Stringify structured object into ssh config text
|
|
493
|
+
*/
|
|
494
|
+
export function stringify(config: SSHConfig): string {
|
|
495
|
+
let str = ''
|
|
496
|
+
|
|
497
|
+
function formatValue(value: string | string[] | Record<string, any>, quoted: boolean) {
|
|
498
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
499
|
+
const result: string[] = []
|
|
500
|
+
for (const key in value) result.push(key, value[key])
|
|
501
|
+
value = result
|
|
502
|
+
}
|
|
503
|
+
if (Array.isArray(value)) {
|
|
504
|
+
return value.map(chunk => formatValue(chunk, RE_SPACE.test(chunk))).join(' ')
|
|
505
|
+
}
|
|
506
|
+
return quoted ? `"${value}"` : value
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
function formatDirective(line) {
|
|
510
|
+
const quoted = line.quoted
|
|
511
|
+
|| (RE_QUOTE_DIRECTIVE.test(line.param) && RE_SPACE.test(line.value))
|
|
512
|
+
const value = formatValue(line.value, quoted)
|
|
513
|
+
return `${line.param}${line.separator}${value}`
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
const format = line => {
|
|
517
|
+
str += line.before
|
|
518
|
+
|
|
519
|
+
if (line.type === LineType.COMMENT) {
|
|
520
|
+
str += line.content
|
|
521
|
+
}
|
|
522
|
+
else if (line.type === LineType.DIRECTIVE && MULTIPLE_VALUE_PROPS.includes(line.param)) {
|
|
523
|
+
[].concat(line.value).forEach(function (value, i, values) {
|
|
524
|
+
str += formatDirective({ ...line, value })
|
|
525
|
+
if (i < values.length - 1) str += `\n${line.before}`
|
|
526
|
+
})
|
|
527
|
+
}
|
|
528
|
+
else if (line.type === LineType.DIRECTIVE) {
|
|
529
|
+
str += formatDirective(line)
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
str += line.after
|
|
533
|
+
|
|
534
|
+
if (line.config) {
|
|
535
|
+
line.config.forEach(format)
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
config.forEach(format)
|
|
540
|
+
|
|
541
|
+
return str
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
export default Object.assign(SSHConfig, { parse, stringify })
|
package/types/index.d.ts
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
declare enum ELine {
|
|
2
|
-
DIRECTIVE = 1,
|
|
3
|
-
COMMENT = 2,
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
interface Directive {
|
|
7
|
-
type: ELine.DIRECTIVE;
|
|
8
|
-
before: string;
|
|
9
|
-
after: string;
|
|
10
|
-
param: string;
|
|
11
|
-
separator: ' ' | '=';
|
|
12
|
-
value: string;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
interface Section extends Directive {
|
|
16
|
-
config: SSHConfig<Line>;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
interface Comment {
|
|
20
|
-
type: ELine.COMMENT;
|
|
21
|
-
content: string;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
type Line = Section | Directive | Comment;
|
|
25
|
-
|
|
26
|
-
declare class SSHConfig<T> extends Array<T> {
|
|
27
|
-
static parse(text: string): SSHConfig<Line>;
|
|
28
|
-
static stringify(config: SSHConfig<Line>): string;
|
|
29
|
-
|
|
30
|
-
static DIRECTIVE: ELine.DIRECTIVE;
|
|
31
|
-
static COMMENT: ELine.COMMENT;
|
|
32
|
-
|
|
33
|
-
toString(): string;
|
|
34
|
-
|
|
35
|
-
compute(host: string): Record<string, string>;
|
|
36
|
-
|
|
37
|
-
find<T>(this: SSHConfig<T>, predicate: (line: T, index: number, config: T[]) => boolean): T;
|
|
38
|
-
find(options: Record<string, string>): Line | Section;
|
|
39
|
-
|
|
40
|
-
remove(options: Record<string, string>): Line | Section;
|
|
41
|
-
|
|
42
|
-
append(options: Record<string, string>): SSHConfig<Line>;
|
|
43
|
-
prepend(options: Record<string, string>): SSHConfig<Line>;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export default class extends SSHConfig<Line> {}
|