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.
Files changed (121) hide show
  1. package/CHANGELOG.md +8 -1
  2. package/README.md +3 -2
  3. package/bin/index.mjs +5 -1
  4. package/{templates → dist}/workspace/.claude/skills/bridge-guide/SKILL.md +3 -3
  5. package/{templates → dist}/workspace/.claude/skills/kickoff/SKILL.md +1 -1
  6. package/{templates → dist}/workspace/.claude/skills/warmup/SKILL.md +3 -3
  7. package/{templates → dist}/workspace/AGENTS.md +1 -1
  8. package/{templates → dist}/workspace/README.md +2 -2
  9. package/{templates → dist}/workspace/mobile/README.md +1 -1
  10. package/{templates → dist}/workspace/mobile/docs/web-adapter/README.md +1 -1
  11. package/{templates/workspace/ssb → dist/workspace/mobile/src/bridge}/contract.ts +3 -2
  12. package/{templates → dist}/workspace/package.json +1 -1
  13. package/{templates → dist}/workspace/scripts/doctor.mjs +29 -1
  14. package/{templates → dist}/workspace/ssb/README.md +1 -1
  15. package/{templates/workspace/mobile/src/bridge → dist/workspace/ssb}/contract.ts +3 -2
  16. package/{templates → dist}/workspace/web/lib/bridge/contract.ts +3 -2
  17. package/package.json +2 -2
  18. package/src/scaffold.mjs +3 -0
  19. /package/{templates → dist}/workspace/.claude/settings.json +0 -0
  20. /package/{templates → dist}/workspace/.claude/skills/eas-deploy-guide/SKILL.md +0 -0
  21. /package/{templates → dist}/workspace/.claude/skills/go-live/SKILL.md +0 -0
  22. /package/{templates → dist}/workspace/.claude/skills/launch/SKILL.md +0 -0
  23. /package/{templates → dist}/workspace/.claude/skills/native-app-guide/SKILL.md +0 -0
  24. /package/{templates → dist}/workspace/.claude/skills/preview/SKILL.md +0 -0
  25. /package/{templates → dist}/workspace/.claude/skills/probe/SKILL.md +0 -0
  26. /package/{templates → dist}/workspace/.claude/skills/release/SKILL.md +0 -0
  27. /package/{templates → dist}/workspace/.claude/skills/sketch/SKILL.md +0 -0
  28. /package/{templates → dist}/workspace/.claude/skills/store-release-guide/SKILL.md +0 -0
  29. /package/{templates → dist}/workspace/.claude/skills/vercel-cron/SKILL.md +0 -0
  30. /package/{templates → dist}/workspace/CLAUDE.md +0 -0
  31. /package/{templates → dist}/workspace/LICENSE +0 -0
  32. /package/{templates → dist}/workspace/START-HERE.md +0 -0
  33. /package/{templates → dist}/workspace/gitignore +0 -0
  34. /package/{templates → dist}/workspace/mobile/.env.example +0 -0
  35. /package/{templates → dist}/workspace/mobile/AGENTS.md +0 -0
  36. /package/{templates → dist}/workspace/mobile/App.tsx +0 -0
  37. /package/{templates → dist}/workspace/mobile/CLAUDE.md +0 -0
  38. /package/{templates → dist}/workspace/mobile/LICENSE +0 -0
  39. /package/{templates → dist}/workspace/mobile/app.config.ts +0 -0
  40. /package/{templates → dist}/workspace/mobile/assets/android-icon-background.png +0 -0
  41. /package/{templates → dist}/workspace/mobile/assets/android-icon-foreground.png +0 -0
  42. /package/{templates → dist}/workspace/mobile/assets/android-icon-monochrome.png +0 -0
  43. /package/{templates → dist}/workspace/mobile/assets/favicon.png +0 -0
  44. /package/{templates → dist}/workspace/mobile/assets/icon.png +0 -0
  45. /package/{templates → dist}/workspace/mobile/assets/splash-icon.png +0 -0
  46. /package/{templates → dist}/workspace/mobile/docs/web-adapter/route-app-bridge.ts +0 -0
  47. /package/{templates → dist}/workspace/mobile/eas.json +0 -0
  48. /package/{templates → dist}/workspace/mobile/gitignore +0 -0
  49. /package/{templates → dist}/workspace/mobile/index.ts +0 -0
  50. /package/{templates → dist}/workspace/mobile/package.json +0 -0
  51. /package/{templates → dist}/workspace/mobile/pnpm-lock.yaml +0 -0
  52. /package/{templates → dist}/workspace/mobile/src/auth/LoginScreen.tsx +0 -0
  53. /package/{templates → dist}/workspace/mobile/src/bridge/capabilities.test.ts +0 -0
  54. /package/{templates → dist}/workspace/mobile/src/bridge/capabilities.ts +0 -0
  55. /package/{templates → dist}/workspace/mobile/src/bridge/contract.test.ts +0 -0
  56. /package/{templates → dist}/workspace/mobile/src/bridge/messaging.test.ts +0 -0
  57. /package/{templates → dist}/workspace/mobile/src/bridge/messaging.ts +0 -0
  58. /package/{templates → dist}/workspace/mobile/src/bridge/reader.test.ts +0 -0
  59. /package/{templates → dist}/workspace/mobile/src/bridge/reader.ts +0 -0
  60. /package/{templates → dist}/workspace/mobile/src/bridge/router.test.ts +0 -0
  61. /package/{templates → dist}/workspace/mobile/src/bridge/router.ts +0 -0
  62. /package/{templates → dist}/workspace/mobile/src/config/env.ts +0 -0
  63. /package/{templates → dist}/workspace/mobile/src/i18n.ts +0 -0
  64. /package/{templates → dist}/workspace/mobile/src/session/secureSession.ts +0 -0
  65. /package/{templates → dist}/workspace/mobile/src/session/sessionHandoff.ts +0 -0
  66. /package/{templates → dist}/workspace/mobile/src/ui/ErrorView.tsx +0 -0
  67. /package/{templates → dist}/workspace/mobile/src/ui/LoadingView.tsx +0 -0
  68. /package/{templates → dist}/workspace/mobile/src/ui/OfflineView.tsx +0 -0
  69. /package/{templates → dist}/workspace/mobile/src/webview/Host.tsx +0 -0
  70. /package/{templates → dist}/workspace/mobile/src/webview/linkBoundary.test.ts +0 -0
  71. /package/{templates → dist}/workspace/mobile/src/webview/linkBoundary.ts +0 -0
  72. /package/{templates → dist}/workspace/mobile/tsconfig.json +0 -0
  73. /package/{templates → dist}/workspace/mobile/vitest.config.ts +0 -0
  74. /package/{templates → dist}/workspace/ssb/reader.ts +0 -0
  75. /package/{templates → dist}/workspace/web/.env.example +0 -0
  76. /package/{templates → dist}/workspace/web/.gitattributes +0 -0
  77. /package/{templates → dist}/workspace/web/.github/workflows/ci.yml +0 -0
  78. /package/{templates → dist}/workspace/web/.vscode/settings.json +0 -0
  79. /package/{templates → dist}/workspace/web/AGENTS.md +0 -0
  80. /package/{templates → dist}/workspace/web/CLAUDE.md +0 -0
  81. /package/{templates → dist}/workspace/web/DESIGN.md +0 -0
  82. /package/{templates → dist}/workspace/web/LICENSE +0 -0
  83. /package/{templates → dist}/workspace/web/README.md +0 -0
  84. /package/{templates → dist}/workspace/web/app/error.tsx +0 -0
  85. /package/{templates → dist}/workspace/web/app/favicon.ico +0 -0
  86. /package/{templates → dist}/workspace/web/app/global-error.tsx +0 -0
  87. /package/{templates → dist}/workspace/web/app/globals.css +0 -0
  88. /package/{templates → dist}/workspace/web/app/layout.tsx +0 -0
  89. /package/{templates → dist}/workspace/web/app/not-found.tsx +0 -0
  90. /package/{templates → dist}/workspace/web/app/page.tsx +0 -0
  91. /package/{templates → dist}/workspace/web/components/ui/button.tsx +0 -0
  92. /package/{templates → dist}/workspace/web/components.json +0 -0
  93. /package/{templates → dist}/workspace/web/docs/ENVIRONMENTS.md +0 -0
  94. /package/{templates → dist}/workspace/web/docs/LIMITS.md +0 -0
  95. /package/{templates → dist}/workspace/web/eslint.config.mjs +0 -0
  96. /package/{templates → dist}/workspace/web/features/.gitkeep +0 -0
  97. /package/{templates → dist}/workspace/web/gitignore +0 -0
  98. /package/{templates → dist}/workspace/web/instrumentation-client.ts +0 -0
  99. /package/{templates → dist}/workspace/web/instrumentation.ts +0 -0
  100. /package/{templates → dist}/workspace/web/lib/app-env.ts +0 -0
  101. /package/{templates → dist}/workspace/web/lib/bridge/reader.ts +0 -0
  102. /package/{templates → dist}/workspace/web/lib/env.server.ts +0 -0
  103. /package/{templates → dist}/workspace/web/lib/env.ts +0 -0
  104. /package/{templates → dist}/workspace/web/lib/logger.ts +0 -0
  105. /package/{templates → dist}/workspace/web/lib/supabase/admin.ts +0 -0
  106. /package/{templates → dist}/workspace/web/lib/supabase/client.ts +0 -0
  107. /package/{templates → dist}/workspace/web/lib/supabase/server.ts +0 -0
  108. /package/{templates → dist}/workspace/web/lib/utils.ts +0 -0
  109. /package/{templates → dist}/workspace/web/next.config.ts +0 -0
  110. /package/{templates → dist}/workspace/web/npmrc +0 -0
  111. /package/{templates → dist}/workspace/web/package.json +0 -0
  112. /package/{templates → dist}/workspace/web/pnpm-lock.yaml +0 -0
  113. /package/{templates → dist}/workspace/web/postcss.config.mjs +0 -0
  114. /package/{templates → dist}/workspace/web/sentry.edge.config.ts +0 -0
  115. /package/{templates → dist}/workspace/web/sentry.server.config.ts +0 -0
  116. /package/{templates → dist}/workspace/web/supabase/migrations/.gitkeep +0 -0
  117. /package/{templates → dist}/workspace/web/tests/setup.ts +0 -0
  118. /package/{templates → dist}/workspace/web/tests/utils.test.ts +0 -0
  119. /package/{templates → dist}/workspace/web/tsconfig.json +0 -0
  120. /package/{templates → dist}/workspace/web/vercel.json +0 -0
  121. /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.0 (미출판)
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는 형제 폴더 `../saas-starter-workspace`입니다. SSOT를 고친 뒤 `npm run stage`로 `templates/workspace`에 동기화합니다. `templates/`를 직접 고치지 마세요.
15
+ - 템플릿 SSOT는 `template/`(편집용 원본)입니다. 고친 뒤 `npm run stage`로 `dist/workspace`(출판용 산출물)에 동기화합니다. `dist/`를 직접 고치지 마세요. `template/`는 `files` 허용목록에 없어 출판되지 않습니다.
16
16
  - `npm test`가 stage를 먼저 실행하고, 작업 트리와 출판 채널(tarball) 양쪽을 검증합니다. `prepublishOnly`가 stage·테스트를 강제하므로 출판 시 드리프트가 끼어들 수 없습니다.
