@mandujs/cli 0.9.43 → 0.9.45
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.
- package/package.json +1 -1
- package/src/commands/init.ts +113 -52
package/package.json
CHANGED
package/src/commands/init.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import path from "path";
|
|
2
|
-
import fs from "fs/promises";
|
|
3
|
-
import { CLI_ERROR_CODES, printCLIError } from "../errors";
|
|
1
|
+
import path from "path";
|
|
2
|
+
import fs from "fs/promises";
|
|
3
|
+
import { CLI_ERROR_CODES, printCLIError } from "../errors";
|
|
4
4
|
|
|
5
5
|
export type CSSFramework = "tailwind" | "panda" | "none";
|
|
6
6
|
export type UILibrary = "shadcn" | "ark" | "none";
|
|
@@ -21,13 +21,13 @@ const CSS_FILES = [
|
|
|
21
21
|
"app/globals.css",
|
|
22
22
|
];
|
|
23
23
|
|
|
24
|
-
const UI_FILES = [
|
|
25
|
-
"src/client/shared/ui/button.tsx",
|
|
26
|
-
"src/client/shared/ui/card.tsx",
|
|
27
|
-
"src/client/shared/ui/input.tsx",
|
|
28
|
-
"src/client/shared/ui/index.ts",
|
|
29
|
-
"src/client/shared/lib/utils.ts",
|
|
30
|
-
];
|
|
24
|
+
const UI_FILES = [
|
|
25
|
+
"src/client/shared/ui/button.tsx",
|
|
26
|
+
"src/client/shared/ui/card.tsx",
|
|
27
|
+
"src/client/shared/ui/input.tsx",
|
|
28
|
+
"src/client/shared/ui/index.ts",
|
|
29
|
+
"src/client/shared/lib/utils.ts",
|
|
30
|
+
];
|
|
31
31
|
|
|
32
32
|
interface CopyOptions {
|
|
33
33
|
projectName: string;
|
|
@@ -46,15 +46,15 @@ function shouldSkipFile(relativePath: string, options: CopyOptions): boolean {
|
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
// Skip UI files if ui is none
|
|
50
|
-
if (options.ui === "none") {
|
|
51
|
-
if (UI_FILES.some((f) => normalizedPath.endsWith(f))) {
|
|
52
|
-
return true;
|
|
53
|
-
}
|
|
54
|
-
// Skip UI/shared directories
|
|
55
|
-
if (normalizedPath.includes("src/client/shared/ui/")) return true;
|
|
56
|
-
if (normalizedPath.includes("src/client/shared/lib/")) return true;
|
|
57
|
-
}
|
|
49
|
+
// Skip UI files if ui is none
|
|
50
|
+
if (options.ui === "none") {
|
|
51
|
+
if (UI_FILES.some((f) => normalizedPath.endsWith(f))) {
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
// Skip UI/shared directories
|
|
55
|
+
if (normalizedPath.includes("src/client/shared/ui/")) return true;
|
|
56
|
+
if (normalizedPath.includes("src/client/shared/lib/")) return true;
|
|
57
|
+
}
|
|
58
58
|
|
|
59
59
|
return false;
|
|
60
60
|
}
|
|
@@ -76,13 +76,13 @@ async function copyDir(
|
|
|
76
76
|
: entry.name;
|
|
77
77
|
|
|
78
78
|
if (entry.isDirectory()) {
|
|
79
|
-
// Skip directories that would be empty when ui=none
|
|
80
|
-
if (options.ui === "none") {
|
|
81
|
-
if (entry.name === "ui" && relativePath === "src/client/shared") continue;
|
|
82
|
-
if (entry.name === "lib" && relativePath === "src/client/shared") continue;
|
|
83
|
-
}
|
|
84
|
-
await copyDir(srcPath, destPath, options, currentRelativePath);
|
|
85
|
-
} else {
|
|
79
|
+
// Skip directories that would be empty when ui=none
|
|
80
|
+
if (options.ui === "none") {
|
|
81
|
+
if (entry.name === "ui" && relativePath === "src/client/shared") continue;
|
|
82
|
+
if (entry.name === "lib" && relativePath === "src/client/shared") continue;
|
|
83
|
+
}
|
|
84
|
+
await copyDir(srcPath, destPath, options, currentRelativePath);
|
|
85
|
+
} else {
|
|
86
86
|
// Check if file should be skipped
|
|
87
87
|
if (shouldSkipFile(currentRelativePath, options)) {
|
|
88
88
|
continue;
|
|
@@ -163,8 +163,8 @@ export async function init(options: InitOptions = {}): Promise<boolean> {
|
|
|
163
163
|
// Check if target directory exists
|
|
164
164
|
try {
|
|
165
165
|
await fs.access(targetDir);
|
|
166
|
-
printCLIError(CLI_ERROR_CODES.INIT_DIR_EXISTS, { path: targetDir });
|
|
167
|
-
return false;
|
|
166
|
+
printCLIError(CLI_ERROR_CODES.INIT_DIR_EXISTS, { path: targetDir });
|
|
167
|
+
return false;
|
|
168
168
|
} catch {
|
|
169
169
|
// Directory doesn't exist, good to proceed
|
|
170
170
|
}
|
|
@@ -176,9 +176,9 @@ export async function init(options: InitOptions = {}): Promise<boolean> {
|
|
|
176
176
|
try {
|
|
177
177
|
await fs.access(templateDir);
|
|
178
178
|
} catch {
|
|
179
|
-
printCLIError(CLI_ERROR_CODES.INIT_TEMPLATE_NOT_FOUND, { template });
|
|
180
|
-
console.error(` 사용 가능한 템플릿: default`);
|
|
181
|
-
return false;
|
|
179
|
+
printCLIError(CLI_ERROR_CODES.INIT_TEMPLATE_NOT_FOUND, { template });
|
|
180
|
+
console.error(` 사용 가능한 템플릿: default`);
|
|
181
|
+
return false;
|
|
182
182
|
}
|
|
183
183
|
|
|
184
184
|
console.log(`📋 템플릿 복사 중...`);
|
|
@@ -215,32 +215,43 @@ export async function init(options: InitOptions = {}): Promise<boolean> {
|
|
|
215
215
|
await updatePackageJson(targetDir, css, ui);
|
|
216
216
|
}
|
|
217
217
|
|
|
218
|
+
// Setup .mcp.json for AI agent integration
|
|
219
|
+
const mcpResult = await setupMcpConfig(targetDir);
|
|
220
|
+
|
|
218
221
|
console.log(`\n✅ 프로젝트 생성 완료!\n`);
|
|
219
222
|
console.log(`📍 위치: ${targetDir}`);
|
|
220
223
|
console.log(`\n🚀 시작하기:`);
|
|
221
224
|
console.log(` cd ${projectName}`);
|
|
222
225
|
console.log(` bun install`);
|
|
223
226
|
console.log(` bun run dev`);
|
|
224
|
-
console.log(`\n📂 파일 구조:`);
|
|
225
|
-
console.log(` app/layout.tsx → 루트 레이아웃`);
|
|
226
|
-
console.log(` app/page.tsx → http://localhost:3000/`);
|
|
227
|
-
console.log(` app/api/*/route.ts → API endpoints`);
|
|
228
|
-
console.log(` src/client/* → 클라이언트 레이어`);
|
|
229
|
-
console.log(` src/server/* → 서버 레이어`);
|
|
230
|
-
console.log(` src/shared/contracts → 계약 (client-safe)`);
|
|
231
|
-
console.log(` src/shared/types → 공용 타입`);
|
|
232
|
-
console.log(` src/shared/utils/client → 클라이언트 safe 유틸`);
|
|
233
|
-
console.log(` src/shared/utils/server → 서버 전용 유틸`);
|
|
234
|
-
console.log(` src/shared/schema → 서버 전용 스키마`);
|
|
235
|
-
console.log(` src/shared/env → 서버 전용 환경`);
|
|
236
|
-
if (css !== "none") {
|
|
237
|
-
console.log(` app/globals.css → 전역 CSS (Tailwind)`);
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
console.log(` src/client/shared/
|
|
242
|
-
|
|
243
|
-
|
|
227
|
+
console.log(`\n📂 파일 구조:`);
|
|
228
|
+
console.log(` app/layout.tsx → 루트 레이아웃`);
|
|
229
|
+
console.log(` app/page.tsx → http://localhost:3000/`);
|
|
230
|
+
console.log(` app/api/*/route.ts → API endpoints`);
|
|
231
|
+
console.log(` src/client/* → 클라이언트 레이어`);
|
|
232
|
+
console.log(` src/server/* → 서버 레이어`);
|
|
233
|
+
console.log(` src/shared/contracts → 계약 (client-safe)`);
|
|
234
|
+
console.log(` src/shared/types → 공용 타입`);
|
|
235
|
+
console.log(` src/shared/utils/client → 클라이언트 safe 유틸`);
|
|
236
|
+
console.log(` src/shared/utils/server → 서버 전용 유틸`);
|
|
237
|
+
console.log(` src/shared/schema → 서버 전용 스키마`);
|
|
238
|
+
console.log(` src/shared/env → 서버 전용 환경`);
|
|
239
|
+
if (css !== "none") {
|
|
240
|
+
console.log(` app/globals.css → 전역 CSS (Tailwind v4)`);
|
|
241
|
+
}
|
|
242
|
+
if (ui !== "none") {
|
|
243
|
+
console.log(` src/client/shared/ui/ → UI 컴포넌트 (shadcn)`);
|
|
244
|
+
console.log(` src/client/shared/lib/utils.ts → 유틸리티 (cn 함수)`);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// MCP 설정 안내
|
|
248
|
+
console.log(`\n🤖 AI 에이전트 통합:`);
|
|
249
|
+
if (mcpResult.created) {
|
|
250
|
+
console.log(` .mcp.json 생성됨 (Claude Code 자동 연결)`);
|
|
251
|
+
} else if (mcpResult.updated) {
|
|
252
|
+
console.log(` .mcp.json에 mandu 서버 추가됨`);
|
|
253
|
+
}
|
|
254
|
+
console.log(` AGENTS.md → 에이전트 가이드 (Bun 사용 명시)`);
|
|
244
255
|
|
|
245
256
|
return true;
|
|
246
257
|
}
|
|
@@ -326,8 +337,10 @@ async function updatePackageJson(
|
|
|
326
337
|
const pkg = JSON.parse(content);
|
|
327
338
|
|
|
328
339
|
if (css === "none") {
|
|
329
|
-
// Remove Tailwind dependencies
|
|
340
|
+
// Remove Tailwind dependencies (v4)
|
|
330
341
|
delete pkg.devDependencies?.tailwindcss;
|
|
342
|
+
delete pkg.devDependencies?.["@tailwindcss/cli"];
|
|
343
|
+
// Legacy v3 (just in case)
|
|
331
344
|
delete pkg.devDependencies?.postcss;
|
|
332
345
|
delete pkg.devDependencies?.autoprefixer;
|
|
333
346
|
}
|
|
@@ -342,3 +355,51 @@ async function updatePackageJson(
|
|
|
342
355
|
|
|
343
356
|
await fs.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
344
357
|
}
|
|
358
|
+
|
|
359
|
+
interface McpConfigResult {
|
|
360
|
+
created: boolean;
|
|
361
|
+
updated: boolean;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* .mcp.json 설정 (AI 에이전트 통합)
|
|
366
|
+
* - 파일 없으면 새로 생성
|
|
367
|
+
* - 파일 있으면 mandu 서버만 추가/업데이트 (다른 설정 유지)
|
|
368
|
+
*/
|
|
369
|
+
async function setupMcpConfig(targetDir: string): Promise<McpConfigResult> {
|
|
370
|
+
const mcpPath = path.join(targetDir, ".mcp.json");
|
|
371
|
+
|
|
372
|
+
const manduServer = {
|
|
373
|
+
command: "bunx",
|
|
374
|
+
args: ["@mandujs/mcp"],
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
try {
|
|
378
|
+
// 기존 파일 확인
|
|
379
|
+
const existingContent = await fs.readFile(mcpPath, "utf-8");
|
|
380
|
+
const existing = JSON.parse(existingContent);
|
|
381
|
+
|
|
382
|
+
// 기존 설정에 mandu 서버 추가/업데이트
|
|
383
|
+
if (!existing.mcpServers) {
|
|
384
|
+
existing.mcpServers = {};
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const hadMandu = !!existing.mcpServers.mandu;
|
|
388
|
+
existing.mcpServers.mandu = manduServer;
|
|
389
|
+
|
|
390
|
+
await fs.writeFile(mcpPath, JSON.stringify(existing, null, 2) + "\n");
|
|
391
|
+
|
|
392
|
+
return { created: false, updated: true };
|
|
393
|
+
} catch {
|
|
394
|
+
// 파일 없음 - 새로 생성
|
|
395
|
+
const newConfig = {
|
|
396
|
+
mcpServers: {
|
|
397
|
+
mandu: manduServer,
|
|
398
|
+
},
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
await fs.writeFile(mcpPath, JSON.stringify(newConfig, null, 2) + "\n");
|
|
402
|
+
|
|
403
|
+
return { created: true, updated: false };
|
|
404
|
+
}
|
|
405
|
+
}
|