@mandujs/cli 0.9.21 β†’ 0.9.23

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 (40) hide show
  1. package/README.ko.md +57 -4
  2. package/README.md +62 -15
  3. package/package.json +2 -2
  4. package/src/commands/check.ts +41 -5
  5. package/src/commands/contract.ts +135 -9
  6. package/src/commands/dev.ts +155 -95
  7. package/src/commands/guard-arch.ts +39 -9
  8. package/src/commands/guard-check.ts +3 -3
  9. package/src/commands/init.ts +264 -9
  10. package/src/commands/monitor.ts +301 -0
  11. package/src/main.ts +421 -361
  12. package/templates/default/app/globals.css +37 -0
  13. package/templates/default/app/layout.tsx +27 -0
  14. package/templates/default/app/page.tsx +27 -49
  15. package/templates/default/package.json +20 -11
  16. package/templates/default/postcss.config.js +6 -0
  17. package/templates/default/src/client/app/index.ts +1 -0
  18. package/templates/default/src/client/entities/index.ts +1 -0
  19. package/templates/default/src/client/features/index.ts +1 -0
  20. package/templates/default/src/client/pages/index.ts +1 -0
  21. package/templates/default/src/client/shared/index.ts +1 -0
  22. package/templates/default/src/client/shared/lib/utils.ts +16 -0
  23. package/templates/default/src/client/shared/ui/button.tsx +57 -0
  24. package/templates/default/src/client/shared/ui/card.tsx +78 -0
  25. package/templates/default/src/client/shared/ui/index.ts +21 -0
  26. package/templates/default/src/client/shared/ui/input.tsx +24 -0
  27. package/templates/default/src/client/widgets/index.ts +1 -0
  28. package/templates/default/src/server/api/index.ts +1 -0
  29. package/templates/default/src/server/application/index.ts +1 -0
  30. package/templates/default/src/server/core/index.ts +1 -0
  31. package/templates/default/src/server/domain/index.ts +1 -0
  32. package/templates/default/src/server/infra/index.ts +1 -0
  33. package/templates/default/src/shared/contracts/index.ts +1 -0
  34. package/templates/default/src/shared/env/index.ts +1 -0
  35. package/templates/default/src/shared/schema/index.ts +1 -0
  36. package/templates/default/src/shared/types/index.ts +1 -0
  37. package/templates/default/src/shared/utils/client/index.ts +1 -0
  38. package/templates/default/src/shared/utils/server/index.ts +1 -0
  39. package/templates/default/tailwind.config.ts +64 -0
  40. package/templates/default/tsconfig.json +14 -3
@@ -4,19 +4,24 @@ import {
4
4
  registerPageLoader,
5
5
  registerPageHandler,
6
6
  registerLayoutLoader,
7
- startDevBundler,
8
- createHMRServer,
9
- needsHydration,
10
- loadEnv,
11
- generateManifest,
12
- watchFSRoutes,
13
- clearDefaultRegistry,
14
- createGuardWatcher,
15
- getPreset,
16
- type RoutesManifest,
17
- type GuardConfig,
18
- type GuardPreset,
19
- } from "@mandujs/core";
7
+ startDevBundler,
8
+ createHMRServer,
9
+ needsHydration,
10
+ loadEnv,
11
+ generateManifest,
12
+ watchFSRoutes,
13
+ clearDefaultRegistry,
14
+ createGuardWatcher,
15
+ checkDirectory,
16
+ printReport,
17
+ formatReportForAgent,
18
+ formatReportAsAgentJSON,
19
+ getPreset,
20
+ type RoutesManifest,
21
+ type GuardConfig,
22
+ type GuardPreset,
23
+ type Violation,
24
+ } from "@mandujs/core";
20
25
  import { isDirectory, resolveFromCwd } from "../util/fs";
21
26
  import { resolveOutputFormat, type OutputFormat } from "../util/output";
22
27
  import path from "path";
@@ -35,10 +40,10 @@ export interface DevOptions {
35
40
  guardFormat?: OutputFormat;
36
41
  }
37
42
 
