@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
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import type { Tool } from "@modelcontextprotocol/sdk/types.js";
|
|
2
|
+
import {
|
|
3
|
+
loadManifest,
|
|
4
|
+
runGuardCheck,
|
|
5
|
+
runAutoCorrect,
|
|
6
|
+
ErrorClassifier,
|
|
7
|
+
type ManduError,
|
|
8
|
+
type GeneratedMap,
|
|
9
|
+
} from "@mandujs/core";
|
|
10
|
+
import { getProjectPaths, readJsonFile } from "../utils/project.js";
|
|
11
|
+
|
|
12
|
+
export const guardToolDefinitions: Tool[] = [
|
|
13
|
+
{
|
|
14
|
+
name: "mandu_guard_check",
|
|
15
|
+
description:
|
|
16
|
+
"Run guard checks to validate spec integrity, generated files, and slot files",
|
|
17
|
+
inputSchema: {
|
|
18
|
+
type: "object",
|
|
19
|
+
properties: {
|
|
20
|
+
autoCorrect: {
|
|
21
|
+
type: "boolean",
|
|
22
|
+
description: "If true, attempt to automatically fix violations",
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
required: [],
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
name: "mandu_analyze_error",
|
|
30
|
+
description:
|
|
31
|
+
"Analyze a ManduError JSON to provide actionable fix guidance",
|
|
32
|
+
inputSchema: {
|
|
33
|
+
type: "object",
|
|
34
|
+
properties: {
|
|
35
|
+
errorJson: {
|
|
36
|
+
type: "string",
|
|
37
|
+
description: "The ManduError JSON string to analyze",
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
required: ["errorJson"],
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
export function guardTools(projectRoot: string) {
|
|
46
|
+
const paths = getProjectPaths(projectRoot);
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
mandu_guard_check: async (args: Record<string, unknown>) => {
|
|
50
|
+
const { autoCorrect = false } = args as { autoCorrect?: boolean };
|
|
51
|
+
|
|
52
|
+
// Load manifest
|
|
53
|
+
const manifestResult = await loadManifest(paths.manifestPath);
|
|
54
|
+
if (!manifestResult.success || !manifestResult.data) {
|
|
55
|
+
return {
|
|
56
|
+
error: "Failed to load manifest",
|
|
57
|
+
details: manifestResult.errors,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Run guard check
|
|
62
|
+
const checkResult = await runGuardCheck(manifestResult.data, projectRoot);
|
|
63
|
+
|
|
64
|
+
if (checkResult.passed) {
|
|
65
|
+
return {
|
|
66
|
+
passed: true,
|
|
67
|
+
violations: [],
|
|
68
|
+
message: "All guard checks passed",
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// If auto-correct requested and there are violations
|
|
73
|
+
if (autoCorrect && checkResult.violations.length > 0) {
|
|
74
|
+
const autoCorrectResult = await runAutoCorrect(
|
|
75
|
+
checkResult.violations,
|
|
76
|
+
manifestResult.data,
|
|
77
|
+
projectRoot
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
passed: autoCorrectResult.fixed,
|
|
82
|
+
violations: autoCorrectResult.remainingViolations,
|
|
83
|
+
autoCorrect: {
|
|
84
|
+
attempted: true,
|
|
85
|
+
fixed: autoCorrectResult.fixed,
|
|
86
|
+
steps: autoCorrectResult.steps,
|
|
87
|
+
retriedCount: autoCorrectResult.retriedCount,
|
|
88
|
+
rolledBack: autoCorrectResult.rolledBack,
|
|
89
|
+
changeId: autoCorrectResult.changeId,
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
passed: false,
|
|
96
|
+
violations: checkResult.violations.map((v) => ({
|
|
97
|
+
ruleId: v.ruleId,
|
|
98
|
+
file: v.file,
|
|
99
|
+
message: v.message,
|
|
100
|
+
suggestion: v.suggestion,
|
|
101
|
+
})),
|
|
102
|
+
message: `Found ${checkResult.violations.length} violation(s)`,
|
|
103
|
+
tip: "Use autoCorrect: true to attempt automatic fixes",
|
|
104
|
+
};
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
mandu_analyze_error: async (args: Record<string, unknown>) => {
|
|
108
|
+
const { errorJson } = args as { errorJson: string };
|
|
109
|
+
|
|
110
|
+
let error: ManduError;
|
|
111
|
+
try {
|
|
112
|
+
error = JSON.parse(errorJson) as ManduError;
|
|
113
|
+
} catch {
|
|
114
|
+
return {
|
|
115
|
+
error: "Invalid JSON format",
|
|
116
|
+
tip: "Provide a valid ManduError JSON string",
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Load generated map for better analysis
|
|
121
|
+
const generatedMap = await readJsonFile<GeneratedMap>(paths.generatedMapPath);
|
|
122
|
+
|
|
123
|
+
// Provide analysis based on error type
|
|
124
|
+
const analysis: Record<string, unknown> = {
|
|
125
|
+
errorType: error.errorType,
|
|
126
|
+
code: error.code,
|
|
127
|
+
summary: error.summary,
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
switch (error.errorType) {
|
|
131
|
+
case "SPEC_ERROR":
|
|
132
|
+
analysis.category = "Specification Error";
|
|
133
|
+
analysis.fixLocation = error.fix?.file || "spec/routes.manifest.json";
|
|
134
|
+
analysis.actions = [
|
|
135
|
+
"Check the spec file for JSON syntax errors",
|
|
136
|
+
"Validate route IDs are unique",
|
|
137
|
+
"Ensure patterns start with /",
|
|
138
|
+
"For page routes, verify componentModule is specified",
|
|
139
|
+
];
|
|
140
|
+
break;
|
|
141
|
+
|
|
142
|
+
case "LOGIC_ERROR":
|
|
143
|
+
analysis.category = "Business Logic Error";
|
|
144
|
+
analysis.fixLocation = error.fix?.file || "spec/slots/";
|
|
145
|
+
analysis.actions = [
|
|
146
|
+
"Review the slot file at the specified location",
|
|
147
|
+
error.fix?.suggestion || "Check the handler logic",
|
|
148
|
+
"Verify ctx.body() and ctx.params are used correctly",
|
|
149
|
+
"Add proper error handling in the slot",
|
|
150
|
+
];
|
|
151
|
+
if (error.fix?.line) {
|
|
152
|
+
analysis.lineNumber = error.fix.line;
|
|
153
|
+
}
|
|
154
|
+
break;
|
|
155
|
+
|
|
156
|
+
case "FRAMEWORK_BUG":
|
|
157
|
+
analysis.category = "Framework Internal Error";
|
|
158
|
+
analysis.fixLocation = error.fix?.file || "packages/core/";
|
|
159
|
+
analysis.actions = [
|
|
160
|
+
"This appears to be a framework bug",
|
|
161
|
+
"Check GitHub issues for similar problems",
|
|
162
|
+
"Consider filing a bug report with the error details",
|
|
163
|
+
];
|
|
164
|
+
analysis.reportUrl = "https://github.com/konamgil/mandu/issues";
|
|
165
|
+
break;
|
|
166
|
+
|
|
167
|
+
default:
|
|
168
|
+
analysis.category = "Unknown Error";
|
|
169
|
+
analysis.actions = [
|
|
170
|
+
"Review the error message for details",
|
|
171
|
+
error.fix?.suggestion || "Check related files",
|
|
172
|
+
];
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Add route context if available
|
|
176
|
+
if (error.route) {
|
|
177
|
+
analysis.routeContext = {
|
|
178
|
+
routeId: error.route.id,
|
|
179
|
+
pattern: error.route.pattern,
|
|
180
|
+
kind: error.route.kind,
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
// Try to find slot mapping
|
|
184
|
+
if (generatedMap && error.route.id) {
|
|
185
|
+
for (const [, entry] of Object.entries(generatedMap.files)) {
|
|
186
|
+
if (entry.routeId === error.route.id && entry.slotMapping) {
|
|
187
|
+
analysis.slotFile = entry.slotMapping.slotPath;
|
|
188
|
+
break;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Add debug info if available
|
|
195
|
+
if (error.debug) {
|
|
196
|
+
analysis.debug = {
|
|
197
|
+
hasStack: !!error.debug.stack,
|
|
198
|
+
generatedFile: error.debug.generatedFile,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return {
|
|
203
|
+
analysis,
|
|
204
|
+
originalError: {
|
|
205
|
+
message: error.message,
|
|
206
|
+
timestamp: error.timestamp,
|
|
207
|
+
},
|
|
208
|
+
};
|
|
209
|
+
},
|
|
210
|
+
};
|
|
211
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import type { Tool } from "@modelcontextprotocol/sdk/types.js";
|
|
2
|
+
import {
|
|
3
|
+
listChanges,
|
|
4
|
+
pruneHistory,
|
|
5
|
+
getChangeStats,
|
|
6
|
+
readSnapshotById,
|
|
7
|
+
} from "@mandujs/core";
|
|
8
|
+
|
|
9
|
+
export const historyToolDefinitions: Tool[] = [
|
|
10
|
+
{
|
|
11
|
+
name: "mandu_list_history",
|
|
12
|
+
description: "List the change history with snapshots",
|
|
13
|
+
inputSchema: {
|
|
14
|
+
type: "object",
|
|
15
|
+
properties: {
|
|
16
|
+
limit: {
|
|
17
|
+
type: "number",
|
|
18
|
+
description: "Maximum number of entries to return (default: 10)",
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
required: [],
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: "mandu_get_snapshot",
|
|
26
|
+
description: "Get details of a specific snapshot",
|
|
27
|
+
inputSchema: {
|
|
28
|
+
type: "object",
|
|
29
|
+
properties: {
|
|
30
|
+
snapshotId: {
|
|
31
|
+
type: "string",
|
|
32
|
+
description: "The snapshot ID to retrieve",
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
required: ["snapshotId"],
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: "mandu_prune_history",
|
|
40
|
+
description: "Remove old snapshots to free up space",
|
|
41
|
+
inputSchema: {
|
|
42
|
+
type: "object",
|
|
43
|
+
properties: {
|
|
44
|
+
keepCount: {
|
|
45
|
+
type: "number",
|
|
46
|
+
description: "Number of snapshots to keep (default: 5)",
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
required: [],
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
export function historyTools(projectRoot: string) {
|
|
55
|
+
return {
|
|
56
|
+
mandu_list_history: async (args: Record<string, unknown>) => {
|
|
57
|
+
const { limit = 10 } = args as { limit?: number };
|
|
58
|
+
|
|
59
|
+
const changes = await listChanges(projectRoot);
|
|
60
|
+
const stats = await getChangeStats(projectRoot);
|
|
61
|
+
|
|
62
|
+
// Sort by createdAt descending and limit
|
|
63
|
+
const sortedChanges = changes
|
|
64
|
+
.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime())
|
|
65
|
+
.slice(0, limit);
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
changes: sortedChanges.map((c) => ({
|
|
69
|
+
id: c.id,
|
|
70
|
+
message: c.message,
|
|
71
|
+
status: c.status,
|
|
72
|
+
createdAt: c.createdAt,
|
|
73
|
+
snapshotId: c.snapshotId,
|
|
74
|
+
autoGenerated: c.autoGenerated,
|
|
75
|
+
})),
|
|
76
|
+
stats: {
|
|
77
|
+
total: stats.total,
|
|
78
|
+
active: stats.active,
|
|
79
|
+
committed: stats.committed,
|
|
80
|
+
rolledBack: stats.rolledBack,
|
|
81
|
+
snapshotCount: stats.snapshotCount,
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
mandu_get_snapshot: async (args: Record<string, unknown>) => {
|
|
87
|
+
const { snapshotId } = args as { snapshotId: string };
|
|
88
|
+
|
|
89
|
+
const snapshot = await readSnapshotById(projectRoot, snapshotId);
|
|
90
|
+
if (!snapshot) {
|
|
91
|
+
return { error: `Snapshot not found: ${snapshotId}` };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const slotFiles = Object.keys(snapshot.slotContents);
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
id: snapshot.id,
|
|
98
|
+
timestamp: snapshot.timestamp,
|
|
99
|
+
manifest: {
|
|
100
|
+
version: snapshot.manifest.version,
|
|
101
|
+
routeCount: snapshot.manifest.routes.length,
|
|
102
|
+
routes: snapshot.manifest.routes.map((r) => ({
|
|
103
|
+
id: r.id,
|
|
104
|
+
pattern: r.pattern,
|
|
105
|
+
kind: r.kind,
|
|
106
|
+
})),
|
|
107
|
+
},
|
|
108
|
+
hasLock: !!snapshot.lock,
|
|
109
|
+
lock: snapshot.lock
|
|
110
|
+
? {
|
|
111
|
+
routesHash: snapshot.lock.routesHash,
|
|
112
|
+
updatedAt: snapshot.lock.updatedAt,
|
|
113
|
+
}
|
|
114
|
+
: null,
|
|
115
|
+
slotFiles,
|
|
116
|
+
slotCount: slotFiles.length,
|
|
117
|
+
};
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
mandu_prune_history: async (args: Record<string, unknown>) => {
|
|
121
|
+
const { keepCount = 5 } = args as { keepCount?: number };
|
|
122
|
+
|
|
123
|
+
const deletedIds = await pruneHistory(projectRoot, keepCount);
|
|
124
|
+
|
|
125
|
+
const stats = await getChangeStats(projectRoot);
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
success: true,
|
|
129
|
+
deletedCount: deletedIds.length,
|
|
130
|
+
deletedIds,
|
|
131
|
+
remaining: {
|
|
132
|
+
snapshotCount: stats.snapshotCount,
|
|
133
|
+
changeCount: stats.total,
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { specTools, specToolDefinitions } from "./spec.js";
|
|
2
|
+
export { generateTools, generateToolDefinitions } from "./generate.js";
|
|
3
|
+
export { transactionTools, transactionToolDefinitions } from "./transaction.js";
|
|
4
|
+
export { historyTools, historyToolDefinitions } from "./history.js";
|
|
5
|
+
export { guardTools, guardToolDefinitions } from "./guard.js";
|
|
6
|
+
export { slotTools, slotToolDefinitions } from "./slot.js";
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import type { Tool } from "@modelcontextprotocol/sdk/types.js";
|
|
2
|
+
import { loadManifest } from "@mandujs/core";
|
|
3
|
+
import { getProjectPaths, isInsideProject } from "../utils/project.js";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import fs from "fs/promises";
|
|
6
|
+
|
|
7
|
+
export const slotToolDefinitions: Tool[] = [
|
|
8
|
+
{
|
|
9
|
+
name: "mandu_read_slot",
|
|
10
|
+
description: "Read the contents of a slot file for a specific route",
|
|
11
|
+
inputSchema: {
|
|
12
|
+
type: "object",
|
|
13
|
+
properties: {
|
|
14
|
+
routeId: {
|
|
15
|
+
type: "string",
|
|
16
|
+
description: "The route ID whose slot file to read",
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
required: ["routeId"],
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
name: "mandu_write_slot",
|
|
24
|
+
description: "Write or update the contents of a slot file",
|
|
25
|
+
inputSchema: {
|
|
26
|
+
type: "object",
|
|
27
|
+
properties: {
|
|
28
|
+
routeId: {
|
|
29
|
+
type: "string",
|
|
30
|
+
description: "The route ID whose slot file to write",
|
|
31
|
+
},
|
|
32
|
+
content: {
|
|
33
|
+
type: "string",
|
|
34
|
+
description: "The TypeScript content to write to the slot file",
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
required: ["routeId", "content"],
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
export function slotTools(projectRoot: string) {
|
|
43
|
+
const paths = getProjectPaths(projectRoot);
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
mandu_read_slot: async (args: Record<string, unknown>) => {
|
|
47
|
+
const { routeId } = args as { routeId: string };
|
|
48
|
+
|
|
49
|
+
// Load manifest to find the route
|
|
50
|
+
const manifestResult = await loadManifest(paths.manifestPath);
|
|
51
|
+
if (!manifestResult.success || !manifestResult.data) {
|
|
52
|
+
return { error: manifestResult.errors };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const route = manifestResult.data.routes.find((r) => r.id === routeId);
|
|
56
|
+
if (!route) {
|
|
57
|
+
return { error: `Route not found: ${routeId}` };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (!route.slotModule) {
|
|
61
|
+
return {
|
|
62
|
+
error: `Route '${routeId}' does not have a slotModule defined`,
|
|
63
|
+
tip: "Add slotModule to the route spec or use mandu_update_route",
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const slotPath = path.join(projectRoot, route.slotModule);
|
|
68
|
+
|
|
69
|
+
// Security check
|
|
70
|
+
if (!isInsideProject(slotPath, projectRoot)) {
|
|
71
|
+
return { error: "Slot path is outside project directory" };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
const file = Bun.file(slotPath);
|
|
76
|
+
if (!(await file.exists())) {
|
|
77
|
+
return {
|
|
78
|
+
exists: false,
|
|
79
|
+
slotPath: route.slotModule,
|
|
80
|
+
message: "Slot file does not exist. Run mandu_generate to create it.",
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const content = await file.text();
|
|
85
|
+
return {
|
|
86
|
+
exists: true,
|
|
87
|
+
slotPath: route.slotModule,
|
|
88
|
+
content,
|
|
89
|
+
lineCount: content.split("\n").length,
|
|
90
|
+
};
|
|
91
|
+
} catch (error) {
|
|
92
|
+
return {
|
|
93
|
+
error: `Failed to read slot file: ${error instanceof Error ? error.message : String(error)}`,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
mandu_write_slot: async (args: Record<string, unknown>) => {
|
|
99
|
+
const { routeId, content } = args as { routeId: string; content: string };
|
|
100
|
+
|
|
101
|
+
// Load manifest to find the route
|
|
102
|
+
const manifestResult = await loadManifest(paths.manifestPath);
|
|
103
|
+
if (!manifestResult.success || !manifestResult.data) {
|
|
104
|
+
return { error: manifestResult.errors };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const route = manifestResult.data.routes.find((r) => r.id === routeId);
|
|
108
|
+
if (!route) {
|
|
109
|
+
return { error: `Route not found: ${routeId}` };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (!route.slotModule) {
|
|
113
|
+
return {
|
|
114
|
+
error: `Route '${routeId}' does not have a slotModule defined`,
|
|
115
|
+
tip: "Add slotModule to the route spec first",
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const slotPath = path.join(projectRoot, route.slotModule);
|
|
120
|
+
|
|
121
|
+
// Security check
|
|
122
|
+
if (!isInsideProject(slotPath, projectRoot)) {
|
|
123
|
+
return { error: "Slot path is outside project directory" };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Basic validation - check for Mandu.filling() pattern
|
|
127
|
+
if (!content.includes("Mandu") && !content.includes("filling")) {
|
|
128
|
+
return {
|
|
129
|
+
warning: "Slot content doesn't appear to use Mandu.filling() pattern",
|
|
130
|
+
tip: "Slot files should export a default using Mandu.filling()",
|
|
131
|
+
proceeding: true,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
try {
|
|
136
|
+
// Ensure directory exists
|
|
137
|
+
const slotDir = path.dirname(slotPath);
|
|
138
|
+
await fs.mkdir(slotDir, { recursive: true });
|
|
139
|
+
|
|
140
|
+
// Check if file exists (for backup/warning)
|
|
141
|
+
const file = Bun.file(slotPath);
|
|
142
|
+
const existed = await file.exists();
|
|
143
|
+
let previousContent: string | null = null;
|
|
144
|
+
|
|
145
|
+
if (existed) {
|
|
146
|
+
previousContent = await file.text();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Write the new content
|
|
150
|
+
await Bun.write(slotPath, content);
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
success: true,
|
|
154
|
+
slotPath: route.slotModule,
|
|
155
|
+
action: existed ? "updated" : "created",
|
|
156
|
+
lineCount: content.split("\n").length,
|
|
157
|
+
previousLineCount: previousContent ? previousContent.split("\n").length : null,
|
|
158
|
+
};
|
|
159
|
+
} catch (error) {
|
|
160
|
+
return {
|
|
161
|
+
error: `Failed to write slot file: ${error instanceof Error ? error.message : String(error)}`,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
}
|