@gaonjs/cli 0.4.0 → 0.10.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 +50 -0
- package/dist/commands/check.js +286 -0
- package/dist/commands/console.d.ts +46 -0
- package/dist/commands/console.js +129 -0
- package/dist/commands/db.d.ts +3 -1
- package/dist/commands/db.js +8 -2
- package/dist/commands/g.d.ts +1 -1
- package/dist/commands/g.js +27 -3
- package/dist/commands/mcp.d.ts +15 -0
- package/dist/commands/mcp.js +78 -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/db/diff.js +5 -0
- package/dist/db/journal.d.ts +34 -0
- package/dist/db/journal.js +71 -0
- package/dist/db/migrate.d.ts +6 -1
- package/dist/db/migrate.js +120 -102
- package/dist/db/replay.d.ts +49 -0
- package/dist/db/replay.js +148 -0
- package/dist/db/status.d.ts +12 -0
- package/dist/db/status.js +61 -0
- package/dist/dev/index.d.ts +2 -0
- package/dist/dev/index.js +2 -0
- package/dist/dev/vite.d.ts +67 -0
- package/dist/dev/vite.js +126 -0
- package/dist/dev.d.ts +18 -0
- package/dist/dev.js +15 -0
- package/dist/doctor/agents-doc-index.d.ts +4 -0
- package/dist/doctor/agents-doc-index.js +80 -0
- package/dist/doctor/fixers/dependency-direction.d.ts +9 -0
- package/dist/doctor/fixers/dependency-direction.js +98 -0
- package/dist/doctor/fixers/index.d.ts +15 -0
- package/dist/doctor/fixers/index.js +66 -0
- package/dist/doctor/fixers/schema-filename.d.ts +14 -0
- package/dist/doctor/fixers/schema-filename.js +104 -0
- package/dist/doctor/fixers/types.d.ts +59 -0
- package/dist/doctor/fixers/types.js +15 -0
- package/dist/doctor/no-auto-import.d.ts +10 -0
- package/dist/doctor/no-auto-import.js +158 -0
- package/dist/doctor/schema-filename.d.ts +6 -0
- package/dist/doctor/schema-filename.js +81 -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 +51 -0
- package/dist/doctor.js +191 -7
- package/dist/generate.js +2 -2
- package/dist/hub.d.ts +1 -1
- package/dist/index.d.ts +6 -2
- package/dist/index.js +154 -16
- package/dist/mcp/index.d.ts +7 -0
- package/dist/mcp/index.js +7 -0
- package/dist/mcp/server.d.ts +50 -0
- package/dist/mcp/server.js +102 -0
- package/dist/mcp/tools.d.ts +109 -0
- package/dist/mcp/tools.js +485 -0
- package/dist/scaffold/app.d.ts +5 -0
- package/dist/scaffold/app.js +172 -0
- package/dist/scaffold/controller.js +2 -2
- package/dist/scaffold/index.d.ts +2 -1
- package/dist/scaffold/index.js +2 -1
- package/dist/scaffold/job.d.ts +5 -0
- package/dist/scaffold/job.js +35 -0
- package/dist/scaffold/model.js +8 -8
- package/dist/templates/auth/auth.wiring.ts.tpl +1 -1
- package/dist/templates/auth/registration.controller.ts.tpl +1 -1
- package/dist/templates/auth/session.controller.ts.tpl +1 -1
- package/dist/templates/auth/user.model.ts.tpl +1 -1
- 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/AGENTS.md.tpl +210 -0
- package/dist/templates/project/CLAUDE.md.tpl +119 -0
- package/dist/templates/project/agents/async.md.tpl +218 -0
- package/dist/templates/project/agents/data.md.tpl +532 -0
- package/dist/templates/project/agents/frontend.md.tpl +201 -0
- package/dist/templates/project/agents/realtime.md.tpl +157 -0
- package/dist/templates/project/agents/security.md.tpl +92 -0
- package/dist/templates/project/agents/testing.md.tpl +101 -0
- package/dist/templates/project/agents/web.md.tpl +177 -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/index.html.tpl +18 -0
- package/dist/templates/project/apps/web/layouts/Default.vue.tpl +43 -0
- package/dist/templates/project/apps/web/main.ts.tpl +24 -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 +30 -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/dist/templates/project/vite.config.ts.tpl +23 -0
- package/dist/tsResolve.js +1 -1
- package/dist/work.d.ts +2 -2
- package/dist/work.js +3 -1
- package/package.json +13 -11
- package/dist/__fixtures__/db-minimal/domain/schema/widgets.d.ts +0 -12
- package/dist/__fixtures__/db-minimal/domain/schema/widgets.js +0 -7
- package/dist/__fixtures__/db-minimal/gaon.config.d.ts +0 -2
- package/dist/__fixtures__/db-minimal/gaon.config.js +0 -11
- package/dist/check.d.ts +0 -29
- package/dist/check.js +0 -92
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export type CheckStep = 'typecheck' | 'vue-tsc' | 'build' | 'doctor';
|
|
2
|
+
export interface CheckCommandOptions {
|
|
3
|
+
readonly cwd?: string;
|
|
4
|
+
readonly json?: boolean;
|
|
5
|
+
/** 지정 시 그 검사 하나만 실행. 나머지는 스킵. */
|
|
6
|
+
readonly only?: CheckStep;
|
|
7
|
+
/** true 면 doctor 도 포함. 기본 false. */
|
|
8
|
+
readonly includeDoctor?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export type CheckStepStatus = 'passed' | 'failed' | 'skipped';
|
|
11
|
+
export interface CheckStepResult {
|
|
12
|
+
readonly step: CheckStep;
|
|
13
|
+
readonly status: CheckStepStatus;
|
|
14
|
+
/** 실행에 쓴 커맨드(디버깅용) — skipped 는 undefined. */
|
|
15
|
+
readonly command?: string;
|
|
16
|
+
/** 원본 exit code. skipped/doctor 는 undefined. */
|
|
17
|
+
readonly exitCode?: number;
|
|
18
|
+
/** stdout+stderr 합본(사람 UI 에 그대로 출력). skipped 는 짧은 사유. */
|
|
19
|
+
readonly output?: string;
|
|
20
|
+
}
|
|
21
|
+
export type RegenStatus = 'done' | 'skipped' | 'failed';
|
|
22
|
+
/**
|
|
23
|
+
* 검사 직전 .gaon 재생성 결과. 규칙 3(§13.4-5) — check 는 최신 타입 브리지
|
|
24
|
+
* 위에서 검증해야 한다. done = 재생성함 · skipped = 재생성할 대상 없음
|
|
25
|
+
* (domain/schema · apps/* 없음) · failed = 스키마/라우트 구문 오류로 재생성
|
|
26
|
+
* 실패(이 경우 검사를 진행하지 않고 실패로 종료).
|
|
27
|
+
*/
|
|
28
|
+
export interface CheckRegen {
|
|
29
|
+
readonly status: RegenStatus;
|
|
30
|
+
/** tables.d.ts 를 재생성했는가. */
|
|
31
|
+
readonly tables: boolean;
|
|
32
|
+
/** routes.d.ts 를 재생성한 앱 이름. */
|
|
33
|
+
readonly apps: readonly string[];
|
|
34
|
+
/** 실패 시 에러 + 수리 안내. done/skipped 는 undefined. */
|
|
35
|
+
readonly output?: string;
|
|
36
|
+
}
|
|
37
|
+
export interface CheckReport {
|
|
38
|
+
readonly ok: boolean;
|
|
39
|
+
/** 검사 직전 .gaon 재생성 단계(규칙 3). */
|
|
40
|
+
readonly regen: CheckRegen;
|
|
41
|
+
readonly steps: readonly CheckStepResult[];
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* `gaon check` 진입점. 검사 전에 .gaon 을 재생성(규칙 3)한 뒤 각 단계를
|
|
45
|
+
* 순서대로 실행하고, 하나라도 실패하면 exit 1. --only 지정 시 그 단계만
|
|
46
|
+
* 실행. --include-doctor 시 doctor 추가.
|
|
47
|
+
*
|
|
48
|
+
* §9 실 인프라 · 목업 X — 실 spawn 으로 검증한다.
|
|
49
|
+
*/
|
|
50
|
+
export declare function runCheckCommand(opts?: CheckCommandOptions): Promise<number>;
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @gaonjs/cli · `gaon check` — 통합 검증 (M9-G · v0.15 §13.5)
|
|
3
|
+
*
|
|
4
|
+
* 배포 · CI · AI 에이전트가 한 번에 신뢰할 수 있는 검사 묶음. 사용자의
|
|
5
|
+
* package.json 관례를 그대로 재사용한다 — `pnpm typecheck` · `pnpm vue-tsc`
|
|
6
|
+
* · `pnpm build`. 스크립트가 없으면 폴백으로 로컬 바이너리를 직접 부른다
|
|
7
|
+
* (`node_modules/.bin/tsc --noEmit` · `node_modules/.bin/vue-tsc --noEmit`
|
|
8
|
+
* · `pnpm -r build` → `npm run build`).
|
|
9
|
+
*
|
|
10
|
+
* The One Way — 하나의 명령이 3(선택 4) 검사를 순서대로 돌린다:
|
|
11
|
+
* 1) typecheck (pnpm typecheck 또는 tsc --noEmit)
|
|
12
|
+
* 2) vue-tsc (pnpm vue-tsc 또는 vue-tsc --noEmit)
|
|
13
|
+
* 3) build (pnpm build)
|
|
14
|
+
* (4) doctor (--include-doctor 시 · 코어 재사용)
|
|
15
|
+
*
|
|
16
|
+
* 옵션 최소:
|
|
17
|
+
* --json 자동화 · 각 검사의 passed/failed/skipped
|
|
18
|
+
* --only <name> 단일 검사만
|
|
19
|
+
* --include-doctor check 안에서 doctor 도 · 기본 off
|
|
20
|
+
*
|
|
21
|
+
* exit code: 검사가 하나라도 실패하면 1, 그 외 0. 미리 검사할 대상이
|
|
22
|
+
* 하나도 없으면(예: 스크립트도 없고 바이너리도 없음) 스킵으로 리포트 —
|
|
23
|
+
* 우회가 아니라 사용자에게 "설정이 부족하다" 를 명확히 노출.
|
|
24
|
+
*
|
|
25
|
+
* §9 실 인프라 — 서브 프로세스 실행은 실 `child_process.spawn` 을 쓴다.
|
|
26
|
+
* 목업 X.
|
|
27
|
+
*/
|
|
28
|
+
import { spawn } from 'node:child_process';
|
|
29
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
30
|
+
import { join } from 'node:path';
|
|
31
|
+
import { generateTablesDts } from '@gaonjs/data';
|
|
32
|
+
import { generateRoutesDts } from '@gaonjs/web';
|
|
33
|
+
import { runDoctorCommand } from '../doctor.js';
|
|
34
|
+
import { regenerateGaonOnce, resolveDevLayout } from '../dev.js';
|
|
35
|
+
import { registerTsResolve } from '../tsResolve.js';
|
|
36
|
+
/**
|
|
37
|
+
* 프로젝트 스크립트 존재 여부. pnpm/npm 어느 쪽이든 `scripts.<name>` 을
|
|
38
|
+
* 정의해 두면 우선 사용한다.
|
|
39
|
+
*/
|
|
40
|
+
function hasScript(cwd, name) {
|
|
41
|
+
const pkgPath = join(cwd, 'package.json');
|
|
42
|
+
if (!existsSync(pkgPath))
|
|
43
|
+
return false;
|
|
44
|
+
try {
|
|
45
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
|
|
46
|
+
return typeof pkg.scripts?.[name] === 'string' && pkg.scripts[name].length > 0;
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function binExists(cwd, name) {
|
|
53
|
+
return existsSync(join(cwd, 'node_modules', '.bin', name));
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* 단일 서브 프로세스를 spawn 해서 stdout+stderr 를 모으고 exit 코드를
|
|
57
|
+
* 돌려준다. 실행 실패(파일 없음)는 exit 127 로 매핑.
|
|
58
|
+
*/
|
|
59
|
+
function runSubprocess(cwd, cmd, args) {
|
|
60
|
+
return new Promise((resolvePromise) => {
|
|
61
|
+
const child = spawn(cmd, args, { cwd, env: process.env });
|
|
62
|
+
let output = '';
|
|
63
|
+
child.stdout.on('data', (b) => (output += b.toString('utf8')));
|
|
64
|
+
child.stderr.on('data', (b) => (output += b.toString('utf8')));
|
|
65
|
+
child.on('error', (err) => {
|
|
66
|
+
resolvePromise({ exitCode: 127, output: output + String(err) });
|
|
67
|
+
});
|
|
68
|
+
child.on('close', (code) => {
|
|
69
|
+
resolvePromise({ exitCode: code ?? 1, output });
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
/** typecheck · vue-tsc 는 노말 검사 · build 는 산출물 생성. */
|
|
74
|
+
async function runStep(step, cwd) {
|
|
75
|
+
// 1) 사용자 스크립트가 정의돼 있으면 그걸 쓴다(한 곳에서 관리).
|
|
76
|
+
const scriptName = step;
|
|
77
|
+
if (hasScript(cwd, scriptName)) {
|
|
78
|
+
const cmd = 'pnpm';
|
|
79
|
+
const args = ['run', scriptName];
|
|
80
|
+
const { exitCode, output } = await runSubprocess(cwd, cmd, args);
|
|
81
|
+
return {
|
|
82
|
+
step,
|
|
83
|
+
status: exitCode === 0 ? 'passed' : 'failed',
|
|
84
|
+
command: `${cmd} ${args.join(' ')}`,
|
|
85
|
+
exitCode,
|
|
86
|
+
output,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
// 2) 폴백 — 로컬 바이너리를 직접 부른다.
|
|
90
|
+
if (step === 'typecheck') {
|
|
91
|
+
// tsc 는 로컬 node_modules 또는 workspace 루트에 있다.
|
|
92
|
+
const tsconfigPath = join(cwd, 'tsconfig.json');
|
|
93
|
+
if (!existsSync(tsconfigPath)) {
|
|
94
|
+
return {
|
|
95
|
+
step,
|
|
96
|
+
status: 'skipped',
|
|
97
|
+
output: `tsconfig.json 이 없어 typecheck 를 건너뜁니다: ${tsconfigPath}\n→ 프로젝트 루트에 tsconfig.json 을 만들거나 package.json 에 "typecheck" 스크립트를 정의하세요.`,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
const tsBin = join(cwd, 'node_modules', 'typescript', 'bin', 'tsc');
|
|
101
|
+
if (!existsSync(tsBin)) {
|
|
102
|
+
return {
|
|
103
|
+
step,
|
|
104
|
+
status: 'skipped',
|
|
105
|
+
output: `typescript 가 설치돼 있지 않아 typecheck 를 건너뜁니다.\n→ pnpm add -D typescript 후 다시 실행하세요.`,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
const cmd = process.execPath;
|
|
109
|
+
const args = [tsBin, '--noEmit', '-p', tsconfigPath];
|
|
110
|
+
const { exitCode, output } = await runSubprocess(cwd, cmd, args);
|
|
111
|
+
return {
|
|
112
|
+
step,
|
|
113
|
+
status: exitCode === 0 ? 'passed' : 'failed',
|
|
114
|
+
command: `node tsc --noEmit -p ${tsconfigPath}`,
|
|
115
|
+
exitCode,
|
|
116
|
+
output,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
if (step === 'vue-tsc') {
|
|
120
|
+
// vue-tsc 는 .vue 가 하나라도 있는 프로젝트에서만 의미가 있다 —
|
|
121
|
+
// 없으면 스킵(설치 안 됐어도 정상).
|
|
122
|
+
if (!binExists(cwd, 'vue-tsc')) {
|
|
123
|
+
return {
|
|
124
|
+
step,
|
|
125
|
+
status: 'skipped',
|
|
126
|
+
output: 'vue-tsc 가 설치돼 있지 않아 스킵합니다(.vue 파일이 없으면 정상).',
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
const tsconfigPath = join(cwd, 'tsconfig.json');
|
|
130
|
+
if (!existsSync(tsconfigPath)) {
|
|
131
|
+
return {
|
|
132
|
+
step,
|
|
133
|
+
status: 'skipped',
|
|
134
|
+
output: 'tsconfig.json 이 없어 vue-tsc 를 건너뜁니다.',
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
const cmd = join(cwd, 'node_modules', '.bin', 'vue-tsc');
|
|
138
|
+
const args = ['--noEmit', '-p', tsconfigPath];
|
|
139
|
+
const { exitCode, output } = await runSubprocess(cwd, cmd, args);
|
|
140
|
+
return {
|
|
141
|
+
step,
|
|
142
|
+
status: exitCode === 0 ? 'passed' : 'failed',
|
|
143
|
+
command: `vue-tsc --noEmit -p ${tsconfigPath}`,
|
|
144
|
+
exitCode,
|
|
145
|
+
output,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
// build — 사용자 스크립트가 없으면 pnpm -r build 를 최선의 폴백으로 시도.
|
|
149
|
+
const cmd = 'pnpm';
|
|
150
|
+
const args = ['-r', 'run', 'build'];
|
|
151
|
+
const { exitCode, output } = await runSubprocess(cwd, cmd, args);
|
|
152
|
+
return {
|
|
153
|
+
step,
|
|
154
|
+
status: exitCode === 0 ? 'passed' : 'failed',
|
|
155
|
+
command: `${cmd} ${args.join(' ')}`,
|
|
156
|
+
exitCode,
|
|
157
|
+
output,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
/** doctor 는 이미 있는 명령을 재사용 — cwd 만 넘긴다. json 은 상위에서. */
|
|
161
|
+
async function runDoctorStep(cwd) {
|
|
162
|
+
try {
|
|
163
|
+
// runDoctorCommand 는 stdout 에 리포트를 그대로 찍는다. 결과 객체로
|
|
164
|
+
// 성공/실패를 판단한다.
|
|
165
|
+
const result = await runDoctorCommand({ cwd, json: false });
|
|
166
|
+
const failed = !!result.fatal || result.errors.length > 0;
|
|
167
|
+
return {
|
|
168
|
+
step: 'doctor',
|
|
169
|
+
status: failed ? 'failed' : 'passed',
|
|
170
|
+
command: 'gaon doctor',
|
|
171
|
+
exitCode: failed ? 1 : 0,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
catch (err) {
|
|
175
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
176
|
+
return {
|
|
177
|
+
step: 'doctor',
|
|
178
|
+
status: 'failed',
|
|
179
|
+
command: 'gaon doctor',
|
|
180
|
+
exitCode: 1,
|
|
181
|
+
output: msg,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* 검사 직전 .gaon 타입 브리지를 1회 재생성한다(규칙 3 · §13.4-5). 개발
|
|
187
|
+
* 서버 없이 부르는 check 가 stale/누락된 .gaon 으로 검증해 잘못된 통과·
|
|
188
|
+
* 실패를 내지 않게 한다. --only 여부와 무관하게 항상 먼저 돈다 —
|
|
189
|
+
* typecheck·vue-tsc·build 가 모두 .gaon 을 소비하기 때문.
|
|
190
|
+
*/
|
|
191
|
+
async function regenerateGaon(cwd) {
|
|
192
|
+
const layout = resolveDevLayout(cwd);
|
|
193
|
+
if (!layout.schemaDir && layout.apps.length === 0) {
|
|
194
|
+
return { status: 'skipped', tables: false, apps: [] };
|
|
195
|
+
}
|
|
196
|
+
// 생성기는 사용자 스키마·컨트롤러 .ts 를 동적 import 한다. TS-for-ESM
|
|
197
|
+
// 관례상 상대 import 가 `.js` 라 Node 가 못 찾으므로 `.js`→`.ts` 해석
|
|
198
|
+
// 훅을 등록한다(gaon dev·serve 와 동일 · tsResolve).
|
|
199
|
+
registerTsResolve();
|
|
200
|
+
try {
|
|
201
|
+
const result = await regenerateGaonOnce(layout, {
|
|
202
|
+
regenerateTables: generateTablesDts,
|
|
203
|
+
regenerateRoutes: generateRoutesDts,
|
|
204
|
+
});
|
|
205
|
+
return { status: 'done', tables: result.tables, apps: result.apps };
|
|
206
|
+
}
|
|
207
|
+
catch (err) {
|
|
208
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
209
|
+
return {
|
|
210
|
+
status: 'failed',
|
|
211
|
+
tables: false,
|
|
212
|
+
apps: [],
|
|
213
|
+
output: `.gaon 타입 브리지 재생성에 실패해 검사를 중단합니다: ${msg}\n` +
|
|
214
|
+
`→ domain/schema/*.ts 와 apps/*/routes.ts·controllers/*.ts 의 구문 오류를 고친 뒤 다시 실행하세요.`,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* `gaon check` 진입점. 검사 전에 .gaon 을 재생성(규칙 3)한 뒤 각 단계를
|
|
220
|
+
* 순서대로 실행하고, 하나라도 실패하면 exit 1. --only 지정 시 그 단계만
|
|
221
|
+
* 실행. --include-doctor 시 doctor 추가.
|
|
222
|
+
*
|
|
223
|
+
* §9 실 인프라 · 목업 X — 실 spawn 으로 검증한다.
|
|
224
|
+
*/
|
|
225
|
+
export async function runCheckCommand(opts = {}) {
|
|
226
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
227
|
+
const json = opts.json ?? false;
|
|
228
|
+
const only = opts.only;
|
|
229
|
+
const includeDoctor = opts.includeDoctor ?? false;
|
|
230
|
+
// 규칙 3 — 검사는 최신 타입 브리지 위에서만 신뢰할 수 있다. 재생성이
|
|
231
|
+
// 실패하면 stale .gaon 으로 검사하지 않고 즉시 중단한다.
|
|
232
|
+
const regen = await regenerateGaon(cwd);
|
|
233
|
+
const steps = regen.status === 'failed'
|
|
234
|
+
? []
|
|
235
|
+
: only
|
|
236
|
+
? [only]
|
|
237
|
+
: ['typecheck', 'vue-tsc', 'build', ...(includeDoctor ? ['doctor'] : [])];
|
|
238
|
+
const results = [];
|
|
239
|
+
for (const step of steps) {
|
|
240
|
+
if (step === 'doctor') {
|
|
241
|
+
results.push(await runDoctorStep(cwd));
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
results.push(await runStep(step, cwd));
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
const ok = regen.status !== 'failed' && results.every((r) => r.status !== 'failed');
|
|
248
|
+
const report = { ok, regen, steps: results };
|
|
249
|
+
if (json) {
|
|
250
|
+
process.stdout.write(JSON.stringify(report) + '\n');
|
|
251
|
+
}
|
|
252
|
+
else {
|
|
253
|
+
writeRegenLine(regen);
|
|
254
|
+
for (const r of results) {
|
|
255
|
+
const mark = r.status === 'passed' ? '✓' : r.status === 'failed' ? '✗' : '·';
|
|
256
|
+
process.stdout.write(` ${mark} ${r.step} — ${r.status}${r.command ? ` (${r.command})` : ''}\n`);
|
|
257
|
+
if (r.status === 'failed' && r.output) {
|
|
258
|
+
process.stdout.write(r.output.endsWith('\n') ? r.output : r.output + '\n');
|
|
259
|
+
}
|
|
260
|
+
if (r.status === 'skipped' && r.output) {
|
|
261
|
+
process.stdout.write(` ${r.output.split('\n').join('\n ')}\n`);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
process.stdout.write(ok ? ' ▶ gaon check · 통과\n' : ' ✗ gaon check · 실패\n');
|
|
265
|
+
}
|
|
266
|
+
return ok ? 0 : 1;
|
|
267
|
+
}
|
|
268
|
+
/** 사람 UI 에 재생성 단계를 한 줄로 보고한다. */
|
|
269
|
+
function writeRegenLine(regen) {
|
|
270
|
+
if (regen.status === 'failed') {
|
|
271
|
+
process.stdout.write(' ✗ .gaon 재생성 — failed\n');
|
|
272
|
+
if (regen.output)
|
|
273
|
+
process.stdout.write(` ${regen.output.split('\n').join('\n ')}\n`);
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
if (regen.status === 'skipped') {
|
|
277
|
+
process.stdout.write(' · .gaon 재생성 — skipped (domain/schema · apps/* 없음)\n');
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
const parts = [];
|
|
281
|
+
if (regen.tables)
|
|
282
|
+
parts.push('tables.d.ts');
|
|
283
|
+
for (const app of regen.apps)
|
|
284
|
+
parts.push(`${app}/routes.d.ts`);
|
|
285
|
+
process.stdout.write(` ✓ .gaon 재생성 — ${parts.join(', ')}\n`);
|
|
286
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @gaonjs/cli · `gaon console` — REPL 진입점 (M9-G · v0.15 §13.5)
|
|
3
|
+
*
|
|
4
|
+
* Rails console 스타일 — 프로젝트 컨텍스트(gaon.config.ts + apps/*)를
|
|
5
|
+
* 로드해 배터리(DB · 모델 · 서비스)가 살아있는 상태로 Node REPL 을 띄운다.
|
|
6
|
+
* 종료는 REPL 관례대로 `.exit` 또는 Ctrl+C(2회).
|
|
7
|
+
*
|
|
8
|
+
* The One Way — 단일 진입점 · 옵션 최소:
|
|
9
|
+
* · (기본) gaon.config.ts 로드 → wireGaon → REPL
|
|
10
|
+
* · --no-config 순수 REPL (config 없이 · 프로젝트 밖 실험용)
|
|
11
|
+
* · --json 부팅 이벤트만 JSON 라인(자동화 · CI 스모크). REPL 자체는
|
|
12
|
+
* stdin TTY 를 그대로 물려 준다.
|
|
13
|
+
*
|
|
14
|
+
* loadDomain 은 domain/models · services 를 REPL 컨텍스트에 노출한다.
|
|
15
|
+
* `.later()` 로 잡을 스케줄해 볼 수 있게 하기 위해 도메인 잡도 함께 로드한다.
|
|
16
|
+
*
|
|
17
|
+
* fail-closed(§7.5.3): config 로드 실패는 stderr 에 수리 안내를 던지고
|
|
18
|
+
* exit code 1 로 나간다 — 우회 없이 정확한 원인을 노출.
|
|
19
|
+
*/
|
|
20
|
+
import { type REPLServer } from 'node:repl';
|
|
21
|
+
export interface ConsoleCommandOptions {
|
|
22
|
+
readonly cwd?: string;
|
|
23
|
+
/** 프로젝트 컨텍스트 로드 없이 순수 REPL. 기본 false. */
|
|
24
|
+
readonly noConfig?: boolean;
|
|
25
|
+
/** 부팅 이벤트를 JSON 라인으로 출력. REPL 세션 자체는 사람 UI. */
|
|
26
|
+
readonly json?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* REPL 서버 팩토리 — 테스트가 stdin/stdout 을 갈아 끼울 수 있게 주입점을
|
|
29
|
+
* 남긴다. 미주입 시 process.stdin/stdout 을 쓰는 기본 REPL.
|
|
30
|
+
*/
|
|
31
|
+
readonly replFactory?: () => REPLServer;
|
|
32
|
+
/** 종료 감지용 signals — 테스트에서 SIGINT 를 주입한다. 기본 process. */
|
|
33
|
+
readonly signals?: {
|
|
34
|
+
on(sig: 'SIGINT' | 'SIGTERM', fn: () => void): void;
|
|
35
|
+
off(sig: 'SIGINT' | 'SIGTERM', fn: () => void): void;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* `gaon console` 진입점.
|
|
40
|
+
*
|
|
41
|
+
* --no-config: env 만 로드하고 순수 REPL 을 띄운다.
|
|
42
|
+
* 기본: loadDotEnv → loadGaonConfig → wireGaon → loadDomain →
|
|
43
|
+
* REPL 컨텍스트에 gaon(wired) · config · apps · models · services
|
|
44
|
+
* · domain 을 심고, .exit 대기.
|
|
45
|
+
*/
|
|
46
|
+
export declare function runConsoleCommand(opts?: ConsoleCommandOptions): Promise<void>;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @gaonjs/cli · `gaon console` — REPL 진입점 (M9-G · v0.15 §13.5)
|
|
3
|
+
*
|
|
4
|
+
* Rails console 스타일 — 프로젝트 컨텍스트(gaon.config.ts + apps/*)를
|
|
5
|
+
* 로드해 배터리(DB · 모델 · 서비스)가 살아있는 상태로 Node REPL 을 띄운다.
|
|
6
|
+
* 종료는 REPL 관례대로 `.exit` 또는 Ctrl+C(2회).
|
|
7
|
+
*
|
|
8
|
+
* The One Way — 단일 진입점 · 옵션 최소:
|
|
9
|
+
* · (기본) gaon.config.ts 로드 → wireGaon → REPL
|
|
10
|
+
* · --no-config 순수 REPL (config 없이 · 프로젝트 밖 실험용)
|
|
11
|
+
* · --json 부팅 이벤트만 JSON 라인(자동화 · CI 스모크). REPL 자체는
|
|
12
|
+
* stdin TTY 를 그대로 물려 준다.
|
|
13
|
+
*
|
|
14
|
+
* loadDomain 은 domain/models · services 를 REPL 컨텍스트에 노출한다.
|
|
15
|
+
* `.later()` 로 잡을 스케줄해 볼 수 있게 하기 위해 도메인 잡도 함께 로드한다.
|
|
16
|
+
*
|
|
17
|
+
* fail-closed(§7.5.3): config 로드 실패는 stderr 에 수리 안내를 던지고
|
|
18
|
+
* exit code 1 로 나간다 — 우회 없이 정확한 원인을 노출.
|
|
19
|
+
*/
|
|
20
|
+
import { start as startRepl } from 'node:repl';
|
|
21
|
+
import { loadDotEnv } from '@gaonjs/core';
|
|
22
|
+
import { loadGaonConfig, wireGaon, findConfigPath } from '@gaonjs/config';
|
|
23
|
+
import { registerTsResolve } from '../tsResolve.js';
|
|
24
|
+
import { loadDomain } from '../domain.js';
|
|
25
|
+
function humanEvent(e) {
|
|
26
|
+
switch (e.kind) {
|
|
27
|
+
case 'starting':
|
|
28
|
+
return ` gaon console · 시작 — cwd=${e.cwd}${e.noConfig ? ' · --no-config' : ''}`;
|
|
29
|
+
case 'loaded': {
|
|
30
|
+
const cfg = e.configPath ? `config=${e.configPath}` : 'config=(없음 · 기본값)';
|
|
31
|
+
const apps = e.apps.length ? `apps=[${e.apps.join(', ')}]` : 'apps=(없음)';
|
|
32
|
+
const d = `domain(jobs=${e.domain.jobs} · listeners=${e.domain.listeners} · mails=${e.domain.mails})`;
|
|
33
|
+
return ` ▶ 로드됨 — ${cfg} · ${apps} · ${d}`;
|
|
34
|
+
}
|
|
35
|
+
case 'ready':
|
|
36
|
+
return e.noConfig
|
|
37
|
+
? ' ▶ REPL 준비 (--no-config · 프로젝트 컨텍스트 없음). .exit 로 종료.'
|
|
38
|
+
: ' ▶ REPL 준비. 컨텍스트: gaon, config, apps, models, services, domain. .exit 로 종료.';
|
|
39
|
+
case 'exited':
|
|
40
|
+
return ' gaon console · 종료';
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* `gaon console` 진입점.
|
|
45
|
+
*
|
|
46
|
+
* --no-config: env 만 로드하고 순수 REPL 을 띄운다.
|
|
47
|
+
* 기본: loadDotEnv → loadGaonConfig → wireGaon → loadDomain →
|
|
48
|
+
* REPL 컨텍스트에 gaon(wired) · config · apps · models · services
|
|
49
|
+
* · domain 을 심고, .exit 대기.
|
|
50
|
+
*/
|
|
51
|
+
export async function runConsoleCommand(opts = {}) {
|
|
52
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
53
|
+
const json = opts.json ?? false;
|
|
54
|
+
const noConfig = opts.noConfig ?? false;
|
|
55
|
+
const signals = opts.signals ?? process;
|
|
56
|
+
const emit = (e) => {
|
|
57
|
+
if (json)
|
|
58
|
+
process.stdout.write(JSON.stringify(e) + '\n');
|
|
59
|
+
else
|
|
60
|
+
process.stdout.write(humanEvent(e) + '\n');
|
|
61
|
+
};
|
|
62
|
+
// .ts 상대 import 해석기 — 사용자 gaon.config.ts · domain/* 로드에 필요.
|
|
63
|
+
registerTsResolve();
|
|
64
|
+
loadDotEnv(cwd);
|
|
65
|
+
emit({ kind: 'starting', cwd, noConfig });
|
|
66
|
+
let wired;
|
|
67
|
+
let domain;
|
|
68
|
+
let configPath;
|
|
69
|
+
const context = {};
|
|
70
|
+
if (!noConfig) {
|
|
71
|
+
configPath = findConfigPath(cwd);
|
|
72
|
+
const config = await loadGaonConfig(cwd);
|
|
73
|
+
wired = await wireGaon(config, cwd);
|
|
74
|
+
domain = await loadDomain(cwd);
|
|
75
|
+
context.gaon = wired;
|
|
76
|
+
context.config = config;
|
|
77
|
+
context.apps = wired.apps;
|
|
78
|
+
context.domain = domain;
|
|
79
|
+
// 모델·서비스 자동 노출은 사용자 코드 스캔이 필요해 v1 은 domain 요약만
|
|
80
|
+
// 노출한다(§13.5 M9-G 최소 스코프). 사용자는 컨텍스트에서 필요 파일을
|
|
81
|
+
// `await import(...)` 로 가져올 수 있다(REPL 은 top-level await 허용).
|
|
82
|
+
context.models = {};
|
|
83
|
+
context.services = {};
|
|
84
|
+
emit({
|
|
85
|
+
kind: 'loaded',
|
|
86
|
+
configPath,
|
|
87
|
+
apps: wired.apps.map((a) => a.name),
|
|
88
|
+
domain: {
|
|
89
|
+
jobs: domain.jobs,
|
|
90
|
+
listeners: domain.listeners,
|
|
91
|
+
mails: domain.mails,
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
emit({ kind: 'ready', noConfig });
|
|
96
|
+
// REPL 시작 — 테스트는 replFactory 로 stdin/stdout 을 갈아 끼운다.
|
|
97
|
+
const repl = opts.replFactory
|
|
98
|
+
? opts.replFactory()
|
|
99
|
+
: startRepl({
|
|
100
|
+
prompt: noConfig ? 'gaon(no-config)> ' : 'gaon> ',
|
|
101
|
+
useColors: true,
|
|
102
|
+
// top-level await 허용 — 배터리 검증용 fetch/DB 조회에 편리.
|
|
103
|
+
// (Node ≥ 22 에서 안정 지원.)
|
|
104
|
+
useGlobal: false,
|
|
105
|
+
});
|
|
106
|
+
// 컨텍스트를 REPL 에 주입.
|
|
107
|
+
Object.assign(repl.context, context);
|
|
108
|
+
// Ctrl+C 로 nudge, 종료는 SIGINT 로 배선 close.
|
|
109
|
+
await new Promise((resolveExit) => {
|
|
110
|
+
const done = async () => {
|
|
111
|
+
signals.off('SIGINT', done);
|
|
112
|
+
signals.off('SIGTERM', done);
|
|
113
|
+
if (wired) {
|
|
114
|
+
try {
|
|
115
|
+
await wired.close();
|
|
116
|
+
}
|
|
117
|
+
catch (err) {
|
|
118
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
119
|
+
process.stderr.write(` ✗ 종료 중 오류: ${msg}\n`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
emit({ kind: 'exited' });
|
|
123
|
+
resolveExit();
|
|
124
|
+
};
|
|
125
|
+
repl.on('exit', () => void done());
|
|
126
|
+
signals.on('SIGINT', () => void done());
|
|
127
|
+
signals.on('SIGTERM', () => void done());
|
|
128
|
+
});
|
|
129
|
+
}
|
package/dist/commands/db.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type DbSubcommand = 'diff' | 'migrate' | 'reset' | 'seed';
|
|
1
|
+
export type DbSubcommand = 'diff' | 'migrate' | 'reset' | 'seed' | 'status';
|
|
2
2
|
export interface DbCommandOptions {
|
|
3
3
|
/** 프로젝트 루트 — gaon.config.ts·domain/schema 위치. 기본 process.cwd(). */
|
|
4
4
|
readonly cwd?: string;
|
|
@@ -12,6 +12,8 @@ export interface DbCommandOptions {
|
|
|
12
12
|
readonly yes?: boolean;
|
|
13
13
|
/** migrate/reset dry-run — 실행 없이 SQL 만 출력. */
|
|
14
14
|
readonly dryRun?: boolean;
|
|
15
|
+
/** `gaon db migrate down` — 가장 최근 이력 한 건 롤백(§4.8). */
|
|
16
|
+
readonly down?: boolean;
|
|
15
17
|
}
|
|
16
18
|
/**
|
|
17
19
|
* `gaon db <sub>` 진입점. 사람/JSON 출력을 자체 처리하고 exitCode 를
|
package/dist/commands/db.js
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import { runDbDiff } from '../db/diff.js';
|
|
11
11
|
import { runDbMigrate } from '../db/migrate.js';
|
|
12
12
|
import { runDbReset } from '../db/reset.js';
|
|
13
|
+
import { runDbStatus } from '../db/status.js';
|
|
13
14
|
import { runDbSeedCommand } from '../db.js';
|
|
14
15
|
/**
|
|
15
16
|
* `gaon db <sub>` 진입점. 사람/JSON 출력을 자체 처리하고 exitCode 를
|
|
@@ -34,7 +35,12 @@ export async function runDbCommand(subcommand, opts = {}) {
|
|
|
34
35
|
return r.exitCode;
|
|
35
36
|
}
|
|
36
37
|
if (subcommand === 'migrate') {
|
|
37
|
-
const r = await runDbMigrate({ cwd, dbKey, json, dryRun, configPath });
|
|
38
|
+
const r = await runDbMigrate({ cwd, dbKey, json, dryRun, down: opts.down ?? false, configPath });
|
|
39
|
+
emit(r.text, r.json);
|
|
40
|
+
return r.exitCode;
|
|
41
|
+
}
|
|
42
|
+
if (subcommand === 'status') {
|
|
43
|
+
const r = await runDbStatus({ cwd, dbKey, json, configPath });
|
|
38
44
|
emit(r.text, r.json);
|
|
39
45
|
return r.exitCode;
|
|
40
46
|
}
|
|
@@ -58,7 +64,7 @@ export async function runDbCommand(subcommand, opts = {}) {
|
|
|
58
64
|
}
|
|
59
65
|
// 방어 — dispatcher 라 도달할 수 없지만 컴파일러 만족용.
|
|
60
66
|
process.stderr.write(` ✗ 알 수 없는 db 서브커맨드: ${String(subcommand)}\n` +
|
|
61
|
-
` → 지원: diff | migrate | reset | seed\n`);
|
|
67
|
+
` → 지원: diff | migrate | reset | seed | status\n`);
|
|
62
68
|
return 1;
|
|
63
69
|
}
|
|
64
70
|
catch (err) {
|
package/dist/commands/g.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type ScaffoldFile, type WriteResult } from '../scaffold/index.js';
|
|
2
|
-
export type GenerateType = 'controller' | 'model' | 'page' | 'job';
|
|
2
|
+
export type GenerateType = 'controller' | 'model' | 'page' | 'job' | 'app';
|
|
3
3
|
export interface GenerateOptions {
|
|
4
4
|
readonly cwd?: string;
|
|
5
5
|
readonly app?: string;
|
package/dist/commands/g.js
CHANGED
|
@@ -11,10 +11,12 @@
|
|
|
11
11
|
//
|
|
12
12
|
// 파일 위치는 프로젝트 관습(CLAUDE.md §2)을 따른다:
|
|
13
13
|
// controller → apps/<app>/controllers/<plural>.ts
|
|
14
|
-
// model → domain/schema/<
|
|
14
|
+
// model → domain/schema/<posts>.ts + domain/models/<Post>.ts (§3.4: 스키마=테이블명 · 모델=Pascal)
|
|
15
15
|
// page → apps/<app>/pages/<path>.vue
|
|
16
16
|
// job → domain/jobs/<camel>.ts
|
|
17
|
-
import {
|
|
17
|
+
import { existsSync } from 'node:fs';
|
|
18
|
+
import { join } from 'node:path';
|
|
19
|
+
import { appScaffoldFiles, controllerScaffold, inflectModel, jobScaffold, jobTestScaffold, modelScaffoldFiles, pageScaffold, writeScaffold, } from '../scaffold/index.js';
|
|
18
20
|
/** argv 에서 옵션을 뽑는다(간단 파서 · runCli 관례와 일치). */
|
|
19
21
|
export function parseGenerateArgs(argv) {
|
|
20
22
|
const type = argv[0];
|
|
@@ -60,6 +62,10 @@ export function planScaffold(type, name, app) {
|
|
|
60
62
|
if (type === 'page') {
|
|
61
63
|
return [pageScaffold(name, app)];
|
|
62
64
|
}
|
|
65
|
+
// app 은 이름 자체가 앱 폴더명이 되므로 '/' 는 허용하지 않는다(app.ts 재검증).
|
|
66
|
+
if (type === 'app') {
|
|
67
|
+
return appScaffoldFiles(name);
|
|
68
|
+
}
|
|
63
69
|
if (name.includes('/')) {
|
|
64
70
|
throw new Error(`${type} 이름에 '/' 는 사용할 수 없습니다: ${name}`);
|
|
65
71
|
}
|
|
@@ -68,8 +74,9 @@ export function planScaffold(type, name, app) {
|
|
|
68
74
|
return [controllerScaffold(names, app)];
|
|
69
75
|
if (type === 'model')
|
|
70
76
|
return modelScaffoldFiles(names);
|
|
77
|
+
// 잡은 통합 테스트 골격을 짝으로 생성한다 (결정 42 · expectJobProcessed).
|
|
71
78
|
if (type === 'job')
|
|
72
|
-
return [jobScaffold(names.pascal)];
|
|
79
|
+
return [jobScaffold(names.pascal), jobTestScaffold(names.pascal)];
|
|
73
80
|
throw new Error(`알 수 없는 제너레이터: ${type}`);
|
|
74
81
|
}
|
|
75
82
|
/** `gaon g <type> <name>` 실행. exitCode 를 반환한다(0=성공, 1=실패). */
|
|
@@ -90,6 +97,23 @@ export function runGenerateCommand(type, name, opts = {}) {
|
|
|
90
97
|
}
|
|
91
98
|
return 1;
|
|
92
99
|
}
|
|
100
|
+
// gaon g app <name>: 앱 폴더가 이미 있으면 사고를 막기 위해 즉시 실패한다
|
|
101
|
+
// (--overwrite 로도 되돌리지 않는다 — 앱 재생성은 파괴적이라 사용자 판단
|
|
102
|
+
// 필요). 다른 스캐폴드는 파일 단위 skip 관례(Rails)를 그대로 유지.
|
|
103
|
+
if (type === 'app') {
|
|
104
|
+
const appDir = join(cwd, 'apps', name);
|
|
105
|
+
if (existsSync(appDir)) {
|
|
106
|
+
const msg = `앱 폴더가 이미 존재합니다: apps/${name}\n` +
|
|
107
|
+
` → 다른 이름을 쓰거나 apps/${name} 을 수동으로 정리한 뒤 다시 실행하세요.`;
|
|
108
|
+
if (opts.json) {
|
|
109
|
+
process.stdout.write(JSON.stringify({ ok: false, error: msg }) + '\n');
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
process.stderr.write(` ✗ gaon g app: ${msg}\n`);
|
|
113
|
+
}
|
|
114
|
+
return 1;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
93
117
|
const write = writeScaffold(cwd, files, { overwrite: opts.overwrite });
|
|
94
118
|
// overwrite 없이 기존 파일이 있으면 실패(사고 방지 · Rails 관례).
|
|
95
119
|
const failed = write.skipped.length > 0;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface McpCommandOptions {
|
|
2
|
+
readonly cwd?: string;
|
|
3
|
+
readonly json?: boolean;
|
|
4
|
+
/**
|
|
5
|
+
* 트랜스포트. v1 은 'stdio' 만. 미래 확장(http)을 위한 자리.
|
|
6
|
+
* 지금 'http' 를 주면 명확히 미지원 에러(§7.5.3 수리 안내).
|
|
7
|
+
*/
|
|
8
|
+
readonly transport?: 'stdio' | 'http';
|
|
9
|
+
/** 서버 버전 문자열(파사드 gaonjs 가 주입). 미주입 시 'dev'. */
|
|
10
|
+
readonly version?: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* `gaon mcp` 진입점. stdio transport 는 stdin close 까지 살아 있는다.
|
|
14
|
+
*/
|
|
15
|
+
export declare function runMcpCommand(opts?: McpCommandOptions): Promise<number>;
|