@gaonjs/cli 0.2.0 → 0.4.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/__fixtures__/db-minimal/domain/schema/widgets.d.ts +12 -0
- package/dist/__fixtures__/db-minimal/domain/schema/widgets.js +7 -0
- package/dist/__fixtures__/db-minimal/gaon.config.d.ts +2 -0
- package/dist/__fixtures__/db-minimal/gaon.config.js +11 -0
- package/dist/commands/db.d.ts +20 -0
- package/dist/commands/db.js +74 -0
- package/dist/commands/dev.d.ts +68 -0
- package/dist/commands/dev.js +287 -0
- package/dist/commands/g.d.ts +26 -0
- package/dist/commands/g.js +124 -0
- package/dist/db/diff.d.ts +17 -0
- package/dist/db/diff.js +57 -0
- package/dist/db/index.d.ts +4 -0
- package/dist/db/index.js +8 -0
- package/dist/db/migrate.d.ts +16 -0
- package/dist/db/migrate.js +173 -0
- package/dist/db/reset.d.ts +18 -0
- package/dist/db/reset.js +150 -0
- package/dist/db/resolve.d.ts +32 -0
- package/dist/db/resolve.js +130 -0
- package/dist/dev/console.d.ts +39 -0
- package/dist/dev/console.js +100 -0
- package/dist/dev/docker.d.ts +52 -0
- package/dist/dev/docker.js +163 -0
- package/dist/dev/index.d.ts +14 -0
- package/dist/dev/index.js +10 -0
- package/dist/dev/tsc.d.ts +41 -0
- package/dist/dev/tsc.js +127 -0
- package/dist/dev/watcher.d.ts +50 -0
- package/dist/dev/watcher.js +95 -0
- package/dist/dev.d.ts +1 -16
- package/dist/dev.js +10 -66
- package/dist/doctor/connections.d.ts +14 -0
- package/dist/doctor/connections.js +168 -0
- package/dist/doctor/dependency-direction.d.ts +11 -0
- package/dist/doctor/dependency-direction.js +186 -0
- package/dist/doctor/migration-diff.d.ts +11 -0
- package/dist/doctor/migration-diff.js +109 -0
- package/dist/doctor/n-plus-one.d.ts +5 -0
- package/dist/doctor/n-plus-one.js +242 -0
- package/dist/doctor/reporter.d.ts +5 -0
- package/dist/doctor/reporter.js +41 -0
- package/dist/doctor/response-mixing.d.ts +12 -0
- package/dist/doctor/response-mixing.js +158 -0
- package/dist/doctor/setup.d.ts +26 -0
- package/dist/doctor/setup.js +52 -0
- package/dist/doctor/types.d.ts +37 -0
- package/dist/doctor/types.js +34 -0
- package/dist/doctor.d.ts +40 -23
- package/dist/doctor.js +131 -202
- package/dist/index.d.ts +14 -2
- package/dist/index.js +171 -28
- package/dist/scaffold/controller.d.ts +12 -0
- package/dist/scaffold/controller.js +50 -0
- package/dist/scaffold/index.d.ts +20 -0
- package/dist/scaffold/index.js +41 -0
- package/dist/scaffold/inflect.d.ts +19 -0
- package/dist/scaffold/inflect.js +50 -0
- package/dist/scaffold/job.d.ts +3 -0
- package/dist/scaffold/job.js +46 -0
- package/dist/scaffold/model.d.ts +8 -0
- package/dist/scaffold/model.js +66 -0
- package/dist/scaffold/page.d.ts +7 -0
- package/dist/scaffold/page.js +46 -0
- package/dist/serve.d.ts +18 -0
- package/dist/serve.js +79 -0
- package/dist/templates/auth/auth.wiring.ts.tpl +1 -1
- package/dist/templates/auth/session.controller.ts.tpl +1 -1
- package/package.json +4 -3
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// @gaonjs/cli · scaffold · page (M9-B)
|
|
2
|
+
//
|
|
3
|
+
// `gaon g page <Path>/<Name>` — Vue 페이지(Inertia SPA · §6).
|
|
4
|
+
// pageProps<K>() 로 컨트롤러 render 결과 타입을 그대로 이어받는다 —
|
|
5
|
+
// 스키마→모델→컨트롤러→(생성된 라우트 지도)→페이지 체인의 마지막 고리(§6.2).
|
|
6
|
+
//
|
|
7
|
+
// 입력 예: `gaon g page Posts/Index` → apps/<app>/pages/Posts/Index.vue.
|
|
8
|
+
// 라우트 키는 소문자·리소스 관례로 `posts#index` 를 기본값으로 넣는다 —
|
|
9
|
+
// 사용자가 라우트를 다르게 걸었으면 수동 수정하면 된다(주석에 안내).
|
|
10
|
+
import { toCamel } from './inflect.js';
|
|
11
|
+
/**
|
|
12
|
+
* 페이지 경로에서 스캐폴드를 만든다.
|
|
13
|
+
* @param pagePath 예: 'Posts/Index' · 'Dashboard' · 'admin/Users/Show'.
|
|
14
|
+
* @param app 대상 앱.
|
|
15
|
+
*/
|
|
16
|
+
export function pageScaffold(pagePath, app) {
|
|
17
|
+
const trimmed = pagePath.trim().replace(/^\/+|\/+$/g, '');
|
|
18
|
+
if (!trimmed) {
|
|
19
|
+
throw new Error(`[gaon g page] 페이지 경로가 비어 있습니다.\n` +
|
|
20
|
+
` → 예: gaon g page Posts/Index (apps/${app}/pages/Posts/Index.vue 생성)`);
|
|
21
|
+
}
|
|
22
|
+
const segments = trimmed.split('/').filter(Boolean);
|
|
23
|
+
const last = segments[segments.length - 1];
|
|
24
|
+
const parent = segments[segments.length - 2] ?? last;
|
|
25
|
+
// 라우트 키 기본값 — 폴더는 리소스명(복수·소문자), 파일은 액션명(소문자).
|
|
26
|
+
const routeKey = `${toCamel(parent).toLowerCase()}#${toCamel(last).toLowerCase()}`;
|
|
27
|
+
const filePath = `apps/${app}/pages/${trimmed}.vue`;
|
|
28
|
+
const lines = [
|
|
29
|
+
`<script setup lang="ts">`,
|
|
30
|
+
`import { pageProps } from 'gaonjs/vue'`,
|
|
31
|
+
``,
|
|
32
|
+
`// 컨트롤러 ${routeKey} 의 render props 타입이 그대로 흐른다(§6.2).`,
|
|
33
|
+
`// 라우트 키가 다르면 이 리터럴을 바꾼다 — .gaon/routes.d.ts 가 유효한 키를 알려준다.`,
|
|
34
|
+
`const props = pageProps<'${routeKey}'>()`,
|
|
35
|
+
`</script>`,
|
|
36
|
+
``,
|
|
37
|
+
`<template>`,
|
|
38
|
+
` <div>`,
|
|
39
|
+
` <h1>${last}</h1>`,
|
|
40
|
+
` <pre>{{ props }}</pre>`,
|
|
41
|
+
` </div>`,
|
|
42
|
+
`</template>`,
|
|
43
|
+
``,
|
|
44
|
+
];
|
|
45
|
+
return { path: filePath, contents: lines.join('\n') };
|
|
46
|
+
}
|
package/dist/serve.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface ServeCommandOptions {
|
|
2
|
+
readonly cwd?: string;
|
|
3
|
+
readonly json?: boolean;
|
|
4
|
+
/** 리슨 포트. 우선순위: 옵션 > config.web.port > env PORT > 3000. */
|
|
5
|
+
readonly port?: number;
|
|
6
|
+
/** 리슨 호스트. 우선순위: 옵션 > config.web.host > '0.0.0.0'. */
|
|
7
|
+
readonly host?: string;
|
|
8
|
+
/** 프로세스 시그널(테스트 주입). 기본 process. */
|
|
9
|
+
readonly signals?: {
|
|
10
|
+
on(sig: 'SIGINT' | 'SIGTERM', fn: () => void): void;
|
|
11
|
+
off(sig: 'SIGINT' | 'SIGTERM', fn: () => void): void;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* `gaon serve` 진입점. loadDotEnv → loadGaonConfig → wireGaon → listen →
|
|
16
|
+
* SIGINT 대기 → graceful close. 예외는 stderr + exit 1.
|
|
17
|
+
*/
|
|
18
|
+
export declare function runServeCommand(opts?: ServeCommandOptions): Promise<void>;
|
package/dist/serve.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @gaonjs/cli · `gaon serve` — 웹 서버 부팅 (§7 · M9-A)
|
|
3
|
+
*
|
|
4
|
+
* 운영 프로세스 3종 중 하나(serve · work · hub). cwd 관례로 gaon.config.ts
|
|
5
|
+
* 를 로드하고, apps/* 를 스캔해 자동 배선한 뒤 Fastify 를 listen 한다.
|
|
6
|
+
* SIGINT/SIGTERM 에 graceful 종료(Fastify → NATS → Redis → DB 순).
|
|
7
|
+
*
|
|
8
|
+
* env 를 먼저 로드한다 — .env 의 값이 gaon.config.ts 안의 env('KEY') 에
|
|
9
|
+
* 들어갈 수 있어야 하기 때문.
|
|
10
|
+
*
|
|
11
|
+
* 워커 수(--workers, WEB_CONCURRENCY)는 v0.8 정본이지만 컨테이너 안 기본
|
|
12
|
+
* 1워커라 M9-A 는 단일 프로세스만 다룬다. node:cluster 통합은 후속(M9-C).
|
|
13
|
+
*/
|
|
14
|
+
import { loadDotEnv } from '@gaonjs/core';
|
|
15
|
+
import { loadGaonConfig, wireGaon, findConfigPath } from '@gaonjs/config';
|
|
16
|
+
import { registerTsResolve } from './tsResolve.js';
|
|
17
|
+
function humanEvent(e) {
|
|
18
|
+
switch (e.kind) {
|
|
19
|
+
case 'starting': {
|
|
20
|
+
const cfg = e.configPath ? `config=${e.configPath}` : 'config=(없음 · 기본값)';
|
|
21
|
+
const apps = e.apps.length > 0 ? `apps=[${e.apps.join(', ')}]` : 'apps=(없음)';
|
|
22
|
+
return ` gaon serve · 시작 — ${cfg} · ${apps}`;
|
|
23
|
+
}
|
|
24
|
+
case 'listening':
|
|
25
|
+
return ` ▶ 리슨 중 — ${e.url} (Ctrl+C 로 종료)`;
|
|
26
|
+
case 'stopping':
|
|
27
|
+
return ' gaon serve · 종료 중 (graceful) ...';
|
|
28
|
+
case 'stopped':
|
|
29
|
+
return ' gaon serve · 종료';
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* `gaon serve` 진입점. loadDotEnv → loadGaonConfig → wireGaon → listen →
|
|
34
|
+
* SIGINT 대기 → graceful close. 예외는 stderr + exit 1.
|
|
35
|
+
*/
|
|
36
|
+
export async function runServeCommand(opts = {}) {
|
|
37
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
38
|
+
const json = opts.json ?? false;
|
|
39
|
+
const signals = opts.signals ?? process;
|
|
40
|
+
// .ts 상대 import 해석기 등록 (사용자 gaon.config.ts, apps/* 로드에 필요).
|
|
41
|
+
registerTsResolve();
|
|
42
|
+
loadDotEnv(cwd);
|
|
43
|
+
const emit = (e) => {
|
|
44
|
+
if (json)
|
|
45
|
+
process.stdout.write(JSON.stringify(e) + '\n');
|
|
46
|
+
else
|
|
47
|
+
process.stdout.write(humanEvent(e) + '\n');
|
|
48
|
+
};
|
|
49
|
+
const configPath = findConfigPath(cwd);
|
|
50
|
+
const config = await loadGaonConfig(cwd);
|
|
51
|
+
const wired = await wireGaon(config, cwd);
|
|
52
|
+
emit({ kind: 'starting', configPath, apps: wired.apps.map((a) => a.name) });
|
|
53
|
+
const host = opts.host ?? config.web?.host ?? '0.0.0.0';
|
|
54
|
+
const port = opts.port ??
|
|
55
|
+
config.web?.port ??
|
|
56
|
+
(process.env.PORT ? Number(process.env.PORT) : 3000);
|
|
57
|
+
await wired.app.listen({ host, port });
|
|
58
|
+
const displayHost = host === '0.0.0.0' ? 'localhost' : host;
|
|
59
|
+
emit({ kind: 'listening', host, port, url: `http://${displayHost}:${port}` });
|
|
60
|
+
await new Promise((resolvePromise) => {
|
|
61
|
+
const stop = () => {
|
|
62
|
+
signals.off('SIGINT', stop);
|
|
63
|
+
signals.off('SIGTERM', stop);
|
|
64
|
+
emit({ kind: 'stopping' });
|
|
65
|
+
void wired
|
|
66
|
+
.close()
|
|
67
|
+
.catch((err) => {
|
|
68
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
69
|
+
process.stderr.write(` ✗ 종료 중 오류: ${msg}\n`);
|
|
70
|
+
})
|
|
71
|
+
.finally(() => {
|
|
72
|
+
emit({ kind: 'stopped' });
|
|
73
|
+
resolvePromise();
|
|
74
|
+
});
|
|
75
|
+
};
|
|
76
|
+
signals.on('SIGINT', stop);
|
|
77
|
+
signals.on('SIGTERM', stop);
|
|
78
|
+
});
|
|
79
|
+
}
|
|
@@ -4,7 +4,7 @@ import { User } from '../../domain/models/user.js'
|
|
|
4
4
|
|
|
5
5
|
// 세션에 심긴 userId 로 사용자를 로드한다(§7). web 은 도메인을 loadUser 로 받는다.
|
|
6
6
|
export const loadUser: AuthOptions['loadUser'] = async (id) =>
|
|
7
|
-
await User.
|
|
7
|
+
await User.where('id', '=', BigInt(String(id))).first()
|
|
8
8
|
|
|
9
9
|
// this.currentUser 에 User 필드 타입을 얹는다(GaonRouteMap 과 동일 관례).
|
|
10
10
|
declare module 'gaonjs/web' {
|
|
@@ -10,7 +10,7 @@ export default controller({
|
|
|
10
10
|
// POST /session — 로그인
|
|
11
11
|
async create() {
|
|
12
12
|
const { email, password } = this.params({ _row: {} as { email: string; password: string } })
|
|
13
|
-
const user = await User.
|
|
13
|
+
const user = await User.where('email', '=', email).first()
|
|
14
14
|
// passwordDigest 는 hidden 이지만 서버 코드에서는 투명하게 읽힌다(§4.2).
|
|
15
15
|
if (user && (await verifyPassword(password, user.passwordDigest))) {
|
|
16
16
|
this.auth.login(user)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gaonjs/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Gaon CLI 구현: 제너레이터·스캐폴딩·로드맵 출력 (M1 스텁)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -24,11 +24,12 @@
|
|
|
24
24
|
"README.md"
|
|
25
25
|
],
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"typescript": "
|
|
27
|
+
"typescript": "^5.9.0",
|
|
28
28
|
"@gaonjs/async": "0.2.2",
|
|
29
|
+
"@gaonjs/config": "0.1.0",
|
|
29
30
|
"@gaonjs/core": "0.1.4",
|
|
30
|
-
"@gaonjs/data": "0.2.3",
|
|
31
31
|
"@gaonjs/mail": "0.1.0",
|
|
32
|
+
"@gaonjs/data": "0.3.0",
|
|
32
33
|
"@gaonjs/web": "0.3.0"
|
|
33
34
|
},
|
|
34
35
|
"scripts": {
|