38
- export async function dev(options: DevOptions = {}): Promise<void> {
39
- const rootDir = resolveFromCwd(".");
40
-
41
- console.log(`πŸ₯Ÿ Mandu Dev Server (FS Routes)`);
43
+ export async function dev(options: DevOptions = {}): Promise<void> {
44
+ const rootDir = resolveFromCwd(".");
45
+
46
+ console.log(`πŸ₯Ÿ Mandu Dev Server (FS Routes)`);
42
47
 
43
48
  // .env 파일 λ‘œλ“œ
44
49
  const envResult = await loadEnv({
@@ -70,11 +75,75 @@ export async function dev(options: DevOptions = {}): Promise<void> {
70
75
  process.exit(1);
71
76
  }
72
77
 
73
- let manifest = result.manifest;
74
- console.log(`βœ… ${manifest.routes.length}개 라우트 발견\n`);
75
-
76
- // Layout 경둜 좔적 (쀑볡 등둝 λ°©μ§€)
77
- const registeredLayouts = new Set<string>();
78
+ let manifest = result.manifest;
79
+ console.log(`βœ… ${manifest.routes.length}개 라우트 발견\n`);
80
+
81
+ const enableFsRoutes = !options.legacy && await isDirectory(path.resolve(rootDir, "app"));
82
+ const guardPreset = options.guardPreset || "mandu";
83
+ const guardFormat = resolveOutputFormat(options.guardFormat);
84
+ const guardConfig: GuardConfig | null =
85
+ options.guard === false
86
+ ? null
87
+ : {
88
+ preset: guardPreset,
89
+ srcDir: "src",
90
+ realtime: true,
91
+ realtimeOutput: guardFormat,
92
+ fsRoutes: enableFsRoutes
93
+ ? {
94
+ noPageToPage: true,
95
+ pageCanImport: [
96
+ "client/pages",
97
+ "client/widgets",
98
+ "client/features",
99
+ "client/entities",
100
+ "client/shared",
101
+ "shared/contracts",
102
+ "shared/types",
103
+ "shared/utils/client",
104
+ ],
105
+ layoutCanImport: [
106
+ "client/app",
107
+ "client/widgets",
108
+ "client/shared",
109
+ "shared/contracts",
110
+ "shared/types",
111
+ "shared/utils/client",
112
+ ],
113
+ routeCanImport: [
114
+ "server/api",
115
+ "server/application",
116
+ "server/domain",
117
+ "server/infra",
118
+ "server/core",
119
+ "shared/contracts",
120
+ "shared/schema",
121
+ "shared/types",
122
+ "shared/utils/client",
123
+ "shared/utils/server",
124
+ "shared/env",
125
+ ],
126
+ }
127
+ : undefined,
128
+ };
129
+
130
+ if (guardConfig) {
131
+ const preflightReport = await checkDirectory(guardConfig, rootDir);
132
+ if (preflightReport.bySeverity.error > 0) {
133
+ if (guardFormat === "json") {
134
+ console.log(formatReportAsAgentJSON(preflightReport, guardPreset));
135
+ } else if (guardFormat === "agent") {
136
+ console.log(formatReportForAgent(preflightReport, guardPreset));
137
+ } else {
138
+ printReport(preflightReport, getPreset(guardPreset).hierarchy);
139
+ }
140
+ console.error("\n❌ Architecture Guard failed. Fix errors before starting dev server.");
141
+ process.exit(1);
142
+ }
143
+ }
144
+
145
+ // Layout 경둜 좔적 (쀑볡 등둝 λ°©μ§€)
146
+ const registeredLayouts = new Set<string>();
78
147
 
79
148
  // ν•Έλ“€λŸ¬ 등둝 ν•¨μˆ˜
80
149
  const registerHandlers = async (manifest: RoutesManifest, isReload = false) => {
@@ -145,9 +214,9 @@ export async function dev(options: DevOptions = {}): Promise<void> {
145
214
  let hmrServer: ReturnType<typeof createHMRServer> | null = null;
146
215
  let devBundler: Awaited<ReturnType<typeof startDevBundler>> | null = null;
147
216
 
148
- const hasIslands = manifest.routes.some(
149
- (r) => r.kind === "page" && r.clientModule && needsHydration(r)
150
- );
217
+ const hasIslands = manifest.routes.some(
218
+ (r) => r.kind === "page" && r.clientModule && needsHydration(r)
219
+ );
151
220
 
152
221
  if (hasIslands && !options.noHmr) {
153
222
  // HMR μ„œλ²„ μ‹œμž‘
@@ -206,59 +275,10 @@ export async function dev(options: DevOptions = {}): Promise<void> {
206
275
  bundleManifest: devBundler?.initialBuild.manifest,
207
276
  });
208
277
 
209
- // Architecture Guard μ‹€μ‹œκ°„ κ°μ‹œ (선택적)
210
- let archGuardWatcher: ReturnType<typeof createGuardWatcher> | null = null;
211
-
212
- if (options.guard !== false) {
213
- const guardPreset = options.guardPreset || "mandu";
214
- const guardFormat = resolveOutputFormat(options.guardFormat);
215
- const enableFsRoutes = !options.legacy && await isDirectory(path.resolve(rootDir, "app"));
216
- const guardConfig: GuardConfig = {
217
- preset: guardPreset,
218
- srcDir: "src",
219
- realtime: true,
220
- realtimeOutput: guardFormat,
221
- fsRoutes: enableFsRoutes
222
- ? {
223
- noPageToPage: true,
224
- pageCanImport: ["widgets", "features", "entities", "shared"],
225
- layoutCanImport: ["widgets", "shared"],
226
- }
227
- : undefined,
228
- };
229
-
230
- console.log(`πŸ›‘οΈ Architecture Guard ν™œμ„±ν™” (${guardPreset})`);
231
-
232
- archGuardWatcher = createGuardWatcher({
233
- config: guardConfig,
234
- rootDir,
235
- onViolation: (violation) => {
236
- // μ‹€μ‹œκ°„ κ²½κ³ λŠ” watcher λ‚΄λΆ€μ—μ„œ 처리
237
- },
238
- onFileAnalyzed: (analysis, violations) => {
239
- if (violations.length > 0) {
240
- // HMR μ—λŸ¬λ‘œ λΈŒλ‘œλ“œμΊμŠ€νŠΈ
241
- hmrServer?.broadcast({
242
- type: "guard-violation",
243
- data: {
244
- file: analysis.filePath,
245
- violations: violations.map((v) => ({
246
- line: v.line,
247
- message: `${v.fromLayer} β†’ ${v.toLayer}: ${v.ruleDescription}`,
248
- })),
249
- },
250
- });
251
- }
252
- },
253
- });
254
-
255
- archGuardWatcher.start();
256
- }
257
-
258
- // FS Routes μ‹€μ‹œκ°„ κ°μ‹œ
259
- const routesWatcher = await watchFSRoutes(rootDir, {
260
- skipLegacy: true,
261
- onChange: async (result) => {
278
+ // FS Routes μ‹€μ‹œκ°„ κ°μ‹œ
279
+ const routesWatcher = await watchFSRoutes(rootDir, {
280
+ skipLegacy: true,
281
+ onChange: async (result) => {
262
282
  const timestamp = new Date().toLocaleTimeString();
263
283
  console.log(`\nπŸ”„ [${timestamp}] 라우트 λ³€κ²½ 감지`);
264
284
 
@@ -279,20 +299,60 @@ export async function dev(options: DevOptions = {}): Promise<void> {
279
299
  data: { timestamp: Date.now() },
280
300
  });
281
301
  }
282
- },
283
- });
284
-
285
- // 정리 ν•¨μˆ˜
286
- const cleanup = () => {
287
- console.log("\nπŸ›‘ μ„œλ²„ μ’…λ£Œ 쀑...");
288
- server.stop();
289
- devBundler?.close();
290
- hmrServer?.close();
291
- routesWatcher.close();
292
- archGuardWatcher?.close();
293
- process.exit(0);
294
- };
295
-
296
- process.on("SIGINT", cleanup);
297
- process.on("SIGTERM", cleanup);
298
- }
302
+ },
303
+ });
304
+
305
+ // Architecture Guard μ‹€μ‹œκ°„ κ°μ‹œ (선택적)
306
+ let archGuardWatcher: ReturnType<typeof createGuardWatcher> | null = null;
307
+ let guardFailed = false;
308
+
309
+ // 정리 ν•¨μˆ˜
310
+ const cleanup = () => {
311
+ console.log("\nπŸ›‘ μ„œλ²„ μ’…λ£Œ 쀑...");
312
+ server.stop();
313
+ devBundler?.close();
314
+ hmrServer?.close();
315
+ routesWatcher.close();
316
+ archGuardWatcher?.close();
317
+ process.exit(0);
318
+ };
319
+
320
+ const stopOnGuardError = (violation: Violation) => {
321
+ if (violation.severity !== "error" || guardFailed) {
322
+ return;
323
+ }
324
+ guardFailed = true;
325
+ console.error("\n❌ Architecture Guard violation detected. Stopping dev server.");
326
+ cleanup();
327
+ };
328
+
329
+ if (guardConfig) {
330
+ console.log(`πŸ›‘οΈ Architecture Guard ν™œμ„±ν™” (${guardPreset})`);
331
+
332
+ archGuardWatcher = createGuardWatcher({
333
+ config: guardConfig,
334
+ rootDir,
335
+ onViolation: stopOnGuardError,
336
+ onFileAnalyzed: (analysis, violations) => {
337
+ if (violations.length > 0) {
338
+ // HMR μ—λŸ¬λ‘œ λΈŒλ‘œλ“œμΊμŠ€νŠΈ
339
+ hmrServer?.broadcast({
340
+ type: "guard-violation",
341
+ data: {
342
+ file: analysis.filePath,
343
+ violations: violations.map((v) => ({
344
+ line: v.line,
345
+ message: `${v.fromLayer} β†’ ${v.toLayer}: ${v.ruleDescription}`,
346
+ })),
347
+ },
348
+ });
349
+ }
350
+ },
351
+ });
352
+
353
+ archGuardWatcher.start();
354
+ }
355
+
356
+ process.on("SIGINT", cleanup);
357
+ process.on("SIGTERM", cleanup);
358
+ }
@@ -109,8 +109,37 @@ export async function guardArch(options: GuardArchOptions = {}): Promise<boolean
109
109
  fsRoutes: enableFsRoutes
110
110
  ? {
111
111
  noPageToPage: true,
112
- pageCanImport: ["widgets", "features", "entities", "shared"],
113
- layoutCanImport: ["widgets", "shared"],
112
+ pageCanImport: [
113
+ "client/pages",
114
+ "client/widgets",
115
+ "client/features",
116
+ "client/entities",
117
+ "client/shared",
118
+ "shared/contracts",
119
+ "shared/types",
120
+ "shared/utils/client",
121
+ ],
122
+ layoutCanImport: [
123
+ "client/app",
124
+ "client/widgets",
125
+ "client/shared",
126
+ "shared/contracts",
127
+ "shared/types",
128
+ "shared/utils/client",
129
+ ],
130
+ routeCanImport: [
131
+ "server/api",
132
+ "server/application",
133
+ "server/domain",
134
+ "server/infra",
135
+ "server/core",
136
+ "shared/contracts",
137
+ "shared/schema",
138
+ "shared/types",
139
+ "shared/utils/client",
140
+ "shared/utils/server",
141
+ "shared/env",
142
+ ],
114
143
  }
115
144
  : undefined,
116
145
  };
@@ -238,19 +267,20 @@ export async function guardArch(options: GuardArchOptions = {}): Promise<boolean
238
267
  }
