@mandujs/cli 0.9.42 → 0.9.43

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.
@@ -0,0 +1,52 @@
1
+ import path from "path";
2
+ import {
3
+ generateManifest,
4
+ loadManifest,
5
+ type RoutesManifest,
6
+ type FSScannerConfig,
7
+ } from "@mandujs/core";
8
+ import { isDirectory } from "./fs";
9
+
10
+ export type ManifestSource = "fs" | "spec";
11
+
12
+ export interface ResolvedManifest {
13
+ manifest: RoutesManifest;
14
+ source: ManifestSource;
15
+ warnings: string[];
16
+ }
17
+
18
+ export async function resolveManifest(
19
+ rootDir: string,
20
+ options: { fsRoutes?: FSScannerConfig; outputPath?: string } = {}
21
+ ): Promise<ResolvedManifest> {
22
+ const appDir = path.resolve(rootDir, "app");
23
+ const hasApp = await isDirectory(appDir);
24
+
25
+ if (hasApp) {
26
+ const result = await generateManifest(rootDir, {
27
+ scanner: options.fsRoutes,
28
+ outputPath: options.outputPath,
29
+ skipLegacy: true,
30
+ });
31
+ return {
32
+ manifest: result.manifest,
33
+ source: "fs",
34
+ warnings: result.warnings,
35
+ };
36
+ }
37
+
38
+ const specPath = path.join(rootDir, "spec", "routes.manifest.json");
39
+ if (await Bun.file(specPath).exists()) {
40
+ const result = await loadManifest(specPath);
41
+ if (!result.success) {
42
+ throw new Error(result.errors?.join(", ") || "Failed to load routes manifest");
43
+ }
44
+ return {
45
+ manifest: result.data!,
46
+ source: "spec",
47
+ warnings: [],
48
+ };
49
+ }
50
+
51
+ throw new Error("No routes found. Create app/ routes or spec/routes.manifest.json");
52
+ }
@@ -0,0 +1,71 @@
1
+ import { createServer } from "net";
2
+
3
+ const DEFAULT_MAX_ATTEMPTS = 10;
4
+
5
+ function isPortUsable(error: unknown): boolean {
6
+ if (!error || typeof error !== "object") return false;
7
+ const code = (error as { code?: string }).code;
8
+ return code === "EADDRINUSE" || code === "EACCES";
9
+ }
10
+
11
+ async function isPortAvailable(port: number, hostname?: string): Promise<boolean> {
12
+ return new Promise((resolve) => {
13
+ const server = createServer();
14
+
15
+ server.once("error", (error) => {
16
+ if (isPortUsable(error)) {
17
+ resolve(false);
18
+ } else {
19
+ resolve(false);
20
+ }
21
+ });
22
+
23
+ server.once("listening", () => {
24
+ server.close(() => resolve(true));
25
+ });
26
+
27
+ try {
28
+ server.listen(port, hostname);
29
+ server.unref();
30
+ } catch {
31
+ resolve(false);
32
+ }
33
+ });
34
+ }
35
+
36
+ export async function resolveAvailablePort(
37
+ startPort: number,
38
+ options: {
39
+ hostname?: string;
40
+ offsets?: number[];
41
+ maxAttempts?: number;
42
+ } = {}
43
+ ): Promise<{ port: number; attempts: number }> {
44
+ const offsets = options.offsets && options.offsets.length > 0 ? options.offsets : [0];
45
+ const maxAttempts = options.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;
46
+
47
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
48
+ const candidate = startPort + attempt;
49
+ if (candidate < 1 || candidate > 65535) {
50
+ continue;
51
+ }
52
+
53
+ const targets = offsets
54
+ .map((offset) => candidate + offset)
55
+ .filter((port) => port >= 1 && port <= 65535);
56
+
57
+ if (targets.length !== offsets.length) {
58
+ continue;
59
+ }
60
+
61
+ const results = await Promise.all(
62
+ targets.map((port) => isPortAvailable(port, options.hostname))
63
+ );
64
+
65
+ if (results.every(Boolean)) {
66
+ return { port: candidate, attempts: attempt };
67
+ }
68
+ }
69
+
70
+ throw new Error(`No available port found starting at ${startPort}`);
71
+ }
@@ -0,0 +1,96 @@
1
+ # AI Agent Instructions for Mandu Project
2
+
3
+ 이 프로젝트는 **Mandu Framework**로 구축되었습니다. AI 에이전트가 이 프로젝트를 다룰 때 아래 지침을 따라주세요.
4
+
5
+ ## 패키지 매니저: Bun (필수)
6
+
7
+ **⚠️ 중요: 이 프로젝트는 Bun만 사용합니다. npm/yarn/pnpm을 사용하지 마세요.**
8
+
9
+ ```bash
10
+ # ✅ 올바른 명령어
11
+ bun install # 의존성 설치
12
+ bun add <package> # 패키지 추가
13
+ bun remove <package> # 패키지 제거
14
+ bun run dev # 개발 서버 시작
15
+ bun run build # 프로덕션 빌드
16
+ bun test # 테스트 실행
17
+
18
+ # ❌ 사용 금지
19
+ npm install / yarn install / pnpm install
20
+ ```
21
+
22
+ ## 프로젝트 구조
23
+
24
+ ```
25
+ ├── app/ # FS 기반 라우팅 (페이지, API)
26
+ │ ├── page.tsx # / 라우트
27
+ │ ├── layout.tsx # 루트 레이아웃
28
+ │ ├── globals.css # Tailwind CSS (v4)
29
+ │ └── api/ # API 라우트
30
+ ├── src/
31
+ │ ├── client/ # 클라이언트 코드 (FSD 구조)
32
+ │ │ ├── shared/ # 공용 UI, 유틸리티
33
+ │ │ ├── entities/ # 엔티티 컴포넌트
34
+ │ │ ├── features/ # 기능 컴포넌트
35
+ │ │ └── widgets/ # 위젯/Island 컴포넌트
36
+ │ ├── server/ # 서버 코드 (Clean Architecture)
37
+ │ │ ├── domain/ # 도메인 모델
38
+ │ │ ├── application/ # 비즈니스 로직
39
+ │ │ └── infra/ # 인프라/DB
40
+ │ └── shared/ # 클라이언트-서버 공유 코드
41
+ │ ├── contracts/ # API 계약 타입
42
+ │ └── types/ # 공용 타입
43
+ └── mandu.config.ts # Mandu 설정 (선택)
44
+ ```
45
+
46
+ ## 주요 규칙
47
+
48
+ ### 1. Island 컴포넌트
49
+ 클라이언트 상호작용이 필요한 컴포넌트는 `*.island.tsx`로 명명:
50
+ ```tsx
51
+ // src/client/widgets/counter/Counter.island.tsx
52
+ "use client";
53
+ export function CounterIsland() { ... }
54
+ ```
55
+
56
+ ### 2. API 라우트
57
+ `app/api/` 폴더에 `route.ts` 파일로 정의:
58
+ ```typescript
59
+ // app/api/users/route.ts
60
+ import { Mandu } from "@mandujs/core";
61
+ export default Mandu.filling()
62
+ .get((ctx) => ctx.ok({ users: [] }))
63
+ .post(async (ctx) => { ... });
64
+ ```
65
+
66
+ ### 3. Tailwind CSS v4
67
+ CSS-first 설정 사용 (`tailwind.config.ts` 없음):
68
+ ```css
69
+ /* app/globals.css */
70
+ @import "tailwindcss";
71
+ @theme {
72
+ --color-primary: hsl(222.2 47.4% 11.2%);
73
+ }
74
+ ```
75
+
76
+ ### 4. Import Alias
77
+ `@/` = `src/` 경로:
78
+ ```typescript
79
+ import { Button } from "@/client/shared/ui/button";
80
+ ```
81
+
82
+ ## 실행 방법
83
+
84
+ ```bash
85
+ bun install # 최초 설치
86
+ bun run dev # 개발 서버 (http://localhost:4000)
87
+ bun run build # 프로덕션 빌드
88
+ bun run guard # 아키텍처 검증
89
+ ```
90
+
91
+ ## 기술 스택
92
+
93
+ - **Runtime**: Bun 1.x
94
+ - **Framework**: Mandu (React 19 + Bun native)
95
+ - **Styling**: Tailwind CSS v4
96
+ - **Language**: TypeScript 5.x
@@ -1,37 +1,49 @@
1
- @tailwind base;
2
- @tailwind components;
3
- @tailwind utilities;
1
+ @import "tailwindcss";
4
2
 
