@@ -287,6 +287,126 @@ describe("ssh config", () => {
287287 } ) . pipe ( Effect . provide ( NodeServices . layer ) , Effect . scoped ) ,
288288 ) ;
289289
290+ it . effect ( "preserves first-value precedence around unresolved Match conditions" , ( ) =>
291+ Effect . gen ( function * ( ) {
292+ const fs = yield * FileSystem . FileSystem ;
293+ const path = yield * Path . Path ;
294+ const fixtures = [
295+ {
296+ config :
297+ 'Match exec "test -f ~/.work"\n HostName conditional.example.test\nHost work\n HostName work.example.test\n' ,
298+ included : null ,
299+ knownHosts : [ "conditional.example.test" , "work.example.test" ] ,
300+ expected : [
301+ [ "conditional.example.test" , "conditional.example.test" ] ,
302+ [ "work" , "work" ] ,
303+ [ "work.example.test" , "work.example.test" ] ,
304+ ] ,
305+ } ,
306+ {
307+ config :
308+ 'Match exec "test -f ~/.work"\n Include guarded.conf\nHost work\n HostName work.example.test\n' ,
309+ included : "Host work\n HostName included.example.test\n" ,
310+ knownHosts : [ "included.example.test" , "work.example.test" ] ,
311+ expected : [
312+ [ "included.example.test" , "included.example.test" ] ,
313+ [ "work" , "work" ] ,
314+ [ "work.example.test" , "work.example.test" ] ,
315+ ] ,
316+ } ,
317+ {
318+ config :
319+ 'Host work\n HostName work.example.test\nMatch exec "test -f ~/.work"\n HostName conditional.example.test\n' ,
320+ included : null ,
321+ knownHosts : [ "conditional.example.test" , "work.example.test" ] ,
322+ expected : [
323+ [ "conditional.example.test" , "conditional.example.test" ] ,
324+ [ "work" , "work.example.test" ] ,
325+ ] ,
326+ } ,
327+ {
328+ config : "Host other\n Include guarded.conf\nHost work\n HostName work.example.test\n" ,
329+ included : 'Match exec "test -f ~/.other"\n HostName unrelated.example.test\n' ,
330+ knownHosts : [ "unrelated.example.test" , "work.example.test" ] ,
331+ expected : [
332+ [ "other" , "other" ] ,
333+ [ "unrelated.example.test" , "unrelated.example.test" ] ,
334+ [ "work" , "work.example.test" ] ,
335+ ] ,
336+ } ,
337+ ] ;
338+
339+ for ( const fixture of fixtures ) {
340+ const homeDir = yield * makeTempHomeDir ( ) ;
341+ const sshDir = path . join ( homeDir , ".ssh" ) ;
342+ yield * fs . makeDirectory ( sshDir ) ;
343+ yield * fs . writeFileString ( path . join ( sshDir , "config" ) , fixture . config ) ;
344+ if ( fixture . included !== null ) {
345+ yield * fs . writeFileString ( path . join ( sshDir , "guarded.conf" ) , fixture . included ) ;
346+ }
347+ yield * fs . writeFileString (
348+ path . join ( sshDir , "known_hosts" ) ,
349+ fixture . knownHosts . map ( ( hostname ) => `${ hostname } ssh-ed25519 AAAA` ) . join ( "\n" ) ,
350+ ) ;
351+
352+ const hosts = yield * discoverSshHosts ( { homeDir } ) ;
353+ assert . deepEqual (
354+ hosts . map ( ( { alias, hostname } ) => [ alias , hostname ] ) ,
355+ fixture . expected ,
356+ ) ;
357+ }
358+ } ) . pipe ( Effect . provide ( NodeServices . layer ) , Effect . scoped ) ,
359+ ) ;
360+
361+ it . effect ( "matches quoted Match originalhost patterns" , ( ) =>
362+ Effect . gen ( function * ( ) {
363+ const fs = yield * FileSystem . FileSystem ;
364+ const path = yield * Path . Path ;
365+ for ( const fixture of [
366+ {
367+ match : 'Match originalhost "work"' ,
368+ matchHostname : "work.example.test" ,
369+ knownHosts : [ "work.example.test" ] ,
370+ expected : [ [ "work" , "work.example.test" ] ] ,
371+ } ,
372+ {
373+ match : "Match originalhost 'other,work'" ,
374+ matchHostname : "work.example.test" ,
375+ knownHosts : [ "work.example.test" ] ,
376+ expected : [ [ "work" , "work.example.test" ] ] ,
377+ } ,
378+ {
379+ match : 'Match originalhost "other"' ,
380+ matchHostname : "other.example.test" ,
381+ knownHosts : [ "other.example.test" , "work.example.test" ] ,
382+ expected : [
383+ [ "other.example.test" , "other.example.test" ] ,
384+ [ "work" , "work" ] ,
385+ [ "work.example.test" , "work.example.test" ] ,
386+ ] ,
387+ } ,
388+ ] ) {
389+ const homeDir = yield * makeTempHomeDir ( ) ;
390+ const sshDir = path . join ( homeDir , ".ssh" ) ;
391+ yield * fs . makeDirectory ( sshDir ) ;
392+ yield * fs . writeFileString (
393+ path . join ( sshDir , "config" ) ,
394+ `${ fixture . match } \n HostName ${ fixture . matchHostname } \nHost work\n` ,
395+ ) ;
396+ yield * fs . writeFileString (
397+ path . join ( sshDir , "known_hosts" ) ,
398+ fixture . knownHosts . map ( ( hostname ) => `${ hostname } ssh-ed25519 AAAA` ) . join ( "\n" ) ,
399+ ) ;
400+
401+ const hosts = yield * discoverSshHosts ( { homeDir } ) ;
402+ assert . deepEqual (
403+ hosts . map ( ( { alias, hostname } ) => [ alias , hostname ] ) ,
404+ fixture . expected ,
405+ ) ;
406+ }
407+ } ) . pipe ( Effect . provide ( NodeServices . layer ) , Effect . scoped ) ,
408+ ) ;
409+
290410 it . effect ( "bounds nested Includes and keeps reading the outer file" , ( ) =>
291411 Effect . gen ( function * ( ) {
292412 const fs = yield * FileSystem . FileSystem ;
0 commit comments