239
268
 
240
269
  // CI λͺ¨λ“œμ—μ„œ μ—λŸ¬κ°€ 있으면 μ‹€νŒ¨
241
- if (ci && report.bySeverity.error > 0) {
242
- console.log("\n❌ Architecture check failed");
243
- return false;
244
- }
270
+ const hasErrors = report.bySeverity.error > 0;
271
+ const hasWarnings = report.bySeverity.warn > 0;
245
272
 
246
273
  if (report.totalViolations === 0) {
247
274
  console.log("\nβœ… Architecture check passed");
248
275
  return true;
249
276
  }
250
277
 
251
- if (report.bySeverity.error > 0) {
252
- console.log(`\n⚠️ ${report.bySeverity.error} error(s) found - please fix before continuing`);
253
- return !ci;
278
+ if (hasErrors || (ci && hasWarnings)) {
279
+ const reason = hasErrors
280
+ ? `${report.bySeverity.error} error(s)`
281
+ : `${report.bySeverity.warn} warning(s)`;
282
+ console.log(`\n❌ Architecture check failed: ${reason}`);
283
+ return false;
254
284
  }
255
285
 
256
286
  console.log(`\n⚠️ ${report.totalViolations} issue(s) found`);
@@ -13,13 +13,13 @@ export interface GuardCheckOptions {
13
13
  autoCorrect?: boolean;
14
14
  }
15
15
 
16
- export async function guardCheck(options: GuardCheckOptions = {}): Promise<boolean> {
17
- const { autoCorrect = true } = options;
16
+ export async function guardCheck(options: GuardCheckOptions = {}): Promise<boolean> {
17
+ const { autoCorrect = true } = options;
18
18
 
19
19
  const specPath = resolveFromCwd("spec/routes.manifest.json");
20
20
  const rootDir = getRootDir();
21
21
 
22
- console.log(`πŸ₯Ÿ Mandu Guard`);
22
+ console.log(`πŸ₯Ÿ Mandu Guard (Legacy Spec)`);
23
23
  console.log(`πŸ“„ Spec 파일: ${specPath}`);
24
24
  console.log(`πŸ”§ Auto-correct: ${autoCorrect ? "ON" : "OFF"}\n`);
25
25