@mandujs/cli 0.12.2 → 0.13.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/README.ko.md +234 -234
- package/README.md +354 -354
- package/package.json +2 -2
- package/src/commands/contract.ts +173 -173
- package/src/commands/dev.ts +8 -68
- package/src/commands/doctor.ts +27 -27
- package/src/commands/guard-arch.ts +303 -303
- package/src/commands/guard-check.ts +3 -3
- package/src/commands/monitor.ts +300 -300
- package/src/commands/openapi.ts +107 -107
- package/src/commands/registry.ts +367 -357
- package/src/commands/routes.ts +228 -228
- package/src/commands/start.ts +184 -0
- package/src/errors/codes.ts +35 -35
- package/src/errors/index.ts +2 -2
- package/src/errors/messages.ts +143 -143
- package/src/hooks/index.ts +17 -17
- package/src/hooks/preaction.ts +256 -256
- package/src/main.ts +37 -34
- package/src/terminal/banner.ts +166 -166
- package/src/terminal/help.ts +306 -306
- package/src/terminal/index.ts +71 -71
- package/src/terminal/output.ts +295 -295
- package/src/terminal/palette.ts +30 -30
- package/src/terminal/progress.ts +327 -327
- package/src/terminal/stream-writer.ts +214 -214
- package/src/terminal/table.ts +354 -354
- package/src/terminal/theme.ts +142 -142
- package/src/util/bun.ts +6 -6
- package/src/util/fs.ts +23 -23
- package/src/util/handlers.ts +96 -0
- package/src/util/manifest.ts +52 -52
- package/src/util/output.ts +22 -22
- package/src/util/port.ts +71 -71
- package/templates/default/AGENTS.md +96 -96
- package/templates/default/app/api/health/route.ts +13 -13
- package/templates/default/app/globals.css +49 -49
- package/templates/default/app/layout.tsx +27 -27
- package/templates/default/app/page.tsx +38 -38
- package/templates/default/package.json +1 -0
- package/templates/default/src/client/shared/lib/utils.ts +16 -16
- package/templates/default/src/client/shared/ui/button.tsx +57 -57
- package/templates/default/src/client/shared/ui/card.tsx +78 -78
- package/templates/default/src/client/shared/ui/index.ts +21 -21
- package/templates/default/src/client/shared/ui/input.tsx +24 -24
- package/templates/default/tests/example.test.ts +58 -58
- package/templates/default/tests/helpers.ts +52 -52
- package/templates/default/tests/setup.ts +9 -9
- package/templates/default/tsconfig.json +12 -14
- package/templates/default/apps/server/main.ts +0 -67
- package/templates/default/apps/web/entry.tsx +0 -35
|
@@ -1,214 +1,214 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* DNA-013: Safe Stream Writer
|
|
3
|
-
*
|
|
4
|
-
* 파이프 환경에서 EPIPE 에러를 안전하게 처리
|
|
5
|
-
* - `mandu routes list | head -5` 같은 파이프 사용 시 안전
|
|
6
|
-
* - Broken pipe 감지 후 추가 쓰기 방지
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Safe Stream Writer 옵션
|
|
11
|
-
*/
|
|
12
|
-
export interface SafeStreamWriterOptions {
|
|
13
|
-
/**
|
|
14
|
-
* Broken pipe 발생 시 콜백
|
|
15
|
-
*/
|
|
16
|
-
onBrokenPipe?: (error: NodeJS.ErrnoException, stream: NodeJS.WriteStream) => void;
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* 조용히 실패 (에러 출력 안 함)
|
|
20
|
-
*/
|
|
21
|
-
silent?: boolean;
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Broken pipe 이외의 에러 핸들러 (선택)
|
|
25
|
-
*/
|
|
26
|
-
onError?: (error: Error, stream: NodeJS.WriteStream) => void;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Safe Stream Writer 인터페이스
|
|
31
|
-
*/
|
|
32
|
-
export interface SafeStreamWriter {
|
|
33
|
-
/**
|
|
34
|
-
* 스트림에 텍스트 쓰기
|
|
35
|
-
* @returns 성공 여부
|
|
36
|
-
*/
|
|
37
|
-
write: (stream: NodeJS.WriteStream, text: string) => boolean;
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* 스트림에 줄 쓰기 (자동 개행)
|
|
41
|
-
* @returns 성공 여부
|
|
42
|
-
*/
|
|
43
|
-
writeLine: (stream: NodeJS.WriteStream, text: string) => boolean;
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* stdout에 쓰기 (편의 메서드)
|
|
47
|
-
*/
|
|
48
|
-
print: (text: string) => boolean;
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* stdout에 줄 쓰기 (편의 메서드)
|
|
52
|
-
*/
|
|
53
|
-
println: (text: string) => boolean;
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* stderr에 쓰기 (편의 메서드)
|
|
57
|
-
*/
|
|
58
|
-
printError: (text: string) => boolean;
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* 상태 리셋
|
|
62
|
-
*/
|
|
63
|
-
reset: () => void;
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* 스트림이 닫혔는지 확인
|
|
67
|
-
*/
|
|
68
|
-
isClosed: () => boolean;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Broken Pipe 에러인지 확인
|
|
73
|
-
*/
|
|
74
|
-
function isBrokenPipeError(err: unknown): err is NodeJS.ErrnoException {
|
|
75
|
-
const errno = err as NodeJS.ErrnoException;
|
|
76
|
-
return errno?.code === "EPIPE" || errno?.code === "EIO";
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Safe Stream Writer 생성
|
|
81
|
-
*
|
|
82
|
-
* @example
|
|
83
|
-
* ```ts
|
|
84
|
-
* const writer = createSafeStreamWriter();
|
|
85
|
-
*
|
|
86
|
-
* // 기본 사용
|
|
87
|
-
* writer.println("Hello, World!");
|
|
88
|
-
*
|
|
89
|
-
* // 파이프에서 안전하게 사용
|
|
90
|
-
* for (const line of lines) {
|
|
91
|
-
* if (!writer.println(line)) {
|
|
92
|
-
* // 파이프가 닫힘, 루프 종료
|
|
93
|
-
* break;
|
|
94
|
-
* }
|
|
95
|
-
* }
|
|
96
|
-
* ```
|
|
97
|
-
*/
|
|
98
|
-
export function createSafeStreamWriter(
|
|
99
|
-
options: SafeStreamWriterOptions = {}
|
|
100
|
-
): SafeStreamWriter {
|
|
101
|
-
const closedStreams = new Set<NodeJS.WriteStream>();
|
|
102
|
-
const errorHandlers = new Map<NodeJS.WriteStream, (err: Error) => void>();
|
|
103
|
-
|
|
104
|
-
const ensureErrorHandler = (stream: NodeJS.WriteStream): void => {
|
|
105
|
-
if (errorHandlers.has(stream)) return;
|
|
106
|
-
|
|
107
|
-
const handler = (err: Error) => {
|
|
108
|
-
if (isBrokenPipeError(err)) {
|
|
109
|
-
closedStreams.add(stream);
|
|
110
|
-
options.onBrokenPipe?.(err, stream);
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
if (options.onError) {
|
|
115
|
-
options.onError(err, stream);
|
|
116
|
-
return;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
if (!options.silent) {
|
|
120
|
-
console.error("[SafeStreamWriter] Stream error:", err);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// 비정상 에러는 기존 동작을 유지하도록 비동기 재-throw
|
|
124
|
-
setTimeout(() => {
|
|
125
|
-
throw err;
|
|
126
|
-
}, 0);
|
|
127
|
-
};
|
|
128
|
-
|
|
129
|
-
stream.on("error", handler);
|
|
130
|
-
errorHandlers.set(stream, handler);
|
|
131
|
-
};
|
|
132
|
-
|
|
133
|
-
const isStreamClosed = (stream: NodeJS.WriteStream): boolean => {
|
|
134
|
-
const anyStream = stream as NodeJS.WriteStream & {
|
|
135
|
-
destroyed?: boolean;
|
|
136
|
-
writableEnded?: boolean;
|
|
137
|
-
};
|
|
138
|
-
if (anyStream.destroyed || anyStream.writableEnded) return true;
|
|
139
|
-
return closedStreams.has(stream);
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
const write = (stream: NodeJS.WriteStream, text: string): boolean => {
|
|
143
|
-
if (isStreamClosed(stream)) return false;
|
|
144
|
-
|
|
145
|
-
ensureErrorHandler(stream);
|
|
146
|
-
|
|
147
|
-
try {
|
|
148
|
-
stream.write(text);
|
|
149
|
-
return true;
|
|
150
|
-
} catch (err) {
|
|
151
|
-
if (!isBrokenPipeError(err)) {
|
|
152
|
-
throw err;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
closedStreams.add(stream);
|
|
156
|
-
options.onBrokenPipe?.(err, stream);
|
|
157
|
-
return false;
|
|
158
|
-
}
|
|
159
|
-
};
|
|
160
|
-
|
|
161
|
-
return {
|
|
162
|
-
write,
|
|
163
|
-
writeLine: (stream, text) => write(stream, `${text}\n`),
|
|
164
|
-
print: (text) => write(process.stdout, text),
|
|
165
|
-
println: (text) => write(process.stdout, `${text}\n`),
|
|
166
|
-
printError: (text) => write(process.stderr, `${text}\n`),
|
|
167
|
-
reset: () => {
|
|
168
|
-
closedStreams.clear();
|
|
169
|
-
for (const [stream, handler] of errorHandlers) {
|
|
170
|
-
stream.removeListener("error", handler);
|
|
171
|
-
}
|
|
172
|
-
errorHandlers.clear();
|
|
173
|
-
},
|
|
174
|
-
isClosed: () => isStreamClosed(process.stdout),
|
|
175
|
-
};
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* 기본 Safe Writer 인스턴스 (싱글톤)
|
|
180
|
-
*/
|
|
181
|
-
let defaultWriter: SafeStreamWriter | null = null;
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* 기본 Safe Writer 가져오기
|
|
185
|
-
*/
|
|
186
|
-
export function getSafeWriter(): SafeStreamWriter {
|
|
187
|
-
if (!defaultWriter) {
|
|
188
|
-
defaultWriter = createSafeStreamWriter({ silent: true });
|
|
189
|
-
}
|
|
190
|
-
return defaultWriter;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
/**
|
|
194
|
-
* 안전한 console.log 대체
|
|
195
|
-
*
|
|
196
|
-
* @example
|
|
197
|
-
* ```ts
|
|
198
|
-
* import { safePrint, safePrintln } from "./stream-writer";
|
|
199
|
-
*
|
|
200
|
-
* safePrintln("Hello, World!");
|
|
201
|
-
* // 파이프가 닫혀도 에러 없음
|
|
202
|
-
* ```
|
|
203
|
-
*/
|
|
204
|
-
export function safePrint(text: string): boolean {
|
|
205
|
-
return getSafeWriter().print(text);
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
export function safePrintln(text: string): boolean {
|
|
209
|
-
return getSafeWriter().println(text);
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
export function safePrintError(text: string): boolean {
|
|
213
|
-
return getSafeWriter().printError(text);
|
|
214
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* DNA-013: Safe Stream Writer
|
|
3
|
+
*
|
|
4
|
+
* 파이프 환경에서 EPIPE 에러를 안전하게 처리
|
|
5
|
+
* - `mandu routes list | head -5` 같은 파이프 사용 시 안전
|
|
6
|
+
* - Broken pipe 감지 후 추가 쓰기 방지
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Safe Stream Writer 옵션
|
|
11
|
+
*/
|
|
12
|
+
export interface SafeStreamWriterOptions {
|
|
13
|
+
/**
|
|
14
|
+
* Broken pipe 발생 시 콜백
|
|
15
|
+
*/
|
|
16
|
+
onBrokenPipe?: (error: NodeJS.ErrnoException, stream: NodeJS.WriteStream) => void;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 조용히 실패 (에러 출력 안 함)
|
|
20
|
+
*/
|
|
21
|
+
silent?: boolean;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Broken pipe 이외의 에러 핸들러 (선택)
|
|
25
|
+
*/
|
|
26
|
+
onError?: (error: Error, stream: NodeJS.WriteStream) => void;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Safe Stream Writer 인터페이스
|
|
31
|
+
*/
|
|
32
|
+
export interface SafeStreamWriter {
|
|
33
|
+
/**
|
|
34
|
+
* 스트림에 텍스트 쓰기
|
|
35
|
+
* @returns 성공 여부
|
|
36
|
+
*/
|
|
37
|
+
write: (stream: NodeJS.WriteStream, text: string) => boolean;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* 스트림에 줄 쓰기 (자동 개행)
|
|
41
|
+
* @returns 성공 여부
|
|
42
|
+
*/
|
|
43
|
+
writeLine: (stream: NodeJS.WriteStream, text: string) => boolean;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* stdout에 쓰기 (편의 메서드)
|
|
47
|
+
*/
|
|
48
|
+
print: (text: string) => boolean;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* stdout에 줄 쓰기 (편의 메서드)
|
|
52
|
+
*/
|
|
53
|
+
println: (text: string) => boolean;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* stderr에 쓰기 (편의 메서드)
|
|
57
|
+
*/
|
|
58
|
+
printError: (text: string) => boolean;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* 상태 리셋
|
|
62
|
+
*/
|
|
63
|
+
reset: () => void;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* 스트림이 닫혔는지 확인
|
|
67
|
+
*/
|
|
68
|
+
isClosed: () => boolean;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Broken Pipe 에러인지 확인
|
|
73
|
+
*/
|
|
74
|
+
function isBrokenPipeError(err: unknown): err is NodeJS.ErrnoException {
|
|
75
|
+
const errno = err as NodeJS.ErrnoException;
|
|
76
|
+
return errno?.code === "EPIPE" || errno?.code === "EIO";
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Safe Stream Writer 생성
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* ```ts
|
|
84
|
+
* const writer = createSafeStreamWriter();
|
|
85
|
+
*
|
|
86
|
+
* // 기본 사용
|
|
87
|
+
* writer.println("Hello, World!");
|
|
88
|
+
*
|
|
89
|
+
* // 파이프에서 안전하게 사용
|
|
90
|
+
* for (const line of lines) {
|
|
91
|
+
* if (!writer.println(line)) {
|
|
92
|
+
* // 파이프가 닫힘, 루프 종료
|
|
93
|
+
* break;
|
|
94
|
+
* }
|
|
95
|
+
* }
|
|
96
|
+
* ```
|
|
97
|
+
*/
|
|
98
|
+
export function createSafeStreamWriter(
|
|
99
|
+
options: SafeStreamWriterOptions = {}
|
|
100
|
+
): SafeStreamWriter {
|
|
101
|
+
const closedStreams = new Set<NodeJS.WriteStream>();
|
|
102
|
+
const errorHandlers = new Map<NodeJS.WriteStream, (err: Error) => void>();
|
|
103
|
+
|
|
104
|
+
const ensureErrorHandler = (stream: NodeJS.WriteStream): void => {
|
|
105
|
+
if (errorHandlers.has(stream)) return;
|
|
106
|
+
|
|
107
|
+
const handler = (err: Error) => {
|
|
108
|
+
if (isBrokenPipeError(err)) {
|
|
109
|
+
closedStreams.add(stream);
|
|
110
|
+
options.onBrokenPipe?.(err, stream);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (options.onError) {
|
|
115
|
+
options.onError(err, stream);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (!options.silent) {
|
|
120
|
+
console.error("[SafeStreamWriter] Stream error:", err);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// 비정상 에러는 기존 동작을 유지하도록 비동기 재-throw
|
|
124
|
+
setTimeout(() => {
|
|
125
|
+
throw err;
|
|
126
|
+
}, 0);
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
stream.on("error", handler);
|
|
130
|
+
errorHandlers.set(stream, handler);
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const isStreamClosed = (stream: NodeJS.WriteStream): boolean => {
|
|
134
|
+
const anyStream = stream as NodeJS.WriteStream & {
|
|
135
|
+
destroyed?: boolean;
|
|
136
|
+
writableEnded?: boolean;
|
|
137
|
+
};
|
|
138
|
+
if (anyStream.destroyed || anyStream.writableEnded) return true;
|
|
139
|
+
return closedStreams.has(stream);
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const write = (stream: NodeJS.WriteStream, text: string): boolean => {
|
|
143
|
+
if (isStreamClosed(stream)) return false;
|
|
144
|
+
|
|
145
|
+
ensureErrorHandler(stream);
|
|
146
|
+
|
|
147
|
+
try {
|
|
148
|
+
stream.write(text);
|
|
149
|
+
return true;
|
|
150
|
+
} catch (err) {
|
|
151
|
+
if (!isBrokenPipeError(err)) {
|
|
152
|
+
throw err;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
closedStreams.add(stream);
|
|
156
|
+
options.onBrokenPipe?.(err, stream);
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
return {
|
|
162
|
+
write,
|
|
163
|
+
writeLine: (stream, text) => write(stream, `${text}\n`),
|
|
164
|
+
print: (text) => write(process.stdout, text),
|
|
165
|
+
println: (text) => write(process.stdout, `${text}\n`),
|
|
166
|
+
printError: (text) => write(process.stderr, `${text}\n`),
|
|
167
|
+
reset: () => {
|
|
168
|
+
closedStreams.clear();
|
|
169
|
+
for (const [stream, handler] of errorHandlers) {
|
|
170
|
+
stream.removeListener("error", handler);
|
|
171
|
+
}
|
|
172
|
+
errorHandlers.clear();
|
|
173
|
+
},
|
|
174
|
+
isClosed: () => isStreamClosed(process.stdout),
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* 기본 Safe Writer 인스턴스 (싱글톤)
|
|
180
|
+
*/
|
|
181
|
+
let defaultWriter: SafeStreamWriter | null = null;
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* 기본 Safe Writer 가져오기
|
|
185
|
+
*/
|
|
186
|
+
export function getSafeWriter(): SafeStreamWriter {
|
|
187
|
+
if (!defaultWriter) {
|
|
188
|
+
defaultWriter = createSafeStreamWriter({ silent: true });
|
|
189
|
+
}
|
|
190
|
+
return defaultWriter;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* 안전한 console.log 대체
|
|
195
|
+
*
|
|
196
|
+
* @example
|
|
197
|
+
* ```ts
|
|
198
|
+
* import { safePrint, safePrintln } from "./stream-writer";
|
|
199
|
+
*
|
|
200
|
+
* safePrintln("Hello, World!");
|
|
201
|
+
* // 파이프가 닫혀도 에러 없음
|
|
202
|
+
* ```
|
|
203
|
+
*/
|
|
204
|
+
export function safePrint(text: string): boolean {
|
|
205
|
+
return getSafeWriter().print(text);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export function safePrintln(text: string): boolean {
|
|
209
|
+
return getSafeWriter().println(text);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function safePrintError(text: string): boolean {
|
|
213
|
+
return getSafeWriter().printError(text);
|
|
214
|
+
}
|