create-web-0to1 0.1.0

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 (193) hide show
  1. package/README.md +68 -0
  2. package/internal/engine/create-feature-crud-script.ts +134 -0
  3. package/internal/engine/create-feature-crud.template.mjs +601 -0
  4. package/internal/engine/create-feature-script.ts +142 -0
  5. package/internal/engine/generator-engine.ts +546 -0
  6. package/internal/engine/standalone-feature-preset.ts +34 -0
  7. package/internal/meta/preset-plan.ts +41 -0
  8. package/internal/meta/runtime-copy-plan.ts +220 -0
  9. package/internal/meta/runtime-layout.ts +262 -0
  10. package/internal/meta/scaffold-manifest.ts +169 -0
  11. package/internal/meta/standalone-dependency-manifest.ts +75 -0
  12. package/package.json +45 -0
  13. package/scripts/create-app.mjs +1612 -0
  14. package/source/core/auth/auth-events.ts +13 -0
  15. package/source/core/error/app-error.ts +85 -0
  16. package/source/core/error/handle-app-error.client.ts +35 -0
  17. package/source/core/lib/dayjs.ts +25 -0
  18. package/source/core/query/query-client.ts +126 -0
  19. package/source/core/request/request-core.ts +210 -0
  20. package/source/core/routes/route-paths.ts +4 -0
  21. package/source/core/ui/button.tsx +24 -0
  22. package/source/core/ui/modal-store.ts +32 -0
  23. package/source/core/ui/text-input-field.tsx +36 -0
  24. package/source/core/utils/build-query-string.ts +30 -0
  25. package/source/core/utils/format/date.ts +41 -0
  26. package/source/core/utils/format/index.ts +3 -0
  27. package/source/core/utils/format/number.ts +13 -0
  28. package/source/core/utils/format/text.ts +15 -0
  29. package/source/core/utils/schema-utils.ts +27 -0
  30. package/source/wrappers/monorepo/core/internal.ts +21 -0
  31. package/source/wrappers/monorepo/core/src/index.ts +4 -0
  32. package/source/wrappers/monorepo/core-next/src/auth.client.ts +1 -0
  33. package/source/wrappers/monorepo/core-next/src/auth.server.ts +94 -0
  34. package/source/wrappers/monorepo/core-next/src/bootstrap.client.tsx +21 -0
  35. package/source/wrappers/monorepo/core-next/src/bootstrap.tsx +18 -0
  36. package/source/wrappers/monorepo/core-next/src/index.ts +1 -0
  37. package/source/wrappers/monorepo/core-react/src/app-providers.tsx +36 -0
  38. package/source/wrappers/monorepo/core-react/src/auth.ts +42 -0
  39. package/source/wrappers/monorepo/core-react/src/hydration.tsx +21 -0
  40. package/source/wrappers/monorepo/core-react/src/index.ts +7 -0
  41. package/source/wrappers/monorepo/core-react/src/provider.tsx +49 -0
  42. package/source/wrappers/monorepo/core-react/src/query-client.ts +48 -0
  43. package/source/wrappers/monorepo/core-react/src/query-error-handler.ts +62 -0
  44. package/source/wrappers/monorepo/core-react/src/query-keys.ts +22 -0
  45. package/source/wrappers/monorepo/request/core-fetch.ts +27 -0
  46. package/source/wrappers/monorepo/request/core-request.ts +93 -0
  47. package/source/wrappers/next/auth/auth-error-listener.tsx +34 -0
  48. package/source/wrappers/next/error/handle-app-error.server.ts +41 -0
  49. package/source/wrappers/next/query/hydration.tsx +20 -0
  50. package/source/wrappers/next/query/providers.tsx +35 -0
  51. package/source/wrappers/next/request/request.client.ts +24 -0
  52. package/source/wrappers/next/request/request.server.ts +64 -0
  53. package/source/wrappers/next/request/request.ts +52 -0
  54. package/source/wrappers/next/ui/global-modal.tsx +29 -0
  55. package/source/wrappers/react/auth/auth-error-listener.tsx +34 -0
  56. package/source/wrappers/react/query/providers.tsx +31 -0
  57. package/source/wrappers/react/request/request.client.ts +24 -0
  58. package/source/wrappers/react/request/request.ts +51 -0
  59. package/source/wrappers/react/ui/global-modal.tsx +27 -0
  60. package/templates/monorepo/.dockerignore +38 -0
  61. package/templates/monorepo/README.md +292 -0
  62. package/templates/monorepo/_gitignore +38 -0
  63. package/templates/monorepo/_npmrc +1 -0
  64. package/templates/monorepo/apps/project/Dockerfile +32 -0
  65. package/templates/monorepo/apps/project/eslint.config.mjs +4 -0
  66. package/templates/monorepo/apps/project/index.html +14 -0
  67. package/templates/monorepo/apps/project/index.ts +15 -0
  68. package/templates/monorepo/apps/project/package.json +21 -0
  69. package/templates/monorepo/apps/project/tsconfig.json +9 -0
  70. package/templates/monorepo/apps/project/vite.config.ts +6 -0
  71. package/templates/monorepo/apps/web/Dockerfile +43 -0
  72. package/templates/monorepo/apps/web/README.md +111 -0
  73. package/templates/monorepo/apps/web/_gitignore +36 -0
  74. package/templates/monorepo/apps/web/app/favicon.ico +0 -0
  75. package/templates/monorepo/apps/web/app/global-error.tsx +12 -0
  76. package/templates/monorepo/apps/web/app/globals.css +0 -0
  77. package/templates/monorepo/apps/web/app/layout.tsx +28 -0
  78. package/templates/monorepo/apps/web/app/page.tsx +7 -0
  79. package/templates/monorepo/apps/web/app/providers.tsx +25 -0
  80. package/templates/monorepo/apps/web/eslint.config.js +4 -0
  81. package/templates/monorepo/apps/web/next-env.d.ts +6 -0
  82. package/templates/monorepo/apps/web/next.config.js +4 -0
  83. package/templates/monorepo/apps/web/package.json +31 -0
  84. package/templates/monorepo/apps/web/public/file-text.svg +3 -0
  85. package/templates/monorepo/apps/web/public/globe.svg +10 -0
  86. package/templates/monorepo/apps/web/public/next.svg +1 -0
  87. package/templates/monorepo/apps/web/public/turborepo-dark.svg +19 -0
  88. package/templates/monorepo/apps/web/public/turborepo-light.svg +19 -0
  89. package/templates/monorepo/apps/web/public/vercel.svg +10 -0
  90. package/templates/monorepo/apps/web/public/window.svg +3 -0
  91. package/templates/monorepo/apps/web/tsconfig.json +20 -0
  92. package/templates/monorepo/package.json +24 -0
  93. package/templates/monorepo/packages/core/eslint.config.mjs +4 -0
  94. package/templates/monorepo/packages/core/package.json +32 -0
  95. package/templates/monorepo/packages/core/tsconfig.json +8 -0
  96. package/templates/monorepo/packages/core-next/eslint.config.mjs +13 -0
  97. package/templates/monorepo/packages/core-next/package.json +43 -0
  98. package/templates/monorepo/packages/core-next/tsconfig.json +8 -0
  99. package/templates/monorepo/packages/core-react/eslint.config.mjs +4 -0
  100. package/templates/monorepo/packages/core-react/package.json +34 -0
  101. package/templates/monorepo/packages/core-react/tsconfig.json +8 -0
  102. package/templates/monorepo/packages/eslint-config/README.md +3 -0
  103. package/templates/monorepo/packages/eslint-config/base.js +57 -0
  104. package/templates/monorepo/packages/eslint-config/next.js +22 -0
  105. package/templates/monorepo/packages/eslint-config/package.json +25 -0
  106. package/templates/monorepo/packages/eslint-config/react-internal.js +33 -0
  107. package/templates/monorepo/packages/typescript-config/base.json +19 -0
  108. package/templates/monorepo/packages/typescript-config/nextjs.json +12 -0
  109. package/templates/monorepo/packages/typescript-config/package.json +9 -0
  110. package/templates/monorepo/packages/typescript-config/react-library.json +7 -0
  111. package/templates/monorepo/packages/ui/eslint.config.mjs +4 -0
  112. package/templates/monorepo/packages/ui/package.json +26 -0
  113. package/templates/monorepo/packages/ui/src/button.tsx +20 -0
  114. package/templates/monorepo/packages/ui/src/card.tsx +27 -0
  115. package/templates/monorepo/packages/ui/src/code.tsx +11 -0
  116. package/templates/monorepo/packages/ui/tsconfig.json +8 -0
  117. package/templates/monorepo/pnpm-workspace.yaml +9 -0
  118. package/templates/monorepo/turbo/generators/config.js +1336 -0
  119. package/templates/monorepo/turbo/generators/templates/next-app/Dockerfile.tpl +30 -0
  120. package/templates/monorepo/turbo/generators/templates/next-app/README.md.tpl +118 -0
  121. package/templates/monorepo/turbo/generators/templates/next-app/app/global-error.tsx.tpl +12 -0
  122. package/templates/monorepo/turbo/generators/templates/next-app/app/globals.css.tpl +1 -0
  123. package/templates/monorepo/turbo/generators/templates/next-app/app/layout.tsx.tpl +29 -0
  124. package/templates/monorepo/turbo/generators/templates/next-app/app/page.tsx.tpl +7 -0
  125. package/templates/monorepo/turbo/generators/templates/next-app/app/providers.tsx.tpl +25 -0
  126. package/templates/monorepo/turbo/generators/templates/next-app/eslint.config.js.tpl +4 -0
  127. package/templates/monorepo/turbo/generators/templates/next-app/next.config.js.tpl +6 -0
  128. package/templates/monorepo/turbo/generators/templates/next-app/tsconfig.json.tpl +18 -0
  129. package/templates/monorepo/turbo/generators/templates/vite-app/Dockerfile.tpl +22 -0
  130. package/templates/monorepo/turbo/generators/templates/vite-app/README.plain.md.tpl +90 -0
  131. package/templates/monorepo/turbo/generators/templates/vite-app/README.react.md.tpl +107 -0
  132. package/templates/monorepo/turbo/generators/templates/vite-app/eslint.config.mjs.tpl +4 -0
  133. package/templates/monorepo/turbo/generators/templates/vite-app/index.html.tpl +12 -0
  134. package/templates/monorepo/turbo/generators/templates/vite-app/index.ts.tpl +22 -0
  135. package/templates/monorepo/turbo/generators/templates/vite-app/tsconfig.json.tpl +9 -0
  136. package/templates/monorepo/turbo/generators/templates/vite-app/vite.config.ts.tpl +6 -0
  137. package/templates/monorepo/turbo.json +28 -0
  138. package/templates/next/.env.example +2 -0
  139. package/templates/next/.prettierignore +9 -0
  140. package/templates/next/.prettierrc.json +9 -0
  141. package/templates/next/README.md +246 -0
  142. package/templates/next/_gitignore +44 -0
  143. package/templates/next/eslint.config.mjs +51 -0
  144. package/templates/next/next.config.ts +7 -0
  145. package/templates/next/package.json +24 -0
  146. package/templates/next/postcss.config.mjs +7 -0
  147. package/templates/next/scripts/create-feature-crud.mjs +5 -0
  148. package/templates/next/scripts/create-feature.mjs +5 -0
  149. package/templates/next/src/app/error.tsx +33 -0
  150. package/templates/next/src/app/globals.css +35 -0
  151. package/templates/next/src/app/layout.tsx +39 -0
  152. package/templates/next/src/app/login/page.tsx +17 -0
  153. package/templates/next/src/app/page.tsx +32 -0
  154. package/templates/next/src/app/providers.tsx +20 -0
  155. package/templates/next/tsconfig.json +34 -0
  156. package/templates/react/.env.example +1 -0
  157. package/templates/react/.prettierignore +10 -0
  158. package/templates/react/.prettierrc.json +9 -0
  159. package/templates/react/README.md +250 -0
  160. package/templates/react/_gitignore +31 -0
  161. package/templates/react/eslint.config.mjs +64 -0
  162. package/templates/react/package.json +19 -0
  163. package/templates/react/scripts/create-feature-crud.mjs +5 -0
  164. package/templates/react/scripts/create-feature.mjs +5 -0
  165. package/templates/react/src/app/app.tsx +15 -0
  166. package/templates/react/src/app/error-boundary.tsx +59 -0
  167. package/templates/react/src/app/frame.tsx +32 -0
  168. package/templates/react/src/app/globals.css +43 -0
  169. package/templates/react/src/app/not-found-page.tsx +23 -0
  170. package/templates/react/src/app/providers.tsx +16 -0
  171. package/templates/react/src/app/router.tsx +62 -0
  172. package/templates/react/src/main.tsx +12 -0
  173. package/templates/react/src/pages/index/page.tsx +36 -0
  174. package/templates/react/src/pages/login/page.tsx +18 -0
  175. package/templates/react/tsconfig.app.json +30 -0
  176. package/templates/react/tsconfig.json +4 -0
  177. package/templates/react/tsconfig.node.json +24 -0
  178. package/templates/react/vite.config.ts +14 -0
  179. package/templates/shared/docs//352/260/234/353/260/234/354/233/220/354/271/231/00-/354/240/204/353/260/230/354/240/201/354/235/270-/355/217/264/353/215/224/352/265/254/354/241/260.md +150 -0
  180. package/templates/shared/docs//352/260/234/353/260/234/354/233/220/354/271/231/01-/352/265/254/354/241/260/354/231/200-/353/235/274/354/232/260/355/214/205.md +186 -0
  181. package/templates/shared/docs//352/260/234/353/260/234/354/233/220/354/271/231/02-/354/204/234/353/262/204/354/231/200-/355/201/264/353/235/274/354/235/264/354/226/270/355/212/270.md +86 -0
  182. package/templates/shared/docs//352/260/234/353/260/234/354/233/220/354/271/231/03-/354/203/201/355/203/234/352/264/200/353/246/254.md +84 -0
  183. package/templates/shared/docs//352/260/234/353/260/234/354/233/220/354/271/231/04-API/354/231/200-/353/215/260/354/235/264/355/204/260.md +199 -0
  184. package/templates/shared/docs//352/260/234/353/260/234/354/233/220/354/271/231/05-/354/227/220/353/237/254/354/231/200-UI-/354/203/201/355/203/234.md +159 -0
  185. package/templates/shared/docs//352/260/234/353/260/234/354/233/220/354/271/231/06-/355/217/274.md +116 -0
  186. package/templates/shared/docs//352/260/234/353/260/234/354/233/220/354/271/231/07-/354/212/244/355/203/200/354/235/274/353/247/201/352/263/274-/354/240/221/352/267/274/354/204/261.md +73 -0
  187. package/templates/shared/docs//352/260/234/353/260/234/354/233/220/354/271/231/08-/353/204/244/354/235/264/353/260/215-/354/204/244/354/240/225-/355/217/254/353/247/267/355/214/205.md +98 -0
  188. package/templates/shared/docs//352/260/234/353/260/234/354/233/220/354/271/231/09-/353/235/274/354/232/260/355/212/270-/354/240/225/354/235/230.md +169 -0
  189. package/templates/shared/docs//352/260/234/353/260/234/354/233/220/354/271/231/10-/354/273/244/353/260/213-/354/273/250/353/262/244/354/205/230.md +64 -0
  190. package/templates/shared/docs//352/260/234/353/260/234/354/233/220/354/271/231/11-/352/270/260/355/203/200-/354/233/220/354/271/231.md +187 -0
  191. package/templates/shared/docs//352/260/234/353/260/234/354/233/220/354/271/231/12-/354/213/244/353/254/264-/353/215/260/354/235/264/355/204/260-/355/214/250/355/204/264.md +302 -0
  192. package/templates/shared/docs//352/260/234/353/260/234/354/233/220/354/271/231/13-/354/204/261/353/212/245-/354/233/220/354/271/231.md +175 -0
  193. package/templates/shared/docs//352/260/234/353/260/234/354/233/220/354/271/231/README.md +39 -0
