d3ro-voice/apps/desktop/scripts/build-sidecar.py
yunchan8804 45a580878a feat(V2-1a): Monorepo 구조 전환 — apps/desktop으로 V1 이동
- npm workspaces 루트 (apps/*, packages/*) 세팅
- V1 전체를 apps/desktop/으로 git mv (src, resources, tests, sidecar,
  scripts, electron.vite.config.ts, electron-builder.yml, vitest.config.ts,
  tsconfig.node.json, tsconfig.web.json)
- apps/desktop/package.json 신규 (name=@d3ro/desktop)
- productName: 'd3ro-voice' 명시 — app.getName()을 고정하여 userData 경로
  %APPDATA%\d3ro-voice\ 그대로 유지 (기존 DB/설정 연속성 보장)
- 루트 package.json을 workspace 루트로 재구성, 공통 devDep만 유지
  (typescript, eslint, prettier)
- turbo.json, tsconfig.base.json 추가 (Turborepo 자체 설치는 별도 sub-phase)
- memory/project_status.md 생성 (규칙 13)

검증:
- npm run typecheck 통과
- npm run build 통과 (electron-vite main+preload+renderer)
- npm run dev 실제 실행 → DB/핫키/Ollama 자동 실행 모두 정상
2026-04-08 14:04:41 +09:00

120 lines
3.8 KiB
Python

"""
scripts/build-sidecar.py
faster-whisper sidecar를 PyInstaller로 빌드한다.
사용법:
pip install pyinstaller
python scripts/build-sidecar.py
출력:
sidecar-dist/sidecar.exe (단일 디렉토리 모드)
"""
import subprocess
import sys
import shutil
from pathlib import Path
PROJECT_ROOT = Path(__file__).parent.parent
SIDECAR_DIR = PROJECT_ROOT / "sidecar"
MAIN_PY = SIDECAR_DIR / "main.py"
OUTPUT_DIR = PROJECT_ROOT / "sidecar-dist"
def check_prerequisites():
"""필수 도구가 설치되어 있는지 확인한다."""
# PyInstaller
try:
import PyInstaller # noqa: F401
except ImportError:
print("PyInstaller가 설치되어 있지 않습니다.")
print("실행: pip install pyinstaller")
sys.exit(1)
# faster-whisper
try:
import faster_whisper # noqa: F401
except ImportError:
print("faster-whisper가 설치되어 있지 않습니다.")
print("실행: pip install -r sidecar/requirements.txt")
sys.exit(1)
if not MAIN_PY.exists():
print(f"sidecar 소스를 찾을 수 없습니다: {MAIN_PY}")
sys.exit(1)
def build():
"""PyInstaller로 sidecar를 빌드한다."""
print("=" * 60)
print("D3RO-VOICE Sidecar 빌드 시작")
print("=" * 60)
# 기존 빌드 정리
if OUTPUT_DIR.exists():
shutil.rmtree(OUTPUT_DIR)
# PyInstaller 실행 — onedir 모드 (onefile보다 시작이 빠름)
cmd = [
sys.executable, "-m", "PyInstaller",
"--name", "sidecar",
"--distpath", str(OUTPUT_DIR),
"--workpath", str(PROJECT_ROOT / "build" / "sidecar-build"),
"--specpath", str(PROJECT_ROOT / "build"),
# onedir 모드 (단일 디렉토리)
"--noconfirm",
"--clean",
# hidden imports (PyInstaller가 자동 감지 못하는 것)
"--hidden-import", "faster_whisper",
"--hidden-import", "ctranslate2",
"--hidden-import", "huggingface_hub",
"--hidden-import", "tokenizers",
"--hidden-import", "uvicorn.logging",
"--hidden-import", "uvicorn.protocols.http",
"--hidden-import", "uvicorn.protocols.http.auto",
"--hidden-import", "uvicorn.protocols.http.h11_impl",
"--hidden-import", "uvicorn.protocols.websockets",
"--hidden-import", "uvicorn.protocols.websockets.auto",
"--hidden-import", "uvicorn.lifespan",
"--hidden-import", "uvicorn.lifespan.on",
"--hidden-import", "uvicorn.lifespan.off",
# 콘솔 없음 (Windows)
"--noconsole",
str(MAIN_PY),
]
print(f"\n실행 명령:\n {' '.join(cmd)}\n")
result = subprocess.run(cmd, cwd=str(PROJECT_ROOT))
if result.returncode != 0:
print(f"\nPyInstaller 빌드 실패 (exit code: {result.returncode})")
sys.exit(1)
# 빌드 결과 확인
exe_path = OUTPUT_DIR / "sidecar" / "sidecar.exe"
if exe_path.exists():
size_mb = exe_path.stat().st_size / (1024 * 1024)
print(f"\n빌드 성공!")
print(f" 경로: {exe_path}")
print(f" 크기: {size_mb:.1f} MB")
# 전체 디렉토리 크기
total_size = sum(f.stat().st_size for f in (OUTPUT_DIR / "sidecar").rglob("*") if f.is_file())
total_mb = total_size / (1024 * 1024)
print(f" 전체 디렉토리: {total_mb:.1f} MB")
else:
print(f"\n빌드 출력을 찾을 수 없습니다: {exe_path}")
# onedir 모드에서는 디렉토리 내부에 exe가 있음
for exe in OUTPUT_DIR.rglob("*.exe"):
print(f" 발견: {exe}")
print("\n빌드 완료!")
print(f"electron-builder에서 이 경로를 extraResources로 지정하세요:")
print(f" from: sidecar-dist/sidecar/")
print(f" to: sidecar/")
if __name__ == "__main__":
check_prerequisites()
build()