@mandujs/mcp 0.3.3
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 +41 -0
- package/src/index.ts +9 -0
- package/src/resources/handlers.ts +176 -0
- package/src/resources/index.ts +1 -0
- package/src/server.ts +232 -0
- package/src/tools/generate.ts +127 -0
- package/src/tools/guard.ts +211 -0
- package/src/tools/history.ts +138 -0
- package/src/tools/index.ts +6 -0
- package/src/tools/slot.ts +166 -0
- package/src/tools/spec.ts +322 -0
- package/src/tools/transaction.ts +154 -0
- package/src/utils/project.ts +71 -0
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mandujs/mcp",
|
|
3
|
+
"version": "0.3.3",
|
|
4
|
+
"description": "Mandu MCP Server - Agent-native interface for Mandu framework operations",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./src/index.ts",
|
|
7
|
+
"bin": {
|
|
8
|
+
"mandu-mcp": "./src/index.ts"
|
|
9
|
+
},
|
|
10
|
+
"exports": {
|
|
11
|
+
".": "./src/index.ts"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"src/**/*"
|
|
15
|
+
],
|
|
16
|
+
"keywords": [
|
|
17
|
+
"mandu",
|
|
18
|
+
"mcp",
|
|
19
|
+
"model-context-protocol",
|
|
20
|
+
"ai",
|
|
21
|
+
"agent",
|
|
22
|
+
"code-generation"
|
|
23
|
+
],
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "https://github.com/konamgil/mandu.git",
|
|
27
|
+
"directory": "packages/mcp"
|
|
28
|
+
},
|
|
29
|
+
"author": "konamgil",
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"publishConfig": {
|
|
32
|
+
"access": "public"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@mandujs/core": "^0.3.3",
|
|
36
|
+
"@modelcontextprotocol/sdk": "^1.25.3"
|
|
37
|
+
},
|
|
38
|
+
"engines": {
|
|
39
|
+
"bun": ">=1.0.0"
|
|
40
|
+
}
|
|
41
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import type { Resource } from "@modelcontextprotocol/sdk/types.js";
|
|
2
|
+
import {
|
|
3
|
+
loadManifest,
|
|
4
|
+
getTransactionStatus,
|
|
5
|
+
type GeneratedMap,
|
|
6
|
+
type SpecLock,
|
|
7
|
+
} from "@mandujs/core";
|
|
8
|
+
import { getProjectPaths, readJsonFile } from "../utils/project.js";
|
|
9
|
+
import path from "path";
|
|
10
|
+
|
|
11
|
+
export const resourceDefinitions: Resource[] = [
|
|
12
|
+
{
|
|
13
|
+
uri: "mandu://spec/manifest",
|
|
14
|
+
name: "Routes Manifest",
|
|
15
|
+
description: "Current routes.manifest.json content",
|
|
16
|
+
mimeType: "application/json",
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
uri: "mandu://spec/lock",
|
|
20
|
+
name: "Spec Lock",
|
|
21
|
+
description: "Current spec.lock.json with hash verification",
|
|
22
|
+
mimeType: "application/json",
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
uri: "mandu://generated/map",
|
|
26
|
+
name: "Generated Map",
|
|
27
|
+
description: "Map of generated files to their source routes",
|
|
28
|
+
mimeType: "application/json",
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
uri: "mandu://transaction/active",
|
|
32
|
+
name: "Active Transaction",
|
|
33
|
+
description: "Current active transaction information",
|
|
34
|
+
mimeType: "application/json",
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
uri: "mandu://slots/{routeId}",
|
|
38
|
+
name: "Route Slot",
|
|
39
|
+
description: "Slot file content for a specific route",
|
|
40
|
+
mimeType: "text/typescript",
|
|
41
|
+
},
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
type ResourceHandler = (params: Record<string, string>) => Promise<unknown>;
|
|
45
|
+
|
|
46
|
+
export function resourceHandlers(
|
|
47
|
+
projectRoot: string
|
|
48
|
+
): Record<string, ResourceHandler> {
|
|
49
|
+
const paths = getProjectPaths(projectRoot);
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
"mandu://spec/manifest": async () => {
|
|
53
|
+
const result = await loadManifest(paths.manifestPath);
|
|
54
|
+
if (!result.success || !result.data) {
|
|
55
|
+
return {
|
|
56
|
+
error: "Failed to load manifest",
|
|
57
|
+
details: result.errors,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
version: result.data.version,
|
|
63
|
+
routeCount: result.data.routes.length,
|
|
64
|
+
routes: result.data.routes,
|
|
65
|
+
};
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
"mandu://spec/lock": async () => {
|
|
69
|
+
const lock = await readJsonFile<SpecLock>(paths.lockPath);
|
|
70
|
+
if (!lock) {
|
|
71
|
+
return {
|
|
72
|
+
exists: false,
|
|
73
|
+
message: "spec.lock.json not found",
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
exists: true,
|
|
79
|
+
routesHash: lock.routesHash,
|
|
80
|
+
updatedAt: lock.updatedAt,
|
|
81
|
+
};
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
"mandu://generated/map": async () => {
|
|
85
|
+
const generatedMap = await readJsonFile<GeneratedMap>(paths.generatedMapPath);
|
|
86
|
+
if (!generatedMap) {
|
|
87
|
+
return {
|
|
88
|
+
exists: false,
|
|
89
|
+
message: "generated.map.json not found. Run mandu generate first.",
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
exists: true,
|
|
95
|
+
version: generatedMap.version,
|
|
96
|
+
generatedAt: generatedMap.generatedAt,
|
|
97
|
+
specSource: generatedMap.specSource,
|
|
98
|
+
fileCount: Object.keys(generatedMap.files).length,
|
|
99
|
+
files: generatedMap.files,
|
|
100
|
+
frameworkPaths: generatedMap.frameworkPaths,
|
|
101
|
+
};
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
"mandu://transaction/active": async () => {
|
|
105
|
+
const { state, change } = await getTransactionStatus(projectRoot);
|
|
106
|
+
|
|
107
|
+
if (!state.active) {
|
|
108
|
+
return {
|
|
109
|
+
hasActive: false,
|
|
110
|
+
message: "No active transaction",
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
hasActive: true,
|
|
116
|
+
changeId: state.changeId,
|
|
117
|
+
snapshotId: state.snapshotId,
|
|
118
|
+
change: change
|
|
119
|
+
? {
|
|
120
|
+
id: change.id,
|
|
121
|
+
message: change.message,
|
|
122
|
+
status: change.status,
|
|
123
|
+
createdAt: change.createdAt,
|
|
124
|
+
autoGenerated: change.autoGenerated,
|
|
125
|
+
}
|
|
126
|
+
: null,
|
|
127
|
+
};
|
|
128
|
+
},
|
|
129
|
+
|
|
130
|
+
"mandu://slots/{routeId}": async (params: Record<string, string>) => {
|
|
131
|
+
const { routeId } = params;
|
|
132
|
+
|
|
133
|
+
if (!routeId) {
|
|
134
|
+
return { error: "routeId parameter is required" };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Load manifest to find the route
|
|
138
|
+
const result = await loadManifest(paths.manifestPath);
|
|
139
|
+
if (!result.success || !result.data) {
|
|
140
|
+
return { error: result.errors };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const route = result.data.routes.find((r) => r.id === routeId);
|
|
144
|
+
if (!route) {
|
|
145
|
+
return { error: `Route not found: ${routeId}` };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (!route.slotModule) {
|
|
149
|
+
return {
|
|
150
|
+
error: `Route '${routeId}' does not have a slotModule`,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const slotPath = path.join(projectRoot, route.slotModule);
|
|
155
|
+
const file = Bun.file(slotPath);
|
|
156
|
+
|
|
157
|
+
if (!(await file.exists())) {
|
|
158
|
+
return {
|
|
159
|
+
exists: false,
|
|
160
|
+
slotModule: route.slotModule,
|
|
161
|
+
message: "Slot file does not exist",
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const content = await file.text();
|
|
166
|
+
return {
|
|
167
|
+
exists: true,
|
|
168
|
+
slotModule: route.slotModule,
|
|
169
|
+
routeId,
|
|
170
|
+
kind: route.kind,
|
|
171
|
+
pattern: route.pattern,
|
|
172
|
+
content,
|
|
173
|
+
};
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { resourceHandlers, resourceDefinitions } from "./handlers.js";
|
package/src/server.ts
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
2
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
import {
|
|
4
|
+
CallToolRequestSchema,
|
|
5
|
+
ListToolsRequestSchema,
|
|
6
|
+
ListResourcesRequestSchema,
|
|
7
|
+
ReadResourceRequestSchema,
|
|
8
|
+
type Tool,
|
|
9
|
+
type Resource,
|
|
10
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
11
|
+
|
|
12
|
+
import { specTools, specToolDefinitions } from "./tools/spec.js";
|
|
13
|
+
import { generateTools, generateToolDefinitions } from "./tools/generate.js";
|
|
14
|
+
import { transactionTools, transactionToolDefinitions } from "./tools/transaction.js";
|
|
15
|
+
import { historyTools, historyToolDefinitions } from "./tools/history.js";
|
|
16
|
+
import { guardTools, guardToolDefinitions } from "./tools/guard.js";
|
|
17
|
+
import { slotTools, slotToolDefinitions } from "./tools/slot.js";
|
|
18
|
+
import { resourceHandlers, resourceDefinitions } from "./resources/handlers.js";
|
|
19
|
+
import { findProjectRoot } from "./utils/project.js";
|
|
20
|
+
|
|
21
|
+
export class ManduMcpServer {
|
|
22
|
+
private server: Server;
|
|
23
|
+
private projectRoot: string;
|
|
24
|
+
|
|
25
|
+
constructor(projectRoot: string) {
|
|
26
|
+
this.projectRoot = projectRoot;
|
|
27
|
+
this.server = new Server(
|
|
28
|
+
{
|
|
29
|
+
name: "mandu-mcp",
|
|
30
|
+
version: "0.1.0",
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
capabilities: {
|
|
34
|
+
tools: {},
|
|
35
|
+
resources: {},
|
|
36
|
+
},
|
|
37
|
+
}
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
this.registerToolHandlers();
|
|
41
|
+
this.registerResourceHandlers();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
private getAllToolDefinitions(): Tool[] {
|
|
45
|
+
return [
|
|
46
|
+
...specToolDefinitions,
|
|
47
|
+
...generateToolDefinitions,
|
|
48
|
+
...transactionToolDefinitions,
|
|
49
|
+
...historyToolDefinitions,
|
|
50
|
+
...guardToolDefinitions,
|
|
51
|
+
...slotToolDefinitions,
|
|
52
|
+
];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
private getAllToolHandlers(): Record<string, (args: Record<string, unknown>) => Promise<unknown>> {
|
|
56
|
+
return {
|
|
57
|
+
...specTools(this.projectRoot),
|
|
58
|
+
...generateTools(this.projectRoot),
|
|
59
|
+
...transactionTools(this.projectRoot),
|
|
60
|
+
...historyTools(this.projectRoot),
|
|
61
|
+
...guardTools(this.projectRoot),
|
|
62
|
+
...slotTools(this.projectRoot),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
private registerToolHandlers(): void {
|
|
67
|
+
const toolHandlers = this.getAllToolHandlers();
|
|
68
|
+
|
|
69
|
+
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
70
|
+
return {
|
|
71
|
+
tools: this.getAllToolDefinitions(),
|
|
72
|
+
};
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
76
|
+
const { name, arguments: args } = request.params;
|
|
77
|
+
|
|
78
|
+
const handler = toolHandlers[name];
|
|
79
|
+
if (!handler) {
|
|
80
|
+
return {
|
|
81
|
+
content: [
|
|
82
|
+
{
|
|
83
|
+
type: "text",
|
|
84
|
+
text: JSON.stringify({ error: `Unknown tool: ${name}` }),
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
isError: true,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
const result = await handler(args || {});
|
|
93
|
+
return {
|
|
94
|
+
content: [
|
|
95
|
+
{
|
|
96
|
+
type: "text",
|
|
97
|
+
text: JSON.stringify(result, null, 2),
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
};
|
|
101
|
+
} catch (error) {
|
|
102
|
+
return {
|
|
103
|
+
content: [
|
|
104
|
+
{
|
|
105
|
+
type: "text",
|
|
106
|
+
text: JSON.stringify({
|
|
107
|
+
error: error instanceof Error ? error.message : String(error),
|
|
108
|
+
}),
|
|
109
|
+
},
|
|
110
|
+
],
|
|
111
|
+
isError: true,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
private registerResourceHandlers(): void {
|
|
118
|
+
const handlers = resourceHandlers(this.projectRoot);
|
|
119
|
+
|
|
120
|
+
this.server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
121
|
+
return {
|
|
122
|
+
resources: resourceDefinitions,
|
|
123
|
+
};
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
127
|
+
const { uri } = request.params;
|
|
128
|
+
|
|
129
|
+
const handler = handlers[uri];
|
|
130
|
+
if (!handler) {
|
|
131
|
+
// Try pattern matching for dynamic resources
|
|
132
|
+
for (const [pattern, h] of Object.entries(handlers)) {
|
|
133
|
+
if (pattern.includes("{") && matchResourcePattern(pattern, uri)) {
|
|
134
|
+
const params = extractResourceParams(pattern, uri);
|
|
135
|
+
const result = await h(params);
|
|
136
|
+
return {
|
|
137
|
+
contents: [
|
|
138
|
+
{
|
|
139
|
+
uri,
|
|
140
|
+
mimeType: "application/json",
|
|
141
|
+
text: JSON.stringify(result, null, 2),
|
|
142
|
+
},
|
|
143
|
+
],
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
contents: [
|
|
150
|
+
{
|
|
151
|
+
uri,
|
|
152
|
+
mimeType: "application/json",
|
|
153
|
+
text: JSON.stringify({ error: `Unknown resource: ${uri}` }),
|
|
154
|
+
},
|
|
155
|
+
],
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
try {
|
|
160
|
+
const result = await handler({});
|
|
161
|
+
return {
|
|
162
|
+
contents: [
|
|
163
|
+
{
|
|
164
|
+
uri,
|
|
165
|
+
mimeType: "application/json",
|
|
166
|
+
text: JSON.stringify(result, null, 2),
|
|
167
|
+
},
|
|
168
|
+
],
|
|
169
|
+
};
|
|
170
|
+
} catch (error) {
|
|
171
|
+
return {
|
|
172
|
+
contents: [
|
|
173
|
+
{
|
|
174
|
+
uri,
|
|
175
|
+
mimeType: "application/json",
|
|
176
|
+
text: JSON.stringify({
|
|
177
|
+
error: error instanceof Error ? error.message : String(error),
|
|
178
|
+
}),
|
|
179
|
+
},
|
|
180
|
+
],
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async run(): Promise<void> {
|
|
187
|
+
const transport = new StdioServerTransport();
|
|
188
|
+
await this.server.connect(transport);
|
|
189
|
+
console.error(`Mandu MCP Server running for project: ${this.projectRoot}`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Match a resource pattern like "mandu://slots/{routeId}" against a URI
|
|
195
|
+
*/
|
|
196
|
+
function matchResourcePattern(pattern: string, uri: string): boolean {
|
|
197
|
+
const regexPattern = pattern.replace(/\{[^}]+\}/g, "([^/]+)");
|
|
198
|
+
const regex = new RegExp(`^${regexPattern}$`);
|
|
199
|
+
return regex.test(uri);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Extract parameters from a URI based on a pattern
|
|
204
|
+
*/
|
|
205
|
+
function extractResourceParams(pattern: string, uri: string): Record<string, string> {
|
|
206
|
+
const paramNames: string[] = [];
|
|
207
|
+
const regexPattern = pattern.replace(/\{([^}]+)\}/g, (_, name) => {
|
|
208
|
+
paramNames.push(name);
|
|
209
|
+
return "([^/]+)";
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
const regex = new RegExp(`^${regexPattern}$`);
|
|
213
|
+
const match = uri.match(regex);
|
|
214
|
+
|
|
215
|
+
if (!match) return {};
|
|
216
|
+
|
|
217
|
+
const params: Record<string, string> = {};
|
|
218
|
+
paramNames.forEach((name, index) => {
|
|
219
|
+
params[name] = match[index + 1];
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
return params;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Create and start the MCP server
|
|
227
|
+
*/
|
|
228
|
+
export async function startServer(projectRoot?: string): Promise<void> {
|
|
229
|
+
const root = projectRoot || (await findProjectRoot()) || process.cwd();
|
|
230
|
+
const server = new ManduMcpServer(root);
|
|
231
|
+
await server.run();
|
|
232
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import type { Tool } from "@modelcontextprotocol/sdk/types.js";
|
|
2
|
+
import { loadManifest, generateRoutes, type GeneratedMap } from "@mandujs/core";
|
|
3
|
+
import { getProjectPaths, readJsonFile } from "../utils/project.js";
|
|
4
|
+
|
|
5
|
+
export const generateToolDefinitions: Tool[] = [
|
|
6
|
+
{
|
|
7
|
+
name: "mandu_generate",
|
|
8
|
+
description:
|
|
9
|
+
"Generate route handlers and components from the spec manifest. Creates server handlers, page components, and slot files.",
|
|
10
|
+
inputSchema: {
|
|
11
|
+
type: "object",
|
|
12
|
+
properties: {
|
|
13
|
+
dryRun: {
|
|
14
|
+
type: "boolean",
|
|
15
|
+
description: "If true, show what would be generated without writing files",
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
required: [],
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
name: "mandu_generate_status",
|
|
23
|
+
description: "Get the current generation status and generated file map",
|
|
24
|
+
inputSchema: {
|
|
25
|
+
type: "object",
|
|
26
|
+
properties: {},
|
|
27
|
+
required: [],
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
export function generateTools(projectRoot: string) {
|
|
33
|
+
const paths = getProjectPaths(projectRoot);
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
mandu_generate: async (args: Record<string, unknown>) => {
|
|
37
|
+
const { dryRun } = args as { dryRun?: boolean };
|
|
38
|
+
|
|
39
|
+
// Load manifest
|
|
40
|
+
const manifestResult = await loadManifest(paths.manifestPath);
|
|
41
|
+
if (!manifestResult.success || !manifestResult.data) {
|
|
42
|
+
return { error: manifestResult.errors };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (dryRun) {
|
|
46
|
+
// Dry run - just show what would be generated
|
|
47
|
+
const routes = manifestResult.data.routes;
|
|
48
|
+
const wouldCreate: string[] = [];
|
|
49
|
+
const wouldSkip: string[] = [];
|
|
50
|
+
|
|
51
|
+
for (const route of routes) {
|
|
52
|
+
// Server handler
|
|
53
|
+
wouldCreate.push(`apps/server/generated/routes/${route.id}.route.ts`);
|
|
54
|
+
|
|
55
|
+
// Page component (for page kind)
|
|
56
|
+
if (route.kind === "page") {
|
|
57
|
+
wouldCreate.push(`apps/web/generated/routes/${route.id}.route.tsx`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Slot file (only if not exists)
|
|
61
|
+
if (route.slotModule) {
|
|
62
|
+
const slotFile = Bun.file(`${projectRoot}/${route.slotModule}`);
|
|
63
|
+
if (await slotFile.exists()) {
|
|
64
|
+
wouldSkip.push(route.slotModule);
|
|
65
|
+
} else {
|
|
66
|
+
wouldCreate.push(route.slotModule);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
dryRun: true,
|
|
73
|
+
wouldCreate,
|
|
74
|
+
wouldSkip,
|
|
75
|
+
routeCount: routes.length,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Actually generate
|
|
80
|
+
const result = await generateRoutes(manifestResult.data, projectRoot);
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
success: result.success,
|
|
84
|
+
created: result.created,
|
|
85
|
+
deleted: result.deleted,
|
|
86
|
+
skipped: result.skipped,
|
|
87
|
+
errors: result.errors,
|
|
88
|
+
summary: {
|
|
89
|
+
createdCount: result.created.length,
|
|
90
|
+
deletedCount: result.deleted.length,
|
|
91
|
+
skippedCount: result.skipped.length,
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
mandu_generate_status: async () => {
|
|
97
|
+
// Read generated map
|
|
98
|
+
const generatedMap = await readJsonFile<GeneratedMap>(paths.generatedMapPath);
|
|
99
|
+
|
|
100
|
+
if (!generatedMap) {
|
|
101
|
+
return {
|
|
102
|
+
hasGeneratedFiles: false,
|
|
103
|
+
message: "No generated.map.json found. Run mandu_generate first.",
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const fileCount = Object.keys(generatedMap.files).length;
|
|
108
|
+
const routeIds = Object.values(generatedMap.files).map((f) => f.routeId);
|
|
109
|
+
const uniqueRoutes = [...new Set(routeIds)];
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
hasGeneratedFiles: true,
|
|
113
|
+
version: generatedMap.version,
|
|
114
|
+
generatedAt: generatedMap.generatedAt,
|
|
115
|
+
specSource: generatedMap.specSource,
|
|
116
|
+
fileCount,
|
|
117
|
+
routeCount: uniqueRoutes.length,
|
|
118
|
+
files: Object.entries(generatedMap.files).map(([path, info]) => ({
|
|
119
|
+
path,
|
|
120
|
+
routeId: info.routeId,
|
|
121
|
+
kind: info.kind,
|
|
122
|
+
hasSlot: !!info.slotMapping,
|
|
123
|
+
})),
|
|
124
|
+
};
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
}
|