mirror of
https://github.com/stablyai/orca.git
synced 2026-10-31 00:57:45 +00:00
fix(wsl-cli): removing the WSL command from a deleted distro forgets its registration (#36227)
wsl.exe refuses any command for a distro that was unregistered, so Remove failed and the distro stayed in wsl-cli-registrations.json for good. A distro-not-found failure now drops every bookkeeping entry for it (registration, inspection time, reconciliation) and reports not installed. Fixes STA-12893
This commit is contained in:
@@ -3,6 +3,7 @@ import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
||||
import {
|
||||
forgetWslCliRegistration,
|
||||
getWslCliRegistrationCandidates,
|
||||
recordWslCliRegistrationObservations,
|
||||
recordWslCliRegistrationRemoved
|
||||
@@ -125,6 +126,29 @@ describe('WSL CLI registration registry', () => {
|
||||
await expect(getWslCliRegistrationCandidates(userDataPath, ['Ubuntu'])).resolves.toEqual([])
|
||||
})
|
||||
|
||||
it('forgets every entry of a deleted distro and leaves the others', async () => {
|
||||
const reconciled = { target: 'C:\\Orca\\resources\\bin\\orca.exe', appVersion: '1.4.244' }
|
||||
await recordWslCliRegistrationObservations(
|
||||
userDataPath,
|
||||
[
|
||||
{ distro: 'orca-uqa-1029', inspected: true, managed: true, reconciled },
|
||||
{ distro: 'Ubuntu', inspected: true, managed: true, reconciled }
|
||||
],
|
||||
{ now: 1_000 }
|
||||
)
|
||||
|
||||
await forgetWslCliRegistration(userDataPath, 'ORCA-UQA-1029')
|
||||
|
||||
expect(
|
||||
JSON.parse(await readFile(join(userDataPath, 'wsl-cli-registrations.json'), 'utf8'))
|
||||
).toEqual({
|
||||
schemaVersion: 2,
|
||||
registeredDistros: ['Ubuntu'],
|
||||
inspectionTimes: { ubuntu: 1_000 },
|
||||
reconciliations: { ubuntu: reconciled }
|
||||
})
|
||||
})
|
||||
|
||||
it('periodically re-inspects a negative entry so restored distros are discovered', async () => {
|
||||
await recordWslCliRegistrationObservations(
|
||||
userDataPath,
|
||||
|
||||
@@ -270,3 +270,18 @@ export function recordWslCliRegistrationRemoved(
|
||||
{ distro, inspected: true, managed: false, reconciled: null }
|
||||
])
|
||||
}
|
||||
|
||||
/** A deleted distro has nothing left to inspect or reconcile, so every entry for it goes. */
|
||||
export function forgetWslCliRegistration(userDataPath: string, distro: string): Promise<void> {
|
||||
const key = normalizeWslDistroKey(distro)
|
||||
return updateState(userDataPath, (state) => {
|
||||
const { [key]: _inspected, ...inspectionTimes } = state.inspectionTimes
|
||||
const { [key]: _reconciled, ...reconciliations } = state.reconciliations
|
||||
return {
|
||||
...state,
|
||||
registeredDistros: removeDistro(state.registeredDistros, distro),
|
||||
inspectionTimes,
|
||||
reconciliations
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -119,6 +119,24 @@ export function unsupportedWslCliStatus(
|
||||
}
|
||||
}
|
||||
|
||||
/** The distro was deleted (`wsl --unregister`), so nothing of Orca's is left in it. */
|
||||
export function deletedDistroWslCliStatus(distro: string): CliInstallStatus {
|
||||
return {
|
||||
platform: 'linux',
|
||||
commandName: WSL_COMMAND_NAME,
|
||||
commandPath: null,
|
||||
pathDirectory: null,
|
||||
pathConfigured: false,
|
||||
launcherPath: null,
|
||||
installMethod: null,
|
||||
supported: false,
|
||||
state: 'not_installed',
|
||||
currentTarget: null,
|
||||
unsupportedReason: null,
|
||||
detail: `${distro} is no longer an installed WSL distro, so nothing is registered in it.`
|
||||
}
|
||||
}
|
||||
|
||||
/** Settings shows this instead of a raw IPC timeout; the row stays refreshable. */
|
||||
export function unreachableWslCliStatus(distro: string): CliInstallStatus {
|
||||
return unsupportedWslCliStatus(
|
||||
|
||||
@@ -7,6 +7,7 @@ const {
|
||||
wslInstallerMock,
|
||||
recordInstalledMock,
|
||||
recordRemovedMock,
|
||||
forgetMock,
|
||||
getDefaultWslDistroMock,
|
||||
readWslDistroRunningStateMock
|
||||
} = vi.hoisted(() => ({
|
||||
@@ -16,6 +17,7 @@ const {
|
||||
wslInstallerMock: vi.fn(),
|
||||
recordInstalledMock: vi.fn(),
|
||||
recordRemovedMock: vi.fn(),
|
||||
forgetMock: vi.fn(),
|
||||
getDefaultWslDistroMock: vi.fn()
|
||||
}))
|
||||
|
||||
@@ -24,7 +26,8 @@ vi.mock('../cli/cli-installer', () => ({ CliInstaller: vi.fn() }))
|
||||
vi.mock('../cli/wsl-cli-installer', () => ({ WslCliInstaller: wslInstallerMock }))
|
||||
vi.mock('../cli/wsl-cli-registration-registry', () => ({
|
||||
recordWslCliRegistrationInstalled: recordInstalledMock,
|
||||
recordWslCliRegistrationRemoved: recordRemovedMock
|
||||
recordWslCliRegistrationRemoved: recordRemovedMock,
|
||||
forgetWslCliRegistration: forgetMock
|
||||
}))
|
||||
vi.mock('../persistence', () => ({ getCanonicalUserDataPath: () => '/canonical-user-data' }))
|
||||
vi.mock('../startup/hydrate-shell-path', () => ({
|
||||
@@ -65,6 +68,7 @@ describe('WSL CLI registration IPC', () => {
|
||||
wslInstallerMock.mockReset()
|
||||
recordInstalledMock.mockReset().mockResolvedValue(undefined)
|
||||
recordRemovedMock.mockReset().mockResolvedValue(undefined)
|
||||
forgetMock.mockReset().mockResolvedValue(undefined)
|
||||
getDefaultWslDistroMock.mockReset().mockReturnValue('Ubuntu')
|
||||
registerCliHandlers()
|
||||
})
|
||||
@@ -120,6 +124,35 @@ describe('WSL CLI registration IPC', () => {
|
||||
expect(recordRemovedMock).toHaveBeenCalledWith('/canonical-user-data', 'Ubuntu')
|
||||
})
|
||||
|
||||
it('forgets the registration of a deleted distro instead of failing the removal', async () => {
|
||||
const remove = vi.fn(async () => {
|
||||
throw new Error('wsl.exe: There is no distribution with the supplied name.')
|
||||
})
|
||||
wslInstallerMock.mockImplementation(function MockWslCliInstaller() {
|
||||
return { remove }
|
||||
})
|
||||
|
||||
await expect(
|
||||
getWslHandler('cli:removeWsl')({}, { distro: 'orca-uqa-1029' })
|
||||
).resolves.toMatchObject({ state: 'not_installed', supported: false })
|
||||
expect(forgetMock).toHaveBeenCalledWith('/canonical-user-data', 'orca-uqa-1029')
|
||||
expect(recordRemovedMock).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('keeps the registration when removal fails for another reason', async () => {
|
||||
const remove = vi.fn(async () => {
|
||||
throw new Error('permission denied')
|
||||
})
|
||||
wslInstallerMock.mockImplementation(function MockWslCliInstaller() {
|
||||
return { remove }
|
||||
})
|
||||
|
||||
await expect(getWslHandler('cli:removeWsl')({}, { distro: 'Ubuntu' })).rejects.toThrow(
|
||||
'permission denied'
|
||||
)
|
||||
expect(forgetMock).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('does not claim ownership when installation is not confirmed', async () => {
|
||||
const install = vi.fn(async () => ({ state: 'unsupported' }))
|
||||
wslInstallerMock.mockImplementation(function MockWslCliInstaller() {
|
||||
|
||||
+17
-1
@@ -4,11 +4,13 @@ import { resolveAppImageCacheKey } from '../cli/appimage-extracted-root'
|
||||
import { CliInstaller } from '../cli/cli-installer'
|
||||
import { runKeyedSerializedOperation } from '../cli/keyed-promise-queue'
|
||||
import {
|
||||
forgetWslCliRegistration,
|
||||
recordWslCliRegistrationInstalled,
|
||||
recordWslCliRegistrationRemoved
|
||||
} from '../cli/wsl-cli-registration-registry'
|
||||
import { WslCliInstaller } from '../cli/wsl-cli-installer'
|
||||
import {
|
||||
deletedDistroWslCliStatus,
|
||||
notRunningWslCliStatus,
|
||||
unreachableWslCliStatus,
|
||||
WslCliUnreachableError
|
||||
@@ -18,6 +20,7 @@ import { getCanonicalUserDataPath } from '../persistence'
|
||||
import { hydrateShellPath, mergePathSegments } from '../startup/hydrate-shell-path'
|
||||
import { getDefaultWslDistro } from '../wsl'
|
||||
import { blockedWslDistroForExec, runAsWslExecUserAct } from '../wsl/wsl-background-exec-gate'
|
||||
import { isWslDistroNotFoundFailure } from '../wsl/wsl-distro-unsupported'
|
||||
import { resolveAppImageRuntimeIdentity } from '../appimage-runtime-identity'
|
||||
|
||||
const APPIMAGE_REPAIR_RETRY_MS = 30_000
|
||||
@@ -189,7 +192,20 @@ export function registerCliHandlers(): void {
|
||||
async (_event, args?: { distro?: string | null }): Promise<CliInstallStatus> => {
|
||||
const distro = resolveWslCliDistro(args)
|
||||
return runWslCliRegistrationOperation(distro, async () => {
|
||||
const status = await new WslCliInstaller({ distro }).remove()
|
||||
let status: CliInstallStatus
|
||||
try {
|
||||
status = await new WslCliInstaller({ distro }).remove()
|
||||
} catch (error) {
|
||||
if (!distro || !isWslDistroNotFoundFailure(error)) {
|
||||
throw error
|
||||
}
|
||||
// Nothing is left inside a deleted distro; only Orca's bookkeeping still names it.
|
||||
await persistWslCliRegistration(
|
||||
() => forgetWslCliRegistration(getCanonicalUserDataPath(), distro),
|
||||
'remove'
|
||||
)
|
||||
return deletedDistroWslCliStatus(distro)
|
||||
}
|
||||
if (distro && status.state === 'not_installed') {
|
||||
await persistWslCliRegistration(
|
||||
() => recordWslCliRegistrationRemoved(getCanonicalUserDataPath(), distro),
|
||||
|
||||
Reference in New Issue
Block a user