@mandujs/cli 0.9.12 → 0.9.18
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/README.md +224 -98
- package/package.json +2 -2
- package/src/commands/check.ts +204 -0
- package/src/commands/dev.ts +204 -67
- package/src/commands/doctor.ts +2 -2
- package/src/commands/guard-arch.ts +256 -0
- package/src/commands/routes.ts +218 -0
- package/src/main.ts +138 -42
- package/src/util/fs.ts +23 -4
- package/src/util/output.ts +41 -0
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FS Routes CLI Commands
|
|
3
|
+
*
|
|
4
|
+
* 파일 시스템 기반 라우트 관리 명령어
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
scanRoutes,
|
|
9
|
+
generateManifest,
|
|
10
|
+
formatRoutesForCLI,
|
|
11
|
+
watchFSRoutes,
|
|
12
|
+
type GenerateOptions,
|
|
13
|
+
type FSScannerConfig,
|
|
14
|
+
} from "@mandujs/core";
|
|
15
|
+
import { resolveFromCwd } from "../util/fs";
|
|
16
|
+
|
|
17
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
18
|
+
// Types
|
|
19
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
20
|
+
|
|
21
|
+
export interface RoutesGenerateOptions {
|
|
22
|
+
/** 출력 파일 경로 */
|
|
23
|
+
output?: string;
|
|
24
|
+
/** 상세 출력 */
|
|
25
|
+
verbose?: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface RoutesListOptions {
|
|
29
|
+
/** 상세 출력 */
|
|
30
|
+
verbose?: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface RoutesWatchOptions {
|
|
34
|
+
/** 출력 파일 경로 */
|
|
35
|
+
output?: string;
|
|
36
|
+
/** 상세 출력 */
|
|
37
|
+
verbose?: boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
41
|
+
// Commands
|
|
42
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* routes generate - FS Routes 스캔 및 매니페스트 생성
|
|
46
|
+
*/
|
|
47
|
+
export async function routesGenerate(options: RoutesGenerateOptions = {}): Promise<boolean> {
|
|
48
|
+
const rootDir = resolveFromCwd(".");
|
|
49
|
+
|
|
50
|
+
console.log("🥟 Mandu FS Routes Generate\n");
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
const generateOptions: GenerateOptions = {
|
|
54
|
+
outputPath: options.output ?? ".mandu/routes.manifest.json",
|
|
55
|
+
skipLegacy: true, // 레거시 병합 비활성화
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const result = await generateManifest(rootDir, generateOptions);
|
|
59
|
+
|
|
60
|
+
// 결과 출력
|
|
61
|
+
console.log(`✅ FS Routes 스캔 완료`);
|
|
62
|
+
console.log(` 📋 라우트: ${result.manifest.routes.length}개\n`);
|
|
63
|
+
|
|
64
|
+
// 경고 출력
|
|
65
|
+
if (result.warnings.length > 0) {
|
|
66
|
+
console.log("⚠️ 경고:");
|
|
67
|
+
for (const warning of result.warnings) {
|
|
68
|
+
console.log(` - ${warning}`);
|
|
69
|
+
}
|
|
70
|
+
console.log("");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// 라우트 목록 출력
|
|
74
|
+
if (options.verbose) {
|
|
75
|
+
console.log(formatRoutesForCLI(result.manifest));
|
|
76
|
+
console.log("");
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// 출력 파일 경로
|
|
80
|
+
if (generateOptions.outputPath) {
|
|
81
|
+
console.log(`📁 매니페스트 저장: ${generateOptions.outputPath}`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return true;
|
|
85
|
+
} catch (error) {
|
|
86
|
+
console.error("❌ FS Routes 생성 실패:", error instanceof Error ? error.message : error);
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* routes list - 현재 라우트 목록 출력
|
|
93
|
+
*/
|
|
94
|
+
export async function routesList(options: RoutesListOptions = {}): Promise<boolean> {
|
|
95
|
+
const rootDir = resolveFromCwd(".");
|
|
96
|
+
|
|
97
|
+
console.log("🥟 Mandu Routes List\n");
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
const result = await scanRoutes(rootDir);
|
|
101
|
+
|
|
102
|
+
if (result.errors.length > 0) {
|
|
103
|
+
console.log("⚠️ 스캔 경고:");
|
|
104
|
+
for (const error of result.errors) {
|
|
105
|
+
console.log(` - ${error.type}: ${error.message}`);
|
|
106
|
+
}
|
|
107
|
+
console.log("");
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (result.routes.length === 0) {
|
|
111
|
+
console.log("📭 라우트가 없습니다.");
|
|
112
|
+
console.log("");
|
|
113
|
+
console.log("💡 app/ 폴더에 page.tsx 또는 route.ts 파일을 생성하세요.");
|
|
114
|
+
console.log("");
|
|
115
|
+
console.log("예시:");
|
|
116
|
+
console.log(" app/page.tsx → /");
|
|
117
|
+
console.log(" app/blog/page.tsx → /blog");
|
|
118
|
+
console.log(" app/api/users/route.ts → /api/users");
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// 라우트 목록 출력
|
|
123
|
+
console.log(`📋 라우트 (${result.routes.length}개)`);
|
|
124
|
+
console.log("─".repeat(70));
|
|
125
|
+
|
|
126
|
+
for (const route of result.routes) {
|
|
127
|
+
const icon = route.kind === "page" ? "📄" : "📡";
|
|
128
|
+
const hydration = route.clientModule ? " 🏝️" : "";
|
|
129
|
+
const pattern = route.pattern.padEnd(35);
|
|
130
|
+
const id = route.id;
|
|
131
|
+
|
|
132
|
+
console.log(`${icon} ${pattern} → ${id}${hydration}`);
|
|
133
|
+
|
|
134
|
+
if (options.verbose) {
|
|
135
|
+
console.log(` 📁 ${route.sourceFile}`);
|
|
136
|
+
if (route.clientModule) {
|
|
137
|
+
console.log(` 🏝️ ${route.clientModule}`);
|
|
138
|
+
}
|
|
139
|
+
if (route.layoutChain.length > 0) {
|
|
140
|
+
console.log(` 📐 layouts: ${route.layoutChain.join(" → ")}`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
console.log("");
|
|
146
|
+
|
|
147
|
+
// 통계
|
|
148
|
+
console.log("📊 통계");
|
|
149
|
+
console.log(` 페이지: ${result.stats.pageCount}개`);
|
|
150
|
+
console.log(` API: ${result.stats.apiCount}개`);
|
|
151
|
+
console.log(` 레이아웃: ${result.stats.layoutCount}개`);
|
|
152
|
+
console.log(` Island: ${result.stats.islandCount}개`);
|
|
153
|
+
console.log(` 스캔 시간: ${result.stats.scanTime}ms`);
|
|
154
|
+
|
|
155
|
+
return true;
|
|
156
|
+
} catch (error) {
|
|
157
|
+
console.error("❌ 라우트 목록 조회 실패:", error instanceof Error ? error.message : error);
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* routes watch - 실시간 라우트 감시
|
|
164
|
+
*/
|
|
165
|
+
export async function routesWatch(options: RoutesWatchOptions = {}): Promise<boolean> {
|
|
166
|
+
const rootDir = resolveFromCwd(".");
|
|
167
|
+
|
|
168
|
+
console.log("🥟 Mandu FS Routes Watch\n");
|
|
169
|
+
console.log("👀 라우트 변경 감시 중... (Ctrl+C로 종료)\n");
|
|
170
|
+
|
|
171
|
+
try {
|
|
172
|
+
// 초기 스캔
|
|
173
|
+
const initialResult = await generateManifest(rootDir, {
|
|
174
|
+
outputPath: options.output ?? ".mandu/routes.manifest.json",
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
console.log(`✅ 초기 스캔: ${initialResult.manifest.routes.length}개 라우트\n`);
|
|
178
|
+
|
|
179
|
+
// 감시 시작
|
|
180
|
+
const watcher = await watchFSRoutes(rootDir, {
|
|
181
|
+
outputPath: options.output ?? ".mandu/routes.manifest.json",
|
|
182
|
+
onChange: (result) => {
|
|
183
|
+
const timestamp = new Date().toLocaleTimeString();
|
|
184
|
+
console.log(`\n🔄 [${timestamp}] 라우트 변경 감지`);
|
|
185
|
+
console.log(` 📋 총 라우트: ${result.manifest.routes.length}개`);
|
|
186
|
+
|
|
187
|
+
if (result.warnings.length > 0) {
|
|
188
|
+
for (const warning of result.warnings) {
|
|
189
|
+
console.log(` ⚠️ ${warning}`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (options.verbose) {
|
|
194
|
+
console.log("");
|
|
195
|
+
console.log(formatRoutesForCLI(result.manifest));
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// 종료 시그널 처리
|
|
201
|
+
const cleanup = () => {
|
|
202
|
+
console.log("\n\n🛑 감시 종료");
|
|
203
|
+
watcher.close();
|
|
204
|
+
process.exit(0);
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
process.on("SIGINT", cleanup);
|
|
208
|
+
process.on("SIGTERM", cleanup);
|
|
209
|
+
|
|
210
|
+
// 무한 대기
|
|
211
|
+
await new Promise(() => {});
|
|
212
|
+
|
|
213
|
+
return true;
|
|
214
|
+
} catch (error) {
|
|
215
|
+
console.error("❌ 라우트 감시 실패:", error instanceof Error ? error.message : error);
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
218
|
+
}
|
package/src/main.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
|
|
3
|
-
import { specUpsert } from "./commands/spec-upsert";
|
|
4
|
-
import { generateApply } from "./commands/generate-apply";
|
|
5
|
-
import { guardCheck } from "./commands/guard-check";
|
|
6
|
-
import {
|
|
3
|
+
import { specUpsert } from "./commands/spec-upsert";
|
|
4
|
+
import { generateApply } from "./commands/generate-apply";
|
|
5
|
+
import { guardCheck } from "./commands/guard-check";
|
|
6
|
+
import { guardArch } from "./commands/guard-arch";
|
|
7
|
+
import { check } from "./commands/check";
|
|
8
|
+
import { dev } from "./commands/dev";
|
|
7
9
|
import { init } from "./commands/init";
|
|
8
10
|
import { build } from "./commands/build";
|
|
9
11
|
import { contractCreate, contractValidate } from "./commands/contract";
|
|
@@ -19,19 +21,30 @@ import {
|
|
|
19
21
|
import { doctor } from "./commands/doctor";
|
|
20
22
|
import { watch } from "./commands/watch";
|
|
21
23
|
import { brainSetup, brainStatus } from "./commands/brain";
|
|
24
|
+
import { routesGenerate, routesList, routesWatch } from "./commands/routes";
|
|
22
25
|
|
|
23
26
|
const HELP_TEXT = `
|
|
24
27
|
🥟 Mandu CLI - Agent-Native Fullstack Framework
|
|
25
28
|
|
|
26
29
|
Usage: bunx mandu <command> [options]
|
|
27
30
|
|
|
28
|
-
Commands:
|
|
29
|
-
init 새 프로젝트 생성
|
|
30
|
-
|
|
31
|
-
generate
|
|
32
|
-
|
|
31
|
+
Commands:
|
|
32
|
+
init 새 프로젝트 생성
|
|
33
|
+
check FS Routes + Guard 통합 검사
|
|
34
|
+
routes generate FS Routes 스캔 및 매니페스트 생성
|
|
35
|
+
routes list 현재 라우트 목록 출력
|
|
36
|
+
routes watch 실시간 라우트 감시
|
|
37
|
+
dev 개발 서버 실행 (FS Routes + Guard 기본)
|
|
38
|
+
dev --no-guard Guard 감시 비활성화
|
|
33
39
|
build 클라이언트 번들 빌드 (Hydration)
|
|
34
|
-
|
|
40
|
+
guard Guard 규칙 검사 (레거시 Spec 기반)
|
|
41
|
+
guard arch 아키텍처 위반 검사 (FSD/Clean/Hexagonal)
|
|
42
|
+
guard arch --watch 실시간 아키텍처 감시
|
|
43
|
+
guard arch --list-presets 사용 가능한 프리셋 목록
|
|
44
|
+
guard arch --output report.md 리포트 파일 생성
|
|
45
|
+
guard arch --show-trend 트렌드 분석 표시
|
|
46
|
+
spec-upsert Spec 파일 검증 및 lock 갱신 (레거시)
|
|
47
|
+
generate Spec에서 코드 생성 (레거시)
|
|
35
48
|
|
|
36
49
|
doctor Guard 실패 분석 + 패치 제안 (Brain)
|
|
37
50
|
watch 실시간 파일 감시 - 경고만 (Brain)
|
|
@@ -52,47 +65,58 @@ Commands:
|
|
|
52
65
|
change list 변경 이력 조회
|
|
53
66
|
change prune 오래된 스냅샷 정리
|
|
54
67
|
|
|
55
|
-
Options:
|
|
56
|
-
--name <name> init 시 프로젝트 이름 (기본: my-mandu-app)
|
|
57
|
-
--file <path> spec-upsert 시 사용할 spec 파일 경로
|
|
58
|
-
--port <port> dev/openapi serve 포트 (기본: 3000/8080)
|
|
59
|
-
--
|
|
68
|
+
Options:
|
|
69
|
+
--name <name> init 시 프로젝트 이름 (기본: my-mandu-app)
|
|
70
|
+
--file <path> spec-upsert 시 사용할 spec 파일 경로
|
|
71
|
+
--port <port> dev/openapi serve 포트 (기본: 3000/8080)
|
|
72
|
+
--guard dev 시 Architecture Guard 실시간 감시 활성화 (기본: ON)
|
|
73
|
+
--no-guard dev 시 Guard 비활성화
|
|
74
|
+
--guard-preset <p> dev --guard 시 프리셋 (기본: mandu)
|
|
75
|
+
--guard-format <f> dev --guard 출력 형식: console, json, agent (기본: 자동)
|
|
76
|
+
--legacy FS Routes 비활성화 (레거시 모드)
|
|
77
|
+
--no-auto-correct guard 시 자동 수정 비활성화
|
|
78
|
+
--preset <name> guard/check 프리셋 (기본: mandu) - fsd, clean, hexagonal, atomic 선택 가능
|
|
79
|
+
--ci guard/check CI 모드 (에러 시 exit 1)
|
|
80
|
+
--quiet guard/check 요약만 출력
|
|
81
|
+
--report-format guard arch 리포트 형식: json, markdown, html
|
|
82
|
+
--save-stats guard arch 통계 저장 (트렌드 분석용)
|
|
83
|
+
--show-trend guard arch 트렌드 분석 표시
|
|
60
84
|
--minify build 시 코드 압축
|
|
61
85
|
--sourcemap build 시 소스맵 생성
|
|
62
|
-
--watch build
|
|
86
|
+
--watch build/guard arch 파일 감시 모드
|
|
63
87
|
--message <msg> change begin 시 설명 메시지
|
|
64
88
|
--id <id> change rollback 시 특정 변경 ID
|
|
65
89
|
--keep <n> change prune 시 유지할 스냅샷 수 (기본: 5)
|
|
66
90
|
--output <path> openapi/doctor 출력 경로
|
|
67
|
-
--format <fmt> doctor 출력 형식: console, json,
|
|
91
|
+
--format <fmt> doctor/guard/check 출력 형식: console, json, agent (기본: 자동)
|
|
68
92
|
--no-llm doctor에서 LLM 사용 안 함 (템플릿 모드)
|
|
69
93
|
--model <name> brain setup 시 모델 이름 (기본: llama3.2)
|
|
70
94
|
--url <url> brain setup 시 Ollama URL
|
|
71
95
|
--verbose 상세 출력
|
|
72
96
|
--help, -h 도움말 표시
|
|
73
97
|
|
|
74
|
-
Examples:
|
|
75
|
-
bunx mandu init --name my-app
|
|
76
|
-
bunx mandu
|
|
77
|
-
bunx mandu
|
|
98
|
+
Examples:
|
|
99
|
+
bunx mandu init --name my-app
|
|
100
|
+
bunx mandu check
|
|
101
|
+
bunx mandu routes list
|
|
102
|
+
bunx mandu routes generate
|
|
103
|
+
bunx mandu dev --port 3000
|
|
104
|
+
bunx mandu dev --no-guard
|
|
105
|
+
bunx mandu build --minify
|
|
78
106
|
bunx mandu guard
|
|
79
|
-
bunx mandu
|
|
80
|
-
bunx mandu
|
|
81
|
-
bunx mandu
|
|
107
|
+
bunx mandu guard arch --preset fsd
|
|
108
|
+
bunx mandu guard arch --watch
|
|
109
|
+
bunx mandu guard arch --ci --format json
|
|
82
110
|
bunx mandu doctor
|
|
83
|
-
bunx mandu doctor --format markdown --output report.md
|
|
84
|
-
bunx mandu watch
|
|
85
111
|
bunx mandu brain setup --model codellama
|
|
86
|
-
bunx mandu brain status
|
|
87
112
|
bunx mandu contract create users
|
|
88
|
-
bunx mandu contract validate --verbose
|
|
89
113
|
bunx mandu openapi generate --output docs/api.json
|
|
90
|
-
bunx mandu openapi serve --port 8080
|
|
91
114
|
bunx mandu change begin --message "Add new route"
|
|
92
|
-
bunx mandu change commit
|
|
93
|
-
bunx mandu change rollback
|
|
94
115
|
|
|
95
|
-
Workflow:
|
|
116
|
+
FS Routes Workflow (권장):
|
|
117
|
+
1. init → 2. app/ 폴더에 page.tsx 생성 → 3. dev → 4. build
|
|
118
|
+
|
|
119
|
+
Legacy Workflow:
|
|
96
120
|
1. init → 2. spec-upsert → 3. generate → 4. build → 5. guard → 6. dev
|
|
97
121
|
|
|
98
122
|
Contract-first Workflow:
|
|
@@ -170,15 +194,46 @@ async function main(): Promise<void> {
|
|
|
170
194
|
success = await specUpsert({ file: options.file });
|
|
171
195
|
break;
|
|
172
196
|
|
|
173
|
-
case "generate":
|
|
174
|
-
success = await generateApply();
|
|
175
|
-
break;
|
|
176
|
-
|
|
177
|
-
case "
|
|
178
|
-
success = await
|
|
179
|
-
|
|
180
|
-
|
|
197
|
+
case "generate":
|
|
198
|
+
success = await generateApply();
|
|
199
|
+
break;
|
|
200
|
+
|
|
201
|
+
case "check":
|
|
202
|
+
success = await check({
|
|
203
|
+
preset: options.preset as any,
|
|
204
|
+
format: options.format as any,
|
|
205
|
+
ci: options.ci === "true",
|
|
206
|
+
quiet: options.quiet === "true",
|
|
207
|
+
legacy: options.legacy === "true",
|
|
208
|
+
});
|
|
209
|
+
break;
|
|
210
|
+
|
|
211
|
+
case "guard": {
|
|
212
|
+
const subCommand = args[1];
|
|
213
|
+
switch (subCommand) {
|
|
214
|
+
case "arch":
|
|
215
|
+
success = await guardArch({
|
|
216
|
+
preset: (options.preset as any) || "fsd",
|
|
217
|
+
watch: options.watch === "true",
|
|
218
|
+
ci: options.ci === "true",
|
|
219
|
+
format: options.format as any,
|
|
220
|
+
quiet: options.quiet === "true",
|
|
221
|
+
srcDir: options["src-dir"],
|
|
222
|
+
listPresets: options["list-presets"] === "true",
|
|
223
|
+
output: options.output,
|
|
224
|
+
reportFormat: (options["report-format"] as any) || "markdown",
|
|
225
|
+
saveStats: options["save-stats"] === "true",
|
|
226
|
+
showTrend: options["show-trend"] === "true",
|
|
227
|
+
});
|
|
228
|
+
break;
|
|
229
|
+
default:
|
|
230
|
+
// 기본값: 레거시 guard-check
|
|
231
|
+
success = await guardCheck({
|
|
232
|
+
autoCorrect: options["no-auto-correct"] !== "true",
|
|
233
|
+
});
|
|
234
|
+
}
|
|
181
235
|
break;
|
|
236
|
+
}
|
|
182
237
|
|
|
183
238
|
case "build":
|
|
184
239
|
success = await build({
|
|
@@ -188,9 +243,50 @@ async function main(): Promise<void> {
|
|
|
188
243
|
});
|
|
189
244
|
break;
|
|
190
245
|
|
|
191
|
-
case "dev":
|
|
192
|
-
await dev({
|
|
246
|
+
case "dev":
|
|
247
|
+
await dev({
|
|
248
|
+
port: parsePort(options.port),
|
|
249
|
+
guard: options["no-guard"] === "true" ? false : options.guard !== "false",
|
|
250
|
+
guardPreset: options["guard-preset"] as any,
|
|
251
|
+
guardFormat: options["guard-format"] as any,
|
|
252
|
+
legacy: options.legacy === "true",
|
|
253
|
+
});
|
|
254
|
+
break;
|
|
255
|
+
|
|
256
|
+
case "routes": {
|
|
257
|
+
const subCommand = args[1];
|
|
258
|
+
switch (subCommand) {
|
|
259
|
+
case "generate":
|
|
260
|
+
success = await routesGenerate({
|
|
261
|
+
output: options.output,
|
|
262
|
+
verbose: options.verbose === "true",
|
|
263
|
+
});
|
|
264
|
+
break;
|
|
265
|
+
case "list":
|
|
266
|
+
success = await routesList({
|
|
267
|
+
verbose: options.verbose === "true",
|
|
268
|
+
});
|
|
269
|
+
break;
|
|
270
|
+
case "watch":
|
|
271
|
+
success = await routesWatch({
|
|
272
|
+
output: options.output,
|
|
273
|
+
verbose: options.verbose === "true",
|
|
274
|
+
});
|
|
275
|
+
break;
|
|
276
|
+
default:
|
|
277
|
+
// 기본값: list
|
|
278
|
+
if (!subCommand) {
|
|
279
|
+
success = await routesList({
|
|
280
|
+
verbose: options.verbose === "true",
|
|
281
|
+
});
|
|
282
|
+
} else {
|
|
283
|
+
console.error(`❌ Unknown routes subcommand: ${subCommand}`);
|
|
284
|
+
console.log("\nUsage: bunx mandu routes <generate|list|watch>");
|
|
285
|
+
process.exit(1);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
193
288
|
break;
|
|
289
|
+
}
|
|
194
290
|
|
|
195
291
|
case "contract": {
|
|
196
292
|
const subCommand = args[1];
|
package/src/util/fs.ts
CHANGED
|
@@ -1,9 +1,28 @@
|
|
|
1
|
-
import path from "path";
|
|
1
|
+
import path from "path";
|
|
2
|
+
import fs from "fs/promises";
|
|
2
3
|
|
|
3
4
|
export function resolveFromCwd(...paths: string[]): string {
|
|
4
5
|
return path.resolve(process.cwd(), ...paths);
|
|
5
6
|
}
|
|
6
7
|
|
|
7
|
-
export function getRootDir(): string {
|
|
8
|
-
return process.cwd();
|
|
9
|
-
}
|
|
8
|
+
export function getRootDir(): string {
|
|
9
|
+
return process.cwd();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function pathExists(targetPath: string): Promise<boolean> {
|
|
13
|
+
try {
|
|
14
|
+
await fs.access(targetPath);
|
|
15
|
+
return true;
|
|
16
|
+
} catch {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function isDirectory(targetPath: string): Promise<boolean> {
|
|
22
|
+
try {
|
|
23
|
+
const stat = await fs.stat(targetPath);
|
|
24
|
+
return stat.isDirectory();
|
|
25
|
+
} catch {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export type OutputFormat = "console" | "agent" | "json";
|
|
2
|
+
|
|
3
|
+
function normalizeFormat(value?: string): OutputFormat | undefined {
|
|
4
|
+
if (!value) return undefined;
|
|
5
|
+
if (value === "console" || value === "agent" || value === "json") {
|
|
6
|
+
return value;
|
|
7
|
+
}
|
|
8
|
+
return undefined;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function resolveOutputFormat(explicit?: OutputFormat): OutputFormat {
|
|
12
|
+
const env = process.env;
|
|
13
|
+
|
|
14
|
+
const direct = normalizeFormat(explicit) ?? normalizeFormat(env.MANDU_OUTPUT);
|
|
15
|
+
if (direct) return direct;
|
|
16
|
+
|
|
17
|
+
const agentSignals = [
|
|
18
|
+
"MANDU_AGENT",
|
|
19
|
+
"CODEX_AGENT",
|
|
20
|
+
"CODEX",
|
|
21
|
+
"CLAUDE_CODE",
|
|
22
|
+
"ANTHROPIC_CLAUDE_CODE",
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
for (const key of agentSignals) {
|
|
26
|
+
const value = env[key];
|
|
27
|
+
if (value === "1" || value === "true") {
|
|
28
|
+
return "json";
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (env.CI === "true") {
|
|
33
|
+
return "json";
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (process.stdout && !process.stdout.isTTY) {
|
|
37
|
+
return "json";
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return "console";
|
|
41
|
+
}
|