fix(electron): preserve unowned roots without project folders

This commit is contained in:
Palash Debnath
2026-09-17 20:30:38 +05:30
parent d6efb14849
commit d217553e2e
2 changed files with 36 additions and 32 deletions
+35 -30
View File
@@ -418,33 +418,38 @@ it.each(['ready', 'compatible'] as const)(
},
);
it('keeps a selected broken environment selected until explicit setup, then preserves unowned files', async () => {
const { resolve, join } = await import('node:path');
const selected = resolve('/selected/VoiceStudio');
mocks.runtimeConfig = { root: selected, owned: false };
mocks.existingProject = true;
mocks.ready.mockResolvedValue(true);
mocks.dependencies.mockImplementation(async (project?: string) => !project?.includes('selected'));
mocks.install.mockRejectedValue(new Error('offline'));
vi.stubGlobal(
'fetch',
vi.fn(async () => {
throw new Error('no backend');
}),
);
vi.stubEnv('OMNIVOICE_BACKEND_CMD', '');
vi.stubEnv('VOICESTUDIO_SKIP_BACKEND', '');
const supervisor = new BackendSupervisor();
await supervisor.start();
expect(supervisor.status.stage).toBe('setup_required');
expect(
mocks.dependencies.mock.calls.every(([project]) => String(project).includes('selected')),
).toBe(true);
expect(mocks.install).not.toHaveBeenCalled();
await supervisor.setupRuntime();
expect(mocks.install.mock.calls[0][1]).toBe(join('/private/voicestudio', 'runtime', 'project'));
expect(mocks.runtimeConfig?.root).not.toBe(selected);
expect(mocks.rm).not.toHaveBeenCalled();
expect(mocks.stage).not.toHaveBeenCalled();
await supervisor.shutdown();
});
it.each([true, false])(
'preserves a selected unowned runtime (project exists: %s)',
async (projectExists) => {
const { resolve, join } = await import('node:path');
const selected = resolve('/selected/VoiceStudio');
mocks.runtimeConfig = { root: selected, owned: false };
mocks.existingProject = projectExists;
mocks.ready.mockResolvedValue(true);
mocks.dependencies.mockImplementation(
async (project?: string) => !project?.includes('selected'),
);
mocks.install.mockRejectedValue(new Error('offline'));
vi.stubGlobal(
'fetch',
vi.fn(async () => {
throw new Error('no backend');
}),
);
vi.stubEnv('OMNIVOICE_BACKEND_CMD', '');
vi.stubEnv('VOICESTUDIO_SKIP_BACKEND', '');
const supervisor = new BackendSupervisor();
await supervisor.start();
expect(supervisor.status.stage).toBe('setup_required');
expect(
mocks.dependencies.mock.calls.every(([project]) => String(project).includes('selected')),
).toBe(true);
expect(mocks.install).not.toHaveBeenCalled();
await supervisor.setupRuntime();
expect(mocks.install.mock.calls[0][1]).toBe(join('/private/voicestudio', 'runtime', 'project'));
expect(mocks.runtimeConfig?.root).not.toBe(selected);
expect(mocks.rm).not.toHaveBeenCalled();
expect(mocks.stage).not.toHaveBeenCalled();
await supervisor.shutdown();
},
);
+1 -2
View File
@@ -549,8 +549,7 @@ export class BackendSupervisor extends EventEmitter<{
configured &&
!configured.owned &&
samePath(configured.root, runtimeRoot) &&
!samePath(runtimeRoot, defaultRuntimeRoot()) &&
existsSync(project)
!samePath(runtimeRoot, defaultRuntimeRoot())
) {
// An explicit setup action may create a new runtime, but must never
// take ownership of (or repair in place) another installation's files.