// scripts/setup-sidecar.mjs // STT 사이드카 개발/빌드 환경을 준비한다. // // - sidecar/.venv 가 없으면 생성한다. // - requirements.txt + pyinstaller 를 설치한다. // // 사용: npm --prefix apps/desktop run sidecar:setup // // 주의: faster-whisper/ctranslate2 휠은 수백 MB이며 최초 실행 시 네트워크가 필요하다. import { spawnSync } from 'node:child_process' import { existsSync, mkdirSync } from 'node:fs' import path from 'node:path' import { fileURLToPath } from 'node:url' const scriptDir = path.dirname(fileURLToPath(import.meta.url)) const desktopDir = path.resolve(scriptDir, '..') const sidecarDir = path.join(desktopDir, 'sidecar') const isWindows = process.platform === 'win32' const venvPython = isWindows ? path.join(sidecarDir, '.venv', 'Scripts', 'python.exe') : path.join(sidecarDir, '.venv', 'bin', 'python3') function run(command, args, label) { console.log(`\n$ ${command} ${args.join(' ')}`) const result = spawnSync(command, args, { stdio: 'inherit', cwd: sidecarDir }) if (result.status !== 0) { console.error(`\n${label} 단계가 실패했습니다 (exit ${result.status ?? 'null'})`) process.exit(result.status ?? 1) } } function findSystemPython() { const candidates = isWindows ? [ ['py', ['-3.11']], ['py', ['-3']], ['python', []], ['python3', []], ] : [ ['python3.11', []], ['python3', []], ['python', []], ] for (const [command, prefixArgs] of candidates) { const probe = spawnSync(command, [...prefixArgs, '--version'], { encoding: 'utf-8', shell: isWindows, }) if (probe.status === 0) { console.log(`시스템 Python 발견: ${command} ${prefixArgs.join(' ')} → ${probe.stdout.trim()}`) return { command, prefixArgs, shell: isWindows } } } console.error( 'Python 3.11+ 를 찾을 수 없습니다. https://www.python.org/downloads/ 에서 설치하거나 PATH에 추가하세요.', ) process.exit(1) } function hasModule(python, moduleName) { return ( spawnSync(python, ['-c', `import ${moduleName}`], { stdio: 'ignore' }).status === 0 ) } if (!existsSync(sidecarDir)) { console.error(`사이드카 디렉토리를 찾을 수 없습니다: ${sidecarDir}`) process.exit(1) } if (existsSync(venvPython) && hasModule(venvPython, 'faster_whisper')) { // CI/재실행 시 불필요한 재설치를 건너뛴다 (수백 MB 다운로드 방지). console.log(`사이드카 환경이 이미 준비되어 있습니다: ${venvPython}`) if (!hasModule(venvPython, 'PyInstaller')) { run(venvPython, ['-m', 'pip', 'install', 'pyinstaller>=6.0'], 'PyInstaller 설치') } } else { if (!existsSync(venvPython)) { const systemPython = findSystemPython() mkdirSync(path.dirname(venvPython), { recursive: true }) run( systemPython.command, [...systemPython.prefixArgs, '-m', 'venv', path.join(sidecarDir, '.venv')], '가상환경 생성', ) } else { console.log(`기존 가상환경 사용: ${venvPython}`) } run(venvPython, ['-m', 'pip', 'install', '--upgrade', 'pip'], 'pip 업그레이드') run( venvPython, ['-m', 'pip', 'install', '-r', path.join(sidecarDir, 'requirements.txt')], '사이드카 의존성 설치', ) run(venvPython, ['-m', 'pip', 'install', 'pyinstaller>=6.0'], 'PyInstaller 설치') } console.log('\n사이드카 환경 준비 완료.')