@gaonjs/cli 0.3.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/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/reporter.d.ts +1 -1
- package/dist/doctor/reporter.js +16 -3
- package/dist/doctor/setup.d.ts +26 -0
- package/dist/doctor/setup.js +52 -0
- package/dist/doctor/types.d.ts +13 -0
- package/dist/doctor/types.js +5 -1
- package/dist/doctor.d.ts +19 -5
- package/dist/doctor.js +64 -5
- package/dist/index.d.ts +5 -1
- package/dist/index.js +71 -21
- package/package.json +5 -5
package/dist/dev.js
CHANGED
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @gaonjs/cli ·
|
|
2
|
+
* @gaonjs/cli · dev — .gaon 갱신 파이프라인 (M3 · v0.15 §6.3)
|
|
3
3
|
*
|
|
4
4
|
* 파일 워처(@gaonjs/data watchDir) → 재생성(tables.d.ts·routes.d.ts) →
|
|
5
|
-
* 디스크 mtime 갱신 → 편집기 tsserver 자동
|
|
5
|
+
* 디스크 mtime 갱신 → 편집기 tsserver 자동 반영. 오케스트레이션은
|
|
6
|
+
* 주입(deps)으로 순수하게 유지하고(테스트 용이 · dbCli 패턴 동일),
|
|
7
|
+
* 상위(gaon dev 오케스트레이터 · commands/dev.ts)가 이 startDev 를 재사용한다.
|
|
6
8
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
9
|
+
* M9-C 이전에는 이 파일이 gaon dev 진입점(runDevCommand)도 겸했으나,
|
|
10
|
+
* M9-C 에서 인프라·서버·타입 검사기를 통합하는 새 진입점이 commands/dev.ts
|
|
11
|
+
* 로 이동했다. 이 파일은 .gaon 재생성 코어(startDev · resolveDevLayout) 만
|
|
12
|
+
* 유지한다.
|
|
11
13
|
*/
|
|
12
14
|
import { readdirSync, existsSync } from 'node:fs';
|
|
13
15
|
import { join, resolve } from 'node:path';
|
|
14
|
-
import { generateTablesDts, watchDir } from '@gaonjs/data';
|
|
15
|
-
import { generateRoutesDts } from '@gaonjs/web';
|
|
16
|
-
import { registerTsResolve } from './tsResolve.js';
|
|
17
16
|
const isSchemaChange = (f) => f.endsWith('.ts') && !f.endsWith('.d.ts') && !f.endsWith('.test.ts') && !f.includes('.gaon');
|
|
18
17
|
const isRoutesChange = (f) => !f.includes('.gaon') &&
|
|
19
18
|
f.endsWith('.ts') &&
|
|
@@ -94,60 +93,5 @@ export function resolveDevLayout(cwd) {
|
|
|
94
93
|
apps: apps.sort((a, b) => a.name.localeCompare(b.name)),
|
|
95
94
|
};
|
|
96
95
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
switch (e.kind) {
|
|
100
|
-
case 'ready': {
|
|
101
|
-
const parts = [];
|
|
102
|
-
if (e.schema)
|
|
103
|
-
parts.push('schema→tables.d.ts');
|
|
104
|
-
for (const a of e.apps)
|
|
105
|
-
parts.push(`${a}→routes.d.ts`);
|
|
106
|
-
const what = parts.length ? parts.join(', ') : '(감시 대상 없음 — domain/schema·apps/* 확인)';
|
|
107
|
-
return ` gaon dev · 워치 시작 — ${what}\n 파일을 저장하면 .gaon 이 재생성되고 편집기가 자동 반영합니다. (Ctrl+C 종료)`;
|
|
108
|
-
}
|
|
109
|
-
case 'regen':
|
|
110
|
-
return e.target === 'tables'
|
|
111
|
-
? ' ↻ tables.d.ts 재생성'
|
|
112
|
-
: ` ↻ ${e.app}/.gaon/routes.d.ts 재생성`;
|
|
113
|
-
case 'stopped':
|
|
114
|
-
return ' gaon dev · 종료';
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
/**
|
|
118
|
-
* `gaon dev` 진입점. cwd 관례로 레이아웃을 엮고 워치 루프를 띄운 뒤
|
|
119
|
-
* SIGINT/SIGTERM 에 graceful stop. 프로세스는 워처가 살아 있는 동안
|
|
120
|
-
* 유지된다(활성 핸들). 반환 프라미스는 종료 시 resolve.
|
|
121
|
-
*/
|
|
122
|
-
export async function runDevCommand(opts = {}) {
|
|
123
|
-
const cwd = opts.cwd ?? process.cwd();
|
|
124
|
-
const json = opts.json ?? false;
|
|
125
|
-
const signals = opts.signals ?? process;
|
|
126
|
-
registerTsResolve(); // 사용자 .ts 의 `.js` 상대 import 를 런타임에 해석
|
|
127
|
-
const layout = resolveDevLayout(cwd);
|
|
128
|
-
const emit = (e) => {
|
|
129
|
-
if (json)
|
|
130
|
-
process.stdout.write(JSON.stringify(e) + '\n');
|
|
131
|
-
else
|
|
132
|
-
process.stdout.write(humanEvent(e) + '\n');
|
|
133
|
-
};
|
|
134
|
-
const deps = {
|
|
135
|
-
layout,
|
|
136
|
-
regenerateTables: generateTablesDts,
|
|
137
|
-
regenerateRoutes: generateRoutesDts,
|
|
138
|
-
watch: watchDir,
|
|
139
|
-
log: emit,
|
|
140
|
-
onError: (err) => process.stderr.write(` ✗ ${err.message}\n`),
|
|
141
|
-
};
|
|
142
|
-
const handle = await startDev(deps);
|
|
143
|
-
await new Promise((resolvePromise) => {
|
|
144
|
-
const stop = () => {
|
|
145
|
-
signals.off('SIGINT', stop);
|
|
146
|
-
signals.off('SIGTERM', stop);
|
|
147
|
-
handle.close();
|
|
148
|
-
resolvePromise();
|
|
149
|
-
};
|
|
150
|
-
signals.on('SIGINT', stop);
|
|
151
|
-
signals.on('SIGTERM', stop);
|
|
152
|
-
});
|
|
153
|
-
}
|
|
96
|
+
// runDevCommand · DevCommandOptions 는 M9-C 에서 commands/dev.ts 로 이동.
|
|
97
|
+
// 이 파일은 .gaon 재생성 코어(startDev · resolveDevLayout)만 유지한다.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { DoctorResult } from './types.js';
|
|
2
|
-
/** JSON 출력 문자열(끝 개행 없음). */
|
|
2
|
+
/** JSON 출력 문자열(끝 개행 없음). fatal 이 있으면 그대로 포함된다. */
|
|
3
3
|
export declare function renderJson(result: DoctorResult): string;
|
|
4
4
|
/** 사람이 읽는 요약(끝 개행 없음). */
|
|
5
5
|
export declare function renderHuman(result: DoctorResult): string;
|
package/dist/doctor/reporter.js
CHANGED
|
@@ -1,13 +1,26 @@
|
|
|
1
|
-
// @gaonjs/cli · doctor · 출력 포맷 (human / JSON) — M9-E
|
|
1
|
+
// @gaonjs/cli · doctor · 출력 포맷 (human / JSON) — M9-E · M9-E-Fix
|
|
2
2
|
//
|
|
3
3
|
// human: 사람이 읽는 요약 + 규칙별 이슈 목록.
|
|
4
|
-
// JSON: 자동화(CI)용 · {passed, warnings, errors} 그대로 직렬화
|
|
5
|
-
|
|
4
|
+
// JSON: 자동화(CI)용 · {passed, warnings, errors, fatal?} 그대로 직렬화
|
|
5
|
+
// · 파싱 안정.
|
|
6
|
+
//
|
|
7
|
+
// fatal 이 있으면 규칙 실행 자체가 불가능한 상황 — 크래시 대신 우아한
|
|
8
|
+
// 안내(§7.5.3 · 에러 = 수리 안내서)를 출력한다.
|
|
9
|
+
/** JSON 출력 문자열(끝 개행 없음). fatal 이 있으면 그대로 포함된다. */
|
|
6
10
|
export function renderJson(result) {
|
|
7
11
|
return JSON.stringify(result);
|
|
8
12
|
}
|
|
9
13
|
/** 사람이 읽는 요약(끝 개행 없음). */
|
|
10
14
|
export function renderHuman(result) {
|
|
15
|
+
if (result.fatal) {
|
|
16
|
+
const lines = [];
|
|
17
|
+
lines.push(` ✗ gaon doctor · 시작 불가 (${result.fatal.code})`);
|
|
18
|
+
for (const ln of result.fatal.message.split('\n')) {
|
|
19
|
+
lines.push(` ${ln}`);
|
|
20
|
+
}
|
|
21
|
+
lines.push(` ${result.fatal.hint}`);
|
|
22
|
+
return lines.join('\n');
|
|
23
|
+
}
|
|
11
24
|
const lines = [];
|
|
12
25
|
const passN = result.passed.length;
|
|
13
26
|
const warnN = result.warnings.length;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { DoctorFatal } from './types.js';
|
|
2
|
+
export type { DoctorFatal, DoctorFatalCode } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* 프로젝트 마커 4종: gaon.config.ts · domain/ · apps/ · shared/.
|
|
5
|
+
* 하나라도 있으면 gaon 프로젝트로 인정한다. 모두 없으면 검사할 대상이
|
|
6
|
+
* 없으므로 우아한 안내 후 종료한다.
|
|
7
|
+
*/
|
|
8
|
+
export declare function detectProject(cwd: string): boolean;
|
|
9
|
+
/**
|
|
10
|
+
* typescript 모듈이 doctor 가 필요로 하는 compiler API 를 노출하는지
|
|
11
|
+
* 검사한다. 예: 사용자 프로젝트가 typescript@7 을 받아 오면 default
|
|
12
|
+
* export 가 `{ version, versionMajorMinor }` 스텁만 담아
|
|
13
|
+
* `ts.ScriptTarget` 이 undefined → `ts.createSourceFile` 호출 시 크래시.
|
|
14
|
+
*
|
|
15
|
+
* 인자 tsMod 는 테스트에서 스텁을 주입하기 위해 기본값을 실 ts 로 둔다.
|
|
16
|
+
*/
|
|
17
|
+
export declare function checkTypeScriptApi(tsMod?: {
|
|
18
|
+
readonly ScriptTarget?: {
|
|
19
|
+
readonly ES2022?: number;
|
|
20
|
+
};
|
|
21
|
+
readonly createSourceFile?: unknown;
|
|
22
|
+
}): boolean;
|
|
23
|
+
/** 프로젝트 마커 없음 · 우아한 안내. */
|
|
24
|
+
export declare function fatalNoProject(cwd: string): DoctorFatal;
|
|
25
|
+
/** typescript compiler API 미노출 · 우아한 안내. */
|
|
26
|
+
export declare function fatalTsApiMissing(installedVersion: string | undefined): DoctorFatal;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// @gaonjs/cli · doctor · 사전 검사 (M9-E-Fix · 크래시 하드닝)
|
|
2
|
+
//
|
|
3
|
+
// doctor 는 사용자 프로젝트의 TS 소스를 AST 로 파싱한다. 두 가지 전제:
|
|
4
|
+
// 1) 실제로 gaon 프로젝트여야 한다 (검사 대상이 존재)
|
|
5
|
+
// 2) typescript 컴파일러 API 를 불러올 수 있어야 한다
|
|
6
|
+
// 전제가 깨지면 규칙 실행 중 크래시("Cannot read properties of undefined
|
|
7
|
+
// (reading 'ES2022')" 등) 대신 우아한 안내 메시지 + exit 2 로 대체한다
|
|
8
|
+
// (CLAUDE.md §7.5.3 · 에러 = 수리 안내서).
|
|
9
|
+
import { existsSync } from 'node:fs';
|
|
10
|
+
import { join } from 'node:path';
|
|
11
|
+
import ts from 'typescript';
|
|
12
|
+
/**
|
|
13
|
+
* 프로젝트 마커 4종: gaon.config.ts · domain/ · apps/ · shared/.
|
|
14
|
+
* 하나라도 있으면 gaon 프로젝트로 인정한다. 모두 없으면 검사할 대상이
|
|
15
|
+
* 없으므로 우아한 안내 후 종료한다.
|
|
16
|
+
*/
|
|
17
|
+
export function detectProject(cwd) {
|
|
18
|
+
const markers = ['gaon.config.ts', 'domain', 'apps', 'shared'];
|
|
19
|
+
return markers.some((m) => existsSync(join(cwd, m)));
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* typescript 모듈이 doctor 가 필요로 하는 compiler API 를 노출하는지
|
|
23
|
+
* 검사한다. 예: 사용자 프로젝트가 typescript@7 을 받아 오면 default
|
|
24
|
+
* export 가 `{ version, versionMajorMinor }` 스텁만 담아
|
|
25
|
+
* `ts.ScriptTarget` 이 undefined → `ts.createSourceFile` 호출 시 크래시.
|
|
26
|
+
*
|
|
27
|
+
* 인자 tsMod 는 테스트에서 스텁을 주입하기 위해 기본값을 실 ts 로 둔다.
|
|
28
|
+
*/
|
|
29
|
+
export function checkTypeScriptApi(tsMod = ts) {
|
|
30
|
+
return (typeof tsMod.createSourceFile === 'function' &&
|
|
31
|
+
tsMod.ScriptTarget !== undefined &&
|
|
32
|
+
tsMod.ScriptTarget.ES2022 !== undefined);
|
|
33
|
+
}
|
|
34
|
+
/** 프로젝트 마커 없음 · 우아한 안내. */
|
|
35
|
+
export function fatalNoProject(cwd) {
|
|
36
|
+
return {
|
|
37
|
+
code: 'no-project',
|
|
38
|
+
message: `gaon 프로젝트를 찾지 못했습니다 (cwd: ${cwd}).\n` +
|
|
39
|
+
`마커(gaon.config.ts · domain/ · apps/ · shared/) 중 하나도 없습니다.`,
|
|
40
|
+
hint: `→ 'gaon new <name>' 으로 새 프로젝트를 만들거나 실 프로젝트 경로로 이동한 뒤 다시 실행하세요.`,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/** typescript compiler API 미노출 · 우아한 안내. */
|
|
44
|
+
export function fatalTsApiMissing(installedVersion) {
|
|
45
|
+
const v = installedVersion ?? '(알 수 없음)';
|
|
46
|
+
return {
|
|
47
|
+
code: 'ts-api-missing',
|
|
48
|
+
message: `typescript 컴파일러 API 를 불러올 수 없습니다 (설치된 typescript: ${v}).\n` +
|
|
49
|
+
`doctor 는 typescript 5.x 의 AST 파서(createSourceFile · ScriptTarget)를 사용합니다.`,
|
|
50
|
+
hint: `→ 프로젝트에 typescript 5.x 를 설치하세요: 'npm i -D typescript@^5.9'.`,
|
|
51
|
+
};
|
|
52
|
+
}
|
package/dist/doctor/types.d.ts
CHANGED
|
@@ -15,6 +15,19 @@ export interface DoctorResult {
|
|
|
15
15
|
readonly passed: readonly DoctorCheck[];
|
|
16
16
|
readonly warnings: readonly DoctorCheck[];
|
|
17
17
|
readonly errors: readonly DoctorCheck[];
|
|
18
|
+
/**
|
|
19
|
+
* 사전 검사 실패(프로젝트 없음 · TS API 미노출) 시에만 채워진다.
|
|
20
|
+
* 존재하면 runCli 는 exit 2 로 종료한다(사용자 오류 · 크래시 아님).
|
|
21
|
+
*/
|
|
22
|
+
readonly fatal?: DoctorFatal;
|
|
23
|
+
}
|
|
24
|
+
/** 사전 검사 실패 원인 코드. */
|
|
25
|
+
export type DoctorFatalCode = 'no-project' | 'ts-api-missing';
|
|
26
|
+
/** 규칙 실행이 시작될 수 없는 상황. runCli 는 exit 2 로 매핑. */
|
|
27
|
+
export interface DoctorFatal {
|
|
28
|
+
readonly code: DoctorFatalCode;
|
|
29
|
+
readonly message: string;
|
|
30
|
+
readonly hint: string;
|
|
18
31
|
}
|
|
19
32
|
/** 하위 검사 모듈이 상위에서 조립되도록 반환하는 결과 묶음. */
|
|
20
33
|
export interface RuleReport {
|
package/dist/doctor/types.js
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
// @gaonjs/cli · doctor 공용 타입 (M9-E)
|
|
1
|
+
// @gaonjs/cli · doctor 공용 타입 (M9-E · M9-E-Fix)
|
|
2
2
|
//
|
|
3
3
|
// 5 검사(response-mixing · n-plus-one · dependency-direction · connections
|
|
4
4
|
// · migration-diff)가 모두 이 DoctorCheck 를 낸다. 상위(runDoctorCommand)는
|
|
5
5
|
// level 로 passed/warnings/errors 로 갈라 담는다. 자동화(CI)는 JSON 을
|
|
6
6
|
// 파싱해 errors.length > 0 이면 fail 로 판단한다.
|
|
7
|
+
//
|
|
8
|
+
// 사전 검사 실패(프로젝트 마커 없음 · TS API 미노출)는 규칙 실행 자체가
|
|
9
|
+
// 불가능한 상황이므로 별도 `fatal` 필드로 표현한다 — runCli 는 이 경우
|
|
10
|
+
// exit 2(사용자 오류)로 매핑한다(M9-E-Fix · CLAUDE.md §7.5.3).
|
|
7
11
|
export function makeResult(reports) {
|
|
8
12
|
const passed = [];
|
|
9
13
|
const warnings = [];
|
package/dist/doctor.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type DoctorCheck, type DoctorLevel, type DoctorResult, type DoctorRule, type RuleReport } from './doctor/types.js';
|
|
2
|
-
export type { DoctorCheck, DoctorLevel, DoctorResult, DoctorRule, RuleReport };
|
|
1
|
+
import { type DoctorCheck, type DoctorFatal, type DoctorFatalCode, type DoctorLevel, type DoctorResult, type DoctorRule, type RuleReport } from './doctor/types.js';
|
|
2
|
+
export type { DoctorCheck, DoctorFatal, DoctorFatalCode, DoctorLevel, DoctorResult, DoctorRule, RuleReport, };
|
|
3
3
|
export type { ResponseKind, ActionUsage } from './doctor/response-mixing.js';
|
|
4
4
|
export { inspectControllerSource, checkResponseMixing } from './doctor/response-mixing.js';
|
|
5
5
|
export { inspectControllerForNPlusOne, checkNPlusOne } from './doctor/n-plus-one.js';
|
|
@@ -7,16 +7,30 @@ export { extractRelativeImports, checkDependencyDirection } from './doctor/depen
|
|
|
7
7
|
export { extractConfigDbKeys, extractKeyUses, checkConnections } from './doctor/connections.js';
|
|
8
8
|
export { scanSchema, checkMigrationDiff } from './doctor/migration-diff.js';
|
|
9
9
|
export { renderHuman, renderJson } from './doctor/reporter.js';
|
|
10
|
+
export { checkTypeScriptApi, detectProject, fatalNoProject, fatalTsApiMissing, } from './doctor/setup.js';
|
|
10
11
|
export interface DoctorCommandOptions {
|
|
11
12
|
readonly cwd?: string;
|
|
12
13
|
readonly json?: boolean;
|
|
13
14
|
readonly checks?: readonly DoctorRule[];
|
|
14
15
|
}
|
|
15
16
|
/**
|
|
16
|
-
* `gaon doctor` 진입점 (M9-E · 확장).
|
|
17
|
+
* `gaon doctor` 진입점 (M9-E · 확장 · M9-E-Fix 하드닝).
|
|
17
18
|
*
|
|
18
|
-
* 반환: DoctorResult({passed, warnings, errors}).
|
|
19
|
-
* exit code 는 진입점(runCli)이
|
|
19
|
+
* 반환: DoctorResult({passed, warnings, errors, fatal?}).
|
|
20
|
+
* exit code 는 진입점(runCli)이 결정한다:
|
|
21
|
+
* - result.fatal 이 있으면 → exit 2 (사용자 오류 · 크래시 아님)
|
|
22
|
+
* - result.errors.length > 0 → exit 1
|
|
23
|
+
* - 그 외 → exit 0
|
|
24
|
+
*
|
|
25
|
+
* 사전 검사 2 단계:
|
|
26
|
+
* 1) detectProject — gaon 프로젝트 마커가 하나도 없으면 fatal:no-project
|
|
27
|
+
* 로 종료(예: 사용자가 실수로 빈 폴더에서 실행).
|
|
28
|
+
* 2) checkTypeScriptApi — typescript 컴파일러 API 가 없으면(예: 사용자
|
|
29
|
+
* 프로젝트가 typescript@7 을 받았을 때 default export 가 스텁이라
|
|
30
|
+
* ts.ScriptTarget 이 undefined) fatal:ts-api-missing 로 종료 —
|
|
31
|
+
* "Cannot read properties of undefined (reading 'ES2022')" 크래시 대신.
|
|
32
|
+
* 규칙 실행 중 예외는 각 규칙 단위로 catch → 그 규칙만 error 로 리포트,
|
|
33
|
+
* 나머지 규칙은 계속 실행(부분 결과 확보).
|
|
20
34
|
*/
|
|
21
35
|
export declare function runDoctorCommand(opts?: DoctorCommandOptions): Promise<DoctorResult>;
|
|
22
36
|
export interface DoctorIssue {
|
package/dist/doctor.js
CHANGED
|
@@ -16,12 +16,14 @@
|
|
|
16
16
|
* 그대로 export — 기존 테스트가 계속 동작한다.
|
|
17
17
|
*/
|
|
18
18
|
import { resolve } from 'node:path';
|
|
19
|
+
import ts from 'typescript';
|
|
19
20
|
import { checkResponseMixing } from './doctor/response-mixing.js';
|
|
20
21
|
import { checkNPlusOne } from './doctor/n-plus-one.js';
|
|
21
22
|
import { checkDependencyDirection } from './doctor/dependency-direction.js';
|
|
22
23
|
import { checkConnections } from './doctor/connections.js';
|
|
23
24
|
import { checkMigrationDiff } from './doctor/migration-diff.js';
|
|
24
25
|
import { renderHuman, renderJson } from './doctor/reporter.js';
|
|
26
|
+
import { checkTypeScriptApi, detectProject, fatalNoProject, fatalTsApiMissing, } from './doctor/setup.js';
|
|
25
27
|
import { makeResult, } from './doctor/types.js';
|
|
26
28
|
export { inspectControllerSource, checkResponseMixing } from './doctor/response-mixing.js';
|
|
27
29
|
export { inspectControllerForNPlusOne, checkNPlusOne } from './doctor/n-plus-one.js';
|
|
@@ -29,6 +31,7 @@ export { extractRelativeImports, checkDependencyDirection } from './doctor/depen
|
|
|
29
31
|
export { extractConfigDbKeys, extractKeyUses, checkConnections } from './doctor/connections.js';
|
|
30
32
|
export { scanSchema, checkMigrationDiff } from './doctor/migration-diff.js';
|
|
31
33
|
export { renderHuman, renderJson } from './doctor/reporter.js';
|
|
34
|
+
export { checkTypeScriptApi, detectProject, fatalNoProject, fatalTsApiMissing, } from './doctor/setup.js';
|
|
32
35
|
/**
|
|
33
36
|
* 실행할 검사 이름. 지정 없음(undefined) = 5개 모두.
|
|
34
37
|
*/
|
|
@@ -47,22 +50,78 @@ const CHECKERS = {
|
|
|
47
50
|
'migration-diff': checkMigrationDiff,
|
|
48
51
|
};
|
|
49
52
|
/**
|
|
50
|
-
* `gaon doctor` 진입점 (M9-E · 확장).
|
|
53
|
+
* `gaon doctor` 진입점 (M9-E · 확장 · M9-E-Fix 하드닝).
|
|
51
54
|
*
|
|
52
|
-
* 반환: DoctorResult({passed, warnings, errors}).
|
|
53
|
-
* exit code 는 진입점(runCli)이
|
|
55
|
+
* 반환: DoctorResult({passed, warnings, errors, fatal?}).
|
|
56
|
+
* exit code 는 진입점(runCli)이 결정한다:
|
|
57
|
+
* - result.fatal 이 있으면 → exit 2 (사용자 오류 · 크래시 아님)
|
|
58
|
+
* - result.errors.length > 0 → exit 1
|
|
59
|
+
* - 그 외 → exit 0
|
|
60
|
+
*
|
|
61
|
+
* 사전 검사 2 단계:
|
|
62
|
+
* 1) detectProject — gaon 프로젝트 마커가 하나도 없으면 fatal:no-project
|
|
63
|
+
* 로 종료(예: 사용자가 실수로 빈 폴더에서 실행).
|
|
64
|
+
* 2) checkTypeScriptApi — typescript 컴파일러 API 가 없으면(예: 사용자
|
|
65
|
+
* 프로젝트가 typescript@7 을 받았을 때 default export 가 스텁이라
|
|
66
|
+
* ts.ScriptTarget 이 undefined) fatal:ts-api-missing 로 종료 —
|
|
67
|
+
* "Cannot read properties of undefined (reading 'ES2022')" 크래시 대신.
|
|
68
|
+
* 규칙 실행 중 예외는 각 규칙 단위로 catch → 그 규칙만 error 로 리포트,
|
|
69
|
+
* 나머지 규칙은 계속 실행(부분 결과 확보).
|
|
54
70
|
*/
|
|
55
71
|
export async function runDoctorCommand(opts = {}) {
|
|
56
72
|
const cwd = opts.cwd ?? process.cwd();
|
|
57
73
|
const root = resolve(cwd);
|
|
74
|
+
const json = opts.json ?? false;
|
|
75
|
+
// 1) 프로젝트 마커 검사
|
|
76
|
+
if (!detectProject(root)) {
|
|
77
|
+
const result = {
|
|
78
|
+
passed: [],
|
|
79
|
+
warnings: [],
|
|
80
|
+
errors: [],
|
|
81
|
+
fatal: fatalNoProject(root),
|
|
82
|
+
};
|
|
83
|
+
process.stdout.write((json ? renderJson(result) : renderHuman(result)) + '\n');
|
|
84
|
+
return result;
|
|
85
|
+
}
|
|
86
|
+
// 2) typescript compiler API 검사
|
|
87
|
+
if (!checkTypeScriptApi()) {
|
|
88
|
+
const installed = ts.version;
|
|
89
|
+
const result = {
|
|
90
|
+
passed: [],
|
|
91
|
+
warnings: [],
|
|
92
|
+
errors: [],
|
|
93
|
+
fatal: fatalTsApiMissing(installed),
|
|
94
|
+
};
|
|
95
|
+
process.stdout.write((json ? renderJson(result) : renderHuman(result)) + '\n');
|
|
96
|
+
return result;
|
|
97
|
+
}
|
|
58
98
|
const rules = opts.checks && opts.checks.length ? opts.checks : ALL_RULES;
|
|
59
99
|
const reports = [];
|
|
60
100
|
for (const rule of rules) {
|
|
61
101
|
const fn = CHECKERS[rule];
|
|
62
|
-
|
|
102
|
+
try {
|
|
103
|
+
reports.push(await fn(root));
|
|
104
|
+
}
|
|
105
|
+
catch (err) {
|
|
106
|
+
// 규칙 하나가 크래시해도 나머지 규칙 실행은 계속한다. 사용자는
|
|
107
|
+
// 크래시 대신 어느 규칙이 왜 실패했는지 안내받는다.
|
|
108
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
109
|
+
reports.push({
|
|
110
|
+
rule,
|
|
111
|
+
issues: [
|
|
112
|
+
{
|
|
113
|
+
rule,
|
|
114
|
+
level: 'error',
|
|
115
|
+
message: `${rule} 검사 중 예외 발생: ${msg}\n` +
|
|
116
|
+
`→ 나머지 규칙만 임시로 실행하려면 '--check=<다른 규칙>' 를 쓰세요.`,
|
|
117
|
+
detail: { thrown: msg },
|
|
118
|
+
},
|
|
119
|
+
],
|
|
120
|
+
});
|
|
121
|
+
}
|
|
63
122
|
}
|
|
64
123
|
const result = makeResult(reports);
|
|
65
|
-
const out =
|
|
124
|
+
const out = json ? renderJson(result) : renderHuman(result);
|
|
66
125
|
process.stdout.write(out + '\n');
|
|
67
126
|
return result;
|
|
68
127
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { type DoctorRule } from "./doctor.js";
|
|
2
|
-
export {
|
|
2
|
+
export { startDev, resolveDevLayout, type DevDeps, type DevLayout, type DevApp, type DevEvent, type DevHandle, } from "./dev.js";
|
|
3
|
+
export { runDevCommand, type DevCommandOptions } from "./commands/dev.js";
|
|
4
|
+
export { createDevConsole, findComposeFile, isDockerAvailable, inspectCompose, composeUp, composeDown, ensureInfra, startTscWatchers, killChild, startRestartWatcher, isRestartChange, resolveWatchRoots, type DevConsole, type DevConsoleOptions, type DevSource, type DevLevel, type ComposeStatus, type ComposeUpOptions, type EnsureInfraResult, type DockerLocateOptions, type TscWatcherOptions, type TscWatcherHandle, type RestartWatcherOptions, type RestartWatcherHandle, } from "./dev/index.js";
|
|
3
5
|
export { runCheck, runCheckCommand, type CheckDeps, type CheckResult, type TypecheckResult, type CheckCommandOptions, } from "./check.js";
|
|
4
6
|
export { writeAuthScaffold, authScaffoldFiles, patchRoutes, runGenerateAuthCommand, type AuthScaffoldOptions, type ScaffoldFile, type ScaffoldResult, type GenerateAuthOptions, } from "./generate.js";
|
|
5
7
|
export { runGenerateCommand, planScaffold, parseGenerateArgs, type GenerateType, type GenerateOptions, type GenerateResult, } from "./commands/g.js";
|
|
@@ -8,6 +10,8 @@ export { runServeCommand, type ServeCommandOptions } from "./serve.js";
|
|
|
8
10
|
export { runWorkCommand, type WorkCommandOptions } from "./work.js";
|
|
9
11
|
export { runJobsCommand, type JobsCommandOptions } from "./jobs.js";
|
|
10
12
|
export { runDbSeedCommand, loadSeed, type DbSeedOptions, type DbSeedResult } from "./db.js";
|
|
13
|
+
export { runDbCommand, type DbSubcommand, type DbCommandOptions, } from "./commands/db.js";
|
|
14
|
+
export { runDbDiff, runDbMigrate, runDbReset, resolveDbTarget, type DbDiffOptions, type DbDiffResult, type DbMigrateOptions, type DbMigrateResult, type DbResetOptions, type DbResetResult, type ResolveDbOptions, type ResolvedDbTarget, } from "./db/index.js";
|
|
11
15
|
export { runDoctorCommand, runDoctor, inspectControllerSource, checkResponseMixing, checkNPlusOne, checkDependencyDirection, checkConnections, checkMigrationDiff, renderHuman, renderJson, type DoctorResult, type DoctorCheck, type DoctorLevel, type DoctorRule, type RuleReport, type DoctorIssue, type LegacyDoctorResult, type ActionUsage, type ResponseKind, type DoctorCommandOptions, } from "./doctor.js";
|
|
12
16
|
export { loadDomain, type LoadedDomain } from "./domain.js";
|
|
13
17
|
export interface RoadmapReport {
|
package/dist/index.js
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @gaonjs/cli — Gaon CLI 구현.
|
|
3
3
|
*
|
|
4
|
-
* 인자 없이 실행하면 로드맵·개발 상태를 출력하는 스텁이다.
|
|
5
|
-
* `gaon dev` 가
|
|
6
|
-
*
|
|
7
|
-
* (
|
|
4
|
+
* 인자 없이 실행하면 로드맵·개발 상태를 출력하는 스텁이다. M9-C 부터
|
|
5
|
+
* `gaon dev` 가 통합 개발 오케스트레이션을 담당한다 — Docker Compose
|
|
6
|
+
* 자동 기동 + .gaon 타입 브리지 재생성 + serve 자식 프로세스 + tsc/vue-tsc
|
|
7
|
+
* --watch + 소스 변경 시 서버 재시작(commands/dev.ts). 모든 명령은 `--json`
|
|
8
|
+
* 출력을 함께 제공한다 (CLAUDE.md §4).
|
|
8
9
|
*
|
|
9
10
|
* 표시 버전은 호출자(파사드)가 주입한다 — 사용자가 설치한 패키지
|
|
10
11
|
* (`gaonjs`) 버전을 그대로 보여주기 위함. 미주입 시 core 버전을 쓴다.
|
|
11
12
|
*/
|
|
12
13
|
import { MILESTONES, VERSION, HOMEPAGE } from "@gaonjs/core";
|
|
13
|
-
import { runDevCommand } from "./dev.js";
|
|
14
|
+
import { runDevCommand } from "./commands/dev.js";
|
|
14
15
|
import { runCheckCommand } from "./check.js";
|
|
15
16
|
import { runGenerateAuthCommand } from "./generate.js";
|
|
16
17
|
import { runGenerateCommand } from "./commands/g.js";
|
|
@@ -18,9 +19,11 @@ import { runHubCommand } from "./hub.js";
|
|
|
18
19
|
import { runServeCommand } from "./serve.js";
|
|
19
20
|
import { runWorkCommand } from "./work.js";
|
|
20
21
|
import { runJobsCommand } from "./jobs.js";
|
|
21
|
-
import {
|
|
22
|
+
import { runDbCommand } from "./commands/db.js";
|
|
22
23
|
import { runDoctorCommand } from "./doctor.js";
|
|
23
|
-
export {
|
|
24
|
+
export { startDev, resolveDevLayout, } from "./dev.js";
|
|
25
|
+
export { runDevCommand } from "./commands/dev.js";
|
|
26
|
+
export { createDevConsole, findComposeFile, isDockerAvailable, inspectCompose, composeUp, composeDown, ensureInfra, startTscWatchers, killChild, startRestartWatcher, isRestartChange, resolveWatchRoots, } from "./dev/index.js";
|
|
24
27
|
export { runCheck, runCheckCommand, } from "./check.js";
|
|
25
28
|
export { writeAuthScaffold, authScaffoldFiles, patchRoutes, runGenerateAuthCommand, } from "./generate.js";
|
|
26
29
|
export { runGenerateCommand, planScaffold, parseGenerateArgs, } from "./commands/g.js";
|
|
@@ -29,6 +32,8 @@ export { runServeCommand } from "./serve.js";
|
|
|
29
32
|
export { runWorkCommand } from "./work.js";
|
|
30
33
|
export { runJobsCommand } from "./jobs.js";
|
|
31
34
|
export { runDbSeedCommand, loadSeed } from "./db.js";
|
|
35
|
+
export { runDbCommand, } from "./commands/db.js";
|
|
36
|
+
export { runDbDiff, runDbMigrate, runDbReset, resolveDbTarget, } from "./db/index.js";
|
|
32
37
|
export { runDoctorCommand, runDoctor, inspectControllerSource, checkResponseMixing, checkNPlusOne, checkDependencyDirection, checkConnections, checkMigrationDiff, renderHuman, renderJson, } from "./doctor.js";
|
|
33
38
|
export { loadDomain } from "./domain.js";
|
|
34
39
|
/** `--json` 출력용 구조화 리포트. */
|
|
@@ -74,8 +79,11 @@ function renderHelp(version = VERSION) {
|
|
|
74
79
|
"",
|
|
75
80
|
" 사용법:",
|
|
76
81
|
" gaon 로드맵과 개발 상태를 출력",
|
|
77
|
-
" gaon dev .gaon
|
|
78
|
-
" gaon dev --
|
|
82
|
+
" gaon dev 개발 스택 통합 (Docker · .gaon · serve · tsc/vue-tsc · 재시작 워처)",
|
|
83
|
+
" gaon dev --stop-docker Ctrl+C 시 Docker Compose 도 down",
|
|
84
|
+
" gaon dev --no-watch|--no-tsc|--no-vue-tsc|--no-docker 개별 debug 옵션",
|
|
85
|
+
" gaon dev --port <n> --host <h> serve 리슨 지정",
|
|
86
|
+
" gaon dev --json 통합 콘솔을 JSON 라인으로 출력(자동화)",
|
|
79
87
|
" gaon serve 웹 서버 부팅 (gaon.config.ts 자동 배선 · Fastify listen)",
|
|
80
88
|
" gaon serve --port <n> --host <h> 리슨 포트·호스트 (config 값을 덮음)",
|
|
81
89
|
" gaon check .gaon 재생성 후 타입 검사 (CI·AI 정합)",
|
|
@@ -92,6 +100,11 @@ function renderHelp(version = VERSION) {
|
|
|
92
100
|
" gaon work 워커 프로세스 (잡·리스너·스케줄·아웃박스 · graceful drain)",
|
|
93
101
|
" gaon jobs list --failed DLQ(실패 잡) 목록",
|
|
94
102
|
" gaon jobs retry <id> DLQ 잡 재적재",
|
|
103
|
+
" gaon db diff 스키마 ↔ DB 차이 계산 (적용 X · --json · --db <키>)",
|
|
104
|
+
" gaon db migrate 스키마 변경을 실제 적용 + _gaon_migrations 이력",
|
|
105
|
+
" gaon db migrate --dry-run 적용 없이 up SQL 만 출력",
|
|
106
|
+
" gaon db reset --yes DROP ALL → 재마이그 → seed (--yes 필수 · production 거부)",
|
|
107
|
+
" gaon db seed domain/seed.ts 실행 (M8)",
|
|
95
108
|
" gaon --json 같은 정보를 JSON 으로 출력",
|
|
96
109
|
" gaon --version 버전 출력",
|
|
97
110
|
" gaon --help 이 도움말",
|
|
@@ -127,10 +140,26 @@ export function parseDoctorChecks(argv) {
|
|
|
127
140
|
/** CLI 진입점. argv 는 실행 인자(process.argv.slice(2))를 받는다. */
|
|
128
141
|
export function runCli(argv, opts = {}) {
|
|
129
142
|
const version = opts.version ?? VERSION;
|
|
130
|
-
// `gaon dev` —
|
|
131
|
-
//
|
|
143
|
+
// `gaon dev` — 통합 개발 오케스트레이션(M9-C · v0.15 §13.5). Docker Compose
|
|
144
|
+
// 자동 기동 + .gaon 재생성 + serve 자식 + tsc/vue-tsc watch + 서버 재시작 워처.
|
|
145
|
+
// SIGINT/SIGTERM 시 순서대로 정리(serve → tsc → 워처 → Docker[--stop-docker 시]).
|
|
146
|
+
// 개별 debug 옵션은 --no-watch / --no-tsc / --no-vue-tsc / --no-docker 뿐.
|
|
132
147
|
if (argv[0] === "dev") {
|
|
133
|
-
|
|
148
|
+
const portIdx = argv.indexOf("--port");
|
|
149
|
+
const hostIdx = argv.indexOf("--host");
|
|
150
|
+
const port = portIdx >= 0 ? Number(argv[portIdx + 1]) : undefined;
|
|
151
|
+
const host = hostIdx >= 0 ? argv[hostIdx + 1] : undefined;
|
|
152
|
+
void runDevCommand({
|
|
153
|
+
json: argv.includes("--json"),
|
|
154
|
+
stopDocker: argv.includes("--stop-docker"),
|
|
155
|
+
noWatch: argv.includes("--no-watch"),
|
|
156
|
+
noTsc: argv.includes("--no-tsc"),
|
|
157
|
+
noVueTsc: argv.includes("--no-vue-tsc"),
|
|
158
|
+
noDocker: argv.includes("--no-docker"),
|
|
159
|
+
timestamp: argv.includes("--timestamp"),
|
|
160
|
+
port,
|
|
161
|
+
host,
|
|
162
|
+
}).catch((err) => {
|
|
134
163
|
const msg = err instanceof Error ? err.message : String(err);
|
|
135
164
|
process.stderr.write(` ✗ gaon dev 실패: ${msg}\n`);
|
|
136
165
|
process.exitCode = 1;
|
|
@@ -164,11 +193,16 @@ export function runCli(argv, opts = {}) {
|
|
|
164
193
|
return;
|
|
165
194
|
}
|
|
166
195
|
// `gaon doctor` — 정적 검사(M9-E · 5 검사). --check=<이름>[,<이름>...] 로
|
|
167
|
-
// 선택 실행, --json 은 자동화 파싱용.
|
|
196
|
+
// 선택 실행, --json 은 자동화 파싱용.
|
|
197
|
+
// exit code (M9-E-Fix): fatal → 2(사용자 오류) / errors > 0 → 1 / 그 외 → 0.
|
|
168
198
|
if (argv[0] === "doctor") {
|
|
169
199
|
const checks = parseDoctorChecks(argv);
|
|
170
200
|
void runDoctorCommand({ json: argv.includes("--json"), checks })
|
|
171
201
|
.then((result) => {
|
|
202
|
+
if (result.fatal) {
|
|
203
|
+
process.exitCode = 2;
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
172
206
|
process.exitCode = result.errors.length > 0 ? 1 : 0;
|
|
173
207
|
})
|
|
174
208
|
.catch((err) => {
|
|
@@ -211,17 +245,33 @@ export function runCli(argv, opts = {}) {
|
|
|
211
245
|
});
|
|
212
246
|
return;
|
|
213
247
|
}
|
|
214
|
-
// `gaon db
|
|
215
|
-
if (argv[0] === "db"
|
|
216
|
-
const
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
process.
|
|
220
|
-
|
|
248
|
+
// `gaon db <sub>` — diff · migrate · reset · seed (§7 M8/M9-D).
|
|
249
|
+
if (argv[0] === "db") {
|
|
250
|
+
const sub = argv[1];
|
|
251
|
+
const known = ["diff", "migrate", "reset", "seed"];
|
|
252
|
+
if (!sub || !known.includes(sub)) {
|
|
253
|
+
process.stderr.write(` ✗ 알 수 없는 db 서브커맨드: ${sub ?? "(없음)"}\n` +
|
|
254
|
+
` → 지원: gaon db diff | migrate | reset | seed\n` +
|
|
255
|
+
` → 옵션: --json · --db <키> · --config <path> · --yes · --dry-run\n`);
|
|
256
|
+
process.exitCode = 1;
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
const dbIdx = argv.indexOf("--db");
|
|
260
|
+
const cfgIdx = argv.indexOf("--config");
|
|
261
|
+
const dbOpts = {
|
|
262
|
+
json: argv.includes("--json"),
|
|
263
|
+
db: dbIdx >= 0 ? argv[dbIdx + 1] : undefined,
|
|
264
|
+
config: cfgIdx >= 0 ? argv[cfgIdx + 1] : undefined,
|
|
265
|
+
yes: argv.includes("--yes"),
|
|
266
|
+
dryRun: argv.includes("--dry-run"),
|
|
267
|
+
};
|
|
268
|
+
void runDbCommand(sub, dbOpts)
|
|
269
|
+
.then((code) => {
|
|
270
|
+
process.exitCode = code;
|
|
221
271
|
})
|
|
222
272
|
.catch((err) => {
|
|
223
273
|
const msg = err instanceof Error ? err.message : String(err);
|
|
224
|
-
process.stderr.write(` ✗ gaon db
|
|
274
|
+
process.stderr.write(` ✗ gaon db ${sub} 실패: ${msg}\n`);
|
|
225
275
|
process.exitCode = 1;
|
|
226
276
|
});
|
|
227
277
|
return;
|
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,13 +24,13 @@
|
|
|
24
24
|
"README.md"
|
|
25
25
|
],
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"typescript": "
|
|
28
|
-
"@gaonjs/config": "0.1.0",
|
|
27
|
+
"typescript": "^5.9.0",
|
|
29
28
|
"@gaonjs/async": "0.2.2",
|
|
29
|
+
"@gaonjs/config": "0.1.0",
|
|
30
30
|
"@gaonjs/core": "0.1.4",
|
|
31
|
+
"@gaonjs/mail": "0.1.0",
|
|
31
32
|
"@gaonjs/data": "0.3.0",
|
|
32
|
-
"@gaonjs/web": "0.3.0"
|
|
33
|
-
"@gaonjs/mail": "0.1.0"
|
|
33
|
+
"@gaonjs/web": "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})\""
|