@gaonjs/cli 0.5.0 → 0.10.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 (82) hide show
  1. package/dist/commands/check.d.ts +21 -2
  2. package/dist/commands/check.js +70 -7
  3. package/dist/commands/db.d.ts +3 -1
  4. package/dist/commands/db.js +8 -2
  5. package/dist/commands/g.d.ts +1 -1
  6. package/dist/commands/g.js +27 -3
  7. package/dist/commands/mcp.d.ts +15 -0
  8. package/dist/commands/mcp.js +78 -0
  9. package/dist/db/diff.js +5 -0
  10. package/dist/db/journal.d.ts +34 -0
  11. package/dist/db/journal.js +71 -0
  12. package/dist/db/migrate.d.ts +6 -1
  13. package/dist/db/migrate.js +120 -102
  14. package/dist/db/replay.d.ts +49 -0
  15. package/dist/db/replay.js +148 -0
  16. package/dist/db/status.d.ts +12 -0
  17. package/dist/db/status.js +61 -0
  18. package/dist/dev/index.d.ts +2 -0
  19. package/dist/dev/index.js +2 -0
  20. package/dist/dev/vite.d.ts +67 -0
  21. package/dist/dev/vite.js +126 -0
  22. package/dist/dev.d.ts +18 -0
  23. package/dist/dev.js +15 -0
  24. package/dist/doctor/agents-doc-index.d.ts +4 -0
  25. package/dist/doctor/agents-doc-index.js +80 -0
  26. package/dist/doctor/fixers/dependency-direction.d.ts +9 -0
  27. package/dist/doctor/fixers/dependency-direction.js +98 -0
  28. package/dist/doctor/fixers/index.d.ts +15 -0
  29. package/dist/doctor/fixers/index.js +66 -0
  30. package/dist/doctor/fixers/schema-filename.d.ts +14 -0
  31. package/dist/doctor/fixers/schema-filename.js +104 -0
  32. package/dist/doctor/fixers/types.d.ts +59 -0
  33. package/dist/doctor/fixers/types.js +15 -0
  34. package/dist/doctor/schema-filename.d.ts +6 -0
  35. package/dist/doctor/schema-filename.js +81 -0
  36. package/dist/doctor/types.d.ts +1 -1
  37. package/dist/doctor.d.ts +49 -0
  38. package/dist/doctor.js +179 -5
  39. package/dist/generate.js +2 -2
  40. package/dist/hub.d.ts +1 -1
  41. package/dist/index.d.ts +2 -1
  42. package/dist/index.js +50 -10
  43. package/dist/mcp/index.d.ts +7 -0
  44. package/dist/mcp/index.js +7 -0
  45. package/dist/mcp/server.d.ts +50 -0
  46. package/dist/mcp/server.js +102 -0
  47. package/dist/mcp/tools.d.ts +109 -0
  48. package/dist/mcp/tools.js +485 -0
  49. package/dist/scaffold/app.d.ts +5 -0
  50. package/dist/scaffold/app.js +172 -0
  51. package/dist/scaffold/controller.js +2 -2
  52. package/dist/scaffold/index.d.ts +2 -1
  53. package/dist/scaffold/index.js +2 -1
  54. package/dist/scaffold/job.d.ts +5 -0
  55. package/dist/scaffold/job.js +35 -0
  56. package/dist/scaffold/model.js +8 -8
  57. package/dist/templates/auth/auth.wiring.ts.tpl +1 -1
  58. package/dist/templates/auth/registration.controller.ts.tpl +1 -1
  59. package/dist/templates/auth/session.controller.ts.tpl +1 -1
  60. package/dist/templates/auth/user.model.ts.tpl +1 -1
  61. package/dist/templates/project/AGENTS.md.tpl +214 -0
  62. package/dist/templates/project/agents/async.md.tpl +218 -0
  63. package/dist/templates/project/agents/data.md.tpl +556 -0
  64. package/dist/templates/project/agents/frontend.md.tpl +201 -0
  65. package/dist/templates/project/agents/realtime.md.tpl +157 -0
  66. package/dist/templates/project/agents/security.md.tpl +92 -0
  67. package/dist/templates/project/agents/testing.md.tpl +101 -0
  68. package/dist/templates/project/agents/web.md.tpl +177 -0
  69. package/dist/templates/project/apps/web/index.html.tpl +18 -0
  70. package/dist/templates/project/apps/web/main.ts.tpl +24 -0
  71. package/dist/templates/project/package.json.tpl +5 -2
  72. package/dist/templates/project/vite.config.ts.tpl +23 -0
  73. package/dist/tsResolve.js +1 -1
  74. package/dist/work.d.ts +2 -2
  75. package/dist/work.js +3 -1
  76. package/package.json +13 -11
  77. package/dist/__fixtures__/db-minimal/domain/schema/widgets.d.ts +0 -12
  78. package/dist/__fixtures__/db-minimal/domain/schema/widgets.js +0 -7
  79. package/dist/__fixtures__/db-minimal/gaon.config.d.ts +0 -2
  80. package/dist/__fixtures__/db-minimal/gaon.config.js +0 -11
  81. package/dist/check.d.ts +0 -29
  82. package/dist/check.js +0 -92
