@mandujs/cli 0.15.1 β 0.15.2
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 +33 -33
- package/README.md +354 -354
- package/package.json +2 -2
- package/src/commands/check.ts +71 -7
- package/src/commands/contract.ts +173 -173
- package/src/commands/dev.ts +9 -42
- package/src/commands/guard-arch.ts +303 -303
- package/src/commands/init.ts +50 -5
- package/src/commands/monitor.ts +300 -300
- package/src/commands/openapi.ts +107 -107
- package/src/commands/registry.ts +1 -0
- package/src/commands/start.ts +9 -42
- package/src/errors/codes.ts +35 -35
- package/src/errors/index.ts +2 -2
- package/src/errors/messages.ts +143 -143
- package/src/hooks/index.ts +17 -17
- package/src/hooks/preaction.ts +256 -256
- package/src/main.ts +9 -7
- package/src/terminal/banner.ts +166 -166
- package/src/terminal/help.ts +306 -306
- package/src/terminal/index.ts +71 -71
- package/src/terminal/output.ts +295 -295
- package/src/terminal/palette.ts +30 -30
- package/src/terminal/progress.ts +327 -327
- package/src/terminal/stream-writer.ts +214 -214
- package/src/terminal/table.ts +354 -354
- package/src/terminal/theme.ts +142 -142
- package/src/util/bun.ts +6 -6
- package/src/util/fs.ts +23 -23
- package/src/util/handlers.ts +49 -5
- package/src/util/lockfile.ts +66 -0
- package/src/util/output.ts +22 -22
- package/src/util/port.ts +71 -71
- package/templates/default/AGENTS.md +96 -96
- package/templates/default/app/api/health/route.ts +13 -13
- package/templates/default/app/globals.css +49 -49
- package/templates/default/app/layout.tsx +27 -27
- package/templates/default/app/page.tsx +38 -38
- package/templates/default/src/client/shared/lib/utils.ts +16 -16
- package/templates/default/src/client/shared/ui/button.tsx +57 -57
- package/templates/default/src/client/shared/ui/card.tsx +1 -1
- package/templates/default/src/client/shared/ui/index.ts +21 -21
- package/templates/default/src/client/shared/ui/input.tsx +5 -1
- package/templates/default/tests/example.test.ts +58 -58
- package/templates/default/tests/helpers.ts +52 -52
- package/templates/default/tests/setup.ts +9 -9
- package/templates/default/tsconfig.json +23 -23
- package/templates/realtime-chat/AGENTS.md +96 -0
- package/templates/realtime-chat/app/api/chat/messages/route.ts +63 -0
- package/templates/realtime-chat/app/api/chat/stream/route.ts +48 -0
- package/templates/realtime-chat/app/api/health/route.ts +13 -0
- package/templates/realtime-chat/app/globals.css +49 -0
- package/templates/realtime-chat/app/layout.tsx +27 -0
- package/templates/realtime-chat/app/page.tsx +16 -0
- package/templates/realtime-chat/package.json +34 -0
- package/templates/realtime-chat/src/client/app/index.ts +1 -0
- package/templates/realtime-chat/src/client/entities/index.ts +1 -0
- package/templates/realtime-chat/src/client/features/chat/chat-api.ts +177 -0
- package/templates/realtime-chat/src/client/features/chat/realtime-chat-starter.client.tsx +89 -0
- package/templates/realtime-chat/src/client/features/chat/use-realtime-chat.ts +73 -0
- package/templates/realtime-chat/src/client/features/index.ts +1 -0
- package/templates/realtime-chat/src/client/pages/index.ts +1 -0
- package/templates/realtime-chat/src/client/shared/index.ts +1 -0
- package/templates/realtime-chat/src/client/shared/lib/utils.ts +16 -0
- package/templates/realtime-chat/src/client/shared/ui/button.tsx +57 -0
- package/templates/realtime-chat/src/client/shared/ui/card.tsx +78 -0
- package/templates/realtime-chat/src/client/shared/ui/index.ts +21 -0
- package/templates/realtime-chat/src/client/shared/ui/input.tsx +28 -0
- package/templates/realtime-chat/src/client/widgets/index.ts +1 -0
- package/templates/realtime-chat/src/server/api/index.ts +1 -0
- package/templates/realtime-chat/src/server/application/ai-adapter.ts +24 -0
- package/templates/realtime-chat/src/server/application/chat-store.ts +88 -0
- package/templates/realtime-chat/src/server/application/index.ts +1 -0
- package/templates/realtime-chat/src/server/core/index.ts +1 -0
- package/templates/realtime-chat/src/server/domain/index.ts +1 -0
- package/templates/realtime-chat/src/server/infra/index.ts +1 -0
- package/templates/realtime-chat/src/shared/contracts/chat.ts +29 -0
- package/templates/realtime-chat/src/shared/contracts/index.ts +1 -0
- package/templates/realtime-chat/src/shared/env/index.ts +1 -0
- package/templates/realtime-chat/src/shared/schema/index.ts +1 -0
- package/templates/realtime-chat/src/shared/types/index.ts +1 -0
- package/templates/realtime-chat/src/shared/utils/client/index.ts +1 -0
- package/templates/realtime-chat/src/shared/utils/server/index.ts +1 -0
- package/templates/realtime-chat/tests/chat-api.sse.test.ts +151 -0
- package/templates/realtime-chat/tests/chat-starter.test.ts +149 -0
- package/templates/realtime-chat/tests/chat-store.concurrency.test.ts +39 -0
- package/templates/realtime-chat/tests/example.test.ts +58 -0
- package/templates/realtime-chat/tests/helpers.ts +52 -0
- package/templates/realtime-chat/tests/setup.ts +9 -0
- package/templates/realtime-chat/tsconfig.json +23 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mandujs/cli",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.2",
|
|
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": "^0.13.
|
|
35
|
+
"@mandujs/core": "^0.13.1",
|
|
36
36
|
"cfonts": "^3.3.0"
|
|
37
37
|
},
|
|
38
38
|
"engines": {
|
package/src/commands/check.ts
CHANGED
|
@@ -15,6 +15,8 @@ import {
|
|
|
15
15
|
runGuardCheck,
|
|
16
16
|
buildGuardReport,
|
|
17
17
|
printReportSummary,
|
|
18
|
+
runAutoCorrect,
|
|
19
|
+
isAutoCorrectableViolation,
|
|
18
20
|
guardConfig,
|
|
19
21
|
formatConfigGuardResult,
|
|
20
22
|
calculateHealthScore,
|
|
@@ -25,6 +27,51 @@ import path from "path";
|
|
|
25
27
|
import { resolveFromCwd, isDirectory, pathExists } from "../util/fs";
|
|
26
28
|
import { resolveOutputFormat } from "../util/output";
|
|
27
29
|
|
|
30
|
+
interface LegacyCheckDeps {
|
|
31
|
+
runGuardCheck: typeof runGuardCheck;
|
|
32
|
+
runAutoCorrect: typeof runAutoCorrect;
|
|
33
|
+
isAutoCorrectableViolation: typeof isAutoCorrectableViolation;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function runLegacyGuardWithAutoHeal(
|
|
37
|
+
manifest: Parameters<typeof runGuardCheck>[0],
|
|
38
|
+
rootDir: string,
|
|
39
|
+
deps: LegacyCheckDeps = { runGuardCheck, runAutoCorrect, isAutoCorrectableViolation }
|
|
40
|
+
): Promise<{
|
|
41
|
+
passed: boolean;
|
|
42
|
+
violations: number;
|
|
43
|
+
autoHealed: boolean;
|
|
44
|
+
nextAction?: string;
|
|
45
|
+
checkResult: Awaited<ReturnType<typeof runGuardCheck>>;
|
|
46
|
+
}> {
|
|
47
|
+
let checkResult = await deps.runGuardCheck(manifest, rootDir);
|
|
48
|
+
let autoHealed = false;
|
|
49
|
+
|
|
50
|
+
if (!checkResult.passed) {
|
|
51
|
+
const hasAutoCorrectableViolation = checkResult.violations.some(
|
|
52
|
+
deps.isAutoCorrectableViolation
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
if (hasAutoCorrectableViolation) {
|
|
56
|
+
const autoCorrectResult = await deps.runAutoCorrect(
|
|
57
|
+
checkResult.violations,
|
|
58
|
+
manifest,
|
|
59
|
+
rootDir
|
|
60
|
+
);
|
|
61
|
+
autoHealed = autoCorrectResult.fixed;
|
|
62
|
+
checkResult = await deps.runGuardCheck(manifest, rootDir);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
passed: checkResult.passed,
|
|
68
|
+
violations: checkResult.violations.length,
|
|
69
|
+
autoHealed,
|
|
70
|
+
nextAction: checkResult.passed ? undefined : "mandu guard legacy",
|
|
71
|
+
checkResult,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
28
75
|
export async function check(): Promise<boolean> {
|
|
29
76
|
const rootDir = resolveFromCwd(".");
|
|
30
77
|
const config = await validateAndReport(rootDir);
|
|
@@ -175,7 +222,14 @@ export async function check(): Promise<boolean> {
|
|
|
175
222
|
}
|
|
176
223
|
|
|
177
224
|
// 3) Legacy Guard κ²μ¬ (spec νμΌμ΄ μμ λλ§)
|
|
178
|
-
let legacySummary: {
|
|
225
|
+
let legacySummary: {
|
|
226
|
+
enabled: boolean;
|
|
227
|
+
passed: boolean;
|
|
228
|
+
violations: number;
|
|
229
|
+
errors?: string[];
|
|
230
|
+
autoHealed?: boolean;
|
|
231
|
+
nextAction?: string;
|
|
232
|
+
} = {
|
|
179
233
|
enabled: false,
|
|
180
234
|
passed: true,
|
|
181
235
|
violations: 0,
|
|
@@ -195,17 +249,27 @@ export async function check(): Promise<boolean> {
|
|
|
195
249
|
manifestResult.errors?.forEach((e) => console.error(` - ${e}`));
|
|
196
250
|
}
|
|
197
251
|
} else {
|
|
198
|
-
const
|
|
199
|
-
legacySummary.passed =
|
|
200
|
-
legacySummary.violations =
|
|
201
|
-
|
|
252
|
+
const legacyResult = await runLegacyGuardWithAutoHeal(manifestResult.data, rootDir);
|
|
253
|
+
legacySummary.passed = legacyResult.passed;
|
|
254
|
+
legacySummary.violations = legacyResult.violations;
|
|
255
|
+
legacySummary.autoHealed = legacyResult.autoHealed;
|
|
256
|
+
legacySummary.nextAction = legacyResult.nextAction;
|
|
257
|
+
|
|
258
|
+
if (format === "console" && legacyResult.autoHealed) {
|
|
259
|
+
log("β
Legacy spec drift μλ 볡ꡬ μλ£");
|
|
260
|
+
}
|
|
261
|
+
if (format === "console" && legacyResult.nextAction) {
|
|
262
|
+
log("π‘ Legacy guard μλ°μ΄ λ¨μ μμ΅λλ€. `mandu guard legacy`λ‘ μμΈ μ κ²/볡ꡬλ₯Ό μ§ννμΈμ.");
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (strictWarnings && legacyResult.violations > 0) {
|
|
202
266
|
success = false;
|
|
203
267
|
} else {
|
|
204
|
-
success = success &&
|
|
268
|
+
success = success && legacyResult.passed;
|
|
205
269
|
}
|
|
206
270
|
|
|
207
271
|
if (format === "console") {
|
|
208
|
-
const legacyReport = buildGuardReport(checkResult);
|
|
272
|
+
const legacyReport = buildGuardReport(legacyResult.checkResult);
|
|
209
273
|
if (quiet) {
|
|
210
274
|
print(`π Legacy Guard: ${legacySummary.violations}κ° μλ°`);
|
|
211
275
|
} else {
|
package/src/commands/contract.ts
CHANGED
|
@@ -3,63 +3,63 @@
|
|
|
3
3
|
* Contract μμ± λ° κ²μ¦ λͺ
λ Ήμ΄
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
runContractGuardCheck,
|
|
8
|
-
generateContractTemplate,
|
|
9
|
-
buildContractRegistry,
|
|
10
|
-
writeContractRegistry,
|
|
11
|
-
readContractRegistry,
|
|
12
|
-
diffContractRegistry,
|
|
13
|
-
validateAndReport,
|
|
14
|
-
} from "@mandujs/core";
|
|
15
|
-
import path from "path";
|
|
16
|
-
import fs from "fs/promises";
|
|
17
|
-
import { resolveManifest } from "../util/manifest";
|
|
6
|
+
import {
|
|
7
|
+
runContractGuardCheck,
|
|
8
|
+
generateContractTemplate,
|
|
9
|
+
buildContractRegistry,
|
|
10
|
+
writeContractRegistry,
|
|
11
|
+
readContractRegistry,
|
|
12
|
+
diffContractRegistry,
|
|
13
|
+
validateAndReport,
|
|
14
|
+
} from "@mandujs/core";
|
|
15
|
+
import path from "path";
|
|
16
|
+
import fs from "fs/promises";
|
|
17
|
+
import { resolveManifest } from "../util/manifest";
|
|
18
18
|
|
|
19
19
|
interface ContractCreateOptions {
|
|
20
20
|
routeId: string;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
interface ContractValidateOptions {
|
|
24
|
-
verbose?: boolean;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
interface ContractBuildOptions {
|
|
28
|
-
output?: string;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
interface ContractDiffOptions {
|
|
32
|
-
from?: string;
|
|
33
|
-
to?: string;
|
|
34
|
-
output?: string;
|
|
35
|
-
json?: boolean;
|
|
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
|
-
}
|
|
23
|
+
interface ContractValidateOptions {
|
|
24
|
+
verbose?: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface ContractBuildOptions {
|
|
28
|
+
output?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface ContractDiffOptions {
|
|
32
|
+
from?: string;
|
|
33
|
+
to?: string;
|
|
34
|
+
output?: string;
|
|
35
|
+
json?: boolean;
|
|
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
|
+
}
|
|
46
46
|
|
|
47
47
|
/**
|
|
48
48
|
* Create a new contract file for a route
|
|
49
49
|
*/
|
|
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
|
-
}
|
|
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
|
+
}
|
|
63
63
|
|
|
64
64
|
// Find the route
|
|
65
65
|
const route = manifest.routes.find((r) => r.id === options.routeId);
|
|
@@ -113,22 +113,22 @@ export async function contractCreate(options: ContractCreateOptions): Promise<bo
|
|
|
113
113
|
/**
|
|
114
114
|
* Validate all contracts against their slot implementations
|
|
115
115
|
*/
|
|
116
|
-
export async function contractValidate(options: ContractValidateOptions = {}): Promise<boolean> {
|
|
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);
|
|
116
|
+
export async function contractValidate(options: ContractValidateOptions = {}): Promise<boolean> {
|
|
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);
|
|
132
132
|
|
|
133
133
|
if (violations.length === 0) {
|
|
134
134
|
console.log(`β
All contracts are valid!\n`);
|
|
@@ -175,112 +175,112 @@ export async function contractValidate(options: ContractValidateOptions = {}): P
|
|
|
175
175
|
console.log(`π‘ Use --verbose for fix suggestions\n`);
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
-
return false;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
/**
|
|
182
|
-
* Build contract registry (.mandu/contracts.json)
|
|
183
|
-
*/
|
|
184
|
-
export async function contractBuild(options: ContractBuildOptions = {}): Promise<boolean> {
|
|
185
|
-
const rootDir = process.cwd();
|
|
186
|
-
const outputPath = options.output || path.join(rootDir, ".mandu", "contracts.json");
|
|
187
|
-
|
|
188
|
-
console.log(`\nπ¦ Building contract registry...\n`);
|
|
189
|
-
|
|
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);
|
|
195
|
-
return false;
|
|
196
|
-
}
|
|
197
|
-
const { registry, warnings } = await buildContractRegistry(manifest, rootDir);
|
|
198
|
-
|
|
199
|
-
if (warnings.length > 0) {
|
|
200
|
-
console.log(`β οΈ ${warnings.length} warning(s):`);
|
|
201
|
-
for (const warning of warnings) {
|
|
202
|
-
console.log(` - ${warning}`);
|
|
203
|
-
}
|
|
204
|
-
console.log();
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
await fs.mkdir(path.dirname(outputPath), { recursive: true });
|
|
208
|
-
await writeContractRegistry(outputPath, registry);
|
|
209
|
-
|
|
210
|
-
console.log(`β
Registry generated: ${path.relative(rootDir, outputPath)}`);
|
|
211
|
-
console.log(`π Contracts: ${registry.contracts.length}`);
|
|
212
|
-
|
|
213
|
-
return true;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
/**
|
|
217
|
-
* Diff current contracts against a registry
|
|
218
|
-
*/
|
|
219
|
-
export async function contractDiff(options: ContractDiffOptions = {}): Promise<boolean> {
|
|
220
|
-
const rootDir = process.cwd();
|
|
221
|
-
const fromPath = options.from || path.join(rootDir, ".mandu", "contracts.json");
|
|
222
|
-
|
|
223
|
-
console.log(`\nπ Diffing contracts...\n`);
|
|
224
|
-
|
|
225
|
-
const fromRegistry = await readContractRegistry(fromPath);
|
|
226
|
-
if (!fromRegistry) {
|
|
227
|
-
console.error(`β Registry not found: ${path.relative(rootDir, fromPath)}`);
|
|
228
|
-
console.log(`π‘ Run \`mandu contract build\` first.`);
|
|
229
|
-
return false;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
let toRegistry = options.to ? await readContractRegistry(options.to) : null;
|
|
233
|
-
|
|
234
|
-
if (!toRegistry) {
|
|
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);
|
|
240
|
-
return false;
|
|
241
|
-
}
|
|
242
|
-
const { registry } = await buildContractRegistry(manifest, rootDir);
|
|
243
|
-
toRegistry = registry;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
const diff = diffContractRegistry(fromRegistry, toRegistry);
|
|
247
|
-
|
|
248
|
-
if (options.output) {
|
|
249
|
-
await fs.mkdir(path.dirname(options.output), { recursive: true });
|
|
250
|
-
await Bun.write(options.output, JSON.stringify(diff, null, 2));
|
|
251
|
-
console.log(`β
Diff saved: ${path.relative(rootDir, options.output)}`);
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
if (options.json) {
|
|
255
|
-
console.log(JSON.stringify(diff, null, 2));
|
|
256
|
-
return diff.summary.major === 0;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
console.log(`π Summary: major ${diff.summary.major}, minor ${diff.summary.minor}, patch ${diff.summary.patch}`);
|
|
260
|
-
|
|
261
|
-
if (diff.added.length > 0) {
|
|
262
|
-
console.log(`\nπ’ Added (${diff.added.length})`);
|
|
263
|
-
for (const entry of diff.added) {
|
|
264
|
-
console.log(` - ${entry.id} (${entry.routeId})`);
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
if (diff.removed.length > 0) {
|
|
269
|
-
console.log(`\nπ΄ Removed (${diff.removed.length})`);
|
|
270
|
-
for (const entry of diff.removed) {
|
|
271
|
-
console.log(` - ${entry.id} (${entry.routeId})`);
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
if (diff.changed.length > 0) {
|
|
276
|
-
console.log(`\nπ‘ Changed (${diff.changed.length})`);
|
|
277
|
-
for (const change of diff.changed) {
|
|
278
|
-
console.log(` - ${change.id} (${change.routeId}) [${change.severity}]`);
|
|
279
|
-
for (const detail of change.changes) {
|
|
280
|
-
console.log(` β’ ${detail}`);
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
return diff.summary.major === 0;
|
|
286
|
-
}
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Build contract registry (.mandu/contracts.json)
|
|
183
|
+
*/
|
|
184
|
+
export async function contractBuild(options: ContractBuildOptions = {}): Promise<boolean> {
|
|
185
|
+
const rootDir = process.cwd();
|
|
186
|
+
const outputPath = options.output || path.join(rootDir, ".mandu", "contracts.json");
|
|
187
|
+
|
|
188
|
+
console.log(`\nπ¦ Building contract registry...\n`);
|
|
189
|
+
|
|
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);
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
const { registry, warnings } = await buildContractRegistry(manifest, rootDir);
|
|
198
|
+
|
|
199
|
+
if (warnings.length > 0) {
|
|
200
|
+
console.log(`β οΈ ${warnings.length} warning(s):`);
|
|
201
|
+
for (const warning of warnings) {
|
|
202
|
+
console.log(` - ${warning}`);
|
|
203
|
+
}
|
|
204
|
+
console.log();
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
await fs.mkdir(path.dirname(outputPath), { recursive: true });
|
|
208
|
+
await writeContractRegistry(outputPath, registry);
|
|
209
|
+
|
|
210
|
+
console.log(`β
Registry generated: ${path.relative(rootDir, outputPath)}`);
|
|
211
|
+
console.log(`π Contracts: ${registry.contracts.length}`);
|
|
212
|
+
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Diff current contracts against a registry
|
|
218
|
+
*/
|
|
219
|
+
export async function contractDiff(options: ContractDiffOptions = {}): Promise<boolean> {
|
|
220
|
+
const rootDir = process.cwd();
|
|
221
|
+
const fromPath = options.from || path.join(rootDir, ".mandu", "contracts.json");
|
|
222
|
+
|
|
223
|
+
console.log(`\nπ Diffing contracts...\n`);
|
|
224
|
+
|
|
225
|
+
const fromRegistry = await readContractRegistry(fromPath);
|
|
226
|
+
if (!fromRegistry) {
|
|
227
|
+
console.error(`β Registry not found: ${path.relative(rootDir, fromPath)}`);
|
|
228
|
+
console.log(`π‘ Run \`mandu contract build\` first.`);
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
let toRegistry = options.to ? await readContractRegistry(options.to) : null;
|
|
233
|
+
|
|
234
|
+
if (!toRegistry) {
|
|
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);
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
const { registry } = await buildContractRegistry(manifest, rootDir);
|
|
243
|
+
toRegistry = registry;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const diff = diffContractRegistry(fromRegistry, toRegistry);
|
|
247
|
+
|
|
248
|
+
if (options.output) {
|
|
249
|
+
await fs.mkdir(path.dirname(options.output), { recursive: true });
|
|
250
|
+
await Bun.write(options.output, JSON.stringify(diff, null, 2));
|
|
251
|
+
console.log(`β
Diff saved: ${path.relative(rootDir, options.output)}`);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (options.json) {
|
|
255
|
+
console.log(JSON.stringify(diff, null, 2));
|
|
256
|
+
return diff.summary.major === 0;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
console.log(`π Summary: major ${diff.summary.major}, minor ${diff.summary.minor}, patch ${diff.summary.patch}`);
|
|
260
|
+
|
|
261
|
+
if (diff.added.length > 0) {
|
|
262
|
+
console.log(`\nπ’ Added (${diff.added.length})`);
|
|
263
|
+
for (const entry of diff.added) {
|
|
264
|
+
console.log(` - ${entry.id} (${entry.routeId})`);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (diff.removed.length > 0) {
|
|
269
|
+
console.log(`\nπ΄ Removed (${diff.removed.length})`);
|
|
270
|
+
for (const entry of diff.removed) {
|
|
271
|
+
console.log(` - ${entry.id} (${entry.routeId})`);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (diff.changed.length > 0) {
|
|
276
|
+
console.log(`\nπ‘ Changed (${diff.changed.length})`);
|
|
277
|
+
for (const change of diff.changed) {
|
|
278
|
+
console.log(` - ${change.id} (${change.routeId}) [${change.severity}]`);
|
|
279
|
+
for (const detail of change.changes) {
|
|
280
|
+
console.log(` β’ ${detail}`);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return diff.summary.major === 0;
|
|
286
|
+
}
|
package/src/commands/dev.ts
CHANGED
|
@@ -15,12 +15,6 @@ import {
|
|
|
15
15
|
validateAndReport,
|
|
16
16
|
isTailwindProject,
|
|
17
17
|
startCSSWatch,
|
|
18
|
-
readLockfile,
|
|
19
|
-
readMcpConfig,
|
|
20
|
-
validateWithPolicy,
|
|
21
|
-
detectMode,
|
|
22
|
-
formatPolicyAction,
|
|
23
|
-
formatValidationResult,
|
|
24
18
|
type RoutesManifest,
|
|
25
19
|
type GuardConfig,
|
|
26
20
|
type Violation,
|
|
@@ -32,6 +26,11 @@ import { CLI_ERROR_CODES, printCLIError } from "../errors";
|
|
|
32
26
|
import { importFresh } from "../util/bun";
|
|
33
27
|
import { resolveManifest } from "../util/manifest";
|
|
34
28
|
import { resolveAvailablePort } from "../util/port";
|
|
29
|
+
import {
|
|
30
|
+
validateRuntimeLockfile,
|
|
31
|
+
handleBlockedLockfile,
|
|
32
|
+
printRuntimeLockfileStatus,
|
|
33
|
+
} from "../util/lockfile";
|
|
35
34
|
import { registerManifestHandlers } from "../util/handlers";
|
|
36
35
|
import path from "path";
|
|
37
36
|
|
|
@@ -49,35 +48,8 @@ export async function dev(options: DevOptions = {}): Promise<void> {
|
|
|
49
48
|
}
|
|
50
49
|
|
|
51
50
|
// Lockfile κ²μ¦ (μ€μ 무결μ±)
|
|
52
|
-
const lockfile = await
|
|
53
|
-
|
|
54
|
-
try {
|
|
55
|
-
mcpConfig = await readMcpConfig(rootDir);
|
|
56
|
-
} catch (error) {
|
|
57
|
-
console.warn(
|
|
58
|
-
`β οΈ MCP μ€μ λ‘λ μ€ν¨: ${error instanceof Error ? error.message : String(error)}`
|
|
59
|
-
);
|
|
60
|
-
}
|
|
61
|
-
const { result: lockResult, action, bypassed } = validateWithPolicy(
|
|
62
|
-
config,
|
|
63
|
-
lockfile,
|
|
64
|
-
detectMode(),
|
|
65
|
-
mcpConfig
|
|
66
|
-
);
|
|
67
|
-
|
|
68
|
-
if (action === "block") {
|
|
69
|
-
console.error("π μλ² μμ μ°¨λ¨: Lockfile λΆμΌμΉ");
|
|
70
|
-
console.error(" μ€μ μ΄ λ³κ²½λμμ΅λλ€. μλν λ³κ²½μ΄λΌλ©΄:");
|
|
71
|
-
console.error(" $ mandu lock");
|
|
72
|
-
console.error("");
|
|
73
|
-
console.error(" λ³κ²½ μ¬ν νμΈ:");
|
|
74
|
-
console.error(" $ mandu lock --diff");
|
|
75
|
-
if (lockResult) {
|
|
76
|
-
console.error("");
|
|
77
|
-
console.error(formatValidationResult(lockResult));
|
|
78
|
-
}
|
|
79
|
-
process.exit(1);
|
|
80
|
-
}
|
|
51
|
+
const { lockfile, lockResult, action, bypassed } = await validateRuntimeLockfile(config, rootDir);
|
|
52
|
+
handleBlockedLockfile(action, lockResult);
|
|
81
53
|
|
|
82
54
|
const serverConfig = config.server ?? {};
|
|
83
55
|
const devConfig = config.dev ?? {};
|
|
@@ -87,13 +59,7 @@ export async function dev(options: DevOptions = {}): Promise<void> {
|
|
|
87
59
|
console.log(`π₯ Mandu Dev Server`);
|
|
88
60
|
|
|
89
61
|
// Lockfile μν μΆλ ₯
|
|
90
|
-
|
|
91
|
-
console.log(`β οΈ ${formatPolicyAction(action, bypassed)}`);
|
|
92
|
-
} else if (lockfile && lockResult?.valid) {
|
|
93
|
-
console.log(`π μ€μ λ¬΄κ²°μ± νμΈλ¨ (${lockResult.currentHash?.slice(0, 8)})`);
|
|
94
|
-
} else if (!lockfile) {
|
|
95
|
-
console.log(`π‘ Lockfile μμ - 'mandu lock'μΌλ‘ μμ± κΆμ₯`);
|
|
96
|
-
}
|
|
62
|
+
printRuntimeLockfileStatus(action, bypassed, lockfile, lockResult);
|
|
97
63
|
|
|
98
64
|
// .env νμΌ λ‘λ
|
|
99
65
|
const envResult = await loadEnv({
|
|
@@ -328,6 +294,7 @@ export async function dev(options: DevOptions = {}): Promise<void> {
|
|
|
328
294
|
bundleManifest: devBundler?.initialBuild.manifest,
|
|
329
295
|
cors: serverConfig.cors,
|
|
330
296
|
streaming: serverConfig.streaming,
|
|
297
|
+
rateLimit: serverConfig.rateLimit,
|
|
331
298
|
// Tailwind κ°μ§ μμλ§ CSS λ§ν¬ μ£Όμ
|
|
332
299
|
cssPath: hasTailwind ? cssWatcher?.serverPath : false,
|
|
333
300
|
});
|