@gaonjs/cli 0.14.0 → 0.18.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/dev.js +3 -1
- package/dist/commands/g.js +8 -0
- package/dist/commands/new.js +5 -0
- package/dist/dev/health.d.ts +75 -0
- package/dist/dev/health.js +156 -0
- package/dist/doctor/types.d.ts +1 -1
- package/dist/doctor/types.js +3 -3
- package/dist/doctor/ui-kit-wiring.d.ts +5 -0
- package/dist/doctor/ui-kit-wiring.js +93 -0
- package/dist/doctor.d.ts +13 -0
- package/dist/doctor.js +57 -26
- package/dist/generate.js +13 -2
- package/dist/index.d.ts +4 -2
- package/dist/index.js +20 -6
- package/dist/scaffold/app-wiring.d.ts +12 -0
- package/dist/scaffold/app-wiring.js +68 -0
- package/dist/serve.d.ts +6 -0
- package/dist/serve.js +9 -0
- package/dist/templates/auth/Dashboard.vue.tpl +21 -5
- package/dist/templates/auth/Login.vue.tpl +37 -8
- package/dist/templates/auth/Signup.vue.tpl +40 -9
- package/dist/templates/auth/registration.controller.ts.tpl +1 -1
- package/dist/templates/auth/session.controller.ts.tpl +2 -2
- package/dist/templates/project/AGENTS.md.tpl +3 -2
- package/dist/templates/project/CLAUDE.md.tpl +3 -2
- package/dist/templates/project/agents/async.md.tpl +15 -7
- package/dist/templates/project/agents/frontend.md.tpl +79 -1
- package/dist/templates/project/apps/web/composables/useGaonHealth.ts.tpl +81 -0
- package/dist/templates/project/apps/web/layouts/Default.vue.tpl +30 -29
- package/dist/templates/project/apps/web/main.ts.tpl +4 -0
- package/dist/templates/project/apps/web/pages/Home/Index.vue.tpl +225 -25
- package/dist/templates/project/apps/web/style.css.tpl +66 -0
- package/dist/templates/project/package.json.tpl +3 -0
- package/dist/templates/project/postcss.config.js.tpl +15 -0
- package/dist/templates/project/tailwind.config.ts.tpl +56 -0
- package/dist/templates/ui-kit/Alert.vue.tpl +23 -0
- package/dist/templates/ui-kit/AlertDescription.vue.tpl +9 -0
- package/dist/templates/ui-kit/AlertTitle.vue.tpl +9 -0
- package/dist/templates/ui-kit/Badge.vue.tpl +25 -0
- package/dist/templates/ui-kit/Button.vue.tpl +39 -0
- package/dist/templates/ui-kit/Card.vue.tpl +10 -0
- package/dist/templates/ui-kit/CardContent.vue.tpl +9 -0
- package/dist/templates/ui-kit/CardDescription.vue.tpl +9 -0
- package/dist/templates/ui-kit/CardFooter.vue.tpl +9 -0
- package/dist/templates/ui-kit/CardHeader.vue.tpl +9 -0
- package/dist/templates/ui-kit/CardTitle.vue.tpl +9 -0
- package/dist/templates/ui-kit/Dialog.vue.tpl +68 -0
- package/dist/templates/ui-kit/Form.vue.tpl +13 -0
- package/dist/templates/ui-kit/FormField.vue.tpl +16 -0
- package/dist/templates/ui-kit/FormMessage.vue.tpl +9 -0
- package/dist/templates/ui-kit/Input.vue.tpl +22 -0
- package/dist/templates/ui-kit/Label.vue.tpl +9 -0
- package/dist/templates/ui-kit/Sheet.vue.tpl +73 -0
- package/dist/templates/ui-kit/utils.ts.tpl +26 -0
- package/dist/uikit.d.ts +28 -0
- package/dist/uikit.js +138 -0
- package/package.json +6 -6
package/dist/commands/dev.js
CHANGED
|
@@ -54,7 +54,9 @@ function resolveSelfCliEntry() {
|
|
|
54
54
|
*/
|
|
55
55
|
function spawnServe(args) {
|
|
56
56
|
const { node, args: nodeArgs } = resolveSelfCliEntry();
|
|
57
|
-
|
|
57
|
+
// --dev: dev 전용 진단 라우트(/_gaon/health)를 켠다(결정 69). gaon dev 의
|
|
58
|
+
// serve 자식만 이 플래그를 받으므로 운영 serve 에는 진단이 노출되지 않는다.
|
|
59
|
+
const serveArgs = ['serve', '--dev'];
|
|
58
60
|
if (args.json)
|
|
59
61
|
serveArgs.push('--json');
|
|
60
62
|
if (args.port != null)
|
package/dist/commands/g.js
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
import { existsSync } from 'node:fs';
|
|
18
18
|
import { join } from 'node:path';
|
|
19
19
|
import { appScaffoldFiles, controllerScaffold, inflectModel, jobScaffold, jobTestScaffold, modelScaffoldFiles, pageScaffold, writeScaffold, } from '../scaffold/index.js';
|
|
20
|
+
import { appWiringFiles, readProjectName } from '../scaffold/app-wiring.js';
|
|
20
21
|
/** argv 에서 옵션을 뽑는다(간단 파서 · runCli 관례와 일치). */
|
|
21
22
|
export function parseGenerateArgs(argv) {
|
|
22
23
|
const type = argv[0];
|
|
@@ -114,6 +115,13 @@ export function runGenerateCommand(type, name, opts = {}) {
|
|
|
114
115
|
return 1;
|
|
115
116
|
}
|
|
116
117
|
}
|
|
118
|
+
// gaon g app: web 앱과 동등한 프론트 배선(style.css·main.ts·index.html)을
|
|
119
|
+
// 함께 심는다 — 이후 gaon g ui-kit --app <name> 이 심는 UI 킷 컴포넌트가
|
|
120
|
+
// Tailwind 유틸을 받아 스타일대로 렌더되도록(결정 76). web 정본 템플릿에서
|
|
121
|
+
// 파생하므로 드리프트가 없다(scaffold/app-wiring).
|
|
122
|
+
if (type === 'app') {
|
|
123
|
+
files = [...files, ...appWiringFiles(name, readProjectName(cwd))];
|
|
124
|
+
}
|
|
117
125
|
const write = writeScaffold(cwd, files, { overwrite: opts.overwrite });
|
|
118
126
|
// overwrite 없이 기존 파일이 있으면 실패(사고 방지 · Rails 관례).
|
|
119
127
|
const failed = write.skipped.length > 0;
|
package/dist/commands/new.js
CHANGED
|
@@ -23,6 +23,7 @@ import { spawnSync } from 'node:child_process';
|
|
|
23
23
|
import { fileURLToPath } from 'node:url';
|
|
24
24
|
import { readFileSync } from 'node:fs';
|
|
25
25
|
import { renderProjectFiles } from '../templates/index.js';
|
|
26
|
+
import { writeUiKitScaffold } from '../uikit.js';
|
|
26
27
|
/** 이름 유효성 — npm 패키지명 규칙(단순 부분)만 검사. */
|
|
27
28
|
function validateProjectName(name) {
|
|
28
29
|
if (!name)
|
|
@@ -175,6 +176,10 @@ export async function runNewCommand(name, opts = {}) {
|
|
|
175
176
|
try {
|
|
176
177
|
mkdirSync(root, { recursive: true });
|
|
177
178
|
filesCreated = writeAll(root, files);
|
|
179
|
+
// 결정 74·75: 기본 앱(web)에 UI 킷을 심는다(배터리 포함 · 단일 소스
|
|
180
|
+
// templates/ui-kit). 랜딩(Home/Index.vue)이 Card 를 쓰고, gaon g auth 도
|
|
181
|
+
// 이 세트를 재사용한다. 정적 중복 없이 프로그램적으로 생성한다.
|
|
182
|
+
filesCreated += writeUiKitScaffold(root, { app: 'web' }).created.length;
|
|
178
183
|
}
|
|
179
184
|
catch (err) {
|
|
180
185
|
return emitError(`파일 생성 실패: ${err instanceof Error ? err.message : String(err)}`, Date.now() - t0);
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { type GaonNats, type StreamStat, type ChannelStat } from '@gaonjs/async';
|
|
2
|
+
import type { GaonConfig } from '@gaonjs/config';
|
|
3
|
+
/** registerDevHealth 가 프로브에 쓰는 컨텍스트(serve 가 채운다). */
|
|
4
|
+
export interface DevHealthContext {
|
|
5
|
+
readonly cwd: string;
|
|
6
|
+
readonly config: GaonConfig;
|
|
7
|
+
readonly apps: readonly {
|
|
8
|
+
readonly name: string;
|
|
9
|
+
readonly dir: string;
|
|
10
|
+
}[];
|
|
11
|
+
readonly nats?: GaonNats;
|
|
12
|
+
readonly port: number;
|
|
13
|
+
readonly host: string;
|
|
14
|
+
}
|
|
15
|
+
export interface WebHealth {
|
|
16
|
+
readonly host: string;
|
|
17
|
+
readonly port: number;
|
|
18
|
+
readonly node: string;
|
|
19
|
+
readonly gaonjs: string;
|
|
20
|
+
readonly apps: readonly {
|
|
21
|
+
readonly name: string;
|
|
22
|
+
readonly prefix: string;
|
|
23
|
+
}[];
|
|
24
|
+
}
|
|
25
|
+
export type DatabaseHealth = {
|
|
26
|
+
readonly configured: false;
|
|
27
|
+
} | {
|
|
28
|
+
readonly configured: true;
|
|
29
|
+
readonly connected: boolean;
|
|
30
|
+
readonly adapter?: string;
|
|
31
|
+
readonly tables?: number;
|
|
32
|
+
readonly tableNames?: readonly string[];
|
|
33
|
+
readonly migrations?: number;
|
|
34
|
+
readonly error?: string;
|
|
35
|
+
};
|
|
36
|
+
export type HubHealth = {
|
|
37
|
+
readonly configured: false;
|
|
38
|
+
} | {
|
|
39
|
+
readonly configured: true;
|
|
40
|
+
readonly connected: boolean;
|
|
41
|
+
/** 잡·이벤트 스트림의 실 대기(pending) 메시지 수(결정 71). */
|
|
42
|
+
readonly streams?: readonly StreamStat[];
|
|
43
|
+
/** 채널별 실 접속자 수(프레즌스 KV 권위). null = 아직 아무도 접속 안 함(결정 71). */
|
|
44
|
+
readonly channels?: readonly ChannelStat[] | null;
|
|
45
|
+
readonly error?: string;
|
|
46
|
+
};
|
|
47
|
+
export interface DoctorHealth {
|
|
48
|
+
readonly checks: number;
|
|
49
|
+
readonly passed: number;
|
|
50
|
+
readonly warnings: number;
|
|
51
|
+
readonly errors: number;
|
|
52
|
+
readonly level: 'pass' | 'warn' | 'error';
|
|
53
|
+
/** 사전 검사 실패(프로젝트 아님 등)면 코드. */
|
|
54
|
+
readonly fatal?: string;
|
|
55
|
+
}
|
|
56
|
+
export interface GaonHealth {
|
|
57
|
+
readonly ok: boolean;
|
|
58
|
+
readonly env: string;
|
|
59
|
+
readonly web: WebHealth;
|
|
60
|
+
readonly database: DatabaseHealth;
|
|
61
|
+
readonly hub: HubHealth;
|
|
62
|
+
readonly doctor: DoctorHealth;
|
|
63
|
+
/** 랜딩 코드 블록용 — 실 routes.ts 원문(dev only). */
|
|
64
|
+
readonly routes: {
|
|
65
|
+
readonly path: string;
|
|
66
|
+
readonly source: string;
|
|
67
|
+
} | null;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* 라이브 헬스 스냅샷을 계산한다. 순수 조회(부작용 없음) — 각 프로브는 독립
|
|
71
|
+
* try/catch 라 하나가 실패해도 전체가 죽지 않는다.
|
|
72
|
+
*/
|
|
73
|
+
export declare function computeHealth(ctx: DevHealthContext): Promise<GaonHealth>;
|
|
74
|
+
/** dev 전용 진단 라우트 경로. serve(--dev)가 이 경로에 computeHealth 를 건다. */
|
|
75
|
+
export declare const DEV_HEALTH_PATH = "/_gaon/health";
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @gaonjs/cli · dev 전용 라이브 헬스 엔드포인트 (결정 69 · W10)
|
|
3
|
+
*
|
|
4
|
+
* `GET /_gaon/health` — 방금 `gaon new` 로 만든 앱이 "실제로 살아 있는지" 를
|
|
5
|
+
* 랜딩(Home/Index.vue)이 라이브로 보여주기 위한 dev 전용 진단 소스다.
|
|
6
|
+
*
|
|
7
|
+
* dev-only by construction:
|
|
8
|
+
* `gaon dev` 는 `gaon serve` 를 자식으로 띄우고(commands/dev.ts), 그때
|
|
9
|
+
* serve 에 `--dev` 를 넘긴다. serve 는 `--dev` 일 때만 이 라우트를 등록한다
|
|
10
|
+
* (serve.ts). 운영 `gaon serve`(플래그 없음)는 등록조차 하지 않으므로
|
|
11
|
+
* production 에 노출되지 않는다(§CLAUDE.md 6 · handover-w10 §3.5·§5).
|
|
12
|
+
*
|
|
13
|
+
* 층 배치: 이 모듈은 doctor(computeDoctorResult)·data introspection·async
|
|
14
|
+
* 스트림 통계를 import 하므로 **cli 층**에 둔다. web/config 에 두면
|
|
15
|
+
* cli→web/config→cli 역방향 사이클이 된다(handover-w10 §5-3).
|
|
16
|
+
*
|
|
17
|
+
* 모든 프로브는 개별 try/catch — 하나가 실패해도 나머지 카드는 채운다.
|
|
18
|
+
* 조회 실패를 0/true 로 가장하지 않는다(결정 60 · 과대 약속 금지).
|
|
19
|
+
*/
|
|
20
|
+
import { readFileSync } from 'node:fs';
|
|
21
|
+
import { join } from 'node:path';
|
|
22
|
+
import { VERSION } from '@gaonjs/core';
|
|
23
|
+
import { getConnection, hasConnection, snapshotFromDb, postgresDialect, MIGRATIONS_TABLE, } from '@gaonjs/data';
|
|
24
|
+
import { streamStats, presenceStats } from '@gaonjs/async';
|
|
25
|
+
import { computeDoctorResult } from '../doctor.js';
|
|
26
|
+
/** web 앱은 관례상 프리픽스 '/', 그 외는 '/<앱>'(dispatch.prefixFor 와 정합). */
|
|
27
|
+
function prefixFor(name) {
|
|
28
|
+
return name === 'web' ? '/' : `/${name}`;
|
|
29
|
+
}
|
|
30
|
+
async function probeDatabase(config) {
|
|
31
|
+
const main = config.db?.main;
|
|
32
|
+
if (!main)
|
|
33
|
+
return { configured: false };
|
|
34
|
+
const adapter = main.adapter;
|
|
35
|
+
try {
|
|
36
|
+
if (!hasConnection('main')) {
|
|
37
|
+
return { configured: true, connected: false, adapter, error: '커넥션 미등록' };
|
|
38
|
+
}
|
|
39
|
+
const db = getConnection('main');
|
|
40
|
+
// introspect 자체가 실 쿼리 — DB 가 죽어 있으면 여기서 throw → connected:false.
|
|
41
|
+
const snap = await snapshotFromDb(db, postgresDialect);
|
|
42
|
+
const tableNames = Object.keys(snap)
|
|
43
|
+
.filter((t) => t !== MIGRATIONS_TABLE)
|
|
44
|
+
.sort();
|
|
45
|
+
let migrations = 0;
|
|
46
|
+
try {
|
|
47
|
+
const row = await db
|
|
48
|
+
.selectFrom(MIGRATIONS_TABLE)
|
|
49
|
+
.select((eb) => eb.fn.countAll().as('c'))
|
|
50
|
+
.executeTakeFirst();
|
|
51
|
+
migrations = Number(row?.c ?? 0);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// _gaon_migrations 미생성(마이그레이션 이력 없음) — 0.
|
|
55
|
+
migrations = 0;
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
configured: true,
|
|
59
|
+
connected: true,
|
|
60
|
+
adapter,
|
|
61
|
+
tables: tableNames.length,
|
|
62
|
+
tableNames,
|
|
63
|
+
migrations,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
return {
|
|
68
|
+
configured: true,
|
|
69
|
+
connected: false,
|
|
70
|
+
adapter,
|
|
71
|
+
error: err instanceof Error ? err.message : String(err),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
async function probeHub(config, nats) {
|
|
76
|
+
if (!config.nats)
|
|
77
|
+
return { configured: false };
|
|
78
|
+
if (!nats)
|
|
79
|
+
return { configured: true, connected: false };
|
|
80
|
+
const connected = !nats.nc.isClosed();
|
|
81
|
+
if (!connected)
|
|
82
|
+
return { configured: true, connected: false };
|
|
83
|
+
try {
|
|
84
|
+
// jobs = JetStream 스트림 대기 수 · channels = 프레즌스 KV 접속자 수.
|
|
85
|
+
// 둘 다 실 introspection(하드코딩·가장 없음 · 결정 71).
|
|
86
|
+
const [streams, channels] = await Promise.all([streamStats(nats), presenceStats(nats)]);
|
|
87
|
+
return { configured: true, connected: true, streams, channels };
|
|
88
|
+
}
|
|
89
|
+
catch (err) {
|
|
90
|
+
return {
|
|
91
|
+
configured: true,
|
|
92
|
+
connected: true,
|
|
93
|
+
error: err instanceof Error ? err.message : String(err),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
async function probeDoctor(cwd) {
|
|
98
|
+
try {
|
|
99
|
+
const result = await computeDoctorResult({ cwd });
|
|
100
|
+
if (result.fatal) {
|
|
101
|
+
return { checks: 0, passed: 0, warnings: 0, errors: 0, level: 'error', fatal: result.fatal.code };
|
|
102
|
+
}
|
|
103
|
+
const passed = result.passed.length;
|
|
104
|
+
const warnings = result.warnings.length;
|
|
105
|
+
const errors = result.errors.length;
|
|
106
|
+
const level = errors > 0 ? 'error' : warnings > 0 ? 'warn' : 'pass';
|
|
107
|
+
return { checks: passed + warnings + errors, passed, warnings, errors, level };
|
|
108
|
+
}
|
|
109
|
+
catch (err) {
|
|
110
|
+
return {
|
|
111
|
+
checks: 0,
|
|
112
|
+
passed: 0,
|
|
113
|
+
warnings: 0,
|
|
114
|
+
errors: 0,
|
|
115
|
+
level: 'error',
|
|
116
|
+
fatal: err instanceof Error ? err.message : String(err),
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/** 랜딩 코드 블록용 routes.ts 원문. web 앱 우선, 없으면 첫 앱. 실패는 null. */
|
|
121
|
+
function readRoutesSource(ctx) {
|
|
122
|
+
const web = ctx.apps.find((a) => a.name === 'web') ?? ctx.apps[0];
|
|
123
|
+
if (!web)
|
|
124
|
+
return null;
|
|
125
|
+
const path = join(web.dir, 'routes.ts');
|
|
126
|
+
try {
|
|
127
|
+
return { path: `apps/${web.name}/routes.ts`, source: readFileSync(path, 'utf8') };
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* 라이브 헬스 스냅샷을 계산한다. 순수 조회(부작용 없음) — 각 프로브는 독립
|
|
135
|
+
* try/catch 라 하나가 실패해도 전체가 죽지 않는다.
|
|
136
|
+
*/
|
|
137
|
+
export async function computeHealth(ctx) {
|
|
138
|
+
const [database, hub, doctor] = await Promise.all([
|
|
139
|
+
probeDatabase(ctx.config),
|
|
140
|
+
probeHub(ctx.config, ctx.nats),
|
|
141
|
+
probeDoctor(ctx.cwd),
|
|
142
|
+
]);
|
|
143
|
+
const web = {
|
|
144
|
+
host: ctx.host,
|
|
145
|
+
port: ctx.port,
|
|
146
|
+
node: process.version,
|
|
147
|
+
gaonjs: VERSION,
|
|
148
|
+
apps: ctx.apps.map((a) => ({ name: a.name, prefix: prefixFor(a.name) })),
|
|
149
|
+
};
|
|
150
|
+
const ok = doctor.level !== 'error' &&
|
|
151
|
+
(database.configured === false || database.connected) &&
|
|
152
|
+
(hub.configured === false || hub.connected);
|
|
153
|
+
return { ok, env: process.env.NODE_ENV ?? 'development', web, database, hub, doctor, routes: readRoutesSource(ctx) };
|
|
154
|
+
}
|
|
155
|
+
/** dev 전용 진단 라우트 경로. serve(--dev)가 이 경로에 computeHealth 를 건다. */
|
|
156
|
+
export const DEV_HEALTH_PATH = '/_gaon/health';
|
package/dist/doctor/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type DoctorRule = 'response-mixing' | 'n-plus-one' | 'dependency-direction' | 'connections' | 'migration-diff' | 'shared-composable-purity' | 'no-auto-import' | 'schema-filename' | 'agents-doc-index' | 'column-casing' | 'model-filename' | 'page-filename' | 'auth-wiring';
|
|
1
|
+
export type DoctorRule = 'response-mixing' | 'n-plus-one' | 'dependency-direction' | 'connections' | 'migration-diff' | 'shared-composable-purity' | 'no-auto-import' | 'schema-filename' | 'agents-doc-index' | 'column-casing' | 'model-filename' | 'page-filename' | 'auth-wiring' | 'ui-kit-wiring';
|
|
2
2
|
export type DoctorLevel = 'passed' | 'warning' | 'error';
|
|
3
3
|
export interface DoctorCheck {
|
|
4
4
|
readonly rule: DoctorRule;
|
package/dist/doctor/types.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// @gaonjs/cli · doctor 공용 타입 (M9-E · M9-E-Fix · M9-E 확장 · E-5)
|
|
2
2
|
//
|
|
3
|
-
//
|
|
3
|
+
// 14 검사(response-mixing · n-plus-one · dependency-direction · connections
|
|
4
4
|
// · migration-diff · shared-composable-purity · no-auto-import · schema-filename
|
|
5
|
-
// · agents-doc-index · column-casing · model-filename · page-filename
|
|
6
|
-
// 이 DoctorCheck 를 낸다. 상위(runDoctorCommand)는 level 로 passed/
|
|
5
|
+
// · agents-doc-index · column-casing · model-filename · page-filename · auth-wiring
|
|
6
|
+
// · ui-kit-wiring)가 모두 이 DoctorCheck 를 낸다. 상위(runDoctorCommand)는 level 로 passed/
|
|
7
7
|
// warnings/errors 로 갈라 담는다. 자동화(CI)는 JSON 을 파싱해
|
|
8
8
|
// errors.length > 0 이면 fail 로 판단한다.
|
|
9
9
|
//
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { RuleReport } from './types.js';
|
|
2
|
+
/** import 경로가 UI 킷 컴포넌트(components/ui/…)를 가리키는지(단위 테스트 진입점). */
|
|
3
|
+
export declare function importsUiKit(source: string): boolean;
|
|
4
|
+
/** apps/ 를 훑어 UI 킷 import ↔ Tailwind 배선 누락을 낸다. */
|
|
5
|
+
export declare function checkUiKitWiring(cwd: string): Promise<RuleReport>;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// @gaonjs/cli · doctor · UI 킷 배선 검사 (결정 76)
|
|
2
|
+
//
|
|
3
|
+
// 앱이 UI 킷 컴포넌트(apps/<앱>/components/ui/*)를 import 하는데 그 앱에
|
|
4
|
+
// Tailwind 배선(apps/<앱>/style.css 의 @tailwind + main.ts 의 style.css import)이
|
|
5
|
+
// 없으면, UI 킷이 쓰는 유틸 클래스(bg-primary·rounded-lg …)가 펼쳐지지 않아
|
|
6
|
+
// 컴포넌트가 스타일 없이 렌더된다 — 컴파일은 통과하므로 check 로는 안 잡히는
|
|
7
|
+
// 조용한 런타임 파손이다(결정 74·75·76). 이 검사가 그 상태를 경고로 낸다.
|
|
8
|
+
//
|
|
9
|
+
// `gaon new`(web)·`gaon g app` 은 이제 배선을 함께 심으므로 정상 경로에서는
|
|
10
|
+
// 걸리지 않는다. 이 규칙은 배선을 지웠거나 구버전 스캐폴드에서 만든 앱을 잡는
|
|
11
|
+
// 안전망이다. 판정은 소스 텍스트 기반(가벼운 정적 검사).
|
|
12
|
+
import { readdir, readFile } from 'node:fs/promises';
|
|
13
|
+
import { join, relative } from 'node:path';
|
|
14
|
+
import { hasAppWiring } from '../scaffold/app-wiring.js';
|
|
15
|
+
/** import 경로가 UI 킷 컴포넌트(components/ui/…)를 가리키는지(단위 테스트 진입점). */
|
|
16
|
+
export function importsUiKit(source) {
|
|
17
|
+
return /from\s+['"][^'"]*\bcomponents\/ui\/[^'"]+['"]/.test(source);
|
|
18
|
+
}
|
|
19
|
+
/** apps/ 를 훑어 UI 킷 import ↔ Tailwind 배선 누락을 낸다. */
|
|
20
|
+
export async function checkUiKitWiring(cwd) {
|
|
21
|
+
const appsDir = join(cwd, 'apps');
|
|
22
|
+
const issues = [];
|
|
23
|
+
for (const app of await safeListDirs(appsDir)) {
|
|
24
|
+
// UI 킷을 import 하는 첫 파일을 찾는다.
|
|
25
|
+
const importer = await findUiKitImporter(join(appsDir, app), cwd);
|
|
26
|
+
if (!importer)
|
|
27
|
+
continue;
|
|
28
|
+
if (hasAppWiring(cwd, app))
|
|
29
|
+
continue;
|
|
30
|
+
issues.push({
|
|
31
|
+
rule: 'ui-kit-wiring',
|
|
32
|
+
level: 'warning',
|
|
33
|
+
file: importer,
|
|
34
|
+
message: `UI 킷 배선 누락: apps/${app} 이 UI 킷 컴포넌트(${importer})를 import 하는데 ` +
|
|
35
|
+
`apps/${app}/style.css 의 @tailwind 배선(또는 main.ts 의 style.css import)이 없습니다. ` +
|
|
36
|
+
`이대로면 UI 킷의 Tailwind 유틸 클래스가 펼쳐지지 않아 컴포넌트가 스타일 없이 렌더됩니다(결정 76).\n` +
|
|
37
|
+
`→ apps/${app}/style.css 에 @tailwind 지시자를 추가하거나 \`gaon g ui-kit --app ${app}\` 을 실행하세요 ` +
|
|
38
|
+
`(후자는 배선까지 멱등으로 보정합니다).`,
|
|
39
|
+
detail: { app, importer },
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
return { rule: 'ui-kit-wiring', issues };
|
|
43
|
+
}
|
|
44
|
+
/** 앱 디렉터리를 재귀 스캔해 UI 킷을 import 하는 첫 .vue/.ts 파일(cwd 상대)을 찾는다. */
|
|
45
|
+
async function findUiKitImporter(appDir, cwd) {
|
|
46
|
+
for (const abs of await walkSources(appDir)) {
|
|
47
|
+
// components/ui 안의 컴포넌트 자체는 서로를 import 하므로 제외한다.
|
|
48
|
+
if (abs.includes(`${join(appDir, 'components', 'ui')}`))
|
|
49
|
+
continue;
|
|
50
|
+
const source = await readFile(abs, 'utf8').catch(() => '');
|
|
51
|
+
if (importsUiKit(source))
|
|
52
|
+
return relative(cwd, abs);
|
|
53
|
+
}
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
/** 디렉터리 트리에서 .vue·.ts(테스트·선언 제외) 파일 절대경로를 모은다. */
|
|
57
|
+
async function walkSources(dir) {
|
|
58
|
+
const out = [];
|
|
59
|
+
const walk = async (d) => {
|
|
60
|
+
let entries;
|
|
61
|
+
try {
|
|
62
|
+
entries = await readdir(d, { withFileTypes: true });
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
for (const e of entries) {
|
|
68
|
+
const abs = join(d, e.name);
|
|
69
|
+
if (e.isDirectory()) {
|
|
70
|
+
if (e.name === 'node_modules' || e.name === '.gaon')
|
|
71
|
+
continue;
|
|
72
|
+
await walk(abs);
|
|
73
|
+
}
|
|
74
|
+
else if (e.isFile()) {
|
|
75
|
+
if (e.name.endsWith('.d.ts') || e.name.endsWith('.test.ts'))
|
|
76
|
+
continue;
|
|
77
|
+
if (e.name.endsWith('.vue') || e.name.endsWith('.ts'))
|
|
78
|
+
out.push(abs);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
await walk(dir);
|
|
83
|
+
return out.sort();
|
|
84
|
+
}
|
|
85
|
+
async function safeListDirs(dir) {
|
|
86
|
+
try {
|
|
87
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
88
|
+
return entries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
return [];
|
|
92
|
+
}
|
|
93
|
+
}
|
package/dist/doctor.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ export { expectedColumnName, extractSnakeColumns, checkColumnCasing, } from './d
|
|
|
14
14
|
export { isPascalCase, checkModelFilename } from './doctor/model-filename.js';
|
|
15
15
|
export { checkPageFilename } from './doctor/page-filename.js';
|
|
16
16
|
export { usesAuthSurface, hasAuthWiring, hasSessionWiring, checkAuthWiring, } from './doctor/auth-wiring.js';
|
|
17
|
+
export { importsUiKit, checkUiKitWiring } from './doctor/ui-kit-wiring.js';
|
|
17
18
|
export { renderHuman, renderJson } from './doctor/reporter.js';
|
|
18
19
|
export { checkTypeScriptApi, detectProject, fatalNoProject, fatalTsApiMissing, } from './doctor/setup.js';
|
|
19
20
|
export interface DoctorCommandOptions {
|
|
@@ -52,6 +53,18 @@ export interface DoctorFixReport {
|
|
|
52
53
|
/** 실제로 편집이 일어났는지(--yes 여부). */
|
|
53
54
|
readonly applied: boolean;
|
|
54
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* doctor 검사를 실행해 결과만 반환한다 — **stdout 에 아무것도 쓰지 않는다.**
|
|
58
|
+
* runDoctorCommand 는 CLI 진입점이라 결과를 stdout 으로 흘리지만, 프로그램에서
|
|
59
|
+
* (예: dev 헬스 엔드포인트가) 결과가 필요할 때는 이 함수를 쓴다.
|
|
60
|
+
*
|
|
61
|
+
* fatal(프로젝트 아님 · TS API 없음)도 write 없이 DoctorResult.fatal 로 담아
|
|
62
|
+
* 반환한다. --fix 는 다루지 않는다(읽기 전용).
|
|
63
|
+
*/
|
|
64
|
+
export declare function computeDoctorResult(opts?: {
|
|
65
|
+
readonly cwd?: string;
|
|
66
|
+
readonly checks?: readonly DoctorRule[];
|
|
67
|
+
}): Promise<DoctorResult>;
|
|
55
68
|
/**
|
|
56
69
|
* `gaon doctor` 진입점 (M9-E · 확장 · M9-E-Fix 하드닝).
|
|
57
70
|
*
|
package/dist/doctor.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @gaonjs/cli · `gaon doctor` — 정적 검사 (M9-E · CLI DX 완성 · E-5 확장)
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* 14 검사를 조립한다:
|
|
5
5
|
* 1) response-mixing (errata E-3 §C · 라이브)
|
|
6
6
|
* 2) n-plus-one (errata E-4 (e))
|
|
7
7
|
* 3) dependency-direction (CLAUDE.md §5 · 4 규칙)
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
* 11) model-filename (§3.4 · 결정 32·46 · 모델 파일명 PascalCase)
|
|
16
16
|
* 12) page-filename (§3.4 · 결정 32·46 · Vue 페이지 파일명 PascalCase)
|
|
17
17
|
* 13) auth-wiring (§7 · 결정 59 · requireAuth ↔ app.config.ts auth 배선)
|
|
18
|
+
* 14) ui-kit-wiring (§6.4 · 결정 76 · UI 킷 import ↔ apps/<앱>/style.css Tailwind 배선)
|
|
18
19
|
*
|
|
19
20
|
* 각 검사는 순수 함수(cwd → RuleReport). 상위 runDoctorCommand 가 조립해
|
|
20
21
|
* DoctorResult 로 낸다. --json 은 자동화(CI)를 위해 반드시 파싱 가능한
|
|
@@ -40,6 +41,7 @@ import { checkColumnCasing } from './doctor/column-casing.js';
|
|
|
40
41
|
import { checkModelFilename } from './doctor/model-filename.js';
|
|
41
42
|
import { checkPageFilename } from './doctor/page-filename.js';
|
|
42
43
|
import { checkAuthWiring } from './doctor/auth-wiring.js';
|
|
44
|
+
import { checkUiKitWiring } from './doctor/ui-kit-wiring.js';
|
|
43
45
|
import { renderHuman, renderJson } from './doctor/reporter.js';
|
|
44
46
|
import { checkTypeScriptApi, detectProject, fatalNoProject, fatalTsApiMissing, } from './doctor/setup.js';
|
|
45
47
|
import { makeResult, } from './doctor/types.js';
|
|
@@ -57,10 +59,11 @@ export { expectedColumnName, extractSnakeColumns, checkColumnCasing, } from './d
|
|
|
57
59
|
export { isPascalCase, checkModelFilename } from './doctor/model-filename.js';
|
|
58
60
|
export { checkPageFilename } from './doctor/page-filename.js';
|
|
59
61
|
export { usesAuthSurface, hasAuthWiring, hasSessionWiring, checkAuthWiring, } from './doctor/auth-wiring.js';
|
|
62
|
+
export { importsUiKit, checkUiKitWiring } from './doctor/ui-kit-wiring.js';
|
|
60
63
|
export { renderHuman, renderJson } from './doctor/reporter.js';
|
|
61
64
|
export { checkTypeScriptApi, detectProject, fatalNoProject, fatalTsApiMissing, } from './doctor/setup.js';
|
|
62
65
|
/**
|
|
63
|
-
* 실행할 검사 이름. 지정 없음(undefined) =
|
|
66
|
+
* 실행할 검사 이름. 지정 없음(undefined) = 14개 모두.
|
|
64
67
|
*/
|
|
65
68
|
const ALL_RULES = [
|
|
66
69
|
'response-mixing',
|
|
@@ -76,6 +79,7 @@ const ALL_RULES = [
|
|
|
76
79
|
'model-filename',
|
|
77
80
|
'page-filename',
|
|
78
81
|
'auth-wiring',
|
|
82
|
+
'ui-kit-wiring',
|
|
79
83
|
];
|
|
80
84
|
const CHECKERS = {
|
|
81
85
|
'response-mixing': checkResponseMixing,
|
|
@@ -91,7 +95,57 @@ const CHECKERS = {
|
|
|
91
95
|
'model-filename': checkModelFilename,
|
|
92
96
|
'page-filename': checkPageFilename,
|
|
93
97
|
'auth-wiring': checkAuthWiring,
|
|
98
|
+
'ui-kit-wiring': checkUiKitWiring,
|
|
94
99
|
};
|
|
100
|
+
/**
|
|
101
|
+
* 규칙을 순서대로 실행해 RuleReport[] 를 낸다. 규칙 하나가 크래시해도 나머지는
|
|
102
|
+
* 계속 — 그 규칙만 error 리포트로 대체한다(부분 결과 확보).
|
|
103
|
+
*/
|
|
104
|
+
async function runRules(root, rules) {
|
|
105
|
+
const reports = [];
|
|
106
|
+
for (const rule of rules) {
|
|
107
|
+
const fn = CHECKERS[rule];
|
|
108
|
+
try {
|
|
109
|
+
reports.push(await fn(root));
|
|
110
|
+
}
|
|
111
|
+
catch (err) {
|
|
112
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
113
|
+
reports.push({
|
|
114
|
+
rule,
|
|
115
|
+
issues: [
|
|
116
|
+
{
|
|
117
|
+
rule,
|
|
118
|
+
level: 'error',
|
|
119
|
+
message: `${rule} 검사 중 예외 발생: ${msg}\n` +
|
|
120
|
+
`→ 나머지 규칙만 임시로 실행하려면 '--check=<다른 규칙>' 를 쓰세요.`,
|
|
121
|
+
detail: { thrown: msg },
|
|
122
|
+
},
|
|
123
|
+
],
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return reports;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* doctor 검사를 실행해 결과만 반환한다 — **stdout 에 아무것도 쓰지 않는다.**
|
|
131
|
+
* runDoctorCommand 는 CLI 진입점이라 결과를 stdout 으로 흘리지만, 프로그램에서
|
|
132
|
+
* (예: dev 헬스 엔드포인트가) 결과가 필요할 때는 이 함수를 쓴다.
|
|
133
|
+
*
|
|
134
|
+
* fatal(프로젝트 아님 · TS API 없음)도 write 없이 DoctorResult.fatal 로 담아
|
|
135
|
+
* 반환한다. --fix 는 다루지 않는다(읽기 전용).
|
|
136
|
+
*/
|
|
137
|
+
export async function computeDoctorResult(opts = {}) {
|
|
138
|
+
const root = resolve(opts.cwd ?? process.cwd());
|
|
139
|
+
if (!detectProject(root)) {
|
|
140
|
+
return { passed: [], warnings: [], errors: [], fatal: fatalNoProject(root) };
|
|
141
|
+
}
|
|
142
|
+
if (!checkTypeScriptApi()) {
|
|
143
|
+
const installed = ts.version;
|
|
144
|
+
return { passed: [], warnings: [], errors: [], fatal: fatalTsApiMissing(installed) };
|
|
145
|
+
}
|
|
146
|
+
const rules = opts.checks && opts.checks.length ? opts.checks : ALL_RULES;
|
|
147
|
+
return makeResult(await runRules(root, rules));
|
|
148
|
+
}
|
|
95
149
|
/**
|
|
96
150
|
* `gaon doctor` 진입점 (M9-E · 확장 · M9-E-Fix 하드닝).
|
|
97
151
|
*
|
|
@@ -139,30 +193,7 @@ export async function runDoctorCommand(opts = {}) {
|
|
|
139
193
|
return result;
|
|
140
194
|
}
|
|
141
195
|
const rules = opts.checks && opts.checks.length ? opts.checks : ALL_RULES;
|
|
142
|
-
const reports =
|
|
143
|
-
for (const rule of rules) {
|
|
144
|
-
const fn = CHECKERS[rule];
|
|
145
|
-
try {
|
|
146
|
-
reports.push(await fn(root));
|
|
147
|
-
}
|
|
148
|
-
catch (err) {
|
|
149
|
-
// 규칙 하나가 크래시해도 나머지 규칙 실행은 계속한다. 사용자는
|
|
150
|
-
// 크래시 대신 어느 규칙이 왜 실패했는지 안내받는다.
|
|
151
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
152
|
-
reports.push({
|
|
153
|
-
rule,
|
|
154
|
-
issues: [
|
|
155
|
-
{
|
|
156
|
-
rule,
|
|
157
|
-
level: 'error',
|
|
158
|
-
message: `${rule} 검사 중 예외 발생: ${msg}\n` +
|
|
159
|
-
`→ 나머지 규칙만 임시로 실행하려면 '--check=<다른 규칙>' 를 쓰세요.`,
|
|
160
|
-
detail: { thrown: msg },
|
|
161
|
-
},
|
|
162
|
-
],
|
|
163
|
-
});
|
|
164
|
-
}
|
|
165
|
-
}
|
|
196
|
+
const reports = await runRules(root, rules);
|
|
166
197
|
const baseResult = makeResult(reports);
|
|
167
198
|
// --fix 가 없으면 종전 동작 그대로.
|
|
168
199
|
if (!opts.fix) {
|
package/dist/generate.js
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
16
16
|
import { dirname, join, resolve } from 'node:path';
|
|
17
17
|
import { fileURLToPath } from 'node:url';
|
|
18
|
+
import { authUiKitFiles, writeUiKitFiles } from './uikit.js';
|
|
18
19
|
// ── 템플릿 로드·치환 ───────────────────────────────────────────
|
|
19
20
|
// 템플릿은 이 모듈과 같은 위치의 templates/auth/ 에 있다(빌드가 dist 로 복사).
|
|
20
21
|
const TEMPLATE_DIR = join(dirname(fileURLToPath(import.meta.url)), 'templates', 'auth');
|
|
@@ -32,8 +33,11 @@ const TEMPLATES = [
|
|
|
32
33
|
{ tpl: 'session.controller.ts.tpl', out: (a) => `apps/${a}/controllers/session.ts` },
|
|
33
34
|
{ tpl: 'registration.controller.ts.tpl', out: (a) => `apps/${a}/controllers/registration.ts` },
|
|
34
35
|
{ tpl: 'dashboard.controller.ts.tpl', out: (a) => `apps/${a}/controllers/dashboard.ts` },
|
|
35
|
-
|
|
36
|
-
|
|
36
|
+
// 결정 32·46: Vue 페이지 경로 세그먼트는 PascalCase(Route 이름) — 'Auth/'.
|
|
37
|
+
// examples/blog 정본과 page-filename doctor 규칙에 정합(소문자 'auth/' 는
|
|
38
|
+
// doctor 가 error 로 잡던 스캐폴드 표류였다 · W10 실측).
|
|
39
|
+
{ tpl: 'Login.vue.tpl', out: (a) => `apps/${a}/pages/Auth/Login.vue` },
|
|
40
|
+
{ tpl: 'Signup.vue.tpl', out: (a) => `apps/${a}/pages/Auth/Signup.vue` },
|
|
37
41
|
{ tpl: 'Dashboard.vue.tpl', out: (a) => `apps/${a}/pages/Dashboard.vue` },
|
|
38
42
|
// 결정 59: 세션·인증 배선은 app.config.ts — 표준 부팅(gaon dev/serve = wireGaon)이
|
|
39
43
|
// 소비한다. 과거의 수동 부팅 스캐폴드(app.ts·server.ts)는 두 번째 부팅 경로를
|
|
@@ -73,6 +77,13 @@ export function writeAuthScaffold(cwd, opts = {}) {
|
|
|
73
77
|
const skipped = [];
|
|
74
78
|
const patched = [];
|
|
75
79
|
const warnings = [];
|
|
80
|
+
// 결정 75: auth 페이지(Login/Signup/Dashboard)는 UI 킷 컴포넌트를 쓴다.
|
|
81
|
+
// 필요한 최소 세트를 먼저 보장한다 — 이미 있으면(gaon g ui-kit 를 먼저 돌린
|
|
82
|
+
// 경우) skip, 없으면 생성. 이 보장이 없으면 스캐폴드 직후 페이지가 컴포넌트를
|
|
83
|
+
// 해상하지 못해 vue-tsc 가 깨진다(gaon new → g auth 단독 경로 · 결정 60 게이트).
|
|
84
|
+
const ui = writeUiKitFiles(root, authUiKitFiles(app));
|
|
85
|
+
created.push(...ui.created);
|
|
86
|
+
skipped.push(...ui.skipped);
|
|
76
87
|
for (const file of authScaffoldFiles(opts)) {
|
|
77
88
|
const abs = join(root, file.path);
|
|
78
89
|
if (existsSync(abs)) {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type DoctorRule } from "./doctor.js";
|
|
2
|
-
export { startDev, resolveDevLayout, type DevDeps, type DevLayout, type DevApp, type DevEvent, type DevHandle, } from "./dev.js";
|
|
2
|
+
export { startDev, resolveDevLayout, regenerateGaonOnce, type DevDeps, type DevLayout, type DevApp, type DevEvent, type DevHandle, type RegenDeps, type RegenResult, } from "./dev.js";
|
|
3
3
|
export { runDevCommand, type DevCommandOptions } from "./commands/dev.js";
|
|
4
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";
|
|
5
5
|
export { runCheckCommand, type CheckCommandOptions, type CheckStep, type CheckStepStatus, type CheckStepResult, } from "./commands/check.js";
|
|
@@ -7,15 +7,17 @@ export { runNewCommand, type NewCommandOptions, type NewCommandResult } from "./
|
|
|
7
7
|
export { runConsoleCommand, type ConsoleCommandOptions } from "./commands/console.js";
|
|
8
8
|
export { runTestCommand, type TestCommandOptions, type TestScope } from "./commands/test.js";
|
|
9
9
|
export { writeAuthScaffold, authScaffoldFiles, patchRoutes, runGenerateAuthCommand, type AuthScaffoldOptions, type ScaffoldFile, type ScaffoldResult, type GenerateAuthOptions, } from "./generate.js";
|
|
10
|
+
export { writeUiKitScaffold, writeUiKitFiles, uiKitScaffoldFiles, authUiKitFiles, runGenerateUiKitCommand, type UiKitScaffoldOptions, type UiKitScaffoldFile, type UiKitScaffoldResult, type GenerateUiKitOptions, } from "./uikit.js";
|
|
10
11
|
export { runGenerateCommand, planScaffold, parseGenerateArgs, type GenerateType, type GenerateOptions, type GenerateResult, } from "./commands/g.js";
|
|
11
12
|
export { runHubCommand, type HubCommandOptions } from "./hub.js";
|
|
12
13
|
export { runServeCommand, type ServeCommandOptions } from "./serve.js";
|
|
14
|
+
export { computeHealth, DEV_HEALTH_PATH, type DevHealthContext, type GaonHealth, } from "./dev/health.js";
|
|
13
15
|
export { runWorkCommand, type WorkCommandOptions } from "./work.js";
|
|
14
16
|
export { runJobsCommand, type JobsCommandOptions } from "./jobs.js";
|
|
15
17
|
export { runDbSeedCommand, loadSeed, type DbSeedOptions, type DbSeedResult } from "./db.js";
|
|
16
18
|
export { runDbCommand, type DbSubcommand, type DbCommandOptions, } from "./commands/db.js";
|
|
17
19
|
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";
|
|
18
|
-
export { runDoctorCommand, runDoctor, runDoctorFix, renderFixHuman, inspectControllerSource, checkResponseMixing, checkNPlusOne, checkDependencyDirection, checkConnections, checkMigrationDiff, renderHuman, renderJson, type DoctorResult, type DoctorResultWithFix, type DoctorCheck, type DoctorLevel, type DoctorRule, type RuleReport, type DoctorIssue, type LegacyDoctorResult, type ActionUsage, type ResponseKind, type DoctorCommandOptions, type FixOutcome, type DoctorFixReport, } from "./doctor.js";
|
|
20
|
+
export { runDoctorCommand, computeDoctorResult, runDoctor, runDoctorFix, renderFixHuman, inspectControllerSource, checkResponseMixing, checkNPlusOne, checkDependencyDirection, checkConnections, checkMigrationDiff, renderHuman, renderJson, type DoctorResult, type DoctorResultWithFix, type DoctorCheck, type DoctorLevel, type DoctorRule, type RuleReport, type DoctorIssue, type LegacyDoctorResult, type ActionUsage, type ResponseKind, type DoctorCommandOptions, type FixOutcome, type DoctorFixReport, } from "./doctor.js";
|
|
19
21
|
export { FIXERS, FIXER_CAPABILITIES, fixDependencyDirection, fixDomainToSharedTypeOnly, type Fixer, type FixerCapability, type FixerPlan, } from "./doctor/fixers/index.js";
|
|
20
22
|
export { loadDomain, type LoadedDomain } from "./domain.js";
|
|
21
23
|
export interface RoadmapReport {
|