// scans/ci/verify-sidecar-bundle.mjs // 패키징 전에 STT 사이드카 번들이 실제로 존재하고 필수 데이터가 들어있는지 검증한다. // // 왜 필요한가: electron-builder의 extraResources는 소스 디렉토리가 없으면 조용히 // 건너뛴다. 그 결과 로컬 전사가 전혀 동작하지 않는 설치 파일이 배포된 이력이 있다. // 패키징 직전에 하드 실패시켜 같은 회귀를 막는다. // // 사용: node scripts/ci/verify-sidecar-bundle.mjs import { existsSync, statSync } from 'node:fs' import path from 'node:path' import { fileURLToPath } from 'node:url' const scriptDir = path.dirname(fileURLToPath(import.meta.url)) const repoRoot = path.resolve(scriptDir, '..', '..') const desktopDir = path.join(repoRoot, 'apps', 'desktop') const bundleDir = path.join(desktopDir, 'sidecar-dist', 'sidecar') const exeSuffix = process.platform === 'win32' ? '.exe' : '' const exePath = path.join(bundleDir, `sidecar${exeSuffix}`) /** 패키지에 반드시 포함돼야 하는 런타임 데이터 (PyInstaller 6은 _internal/ 하위) */ const REQUIRED_RELATIVE = [ // faster-whisper VAD onnx — 누락하면 vad_filter=true 전사가 런타임에 실패한다. ['faster_whisper', 'assets', 'silero_vad_v6.onnx'], ] const problems = [] if (!existsSync(exePath)) { problems.push( `사이드카 실행 파일이 없습니다: ${exePath}\n` + ' 빌드: npm --prefix apps/desktop run sidecar:setup && npm --prefix apps/desktop run sidecar:build', ) } if (existsSync(bundleDir)) { for (const parts of REQUIRED_RELATIVE) { const candidates = [ path.join(bundleDir, '_internal', ...parts), path.join(bundleDir, ...parts), ] if (!candidates.some((candidate) => existsSync(candidate))) { problems.push( `사이드카 번들에 필수 데이터가 없습니다: ${candidates[0]} (또는 ${candidates[1]})`, ) } } } if (problems.length > 0) { console.error('STT 사이드카 번들 검증 실패:') for (const problem of problems) console.error(`- ${problem}`) process.exit(1) } const sizeMb = statSync(exePath).size / (1024 * 1024) console.log(`STT 사이드카 번들 검증 통과: ${exePath} (${sizeMb.toFixed(1)} MB)`)