@@ -0,0 +1,177 @@
1
+ # agents/web.md — 웹 레이어 (라우팅 · 컨트롤러 · params · JSON 액션 · 인증)
2
+
3
+ > 골격: **정본 규칙 → 정본 예시 → 알려진 함정 → 관련 결정 번호** (결정 40 · 2층 구조).
4
+ > 루트 `AGENTS.md` 는 코어 요약만 담는다 — 시그니처·규칙 상세의 정본은 이 파일이다.
5
+ > 대상 패키지: `@gaonjs/web` (파사드 import 는 `gaonjs/web`).
6
+
7
+ ## 정본 규칙
8
+
9
+ ### 1. JSON 액션 — 반환값이 곧 응답 (errata E-3 §3.1)
10
+
11
+ 컨트롤러 액션이 `this.render(...)` 나 `this.redirect(...)` 대신
12
+ **평범한 객체/배열을 반환하면 그것이 곧 JSON 응답**이다. 별도
13
+ 데코레이터·설정·헬퍼 호출이 없다 (E-3 §3.1 원문).
14
+
15
+ ```ts
16
+ // apps/web/controllers/posts.ts
17
+ import { controller } from 'gaonjs/web' // 파사드 · 함수형 API
18
+ import { Post } from '../../../domain/models/Post.js' // 앱→도메인 (rule 5)
19
+
20
+ export default controller({
21
+ async index() {
22
+ return this.render('Posts/Index', { posts: await Post.latest().limit(20).all() })
23
+ },
24
+
25
+ // JSON 액션 — 객체를 반환하면 JSON 응답
26
+ async search() {
27
+ const { q } = this.params(Post.searchForm) // 검증 실패 시 자동 422 JSON
28
+ return { results: await Post.published().search(q).limit(10).all() }
29
+ },
30
+ })
31
+ ```
32
+
33
+ - **import 관례** — 프레임웍 심볼은 파사드 `gaonjs/*` 에서 (`gaonjs/web`·
34
+ `gaonjs/data`·`gaonjs/vue`·`gaonjs/async` 등), 도메인 모델은 상대경로
35
+ `../../../domain/models/<Pascal>.js` 로 참조한다. `@gaonjs/*` (스코프
36
+ 이름) 은 내부 패키지 이름 — 앱 코드에서 직접 import 하지 않는다.
37
+ `@inertiajs/vue3` 는 어댑터 내부 의존 — 앱에서 직접 안 쓴다.
38
+ - **`this.render` 인자** = `<PageFolder>/<Page>` (PascalCase 폴더 · Vue
39
+ 파일명과 정합) — 예: `'Posts/Index'` → `apps/<app>/pages/Posts/Index.vue`.
40
+ - **리소스 부재 = `this.notFound()`** — 조회 결과가 없으면 404 를 손으로 만들지
41
+ 말고 `this.notFound()` 로 마감한다(`requireAuth()` 와 동형). `never` 를 반환해
42
+ 이 뒤로 값이 존재하는 것으로 좁혀지므로, `render` 반환 타입(타입 브리지)도 그대로
43
+ 유지된다. 메시지는 선택: `this.notFound('post 없음')`.
44
+
45
+ ```ts
46
+ async show() {
47
+ const post = await Post.find(this.params(Post.showForm).id)
48
+ if (!post) return this.notFound() // 404 · 이 뒤로 post 는 non-null
49
+ return this.render('Posts/Show', { post })
50
+ }
51
+ ```
52
+
53
+ ### 2. 응답 규칙 — 한 액션은 한 종류 응답만
54
+
55
+ - **한 액션은 한 종류 응답만 낸다** (render 또는 JSON 또는 redirect —
56
+ 조건 분기 혼용 금지). `gaon doctor` 의 **response-mixing** 검사가
57
+ 혼용을 잡는다.
58
+ - 검증은 페이지 액션과 동일하게 `this.params(스키마)` — 실패 시
59
+ 422 JSON.
60
+ - 인증·세션은 같은 앱의 세션 쿠키를 그대로 쓴다. CSRF·rate limit 등
61
+ 보안 기본값도 그대로 적용된다 (`agents/security.md` · 끄는 것은
62
+ 명시적 설정으로만).
63
+
64
+ ### 3. `this.params` 통합 입력 · 안전 규칙 (errata E-3 §5 · 결정 24)
65
+
66
+ `this.params` 는 라우트 파라미터·query·body(업로드 파일 포함)를
67
+ 하나로 합쳐서 주는 Rails 식 통합 입력이다. 안전을 위해 아래 세 규칙이
68
+ 반드시 함께 적용된다.
69
+
70
+ **출처 우선순위 — 고정, 설정 불가:**
71
+
72
+ - **라우트 파라미터 > body > query** 순서로 병합 (E-3 §5.1 원문).
73
+ - 라우트 파라미터가 절대 덮어써지지 않는 것이 핵심 — `/posts/:id` 의
74
+ `id` 를 공격자가 body 의 `id: 999` 로 바꿔치기해서 권한 검사를
75
+ 우회하는 파라미터 오염 공격을 원천 차단한다.
76
+ - 이 우선순위는 문서화된 고정 규칙이며 **설정으로 바꿀 수 없다**.
77
+
78
+ **중복 키:**
79
+
80
+ - `?tag=a&tag=b` 처럼 같은 출처에서 키가 중복되면: 스키마의 해당
81
+ 필드가 배열 타입이면 배열로 수집, 아니면 **마지막 값**을 쓴다
82
+ (E-3 §5.2 원문).
83
+
84
+ **출처 명시 탈출구 — `this.body()` / `this.query()`:**
85
+
86
+ - 출처 자체가 의미를 갖는 드문 경우(웹훅 수신처럼 반드시 body에서만
87
+ 와야 하는 데이터, 서명 검증 대상 등)에만 쓴다.
88
+ - 기본 경로는 여전히 `this.params` 하나(The One Way) — 스캐폴드·문서·
89
+ 기본 예시는 `this.params` 만 쓴다 (E-3 §5.3 원문).
90
+
91
+ ### 4. 데이터 경로 판단 — 루트 판단표가 정본
92
+
93
+ 데이터가 필요할 때는 루트 `AGENTS.md` 의 데이터 경로 4종 판단표를 따른다
94
+ (Inertia partial reload · 채널/프레즌스 · JSON 액션 + `api()` · 별도 API 앱 + JWT).
95
+ **Inertia = SPA + 서버 라우팅**이지 SSR 이 아니다. 로그인·회원가입도 컨트롤러
96
+ `this.render('auth/Login')` + Vue 페이지 `Inertia.post()` → 서버
97
+ redirect 로 처리한다 — 전체 페이지 리로드도, 별도 REST 엔드포인트도
98
+ 없다. REST + `fetch()` 는 **API 앱(JWT) 전용**.
99
+
100
+ ### 5. 비밀번호 해싱 — `hashPassword` · `verifyPassword` (`gaonjs/web`)
101
+
102
+ 회원가입·로그인에서 비밀번호를 다룰 때는 **직접 crypto/bcrypt 를 import 하거나
103
+ base64·해시를 손으로 짜지 말고** `gaonjs/web` 의 헬퍼를 쓴다(The One Way · §7 인증).
104
+ bcrypt(cost 10)로 해싱하며 상수시간 비교를 제공한다.
105
+
106
+ ```ts
107
+ import { hashPassword, verifyPassword } from 'gaonjs/web'
108
+
109
+ // 가입 — 서비스에서 평문을 해싱해 hidden 컬럼(passwordDigest)에 저장.
110
+ const passwordDigest = await hashPassword(plain) // Promise<string>
111
+ const user = await User.create({ name, email, passwordDigest })
112
+
113
+ // 로그인 — 저장된 다이제스트와 평문을 비교(상수시간).
114
+ const ok = await verifyPassword(plain, user.passwordDigest) // Promise<boolean>
115
+ ```
116
+
117
+ - **시그니처** — `hashPassword(plain: string): Promise<string>` ·
118
+ `verifyPassword(plain: string, digest: string): Promise<boolean>`. 둘 다 async.
119
+ - **기본값** — bcrypt cost 10(보안/지연 균형점). 순수 JS(`bcryptjs`)라 네이티브
120
+ 빌드 없이 어느 환경에서나 설치가 확실하다.
121
+ - **저장 위치** — 결과는 스키마의 hidden 컬럼(`passwordDigest: t.string().hidden()`)
122
+ 에 담는다 — 응답 경계에서 타입·런타임 양쪽으로 페이지 노출이 막힌다.
123
+ - 회원 생성 로직은 컨트롤러가 아니라 **서비스**(`agents/data.md` §9 · registerUser)에
124
+ 둔다 — `const user = await RegisterUser.call({ name, email, password })`.
125
+
126
+ ### 6. 인증·세션 (v0.15 §7 · M5)
127
+
128
+ - 세션 쿠키가 기본, JWT 는 **API 앱 전용 옵션** (v0.11 확정).
129
+ - 세션은 앱별 완전 분리 (Fastify 캡슐화 스코프): 쿠키 이름(`<app>_sid`) ·
130
+ 서명 secret · Redis 키 prefix · 쿠키 path 가 앱 단위로 갇힌다.
131
+ - 스캐폴드는 `gaon g auth` — 로그인/회원가입 컨트롤러·페이지·라우트 일습.
132
+ - 로그인 필요 액션은 `this.requireAuth()` 관례 (notFound 와 동형 — never 좁힘).
133
+
134
+ ## 정본 예시
135
+
136
+ ```ts
137
+ // apps/web/controllers/registration.ts — 회원 가입 (E-3 · Inertia SPA)
138
+ import { controller } from 'gaonjs/web'
139
+ import { RegisterUser } from '../../../domain/services/registerUser.js'
140
+ import { SendWelcomeMail } from '../../../domain/jobs/sendWelcomeMail.js'
141
+
142
+ export default controller({
143
+ async new() {
144
+ return this.render('Auth/Register', {})
145
+ },
146
+ async create() {
147
+ const user = await RegisterUser.call(this.params(RegisterUser.form))
148
+ await SendWelcomeMail.later(user.id) // 잡 발행 위치는 결정 32 — 서비스 afterCommit 도 정합
149
+ return this.redirect('/dashboard')
150
+ },
151
+ })
152
+ ```
153
+
154
+ ## 알려진 함정
155
+
156
+ - **render/JSON/redirect 를 한 액션에서 조건 혼용하면 doctor
157
+ response-mixing 위반** — 액션을 나눈다.
158
+ - **`fetch()` 로 로그인 폼 구현 금지** — 세션 앱 폼은 `Inertia.post()`.
159
+ REST + fetch 는 API 앱(JWT) 전용.
160
+ - **컨트롤러에 비즈니스 로직 인라인 금지** (§5.3 One Way) — 여러 모델·
161
+ 트랜잭션·외부 API 가 얽히면 `domain/services/`.
162
+ - **컨트롤러에서 메일·외부 발송 직접 호출 금지** — 잡 발행만
163
+ (`agents/async.md`). nodemailer·resend 등 SDK 직접 import 는 함정.
164
+ - **bcrypt·crypto 직접 import 금지** — `hashPassword`/`verifyPassword`.
165
+ - **`@gaonjs/*` 스코프 직접 import 금지** — 파사드 `gaonjs/*` 만.
166
+ - **bigint PK 를 render props 로 흘릴 때는 `String(p.id)` 정규화**
167
+ (결정 37 · 상세는 `agents/frontend.md`).
168
+
169
+ ## 관련 결정 번호
170
+
171
+ | 결정 | 내용 |
172
+ |---|---|
173
+ | 결정 23 (E-3) | 앱 내 JSON 액션 — 반환값 = 응답 |
174
+ | 결정 24 (E-3 §5) | `this.params` 안전 규칙 (라우트 > body > query · 중복 키 · body/query 탈출구) |
175
+ | 결정 32 | 잡 발행 위치 자유 (컨트롤러·서비스·리스너 — `agents/async.md`) |
176
+ | 결정 37 | bigint PK 컨트롤러 `String()` 정규화 (`agents/frontend.md`) |
177
+ | E-1 | 파사드 = `gaonjs` · CLI = `gaon` |
@@ -0,0 +1,18 @@
1
+ <!doctype html>
2
+ <html lang="ko">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>{{PROJECT_NAME}}</title>
7
+ </head>
8
+ <body>
9
+ <!--
10
+ Vite 개발 서버가 이 index.html 을 서빙한다(dev). 운영 빌드는
11
+ vite build 가 이 파일을 진입점 삼아 프로덕션 번들을 만든다.
12
+ Fastify(gaonjs/web) 는 초기 SPA 응답 셸에서 아래와 같은 구조의
13
+ <div id="app" data-page="..."> 를 내려보낸다 — 개발과 운영이 같은 셸.
14
+ -->
15
+ <div id="app"></div>
16
+ <script type="module" src="/main.ts"></script>
17
+ </body>
18
+ </html>
@@ -0,0 +1,24 @@
1
+ // apps/web/main.ts — 프론트엔드 진입 (v0.16 §6.4 · M3 최소 웹 레이어).
2
+ //
3
+ // 브라우저 부팅 흐름:
4
+ // 1) index.html 이 이 파일을 <script type="module"> 로 로드한다.
5
+ // 2) createGaonApp 이 Inertia SPA 를 마운트한다.
6
+ // 3) 서버 응답의 data-page 를 어댑터가 읽어 첫 페이지를 그린다.
7
+ //
8
+ // 자동 import 금지(E-5 §2.4) — 페이지·레이아웃 지도는 아래처럼 명시적으로
9
+ // import.meta.glob 으로 만든다. 심볼 출처가 코드에 그대로 보인다.
10
+ import { createGaonApp } from 'gaonjs/vue'
11
+
12
+ // 페이지는 지연 로드(코드 스플리팅) — 큰 앱에서도 첫 페이지 로딩이 빠르다.
13
+ // eager 로 바꿔도 되지만, One Way 의 기본은 지연 로드다.
14
+ const pages = import.meta.glob('./pages/**/*.vue')
15
+
16
+ // 레이아웃은 eager — 소수·최초 진입에도 필요하므로 굳이 지연 로드하지 않는다.
17
+ // layouts/Default.vue 가 있으면 모든 페이지에 자동 적용된다(errata E-5 §2.3).
18
+ const layouts = import.meta.glob('./layouts/*.vue', { eager: true })
19
+
20
+ void createGaonApp({
21
+ pages,
22
+ layouts,
23
+ title: (t) => (t ? `${t} · {{PROJECT_NAME}}` : '{{PROJECT_NAME}}'),
24
+ })
@@ -16,11 +16,14 @@
16
16
  "test": "vitest run"
