@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
@@ -3,10 +3,10 @@
3
3
  * OpenAPI 스펙 생성 명령어
4
4
  */
5
5
 
6
- import { generateOpenAPIDocument, openAPIToJSON, validateAndReport } from "@mandujs/core";
7
- import path from "path";
8
- import fs from "fs/promises";
9
- import { resolveManifest } from "../util/manifest";
6
+ import { generateOpenAPIDocument, openAPIToJSON, validateAndReport } from "@mandujs/core";
7
+ import path from "path";
8
+ import fs from "fs/promises";
9
+ import { resolveManifest } from "../util/manifest";
10
10
 
11
11
  interface OpenAPIGenerateOptions {
12
12
  output?: string;
@@ -14,78 +14,78 @@ interface OpenAPIGenerateOptions {
14
14
  version?: string;
15
15
  }
16
16
 
17
- interface OpenAPIServeOptions {
18
- port?: number;
19
- }
20
-
21
- function normalizePort(value: string | number | undefined, label: string): number | undefined {
22
- if (value === undefined || value === null || value === "") {
23
- return undefined;
24
- }
25
- const port = typeof value === "string" ? Number(value) : value;
26
- if (!Number.isFinite(port) || !Number.isInteger(port)) {
27
- console.warn(`⚠️ Invalid ${label} value: "${value}" (using default)`);
28
- return undefined;
29
- }
30
- if (port < 1 || port > 65535) {
31
- console.warn(`⚠️ Invalid ${label} range: ${port} (must be 1-65535, using default)`);
32
- return undefined;
33
- }
34
- return port;
35
- }
36
-
37
- function isPortInUse(error: unknown): boolean {
38
- if (!error || typeof error !== "object") return false;
39
- const code = (error as { code?: string }).code;
40
- const message = (error as { message?: string }).message ?? "";
41
- return code === "EADDRINUSE" || message.includes("EADDRINUSE") || message.includes("address already in use");
42
- }
43
-
44
- function serveWithAutoPort(
45
- startPort: number,
46
- fetch: (req: Request) => Response
47
- ): { server: ReturnType<typeof Bun.serve>; port: number; attempts: number } {
48
- const maxAttempts = 10;
49
- let lastError: unknown = null;
50
-
51
- for (let attempt = 0; attempt < maxAttempts; attempt++) {
52
- const candidate = startPort + attempt;
53
- if (candidate < 1 || candidate > 65535) {
54
- continue;
55
- }
56
- try {
57
- const server = Bun.serve({ port: candidate, fetch });
58
- return { server, port: server.port ?? candidate, attempts: attempt };
59
- } catch (error) {
60
- if (!isPortInUse(error)) {
61
- throw error;
62
- }
63
- lastError = error;
64
- }
65
- }
66
-
67
- throw lastError ?? new Error(`No available port found starting at ${startPort}`);
68
- }
69
-
70
- /**
71
- * Generate OpenAPI specification from contracts
72
- */
73
- export async function openAPIGenerate(options: OpenAPIGenerateOptions = {}): Promise<boolean> {
74
- const rootDir = process.cwd();
75
-
76
- console.log(`\n📄 Generating OpenAPI specification...\n`);
77
-
78
- // Load manifest (FS Routes 우선)
79
- let manifest: Awaited<ReturnType<typeof resolveManifest>>["manifest"];
80
- try {
81
- const config = await validateAndReport(rootDir);
82
- if (!config) return false;
83
- const resolved = await resolveManifest(rootDir, { fsRoutes: config.fsRoutes });
84
- manifest = resolved.manifest;
85
- } catch (error) {
86
- console.error("❌ Failed to load manifest:", error instanceof Error ? error.message : error);
87
- return false;
88
- }
17
+ interface OpenAPIServeOptions {
18
+ port?: number;
19
+ }
20
+
21
+ function normalizePort(value: string | number | undefined, label: string): number | undefined {
22
+ if (value === undefined || value === null || value === "") {
23
+ return undefined;
24
+ }
25
+ const port = typeof value === "string" ? Number(value) : value;
26
+ if (!Number.isFinite(port) || !Number.isInteger(port)) {
27
+ console.warn(`⚠️ Invalid ${label} value: "${value}" (using default)`);
28
+ return undefined;
29
+ }
30
+ if (port < 1 || port > 65535) {
31
+ console.warn(`⚠️ Invalid ${label} range: ${port} (must be 1-65535, using default)`);
32
+ return undefined;
33
+ }
34
+ return port;
35
+ }
36
+
37
+ function isPortInUse(error: unknown): boolean {
38
+ if (!error || typeof error !== "object") return false;
39
+ const code = (error as { code?: string }).code;
40
+ const message = (error as { message?: string }).message ?? "";
41
+ return code === "EADDRINUSE" || message.includes("EADDRINUSE") || message.includes("address already in use");
42
+ }
43
+
44
+ function serveWithAutoPort(
45
+ startPort: number,
46
+ fetch: (req: Request) => Response
47
+ ): { server: ReturnType<typeof Bun.serve>; port: number; attempts: number } {
48
+ const maxAttempts = 10;
49
+ let lastError: unknown = null;
50
+
51
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
52
+ const candidate = startPort + attempt;
53
+ if (candidate < 1 || candidate > 65535) {
54
+ continue;
55
+ }
56
+ try {
57
+ const server = Bun.serve({ port: candidate, fetch });
58
+ return { server, port: server.port ?? candidate, attempts: attempt };
59
+ } catch (error) {
60
+ if (!isPortInUse(error)) {
61
+ throw error;
62
+ }
63
+ lastError = error;
64
+ }
65
+ }
66
+
67
+ throw lastError ?? new Error(`No available port found starting at ${startPort}`);
68
+ }
69
+
70
+ /**
71
+ * Generate OpenAPI specification from contracts
72
+ */
73
+ export async function openAPIGenerate(options: OpenAPIGenerateOptions = {}): Promise<boolean> {
74
+ const rootDir = process.cwd();
75
+
76
+ console.log(`\n📄 Generating OpenAPI specification...\n`);
77
+
78
+ // Load manifest (FS Routes 우선)
79
+ let manifest: Awaited<ReturnType<typeof resolveManifest>>["manifest"];
80
+ try {
81
+ const config = await validateAndReport(rootDir);
82
+ if (!config) return false;
83
+ const resolved = await resolveManifest(rootDir, { fsRoutes: config.fsRoutes });
84
+ manifest = resolved.manifest;
85
+ } catch (error) {
86
+ console.error("❌ Failed to load manifest:", error instanceof Error ? error.message : error);
87
+ return false;
88
+ }
89
89
 
90
90
  // Count routes with contracts
91
91
  const contractRoutes = manifest.routes.filter((r) => r.contractModule);
@@ -148,16 +148,16 @@ export async function openAPIGenerate(options: OpenAPIGenerateOptions = {}): Pro
148
148
  /**
149
149
  * Serve Swagger UI for OpenAPI documentation
150
150
  */
151
- export async function openAPIServe(options: OpenAPIServeOptions = {}): Promise<boolean> {
152
- const rootDir = process.cwd();
153
- const config = await validateAndReport(rootDir);
154
- if (!config) return false;
155
-
156
- const optionPort = normalizePort(options.port, "openapi.port");
157
- const envPort = normalizePort(process.env.PORT, "PORT");
158
- const configPort = normalizePort(config.server?.port, "mandu.config server.port");
159
- const desiredPort = optionPort ?? envPort ?? configPort ?? 8080;
160
- const openAPIPath = path.join(rootDir, "openapi.json");
151
+ export async function openAPIServe(options: OpenAPIServeOptions = {}): Promise<boolean> {
152
+ const rootDir = process.cwd();
153
+ const config = await validateAndReport(rootDir);
154
+ if (!config) return false;
155
+
156
+ const optionPort = normalizePort(options.port, "openapi.port");
157
+ const envPort = normalizePort(process.env.PORT, "PORT");
158
+ const configPort = normalizePort(config.server?.port, "mandu.config server.port");
159
+ const desiredPort = optionPort ?? envPort ?? configPort ?? 8080;
160
+ const openAPIPath = path.join(rootDir, "openapi.json");
161
161
 
162
162
  console.log(`\n🌐 Starting OpenAPI documentation server...\n`);
163
163
 
@@ -212,27 +212,27 @@ export async function openAPIServe(options: OpenAPIServeOptions = {}): Promise<b
212
212
  </html>
213
213
  `.trim();
214
214
 
215
- // Start server (auto port fallback)
216
- const { port, attempts } = serveWithAutoPort(desiredPort, (req) => {
217
- const url = new URL(req.url);
218
-
219
- if (url.pathname === "/openapi.json") {
220
- return new Response(specContent, {
221
- headers: { "Content-Type": "application/json" },
222
- });
223
- }
224
-
225
- return new Response(swaggerHTML, {
226
- headers: { "Content-Type": "text/html" },
227
- });
228
- });
229
-
230
- if (attempts > 0) {
231
- console.warn(`⚠️ Port ${desiredPort} is in use. Using ${port} instead.`);
232
- }
233
-
234
- console.log(`✅ Swagger UI is running at http://localhost:${port}`);
235
- console.log(` OpenAPI spec: http://localhost:${port}/openapi.json`);
215
+ // Start server (auto port fallback)
216
+ const { port, attempts } = serveWithAutoPort(desiredPort, (req) => {
217
+ const url = new URL(req.url);
218
+
219
+ if (url.pathname === "/openapi.json") {
220
+ return new Response(specContent, {
221
+ headers: { "Content-Type": "application/json" },
222
+ });
223
+ }
224
+
225
+ return new Response(swaggerHTML, {
226
+ headers: { "Content-Type": "text/html" },
227
+ });
228
+ });
229
+
230
+ if (attempts > 0) {
231
+ console.warn(`⚠️ Port ${desiredPort} is in use. Using ${port} instead.`);
232
+ }
233
+
234
+ console.log(`✅ Swagger UI is running at http://localhost:${port}`);
235
+ console.log(` OpenAPI spec: http://localhost:${port}/openapi.json`);
236
236
  console.log(`\nPress Ctrl+C to stop.\n`);
237
237
 
238
238
  // Keep server running