@mandujs/cli 0.15.0 → 0.15.2

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.
Files changed (90) hide show
  1. package/README.ko.md +33 -33
  2. package/README.md +354 -354
  3. package/package.json +2 -2
  4. package/src/commands/check.ts +71 -7
  5. package/src/commands/contract.ts +173 -173
  6. package/src/commands/dev.ts +9 -42
  7. package/src/commands/guard-arch.ts +303 -303
  8. package/src/commands/init.ts +50 -5
  9. package/src/commands/monitor.ts +300 -300
  10. package/src/commands/openapi.ts +107 -107
  11. package/src/commands/registry.ts +1 -0
  12. package/src/commands/start.ts +9 -42
  13. package/src/errors/codes.ts +35 -35
  14. package/src/errors/index.ts +2 -2
  15. package/src/errors/messages.ts +143 -143
  16. package/src/hooks/index.ts +17 -17
  17. package/src/hooks/preaction.ts +256 -256
  18. package/src/main.ts +9 -7
  19. package/src/terminal/banner.ts +166 -166
  20. package/src/terminal/help.ts +306 -306
  21. package/src/terminal/index.ts +71 -71
  22. package/src/terminal/output.ts +295 -295
  23. package/src/terminal/palette.ts +30 -30
  24. package/src/terminal/progress.ts +327 -327
  25. package/src/terminal/stream-writer.ts +214 -214
  26. package/src/terminal/table.ts +354 -354
  27. package/src/terminal/theme.ts +142 -142
  28. package/src/util/bun.ts +6 -6
  29. package/src/util/fs.ts +23 -23
  30. package/src/util/handlers.ts +49 -5
  31. package/src/util/lockfile.ts +66 -0
  32. package/src/util/output.ts +22 -22
  33. package/src/util/port.ts +71 -71
  34. package/templates/default/AGENTS.md +96 -96
  35. package/templates/default/app/api/health/route.ts +13 -13
  36. package/templates/default/app/globals.css +49 -49
  37. package/templates/default/app/layout.tsx +27 -27
  38. package/templates/default/app/page.tsx +38 -38
  39. package/templates/default/src/client/shared/lib/utils.ts +16 -16
  40. package/templates/default/src/client/shared/ui/button.tsx +57 -57
  41. package/templates/default/src/client/shared/ui/card.tsx +1 -1
  42. package/templates/default/src/client/shared/ui/index.ts +21 -21
  43. package/templates/default/src/client/shared/ui/input.tsx +5 -1
  44. package/templates/default/tests/example.test.ts +58 -58
  45. package/templates/default/tests/helpers.ts +52 -52
  46. package/templates/default/tests/setup.ts +9 -9
  47. package/templates/default/tsconfig.json +23 -23
  48. package/templates/realtime-chat/AGENTS.md +96 -0
  49. package/templates/realtime-chat/app/api/chat/messages/route.ts +63 -0
  50. package/templates/realtime-chat/app/api/chat/stream/route.ts +48 -0
  51. package/templates/realtime-chat/app/api/health/route.ts +13 -0
  52. package/templates/realtime-chat/app/globals.css +49 -0
  53. package/templates/realtime-chat/app/layout.tsx +27 -0
  54. package/templates/realtime-chat/app/page.tsx +16 -0
  55. package/templates/realtime-chat/package.json +34 -0
  56. package/templates/realtime-chat/src/client/app/index.ts +1 -0
  57. package/templates/realtime-chat/src/client/entities/index.ts +1 -0
  58. package/templates/realtime-chat/src/client/features/chat/chat-api.ts +177 -0
  59. package/templates/realtime-chat/src/client/features/chat/realtime-chat-starter.client.tsx +89 -0
  60. package/templates/realtime-chat/src/client/features/chat/use-realtime-chat.ts +73 -0
  61. package/templates/realtime-chat/src/client/features/index.ts +1 -0
  62. package/templates/realtime-chat/src/client/pages/index.ts +1 -0
  63. package/templates/realtime-chat/src/client/shared/index.ts +1 -0
  64. package/templates/realtime-chat/src/client/shared/lib/utils.ts +16 -0
  65. package/templates/realtime-chat/src/client/shared/ui/button.tsx +57 -0
  66. package/templates/realtime-chat/src/client/shared/ui/card.tsx +78 -0
  67. package/templates/realtime-chat/src/client/shared/ui/index.ts +21 -0
  68. package/templates/realtime-chat/src/client/shared/ui/input.tsx +28 -0
  69. package/templates/realtime-chat/src/client/widgets/index.ts +1 -0
  70. package/templates/realtime-chat/src/server/api/index.ts +1 -0
  71. package/templates/realtime-chat/src/server/application/ai-adapter.ts +24 -0
  72. package/templates/realtime-chat/src/server/application/chat-store.ts +88 -0
  73. package/templates/realtime-chat/src/server/application/index.ts +1 -0
  74. package/templates/realtime-chat/src/server/core/index.ts +1 -0
  75. package/templates/realtime-chat/src/server/domain/index.ts +1 -0
  76. package/templates/realtime-chat/src/server/infra/index.ts +1 -0
  77. package/templates/realtime-chat/src/shared/contracts/chat.ts +29 -0
  78. package/templates/realtime-chat/src/shared/contracts/index.ts +1 -0
  79. package/templates/realtime-chat/src/shared/env/index.ts +1 -0
  80. package/templates/realtime-chat/src/shared/schema/index.ts +1 -0
  81. package/templates/realtime-chat/src/shared/types/index.ts +1 -0
  82. package/templates/realtime-chat/src/shared/utils/client/index.ts +1 -0
  83. package/templates/realtime-chat/src/shared/utils/server/index.ts +1 -0
  84. package/templates/realtime-chat/tests/chat-api.sse.test.ts +151 -0
  85. package/templates/realtime-chat/tests/chat-starter.test.ts +149 -0
  86. package/templates/realtime-chat/tests/chat-store.concurrency.test.ts +39 -0
  87. package/templates/realtime-chat/tests/example.test.ts +58 -0
  88. package/templates/realtime-chat/tests/helpers.ts +52 -0
  89. package/templates/realtime-chat/tests/setup.ts +9 -0
  90. package/templates/realtime-chat/tsconfig.json +23 -0
