@contractkit/plugin-typescript 0.28.1 → 0.29.0
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/.turbo/turbo-build$colon$ci.log +5 -5
- package/.turbo/turbo-test$colon$ci.log +21 -19
- package/CHANGELOG.md +16 -0
- package/README.md +4 -0
- package/dist/codegen-contract.d.ts +7 -0
- package/dist/codegen-contract.d.ts.map +1 -1
- package/dist/codegen-mcp.d.ts +38 -0
- package/dist/codegen-mcp.d.ts.map +1 -0
- package/dist/codegen-operation.d.ts +42 -1
- package/dist/codegen-operation.d.ts.map +1 -1
- package/dist/codegen-plain-types.d.ts +3 -0
- package/dist/codegen-plain-types.d.ts.map +1 -1
- package/dist/index.d.ts +39 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +686 -96
- package/dist/index.js.map +1 -1
- package/dist/ts-render.d.ts +21 -3
- package/dist/ts-render.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/codegen-contract.ts +7 -0
- package/src/codegen-mcp.ts +501 -0
- package/src/codegen-operation.ts +104 -10
- package/src/codegen-plain-types.ts +47 -22
- package/src/index.ts +175 -1
- package/src/ts-render.ts +52 -32
- package/tests/codegen-mcp.test.ts +246 -0
- package/tests/codegen-operation.test.ts +89 -0
- package/tests/codegen-plain-types.test.ts +48 -0
- package/tests/codegen-server.test.ts +37 -0
- package/tests/pipeline.test.ts +59 -0
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@ var __defProp = Object.defineProperty;
|
|
|
2
2
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
3
|
|
|
4
4
|
// src/index.ts
|
|
5
|
-
import { resolve as resolve2, join as join2, relative as
|
|
5
|
+
import { resolve as resolve2, join as join2, relative as relative7, dirname as dirname7, basename as basename4 } from "path";
|
|
6
6
|
import { existsSync, readFileSync, writeFileSync, mkdirSync, rmSync, readdirSync, rmdirSync } from "fs";
|
|
7
7
|
|
|
8
8
|
// src/codegen-contract.ts
|
|
@@ -31,41 +31,41 @@ function headerNameToProperty(name) {
|
|
|
31
31
|
}).join("");
|
|
32
32
|
}
|
|
33
33
|
__name(headerNameToProperty, "headerNameToProperty");
|
|
34
|
-
function renderTsType(type) {
|
|
34
|
+
function renderTsType(type, target = "client") {
|
|
35
35
|
switch (type.kind) {
|
|
36
36
|
case "scalar":
|
|
37
|
-
return renderTsScalar(type.name);
|
|
37
|
+
return renderTsScalar(type.name, target);
|
|
38
38
|
case "array": {
|
|
39
|
-
const inner = renderTsType(type.item);
|
|
39
|
+
const inner = renderTsType(type.item, target);
|
|
40
40
|
const needsParens = type.item.kind === "union" || type.item.kind === "discriminatedUnion" || type.item.kind === "intersection" || type.item.kind === "enum";
|
|
41
41
|
return needsParens ? `(${inner})[]` : `${inner}[]`;
|
|
42
42
|
}
|
|
43
43
|
case "tuple":
|
|
44
|
-
return `[${type.items.map(renderTsType).join(", ")}]`;
|
|
44
|
+
return `[${type.items.map((i) => renderTsType(i, target)).join(", ")}]`;
|
|
45
45
|
case "record":
|
|
46
|
-
return `Record<${renderTsType(type.key)}, ${renderTsType(type.value)}>`;
|
|
46
|
+
return `Record<${renderTsType(type.key, target)}, ${renderTsType(type.value, target)}>`;
|
|
47
47
|
case "enum":
|
|
48
48
|
return type.values.map((v) => `'${escapeSingleQuoted(v)}'`).join(" | ");
|
|
49
49
|
case "literal":
|
|
50
50
|
return typeof type.value === "string" ? `'${escapeSingleQuoted(type.value)}'` : String(type.value);
|
|
51
51
|
case "union":
|
|
52
|
-
return type.members.map(renderTsType).join(" | ");
|
|
52
|
+
return type.members.map((m) => renderTsType(m, target)).join(" | ");
|
|
53
53
|
case "discriminatedUnion":
|
|
54
|
-
return type.members.map(renderTsType).join(" | ");
|
|
54
|
+
return type.members.map((m) => renderTsType(m, target)).join(" | ");
|
|
55
55
|
case "intersection":
|
|
56
|
-
return type.members.map(renderTsType).join(" & ");
|
|
56
|
+
return type.members.map((m) => renderTsType(m, target)).join(" & ");
|
|
57
57
|
case "ref":
|
|
58
58
|
return type.name;
|
|
59
59
|
case "lazy":
|
|
60
|
-
return renderTsType(type.inner);
|
|
60
|
+
return renderTsType(type.inner, target);
|
|
61
61
|
case "inlineObject":
|
|
62
|
-
return renderTsInlineObject(type.fields);
|
|
62
|
+
return renderTsInlineObject(type.fields, target);
|
|
63
63
|
default:
|
|
64
64
|
return "unknown";
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
67
|
__name(renderTsType, "renderTsType");
|
|
68
|
-
function renderTsScalar(name) {
|
|
68
|
+
function renderTsScalar(name, target) {
|
|
69
69
|
switch (name) {
|
|
70
70
|
case "string":
|
|
71
71
|
case "email":
|
|
@@ -92,7 +92,7 @@ function renderTsScalar(name) {
|
|
|
92
92
|
case "object":
|
|
93
93
|
return "Record<string, unknown>";
|
|
94
94
|
case "binary":
|
|
95
|
-
return "Blob";
|
|
95
|
+
return target === "server" ? "Buffer" : "Blob";
|
|
96
96
|
case "json":
|
|
97
97
|
return "JsonValue";
|
|
98
98
|
default: {
|
|
@@ -102,61 +102,61 @@ function renderTsScalar(name) {
|
|
|
102
102
|
}
|
|
103
103
|
}
|
|
104
104
|
__name(renderTsScalar, "renderTsScalar");
|
|
105
|
-
function renderTsInlineObject(fields) {
|
|
105
|
+
function renderTsInlineObject(fields, target) {
|
|
106
106
|
const entries = fields.map((f) => {
|
|
107
107
|
const opt = f.optional ? "?" : "";
|
|
108
|
-
return `${quoteKey(f.name)}${opt}: ${renderTsType(f.type)}`;
|
|
108
|
+
return `${quoteKey(f.name)}${opt}: ${renderTsType(f.type, target)}`;
|
|
109
109
|
});
|
|
110
110
|
return `{ ${entries.join("; ")} }`;
|
|
111
111
|
}
|
|
112
112
|
__name(renderTsInlineObject, "renderTsInlineObject");
|
|
113
|
-
function renderInputTsType(type, modelsWithInput) {
|
|
114
|
-
if (!modelsWithInput || modelsWithInput.size === 0) return renderTsType(type);
|
|
113
|
+
function renderInputTsType(type, modelsWithInput, target = "client") {
|
|
114
|
+
if (!modelsWithInput || modelsWithInput.size === 0) return renderTsType(type, target);
|
|
115
115
|
switch (type.kind) {
|
|
116
116
|
case "ref":
|
|
117
117
|
return modelsWithInput.has(type.name) ? `${type.name}Input` : type.name;
|
|
118
118
|
case "array": {
|
|
119
|
-
const inner = renderInputTsType(type.item, modelsWithInput);
|
|
119
|
+
const inner = renderInputTsType(type.item, modelsWithInput, target);
|
|
120
120
|
const needsParens = type.item.kind === "union" || type.item.kind === "discriminatedUnion" || type.item.kind === "intersection" || type.item.kind === "enum";
|
|
121
121
|
return needsParens ? `(${inner})[]` : `${inner}[]`;
|
|
122
122
|
}
|
|
123
123
|
case "intersection":
|
|
124
|
-
return type.members.map((m) => renderInputTsType(m, modelsWithInput)).join(" & ");
|
|
124
|
+
return type.members.map((m) => renderInputTsType(m, modelsWithInput, target)).join(" & ");
|
|
125
125
|
case "union":
|
|
126
|
-
return type.members.map((m) => renderInputTsType(m, modelsWithInput)).join(" | ");
|
|
126
|
+
return type.members.map((m) => renderInputTsType(m, modelsWithInput, target)).join(" | ");
|
|
127
127
|
case "discriminatedUnion":
|
|
128
|
-
return type.members.map((m) => renderInputTsType(m, modelsWithInput)).join(" | ");
|
|
128
|
+
return type.members.map((m) => renderInputTsType(m, modelsWithInput, target)).join(" | ");
|
|
129
129
|
case "inlineObject":
|
|
130
|
-
return `{ ${type.fields.map((f) => `${quoteKey(f.name)}${f.optional ? "?" : ""}: ${renderInputTsType(f.type, modelsWithInput)}`).join("; ")} }`;
|
|
130
|
+
return `{ ${type.fields.map((f) => `${quoteKey(f.name)}${f.optional ? "?" : ""}: ${renderInputTsType(f.type, modelsWithInput, target)}`).join("; ")} }`;
|
|
131
131
|
case "lazy":
|
|
132
|
-
return renderInputTsType(type.inner, modelsWithInput);
|
|
132
|
+
return renderInputTsType(type.inner, modelsWithInput, target);
|
|
133
133
|
default:
|
|
134
|
-
return renderTsType(type);
|
|
134
|
+
return renderTsType(type, target);
|
|
135
135
|
}
|
|
136
136
|
}
|
|
137
137
|
__name(renderInputTsType, "renderInputTsType");
|
|
138
|
-
function renderOutputTsType(type, modelsWithOutput) {
|
|
139
|
-
if (!modelsWithOutput || modelsWithOutput.size === 0) return renderTsType(type);
|
|
138
|
+
function renderOutputTsType(type, modelsWithOutput, target = "client") {
|
|
139
|
+
if (!modelsWithOutput || modelsWithOutput.size === 0) return renderTsType(type, target);
|
|
140
140
|
switch (type.kind) {
|
|
141
141
|
case "ref":
|
|
142
142
|
return modelsWithOutput.has(type.name) ? `${type.name}Output` : type.name;
|
|
143
143
|
case "array": {
|
|
144
|
-
const inner = renderOutputTsType(type.item, modelsWithOutput);
|
|
144
|
+
const inner = renderOutputTsType(type.item, modelsWithOutput, target);
|
|
145
145
|
const needsParens = type.item.kind === "union" || type.item.kind === "discriminatedUnion" || type.item.kind === "intersection" || type.item.kind === "enum";
|
|
146
146
|
return needsParens ? `(${inner})[]` : `${inner}[]`;
|
|
147
147
|
}
|
|
148
148
|
case "intersection":
|
|
149
|
-
return type.members.map((m) => renderOutputTsType(m, modelsWithOutput)).join(" & ");
|
|
149
|
+
return type.members.map((m) => renderOutputTsType(m, modelsWithOutput, target)).join(" & ");
|
|
150
150
|
case "union":
|
|
151
|
-
return type.members.map((m) => renderOutputTsType(m, modelsWithOutput)).join(" | ");
|
|
151
|
+
return type.members.map((m) => renderOutputTsType(m, modelsWithOutput, target)).join(" | ");
|
|
152
152
|
case "discriminatedUnion":
|
|
153
|
-
return type.members.map((m) => renderOutputTsType(m, modelsWithOutput)).join(" | ");
|
|
153
|
+
return type.members.map((m) => renderOutputTsType(m, modelsWithOutput, target)).join(" | ");
|
|
154
154
|
case "inlineObject":
|
|
155
|
-
return `{ ${type.fields.map((f) => `${quoteKey(f.name)}${f.optional ? "?" : ""}: ${renderOutputTsType(f.type, modelsWithOutput)}`).join("; ")} }`;
|
|
155
|
+
return `{ ${type.fields.map((f) => `${quoteKey(f.name)}${f.optional ? "?" : ""}: ${renderOutputTsType(f.type, modelsWithOutput, target)}`).join("; ")} }`;
|
|
156
156
|
case "lazy":
|
|
157
|
-
return renderOutputTsType(type.inner, modelsWithOutput);
|
|
157
|
+
return renderOutputTsType(type.inner, modelsWithOutput, target);
|
|
158
158
|
default:
|
|
159
|
-
return renderTsType(type);
|
|
159
|
+
return renderTsType(type, target);
|
|
160
160
|
}
|
|
161
161
|
}
|
|
162
162
|
__name(renderOutputTsType, "renderOutputTsType");
|
|
@@ -1200,8 +1200,12 @@ function generateOp(root, options = {}) {
|
|
|
1200
1200
|
if (types.length > 0) {
|
|
1201
1201
|
body.push(...generateTypeImports(types, root.file, options));
|
|
1202
1202
|
}
|
|
1203
|
-
|
|
1204
|
-
|
|
1203
|
+
const luxonImports = [];
|
|
1204
|
+
if (opNeedsDateTime(root)) luxonImports.push("DateTime");
|
|
1205
|
+
if (opNeedsScalar(root, "duration")) luxonImports.push("Duration");
|
|
1206
|
+
if (opNeedsScalar(root, "interval")) luxonImports.push("Interval");
|
|
1207
|
+
if (luxonImports.length > 0) {
|
|
1208
|
+
body.push(`import { ${luxonImports.join(", ")} } from 'luxon';`);
|
|
1205
1209
|
}
|
|
1206
1210
|
if (needsParseAndValidate) {
|
|
1207
1211
|
body.push(`import { parseAndValidate } from '@maroonedsoftware/zod';`);
|
|
@@ -1216,6 +1220,9 @@ function generateOp(root, options = {}) {
|
|
|
1216
1220
|
if (opNeedsScalar(root, "datetime")) {
|
|
1217
1221
|
helpers.push(`const _ZodDatetime = z.preprocess((val) => typeof val === 'string' ? DateTime.fromISO(val) : val, z.custom<DateTime>((val) => val instanceof DateTime && val.isValid, { message: 'Must be in ISO 8601 format' }));`);
|
|
1218
1222
|
}
|
|
1223
|
+
if (opNeedsScalar(root, "interval")) {
|
|
1224
|
+
helpers.push(`const _ZodInterval = z.preprocess((val) => typeof val === 'string' ? Interval.fromISO(val) : val, z.custom<Interval>((val) => val instanceof Interval && val.isValid, { message: 'Must be an ISO 8601 interval' })).transform(val => val.toISO()!);`);
|
|
1225
|
+
}
|
|
1219
1226
|
if (opNeedsScalar(root, "json")) {
|
|
1220
1227
|
helpers.push(`type _JsonValue = string | number | boolean | null | _JsonValue[] | { [key: string]: _JsonValue };`);
|
|
1221
1228
|
helpers.push(`const _ZodJson: z.ZodType<_JsonValue> = z.lazy(() => z.union([z.string(), z.number(), z.boolean(), z.null(), z.array(_ZodJson), z.record(z.string(), _ZodJson)]));`);
|
|
@@ -1290,7 +1297,7 @@ function generateHandler(route, op, root, options) {
|
|
|
1290
1297
|
middlewares.push(`requireSignature(${sigArgs})`);
|
|
1291
1298
|
}
|
|
1292
1299
|
const middlewareStr = middlewares.length > 0 ? `, ${middlewares.join(", ")},` : ",";
|
|
1293
|
-
lines.push(`${deriveRouterName(file)}.${method}('${path}'${middlewareStr} async
|
|
1300
|
+
lines.push(`${deriveRouterName(file)}.${method}('${path}'${middlewareStr} async ctx => {`);
|
|
1294
1301
|
lines.push(...generateParamValidation(route.params, "ctx.params", "params", route.paramsMode ?? "strict", "", modelsWithInput));
|
|
1295
1302
|
lines.push(...generateParamValidation(op.query, "ctx.query", "query", op.queryMode ?? "strict", "", modelsWithInput));
|
|
1296
1303
|
lines.push(...generateParamValidation(op.headers, "ctx.headers", "headers", op.headersMode ?? "strip", "", modelsWithInput));
|
|
@@ -1321,13 +1328,13 @@ function generateHandler(route, op, root, options) {
|
|
|
1321
1328
|
lines.push("");
|
|
1322
1329
|
}
|
|
1323
1330
|
}
|
|
1324
|
-
const
|
|
1331
|
+
const primaryResponse2 = op.responses.find((r) => r.bodyType) ?? op.responses[0];
|
|
1325
1332
|
const serviceParts = inferService(op, route, file);
|
|
1326
|
-
const respHeaders =
|
|
1333
|
+
const respHeaders = primaryResponse2?.headers ?? [];
|
|
1327
1334
|
const hasRespHeaders = respHeaders.length > 0;
|
|
1328
|
-
const headersAnnotation = hasRespHeaders ? `{ ${respHeaders.map((h) => `${quoteKey(headerNameToProperty(h.name))}${h.optional ? "?" : ""}: ${renderOutputTsType(h.type, options.modelsWithOutput)}`).join("; ")} }` : "";
|
|
1329
|
-
if (
|
|
1330
|
-
const { annotation, prelude } = formatTypeAnnotation(
|
|
1335
|
+
const headersAnnotation = hasRespHeaders ? `{ ${respHeaders.map((h) => `${quoteKey(headerNameToProperty(h.name))}${h.optional ? "?" : ""}: ${renderOutputTsType(h.type, options.modelsWithOutput, "server")}`).join("; ")} }` : "";
|
|
1336
|
+
if (primaryResponse2?.bodyType) {
|
|
1337
|
+
const { annotation, prelude } = formatTypeAnnotation(primaryResponse2.bodyType, options.modelsWithOutput);
|
|
1331
1338
|
if (prelude) {
|
|
1332
1339
|
lines.push(` ${prelude}`);
|
|
1333
1340
|
}
|
|
@@ -1346,7 +1353,7 @@ function generateHandler(route, op, root, options) {
|
|
|
1346
1353
|
}
|
|
1347
1354
|
}
|
|
1348
1355
|
lines.push("");
|
|
1349
|
-
lines.push(` ctx.status = ${
|
|
1356
|
+
lines.push(` ctx.status = ${primaryResponse2?.statusCode ?? 200};`);
|
|
1350
1357
|
if (hasRespHeaders) {
|
|
1351
1358
|
for (const h of respHeaders) {
|
|
1352
1359
|
const accessor = `result.headers[${JSON.stringify(headerNameToProperty(h.name))}]`;
|
|
@@ -1357,8 +1364,8 @@ function generateHandler(route, op, root, options) {
|
|
|
1357
1364
|
}
|
|
1358
1365
|
}
|
|
1359
1366
|
}
|
|
1360
|
-
if (
|
|
1361
|
-
lines.push(` ctx.type = '${
|
|
1367
|
+
if (primaryResponse2?.bodyType && primaryResponse2.contentType) {
|
|
1368
|
+
lines.push(` ctx.type = '${primaryResponse2.contentType}';`);
|
|
1362
1369
|
lines.push(` ctx.body = ${hasRespHeaders ? "result.body" : "result"};`);
|
|
1363
1370
|
}
|
|
1364
1371
|
lines.push(`});`);
|
|
@@ -1419,6 +1426,45 @@ function buildArgs(route, op) {
|
|
|
1419
1426
|
return args.join(", ");
|
|
1420
1427
|
}
|
|
1421
1428
|
__name(buildArgs, "buildArgs");
|
|
1429
|
+
function serverTsScalar(name) {
|
|
1430
|
+
switch (name) {
|
|
1431
|
+
case "string":
|
|
1432
|
+
case "email":
|
|
1433
|
+
case "url":
|
|
1434
|
+
case "uuid":
|
|
1435
|
+
return "string";
|
|
1436
|
+
case "number":
|
|
1437
|
+
case "int":
|
|
1438
|
+
return "number";
|
|
1439
|
+
case "bigint":
|
|
1440
|
+
return "bigint";
|
|
1441
|
+
case "boolean":
|
|
1442
|
+
return "boolean";
|
|
1443
|
+
case "date":
|
|
1444
|
+
case "time":
|
|
1445
|
+
case "datetime":
|
|
1446
|
+
return "DateTime";
|
|
1447
|
+
case "duration":
|
|
1448
|
+
return "Duration";
|
|
1449
|
+
case "interval":
|
|
1450
|
+
return "string";
|
|
1451
|
+
case "binary":
|
|
1452
|
+
return "Buffer";
|
|
1453
|
+
case "json":
|
|
1454
|
+
return "_JsonValue";
|
|
1455
|
+
case "object":
|
|
1456
|
+
return "Record<string, unknown>";
|
|
1457
|
+
case "null":
|
|
1458
|
+
return "null";
|
|
1459
|
+
case "unknown":
|
|
1460
|
+
return "unknown";
|
|
1461
|
+
default: {
|
|
1462
|
+
const _exhaustive = name;
|
|
1463
|
+
throw new Error(`plugin-typescript: unmapped scalar '${String(_exhaustive)}' \u2014 add a case`);
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
__name(serverTsScalar, "serverTsScalar");
|
|
1422
1468
|
function formatTypeAnnotation(bodyType, modelsWithOutput) {
|
|
1423
1469
|
if (bodyType.kind === "array") {
|
|
1424
1470
|
const inner = formatTypeAnnotation(bodyType.item, modelsWithOutput);
|
|
@@ -1434,7 +1480,7 @@ function formatTypeAnnotation(bodyType, modelsWithOutput) {
|
|
|
1434
1480
|
};
|
|
1435
1481
|
}
|
|
1436
1482
|
if (bodyType.kind === "scalar") return {
|
|
1437
|
-
annotation: bodyType.name
|
|
1483
|
+
annotation: serverTsScalar(bodyType.name)
|
|
1438
1484
|
};
|
|
1439
1485
|
const schema = renderType(bodyType);
|
|
1440
1486
|
return {
|
|
@@ -1960,11 +2006,11 @@ function generateMethod(route, op, file, options) {
|
|
|
1960
2006
|
const { modelsWithInput, modelsWithOutput } = options;
|
|
1961
2007
|
const params = buildMethodParams(route, op, modelsWithInput);
|
|
1962
2008
|
const paramStr = params.map((p) => `${p.name}${p.optional ? "?" : ""}: ${p.type}`).join(", ");
|
|
1963
|
-
const
|
|
1964
|
-
const isVoid = !
|
|
1965
|
-
const respCategory =
|
|
1966
|
-
const dataType = isVoid ? "void" : respCategory === "text" ? "string" : respCategory === "binary" ? "Blob" : renderOutputTsType(
|
|
1967
|
-
const respHeaders =
|
|
2009
|
+
const primaryResponse2 = op.responses.find((r) => r.bodyType) ?? op.responses[0];
|
|
2010
|
+
const isVoid = !primaryResponse2?.bodyType;
|
|
2011
|
+
const respCategory = primaryResponse2?.contentType ? classifyContentType2(primaryResponse2.contentType) : "json";
|
|
2012
|
+
const dataType = isVoid ? "void" : respCategory === "text" ? "string" : respCategory === "binary" ? "Blob" : renderOutputTsType(primaryResponse2.bodyType, modelsWithOutput);
|
|
2013
|
+
const respHeaders = primaryResponse2?.headers ?? [];
|
|
1968
2014
|
const hasRespHeaders = respHeaders.length > 0;
|
|
1969
2015
|
const headersShape = hasRespHeaders ? `{ ${respHeaders.map((h) => `${quoteKey(headerNameToProperty(h.name))}${h.optional ? "?" : ""}: ${renderOutputTsType(h.type, modelsWithOutput)}`).join("; ")} }` : "";
|
|
1970
2016
|
const returnType = hasRespHeaders ? isVoid ? `{ headers: ${headersShape} }` : `{ data: ${dataType}; headers: ${headersShape} }` : dataType;
|
|
@@ -2807,6 +2853,7 @@ __name(generateSdkAggregator, "generateSdkAggregator");
|
|
|
2807
2853
|
import { relative as relative4, dirname as dirname4 } from "path";
|
|
2808
2854
|
import { computeModelsWithOutput, collectExternalOutputRefs } from "@contractkit/core";
|
|
2809
2855
|
function generatePlainTypes(root, context) {
|
|
2856
|
+
const target = context?.target ?? "client";
|
|
2810
2857
|
const externalRefs = collectExternalRefs(root);
|
|
2811
2858
|
const lines = [];
|
|
2812
2859
|
const externalModelsWithInput = context?.modelsWithInput ?? /* @__PURE__ */ new Set();
|
|
@@ -2848,21 +2895,21 @@ function generatePlainTypes(root, context) {
|
|
|
2848
2895
|
m
|
|
2849
2896
|
]));
|
|
2850
2897
|
for (const model of topoSortModels(root.models)) {
|
|
2851
|
-
lines.push(...generateModel2(model, context?.currentOutPath, allModelsWithInput, allModelsWithOutput, modelMap));
|
|
2898
|
+
lines.push(...generateModel2(model, target, context?.currentOutPath, allModelsWithInput, allModelsWithOutput, modelMap));
|
|
2852
2899
|
lines.push("");
|
|
2853
2900
|
}
|
|
2854
2901
|
return lines.join("\n");
|
|
2855
2902
|
}
|
|
2856
2903
|
__name(generatePlainTypes, "generatePlainTypes");
|
|
2857
|
-
function generateModel2(model, outPath, modelsWithInput, modelsWithOutput, modelMap) {
|
|
2904
|
+
function generateModel2(model, target, outPath, modelsWithInput, modelsWithOutput, modelMap) {
|
|
2858
2905
|
if (model.type) {
|
|
2859
|
-
return generateTypeAlias2(model, outPath, modelsWithInput, modelsWithOutput);
|
|
2906
|
+
return generateTypeAlias2(model, target, outPath, modelsWithInput, modelsWithOutput);
|
|
2860
2907
|
}
|
|
2861
2908
|
const needsInputSplit = model.fields.some((f) => f.visibility !== "normal") || (modelsWithInput?.has(model.name) ?? false);
|
|
2862
|
-
const lines = needsInputSplit ? generateVisibilityModel(model, outPath, modelsWithInput, modelMap) : generateSimpleModel2(model, outPath, modelMap);
|
|
2909
|
+
const lines = needsInputSplit ? generateVisibilityModel(model, target, outPath, modelsWithInput, modelMap) : generateSimpleModel2(model, target, outPath, modelMap);
|
|
2863
2910
|
if (modelsWithOutput?.has(model.name)) {
|
|
2864
2911
|
lines.push("");
|
|
2865
|
-
lines.push(...generateOutputModel(model, modelsWithOutput));
|
|
2912
|
+
lines.push(...generateOutputModel(model, target, modelsWithOutput));
|
|
2866
2913
|
}
|
|
2867
2914
|
return lines;
|
|
2868
2915
|
}
|
|
@@ -2899,15 +2946,15 @@ function generateComments2(model, outPath) {
|
|
|
2899
2946
|
return lines;
|
|
2900
2947
|
}
|
|
2901
2948
|
__name(generateComments2, "generateComments");
|
|
2902
|
-
function generateTypeAlias2(model, outPath, modelsWithInput, modelsWithOutput) {
|
|
2949
|
+
function generateTypeAlias2(model, target, outPath, modelsWithInput, modelsWithOutput) {
|
|
2903
2950
|
const lines = [];
|
|
2904
2951
|
lines.push(...generateComments2(model, outPath));
|
|
2905
|
-
lines.push(`export type ${model.name} = ${renderTsType(model.type)};`);
|
|
2952
|
+
lines.push(`export type ${model.name} = ${renderTsType(model.type, target)};`);
|
|
2906
2953
|
if (modelsWithInput?.has(model.name)) {
|
|
2907
|
-
lines.push(`export type ${model.name}Input = ${renderInputTsType(model.type, modelsWithInput)};`);
|
|
2954
|
+
lines.push(`export type ${model.name}Input = ${renderInputTsType(model.type, modelsWithInput, target)};`);
|
|
2908
2955
|
}
|
|
2909
2956
|
if (modelsWithOutput?.has(model.name)) {
|
|
2910
|
-
lines.push(`export type ${model.name}Output = ${renderOutputTsType(model.type, modelsWithOutput)};`);
|
|
2957
|
+
lines.push(`export type ${model.name}Output = ${renderOutputTsType(model.type, modelsWithOutput, target)};`);
|
|
2911
2958
|
}
|
|
2912
2959
|
return lines;
|
|
2913
2960
|
}
|
|
@@ -2920,20 +2967,20 @@ function buildExtendsClause(bases, overrideNames, baseNameResolver) {
|
|
|
2920
2967
|
return ` extends ${wrapped.join(", ")}`;
|
|
2921
2968
|
}
|
|
2922
2969
|
__name(buildExtendsClause, "buildExtendsClause");
|
|
2923
|
-
function generateSimpleModel2(model, outPath, modelMap) {
|
|
2970
|
+
function generateSimpleModel2(model, target, outPath, modelMap) {
|
|
2924
2971
|
const lines = [];
|
|
2925
2972
|
lines.push(...generateComments2(model, outPath));
|
|
2926
2973
|
const bases = model.bases ?? [];
|
|
2927
2974
|
const overrideNames = computeOverrideNames(model, modelMap);
|
|
2928
2975
|
lines.push(`export interface ${model.name}${buildExtendsClause(bases, overrideNames, (b) => b)} {`);
|
|
2929
2976
|
for (const field of model.fields) {
|
|
2930
|
-
lines.push(` ${renderField2(field)}`);
|
|
2977
|
+
lines.push(` ${renderField2(field, target)}`);
|
|
2931
2978
|
}
|
|
2932
2979
|
lines.push("}");
|
|
2933
2980
|
return lines;
|
|
2934
2981
|
}
|
|
2935
2982
|
__name(generateSimpleModel2, "generateSimpleModel");
|
|
2936
|
-
function generateVisibilityModel(model, outPath, modelsWithInput, modelMap) {
|
|
2983
|
+
function generateVisibilityModel(model, target, outPath, modelsWithInput, modelMap) {
|
|
2937
2984
|
const lines = [];
|
|
2938
2985
|
lines.push(...generateComments2(model, outPath));
|
|
2939
2986
|
const bases = model.bases ?? [];
|
|
@@ -2941,7 +2988,7 @@ function generateVisibilityModel(model, outPath, modelsWithInput, modelMap) {
|
|
|
2941
2988
|
const readFields = model.fields.filter((f) => f.visibility !== "writeonly");
|
|
2942
2989
|
lines.push(`export interface ${model.name}${buildExtendsClause(bases, overrideNames, (b) => b)} {`);
|
|
2943
2990
|
for (const field of readFields) {
|
|
2944
|
-
lines.push(` ${renderField2(field)}`);
|
|
2991
|
+
lines.push(` ${renderField2(field, target)}`);
|
|
2945
2992
|
}
|
|
2946
2993
|
lines.push("}");
|
|
2947
2994
|
lines.push("");
|
|
@@ -2949,7 +2996,7 @@ function generateVisibilityModel(model, outPath, modelsWithInput, modelMap) {
|
|
|
2949
2996
|
const inputResolver = /* @__PURE__ */ __name((b) => modelsWithInput?.has(b) ? `${b}Input` : b, "inputResolver");
|
|
2950
2997
|
lines.push(`export interface ${model.name}Input${buildExtendsClause(bases, overrideNames, inputResolver)} {`);
|
|
2951
2998
|
for (const field of writeFields) {
|
|
2952
|
-
lines.push(` ${modelsWithInput ? renderInputField2(field, modelsWithInput) : renderField2(field)}`);
|
|
2999
|
+
lines.push(` ${modelsWithInput ? renderInputField2(field, modelsWithInput, target) : renderField2(field, target)}`);
|
|
2953
3000
|
}
|
|
2954
3001
|
lines.push("}");
|
|
2955
3002
|
return lines;
|
|
@@ -2969,9 +3016,9 @@ ${body}
|
|
|
2969
3016
|
${line}`;
|
|
2970
3017
|
}
|
|
2971
3018
|
__name(withFieldJsDoc, "withFieldJsDoc");
|
|
2972
|
-
function renderField2(field) {
|
|
3019
|
+
function renderField2(field, target) {
|
|
2973
3020
|
const opt = field.optional || field.default !== void 0 ? "?" : "";
|
|
2974
|
-
let typeStr = renderTsType(field.type);
|
|
3021
|
+
let typeStr = renderTsType(field.type, target);
|
|
2975
3022
|
if (field.nullable) typeStr += " | null";
|
|
2976
3023
|
const line = `${quoteKey(field.name)}${opt}: ${typeStr};`;
|
|
2977
3024
|
const jsdocParts = [];
|
|
@@ -2980,9 +3027,9 @@ function renderField2(field) {
|
|
|
2980
3027
|
return withFieldJsDoc(jsdocParts, line);
|
|
2981
3028
|
}
|
|
2982
3029
|
__name(renderField2, "renderField");
|
|
2983
|
-
function renderInputField2(field, modelsWithInput) {
|
|
3030
|
+
function renderInputField2(field, modelsWithInput, target) {
|
|
2984
3031
|
const opt = field.optional || field.default !== void 0 ? "?" : "";
|
|
2985
|
-
let typeStr = renderInputTsType(field.type, modelsWithInput);
|
|
3032
|
+
let typeStr = renderInputTsType(field.type, modelsWithInput, target);
|
|
2986
3033
|
if (field.nullable) typeStr += " | null";
|
|
2987
3034
|
const line = `${quoteKey(field.name)}${opt}: ${typeStr};`;
|
|
2988
3035
|
const jsdocParts = [];
|
|
@@ -3005,7 +3052,7 @@ function applyOutputCase(name, c) {
|
|
|
3005
3052
|
return camelToPascal2(name);
|
|
3006
3053
|
}
|
|
3007
3054
|
__name(applyOutputCase, "applyOutputCase");
|
|
3008
|
-
function generateOutputModel(model, modelsWithOutput) {
|
|
3055
|
+
function generateOutputModel(model, target, modelsWithOutput) {
|
|
3009
3056
|
const lines = [];
|
|
3010
3057
|
const outputCase = model.outputCase && model.outputCase !== "camel" ? model.outputCase : void 0;
|
|
3011
3058
|
const readFields = model.fields.filter((f) => f.visibility !== "writeonly");
|
|
@@ -3013,23 +3060,23 @@ function generateOutputModel(model, modelsWithOutput) {
|
|
|
3013
3060
|
const baseExt = model.bases?.[0] && modelsWithOutput.has(model.bases?.[0]) ? ` extends ${model.bases?.[0]}Output` : model.bases?.[0] ? ` extends ${model.bases?.[0]}` : "";
|
|
3014
3061
|
lines.push(`export interface ${model.name}Output${baseExt} {`);
|
|
3015
3062
|
for (const field of readFields) {
|
|
3016
|
-
lines.push(` ${renderOutputField(field, model.outputCase, modelsWithOutput)}`);
|
|
3063
|
+
lines.push(` ${renderOutputField(field, model.outputCase, modelsWithOutput, target)}`);
|
|
3017
3064
|
}
|
|
3018
3065
|
lines.push("}");
|
|
3019
3066
|
return lines;
|
|
3020
3067
|
}
|
|
3021
3068
|
lines.push(`export interface ${model.name}Output {`);
|
|
3022
3069
|
for (const field of readFields) {
|
|
3023
|
-
lines.push(` ${renderOutputField(field, outputCase, modelsWithOutput)}`);
|
|
3070
|
+
lines.push(` ${renderOutputField(field, outputCase, modelsWithOutput, target)}`);
|
|
3024
3071
|
}
|
|
3025
3072
|
lines.push("}");
|
|
3026
3073
|
return lines;
|
|
3027
3074
|
}
|
|
3028
3075
|
__name(generateOutputModel, "generateOutputModel");
|
|
3029
|
-
function renderOutputField(field, outputCase, modelsWithOutput) {
|
|
3076
|
+
function renderOutputField(field, outputCase, modelsWithOutput, target) {
|
|
3030
3077
|
const opt = field.optional || field.default !== void 0 ? "?" : "";
|
|
3031
3078
|
const key = applyOutputCase(field.name, outputCase);
|
|
3032
|
-
let typeStr = renderOutputTsType(field.type, modelsWithOutput);
|
|
3079
|
+
let typeStr = renderOutputTsType(field.type, modelsWithOutput, target);
|
|
3033
3080
|
if (field.nullable) typeStr += " | null";
|
|
3034
3081
|
const line = `${quoteKey(key)}${opt}: ${typeStr};`;
|
|
3035
3082
|
const jsdocParts = [];
|
|
@@ -3039,12 +3086,443 @@ function renderOutputField(field, outputCase, modelsWithOutput) {
|
|
|
3039
3086
|
}
|
|
3040
3087
|
__name(renderOutputField, "renderOutputField");
|
|
3041
3088
|
|
|
3089
|
+
// src/codegen-mcp.ts
|
|
3090
|
+
import { resolveModifiers as resolveModifiers3 } from "@contractkit/core";
|
|
3091
|
+
import { basename as basename3, dirname as dirname5, relative as relative5 } from "path";
|
|
3092
|
+
function mcpConfig(op) {
|
|
3093
|
+
return op.mcp && typeof op.mcp === "object" ? op.mcp : void 0;
|
|
3094
|
+
}
|
|
3095
|
+
__name(mcpConfig, "mcpConfig");
|
|
3096
|
+
function hasMcpOperations(root, includeInternal = false) {
|
|
3097
|
+
for (const route of root.routes) {
|
|
3098
|
+
for (const op of route.operations) {
|
|
3099
|
+
if (!op.mcp) continue;
|
|
3100
|
+
if (!includeInternal && resolveModifiers3(route, op).includes("internal")) continue;
|
|
3101
|
+
return true;
|
|
3102
|
+
}
|
|
3103
|
+
}
|
|
3104
|
+
return false;
|
|
3105
|
+
}
|
|
3106
|
+
__name(hasMcpOperations, "hasMcpOperations");
|
|
3107
|
+
function toSnake(s) {
|
|
3108
|
+
return s.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[\s\-.]+/g, "_").toLowerCase().replace(/[^a-z0-9_]/g, "").replace(/_+/g, "_").replace(/^_|_$/g, "");
|
|
3109
|
+
}
|
|
3110
|
+
__name(toSnake, "toSnake");
|
|
3111
|
+
function toPascal(s) {
|
|
3112
|
+
return s.split("_").filter(Boolean).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join("");
|
|
3113
|
+
}
|
|
3114
|
+
__name(toPascal, "toPascal");
|
|
3115
|
+
function inferToolName(method, path) {
|
|
3116
|
+
const parts = [
|
|
3117
|
+
method.toLowerCase()
|
|
3118
|
+
];
|
|
3119
|
+
for (const seg of path.split("/").filter(Boolean)) {
|
|
3120
|
+
if (seg.startsWith("{")) {
|
|
3121
|
+
parts.push("by", toSnake(seg.slice(1, -1)));
|
|
3122
|
+
} else {
|
|
3123
|
+
parts.push(toSnake(seg));
|
|
3124
|
+
}
|
|
3125
|
+
}
|
|
3126
|
+
return parts.filter(Boolean).join("_");
|
|
3127
|
+
}
|
|
3128
|
+
__name(inferToolName, "inferToolName");
|
|
3129
|
+
function deriveToolName(op, route) {
|
|
3130
|
+
const cfg = mcpConfig(op);
|
|
3131
|
+
if (cfg?.name) return cfg.name;
|
|
3132
|
+
if (op.sdk) return toSnake(op.sdk);
|
|
3133
|
+
if (op.name) return toSnake(op.name);
|
|
3134
|
+
return inferToolName(op.method, route.path);
|
|
3135
|
+
}
|
|
3136
|
+
__name(deriveToolName, "deriveToolName");
|
|
3137
|
+
function deriveToolClassName(toolName) {
|
|
3138
|
+
return `${toPascal(toolName)}McpTool`;
|
|
3139
|
+
}
|
|
3140
|
+
__name(deriveToolClassName, "deriveToolClassName");
|
|
3141
|
+
function buildArgsProps(route, op, modelsWithInput) {
|
|
3142
|
+
const props = [];
|
|
3143
|
+
if (route.params) {
|
|
3144
|
+
if (route.params.kind === "params") {
|
|
3145
|
+
for (const node of route.params.nodes) {
|
|
3146
|
+
props.push({
|
|
3147
|
+
key: node.name,
|
|
3148
|
+
expr: renderInputType(node.type, modelsWithInput),
|
|
3149
|
+
optional: false
|
|
3150
|
+
});
|
|
3151
|
+
}
|
|
3152
|
+
} else if (route.params.kind === "ref") {
|
|
3153
|
+
props.push({
|
|
3154
|
+
key: "params",
|
|
3155
|
+
expr: refSchema(route.params.name, modelsWithInput),
|
|
3156
|
+
optional: false
|
|
3157
|
+
});
|
|
3158
|
+
} else {
|
|
3159
|
+
props.push({
|
|
3160
|
+
key: "params",
|
|
3161
|
+
expr: renderInputType(route.params.node, modelsWithInput),
|
|
3162
|
+
optional: false
|
|
3163
|
+
});
|
|
3164
|
+
}
|
|
3165
|
+
}
|
|
3166
|
+
const bodies = op.request?.bodies ?? [];
|
|
3167
|
+
if (bodies.length === 1 && bodies[0].contentType === "multipart/form-data") {
|
|
3168
|
+
props.push({
|
|
3169
|
+
key: "multipartBody",
|
|
3170
|
+
expr: "z.unknown()",
|
|
3171
|
+
optional: false
|
|
3172
|
+
});
|
|
3173
|
+
} else if (bodies.length === 1) {
|
|
3174
|
+
props.push({
|
|
3175
|
+
key: "body",
|
|
3176
|
+
expr: renderInputType(bodies[0].bodyType, modelsWithInput),
|
|
3177
|
+
optional: false
|
|
3178
|
+
});
|
|
3179
|
+
} else if (bodies.length > 1) {
|
|
3180
|
+
props.push({
|
|
3181
|
+
key: "body",
|
|
3182
|
+
expr: "z.unknown()",
|
|
3183
|
+
optional: false
|
|
3184
|
+
});
|
|
3185
|
+
}
|
|
3186
|
+
if (op.query) props.push({
|
|
3187
|
+
key: "query",
|
|
3188
|
+
expr: paramSourceSchema(op.query, modelsWithInput),
|
|
3189
|
+
optional: true
|
|
3190
|
+
});
|
|
3191
|
+
if (op.headers) props.push({
|
|
3192
|
+
key: "headers",
|
|
3193
|
+
expr: paramSourceSchema(op.headers, modelsWithInput),
|
|
3194
|
+
optional: true
|
|
3195
|
+
});
|
|
3196
|
+
return props;
|
|
3197
|
+
}
|
|
3198
|
+
__name(buildArgsProps, "buildArgsProps");
|
|
3199
|
+
function refSchema(name, modelsWithInput) {
|
|
3200
|
+
return modelsWithInput?.has(name) ? `${name}Input` : name;
|
|
3201
|
+
}
|
|
3202
|
+
__name(refSchema, "refSchema");
|
|
3203
|
+
function paramSourceSchema(src, modelsWithInput) {
|
|
3204
|
+
if (src.kind === "ref") return refSchema(src.name, modelsWithInput);
|
|
3205
|
+
if (src.kind === "type") return renderInputType(src.node, modelsWithInput);
|
|
3206
|
+
const fields = src.nodes.map((n) => `${quoteKey(n.name)}: ${renderInputType(n.type, modelsWithInput)}`).join(", ");
|
|
3207
|
+
return `z.object({ ${fields} })`;
|
|
3208
|
+
}
|
|
3209
|
+
__name(paramSourceSchema, "paramSourceSchema");
|
|
3210
|
+
function argsSchemaExpr(props) {
|
|
3211
|
+
if (props.length === 0) return "z.object({})";
|
|
3212
|
+
const fields = props.map((p) => `${quoteKey(p.key)}: ${p.expr}${p.optional ? ".optional()" : ""}`).join(", ");
|
|
3213
|
+
return `z.object({ ${fields} })`;
|
|
3214
|
+
}
|
|
3215
|
+
__name(argsSchemaExpr, "argsSchemaExpr");
|
|
3216
|
+
function primaryResponse(op) {
|
|
3217
|
+
return op.responses.find((r) => r.bodyType) ?? op.responses[0];
|
|
3218
|
+
}
|
|
3219
|
+
__name(primaryResponse, "primaryResponse");
|
|
3220
|
+
function outputSchemaExpr(op) {
|
|
3221
|
+
const body = primaryResponse(op)?.bodyType;
|
|
3222
|
+
if (!body) return void 0;
|
|
3223
|
+
if (body.kind === "ref") return body.name;
|
|
3224
|
+
if (body.kind === "inlineObject") return renderType(body);
|
|
3225
|
+
return void 0;
|
|
3226
|
+
}
|
|
3227
|
+
__name(outputSchemaExpr, "outputSchemaExpr");
|
|
3228
|
+
var HINT_KEYS = [
|
|
3229
|
+
"readOnlyHint",
|
|
3230
|
+
"destructiveHint",
|
|
3231
|
+
"idempotentHint",
|
|
3232
|
+
"openWorldHint"
|
|
3233
|
+
];
|
|
3234
|
+
function annotationsExpr(cfg) {
|
|
3235
|
+
if (!cfg) return void 0;
|
|
3236
|
+
const parts = [];
|
|
3237
|
+
for (const key of HINT_KEYS) {
|
|
3238
|
+
const val = cfg[key];
|
|
3239
|
+
if (val !== void 0) parts.push(`${key}: ${val}`);
|
|
3240
|
+
}
|
|
3241
|
+
return parts.length > 0 ? `{ ${parts.join(", ")} }` : void 0;
|
|
3242
|
+
}
|
|
3243
|
+
__name(annotationsExpr, "annotationsExpr");
|
|
3244
|
+
function walkTypeRefs(type, ids, variant, modelsWithInput) {
|
|
3245
|
+
switch (type.kind) {
|
|
3246
|
+
case "ref":
|
|
3247
|
+
ids.add(variant === "input" ? refSchema(type.name, modelsWithInput) : type.name);
|
|
3248
|
+
break;
|
|
3249
|
+
case "array":
|
|
3250
|
+
walkTypeRefs(type.item, ids, variant, modelsWithInput);
|
|
3251
|
+
break;
|
|
3252
|
+
case "tuple":
|
|
3253
|
+
type.items.forEach((t) => walkTypeRefs(t, ids, variant, modelsWithInput));
|
|
3254
|
+
break;
|
|
3255
|
+
case "record":
|
|
3256
|
+
walkTypeRefs(type.key, ids, variant, modelsWithInput);
|
|
3257
|
+
walkTypeRefs(type.value, ids, variant, modelsWithInput);
|
|
3258
|
+
break;
|
|
3259
|
+
case "union":
|
|
3260
|
+
case "discriminatedUnion":
|
|
3261
|
+
case "intersection":
|
|
3262
|
+
type.members.forEach((t) => walkTypeRefs(t, ids, variant, modelsWithInput));
|
|
3263
|
+
break;
|
|
3264
|
+
case "inlineObject":
|
|
3265
|
+
type.fields.forEach((f) => walkTypeRefs(f.type, ids, variant, modelsWithInput));
|
|
3266
|
+
break;
|
|
3267
|
+
case "lazy":
|
|
3268
|
+
walkTypeRefs(type.inner, ids, variant, modelsWithInput);
|
|
3269
|
+
break;
|
|
3270
|
+
}
|
|
3271
|
+
}
|
|
3272
|
+
__name(walkTypeRefs, "walkTypeRefs");
|
|
3273
|
+
function walkSourceRefs(src, ids, modelsWithInput) {
|
|
3274
|
+
if (!src) return;
|
|
3275
|
+
if (src.kind === "ref") ids.add(refSchema(src.name, modelsWithInput));
|
|
3276
|
+
else if (src.kind === "params") src.nodes.forEach((n) => walkTypeRefs(n.type, ids, "input", modelsWithInput));
|
|
3277
|
+
else walkTypeRefs(src.node, ids, "input", modelsWithInput);
|
|
3278
|
+
}
|
|
3279
|
+
__name(walkSourceRefs, "walkSourceRefs");
|
|
3280
|
+
function collectSchemaIds(ops, modelsWithInput) {
|
|
3281
|
+
const ids = /* @__PURE__ */ new Set();
|
|
3282
|
+
for (const { route, op } of ops) {
|
|
3283
|
+
walkSourceRefs(route.params, ids, modelsWithInput);
|
|
3284
|
+
const bodies = op.request?.bodies ?? [];
|
|
3285
|
+
if (bodies.length === 1 && bodies[0].contentType !== "multipart/form-data") {
|
|
3286
|
+
walkTypeRefs(bodies[0].bodyType, ids, "input", modelsWithInput);
|
|
3287
|
+
}
|
|
3288
|
+
walkSourceRefs(op.query, ids, modelsWithInput);
|
|
3289
|
+
walkSourceRefs(op.headers, ids, modelsWithInput);
|
|
3290
|
+
const body = primaryResponse(op)?.bodyType;
|
|
3291
|
+
if (body && (body.kind === "ref" || body.kind === "inlineObject")) walkTypeRefs(body, ids, "read");
|
|
3292
|
+
}
|
|
3293
|
+
return ids;
|
|
3294
|
+
}
|
|
3295
|
+
__name(collectSchemaIds, "collectSchemaIds");
|
|
3296
|
+
function schemaImportLines(ids, options) {
|
|
3297
|
+
const lines = [];
|
|
3298
|
+
const { modelOutPaths, outPath } = options;
|
|
3299
|
+
if (ids.size === 0) return lines;
|
|
3300
|
+
if (modelOutPaths && outPath) {
|
|
3301
|
+
const byFile = /* @__PURE__ */ new Map();
|
|
3302
|
+
const unresolved = [];
|
|
3303
|
+
for (const id of ids) {
|
|
3304
|
+
const p = modelOutPaths.get(id);
|
|
3305
|
+
if (p) {
|
|
3306
|
+
const group = byFile.get(p) ?? [];
|
|
3307
|
+
group.push(id);
|
|
3308
|
+
byFile.set(p, group);
|
|
3309
|
+
} else {
|
|
3310
|
+
unresolved.push(id);
|
|
3311
|
+
}
|
|
3312
|
+
}
|
|
3313
|
+
const fromDir = dirname5(outPath);
|
|
3314
|
+
for (const [file, names] of byFile) {
|
|
3315
|
+
let rel = relative5(fromDir, file).replace(/\.ts$/, ".js");
|
|
3316
|
+
if (!rel.startsWith(".")) rel = "./" + rel;
|
|
3317
|
+
lines.push(`import { ${names.sort().join(", ")} } from '${rel}';`);
|
|
3318
|
+
}
|
|
3319
|
+
for (const id of unresolved.sort()) lines.push(`import { ${id} } from './${pascalToDotCase(id)}.js';`);
|
|
3320
|
+
} else {
|
|
3321
|
+
for (const id of [
|
|
3322
|
+
...ids
|
|
3323
|
+
].sort()) lines.push(`import { ${id} } from './${pascalToDotCase(id)}.js';`);
|
|
3324
|
+
}
|
|
3325
|
+
return lines;
|
|
3326
|
+
}
|
|
3327
|
+
__name(schemaImportLines, "schemaImportLines");
|
|
3328
|
+
function scalarHelperLines(body) {
|
|
3329
|
+
const lines = [];
|
|
3330
|
+
if (body.includes("_ZodBinary")) {
|
|
3331
|
+
lines.push(`const _ZodBinary = z.custom<Buffer>((val) => Buffer.isBuffer(val), { error: 'Must be binary data' });`);
|
|
3332
|
+
}
|
|
3333
|
+
if (body.includes("_ZodDatetime")) {
|
|
3334
|
+
lines.push(`const _ZodDatetime = z.preprocess((val) => typeof val === 'string' ? DateTime.fromISO(val) : val, z.custom<DateTime>((val) => val instanceof DateTime && val.isValid, { message: 'Must be in ISO 8601 format' }));`);
|
|
3335
|
+
}
|
|
3336
|
+
if (body.includes("_ZodInterval")) {
|
|
3337
|
+
lines.push(`const _ZodInterval = z.preprocess((val) => typeof val === 'string' ? Interval.fromISO(val) : val, z.custom<Interval>((val) => val instanceof Interval && val.isValid, { message: 'Must be an ISO 8601 interval' })).transform(val => val.toISO()!);`);
|
|
3338
|
+
}
|
|
3339
|
+
if (body.includes("_ZodJson")) {
|
|
3340
|
+
lines.push(`type _JsonValue = string | number | boolean | null | _JsonValue[] | { [key: string]: _JsonValue };`);
|
|
3341
|
+
lines.push(`const _ZodJson: z.ZodType<_JsonValue> = z.lazy(() => z.union([z.string(), z.number(), z.boolean(), z.null(), z.array(_ZodJson), z.record(z.string(), _ZodJson)]));`);
|
|
3342
|
+
}
|
|
3343
|
+
return lines;
|
|
3344
|
+
}
|
|
3345
|
+
__name(scalarHelperLines, "scalarHelperLines");
|
|
3346
|
+
function planTools(root, includeInternal) {
|
|
3347
|
+
const plans = [];
|
|
3348
|
+
for (const route of root.routes) {
|
|
3349
|
+
for (const op of route.operations) {
|
|
3350
|
+
if (!op.mcp) continue;
|
|
3351
|
+
if (!includeInternal && resolveModifiers3(route, op).includes("internal")) continue;
|
|
3352
|
+
const toolName = deriveToolName(op, route);
|
|
3353
|
+
const className = deriveToolClassName(toolName);
|
|
3354
|
+
plans.push({
|
|
3355
|
+
route,
|
|
3356
|
+
op,
|
|
3357
|
+
toolName,
|
|
3358
|
+
className,
|
|
3359
|
+
argsConstName: `${toPascal(toolName)}Args`
|
|
3360
|
+
});
|
|
3361
|
+
}
|
|
3362
|
+
}
|
|
3363
|
+
return plans;
|
|
3364
|
+
}
|
|
3365
|
+
__name(planTools, "planTools");
|
|
3366
|
+
function renderToolClass(plan, file, options) {
|
|
3367
|
+
const { route, op, toolName, className, argsConstName } = plan;
|
|
3368
|
+
const cfg = mcpConfig(op);
|
|
3369
|
+
const lines = [];
|
|
3370
|
+
const relFile = options.outPath ? relative5(dirname5(options.outPath), file) : file;
|
|
3371
|
+
lines.push("/**");
|
|
3372
|
+
lines.push(` * from [${basename3(file)}](file://./${relFile}#L${op.loc.line})`);
|
|
3373
|
+
lines.push(" */");
|
|
3374
|
+
lines.push("@Injectable()");
|
|
3375
|
+
lines.push(`export class ${className} implements McpToolHandler {`);
|
|
3376
|
+
lines.push(" readonly definition: Tool = {");
|
|
3377
|
+
lines.push(` name: '${escapeSingleQuoted(toolName)}',`);
|
|
3378
|
+
if (cfg?.title) lines.push(` title: '${escapeSingleQuoted(cfg.title)}',`);
|
|
3379
|
+
const desc = cfg?.description ?? op.description ?? route.description;
|
|
3380
|
+
if (desc) lines.push(` description: '${escapeSingleQuoted(desc)}',`);
|
|
3381
|
+
lines.push(` inputSchema: z.toJSONSchema(${argsConstName}, { unrepresentable: 'any' }) as Tool['inputSchema'],`);
|
|
3382
|
+
const outExpr = outputSchemaExpr(op);
|
|
3383
|
+
if (outExpr) lines.push(` outputSchema: z.toJSONSchema(${outExpr}, { unrepresentable: 'any' }) as Tool['outputSchema'],`);
|
|
3384
|
+
const annotations = annotationsExpr(cfg);
|
|
3385
|
+
if (annotations) lines.push(` annotations: ${annotations},`);
|
|
3386
|
+
lines.push(" };");
|
|
3387
|
+
lines.push("");
|
|
3388
|
+
const service = inferService(op, route, file);
|
|
3389
|
+
lines.push(` constructor(private readonly service: ${service.className}) {}`);
|
|
3390
|
+
lines.push("");
|
|
3391
|
+
const props = buildArgsProps(route, op, options.modelsWithInput);
|
|
3392
|
+
const destructure = props.map((p) => p.key);
|
|
3393
|
+
const callArgs = buildArgs(route, op);
|
|
3394
|
+
const isVoid = !primaryResponse(op)?.bodyType;
|
|
3395
|
+
const structured = !!outExpr;
|
|
3396
|
+
lines.push(" async handle(args: Record<string, unknown>, _context: McpToolContext): Promise<CallToolResult> {");
|
|
3397
|
+
if (destructure.length > 0) {
|
|
3398
|
+
lines.push(` const { ${destructure.join(", ")} } = await parseAndValidate(args, ${argsConstName});`);
|
|
3399
|
+
}
|
|
3400
|
+
if (isVoid) {
|
|
3401
|
+
lines.push(` await this.service.${service.methodName}(${callArgs});`);
|
|
3402
|
+
lines.push(` return { content: [{ type: 'text', text: 'OK' }] };`);
|
|
3403
|
+
} else {
|
|
3404
|
+
lines.push(` const result = await this.service.${service.methodName}(${callArgs});`);
|
|
3405
|
+
if (structured) {
|
|
3406
|
+
lines.push(` return { content: [{ type: 'text', text: JSON.stringify(result) }], structuredContent: result };`);
|
|
3407
|
+
} else {
|
|
3408
|
+
lines.push(` return { content: [{ type: 'text', text: JSON.stringify(result) }] };`);
|
|
3409
|
+
}
|
|
3410
|
+
}
|
|
3411
|
+
lines.push(" }");
|
|
3412
|
+
lines.push("}");
|
|
3413
|
+
return lines;
|
|
3414
|
+
}
|
|
3415
|
+
__name(renderToolClass, "renderToolClass");
|
|
3416
|
+
function deriveMcpRegisterFnName(file) {
|
|
3417
|
+
return `register${deriveBaseName(file)}McpTools`;
|
|
3418
|
+
}
|
|
3419
|
+
__name(deriveMcpRegisterFnName, "deriveMcpRegisterFnName");
|
|
3420
|
+
function generateMcpFile(root, options = {}) {
|
|
3421
|
+
const includeInternal = options.includeInternal ?? false;
|
|
3422
|
+
const plans = planTools(root, includeInternal);
|
|
3423
|
+
const argsConsts = plans.map((p) => `const ${p.argsConstName} = ${argsSchemaExpr(buildArgsProps(p.route, p.op, options.modelsWithInput))};`);
|
|
3424
|
+
const classes = plans.map((p) => renderToolClass(p, root.file, options).join("\n"));
|
|
3425
|
+
const registerFn = [];
|
|
3426
|
+
registerFn.push(`/** Add this file's tools to the shared catalog. */`);
|
|
3427
|
+
registerFn.push(`export function ${deriveMcpRegisterFnName(root.file)}(map: McpToolHandlerMap, container: Container): void {`);
|
|
3428
|
+
for (const p of plans) registerFn.push(` map.set('${escapeSingleQuoted(p.toolName)}', container.get(${p.className}));`);
|
|
3429
|
+
registerFn.push("}");
|
|
3430
|
+
const bodyCore = [
|
|
3431
|
+
argsConsts.join("\n"),
|
|
3432
|
+
classes.join("\n\n"),
|
|
3433
|
+
registerFn.join("\n")
|
|
3434
|
+
].filter(Boolean).join("\n\n");
|
|
3435
|
+
const helperConsts = scalarHelperLines(bodyCore);
|
|
3436
|
+
const bodyWithHelpers = [
|
|
3437
|
+
helperConsts.join("\n"),
|
|
3438
|
+
bodyCore
|
|
3439
|
+
].filter(Boolean).join("\n\n");
|
|
3440
|
+
const needsParseAndValidate = plans.some((p) => buildArgsProps(p.route, p.op, options.modelsWithInput).length > 0);
|
|
3441
|
+
const imports = [];
|
|
3442
|
+
imports.push(`import { Injectable, type Container } from 'injectkit';`);
|
|
3443
|
+
imports.push(`import { z } from 'zod';`);
|
|
3444
|
+
const luxon = [];
|
|
3445
|
+
if (/\bDateTime\b/.test(bodyWithHelpers)) luxon.push("DateTime");
|
|
3446
|
+
if (/\bInterval\b/.test(bodyWithHelpers)) luxon.push("Interval");
|
|
3447
|
+
if (/\bDuration\b/.test(bodyWithHelpers)) luxon.push("Duration");
|
|
3448
|
+
if (luxon.length > 0) imports.push(`import { ${luxon.join(", ")} } from 'luxon';`);
|
|
3449
|
+
imports.push(`import type { CallToolResult, Tool } from '@modelcontextprotocol/sdk/types.js';`);
|
|
3450
|
+
imports.push(`import type { McpToolHandler, McpToolHandlerMap, McpToolContext } from '@maroonedsoftware/mcp';`);
|
|
3451
|
+
if (needsParseAndValidate) imports.push(`import { parseAndValidate } from '@maroonedsoftware/zod';`);
|
|
3452
|
+
const serviceModules = /* @__PURE__ */ new Map();
|
|
3453
|
+
for (const p of plans) {
|
|
3454
|
+
const svc = inferService(p.op, p.route, root.file).className;
|
|
3455
|
+
if (!serviceModules.has(svc)) {
|
|
3456
|
+
serviceModules.set(svc, root.services?.[svc] ?? root.meta[svc] ?? deriveModulePath(svc, options.servicePathTemplate));
|
|
3457
|
+
}
|
|
3458
|
+
}
|
|
3459
|
+
for (const [svc, mod] of [
|
|
3460
|
+
...serviceModules.entries()
|
|
3461
|
+
].sort(([a], [b]) => a.localeCompare(b))) {
|
|
3462
|
+
imports.push(`import { ${svc} } from '${mod}';`);
|
|
3463
|
+
}
|
|
3464
|
+
imports.push(...schemaImportLines(collectSchemaIds(plans, options.modelsWithInput), options));
|
|
3465
|
+
const relFile = options.outPath ? relative5(dirname5(options.outPath), root.file) : root.file;
|
|
3466
|
+
const header = `// Auto-generated MCP tools
|
|
3467
|
+
// generated from [${basename3(root.file)}](file://./${relFile})`;
|
|
3468
|
+
return `${header}
|
|
3469
|
+
${imports.join("\n")}
|
|
3470
|
+
|
|
3471
|
+
${bodyWithHelpers}
|
|
3472
|
+
`;
|
|
3473
|
+
}
|
|
3474
|
+
__name(generateMcpFile, "generateMcpFile");
|
|
3475
|
+
function generateMcpAggregator(entries) {
|
|
3476
|
+
const sorted = [
|
|
3477
|
+
...entries
|
|
3478
|
+
].sort((a, b) => a.registerFn.localeCompare(b.registerFn));
|
|
3479
|
+
const lines = [];
|
|
3480
|
+
lines.push(`import { type Container } from 'injectkit';`);
|
|
3481
|
+
lines.push(`import { McpToolHandlerMap } from '@maroonedsoftware/mcp';`);
|
|
3482
|
+
for (const e of sorted) lines.push(`import { ${e.registerFn} } from '${e.importPath}';`);
|
|
3483
|
+
lines.push("");
|
|
3484
|
+
lines.push("/** Build + register the MCP tool catalog. Call once at startup. */");
|
|
3485
|
+
lines.push("export function registerMcpTools(container: Container): McpToolHandlerMap {");
|
|
3486
|
+
lines.push(" const map = new McpToolHandlerMap();");
|
|
3487
|
+
for (const e of sorted) lines.push(` ${e.registerFn}(map, container);`);
|
|
3488
|
+
lines.push(" container.register(McpToolHandlerMap, { useValue: map });");
|
|
3489
|
+
lines.push(" return map;");
|
|
3490
|
+
lines.push("}");
|
|
3491
|
+
return lines.join("\n") + "\n";
|
|
3492
|
+
}
|
|
3493
|
+
__name(generateMcpAggregator, "generateMcpAggregator");
|
|
3494
|
+
function generateMcpRouter(options = {}) {
|
|
3495
|
+
const path = options.path ?? "/mcp";
|
|
3496
|
+
return `import { ServerKitRouter, requireSignature } from '@maroonedsoftware/koa';
|
|
3497
|
+
import { McpDispatcher, createMcpRequestContext, MCP_AUTH_POLICY } from '@maroonedsoftware/mcp';
|
|
3498
|
+
|
|
3499
|
+
/** Mount the MCP endpoint onto a ServerKit router. Call \`registerMcpTools(container)\` at startup. */
|
|
3500
|
+
export function mountMcp(router: ReturnType<typeof ServerKitRouter>): void {
|
|
3501
|
+
router.post('${path}', requireSignature('mcp', { policy: MCP_AUTH_POLICY }), async (ctx) => {
|
|
3502
|
+
const dispatcher = ctx.container.get(McpDispatcher);
|
|
3503
|
+
const context = createMcpRequestContext({ requestId: ctx.requestId, logger: ctx.logger });
|
|
3504
|
+
if (dispatcher.sessionMode === 'stateful') {
|
|
3505
|
+
ctx.respond = false;
|
|
3506
|
+
await dispatcher.dispatchStateful(
|
|
3507
|
+
{ req: ctx.req, res: ctx.res, body: ctx.request.body, sessionId: ctx.get('mcp-session-id') },
|
|
3508
|
+
context,
|
|
3509
|
+
);
|
|
3510
|
+
} else {
|
|
3511
|
+
const response = await dispatcher.dispatch(JSON.parse(ctx.rawBody), context);
|
|
3512
|
+
if (response) ctx.body = response;
|
|
3513
|
+
}
|
|
3514
|
+
});
|
|
3515
|
+
}
|
|
3516
|
+
`;
|
|
3517
|
+
}
|
|
3518
|
+
__name(generateMcpRouter, "generateMcpRouter");
|
|
3519
|
+
|
|
3042
3520
|
// src/path-utils.ts
|
|
3043
|
-
import { resolve, join, relative as
|
|
3521
|
+
import { resolve, join, relative as relative6, dirname as dirname6, isAbsolute } from "path";
|
|
3044
3522
|
import { collectTypeRefs as collectTypeRefs2, collectPublicTypeNames } from "@contractkit/core";
|
|
3045
3523
|
var TEMPLATE_VAR_RE = /\{\w+\}/;
|
|
3046
3524
|
function assertWithinBase(baseOutDir, outPath) {
|
|
3047
|
-
const rel =
|
|
3525
|
+
const rel = relative6(resolve(baseOutDir), resolve(outPath));
|
|
3048
3526
|
if (rel === "" || rel.startsWith("..") || isAbsolute(rel)) {
|
|
3049
3527
|
throw new Error(`Refusing to emit outside output directory: resolved path "${outPath}" escapes "${baseOutDir}" (check options { keys } values used in output path templates)`);
|
|
3050
3528
|
}
|
|
@@ -3062,7 +3540,7 @@ function includesFilename(p) {
|
|
|
3062
3540
|
__name(includesFilename, "includesFilename");
|
|
3063
3541
|
function commonDir(files, rootDir) {
|
|
3064
3542
|
if (files.length === 0) return resolve(rootDir);
|
|
3065
|
-
const parts = files.map((f) =>
|
|
3543
|
+
const parts = files.map((f) => dirname6(f).split("/"));
|
|
3066
3544
|
const first = parts[0];
|
|
3067
3545
|
let depth = first.length;
|
|
3068
3546
|
for (const p of parts) {
|
|
@@ -3078,7 +3556,7 @@ function commonDir(files, rootDir) {
|
|
|
3078
3556
|
__name(commonDir, "commonDir");
|
|
3079
3557
|
function computeOpOutPath(filePath, baseDir, output, defaultSuffix, commonRoot, meta = {}) {
|
|
3080
3558
|
const baseName = filePath.split("/").pop();
|
|
3081
|
-
const relDir =
|
|
3559
|
+
const relDir = relative6(commonRoot, dirname6(filePath));
|
|
3082
3560
|
const filename = baseName.replace(/\.ck$/, "");
|
|
3083
3561
|
const defaultName = `${filename}${defaultSuffix}`;
|
|
3084
3562
|
const baseOutDir = resolve(baseDir);
|
|
@@ -3108,7 +3586,7 @@ function computeSdkOutPath(filePath, rootDir, clientOutput, commonRoot, meta = {
|
|
|
3108
3586
|
const baseName = filePath.split("/").pop();
|
|
3109
3587
|
const defaultOutName = baseName.replace(/\.ck$/, ".client.ts");
|
|
3110
3588
|
const baseOutDir = resolve(rootDir);
|
|
3111
|
-
const relDir =
|
|
3589
|
+
const relDir = relative6(commonRoot, dirname6(filePath));
|
|
3112
3590
|
const filename = baseName.replace(/\.ck$/, "");
|
|
3113
3591
|
if (clientOutput && TEMPLATE_VAR_RE.test(clientOutput)) {
|
|
3114
3592
|
const resolved = resolveTemplate(clientOutput, {
|
|
@@ -3160,7 +3638,7 @@ function computeSdkTypeOutPath(filePath, rootDir, typeOutput, commonRoot, meta =
|
|
|
3160
3638
|
const baseName = filePath.split("/").pop();
|
|
3161
3639
|
const defaultOutName = baseName.replace(/\.ck$/, ".ts");
|
|
3162
3640
|
const baseOutDir = resolve(rootDir);
|
|
3163
|
-
const relDir =
|
|
3641
|
+
const relDir = relative6(commonRoot, dirname6(filePath));
|
|
3164
3642
|
const filename = baseName.replace(/\.ck$/, "");
|
|
3165
3643
|
if (TEMPLATE_VAR_RE.test(typeOutput)) {
|
|
3166
3644
|
const resolved = resolveTemplate(typeOutput, {
|
|
@@ -3179,7 +3657,7 @@ __name(computeSdkTypeOutPath, "computeSdkTypeOutPath");
|
|
|
3179
3657
|
function generateBarrelFiles(contractPaths) {
|
|
3180
3658
|
const byDir = /* @__PURE__ */ new Map();
|
|
3181
3659
|
for (const outPath of contractPaths) {
|
|
3182
|
-
const dir =
|
|
3660
|
+
const dir = dirname6(outPath);
|
|
3183
3661
|
const group = byDir.get(dir) ?? [];
|
|
3184
3662
|
group.push(outPath);
|
|
3185
3663
|
byDir.set(dir, group);
|
|
@@ -3273,6 +3751,7 @@ async function runTypescriptCodegen(inputs, ctx, config, rootDir) {
|
|
|
3273
3751
|
if (config.sdk) collectSdkOutput(config.sdk, rootDir, inputs, units, globalFiles);
|
|
3274
3752
|
if (config.zod) collectZodOutput(config.zod, rootDir, inputs, units);
|
|
3275
3753
|
if (config.types) collectTypesOutput(config.types, rootDir, inputs, units);
|
|
3754
|
+
if (config.mcp) collectMcpOutput(config.mcp, config, rootDir, inputs, units, globalFiles);
|
|
3276
3755
|
const result = runIncrementalCodegen({
|
|
3277
3756
|
codegenVersion: TYPESCRIPT_CODEGEN_VERSION,
|
|
3278
3757
|
prevManifest,
|
|
@@ -3424,7 +3903,9 @@ function collectServerOutput(config, rootDir, inputs, units) {
|
|
|
3424
3903
|
modelOutPaths: serverModelOutPaths,
|
|
3425
3904
|
currentOutPath: typeOutPath,
|
|
3426
3905
|
modelsWithInput,
|
|
3427
|
-
modelsWithOutput
|
|
3906
|
+
modelsWithOutput,
|
|
3907
|
+
// These types are consumed by Koa handlers, so `binary` is a Buffer, not a Blob.
|
|
3908
|
+
target: "server"
|
|
3428
3909
|
};
|
|
3429
3910
|
const content = config.zod ? generateContract(ast, renderCtx) : generatePlainTypes(ast, renderCtx);
|
|
3430
3911
|
return [
|
|
@@ -3479,7 +3960,7 @@ function collectSdkOutput(config, rootDir, inputs, units, globalFiles) {
|
|
|
3479
3960
|
const sdkEntryPath = sdkOutput ? join2(sdkBase, TEMPLATE_VAR_RE.test(sdkOutput) ? resolveTemplate(sdkOutput, {
|
|
3480
3961
|
name: sdkName ?? "sdk"
|
|
3481
3962
|
}) : sdkOutput) : join2(sdkBase, "sdk.ts");
|
|
3482
|
-
const sdkOptionsPath = join2(
|
|
3963
|
+
const sdkOptionsPath = join2(dirname7(sdkEntryPath), "sdk-options.ts");
|
|
3483
3964
|
const subConfigKey = stableSubConfig(config);
|
|
3484
3965
|
const modelsWithInput = inputs.modelsWithInput;
|
|
3485
3966
|
const modelsWithOutput = inputs.modelsWithOutput;
|
|
@@ -3538,7 +4019,7 @@ function collectSdkOutput(config, rootDir, inputs, units, globalFiles) {
|
|
|
3538
4019
|
modelsWithOutput
|
|
3539
4020
|
});
|
|
3540
4021
|
} else {
|
|
3541
|
-
let rel =
|
|
4022
|
+
let rel = relative7(dirname7(typeOutPath), sdkOptionsPath).replace(/\.ts$/, ".js");
|
|
3542
4023
|
if (!rel.startsWith(".")) rel = "./" + rel;
|
|
3543
4024
|
content = generatePlainTypes(ast, {
|
|
3544
4025
|
modelOutPaths: sdkModelOutPaths,
|
|
@@ -3679,12 +4160,12 @@ function collectSdkOutput(config, rootDir, inputs, units, globalFiles) {
|
|
|
3679
4160
|
const hasAnything = sdkClientInfos.length > 0 || areaBuckets.size > 0;
|
|
3680
4161
|
const areaClientOutPaths = /* @__PURE__ */ new Map();
|
|
3681
4162
|
if (hasAnything) {
|
|
3682
|
-
const sdkEntryDir =
|
|
3683
|
-
const sdkOptionsRel =
|
|
4163
|
+
const sdkEntryDir = dirname7(sdkEntryPath);
|
|
4164
|
+
const sdkOptionsRel = relative7(sdkEntryDir, sdkOptionsPath).replace(/\.ts$/, ".js");
|
|
3684
4165
|
const sdkOptionsImportPath = sdkOptionsRel.startsWith(".") ? sdkOptionsRel : "./" + sdkOptionsRel;
|
|
3685
4166
|
const sdkClassName = sdkName ? sdkName.split(/[-._\s]+/).map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join("") + "Sdk" : "Sdk";
|
|
3686
4167
|
const toClientImport = /* @__PURE__ */ __name((sourceDir, info) => {
|
|
3687
|
-
let rel =
|
|
4168
|
+
let rel = relative7(sourceDir, info.outPath).replace(/\.ts$/, ".js");
|
|
3688
4169
|
if (!rel.startsWith(".")) rel = "./" + rel;
|
|
3689
4170
|
return {
|
|
3690
4171
|
className: info.className,
|
|
@@ -3713,7 +4194,7 @@ function collectSdkOutput(config, rootDir, inputs, units, globalFiles) {
|
|
|
3713
4194
|
});
|
|
3714
4195
|
const subareaClients = bucket.leaves.sort((a, b) => a.subarea.localeCompare(b.subarea)).map((l) => ({
|
|
3715
4196
|
propertyName: deriveSubareaPropertyName(l.subarea),
|
|
3716
|
-
client: toClientImport(
|
|
4197
|
+
client: toClientImport(dirname7(areaClientOutPath), {
|
|
3717
4198
|
outPath: l.outPath,
|
|
3718
4199
|
className: deriveSubareaClientClassName(area, l.subarea),
|
|
3719
4200
|
propertyName: deriveSubareaPropertyName(l.subarea)
|
|
@@ -3784,23 +4265,23 @@ function collectSdkOutput(config, rootDir, inputs, units, globalFiles) {
|
|
|
3784
4265
|
})
|
|
3785
4266
|
});
|
|
3786
4267
|
}
|
|
3787
|
-
const sdkSrcDir =
|
|
4268
|
+
const sdkSrcDir = dirname7(sdkEntryPath);
|
|
3788
4269
|
const sdkTypeBarrels = generateBarrelFiles(sdkTypePaths);
|
|
3789
4270
|
for (const barrel of sdkTypeBarrels) globalFiles.push({
|
|
3790
4271
|
relativePath: barrel.outPath,
|
|
3791
4272
|
content: barrel.content
|
|
3792
4273
|
});
|
|
3793
4274
|
const rootExports = [
|
|
3794
|
-
`export * from './${
|
|
4275
|
+
`export * from './${basename4(sdkOptionsPath).replace(/\.ts$/, ".js")}';`
|
|
3795
4276
|
];
|
|
3796
|
-
if (hasAnything) rootExports.push(`export * from './${
|
|
4277
|
+
if (hasAnything) rootExports.push(`export * from './${basename4(sdkEntryPath).replace(/\.ts$/, ".js")}';`);
|
|
3797
4278
|
for (const c of sdkClientInfos) {
|
|
3798
|
-
let rel =
|
|
4279
|
+
let rel = relative7(sdkSrcDir, c.outPath).replace(/\.ts$/, ".js");
|
|
3799
4280
|
if (!rel.startsWith(".")) rel = "./" + rel;
|
|
3800
4281
|
rootExports.push(`export * from '${rel}';`);
|
|
3801
4282
|
}
|
|
3802
4283
|
for (const barrel of sdkTypeBarrels) {
|
|
3803
|
-
let rel =
|
|
4284
|
+
let rel = relative7(sdkSrcDir, barrel.outPath).replace(/\.ts$/, ".js");
|
|
3804
4285
|
if (!rel.startsWith(".")) rel = "./" + rel;
|
|
3805
4286
|
rootExports.push(`export * from '${rel}';`);
|
|
3806
4287
|
}
|
|
@@ -3936,7 +4417,8 @@ function collectTypesOutput(config, rootDir, inputs, units) {
|
|
|
3936
4417
|
modelOutPaths,
|
|
3937
4418
|
currentOutPath: outPath,
|
|
3938
4419
|
modelsWithInput,
|
|
3939
|
-
modelsWithOutput
|
|
4420
|
+
modelsWithOutput,
|
|
4421
|
+
target: config.target
|
|
3940
4422
|
})
|
|
3941
4423
|
}
|
|
3942
4424
|
], "render")
|
|
@@ -3944,6 +4426,114 @@ function collectTypesOutput(config, rootDir, inputs, units) {
|
|
|
3944
4426
|
}
|
|
3945
4427
|
}
|
|
3946
4428
|
__name(collectTypesOutput, "collectTypesOutput");
|
|
4429
|
+
function resolveMcpModelOutPaths(config, rootDir, contractRoots, commonRoot, modelsWithInput, modelsWithOutput) {
|
|
4430
|
+
const map = /* @__PURE__ */ new Map();
|
|
4431
|
+
let base;
|
|
4432
|
+
let template;
|
|
4433
|
+
let suffix;
|
|
4434
|
+
if (config.mcp?.output?.types) {
|
|
4435
|
+
base = resolve2(rootDir, config.mcp.baseDir ?? ".");
|
|
4436
|
+
template = config.mcp.output.types;
|
|
4437
|
+
suffix = ".ts";
|
|
4438
|
+
} else if (config.server?.zod && config.server.output?.types) {
|
|
4439
|
+
base = resolve2(rootDir, config.server.baseDir ?? ".");
|
|
4440
|
+
template = config.server.output.types;
|
|
4441
|
+
suffix = ".ts";
|
|
4442
|
+
} else if (config.zod) {
|
|
4443
|
+
base = resolve2(rootDir, config.zod.baseDir ?? ".");
|
|
4444
|
+
template = config.zod.output;
|
|
4445
|
+
suffix = ".schema.ts";
|
|
4446
|
+
} else {
|
|
4447
|
+
return map;
|
|
4448
|
+
}
|
|
4449
|
+
for (const ast of contractRoots) {
|
|
4450
|
+
const outPath = computeContractOutPath(ast.file, base, template, suffix, commonRoot, ast.meta);
|
|
4451
|
+
for (const model of ast.models) {
|
|
4452
|
+
map.set(model.name, outPath);
|
|
4453
|
+
if (modelsWithInput.has(model.name)) map.set(`${model.name}Input`, outPath);
|
|
4454
|
+
if (modelsWithOutput.has(model.name)) map.set(`${model.name}Output`, outPath);
|
|
4455
|
+
}
|
|
4456
|
+
}
|
|
4457
|
+
return map;
|
|
4458
|
+
}
|
|
4459
|
+
__name(resolveMcpModelOutPaths, "resolveMcpModelOutPaths");
|
|
4460
|
+
function collectMcpOutput(config, fullConfig, rootDir, inputs, units, globalFiles) {
|
|
4461
|
+
const mcpBase = resolve2(rootDir, config.baseDir ?? ".");
|
|
4462
|
+
const modelsWithInput = inputs.modelsWithInput;
|
|
4463
|
+
const modelsWithOutput = inputs.modelsWithOutput;
|
|
4464
|
+
const modelMap = buildModelMap(inputs.contractRoots);
|
|
4465
|
+
const allFiles = [
|
|
4466
|
+
...inputs.contractRoots.map((r) => r.file),
|
|
4467
|
+
...inputs.opRoots.map((r) => r.file)
|
|
4468
|
+
];
|
|
4469
|
+
const commonRoot = commonDir(allFiles, rootDir);
|
|
4470
|
+
const subConfigKey = stableSubConfig(config);
|
|
4471
|
+
const includeInternal = config.includeInternal ?? false;
|
|
4472
|
+
const modelOutPaths = resolveMcpModelOutPaths(fullConfig, rootDir, inputs.contractRoots, commonRoot, modelsWithInput, modelsWithOutput);
|
|
4473
|
+
const entries = [];
|
|
4474
|
+
for (const ast of inputs.opRoots) {
|
|
4475
|
+
if (!hasMcpOperations(ast, includeInternal)) continue;
|
|
4476
|
+
const outPath = computeOpOutPath(ast.file, mcpBase, config.output?.tools, ".mcp.ts", commonRoot, ast.meta);
|
|
4477
|
+
const refs = collectOpRootRefs(ast, modelMap);
|
|
4478
|
+
const fingerprint = hashFingerprint({
|
|
4479
|
+
kind: "mcp-tools",
|
|
4480
|
+
v: TYPESCRIPT_CODEGEN_VERSION,
|
|
4481
|
+
outPath,
|
|
4482
|
+
root: ast,
|
|
4483
|
+
outPathSlice: sliceOutPathMap(refs, modelOutPaths, modelsWithInput, modelsWithOutput),
|
|
4484
|
+
modelsWithInput: sliceModelSet(refs, /* @__PURE__ */ new Set(), modelsWithInput),
|
|
4485
|
+
modelsWithOutput: sliceModelSet(refs, /* @__PURE__ */ new Set(), modelsWithOutput),
|
|
4486
|
+
servicePathTemplate: config.servicePathTemplate ?? null,
|
|
4487
|
+
includeInternal,
|
|
4488
|
+
sub: subConfigKey
|
|
4489
|
+
});
|
|
4490
|
+
units.push({
|
|
4491
|
+
key: `mcp-tools::${outPath}`,
|
|
4492
|
+
fingerprint,
|
|
4493
|
+
render: /* @__PURE__ */ __name(() => [
|
|
4494
|
+
{
|
|
4495
|
+
relativePath: outPath,
|
|
4496
|
+
content: generateMcpFile(ast, {
|
|
4497
|
+
outPath,
|
|
4498
|
+
modelOutPaths,
|
|
4499
|
+
modelsWithInput,
|
|
4500
|
+
modelsWithOutput,
|
|
4501
|
+
servicePathTemplate: config.servicePathTemplate,
|
|
4502
|
+
includeInternal
|
|
4503
|
+
})
|
|
4504
|
+
}
|
|
4505
|
+
], "render")
|
|
4506
|
+
});
|
|
4507
|
+
entries.push({
|
|
4508
|
+
outPath,
|
|
4509
|
+
registerFn: deriveMcpRegisterFnName(ast.file)
|
|
4510
|
+
});
|
|
4511
|
+
}
|
|
4512
|
+
if (entries.length === 0) return;
|
|
4513
|
+
const indexPath = join2(mcpBase, config.output?.index ?? "mcp.tools.ts");
|
|
4514
|
+
const aggregatorEntries = entries.map((e) => {
|
|
4515
|
+
let rel = relative7(dirname7(indexPath), e.outPath).replace(/\.ts$/, ".js");
|
|
4516
|
+
if (!rel.startsWith(".")) rel = "./" + rel;
|
|
4517
|
+
return {
|
|
4518
|
+
registerFn: e.registerFn,
|
|
4519
|
+
importPath: rel
|
|
4520
|
+
};
|
|
4521
|
+
}).sort((a, b) => a.registerFn.localeCompare(b.registerFn));
|
|
4522
|
+
globalFiles.push({
|
|
4523
|
+
relativePath: indexPath,
|
|
4524
|
+
content: generateMcpAggregator(aggregatorEntries)
|
|
4525
|
+
});
|
|
4526
|
+
if (config.emitRouter !== false) {
|
|
4527
|
+
const routerPath = join2(mcpBase, config.output?.router ?? "mcp.router.ts");
|
|
4528
|
+
globalFiles.push({
|
|
4529
|
+
relativePath: routerPath,
|
|
4530
|
+
content: generateMcpRouter({
|
|
4531
|
+
path: config.path
|
|
4532
|
+
})
|
|
4533
|
+
});
|
|
4534
|
+
}
|
|
4535
|
+
}
|
|
4536
|
+
__name(collectMcpOutput, "collectMcpOutput");
|
|
3947
4537
|
function readManifest(manifestPath) {
|
|
3948
4538
|
if (!existsSync(manifestPath)) return emptyIncrementalManifest(TYPESCRIPT_CODEGEN_VERSION);
|
|
3949
4539
|
try {
|
|
@@ -3955,7 +4545,7 @@ function readManifest(manifestPath) {
|
|
|
3955
4545
|
__name(readManifest, "readManifest");
|
|
3956
4546
|
function writeManifest(manifestPath, manifest) {
|
|
3957
4547
|
try {
|
|
3958
|
-
mkdirSync(
|
|
4548
|
+
mkdirSync(dirname7(manifestPath), {
|
|
3959
4549
|
recursive: true
|
|
3960
4550
|
});
|
|
3961
4551
|
writeFileSync(manifestPath, serializeIncrementalManifest(manifest), "utf-8");
|
|
@@ -3971,7 +4561,7 @@ function deleteStalePaths(absPaths) {
|
|
|
3971
4561
|
rmSync(abs, {
|
|
3972
4562
|
force: true
|
|
3973
4563
|
});
|
|
3974
|
-
removedDirs.add(
|
|
4564
|
+
removedDirs.add(dirname7(abs));
|
|
3975
4565
|
}
|
|
3976
4566
|
}
|
|
3977
4567
|
for (const dir of removedDirs) {
|
|
@@ -3980,7 +4570,7 @@ function deleteStalePaths(absPaths) {
|
|
|
3980
4570
|
try {
|
|
3981
4571
|
if (readdirSync(current).length === 0) {
|
|
3982
4572
|
rmdirSync(current);
|
|
3983
|
-
current =
|
|
4573
|
+
current = dirname7(current);
|
|
3984
4574
|
} else {
|
|
3985
4575
|
break;
|
|
3986
4576
|
}
|