17
17
  },
18
18
  "dependencies": {
19
- "gaonjs": "{{GAONJS_VERSION}}"
19
+ "gaonjs": "{{GAONJS_VERSION}}",
20
+ "@inertiajs/vue3": "^3.6.0",
21
+ "vue": "^3.5.0"
20
22
  },
21
23
  "devDependencies": {
24
+ "@vitejs/plugin-vue": "^6.0.0",
22
25
  "typescript": "^5.9.0",
23
- "vue": "^3.5.0",
26
+ "vite": "^7.0.0",
24
27
  "vue-tsc": "^3.3.0",
25
28
  "vitest": "^3.0.0"
26
29
  }
@@ -0,0 +1,23 @@
1
+ // vite.config.ts — 프론트엔드 빌드/개발 서버 설정 (v0.16 §6.4).
2
+ //
3
+ // The One Way: gaon dev 가 Vite 를 middlewareMode 로 붙여 Fastify 한 포트로
4
+ // 서빙한다(§CLAUDE.md 6 · 이 파일의 server 옵션은 그때 재정의된다). vite
5
+ // build 는 이 파일을 그대로 사용한다.
6
+ //
7
+ // 앱이 하나 이상이면 각 앱마다 vite.config.ts 를 두는 것이 아니라, 이 루트
8
+ // 파일 하나가 root 를 apps/<앱> 으로 잡고 여러 번 실행된다(gaon dev 가 앱별
9
+ // Vite 서버를 띄운다). 관례가 곧 배치.
10
+ import { defineConfig } from 'vite'
11
+ import vue from '@vitejs/plugin-vue'
12
+
13
+ export default defineConfig({
14
+ // 기본 앱은 apps/web · gaon dev 가 다른 앱에 대해 root 를 재정의한다.
15
+ root: 'apps/web',
16
+ plugins: [vue()],
17
+ build: {
18
+ outDir: '../../dist/web',
19
+ emptyOutDir: true,
20
+ // Inertia SPA 는 index.html 하나가 진입 · 라우팅은 서버 몫이다.
21
+ rollupOptions: {},
22
+ },
23
+ })
package/dist/tsResolve.js CHANGED
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * 생성기(tables·routes)는 사용자 스키마·컨트롤러 .ts 를 런타임 동적
5
5
  * import 해 구조를 읽는다. TS-for-ESM 관례상 상대 import 는 `.js` 로