@@ -1,256 +1,256 @@
1
- /**
2
- * DNA-016: Pre-Action Hooks
3
- *
4
- * 명령어 실행 전 공통 작업 수행
5
- * - 프로세스 타이틀 설정
6
- * - 조건부 배너 표시
7
- * - Verbose 모드 설정
8
- * - 설정 로드
9
- */
10
-
11
- import { shouldShowBanner, renderMiniBanner } from "../terminal/banner.js";
12
- import { loadManduConfig, type ManduConfig } from "@mandujs/core";
13
-
14
- /**
15
- * Pre-Action 컨텍스트
16
- */
17
- export interface PreActionContext {
18
- /** 현재 명령어 */
19
- command: string;
20
- /** 서브커맨드 */
21
- subcommand?: string;
22
- /** 명령어 옵션 */
23
- options: Record<string, string>;
24
- /** 로드된 설정 */
25
- config?: ManduConfig;
26
- /** verbose 모드 여부 */
27
- verbose: boolean;
28
- /** 작업 디렉토리 */
29
- cwd: string;
30
- }
31
-
32
- /**
33
- * Pre-Action 훅 타입
34
- */
35
- export type PreActionHook = (ctx: PreActionContext) => void | Promise<void>;
36
-
37
- /**
38
- * Pre-Action 훅 레지스트리
39
- */
40
- class PreActionRegistry {
41
- private hooks: PreActionHook[] = [];
42
-
43
- /**
44
- * 훅 등록
45
- */
46
- register(hook: PreActionHook): void {
47
- this.hooks.push(hook);
48
- }
49
-
50
- /**
51
- * 훅 제거
52
- */
53
- unregister(hook: PreActionHook): boolean {
54
- const index = this.hooks.indexOf(hook);
55
- if (index >= 0) {
56
- this.hooks.splice(index, 1);
57
- return true;
58
- }
59
- return false;
60
- }
61
-
62
- /**
63
- * 모든 훅 실행
64
- */
65
- async runAll(ctx: PreActionContext): Promise<void> {
66
- for (const hook of this.hooks) {
67
- await hook(ctx);
68
- }
69
- }
70
-
71
- /**
72
- * 훅 초기화
73
- */
74
- clear(): void {
75
- this.hooks = [];
76
- }
77
-
78
- /**
79
- * 등록된 훅 수
80
- */
81
- get size(): number {
82
- return this.hooks.length;
83
- }
84
- }
85
-
86
- /**
87
- * 전역 Pre-Action 훅 레지스트리
88
- */
89
- export const preActionRegistry = new PreActionRegistry();
90
-
91
- /**
92
- * 설정 로드가 필요없는 명령어
93
- */
94
- const SKIP_CONFIG_COMMANDS = new Set([
95
- "init",
96
- "help",
97
- "version",
98
- "completion",
99
- ]);
100
-
101
- /**
102
- * 배너 표시가 필요없는 명령어
103
- */
104
- const SKIP_BANNER_COMMANDS = new Set([
105
- "completion",
106
- "version",
107
- ]);
108
-
109
- /**
110
- * verbose 전역 상태
111
- */
112
- let globalVerbose = false;
113
-
114
- /**
115
- * verbose 모드 설정
116
- */
117
- export function setVerbose(value: boolean): void {
118
- globalVerbose = value;
119
- }
120
-
121
- /**
122
- * verbose 모드 확인
123
- */
124
- export function isVerbose(): boolean {
125
- return globalVerbose;
126
- }
127
-
128
- /**
129
- * 프로세스 타이틀 설정
130
- */
131
- export function setProcessTitle(command: string, subcommand?: string): void {
132
- const title = subcommand
133
- ? `mandu ${command} ${subcommand}`
134
- : `mandu ${command}`;
135
-
136
- if (typeof process.title !== "undefined") {
137
- process.title = title;
138
- }
139
- }
140
-
141
- /**
142
- * 기본 Pre-Action 실행
143
- *
144
- * @example
145
- * ```ts
146
- * const ctx = await runPreAction({
147
- * command: "dev",
148
- * options: { port: "3000" },
149
- * });
150
- *
151
- * // ctx.config 에서 로드된 설정 사용
152
- * // ctx.verbose 로 verbose 모드 확인
153
- * ```
154
- */
155
- export async function runPreAction(params: {
156
- command: string;
157
- subcommand?: string;
158
- options: Record<string, string>;
159
- cwd?: string;
160
- version?: string;
161
- }): Promise<PreActionContext> {
162
- const {
163
- command,
164
- subcommand,
165
- options,
166
- cwd = process.cwd(),
167
- version,
168
- } = params;
169
-
170
- // 1. verbose 모드 확인
171
- const verbose = options.verbose === "true" || process.env.MANDU_VERBOSE === "true";
172
- setVerbose(verbose);
173
-
174
- // 2. 프로세스 타이틀 설정
175
- setProcessTitle(command, subcommand);
176
-
177
- // 3. 조건부 배너 표시
178
- const showBanner =
179
- !SKIP_BANNER_COMMANDS.has(command) &&
180
- !isTruthyEnv("MANDU_HIDE_BANNER") &&
181
- shouldShowBanner();
182
-
183
- if (showBanner && version) {
184
- console.log(renderMiniBanner(version));
185
- console.log();
186
- }
187
-
188
- // 4. 설정 로드 (필요한 명령어만)
189
- let config: ManduConfig | undefined;
190
- if (!SKIP_CONFIG_COMMANDS.has(command)) {
191
- try {
192
- config = await loadManduConfig(cwd);
193
- } catch {
194
- // 설정 로드 실패 시 무시 (옵션 설정만 사용)
195
- if (verbose) {
196
- console.warn("[mandu] Config load failed, using defaults");
197
- }
198
- }
199
- }
200
-
201
- // Pre-Action 컨텍스트 생성
202
- const ctx: PreActionContext = {
203
- command,
204
- subcommand,
205
- options,
206
- config,
207
- verbose,
208
- cwd,
209
- };
210
-
211
- // 5. 등록된 훅 실행
212
- await preActionRegistry.runAll(ctx);
213
-
214
- return ctx;
215
- }
216
-
217
- /**
218
- * 환경변수가 truthy인지 확인
219
- */
220
- function isTruthyEnv(key: string): boolean {
221
- const value = process.env[key];
222
- if (!value) return false;
223
- return !["0", "false", "no", ""].includes(value.toLowerCase());
224
- }
225
-
226
- /**
227
- * Pre-Action 훅 등록 헬퍼
228
- *
229
- * @example
230
- * ```ts
231
- * registerPreActionHook(async (ctx) => {
232
- * if (ctx.command === "dev") {
233
- * console.log("Starting development mode...");
234
- * }
235
- * });
236
- * ```
237
- */
238
- export function registerPreActionHook(hook: PreActionHook): () => void {
239
- preActionRegistry.register(hook);
240
- return () => preActionRegistry.unregister(hook);
241
- }
242
-
243
- /**
244
- * 기본 훅들 등록
245
- */
246
- export function registerDefaultHooks(): void {
247
- // 예: 개발 모드에서 추가 정보 표시
248
- registerPreActionHook((ctx) => {
249
- if (ctx.verbose && ctx.config) {
250
- console.log(`[mandu] Config loaded from ${ctx.cwd}`);
251
- if (ctx.config.server?.port) {
252
- console.log(`[mandu] Server port: ${ctx.config.server.port}`);
253
- }
254
- }
255
- });
256
- }
1
+ /**
2
+ * DNA-016: Pre-Action Hooks
3
+ *
4
+ * 명령어 실행 전 공통 작업 수행
5
+ * - 프로세스 타이틀 설정
6
+ * - 조건부 배너 표시
7
+ * - Verbose 모드 설정
8
+ * - 설정 로드
9
+ */
10
+
11
+ import { shouldShowBanner, renderMiniBanner } from "../terminal/banner.js";
12
+ import { loadManduConfig, type ManduConfig } from "@mandujs/core";
13
+
14
+ /**
15
+ * Pre-Action 컨텍스트
16
+ */
17
+ export interface PreActionContext {
18
+ /** 현재 명령어 */
19
+ command: string;
20
+ /** 서브커맨드 */
21
+ subcommand?: string;
22
+ /** 명령어 옵션 */
23
+ options: Record<string, string>;
24
+ /** 로드된 설정 */
25
+ config?: ManduConfig;
26
+ /** verbose 모드 여부 */
27
+ verbose: boolean;
28
+ /** 작업 디렉토리 */
29
+ cwd: string;
30
+ }
31
+
32
+ /**
33
+ * Pre-Action 훅 타입
34
+ */
35
+ export type PreActionHook = (ctx: PreActionContext) => void | Promise<void>;
36
+
37
+ /**
38
+ * Pre-Action 훅 레지스트리
39
+ */
40
+ class PreActionRegistry {
41
+ private hooks: PreActionHook[] = [];
42
+
43
+ /**
44
+ * 훅 등록
45
+ */
46
+ register(hook: PreActionHook): void {
47
+ this.hooks.push(hook);
48
+ }
49
+
50
+ /**
51
+ * 훅 제거
52
+ */
53
+ unregister(hook: PreActionHook): boolean {
54
+ const index = this.hooks.indexOf(hook);
55
+ if (index >= 0) {
56
+ this.hooks.splice(index, 1);
57
+ return true;
58
+ }
59
+ return false;
60
+ }
61
+
62
+ /**
63
+ * 모든 훅 실행
64
+ */
65
+ async runAll(ctx: PreActionContext): Promise<void> {
66
+ for (const hook of this.hooks) {
67
+ await hook(ctx);
68
+ }
69
+ }
70
+
71
+ /**
72
+ * 훅 초기화
73
+ */
74
+ clear(): void {
75
+ this.hooks = [];
76
+ }
77
+
78
+ /**
79
+ * 등록된 훅 수
80
+ */
81
+ get size(): number {
82
+ return this.hooks.length;
83
+ }
84
+ }
85
+
86
+ /**
87
+ * 전역 Pre-Action 훅 레지스트리
88
+ */
89
+ export const preActionRegistry = new PreActionRegistry();
90
+
91
+ /**
92
+ * 설정 로드가 필요없는 명령어
93
+ */
94
+ const SKIP_CONFIG_COMMANDS = new Set([
95
+ "init",
96
+ "help",
97
+ "version",
98
+ "completion",
99
+ ]);
100
+
101
+ /**
102
+ * 배너 표시가 필요없는 명령어
103
+ */
104
+ const SKIP_BANNER_COMMANDS = new Set([
105
+ "completion",
106
+ "version",
107
+ ]);
108
+
109
+ /**
110
+ * verbose 전역 상태
111
+ */
112
+ let globalVerbose = false;
113
+
114
+ /**
115
+ * verbose 모드 설정
116
+ */
117
+ export function setVerbose(value: boolean): void {
118
+ globalVerbose = value;
119
+ }
120
+
121
+ /**
122
+ * verbose 모드 확인
123
+ */
124
+ export function isVerbose(): boolean {
125
+ return globalVerbose;
126
+ }
127
+
128
+ /**
129
+ * 프로세스 타이틀 설정
130
+ */
131
+ export function setProcessTitle(command: string, subcommand?: string): void {
132
+ const title = subcommand
133
+ ? `mandu ${command} ${subcommand}`
134
+ : `mandu ${command}`;
135
+
136
+ if (typeof process.title !== "undefined") {
137
+ process.title = title;
138
+ }
139
+ }
140
+
141
+ /**
142
+ * 기본 Pre-Action 실행
143
+ *
144
+ * @example
145
+ * ```ts
146
+ * const ctx = await runPreAction({
147
+ * command: "dev",
148
+ * options: { port: "3000" },
149
+ * });
150
+ *
151
+ * // ctx.config 에서 로드된 설정 사용
152
+ * // ctx.verbose 로 verbose 모드 확인
153
+ * ```
154
+ */
155
+ export async function runPreAction(params: {
156
+ command: string;
157
+ subcommand?: string;
158
+ options: Record<string, string>;
159
+ cwd?: string;
160
+ version?: string;
161
+ }): Promise<PreActionContext> {
162
+ const {
163
+ command,
164
+ subcommand,
165
+ options,
166
+ cwd = process.cwd(),
167
+ version,
168
+ } = params;
169
+
170
+ // 1. verbose 모드 확인
171
+ const verbose = options.verbose === "true" || process.env.MANDU_VERBOSE === "true";
172
+ setVerbose(verbose);
173
+
174
+ // 2. 프로세스 타이틀 설정
175
+ setProcessTitle(command, subcommand);
176
+
177
+ // 3. 조건부 배너 표시
178
+ const showBanner =
179
+ !SKIP_BANNER_COMMANDS.has(command) &&
180
+ !isTruthyEnv("MANDU_HIDE_BANNER") &&
181
+ shouldShowBanner();
182
+
183
+ if (showBanner && version) {
184
+ console.log(renderMiniBanner(version));
185
+ console.log();
186
+ }
187
+
188
+ // 4. 설정 로드 (필요한 명령어만)
189
+ let config: ManduConfig | undefined;
190
+ if (!SKIP_CONFIG_COMMANDS.has(command)) {
191
+ try {
192
+ config = await loadManduConfig(cwd);
193
+ } catch {
194
+ // 설정 로드 실패 시 무시 (옵션 설정만 사용)
195
+ if (verbose) {
196
+ console.warn("[mandu] Config load failed, using defaults");
197
+ }
198
+ }
199
+ }
200
+
201
+ // Pre-Action 컨텍스트 생성
202
+ const ctx: PreActionContext = {
203
+ command,
204
+ subcommand,
205
+ options,
206
+ config,
207
+ verbose,
208
+ cwd,
209
+ };
210
+
211
+ // 5. 등록된 훅 실행
212
+ await preActionRegistry.runAll(ctx);
213
+
214
+ return ctx;
215
+ }
216
+
217
+ /**
218
+ * 환경변수가 truthy인지 확인
219
+ */
220
+ function isTruthyEnv(key: string): boolean {
221
+ const value = process.env[key];
222
+ if (!value) return false;
223
+ return !["0", "false", "no", ""].includes(value.toLowerCase());
224
+ }
225
+
226
+ /**
227
+ * Pre-Action 훅 등록 헬퍼
228
+ *
229
+ * @example
230
+ * ```ts
231
+ * registerPreActionHook(async (ctx) => {
232
+ * if (ctx.command === "dev") {
233
+ * console.log("Starting development mode...");
234
+ * }
235
+ * });
236
+ * ```
237
+ */
238
+ export function registerPreActionHook(hook: PreActionHook): () => void {
239
+ preActionRegistry.register(hook);
240
+ return () => preActionRegistry.unregister(hook);
241
+ }
242
+
243
+ /**
244
+ * 기본 훅들 등록
245
+ */
246
+ export function registerDefaultHooks(): void {
247
+ // 예: 개발 모드에서 추가 정보 표시
248
+ registerPreActionHook((ctx) => {
249
+ if (ctx.verbose && ctx.config) {
250
+ console.log(`[mandu] Config loaded from ${ctx.cwd}`);
251
+ if (ctx.config.server?.port) {
252
+ console.log(`[mandu] Server port: ${ctx.config.server.port}`);
253
+ }
254
+ }
255
+ });
256
+ }
package/src/main.ts CHANGED
@@ -62,6 +62,7 @@ Commands:
62
62
 
