diff --git a/web/packages/teleterm/src/mainProcess/fixtures/mocks.ts b/web/packages/teleterm/src/mainProcess/fixtures/mocks.ts index 41b7471326b72..2e5290ea59b56 100644 --- a/web/packages/teleterm/src/mainProcess/fixtures/mocks.ts +++ b/web/packages/teleterm/src/mainProcess/fixtures/mocks.ts @@ -232,6 +232,7 @@ export const makeRuntimeSettings = ( insecure: true, userDataDir: '', sessionDataDir: '', + homeDir: '', tempDataDir: '', agentBinaryPath: '', binDir: '', diff --git a/web/packages/teleterm/src/mainProcess/runtimeSettings.ts b/web/packages/teleterm/src/mainProcess/runtimeSettings.ts index 843d6c76ebe15..4a8980e612cb3 100644 --- a/web/packages/teleterm/src/mainProcess/runtimeSettings.ts +++ b/web/packages/teleterm/src/mainProcess/runtimeSettings.ts @@ -57,6 +57,7 @@ const insecure = (dev && !!env.CONNECT_INSECURE); export async function getRuntimeSettings(): Promise { + const homeDir = app.getPath('home'); const userDataDir = app.getPath('userData'); const sessionDataDir = app.getPath('sessionData'); const tempDataDir = app.getPath('temp'); @@ -106,6 +107,7 @@ export async function getRuntimeSettings(): Promise { tshd, sharedProcess, tshdEvents, + homeDir, userDataDir, sessionDataDir, tempDataDir, diff --git a/web/packages/teleterm/src/mainProcess/types.ts b/web/packages/teleterm/src/mainProcess/types.ts index 439ea54ab21bd..abddc9aad3e2a 100644 --- a/web/packages/teleterm/src/mainProcess/types.ts +++ b/web/packages/teleterm/src/mainProcess/types.ts @@ -57,6 +57,8 @@ export type RuntimeSettings = { * - Starting the app in dev mode with the CONNECT_INSECURE env var. */ insecure: boolean; + /** User's home directory. */ + homeDir: string; userDataDir: string; sessionDataDir: string; tempDataDir: string; diff --git a/web/packages/teleterm/src/services/config/appConfigSchema.ts b/web/packages/teleterm/src/services/config/appConfigSchema.ts index e7600ae054886..1f5489cd2f958 100644 --- a/web/packages/teleterm/src/services/config/appConfigSchema.ts +++ b/web/packages/teleterm/src/services/config/appConfigSchema.ts @@ -50,6 +50,7 @@ export const createAppConfigSchema = (settings: RuntimeSettings) => { CUSTOM_SHELL_ID, ]; + const pathSchema = tildeExpandingPathSchema(settings.homeDir); const shortcutSchema = createKeyboardShortcutSchema(settings.platform); // `keymap.` prefix is used in `initUi.ts` in a predicate function. @@ -70,10 +71,10 @@ export const createAppConfigSchema = (settings: RuntimeSettings) => { .describe( 'Keeps the app running in the menu bar/system tray even when the main window is closed. On Linux, displaying the system tray icon may require installing shell extensions.' ), - tshHome: z - .string() - .default(settings.tshd.defaultHomeDir) - .describe('Home location for tsh configuration and data.'), + tshHome: pathSchema({ + defaultPath: settings.tshd.defaultHomeDir, + description: 'Home location for tsh configuration and data.', + }), /** * This value can be provided by the user and is unsanitized. This means that it cannot be directly interpolated * in a styled component or used in CSS, as it may inject malicious CSS code. @@ -112,12 +113,11 @@ export const createAppConfigSchema = (settings: RuntimeSettings) => { `Cannot find the shell "${iss.input}". Available options are: ${availableShellIdsWithCustom.join(', ')}. Using platform default.`, } ), - 'terminal.customShell': z - .string() - .default('') - .describe( - 'Path to the custom shell that is used when `terminal.shell` is set to `custom`. It is best to configure it through UI (right click on a terminal tab > Custom Shell…).' - ), + 'terminal.customShell': pathSchema({ + defaultPath: '', + description: + 'Path to the custom shell that is used when `terminal.shell` is set to `custom`. It is best to configure it through UI (right click on a terminal tab > Custom Shell…).', + }), 'terminal.rightClick': z .enum(['paste', 'copyPaste', 'menu']) .default(settings.platform === 'win32' ? 'copyPaste' : 'menu') @@ -375,3 +375,22 @@ z.config({ } }, }); + +/** + * Creates a Zod string schema for filesystem paths that automatically expand + * leading "~" to the provided home directory. + */ +function tildeExpandingPathSchema(homeDir: string) { + return ({ + defaultPath, + description, + }: { + defaultPath: string; + description: string; + }) => + z + .string() + .default(defaultPath) + .describe(description) + .transform(p => p.replace(/^~/, homeDir)); +} diff --git a/web/packages/teleterm/src/services/config/configService.test.ts b/web/packages/teleterm/src/services/config/configService.test.ts index a211c48dcd567..10a2f67392ef4 100644 --- a/web/packages/teleterm/src/services/config/configService.test.ts +++ b/web/packages/teleterm/src/services/config/configService.test.ts @@ -177,3 +177,18 @@ test(`enum validation`, () => { path: expect.arrayContaining(['theme']), }); }); + +test('tilde expanding in path schema', () => { + const configFile = createMockFileStorage(); + configFile.replace({ + tshHome: '~/.~tsh-dev', + }); + const configService = createConfigService({ + configFile, + jsonSchemaFile: createMockFileStorage(), + settings: makeRuntimeSettings({ homeDir: '/Users/testuser' }), + }); + expect(configService.get('tshHome')).toMatchObject({ + value: '/Users/testuser/.~tsh-dev', + }); +});