From d471d9576a8aff1cfcf60e1423024f4f42ebbeed Mon Sep 17 00:00:00 2001 From: DuProcess <273172371+DuProcess@users.noreply.github.com> Date: Thu, 16 Jul 2026 22:38:52 -0400 Subject: [PATCH] Expand VS Code generated SSH config paths --- vscode-extension/extension.js | 34 ++++++++++++++++++++++++-- vscode-extension/extension.test.js | 38 ++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 2 deletions(-) diff --git a/vscode-extension/extension.js b/vscode-extension/extension.js index 668698e..f79bbb7 100644 --- a/vscode-extension/extension.js +++ b/vscode-extension/extension.js @@ -84,7 +84,11 @@ async function configureHost() { }); const sshDir = path.join(os.homedir(), '.ssh'); fs.mkdirSync(sshDir, { recursive: true }); - const generatedPath = config.get('generatedSshConfig') || path.join(sshDir, 'config.dosh'); + const generatedPath = expandHomePath( + config.get('generatedSshConfig') || path.join(sshDir, 'config.dosh'), + os.homedir(), + process.platform + ); const mainConfig = path.join(sshDir, 'config'); ensureInclude(mainConfig, sshIncludeTarget(mainConfig, generatedPath, process.platform)); upsertBlock(generatedPath, alias, block); @@ -116,6 +120,10 @@ function sshBlock(options) { } function ensureInclude(configPath, includeFile) { + const dir = path.dirname(configPath); + if (dir && dir !== '.') { + fs.mkdirSync(dir, { recursive: true }); + } const raw = fs.existsSync(configPath) ? fs.readFileSync(configPath, 'utf8') : ''; if (raw.split(/\r?\n/).some((line) => line.trim().toLowerCase() === `include ${includeFile}`.toLowerCase())) { return; @@ -136,6 +144,10 @@ function sshIncludeTarget(mainConfigPath, generatedPath, platform = process.plat } function upsertBlock(configPath, alias, block) { + const dir = path.dirname(configPath); + if (dir && dir !== '.') { + fs.mkdirSync(dir, { recursive: true }); + } const raw = fs.existsSync(configPath) ? fs.readFileSync(configPath, 'utf8') : ''; const begin = `# BEGIN DOSH ${alias}`; const end = `# END DOSH ${alias}`; @@ -173,7 +185,11 @@ async function showSshConfig() { const api = requireVscode(); const config = api.workspace.getConfiguration('dosh'); const sshDir = path.join(os.homedir(), '.ssh'); - const generatedPath = config.get('generatedSshConfig') || path.join(sshDir, 'config.dosh'); + const generatedPath = expandHomePath( + config.get('generatedSshConfig') || path.join(sshDir, 'config.dosh'), + os.homedir(), + process.platform + ); if (!fs.existsSync(generatedPath)) { api.window.showWarningMessage('No generated Dosh SSH config yet.'); return; @@ -213,6 +229,17 @@ function normalizeCase(value, platform) { return platform === 'win32' ? value.toLowerCase() : value; } +function expandHomePath(value, home = os.homedir(), platform = process.platform) { + if (value === '~') { + return home; + } + if (value.startsWith('~/') || value.startsWith('~\\')) { + const pathApi = platform === 'win32' ? path.win32 : path; + return pathApi.join(home, value.slice(2)); + } + return value; +} + function windowsCommandWord(value) { let out = '"'; let backslashes = 0; @@ -241,10 +268,13 @@ function deactivate() {} module.exports = { activate, deactivate, + expandHomePath, + ensureInclude, sshBlock, sshConfigPathWord, shellQuote, sshIncludeTarget, sshConfigWord, + upsertBlock, windowsCommandWord }; diff --git a/vscode-extension/extension.test.js b/vscode-extension/extension.test.js index 4e4fc81..27ce282 100644 --- a/vscode-extension/extension.test.js +++ b/vscode-extension/extension.test.js @@ -1,12 +1,18 @@ const test = require('node:test'); const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const os = require('node:os'); +const path = require('node:path'); const { + expandHomePath, + ensureInclude, shellQuote, sshBlock, sshConfigPathWord, sshIncludeTarget, sshConfigWord, + upsertBlock, windowsCommandWord } = require('./extension'); @@ -83,3 +89,35 @@ test('ssh include target handles Windows custom generated config paths', () => { 'C:/Users/palav/.ssh/config.dosh' ); }); + +test('home-relative generated config paths expand for POSIX and Windows', () => { + assert.equal( + expandHomePath('~/Library/Application Support/Dosh/config.dosh', '/Users/palav', 'darwin'), + '/Users/palav/Library/Application Support/Dosh/config.dosh' + ); + assert.equal( + expandHomePath('~\\.ssh\\config.dosh', 'C:\\Users\\palav', 'win32'), + 'C:\\Users\\palav\\.ssh\\config.dosh' + ); +}); + +test('config writers create missing parent directories', () => { + const root = fs.mkdtempSync(path.join(os.tmpdir(), 'dosh-vscode-')); + const mainConfig = path.join(root, 'ssh', 'config'); + const generatedConfig = path.join(root, 'generated', 'config.dosh'); + + ensureInclude(mainConfig, '../generated/config.dosh'); + upsertBlock( + generatedConfig, + 'dosh-test', + [ + '# BEGIN DOSH dosh-test', + 'Host dosh-test', + ' ProxyCommand dosh proxy-stdio test %h %p', + '# END DOSH dosh-test' + ].join('\n') + ); + + assert.equal(fs.readFileSync(mainConfig, 'utf8'), 'Include ../generated/config.dosh\n'); + assert.match(fs.readFileSync(generatedConfig, 'utf8'), /Host dosh-test/); +});