diff --git a/src/hosted/config.ts b/src/hosted/config.ts index 80b7716ce..2856cdfb7 100644 --- a/src/hosted/config.ts +++ b/src/hosted/config.ts @@ -11,7 +11,7 @@ export interface HostedManifestCache { export type LocalBrowserConfig = | { kind: 'cloak' } - | { kind: 'chrome'; executablePath: string; syncToChrome?: boolean } + | { kind: 'chrome'; executablePath: string; chromeProfile?: string; syncToChrome?: boolean } | { kind: 'slab' } | { kind: 'custom'; executablePath: string }; @@ -208,10 +208,23 @@ function readCredentialBackend(value: unknown): HostedCredentialBackend | undefi function readLocalBrowser(value: unknown): LocalBrowserConfig { if (value && typeof value === 'object') { - const browser = value as { kind?: unknown; executablePath?: unknown; syncToChrome?: unknown }; + const browser = value as { + kind?: unknown; + executablePath?: unknown; + chromeProfile?: unknown; + syncToChrome?: unknown; + }; if (browser.kind === 'cloak' || browser.kind === 'slab') return { kind: browser.kind }; if (browser.kind === 'chrome' && typeof browser.executablePath === 'string' && path.isAbsolute(browser.executablePath)) { - return { kind: 'chrome', executablePath: browser.executablePath, ...(browser.syncToChrome === true ? { syncToChrome: true } : {}) }; + const chromeProfile = typeof browser.chromeProfile === 'string' && browser.chromeProfile.trim() + ? browser.chromeProfile.trim() + : undefined; + return { + kind: 'chrome', + executablePath: browser.executablePath, + ...(chromeProfile ? { chromeProfile } : {}), + ...(browser.syncToChrome === true ? { syncToChrome: true } : {}), + }; } if (browser.kind === 'custom' && typeof browser.executablePath === 'string' && path.isAbsolute(browser.executablePath)) { return { kind: 'custom', executablePath: browser.executablePath }; diff --git a/src/hosted/setup.test.ts b/src/hosted/setup.test.ts index 3da70c4ea..d2807228b 100644 --- a/src/hosted/setup.test.ts +++ b/src/hosted/setup.test.ts @@ -6,7 +6,7 @@ import { Writable } from 'node:stream'; import { fileURLToPath } from 'node:url'; import { afterEach, describe, expect, it, vi } from 'vitest'; import { getConfigPath, makeLocalConfig, saveWebcmdConfig } from './config.js'; -import { runHostedSetup } from './setup.js'; +import { getSetupStatus, runHostedSetup } from './setup.js'; import type { SlabSetupStatus } from '../slab/status.js'; let tempDir: string | undefined; @@ -188,6 +188,31 @@ describe('webcmd setup', () => { }); }); + it('persists and reports the selected Chrome profile in setup status', async () => { + tempDir = await mkdtemp(join(tmpdir(), 'webcmd-setup-chrome-profile-status-')); + const env = { WEBCMD_CONFIG_DIR: tempDir } as NodeJS.ProcessEnv; + const executablePath = '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'; + + await expect(runHostedSetup({ + env, + argv: ['--browser', 'chrome', '--chrome-profile', 'Profile 15'], + isTTY: false, + resolveGoogleChromeExecutable: async () => executablePath, + fetchDaemonStatus: async () => null, + write: () => undefined, + })).resolves.toBe(0); + + expect(JSON.parse(await readFile(getConfigPath({ env }), 'utf8'))).toMatchObject({ + mode: 'local', + browser: { kind: 'chrome', executablePath, chromeProfile: 'Profile 15' }, + }); + expect(await getSetupStatus({ env })).toMatchObject({ + configured: true, + mode: 'local', + browser: { kind: 'chrome', executablePath, chromeProfile: 'Profile 15' }, + }); + }); + it('explains how to install Google Chrome when it is unavailable', async () => { tempDir = await mkdtemp(join(tmpdir(), 'webcmd-setup-chrome-missing-')); const messages: string[] = []; diff --git a/src/hosted/setup.ts b/src/hosted/setup.ts index 65a5c8f6b..f44b3c505 100644 --- a/src/hosted/setup.ts +++ b/src/hosted/setup.ts @@ -146,9 +146,11 @@ export async function runHostedSetup(io: SetupIo = {}): Promise { const before = await (io.fetchDaemonStatus ?? fetchDaemonStatus)(); try { const selected = await validateLocalBrowser(browser, io, chromeDiscovery, syncToChrome); + const chromeProfile = selected.kind === 'chrome' ? parsed.chromeProfile?.trim() : undefined; + const persistedSelected = selected.kind === 'chrome' && chromeProfile ? { ...selected, chromeProfile } : selected; if (selected.kind === 'chrome') await maybeImportChromeCookies(parsed, io, interactive, ask, write); - (io.saveConfig ?? saveWebcmdConfig)(makeLocalConfig(io.now?.() ?? new Date(), selected), io); - if (before) await restartConfiguredDaemon(selected, io); + (io.saveConfig ?? saveWebcmdConfig)(makeLocalConfig(io.now?.() ?? new Date(), persistedSelected), io); + if (before) await restartConfiguredDaemon(persistedSelected, io); } catch (err) { const message = err instanceof Error ? err.message : String(err); await write(`Local browser setup failed: ${message}\n`);