create-saas-starter-workspace 0.1.1 → 0.1.3
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/CHANGELOG.md +8 -2
- package/bin/index.mjs +16 -9
- package/dist/workspace/.claude/skills/bridge-guide/SKILL.md +2 -2
- package/dist/workspace/.claude/skills/go-live/SKILL.md +3 -1
- package/dist/workspace/.claude/skills/kickoff/SKILL.md +1 -1
- package/dist/workspace/.claude/skills/warmup/SKILL.md +1 -1
- package/dist/workspace/AGENTS.md +1 -1
- package/dist/workspace/README.md +1 -1
- package/dist/workspace/START-HERE.md +2 -2
- package/dist/workspace/mobile/docs/web-adapter/README.md +1 -1
- package/dist/workspace/mobile/src/bridge/contract.ts +1 -1
- package/dist/workspace/scripts/doctor.mjs +27 -9
- package/dist/workspace/ssb/README.md +1 -1
- package/dist/workspace/ssb/contract.ts +1 -1
- package/dist/workspace/web/lib/bridge/contract.ts +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.1.
|
|
3
|
+
## 0.1.2 (미출판)
|
|
4
|
+
|
|
5
|
+
- 문서·doctor 안내의 점검 명령을 `pnpm run doctor`로 정정 — `doctor`는 pnpm 내장 명령이라 `pnpm doctor`는 우리 스크립트 대신 내장 명령(무출력)을 실행했다.
|
|
6
|
+
|
|
7
|
+
- doctor [웹]에 Sentry CLI 점검 추가(`sentry-cli info`의 Unauthorized 파싱 — exit 0이어도 미로그인 판별). setup이 Sentry 로그인까지 처리하게 된 변경과 짝.
|
|
8
|
+
|
|
9
|
+
## 0.1.1 — 2026-06-12
|
|
4
10
|
|
|
5
11
|
- doctor [공통]에 브릿지 계약 점검 추가: `ssb/`(원본)와 `web/lib/bridge/`·`mobile/src/bridge/` 사본의 byte-동일성을 단언한다. 템플릿 문서들이 약속하던 "CI checksum"(실재하지 않던 게이트)을 doctor 기준으로 전부 정정.
|
|
6
|
-
- 워크스페이스 루트의 자기명칭도 프로젝트 이름으로 렌더: 루트 `package.json`(`<이름>-workspace`)과 `README.md`·`AGENTS.md` 제목. `pnpm doctor` 등 루트 명령의 출력에 학생 프로젝트명이 보인다.
|
|
12
|
+
- 워크스페이스 루트의 자기명칭도 프로젝트 이름으로 렌더: 루트 `package.json`(`<이름>-workspace`)과 `README.md`·`AGENTS.md` 제목. `pnpm run doctor` 등 루트 명령의 출력에 학생 프로젝트명이 보인다.
|
|
7
13
|
- bin이 `dist/` 부재 시(개발 트리 직접 실행) `npm run stage` 안내와 함께 명확히 실패한다.
|
|
8
14
|
- 패키지 내부 레이아웃 정리: 템플릿 SSOT를 패키지 내 `template/`(소스)로 이동, stage 산출물은 `templates/` → `dist/`(표준 src/dist 관례). 소비자(npx) 영향 없음.
|
|
9
15
|
|
package/bin/index.mjs
CHANGED
|
@@ -28,6 +28,7 @@ const HELP = `create-saas-starter-workspace v${pkg.version}
|
|
|
28
28
|
--agent <claude|codex|opencode> 사용할 코딩 에이전트 기록 (기본: claude)
|
|
29
29
|
--skip-install 의존성 설치 생략
|
|
30
30
|
--no-doctor 생성 후 doctor 점검 생략
|
|
31
|
+
--no-summary 마무리 안내 생략 (setup 스크립트가 호출할 때 사용)
|
|
31
32
|
-v, --version 버전 출력
|
|
32
33
|
-h, --help 이 도움말
|
|
33
34
|
|
|
@@ -42,7 +43,7 @@ function fail(message) {
|
|
|
42
43
|
// ---------- 인자 파싱 ----------
|
|
43
44
|
|
|
44
45
|
function parseArgs(argv) {
|
|
45
|
-
const opts = { name: null, agent: "claude", skipInstall: false, noDoctor: false };
|
|
46
|
+
const opts = { name: null, agent: "claude", skipInstall: false, noDoctor: false, noSummary: false };
|
|
46
47
|
const positionals = [];
|
|
47
48
|
for (let i = 0; i < argv.length; i++) {
|
|
48
49
|
const arg = argv[i];
|
|
@@ -56,6 +57,9 @@ function parseArgs(argv) {
|
|
|
56
57
|
opts.skipInstall = true;
|
|
57
58
|
} else if (arg === "--no-doctor") {
|
|
58
59
|
opts.noDoctor = true;
|
|
60
|
+
} else if (arg === "--no-summary") {
|
|
61
|
+
// 부트스트랩(setup)이 호출할 때 쓴다 — 마무리 멘트는 흐름의 주인이 한 번만 한다.
|
|
62
|
+
opts.noSummary = true;
|
|
59
63
|
} else if (arg === "--agent") {
|
|
60
64
|
const value = argv[++i];
|
|
61
65
|
if (value === undefined || value.startsWith("-")) {
|
|
@@ -159,7 +163,7 @@ async function main() {
|
|
|
159
163
|
} else if (!runStepSilent("pnpm", ["--version"])) {
|
|
160
164
|
installFailed = ["web", "mobile"];
|
|
161
165
|
console.log("\n▸ [2/3] 의존성 설치 — pnpm이 없어 건너뜁니다.");
|
|
162
|
-
console.log(" → npm install -g pnpm@10 후, 아래
|
|
166
|
+
console.log(" → npm install -g pnpm@10 후, 아래 안내의 설치 명령을 실행하세요.");
|
|
163
167
|
} else {
|
|
164
168
|
for (const sub of ["web", "mobile"]) {
|
|
165
169
|
console.log(`\n▸ [2/3] 의존성 설치: ${sub}/ (몇 분 걸릴 수 있습니다)`);
|
|
@@ -179,17 +183,20 @@ async function main() {
|
|
|
179
183
|
spawnSync(process.execPath, [path.join(dest, "scripts", "doctor.mjs")], { cwd: dest, stdio: "inherit" });
|
|
180
184
|
}
|
|
181
185
|
|
|
182
|
-
|
|
183
|
-
console.log(`🎉 "${name}" 워크스페이스가 만들어졌습니다.`);
|
|
186
|
+
// 실패 안내는 모드와 무관하게 항상 출력한다 — 축하·다음 단계만 호출자(setup)에게 양보한다.
|
|
184
187
|
if (installFailed.length > 0) {
|
|
185
188
|
console.log(`\n⚠ 의존성 설치가 끝나지 않았습니다 (${installFailed.join(", ")}). 이렇게 마무리하세요:`);
|
|
186
189
|
for (const sub of installFailed) console.log(` cd ${name}/${sub} && pnpm install --frozen-lockfile`);
|
|
187
|
-
console.log(` 그다음 cd ${name} && pnpm doctor 로 다시 확인합니다.`);
|
|
190
|
+
console.log(` 그다음 cd ${name} && pnpm run doctor 로 다시 확인합니다.`);
|
|
191
|
+
}
|
|
192
|
+
if (!opts.noSummary) {
|
|
193
|
+
console.log("─".repeat(60));
|
|
194
|
+
console.log(`🎉 "${name}" 워크스페이스가 만들어졌습니다.`);
|
|
195
|
+
console.log(`\n다음 단계:`);
|
|
196
|
+
console.log(` 1. cd ${name}`);
|
|
197
|
+
console.log(` 2. 코딩 에이전트 실행: ${opts.agent === "claude" ? "claude" : opts.agent} (첫 실행 시 로그인 안내가 나옵니다)`);
|
|
198
|
+
console.log(` 3. 자세한 길잡이는 ${name}/START-HERE.md`);
|
|
188
199
|
}
|
|
189
|
-
console.log(`\n다음 단계:`);
|
|
190
|
-
console.log(` 1. cd ${name}`);
|
|
191
|
-
console.log(` 2. 코딩 에이전트 실행: ${opts.agent === "claude" ? "claude" : opts.agent} (첫 실행 시 로그인 안내가 나옵니다)`);
|
|
192
|
-
console.log(` 3. 자세한 길잡이는 ${name}/START-HERE.md`);
|
|
193
200
|
console.log("");
|
|
194
201
|
}
|
|
195
202
|
|
|
@@ -6,7 +6,7 @@ user-invocable: false
|
|
|
6
6
|
|
|
7
7
|
# 앱↔웹 대화 규격 (ssb 계약)
|
|
8
8
|
|
|
9
|
-
앱과 웹은 정해진 형식의 메시지로만 대화한다. 그 형식 정의는 양쪽이 똑같은 파일 하나(`src/bridge/contract.ts` + `reader.ts`)로 공유한다. 어긋나면 워크스페이스 루트의 `pnpm doctor`가 잡아낸다.
|
|
9
|
+
앱과 웹은 정해진 형식의 메시지로만 대화한다. 그 형식 정의는 양쪽이 똑같은 파일 하나(`src/bridge/contract.ts` + `reader.ts`)로 공유한다. 어긋나면 워크스페이스 루트의 `pnpm run doctor`가 잡아낸다.
|
|
10
10
|
|
|
11
11
|
## 봉투 & 전달 방식
|
|
12
12
|
|
|
@@ -52,7 +52,7 @@ user-invocable: false
|
|
|
52
52
|
4. inbound라면 `router.ts`의 `handleInbound` switch에 case를 추가한다. UNKNOWN은 그대로 무시되니 안전하다.
|
|
53
53
|
5. 새 능력이면 `capabilities.ts`의 `APP_CAPABILITIES`에 capability 문자열을 추가한다. 웹이 HELLO로 감지한다.
|
|
54
54
|
6. 네이티브 변경이므로 앱을 재빌드한다(OTA 아님 — `eas-deploy-guide`).
|
|
55
|
-
7. 동기화한다. 같은 변경을 `ssb/`(원본)와 `web/lib/bridge/`·`mobile/src/bridge/` 세 곳에 바이트 단위로 동일하게 반영하고, 워크스페이스 루트에서 `pnpm doctor`로 동일성을 확인한다.
|
|
55
|
+
7. 동기화한다. 같은 변경을 `ssb/`(원본)와 `web/lib/bridge/`·`mobile/src/bridge/` 세 곳에 바이트 단위로 동일하게 반영하고, 워크스페이스 루트에서 `pnpm run doctor`로 동일성을 확인한다.
|
|
56
56
|
|
|
57
57
|
> 새 기능은 앱에 먼저 들어가고(HELLO capability), 웹이 나중에 감지해 쓴다. 순서가 어긋나도 웹 코드가 capability 게이트로 막혀 있어 안전하다.
|
|
58
58
|
|
|
@@ -11,7 +11,9 @@ dev/prod 클라우드 환경을 뚫어 첫 배포를 진행한다. 최신 외부
|
|
|
11
11
|
|
|
12
12
|
## 1. 이름·GitHub repo
|
|
13
13
|
|
|
14
|
-
이름을 먼저 결정한다. 이후 모든 리소스가 이 이름을 기반으로
|
|
14
|
+
이름을 먼저 결정한다. 기본값 = 워크스페이스 루트 `workspace.json`의 `name`(생성 시 입력한 프로젝트명). 이후 모든 리소스가 이 이름을 기반으로 한다: GitHub repo = `<name>`, Vercel 프로젝트 = `<name>`, Supabase = `<name>-dev`/`-prod`, Sentry 프로젝트 = `<name>`. **폴더명(`web`)을 리소스 이름으로 쓰지 않는다** — 특히 `vercel link`는 폴더명 `web`을 기본 프로젝트명으로 제안하므로 그대로 수락하지 말고 `<name>`으로 바꾼다. (앱 단계의 mobile repo는 `<name>-app` — package.json·Expo slug와 일치.)
|
|
15
|
+
|
|
16
|
+
private repo를 생성한다. secret scanning push protection은 private free에서 미지원이고 GitHub Advanced Security 유료 기능이다. 9-6에서 점검만 skip한다.
|
|
15
17
|
|
|
16
18
|
## 2. Supabase dev + prod
|
|
17
19
|
|
|
@@ -44,7 +44,7 @@ Owner confirm. 비용을 알린 뒤 계속할지 확정받고서야 다음 단
|
|
|
44
44
|
|
|
45
45
|
## 4. 웹 어댑터 (web/)
|
|
46
46
|
|
|
47
|
-
계약(`contract.ts`·`reader.ts`)은 이미 `web/lib/bridge/`에 바이트 단위로 동일하게 설치돼 있고(`pnpm doctor`가 동일성을 점검한다) 다시 복사하지 않는다. 웹이 앱과 실제로 대화하게 만드는 얇은 옵트인 레이어만 필요할 때 추가한다. 규약·보안 불변식은 인라인하지 않고 `bridge-guide`를 따른다.
|
|
47
|
+
계약(`contract.ts`·`reader.ts`)은 이미 `web/lib/bridge/`에 바이트 단위로 동일하게 설치돼 있고(`pnpm run doctor`가 동일성을 점검한다) 다시 복사하지 않는다. 웹이 앱과 실제로 대화하게 만드는 얇은 옵트인 레이어만 필요할 때 추가한다. 규약·보안 불변식은 인라인하지 않고 `bridge-guide`를 따른다.
|
|
48
48
|
|
|
49
49
|
세션 핸드오프는 fast-follow다. v1은 호출자가 없다. 기본 로그인은 웹뷰 안의 웹 로그인이 그대로 동작하고, 핸드오프 라우트(POST `/auth/app-bridge` + nonce)는 네이티브 소셜 로그인을 켤 때만 살아난다. 토큰은 브릿지·URL로 보내지 않는다(1회용 nonce만 보낸다).
|
|
50
50
|
|
|
@@ -25,7 +25,7 @@ Owner의 행동이 필요한 부분은 AskUserQuestion으로 해결한다.
|
|
|
25
25
|
|
|
26
26
|
## 의존성
|
|
27
27
|
|
|
28
|
-
워크스페이스 루트의 `package.json`은 `pnpm doctor`(환경 점검) 진입점만 가진다 — 루트에 의존성을 설치하지 않는다. `web/`과 `mobile/`은 각자 독립 프로젝트이므로 의존성도 각자 설치한다.
|
|
28
|
+
워크스페이스 루트의 `package.json`은 `pnpm run doctor`(환경 점검) 진입점만 가진다 — 루트에 의존성을 설치하지 않는다. `web/`과 `mobile/`은 각자 독립 프로젝트이므로 의존성도 각자 설치한다.
|
|
29
29
|
|
|
30
30
|
- `web/`에서 `pnpm install` 후 `pnpm audit`.
|
|
31
31
|
- `mobile/`에서도 동일하게 한다.
|
package/dist/workspace/AGENTS.md
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
- `mobile/` — 배포된 web을 WebView로 감싸는 네이티브 셸 (Expo). 자기 git → EAS → 스토어로 배포한다. 웹뷰가 못 메우는 네이티브 공백(푸시·홈 화면 아이콘·오프라인 화면)만 채운다.
|
|
11
11
|
- `ssb/` — web ↔ mobile postMessage 계약의 SSOT. `web/lib/bridge/`와 `mobile/src/bridge/`에 바이트 단위로 동일하게 복사된다.
|
|
12
12
|
- `.claude/` — 에이전트 도구(스킬·설정). 워크스페이스 루트에만 둔다.
|
|
13
|
-
- `scripts/doctor.mjs` — 환경·로그인 점검(`pnpm doctor`). 진단과 안내까지만 하고 자동 수리는 하지 않는다. Owner가 ✗ 해결을 부탁하면 doctor의 → 안내를 따라 수행하고 다시 doctor로 검증한다.
|
|
13
|
+
- `scripts/doctor.mjs` — 환경·로그인 점검(`pnpm run doctor`). 진단과 안내까지만 하고 자동 수리는 하지 않는다. Owner가 ✗ 해결을 부탁하면 doctor의 → 안내를 따라 수행하고 다시 doctor로 검증한다.
|
|
14
14
|
- `workspace.json` — 생성기가 기록한 메타(프로젝트명·선택한 코딩 에이전트). doctor가 읽는다.
|
|
15
15
|
|
|
16
16
|
## 불변식
|
package/dist/workspace/README.md
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
├── web/ 웹 SaaS (Next.js · Supabase · Vercel). 화면·기능·데이터의 본체.
|
|
12
12
|
├── mobile/ 그 웹을 감싸는 스토어 앱 (Expo · EAS).
|
|
13
13
|
├── ssb/ web ↔ mobile 통신 계약. 손댈 일은 거의 없습니다.
|
|
14
|
-
├── scripts/ 환경 점검 도구(`pnpm doctor`).
|
|
14
|
+
├── scripts/ 환경 점검 도구(`pnpm run doctor`).
|
|
15
15
|
└── .claude/ 에이전트 도구(스킬·설정).
|
|
16
16
|
```
|
|
17
17
|
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
2. 처음 열면 에이전트가 로그인(구독 계정)을 안내합니다. 한 번만 하면 됩니다.
|
|
11
11
|
3. 그다음부터는 아래 명령을 입력하거나, 평소 말로 부탁하면 됩니다.
|
|
12
12
|
|
|
13
|
-
> 환경이 멀쩡한지 궁금할 땐 언제든 터미널에서 `pnpm doctor`를 실행하세요. 도구·로그인 상태를 ✓/✗로 보여주고, ✗마다 해결 방법을 알려줍니다.
|
|
13
|
+
> 환경이 멀쩡한지 궁금할 땐 언제든 터미널에서 `pnpm run doctor`를 실행하세요. 도구·로그인 상태를 ✓/✗로 보여주고, ✗마다 해결 방법을 알려줍니다.
|
|
14
14
|
|
|
15
15
|
## 2. 명령 순서
|
|
16
16
|
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
├── web/ 당신의 웹 서비스 (Next.js → Vercel). 모든 화면·기능·데이터의 본체.
|
|
38
38
|
├── mobile/ 그 웹을 감싸는 스토어 앱 (Expo → 앱·플레이스토어). 푸시·홈 화면 아이콘처럼 앱만의 것.
|
|
39
39
|
├── ssb/ web ↔ mobile이 주고받는 통신 계약. 손대지 않아도 됩니다.
|
|
40
|
-
├── scripts/ 환경 점검 도구(`pnpm doctor`).
|
|
40
|
+
├── scripts/ 환경 점검 도구(`pnpm run doctor`).
|
|
41
41
|
└── .claude/ 위 명령(스킬)이 있는 곳.
|
|
42
42
|
```
|
|
43
43
|
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
| `src/bridge/contract.ts` | `lib/bridge/contract.ts` |
|
|
22
22
|
| `src/bridge/reader.ts` | `lib/bridge/reader.ts` |
|
|
23
23
|
|
|
24
|
-
두 사본은 바이트 단위로 동일해야 한다. 어긋나면 양쪽이 메시지 형태를 다르게 해석해 브릿지가 오작동하므로, 워크스페이스 루트의 `pnpm doctor`가 동일성을 점검한다.
|
|
24
|
+
두 사본은 바이트 단위로 동일해야 한다. 어긋나면 양쪽이 메시지 형태를 다르게 해석해 브릿지가 오작동하므로, 워크스페이스 루트의 `pnpm run doctor`가 동일성을 점검한다.
|
|
25
25
|
|
|
26
26
|
```bash
|
|
27
27
|
cmp -s mobile/src/bridge/contract.ts web/lib/bridge/contract.ts || exit 1
|
|
@@ -4,7 +4,7 @@ import { z } from "zod";
|
|
|
4
4
|
* App ↔ Web bridge contract — SINGLE SOURCE OF TRUTH.
|
|
5
5
|
*
|
|
6
6
|
* This file is copied byte-for-byte into the web (web/lib/bridge/) and the app
|
|
7
|
-
* (mobile/src/bridge/). All copies MUST stay identical; `pnpm doctor` at the
|
|
7
|
+
* (mobile/src/bridge/). All copies MUST stay identical; `pnpm run doctor` at the
|
|
8
8
|
* workspace root checks this.
|
|
9
9
|
*
|
|
10
10
|
* Invariants (never break these):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// doctor — 워크스페이스 생성 이후의 환경·로그인 상태를 결정적으로 점검한다.
|
|
2
2
|
//
|
|
3
|
-
// 실행: 워크스페이스 루트에서 `pnpm doctor` (또는 `node scripts/doctor.mjs`).
|
|
3
|
+
// 실행: 워크스페이스 루트에서 `pnpm run doctor` (또는 `node scripts/doctor.mjs`).
|
|
4
4
|
// 의존성 0 — node 내장 모듈만 쓴다. 루트에는 node_modules가 없어도 돈다.
|
|
5
5
|
//
|
|
6
6
|
// 원칙:
|
|
@@ -191,7 +191,7 @@ function checkVercel() {
|
|
|
191
191
|
const r = run("vercel whoami", { timeout: 20000 });
|
|
192
192
|
if (r.ok) return { status: "ok", label: `Vercel CLI — 로그인됨 (${r.stdout.split("\n").pop()})` };
|
|
193
193
|
if (r.timedOut || r.networkIssue)
|
|
194
|
-
return { status: "warn", label: "Vercel CLI", detail: "네트워크 문제로 확인하지 못했습니다.", fix: "인터넷 연결 후 pnpm doctor 재실행" };
|
|
194
|
+
return { status: "warn", label: "Vercel CLI", detail: "네트워크 문제로 확인하지 못했습니다.", fix: "인터넷 연결 후 pnpm run doctor 재실행" };
|
|
195
195
|
return { status: "fail", label: "Vercel CLI", detail: "로그인이 안 되어 있습니다.", fix: "vercel login" };
|
|
196
196
|
}
|
|
197
197
|
|
|
@@ -207,7 +207,7 @@ function checkSupabase() {
|
|
|
207
207
|
const r = run(`"${bin}" projects list`, { cwd: path.join(ROOT, "web"), timeout: 25000 });
|
|
208
208
|
if (r.ok) return { status: "ok", label: "Supabase CLI — 로그인됨" };
|
|
209
209
|
if (r.timedOut || r.networkIssue)
|
|
210
|
-
return { status: "warn", label: "Supabase CLI", detail: "네트워크 문제로 확인하지 못했습니다.", fix: "인터넷 연결 후 pnpm doctor 재실행" };
|
|
210
|
+
return { status: "warn", label: "Supabase CLI", detail: "네트워크 문제로 확인하지 못했습니다.", fix: "인터넷 연결 후 pnpm run doctor 재실행" };
|
|
211
211
|
return {
|
|
212
212
|
status: "fail",
|
|
213
213
|
label: "Supabase CLI",
|
|
@@ -227,7 +227,7 @@ function checkEas() {
|
|
|
227
227
|
const r = run("eas whoami", { timeout: 20000 });
|
|
228
228
|
if (r.ok) return { status: "ok", label: `EAS CLI — 로그인됨 (${r.stdout.split("\n").pop()})` };
|
|
229
229
|
if (r.timedOut || r.networkIssue)
|
|
230
|
-
return { status: "warn", label: "EAS CLI", detail: "네트워크 문제로 확인하지 못했습니다.", fix: "인터넷 연결 후 pnpm doctor 재실행" };
|
|
230
|
+
return { status: "warn", label: "EAS CLI", detail: "네트워크 문제로 확인하지 못했습니다.", fix: "인터넷 연결 후 pnpm run doctor 재실행" };
|
|
231
231
|
return {
|
|
232
232
|
status: "fail",
|
|
233
233
|
label: "EAS CLI",
|
|
@@ -236,6 +236,22 @@ function checkEas() {
|
|
|
236
236
|
};
|
|
237
237
|
}
|
|
238
238
|
|
|
239
|
+
function checkSentry() {
|
|
240
|
+
if (!installed("sentry-cli"))
|
|
241
|
+
return { status: "fail", label: "Sentry CLI", detail: "설치되어 있지 않습니다.", fix: "npm install -g @sentry/cli" };
|
|
242
|
+
// sentry-cli info는 미로그인이어도 exit 0 — 출력의 Unauthorized로 판별한다.
|
|
243
|
+
const r = run("sentry-cli info", { timeout: 15000 });
|
|
244
|
+
if (r.ok && !/unauthorized/i.test(r.stdout + r.stderr)) return { status: "ok", label: "Sentry CLI — 로그인됨" };
|
|
245
|
+
if (r.timedOut || r.networkIssue)
|
|
246
|
+
return { status: "warn", label: "Sentry CLI", detail: "네트워크 문제로 확인하지 못했습니다.", fix: "인터넷 연결 후 pnpm run doctor 재실행" };
|
|
247
|
+
return {
|
|
248
|
+
status: "fail",
|
|
249
|
+
label: "Sentry CLI",
|
|
250
|
+
detail: "로그인이 안 되어 있습니다.",
|
|
251
|
+
fix: "sentry-cli login --global → 열린 페이지에서 [Create New Token] → 토큰 복사 → 터미널의 Enter your token:에 붙여넣기",
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
239
255
|
// 브릿지 계약 동기 — ssb/(원본)와 web/mobile 사본의 byte-동일성.
|
|
240
256
|
// 어긋난 채 배포되면 앱·웹이 메시지를 다르게 해석해 조용히 오작동한다.
|
|
241
257
|
function checkBridgeSync() {
|
|
@@ -274,7 +290,7 @@ console.log(dim(" 각 항목을 점검합니다. 네트워크 확인이 포함
|
|
|
274
290
|
|
|
275
291
|
const sections = [
|
|
276
292
|
{ title: "공통", blocking: true, checks: [checkNode, checkPnpm, checkGit, () => checkAgent(meta), checkGh, checkVercel, checkBridgeSync] },
|
|
277
|
-
{ title: "웹", blocking: true, checks: [() => checkDeps("web"), checkSupabase] },
|
|
293
|
+
{ title: "웹", blocking: true, checks: [() => checkDeps("web"), checkSupabase, checkSentry] },
|
|
278
294
|
{ title: "앱", blocking: false, note: "'앱으로 확장' 챕터 전까지는 ✗여도 정상입니다.", checks: [() => checkDeps("mobile"), checkEas] },
|
|
279
295
|
];
|
|
280
296
|
|
|
@@ -303,13 +319,15 @@ for (const section of sections) {
|
|
|
303
319
|
|
|
304
320
|
console.log("");
|
|
305
321
|
if (blockingFails === 0) {
|
|
306
|
-
|
|
322
|
+
// [앱]까지 전부 ✓면 그 사실도 인정한다 — "웹만 완료"로 읽히지 않게.
|
|
323
|
+
const verdict = appFails === 0 ? "✅ 웹·앱 개발 준비 모두 완료" : "✅ 웹 개발 준비 완료";
|
|
324
|
+
console.log(green(bold(verdict)) + (warns ? dim(` (⚠ ${warns}개는 안내를 참고해 직접 확인하세요)`) : ""));
|
|
307
325
|
} else {
|
|
308
|
-
console.log(red(bold(`✗ ${blockingFails}개 항목이 남았습니다.`)) + " 위의 → 안내를 따라 해결한 뒤 다시 실행하세요: " + bold("pnpm doctor"));
|
|
326
|
+
console.log(red(bold(`✗ ${blockingFails}개 항목이 남았습니다.`)) + " 위의 → 안내를 따라 해결한 뒤 다시 실행하세요: " + bold("pnpm run doctor"));
|
|
309
327
|
console.log("");
|
|
310
328
|
console.log("📋 직접 하기 어렵다면, 코딩 에이전트에 아래 문장을 그대로 붙여넣으세요:");
|
|
311
|
-
console.log(bold(' "pnpm doctor를 실행해서 ✗ 항목을 확인하고, 각 항목의 → 안내를 따라 해결해줘.'));
|
|
312
|
-
console.log(bold(' 브라우저 로그인이 필요한 단계는 멈추고 나에게 알려줘. 끝나면 pnpm doctor로 다시 검증해줘."'));
|
|
329
|
+
console.log(bold(' "pnpm run doctor를 실행해서 ✗ 항목을 확인하고, 각 항목의 → 안내를 따라 해결해줘.'));
|
|
330
|
+
console.log(bold(' 브라우저 로그인이 필요한 단계는 멈추고 나에게 알려줘. 끝나면 pnpm run doctor로 다시 검증해줘."'));
|
|
313
331
|
}
|
|
314
332
|
if (appFails > 0 && blockingFails === 0) {
|
|
315
333
|
console.log(dim(`📦 [앱] ✗ ${appFails}개는 '앱으로 확장' 챕터에서 해결합니다. 지금은 무시해도 됩니다.`));
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# ssb — 앱↔웹 브릿지 계약 (SSOT)
|
|
2
2
|
|
|
3
|
-
이 폴더가 앱↔웹 통신 계약의 원본이다. 메시지는 `ns: "ssb"` 네임스페이스를 달아 페이지의 다른 postMessage 트래픽과 섞이지 않는다. 이 원본은 app(`mobile/src/bridge/`)과 web(`web/lib/bridge/`)에 바이트 단위로 동일하게 복사되어 있고, 워크스페이스 루트의 `pnpm doctor`가 세 사본의 동일성을 점검한다.
|
|
3
|
+
이 폴더가 앱↔웹 통신 계약의 원본이다. 메시지는 `ns: "ssb"` 네임스페이스를 달아 페이지의 다른 postMessage 트래픽과 섞이지 않는다. 이 원본은 app(`mobile/src/bridge/`)과 web(`web/lib/bridge/`)에 바이트 단위로 동일하게 복사되어 있고, 워크스페이스 루트의 `pnpm run doctor`가 세 사본의 동일성을 점검한다.
|
|
4
4
|
|
|
5
5
|
불변식 (상세는 `contract.ts` 헤더):
|
|
6
6
|
|
|
@@ -4,7 +4,7 @@ import { z } from "zod";
|
|
|
4
4
|
* App ↔ Web bridge contract — SINGLE SOURCE OF TRUTH.
|
|
5
5
|
*
|
|
6
6
|
* This file is copied byte-for-byte into the web (web/lib/bridge/) and the app
|
|
7
|
-
* (mobile/src/bridge/). All copies MUST stay identical; `pnpm doctor` at the
|
|
7
|
+
* (mobile/src/bridge/). All copies MUST stay identical; `pnpm run doctor` at the
|
|
8
8
|
* workspace root checks this.
|
|
9
9
|
*
|
|
10
10
|
* Invariants (never break these):
|
|
@@ -4,7 +4,7 @@ import { z } from "zod";
|
|
|
4
4
|
* App ↔ Web bridge contract — SINGLE SOURCE OF TRUTH.
|
|
5
5
|
*
|
|
6
6
|
* This file is copied byte-for-byte into the web (web/lib/bridge/) and the app
|
|
7
|
-
* (mobile/src/bridge/). All copies MUST stay identical; `pnpm doctor` at the
|
|
7
|
+
* (mobile/src/bridge/). All copies MUST stay identical; `pnpm run doctor` at the
|
|
8
8
|
* workspace root checks this.
|
|
9
9
|
*
|
|
10
10
|
* Invariants (never break these):
|
package/package.json
CHANGED