6
- * 쓰지만(`import { Post } from '../models/post.js'`), 개발 중 소스는
6
+ * 쓰지만(`import { Post } from '../models/Post.js'`), 개발 중 소스는
7
7
  * `.ts` 다 — Node 는 `.js`→`.ts` 재매핑을 하지 않아 그대로면 모듈을
8
8
  * 못 찾는다(vitest 는 Vite 가 재작성해 문제 없음, 실 gaon dev/check 만
9
9
  * 해당). 이 훅이 `.js` 가 없고 형제 `.ts` 가 있으면 `.ts` 로 넘긴다.
package/dist/work.d.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  export interface WorkCommandOptions {
2
2
  readonly json?: boolean;
3
- /** NATS 접속지. 생략 시 GAON_NATS_URL, 그다음 기본(4222). */
3
+ /** NATS 접속지. 생략 시 NATS_URL(그다음 하위호환 GAON_NATS_URL), 기본(4222). */
4
4
  readonly natsUrl?: string;
5
5
  /** 도메인 루트(domain/ 의 부모). 기본 cwd. */
6
6
  readonly root?: string;
7
- /** 아웃박스 릴레이용 DB URL. 생략 시 GAON_DATABASE_URL, 없으면 릴레이 미기동. */
7
+ /** 아웃박스 릴레이용 DB URL. 생략 시 DATABASE_URL(그다음 GAON_DATABASE_URL), 없으면 릴레이 미기동. */
8
8
  readonly databaseUrl?: string;
9
9
  /** 큐 기본 동시성. */
10
10
  readonly concurrency?: number;
package/dist/work.js CHANGED
@@ -68,7 +68,9 @@ export async function runWorkCommand(opts = {}) {
68
68
  };
69
69
  const nats = await connectNats({ servers: opts.natsUrl, name: `work@${id}` });
70
70
  let db;
71
- const dbUrl = opts.databaseUrl ?? process.env.GAON_DATABASE_URL;
71
+ // 스캐폴드 .env 관례(DATABASE_URL)를 정본으로 · GAON_DATABASE_URL 은 하위호환.
72
+ // serve(config 경유 DATABASE_URL)와 work 가 같은 env 를 보게 한다.
73
+ const dbUrl = opts.databaseUrl ?? process.env.DATABASE_URL ?? process.env.GAON_DATABASE_URL;
72
74
  if (dbUrl)
73
75
  db = createDb(dbConfigFromUrl(dbUrl));
74
76
  const domain = await loadDomain(root);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gaonjs/cli",
3
- "version": "0.5.0",
3
+ "version": "0.10.1",
4
4
  "description": "Gaon CLI 구현: 제너레이터·스캐폴딩·로드맵 출력 (M1 스텁)",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -23,16 +23,18 @@
23
23
  "dist",
24
24
  "README.md"
25
25
  ],