17
- - `.gitignore`·`.npmrc`는 npm pack이 strip하므로 dotless(`gitignore`·`npmrc`)스테이지하고 생성기가 복원합니다.
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, "templates", "workspace");
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`)로 공유한다. 어긋나면 CI checksum이 막는다.
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. 웹과 동기화한다. `contract.ts`·`reader.ts`를 repo에 바이트 단위로 동일하게 복사하고 checksum을 갱신한다. CI가 불일치를 게이트한다.
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
- repo에는 v1 기준 아무것도 설치돼 있지 않다. Owner가 `docs/web-adapter/`의 `contract.ts`·`reader.ts`·POST 라우트(`route-app-bridge.ts`)·nonce·CSP를 웹 repo에 직접 복사·설치해야 한다(옵트인). 웹이 추가하는 의존성은 `zod` 하나뿐이다. 네이티브·webview 라이브러리는 웹에 들어가지 않는다. 웹은 `window.ReactNativeWebView` 전역으로만 앱에 닿는다. gronxb 등 라이브러리는 기각한다(네이티브-API-소유·monorepo 전제가 web=SSOT와 충돌한다). 설치하더라도 네이티브-소셜 핸드오프는 idToken 획득 코드가 없어 `requestSessionHandoff` 호출자가 여전히 없는 fast-follow 흐름이다.
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/`에 바이트 단위로 동일하게 설치돼 있고(CI checksum이 드리프트를 막는다) 다시 복사하지 않는다. 웹이 앱과 실제로 대화하게 만드는 얇은 옵트인 레이어만 필요할 때 추가한다. 규약·보안 불변식은 인라인하지 않고 `bridge-guide`를 따른다.
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/ (and mobile/ when present), fixing issues where possible. Use when the Owner runs /warmup, just opened the workspace, or before tasks that depend on external CLIs working.
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` — latest (워크스페이스는 pnpm 기준 · 각 repo `package.json`의 `packageManager`로 버전 고정)
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/`이 있으면(앱 단계 이후) `mobile/`에서도 동일하게 한다.
31
+ - `mobile/`에서도 동일하게 한다.
32
32
 
