@@ -170,8 +170,8 @@ function normalizeKnownHostsHostname(rawHost: string): string {
170170 return firstColonIndex === lastColonIndex ? rawHost . slice ( 0 , lastColonIndex ) : rawHost ;
171171}
172172
173- export function parseKnownHostsHostnames ( raw : string ) : ReadonlyArray < string > {
174- const hostnames = new Set < string > ( ) ;
173+ function parseKnownHostsTargets ( raw : string ) : ReadonlyArray < DesktopDiscoveredSshHost > {
174+ const targets = new Map < string , DesktopDiscoveredSshHost > ( ) ;
175175
176176 for ( const line of raw . split ( / \r ? \n / u) ) {
177177 const trimmed = line . trim ( ) ;
@@ -192,19 +192,29 @@ export function parseKnownHostsHostnames(raw: string): ReadonlyArray<string> {
192192 if ( host . length === 0 || hasSshPattern ( host ) ) {
193193 continue ;
194194 }
195- hostnames . add ( host ) ;
195+ const portText =
196+ / ^ \[ [ ^ \] ] + \] : ( \d + ) $ / . exec ( rawHost ) ?. [ 1 ] ?? / ^ [ ^ : ] + : ( \d + ) $ / . exec ( rawHost ) ?. [ 1 ] ;
197+ const port = portText === undefined ? null : Number ( portText ) ;
198+ if ( port !== null && ( ! Number . isSafeInteger ( port ) || port < 1 || port > 65535 ) ) continue ;
199+ const alias =
200+ port === null ? host : `ssh://${ host . includes ( ":" ) ? `[${ host } ]` : host } :${ port } ` ;
201+ targets . set ( alias , { alias, hostname : host , username : null , port, source : "known-hosts" } ) ;
196202 }
197203 }
198204
199- return [ ...hostnames ] . toSorted ( ( left , right ) => left . localeCompare ( right ) ) ;
205+ return [ ...targets . values ( ) ] . toSorted ( ( left , right ) => left . alias . localeCompare ( right . alias ) ) ;
206+ }
207+
208+ export function parseKnownHostsHostnames ( raw : string ) : ReadonlyArray < string > {
209+ return [ ...new Set ( parseKnownHostsTargets ( raw ) . map ( ( target ) => target . hostname ) ) ] . toSorted (
210+ ( left , right ) => left . localeCompare ( right ) ,
211+ ) ;
200212}
201213
202- const readKnownHostsHostnames = Effect . fnUntraced ( function * ( filePath : string ) {
214+ const readKnownHostsTargets = Effect . fnUntraced ( function * ( filePath : string ) {
203215 const fs = yield * FileSystem . FileSystem ;
204- if ( ! ( yield * fs . exists ( filePath ) ) ) {
205- return NO_HOSTS ;
206- }
207- return parseKnownHostsHostnames ( yield * fs . readFileString ( filePath ) ) ;
216+ if ( ! ( yield * fs . exists ( filePath ) ) ) return [ ] ;
217+ return parseKnownHostsTargets ( yield * fs . readFileString ( filePath ) ) ;
208218} ) ;
209219
210220export const discoverSshHosts = Effect . fnUntraced (
@@ -229,7 +239,7 @@ export const discoverSshHosts = Effect.fnUntraced(
229239 new Set < string > ( ) ,
230240 homeDir ,
231241 ) ;
232- const knownHosts = yield * readKnownHostsHostnames ( path . join ( sshDirectory , "known_hosts" ) ) ;
242+ const knownHosts = yield * readKnownHostsTargets ( path . join ( sshDirectory , "known_hosts" ) ) ;
233243 const discovered = new Map < string , DesktopDiscoveredSshHost > ( ) ;
234244
235245 for ( const alias of configAliases ) {
@@ -242,17 +252,8 @@ export const discoverSshHosts = Effect.fnUntraced(
242252 } ) ;
243253 }
244254
245- for ( const hostname of knownHosts ) {
246- if ( discovered . has ( hostname ) ) {
247- continue ;
248- }
249- discovered . set ( hostname , {
250- alias : hostname ,
251- hostname,
252- username : null ,
253- port : null ,
254- source : "known-hosts" ,
255- } ) ;
255+ for ( const target of knownHosts ) {
256+ if ( ! discovered . has ( target . alias ) ) discovered . set ( target . alias , target ) ;
256257 }
257258
258259 return [ ...discovered . values ( ) ] . toSorted ( ( left , right ) =>
0 commit comments