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.
- package/README.md +68 -0
- package/internal/engine/create-feature-crud-script.ts +134 -0
- package/internal/engine/create-feature-crud.template.mjs +601 -0
- package/internal/engine/create-feature-script.ts +142 -0
- package/internal/engine/generator-engine.ts +546 -0
- package/internal/engine/standalone-feature-preset.ts +34 -0
- package/internal/meta/preset-plan.ts +41 -0
- package/internal/meta/runtime-copy-plan.ts +220 -0
- package/internal/meta/runtime-layout.ts +262 -0
- package/internal/meta/scaffold-manifest.ts +169 -0
- package/internal/meta/standalone-dependency-manifest.ts +75 -0
- package/package.json +45 -0
- package/scripts/create-app.mjs +1612 -0
- package/source/core/auth/auth-events.ts +13 -0
- package/source/core/error/app-error.ts +85 -0
- package/source/core/error/handle-app-error.client.ts +35 -0
- package/source/core/lib/dayjs.ts +25 -0
- package/source/core/query/query-client.ts +126 -0
- package/source/core/request/request-core.ts +210 -0
- package/source/core/routes/route-paths.ts +4 -0
- package/source/core/ui/button.tsx +24 -0
- package/source/core/ui/modal-store.ts +32 -0
- package/source/core/ui/text-input-field.tsx +36 -0
- package/source/core/utils/build-query-string.ts +30 -0
- package/source/core/utils/format/date.ts +41 -0
- package/source/core/utils/format/index.ts +3 -0
- package/source/core/utils/format/number.ts +13 -0
- package/source/core/utils/format/text.ts +15 -0
- package/source/core/utils/schema-utils.ts +27 -0
- package/source/wrappers/monorepo/core/internal.ts +21 -0
- package/source/wrappers/monorepo/core/src/index.ts +4 -0
- package/source/wrappers/monorepo/core-next/src/auth.client.ts +1 -0
- package/source/wrappers/monorepo/core-next/src/auth.server.ts +94 -0
- package/source/wrappers/monorepo/core-next/src/bootstrap.client.tsx +21 -0
- package/source/wrappers/monorepo/core-next/src/bootstrap.tsx +18 -0
- package/source/wrappers/monorepo/core-next/src/index.ts +1 -0
- package/source/wrappers/monorepo/core-react/src/app-providers.tsx +36 -0
- package/source/wrappers/monorepo/core-react/src/auth.ts +42 -0
- package/source/wrappers/monorepo/core-react/src/hydration.tsx +21 -0
- package/source/wrappers/monorepo/core-react/src/index.ts +7 -0
- package/source/wrappers/monorepo/core-react/src/provider.tsx +49 -0
- package/source/wrappers/monorepo/core-react/src/query-client.ts +48 -0
- package/source/wrappers/monorepo/core-react/src/query-error-handler.ts +62 -0
- package/source/wrappers/monorepo/core-react/src/query-keys.ts +22 -0
- package/source/wrappers/monorepo/request/core-fetch.ts +27 -0
- package/source/wrappers/monorepo/request/core-request.ts +93 -0
- package/source/wrappers/next/auth/auth-error-listener.tsx +34 -0
- package/source/wrappers/next/error/handle-app-error.server.ts +41 -0
- package/source/wrappers/next/query/hydration.tsx +20 -0
- package/source/wrappers/next/query/providers.tsx +35 -0
- package/source/wrappers/next/request/request.client.ts +24 -0
- package/source/wrappers/next/request/request.server.ts +64 -0
- package/source/wrappers/next/request/request.ts +52 -0
- package/source/wrappers/next/ui/global-modal.tsx +29 -0
- package/source/wrappers/react/auth/auth-error-listener.tsx +34 -0
- package/source/wrappers/react/query/providers.tsx +31 -0
- package/source/wrappers/react/request/request.client.ts +24 -0
- package/source/wrappers/react/request/request.ts +51 -0
- package/source/wrappers/react/ui/global-modal.tsx +27 -0
- package/templates/monorepo/.dockerignore +38 -0
- package/templates/monorepo/README.md +292 -0
- package/templates/monorepo/_gitignore +38 -0
- package/templates/monorepo/_npmrc +1 -0
- package/templates/monorepo/apps/project/Dockerfile +32 -0
- package/templates/monorepo/apps/project/eslint.config.mjs +4 -0
- package/templates/monorepo/apps/project/index.html +14 -0
- package/templates/monorepo/apps/project/index.ts +15 -0
- package/templates/monorepo/apps/project/package.json +21 -0
- package/templates/monorepo/apps/project/tsconfig.json +9 -0
- package/templates/monorepo/apps/project/vite.config.ts +6 -0
- package/templates/monorepo/apps/web/Dockerfile +43 -0
- package/templates/monorepo/apps/web/README.md +111 -0
- package/templates/monorepo/apps/web/_gitignore +36 -0
- package/templates/monorepo/apps/web/app/favicon.ico +0 -0
- package/templates/monorepo/apps/web/app/global-error.tsx +12 -0
- package/templates/monorepo/apps/web/app/globals.css +0 -0
- package/templates/monorepo/apps/web/app/layout.tsx +28 -0
- package/templates/monorepo/apps/web/app/page.tsx +7 -0
- package/templates/monorepo/apps/web/app/providers.tsx +25 -0
- package/templates/monorepo/apps/web/eslint.config.js +4 -0
- package/templates/monorepo/apps/web/next-env.d.ts +6 -0
- package/templates/monorepo/apps/web/next.config.js +4 -0
- package/templates/monorepo/apps/web/package.json +31 -0
- package/templates/monorepo/apps/web/public/file-text.svg +3 -0
- package/templates/monorepo/apps/web/public/globe.svg +10 -0
- package/templates/monorepo/apps/web/public/next.svg +1 -0
- package/templates/monorepo/apps/web/public/turborepo-dark.svg +19 -0
- package/templates/monorepo/apps/web/public/turborepo-light.svg +19 -0
- package/templates/monorepo/apps/web/public/vercel.svg +10 -0
- package/templates/monorepo/apps/web/public/window.svg +3 -0
- package/templates/monorepo/apps/web/tsconfig.json +20 -0
- package/templates/monorepo/package.json +24 -0
- package/templates/monorepo/packages/core/eslint.config.mjs +4 -0
- package/templates/monorepo/packages/core/package.json +32 -0
- package/templates/monorepo/packages/core/tsconfig.json +8 -0
- package/templates/monorepo/packages/core-next/eslint.config.mjs +13 -0
- package/templates/monorepo/packages/core-next/package.json +43 -0
- package/templates/monorepo/packages/core-next/tsconfig.json +8 -0
- package/templates/monorepo/packages/core-react/eslint.config.mjs +4 -0
- package/templates/monorepo/packages/core-react/package.json +34 -0
- package/templates/monorepo/packages/core-react/tsconfig.json +8 -0
- package/templates/monorepo/packages/eslint-config/README.md +3 -0
- package/templates/monorepo/packages/eslint-config/base.js +57 -0
- package/templates/monorepo/packages/eslint-config/next.js +22 -0
- package/templates/monorepo/packages/eslint-config/package.json +25 -0
- package/templates/monorepo/packages/eslint-config/react-internal.js +33 -0
- package/templates/monorepo/packages/typescript-config/base.json +19 -0
- package/templates/monorepo/packages/typescript-config/nextjs.json +12 -0
- package/templates/monorepo/packages/typescript-config/package.json +9 -0
- package/templates/monorepo/packages/typescript-config/react-library.json +7 -0
- package/templates/monorepo/packages/ui/eslint.config.mjs +4 -0
- package/templates/monorepo/packages/ui/package.json +26 -0
- package/templates/monorepo/packages/ui/src/button.tsx +20 -0
- package/templates/monorepo/packages/ui/src/card.tsx +27 -0
- package/templates/monorepo/packages/ui/src/code.tsx +11 -0
- package/templates/monorepo/packages/ui/tsconfig.json +8 -0
- package/templates/monorepo/pnpm-workspace.yaml +9 -0
- package/templates/monorepo/turbo/generators/config.js +1336 -0
- package/templates/monorepo/turbo/generators/templates/next-app/Dockerfile.tpl +30 -0
- package/templates/monorepo/turbo/generators/templates/next-app/README.md.tpl +118 -0
- package/templates/monorepo/turbo/generators/templates/next-app/app/global-error.tsx.tpl +12 -0
- package/templates/monorepo/turbo/generators/templates/next-app/app/globals.css.tpl +1 -0
- package/templates/monorepo/turbo/generators/templates/next-app/app/layout.tsx.tpl +29 -0
- package/templates/monorepo/turbo/generators/templates/next-app/app/page.tsx.tpl +7 -0
- package/templates/monorepo/turbo/generators/templates/next-app/app/providers.tsx.tpl +25 -0
- package/templates/monorepo/turbo/generators/templates/next-app/eslint.config.js.tpl +4 -0
- package/templates/monorepo/turbo/generators/templates/next-app/next.config.js.tpl +6 -0
- package/templates/monorepo/turbo/generators/templates/next-app/tsconfig.json.tpl +18 -0
- package/templates/monorepo/turbo/generators/templates/vite-app/Dockerfile.tpl +22 -0
- package/templates/monorepo/turbo/generators/templates/vite-app/README.plain.md.tpl +90 -0
- package/templates/monorepo/turbo/generators/templates/vite-app/README.react.md.tpl +107 -0
- package/templates/monorepo/turbo/generators/templates/vite-app/eslint.config.mjs.tpl +4 -0
- package/templates/monorepo/turbo/generators/templates/vite-app/index.html.tpl +12 -0
- package/templates/monorepo/turbo/generators/templates/vite-app/index.ts.tpl +22 -0
- package/templates/monorepo/turbo/generators/templates/vite-app/tsconfig.json.tpl +9 -0
- package/templates/monorepo/turbo/generators/templates/vite-app/vite.config.ts.tpl +6 -0
- package/templates/monorepo/turbo.json +28 -0
- package/templates/next/.env.example +2 -0
- package/templates/next/.prettierignore +9 -0
- package/templates/next/.prettierrc.json +9 -0
- package/templates/next/README.md +246 -0
- package/templates/next/_gitignore +44 -0
- package/templates/next/eslint.config.mjs +51 -0
- package/templates/next/next.config.ts +7 -0
- package/templates/next/package.json +24 -0
- package/templates/next/postcss.config.mjs +7 -0
- package/templates/next/scripts/create-feature-crud.mjs +5 -0
- package/templates/next/scripts/create-feature.mjs +5 -0
- package/templates/next/src/app/error.tsx +33 -0
- package/templates/next/src/app/globals.css +35 -0
- package/templates/next/src/app/layout.tsx +39 -0
- package/templates/next/src/app/login/page.tsx +17 -0
- package/templates/next/src/app/page.tsx +32 -0
- package/templates/next/src/app/providers.tsx +20 -0
- package/templates/next/tsconfig.json +34 -0
- package/templates/react/.env.example +1 -0
- package/templates/react/.prettierignore +10 -0
- package/templates/react/.prettierrc.json +9 -0
- package/templates/react/README.md +250 -0
- package/templates/react/_gitignore +31 -0
- package/templates/react/eslint.config.mjs +64 -0
- package/templates/react/package.json +19 -0
- package/templates/react/scripts/create-feature-crud.mjs +5 -0
- package/templates/react/scripts/create-feature.mjs +5 -0
- package/templates/react/src/app/app.tsx +15 -0
- package/templates/react/src/app/error-boundary.tsx +59 -0
- package/templates/react/src/app/frame.tsx +32 -0
- package/templates/react/src/app/globals.css +43 -0
- package/templates/react/src/app/not-found-page.tsx +23 -0
- package/templates/react/src/app/providers.tsx +16 -0
- package/templates/react/src/app/router.tsx +62 -0
- package/templates/react/src/main.tsx +12 -0
- package/templates/react/src/pages/index/page.tsx +36 -0
- package/templates/react/src/pages/login/page.tsx +18 -0
- package/templates/react/tsconfig.app.json +30 -0
- package/templates/react/tsconfig.json +4 -0
- package/templates/react/tsconfig.node.json +24 -0
- package/templates/react/vite.config.ts +14 -0
- 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
- 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
- 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
- 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
- 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
- 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
- package/templates/shared/docs//352/260/234/353/260/234/354/233/220/354/271/231/06-/355/217/274.md +116 -0
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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,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,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
|
|
Binary file
|
|
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
|
+
}
|