@mandujs/cli 0.9.24 → 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.
- package/README.ko.md +1 -1
- package/README.md +3 -3
- package/package.json +2 -2
- package/src/commands/build.ts +50 -22
- package/src/commands/check.ts +16 -18
- package/src/commands/contract.ts +50 -42
- package/src/commands/dev.ts +294 -212
- package/src/commands/doctor.ts +27 -25
- package/src/commands/guard-arch.ts +25 -10
- package/src/commands/init.ts +8 -7
- package/src/commands/monitor.ts +2 -3
- package/src/commands/openapi.ts +107 -48
- package/src/commands/routes.ts +11 -1
- package/src/errors/codes.ts +35 -0
- package/src/errors/index.ts +2 -0
- package/src/errors/messages.ts +143 -0
- package/src/main.ts +103 -157
- package/src/util/bun.ts +6 -0
- package/src/util/manifest.ts +52 -0
- package/src/util/port.ts +71 -0
- package/templates/default/AGENTS.md +96 -0
- package/templates/default/app/globals.css +45 -33
- package/templates/default/package.json +15 -12
- package/templates/default/postcss.config.js +0 -6
- package/templates/default/tailwind.config.ts +0 -64
package/README.ko.md
CHANGED
package/README.md
CHANGED
|
@@ -43,7 +43,9 @@ bun run dev
|
|
|
43
43
|
bunx mandu dev
|
|
44
44
|
```
|
|
45
45
|
|
|
46
|
-
Your app is now running at `http://localhost:
|
|
46
|
+
Your app is now running at `http://localhost:3333`.
|
|
47
|
+
|
|
48
|
+
To change the port, set `PORT` or use `server.port` in `mandu.config`. If the port is in use, Mandu will pick the next available port.
|
|
47
49
|
|
|
48
50
|
### 3. Create Pages in `app/` Directory
|
|
49
51
|
|
|
@@ -254,7 +256,6 @@ bunx mandu guard --preset fsd
|
|
|
254
256
|
|
|
255
257
|
| Option | Description |
|
|
256
258
|
|--------|-------------|
|
|
257
|
-
| `--port <n>` | Server port (default: 3000) |
|
|
258
259
|
| `--guard` | Enable Guard watching |
|
|
259
260
|
| `--guard-preset <p>` | Guard preset (default: mandu) |
|
|
260
261
|
|
|
@@ -307,7 +308,6 @@ bunx mandu guard --preset fsd
|
|
|
307
308
|
bunx @mandujs/cli init my-app
|
|
308
309
|
|
|
309
310
|
# Development
|
|
310
|
-
bunx mandu dev --port 3000
|
|
311
311
|
bunx mandu dev --guard
|
|
312
312
|
|
|
313
313
|
# Routes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mandujs/cli",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.43",
|
|
4
4
|
"description": "Agent-Native Fullstack Framework - 에이전트가 코딩해도 아키텍처가 무너지지 않는 개발 OS",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/main.ts",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"access": "public"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@mandujs/core": "
|
|
35
|
+
"@mandujs/core": "workspace:*"
|
|
36
36
|
},
|
|
37
37
|
"engines": {
|
|
38
38
|
"bun": ">=1.0.0"
|
package/src/commands/build.ts
CHANGED
|
@@ -2,11 +2,13 @@
|
|
|
2
2
|
* mandu build - 클라이언트 번들 빌드
|
|
3
3
|
*
|
|
4
4
|
* Hydration이 필요한 Island들을 번들링합니다.
|
|
5
|
+
* Tailwind v4 프로젝트는 CSS도 함께 빌드합니다.
|
|
5
6
|
*/
|
|
6
7
|
|
|
7
|
-
import {
|
|
8
|
+
import { buildClientBundles, printBundleStats, validateAndReport, isTailwindProject, buildCSS, type RoutesManifest } from "@mandujs/core";
|
|
8
9
|
import path from "path";
|
|
9
10
|
import fs from "fs/promises";
|
|
11
|
+
import { resolveManifest } from "../util/manifest";
|
|
10
12
|
|
|
11
13
|
export interface BuildOptions {
|
|
12
14
|
/** 코드 압축 (기본: production에서 true) */
|
|
@@ -21,22 +23,26 @@ export interface BuildOptions {
|
|
|
21
23
|
|
|
22
24
|
export async function build(options: BuildOptions = {}): Promise<boolean> {
|
|
23
25
|
const cwd = process.cwd();
|
|
24
|
-
const specPath = path.join(cwd, "spec", "routes.manifest.json");
|
|
25
26
|
|
|
26
27
|
console.log("📦 Mandu Build - Client Bundle Builder\n");
|
|
27
28
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
if (!specResult.success) {
|
|
31
|
-
console.error("❌ Spec 로드 실패:");
|
|
32
|
-
for (const error of specResult.errors) {
|
|
33
|
-
console.error(` ${error}`);
|
|
34
|
-
}
|
|
29
|
+
const config = await validateAndReport(cwd);
|
|
30
|
+
if (!config) {
|
|
35
31
|
return false;
|
|
36
32
|
}
|
|
33
|
+
const buildConfig = config.build ?? {};
|
|
37
34
|
|
|
38
|
-
|
|
39
|
-
|
|
35
|
+
// 1. 라우트 매니페스트 로드 (FS Routes 우선)
|
|
36
|
+
let manifest: Awaited<ReturnType<typeof resolveManifest>>["manifest"];
|
|
37
|
+
try {
|
|
38
|
+
const resolved = await resolveManifest(cwd, { fsRoutes: config.fsRoutes });
|
|
39
|
+
manifest = resolved.manifest;
|
|
40
|
+
console.log(`✅ 라우트 로드 완료 (${resolved.source}): ${manifest.routes.length}개 라우트`);
|
|
41
|
+
} catch (error) {
|
|
42
|
+
console.error("❌ 라우트 로드 실패:");
|
|
43
|
+
console.error(` ${error instanceof Error ? error.message : error}`);
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
40
46
|
|
|
41
47
|
// 2. Hydration이 필요한 라우트 확인
|
|
42
48
|
const hydratedRoutes = manifest.routes.filter(
|
|
@@ -58,15 +64,34 @@ export async function build(options: BuildOptions = {}): Promise<boolean> {
|
|
|
58
64
|
console.log(` - ${route.id} (${hydration.strategy}, ${hydration.priority || "visible"})`);
|
|
59
65
|
}
|
|
60
66
|
|
|
61
|
-
// 3.
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
67
|
+
// 3. Tailwind CSS 빌드 (감지 시에만)
|
|
68
|
+
const hasTailwind = await isTailwindProject(cwd);
|
|
69
|
+
if (hasTailwind) {
|
|
70
|
+
console.log(`\n🎨 Tailwind CSS v4 빌드 중...`);
|
|
71
|
+
const cssResult = await buildCSS({
|
|
72
|
+
rootDir: cwd,
|
|
73
|
+
minify: options.minify ?? true,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
if (!cssResult.success) {
|
|
77
|
+
console.error(`\n❌ CSS 빌드 실패: ${cssResult.error}`);
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
console.log(` ✅ CSS 빌드 완료 (${cssResult.buildTime?.toFixed(0)}ms)`);
|
|
82
|
+
console.log(` 출력: ${cssResult.outputPath}`);
|
|
83
|
+
}
|
|
68
84
|
|
|
69
|
-
// 4.
|
|
85
|
+
// 4. 번들 빌드
|
|
86
|
+
const startTime = performance.now();
|
|
87
|
+
const resolvedBuildOptions: BuildOptions = {
|
|
88
|
+
minify: options.minify ?? buildConfig.minify,
|
|
89
|
+
sourcemap: options.sourcemap ?? buildConfig.sourcemap,
|
|
90
|
+
outDir: options.outDir ?? buildConfig.outDir,
|
|
91
|
+
};
|
|
92
|
+
const result = await buildClientBundles(manifest, cwd, resolvedBuildOptions);
|
|
93
|
+
|
|
94
|
+
// 5. 결과 출력
|
|
70
95
|
console.log("");
|
|
71
96
|
printBundleStats(result);
|
|
72
97
|
|
|
@@ -78,13 +103,16 @@ export async function build(options: BuildOptions = {}): Promise<boolean> {
|
|
|
78
103
|
const elapsed = (performance.now() - startTime).toFixed(0);
|
|
79
104
|
console.log(`\n✅ 빌드 완료 (${elapsed}ms)`);
|
|
80
105
|
console.log(` 출력: .mandu/client/`);
|
|
106
|
+
if (hasTailwind) {
|
|
107
|
+
console.log(` CSS: .mandu/client/globals.css`);
|
|
108
|
+
}
|
|
81
109
|
|
|
82
|
-
//
|
|
110
|
+
// 6. 감시 모드
|
|
83
111
|
if (options.watch) {
|
|
84
112
|
console.log("\n👀 파일 감시 모드...");
|
|
85
113
|
console.log(" Ctrl+C로 종료\n");
|
|
86
114
|
|
|
87
|
-
await watchAndRebuild(manifest, cwd,
|
|
115
|
+
await watchAndRebuild(manifest, cwd, resolvedBuildOptions);
|
|
88
116
|
}
|
|
89
117
|
|
|
90
118
|
return true;
|
|
@@ -94,7 +122,7 @@ export async function build(options: BuildOptions = {}): Promise<boolean> {
|
|
|
94
122
|
* 파일 감시 및 재빌드
|
|
95
123
|
*/
|
|
96
124
|
async function watchAndRebuild(
|
|
97
|
-
manifest:
|
|
125
|
+
manifest: RoutesManifest,
|
|
98
126
|
rootDir: string,
|
|
99
127
|
options: BuildOptions
|
|
100
128
|
): Promise<void> {
|
package/src/commands/check.ts
CHANGED
|
@@ -10,32 +10,28 @@ import {
|
|
|
10
10
|
checkDirectory,
|
|
11
11
|
printReport,
|
|
12
12
|
getPreset,
|
|
13
|
+
validateAndReport,
|
|
13
14
|
loadManifest,
|
|
14
15
|
runGuardCheck,
|
|
15
16
|
buildGuardReport,
|
|
16
17
|
printReportSummary,
|
|
17
18
|
type GuardConfig,
|
|
18
|
-
type GuardPreset,
|
|
19
19
|
} from "@mandujs/core";
|
|
20
20
|
import path from "path";
|
|
21
21
|
import { resolveFromCwd, isDirectory, pathExists } from "../util/fs";
|
|
22
|
-
import { resolveOutputFormat
|
|
23
|
-
|
|
24
|
-
export interface CheckOptions {
|
|
25
|
-
preset?: GuardPreset;
|
|
26
|
-
format?: OutputFormat;
|
|
27
|
-
ci?: boolean;
|
|
28
|
-
quiet?: boolean;
|
|
29
|
-
legacy?: boolean;
|
|
30
|
-
}
|
|
22
|
+
import { resolveOutputFormat } from "../util/output";
|
|
31
23
|
|
|
32
|
-
export async function check(
|
|
24
|
+
export async function check(): Promise<boolean> {
|
|
33
25
|
const rootDir = resolveFromCwd(".");
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
const
|
|
38
|
-
const
|
|
26
|
+
const config = await validateAndReport(rootDir);
|
|
27
|
+
if (!config) return false;
|
|
28
|
+
|
|
29
|
+
const guardConfigFromFile = config.guard ?? {};
|
|
30
|
+
const preset = guardConfigFromFile.preset ?? "mandu";
|
|
31
|
+
const format = resolveOutputFormat();
|
|
32
|
+
const quiet = false;
|
|
33
|
+
const strictWarnings = process.env.CI === "true";
|
|
34
|
+
const enableFsRoutes = await isDirectory(path.resolve(rootDir, "app"));
|
|
39
35
|
const specPath = resolveFromCwd("spec/routes.manifest.json");
|
|
40
36
|
const hasSpec = await pathExists(specPath);
|
|
41
37
|
|
|
@@ -70,6 +66,7 @@ export async function check(options: CheckOptions = {}): Promise<boolean> {
|
|
|
70
66
|
try {
|
|
71
67
|
if (format === "console") {
|
|
72
68
|
const result = await generateManifest(rootDir, {
|
|
69
|
+
scanner: config.fsRoutes,
|
|
73
70
|
outputPath: ".mandu/routes.manifest.json",
|
|
74
71
|
skipLegacy: true,
|
|
75
72
|
});
|
|
@@ -95,7 +92,7 @@ export async function check(options: CheckOptions = {}): Promise<boolean> {
|
|
|
95
92
|
log("");
|
|
96
93
|
}
|
|
97
94
|
} else {
|
|
98
|
-
const scan = await scanRoutes(rootDir);
|
|
95
|
+
const scan = await scanRoutes(rootDir, config.fsRoutes);
|
|
99
96
|
routesSummary.count = scan.routes.length;
|
|
100
97
|
routesSummary.warnings = scan.errors.map((e) => `${e.type}: ${e.message}`);
|
|
101
98
|
}
|
|
@@ -119,7 +116,8 @@ export async function check(options: CheckOptions = {}): Promise<boolean> {
|
|
|
119
116
|
// 2) Architecture Guard 검사
|
|
120
117
|
const guardConfig: GuardConfig = {
|
|
121
118
|
preset,
|
|
122
|
-
srcDir: "src",
|
|
119
|
+
srcDir: guardConfigFromFile.srcDir ?? "src",
|
|
120
|
+
exclude: guardConfigFromFile.exclude,
|
|
123
121
|
fsRoutes: enableFsRoutes
|
|
124
122
|
? {
|
|
125
123
|
noPageToPage: true,
|
package/src/commands/contract.ts
CHANGED
|
@@ -4,16 +4,17 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import {
|
|
7
|
-
loadManifest,
|
|
8
7
|
runContractGuardCheck,
|
|
9
8
|
generateContractTemplate,
|
|
10
9
|
buildContractRegistry,
|
|
11
10
|
writeContractRegistry,
|
|
12
11
|
readContractRegistry,
|
|
13
12
|
diffContractRegistry,
|
|
13
|
+
validateAndReport,
|
|
14
14
|
} from "@mandujs/core";
|
|
15
15
|
import path from "path";
|
|
16
16
|
import fs from "fs/promises";
|
|
17
|
+
import { resolveManifest } from "../util/manifest";
|
|
17
18
|
|
|
18
19
|
interface ContractCreateOptions {
|
|
19
20
|
routeId: string;
|
|
@@ -33,24 +34,32 @@ interface ContractDiffOptions {
|
|
|
33
34
|
output?: string;
|
|
34
35
|
json?: boolean;
|
|
35
36
|
}
|
|
37
|
+
|
|
38
|
+
async function loadRoutesManifest(rootDir: string) {
|
|
39
|
+
const config = await validateAndReport(rootDir);
|
|
40
|
+
if (!config) {
|
|
41
|
+
throw new Error("Invalid mandu.config");
|
|
42
|
+
}
|
|
43
|
+
const resolved = await resolveManifest(rootDir, { fsRoutes: config.fsRoutes });
|
|
44
|
+
return resolved.manifest;
|
|
45
|
+
}
|
|
36
46
|
|
|
37
47
|
/**
|
|
38
48
|
* Create a new contract file for a route
|
|
39
49
|
*/
|
|
40
|
-
export async function contractCreate(options: ContractCreateOptions): Promise<boolean> {
|
|
41
|
-
const rootDir = process.cwd();
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const manifest = manifestResult.data!;
|
|
50
|
+
export async function contractCreate(options: ContractCreateOptions): Promise<boolean> {
|
|
51
|
+
const rootDir = process.cwd();
|
|
52
|
+
|
|
53
|
+
console.log(`\n📜 Creating contract for route: ${options.routeId}\n`);
|
|
54
|
+
|
|
55
|
+
// Load manifest
|
|
56
|
+
let manifest;
|
|
57
|
+
try {
|
|
58
|
+
manifest = await loadRoutesManifest(rootDir);
|
|
59
|
+
} catch (error) {
|
|
60
|
+
console.error("❌ Failed to load manifest:", error instanceof Error ? error.message : error);
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
54
63
|
|
|
55
64
|
// Find the route
|
|
56
65
|
const route = manifest.routes.find((r) => r.id === options.routeId);
|
|
@@ -105,22 +114,21 @@ export async function contractCreate(options: ContractCreateOptions): Promise<bo
|
|
|
105
114
|
* Validate all contracts against their slot implementations
|
|
106
115
|
*/
|
|
107
116
|
export async function contractValidate(options: ContractValidateOptions = {}): Promise<boolean> {
|
|
108
|
-
const rootDir = process.cwd();
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
const violations = await runContractGuardCheck(manifest, rootDir);
|
|
117
|
+
const rootDir = process.cwd();
|
|
118
|
+
|
|
119
|
+
console.log(`\n🔍 Validating contracts...\n`);
|
|
120
|
+
|
|
121
|
+
// Load manifest
|
|
122
|
+
let manifest;
|
|
123
|
+
try {
|
|
124
|
+
manifest = await loadRoutesManifest(rootDir);
|
|
125
|
+
} catch (error) {
|
|
126
|
+
console.error("❌ Failed to load manifest:", error instanceof Error ? error.message : error);
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Run contract guard check
|
|
131
|
+
const violations = await runContractGuardCheck(manifest, rootDir);
|
|
124
132
|
|
|
125
133
|
if (violations.length === 0) {
|
|
126
134
|
console.log(`✅ All contracts are valid!\n`);
|
|
@@ -175,18 +183,17 @@ export async function contractValidate(options: ContractValidateOptions = {}): P
|
|
|
175
183
|
*/
|
|
176
184
|
export async function contractBuild(options: ContractBuildOptions = {}): Promise<boolean> {
|
|
177
185
|
const rootDir = process.cwd();
|
|
178
|
-
const manifestPath = path.join(rootDir, "spec/routes.manifest.json");
|
|
179
186
|
const outputPath = options.output || path.join(rootDir, ".mandu", "contracts.json");
|
|
180
187
|
|
|
181
188
|
console.log(`\n📦 Building contract registry...\n`);
|
|
182
189
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
190
|
+
let manifest;
|
|
191
|
+
try {
|
|
192
|
+
manifest = await loadRoutesManifest(rootDir);
|
|
193
|
+
} catch (error) {
|
|
194
|
+
console.error("❌ Failed to load manifest:", error instanceof Error ? error.message : error);
|
|
186
195
|
return false;
|
|
187
196
|
}
|
|
188
|
-
|
|
189
|
-
const manifest = manifestResult.data!;
|
|
190
197
|
const { registry, warnings } = await buildContractRegistry(manifest, rootDir);
|
|
191
198
|
|
|
192
199
|
if (warnings.length > 0) {
|
|
@@ -211,7 +218,6 @@ export async function contractBuild(options: ContractBuildOptions = {}): Promise
|
|
|
211
218
|
*/
|
|
212
219
|
export async function contractDiff(options: ContractDiffOptions = {}): Promise<boolean> {
|
|
213
220
|
const rootDir = process.cwd();
|
|
214
|
-
const manifestPath = path.join(rootDir, "spec/routes.manifest.json");
|
|
215
221
|
const fromPath = options.from || path.join(rootDir, ".mandu", "contracts.json");
|
|
216
222
|
|
|
217
223
|
console.log(`\n🔍 Diffing contracts...\n`);
|
|
@@ -226,12 +232,14 @@ export async function contractDiff(options: ContractDiffOptions = {}): Promise<b
|
|
|
226
232
|
let toRegistry = options.to ? await readContractRegistry(options.to) : null;
|
|
227
233
|
|
|
228
234
|
if (!toRegistry) {
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
235
|
+
let manifest;
|
|
236
|
+
try {
|
|
237
|
+
manifest = await loadRoutesManifest(rootDir);
|
|
238
|
+
} catch (error) {
|
|
239
|
+
console.error("❌ Failed to load manifest:", error instanceof Error ? error.message : error);
|
|
232
240
|
return false;
|
|
233
241
|
}
|
|
234
|
-
const { registry } = await buildContractRegistry(
|
|
242
|
+
const { registry } = await buildContractRegistry(manifest, rootDir);
|
|
235
243
|
toRegistry = registry;
|
|
236
244
|
}
|
|
237
245
|
|