5
- @layer base {
6
- :root {
7
- --background: 0 0% 100%;
8
- --foreground: 222.2 84% 4.9%;
9
- --card: 0 0% 100%;
10
- --card-foreground: 222.2 84% 4.9%;
11
- --popover: 0 0% 100%;
12
- --popover-foreground: 222.2 84% 4.9%;
13
- --primary: 222.2 47.4% 11.2%;
14
- --primary-foreground: 210 40% 98%;
15
- --secondary: 210 40% 96.1%;
16
- --secondary-foreground: 222.2 47.4% 11.2%;
17
- --muted: 210 40% 96.1%;
18
- --muted-foreground: 215.4 16.3% 46.9%;
19
- --accent: 210 40% 96.1%;
20
- --accent-foreground: 222.2 47.4% 11.2%;
21
- --destructive: 0 84.2% 60.2%;
22
- --destructive-foreground: 210 40% 98%;
23
- --border: 214.3 31.8% 91.4%;
24
- --input: 214.3 31.8% 91.4%;
25
- --ring: 222.2 84% 4.9%;
26
- --radius: 0.5rem;
27
- }
3
+ /*
4
+ * Tailwind CSS v4 - CSS-first Configuration
5
+ * https://tailwindcss.com/docs/v4
6
+ */
7
+
8
+ @theme {
9
+ /* Colors - shadcn/ui compatible */
10
+ --color-background: hsl(0 0% 100%);
11
+ --color-foreground: hsl(222.2 84% 4.9%);
12
+ --color-card: hsl(0 0% 100%);
13
+ --color-card-foreground: hsl(222.2 84% 4.9%);
14
+ --color-popover: hsl(0 0% 100%);
15
+ --color-popover-foreground: hsl(222.2 84% 4.9%);
16
+ --color-primary: hsl(222.2 47.4% 11.2%);
17
+ --color-primary-foreground: hsl(210 40% 98%);
18
+ --color-secondary: hsl(210 40% 96.1%);
19
+ --color-secondary-foreground: hsl(222.2 47.4% 11.2%);
20
+ --color-muted: hsl(210 40% 96.1%);
21
+ --color-muted-foreground: hsl(215.4 16.3% 46.9%);
22
+ --color-accent: hsl(210 40% 96.1%);
23
+ --color-accent-foreground: hsl(222.2 47.4% 11.2%);
24
+ --color-destructive: hsl(0 84.2% 60.2%);
25
+ --color-destructive-foreground: hsl(210 40% 98%);
26
+ --color-border: hsl(214.3 31.8% 91.4%);
27
+ --color-input: hsl(214.3 31.8% 91.4%);
28
+ --color-ring: hsl(222.2 84% 4.9%);
29
+
30
+ /* Radius */
31
+ --radius-sm: 0.25rem;
32
+ --radius-md: 0.5rem;
33
+ --radius-lg: 0.75rem;
34
+ --radius-xl: 1rem;
35
+
36
+ /* Fonts */
37
+ --font-sans: 'Inter', ui-sans-serif, system-ui, sans-serif;
38
+ }
39
+
40
+ /* Base styles */
41
+ * {
42
+ border-color: var(--color-border);
28
43
  }
29
44
 
30
- @layer base {
31
- * {
32
- @apply border-border;
33
- }
34
- body {
35
- @apply bg-background text-foreground;
36
- }
45
+ body {
46
+ background-color: var(--color-background);
47
+ color: var(--color-foreground);
48
+ font-family: var(--font-sans);
37
49
  }
@@ -2,15 +2,19 @@
2
2
  "name": "{{PROJECT_NAME}}",
3
3
  "version": "0.1.0",
4
4
  "type": "module",
5
- "scripts": {
6
- "dev": "mandu dev",
7
- "build": "mandu build",
8
- "check": "mandu check",
9
- "guard": "mandu guard",
10
- "test": "bun test"
11
- },
5
+ "packageManager": "bun@1.2.0",
6
+ "engines": {
7
+ "bun": ">=1.0.0"
8
+ },
9
+ "scripts": {
10
+ "dev": "mandu dev",
11
+ "build": "mandu build",
12
+ "check": "mandu check",
13
+ "guard": "mandu guard",
14
+ "test": "bun test"
15
+ },
12
16
  "dependencies": {
13
- "@mandujs/core": "^0.9.39",
17
+ "@mandujs/core": "^0.9.42",
14
18
  "@radix-ui/react-slot": "^1.1.0",
15
19
  "class-variance-authority": "^0.7.0",
16
20
  "clsx": "^2.1.1",
@@ -19,12 +23,11 @@
19
23
  "tailwind-merge": "^2.5.2"
20
24
  },
21
25
  "devDependencies": {
22
- "@mandujs/cli": "^0.9.21",
26
+ "@mandujs/cli": "^0.9.42",
27
+ "@tailwindcss/cli": "^4.1.0",
23
28
  "@types/react": "^19.2.0",
24
29
  "@types/react-dom": "^19.2.0",
25
- "autoprefixer": "^10.4.20",
26
- "postcss": "^8.4.47",
27
- "tailwindcss": "^3.4.14",
30
+ "tailwindcss": "^4.1.0",
28
31
  "typescript": "^5.0.0"
29
32
  }
30
33
  }
@@ -1,6 +0,0 @@
1
- export default {
2
- plugins: {
3
- tailwindcss: {},
4
- autoprefixer: {},
5
- },
6
- };
@@ -1,64 +0,0 @@
1
- import type { Config } from "tailwindcss";
2
-
3
- const config: Config = {
4
- content: [
5
- "./app/**/*.{ts,tsx,mdx}",
6
- "./src/**/*.{ts,tsx,mdx}",
7
- ],
8
- theme: {
9
- extend: {
10
- colors: {
11
- background: "hsl(var(--background))",
12
- foreground: "hsl(var(--foreground))",
13
- card: {
14
- DEFAULT: "hsl(var(--card))",
15
- foreground: "hsl(var(--card-foreground))",
16
- },
17
- popover: {
18
- DEFAULT: "hsl(var(--popover))",
19
- foreground: "hsl(var(--popover-foreground))",
20
- },
21
- primary: {
22
- DEFAULT: "hsl(var(--primary))",
23
- foreground: "hsl(var(--primary-foreground))",
24
- },
25
- secondary: {
26
- DEFAULT: "hsl(var(--secondary))",
27
- foreground: "hsl(var(--secondary-foreground))",
28
- },
29
- muted: {
30
- DEFAULT: "hsl(var(--muted))",
31
- foreground: "hsl(var(--muted-foreground))",
32
- },
33
- accent: {
34
- DEFAULT: "hsl(var(--accent))",
35
- foreground: "hsl(var(--accent-foreground))",
36
- },
37
- destructive: {
38
- DEFAULT: "hsl(var(--destructive))",
39
- foreground: "hsl(var(--destructive-foreground))",
40
- },
41
- border: "hsl(var(--border))",
42
- input: "hsl(var(--input))",
43
- ring: "hsl(var(--ring))",
44
- },
45
- borderRadius: {
46
- lg: "var(--radius)",
47
- md: "calc(var(--radius) - 2px)",
48
- sm: "calc(var(--radius) - 4px)",
49
- },
50
- fontFamily: {
51
- sans: [
52
- "-apple-system",
53
- "BlinkMacSystemFont",
54
- "Segoe UI",
55
- "Roboto",
56
- "sans-serif",
57
- ],
58
- },
59
- },
60
- },
61
- plugins: [],
62
- };
63
-
64
- export default config;