33
33
  세부 정책은 각 repo의 `.npmrc`와 CI 설정을 따른다. 보안 게이트(cooldown, audit level 등)는 우회하거나 수정하지 않는다.
@@ -1,4 +1,4 @@
1
- # saas-starter-workspace
1
+ # my-space — 워크스페이스
2
2
 
3
3
  서비스들 위의 컨텍스트 레이어. 이 repo의 git은 공유 맥락(에이전트 도구·계약·문서)만 추적한다. `web/`와 `mobile/`은 각자 자기 git·배포를 가진 독립 repo다.
4
4
 
@@ -1,4 +1,4 @@
1
- # saas-starter-workspace
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
- 수강생에게 배포되는 강의용 템플릿입니다. [LICENSE](LICENSE).
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
- 두 사본은 바이트 단위로 동일해야 한다. 어긋나면 양쪽이 메시지 형태를 다르게 해석해 브릿지가 오작동하므로, CI checksum으로 강제한다.
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 project (docs/web-adapter/contract.ts).
7
- * The two MUST stay identical; the web adapter ships a checksum check to enforce it.
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
@@ -1,5 +1,5 @@
1
1
  {
2
- "name": "saas-starter-workspace",
2
+ "name": "my-space-workspace",
3
3
  "version": "0.1.0",
4
4
  "private": true,
5
5
  "description": "워크스페이스 루트 명령. 서비스 코드는 web/·mobile/이 각자 가진다.",
@@ -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 트래픽과 섞이지 않는다. `/kickoff`이 여기서 app(`src/bridge/`)과 web(`lib/bridge/`)으로 바이트 단위로 동일하게 복사하고, CI checksum이 드리프트를 막는다.
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 project (docs/web-adapter/contract.ts).
7
- * The two MUST stay identical; the web adapter ships a checksum check to enforce it.
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 project (docs/web-adapter/contract.ts).
7
- * The two MUST stay identical; the web adapter ships a checksum check to enforce it.
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.0",
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
- "templates",
13
+ "dist",
14
14
  "CHANGELOG.md"
15
15
  ],
16
16
  "engines": {
package/src/scaffold.mjs CHANGED
@@ -11,6 +11,9 @@ export const TOKEN = "my-space";
11
11
 
12
12
  // 토큰을 소비해야 하는 파일 목록. 토큰이 없으면 드리프트로 보고 실패한다(리네임 감지).
13
13
  export const RENDER_FILES = [
14
+ "package.json",
15
+ "README.md",
16
+ "AGENTS.md",
14
17
  "web/package.json",
15
18
  "web/README.md",
16
19
  "web/.env.example",
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