create-saas-starter-workspace 0.1.8 → 0.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/workspace/.claude/skills/app-launch/SKILL.md +82 -0
- package/dist/workspace/.claude/skills/{release → app-update}/SKILL.md +5 -5
- package/dist/workspace/.claude/skills/bridge-guide/SKILL.md +43 -26
- package/dist/workspace/.claude/skills/eas-deploy-guide/SKILL.md +37 -79
- package/dist/workspace/.claude/skills/native-app-guide/SKILL.md +43 -9
- package/dist/workspace/.claude/skills/probe/SKILL.md +14 -2
- package/dist/workspace/.claude/skills/sketch/SKILL.md +14 -8
- package/dist/workspace/.claude/skills/store-release-guide/SKILL.md +54 -57
- package/dist/workspace/.claude/skills/web-launch/SKILL.md +59 -0
- package/dist/workspace/AGENTS.md +1 -1
- package/dist/workspace/START-HERE.md +3 -6
- package/dist/workspace/mobile/.env.example +3 -1
- package/dist/workspace/mobile/AGENTS.md +9 -1
- package/dist/workspace/mobile/README.md +1 -1
- package/dist/workspace/mobile/eas.json +2 -2
- package/dist/workspace/scripts/connect-repos.mjs +1 -1
- package/dist/workspace/web/.env.example +1 -1
- package/dist/workspace/web/AGENTS.md +8 -8
- package/dist/workspace/web/README.md +6 -7
- package/dist/workspace/web/docs/ENVIRONMENTS.md +8 -4
- package/dist/workspace/web/instrumentation-client.ts +4 -2
- package/package.json +1 -1
- package/src/scaffold.mjs +1 -1
- package/dist/workspace/.claude/skills/go-live/SKILL.md +0 -68
- package/dist/workspace/.claude/skills/kickoff/SKILL.md +0 -72
- package/dist/workspace/.claude/skills/launch/SKILL.md +0 -69
- package/dist/workspace/.claude/skills/preview/SKILL.md +0 -43
- package/dist/workspace/.claude/skills/vercel-cron/SKILL.md +0 -17
- package/dist/workspace/.claude/skills/warmup/SKILL.md +0 -33
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: store-release-guide
|
|
3
|
-
description: 스토어 출시
|
|
3
|
+
description: 스토어 출시 정책·콘솔·심사, 그리고 개발자 계정 비용·테스트 게이트의 정본(SSOT). 개발자 계정 가입(Apple 유료 연구독·Google 1회 등록비)·Apple 4.2/4.2.2 웹뷰 래퍼 심사 통과 전략·소셜 로그인 OAuth 콘솔 셋업·Play 비공개(closed) 테스트 게이트·TestFlight 테스터 관리·App Store "Submit for Review"·App Privacy/데이터 안전 라벨·계정 삭제 컴플라이언스 작업 시 자동 로드됩니다. 다른 스킬이 가격·정책 수치를 물으면 여기를 가리킵니다. (EAS 빌드·제출 명령·OTA는 eas-deploy-guide.)
|
|
4
4
|
user-invocable: false
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# 스토어 셋업·출시 가이드 (출시 준비 + 소셜 로그인 fast-follow)
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
스토어 출시 정책·콘솔 절차·심사 전략의 정본이다. 앱 출시는 AI가 단계별로 안내하되, 돈·신원·2FA가 걸린 일은 Owner가 직접 한다.
|
|
10
|
+
|
|
11
|
+
이 문서는 **외부 벤더 수치(개발자 등록비·테스트 게이트·EAS 무료 티어)의 SSOT**다. 다른 스킬(`app-launch`·`app-update`·`eas-deploy-guide`·`native-app-guide`)은 이 숫자를 재진술하지 않고 여기를 가리킨다. 그래서 드리프트가 한 곳에서만 일어난다. 단 이 숫자들조차 벤더가 언제든 바꾸므로 **여기 적힌 값은 스냅샷**이다 — 각 값에 공식 출처와 `(2026 기준 · verify live)` 스탬프를 달았고, Owner에게 안내하기 전 그 URL로 현재값을 한 번 확인한다.
|
|
12
|
+
|
|
13
|
+
## 빠른 참조 (값은 여기서 읽는다)
|
|
14
|
+
|
|
15
|
+
- 빌드 명령·프로파일·`EXPO_PUBLIC_*`·OTA 판단: `eas-deploy-guide`. 이 문서는 콘솔·계정·심사만 다룬다.
|
|
16
|
+
- 앱 셸이 v1에 채우는 표면·세션 핸드오프 배선: `native-app-guide`·`bridge-guide`(코드가 SSOT).
|
|
17
|
+
- 외부 정책·동작 주장의 권위 있는 출처는 본문 인라인 링크 + 맨 아래 [출처](#출처-sources). 메모리로 답하지 않는다.
|
|
11
18
|
|
|
12
19
|
## 누가 무엇을 하나 (가역성 분류)
|
|
13
20
|
|
|
@@ -23,14 +30,14 @@ user-invocable: false
|
|
|
23
30
|
|
|
24
31
|
| | Apple Developer Program | Google Play Console |
|
|
25
32
|
|---|---|---|
|
|
26
|
-
| 비용 |
|
|
33
|
+
| 비용 (스냅샷) | **~$99 / 년** (갱신형, 환불 불가) | **~$25** (1회, 환불 불가) |
|
|
27
34
|
| 계정 유형 | Individual 권장 (D-U-N-S 회피, 신원 검증만) | Personal 권장 |
|
|
28
35
|
| 판매자명 | 개인 법적 이름이 **공개 판매자명**이 된다 (프라이버시 1줄 confirm) | 동일 |
|
|
29
|
-
| 리드타임 함정 | 신원 검증 수 시간~2일 | **신규 개인계정은 production 전 비공개(closed) 테스트
|
|
36
|
+
| 리드타임 함정 | 신원 검증 수 시간~2일 | **신규 개인계정은 production 전 비공개(closed) 테스트 게이트 강제** (내부 테스트는 카운트 X·조직계정 면제) |
|
|
30
37
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
38
|
+
비용은 스냅샷이다 — Owner에게 액수를 말하기 전 공식 페이지로 현재값을 확인한다([Apple Developer Program](https://developer.apple.com/programs/) · [Google Play 등록](https://support.google.com/googleplay/android-developer/answer/6112435)) (2026 기준 · verify live).
|
|
39
|
+
|
|
40
|
+
리드타임을 지배하는 건 Apple 신원 검증이 아니라 **Google의 비공개 테스트 게이트**다: 신규 개인계정은 production 전에 closed 트랙에서 **테스터 12명·14일 연속**을 채워야 한다([Play 정책](https://support.google.com/googleplay/android-developer/answer/14151465)) (2026 기준 · verify live — 인원·일수는 Google이 조정해 왔다). 그래서 **Google Play Console 가입을 가장 먼저** 해 둔다. Apple도 $99 결제 후 멤버십 활성화에 시간이 걸린다. 둘 다 코드 작업 전에 시작한다.
|
|
34
41
|
|
|
35
42
|
이 둘은 AI가 대신 가입하지 못한다 (카드·신원·2FA). AI는 가입 화면과 입력값을 안내한다.
|
|
36
43
|
|
|
@@ -38,12 +45,11 @@ user-invocable: false
|
|
|
38
45
|
|
|
39
46
|
## 2. Apple Guideline 4.2 — 웹뷰 래퍼 심사 통과 (가장 중요)
|
|
40
47
|
|
|
41
|
-
웹사이트를 그대로 비추는 얇은 웹뷰는 4.2.2(웹 클리핑·애그리게이터) 정석 거절 사유다 ([App Review Guidelines §4.2](https://developer.apple.com/app-store/review/guidelines/)).
|
|
42
|
-
통과의 핵심은 폰이기 때문에 가능한 진짜 네이티브 차별 요소가 하나라도 있어야 한다는 것이다.
|
|
48
|
+
웹사이트를 그대로 비추는 얇은 웹뷰는 4.2.2(웹 클리핑·애그리게이터) 정석 거절 사유다 ([App Review Guidelines §4.2](https://developer.apple.com/app-store/review/guidelines/)). 웹뷰 앱이 막히는 1순위가 4.2 / 4.2.2다. 통과의 핵심은 폰이라서 가능한 진짜 네이티브 차별 요소가 하나라도 있어야 한다는 것이다.
|
|
43
49
|
|
|
44
50
|
### 셸이 실제로 채우는 것 (정직하게)
|
|
45
51
|
|
|
46
|
-
셸은 화면을 만들지 않는다. 웹뷰가 막거나 없는 네이티브 빈틈만 메운다.
|
|
52
|
+
셸은 화면을 만들지 않는다. 웹뷰가 막거나 없는 네이티브 빈틈만 메운다. 확실히 네이티브 전용인 표면은 오프라인 화면뿐이고, 로딩·에러·로그인은 웹과 겹치거나 웹의 것이다.
|
|
47
53
|
|
|
48
54
|
| 요소 | v1 상태 | 설명 |
|
|
49
55
|
|---|---|---|
|
|
@@ -53,7 +59,7 @@ user-invocable: false
|
|
|
53
59
|
| 외부 링크 네이티브 처리 | ✅ v1 | 외부 origin·`mailto:`·`tel:`은 시스템 브라우저/앱으로 (아웃바운드) |
|
|
54
60
|
| safe-area(노치) 여백 | ✅ v1 | 네이티브가 측정한 inset을 CSS 변수로 웹에 주입 |
|
|
55
61
|
| 카메라·마이크·파일 업로드 권한 연결 | ✅ v1 (enablement) | 기능 자체는 웹(`getUserMedia`)·라이브러리(`<input type=file>` 피커)의 것. 셸은 동작하도록 권한 배선만 더한다 (§3·아래) |
|
|
56
|
-
| 푸시 알림 | ❌ v1 부재 | 4.2를 위해 권장하는 네이티브 차별 요소. v1엔
|
|
62
|
+
| 푸시 알림 | ❌ v1 부재 | 4.2를 위해 권장하는 네이티브 차별 요소. v1엔 없고(앱 의존성·기능 없음), 요청 시 AI가 구현한다 |
|
|
57
63
|
| 생체인증 (Face ID 등) | ❌ v1 부재 | 진짜 네이티브 전용. v1 이후 |
|
|
58
64
|
| 네이티브 소셜 로그인 (Google·Apple 원클릭) | ❌ v1 부재 | onPress 없는 disabled 버튼만 존재(Apple은 iOS 한정). 켜려면 OAuth 클라이언트 ID 발급(§3) + 네이티브 SDK→idToken→`requestSessionHandoff` 배선이 필요하다. "준비 중" 라벨은 자동 활성화가 아니라 미구현 표시다 |
|
|
59
65
|
|
|
@@ -82,14 +88,13 @@ user-invocable: false
|
|
|
82
88
|
|
|
83
89
|
### 왜 소셜은 네이티브여야 하나 (in-webview OAuth는 동작하지 않는다)
|
|
84
90
|
|
|
85
|
-
|
|
86
|
-
> 그래서 소셜 로그인은 반드시 **네이티브**(시스템 브라우저 / 네이티브 credential picker)여야 한다.
|
|
91
|
+
임베디드 웹뷰 안에서는 소셜 OAuth가 작동하지 않는다. Google은 임베디드 웹뷰의 OAuth를 차단하고([`disallowed_useragent`](https://developers.googleblog.com/upcoming-security-changes-to-googles-oauth-20-authorization-endpoint-in-embedded-webviews/)), [RFC 8252](https://datatracker.ietf.org/doc/html/rfc8252)는 임베디드 user-agent를 금지한다. 그래서 소셜 로그인은 반드시 **네이티브**(시스템 브라우저 / 네이티브 credential picker)여야 한다. 이건 모델이 일반화로 틀리는 침묵 함정이다 — 웹에서 되니 웹뷰에서도 될 거라 가정하면 무한 로딩으로 끝난다.
|
|
87
92
|
|
|
88
93
|
### 규칙: 소셜을 쓰면 Sign in with Apple 강제 (Apple 4.8 — 조건부)
|
|
89
94
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
95
|
+
4.8은 조건부다. Google이나 다른 서드파티 소셜 로그인을 (주 계정용으로) 하나라도 켜면, iOS에서는 "Sign in with Apple"을 동등하게 넣어야 한다. 웹이 Supabase 이메일/비밀번호만 쓰면 4.8은 적용되지 않는다.
|
|
96
|
+
|
|
97
|
+
소셜을 켤 경우, 이건 구조적 불변식이자 백엔드(Supabase Apple provider) 실배선까지 진단 대상이다. "Google만 켜고 Apple은 나중에"는 불가능하다 — 둘은 같이 켜진다.
|
|
93
98
|
|
|
94
99
|
### Google OAuth 클라이언트 ID (3종)
|
|
95
100
|
|
|
@@ -113,32 +118,21 @@ Google은 플랫폼마다 OAuth 클라이언트가 필요하다. 코드에는 **
|
|
|
113
118
|
| 방식 | 필요한 것 | 권장 |
|
|
114
119
|
|---|---|---|
|
|
115
120
|
| **네이티브 idToken** (기본) | bundle ID만 | ✅ — Services ID 회피 |
|
|
116
|
-
| 웹 OAuth | Apple Services ID + `.p8` 키 +
|
|
121
|
+
| 웹 OAuth | Apple Services ID + `.p8` 키 + 주기적 시크릿 갱신 | 피한다 |
|
|
117
122
|
|
|
118
|
-
|
|
119
|
-
> 이건 웹 OAuth 방식에만 해당한다. 기본인 네이티브 idToken 방식을 쓰면 Services ID 자체가 없어 이 요구를 회피한다.
|
|
120
|
-
> 그래서 한국 Owner에게는 네이티브 idToken 방식을 강력 권장한다.
|
|
123
|
+
한국 개발자는 Apple Services ID 등록/갱신 시 server-to-server 엔드포인트가 필수가 됐다 ([Apple 공지](https://developer.apple.com/news/?id=j9zukcr6)). 이건 웹 OAuth 방식에만 해당하므로, 기본인 네이티브 idToken 방식을 쓰면 Services ID 자체가 없어 이 요구를 회피한다. 그래서 한국 Owner에게는 네이티브 idToken 방식을 강력 권장한다.
|
|
121
124
|
|
|
122
|
-
핸드오프 함수(`requestSessionHandoff`)는 구현·테스트됐으나 호출자가 없다. 켜려면 입력인 네이티브 idToken 획득도 미배선이고, 출력을 받는 웹 라우트(POST `/auth/app-bridge` + nonce consume)는 이 앱이 아니라 `docs/web-adapter`에서 웹 repo에 설치해야 한다. `secureSession` get/set도 v1 호출자 없음. 기본 로그인은 웹의 것이다. 실제 켜기·콘솔 셋업은 fast-follow이며 이 문서가 단계별 절차를
|
|
125
|
+
핸드오프 함수(`requestSessionHandoff`)는 구현·테스트됐으나 호출자가 없다. 켜려면 입력인 네이티브 idToken 획득도 미배선이고, 출력을 받는 웹 라우트(POST `/auth/app-bridge` + nonce consume)는 이 앱이 아니라 `docs/web-adapter`에서 웹 repo에 설치해야 한다. `secureSession` get/set도 v1 호출자 없음. 기본 로그인은 웹의 것이다. 실제 켜기·콘솔 셋업은 fast-follow이며 이 문서가 단계별 절차를 담는다.
|
|
123
126
|
|
|
124
127
|
---
|
|
125
128
|
|
|
126
129
|
## 4. EAS Build + Submit 흐름 (🟢 대부분 자동)
|
|
127
130
|
|
|
128
|
-
EAS가 서명을 관리한다. Owner가 fastlane을 직접 구성할 일은 없다. `eas build`/`eas submit`이 빌드 + 테스트 채널(TestFlight / Play Internal) 업로드까지 해주고, 나머지(스토어 리스팅·스크린샷·App Privacy·"Submit for Review"·Play production 승급·SA JSON)는 GUI/수동이다.
|
|
129
|
-
|
|
130
|
-
```bash
|
|
131
|
-
# 1) 빌드 (클라우드) — production 빌드 하나로 테스트와 출시를 모두 충당
|
|
132
|
-
npx eas-cli build --profile production --platform all # iOS IPA + Android AAB
|
|
131
|
+
EAS가 서명을 관리한다. Owner가 fastlane을 직접 구성할 일은 없다. `eas build`/`eas submit`이 빌드 + 테스트 채널(TestFlight / Play Internal) 업로드까지 해주고, 나머지(스토어 리스팅·스크린샷·App Privacy·"Submit for Review"·Play production 승급·SA JSON)는 GUI/수동이다. 빌드 명령·프로파일·OTA 판단은 `eas-deploy-guide`가 정본이고, 여기선 콘솔이 얽히는 첫 제출 차이만 짚는다.
|
|
133
132
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
> 첫 제출은 두 스토어가 다르다:
|
|
140
|
-
> - **iOS** — EAS가 ASC 앱 레코드를 (없으면) 자동 생성한다 → 첫 `eas submit -p ios`가 그대로 TestFlight까지 간다 ([Expo Submit iOS](https://docs.expo.dev/submit/ios/)).
|
|
141
|
-
> - **Android** — Google Play API는 첫 릴리스를 만들지 못한다. Owner가 Play Console에서 앱을 만들고 첫 AAB를 수동 업로드해야(아무 트랙이나) 하며, 그 다음부터 `eas submit -p android`가 동작한다 ([Expo: first Android submission](https://github.com/expo/fyi/blob/main/first-android-submission.md)).
|
|
133
|
+
첫 제출은 두 스토어가 다르다:
|
|
134
|
+
- **iOS** — EAS가 ASC 앱 레코드를 (없으면) 자동 생성한다 → 첫 `eas submit -p ios`가 그대로 TestFlight까지 간다 ([Expo Submit iOS](https://docs.expo.dev/submit/ios/)).
|
|
135
|
+
- **Android** — Google Play API는 첫 릴리스를 만들지 못한다. Owner가 Play Console에서 앱을 만들고 첫 AAB를 수동 업로드해야(아무 트랙이나) 하며, 그 다음부터 `eas submit -p android`가 동작한다 ([Expo: first Android submission](https://github.com/expo/fyi/blob/main/first-android-submission.md)). 첫 자동 제출 실패는 정상이니 선고지한다.
|
|
142
136
|
|
|
143
137
|
### EAS가 자동 생성·관리하는 것 (🟢 — .cer/.jks 손 안 댐)
|
|
144
138
|
|
|
@@ -158,27 +152,25 @@ npx eas-cli submit --profile production --platform android # → Play 내부
|
|
|
158
152
|
> `eas.json`엔 public 값만 커밋한다. 시크릿은 EAS 서버에 둔다.
|
|
159
153
|
> `google-services.json`(public·커밋) ↔ FCM SA JSON(시크릿) ↔ Play 제출 SA JSON. 셋은 별개다. 혼동하지 말라.
|
|
160
154
|
|
|
161
|
-
### EAS 무료 티어 (선고지)
|
|
155
|
+
### EAS 무료 티어 (선고지 — 스냅샷)
|
|
156
|
+
|
|
157
|
+
무료 티어는 월 빌드 수·동시성·우선순위·빌드당 시간에 한도가 있다([Expo 요금](https://expo.dev/pricing)) (2026 기준 · verify live — Expo가 한도를 조정해 왔다). 정확한 현재 한도는 이 페이지가 SSOT이니 Owner에게 말하기 전 확인한다. 행동상 함의:
|
|
162
158
|
|
|
163
|
-
-
|
|
164
|
-
-
|
|
165
|
-
- JS 변경은 빌드 풀을 소모하지 않지만(OTA), 초기 네이티브 셋업엔 iOS 빌드 수 개를 소모할 수 있다.
|
|
159
|
+
- 첫 빌드는 큐 대기 포함 10–20분이다. 멈춘 게 아니다.
|
|
160
|
+
- JS 변경은 빌드 풀을 소모하지 않지만(OTA), 초기 네이티브 셋업엔 iOS 빌드 수 개를 소모할 수 있으니 ad-hoc 빌드로 낭비하지 않는다.
|
|
166
161
|
|
|
167
162
|
---
|
|
168
163
|
|
|
169
164
|
## 5. TestFlight / Play Internal → 공개 출시 (🔴 트랩)
|
|
170
165
|
|
|
171
|
-
|
|
172
|
-
> iOS는 TestFlight까지, Android는 internal 트랙까지만 올린다.
|
|
173
|
-
|
|
174
|
-
공개 출시는 🟡 수동 + 명시적 confirm이다:
|
|
166
|
+
`eas submit`은 공개 심사 제출을 하지 않는다. iOS는 TestFlight까지, Android는 internal 트랙까지만 올린다. 여기서 공개로 가는 마지막 한 발은 🟡 수동 + 명시적 confirm이다.
|
|
175
167
|
|
|
176
168
|
| 플랫폼 | 테스트 채널 (eas submit 도달점) | 공개 출시 (수동) |
|
|
177
169
|
|---|---|---|
|
|
178
170
|
| iOS | TestFlight | App Store Connect에서 **"Submit for Review"** |
|
|
179
|
-
| Android | Internal 트랙 | **비공개(closed) 테스트
|
|
171
|
+
| Android | Internal 트랙 | **비공개(closed) 테스트 게이트 통과 후** production 승급 |
|
|
180
172
|
|
|
181
|
-
- 내부(internal) 테스트는
|
|
173
|
+
- 내부(internal) 테스트는 §1의 closed 게이트에 카운트되지 않는다. `eas submit`이 올리는 내부 트랙은 Owner 본인 확인용이고, production 전엔 비공개(closed) 트랙에 테스터를 따로 채워야 한다(Google이 그 기간 테스터 실사용 시간까지 측정). 조직(D-U-N-S) 계정은 이 게이트를 면제받는다. 인원·일수는 §1의 스냅샷이다.
|
|
182
174
|
- Android 첫 AAB는 Play Console에 수동으로 1회 업로드해야 한다 (Play API는 첫 릴리스를 못 만든다. 첫 자동 제출 실패는 정상이니 선고지하라).
|
|
183
175
|
- 초기 확인도 production 빌드를 TestFlight·Play 내부 테스트로 보내 실기기에서 한다. 별도 preview/ad-hoc 빌드를 만들지 않는다(같은 빌드를 그대로 출시로 승급 → EAS 빌드 수 절약). 빠른 사이드로드가 꼭 필요하면 `distribution: "internal"` 프로파일을 따로 추가할 수 있다.
|
|
184
176
|
|
|
@@ -186,13 +178,9 @@ npx eas-cli submit --profile production --platform android # → Play 내부
|
|
|
186
178
|
|
|
187
179
|
## 6. 컴플라이언스 (🟡 수동)
|
|
188
180
|
|
|
189
|
-
- **계정 삭제 (5.1.1(v))** ([Apple 가이드](https://developer.apple.com/support/offering-account-deletion-in-your-app/)): 인앱 삭제 경로가 필수다 (비활성화로는 불충분). 이 셸은 계정 UI를 웹에 위임하므로,
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
- **세 프라이버시 산출물** — App Privacy(iOS 라벨) · Data Safety(Android) · `PrivacyInfo.xcprivacy` — 는 taxonomy가 달라
|
|
193
|
-
복붙이 안 된다. 단일 데이터 인벤토리에서 각각 매핑한다.
|
|
194
|
-
- **ATT**: 기본 No (Supabase·Sentry는 비추적). 단 웹뷰가 로드하는 웹의 트래커(GA4·Pixel 등)는 개발자 책임이다 →
|
|
195
|
-
배포된 웹의 스크립트를 실점검하라.
|
|
181
|
+
- **계정 삭제 (5.1.1(v))** ([Apple 가이드](https://developer.apple.com/support/offering-account-deletion-in-your-app/)): 인앱 삭제 경로가 필수다 (비활성화로는 불충분). 이 셸은 계정 UI를 웹에 위임하므로, 배포된 웹에 삭제 화면이 있어야 한다. 웹뷰 안에서 도달 가능하면 "인앱"으로 인정된다. 네이티브 소셜 포함 모든 로그인에서 도달 가능해야 하고, 서버에서 `auth.admin.deleteUser` (server-only)로 실제 삭제한다.
|
|
182
|
+
- **세 프라이버시 산출물** — App Privacy(iOS 라벨) · Data Safety(Android) · `PrivacyInfo.xcprivacy` — 는 taxonomy가 달라 복붙이 안 된다. 단일 데이터 인벤토리에서 각각 매핑한다.
|
|
183
|
+
- **ATT**: 기본 No (Supabase·Sentry는 비추적). 단 웹뷰가 로드하는 웹의 트래커(GA4·Pixel 등)는 개발자 책임이다 → 배포된 웹의 스크립트를 실점검하라.
|
|
196
184
|
- 스크린샷 · 연령등급(IARC) = 🟡 수동.
|
|
197
185
|
|
|
198
186
|
---
|
|
@@ -207,22 +195,31 @@ npx eas-cli submit --profile production --platform android # → Play 내부
|
|
|
207
195
|
| 생체인증 | 부재 | v1 이후 |
|
|
208
196
|
| 출시 | EAS Build/Submit 흐름 문서화 | 첫 공개 심사 제출 (§5 절차로 안내) |
|
|
209
197
|
|
|
210
|
-
|
|
198
|
+
## 검증
|
|
199
|
+
|
|
200
|
+
출시 준비가 됐다는 판정 — 그리고 조용히 깨지는 지점:
|
|
201
|
+
|
|
202
|
+
- **계정**: Apple 멤버십이 활성이고 Google Play Console이 가입돼 있다. closed 게이트가 **production 출시일을 가장 멀리 미루므로**(§1), Google이 먼저 시작됐는지 본다. 안 그러면 코드가 다 돼도 14일을 기다리며 막힌다.
|
|
203
|
+
- **4.2**: 제출 전 체크리스트(§2)가 실기기에서 전부 통과한다. 네이티브 차별 요소가 0이면 심사에서 4.2.2로 조용히 막힌다(빌드는 성공해도).
|
|
204
|
+
- **소셜을 켰다면**: Sign in with Apple이 동등하게 있고 Supabase Apple provider가 실배선됐다. Google만 켜면 4.8로 거절된다(인앱 OAuth 무한 로딩과는 다른 실패다).
|
|
205
|
+
- **컴플라이언스**: 배포된 웹에 모든 로그인에서 도달 가능한 계정 삭제 경로가 있고, 세 프라이버시 산출물이 매핑돼 있으며, 웹의 트래커가 ATT 선언과 일치한다.
|
|
206
|
+
- 출시 자체의 성공 = TestFlight/Play 내부 테스트의 빌드가 실기기에서 prod 웹을 띄우고, 그 동일 빌드를 콘솔에서 수동 confirm으로 공개 트랙에 승급한 것.
|
|
211
207
|
|
|
212
208
|
## 출처 (Sources)
|
|
213
209
|
|
|
214
|
-
외부 정책·동작 주장의 권위 있는
|
|
210
|
+
외부 정책·동작 주장의 권위 있는 출처다. 위 본문의 모든 벤더 수치(비용·게이트·티어)는 스냅샷이므로 안내 전 해당 링크로 현재값을 확인한다. (앱 자체 동작은 코드·다른 가이드가 SSOT다.)
|
|
215
211
|
|
|
216
212
|
**Apple**
|
|
217
213
|
|
|
218
214
|
- [App Store Review Guidelines](https://developer.apple.com/app-store/review/guidelines/) — 4.2(최소 기능)·4.8(Sign in with Apple)·5.1.1(v)(계정 삭제)
|
|
219
215
|
- [계정 삭제 요건 가이드](https://developer.apple.com/support/offering-account-deletion-in-your-app/) — 5.1.1(v)
|
|
220
|
-
- [Sign in with Apple — 한국 server-to-server 요건
|
|
221
|
-
- [Apple Developer Program](https://developer.apple.com/programs/)
|
|
216
|
+
- [Sign in with Apple — 한국 server-to-server 요건](https://developer.apple.com/news/?id=j9zukcr6) · [S2S 알림 설정 help](https://developer.apple.com/help/account/configure-app-capabilities/enabling-server-to-server-notifications/)
|
|
217
|
+
- [Apple Developer Program](https://developer.apple.com/programs/) — 연 구독 비용
|
|
222
218
|
|
|
223
219
|
**Google Play**
|
|
224
220
|
|
|
225
|
-
- [
|
|
221
|
+
- [Google Play Console 등록 비용](https://support.google.com/googleplay/android-developer/answer/6112435) — 1회 등록비
|
|
222
|
+
- [신규 개인계정 테스트 요건 — closed 테스터·연속일 게이트](https://support.google.com/googleplay/android-developer/answer/14151465)
|
|
226
223
|
- [테스트 트랙 설정 — internal·closed·open](https://support.google.com/googleplay/android-developer/answer/9845334)
|
|
227
224
|
|
|
228
225
|
**Expo / EAS**
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: web-launch
|
|
3
|
+
description: Provision a private GitHub repo, Vercel project, Supabase dev/prod, and Sentry, then ship the web service's first real deploy and run a security audit. Use when the Owner is ready to take the web live for the first time — stand up its cloud and put it on a real URL — or invokes /web-launch.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# /web-launch
|
|
7
|
+
|
|
8
|
+
빈 `web/`를 dev/prod 클라우드에 올려 **실제 주소로 첫 배포**까지 잇는다. 외부 서비스(GitHub·Vercel·Supabase·Sentry)는 API·응답 필드명이 자주 바뀌므로, 기억에 의존하지 말고 그때그때 공식 문서·`--help`·실제 응답을 확인하며 적응한다.
|
|
9
|
+
|
|
10
|
+
> 배포 단위는 `web/`다. repo 연결·`vercel link`·Supabase·Sentry·`git push`는 모두 `web/` 안에서 한다(먼저 `cd web/`). 워크스페이스 루트나 `mobile/`에서 하지 않는다 — 앱은 별도이고 `mobile/AGENTS.md`가 맡는다.
|
|
11
|
+
|
|
12
|
+
## 빠른 참조 (값은 여기서 읽는다)
|
|
13
|
+
|
|
14
|
+
- 리소스 이름: 루트 `workspace.json`의 `name`. repo=`<name>-web`, Vercel=`<name>`, Supabase=`<name>-dev`/`-prod`, Sentry=`<name>`.
|
|
15
|
+
- env 키 목록과 어느 환경에 들어가는지: `web/.env.example`.
|
|
16
|
+
- dev/prod 분리 매트릭스: `web/docs/ENVIRONMENTS.md`. 무료 티어 한도·플랜별 가용성: `web/docs/LIMITS.md`.
|
|
17
|
+
|
|
18
|
+
## 절차 (순서가 중요하다)
|
|
19
|
+
|
|
20
|
+
1. **GitHub repo는 이미 있다.** 셋업의 `connect-repos`가 `<name>-web` private repo를 만들어 `web/` origin에 연결·push해 뒀다 — `gh repo create`·`git init`을 새로 하지 않는다. 없으면(doctor가 ✗로 짚는다) 루트에서 `node scripts/connect-repos.mjs`로 먼저 만든다.
|
|
21
|
+
|
|
22
|
+
2. **Supabase dev + prod를 따로 만든다.** Region·DB password는 Owner와 정한다. ref·url·publishable·secret을 Management API로 뽑되, 응답 필드명이 버전마다 다르니 `jq`로 탐색해 적응한다. dev Auth redirect는 지금 설정한다(`localhost:3000/**` + `https://*.vercel.app/**`). **prod redirect는 7로 미룬다 — 실 prod 도메인이 아직 없다.**
|
|
23
|
+
|
|
24
|
+
3. **Vercel link + env 주입.** `vercel link`는 폴더명 `web`을 기본 프로젝트명으로 제안하니, 그대로 수락하지 말고 `<name>`으로 바꾼다. production 슬롯엔 prod Supabase + `SUPABASE_PROD_REF`, preview·development 슬롯엔 dev + `SUPABASE_DEV_REF`를 채운다. 끝나면 `vercel env pull`로 `.env.local`을 동기화한다.
|
|
25
|
+
|
|
26
|
+
4. **Sentry.** config 파일은 이미 있어 env만 채우면 된다(wizard 불필요). org·project는 `sentry-cli`로 조회·생성하고, CLI로 못 만드는 auth token은 Owner가 대시보드(Settings → Auth Tokens)에서 발급한다. **`NEXT_PUBLIC_SENTRY_DSN`은 Vercel Production 슬롯에만 넣는다**(이유는 아래 함정). 빌드용 3종(`SENTRY_ORG`·`SENTRY_PROJECT`·`SENTRY_AUTH_TOKEN`, sourcemap 업로드)은 3환경 + GitHub Secrets에 넣는다.
|
|
27
|
+
|
|
28
|
+
5. **`logs` 테이블 마이그레이션.** 감사 로그용이다. `service_role`만 insert하는 RLS 정책을 같은 마이그레이션 파일에 동봉한다. dev는 자동 적용하고, prod는 Owner confirm 후 적용한다.
|
|
29
|
+
|
|
30
|
+
6. **GitHub Secrets.** CI 빌드용이다. `.env.local`에서 publishable + URL + Sentry 4종만 `gh secret set`으로 넣는다. secret key·DB ref는 CI 컴파일에 불필요하니 넣지 않는다(보안 표면 최소화).
|
|
31
|
+
|
|
32
|
+
7. **첫 배포 → prod redirect 갱신.** main에 push하면 CI와 Vercel 배포가 함께 트리거된다. 배포가 끝나면 **실 production 도메인을 확인해 prod Supabase의 `site_url`·`uri_allow_list`에 반영한다.** 2에서 이걸 미뤘던 이유이고, 빠뜨리면 운영 사이트 로그인이 조용히 깨진다.
|
|
33
|
+
|
|
34
|
+
8. **Owner 대시보드 설정.** CI를 main merge의 Required status check로 지정하고(GitHub branch protection — 플랜 무관·벤더 중립), Preview에 Vercel Authentication을 켜 preview URL 크롤러를 막는다. 플랜별 가용성·한도는 본문에 박지 말고 `web/docs/LIMITS.md`를 따른다.
|
|
35
|
+
|
|
36
|
+
## 함정 (조용히 깨지는 것)
|
|
37
|
+
|
|
38
|
+
- **prod redirect 미갱신**(7) → 운영 로그인이 무음으로 깨진다. 가장 흔한 사고다.
|
|
39
|
+
- **Sentry DSN을 비-prod 슬롯에 주입** → preview 에러가 production으로 찍혀 무료 한도를 깎는다. 클라이언트 SDK는 번들에 `APP_ENV` 신호가 없어 DSN 존재만으로 송출을 켜기 때문이다(서버·엣지는 `APP_ENV`로 가드돼 안전하다). 그래서 4의 DSN은 Production 슬롯 전용이다.
|
|
40
|
+
- **`vercel link` 기본 프로젝트명**이 폴더명 `web`(3) → `<name>`으로 바꾸지 않으면 리소스 이름이 어긋난다.
|
|
41
|
+
- **secret scanning push protection**은 private+free에서 미지원이다(GitHub Advanced Security 유료) — 진단에서 이 항목만 건너뛴다.
|
|
42
|
+
|
|
43
|
+
## 보안 진단 (배포 후)
|
|
44
|
+
|
|
45
|
+
| # | 검사 | 실패 시 |
|
|
46
|
+
|---|---|---|
|
|
47
|
+
| 1 | `.env*` gitignored | `.gitignore` 패치 |
|
|
48
|
+
| 2 | secret key가 `web/app/`·`web/components/`에 노출(grep) | **critical** |
|
|
49
|
+
| 3 | `NEXT_PUBLIC_` 접두사가 secret·service·token에 | **critical** |
|
|
50
|
+
| 4 | repo visibility = PRIVATE | confirm 후 전환 |
|
|
51
|
+
| 5 | 대시보드 설정(8) 완료 | Owner에 확인 |
|
|
52
|
+
|
|
53
|
+
## confirm 경계
|
|
54
|
+
|
|
55
|
+
prod `db push`(5)와 repo를 public으로 전환할 때(진단 4)만 Owner confirm을 받는다. 그 외 프로비저닝·env 주입·첫 배포는 자동이다.
|
|
56
|
+
|
|
57
|
+
## 검증
|
|
58
|
+
|
|
59
|
+
성공은 셋이 모두 참일 때다: 첫 배포가 **실제 prod 도메인으로 LIVE**이고, 그 도메인이 prod Supabase Auth redirect에 반영돼 있고(7), 보안 진단을 통과하며 `pnpm run doctor`가 클라우드 연결을 ✓로 확인한다.
|
package/dist/workspace/AGENTS.md
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
- 각 서비스는 독립 repo다. 서로 import하지 않고, 공유는 복사된 계약(`ssb/`)으로만 한다.
|
|
20
20
|
- web은 단독으로 완결된다. mobile은 그 위에 얹는 선택적 레이어이고, web은 mobile에 의존하지 않는다.
|
|
21
21
|
- 세 레이어가 각자 독립 git repo다: 루트(컨텍스트 레이어)·`web/`·`mobile/`. 루트 repo는 `.gitignore` 화이트리스트로 컨텍스트 레이어(에이전트 도구·계약·문서)만 추적하고 `web/`·`mobile/`은 추적하지 않는다(모노레포로 묶지 않는다 — 부모 git에 add하지 않는다). 셋은 셋업의 `connect-repos`가 Owner GitHub 계정의 private 원격 `<name>-workspace`·`<name>-web`·`<name>-mobile`에 각각 연결한다.
|
|
22
|
-
- 소유(git 연결)와 배포(클라우드)는 분리한다. 코드 백업은 셋 다 위 원격으로, 클라우드 프로비저닝·배포는 해당 서비스 폴더에서: `/
|
|
22
|
+
- 소유(git 연결)와 배포(클라우드)는 분리한다. 코드 백업은 셋 다 위 원격으로, 클라우드 프로비저닝·배포는 해당 서비스 폴더에서: `/web-launch`는 `web/`에서(web이 배포 단위), 앱 빌드·출시는 `mobile/`에서.
|
|
23
23
|
- 에이전트 도구(스킬·`.claude/` 설정)는 워크스페이스 루트에만 둔다. `web/`·`mobile/`은 도구가 없는 순수 코드이고, 각자 자기 `AGENTS.md`(그 repo를 직접 손보는 사람용)만 둔다.
|
|
24
24
|
- 한 서비스에만 속한 지식은 그 서비스 repo가 진다. 서비스를 가로지르는 맥락만 여기가 진다.
|
|
25
25
|
|
|
@@ -18,17 +18,14 @@
|
|
|
18
18
|
|
|
19
19
|
| 명령 | 무엇을 하나 | 당신이 하는 일 |
|
|
20
20
|
| --- | --- | --- |
|
|
21
|
-
| `/
|
|
22
|
-
| `/go-live` | GitHub·Vercel·Supabase·Sentry를 만들고 `web/`을 실제 주소로 첫 배포 | 비가역 작업만 확인 |
|
|
21
|
+
| `/web-launch` | GitHub·Vercel·Supabase·Sentry를 만들고 `web/`을 실제 주소로 첫 배포 | 비가역 작업만 확인 |
|
|
23
22
|
| `/probe <아이디어>` | 만들 서비스를 인터뷰로 구체화 | 질문에 답하기 |
|
|
24
23
|
| `/sketch` | 화면 디자인 시안 보여주기 | 마음에 드는 안 고르기 |
|
|
25
24
|
| "OO 기능 만들어줘" | 로그인·글쓰기·피드·승인 등 기능을 대화로 제작 | 원하는 바 말하기 |
|
|
26
25
|
| `git push` (AI가 실행) | 자동 재배포 → 즉시 라이브 | 주소 확인 |
|
|
27
|
-
| `/
|
|
28
|
-
| `/preview` | 앱을 폰에 설치해 보기(TestFlight·Play 내부 테스트) | 폰에서 확인 |
|
|
29
|
-
| `/launch` · `/release` | 스토어 첫 출시 · 이후 업데이트 | 콘솔에서 공개 버튼 |
|
|
26
|
+
| `/app-launch` · `/app-update` | 스토어 첫 출시 · 이후 업데이트 | 콘솔에서 공개 버튼 |
|
|
30
27
|
|
|
31
|
-
`/
|
|
28
|
+
`/web-launch`로 빈 서비스를 먼저 띄우고, 그 위에 기능을 하나씩 얹어 `git push`로 계속 배포합니다. 웹에 올린 변화는 앱에도 재심사 없이 그대로 반영됩니다.
|
|
32
29
|
|
|
33
30
|
## 3. 이 안에 뭐가 있나
|
|
34
31
|
|
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
|
|
8
8
|
# 앱이 띄울 실서비스 웹 주소. 항상 배포된 prod 웹(https)을 가리킵니다.
|
|
9
9
|
# (실기기는 localhost에 못 닿고 http는 카메라·로그인 쿠키가 조용히 깨집니다 → 반드시 https.)
|
|
10
|
-
EXPO_PUBLIC_WEB_URL=
|
|
10
|
+
EXPO_PUBLIC_WEB_URL=https://REPLACE-WITH-YOUR-PROD-WEB-URL
|
|
11
|
+
# 시뮬레이터/에뮬레이터 dev-loop 전용(실기기 금지): 위 줄을 아래 중 하나로 바꿔 로컬 웹과 함께 본다.
|
|
12
|
+
# iOS 시뮬: http://localhost:3000 · Android 에뮬: http://10.0.2.2:3000
|
|
11
13
|
|
|
12
14
|
# 스토어/홈 화면에 표시될 앱 이름.
|
|
13
15
|
EXPO_PUBLIC_APP_NAME=My App
|
|
@@ -23,7 +23,7 @@ src/
|
|
|
23
23
|
session/ # 세션 핸드오프 (opt-in, v1 미사용)
|
|
24
24
|
auth/ # 네이티브 로그인 모달 (opt-in 스캐폴드, 소셜은 비활성 stub)
|
|
25
25
|
ui/ # 셸 전용 프리미티브(로딩·에러·오프라인 화면 등)
|
|
26
|
-
docs/web-adapter/ # 웹 측 브릿지 어댑터(
|
|
26
|
+
docs/web-adapter/ # 웹 측 브릿지 어댑터(앱 부트스트랩 시 웹 repo에 설치)
|
|
27
27
|
```
|
|
28
28
|
에이전트 스킬은 이 repo에 두지 않는다. 워크스페이스 루트 `.claude/`에만 둔다.
|
|
29
29
|
|
|
@@ -51,6 +51,14 @@ docs/web-adapter/ # 웹 측 브릿지 어댑터(/kickoff이 웹 repo에
|
|
|
51
51
|
|
|
52
52
|
5. **네이티브 코드는 추측하지 않는다.** 작성 전 v56 문서나 설치된 타입을 읽는다(상단 Expo 절 참고). 구체 레시피는 → `native-app-guide`.
|
|
53
53
|
|
|
54
|
+
## 앱 부트스트랩 — 적합 판단·비용·비가역 confirm
|
|
55
|
+
|
|
56
|
+
배포된 웹을 처음 앱으로 감쌀 때(셸을 이 웹에 맞춰 1회 설정할 때) 아래 셋은 Owner가 직접 결정·확인해야 하는 게이트다. 노력을 들이기 전에 먼저 짚는다.
|
|
57
|
+
|
|
58
|
+
- **웹뷰 적합도는 솔직히 판정하되 막지 않는다.** 핵심 경험이 60fps 제스처·카메라 AR·미디어 캡처거나, 앱 자체가 주력 상품이거나 게임이면 이 얇은 웹뷰 셸이 맞지 않으니 네이티브로 따로 만들 것을 권고한다. 권고일 뿐, 최종 결정은 Owner가 한다.
|
|
59
|
+
- **드는 비용을 미리 알리고, 계속할지 확정받은 뒤에만 진행한다.** 개발자 등록비는 환불되지 않는다(Apple Developer 연 구독 · Google Play 1회 등록비 — 현재 액수는 `store-release-guide`). EAS 무료 티어도 한도가 있다(동시성 1 · 월 빌드 수 · 큐 대기). 돈이 나가는 단계이므로 announce → Owner confirm을 건너뛰지 않는다.
|
|
60
|
+
- **번들 ID·스킴은 확정하면 사실상 되돌릴 수 없다.** 스토어 콘솔 등록값과 정확히 같아야 하고 출시 뒤엔 바꾸기 어려우므로, `app.config.ts`에 굳히기 전에 Owner confirm으로 못박는다(앱 이름·아이콘은 나중에 바꿀 수 있다). 이건 #3의 URL 인라인(빌드 시점 고정)과는 다른 함정이다.
|
|
61
|
+
|
|
54
62
|
## 주제 → 가이드 스킬
|
|
55
63
|
|
|
56
64
|
작업을 시작하면 해당 가이드가 자동 로드된다. 상세 규약은 여기 인라인하지 않는다.
|
|
@@ -34,7 +34,7 @@ pnpm install
|
|
|
34
34
|
앱 이름, 스토어에서 앱을 구분하는 고유 식별자(iOS·Android 각각, 예 `com.이름.앱`), 앱이 띄울 웹 주소 등을 정합니다. "현재 상태를 파악한 뒤 앱 설정을 같이 정하자"라고 부탁하면 됩니다.
|
|
35
35
|
|
|
36
36
|
> 고유 식별자(bundle ID·패키지명)는 영구입니다. 한 번 스토어에 올리면 못 바꾸고, 스토어 콘솔에 등록한 값과 정확히 같아야 합니다(앱 이름·아이콘은 나중에 바꿔도 됩니다).
|
|
37
|
-
> 웹 주소는 빌드할 때 앱 안에 고정됩니다(`eas.json`). 기본값은 자리표시 값이라 그대로 빌드하면 앱 시작 시 멈춥니다(흰 화면 대신 명확한 에러로).
|
|
37
|
+
> 웹 주소는 빌드할 때 앱 안에 고정됩니다(`eas.json`). 기본값은 자리표시 값이라 그대로 빌드하면 앱 시작 시 멈춥니다(흰 화면 대신 명확한 에러로). 앱을 만들 때 AI가 실제 https 주소로 바꿔 줍니다. 주소를 바꾸면 재빌드해야 반영됩니다.
|
|
38
38
|
|
|
39
39
|
## 3. 미리보기 (폰에서 보기)
|
|
40
40
|
|
|
@@ -8,13 +8,13 @@
|
|
|
8
8
|
"developmentClient": true,
|
|
9
9
|
"distribution": "internal",
|
|
10
10
|
"env": {
|
|
11
|
-
"EXPO_PUBLIC_WEB_URL": "
|
|
11
|
+
"EXPO_PUBLIC_WEB_URL": "https://REPLACE-WITH-YOUR-DEV-WEB-URL"
|
|
12
12
|
}
|
|
13
13
|
},
|
|
14
14
|
"production": {
|
|
15
15
|
"autoIncrement": true,
|
|
16
16
|
"env": {
|
|
17
|
-
"EXPO_PUBLIC_WEB_URL": "
|
|
17
|
+
"EXPO_PUBLIC_WEB_URL": "https://REPLACE-WITH-YOUR-PROD-WEB-URL"
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// connect-repos — 워크스페이스의 세 로컬 git 레포를 내 GitHub 계정의 private 원격에 연결·백업한다.
|
|
2
2
|
//
|
|
3
3
|
// 레이어 = 레포: 루트(컨텍스트) → `<name>-workspace`, web/ → `<name>-web`, mobile/ → `<name>-mobile`.
|
|
4
|
-
// 소유(여기서 하는 git 연결)와 배포(/
|
|
4
|
+
// 소유(여기서 하는 git 연결)와 배포(/web-launch와 앱 빌드·출시의 클라우드 프로비저닝)는 분리돼 있다 —
|
|
5
5
|
// 이 스크립트는 클라우드를 건드리지 않고 "내 코드를 내 GitHub에 백업"만 한다.
|
|
6
6
|
//
|
|
7
7
|
// 호출: 부트스트랩(setup.sh/ps1·코드스페이스)이 gh 로그인 직후 한 번. 직접 실행도 안전하다:
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# my-space — 환경변수 명세
|
|
3
3
|
#
|
|
4
4
|
# 이 파일엔 *키 이름과 어느 환경에 들어가는지*만 있다.
|
|
5
|
-
# 실제 값은 /
|
|
5
|
+
# 실제 값은 /web-launch가 Vercel에 박은 뒤 `vercel env pull` 로 동기화.
|
|
6
6
|
# ─────────────────────────────────────────────────────────────
|
|
7
7
|
|
|
8
8
|
# ─── Supabase (dev/prod 별개 프로젝트) ──────────────────────────
|
|
@@ -10,14 +10,13 @@
|
|
|
10
10
|
|
|
11
11
|
## 배포 시나리오 (v1)
|
|
12
12
|
|
|
13
|
-
1. `/
|
|
14
|
-
2. `/
|
|
15
|
-
3. `/
|
|
16
|
-
4.
|
|
17
|
-
5.
|
|
18
|
-
6. `git push` — CI 통과 시 Vercel 자동 배포
|
|
13
|
+
1. `/web-launch` — 클라우드 리소스(GitHub·Vercel·Supabase dev/prod·Sentry) 생성, 첫 배포, 보안 진단
|
|
14
|
+
2. `/probe <아이디어>` — 적응형 인터뷰로 요구사항 도출
|
|
15
|
+
3. `/sketch` — shadcn과 프로젝트 디자인 컨벤션 기반 시안
|
|
16
|
+
4. TDD 개발 — Vitest 테스트 작성, 통과, 커밋을 반복
|
|
17
|
+
5. `git push` — CI 통과 시 Vercel 자동 배포
|
|
19
18
|
|
|
20
|
-
|
|
19
|
+
`.vercel/`이 없으면 `/web-launch`를 먼저 실행한다. 의존성·CLI 로그인은 설치 스크립트와 `pnpm run doctor`가 챙긴다.
|
|
21
20
|
|
|
22
21
|
## AI 도구 (CLI)
|
|
23
22
|
|
|
@@ -30,7 +29,7 @@ AI는 아래 CLI로 클라우드를 직접 조작한다. 가역성 등급에 따
|
|
|
30
29
|
| `pnpm exec supabase` (web/에서) | projects·migration·db push·gen types·link | dev 자동, prod `db push`·`db reset`은 확인 |
|
|
31
30
|
| `sentry-cli` | organization·project 조회/생성, sourcemap·release | 자동 |
|
|
32
31
|
| `pnpm` | install·add·remove·audit·dlx | 자동, major 업그레이드는 확인 |
|
|
33
|
-
| `curl` + Supabase Management API | Auth redirect URL 등 CLI 미커버 항목 | 자동 (PAT = `supabase login`이 저장한 토큰,
|
|
32
|
+
| `curl` + Supabase Management API | Auth redirect URL 등 CLI 미커버 항목 | 자동 (PAT = `supabase login`이 저장한 토큰, `pnpm run doctor`가 로그인을 점검) |
|
|
34
33
|
| `git` | 표준 | 자동, force push·hard reset은 확인 |
|
|
35
34
|
|
|
36
35
|
비가역·고위험은 반드시 Owner confirm: prod DB 마이그레이션, prod env 삭제, repo public 전환, prod `db reset`, main force push.
|
|
@@ -51,6 +50,7 @@ Owner가 직접(CLI 미지원, AI는 안내만): 카드 결제, 도메인 DNS
|
|
|
51
50
|
- secret key: `lib/supabase/admin.ts`에서만 쓴다(server-only 가드). `NEXT_PUBLIC_*` 접두사를 붙이지 않는다. auth 흐름에서 `admin.ts`를 import하지 않는다(RLS·세션 우회 위험).
|
|
52
51
|
- Supabase Auth: `@supabase/ssr`을 쓴다. `@supabase/auth-helpers-nextjs`(deprecated)는 쓰지 않는다.
|
|
53
52
|
- 환경 분기: 서버 코드는 `import { env } from "@/lib/env.server"` 후 `env.APP_ENV`를 비교한다(값: `production` | `preview` | `development`). `APP_ENV`는 벤더 중립 신호다. Vercel은 `VERCEL_ENV`를 여기 매핑하고, 그 외 호스트는 `APP_ENV`를 직접 설정한다(`lib/app-env.ts`).
|
|
53
|
+
- Vercel Cron route: prod에서만 돈다. 핸들러 시작에서 `env.APP_ENV !== "production"`이면 즉시 no-op으로 반환해, preview가 dev DB·외부 API를 낭비하지 않게 한다. 성공은 `audit`, 실패는 `error`로 `lib/logger.ts`에 남긴다(Sentry·logs 송출은 자동). 테스트 함정: `env`는 모듈 로드 시 한 번만 parse돼서 `vi.stubEnv`가 듣지 않는다 — cron 테스트는 `vi.mock("@/lib/env.server")`로 모듈을 통째 교체한다. 헤더 검증·idempotent·`CRON_SECRET`·`vercel.json`은 Vercel 문서를 따른다.
|
|
54
54
|
- 의존성 정책: caret range, `pnpm-lock.yaml` 커밋, CI `--frozen-lockfile`. `.npmrc`의 `minimum-release-age=14d`로 갓 나온 npm 패키지는 cooldown 후에만 설치한다. 의존성 변경은 lockfile과 같은 커밋에 포함한다.
|
|
55
55
|
|
|
56
56
|
## 폴더 구조 (FSD-light)
|
|
@@ -12,18 +12,17 @@ Next.js (App Router) · TypeScript · Tailwind v4 · shadcn/ui · Supabase · Ve
|
|
|
12
12
|
|
|
13
13
|
| 단계 | 명령 | AI가 하는 일 | Owner가 하는 일 |
|
|
14
14
|
| --- | --- | --- | --- |
|
|
15
|
-
| 1.
|
|
16
|
-
| 2.
|
|
17
|
-
| 3.
|
|
18
|
-
| 4.
|
|
19
|
-
| 5.
|
|
20
|
-
| 6. 푸시 | `git push` | CI 통과 시 Vercel 자동 배포 | URL 확인 |
|
|
15
|
+
| 1. 첫 배포 | `/web-launch` | GitHub·Vercel·Supabase dev/prod·Sentry 프로비저닝, env 동기화, 첫 배포, 보안 진단 | 비가역 작업 confirm |
|
|
16
|
+
| 2. 기획 | `/probe <아이디어>` | 인터뷰로 요구사항 도출 | 질문에 답변 |
|
|
17
|
+
| 3. 디자인 | `/sketch` | shadcn과 프로젝트 디자인 컨벤션 기반 UI | 원하면 DESIGN.md 채우기 |
|
|
18
|
+
| 4. 개발 | "X 기능 만들어줘" | TDD로 개발, Vitest 통과까지 반복 | 요구사항 명시·검토 |
|
|
19
|
+
| 5. 푸시 | `git push` | CI 통과 시 Vercel 자동 배포 | URL 확인 |
|
|
21
20
|
|
|
22
21
|
코드 규칙·폴더 구조·환경 분기 규약은 [`AGENTS.md`](AGENTS.md)에 있습니다.
|
|
23
22
|
|
|
24
23
|
## 사전 준비물
|
|
25
24
|
|
|
26
|
-
|
|
25
|
+
설치 스크립트와 `pnpm run doctor`가 안내하지만, 미리 가입·설치하면 빠릅니다.
|
|
27
26
|
|
|
28
27
|
- [Node.js](https://nodejs.org/) 현재 LTS
|
|
29
28
|
- [pnpm](https://pnpm.io/) 최신
|
|
@@ -71,13 +71,17 @@ const url = env.NEXT_PUBLIC_SUPABASE_URL;
|
|
|
71
71
|
|
|
72
72
|
```ts
|
|
73
73
|
// 예외: 빌드 시점 설정 파일(sentry.{server,edge}.config.ts·instrumentation*.ts·next.config.ts)은
|
|
74
|
-
// env 모듈보다 먼저 실행되므로 process.env를 직접
|
|
74
|
+
// env 모듈보다 먼저 실행되므로 process.env를 직접 읽고, lib/app-env.ts로 APP_ENV를 해석한다.
|
|
75
|
+
import { APP_ENV } from "@/lib/app-env";
|
|
75
76
|
Sentry.init({
|
|
76
77
|
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
|
|
77
|
-
enabled:
|
|
78
|
-
environment:
|
|
78
|
+
enabled: APP_ENV === "production", // 서버/엣지: 비-prod는 송출 off
|
|
79
|
+
environment: APP_ENV,
|
|
79
80
|
tracesSampleRate: 0,
|
|
80
81
|
});
|
|
82
|
+
|
|
83
|
+
// 클라이언트(instrumentation-client.ts)는 번들에 APP_ENV가 없어 DSN 존재로 게이트한다.
|
|
84
|
+
// 그래서 web-launch는 NEXT_PUBLIC_SENTRY_DSN을 Vercel Production 슬롯에만 주입한다(비-prod엔 DSN 없음 → 송출 off).
|
|
81
85
|
```
|
|
82
86
|
|
|
83
87
|
## 마이그레이션 워크플로 (AI 주도)
|
|
@@ -89,7 +93,7 @@ Sentry.init({
|
|
|
89
93
|
5. Owner confirm
|
|
90
94
|
6. `npx supabase db push --project-ref $SUPABASE_PROD_REF`로 prod 적용
|
|
91
95
|
|
|
92
|
-
`$SUPABASE_DEV_REF`·`$SUPABASE_PROD_REF`는 `/
|
|
96
|
+
`$SUPABASE_DEV_REF`·`$SUPABASE_PROD_REF`는 `/web-launch`가 `.env.local`과 Vercel env에 저장한다.
|
|
93
97
|
|
|
94
98
|
## 비용·한도
|
|
95
99
|
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
// Browser SDK 초기화. Next가 client bundle에 자동 포함.
|
|
2
2
|
import * as Sentry from "@sentry/nextjs";
|
|
3
3
|
|
|
4
|
-
//
|
|
5
|
-
// DSN
|
|
4
|
+
// 브라우저 번들엔 배포 환경 신호(APP_ENV/VERCEL_ENV)가 없다 — Next는 NEXT_PUBLIC_* 만 인라인한다.
|
|
5
|
+
// 그래서 송출 게이트를 DSN 존재로 대신한다. 이 코드의 정확성은 "web-launch가 DSN을 Vercel Production
|
|
6
|
+
// 슬롯에만 주입한다"는 계약에 달려 있다. preview·development에 DSN을 넣으면 그 환경 에러가 production으로
|
|
7
|
+
// 오송출되니, DSN을 비-prod 슬롯에 추가하지 말 것.
|
|
6
8
|
const dsn = process.env.NEXT_PUBLIC_SENTRY_DSN;
|
|
7
9
|
|
|
8
10
|
Sentry.init({
|
package/package.json
CHANGED
package/src/scaffold.mjs
CHANGED
|
@@ -94,7 +94,7 @@ async function assertNoLeftoverToken(dir, base = dir, found = []) {
|
|
|
94
94
|
// 루트(컨텍스트 레이어)·web/·mobile/(각자 독립 repo)에 git을 처음부터 만든다 — 루트 .gitignore의
|
|
95
95
|
// 화이트리스트가 전제하는 구조를 스캐폴드가 실제로 완성한다. 1일차부터 undo 안전망이 생기고,
|
|
96
96
|
// 부모 폴더가 git repo인 환경(Codespaces)에서 git 명령이 바깥 레포로 새는 사고를 차단한다.
|
|
97
|
-
//
|
|
97
|
+
// web-launch는 여기에 리모트만 붙인다. git 부재·identity 미설정 등 어떤 실패도 스캐폴드를
|
|
98
98
|
// 막지 않는다(git 부재는 doctor가 짚고, identity가 없으면 staged 상태로 두어 히스토리를 오염시키지 않는다).
|
|
99
99
|
function initGitRepos(dest) {
|
|
100
100
|
const git = (args, cwd) => spawnSync("git", args, { cwd, stdio: "ignore" }).status === 0;
|