63
63
  Options:
64
64
  --name <name> init 시 프로젝트 이름 (기본: my-mandu-app)
65
+ --template <name> init 템플릿: default, realtime-chat (기본: default)
65
66
  --css <framework> init 시 CSS 프레임워크: tailwind, panda, none (기본: tailwind)
66
67
  --ui <library> init 시 UI 라이브러리: shadcn, ark, none (기본: shadcn)
67
68
  --theme init 시 다크모드 테마 시스템 추가
@@ -102,8 +103,9 @@ Notes:
102
103
  - 포트 충돌 시 다음 사용 가능한 포트로 자동 변경됩니다.
103
104
 
104
105
  Examples:
105
- bunx mandu init --name my-app # Tailwind + shadcn/ui 기본
106
- bunx mandu init my-app --minimal # CSS/UI 없이 최소 템플릿
106
+ bunx mandu init --name my-app # Tailwind + shadcn/ui 기본
107
+ bunx mandu init --name chat-app --template realtime-chat # 실시간 채팅 스타터 템플릿
108
+ bunx mandu init my-app --minimal # CSS/UI 없이 최소 템플릿
107
109
  bunx mandu dev
108
110
  bunx mandu build --watch
109
111
  bunx mandu guard
@@ -141,7 +143,7 @@ Brain (sLLM) Workflow:
141
143
  /**
142
144
  * 인자 파싱
143
145
  */
144
- function parseArgs(args: string[]): { command: string; options: Record<string, string> } {
146
+ export function parseArgs(args: string[]): { command: string; options: Record<string, string> } {
145
147
  const options: Record<string, string> = {};
146
148
  let command = "";
147
149
  const shortFlags: Record<string, string> = {
@@ -184,8 +186,7 @@ function parseArgs(args: string[]): { command: string; options: Record<string, s
184
186
  /**
185
187
  * 메인 함수
186
188
  */
187
- async function main(): Promise<void> {
188
- const args = process.argv.slice(2);
189
+ export async function main(args = process.argv.slice(2)): Promise<void> {
189
190
  const { command, options } = parseArgs(args);
190
191
 
191
192
  // 도움말 처리
@@ -234,7 +235,8 @@ async function main(): Promise<void> {
234
235
  process.exit(1);
235
236
  }
236
237
 
237
- process.exit(0);
238
238
  }
239
239
 
240
- main().catch((error) => handleCLIError(error));
240
+ if (import.meta.main) {
241
+ main().catch((error) => handleCLIError(error));
242
+ }