@@ -0,0 +1,292 @@
1
+ # AI CMS Monorepo
2
+
3
+ 단일 Next.js 앱을 기준으로 CMS 프론트엔드와 공통 패키지를 관리하는 모노레포입니다.
4
+
5
+ 현재 구조는 다음 전제를 기준으로 정리되어 있습니다.
6
+
7
+ - 앱 코드는 named export만 사용합니다.
8
+ - API 함수는 `@repo/core/api/*`에서 직접 import 합니다.
9
+ - `@repo/core-react`는 React 클라이언트 공통 기능을 제공합니다.
10
+ - `@repo/core-next`는 Next.js 전용 기능과 `@repo/core-react` 재export를 제공합니다.
11
+ - `ui` 패키지는 별도 디자인 시스템으로 보고, fetch/error/core-next/eslint 정리와 분리해서 유지합니다.
12
+
13
+ ## 전제
14
+
15
+ - 현재 `initCore()`는 module-scoped state를 사용합니다.
16
+ - 그래서 `siteId`와 `baseUrl`이 앱 단위로 고정되는 구조를 전제로 합니다.
17
+ - 고객사별로 앱이 아예 분리 배포되거나, 앱 하나가 사실상 고정 설정으로 운영되는 경우에는 지금 구조로 충분합니다.
18
+ - 현재 이 레포는 그 전제를 따르고 있으므로, `initCore()`는 가장 단순하고 유지비가 낮은 선택으로 봅니다.
19
+ - 하나의 서버 프로세스가 요청마다 다른 `siteId`를 처리하는 진짜 멀티테넌트 구조로 가면, 이 초기화 방식은 나중에 바꿔야 합니다.
20
+
21
+ ## 폴더 구조
22
+
23
+ - `apps/web`: Next.js App Router 기준 앱
24
+ - `apps/project`: Vite 예제 앱
25
+ - `packages/core`: 프레임워크 비의존 fetch, request, error, core API
26
+ - `packages/core-react`: React Query provider, query client, client auth wrapper
27
+ - `packages/core-next`: Next.js 전용 bootstrap, server auth wrapper, `core-react` facade
28
+ - `packages/ui`: 공용 UI 패키지
29
+ - `packages/eslint-config`: 공용 ESLint 설정
30
+ - `packages/typescript-config`: 공용 TypeScript 설정
31
+
32
+ ## 실행
33
+
34
+ ```bash
35
+ pnpm install
36
+ pnpm dev
37
+ ```
38
+
39
+ 자주 쓰는 명령:
40
+
41
+ ```bash
42
+ pnpm build
43
+ pnpm lint
44
+ pnpm check-types
45
+ ```
46
+
47
+ ## 앱 생성
48
+
49
+ 이 monorepo에는 `turbo/generators/config.js` 기준의 custom generator가 같이 들어 있습니다.
50
+
51
+ 새 앱을 추가할 때는 레포 루트에서 아래 명령을 실행합니다.
52
+
53
+ ```bash
54
+ pnpm turbo gen create-next
55
+ pnpm turbo gen create-vite
56
+ ```
57
+
58
+ 자주 쓰게 되면 아래 shortcut script로 실행해도 됩니다.
59
+
60
+ ```bash
61
+ pnpm gen:next
62
+ pnpm gen:vite
63
+ ```
64
+
65
+ 현재 generator가 묻는 대표 질문:
66
+
67
+ - 앱 이름
68
+ - `siteId`
69
+ - API base URL
70
+ - 추가할 workspace dependency / devDependency
71
+
72
+ 역할 분리:
73
+
74
+ - `turbo/generators/config.js`: monorepo 전용 generator 동작
75
+ - `turbo/generators/templates/**/*.tpl`: generator가 복사하는 정적 scaffold
76
+ - `packages/core*`: generator가 연결하는 공통 runtime 패키지
77
+
78
+ 이 generator 자산은 standalone `create-app` 흐름과 달리 monorepo preset 안에서 그대로 유지하는 static template 경계로 봅니다.
79
+
80
+ ## 패키지 규칙
81
+
82
+ ### `@repo/core`
83
+
84
+ 역할:
85
+
86
+ - 순수 request 실행기
87
+ - 공용 에러 타입과 정규화
88
+ - API 함수 구현
89
+
90
+ 공개 경로:
91
+
92
+ - `@repo/core`
93
+ - `@repo/core/api/*`
94
+
95
+ 루트 export에는 다음 성격의 것만 둡니다.
96
+
97
+ - `initCore`
98
+ - request 타입
99
+ - error 타입/유틸
100
+
101
+ API 함수는 루트에 다시 모으지 않습니다.
102
+
103
+ 이유:
104
+
105
+ - 파일 하나 만들고 바로 import 가능해야 합니다.
106
+ - 중앙 barrel 파일을 계속 수정하지 않기 위해서입니다.
107
+ - 클라이언트 번들에서 tree-shaking이 잘 되게 하기 위해서입니다.
108
+
109
+ 예시:
110
+
111
+ ```ts
112
+ import { initCore } from "@repo/core";
113
+ import { testFetch } from "@repo/core/api/test";
114
+ ```
115
+
116
+ ### `@repo/core-next`
117
+
118
+ 역할:
119
+
120
+ - Next.js 환경에서 필요한 부가 기능 제공
121
+ - `@repo/core-react`를 재export해서 Next 앱에서 한 패키지로 쓰기 쉽게 구성
122
+ - `@repo/core` API를 다시 pass-through 하지는 않음
123
+
124
+ 공개 경로:
125
+
126
+ - `@repo/core-next`
127
+ - `@repo/core-next/bootstrap`
128
+ - `@repo/core-next/server-auth`
129
+ - `@repo/core-next/client-auth`
130
+
131
+ 이 패키지에는 API 함수 자체를 두지 않습니다. API 함수는 항상 `@repo/core/api/*`에서 직접 가져옵니다.
132
+
133
+ ### `@repo/core-react`
134
+
135
+ 역할:
136
+
137
+ - React Query `QueryClient` 생성
138
+ - 공용 React provider
139
+ - 조합형 app providers
140
+ - query hydration helper
141
+ - 클라이언트 auth wrapper
142
+ - query key factory
143
+ - 클라이언트 query 에러 핸들러
144
+
145
+ 공개 경로:
146
+
147
+ - `@repo/core-react`
148
+
149
+ ## 사용법
150
+
151
+ ### 1. 앱 초기화
152
+
153
+ Next.js에서는 루트 레이아웃에 `CoreBootstrap`을 한 번만 둡니다.
154
+ 클라이언트 provider에서 함수 prop을 다뤄야 하면 `app/providers.tsx` 같은 client component로 분리합니다.
155
+
156
+ ```tsx
157
+ import "./globals.css";
158
+
159
+ import { CoreBootstrap } from "@repo/core-next/bootstrap";
160
+ import { Providers } from "./providers";
161
+
162
+ const SITE_ID = "a";
163
+
164
+ export default function RootLayout({
165
+ children,
166
+ }: Readonly<{
167
+ children: React.ReactNode;
168
+ }>) {
169
+ return (
170
+ <html lang="ko">
171
+ <body>
172
+ <CoreBootstrap siteId={SITE_ID} />
173
+ <Providers>{children}</Providers>
174
+ </body>
175
+ </html>
176
+ );
177
+ }
178
+ ```
179
+
180
+ ```tsx
181
+ "use client";
182
+
183
+ import type { ReactNode } from "react";
184
+
185
+ import {
186
+ AppProviders,
187
+ createClientQueryErrorHandler,
188
+ } from "@repo/core-next";
189
+
190
+ const handleClientQueryError = createClientQueryErrorHandler({
191
+ onUnauthorized: () => {
192
+ window.location.href = "/login";
193
+ },
194
+ });
195
+
196
+ export function Providers({ children }: { children: ReactNode }) {
197
+ return (
198
+ <AppProviders onGlobalError={handleClientQueryError}>
199
+ {children}
200
+ </AppProviders>
201
+ );
202
+ }
203
+ ```
204
+
205
+ 동작:
206
+
207
+ - 서버 렌더 시 한 번 초기화
208
+ - 클라이언트 하이드레이션 시 한 번 초기화
209
+
210
+ 주의:
211
+
212
+ - 이 방식은 `siteId`/`baseUrl`이 앱에서 사실상 고정일 때를 전제로 합니다.
213
+
214
+ ### 2. API 함수 작성
215
+
216
+ API 함수는 `packages/core/src/api/*` 아래 파일별로 작성합니다.
217
+
218
+ 예시:
219
+
220
+ ```ts
221
+ // packages/core/src/api/test.ts
222
+ import { coreFetch, type CoreFetchOptions } from "../api";
223
+
224
+ export function testFetch(options?: CoreFetchOptions) {
225
+ return coreFetch("https://jsonplaceholder.typicode.com/posts", options);
226
+ }
227
+ ```
228
+
229
+ 사용:
230
+
231
+ ```ts
232
+ import { testFetch } from "@repo/core/api/test";
233
+ ```
234
+
235
+ 규칙:
236
+
237
+ - 각 파일에서 named export
238
+ - 루트 `@repo/core`로 다시 모으지 않음
239
+ - 새 API를 추가할 때 `package.json`이나 중앙 export 파일을 건드리지 않음
240
+
241
+ ### 3. 서버에서 인증 쿠키와 함께 호출
242
+
243
+ 서버에서는 `withServerAuth()`로 API 함수를 감쌉니다.
244
+
245
+ ```ts
246
+ import { testFetch } from "@repo/core/api/test";
247
+ import { withServerAuth } from "@repo/core-next/server-auth";
248
+
249
+ const authTestFetch = withServerAuth(testFetch);
250
+ const data = await authTestFetch();
251
+ ```
252
+
253
+ 현재 동작:
254
+
255
+ - `next/headers`의 `cookies()` 사용
256
+ - allowlist 쿠키만 `Cookie` 헤더에 붙임
257
+
258
+ 현재 allowlist:
259
+
260
+ - `session`
261
+ - `access_token`
262
+ - `refresh_token`
263
+
264
+ ### 4. 클라이언트에서 인증 포함 호출
265
+
266
+ 클라이언트에서는 `withClientAuth()`로 API 함수를 감쌉니다.
267
+
268
+ ```ts
269
+ import { testFetch } from "@repo/core/api/test";
270
+ import { withClientAuth } from "@repo/core-next/client-auth";
271
+
272
+ const authTestFetch = withClientAuth(testFetch);
273
+ const data = await authTestFetch();
274
+ ```
275
+
276
+ 현재 동작:
277
+
278
+ - 마지막 인자를 request options로 보고 `credentials: "include"`를 주입합니다.
279
+
280
+ 전제:
281
+
282
+ - API 함수 시그니처는 마지막 인자가 request options인 형태여야 합니다.
283
+
284
+ 예:
285
+
286
+ - `fn(options?)`
287
+ - `fn(id, options?)`
288
+ - `fn(endpoint, options?)`
289
+
290
+ ### 5. React Query provider와 hydration
291
+
292
+ `@repo/core-react`는 템플릿 기준으로 query infra를 패키지로 올려둔 구조입니다.
@@ -0,0 +1,38 @@
1
+ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2
+
3
+ # Dependencies
4
+ node_modules
5
+ .pnp
6
+ .pnp.js
7
+
8
+ # Local env files
9
+ .env
10
+ .env.local
11
+ .env.development.local
12
+ .env.test.local
13
+ .env.production.local
14
+
15
+ # Testing
16
+ coverage
17
+
18
+ # Turbo
19
+ .turbo
20
+
21
+ # Vercel
22
+ .vercel
23
+
24
+ # Build Outputs
25
+ .next/
26
+ out/
27
+ build
28
+ dist
29
+
30
+
31
+ # Debug
32
+ npm-debug.log*
33
+ yarn-debug.log*
34
+ yarn-error.log*
35
+
36
+ # Misc
37
+ .DS_Store
38
+ *.pem
@@ -0,0 +1 @@
1
+
@@ -0,0 +1,32 @@
1
+ # 1. Base 이미지
2
+ FROM node:lts-alpine AS base
3
+ RUN npm install -g pnpm turbo
4
+ WORKDIR /app
5
+
6
+ # 2. Prune 단계 (루트의 모든 파일을 보고 필요한 것만 추출)
7
+ FROM base AS pruner
8
+ COPY . .
9
+ # 여기서 @repo/web은 실제 package.json의 name입니다.
10
+ RUN turbo prune project --out-dir=out --docker
11
+
12
+ # 3. Build 단계
13
+ FROM base AS builder
14
+ WORKDIR /app
15
+
16
+ # 의존성 설치를 위해 json 파일들 먼저 복사
17
+ COPY --from=pruner /app/out/json/ .
18
+ COPY --from=pruner /app/out/pnpm-lock.yaml ./pnpm-lock.yaml
19
+ RUN pnpm install --frozen-lockfile
20
+
21
+ # 실제 소스 코드 복사 (여기에 turbo.json이 포함되어 있음)
22
+ COPY --from=pruner /app/out/full/ .
23
+ # 별도의 COPY turbo.json turbo.json 줄은 삭제하세요. 이미 위 줄에서 복사되었습니다.
24
+
25
+ RUN pnpm turbo build --filter=project
26
+
27
+ # 4. Runner 단계 (생략 - 이전과 동일)
28
+ FROM nginx:stable-alpine AS runner
29
+ # 빌드된 dist 경로를 확인하여 복사 (보통 apps/앱이름/dist)
30
+ COPY --from=builder /app/apps/project/dist /usr/share/nginx/html
31
+ EXPOSE 80
32
+ CMD ["nginx", "-g", "daemon off;"]
@@ -0,0 +1,4 @@
1
+ import { config } from "@repo/eslint-config/base";
2
+
3
+ /** @type {import("eslint").Linter.Config} */
4
+ export default config;
@@ -0,0 +1,14 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport"
6
+ content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
7
+ <meta http-equiv="X-UA-Compatible" content="ie=edge">
8
+ <title>Document</title>
9
+ <script src="./index.ts" type="module"></script>
10
+ </head>
11
+ <body>
12
+ asdfㅁㄴㅇㄹㅁㄴㅇㄹㅁㄴㅇㄹ asdfasdfasdfasdf
13
+ </body>
14
+ </html>
@@ -0,0 +1,15 @@
1
+ import { type AppError, initCore } from "@repo/core";
2
+ import { testFetch } from "@repo/core/api/test";
3
+
4
+ initCore("a");
5
+
6
+ (async () => {
7
+ try {
8
+ const data = await testFetch();
9
+
10
+ document.body.innerText = JSON.stringify(data);
11
+ } catch (error) {
12
+ const appError = error as AppError;
13
+ alert(appError.message);
14
+ }
15
+ })();
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "project",
3
+ "type": "module",
4
+ "dependencies": {
5
+ "@repo/core": "workspace:*"
6
+ },
7
+ "devDependencies": {
8
+ "@repo/eslint-config": "workspace:*",
9
+ "@repo/typescript-config": "workspace:*",
10
+ "@types/node": "^22.15.3",
11
+ "eslint": "^9.39.1",
12
+ "typescript": "^5.9.3",
13
+ "vite": "^7.3.1",
14
+ "vite-tsconfig-paths": "^6.0.5"
15
+ },
16
+ "scripts": {
17
+ "dev": "vite",
18
+ "build": "vite build",
19
+ "preview": "vite preview"
20
+ }
21
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "@repo/typescript-config/base.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist",
5
+ "moduleResolution": "nodenext"
6
+ },
7
+ "include": ["src", "./index.ts"],
8
+ "exclude": ["node_modules", "dist"]
9
+ }
@@ -0,0 +1,6 @@
1
+ import { defineConfig } from "vite";
2
+ import tsConfigPaths from "vite-tsconfig-paths";
3
+
4
+ export default defineConfig({
5
+ plugins: [tsConfigPaths()],
6
+ });
@@ -0,0 +1,43 @@
1
+ # 1단계: Base 설정
2
+ FROM node:lts-alpine AS base
3
+ RUN npm install -g pnpm turbo
4
+ WORKDIR /app
5
+
6
+ # 2단계: Prune - 필요한 패키지만 추출
7
+ FROM base AS pruner
8
+ COPY . .
9
+ # 💡 @repo/web은 apps/web/package.json의 "name" 필드값입니다.
10
+ RUN turbo prune web --docker
11
+
12
+ # 3단계: Build - 의존성 설치 및 빌드
13
+ FROM base AS builder
14
+ WORKDIR /app
15
+
16
+ # 캐시 효율을 위해 json 파일만 먼저 복사하여 설치
17
+ COPY --from=pruner /app/out/json/ .
18
+ COPY --from=pruner /app/out/pnpm-lock.yaml ./pnpm-lock.yaml
19
+ RUN pnpm install --no-frozen-lockfile
20
+
21
+ # 전체 소스 코드 복사 (out/full에 이미 turbo.json이 포함됨)
22
+ COPY --from=pruner /app/out/full/ .
23
+ RUN pnpm turbo build --filter=web
24
+
25
+ # 4단계: Runner - 실행 전용 이미지
26
+ FROM node:lts-alpine AS runner
27
+ WORKDIR /app
28
+
29
+ ENV NODE_ENV production
30
+ # Cloud Run 등에서 사용할 포트 (기본 3000)
31
+ ENV PORT 3000
32
+ ENV HOSTNAME "0.0.0.0"
33
+
34
+ # standalone 실행에 필요한 최소 파일만 복사
35
+ # 💡 경로 주의: apps/web 부분은 실제 본인의 폴더 구조를 따라갑니다.
36
+ COPY --from=builder /app/apps/web/public ./apps/web/public
37
+ COPY --from=builder /app/apps/web/.next/standalone ./
38
+ COPY --from=builder /app/apps/web/.next/static ./apps/web/.next/static
39
+
40
+ EXPOSE 3000
41
+
42
+ # standalone 모드에서는 서버를 node로 직접 실행합니다.
43
+ CMD ["node", "apps/web/server.js"]
@@ -0,0 +1,111 @@
1
+ # web
2
+
3
+ `apps/web`은 monorepo generator로 만든 Next.js 앱입니다. `create-next-app` 기본 scaffold 위에 공용 설정과 core wiring이 이미 연결되어 있습니다.
4
+
5
+ ## 빠른 시작
6
+
7
+ 레포 루트에서 실행하세요.
8
+
9
+ ```bash
10
+ pnpm install
11
+ pnpm --filter "./apps/web" dev
12
+ ```
13
+
14
+ 자주 쓰는 명령:
15
+
16
+ ```bash
17
+ pnpm --filter "./apps/web" lint
18
+ pnpm --filter "./apps/web" check-types
19
+ pnpm --filter "./apps/web" build
20
+ ```
21
+
22
+ ## 기본 연결
23
+
24
+ - `apps/web/app/layout.tsx`: `CoreBootstrap`와 `Providers`를 연결합니다.
25
+ - `apps/web/app/providers.tsx`: `AppProviders`와 전역 query 에러 핸들러를 연결합니다.
26
+ - `apps/web/app/page.tsx`: 첫 페이지 예시입니다.
27
+ - `apps/web/app/global-error.tsx`: 전역 에러 UI입니다.
28
+
29
+ 상대 경로 API를 공통으로 쓰실 계획이면 `CoreBootstrap`에 `baseUrl`도 같이 넘겨주세요.
30
+
31
+ ```tsx
32
+ <CoreBootstrap siteId="a" baseUrl="https://api.example.com" />
33
+ ```
34
+
35
+ ## API 사용 규칙
36
+
37
+ API 함수는 항상 `@repo/core/api/*`에서 직접 import합니다.
38
+
39
+ ```tsx
40
+ import { testFetch } from "@repo/core/api/test";
41
+
42
+ export default async function Page() {
43
+ const data = await testFetch();
44
+
45
+ return <pre>{JSON.stringify(data, null, 2)}</pre>;
46
+ }
47
+ ```
48
+
49
+ 다음 패턴은 피해주세요.
50
+
51
+ - `@repo/core` 루트에 API를 다시 모으기
52
+ - `@repo/core-next`에서 API를 가져오기
53
+
54
+ ## 새 API 추가하기
55
+
56
+ 새 API는 `packages/core/src/api/*.ts`에 파일 단위로 추가합니다.
57
+
58
+ ```ts
59
+ import { z } from "zod";
60
+
61
+ import { coreFetch, type CoreFetchOptions } from "../api";
62
+ import { parseWithSchema } from "../schema-utils";
63
+
64
+ const articleSchema = z.object({
65
+ id: z.number(),
66
+ title: z.string(),
67
+ });
68
+
69
+ const articleListSchema = z.array(articleSchema);
70
+
71
+ export async function listArticles(options?: CoreFetchOptions) {
72
+ const payload = await coreFetch<unknown>("/articles", {
73
+ ...options,
74
+ baseUrl: "https://api.example.com",
75
+ });
76
+
77
+ return parseWithSchema(articleListSchema, payload);
78
+ }
79
+ ```
80
+
81
+ 앱에서는 바로 이렇게 가져다 쓰시면 됩니다.
82
+
83
+ ```ts
84
+ import { listArticles } from "@repo/core/api/article";
85
+ ```
86
+
87
+ ## 인증 포함 호출
88
+
89
+ 서버 컴포넌트나 route handler에서는 `withServerAuth()`를 붙이면 됩니다.
90
+
91
+ ```tsx
92
+ import { testFetch } from "@repo/core/api/test";
93
+ import { withServerAuth } from "@repo/core-next/server-auth";
94
+
95
+ const authTestFetch = withServerAuth(testFetch);
96
+ const data = await authTestFetch();
97
+ ```
98
+
99
+ 클라이언트 호출에서 쿠키를 같이 보내려면 `withClientAuth()`를 사용하세요.
100
+
101
+ ```tsx
102
+ import { testFetch } from "@repo/core/api/test";
103
+ import { withClientAuth } from "@repo/core-next/client-auth";
104
+
105
+ const authTestFetch = withClientAuth(testFetch);
106
+ const data = await authTestFetch();
107
+ ```
108
+
109
+ ## 더 보기
110
+
111
+ 공통 규칙과 패키지 구조는 [루트 README](../../README.md)를 참고해주세요.
@@ -0,0 +1,36 @@
1
+ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2
+
3
+ # dependencies
4
+ /node_modules
5
+ /.pnp
6
+ .pnp.js
7
+ .yarn/install-state.gz
8
+
9
+ # testing
10
+ /coverage
11
+
12
+ # next.js
13
+ /.next/
14
+ /out/
15
+
16
+ # production
17
+ /build
18
+
19
+ # misc
20
+ .DS_Store
21
+ *.pem
22
+
23
+ # debug
24
+ npm-debug.log*
25
+ yarn-debug.log*
26
+ yarn-error.log*
27
+
28
+ # env files (can opt-in for commiting if needed)
29
+ .env*
30
+
31
+ # vercel
32
+ .vercel
33
+
34
+ # typescript
35
+ *.tsbuildinfo
36
+ next-env.d.ts
@@ -0,0 +1,12 @@
1
+ "use client";
2
+
3
+ export default function GlobalError({ error }: { error: Error }) {
4
+ return (
5
+ <html>
6
+ <body>
7
+ <h2>에러 발생</h2>
8
+ <pre>{error.message}</pre>
9
+ </body>
10
+ </html>
11
+ );
12
+ }
File without changes
@@ -0,0 +1,28 @@
1
+ import "./globals.css";
2
+
3
+ import { CoreBootstrap } from "@repo/core-next/bootstrap";
4
+ import type { Metadata } from "next";
5
+
6
+ import { Providers } from "./providers";
7
+
8
+ export const metadata: Metadata = {
9
+ title: "Create Next App",
10
+ description: "Generated by create next app",
11
+ };
12
+
13
+ const SITE_ID = "a";
14
+
15
+ export default function RootLayout({
16
+ children,
17
+ }: Readonly<{
18
+ children: React.ReactNode;
19
+ }>) {
20
+ return (
21
+ <html lang="ko">
22
+ <body>
23
+ <CoreBootstrap siteId={SITE_ID} />
24
+ <Providers>{children}</Providers>
25
+ </body>
26
+ </html>
27
+ );
28
+ }
@@ -0,0 +1,7 @@
1
+ import { testFetch } from "@repo/core/api/test";
2
+
3
+ export default async function Home() {
4
+ const data = await testFetch();
5
+
6
+ return <div className={""}>{JSON.stringify(data)}</div>;
7
+ }