Main
Electron main process입니다. Bottle, Wine, DXMT, update, process, log, preference 같은 런처 핵심 동작을 관리합니다.
이 문서는 BDIH-Launcher 저장소를 기준으로 런처 앱을 빌드하고 패키징하는 방법을 정리합니다.
단순히 pnpm build 명령만 나열하는 것이 아니라, 저장소의 docs/에 정리된 배포 채널, 서명, 업데이트 테스트, 데이터 보존, Bottle 실행 구조까지 함께 설명합니다.
BDIH-Launcher는 macOS arm64를 대상으로 하는 Electron 앱입니다.
빌드 결과는 dist/에 JavaScript bundle로 생성되고, electron-builder가 이를 .app, .dmg, .zip 형태로 패키징합니다.
Main
Electron main process입니다. Bottle, Wine, DXMT, update, process, log, preference 같은 런처 핵심 동작을 관리합니다.
Preload
Renderer와 Main 사이의 IPC bridge를 제공합니다.
Renderer
React 기반 UI입니다. Main에서 제공하는 상태 snapshot과 IPC 결과를 화면에 표시합니다.
Guardian
런처가 비정상 종료될 때 관리 중인 Wine process를 정리하는 native C helper입니다.
주요 기준은 다음과 같습니다.
| 항목 | 기준 |
|---|---|
| 대상 OS | macOS |
| 대상 CPU | arm64 |
| 패키저 | electron-builder |
| UI | React 19 |
| 언어 | TypeScript 6 |
| 번들러 | Webpack 5 |
| 업데이트 | electron-updater |
의존성은 pnpm-lock.yaml을 기준으로 설치합니다.
pnpm install --frozen-lockfile저장소에는 별도 Node.js engine이 고정되어 있지 않으므로, CI 또는 로컬 기준 Node.js 버전을 맞춘 뒤 lockfile을 기준으로 설치하는 것이 좋습니다. macOS 패키징과 native helper 검증에는 macOS 환경이 필요합니다.
가장 기본적인 빌드는 다음 명령입니다.
pnpm buildpnpm build는 내부적으로 다음 순서로 실행됩니다.
pnpm build:guardianpnpm build:preloadpnpm build:rendererpnpm build:main각 명령의 역할은 다음과 같습니다.
| 명령 | 역할 |
|---|---|
pnpm build:guardian | native/BDIHGuardian/guardian.c를 빌드하고 app bundle에 넣을 native helper를 준비합니다. |
pnpm build:preload | Preload script를 production Webpack 설정으로 빌드합니다. |
pnpm build:renderer | React Renderer를 production bundle로 빌드합니다. |
pnpm build:main | Electron Main process bundle을 빌드합니다. |
pnpm start | 이미 빌드된 dist/main/main.js를 기준으로 Electron 앱을 실행합니다. |
pnpm start:all | 빌드 후 Electron 앱을 실행합니다. |
개발 중 Main과 Preload만 빠르게 다시 묶을 때는 다음 명령을 사용할 수 있습니다.
pnpm build:devpnpm build:dev:rendererunpacked app 디렉터리만 확인하려면 pack을 사용합니다.
pnpm pack배포용 .dmg와 .zip까지 만들려면 dist를 사용합니다.
pnpm dist기본 출력 경로는 release/입니다.
패키지에는 dist/**/*, package.json, 앱 아이콘, ko.lproj, locale 파일, native Guardian이 포함됩니다.
release/ mac-arm64/ BDIH Launcher.app BDIH-Launcher-<version>-arm64.dmg BDIH-Launcher-<version>-arm64.zip latest-mac.yml *.blockmapelectron-builder.config.cjs는 Stable, Beta, Nightly를 하나의 production 설정에서 처리합니다.
주요 환경 변수는 다음과 같습니다.
| 변수 | 역할 |
|---|---|
UPDATE_CHANNEL | latest, beta, nightly update feed를 선택합니다. |
RELEASE_TYPE | GitHub Release의 release 또는 prerelease 타입을 선택합니다. |
BDIH_RELEASE_CHANNEL | 런처 내부 채널 marker를 stable, beta, nightly 중 하나로 기록합니다. |
BDIH_REQUIRE_CODE_SIGNING | true일 때 electron-builder 서명을 필수로 요구합니다. |
PUBLISH_REPOSITORY | electron-builder publish 대상 GitHub repository를 지정합니다. |
Nightly는 BDIH Launcher Nightly product name과 day.faby.bdih-launcher.nightly bundle identifier를 사용합니다.
Stable과 Beta는 일반 BDIH Launcher product name과 day.faby.bdih-launcher bundle identifier를 공유합니다.
Staging은 production 앱을 그대로 덮어쓰는 테스트 앱이 아닙니다.
electron-builder.staging.config.cjs는 별도 product name, bundle identifier, app data를 사용합니다.
| 항목 | 값 |
|---|---|
| Product name | BDIH Launcher Staging |
| Bundle ID | day.faby.bdih-launcher.staging |
| 기본 release repository | Bob-Ddong-Iri-Hoyo/BDIH-Launcher-TestProduction |
| Stable feed | latest |
| Beta feed | beta |
Staging 빌드는 CI에서 주로 사용하지만, 구조상 다음 환경 값으로 channel과 version을 주입합니다.
BDIH_STAGING_VERSIONBDIH_STAGING_CHANNELBDIH_STAGING_SOURCE_COMMITBDIH_STAGING_OUTPUT_DIRStaging artifact 이름은 architecture를 생략합니다. 현재 staging이 arm64-only라는 전제를 명확히 하기 위한 선택입니다.
BDIH-Launcher-Staging-Stable-1.0.0-rc.2.dmgBDIH-Launcher-Staging-Stable-1.0.0-rc.2.zipBDIH-Launcher-Staging-Beta-1.0.0-beta.1.staging.2.dmgBDIH-Launcher-Staging-Beta-1.0.0-beta.1.staging.2.zip업데이트 UI와 Squirrel.Mac 흐름은 일반 태그 릴리즈와 분리해서 테스트합니다. 로컬 업데이트 테스트 앱은 production 앱과 다른 identity와 저장소를 사용합니다.
| 항목 | Stable/Beta 테스트 |
|---|---|
| Product name | BDIH Launcher Update Test |
| Bundle ID | day.faby.bdih-launcher.update-test |
| 앱 위치 | tests/Release/apps/stable-beta/BDIH Launcher Update Test.app |
| 상태 루트 | tests/Release/state/stable-beta |
| 로컬 feed | http://127.0.0.1:45678/ |
Nightly 업데이트 테스트는 별도 product name과 bundle identifier를 사용합니다.
BDIH Launcher Nightly Update Testday.faby.bdih-launcher.nightly.update-test테스트 artifact를 만들 때는 다음 명령을 사용합니다.
pnpm run build:test -- --version 1.0.0 --channel stablepnpm run build:test -- --version 1.1.0-beta.1 --channel betapnpm run build:test:nightly -- --version 1.2.0-nightly.1여러 버전을 한 번에 만들 때는 range를 사용할 수 있습니다.
pnpm run build:test:stable -- --range 1.0.0~1.0.9pnpm run build:test:beta -- --range 1.1.0-beta.1~1.1.0-beta.9로컬 feed를 띄운 뒤, 설치된 테스트 앱을 통해 업데이트를 확인합니다.
pnpm update:test:servepnpm install:testpnpm reveal:testpnpm start:testNightly는 다음 명령을 사용합니다.
pnpm install:test:nightlypnpm reveal:test:nightlypnpm start:test:nightly업데이트 테스트 앱은 production Bottle, Wine, DXMT 경로를 거부하도록 만들어져 있습니다. production 설정 파일을 테스트 상태 디렉터리에 복사하지 않는 것이 원칙입니다.
기본 테스트는 Jest를 사용합니다.
pnpm test빌드와 함께 자주 확인해야 하는 테스트는 다음과 같습니다.
| 명령 | 확인 내용 |
|---|---|
pnpm test:guardian | Guardian helper의 clean disarm, EOF, owner exit, signal cleanup을 확인합니다. |
pnpm test:guardian:app | Electron 앱과 Guardian을 함께 띄워 crash recovery와 single-instance 동작을 확인합니다. |
pnpm test:update-build-script | 업데이트 테스트 artifact 빌드 스크립트의 version/range 해석을 확인합니다. |
pnpm test:staging-promotion-policy | Staging candidate와 production promotion 정책을 확인합니다. |
pnpm test:signing-script | .p12 생성, 변환, renewal 흐름을 확인합니다. |
pnpm test:hoyo-proxy:build | wine-build에 있는 HoYoPlay proxy helper를 빌드합니다. |
Renderer UI는 Storybook으로 확인할 수 있습니다.
pnpm storybookpnpm build-storybookpnpm screenshotpnpm screenshot은 Storybook static build를 만든 뒤 launcher shell story를 캡처합니다.
출력은 다음 경로를 사용합니다.
output/screenshot/launcher-view.png배포 워크플로는 BDIH Launcher Update Signing identity를 temporary Keychain에 설치하고, electron-builder가 이 identity로 app bundle을 서명하도록 강제합니다.
secret이 없거나 잘못된 경우 ad-hoc signing으로 조용히 fallback하지 않고 빌드를 실패시키는 것이 기준입니다.
필요한 GitHub Actions secret은 다음과 같습니다.
| Secret | 역할 |
|---|---|
MACOS_SIGNING_P12_BASE64 | signing .p12의 Base64 값입니다. |
MACOS_SIGNING_P12_PASSWORD | .p12 export password입니다. |
STAGING_RELEASE_TOKEN | TestProduction repository에 release asset을 올리기 위한 fine-grained token입니다. |
로컬에서 새 .p12를 만들 때는 다음 스크립트를 사용합니다.
./scripts/createP12.sh --copy-base64이 signing identity는 Squirrel.Mac 업데이트 연속성을 위한 self-signed update identity입니다. Apple Developer ID 서명이나 notarization 신뢰를 제공하는 것은 아닙니다.
Production Stable과 Beta는 수동으로 production tag를 밀어서 만들지 않습니다. 검증된 TestProduction candidate를 승격하는 방식으로 만듭니다.
GitHub environment는 두 개를 사용합니다.
| Environment | 역할 |
|---|---|
production-candidate-approval | TestProduction candidate를 확인한 뒤 unpublished Production Draft 생성을 허용합니다. |
production-release | 이미 만들어진 Draft를 다시 검증한 뒤 사용자에게 공개합니다. |
Stable 릴리즈의 기본 흐름은 다음과 같습니다.
package.json version 준비 -> TestProduction Candidate 실행 -> rc.N candidate 생성 -> candidate 테스트 -> production-candidate-approval 승인 -> Production Draft 생성 -> production-release 승인 -> v<version> 공개Beta 릴리즈는 package.json의 release train을 유지한 채 Beta 번호를 workflow 입력으로 주입합니다.
Staging suffix는 production version으로 들어가지 않습니다.
package.json: 1.2.3TestProduction candidate: 1.2.3-beta.2.staging.3Production release: 1.2.3-beta.2Stable candidate는 다음 구조를 따릅니다.
package.json: 1.2.3TestProduction candidate: 1.2.3-rc.2Production release: 1.2.3candidate tag와 release는 immutable하게 취급합니다. 같은 대상에 수정이 필요하면 기존 tag를 덮어쓰지 않고 다음 attempt를 만듭니다.
1.0.0-rc.1 -> 1.0.0-rc.21.0.0-beta.1.staging.1 -> 1.0.0-beta.1.staging.2Stable, Beta, Nightly는 업데이트 채널뿐 아니라 app data cleanup 정책에도 영향을 줍니다.
런처는 마지막으로 성공적으로 열린 앱 버전과 channel을 app-data-lifecycle.json에 기록합니다.
retired file cleanup은 같은 channel에서 version이 바뀔 때만 허용합니다.
| 이전 빌드 | 현재 빌드 | retired-file cleanup |
|---|---|---|
| Stable | Stable | 허용 |
| Beta | Beta | 허용 |
| Nightly | Nightly | 허용 |
| Stable | Beta | 보존 |
| Beta | Stable | 보존 |
| 첫 실행 | Any | 보존 |
| 같은 버전 | 같은 channel | 생략 |
cleanup은 allowlist 기반입니다. Wine registry, prefix, drive, unknown file을 현재 빌드가 읽지 않는다는 이유만으로 삭제하지 않습니다. Stable과 Beta 사이를 이동할 때는 channel transition snapshot을 만들고, 데이터 호환성 확인이 끝난 뒤에만 다음 단계로 진행합니다.
런처 빌드에는 native Guardian과 Main-owned execution state가 함께 포함됩니다. 이 구조는 빌드 결과가 단순 UI 앱이 아니라 Wine process lifecycle을 직접 관리하는 앱이라는 점 때문에 중요합니다.
현재 실행 흐름은 다음처럼 정리할 수 있습니다.
Renderer -> IPCManager -> BottleExecutionManager -> BottleExecutionStateRegistry -> Wine/DXMT/Jadeite/process helpers -> Renderer snapshot projectionMain process가 application launch ownership의 최종 권한을 가집니다. Renderer는 중복 클릭 방지나 process lifetime을 직접 판단하지 않고, Main이 제공하는 versioned snapshot을 표시합니다.
동시 실행 요청은 같은 logical target에 대해 하나의 launch promise로 합쳐집니다. 이미 실행 중인 target은 기존 logical process ID를 반환하고, stopping 상태의 target은 retry 가능한 실패로 처리합니다.
새 Wine runtime은 bdih.wine.process.v1 process telemetry를 제공할 수 있습니다.
런처는 runtime metadata가 정확한 capability를 선언할 때만 FIFO 환경 변수를 주입합니다.
WINE_BDIH_PROCESS_TELEMETRY=1WINE_BDIH_PROCESS_PIPE=/absolute/path/to/process-events.fifoWine은 process fact만 보고하고, 어떤 프로세스가 Steam, HoYoPlay, updater, game인지 분류하는 책임은 런처에 있습니다. telemetry가 없는 runtime에서는 기존 wineserver observer와 process discovery가 fallback으로 동작합니다.
실행 구조는 점진적으로 Provider/Strategy 구조로 이동 중입니다.
src/Main/Data 아래의 각 application profile과 strategy가 실행 요구사항을 선언하고, BottleExecutionManager는 그 요구사항을 검사한 뒤 실제 side effect를 수행합니다.
현재 등록된 application-owned Strategy 범위는 다음과 같습니다.
BDIH-Launcher 저장소의 docs/는 다음 역할을 가집니다.
| 문서 | 역할 |
|---|---|
docs/build.md | 현재 내용이 없는 placeholder입니다. 실제 빌드 정보는 package scripts와 packaging config가 기준입니다. |
docs/release.md | Stable/Beta production promotion, candidate safety rule, version rule을 설명합니다. |
docs/publish.md | macOS signing, GitHub secret, staging candidate, certificate renewal을 설명합니다. |
docs/update-testing.md | update checker UI, mock feed, signing-certificate transition test 방향을 설명합니다. |
docs/test.md | isolated update test app, local feed, Stable/Beta/Nightly update 시나리오를 설명합니다. |
docs/data-lifecycle.md | channel-safe cleanup, app data ownership, snapshot 규칙을 설명합니다. |
docs/bottle-execution.md | Bottle 실행 구조, Guardian, execution state, Provider/Strategy migration을 설명합니다. |
docs/TODO/wine-process-telemetry.md | Wine process telemetry prototype과 남은 검증 항목을 정리합니다. |
docs/TODO/bottle-app-launch-deduplication.md | 중복 실행 방지와 logical target ownership 규칙을 정리합니다. |
런처 빌드나 배포를 변경할 때는 단순히 pnpm build 성공만 확인하지 말고, 변경 범위에 따라 위 문서의 정책과 테스트도 함께 확인해야 합니다.