@@ -2,6 +2,7 @@ import { it, afterEach, vi, expect, describe } from "vitest";
22
33import {
44 SSHConfig ,
5+ parseCoderSshOptions ,
56 parseSshConfig ,
67 mergeSshConfigValues ,
78} from "@/remote/sshConfig" ;
@@ -11,6 +12,8 @@ import {
1112// and makes mistakes abundantly clear.
1213const sshFilePath = "/Path/To/UserHomeDir/.sshConfigDir/sshConfigFile" ;
1314const sshTempFilePathExpr = `^/Path/To/UserHomeDir/\\.sshConfigDir/\\.sshConfigFile\\.vscode-coder-tmp\\.[a-z0-9]+$` ;
15+ const managedHeader = `# This section is managed by the Coder VS Code extension.
16+ # Changes will be overwritten on the next workspace connection.` ;
1417
1518const mockFileSystem = {
1619 mkdir : vi . fn ( ) ,
@@ -40,6 +43,7 @@ it("creates a new file and adds config with empty label", async () => {
4043 } ) ;
4144
4245 const expectedOutput = `# --- START CODER VSCODE ---
46+ ${ managedHeader }
4347Host coder-vscode--*
4448 ConnectTimeout 0
4549 LogLevel ERROR
@@ -82,6 +86,7 @@ it("creates a new file and adds the config", async () => {
8286 } ) ;
8387
8488 const expectedOutput = `# --- START CODER VSCODE dev.coder.com ---
89+ ${ managedHeader }
8590Host coder-vscode.dev.coder.com--*
8691 ConnectTimeout 0
8792 LogLevel ERROR
@@ -133,6 +138,7 @@ it("adds a new coder config in an existent SSH configuration", async () => {
133138 const expectedOutput = `${ existentSSHConfig }
134139
135140# --- START CODER VSCODE dev.coder.com ---
141+ ${ managedHeader }
136142Host coder-vscode.dev.coder.com--*
137143 ConnectTimeout 0
138144 LogLevel ERROR
@@ -203,6 +209,7 @@ Host *
203209 const expectedOutput = `${ keepSSHConfig }
204210
205211# --- START CODER VSCODE dev.coder.com ---
212+ ${ managedHeader }
206213Host coder-vscode.dev-updated.coder.com--*
207214 ConnectTimeout 1
208215 LogLevel ERROR
@@ -260,6 +267,7 @@ Host coder-vscode--*
260267 const expectedOutput = `${ existentSSHConfig }
261268
262269# --- START CODER VSCODE dev.coder.com ---
270+ ${ managedHeader }
263271Host coder-vscode.dev.coder.com--*
264272 ConnectTimeout 0
265273 LogLevel ERROR
@@ -303,6 +311,7 @@ it("it does not remove a user-added block that only matches the host of an old c
303311 ForwardAgent=yes
304312
305313# --- START CODER VSCODE dev.coder.com ---
314+ ${ managedHeader }
306315Host coder-vscode.dev.coder.com--*
307316 ConnectTimeout 0
308317 LogLevel ERROR
@@ -573,6 +582,7 @@ Host donotdelete
573582 User please
574583
575584# --- START CODER VSCODE dev.coder.com ---
585+ ${ managedHeader }
576586Host coder-vscode.dev.coder.com--*
577587 ConnectTimeout 0
578588 LogLevel ERROR
@@ -637,6 +647,7 @@ it("override values", async () => {
637647 ) ;
638648
639649 const expectedOutput = `# --- START CODER VSCODE dev.coder.com ---
650+ ${ managedHeader }
640651Host coder-vscode.dev.coder.com--*
641652 Buzz baz
642653 ConnectTimeout 500
@@ -853,3 +864,93 @@ describe("mergeSshConfigValues", () => {
853864 expect ( mergeSshConfigValues ( config , overrides ) ) . toEqual ( expected ) ;
854865 } ) ;
855866} ) ;
867+
868+ describe ( "parseCoderSshOptions" , ( ) => {
869+ it ( "extracts a single ssh-option" , ( ) => {
870+ const raw = `# ------------START-CODER-----------
871+ # :ssh-option=ForwardX11=yes
872+ # ------------END-CODER------------` ;
873+ expect ( parseCoderSshOptions ( raw ) ) . toEqual ( { ForwardX11 : "yes" } ) ;
874+ } ) ;
875+
876+ it ( "extracts multiple ssh-options" , ( ) => {
877+ const raw = `# ------------START-CODER-----------
878+ # :ssh-option=ForwardX11=yes
879+ # :ssh-option=ForwardX11Trusted=yes
880+ # ------------END-CODER------------` ;
881+ expect ( parseCoderSshOptions ( raw ) ) . toEqual ( {
882+ ForwardX11 : "yes" ,
883+ ForwardX11Trusted : "yes" ,
884+ } ) ;
885+ } ) ;
886+
887+ it ( "returns empty for no CLI block" , ( ) => {
888+ const raw = `Host myhost
889+ HostName example.com
890+ User me` ;
891+ expect ( parseCoderSshOptions ( raw ) ) . toEqual ( { } ) ;
892+ } ) ;
893+
894+ it ( "returns empty for empty string" , ( ) => {
895+ expect ( parseCoderSshOptions ( "" ) ) . toEqual ( { } ) ;
896+ } ) ;
897+
898+ it ( "ignores non-ssh-option comment keys" , ( ) => {
899+ const raw = `# ------------START-CODER-----------
900+ # :wait=yes
901+ # :disable-autostart=true
902+ # :ssh-option=ForwardX11=yes
903+ # ------------END-CODER------------` ;
904+ expect ( parseCoderSshOptions ( raw ) ) . toEqual ( { ForwardX11 : "yes" } ) ;
905+ } ) ;
906+
907+ it ( "accumulates SetEnv across multiple lines" , ( ) => {
908+ const raw = `# ------------START-CODER-----------
909+ # :ssh-option=SetEnv=FOO=1
910+ # :ssh-option=SetEnv=BAR=2
911+ # ------------END-CODER------------` ;
912+ expect ( parseCoderSshOptions ( raw ) ) . toEqual ( { SetEnv : "FOO=1 BAR=2" } ) ;
913+ } ) ;
914+
915+ it ( "tolerates different dash counts in markers" , ( ) => {
916+ const raw = `# ---START-CODER---
917+ # :ssh-option=ForwardX11=yes
918+ # ---END-CODER---` ;
919+ expect ( parseCoderSshOptions ( raw ) ) . toEqual ( { ForwardX11 : "yes" } ) ;
920+ } ) ;
921+
922+ it ( "extracts only ssh-options from a full realistic config" , ( ) => {
923+ const raw = `Host personal-server
924+ HostName 10.0.0.1
925+ User admin
926+
927+ # ------------START-CODER-----------
928+ # This file is managed by coder. DO NOT EDIT.
929+ #
930+ # You should not hand-edit this file, changes may be overwritten.
931+ # For more information, see https://coder.com/docs
932+ #
933+ # :wait=yes
934+ # :disable-autostart=true
935+ # :ssh-option=ForwardX11=yes
936+ # :ssh-option=ForwardX11Trusted=yes
937+
938+ Host coder.mydeployment--*
939+ ConnectTimeout 0
940+ ForwardX11 yes
941+ ForwardX11Trusted yes
942+ StrictHostKeyChecking no
943+ UserKnownHostsFile /dev/null
944+ LogLevel ERROR
945+ ProxyCommand /usr/bin/coder ssh --stdio --ssh-host-prefix coder.mydeployment-- %h
946+ # ------------END-CODER------------
947+
948+ Host work-server
949+ HostName 10.0.0.2
950+ User work` ;
951+ expect ( parseCoderSshOptions ( raw ) ) . toEqual ( {
952+ ForwardX11 : "yes" ,
953+ ForwardX11Trusted : "yes" ,
954+ } ) ;
955+ } ) ;
956+ } ) ;
0 commit comments