빌드·배포 대상 표 — 웹·관리자·문서·Edge·API·iOS·Android 의 진입점·환경 값·도구·버전 원천 (이슈 #1332, B02)
저장소 분리 적용 범위 (#1463): 이 문서의 실행 코드·DB·앱 release 절차는
dekerd/Barbelic에 적용한다. 문서·관리자·랜딩은 각각의 독립 저장소 절차를 따른다. 과거 docs/admin 결합·앱 main 문서 직행 설명은 분리 전 이력이며 저장소 경계와 독립 운영·실제 전환 상태가 그 부분을 대체한다.
2026-09-09 개정 (이슈 #1456·#1457·#1458, 오너 결정) — 명령·워크플로 이름이 바뀌었다. 작업 PR 통합 요청
npm run merge:request(구 landing:request), PR 전 사전 검증npm run ci:precheck-local(최신 release 합치기 → verify → 서버 변경 시 DB 단계, 브라우저 없음), 전체 검증npm run ci:full-local(릴리스 큐 runner 안에서만; 세션은--only <단계>재현만). 워크플로 표시 이름:3-Release Merge Request(release-merge-request.yml) ·GitHub Full CI(policy-contract.yml) ·1-Production Deploy/2-Staging Deploy(production-deploy.yml·staging-deploy.yml, 단계 정의는 deploy-steps.yml) ·Production smoke (manual)·Social login daily check·Daily data backup.landing:lock·db:preflight스크립트는 폐기됐다(GitHub concurrency 가 잠금, DB 검사는 사전 검증과 큐가 한다). 아래 본문의 옛 이름은 그 개정 전 기록이다.
2026-09-09 배포 브랜치 전환: 작업 → release → staging → main(Production)의 정본은 릴리스 프로세스다. 아래 대상 표는 새 코드의 main=Production, staging=staging 매핑이다. 앱과 docs/admin은 같은 SHA·환경으로 DB·Edge 이후 Actions에서 배포한다.
DEPLOYMENT_BRANCH_CUTOVER=true활성화와 실제 배포 검증은 별도 완료 항목이며 기존 Supabase 프로젝트·secret·네이티브 연결 값은 유지한다. 앱·docs의 두 환경 브랜치, docs staging 공개 Supabase 값·BARBELIC_TARGET,VERCEL_DOCS_PROJECT_ID는 설정 확인을 마쳤다. 로컬 산출물 검증도 통과했지만 원격 main 설치·활성화·실제 Linux Deploy는 미완료다.
한 문장 — 같은 소스가 여러 곳으로 나갈 때 "어느 환경으로 나가는 빌드인가" 를 추측하지 않게 하는 표다. 배포 대상 값의 원천은
deployment-targets.json하나이고, 그 값을 옮겨 적은 파일은npm run check:deployment가 대조한다. 빌드는 자기 대상을 선언해야 하고(BARBELIC_TARGET또는 Vercel 시스템 변수), 대상과 Supabase 값이 어긋나면 빌드가 실패한다 — 값이 없다고 Production 으로 대체하지 않는다.
- 이슈: #1332 (계획 ID B02). 코드:
vite.deploymentTarget.mjs(대상 선언·검증·index.html 도장),scripts/check-build-artifact.mjs(산출물 검사),scripts/check-deployment-pipeline.mjs(복사본 대조). 테스트:tests/react/deploymentTargets.test.mjs·appConfigEnv.test.mjs. - 누가 쓰나: 배포 워크플로(
deploy-steps.yml(1-Production Deploy·2-Staging Deploy 가 호출)), CI(policy-contract.yml),npm run ci:local, N01(네이티브 셸)·R05(릴리스 리허설)·U05(번들 기준). - 관련 정본: 배포 파이프라인 · 환경 분리 런북 · HQ 운영 정본 §3·§4.
1. 읽는 법 — 운영자 A 의 하루로
운영자 A 가 staging 에 새 빌드를 올린다. 예전에는 Vercel staging 환경 변수에서 VITE_SUPABASE_URL 을 빠뜨려도 빌드가 조용히 성공했고, 앱은 Production 데이터베이스를 바라봤다. 테스트 계정으로 기록을 저장하면 Production 에 들어갔다. 지금은 빌드가 "staging 빌드에 VITE_SUPABASE_URL 이 없습니다. 값이 없다고 Production 을 바라보게 두지 않습니다" 라는 오류로 멈춘다. 빌드가 통과했다면 산출물의 index.html 첫 줄에 __BARBELIC_DEPLOYMENT__={"target":"staging","supabaseUrl":"https://ajqd….supabase.co",…} 가 새겨져 있고, 배포 워크플로는 배포 직전에 그 줄을 다시 읽어 대상이 맞는지, 잡 환경의 비밀값이 산출물에 없는지 확인한다(값은 출력하지 않는다).
2. 배포 대상 (deployment-targets.json)
| 대상 | Supabase 프로젝트 | Vercel 환경 | 브랜치 | 어떻게 정해지나 | VITE_SUPABASE_* 규칙 |
|---|---|---|---|---|---|
production | kobxeylancdimqhfkbnl | production | main | deploy-steps.yml(1-Production Deploy·2-Staging Deploy 가 호출) 이 BARBELIC_TARGET=production 으로 빌드 | 없어도 됨(원천 파일 값 사용). 있으면 Production 값과 같아야 함 |
staging | ajqddlglezvxzsawquku | staging(커스텀 환경) | staging | deploy-steps.yml(1-Production Deploy·2-Staging Deploy 가 호출) 이 BARBELIC_TARGET=staging 으로 빌드 | 필수, staging 프로젝트여야 함 |
preview | ajqddlglezvxzsawquku | preview | PR 브랜치(claude/ui-*) | Vercel git 통합 빌드: VERCEL_ENV=preview 자동 | 필수, staging 프로젝트여야 함 |
local | (선택) | development | — | BARBELIC_TARGET=local npm run build · npm run dev 기본 | 없으면 Production 값(로컬 미리보기·e2e 는 __BARBELIC_E2E_CONFIG__ 가 덮는다). 있으면 그대로 |
대상 판정 순서: BARBELIC_TARGET → (Vercel 이면) VERCEL_TARGET_ENV → VERCEL_ENV → (vite dev 만) local. vite build 에 대상이 없으면 실패. Vercel 시스템 변수는 프로젝트 설정 "Enable access to System Environment Variables" 가 켜져 있어야 노출된다(기본 켜짐). GitHub Actions 배포는 시스템 변수에 기대지 않고 BARBELIC_TARGET 을 명시한다.
관리자 셸도 배포 환경을 따른다. Actions가 BARBELIC_TARGET=staging 또는 production을 명시하고, 관리자(별도 Vercel 프로젝트, 자체 주소 — deployment-targets.json 환경별 adminOrigin, Production https://admin.barbelic.com, #1666)는 해당 환경의 Supabase를 본다. 배포 뒤 deploy-steps.yml이 adminOrigin으로 앱 서버 관리자 API에 CORS 사전 요청을 보내 허용 목록(Vercel 변수 ADMIN_ALLOWED_ORIGINS)을 확인한다. staging 관리자 빌드에는 staging 공개 URL·키가 반드시 필요하다. vite.admin.config.mjs의 fixedTarget: "production"은 명시값이 없을 때의 기본값이며 BARBELIC_TARGET이 우선한다. 새 배포 경로는 이 기본값에 기대지 않고 앱과 같은 SHA·환경을 전달한다. main/staging 직접 Git 배포를 껐으므로 순수 Markdown을 CI·배포 생략 예외로 합친 경우 문서 사이트는 다음 해당 환경 배포에서 갱신된다.
3. target 표 — 진입점·환경 값·runtime·toolchain·버전 원천
| target | 진입점·빌드 명령 | 산출물·배포 | 공개 환경 값(번들에 실림) | 비밀(서버 전용, 번들 금지) | 읽는 코드 → 전달 경로 | runtime | toolchain 원천 | 버전 원천 |
|---|---|---|---|---|---|---|---|---|
| web 앱 | index.html → src/react/vite/main.tsx · npm run build(vite.config.mjs) | dist-vite/ → Vercel barbelic(vercel.json; main/staging은 DB·Edge 이후 Actions CLI, PR 프리뷰는 git 통합) | VITE_SUPABASE_URL·VITE_SUPABASE_PUBLISHABLE_KEY(선택, 대상과 일치해야 함) · BARBELIC_TARGET/VERCEL_*(대상 판정) | 없음 — VITE_ 이름에 SERVICE_ROLE·SECRET·PASSWORD·TOKEN 이 들어오면 빌드 실패 | vite.deploymentTarget.mjs 가 검증 → index.html __BARBELIC_DEPLOYMENT__ → app-config.js → supabaseAuth.ts·connectivityAppAdapter.ts·appErrorReporter.ts | 브라우저(iOS WKWebView·Android WebView 포함) | Node 22(워크플로)·Vite package.json | 릴리스 SHA = __BARBELIC_RELEASE__(VERCEL_GIT_COMMIT_SHA → GITHUB_SHA → dev) · 마이그레이션 = deploymentManifest.ts EXPECTED_LATEST_MIGRATION |
| 관리자 셸 | admin/index.html → src/react/vite/admin/main.tsx · npm run build:admin(vite.admin.config.mjs) | dist-admin/admin/ → docs 빌드가 BARBELIC_ADMIN_OUT_DIR=.vitepress/dist 로 동봉 → 해당 환경 docs 도메인 /admin/ | 앱과 같은 BARBELIC_TARGET=staging 또는 production 및 해당 Supabase 공개 값 | 없음 | 위와 같음 | 브라우저(데스크톱) | Actions의 .nvmrc; 프리뷰는 docs Vercel 프로젝트 Node 설정 | 앱과 같은 SHA·app-config.js·deploymentManifest.ts |
| 문서 사이트 | docs/(VitePress) · npm run build(docs/package.json = vitepress build && node scripts/build-admin.mjs) | docs/.vitepress/dist → Vercel barbelic-docs(main/staging은 DB·Edge 이후 Actions, 프리뷰는 git 통합; docs/vercel.json) | 문서 본문에는 없음; 동봉 관리자 셸에는 위 공개 값 | 없음 | docs/scripts/build-admin.mjs 가 루트 vite 실행(루트 npm ci 필요); 별도 docs Vercel 프로젝트 ID는 저장소 변수로 전달 | 정적 | docs/package-lock.json(vitepress) | 동봉 관리자 셸은 같은 배포 SHA |
| Vercel API 함수 | api/account/delete.js·api/admin/{users,impersonate}.js·api/auth/test-admin/session.js | 앱 프로젝트와 함께 배포(vercel.json 기본 함수 설정) | 없음 | SUPABASE_URL·SUPABASE_PUBLISHABLE_KEY·SUPABASE_SERVICE_ROLE_KEY·ADMIN_IMPERSONATION_ENABLED·TEST_ADMIN_*·KAKAO_ADMIN_KEY·APPLE_* — Vercel 환경 변수 | process.env 직접(env(name) || ""), 빠지면 500(코드만 기록) | Node(Vercel 서버리스) | Vercel 프로젝트 Node 설정 | 앱과 같은 배포 |
| Edge 함수(Supabase) | supabase/functions/{wodup-start-import,wodup-process-import-jobs,stats-process-refresh-jobs} · supabase functions deploy | deploy-steps.yml(1-Production Deploy·2-Staging Deploy 가 호출) functions 잡 → 대상 프로젝트 | 없음 | SUPABASE_URL·SUPABASE_ANON_KEY·SUPABASE_SERVICE_ROLE_KEY(플랫폼 자동 주입) | Deno.env.get, 빠지면 500 supabase_env_missing | Deno(Supabase Edge Runtime) | Supabase CLI package.json supabaseToolchain.cli | 응답 헤더 x-barbelic-deployment-version = deploymentManifest.ts |
| iOS 셸 | ios/App(Capacitor, capacitor.config.json webDir: dist-vite) · Xcode Archive | App Store(수동, docs/platform/app-store-submission.md) | ios/{Debug,Release}.xcconfig → Info.plist: 서버 origin·인증 origin·콜백 scheme·app-bound domain (원천 native 항목) | 없음(서명 = Xcode 계정) | MainViewController.swift AppEnvironment(plist 없으면 같은 값 fallback) → WebView 가 https://www.barbelic.com/ 을 연다 | iOS 15+ WKWebView | Xcode(macOS 전용), CocoaPods/SPM | MARKETING_VERSION 1.0·CURRENT_PROJECT_VERSION 1(project.pbxproj) — 웹과 독립 |
| Android 셸 | android/(Capacitor) · npm run android:bundle(gradlew.bat bundleRelease) | Play(수동, docs/platform/android-release.md) | android/app/build.gradle def barbelic* → BuildConfig(원천 native 항목) | 서명 키 = android/keystore.properties(git 제외) | AppEnvironment.kt(빈 값이면 같은 값 fallback) → WebView | Android minSdk 24 / target 36 | JDK 21·Android SDK(variables.gradle), Windows/macOS 로컬 | versionCode 1·versionName "1.0.0"(build.gradle) — 웹과 독립 |
| Node 스크립트·테스트 | scripts/**·tests/**·e2e/** | — | app-config.js Node 전용 기본값(= Production 공개 값) | E2E_*·BARBELIC_DB_SANDBOX·SUPABASE_ACCESS_TOKEN 등 실행 환경 | process.env | Node 22 | package.json engines·supabaseToolchain | — |
import.meta.env 를 읽는 앱 코드는 app.tsx(DEV)·vite/main.tsx(DEV)·offlineServiceWorker.ts(PROD) 셋뿐이고 Supabase 값은 전부 app-config.js 한 곳을 지난다(2026-09-07 실측).
4. 경계 검사 — 무엇이 어디서 막히나
| 잘못 | 막는 곳 | 결과 |
|---|---|---|
vite build 에 대상이 없다 | vite.deploymentTarget.mjs resolveBuildTarget | 빌드 실패: "빌드 대상이 없습니다 … 기본값(Production)으로 대체하지 않습니다" |
staging/preview 빌드에 VITE_SUPABASE_* 가 없다 | validateBuildEnvironment | 빌드 실패(Production 으로 대체하지 않음) |
| production 빌드에 staging 값, 또는 그 반대 | validateBuildEnvironment | 빌드 실패(어느 프로젝트가 왔는지 ref 로 표시) |
VITE_SUPABASE_SERVICE_ROLE_KEY 처럼 서버 전용 이름이 VITE_ 로 들어온다 | validateBuildEnvironment | 빌드 실패(이름만 표시, 값은 읽지 않음) |
| 플러그인 없이 만든 번들 | app-config.js resolveBarbelicConfig | 번들이 시작을 거부(__BARBELIC_DEPLOYMENT__ 없음) |
| 산출물이 다른 대상용이거나 비밀값·source map 이 들어 있다 | scripts/check-build-artifact.mjs(deploy-steps.yml(1-Production Deploy·2-Staging Deploy 가 호출) 배포 직전, CI check:artifacts) | 배포 중단 |
| 원천 값의 복사본이 어긋난다(config.toml·deploy.yml·xcconfig·gradle·Swift/Kotlin·스크립트) | npm run check:deployment | check 실패 |
| 배포 비밀값(DB 비밀번호·smoke 계정·ref)이 없다 | deploy-steps.yml(1-Production Deploy·2-Staging Deploy 가 호출) 각 잡의 이름 검사 | 잡 실패(건너뜀 없음, 다른 환경 값으로 대체 없음) |
| Production smoke 가 다른 프로젝트를 친다 | deploy-steps.yml(1-Production Deploy·2-Staging Deploy 가 호출) smoke: URL 을 고정 ref 에서 만든다 | PROD_SUPABASE_URL 이 있으면 일치 검사 |
앱 서버의 관리자 API 허용 목록(ADMIN_ALLOWED_ORIGINS)에 그 환경의 관리자 주소가 없다 | deploy-steps.yml frontend: 공개 주소 확인 뒤 adminOrigin에서 보낸 OPTIONS /api/admin/deployment-manifest가 204인지(최대 6회, #1666) | 배포 실행 실패(배포 자체는 완료된 뒤의 확인 단계) |
4-1. 도구 버전의 원천과 CI 경계 (Phase 2)
| 도구 | 원천 | 읽는 곳 | 검사 |
|---|---|---|---|
| Node | .nvmrc(22; package.json engines 범위 안) | 모든 워크플로 setup-node 의 node-version-file, npm-install action 의 node_modules 캐시 키(hashFiles('.nvmrc')) | tests/react/ciToolchain.test.mjs — 손으로 적은 node-version: 0곳 |
| Vercel CLI | package.json vercelToolchain.cli(정확 버전) | deploy-steps.yml(1-Production Deploy·2-Staging Deploy 가 호출) frontend 잡(npm install --global vercel@<버전>) | 같은 테스트 — @latest 금지 |
| Supabase CLI·Postgres 이미지 | package.json supabaseToolchain(#1233) | deploy-steps.yml(1-Production Deploy·2-Staging Deploy 가 호출)·local-supabase-stack action·ci:local | tests/react/ciLocal.test.mjs |
supabase/setup-cli action | 커밋 SHA + 태그 주석(@ab05898… # v1.7.1) | 3곳 | 같은 테스트 — 서드파티 action 은 SHA 핀만 허용 |
| 의존성 lock | 루트 package-lock.json(npm ci 만) · docs/package-lock.json(CI docs-build 잡이 npm ci --prefix docs + 빌드) · 랜딩은 Barbelic-landing의 package-lock.json으로 독립 설치·전용 CI 검증 | — | npm-install action 은 lock 해시로 캐시 |
| Vercel 프로젝트의 Node | 대시보드 설정(.nvmrc 와 자동 연동되지 않음) | Vercel 빌드 | 오너 손 목록 §6 |
노출 경계(2026-09-07 검토 결과): source map 은 어느 빌드도 만들지 않는다(Vite 기본값 false, 독립 랜딩 설정은 명시 false). 빌드 산출물(dist-*·.vercel/output·.vitepress/dist)은 artifact 로 올리지 않는다. 비밀은 env: 로만 셸에 건네고 run: 본문에 워크플로 표현식(secrets.*)을 직접 끼우지 않으며 $GITHUB_ENV 로 잡 전체에 퍼뜨리지 않는다. Production 카나리(prod-smoke.yml)는 E2E_EVIDENCE=off 로 trace·video·screenshot 을 만들지 않고 결과 JSON 만 7일 보관한다. 모든 워크플로는 최상위 contents: read, 쓰기 권한은 실제로 쓰는 잡(release-tag 태그 생성, landing-queue 머지·댓글·workflow dispatch)에만 있다. 매일 사본(daily-data-backup.yml)의 90일 artifact 는 저장소 읽기 권한자 전원이 내려받을 수 있는 유저 데이터 사본이다 — 의도된 PITR 대체(S11 소유)이며 여기서 바꾸지 않는다.
2026-09-09 랜딩 분리 후속: 랜딩 전용 CI는 독립 저장소 main 16c6d32의 Landing CI에서 검증됐다. 앱의 이전 landing-page/ 복사본 제거는 #1477, 통합 PR #1484로 v0.17.7 release와 staging에 반영됐다. main·Production 완료는 이 기록에서 확정하지 않는다. 별도 호스팅 연결·공개 배포·도메인 이전의 완료를 의미하지 않는다(현재 실행·배포 안내).
5. 로컬에서 쓰는 법
# 로컬 미리보기·e2e 빌드 (Production 공개 값, e2e 는 런타임 override)
BARBELIC_TARGET=local npm run build
# staging 번들을 손으로 만들어 보기 (값은 Vercel staging 환경과 같아야 한다)
BARBELIC_TARGET=staging VITE_SUPABASE_URL=https://ajqddlglezvxzsawquku.supabase.co VITE_SUPABASE_PUBLISHABLE_KEY=<staging 공개 키> npm run build
# 산출물 검사
node scripts/check-build-artifact.mjs --dir dist-vite --target stagingPowerShell 은 $env:BARBELIC_TARGET='local'; npm run build. npm run dev 는 대상 없이 local 로 뜨고 어느 프로젝트를 보는지 한 줄 알린다.
6. 오너 손이 필요한 것 (진행을 막지 않음)
- Vercel
barbelic·barbelic-docs프로젝트의 "Enable access to System Environment Variables" 가 켜져 있는지(기본 켜짐). 꺼져 있으면 PR 프리뷰(git 통합) 빌드가 대상을 알 수 없어 실패한다 — 실패가 곧 안내다. - Vercel Production 환경에
VITE_SUPABASE_*를 명시하면 원천 파일 기본값 의존이 사라진다(선택). - Vercel 두 프로젝트의 Node 버전이
.nvmrc(22.x)와 같은지 확인(대시보드 설정, 자동 연동 없음).
7. 호환 표 — 관리자 · 웹 · 서버 · 설치본 (Phase 4)
웹 앱과 docs/admin은 같은 SHA·환경의 DB·Edge가 준비된 뒤 배포하고, 네이티브 설치본은 별도로 출시한다. "지금 사용자가 보는 조합"은 아래 표의 행들이 만나는 것이다. npm run check:deployment 가 이 표의 설치본 버전·서비스 워커 캐시 버전·Edge 배포 버전 문자열을 실제 파일(gradle·pbxproj·sw.js·deploymentManifest.ts)과 대조한다 — 값을 올리면 표도 같은 PR 에서 고친다.
| 표면 | 나가는 시점 | 가리키는 서버 | 버전 원천(현재 값) | 다른 표면과의 호환 규칙 |
|---|---|---|---|---|
웹 앱 (www.barbelic.com) | staging → main 승격 PR 머지(오너) 후 DB·Edge 배포 성공 | Production Supabase | 릴리스 SHA(__BARBELIC_RELEASE__) · EXPECTED_LATEST_MIGRATION(deploymentManifest.ts, check:deployment 가 마이그레이션 꼬리와 대조) | DB 먼저·함수·프론트 순으로 나가므로 프론트는 항상 자기보다 새롭거나 같은 DB 를 본다(deploy-steps.yml(1-Production Deploy·2-Staging Deploy 가 호출) 잡 의존). 열린 탭의 구 번들은 staleChunkRecovery 로 새 청크를 받는다 |
| 스테이징 앱 | release/vX.Y.Z → staging 승격 PR 머지 후 DB·Edge 배포 성공 | staging Supabase(ajqddlglezvxzsawquku) | 같은 SHA·같은 manifest | Production 과 만나지 않는다(대상 도장이 staging 이 아니면 배포 중단, §4) |
관리자 셸 (각 환경 docs 도메인 /admin/) | 앱과 같은 SHA·환경의 DB·Edge 배포 성공 후 Actions 배포 | staging은 staging Supabase, Production은 Production Supabase | 앱과 같은 SHA·app-config.js·deploymentManifest.ts | 서버 계약 배포 뒤 관리자 셸이 나간다. 관리자 게이트 admin-hold/admin-compat-confirmed도 유지하여 지원 대상 서버 계약과의 호환을 검토한다 |
| Edge 함수 (3개) | 릴리스 PR 머지(functions 잡) | 자기 프로젝트 | 응답 헤더 x-barbelic-deployment-version = 20260821-admin-on-behalf-import-v1(wodup 2개) · 20260821-barbelic-headers-stats-v1(stats) | 관리자 운영 화면이 헤더를 읽어 manifest 와 대조. 함수 이름·목록은 config.toml·폴더·manifest 3자 일치를 check:deployment 가 강제 |
서비스 워커 (public/sw.js) | 웹 앱과 함께 | — | CACHE_VERSION = barbelic-offline-v1 | 셸 자원만 캐시하고 API/auth 는 캐시하지 않는다. 캐시 형식을 바꾸면 버전을 올려 옛 저장소를 비운다(N01 소유) |
| iOS 설치본 | App Store 심사(오너 Mac, 수동) | Production(origin 고정: www.barbelic.com·인증 origin, xcconfig) | iOS 1.0 (1) = MARKETING_VERSION / CURRENT_PROJECT_VERSION(project.pbxproj) | 설치본은 웹 번들을 싣지 않고 origin 을 연다 → 항상 현재 Production 웹을 본다. 셸이 주입하는 capability: secureAuthSessionV1·securePkceVerifierV1·socialAuthV1·splashOwnerV1·appleAccountDeletionReauthV1·shareFileV1. 웹은 플래그가 없으면 브라우저 경로로 동작하고, 새 셸이 필요한 기능은 LG426(앱 업데이트 안내)로 막는다 |
| Android 설치본 | Play 업로드(수동, docs/platform/android-release.md) | Production(origin 고정, build.gradle → BuildConfig) | Android 1.0.0 (1) = versionName / versionCode(build.gradle) | 같은 규칙. capability: secureAuthSessionV1·securePkceVerifierV1·socialAuthV1(iOS 의 splashOwnerV1·appleAccountDeletionReauthV1·shareFileV1 는 없음 → 웹이 스플래시를 그린다) |
허용 조합의 뜻: 설치본 × 웹 은 어떤 설치본이든 현재 Production 웹과 만난다(웹이 capability 로 분기). 관리자 × 서버 는 관리자 게이트가 지킨다. 웹 × 서버 는 배포 순서가 지킨다. 구·신 설치본과 열린 탭·IDB 의 실제 공존 실험은 R02 몫이다(이 표는 그 실험의 행 목록이다).
8. native release 빌드 증거와 인계 (Phase 4)
8-1. Android — 이 PC(Windows)에서 실제 실행
| 항목 | 값 |
|---|---|
| 명령 | BARBELIC_TARGET=production npm run build → npx cap sync android → npm run android:bundle(= android/gradlew.bat bundleRelease). Git Bash 에서 gradlew.bat 를 상대 이름으로 부르면 cmd 가 찾지 못한다 — PowerShell 이나 전체 경로로 부른다 |
| toolchain(실측) | Windows 11 · JDK 21.0.12(Microsoft, JAVA_HOME) · Android SDK build-tools 36.0.0 / platform android-36(ANDROID_HOME=%LOCALAPPDATA%\Android\Sdk) · Gradle wrapper 8.14.3(--no-daemon) · AGP 8.13.0 · Kotlin 2.2.0 · @capacitor/core 8.5.0·@capacitor/android 8.5.0·@capacitor/cli 8.4.0(설치본 기준) |
| 서명 | android/keystore.properties 없음 → 서명 없는 번들. 업로드 키 서명·Play App Signing 검증은 R05(오너 keystore) |
| 결과(2026-09-07 11:00Z, 세션 7cd84cdd) | BUILD SUCCESSFUL in 1m 3s(122 tasks) → android/app/build/outputs/bundle/release/app-release.aab 6,289,676 bytes · SHA-256 9ec568f78db6210cbf44a7413fdefbe263e3929dfb8806646253c2bc4b2daa4b · 번들 안 base/assets/public/index.html 도장 = target:"production"·kobxeylancdimqhfkbnl·release:"dev"(로컬 빌드라 커밋 SHA 없음 — Actions/Vercel 빌드에서만 SHA 가 들어간다) · 서버 비밀 문자열 0건 · 최상위 META-INF/*.RSA·.SF 없음(= 서명 없음). 빌드 로그는 세션 scratchpad(레포 밖) |
| 경고 | Kotlin 경고 10건(NativeBridge.kt 163·164·165·182·200·201·213·242·243·244 — Java 타입 Nothing? 를 String 자리에 넘김). 빌드는 통과하며 셸 코드는 N01 소유라 여기서 고치지 않는다 |
| 설치본 버전 | versionName "1.0.0"·versionCode 1(§7 표와 check:deployment 대조) — Play 업로드마다 둘 다 올린다 |
8-2. iOS — Windows 라 미실행 (R05 인계)
Swift 컴파일·Archive 는 macOS Xcode 에서만 된다(docs/platform/ios-setup.md §"Windows"). Windows 의 웹 빌드나 config 문자열 검사로 대신하지 않는다. R05 가 오너 Mac 에서 아래를 실행하고 산출물 버전을 이 표에 적는다:
BARBELIC_TARGET=production npm run build && npx cap sync ios- Xcode:
App타깃 General → Version(MARKETING_VERSION)·Build(CURRENT_PROJECT_VERSION) 확인 → Product → Archive → Distribute → App Store Connect(docs/platform/app-store-submission.md§제출 절차). - 기록할 것: Xcode 버전·macOS 버전·Archive 의 Version/Build·업로드 시각·
ios/App/App.xcodeproj의 두 값이 표와 같은지. 결과를 이 절과 R05 리허설 문서에 적는다.
8-3. N01 인계 — 설치본/웹/서버 조합과 빌드 근거
- 설치본은 웹 번들을 싣지 않고 origin 을 연다: 설치본 버전은 "어느 웹" 이 아니라 "어느 bridge capability" 를 뜻한다(§7 표). N01 이 bridge·SW·IDB 호환 matrix 를 만들 때 이 표의 capability 목록과
CACHE_VERSION을 행으로 쓴다. - 환경 값(origin·scheme·app id)은
deployment-targets.jsonnative항목이 원천이고 xcconfig·gradle·Swift/Kotlin fallback 은check:deployment가 대조한다. N01 이 셸 코드를 바꿔도 이 값들은 손대지 않는다.