@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.
Files changed (51) hide show
  1. package/README.ko.md +234 -234
  2. package/README.md +354 -354
  3. package/package.json +2 -2
  4. package/src/commands/contract.ts +173 -173
  5. package/src/commands/dev.ts +8 -68
  6. package/src/commands/doctor.ts +27 -27
  7. package/src/commands/guard-arch.ts +303 -303
  8. package/src/commands/guard-check.ts +3 -3
  9. package/src/commands/monitor.ts +300 -300
  10. package/src/commands/openapi.ts +107 -107
  11. package/src/commands/registry.ts +367 -357
  12. package/src/commands/routes.ts +228 -228
  13. package/src/commands/start.ts +184 -0
  14. package/src/errors/codes.ts +35 -35
  15. package/src/errors/index.ts +2 -2
  16. package/src/errors/messages.ts +143 -143
  17. package/src/hooks/index.ts +17 -17
  18. package/src/hooks/preaction.ts +256 -256
  19. package/src/main.ts +37 -34
  20. package/src/terminal/banner.ts +166 -166
  21. package/src/terminal/help.ts +306 -306
  22. package/src/terminal/index.ts +71 -71
  23. package/src/terminal/output.ts +295 -295
  24. package/src/terminal/palette.ts +30 -30
  25. package/src/terminal/progress.ts +327 -327
  26. package/src/terminal/stream-writer.ts +214 -214
  27. package/src/terminal/table.ts +354 -354
  28. package/src/terminal/theme.ts +142 -142
  29. package/src/util/bun.ts +6 -6
  30. package/src/util/fs.ts +23 -23
  31. package/src/util/handlers.ts +96 -0
  32. package/src/util/manifest.ts +52 -52
  33. package/src/util/output.ts +22 -22
  34. package/src/util/port.ts +71 -71
  35. package/templates/default/AGENTS.md +96 -96
  36. package/templates/default/app/api/health/route.ts +13 -13
  37. package/templates/default/app/globals.css +49 -49
  38. package/templates/default/app/layout.tsx +27 -27
  39. package/templates/default/app/page.tsx +38 -38
  40. package/templates/default/package.json +1 -0
  41. package/templates/default/src/client/shared/lib/utils.ts +16 -16
  42. package/templates/default/src/client/shared/ui/button.tsx +57 -57
  43. package/templates/default/src/client/shared/ui/card.tsx +78 -78
  44. package/templates/default/src/client/shared/ui/index.ts +21 -21
  45. package/templates/default/src/client/shared/ui/input.tsx +24 -24
  46. package/templates/default/tests/example.test.ts +58 -58
  47. package/templates/default/tests/helpers.ts +52 -52
  48. package/templates/default/tests/setup.ts +9 -9
  49. package/templates/default/tsconfig.json +12 -14
  50. package/templates/default/apps/server/main.ts +0 -67
  51. package/templates/default/apps/web/entry.tsx +0 -35
@@ -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
@@ -27,6 +27,7 @@ Commands:
27
27
  routes watch 실시간 라우트 감시
28
28
  dev 개발 서버 실행 (FS Routes + Guard 기본)
29
29
  build 클라이언트 번들 빌드 (Hydration)
30
+ start 프로덕션 서버 실행 (build 후)
30
31
  guard 아키텍처 위반 검사 (기본)
31
32
  guard arch 아키텍처 위반 검사 (FSD/Clean/Hexagonal)
32
33
  guard legacy 레거시 Spec Guard 검사
@@ -125,7 +126,7 @@ Examples:
125
126
  bunx mandu lock --diff --show-secrets # 변경사항 상세 비교
126
127
 
127
128
  FS Routes Workflow (권장):
128
- 1. init → 2. app/ 폴더에 page.tsx 생성 → 3. dev → 4. build
129
+ 1. init → 2. app/ 폴더에 page.tsx 생성 → 3. dev → 4. build → 5. start
129
130
 
130
131
  Legacy Workflow:
131
132
  1. init → 2. spec-upsert → 3. generate → 4. build → 5. guard → 6. dev
@@ -140,39 +141,39 @@ Brain (sLLM) Workflow:
140
141
  /**
141
142
  * 인자 파싱
142
143
  */
143
- function parseArgs(args: string[]): { command: string; options: Record<string, string> } {
144
- const options: Record<string, string> = {};
145
- let command = "";
146
- const shortFlags: Record<string, string> = {
147
- h: "help",
148
- q: "quiet",
149
- v: "verify",
150
- d: "diff",
151
- };
152
-
153
- for (let i = 0; i < args.length; i++) {
154
- const arg = args[i];
155
-
156
- // 플래그 처리
157
- if (arg.startsWith("--")) {
158
- const key = arg.slice(2);
159
- const value = args[i + 1] && !args[i + 1].startsWith("--") ? args[++i] : "true";
160
- options[key] = value;
161
- } else if (arg.startsWith("-") && arg.length > 1) {
162
- const flags = arg.slice(1).split("");
163
- for (const flag of flags) {
164
- const mapped = shortFlags[flag];
165
- if (mapped) {
166
- options[mapped] = "true";
167
- } else {
168
- options[flag] = "true";
169
- }
170
- }
171
- } else if (!command) {
172
- // 첫 번째 비플래그 인자가 명령어
173
- command = arg;
174
- } else if (!options._positional) {
175
- // 두 번째 비플래그 인자가 positional
144
+ function parseArgs(args: string[]): { command: string; options: Record<string, string> } {
145
+ const options: Record<string, string> = {};
146
+ let command = "";
147
+ const shortFlags: Record<string, string> = {
148
+ h: "help",
149
+ q: "quiet",
150
+ v: "verify",
151
+ d: "diff",
152
+ };
153
+
154
+ for (let i = 0; i < args.length; i++) {
155
+ const arg = args[i];
156
+
157
+ // 플래그 처리
158
+ if (arg.startsWith("--")) {
159
+ const key = arg.slice(2);
160
+ const value = args[i + 1] && !args[i + 1].startsWith("--") ? args[++i] : "true";
161
+ options[key] = value;
162
+ } else if (arg.startsWith("-") && arg.length > 1) {
163
+ const flags = arg.slice(1).split("");
164
+ for (const flag of flags) {
165
+ const mapped = shortFlags[flag];
166
+ if (mapped) {
167
+ options[mapped] = "true";
168
+ } else {
169
+ options[flag] = "true";
170
+ }
171
+ }
172
+ } else if (!command) {
173
+ // 첫 번째 비플래그 인자가 명령어
174
+ command = arg;
175
+ } else if (!options._positional) {
176
+ // 두 번째 비플래그 인자가 positional
176
177
  options._positional = arg;
177
178
  }
178
179
  }
@@ -232,6 +233,8 @@ async function main(): Promise<void> {
232
233
  }
233
234
  process.exit(1);
234
235
  }
236
+
237
+ process.exit(0);
235
238
  }
236
239
 
237
240
  main().catch((error) => handleCLIError(error));