26
- "dependencies": {
27
- "typescript": "^5.9.0",
28
- "@gaonjs/async": "0.2.2",
29
- "@gaonjs/config": "0.1.0",
30
- "@gaonjs/core": "0.1.4",
31
- "@gaonjs/mail": "0.1.0",
32
- "@gaonjs/web": "0.3.0",
33
- "@gaonjs/data": "0.3.0"
34
- },
35
26
  "scripts": {
36
27
  "build": "node ../../node_modules/typescript/bin/tsc -p tsconfig.json && node -e \"require('fs').cpSync('src/templates','dist/templates',{recursive:true})\""
28
+ },
29
+ "dependencies": {
30
+ "@gaonjs/async": "workspace:*",
31
+ "@gaonjs/config": "workspace:*",
32
+ "@gaonjs/core": "workspace:*",
33
+ "@gaonjs/data": "workspace:*",
34
+ "@gaonjs/web": "workspace:*",
35
+ "@gaonjs/mail": "workspace:*",
36
+ "@modelcontextprotocol/sdk": "^1.29.0",
37
+ "typescript": "^5.9.0",
38
+ "vite": "^7.0.0"
37
39
  }
38
- }
40
+ }
@@ -1,12 +0,0 @@
1
- import { type RowOf } from '@gaonjs/data';
2
- export declare const m9dWidgets: import("@gaonjs/data").TableDef<"m9d_widgets", {
3
- id: import("@gaonjs/data").BigintCol<{
4
- hasDefault: true;
5
- }>;
6
- name: import("@gaonjs/data").StringCol<{}>;
7
- }>;
8
- declare module '@gaonjs/data' {
9
- interface GaonTables {
10
- m9d_widgets: RowOf<typeof m9dWidgets>;
11
- }
12
- }
@@ -1,7 +0,0 @@
1
- // db 통합 테스트 픽스처 — main 커넥션의 유일한 테이블.
2
- // 이름은 m9d_widgets (M8 seed 픽스처의 widgets 와 GaonTables 병합 충돌 회피).
3
- import { table, t } from '@gaonjs/data';
4
- export const m9dWidgets = table('m9d_widgets', {
5
- id: t.id(),
6
- name: t.string().max(30),
7
- });
@@ -1,2 +0,0 @@
1
- declare const _default: import("@gaonjs/config").GaonConfig;
2
- export default _default;
@@ -1,11 +0,0 @@
1
- // db 통합 테스트 픽스처 — main 커넥션만 있는 최소 gaon.config.ts.
2
- // url 은 GAON_TEST_PG_URL 우선 · 로컬 default 는 packages/data/compose.yaml 정합.
3
- import { defineConfig } from '@gaonjs/config';
4
- export default defineConfig({
5
- db: {
6
- main: {
7
- adapter: 'postgres',
8
- url: process.env.GAON_TEST_PG_URL ?? 'postgres://gaon:gaon@127.0.0.1:5433/gaon_test',
9
- },
10
- },
11
- });
package/dist/check.d.ts DELETED
@@ -1,29 +0,0 @@
1
- import { type DevLayout } from './dev.js';
2
- export interface TypecheckResult {
3
- readonly ok: boolean;
4
- readonly output: string;
5
- /** 실행한 체커 이름(vue-tsc | tsc | none). */
6
- readonly checker: string;
7
- }
8
- export interface CheckDeps {
9
- readonly layout: DevLayout;
10
- regenerateTables(schemaDir: string, out: string): Promise<unknown>;
11
- regenerateRoutes(appDir: string, out: string): Promise<unknown>;
12
- typecheck(): Promise<TypecheckResult>;
13
- }
14
- export interface CheckResult {
15
- readonly ok: boolean;
16
- readonly regenerated: {
17
- readonly tables: boolean;
18
- readonly routes: string[];
19
- };
20
- readonly typecheck: TypecheckResult;
21
- }
22
- /** .gaon 재생성 → 타입 체크. 재생성이 검사보다 먼저다(§13.4-5). */
23
- export declare function runCheck(deps: CheckDeps): Promise<CheckResult>;
24
- export interface CheckCommandOptions {
25
- readonly cwd?: string;
26
- readonly json?: boolean;
27
- }
28
- /** `gaon check` 진입점. */
29
- export declare function runCheckCommand(opts?: CheckCommandOptions): Promise<number>;
package/dist/check.js DELETED
@@ -1,92 +0,0 @@
1
- /**
2
- * @gaonjs/cli · `gaon check` — 검사 전 .gaon 재생성 (M3, §13.4-5)
3
- *
4
- * 개발 서버(gaon dev)가 떠 있지 않은 CI·AI 에이전트 환경에서도 타입
5
- * 체인이 항상 최신으로 검증되게, 타입 검사 **전에** .gaon(tables·routes)
6
- * 을 먼저 재생성한 뒤 tsc/vue-tsc 를 돌린다.
7
- *
8
- * 오케스트레이션(runCheck)은 주입으로 순수하게 유지하고, 진입점이 cwd
9
- * 관례로 실제 재생성기·타입 체커를 엮는다(dev.ts 와 동일 패턴).
10
- */
11
- import { existsSync } from 'node:fs';
12
- import { join } from 'node:path';
13
- import { spawn } from 'node:child_process';
14
- import { generateTablesDts } from '@gaonjs/data';
15
- import { generateRoutesDts } from '@gaonjs/web';
16
- import { resolveDevLayout } from './dev.js';
17
- import { registerTsResolve } from './tsResolve.js';
18
- /** .gaon 재생성 → 타입 체크. 재생성이 검사보다 먼저다(§13.4-5). */
19
- export async function runCheck(deps) {
20
- const { layout } = deps;
21
- const routes = [];
22
- if (layout.schemaDir)
23
- await deps.regenerateTables(layout.schemaDir, layout.tablesOut);
24
- for (const app of layout.apps) {
25
- await deps.regenerateRoutes(app.appDir, app.routesOut);
26
- routes.push(app.name);
27
- }
28
- const typecheck = await deps.typecheck();
29
- return {
30
- ok: typecheck.ok,
31
- regenerated: { tables: !!layout.schemaDir, routes },
32
- typecheck,
33
- };
34
- }
35
- /** 프로젝트에 맞는 타입 체커를 골라 실행한다. .vue 가 있으면 vue-tsc. */
36
- function makeProjectTypecheck(cwd) {
37
- return () => new Promise((resolve) => {
38
- const tsconfig = join(cwd, 'tsconfig.json');
39
- if (!existsSync(tsconfig)) {
40
- resolve({
41
- ok: false,
42
- checker: 'none',
43
- output: `[gaon check] tsconfig.json 이 없습니다: ${tsconfig}\n` +
44
- `→ 프로젝트 루트에 tsconfig.json 을 만들고 다시 실행하세요.`,
45
- });
46
- return;
47
- }
48
- const vueTsc = join(cwd, 'node_modules', '.bin', 'vue-tsc');
49
- const useVue = existsSync(vueTsc);
50
- const checker = useVue ? 'vue-tsc' : 'tsc';
51
- const cmd = useVue ? vueTsc : process.execPath;
52
- const args = useVue
53
- ? ['--noEmit', '-p', tsconfig]
54
- : [join(cwd, 'node_modules', 'typescript', 'bin', 'tsc'), '--noEmit', '-p', tsconfig];
55
- const child = spawn(cmd, args, { cwd });
56
- let output = '';
57
- child.stdout.on('data', (b) => (output += b.toString()));
58
- child.stderr.on('data', (b) => (output += b.toString()));
59
- child.on('error', (e) => resolve({ ok: false, checker, output: String(e) }));
60
- child.on('close', (code) => resolve({ ok: code === 0, checker, output }));
61
- });
62
- }
63
- /** `gaon check` 진입점. */
64
- export async function runCheckCommand(opts = {}) {
65
- const cwd = opts.cwd ?? process.cwd();
66
- registerTsResolve(); // 사용자 .ts 의 `.js` 상대 import 를 런타임에 해석
67
- const deps = {
68
- layout: resolveDevLayout(cwd),
69
- regenerateTables: generateTablesDts,
70
- regenerateRoutes: generateRoutesDts,
71
- typecheck: makeProjectTypecheck(cwd),
72
- };
73
- const result = await runCheck(deps);
74
- if (opts.json) {
75
- process.stdout.write(JSON.stringify(result) + '\n');
76
- }
77
- else {
78
- const r = result.regenerated;
79
- const gen = [r.tables ? 'tables.d.ts' : null, ...r.routes.map((a) => `${a}/routes.d.ts`)]
80
- .filter(Boolean)
81
- .join(', ');
82
- process.stdout.write(` 재생성: ${gen || '(대상 없음)'}\n`);
83
- if (result.ok) {
84
- process.stdout.write(` ✓ 타입 검사 통과 (${result.typecheck.checker})\n`);
85
- }
86
- else {
87
- process.stdout.write(` ✗ 타입 검사 실패 (${result.typecheck.checker})\n`);
88
- process.stdout.write(result.typecheck.output);
89
- }
90
- }
91
- return result.ok ? 0 : 1;
92
- }