@gaonjs/cli 0.4.0 → 0.5.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/dist/commands/check.d.ts +31 -0
- package/dist/commands/check.js +223 -0
- package/dist/commands/console.d.ts +46 -0
- package/dist/commands/console.js +129 -0
- package/dist/commands/new.d.ts +45 -0
- package/dist/commands/new.js +274 -0
- package/dist/commands/test.d.ts +11 -0
- package/dist/commands/test.js +119 -0
- package/dist/doctor/no-auto-import.d.ts +10 -0
- package/dist/doctor/no-auto-import.js +158 -0
- package/dist/doctor/shared-composable-purity.d.ts +8 -0
- package/dist/doctor/shared-composable-purity.js +164 -0
- package/dist/doctor/types.d.ts +1 -1
- package/dist/doctor/types.js +6 -5
- package/dist/doctor.d.ts +2 -0
- package/dist/doctor.js +13 -3
- package/dist/index.d.ts +4 -1
- package/dist/index.js +104 -6
- package/dist/templates/index.d.ts +23 -0
- package/dist/templates/index.js +66 -0
- package/dist/templates/index.ts +85 -0
- package/dist/templates/project/.env.example.tpl +18 -0
- package/dist/templates/project/.gitignore.tpl +24 -0
- package/dist/templates/project/.npmrc.tpl +4 -0
- package/dist/templates/project/CLAUDE.md.tpl +119 -0
- package/dist/templates/project/apps/web/channels/.gitkeep.tpl +1 -0
- package/dist/templates/project/apps/web/components/.gitkeep.tpl +1 -0
- package/dist/templates/project/apps/web/composables/useApiPing.ts.tpl +25 -0
- package/dist/templates/project/apps/web/controllers/home.ts.tpl +19 -0
- package/dist/templates/project/apps/web/layouts/Default.vue.tpl +43 -0
- package/dist/templates/project/apps/web/pages/Home/Index.vue.tpl +36 -0
- package/dist/templates/project/apps/web/routes.ts.tpl +8 -0
- package/dist/templates/project/docker-compose.yaml.tpl +73 -0
- package/dist/templates/project/domain/events/.gitkeep.tpl +1 -0
- package/dist/templates/project/domain/jobs/.gitkeep.tpl +1 -0
- package/dist/templates/project/domain/listeners/.gitkeep.tpl +1 -0
- package/dist/templates/project/domain/mails/.gitkeep.tpl +1 -0
- package/dist/templates/project/domain/models/.gitkeep.tpl +1 -0
- package/dist/templates/project/domain/schema/.gitkeep.tpl +1 -0
- package/dist/templates/project/domain/services/.gitkeep.tpl +1 -0
- package/dist/templates/project/gaon.config.ts.tpl +27 -0
- package/dist/templates/project/package.json.tpl +27 -0
- package/dist/templates/project/pnpm-workspace.yaml.tpl +11 -0
- package/dist/templates/project/shared/components/.gitkeep.tpl +1 -0
- package/dist/templates/project/shared/composables/useDebounce.ts.tpl +21 -0
- package/dist/templates/project/tsconfig.json.tpl +25 -0
- package/package.json +3 -3
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# {{PROJECT_NAME}} — AI 작업 지침
|
|
2
|
+
|
|
3
|
+
이 파일은 이 프로젝트에서 AI(Claude 등)가 코드를 만질 때 지켜야 할
|
|
4
|
+
관례를 담는다. Gaon 프레임웍이 이 관례를 스캐폴드(`gaon new`)로 자동
|
|
5
|
+
심어 두는 이유는 하나다 — 첫 시도부터 프로젝트 관례에 맞게 코드를 짜
|
|
6
|
+
반복 수정을 없애기 위함(§1.1 AI 첫 시도 성공률).
|
|
7
|
+
|
|
8
|
+
## 0. 먼저 읽을 것
|
|
9
|
+
|
|
10
|
+
Gaon 프레임웍 문서: https://gaonjs.dev
|
|
11
|
+
|
|
12
|
+
- 설계 정본(v0.15) · errata E-1(파사드 = `gaonjs`) · E-3(JSON 액션 +
|
|
13
|
+
`api()`) · E-4(컬럼 확장) · E-5(컴포저블·레이아웃)
|
|
14
|
+
- 앱 내 One Way(§1) — 선택지가 있는 것을 만들지 않는다. 하나로 정한다.
|
|
15
|
+
|
|
16
|
+
## 1. 절대 규칙 (12개 · 프레임웍 정본에서 유래)
|
|
17
|
+
|
|
18
|
+
1. **TypeScript 전용.** JS 파일 추가 금지. 데코레이터 금지 — 함수·객체
|
|
19
|
+
스타일(`model()`·`controller()`·`job()`) 만 쓴다.
|
|
20
|
+
2. **`.gaon/` 자동 생성 파일 편집 금지.** `routes.d.ts`·`tables.d.ts`
|
|
21
|
+
는 `gaon check` / `gaon dev` 가 재생성한다.
|
|
22
|
+
3. **의존 방향 4규칙**(doctor 강제): 앱→domain 허용 · domain→앱 금지 ·
|
|
23
|
+
앱→앱 금지 · 앱→shared 허용(shared 는 앱 import 금지, domain 은
|
|
24
|
+
타입 import 만).
|
|
25
|
+
4. **PM2·pnpm dev-server 금지.** 클러스터는 `node:cluster` 내장이 유일.
|
|
26
|
+
컨테이너 기본 워커 1.
|
|
27
|
+
5. **프론트엔드는 프로젝트당 하나.** v1 은 Vue 단일. 앱 간 Vue/React
|
|
28
|
+
혼용 금지.
|
|
29
|
+
6. **보안 기본값(CORS·rate limit·CSRF)은 켠 채로 둔다.** 끄는 것은
|
|
30
|
+
명시적 설정으로만.
|
|
31
|
+
7. **멀티 DB 커넥션 규칙**(§4.5): 스키마는 `{ db: '키' }` 로 바인딩,
|
|
32
|
+
생략 = main. 커넥션을 가로지르는 belongsTo 금지, 서비스 트랜잭션은
|
|
33
|
+
단일 커넥션에서만 원자적. MongoDB 는 v1 구현 금지.
|
|
34
|
+
8. **테스트는 Docker 실 인프라 필수 · DB·NATS 목업 절대 금지**(§9).
|
|
35
|
+
SQLite 인메모리·NATS 목업으로 테스트 돌리는 코드 만들지 말 것.
|
|
36
|
+
9. **실시간은 v1 포함**(§7): 웹서버 ↔ 허브는 TCP 지속 연결 · NATS 는
|
|
37
|
+
broadcast 전용(errata E-2). 운영 프로세스는 serve·work·hub 3종.
|
|
38
|
+
10. **인증·폼은 Inertia SPA**(§6 · SSR 아님). 로그인/회원가입은
|
|
39
|
+
`this.render('auth/Login')` + `Inertia.post()` → 서버 redirect.
|
|
40
|
+
REST + `fetch()` 는 API 앱(JWT) 전용.
|
|
41
|
+
11. **컴포저블·레이아웃**(errata E-5): 컴포저블은 컴포넌트와 대칭
|
|
42
|
+
(`apps/<앱>/composables/` + `shared/composables/`, `use` 접두사).
|
|
43
|
+
shared 컴포저블은 인자로만 받는 순수 로직(api·pageProps 금지).
|
|
44
|
+
레이아웃은 `apps/<앱>/layouts/Default.vue` 존재 시 자동 적용.
|
|
45
|
+
**자동 import 금지** — 모든 import 는 명시적으로.
|
|
46
|
+
12. **JSON 액션 + 타입드 `api()`**(errata E-3): 페이지와 무관한 데이터
|
|
47
|
+
요청은 컨트롤러의 JSON 액션(반환값 = 응답) · Vue 는 `api()`
|
|
48
|
+
클라이언트로 호출. `this.params()` 출처 우선순위 = 라우트 > body >
|
|
49
|
+
query(고정). 출처 명시는 `this.body()`·`this.query()`.
|
|
50
|
+
|
|
51
|
+
## 2. 프로젝트 구조
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
{{PROJECT_NAME}}/
|
|
55
|
+
├─ apps/ 앱마다 폴더 = URL 프리픽스
|
|
56
|
+
│ └─ web/ web 앱 (프리픽스 '/')
|
|
57
|
+
│ ├─ app.config.ts (선택) 앱 오버라이드
|
|
58
|
+
│ ├─ routes.ts 라우트 정의
|
|
59
|
+
│ ├─ controllers/ 컨트롤러 (Rails 관례)
|
|
60
|
+
│ ├─ pages/ Vue 페이지 (Inertia SPA)
|
|
61
|
+
│ ├─ components/ 앱 전용 컴포넌트
|
|
62
|
+
│ ├─ composables/ 앱 전용 컴포저블 (E-5)
|
|
63
|
+
│ ├─ layouts/ 앱별 레이아웃 (E-5 · Default.vue 자동)
|
|
64
|
+
│ └─ channels/ (선택) 실시간 채널 (M6)
|
|
65
|
+
├─ domain/ 비즈니스 로직 · 앱 간 공유
|
|
66
|
+
│ ├─ schema/ 테이블 스키마 (@gaonjs/data)
|
|
67
|
+
│ ├─ models/ 모델 (조회·연관·훅)
|
|
68
|
+
│ ├─ services/ 트랜잭션 · 규칙
|
|
69
|
+
│ ├─ jobs/ 비동기 잡 (@gaonjs/async)
|
|
70
|
+
│ ├─ events/ 이벤트 정의
|
|
71
|
+
│ ├─ listeners/ 이벤트 리스너
|
|
72
|
+
│ ├─ mails/ 메일 (@gaonjs/mail)
|
|
73
|
+
│ └─ schedule.ts (선택) 크론 스케줄
|
|
74
|
+
├─ shared/ 앱 간 공용 (props로만)
|
|
75
|
+
│ ├─ components/ shared 컴포넌트 (순수 UI)
|
|
76
|
+
│ └─ composables/ shared 컴포저블 (인자로만 · E-5)
|
|
77
|
+
├─ gaon.config.ts 루트 설정 (DB · Redis · NATS · ...)
|
|
78
|
+
├─ docker-compose.yaml 개발 인프라 (gaon dev 자동 기동)
|
|
79
|
+
├─ .env.example env 템플릿 (cp .env.example .env)
|
|
80
|
+
└─ package.json 개발자는 gaonjs 하나만 설치
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## 3. 개발 검증 루프 (작업마다 실행)
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
gaon check # .gaon 재생성 후 타입 검사 (CI 정합)
|
|
87
|
+
gaon doctor # 정적 검사 5종 (응답·N+1·의존·커넥션·마이그)
|
|
88
|
+
npm test # Vitest · DB 테스트는 실 Docker 필수 (§9)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## 4. 작업 관례
|
|
92
|
+
|
|
93
|
+
- **추론 금지 · 사실 검증.** 확인 안 된 것은 실행·측정으로 검증하거나
|
|
94
|
+
사용자에게 묻는다.
|
|
95
|
+
- **주석은 개발자가 단 것처럼.** AI가 단 티가 나는 주석(`// AI 판단…`
|
|
96
|
+
`// TODO(AI)` 등) 금지. 기술적 이유(왜 이 코드가 이런지, 특수 케이스
|
|
97
|
+
근거) 만 담는다.
|
|
98
|
+
- **파일 작게 · 역할은 하나로.** 영리한 코드보다 읽히는 코드.
|
|
99
|
+
- **에러 메시지**(§7.5.3): "어느 파일에 무엇을 추가/수정하고 어떤
|
|
100
|
+
명령을 실행하라" 까지 쓴다.
|
|
101
|
+
|
|
102
|
+
## 5. 자주 쓰는 명령
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
gaon dev # 개발 (Docker · 타입 브리지 · watch)
|
|
106
|
+
gaon serve # 서버만 (운영 프로세스 1/3)
|
|
107
|
+
gaon work # 워커 (잡·리스너·아웃박스)
|
|
108
|
+
gaon hub # 실시간 허브 (프레즌스 · 리더 선출 HA)
|
|
109
|
+
gaon g controller <name> # 컨트롤러 스캐폴드
|
|
110
|
+
gaon g model <Name> # 스키마 + 모델 스캐폴드 (E-4)
|
|
111
|
+
gaon g page <Path>/<Name> # Vue 페이지 (Inertia SPA · pageProps)
|
|
112
|
+
gaon g job <Name> # 비동기 잡
|
|
113
|
+
gaon g auth # 인증 스캐폴드 (세션 + JWT 옵션)
|
|
114
|
+
gaon db diff # 스키마 ↔ DB 차이 (적용 X)
|
|
115
|
+
gaon db migrate # 실제 적용 + _gaon_migrations 이력
|
|
116
|
+
gaon db seed # domain/seed.ts 실행
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
문서 · 진행 상황: https://gaonjs.dev
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# 이 폴더는 gaon g <type> <name> 로 채워집니다. .gitkeep 은 스캐폴드 관례상 유지.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# 이 폴더는 gaon g <type> <name> 로 채워집니다. .gitkeep 은 스캐폴드 관례상 유지.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// useApiPing — 앱 전용 컴포저블(errata E-5 §2.1).
|
|
2
|
+
// 파일 하나에 컴포저블 하나 · 파일명 = 컴포저블명 · use 접두사.
|
|
3
|
+
// 앱 전용 컴포저블은 api()/pageProps() 를 자유롭게 쓸 수 있다.
|
|
4
|
+
// shared/composables 는 반대 — 인자로만 받는 순수 로직(E-5 §2.2).
|
|
5
|
+
import { ref } from 'vue'
|
|
6
|
+
|
|
7
|
+
/** GET /health 를 두드려 서버가 살아 있는지 확인하는 예시 컴포저블. */
|
|
8
|
+
export function useApiPing() {
|
|
9
|
+
const ok = ref<boolean | null>(null)
|
|
10
|
+
const error = ref<string | null>(null)
|
|
11
|
+
|
|
12
|
+
async function ping(): Promise<void> {
|
|
13
|
+
try {
|
|
14
|
+
const res = await fetch('/health', { headers: { Accept: 'application/json' } })
|
|
15
|
+
const body = (await res.json()) as { ok: boolean }
|
|
16
|
+
ok.value = body.ok === true
|
|
17
|
+
error.value = null
|
|
18
|
+
} catch (err) {
|
|
19
|
+
ok.value = false
|
|
20
|
+
error.value = err instanceof Error ? err.message : String(err)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return { ok, error, ping }
|
|
25
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// home 컨트롤러 — apps/web/controllers/home.ts. 페이지 액션(this.render)과
|
|
2
|
+
// JSON 액션(반환값 = 응답 · errata E-3)의 두 대표 패턴을 함께 담는다.
|
|
3
|
+
import { controller } from 'gaonjs/web'
|
|
4
|
+
|
|
5
|
+
export default controller({
|
|
6
|
+
// GET / — Inertia SPA 홈. this.render 는 Vue 페이지(Home/Index.vue) 로 넘긴다.
|
|
7
|
+
async index() {
|
|
8
|
+
return this.render('Home/Index', {
|
|
9
|
+
title: '{{PROJECT_NAME}}',
|
|
10
|
+
docs: 'https://gaonjs.dev',
|
|
11
|
+
})
|
|
12
|
+
},
|
|
13
|
+
|
|
14
|
+
// GET /health — JSON 액션(errata E-3). 반환값이 곧 응답.
|
|
15
|
+
// 배포 후 헬스체크·60초 실측(v0.15 §13.5 M9 완료 기준)에 쓰인다.
|
|
16
|
+
async health() {
|
|
17
|
+
return { ok: true, service: '{{PROJECT_NAME}}' }
|
|
18
|
+
},
|
|
19
|
+
})
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// 기본 레이아웃 — apps/web/layouts/Default.vue (errata E-5 §2.3).
|
|
3
|
+
// 파일이 존재하면 이 앱의 모든 페이지에 자동 적용된다(파일 존재 = 등록).
|
|
4
|
+
// 다른 레이아웃이 필요한 페이지만 페이지 파일에서 명시적으로 바꾼다.
|
|
5
|
+
//
|
|
6
|
+
// 레이아웃은 shared 에 두지 않는다(E-5 §2.3) — 앱마다 레이아웃이 다른 것이
|
|
7
|
+
// 정상이고, 공용 조각(로고·푸터 등) 만 shared/components 로 뽑는다.
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<template>
|
|
11
|
+
<div class="layout">
|
|
12
|
+
<header class="layout-header">
|
|
13
|
+
<strong>{{PROJECT_NAME}}</strong>
|
|
14
|
+
</header>
|
|
15
|
+
<slot />
|
|
16
|
+
<footer class="layout-footer">
|
|
17
|
+
<small>Powered by <a href="https://gaonjs.dev" target="_blank">Gaon</a></small>
|
|
18
|
+
</footer>
|
|
19
|
+
</div>
|
|
20
|
+
</template>
|
|
21
|
+
|
|
22
|
+
<style scoped>
|
|
23
|
+
.layout {
|
|
24
|
+
min-height: 100vh;
|
|
25
|
+
display: flex;
|
|
26
|
+
flex-direction: column;
|
|
27
|
+
}
|
|
28
|
+
.layout-header,
|
|
29
|
+
.layout-footer {
|
|
30
|
+
padding: 1rem;
|
|
31
|
+
background: #f6f8fa;
|
|
32
|
+
border-color: #e1e4e8;
|
|
33
|
+
}
|
|
34
|
+
.layout-header {
|
|
35
|
+
border-bottom: 1px solid #e1e4e8;
|
|
36
|
+
}
|
|
37
|
+
.layout-footer {
|
|
38
|
+
border-top: 1px solid #e1e4e8;
|
|
39
|
+
margin-top: auto;
|
|
40
|
+
text-align: center;
|
|
41
|
+
color: #6a737d;
|
|
42
|
+
}
|
|
43
|
+
</style>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { pageProps } from 'gaonjs/vue'
|
|
3
|
+
|
|
4
|
+
// home#index 의 render props — Serialized<> 로 넘어온다(§6.2).
|
|
5
|
+
// 라우트 키는 .gaon/routes.d.ts 가 유효한 값을 알려준다.
|
|
6
|
+
const props = pageProps<'home#index'>()
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
<template>
|
|
10
|
+
<main class="home">
|
|
11
|
+
<h1>{{ props.title }}</h1>
|
|
12
|
+
<p>Gaon 프레임웍이 방금 이 앱을 만들었습니다.</p>
|
|
13
|
+
<p>
|
|
14
|
+
문서: <a :href="props.docs" target="_blank">{{ props.docs }}</a>
|
|
15
|
+
</p>
|
|
16
|
+
<hr />
|
|
17
|
+
<h2>다음 단계</h2>
|
|
18
|
+
<ol>
|
|
19
|
+
<li><code>gaon g auth</code> — 인증 스캐폴드 생성 (회원가입·로그인·세션)</li>
|
|
20
|
+
<li><code>gaon g model Post</code> — 모델 스캐폴드 (스키마 + 모델 · E-4)</li>
|
|
21
|
+
<li><code>gaon g controller posts</code> — 컨트롤러 스캐폴드</li>
|
|
22
|
+
<li><code>gaon g page Posts/Index</code> — Vue 페이지 (Inertia SPA)</li>
|
|
23
|
+
<li><code>gaon dev</code> — Docker 자동 기동 · 타입 브리지 · watch</li>
|
|
24
|
+
</ol>
|
|
25
|
+
</main>
|
|
26
|
+
</template>
|
|
27
|
+
|
|
28
|
+
<style scoped>
|
|
29
|
+
.home {
|
|
30
|
+
max-width: 640px;
|
|
31
|
+
margin: 4rem auto;
|
|
32
|
+
padding: 0 1rem;
|
|
33
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
34
|
+
line-height: 1.6;
|
|
35
|
+
}
|
|
36
|
+
</style>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// web 앱 라우트 — apps/web/routes.ts. 앱 폴더명(web)이 URL 프리픽스가 되지만
|
|
2
|
+
// web 앱은 관례상 프리픽스 '/' 를 쓴다(v0.15 §6.1).
|
|
3
|
+
import { routes } from 'gaonjs/web'
|
|
4
|
+
|
|
5
|
+
export default routes((r) => {
|
|
6
|
+
r.get('/', 'home#index') // GET / → home#index (Inertia SPA · Home/Index.vue)
|
|
7
|
+
r.get('/health', 'home#health') // GET /health → JSON { ok: true } (헬스체크)
|
|
8
|
+
})
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# {{PROJECT_NAME}} 개발 스택 — gaon dev 가 자동 기동한다(CLAUDE.md §2 · v0.15 §9).
|
|
2
|
+
# 목업·인메모리 대체는 금지 — 개발·테스트·운영 모두 실 인프라를 쓴다.
|
|
3
|
+
#
|
|
4
|
+
# 기동: docker compose up -d (gaon dev 가 자동 실행)
|
|
5
|
+
# 정지: docker compose down
|
|
6
|
+
name: {{PROJECT_NAME}}
|
|
7
|
+
|
|
8
|
+
services:
|
|
9
|
+
postgres:
|
|
10
|
+
image: postgres:16-alpine
|
|
11
|
+
environment:
|
|
12
|
+
POSTGRES_USER: {{PROJECT_NAME}}
|
|
13
|
+
POSTGRES_PASSWORD: {{PROJECT_NAME}}
|
|
14
|
+
POSTGRES_DB: {{PROJECT_NAME}}_dev
|
|
15
|
+
ports:
|
|
16
|
+
- "5432:5432"
|
|
17
|
+
healthcheck:
|
|
18
|
+
test: ["CMD-SHELL", "pg_isready -U {{PROJECT_NAME}} -d {{PROJECT_NAME}}_dev"]
|
|
19
|
+
interval: 2s
|
|
20
|
+
timeout: 3s
|
|
21
|
+
retries: 30
|
|
22
|
+
|
|
23
|
+
# 세션 스토어(§7.4). 캐시·세션은 Redis 가 기본이다.
|
|
24
|
+
redis:
|
|
25
|
+
image: redis:7-alpine
|
|
26
|
+
ports:
|
|
27
|
+
- "6379:6379"
|
|
28
|
+
healthcheck:
|
|
29
|
+
test: ["CMD", "redis-cli", "ping"]
|
|
30
|
+
interval: 2s
|
|
31
|
+
timeout: 3s
|
|
32
|
+
retries: 30
|
|
33
|
+
|
|
34
|
+
# 실시간 백본(§7 · M6). NATS JetStream(-js) — 채널·잡·프레즌스 KV 를 모두 처리.
|
|
35
|
+
nats:
|
|
36
|
+
image: nats:2.10-alpine
|
|
37
|
+
command: ["-js", "-m", "8222"]
|
|
38
|
+
ports:
|
|
39
|
+
- "4222:4222"
|
|
40
|
+
- "8222:8222"
|
|
41
|
+
healthcheck:
|
|
42
|
+
test: ["CMD", "wget", "--spider", "-q", "http://127.0.0.1:8222/healthz"]
|
|
43
|
+
interval: 2s
|
|
44
|
+
timeout: 3s
|
|
45
|
+
retries: 30
|
|
46
|
+
|
|
47
|
+
# 메일 배터리(§7 · M8). MailPit = SMTP 싱크 + 웹 UI(:8025) — 개발 미리보기의 실 인프라.
|
|
48
|
+
mailpit:
|
|
49
|
+
image: axllent/mailpit:latest
|
|
50
|
+
ports:
|
|
51
|
+
- "1025:1025"
|
|
52
|
+
- "8025:8025"
|
|
53
|
+
healthcheck:
|
|
54
|
+
test: ["CMD", "wget", "--spider", "-q", "http://127.0.0.1:8025/readyz"]
|
|
55
|
+
interval: 2s
|
|
56
|
+
timeout: 3s
|
|
57
|
+
retries: 30
|
|
58
|
+
|
|
59
|
+
# 파일 스토리지(§7 · M8). MinIO = S3 호환 — 운영 R2/S3 와 동일 API.
|
|
60
|
+
minio:
|
|
61
|
+
image: minio/minio:latest
|
|
62
|
+
command: ["server", "/data", "--console-address", ":9001"]
|
|
63
|
+
environment:
|
|
64
|
+
MINIO_ROOT_USER: {{PROJECT_NAME}}
|
|
65
|
+
MINIO_ROOT_PASSWORD: {{PROJECT_NAME}}_secret
|
|
66
|
+
ports:
|
|
67
|
+
- "9000:9000"
|
|
68
|
+
- "9001:9001"
|
|
69
|
+
healthcheck:
|
|
70
|
+
test: ["CMD", "mc", "ready", "local"]
|
|
71
|
+
interval: 2s
|
|
72
|
+
timeout: 3s
|
|
73
|
+
retries: 30
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# 이 폴더는 gaon g <type> <name> 로 채워집니다. .gitkeep 은 스캐폴드 관례상 유지.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# 이 폴더는 gaon g <type> <name> 로 채워집니다. .gitkeep 은 스캐폴드 관례상 유지.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# 이 폴더는 gaon g <type> <name> 로 채워집니다. .gitkeep 은 스캐폴드 관례상 유지.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# 이 폴더는 gaon g <type> <name> 로 채워집니다. .gitkeep 은 스캐폴드 관례상 유지.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# 이 폴더는 gaon g <type> <name> 로 채워집니다. .gitkeep 은 스캐폴드 관례상 유지.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# 이 폴더는 gaon g <type> <name> 로 채워집니다. .gitkeep 은 스캐폴드 관례상 유지.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# 이 폴더는 gaon g <type> <name> 로 채워집니다. .gitkeep 은 스캐폴드 관례상 유지.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// {{PROJECT_NAME}} 루트 설정 (§3.3). 개발자는 이 파일 하나로 배터리를 켠다.
|
|
2
|
+
// 환경 변수는 .env 에서 로드된다 — gaon dev / gaon serve 가 자동 배선.
|
|
3
|
+
import { defineConfig } from 'gaonjs/config'
|
|
4
|
+
|
|
5
|
+
export default defineConfig({
|
|
6
|
+
// DB — main 커넥션. 스키마에서 { db: '키' } 로 다른 커넥션에 붙일 수 있다(§4.5).
|
|
7
|
+
db: process.env.DATABASE_URL
|
|
8
|
+
? {
|
|
9
|
+
main: {
|
|
10
|
+
adapter: 'postgres',
|
|
11
|
+
url: process.env.DATABASE_URL,
|
|
12
|
+
},
|
|
13
|
+
}
|
|
14
|
+
: undefined,
|
|
15
|
+
|
|
16
|
+
// Redis — 세션 스토어 · 캐시.
|
|
17
|
+
redis: process.env.REDIS_URL ? { url: process.env.REDIS_URL } : undefined,
|
|
18
|
+
|
|
19
|
+
// NATS — 실시간·비동기 백본(§7). broadcast 전용(errata E-2).
|
|
20
|
+
nats: process.env.NATS_URL ? { url: process.env.NATS_URL } : undefined,
|
|
21
|
+
|
|
22
|
+
// 웹 서버 리슨 옵션. --port · env PORT 로 덮을 수 있다.
|
|
23
|
+
web: {
|
|
24
|
+
port: process.env.PORT ? Number(process.env.PORT) : 3000,
|
|
25
|
+
cookieSecret: process.env.COOKIE_SECRET,
|
|
26
|
+
},
|
|
27
|
+
})
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{PROJECT_NAME}}",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=22"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"dev": "gaon dev",
|
|
11
|
+
"serve": "gaon serve",
|
|
12
|
+
"work": "gaon work",
|
|
13
|
+
"hub": "gaon hub",
|
|
14
|
+
"check": "gaon check",
|
|
15
|
+
"doctor": "gaon doctor",
|
|
16
|
+
"test": "vitest run"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"gaonjs": "{{GAONJS_VERSION}}"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"typescript": "^5.9.0",
|
|
23
|
+
"vue": "^3.5.0",
|
|
24
|
+
"vue-tsc": "^3.3.0",
|
|
25
|
+
"vitest": "^3.0.0"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# pnpm workspace — 앱은 apps/*, 도메인은 domain/, 공용은 shared/ 아래에 둔다.
|
|
2
|
+
# 이 관례는 CLAUDE.md §2 · v0.15 §3.2 를 따른다(앱과 domain 은 별도 폴더).
|
|
3
|
+
packages:
|
|
4
|
+
- "packages/*"
|
|
5
|
+
|
|
6
|
+
# pnpm 11 부터 build script 실행은 명시적 승인 필요(보안 기본값 = 비 TTY 에서 exit 1).
|
|
7
|
+
# 스캐폴드 첫 install 이 잡히지 않도록 완화하고, 실제 실행 허용 목록만 명시한다.
|
|
8
|
+
# 정본 문서: https://pnpm.io/settings (구 package.json 의 pnpm 필드 대체).
|
|
9
|
+
strictDepBuilds: false
|
|
10
|
+
onlyBuiltDependencies:
|
|
11
|
+
- esbuild
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# 이 폴더는 gaon g <type> <name> 로 채워집니다. .gitkeep 은 스캐폴드 관례상 유지.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// useDebounce — shared 컴포저블(errata E-5 §2.2).
|
|
2
|
+
// shared 는 shared 컴포넌트의 "props 로만" 규칙과 정확히 대칭이다:
|
|
3
|
+
// gaonjs/vue 서브패스 import 금지 · domain 은 타입 import 만 · 인자로만
|
|
4
|
+
// 받는 순수 로직. (`.gaon/routes.d.ts` 는 앱별 생성이라 구조적으로도
|
|
5
|
+
// 라우트를 몰라야 한다.)
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* fn 을 delay(ms) 만큼 지연 실행. 새 호출이 오면 이전 타이머를 취소한다.
|
|
9
|
+
* @param fn 실행할 함수 (인자로 받는다 — shared 는 라우트를 모른다).
|
|
10
|
+
* @param delay 지연 시간(ms).
|
|
11
|
+
*/
|
|
12
|
+
export function useDebounce<A extends readonly unknown[]>(
|
|
13
|
+
fn: (...args: A) => void,
|
|
14
|
+
delay: number,
|
|
15
|
+
): (...args: A) => void {
|
|
16
|
+
let timer: ReturnType<typeof setTimeout> | undefined
|
|
17
|
+
return (...args: A) => {
|
|
18
|
+
if (timer !== undefined) clearTimeout(timer)
|
|
19
|
+
timer = setTimeout(() => fn(...args), delay)
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"lib": ["ES2022", "DOM"],
|
|
5
|
+
"module": "nodenext",
|
|
6
|
+
"moduleResolution": "nodenext",
|
|
7
|
+
"strict": true,
|
|
8
|
+
"esModuleInterop": true,
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"forceConsistentCasingInFileNames": true,
|
|
11
|
+
"resolveJsonModule": true,
|
|
12
|
+
"noEmit": true,
|
|
13
|
+
"jsx": "preserve",
|
|
14
|
+
"types": ["node"]
|
|
15
|
+
},
|
|
16
|
+
"include": [
|
|
17
|
+
"apps/**/*.ts",
|
|
18
|
+
"apps/**/*.vue",
|
|
19
|
+
"domain/**/*.ts",
|
|
20
|
+
"shared/**/*.ts",
|
|
21
|
+
"shared/**/*.vue",
|
|
22
|
+
"gaon.config.ts"
|
|
23
|
+
],
|
|
24
|
+
"exclude": ["node_modules", "dist", ".gaon"]
|
|
25
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gaonjs/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Gaon CLI 구현: 제너레이터·스캐폴딩·로드맵 출력 (M1 스텁)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
"@gaonjs/config": "0.1.0",
|
|
30
30
|
"@gaonjs/core": "0.1.4",
|
|
31
31
|
"@gaonjs/mail": "0.1.0",
|
|
32
|
-
"@gaonjs/
|
|
33
|
-
"@gaonjs/
|
|
32
|
+
"@gaonjs/web": "0.3.0",
|
|
33
|
+
"@gaonjs/data": "0.3.0"
|
|
34
34
|
},
|
|
35
35
|
"scripts": {
|
|
36
36
|
"build": "node ../../node_modules/typescript/bin/tsc -p tsconfig.json && node -e \"require('fs').cpSync('src/templates','dist/templates',{recursive:true})\""
|