fix(v033): sync configure-sync — git init 전 syncDir mkdir(recursive)

settings:configure-sync IPC 핸들러가 `git -C <syncDir> init` 호출 전에
syncDir 디렉토리를 생성하지 않아, sync 첫 설정 시 git 이 chdir 단계에서
`fatal: cannot change to '<profileDir>/sync': No such file or directory` 로
실패하던 문제. SyncService.runSync() 의 동일 패턴 (mkdir recursive) 을
핸들러에도 추가.

연쇄 증상: SyncSection 의 "연결 테스트" 버튼 disabled 조건이 저장된 url
state 기반이라, 저장 실패로 url 영영 비어 있어 버튼 활성화 불가 (닭/달걀).
mkdir fix 로 자동 해소.

회귀: sync-ipc.test.ts 에 mkdir 호출 순서 검증 1건 추가 (18 pass).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
altair823
2026-05-10 22:04:46 +09:00
parent f37e17dd81
commit 2221113329
2 changed files with 27 additions and 0 deletions

View File

@@ -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())) {

View File

@@ -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 <syncDir> 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<typeof vi.fn>).mockImplementationOnce(async () => { callOrder.push('mkdir'); });
(gitInstance.run as unknown as ReturnType<typeof vi.fn>).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);