create-saas-starter-workspace 0.1.0 → 0.1.1
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 -1
- package/README.md +3 -2
- package/bin/index.mjs +5 -1
- package/{templates → dist}/workspace/.claude/skills/bridge-guide/SKILL.md +3 -3
- package/{templates → dist}/workspace/.claude/skills/kickoff/SKILL.md +1 -1
- package/{templates → dist}/workspace/.claude/skills/warmup/SKILL.md +3 -3
- package/{templates → dist}/workspace/AGENTS.md +1 -1
- package/{templates → dist}/workspace/README.md +2 -2
- package/{templates → dist}/workspace/mobile/README.md +1 -1
- package/{templates → dist}/workspace/mobile/docs/web-adapter/README.md +1 -1
- package/{templates/workspace/ssb → dist/workspace/mobile/src/bridge}/contract.ts +3 -2
- package/{templates → dist}/workspace/package.json +1 -1
- package/{templates → dist}/workspace/scripts/doctor.mjs +29 -1
- package/{templates → dist}/workspace/ssb/README.md +1 -1
- package/{templates/workspace/mobile/src/bridge → dist/workspace/ssb}/contract.ts +3 -2
- package/{templates → dist}/workspace/web/lib/bridge/contract.ts +3 -2
- package/package.json +2 -2
- package/src/scaffold.mjs +3 -0
- /package/{templates → dist}/workspace/.claude/settings.json +0 -0
- /package/{templates → dist}/workspace/.claude/skills/eas-deploy-guide/SKILL.md +0 -0
- /package/{templates → dist}/workspace/.claude/skills/go-live/SKILL.md +0 -0
- /package/{templates → dist}/workspace/.claude/skills/launch/SKILL.md +0 -0
- /package/{templates → dist}/workspace/.claude/skills/native-app-guide/SKILL.md +0 -0
- /package/{templates → dist}/workspace/.claude/skills/preview/SKILL.md +0 -0
- /package/{templates → dist}/workspace/.claude/skills/probe/SKILL.md +0 -0
- /package/{templates → dist}/workspace/.claude/skills/release/SKILL.md +0 -0
- /package/{templates → dist}/workspace/.claude/skills/sketch/SKILL.md +0 -0
- /package/{templates → dist}/workspace/.claude/skills/store-release-guide/SKILL.md +0 -0
- /package/{templates → dist}/workspace/.claude/skills/vercel-cron/SKILL.md +0 -0
- /package/{templates → dist}/workspace/CLAUDE.md +0 -0
- /package/{templates → dist}/workspace/LICENSE +0 -0
- /package/{templates → dist}/workspace/START-HERE.md +0 -0
- /package/{templates → dist}/workspace/gitignore +0 -0
- /package/{templates → dist}/workspace/mobile/.env.example +0 -0
- /package/{templates → dist}/workspace/mobile/AGENTS.md +0 -0
- /package/{templates → dist}/workspace/mobile/App.tsx +0 -0
- /package/{templates → dist}/workspace/mobile/CLAUDE.md +0 -0
- /package/{templates → dist}/workspace/mobile/LICENSE +0 -0
- /package/{templates → dist}/workspace/mobile/app.config.ts +0 -0
- /package/{templates → dist}/workspace/mobile/assets/android-icon-background.png +0 -0
- /package/{templates → dist}/workspace/mobile/assets/android-icon-foreground.png +0 -0
- /package/{templates → dist}/workspace/mobile/assets/android-icon-monochrome.png +0 -0
- /package/{templates → dist}/workspace/mobile/assets/favicon.png +0 -0
- /package/{templates → dist}/workspace/mobile/assets/icon.png +0 -0
- /package/{templates → dist}/workspace/mobile/assets/splash-icon.png +0 -0
- /package/{templates → dist}/workspace/mobile/docs/web-adapter/route-app-bridge.ts +0 -0
- /package/{templates → dist}/workspace/mobile/eas.json +0 -0
- /package/{templates → dist}/workspace/mobile/gitignore +0 -0
- /package/{templates → dist}/workspace/mobile/index.ts +0 -0
- /package/{templates → dist}/workspace/mobile/package.json +0 -0
- /package/{templates → dist}/workspace/mobile/pnpm-lock.yaml +0 -0
- /package/{templates → dist}/workspace/mobile/src/auth/LoginScreen.tsx +0 -0
- /package/{templates → dist}/workspace/mobile/src/bridge/capabilities.test.ts +0 -0
- /package/{templates → dist}/workspace/mobile/src/bridge/capabilities.ts +0 -0
- /package/{templates → dist}/workspace/mobile/src/bridge/contract.test.ts +0 -0
- /package/{templates → dist}/workspace/mobile/src/bridge/messaging.test.ts +0 -0
- /package/{templates → dist}/workspace/mobile/src/bridge/messaging.ts +0 -0
- /package/{templates → dist}/workspace/mobile/src/bridge/reader.test.ts +0 -0
- /package/{templates → dist}/workspace/mobile/src/bridge/reader.ts +0 -0
- /package/{templates → dist}/workspace/mobile/src/bridge/router.test.ts +0 -0
- /package/{templates → dist}/workspace/mobile/src/bridge/router.ts +0 -0
- /package/{templates → dist}/workspace/mobile/src/config/env.ts +0 -0
- /package/{templates → dist}/workspace/mobile/src/i18n.ts +0 -0
- /package/{templates → dist}/workspace/mobile/src/session/secureSession.ts +0 -0
- /package/{templates → dist}/workspace/mobile/src/session/sessionHandoff.ts +0 -0
- /package/{templates → dist}/workspace/mobile/src/ui/ErrorView.tsx +0 -0
- /package/{templates → dist}/workspace/mobile/src/ui/LoadingView.tsx +0 -0
- /package/{templates → dist}/workspace/mobile/src/ui/OfflineView.tsx +0 -0
- /package/{templates → dist}/workspace/mobile/src/webview/Host.tsx +0 -0
- /package/{templates → dist}/workspace/mobile/src/webview/linkBoundary.test.ts +0 -0
- /package/{templates → dist}/workspace/mobile/src/webview/linkBoundary.ts +0 -0
- /package/{templates → dist}/workspace/mobile/tsconfig.json +0 -0
- /package/{templates → dist}/workspace/mobile/vitest.config.ts +0 -0
- /package/{templates → dist}/workspace/ssb/reader.ts +0 -0
- /package/{templates → dist}/workspace/web/.env.example +0 -0
- /package/{templates → dist}/workspace/web/.gitattributes +0 -0
- /package/{templates → dist}/workspace/web/.github/workflows/ci.yml +0 -0
- /package/{templates → dist}/workspace/web/.vscode/settings.json +0 -0
- /package/{templates → dist}/workspace/web/AGENTS.md +0 -0
- /package/{templates → dist}/workspace/web/CLAUDE.md +0 -0
- /package/{templates → dist}/workspace/web/DESIGN.md +0 -0
- /package/{templates → dist}/workspace/web/LICENSE +0 -0
- /package/{templates → dist}/workspace/web/README.md +0 -0
- /package/{templates → dist}/workspace/web/app/error.tsx +0 -0
- /package/{templates → dist}/workspace/web/app/favicon.ico +0 -0
- /package/{templates → dist}/workspace/web/app/global-error.tsx +0 -0
- /package/{templates → dist}/workspace/web/app/globals.css +0 -0
- /package/{templates → dist}/workspace/web/app/layout.tsx +0 -0
- /package/{templates → dist}/workspace/web/app/not-found.tsx +0 -0
- /package/{templates → dist}/workspace/web/app/page.tsx +0 -0
- /package/{templates → dist}/workspace/web/components/ui/button.tsx +0 -0
- /package/{templates → dist}/workspace/web/components.json +0 -0
- /package/{templates → dist}/workspace/web/docs/ENVIRONMENTS.md +0 -0
- /package/{templates → dist}/workspace/web/docs/LIMITS.md +0 -0
- /package/{templates → dist}/workspace/web/eslint.config.mjs +0 -0
- /package/{templates → dist}/workspace/web/features/.gitkeep +0 -0
- /package/{templates → dist}/workspace/web/gitignore +0 -0
- /package/{templates → dist}/workspace/web/instrumentation-client.ts +0 -0
- /package/{templates → dist}/workspace/web/instrumentation.ts +0 -0
- /package/{templates → dist}/workspace/web/lib/app-env.ts +0 -0
- /package/{templates → dist}/workspace/web/lib/bridge/reader.ts +0 -0
- /package/{templates → dist}/workspace/web/lib/env.server.ts +0 -0
- /package/{templates → dist}/workspace/web/lib/env.ts +0 -0
- /package/{templates → dist}/workspace/web/lib/logger.ts +0 -0
- /package/{templates → dist}/workspace/web/lib/supabase/admin.ts +0 -0
- /package/{templates → dist}/workspace/web/lib/supabase/client.ts +0 -0
- /package/{templates → dist}/workspace/web/lib/supabase/server.ts +0 -0
- /package/{templates → dist}/workspace/web/lib/utils.ts +0 -0
- /package/{templates → dist}/workspace/web/next.config.ts +0 -0
- /package/{templates → dist}/workspace/web/npmrc +0 -0
- /package/{templates → dist}/workspace/web/package.json +0 -0
- /package/{templates → dist}/workspace/web/pnpm-lock.yaml +0 -0
- /package/{templates → dist}/workspace/web/postcss.config.mjs +0 -0
- /package/{templates → dist}/workspace/web/sentry.edge.config.ts +0 -0
- /package/{templates → dist}/workspace/web/sentry.server.config.ts +0 -0
- /package/{templates → dist}/workspace/web/supabase/migrations/.gitkeep +0 -0
- /package/{templates → dist}/workspace/web/tests/setup.ts +0 -0
- /package/{templates → dist}/workspace/web/tests/utils.test.ts +0 -0
- /package/{templates → dist}/workspace/web/tsconfig.json +0 -0
- /package/{templates → dist}/workspace/web/vercel.json +0 -0
- /package/{templates → dist}/workspace/web/vitest.config.ts +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.1.
|
|
3
|
+
## 0.1.1 (미출판)
|
|
4
|
+
|
|
5
|
+
- doctor [공통]에 브릿지 계약 점검 추가: `ssb/`(원본)와 `web/lib/bridge/`·`mobile/src/bridge/` 사본의 byte-동일성을 단언한다. 템플릿 문서들이 약속하던 "CI checksum"(실재하지 않던 게이트)을 doctor 기준으로 전부 정정.
|
|
6
|
+
- 워크스페이스 루트의 자기명칭도 프로젝트 이름으로 렌더: 루트 `package.json`(`<이름>-workspace`)과 `README.md`·`AGENTS.md` 제목. `pnpm doctor` 등 루트 명령의 출력에 학생 프로젝트명이 보인다.
|
|
7
|
+
- bin이 `dist/` 부재 시(개발 트리 직접 실행) `npm run stage` 안내와 함께 명확히 실패한다.
|
|
8
|
+
- 패키지 내부 레이아웃 정리: 템플릿 SSOT를 패키지 내 `template/`(소스)로 이동, stage 산출물은 `templates/` → `dist/`(표준 src/dist 관례). 소비자(npx) 영향 없음.
|
|
9
|
+
|
|
10
|
+
## 0.1.0
|
|
4
11
|
|
|
5
12
|
- 첫 버전. 통합 워크스페이스(web + mobile + ssb + .claude) 생성, 프로젝트 이름 렌더, pnpm 의존성 설치(`--frozen-lockfile`), doctor 자동 실행.
|
|
6
13
|
- `--agent <claude|codex|opencode>` 선택을 `workspace.json`에 기록하고 doctor가 읽는다.
|
package/README.md
CHANGED
|
@@ -12,8 +12,9 @@ npx create-saas-starter-workspace@latest my-service
|
|
|
12
12
|
|
|
13
13
|
## 개발 (강사용)
|
|
14
14
|
|
|
15
|
-
- 템플릿 SSOT는
|
|
15
|
+
- 템플릿 SSOT는 `template/`(편집용 원본)입니다. 고친 뒤 `npm run stage`로 `dist/workspace`(출판용 산출물)에 동기화합니다. `dist/`를 직접 고치지 마세요. `template/`는 `files` 허용목록에 없어 출판되지 않습니다.
|
|
16
16
|
- `npm test`가 stage를 먼저 실행하고, 작업 트리와 출판 채널(tarball) 양쪽을 검증합니다. `prepublishOnly`가 stage·테스트를 강제하므로 출판 시 드리프트가 끼어들 수 없습니다.
|
|
17
|
-
-
|
|
17
|
+
- `engines`는 `>=20.12`로 둡니다 — npx로 직접 실행하는 사용자를 막지 않기 위한 하한이고, 부트스트랩 경로는 24 LTS를 설치합니다(doctor는 22 미만에 ⚠).
|
|
18
|
+
- `.gitignore`·`.npmrc`는 npm pack이 strip하므로 dotless(`gitignore`·`npmrc`)로 스테이지하고 생성기가 복원합니다. 단 템플릿 루트의 것은 SSOT에서부터 dotless(`template/gitignore`)입니다 — 화이트리스트 방식이라 .gitignore 그대로 두면 이 레포의 git이 템플릿 소스(web/·mobile/)를 추적하지 못하기 때문입니다.
|
|
18
19
|
|
|
19
20
|
강의 수강생에게 배포되는 패키지입니다. [LICENSE](LICENSE).
|
package/bin/index.mjs
CHANGED
|
@@ -15,7 +15,7 @@ import { scaffold } from "../src/scaffold.mjs";
|
|
|
15
15
|
|
|
16
16
|
const PKG_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
17
17
|
const pkg = JSON.parse(readFileSync(path.join(PKG_ROOT, "package.json"), "utf8"));
|
|
18
|
-
const TEMPLATE_DIR = path.join(PKG_ROOT, "
|
|
18
|
+
const TEMPLATE_DIR = path.join(PKG_ROOT, "dist", "workspace");
|
|
19
19
|
|
|
20
20
|
const AGENTS = ["claude", "codex", "opencode"];
|
|
21
21
|
|
|
@@ -136,6 +136,10 @@ async function main() {
|
|
|
136
136
|
process.on("SIGINT", () => process.exit(130));
|
|
137
137
|
|
|
138
138
|
const opts = parseArgs(process.argv.slice(2));
|
|
139
|
+
if (!existsSync(TEMPLATE_DIR)) {
|
|
140
|
+
// 출판본·npm test 경로에서는 항상 존재한다. 개발 트리에서 bin을 직접 실행한 경우뿐.
|
|
141
|
+
fail("템플릿 산출물(dist/)이 없습니다. 개발 트리에서 직접 실행했다면 먼저 npm run stage 를 실행하세요.");
|
|
142
|
+
}
|
|
139
143
|
const name = await resolveName(opts.name);
|
|
140
144
|
const dest = path.resolve(process.cwd(), name);
|
|
141
145
|
|
|
@@ -6,7 +6,7 @@ user-invocable: false
|
|
|
6
6
|
|
|
7
7
|
# 앱↔웹 대화 규격 (ssb 계약)
|
|
8
8
|
|
|
9
|
-
앱과 웹은 정해진 형식의 메시지로만 대화한다. 그 형식 정의는 양쪽이 똑같은 파일 하나(`src/bridge/contract.ts` + `reader.ts`)로 공유한다. 어긋나면
|
|
9
|
+
앱과 웹은 정해진 형식의 메시지로만 대화한다. 그 형식 정의는 양쪽이 똑같은 파일 하나(`src/bridge/contract.ts` + `reader.ts`)로 공유한다. 어긋나면 워크스페이스 루트의 `pnpm 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.
|
|
55
|
+
7. 동기화한다. 같은 변경을 `ssb/`(원본)와 `web/lib/bridge/`·`mobile/src/bridge/` 세 곳에 바이트 단위로 동일하게 반영하고, 워크스페이스 루트에서 `pnpm doctor`로 동일성을 확인한다.
|
|
56
56
|
|
|
57
57
|
> 새 기능은 앱에 먼저 들어가고(HELLO capability), 웹이 나중에 감지해 쓴다. 순서가 어긋나도 웹 코드가 capability 게이트로 막혀 있어 안전하다.
|
|
58
58
|
|
|
@@ -68,4 +68,4 @@ user-invocable: false
|
|
|
68
68
|
|
|
69
69
|
## 어댑터 (웹 측)
|
|
70
70
|
|
|
71
|
-
|
|
71
|
+
계약(`contract.ts`·`reader.ts`)은 이미 `web/lib/bridge/`에 설치돼 있다. 옵트인은 세션 핸드오프 레이어뿐이다 — 네이티브 소셜 로그인을 켤 때만 `docs/web-adapter/`의 POST 라우트(`route-app-bridge.ts`)·nonce·CSP를 웹 repo에 추가한다. 웹이 추가하는 의존성은 `zod` 하나뿐이다. 네이티브·webview 라이브러리는 웹에 들어가지 않는다. 웹은 `window.ReactNativeWebView` 전역으로만 앱에 닿는다. gronxb 등 라이브러리는 기각한다(네이티브-API-소유·monorepo 전제가 web=SSOT와 충돌한다). 설치하더라도 네이티브-소셜 핸드오프는 idToken 획득 코드가 없어 `requestSessionHandoff` 호출자가 여전히 없는 fast-follow 흐름이다.
|
|
@@ -44,7 +44,7 @@ Owner confirm. 비용을 알린 뒤 계속할지 확정받고서야 다음 단
|
|
|
44
44
|
|
|
45
45
|
## 4. 웹 어댑터 (web/)
|
|
46
46
|
|
|
47
|
-
계약(`contract.ts`·`reader.ts`)은 이미 `web/lib/bridge/`에 바이트 단위로 동일하게 설치돼 있고(
|
|
47
|
+
계약(`contract.ts`·`reader.ts`)은 이미 `web/lib/bridge/`에 바이트 단위로 동일하게 설치돼 있고(`pnpm doctor`가 동일성을 점검한다) 다시 복사하지 않는다. 웹이 앱과 실제로 대화하게 만드는 얇은 옵트인 레이어만 필요할 때 추가한다. 규약·보안 불변식은 인라인하지 않고 `bridge-guide`를 따른다.
|
|
48
48
|
|
|
49
49
|
세션 핸드오프는 fast-follow다. v1은 호출자가 없다. 기본 로그인은 웹뷰 안의 웹 로그인이 그대로 동작하고, 핸드오프 라우트(POST `/auth/app-bridge` + nonce)는 네이티브 소셜 로그인을 켤 때만 살아난다. 토큰은 브릿지·URL로 보내지 않는다(1회용 nonce만 보낸다).
|
|
50
50
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: warmup
|
|
3
|
-
description: Verify the AI agent's tools and environment (CLI installs, logins) and install/audit dependencies in web/
|
|
3
|
+
description: Verify the AI agent's tools and environment (CLI installs, logins) and install/audit dependencies in web/ and mobile/, fixing issues where possible. Use when the Owner runs /warmup, just opened the workspace, or before tasks that depend on external CLIs working.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# /warmup
|
|
@@ -14,7 +14,7 @@ Owner의 행동이 필요한 부분은 AskUserQuestion으로 해결한다.
|
|
|
14
14
|
## CLI
|
|
15
15
|
|
|
16
16
|
- `node` — 현 LTS
|
|
17
|
-
- `pnpm` —
|
|
17
|
+
- `pnpm` — 10 이상 (각 repo `package.json`의 `packageManager`로 버전 고정)
|
|
18
18
|
- `git`
|
|
19
19
|
- `gh` — 로그인
|
|
20
20
|
- `vercel` — 로그인
|
|
@@ -28,6 +28,6 @@ Owner의 행동이 필요한 부분은 AskUserQuestion으로 해결한다.
|
|
|
28
28
|
워크스페이스 루트의 `package.json`은 `pnpm doctor`(환경 점검) 진입점만 가진다 — 루트에 의존성을 설치하지 않는다. `web/`과 `mobile/`은 각자 독립 프로젝트이므로 의존성도 각자 설치한다.
|
|
29
29
|
|
|
30
30
|
- `web/`에서 `pnpm install` 후 `pnpm audit`.
|
|
31
|
-
- `mobile
|
|
31
|
+
- `mobile/`에서도 동일하게 한다.
|
|
32
32
|
|
|
33
33
|
세부 정책은 각 repo의 `.npmrc`와 CI 설정을 따른다. 보안 게이트(cooldown, audit level 등)는 우회하거나 수정하지 않는다.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# my-space
|
|
2
2
|
|
|
3
3
|
웹 SaaS를 만들고, 원하면 그대로 스토어 앱으로 감싸는 스타터입니다. 화면·코드·배포는 AI 코딩 에이전트가 맡고, 당신은 무엇을 만들지 정합니다.
|
|
4
4
|
|
|
@@ -17,4 +17,4 @@
|
|
|
17
17
|
|
|
18
18
|
`web/`과 `mobile/`은 각자 배포되는 독립 프로젝트입니다. 이 워크스페이스는 둘을 잇는 공유 맥락(에이전트 도구·계약)만 둡니다. 에이전트 운영 규칙은 [AGENTS.md](AGENTS.md)에 있습니다.
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
이 워크스페이스는 강의용 템플릿에서 생성되었습니다. [LICENSE](LICENSE).
|
|
@@ -8,7 +8,7 @@ Owner가 만든 웹을 감싸는 앱(웹뷰 래퍼)입니다. 화면과 기능
|
|
|
8
8
|
|
|
9
9
|
- 웹을 고치면 앱도 같이 바뀝니다. 대부분의 변경은 웹만 다시 배포하면 끝이고, 앱을 다시 만들거나 제출할 필요가 없습니다.
|
|
10
10
|
- 셸이 채우는 것: 외부 링크를 모바일 브라우저로 열기, Android 뒤로가기, 카메라·마이크·파일 올리기 권한, 화면 안전 여백, 오프라인 화면, 첫 로딩·크래시 복구 화면. 웹뷰만으로 막히는 네이티브 빈틈만 메웁니다.
|
|
11
|
-
- 소셜 로그인(구글·애플): 웹뷰
|
|
11
|
+
- 소셜 로그인(구글·애플): 구글 등은 웹뷰 안 로그인 창을 보안 정책으로 차단할 수 있어, 필요하면 외부 브라우저로 열거나 네이티브 로그인 화면(옵트인)을 켭니다.
|
|
12
12
|
|
|
13
13
|
## 시작 전에 (한 번만)
|
|
14
14
|
|
|
@@ -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
|
-
두 사본은 바이트 단위로 동일해야 한다. 어긋나면 양쪽이 메시지 형태를 다르게 해석해 브릿지가 오작동하므로,
|
|
24
|
+
두 사본은 바이트 단위로 동일해야 한다. 어긋나면 양쪽이 메시지 형태를 다르게 해석해 브릿지가 오작동하므로, 워크스페이스 루트의 `pnpm doctor`가 동일성을 점검한다.
|
|
25
25
|
|
|
26
26
|
```bash
|
|
27
27
|
cmp -s mobile/src/bridge/contract.ts web/lib/bridge/contract.ts || exit 1
|
|
@@ -3,8 +3,9 @@ import { z } from "zod";
|
|
|
3
3
|
/**
|
|
4
4
|
* App ↔ Web bridge contract — SINGLE SOURCE OF TRUTH.
|
|
5
5
|
*
|
|
6
|
-
* This file is copied byte-for-byte into the web
|
|
7
|
-
*
|
|
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
|
|
8
|
+
* workspace root checks this.
|
|
8
9
|
*
|
|
9
10
|
* Invariants (never break these):
|
|
10
11
|
* 1. Every message is namespaced (`ns: "ssb"`) so unrelated postMessage traffic on
|
|
@@ -236,6 +236,34 @@ function checkEas() {
|
|
|
236
236
|
};
|
|
237
237
|
}
|
|
238
238
|
|
|
239
|
+
// 브릿지 계약 동기 — ssb/(원본)와 web/mobile 사본의 byte-동일성.
|
|
240
|
+
// 어긋난 채 배포되면 앱·웹이 메시지를 다르게 해석해 조용히 오작동한다.
|
|
241
|
+
function checkBridgeSync() {
|
|
242
|
+
const diverged = [];
|
|
243
|
+
for (const file of ["contract.ts", "reader.ts"]) {
|
|
244
|
+
let original;
|
|
245
|
+
try {
|
|
246
|
+
original = readFileSync(path.join(ROOT, "ssb", file));
|
|
247
|
+
} catch {
|
|
248
|
+
return { status: "warn", label: "브릿지 계약 (ssb)", detail: `ssb/${file}를 읽지 못했습니다.` };
|
|
249
|
+
}
|
|
250
|
+
for (const dir of ["web/lib/bridge", "mobile/src/bridge"]) {
|
|
251
|
+
try {
|
|
252
|
+
if (!original.equals(readFileSync(path.join(ROOT, dir, file)))) diverged.push(`${dir}/${file}`);
|
|
253
|
+
} catch {
|
|
254
|
+
diverged.push(`${dir}/${file} (없음)`);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
if (diverged.length === 0) return { status: "ok", label: "브릿지 계약 — 3곳 동일 (ssb · web · mobile)" };
|
|
259
|
+
return {
|
|
260
|
+
status: "fail",
|
|
261
|
+
label: "브릿지 계약",
|
|
262
|
+
detail: `ssb/ 원본과 어긋났습니다: ${diverged.join(", ")}`,
|
|
263
|
+
fix: '코딩 에이전트에게 부탁: "ssb/의 contract.ts·reader.ts를 web/lib/bridge/와 mobile/src/bridge/에 바이트 단위로 동일하게 복사해줘"',
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
239
267
|
// ---------- 실행 ----------
|
|
240
268
|
|
|
241
269
|
const meta = readWorkspaceMeta();
|
|
@@ -245,7 +273,7 @@ console.log(bold(`🩺 doctor — ${meta.name ?? path.basename(ROOT)} 환경 점
|
|
|
245
273
|
console.log(dim(" 각 항목을 점검합니다. 네트워크 확인이 포함되어 몇십 초 걸릴 수 있습니다."));
|
|
246
274
|
|
|
247
275
|
const sections = [
|
|
248
|
-
{ title: "공통", blocking: true, checks: [checkNode, checkPnpm, checkGit, () => checkAgent(meta), checkGh, checkVercel] },
|
|
276
|
+
{ title: "공통", blocking: true, checks: [checkNode, checkPnpm, checkGit, () => checkAgent(meta), checkGh, checkVercel, checkBridgeSync] },
|
|
249
277
|
{ title: "웹", blocking: true, checks: [() => checkDeps("web"), checkSupabase] },
|
|
250
278
|
{ title: "앱", blocking: false, note: "'앱으로 확장' 챕터 전까지는 ✗여도 정상입니다.", checks: [() => checkDeps("mobile"), checkEas] },
|
|
251
279
|
];
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# ssb — 앱↔웹 브릿지 계약 (SSOT)
|
|
2
2
|
|
|
3
|
-
이 폴더가 앱↔웹 통신 계약의 원본이다. 메시지는 `ns: "ssb"` 네임스페이스를 달아 페이지의 다른 postMessage 트래픽과 섞이지 않는다.
|
|
3
|
+
이 폴더가 앱↔웹 통신 계약의 원본이다. 메시지는 `ns: "ssb"` 네임스페이스를 달아 페이지의 다른 postMessage 트래픽과 섞이지 않는다. 이 원본은 app(`mobile/src/bridge/`)과 web(`web/lib/bridge/`)에 바이트 단위로 동일하게 복사되어 있고, 워크스페이스 루트의 `pnpm doctor`가 세 사본의 동일성을 점검한다.
|
|
4
4
|
|
|
5
5
|
불변식 (상세는 `contract.ts` 헤더):
|
|
6
6
|
|
|
@@ -3,8 +3,9 @@ import { z } from "zod";
|
|
|
3
3
|
/**
|
|
4
4
|
* App ↔ Web bridge contract — SINGLE SOURCE OF TRUTH.
|
|
5
5
|
*
|
|
6
|
-
* This file is copied byte-for-byte into the web
|
|
7
|
-
*
|
|
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
|
|
8
|
+
* workspace root checks this.
|
|
8
9
|
*
|
|
9
10
|
* Invariants (never break these):
|
|
10
11
|
* 1. Every message is namespaced (`ns: "ssb"`) so unrelated postMessage traffic on
|
|
@@ -3,8 +3,9 @@ import { z } from "zod";
|
|
|
3
3
|
/**
|
|
4
4
|
* App ↔ Web bridge contract — SINGLE SOURCE OF TRUTH.
|
|
5
5
|
*
|
|
6
|
-
* This file is copied byte-for-byte into the web
|
|
7
|
-
*
|
|
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
|
|
8
|
+
* workspace root checks this.
|
|
8
9
|
*
|
|
9
10
|
* Invariants (never break these):
|
|
10
11
|
* 1. Every message is namespaced (`ns: "ssb"`) so unrelated postMessage traffic on
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-saas-starter-workspace",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "강의용 SaaS 워크스페이스(web + mobile + 에이전트 도구)를 한 번에 만드는 생성기. 생성 → 의존성 설치 → doctor 점검까지 한 흐름.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"files": [
|
|
11
11
|
"bin",
|
|
12
12
|
"src",
|
|
13
|
-
"
|
|
13
|
+
"dist",
|
|
14
14
|
"CHANGELOG.md"
|
|
15
15
|
],
|
|
16
16
|
"engines": {
|
package/src/scaffold.mjs
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|