diff --git a/src/main/ipc/settingsApi.ts b/src/main/ipc/settingsApi.ts index 1214ef5..063194b 100644 --- a/src/main/ipc/settingsApi.ts +++ b/src/main/ipc/settingsApi.ts @@ -1,6 +1,7 @@ import electron from 'electron'; import type { BrowserWindow } from 'electron'; import { platform, release, EOL } from 'node:os'; +import { mkdir } from 'node:fs/promises'; const { ipcMain, app, dialog, Notification, shell, clipboard } = electron; import { logger } from '../logger.js'; import type { BackupService } from '../services/BackupService.js'; @@ -324,6 +325,11 @@ export function registerSettingsApi(deps?: SettingsIpcDeps): void { // git init + remote add origin const syncDir = deps.syncSvc.getSyncDir(); + try { + await mkdir(syncDir, { recursive: true }); + } catch (e) { + return { ok: false as const, reason: `mkdir failed: ${(e as Error).message}` }; + } const git = new GitClient(syncDir); if (!(await git.isRepo())) { diff --git a/tests/unit/sync-ipc.test.ts b/tests/unit/sync-ipc.test.ts index 866991a..6a0f9a0 100644 --- a/tests/unit/sync-ipc.test.ts +++ b/tests/unit/sync-ipc.test.ts @@ -2,8 +2,10 @@ import { describe, it, expect, beforeEach, vi } from 'vitest'; vi.mock('electron', () => ({ default: { ipcMain: { handle: vi.fn() }, dialog: {}, shell: {} } })); vi.mock('../../src/main/services/GitClient.js'); +vi.mock('node:fs/promises', () => ({ mkdir: vi.fn(async () => undefined) })); import electron from 'electron'; +import { mkdir } from 'node:fs/promises'; import { GitClient } from '../../src/main/services/GitClient.js'; import { registerSettingsApi } from '../../src/main/ipc/settingsApi.js'; import type { SettingsIpcDeps } from '../../src/main/ipc/settingsApi.js'; @@ -105,6 +107,25 @@ describe('sync IPC channels', () => { expect(r).toEqual({ ok: true }); }); + // Regression: syncDir 미생성 상태에서 `git -C init` 호출 시 + // git 이 chdir 실패로 죽음 → mkdir(recursive) 가 init 보다 먼저 호출되어야 함. + // (runSync 의 line 135 패턴과 동일.) + it('mkdir(syncDir, recursive) 가 git init 전에 호출됨', async () => { + const { deps, gitInstance } = makeDeps(); + gitInstance.isRepo.mockResolvedValue(false); + const callOrder: string[] = []; + (mkdir as unknown as ReturnType).mockImplementationOnce(async () => { callOrder.push('mkdir'); }); + (gitInstance.run as unknown as ReturnType).mockImplementation(async (args: string[]) => { + callOrder.push(`git:${args[0]}`); + return { stdout: '', stderr: '', exitCode: 0 }; + }); + registerSettingsApi(deps as SettingsIpcDeps); + const h = getHandler('settings:configure-sync'); + await h({}, 'git@github.com:user/repo.git'); + expect(mkdir).toHaveBeenCalledWith('/tmp/sync', { recursive: true }); + expect(callOrder.indexOf('mkdir')).toBeLessThan(callOrder.indexOf('git:init')); + }); + it('valid URL → isRepo=true, hasRemote=true → remote set-url', async () => { const { deps, gitInstance } = makeDeps(); gitInstance.isRepo.mockResolvedValue(true);