mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 00:02:31 +00:00
test(e2e): cover tab-bar agent launches on Windows and WSL (#16110)
* test(e2e): gate the tab-bar agent launcher on Windows shells and WSL The `+` menu agent launcher had no golden coverage in the Windows lane, so a Windows-only break anywhere in its chain (detection row, startup-plan build, tab create, PTY spawn, startup-command injection) could ship unnoticed. Adds a golden spec that launches a stub agent from the menu and asserts the agent's own banner reached the pane — a tab that spawned a bare shell instead is indistinguishable at the store/tab layer. Runs two agents everywhere, and on Windows also PowerShell, cmd, Git Bash and a WSL project runtime. * test(e2e): track WSL stub agent staging state for precise cleanup Refactor `stageWslGoldenStubAgent` to track which artifacts it creates during setup, then only remove those artifacts during cleanup. This prevents the test from destructively removing pre-existing symlinks or state from previous runs, improving test isolation and idempotency. * test(e2e): track WSL stub agent staging state for precise cleanup - Back up and restore pre-existing stub agents to avoid destroying them - Simplify verbose test comments to match project style guidelines * test(e2e): serialize WSL stub agent setup with distributed lock - Add mkdir-based lock to prevent concurrent staging invocations - Reclaim stale locks after 10 minutes to recover from crashes - Track lock ownership in stage state for safe cleanup * test(e2e): track WSL stub agent staging state for precise cleanup Track which stubs this test helper stages by writing a marker file, then only remove stubs during stale-lock recovery if we created them. Prevents cleanup from removing stubs left by other processes.
This commit is contained in:
@@ -74,6 +74,7 @@ jobs:
|
||||
xvfb-run --auto-servernum env SKIP_BUILD=1 ORCA_E2E_FORWARD_APP_LOGS=1 pnpm run test:e2e:terminal-rendering-golden
|
||||
xvfb-run --auto-servernum env SKIP_BUILD=1 ORCA_E2E_FORWARD_APP_LOGS=1 pnpm run --if-present test:e2e:posix-profile-index-golden
|
||||
xvfb-run --auto-servernum env SKIP_BUILD=1 ORCA_E2E_FORWARD_APP_LOGS=1 pnpm run --if-present test:e2e:agent-tui-golden
|
||||
xvfb-run --auto-servernum env SKIP_BUILD=1 ORCA_E2E_FORWARD_APP_LOGS=1 pnpm run --if-present test:e2e:tab-bar-agent-launch-golden
|
||||
xvfb-run --auto-servernum env SKIP_BUILD=1 ORCA_E2E_FORWARD_APP_LOGS=1 pnpm run --if-present test:e2e:source-control-golden
|
||||
|
||||
- name: Run golden E2E tests on macOS
|
||||
@@ -87,6 +88,7 @@ jobs:
|
||||
env SKIP_BUILD=1 ORCA_E2E_FORWARD_APP_LOGS=1 pnpm run test:e2e:terminal-rendering-golden
|
||||
env SKIP_BUILD=1 ORCA_E2E_FORWARD_APP_LOGS=1 pnpm run --if-present test:e2e:posix-profile-index-golden
|
||||
env SKIP_BUILD=1 ORCA_E2E_FORWARD_APP_LOGS=1 pnpm run --if-present test:e2e:agent-tui-golden
|
||||
env SKIP_BUILD=1 ORCA_E2E_FORWARD_APP_LOGS=1 pnpm run --if-present test:e2e:tab-bar-agent-launch-golden
|
||||
env SKIP_BUILD=1 ORCA_E2E_FORWARD_APP_LOGS=1 pnpm run --if-present test:e2e:source-control-golden
|
||||
|
||||
- name: Run golden E2E tests on Windows
|
||||
@@ -97,6 +99,7 @@ jobs:
|
||||
$env:ORCA_E2E_FORWARD_APP_LOGS = '1'
|
||||
pnpm run --if-present test:e2e:workspace-session-golden
|
||||
pnpm run --if-present test:e2e:windows-fresh-startup-golden
|
||||
pnpm run --if-present test:e2e:tab-bar-agent-launch-golden
|
||||
if (Test-Path tests/e2e/golden-fresh-profile-terminal.spec.ts) {
|
||||
pnpm run test:e2e -- tests/e2e/golden-fresh-profile-terminal.spec.ts tests/e2e/golden-shell-command.spec.ts
|
||||
}
|
||||
|
||||
@@ -100,6 +100,7 @@
|
||||
"test:e2e:posix-profile-index-golden": "pnpm run ensure:electron-runtime && npx playwright test tests/e2e/golden-posix-fresh-startup.spec.ts tests/e2e/golden-posix-profile-index-fsync.spec.ts --grep @posix-profile-index-golden --config tests/playwright.config.ts --project electron-headless --workers=1",
|
||||
"test:e2e:windows-fresh-startup-golden": "pnpm run ensure:electron-runtime && npx playwright test tests/e2e/golden-windows-fresh-startup.spec.ts --grep @windows-fresh-startup-golden --config tests/playwright.config.ts --project electron-headless --workers=1",
|
||||
"test:e2e:agent-tui-golden": "pnpm run ensure:electron-runtime && npx playwright test tests/e2e/golden-agent-tui-launch.spec.ts tests/e2e/golden-shell-after-agent-exit.spec.ts --config tests/playwright.config.ts --project electron-headless --workers=1",
|
||||
"test:e2e:tab-bar-agent-launch-golden": "pnpm run ensure:electron-runtime && npx playwright test tests/e2e/golden-tab-bar-agent-launch.spec.ts --config tests/playwright.config.ts --project electron-headless --workers=1",
|
||||
"test:e2e:terminal-rendering-release-evidence": "pnpm run ensure:electron-runtime && npx playwright test tests/e2e/terminal-opencode-emoji-table-rendering.spec.ts tests/e2e/terminal-long-table-scroll-restore.spec.ts --config tests/playwright.config.ts --project electron-headless --workers=2",
|
||||
"test:e2e:terminal-perf": "pnpm run ensure:electron-runtime && npx playwright test tests/e2e/terminal-typing-latency.spec.ts tests/e2e/terminal-foreground-redraw-freeze.spec.ts tests/e2e/terminal-output-scheduler.spec.ts tests/e2e/terminal-hidden-tui-visual-restore.spec.ts tests/e2e/artificial-opencode-terminal-load.spec.ts --config tests/playwright.config.ts --project electron-headless --workers=2",
|
||||
"test:e2e:terminal-perf:scale": "pnpm run ensure:electron-runtime && node config/scripts/run-terminal-scale-perf-e2e.mjs",
|
||||
|
||||
Executable
+2
@@ -0,0 +1,2 @@
|
||||
#!/bin/sh
|
||||
exec "$(dirname "$0")/golden-stub-agent" "$@"
|
||||
@@ -0,0 +1,2 @@
|
||||
@echo off
|
||||
call "%~dp0golden-stub-agent.cmd" %*
|
||||
@@ -0,0 +1,90 @@
|
||||
import type { Page } from '@stablyai/playwright-test'
|
||||
import { expect, test } from './helpers/orca-app'
|
||||
import {
|
||||
configureGoldenStubAgent,
|
||||
getGoldenStubAgentLaunchEnv,
|
||||
GOLDEN_STUB_AGENTS,
|
||||
GOLDEN_STUB_READY_MARKER,
|
||||
launchGoldenStubAgentFromNewTab
|
||||
} from './helpers/golden-stub-agent'
|
||||
import {
|
||||
getFirstWslDistro,
|
||||
removeWslGoldenStubAgent,
|
||||
stageWslGoldenStubAgent,
|
||||
useWslRuntimeForActiveProject
|
||||
} from './helpers/wsl-golden-stub-agent'
|
||||
import { ensureTerminalVisible, waitForActiveWorktree, waitForSessionReady } from './helpers/store'
|
||||
import { getTerminalContent } from './helpers/terminal'
|
||||
import type { BuiltInWindowsTerminalShell } from '../../src/shared/windows-terminal-shell'
|
||||
|
||||
// Covers the Windows-only tab-bar launch path that the default-shell test misses.
|
||||
|
||||
test.use({ launchEnv: getGoldenStubAgentLaunchEnv() })
|
||||
|
||||
const WINDOWS_SHELLS: readonly BuiltInWindowsTerminalShell[] = [
|
||||
'powershell.exe',
|
||||
'cmd.exe',
|
||||
'git-bash'
|
||||
]
|
||||
|
||||
async function openWorkspaceTerminal(page: Page): Promise<void> {
|
||||
await waitForSessionReady(page)
|
||||
await waitForActiveWorktree(page)
|
||||
await ensureTerminalVisible(page)
|
||||
}
|
||||
|
||||
for (const { id, menuItemName } of GOLDEN_STUB_AGENTS) {
|
||||
test(`tab-bar + menu launches ${id} into a live TUI @tab-bar-agent-launch-golden`, async ({
|
||||
orcaPage
|
||||
}) => {
|
||||
await openWorkspaceTerminal(orcaPage)
|
||||
await configureGoldenStubAgent(orcaPage, { agent: id })
|
||||
await launchGoldenStubAgentFromNewTab(orcaPage, menuItemName)
|
||||
|
||||
const activeTab = orcaPage.locator('[data-testid="sortable-tab"][data-active="true"]')
|
||||
await expect(activeTab).toHaveAttribute('data-tab-title', /Golden Stub Agent|Codex|Claude/i)
|
||||
// The marker distinguishes an agent launch from an identical bare-shell tab.
|
||||
expect(await getTerminalContent(orcaPage)).toContain(GOLDEN_STUB_READY_MARKER)
|
||||
})
|
||||
}
|
||||
|
||||
// Suite-level skipping avoids launching Electron for each unsupported case.
|
||||
test.describe('Windows runtimes', () => {
|
||||
test.skip(process.platform !== 'win32', 'Windows agent launch matrix is Windows-only')
|
||||
|
||||
for (const shell of WINDOWS_SHELLS) {
|
||||
test(`tab-bar + menu launches an agent under ${shell} @tab-bar-agent-launch-golden`, async ({
|
||||
orcaPage
|
||||
}) => {
|
||||
await openWorkspaceTerminal(orcaPage)
|
||||
// Each shell family requires different launch-command quoting.
|
||||
await configureGoldenStubAgent(orcaPage, { agent: 'codex', windowsShell: shell })
|
||||
await launchGoldenStubAgentFromNewTab(orcaPage)
|
||||
|
||||
expect(await getTerminalContent(orcaPage)).toContain(GOLDEN_STUB_READY_MARKER)
|
||||
})
|
||||
}
|
||||
|
||||
test('tab-bar + menu launches an agent inside WSL @tab-bar-agent-launch-golden', async ({
|
||||
orcaPage
|
||||
}) => {
|
||||
await openWorkspaceTerminal(orcaPage)
|
||||
|
||||
const distro = await getFirstWslDistro(orcaPage)
|
||||
test.skip(!distro, 'No WSL distro is available on this Windows host')
|
||||
const stage = stageWslGoldenStubAgent(distro!)
|
||||
test.skip(!stage, 'WSL distro would not accept the staged stub agent')
|
||||
|
||||
try {
|
||||
// WSL must retarget both agent detection and the PTY.
|
||||
await useWslRuntimeForActiveProject(orcaPage, distro!)
|
||||
await configureGoldenStubAgent(orcaPage, { agent: 'codex' })
|
||||
await launchGoldenStubAgentFromNewTab(orcaPage)
|
||||
|
||||
// The distro-only marker proves the agent ran in WSL.
|
||||
expect(await getTerminalContent(orcaPage)).toContain(GOLDEN_STUB_READY_MARKER)
|
||||
} finally {
|
||||
removeWslGoldenStubAgent(distro!, stage!)
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -2,10 +2,17 @@ import path from 'node:path'
|
||||
import type { Page } from '@stablyai/playwright-test'
|
||||
import { expect } from '@stablyai/playwright-test'
|
||||
import { focusActiveTerminalInput, waitForTerminalOutput } from './terminal'
|
||||
import type { BuiltInWindowsTerminalShell } from '../../../src/shared/windows-terminal-shell'
|
||||
|
||||
export const GOLDEN_STUB_READY_MARKER = 'GOLDEN_STUB_AGENT_READY'
|
||||
export const GOLDEN_STUB_EXIT_MARKER = 'GOLDEN_STUB_AGENT_EXITED'
|
||||
|
||||
/** Agents exposed by the fixture directory for tab-bar detection. */
|
||||
export const GOLDEN_STUB_AGENTS = [
|
||||
{ id: 'codex', menuItemName: /^Codex(?:\s|$)/i },
|
||||
{ id: 'claude', menuItemName: /^Claude(?:\s|$)/i }
|
||||
] as const
|
||||
|
||||
const fixtureDir = path.join(process.cwd(), 'tests', 'e2e', 'fixtures', 'golden-stub-agent')
|
||||
|
||||
export function getGoldenStubAgentLaunchEnv(): NodeJS.ProcessEnv {
|
||||
@@ -15,23 +22,38 @@ export function getGoldenStubAgentLaunchEnv(): NodeJS.ProcessEnv {
|
||||
}
|
||||
}
|
||||
|
||||
export async function configureGoldenStubAgent(page: Page): Promise<void> {
|
||||
await page.evaluate(async () => {
|
||||
const store = window.__store
|
||||
if (!store) {
|
||||
throw new Error('Orca store is unavailable')
|
||||
}
|
||||
await store.getState().updateSettings({
|
||||
defaultTuiAgent: 'codex',
|
||||
agentCmdOverrides: { codex: 'golden-stub-agent' },
|
||||
agentDefaultArgs: { codex: '' }
|
||||
})
|
||||
})
|
||||
export async function configureGoldenStubAgent(
|
||||
page: Page,
|
||||
options: {
|
||||
agent?: (typeof GOLDEN_STUB_AGENTS)[number]['id']
|
||||
/** Windows default shell the launch command must survive; ignored elsewhere. */
|
||||
windowsShell?: BuiltInWindowsTerminalShell
|
||||
} = {}
|
||||
): Promise<void> {
|
||||
const agent = options.agent ?? 'codex'
|
||||
await page.evaluate(
|
||||
async ({ agent, windowsShell }) => {
|
||||
const store = window.__store
|
||||
if (!store) {
|
||||
throw new Error('Orca store is unavailable')
|
||||
}
|
||||
await store.getState().updateSettings({
|
||||
defaultTuiAgent: agent,
|
||||
agentCmdOverrides: { [agent]: 'golden-stub-agent' },
|
||||
agentDefaultArgs: { [agent]: '' },
|
||||
...(windowsShell ? { terminalWindowsShell: windowsShell } : {})
|
||||
})
|
||||
},
|
||||
{ agent, windowsShell: options.windowsShell ?? null }
|
||||
)
|
||||
}
|
||||
|
||||
export async function launchGoldenStubAgentFromNewTab(page: Page): Promise<void> {
|
||||
export async function launchGoldenStubAgentFromNewTab(
|
||||
page: Page,
|
||||
menuItemName: RegExp = /^Codex(?:\s|$)/i
|
||||
): Promise<void> {
|
||||
await page.getByRole('button', { name: 'New tab' }).click({ force: true })
|
||||
const launchOption = page.getByRole('menuitem', { name: /^Codex(?:\s|$)/i }).first()
|
||||
const launchOption = page.getByRole('menuitem', { name: menuItemName }).first()
|
||||
await expect(launchOption).toBeVisible({ timeout: 15_000 })
|
||||
await launchOption.click({ force: true })
|
||||
await focusActiveTerminalInput(page)
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
import { execFileSync } from 'node:child_process'
|
||||
import type { Page } from '@stablyai/playwright-test'
|
||||
import { buildWslExecArgs } from '../../../src/shared/wsl-login-shell-command'
|
||||
|
||||
/** A WSL-only path makes the stub marker proof that the pane ran in the distro. */
|
||||
const WSL_STUB_PATH = '/usr/local/bin/golden-stub-agent'
|
||||
const WSL_STUB_AGENT_LINK = '/usr/local/bin/codex'
|
||||
const WSL_STUB_BACKUP_PATH = '/usr/local/bin/golden-stub-agent.orca-e2e-backup'
|
||||
/** mkdir is atomic in the distro, so the lock dir serializes overlapping invocations. */
|
||||
const WSL_STUB_LOCK_PATH = '/usr/local/bin/golden-stub-agent.orca-e2e-lock'
|
||||
const WSL_STUB_LINK_MARKER = `${WSL_STUB_LOCK_PATH}/created-codex-link`
|
||||
const WSL_STUB_STAGED_MARKER = `${WSL_STUB_LOCK_PATH}/staged-stub`
|
||||
const WSL_STUB_LOCK_STALE_MINUTES = 10
|
||||
const WSL_STUB_LOCK_WAIT_SECONDS = 60
|
||||
|
||||
// Undoes a lock holder that died mid-run, so its leftovers cannot poison later invocations.
|
||||
const RECLAIM_STALE_LOCK_SCRIPT =
|
||||
`if [ -e ${WSL_STUB_LINK_MARKER} ]; then rm -f ${WSL_STUB_AGENT_LINK}; fi; ` +
|
||||
`if [ -e ${WSL_STUB_STAGED_MARKER} ]; then rm -f ${WSL_STUB_PATH}; fi; ` +
|
||||
`if [ -e ${WSL_STUB_BACKUP_PATH} ] || [ -L ${WSL_STUB_BACKUP_PATH} ]; then ` +
|
||||
`mv ${WSL_STUB_BACKUP_PATH} ${WSL_STUB_PATH}; fi; ` +
|
||||
`rm -rf ${WSL_STUB_LOCK_PATH}`
|
||||
|
||||
const ACQUIRE_LOCK_SCRIPT =
|
||||
`mkdir -p /usr/local/bin || exit 1; i=0; ` +
|
||||
`while [ $i -lt ${WSL_STUB_LOCK_WAIT_SECONDS} ]; do ` +
|
||||
`if mkdir ${WSL_STUB_LOCK_PATH} 2>/dev/null; then printf acquired; exit 0; fi; ` +
|
||||
`if [ -n "$(find ${WSL_STUB_LOCK_PATH} -maxdepth 0 -mmin +${WSL_STUB_LOCK_STALE_MINUTES} ` +
|
||||
`2>/dev/null)" ]; then ${RECLAIM_STALE_LOCK_SCRIPT}; else sleep 1; fi; i=$((i+1)); ` +
|
||||
`done; printf timeout`
|
||||
|
||||
// Keep the cross-boundary script newline-free to avoid Windows argv-encoding surprises.
|
||||
// Moving the entry avoids following and overwriting a pre-existing symlink.
|
||||
const BACKUP_EXISTING_STUB_SCRIPT =
|
||||
`mkdir -p /usr/local/bin && ` +
|
||||
`if [ -e ${WSL_STUB_BACKUP_PATH} ] || [ -L ${WSL_STUB_BACKUP_PATH} ]; then exit 1; fi && ` +
|
||||
`if [ -e ${WSL_STUB_PATH} ] || [ -L ${WSL_STUB_PATH} ]; then ` +
|
||||
`mv ${WSL_STUB_PATH} ${WSL_STUB_BACKUP_PATH} && ` +
|
||||
`printf backed-up; else printf none; fi`
|
||||
|
||||
// The marker is written first so stale-lock recovery only removes a stub this helper wrote.
|
||||
const STAGE_SCRIPT =
|
||||
`mkdir -p /usr/local/bin && : > ${WSL_STUB_STAGED_MARKER} && ` +
|
||||
`printf '#!/bin/sh\\necho GOLDEN_STUB_AGENT_READY\\nexec sleep 3600\\n' > ${WSL_STUB_PATH} && ` +
|
||||
`chmod 0755 ${WSL_STUB_PATH}`
|
||||
|
||||
// The marker is written before the link so a crashed run over-reports rather than leaks a link.
|
||||
const STAGE_CODEX_LINK_IF_MISSING_SCRIPT =
|
||||
`if [ -e ${WSL_STUB_AGENT_LINK} ] || [ -L ${WSL_STUB_AGENT_LINK} ]; then ` +
|
||||
`printf existing; else : > ${WSL_STUB_LINK_MARKER} && ` +
|
||||
`ln -s ${WSL_STUB_PATH} ${WSL_STUB_AGENT_LINK} && printf created; fi`
|
||||
|
||||
// `;` between steps so the lock is released even when a restore step fails.
|
||||
function buildRestoreScript(stage: WslGoldenStubAgentStage): string {
|
||||
const steps: string[] = []
|
||||
if (stage.ownsStubPath) {
|
||||
const removed = stage.createdCodexLink
|
||||
? `${WSL_STUB_AGENT_LINK} ${WSL_STUB_PATH}`
|
||||
: WSL_STUB_PATH
|
||||
steps.push(`rm -f ${removed}`)
|
||||
if (stage.backedUpStub) {
|
||||
steps.push(`mv ${WSL_STUB_BACKUP_PATH} ${WSL_STUB_PATH}`)
|
||||
}
|
||||
}
|
||||
if (stage.heldLock) {
|
||||
steps.push(`rm -rf ${WSL_STUB_LOCK_PATH}`)
|
||||
}
|
||||
return steps.join(' ; ')
|
||||
}
|
||||
|
||||
// --exec prevents wsl.exe from expanding shell variables in argv.
|
||||
function runInWslAsRoot(distro: string, script: string): string {
|
||||
return execFileSync(
|
||||
'wsl.exe',
|
||||
['-u', 'root', ...buildWslExecArgs(distro, ['sh', '-c', script])],
|
||||
{ encoding: 'utf8', stdio: 'pipe', windowsHide: true }
|
||||
)
|
||||
}
|
||||
|
||||
export async function getFirstWslDistro(page: Page): Promise<string | null> {
|
||||
const wsl = await page.evaluate(async () => ({
|
||||
available: await window.api.wsl.isAvailable(),
|
||||
distros: await window.api.wsl.listDistros()
|
||||
}))
|
||||
return wsl.available ? (wsl.distros[0] ?? null) : null
|
||||
}
|
||||
|
||||
export type WslGoldenStubAgentStage = {
|
||||
createdCodexLink: boolean
|
||||
backedUpStub: boolean
|
||||
ownsStubPath: boolean
|
||||
heldLock: boolean
|
||||
}
|
||||
|
||||
/** Returns null when the distro cannot stage the stub. Holds a distro lock until cleanup. */
|
||||
export function stageWslGoldenStubAgent(distro: string): WslGoldenStubAgentStage | null {
|
||||
const stage: WslGoldenStubAgentStage = {
|
||||
createdCodexLink: false,
|
||||
backedUpStub: false,
|
||||
ownsStubPath: false,
|
||||
heldLock: false
|
||||
}
|
||||
try {
|
||||
if (runInWslAsRoot(distro, ACQUIRE_LOCK_SCRIPT).trim() !== 'acquired') {
|
||||
return null
|
||||
}
|
||||
stage.heldLock = true
|
||||
stage.backedUpStub = runInWslAsRoot(distro, BACKUP_EXISTING_STUB_SCRIPT).trim() === 'backed-up'
|
||||
stage.ownsStubPath = true
|
||||
runInWslAsRoot(distro, STAGE_SCRIPT)
|
||||
stage.createdCodexLink =
|
||||
runInWslAsRoot(distro, STAGE_CODEX_LINK_IF_MISSING_SCRIPT).trim() === 'created'
|
||||
return stage
|
||||
} catch {
|
||||
removeWslGoldenStubAgent(distro, stage)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export function removeWslGoldenStubAgent(distro: string, stage: WslGoldenStubAgentStage): void {
|
||||
const script = buildRestoreScript(stage)
|
||||
if (!script) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
runInWslAsRoot(distro, script)
|
||||
stage.heldLock = false
|
||||
stage.ownsStubPath = false
|
||||
} catch {
|
||||
// Best-effort cleanup; a leftover stub only affects this fixture's own name.
|
||||
}
|
||||
}
|
||||
|
||||
/** Retargets project agent detection and terminal spawning to WSL. */
|
||||
export async function useWslRuntimeForActiveProject(page: Page, distro: string): Promise<void> {
|
||||
await page.evaluate(async (wslDistro) => {
|
||||
const store = window.__store
|
||||
if (!store) {
|
||||
throw new Error('Orca store is unavailable')
|
||||
}
|
||||
const state = store.getState()
|
||||
const worktreeId = state.activeWorktreeId
|
||||
if (!worktreeId) {
|
||||
throw new Error('No active worktree')
|
||||
}
|
||||
const activeWorktree = Object.values(state.worktreesByRepo)
|
||||
.flat()
|
||||
.find((worktree) => worktree.id === worktreeId)
|
||||
const activeProject = state.projects.find((project) =>
|
||||
activeWorktree ? project.sourceRepoIds.includes(activeWorktree.repoId) : false
|
||||
)
|
||||
if (!activeProject) {
|
||||
throw new Error('No active project')
|
||||
}
|
||||
await state.updateProject(activeProject.id, {
|
||||
localWindowsRuntimePreference: { kind: 'wsl', distro: wslDistro }
|
||||
})
|
||||
}, distro)
|
||||
}
|
||||
Reference in New Issue
Block a user