@contractkit/plugin-typescript 0.28.0 → 0.28.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build$colon$ci.log +5 -5
- package/.turbo/turbo-test$colon$ci.log +22 -19
- package/CHANGELOG.md +14 -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-sdk.d.ts.map +1 -1
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +807 -243
- package/dist/index.js.map +1 -1
- package/dist/path-utils.d.ts.map +1 -1
- package/dist/ts-render.d.ts +6 -0
- package/dist/ts-render.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/codegen-contract.ts +7 -4
- package/src/codegen-mcp.ts +501 -0
- package/src/codegen-operation.ts +43 -9
- package/src/codegen-plain-types.ts +17 -14
- package/src/codegen-sdk.ts +5 -4
- package/src/index.ts +154 -0
- package/src/path-utils.ts +37 -20
- package/src/ts-render.ts +21 -6
- package/tests/codegen-contract.test.ts +4 -0
- package/tests/codegen-mcp.test.ts +246 -0
- package/tests/codegen-operation.test.ts +18 -0
- package/tests/codegen-sdk.test.ts +8 -0
- package/tests/escaping-security.test.ts +143 -0
- package/tests/pipeline.test.ts +59 -0
- package/coverage/base.css +0 -224
- package/coverage/block-navigation.js +0 -87
- package/coverage/clover.xml +0 -2213
- package/coverage/coverage-final.json +0 -9
- package/coverage/favicon.png +0 -0
- package/coverage/index.html +0 -131
- package/coverage/prettify.css +0 -1
- package/coverage/prettify.js +0 -2
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +0 -210
- package/coverage/src/codegen-contract.ts.html +0 -3661
- package/coverage/src/codegen-operation.ts.html +0 -2584
- package/coverage/src/codegen-plain-types.ts.html +0 -997
- package/coverage/src/codegen-sdk.ts.html +0 -3901
- package/coverage/src/index.html +0 -206
- package/coverage/src/index.ts.html +0 -2761
- package/coverage/src/path-utils.ts.html +0 -745
- package/coverage/src/ts-render.ts.html +0 -592
- package/coverage/tests/helpers.ts.html +0 -826
- package/coverage/tests/index.html +0 -116
package/dist/index.js
CHANGED
|
@@ -2,12 +2,166 @@ 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
|
|
9
9
|
import { relative, dirname } from "path";
|
|
10
10
|
import { collectTypeRefs, computeModelsWithOutput as ckComputeModelsWithOutput, collectExternalOutputRefs as ckCollectExternalOutputRefs } from "@contractkit/core";
|
|
11
|
+
|
|
12
|
+
// src/ts-render.ts
|
|
13
|
+
var JSON_VALUE_TYPE_DECL = "export type JsonValue = string | number | boolean | null | JsonValue[] | { [key: string]: JsonValue };";
|
|
14
|
+
function quoteKey(name) {
|
|
15
|
+
return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name) ? name : `'${name}'`;
|
|
16
|
+
}
|
|
17
|
+
__name(quoteKey, "quoteKey");
|
|
18
|
+
function escapeJsDocLines(text) {
|
|
19
|
+
return text.replace(/\*\//g, "*\\/").split("\n");
|
|
20
|
+
}
|
|
21
|
+
__name(escapeJsDocLines, "escapeJsDocLines");
|
|
22
|
+
function escapeSingleQuoted(s) {
|
|
23
|
+
return s.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/\n/g, "\\n").replace(/\r/g, "\\r");
|
|
24
|
+
}
|
|
25
|
+
__name(escapeSingleQuoted, "escapeSingleQuoted");
|
|
26
|
+
function headerNameToProperty(name) {
|
|
27
|
+
const parts = name.split(/[-_]/).filter(Boolean);
|
|
28
|
+
return parts.map((p, i) => {
|
|
29
|
+
const lower = p.toLowerCase();
|
|
30
|
+
return i === 0 ? lower : lower.charAt(0).toUpperCase() + lower.slice(1);
|
|
31
|
+
}).join("");
|
|
32
|
+
}
|
|
33
|
+
__name(headerNameToProperty, "headerNameToProperty");
|
|
34
|
+
function renderTsType(type) {
|
|
35
|
+
switch (type.kind) {
|
|
36
|
+
case "scalar":
|
|
37
|
+
return renderTsScalar(type.name);
|
|
38
|
+
case "array": {
|
|
39
|
+
const inner = renderTsType(type.item);
|
|
40
|
+
const needsParens = type.item.kind === "union" || type.item.kind === "discriminatedUnion" || type.item.kind === "intersection" || type.item.kind === "enum";
|
|
41
|
+
return needsParens ? `(${inner})[]` : `${inner}[]`;
|
|
42
|
+
}
|
|
43
|
+
case "tuple":
|
|
44
|
+
return `[${type.items.map(renderTsType).join(", ")}]`;
|
|
45
|
+
case "record":
|
|
46
|
+
return `Record<${renderTsType(type.key)}, ${renderTsType(type.value)}>`;
|
|
47
|
+
case "enum":
|
|
48
|
+
return type.values.map((v) => `'${escapeSingleQuoted(v)}'`).join(" | ");
|
|
49
|
+
case "literal":
|
|
50
|
+
return typeof type.value === "string" ? `'${escapeSingleQuoted(type.value)}'` : String(type.value);
|
|
51
|
+
case "union":
|
|
52
|
+
return type.members.map(renderTsType).join(" | ");
|
|
53
|
+
case "discriminatedUnion":
|
|
54
|
+
return type.members.map(renderTsType).join(" | ");
|
|
55
|
+
case "intersection":
|
|
56
|
+
return type.members.map(renderTsType).join(" & ");
|
|
57
|
+
case "ref":
|
|
58
|
+
return type.name;
|
|
59
|
+
case "lazy":
|
|
60
|
+
return renderTsType(type.inner);
|
|
61
|
+
case "inlineObject":
|
|
62
|
+
return renderTsInlineObject(type.fields);
|
|
63
|
+
default:
|
|
64
|
+
return "unknown";
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
__name(renderTsType, "renderTsType");
|
|
68
|
+
function renderTsScalar(name) {
|
|
69
|
+
switch (name) {
|
|
70
|
+
case "string":
|
|
71
|
+
case "email":
|
|
72
|
+
case "url":
|
|
73
|
+
case "uuid":
|
|
74
|
+
return "string";
|
|
75
|
+
case "number":
|
|
76
|
+
case "int":
|
|
77
|
+
return "number";
|
|
78
|
+
case "bigint":
|
|
79
|
+
return "bigint";
|
|
80
|
+
case "boolean":
|
|
81
|
+
return "boolean";
|
|
82
|
+
case "date":
|
|
83
|
+
case "time":
|
|
84
|
+
case "datetime":
|
|
85
|
+
case "duration":
|
|
86
|
+
case "interval":
|
|
87
|
+
return "string";
|
|
88
|
+
case "null":
|
|
89
|
+
return "null";
|
|
90
|
+
case "unknown":
|
|
91
|
+
return "unknown";
|
|
92
|
+
case "object":
|
|
93
|
+
return "Record<string, unknown>";
|
|
94
|
+
case "binary":
|
|
95
|
+
return "Blob";
|
|
96
|
+
case "json":
|
|
97
|
+
return "JsonValue";
|
|
98
|
+
default: {
|
|
99
|
+
const _exhaustive = name;
|
|
100
|
+
throw new Error(`plugin-typescript: unmapped scalar '${String(_exhaustive)}' \u2014 add a case`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
__name(renderTsScalar, "renderTsScalar");
|
|
105
|
+
function renderTsInlineObject(fields) {
|
|
106
|
+
const entries = fields.map((f) => {
|
|
107
|
+
const opt = f.optional ? "?" : "";
|
|
108
|
+
return `${quoteKey(f.name)}${opt}: ${renderTsType(f.type)}`;
|
|
109
|
+
});
|
|
110
|
+
return `{ ${entries.join("; ")} }`;
|
|
111
|
+
}
|
|
112
|
+
__name(renderTsInlineObject, "renderTsInlineObject");
|
|
113
|
+
function renderInputTsType(type, modelsWithInput) {
|
|
114
|
+
if (!modelsWithInput || modelsWithInput.size === 0) return renderTsType(type);
|
|
115
|
+
switch (type.kind) {
|
|
116
|
+
case "ref":
|
|
117
|
+
return modelsWithInput.has(type.name) ? `${type.name}Input` : type.name;
|
|
118
|
+
case "array": {
|
|
119
|
+
const inner = renderInputTsType(type.item, modelsWithInput);
|
|
120
|
+
const needsParens = type.item.kind === "union" || type.item.kind === "discriminatedUnion" || type.item.kind === "intersection" || type.item.kind === "enum";
|
|
121
|
+
return needsParens ? `(${inner})[]` : `${inner}[]`;
|
|
122
|
+
}
|
|
123
|
+
case "intersection":
|
|
124
|
+
return type.members.map((m) => renderInputTsType(m, modelsWithInput)).join(" & ");
|
|
125
|
+
case "union":
|
|
126
|
+
return type.members.map((m) => renderInputTsType(m, modelsWithInput)).join(" | ");
|
|
127
|
+
case "discriminatedUnion":
|
|
128
|
+
return type.members.map((m) => renderInputTsType(m, modelsWithInput)).join(" | ");
|
|
129
|
+
case "inlineObject":
|
|
130
|
+
return `{ ${type.fields.map((f) => `${quoteKey(f.name)}${f.optional ? "?" : ""}: ${renderInputTsType(f.type, modelsWithInput)}`).join("; ")} }`;
|
|
131
|
+
case "lazy":
|
|
132
|
+
return renderInputTsType(type.inner, modelsWithInput);
|
|
133
|
+
default:
|
|
134
|
+
return renderTsType(type);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
__name(renderInputTsType, "renderInputTsType");
|
|
138
|
+
function renderOutputTsType(type, modelsWithOutput) {
|
|
139
|
+
if (!modelsWithOutput || modelsWithOutput.size === 0) return renderTsType(type);
|
|
140
|
+
switch (type.kind) {
|
|
141
|
+
case "ref":
|
|
142
|
+
return modelsWithOutput.has(type.name) ? `${type.name}Output` : type.name;
|
|
143
|
+
case "array": {
|
|
144
|
+
const inner = renderOutputTsType(type.item, modelsWithOutput);
|
|
145
|
+
const needsParens = type.item.kind === "union" || type.item.kind === "discriminatedUnion" || type.item.kind === "intersection" || type.item.kind === "enum";
|
|
146
|
+
return needsParens ? `(${inner})[]` : `${inner}[]`;
|
|
147
|
+
}
|
|
148
|
+
case "intersection":
|
|
149
|
+
return type.members.map((m) => renderOutputTsType(m, modelsWithOutput)).join(" & ");
|
|
150
|
+
case "union":
|
|
151
|
+
return type.members.map((m) => renderOutputTsType(m, modelsWithOutput)).join(" | ");
|
|
152
|
+
case "discriminatedUnion":
|
|
153
|
+
return type.members.map((m) => renderOutputTsType(m, modelsWithOutput)).join(" | ");
|
|
154
|
+
case "inlineObject":
|
|
155
|
+
return `{ ${type.fields.map((f) => `${quoteKey(f.name)}${f.optional ? "?" : ""}: ${renderOutputTsType(f.type, modelsWithOutput)}`).join("; ")} }`;
|
|
156
|
+
case "lazy":
|
|
157
|
+
return renderOutputTsType(type.inner, modelsWithOutput);
|
|
158
|
+
default:
|
|
159
|
+
return renderTsType(type);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
__name(renderOutputTsType, "renderOutputTsType");
|
|
163
|
+
|
|
164
|
+
// src/codegen-contract.ts
|
|
11
165
|
function modeToWrapper(mode) {
|
|
12
166
|
switch (mode) {
|
|
13
167
|
case "strict":
|
|
@@ -56,7 +210,7 @@ function generateComments(model, outPath) {
|
|
|
56
210
|
lines.push(` * @deprecated`);
|
|
57
211
|
}
|
|
58
212
|
if (model.description) {
|
|
59
|
-
lines.push(` * ${
|
|
213
|
+
for (const l of escapeJsDocLines(model.description)) lines.push(` * ${l}`);
|
|
60
214
|
}
|
|
61
215
|
const relPath = outPath ? relative(dirname(outPath), model.loc.file) : model.loc.file;
|
|
62
216
|
lines.push(` * generated from [${model.name}](file://./${relPath}#L${model.loc.line})`);
|
|
@@ -199,9 +353,9 @@ function generateSimpleModel(model, outPath) {
|
|
|
199
353
|
const outputKey = applyCase(field.name, outputCase);
|
|
200
354
|
if (field.optional) {
|
|
201
355
|
const guard = hasInputTransform ? `data.${inputKey} != null` : `data.${inputKey} !== undefined`;
|
|
202
|
-
lines.push(` ...(${guard} ? { ${
|
|
356
|
+
lines.push(` ...(${guard} ? { ${quoteKey2(outputKey)}: data.${inputKey} } : {}),`);
|
|
203
357
|
} else {
|
|
204
|
-
lines.push(` ${
|
|
358
|
+
lines.push(` ${quoteKey2(outputKey)}: data.${inputKey},`);
|
|
205
359
|
}
|
|
206
360
|
}
|
|
207
361
|
lines.push(`}));`);
|
|
@@ -298,7 +452,7 @@ function generateThreeSchemaModel(model, outPath, modelsWithInput, modelsWithWri
|
|
|
298
452
|
}
|
|
299
453
|
const omitClause = fieldsToOmit.size > 0 ? `.omit({ ${[
|
|
300
454
|
...fieldsToOmit
|
|
301
|
-
].map((f) => `${
|
|
455
|
+
].map((f) => `${quoteKey2(f)}: true`).join(", ")} })` : "";
|
|
302
456
|
if (bases.length > 0) {
|
|
303
457
|
const { head, tail } = buildExtendChain(bases, (b) => modelsWithInput?.has(b) ? `${b}Input` : b);
|
|
304
458
|
lines.push(`export const ${name}Input = ${head}${tail}${omitClause}.extend({`);
|
|
@@ -343,7 +497,7 @@ function renderFieldsAsPascalCase(fields, defaultMode) {
|
|
|
343
497
|
expr += ".nullable()";
|
|
344
498
|
}
|
|
345
499
|
if (f.description) expr += `.describe("${escapeString(f.description)}")`;
|
|
346
|
-
return `${
|
|
500
|
+
return `${quoteKey2(pascalKey)}: ${expr},`;
|
|
347
501
|
});
|
|
348
502
|
}
|
|
349
503
|
__name(renderFieldsAsPascalCase, "renderFieldsAsPascalCase");
|
|
@@ -361,7 +515,7 @@ function renderFieldsAsSnakeCase(fields, defaultMode) {
|
|
|
361
515
|
expr += ".nullable()";
|
|
362
516
|
}
|
|
363
517
|
if (f.description) expr += `.describe("${escapeString(f.description)}")`;
|
|
364
|
-
return `${
|
|
518
|
+
return `${quoteKey2(snakeKey)}: ${expr},`;
|
|
365
519
|
});
|
|
366
520
|
}
|
|
367
521
|
__name(renderFieldsAsSnakeCase, "renderFieldsAsSnakeCase");
|
|
@@ -377,7 +531,7 @@ function renderField(field, defaultMode) {
|
|
|
377
531
|
expr += ".optional()";
|
|
378
532
|
}
|
|
379
533
|
if (field.description) expr += `.describe("${escapeString(field.description)}")`;
|
|
380
|
-
lines.push(`${
|
|
534
|
+
lines.push(`${quoteKey2(field.name)}: ${expr},`);
|
|
381
535
|
return lines;
|
|
382
536
|
}
|
|
383
537
|
__name(renderField, "renderField");
|
|
@@ -502,8 +656,10 @@ function renderScalar(s) {
|
|
|
502
656
|
return "_ZodBinary";
|
|
503
657
|
case "json":
|
|
504
658
|
return "_ZodJson";
|
|
505
|
-
default:
|
|
506
|
-
|
|
659
|
+
default: {
|
|
660
|
+
const _exhaustive = s.name;
|
|
661
|
+
throw new Error(`plugin-typescript: unmapped scalar '${String(_exhaustive)}' \u2014 add a case`);
|
|
662
|
+
}
|
|
507
663
|
}
|
|
508
664
|
}
|
|
509
665
|
__name(renderScalar, "renderScalar");
|
|
@@ -523,7 +679,7 @@ function renderRecord(r) {
|
|
|
523
679
|
}
|
|
524
680
|
__name(renderRecord, "renderRecord");
|
|
525
681
|
function renderEnum(e) {
|
|
526
|
-
const vals = e.values.map((v) => `"${v}"`).join(", ");
|
|
682
|
+
const vals = e.values.map((v) => `"${escapeString(v)}"`).join(", ");
|
|
527
683
|
return `z.enum([${vals}])`;
|
|
528
684
|
}
|
|
529
685
|
__name(renderEnum, "renderEnum");
|
|
@@ -572,9 +728,9 @@ function renderInlineObject(o, parseCaseTransform, defaultMode) {
|
|
|
572
728
|
const transformEntries = o.fields.map((f) => {
|
|
573
729
|
const snakeKey = camelToSnake(f.name);
|
|
574
730
|
if (f.optional) {
|
|
575
|
-
return ` ...(data.${snakeKey} != null ? { ${
|
|
731
|
+
return ` ...(data.${snakeKey} != null ? { ${quoteKey2(f.name)}: data.${snakeKey} } : {}),`;
|
|
576
732
|
}
|
|
577
|
-
return ` ${
|
|
733
|
+
return ` ${quoteKey2(f.name)}: data.${snakeKey},`;
|
|
578
734
|
}).join("\n");
|
|
579
735
|
return `${wrapper}({
|
|
580
736
|
${joined}
|
|
@@ -588,9 +744,9 @@ ${transformEntries}
|
|
|
588
744
|
const transformEntries = o.fields.map((f) => {
|
|
589
745
|
const pascalKey = camelToPascal(f.name);
|
|
590
746
|
if (f.optional) {
|
|
591
|
-
return ` ...(data.${pascalKey} != null ? { ${
|
|
747
|
+
return ` ...(data.${pascalKey} != null ? { ${quoteKey2(f.name)}: data.${pascalKey} } : {}),`;
|
|
592
748
|
}
|
|
593
|
-
return ` ${
|
|
749
|
+
return ` ${quoteKey2(f.name)}: data.${pascalKey},`;
|
|
594
750
|
}).join("\n");
|
|
595
751
|
return `${wrapper}({
|
|
596
752
|
${joined}
|
|
@@ -676,7 +832,7 @@ function renderInputField(field, modelsWithInput, defaultMode) {
|
|
|
676
832
|
expr += ".optional()";
|
|
677
833
|
}
|
|
678
834
|
if (field.description) expr += `.describe("${escapeString(field.description)}")`;
|
|
679
|
-
lines.push(`${
|
|
835
|
+
lines.push(`${quoteKey2(field.name)}: ${expr},`);
|
|
680
836
|
return lines;
|
|
681
837
|
}
|
|
682
838
|
__name(renderInputField, "renderInputField");
|
|
@@ -736,17 +892,17 @@ function renderQueryField(field, modelsWithInput, defaultMode) {
|
|
|
736
892
|
expr += ".optional()";
|
|
737
893
|
}
|
|
738
894
|
if (field.description) expr += `.describe("${escapeString(field.description)}")`;
|
|
739
|
-
return `${
|
|
895
|
+
return `${quoteKey2(field.name)}: ${expr},`;
|
|
740
896
|
}
|
|
741
897
|
__name(renderQueryField, "renderQueryField");
|
|
742
898
|
function isValidIdentifier(name) {
|
|
743
899
|
return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
|
|
744
900
|
}
|
|
745
901
|
__name(isValidIdentifier, "isValidIdentifier");
|
|
746
|
-
function
|
|
902
|
+
function quoteKey2(name) {
|
|
747
903
|
return isValidIdentifier(name) ? name : `'${name}'`;
|
|
748
904
|
}
|
|
749
|
-
__name(
|
|
905
|
+
__name(quoteKey2, "quoteKey");
|
|
750
906
|
function escapeString(s) {
|
|
751
907
|
return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r");
|
|
752
908
|
}
|
|
@@ -950,149 +1106,6 @@ __name(pascalToDotCase, "pascalToDotCase");
|
|
|
950
1106
|
|
|
951
1107
|
// src/codegen-operation.ts
|
|
952
1108
|
import { resolveModifiers, resolveSecurity, SECURITY_NONE, classifyContentType } from "@contractkit/core";
|
|
953
|
-
|
|
954
|
-
// src/ts-render.ts
|
|
955
|
-
var JSON_VALUE_TYPE_DECL = "export type JsonValue = string | number | boolean | null | JsonValue[] | { [key: string]: JsonValue };";
|
|
956
|
-
function quoteKey2(name) {
|
|
957
|
-
return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name) ? name : `'${name}'`;
|
|
958
|
-
}
|
|
959
|
-
__name(quoteKey2, "quoteKey");
|
|
960
|
-
function headerNameToProperty(name) {
|
|
961
|
-
const parts = name.split(/[-_]/).filter(Boolean);
|
|
962
|
-
return parts.map((p, i) => {
|
|
963
|
-
const lower = p.toLowerCase();
|
|
964
|
-
return i === 0 ? lower : lower.charAt(0).toUpperCase() + lower.slice(1);
|
|
965
|
-
}).join("");
|
|
966
|
-
}
|
|
967
|
-
__name(headerNameToProperty, "headerNameToProperty");
|
|
968
|
-
function renderTsType(type) {
|
|
969
|
-
switch (type.kind) {
|
|
970
|
-
case "scalar":
|
|
971
|
-
return renderTsScalar(type.name);
|
|
972
|
-
case "array": {
|
|
973
|
-
const inner = renderTsType(type.item);
|
|
974
|
-
const needsParens = type.item.kind === "union" || type.item.kind === "discriminatedUnion" || type.item.kind === "intersection" || type.item.kind === "enum";
|
|
975
|
-
return needsParens ? `(${inner})[]` : `${inner}[]`;
|
|
976
|
-
}
|
|
977
|
-
case "tuple":
|
|
978
|
-
return `[${type.items.map(renderTsType).join(", ")}]`;
|
|
979
|
-
case "record":
|
|
980
|
-
return `Record<${renderTsType(type.key)}, ${renderTsType(type.value)}>`;
|
|
981
|
-
case "enum":
|
|
982
|
-
return type.values.map((v) => `'${v}'`).join(" | ");
|
|
983
|
-
case "literal":
|
|
984
|
-
return typeof type.value === "string" ? `'${type.value}'` : String(type.value);
|
|
985
|
-
case "union":
|
|
986
|
-
return type.members.map(renderTsType).join(" | ");
|
|
987
|
-
case "discriminatedUnion":
|
|
988
|
-
return type.members.map(renderTsType).join(" | ");
|
|
989
|
-
case "intersection":
|
|
990
|
-
return type.members.map(renderTsType).join(" & ");
|
|
991
|
-
case "ref":
|
|
992
|
-
return type.name;
|
|
993
|
-
case "lazy":
|
|
994
|
-
return renderTsType(type.inner);
|
|
995
|
-
case "inlineObject":
|
|
996
|
-
return renderTsInlineObject(type.fields);
|
|
997
|
-
default:
|
|
998
|
-
return "unknown";
|
|
999
|
-
}
|
|
1000
|
-
}
|
|
1001
|
-
__name(renderTsType, "renderTsType");
|
|
1002
|
-
function renderTsScalar(name) {
|
|
1003
|
-
switch (name) {
|
|
1004
|
-
case "string":
|
|
1005
|
-
case "email":
|
|
1006
|
-
case "url":
|
|
1007
|
-
case "uuid":
|
|
1008
|
-
return "string";
|
|
1009
|
-
case "number":
|
|
1010
|
-
case "int":
|
|
1011
|
-
return "number";
|
|
1012
|
-
case "bigint":
|
|
1013
|
-
return "bigint";
|
|
1014
|
-
case "boolean":
|
|
1015
|
-
return "boolean";
|
|
1016
|
-
case "date":
|
|
1017
|
-
case "datetime":
|
|
1018
|
-
case "duration":
|
|
1019
|
-
case "interval":
|
|
1020
|
-
return "string";
|
|
1021
|
-
case "null":
|
|
1022
|
-
return "null";
|
|
1023
|
-
case "unknown":
|
|
1024
|
-
return "unknown";
|
|
1025
|
-
case "object":
|
|
1026
|
-
return "Record<string, unknown>";
|
|
1027
|
-
case "binary":
|
|
1028
|
-
return "Blob";
|
|
1029
|
-
case "json":
|
|
1030
|
-
return "JsonValue";
|
|
1031
|
-
default:
|
|
1032
|
-
return "unknown";
|
|
1033
|
-
}
|
|
1034
|
-
}
|
|
1035
|
-
__name(renderTsScalar, "renderTsScalar");
|
|
1036
|
-
function renderTsInlineObject(fields) {
|
|
1037
|
-
const entries = fields.map((f) => {
|
|
1038
|
-
const opt = f.optional ? "?" : "";
|
|
1039
|
-
return `${quoteKey2(f.name)}${opt}: ${renderTsType(f.type)}`;
|
|
1040
|
-
});
|
|
1041
|
-
return `{ ${entries.join("; ")} }`;
|
|
1042
|
-
}
|
|
1043
|
-
__name(renderTsInlineObject, "renderTsInlineObject");
|
|
1044
|
-
function renderInputTsType(type, modelsWithInput) {
|
|
1045
|
-
if (!modelsWithInput || modelsWithInput.size === 0) return renderTsType(type);
|
|
1046
|
-
switch (type.kind) {
|
|
1047
|
-
case "ref":
|
|
1048
|
-
return modelsWithInput.has(type.name) ? `${type.name}Input` : type.name;
|
|
1049
|
-
case "array": {
|
|
1050
|
-
const inner = renderInputTsType(type.item, modelsWithInput);
|
|
1051
|
-
const needsParens = type.item.kind === "union" || type.item.kind === "discriminatedUnion" || type.item.kind === "intersection" || type.item.kind === "enum";
|
|
1052
|
-
return needsParens ? `(${inner})[]` : `${inner}[]`;
|
|
1053
|
-
}
|
|
1054
|
-
case "intersection":
|
|
1055
|
-
return type.members.map((m) => renderInputTsType(m, modelsWithInput)).join(" & ");
|
|
1056
|
-
case "union":
|
|
1057
|
-
return type.members.map((m) => renderInputTsType(m, modelsWithInput)).join(" | ");
|
|
1058
|
-
case "discriminatedUnion":
|
|
1059
|
-
return type.members.map((m) => renderInputTsType(m, modelsWithInput)).join(" | ");
|
|
1060
|
-
case "inlineObject":
|
|
1061
|
-
return `{ ${type.fields.map((f) => `${quoteKey2(f.name)}${f.optional ? "?" : ""}: ${renderInputTsType(f.type, modelsWithInput)}`).join("; ")} }`;
|
|
1062
|
-
case "lazy":
|
|
1063
|
-
return renderInputTsType(type.inner, modelsWithInput);
|
|
1064
|
-
default:
|
|
1065
|
-
return renderTsType(type);
|
|
1066
|
-
}
|
|
1067
|
-
}
|
|
1068
|
-
__name(renderInputTsType, "renderInputTsType");
|
|
1069
|
-
function renderOutputTsType(type, modelsWithOutput) {
|
|
1070
|
-
if (!modelsWithOutput || modelsWithOutput.size === 0) return renderTsType(type);
|
|
1071
|
-
switch (type.kind) {
|
|
1072
|
-
case "ref":
|
|
1073
|
-
return modelsWithOutput.has(type.name) ? `${type.name}Output` : type.name;
|
|
1074
|
-
case "array": {
|
|
1075
|
-
const inner = renderOutputTsType(type.item, modelsWithOutput);
|
|
1076
|
-
const needsParens = type.item.kind === "union" || type.item.kind === "discriminatedUnion" || type.item.kind === "intersection" || type.item.kind === "enum";
|
|
1077
|
-
return needsParens ? `(${inner})[]` : `${inner}[]`;
|
|
1078
|
-
}
|
|
1079
|
-
case "intersection":
|
|
1080
|
-
return type.members.map((m) => renderOutputTsType(m, modelsWithOutput)).join(" & ");
|
|
1081
|
-
case "union":
|
|
1082
|
-
return type.members.map((m) => renderOutputTsType(m, modelsWithOutput)).join(" | ");
|
|
1083
|
-
case "discriminatedUnion":
|
|
1084
|
-
return type.members.map((m) => renderOutputTsType(m, modelsWithOutput)).join(" | ");
|
|
1085
|
-
case "inlineObject":
|
|
1086
|
-
return `{ ${type.fields.map((f) => `${quoteKey2(f.name)}${f.optional ? "?" : ""}: ${renderOutputTsType(f.type, modelsWithOutput)}`).join("; ")} }`;
|
|
1087
|
-
case "lazy":
|
|
1088
|
-
return renderOutputTsType(type.inner, modelsWithOutput);
|
|
1089
|
-
default:
|
|
1090
|
-
return renderTsType(type);
|
|
1091
|
-
}
|
|
1092
|
-
}
|
|
1093
|
-
__name(renderOutputTsType, "renderOutputTsType");
|
|
1094
|
-
|
|
1095
|
-
// src/codegen-operation.ts
|
|
1096
1109
|
import { basename, dirname as dirname2, relative as relative2 } from "path";
|
|
1097
1110
|
function bodyParserToken(contentType) {
|
|
1098
1111
|
switch (classifyContentType(contentType)) {
|
|
@@ -1244,7 +1257,7 @@ function generateHandler(route, op, root, options) {
|
|
|
1244
1257
|
lines.push("/**");
|
|
1245
1258
|
const desc = op.description ?? route.description;
|
|
1246
1259
|
if (desc) {
|
|
1247
|
-
lines.push(` * ${
|
|
1260
|
+
for (const l of escapeJsDocLines(desc)) lines.push(` * ${l}`);
|
|
1248
1261
|
}
|
|
1249
1262
|
const relFile = outPath ? relative2(dirname2(outPath), file) : file;
|
|
1250
1263
|
lines.push(` * from [${basename(file)}](file://./${relFile}#L${op.loc.line})`);
|
|
@@ -1273,11 +1286,11 @@ function generateHandler(route, op, root, options) {
|
|
|
1273
1286
|
middlewares.push(`bodyParserMiddleware([${tokensExpr}])`);
|
|
1274
1287
|
}
|
|
1275
1288
|
if (op.signature) {
|
|
1276
|
-
const sigArgs = op.signaturePolicy ? `'${op.signature}', { policy: '${op.signaturePolicy}' }` : `'${op.signature}'`;
|
|
1289
|
+
const sigArgs = op.signaturePolicy ? `'${escapeSingleQuoted(op.signature)}', { policy: '${escapeSingleQuoted(op.signaturePolicy)}' }` : `'${escapeSingleQuoted(op.signature)}'`;
|
|
1277
1290
|
middlewares.push(`requireSignature(${sigArgs})`);
|
|
1278
1291
|
}
|
|
1279
1292
|
const middlewareStr = middlewares.length > 0 ? `, ${middlewares.join(", ")},` : ",";
|
|
1280
|
-
lines.push(`${deriveRouterName(file)}.${method}('${path}'${middlewareStr} async
|
|
1293
|
+
lines.push(`${deriveRouterName(file)}.${method}('${path}'${middlewareStr} async ctx => {`);
|
|
1281
1294
|
lines.push(...generateParamValidation(route.params, "ctx.params", "params", route.paramsMode ?? "strict", "", modelsWithInput));
|
|
1282
1295
|
lines.push(...generateParamValidation(op.query, "ctx.query", "query", op.queryMode ?? "strict", "", modelsWithInput));
|
|
1283
1296
|
lines.push(...generateParamValidation(op.headers, "ctx.headers", "headers", op.headersMode ?? "strip", "", modelsWithInput));
|
|
@@ -1308,13 +1321,13 @@ function generateHandler(route, op, root, options) {
|
|
|
1308
1321
|
lines.push("");
|
|
1309
1322
|
}
|
|
1310
1323
|
}
|
|
1311
|
-
const
|
|
1324
|
+
const primaryResponse2 = op.responses.find((r) => r.bodyType) ?? op.responses[0];
|
|
1312
1325
|
const serviceParts = inferService(op, route, file);
|
|
1313
|
-
const respHeaders =
|
|
1326
|
+
const respHeaders = primaryResponse2?.headers ?? [];
|
|
1314
1327
|
const hasRespHeaders = respHeaders.length > 0;
|
|
1315
|
-
const headersAnnotation = hasRespHeaders ? `{ ${respHeaders.map((h) => `${
|
|
1316
|
-
if (
|
|
1317
|
-
const { annotation, prelude } = formatTypeAnnotation(
|
|
1328
|
+
const headersAnnotation = hasRespHeaders ? `{ ${respHeaders.map((h) => `${quoteKey(headerNameToProperty(h.name))}${h.optional ? "?" : ""}: ${renderOutputTsType(h.type, options.modelsWithOutput)}`).join("; ")} }` : "";
|
|
1329
|
+
if (primaryResponse2?.bodyType) {
|
|
1330
|
+
const { annotation, prelude } = formatTypeAnnotation(primaryResponse2.bodyType, options.modelsWithOutput);
|
|
1318
1331
|
if (prelude) {
|
|
1319
1332
|
lines.push(` ${prelude}`);
|
|
1320
1333
|
}
|
|
@@ -1333,7 +1346,7 @@ function generateHandler(route, op, root, options) {
|
|
|
1333
1346
|
}
|
|
1334
1347
|
}
|
|
1335
1348
|
lines.push("");
|
|
1336
|
-
lines.push(` ctx.status = ${
|
|
1349
|
+
lines.push(` ctx.status = ${primaryResponse2?.statusCode ?? 200};`);
|
|
1337
1350
|
if (hasRespHeaders) {
|
|
1338
1351
|
for (const h of respHeaders) {
|
|
1339
1352
|
const accessor = `result.headers[${JSON.stringify(headerNameToProperty(h.name))}]`;
|
|
@@ -1344,8 +1357,8 @@ function generateHandler(route, op, root, options) {
|
|
|
1344
1357
|
}
|
|
1345
1358
|
}
|
|
1346
1359
|
}
|
|
1347
|
-
if (
|
|
1348
|
-
lines.push(` ctx.type = '${
|
|
1360
|
+
if (primaryResponse2?.bodyType && primaryResponse2.contentType) {
|
|
1361
|
+
lines.push(` ctx.type = '${primaryResponse2.contentType}';`);
|
|
1349
1362
|
lines.push(` ctx.body = ${hasRespHeaders ? "result.body" : "result"};`);
|
|
1350
1363
|
}
|
|
1351
1364
|
lines.push(`});`);
|
|
@@ -1947,24 +1960,25 @@ function generateMethod(route, op, file, options) {
|
|
|
1947
1960
|
const { modelsWithInput, modelsWithOutput } = options;
|
|
1948
1961
|
const params = buildMethodParams(route, op, modelsWithInput);
|
|
1949
1962
|
const paramStr = params.map((p) => `${p.name}${p.optional ? "?" : ""}: ${p.type}`).join(", ");
|
|
1950
|
-
const
|
|
1951
|
-
const isVoid = !
|
|
1952
|
-
const respCategory =
|
|
1953
|
-
const dataType = isVoid ? "void" : respCategory === "text" ? "string" : respCategory === "binary" ? "Blob" : renderOutputTsType(
|
|
1954
|
-
const respHeaders =
|
|
1963
|
+
const primaryResponse2 = op.responses.find((r) => r.bodyType) ?? op.responses[0];
|
|
1964
|
+
const isVoid = !primaryResponse2?.bodyType;
|
|
1965
|
+
const respCategory = primaryResponse2?.contentType ? classifyContentType2(primaryResponse2.contentType) : "json";
|
|
1966
|
+
const dataType = isVoid ? "void" : respCategory === "text" ? "string" : respCategory === "binary" ? "Blob" : renderOutputTsType(primaryResponse2.bodyType, modelsWithOutput);
|
|
1967
|
+
const respHeaders = primaryResponse2?.headers ?? [];
|
|
1955
1968
|
const hasRespHeaders = respHeaders.length > 0;
|
|
1956
|
-
const headersShape = hasRespHeaders ? `{ ${respHeaders.map((h) => `${
|
|
1969
|
+
const headersShape = hasRespHeaders ? `{ ${respHeaders.map((h) => `${quoteKey(headerNameToProperty(h.name))}${h.optional ? "?" : ""}: ${renderOutputTsType(h.type, modelsWithOutput)}`).join("; ")} }` : "";
|
|
1957
1970
|
const returnType = hasRespHeaders ? isVoid ? `{ headers: ${headersShape} }` : `{ data: ${dataType}; headers: ${headersShape} }` : dataType;
|
|
1958
1971
|
const desc = op.description ?? route.description;
|
|
1959
1972
|
if (op.name || desc) {
|
|
1960
1973
|
const tags = [];
|
|
1961
1974
|
if (op.name) tags.push(`@name ${op.name}`);
|
|
1962
1975
|
if (desc) tags.push(`@description ${desc}`);
|
|
1963
|
-
|
|
1964
|
-
|
|
1976
|
+
const contentLines = tags.flatMap((t) => escapeJsDocLines(t));
|
|
1977
|
+
if (contentLines.length === 1) {
|
|
1978
|
+
lines.push(` /** ${contentLines[0]} */`);
|
|
1965
1979
|
} else {
|
|
1966
1980
|
lines.push(` /**`);
|
|
1967
|
-
for (const
|
|
1981
|
+
for (const l of contentLines) lines.push(` * ${l}`);
|
|
1968
1982
|
lines.push(` */`);
|
|
1969
1983
|
}
|
|
1970
1984
|
}
|
|
@@ -2040,7 +2054,7 @@ function generateMethod(route, op, file, options) {
|
|
|
2040
2054
|
}
|
|
2041
2055
|
const readBodyExpr = respCategory === "text" ? `await result.text()` : respCategory === "binary" ? `await result.blob()` : `await parseJson<${dataType}>(result)`;
|
|
2042
2056
|
if (hasRespHeaders) {
|
|
2043
|
-
const headerEntries = respHeaders.map((h) => `${
|
|
2057
|
+
const headerEntries = respHeaders.map((h) => `${quoteKey(headerNameToProperty(h.name))}: result.headers.get('${h.name}') ?? undefined`).join(", ");
|
|
2044
2058
|
if (isVoid) {
|
|
2045
2059
|
lines.push(` return { headers: { ${headerEntries} } };`);
|
|
2046
2060
|
} else {
|
|
@@ -2152,7 +2166,7 @@ function buildMethodParams(route, op, modelsWithInput) {
|
|
|
2152
2166
|
}
|
|
2153
2167
|
if (op.query) {
|
|
2154
2168
|
if (op.query.kind === "params") {
|
|
2155
|
-
const fields = op.query.nodes.map((p) => `${
|
|
2169
|
+
const fields = op.query.nodes.map((p) => `${quoteKey(p.name)}?: ${renderInputTsType(p.type, modelsWithInput)}`).join("; ");
|
|
2156
2170
|
params.push({
|
|
2157
2171
|
name: "query",
|
|
2158
2172
|
type: `{ ${fields} }`,
|
|
@@ -2175,7 +2189,7 @@ function buildMethodParams(route, op, modelsWithInput) {
|
|
|
2175
2189
|
}
|
|
2176
2190
|
if (op.headers) {
|
|
2177
2191
|
if (op.headers.kind === "params") {
|
|
2178
|
-
const fields = op.headers.nodes.map((p) => `${
|
|
2192
|
+
const fields = op.headers.nodes.map((p) => `${quoteKey(p.name)}?: ${renderInputTsType(p.type, modelsWithInput)}`).join("; ");
|
|
2179
2193
|
params.push({
|
|
2180
2194
|
name: "customHeaders",
|
|
2181
2195
|
type: `{ ${fields} }`,
|
|
@@ -2877,7 +2891,7 @@ function generateComments2(model, outPath) {
|
|
|
2877
2891
|
lines.push(` * @deprecated`);
|
|
2878
2892
|
}
|
|
2879
2893
|
if (model.description) {
|
|
2880
|
-
lines.push(` * ${
|
|
2894
|
+
for (const l of escapeJsDocLines(model.description)) lines.push(` * ${l}`);
|
|
2881
2895
|
}
|
|
2882
2896
|
const relPath = outPath ? relative4(dirname4(outPath), model.loc.file) : model.loc.file;
|
|
2883
2897
|
lines.push(` * generated from [${model.name}](file://./${relPath}#L${model.loc.line})`);
|
|
@@ -2941,34 +2955,40 @@ function generateVisibilityModel(model, outPath, modelsWithInput, modelMap) {
|
|
|
2941
2955
|
return lines;
|
|
2942
2956
|
}
|
|
2943
2957
|
__name(generateVisibilityModel, "generateVisibilityModel");
|
|
2958
|
+
function withFieldJsDoc(jsdocParts, line) {
|
|
2959
|
+
if (jsdocParts.length === 0) return line;
|
|
2960
|
+
const contentLines = escapeJsDocLines(jsdocParts.join(" "));
|
|
2961
|
+
if (contentLines.length === 1) {
|
|
2962
|
+
return `/** ${contentLines[0]} */
|
|
2963
|
+
${line}`;
|
|
2964
|
+
}
|
|
2965
|
+
const body = contentLines.map((l) => ` * ${l}`).join("\n");
|
|
2966
|
+
return `/**
|
|
2967
|
+
${body}
|
|
2968
|
+
*/
|
|
2969
|
+
${line}`;
|
|
2970
|
+
}
|
|
2971
|
+
__name(withFieldJsDoc, "withFieldJsDoc");
|
|
2944
2972
|
function renderField2(field) {
|
|
2945
2973
|
const opt = field.optional || field.default !== void 0 ? "?" : "";
|
|
2946
2974
|
let typeStr = renderTsType(field.type);
|
|
2947
2975
|
if (field.nullable) typeStr += " | null";
|
|
2948
|
-
const line = `${
|
|
2976
|
+
const line = `${quoteKey(field.name)}${opt}: ${typeStr};`;
|
|
2949
2977
|
const jsdocParts = [];
|
|
2950
2978
|
if (field.deprecated) jsdocParts.push("@deprecated");
|
|
2951
2979
|
if (field.description) jsdocParts.push(field.description);
|
|
2952
|
-
|
|
2953
|
-
return `/** ${jsdocParts.join(" ")} */
|
|
2954
|
-
${line}`;
|
|
2955
|
-
}
|
|
2956
|
-
return line;
|
|
2980
|
+
return withFieldJsDoc(jsdocParts, line);
|
|
2957
2981
|
}
|
|
2958
2982
|
__name(renderField2, "renderField");
|
|
2959
2983
|
function renderInputField2(field, modelsWithInput) {
|
|
2960
2984
|
const opt = field.optional || field.default !== void 0 ? "?" : "";
|
|
2961
2985
|
let typeStr = renderInputTsType(field.type, modelsWithInput);
|
|
2962
2986
|
if (field.nullable) typeStr += " | null";
|
|
2963
|
-
const line = `${
|
|
2987
|
+
const line = `${quoteKey(field.name)}${opt}: ${typeStr};`;
|
|
2964
2988
|
const jsdocParts = [];
|
|
2965
2989
|
if (field.deprecated) jsdocParts.push("@deprecated");
|
|
2966
2990
|
if (field.description) jsdocParts.push(field.description);
|
|
2967
|
-
|
|
2968
|
-
return `/** ${jsdocParts.join(" ")} */
|
|
2969
|
-
${line}`;
|
|
2970
|
-
}
|
|
2971
|
-
return line;
|
|
2991
|
+
return withFieldJsDoc(jsdocParts, line);
|
|
2972
2992
|
}
|
|
2973
2993
|
__name(renderInputField2, "renderInputField");
|
|
2974
2994
|
function camelToSnake2(s) {
|
|
@@ -3011,22 +3031,457 @@ function renderOutputField(field, outputCase, modelsWithOutput) {
|
|
|
3011
3031
|
const key = applyOutputCase(field.name, outputCase);
|
|
3012
3032
|
let typeStr = renderOutputTsType(field.type, modelsWithOutput);
|
|
3013
3033
|
if (field.nullable) typeStr += " | null";
|
|
3014
|
-
const line = `${
|
|
3034
|
+
const line = `${quoteKey(key)}${opt}: ${typeStr};`;
|
|
3015
3035
|
const jsdocParts = [];
|
|
3016
3036
|
if (field.deprecated) jsdocParts.push("@deprecated");
|
|
3017
3037
|
if (field.description) jsdocParts.push(field.description);
|
|
3018
|
-
|
|
3019
|
-
return `/** ${jsdocParts.join(" ")} */
|
|
3020
|
-
${line}`;
|
|
3021
|
-
}
|
|
3022
|
-
return line;
|
|
3038
|
+
return withFieldJsDoc(jsdocParts, line);
|
|
3023
3039
|
}
|
|
3024
3040
|
__name(renderOutputField, "renderOutputField");
|
|
3025
3041
|
|
|
3042
|
+
// src/codegen-mcp.ts
|
|
3043
|
+
import { resolveModifiers as resolveModifiers3 } from "@contractkit/core";
|
|
3044
|
+
import { basename as basename3, dirname as dirname5, relative as relative5 } from "path";
|
|
3045
|
+
function mcpConfig(op) {
|
|
3046
|
+
return op.mcp && typeof op.mcp === "object" ? op.mcp : void 0;
|
|
3047
|
+
}
|
|
3048
|
+
__name(mcpConfig, "mcpConfig");
|
|
3049
|
+
function hasMcpOperations(root, includeInternal = false) {
|
|
3050
|
+
for (const route of root.routes) {
|
|
3051
|
+
for (const op of route.operations) {
|
|
3052
|
+
if (!op.mcp) continue;
|
|
3053
|
+
if (!includeInternal && resolveModifiers3(route, op).includes("internal")) continue;
|
|
3054
|
+
return true;
|
|
3055
|
+
}
|
|
3056
|
+
}
|
|
3057
|
+
return false;
|
|
3058
|
+
}
|
|
3059
|
+
__name(hasMcpOperations, "hasMcpOperations");
|
|
3060
|
+
function toSnake(s) {
|
|
3061
|
+
return s.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[\s\-.]+/g, "_").toLowerCase().replace(/[^a-z0-9_]/g, "").replace(/_+/g, "_").replace(/^_|_$/g, "");
|
|
3062
|
+
}
|
|
3063
|
+
__name(toSnake, "toSnake");
|
|
3064
|
+
function toPascal(s) {
|
|
3065
|
+
return s.split("_").filter(Boolean).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join("");
|
|
3066
|
+
}
|
|
3067
|
+
__name(toPascal, "toPascal");
|
|
3068
|
+
function inferToolName(method, path) {
|
|
3069
|
+
const parts = [
|
|
3070
|
+
method.toLowerCase()
|
|
3071
|
+
];
|
|
3072
|
+
for (const seg of path.split("/").filter(Boolean)) {
|
|
3073
|
+
if (seg.startsWith("{")) {
|
|
3074
|
+
parts.push("by", toSnake(seg.slice(1, -1)));
|
|
3075
|
+
} else {
|
|
3076
|
+
parts.push(toSnake(seg));
|
|
3077
|
+
}
|
|
3078
|
+
}
|
|
3079
|
+
return parts.filter(Boolean).join("_");
|
|
3080
|
+
}
|
|
3081
|
+
__name(inferToolName, "inferToolName");
|
|
3082
|
+
function deriveToolName(op, route) {
|
|
3083
|
+
const cfg = mcpConfig(op);
|
|
3084
|
+
if (cfg?.name) return cfg.name;
|
|
3085
|
+
if (op.sdk) return toSnake(op.sdk);
|
|
3086
|
+
if (op.name) return toSnake(op.name);
|
|
3087
|
+
return inferToolName(op.method, route.path);
|
|
3088
|
+
}
|
|
3089
|
+
__name(deriveToolName, "deriveToolName");
|
|
3090
|
+
function deriveToolClassName(toolName) {
|
|
3091
|
+
return `${toPascal(toolName)}McpTool`;
|
|
3092
|
+
}
|
|
3093
|
+
__name(deriveToolClassName, "deriveToolClassName");
|
|
3094
|
+
function buildArgsProps(route, op, modelsWithInput) {
|
|
3095
|
+
const props = [];
|
|
3096
|
+
if (route.params) {
|
|
3097
|
+
if (route.params.kind === "params") {
|
|
3098
|
+
for (const node of route.params.nodes) {
|
|
3099
|
+
props.push({
|
|
3100
|
+
key: node.name,
|
|
3101
|
+
expr: renderInputType(node.type, modelsWithInput),
|
|
3102
|
+
optional: false
|
|
3103
|
+
});
|
|
3104
|
+
}
|
|
3105
|
+
} else if (route.params.kind === "ref") {
|
|
3106
|
+
props.push({
|
|
3107
|
+
key: "params",
|
|
3108
|
+
expr: refSchema(route.params.name, modelsWithInput),
|
|
3109
|
+
optional: false
|
|
3110
|
+
});
|
|
3111
|
+
} else {
|
|
3112
|
+
props.push({
|
|
3113
|
+
key: "params",
|
|
3114
|
+
expr: renderInputType(route.params.node, modelsWithInput),
|
|
3115
|
+
optional: false
|
|
3116
|
+
});
|
|
3117
|
+
}
|
|
3118
|
+
}
|
|
3119
|
+
const bodies = op.request?.bodies ?? [];
|
|
3120
|
+
if (bodies.length === 1 && bodies[0].contentType === "multipart/form-data") {
|
|
3121
|
+
props.push({
|
|
3122
|
+
key: "multipartBody",
|
|
3123
|
+
expr: "z.unknown()",
|
|
3124
|
+
optional: false
|
|
3125
|
+
});
|
|
3126
|
+
} else if (bodies.length === 1) {
|
|
3127
|
+
props.push({
|
|
3128
|
+
key: "body",
|
|
3129
|
+
expr: renderInputType(bodies[0].bodyType, modelsWithInput),
|
|
3130
|
+
optional: false
|
|
3131
|
+
});
|
|
3132
|
+
} else if (bodies.length > 1) {
|
|
3133
|
+
props.push({
|
|
3134
|
+
key: "body",
|
|
3135
|
+
expr: "z.unknown()",
|
|
3136
|
+
optional: false
|
|
3137
|
+
});
|
|
3138
|
+
}
|
|
3139
|
+
if (op.query) props.push({
|
|
3140
|
+
key: "query",
|
|
3141
|
+
expr: paramSourceSchema(op.query, modelsWithInput),
|
|
3142
|
+
optional: true
|
|
3143
|
+
});
|
|
3144
|
+
if (op.headers) props.push({
|
|
3145
|
+
key: "headers",
|
|
3146
|
+
expr: paramSourceSchema(op.headers, modelsWithInput),
|
|
3147
|
+
optional: true
|
|
3148
|
+
});
|
|
3149
|
+
return props;
|
|
3150
|
+
}
|
|
3151
|
+
__name(buildArgsProps, "buildArgsProps");
|
|
3152
|
+
function refSchema(name, modelsWithInput) {
|
|
3153
|
+
return modelsWithInput?.has(name) ? `${name}Input` : name;
|
|
3154
|
+
}
|
|
3155
|
+
__name(refSchema, "refSchema");
|
|
3156
|
+
function paramSourceSchema(src, modelsWithInput) {
|
|
3157
|
+
if (src.kind === "ref") return refSchema(src.name, modelsWithInput);
|
|
3158
|
+
if (src.kind === "type") return renderInputType(src.node, modelsWithInput);
|
|
3159
|
+
const fields = src.nodes.map((n) => `${quoteKey(n.name)}: ${renderInputType(n.type, modelsWithInput)}`).join(", ");
|
|
3160
|
+
return `z.object({ ${fields} })`;
|
|
3161
|
+
}
|
|
3162
|
+
__name(paramSourceSchema, "paramSourceSchema");
|
|
3163
|
+
function argsSchemaExpr(props) {
|
|
3164
|
+
if (props.length === 0) return "z.object({})";
|
|
3165
|
+
const fields = props.map((p) => `${quoteKey(p.key)}: ${p.expr}${p.optional ? ".optional()" : ""}`).join(", ");
|
|
3166
|
+
return `z.object({ ${fields} })`;
|
|
3167
|
+
}
|
|
3168
|
+
__name(argsSchemaExpr, "argsSchemaExpr");
|
|
3169
|
+
function primaryResponse(op) {
|
|
3170
|
+
return op.responses.find((r) => r.bodyType) ?? op.responses[0];
|
|
3171
|
+
}
|
|
3172
|
+
__name(primaryResponse, "primaryResponse");
|
|
3173
|
+
function outputSchemaExpr(op) {
|
|
3174
|
+
const body = primaryResponse(op)?.bodyType;
|
|
3175
|
+
if (!body) return void 0;
|
|
3176
|
+
if (body.kind === "ref") return body.name;
|
|
3177
|
+
if (body.kind === "inlineObject") return renderType(body);
|
|
3178
|
+
return void 0;
|
|
3179
|
+
}
|
|
3180
|
+
__name(outputSchemaExpr, "outputSchemaExpr");
|
|
3181
|
+
var HINT_KEYS = [
|
|
3182
|
+
"readOnlyHint",
|
|
3183
|
+
"destructiveHint",
|
|
3184
|
+
"idempotentHint",
|
|
3185
|
+
"openWorldHint"
|
|
3186
|
+
];
|
|
3187
|
+
function annotationsExpr(cfg) {
|
|
3188
|
+
if (!cfg) return void 0;
|
|
3189
|
+
const parts = [];
|
|
3190
|
+
for (const key of HINT_KEYS) {
|
|
3191
|
+
const val = cfg[key];
|
|
3192
|
+
if (val !== void 0) parts.push(`${key}: ${val}`);
|
|
3193
|
+
}
|
|
3194
|
+
return parts.length > 0 ? `{ ${parts.join(", ")} }` : void 0;
|
|
3195
|
+
}
|
|
3196
|
+
__name(annotationsExpr, "annotationsExpr");
|
|
3197
|
+
function walkTypeRefs(type, ids, variant, modelsWithInput) {
|
|
3198
|
+
switch (type.kind) {
|
|
3199
|
+
case "ref":
|
|
3200
|
+
ids.add(variant === "input" ? refSchema(type.name, modelsWithInput) : type.name);
|
|
3201
|
+
break;
|
|
3202
|
+
case "array":
|
|
3203
|
+
walkTypeRefs(type.item, ids, variant, modelsWithInput);
|
|
3204
|
+
break;
|
|
3205
|
+
case "tuple":
|
|
3206
|
+
type.items.forEach((t) => walkTypeRefs(t, ids, variant, modelsWithInput));
|
|
3207
|
+
break;
|
|
3208
|
+
case "record":
|
|
3209
|
+
walkTypeRefs(type.key, ids, variant, modelsWithInput);
|
|
3210
|
+
walkTypeRefs(type.value, ids, variant, modelsWithInput);
|
|
3211
|
+
break;
|
|
3212
|
+
case "union":
|
|
3213
|
+
case "discriminatedUnion":
|
|
3214
|
+
case "intersection":
|
|
3215
|
+
type.members.forEach((t) => walkTypeRefs(t, ids, variant, modelsWithInput));
|
|
3216
|
+
break;
|
|
3217
|
+
case "inlineObject":
|
|
3218
|
+
type.fields.forEach((f) => walkTypeRefs(f.type, ids, variant, modelsWithInput));
|
|
3219
|
+
break;
|
|
3220
|
+
case "lazy":
|
|
3221
|
+
walkTypeRefs(type.inner, ids, variant, modelsWithInput);
|
|
3222
|
+
break;
|
|
3223
|
+
}
|
|
3224
|
+
}
|
|
3225
|
+
__name(walkTypeRefs, "walkTypeRefs");
|
|
3226
|
+
function walkSourceRefs(src, ids, modelsWithInput) {
|
|
3227
|
+
if (!src) return;
|
|
3228
|
+
if (src.kind === "ref") ids.add(refSchema(src.name, modelsWithInput));
|
|
3229
|
+
else if (src.kind === "params") src.nodes.forEach((n) => walkTypeRefs(n.type, ids, "input", modelsWithInput));
|
|
3230
|
+
else walkTypeRefs(src.node, ids, "input", modelsWithInput);
|
|
3231
|
+
}
|
|
3232
|
+
__name(walkSourceRefs, "walkSourceRefs");
|
|
3233
|
+
function collectSchemaIds(ops, modelsWithInput) {
|
|
3234
|
+
const ids = /* @__PURE__ */ new Set();
|
|
3235
|
+
for (const { route, op } of ops) {
|
|
3236
|
+
walkSourceRefs(route.params, ids, modelsWithInput);
|
|
3237
|
+
const bodies = op.request?.bodies ?? [];
|
|
3238
|
+
if (bodies.length === 1 && bodies[0].contentType !== "multipart/form-data") {
|
|
3239
|
+
walkTypeRefs(bodies[0].bodyType, ids, "input", modelsWithInput);
|
|
3240
|
+
}
|
|
3241
|
+
walkSourceRefs(op.query, ids, modelsWithInput);
|
|
3242
|
+
walkSourceRefs(op.headers, ids, modelsWithInput);
|
|
3243
|
+
const body = primaryResponse(op)?.bodyType;
|
|
3244
|
+
if (body && (body.kind === "ref" || body.kind === "inlineObject")) walkTypeRefs(body, ids, "read");
|
|
3245
|
+
}
|
|
3246
|
+
return ids;
|
|
3247
|
+
}
|
|
3248
|
+
__name(collectSchemaIds, "collectSchemaIds");
|
|
3249
|
+
function schemaImportLines(ids, options) {
|
|
3250
|
+
const lines = [];
|
|
3251
|
+
const { modelOutPaths, outPath } = options;
|
|
3252
|
+
if (ids.size === 0) return lines;
|
|
3253
|
+
if (modelOutPaths && outPath) {
|
|
3254
|
+
const byFile = /* @__PURE__ */ new Map();
|
|
3255
|
+
const unresolved = [];
|
|
3256
|
+
for (const id of ids) {
|
|
3257
|
+
const p = modelOutPaths.get(id);
|
|
3258
|
+
if (p) {
|
|
3259
|
+
const group = byFile.get(p) ?? [];
|
|
3260
|
+
group.push(id);
|
|
3261
|
+
byFile.set(p, group);
|
|
3262
|
+
} else {
|
|
3263
|
+
unresolved.push(id);
|
|
3264
|
+
}
|
|
3265
|
+
}
|
|
3266
|
+
const fromDir = dirname5(outPath);
|
|
3267
|
+
for (const [file, names] of byFile) {
|
|
3268
|
+
let rel = relative5(fromDir, file).replace(/\.ts$/, ".js");
|
|
3269
|
+
if (!rel.startsWith(".")) rel = "./" + rel;
|
|
3270
|
+
lines.push(`import { ${names.sort().join(", ")} } from '${rel}';`);
|
|
3271
|
+
}
|
|
3272
|
+
for (const id of unresolved.sort()) lines.push(`import { ${id} } from './${pascalToDotCase(id)}.js';`);
|
|
3273
|
+
} else {
|
|
3274
|
+
for (const id of [
|
|
3275
|
+
...ids
|
|
3276
|
+
].sort()) lines.push(`import { ${id} } from './${pascalToDotCase(id)}.js';`);
|
|
3277
|
+
}
|
|
3278
|
+
return lines;
|
|
3279
|
+
}
|
|
3280
|
+
__name(schemaImportLines, "schemaImportLines");
|
|
3281
|
+
function scalarHelperLines(body) {
|
|
3282
|
+
const lines = [];
|
|
3283
|
+
if (body.includes("_ZodBinary")) {
|
|
3284
|
+
lines.push(`const _ZodBinary = z.custom<Buffer>((val) => Buffer.isBuffer(val), { error: 'Must be binary data' });`);
|
|
3285
|
+
}
|
|
3286
|
+
if (body.includes("_ZodDatetime")) {
|
|
3287
|
+
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' }));`);
|
|
3288
|
+
}
|
|
3289
|
+
if (body.includes("_ZodInterval")) {
|
|
3290
|
+
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()!);`);
|
|
3291
|
+
}
|
|
3292
|
+
if (body.includes("_ZodJson")) {
|
|
3293
|
+
lines.push(`type _JsonValue = string | number | boolean | null | _JsonValue[] | { [key: string]: _JsonValue };`);
|
|
3294
|
+
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)]));`);
|
|
3295
|
+
}
|
|
3296
|
+
return lines;
|
|
3297
|
+
}
|
|
3298
|
+
__name(scalarHelperLines, "scalarHelperLines");
|
|
3299
|
+
function planTools(root, includeInternal) {
|
|
3300
|
+
const plans = [];
|
|
3301
|
+
for (const route of root.routes) {
|
|
3302
|
+
for (const op of route.operations) {
|
|
3303
|
+
if (!op.mcp) continue;
|
|
3304
|
+
if (!includeInternal && resolveModifiers3(route, op).includes("internal")) continue;
|
|
3305
|
+
const toolName = deriveToolName(op, route);
|
|
3306
|
+
const className = deriveToolClassName(toolName);
|
|
3307
|
+
plans.push({
|
|
3308
|
+
route,
|
|
3309
|
+
op,
|
|
3310
|
+
toolName,
|
|
3311
|
+
className,
|
|
3312
|
+
argsConstName: `${toPascal(toolName)}Args`
|
|
3313
|
+
});
|
|
3314
|
+
}
|
|
3315
|
+
}
|
|
3316
|
+
return plans;
|
|
3317
|
+
}
|
|
3318
|
+
__name(planTools, "planTools");
|
|
3319
|
+
function renderToolClass(plan, file, options) {
|
|
3320
|
+
const { route, op, toolName, className, argsConstName } = plan;
|
|
3321
|
+
const cfg = mcpConfig(op);
|
|
3322
|
+
const lines = [];
|
|
3323
|
+
const relFile = options.outPath ? relative5(dirname5(options.outPath), file) : file;
|
|
3324
|
+
lines.push("/**");
|
|
3325
|
+
lines.push(` * from [${basename3(file)}](file://./${relFile}#L${op.loc.line})`);
|
|
3326
|
+
lines.push(" */");
|
|
3327
|
+
lines.push("@Injectable()");
|
|
3328
|
+
lines.push(`export class ${className} implements McpToolHandler {`);
|
|
3329
|
+
lines.push(" readonly definition: Tool = {");
|
|
3330
|
+
lines.push(` name: '${escapeSingleQuoted(toolName)}',`);
|
|
3331
|
+
if (cfg?.title) lines.push(` title: '${escapeSingleQuoted(cfg.title)}',`);
|
|
3332
|
+
const desc = cfg?.description ?? op.description ?? route.description;
|
|
3333
|
+
if (desc) lines.push(` description: '${escapeSingleQuoted(desc)}',`);
|
|
3334
|
+
lines.push(` inputSchema: z.toJSONSchema(${argsConstName}, { unrepresentable: 'any' }) as Tool['inputSchema'],`);
|
|
3335
|
+
const outExpr = outputSchemaExpr(op);
|
|
3336
|
+
if (outExpr) lines.push(` outputSchema: z.toJSONSchema(${outExpr}, { unrepresentable: 'any' }) as Tool['outputSchema'],`);
|
|
3337
|
+
const annotations = annotationsExpr(cfg);
|
|
3338
|
+
if (annotations) lines.push(` annotations: ${annotations},`);
|
|
3339
|
+
lines.push(" };");
|
|
3340
|
+
lines.push("");
|
|
3341
|
+
const service = inferService(op, route, file);
|
|
3342
|
+
lines.push(` constructor(private readonly service: ${service.className}) {}`);
|
|
3343
|
+
lines.push("");
|
|
3344
|
+
const props = buildArgsProps(route, op, options.modelsWithInput);
|
|
3345
|
+
const destructure = props.map((p) => p.key);
|
|
3346
|
+
const callArgs = buildArgs(route, op);
|
|
3347
|
+
const isVoid = !primaryResponse(op)?.bodyType;
|
|
3348
|
+
const structured = !!outExpr;
|
|
3349
|
+
lines.push(" async handle(args: Record<string, unknown>, _context: McpToolContext): Promise<CallToolResult> {");
|
|
3350
|
+
if (destructure.length > 0) {
|
|
3351
|
+
lines.push(` const { ${destructure.join(", ")} } = await parseAndValidate(args, ${argsConstName});`);
|
|
3352
|
+
}
|
|
3353
|
+
if (isVoid) {
|
|
3354
|
+
lines.push(` await this.service.${service.methodName}(${callArgs});`);
|
|
3355
|
+
lines.push(` return { content: [{ type: 'text', text: 'OK' }] };`);
|
|
3356
|
+
} else {
|
|
3357
|
+
lines.push(` const result = await this.service.${service.methodName}(${callArgs});`);
|
|
3358
|
+
if (structured) {
|
|
3359
|
+
lines.push(` return { content: [{ type: 'text', text: JSON.stringify(result) }], structuredContent: result };`);
|
|
3360
|
+
} else {
|
|
3361
|
+
lines.push(` return { content: [{ type: 'text', text: JSON.stringify(result) }] };`);
|
|
3362
|
+
}
|
|
3363
|
+
}
|
|
3364
|
+
lines.push(" }");
|
|
3365
|
+
lines.push("}");
|
|
3366
|
+
return lines;
|
|
3367
|
+
}
|
|
3368
|
+
__name(renderToolClass, "renderToolClass");
|
|
3369
|
+
function deriveMcpRegisterFnName(file) {
|
|
3370
|
+
return `register${deriveBaseName(file)}McpTools`;
|
|
3371
|
+
}
|
|
3372
|
+
__name(deriveMcpRegisterFnName, "deriveMcpRegisterFnName");
|
|
3373
|
+
function generateMcpFile(root, options = {}) {
|
|
3374
|
+
const includeInternal = options.includeInternal ?? false;
|
|
3375
|
+
const plans = planTools(root, includeInternal);
|
|
3376
|
+
const argsConsts = plans.map((p) => `const ${p.argsConstName} = ${argsSchemaExpr(buildArgsProps(p.route, p.op, options.modelsWithInput))};`);
|
|
3377
|
+
const classes = plans.map((p) => renderToolClass(p, root.file, options).join("\n"));
|
|
3378
|
+
const registerFn = [];
|
|
3379
|
+
registerFn.push(`/** Add this file's tools to the shared catalog. */`);
|
|
3380
|
+
registerFn.push(`export function ${deriveMcpRegisterFnName(root.file)}(map: McpToolHandlerMap, container: Container): void {`);
|
|
3381
|
+
for (const p of plans) registerFn.push(` map.set('${escapeSingleQuoted(p.toolName)}', container.get(${p.className}));`);
|
|
3382
|
+
registerFn.push("}");
|
|
3383
|
+
const bodyCore = [
|
|
3384
|
+
argsConsts.join("\n"),
|
|
3385
|
+
classes.join("\n\n"),
|
|
3386
|
+
registerFn.join("\n")
|
|
3387
|
+
].filter(Boolean).join("\n\n");
|
|
3388
|
+
const helperConsts = scalarHelperLines(bodyCore);
|
|
3389
|
+
const bodyWithHelpers = [
|
|
3390
|
+
helperConsts.join("\n"),
|
|
3391
|
+
bodyCore
|
|
3392
|
+
].filter(Boolean).join("\n\n");
|
|
3393
|
+
const needsParseAndValidate = plans.some((p) => buildArgsProps(p.route, p.op, options.modelsWithInput).length > 0);
|
|
3394
|
+
const imports = [];
|
|
3395
|
+
imports.push(`import { Injectable, type Container } from 'injectkit';`);
|
|
3396
|
+
imports.push(`import { z } from 'zod';`);
|
|
3397
|
+
const luxon = [];
|
|
3398
|
+
if (/\bDateTime\b/.test(bodyWithHelpers)) luxon.push("DateTime");
|
|
3399
|
+
if (/\bInterval\b/.test(bodyWithHelpers)) luxon.push("Interval");
|
|
3400
|
+
if (/\bDuration\b/.test(bodyWithHelpers)) luxon.push("Duration");
|
|
3401
|
+
if (luxon.length > 0) imports.push(`import { ${luxon.join(", ")} } from 'luxon';`);
|
|
3402
|
+
imports.push(`import type { CallToolResult, Tool } from '@modelcontextprotocol/sdk/types.js';`);
|
|
3403
|
+
imports.push(`import type { McpToolHandler, McpToolHandlerMap, McpToolContext } from '@maroonedsoftware/mcp';`);
|
|
3404
|
+
if (needsParseAndValidate) imports.push(`import { parseAndValidate } from '@maroonedsoftware/zod';`);
|
|
3405
|
+
const serviceModules = /* @__PURE__ */ new Map();
|
|
3406
|
+
for (const p of plans) {
|
|
3407
|
+
const svc = inferService(p.op, p.route, root.file).className;
|
|
3408
|
+
if (!serviceModules.has(svc)) {
|
|
3409
|
+
serviceModules.set(svc, root.services?.[svc] ?? root.meta[svc] ?? deriveModulePath(svc, options.servicePathTemplate));
|
|
3410
|
+
}
|
|
3411
|
+
}
|
|
3412
|
+
for (const [svc, mod] of [
|
|
3413
|
+
...serviceModules.entries()
|
|
3414
|
+
].sort(([a], [b]) => a.localeCompare(b))) {
|
|
3415
|
+
imports.push(`import { ${svc} } from '${mod}';`);
|
|
3416
|
+
}
|
|
3417
|
+
imports.push(...schemaImportLines(collectSchemaIds(plans, options.modelsWithInput), options));
|
|
3418
|
+
const relFile = options.outPath ? relative5(dirname5(options.outPath), root.file) : root.file;
|
|
3419
|
+
const header = `// Auto-generated MCP tools
|
|
3420
|
+
// generated from [${basename3(root.file)}](file://./${relFile})`;
|
|
3421
|
+
return `${header}
|
|
3422
|
+
${imports.join("\n")}
|
|
3423
|
+
|
|
3424
|
+
${bodyWithHelpers}
|
|
3425
|
+
`;
|
|
3426
|
+
}
|
|
3427
|
+
__name(generateMcpFile, "generateMcpFile");
|
|
3428
|
+
function generateMcpAggregator(entries) {
|
|
3429
|
+
const sorted = [
|
|
3430
|
+
...entries
|
|
3431
|
+
].sort((a, b) => a.registerFn.localeCompare(b.registerFn));
|
|
3432
|
+
const lines = [];
|
|
3433
|
+
lines.push(`import { type Container } from 'injectkit';`);
|
|
3434
|
+
lines.push(`import { McpToolHandlerMap } from '@maroonedsoftware/mcp';`);
|
|
3435
|
+
for (const e of sorted) lines.push(`import { ${e.registerFn} } from '${e.importPath}';`);
|
|
3436
|
+
lines.push("");
|
|
3437
|
+
lines.push("/** Build + register the MCP tool catalog. Call once at startup. */");
|
|
3438
|
+
lines.push("export function registerMcpTools(container: Container): McpToolHandlerMap {");
|
|
3439
|
+
lines.push(" const map = new McpToolHandlerMap();");
|
|
3440
|
+
for (const e of sorted) lines.push(` ${e.registerFn}(map, container);`);
|
|
3441
|
+
lines.push(" container.register(McpToolHandlerMap, { useValue: map });");
|
|
3442
|
+
lines.push(" return map;");
|
|
3443
|
+
lines.push("}");
|
|
3444
|
+
return lines.join("\n") + "\n";
|
|
3445
|
+
}
|
|
3446
|
+
__name(generateMcpAggregator, "generateMcpAggregator");
|
|
3447
|
+
function generateMcpRouter(options = {}) {
|
|
3448
|
+
const path = options.path ?? "/mcp";
|
|
3449
|
+
return `import { ServerKitRouter, requireSignature } from '@maroonedsoftware/koa';
|
|
3450
|
+
import { McpDispatcher, createMcpRequestContext, MCP_AUTH_POLICY } from '@maroonedsoftware/mcp';
|
|
3451
|
+
|
|
3452
|
+
/** Mount the MCP endpoint onto a ServerKit router. Call \`registerMcpTools(container)\` at startup. */
|
|
3453
|
+
export function mountMcp(router: ReturnType<typeof ServerKitRouter>): void {
|
|
3454
|
+
router.post('${path}', requireSignature('mcp', { policy: MCP_AUTH_POLICY }), async (ctx) => {
|
|
3455
|
+
const dispatcher = ctx.container.get(McpDispatcher);
|
|
3456
|
+
const context = createMcpRequestContext({ requestId: ctx.requestId, logger: ctx.logger });
|
|
3457
|
+
if (dispatcher.sessionMode === 'stateful') {
|
|
3458
|
+
ctx.respond = false;
|
|
3459
|
+
await dispatcher.dispatchStateful(
|
|
3460
|
+
{ req: ctx.req, res: ctx.res, body: ctx.request.body, sessionId: ctx.get('mcp-session-id') },
|
|
3461
|
+
context,
|
|
3462
|
+
);
|
|
3463
|
+
} else {
|
|
3464
|
+
const response = await dispatcher.dispatch(JSON.parse(ctx.rawBody), context);
|
|
3465
|
+
if (response) ctx.body = response;
|
|
3466
|
+
}
|
|
3467
|
+
});
|
|
3468
|
+
}
|
|
3469
|
+
`;
|
|
3470
|
+
}
|
|
3471
|
+
__name(generateMcpRouter, "generateMcpRouter");
|
|
3472
|
+
|
|
3026
3473
|
// src/path-utils.ts
|
|
3027
|
-
import { resolve, join, relative as
|
|
3474
|
+
import { resolve, join, relative as relative6, dirname as dirname6, isAbsolute } from "path";
|
|
3028
3475
|
import { collectTypeRefs as collectTypeRefs2, collectPublicTypeNames } from "@contractkit/core";
|
|
3029
3476
|
var TEMPLATE_VAR_RE = /\{\w+\}/;
|
|
3477
|
+
function assertWithinBase(baseOutDir, outPath) {
|
|
3478
|
+
const rel = relative6(resolve(baseOutDir), resolve(outPath));
|
|
3479
|
+
if (rel === "" || rel.startsWith("..") || isAbsolute(rel)) {
|
|
3480
|
+
throw new Error(`Refusing to emit outside output directory: resolved path "${outPath}" escapes "${baseOutDir}" (check options { keys } values used in output path templates)`);
|
|
3481
|
+
}
|
|
3482
|
+
return outPath;
|
|
3483
|
+
}
|
|
3484
|
+
__name(assertWithinBase, "assertWithinBase");
|
|
3030
3485
|
function resolveTemplate(template, vars) {
|
|
3031
3486
|
return template.replace(/\{(\w+)\}/g, (_, key) => vars[key] ?? `{${key}}`);
|
|
3032
3487
|
}
|
|
@@ -3038,7 +3493,7 @@ function includesFilename(p) {
|
|
|
3038
3493
|
__name(includesFilename, "includesFilename");
|
|
3039
3494
|
function commonDir(files, rootDir) {
|
|
3040
3495
|
if (files.length === 0) return resolve(rootDir);
|
|
3041
|
-
const parts = files.map((f) =>
|
|
3496
|
+
const parts = files.map((f) => dirname6(f).split("/"));
|
|
3042
3497
|
const first = parts[0];
|
|
3043
3498
|
let depth = first.length;
|
|
3044
3499
|
for (const p of parts) {
|
|
@@ -3054,7 +3509,7 @@ function commonDir(files, rootDir) {
|
|
|
3054
3509
|
__name(commonDir, "commonDir");
|
|
3055
3510
|
function computeOpOutPath(filePath, baseDir, output, defaultSuffix, commonRoot, meta = {}) {
|
|
3056
3511
|
const baseName = filePath.split("/").pop();
|
|
3057
|
-
const relDir =
|
|
3512
|
+
const relDir = relative6(commonRoot, dirname6(filePath));
|
|
3058
3513
|
const filename = baseName.replace(/\.ck$/, "");
|
|
3059
3514
|
const defaultName = `${filename}${defaultSuffix}`;
|
|
3060
3515
|
const baseOutDir = resolve(baseDir);
|
|
@@ -3065,14 +3520,14 @@ function computeOpOutPath(filePath, baseDir, output, defaultSuffix, commonRoot,
|
|
|
3065
3520
|
ext: "ck",
|
|
3066
3521
|
...meta
|
|
3067
3522
|
});
|
|
3068
|
-
if (includesFilename(resolved)) return join(baseOutDir, resolved);
|
|
3069
|
-
return join(baseOutDir, resolved, defaultName);
|
|
3523
|
+
if (includesFilename(resolved)) return assertWithinBase(baseOutDir, join(baseOutDir, resolved));
|
|
3524
|
+
return assertWithinBase(baseOutDir, join(baseOutDir, resolved, defaultName));
|
|
3070
3525
|
}
|
|
3071
3526
|
if (output) {
|
|
3072
|
-
if (includesFilename(output)) return join(baseOutDir, output);
|
|
3073
|
-
return join(baseOutDir, output, relDir, defaultName);
|
|
3527
|
+
if (includesFilename(output)) return assertWithinBase(baseOutDir, join(baseOutDir, output));
|
|
3528
|
+
return assertWithinBase(baseOutDir, join(baseOutDir, output, relDir, defaultName));
|
|
3074
3529
|
}
|
|
3075
|
-
return join(baseOutDir, relDir, defaultName);
|
|
3530
|
+
return assertWithinBase(baseOutDir, join(baseOutDir, relDir, defaultName));
|
|
3076
3531
|
}
|
|
3077
3532
|
__name(computeOpOutPath, "computeOpOutPath");
|
|
3078
3533
|
function computeContractOutPath(filePath, baseDir, output, defaultSuffix, commonRoot, meta = {}) {
|
|
@@ -3084,7 +3539,7 @@ function computeSdkOutPath(filePath, rootDir, clientOutput, commonRoot, meta = {
|
|
|
3084
3539
|
const baseName = filePath.split("/").pop();
|
|
3085
3540
|
const defaultOutName = baseName.replace(/\.ck$/, ".client.ts");
|
|
3086
3541
|
const baseOutDir = resolve(rootDir);
|
|
3087
|
-
const relDir =
|
|
3542
|
+
const relDir = relative6(commonRoot, dirname6(filePath));
|
|
3088
3543
|
const filename = baseName.replace(/\.ck$/, "");
|
|
3089
3544
|
if (clientOutput && TEMPLATE_VAR_RE.test(clientOutput)) {
|
|
3090
3545
|
const resolved = resolveTemplate(clientOutput, {
|
|
@@ -3093,14 +3548,14 @@ function computeSdkOutPath(filePath, rootDir, clientOutput, commonRoot, meta = {
|
|
|
3093
3548
|
ext: "ck",
|
|
3094
3549
|
...meta
|
|
3095
3550
|
});
|
|
3096
|
-
if (includesFilename(resolved)) return join(baseOutDir, resolved);
|
|
3097
|
-
return join(baseOutDir, resolved, defaultOutName);
|
|
3551
|
+
if (includesFilename(resolved)) return assertWithinBase(baseOutDir, join(baseOutDir, resolved));
|
|
3552
|
+
return assertWithinBase(baseOutDir, join(baseOutDir, resolved, defaultOutName));
|
|
3098
3553
|
}
|
|
3099
3554
|
if (clientOutput) {
|
|
3100
|
-
if (includesFilename(clientOutput)) return join(baseOutDir, clientOutput);
|
|
3101
|
-
return join(baseOutDir, clientOutput, relDir, defaultOutName);
|
|
3555
|
+
if (includesFilename(clientOutput)) return assertWithinBase(baseOutDir, join(baseOutDir, clientOutput));
|
|
3556
|
+
return assertWithinBase(baseOutDir, join(baseOutDir, clientOutput, relDir, defaultOutName));
|
|
3102
3557
|
}
|
|
3103
|
-
return join(baseOutDir, relDir, defaultOutName);
|
|
3558
|
+
return assertWithinBase(baseOutDir, join(baseOutDir, relDir, defaultOutName));
|
|
3104
3559
|
}
|
|
3105
3560
|
__name(computeSdkOutPath, "computeSdkOutPath");
|
|
3106
3561
|
function computeSdkAreaClientOutPath(area, rootDir, clientOutput) {
|
|
@@ -3121,14 +3576,14 @@ function computeSdkAreaClientOutPath(area, rootDir, clientOutput) {
|
|
|
3121
3576
|
subarea: ""
|
|
3122
3577
|
});
|
|
3123
3578
|
const cleaned = fixHiddenSegment(resolved.replace(/\/+/g, "/").replace(/^\//, ""));
|
|
3124
|
-
if (includesFilename(cleaned)) return join(baseOutDir, cleaned);
|
|
3125
|
-
return join(baseOutDir, cleaned, `${filename}.client.ts`);
|
|
3579
|
+
if (includesFilename(cleaned)) return assertWithinBase(baseOutDir, join(baseOutDir, cleaned));
|
|
3580
|
+
return assertWithinBase(baseOutDir, join(baseOutDir, cleaned, `${filename}.client.ts`));
|
|
3126
3581
|
}
|
|
3127
3582
|
if (clientOutput) {
|
|
3128
|
-
if (includesFilename(clientOutput)) return join(baseOutDir, clientOutput);
|
|
3129
|
-
return join(baseOutDir, clientOutput, `${filename}.client.ts`);
|
|
3583
|
+
if (includesFilename(clientOutput)) return assertWithinBase(baseOutDir, join(baseOutDir, clientOutput));
|
|
3584
|
+
return assertWithinBase(baseOutDir, join(baseOutDir, clientOutput, `${filename}.client.ts`));
|
|
3130
3585
|
}
|
|
3131
|
-
return join(baseOutDir, `${filename}.client.ts`);
|
|
3586
|
+
return assertWithinBase(baseOutDir, join(baseOutDir, `${filename}.client.ts`));
|
|
3132
3587
|
}
|
|
3133
3588
|
__name(computeSdkAreaClientOutPath, "computeSdkAreaClientOutPath");
|
|
3134
3589
|
function computeSdkTypeOutPath(filePath, rootDir, typeOutput, commonRoot, meta = {}) {
|
|
@@ -3136,7 +3591,7 @@ function computeSdkTypeOutPath(filePath, rootDir, typeOutput, commonRoot, meta =
|
|
|
3136
3591
|
const baseName = filePath.split("/").pop();
|
|
3137
3592
|
const defaultOutName = baseName.replace(/\.ck$/, ".ts");
|
|
3138
3593
|
const baseOutDir = resolve(rootDir);
|
|
3139
|
-
const relDir =
|
|
3594
|
+
const relDir = relative6(commonRoot, dirname6(filePath));
|
|
3140
3595
|
const filename = baseName.replace(/\.ck$/, "");
|
|
3141
3596
|
if (TEMPLATE_VAR_RE.test(typeOutput)) {
|
|
3142
3597
|
const resolved = resolveTemplate(typeOutput, {
|
|
@@ -3145,17 +3600,17 @@ function computeSdkTypeOutPath(filePath, rootDir, typeOutput, commonRoot, meta =
|
|
|
3145
3600
|
ext: "ck",
|
|
3146
3601
|
...meta
|
|
3147
3602
|
});
|
|
3148
|
-
if (includesFilename(resolved)) return join(baseOutDir, resolved);
|
|
3149
|
-
return join(baseOutDir, resolved, defaultOutName);
|
|
3603
|
+
if (includesFilename(resolved)) return assertWithinBase(baseOutDir, join(baseOutDir, resolved));
|
|
3604
|
+
return assertWithinBase(baseOutDir, join(baseOutDir, resolved, defaultOutName));
|
|
3150
3605
|
}
|
|
3151
|
-
if (includesFilename(typeOutput)) return join(baseOutDir, typeOutput);
|
|
3152
|
-
return join(baseOutDir, typeOutput, relDir, defaultOutName);
|
|
3606
|
+
if (includesFilename(typeOutput)) return assertWithinBase(baseOutDir, join(baseOutDir, typeOutput));
|
|
3607
|
+
return assertWithinBase(baseOutDir, join(baseOutDir, typeOutput, relDir, defaultOutName));
|
|
3153
3608
|
}
|
|
3154
3609
|
__name(computeSdkTypeOutPath, "computeSdkTypeOutPath");
|
|
3155
3610
|
function generateBarrelFiles(contractPaths) {
|
|
3156
3611
|
const byDir = /* @__PURE__ */ new Map();
|
|
3157
3612
|
for (const outPath of contractPaths) {
|
|
3158
|
-
const dir =
|
|
3613
|
+
const dir = dirname6(outPath);
|
|
3159
3614
|
const group = byDir.get(dir) ?? [];
|
|
3160
3615
|
group.push(outPath);
|
|
3161
3616
|
byDir.set(dir, group);
|
|
@@ -3249,6 +3704,7 @@ async function runTypescriptCodegen(inputs, ctx, config, rootDir) {
|
|
|
3249
3704
|
if (config.sdk) collectSdkOutput(config.sdk, rootDir, inputs, units, globalFiles);
|
|
3250
3705
|
if (config.zod) collectZodOutput(config.zod, rootDir, inputs, units);
|
|
3251
3706
|
if (config.types) collectTypesOutput(config.types, rootDir, inputs, units);
|
|
3707
|
+
if (config.mcp) collectMcpOutput(config.mcp, config, rootDir, inputs, units, globalFiles);
|
|
3252
3708
|
const result = runIncrementalCodegen({
|
|
3253
3709
|
codegenVersion: TYPESCRIPT_CODEGEN_VERSION,
|
|
3254
3710
|
prevManifest,
|
|
@@ -3455,7 +3911,7 @@ function collectSdkOutput(config, rootDir, inputs, units, globalFiles) {
|
|
|
3455
3911
|
const sdkEntryPath = sdkOutput ? join2(sdkBase, TEMPLATE_VAR_RE.test(sdkOutput) ? resolveTemplate(sdkOutput, {
|
|
3456
3912
|
name: sdkName ?? "sdk"
|
|
3457
3913
|
}) : sdkOutput) : join2(sdkBase, "sdk.ts");
|
|
3458
|
-
const sdkOptionsPath = join2(
|
|
3914
|
+
const sdkOptionsPath = join2(dirname7(sdkEntryPath), "sdk-options.ts");
|
|
3459
3915
|
const subConfigKey = stableSubConfig(config);
|
|
3460
3916
|
const modelsWithInput = inputs.modelsWithInput;
|
|
3461
3917
|
const modelsWithOutput = inputs.modelsWithOutput;
|
|
@@ -3514,7 +3970,7 @@ function collectSdkOutput(config, rootDir, inputs, units, globalFiles) {
|
|
|
3514
3970
|
modelsWithOutput
|
|
3515
3971
|
});
|
|
3516
3972
|
} else {
|
|
3517
|
-
let rel =
|
|
3973
|
+
let rel = relative7(dirname7(typeOutPath), sdkOptionsPath).replace(/\.ts$/, ".js");
|
|
3518
3974
|
if (!rel.startsWith(".")) rel = "./" + rel;
|
|
3519
3975
|
content = generatePlainTypes(ast, {
|
|
3520
3976
|
modelOutPaths: sdkModelOutPaths,
|
|
@@ -3655,12 +4111,12 @@ function collectSdkOutput(config, rootDir, inputs, units, globalFiles) {
|
|
|
3655
4111
|
const hasAnything = sdkClientInfos.length > 0 || areaBuckets.size > 0;
|
|
3656
4112
|
const areaClientOutPaths = /* @__PURE__ */ new Map();
|
|
3657
4113
|
if (hasAnything) {
|
|
3658
|
-
const sdkEntryDir =
|
|
3659
|
-
const sdkOptionsRel =
|
|
4114
|
+
const sdkEntryDir = dirname7(sdkEntryPath);
|
|
4115
|
+
const sdkOptionsRel = relative7(sdkEntryDir, sdkOptionsPath).replace(/\.ts$/, ".js");
|
|
3660
4116
|
const sdkOptionsImportPath = sdkOptionsRel.startsWith(".") ? sdkOptionsRel : "./" + sdkOptionsRel;
|
|
3661
4117
|
const sdkClassName = sdkName ? sdkName.split(/[-._\s]+/).map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join("") + "Sdk" : "Sdk";
|
|
3662
4118
|
const toClientImport = /* @__PURE__ */ __name((sourceDir, info) => {
|
|
3663
|
-
let rel =
|
|
4119
|
+
let rel = relative7(sourceDir, info.outPath).replace(/\.ts$/, ".js");
|
|
3664
4120
|
if (!rel.startsWith(".")) rel = "./" + rel;
|
|
3665
4121
|
return {
|
|
3666
4122
|
className: info.className,
|
|
@@ -3689,7 +4145,7 @@ function collectSdkOutput(config, rootDir, inputs, units, globalFiles) {
|
|
|
3689
4145
|
});
|
|
3690
4146
|
const subareaClients = bucket.leaves.sort((a, b) => a.subarea.localeCompare(b.subarea)).map((l) => ({
|
|
3691
4147
|
propertyName: deriveSubareaPropertyName(l.subarea),
|
|
3692
|
-
client: toClientImport(
|
|
4148
|
+
client: toClientImport(dirname7(areaClientOutPath), {
|
|
3693
4149
|
outPath: l.outPath,
|
|
3694
4150
|
className: deriveSubareaClientClassName(area, l.subarea),
|
|
3695
4151
|
propertyName: deriveSubareaPropertyName(l.subarea)
|
|
@@ -3760,23 +4216,23 @@ function collectSdkOutput(config, rootDir, inputs, units, globalFiles) {
|
|
|
3760
4216
|
})
|
|
3761
4217
|
});
|
|
3762
4218
|
}
|
|
3763
|
-
const sdkSrcDir =
|
|
4219
|
+
const sdkSrcDir = dirname7(sdkEntryPath);
|
|
3764
4220
|
const sdkTypeBarrels = generateBarrelFiles(sdkTypePaths);
|
|
3765
4221
|
for (const barrel of sdkTypeBarrels) globalFiles.push({
|
|
3766
4222
|
relativePath: barrel.outPath,
|
|
3767
4223
|
content: barrel.content
|
|
3768
4224
|
});
|
|
3769
4225
|
const rootExports = [
|
|
3770
|
-
`export * from './${
|
|
4226
|
+
`export * from './${basename4(sdkOptionsPath).replace(/\.ts$/, ".js")}';`
|
|
3771
4227
|
];
|
|
3772
|
-
if (hasAnything) rootExports.push(`export * from './${
|
|
4228
|
+
if (hasAnything) rootExports.push(`export * from './${basename4(sdkEntryPath).replace(/\.ts$/, ".js")}';`);
|
|
3773
4229
|
for (const c of sdkClientInfos) {
|
|
3774
|
-
let rel =
|
|
4230
|
+
let rel = relative7(sdkSrcDir, c.outPath).replace(/\.ts$/, ".js");
|
|
3775
4231
|
if (!rel.startsWith(".")) rel = "./" + rel;
|
|
3776
4232
|
rootExports.push(`export * from '${rel}';`);
|
|
3777
4233
|
}
|
|
3778
4234
|
for (const barrel of sdkTypeBarrels) {
|
|
3779
|
-
let rel =
|
|
4235
|
+
let rel = relative7(sdkSrcDir, barrel.outPath).replace(/\.ts$/, ".js");
|
|
3780
4236
|
if (!rel.startsWith(".")) rel = "./" + rel;
|
|
3781
4237
|
rootExports.push(`export * from '${rel}';`);
|
|
3782
4238
|
}
|
|
@@ -3920,6 +4376,114 @@ function collectTypesOutput(config, rootDir, inputs, units) {
|
|
|
3920
4376
|
}
|
|
3921
4377
|
}
|
|
3922
4378
|
__name(collectTypesOutput, "collectTypesOutput");
|
|
4379
|
+
function resolveMcpModelOutPaths(config, rootDir, contractRoots, commonRoot, modelsWithInput, modelsWithOutput) {
|
|
4380
|
+
const map = /* @__PURE__ */ new Map();
|
|
4381
|
+
let base;
|
|
4382
|
+
let template;
|
|
4383
|
+
let suffix;
|
|
4384
|
+
if (config.mcp?.output?.types) {
|
|
4385
|
+
base = resolve2(rootDir, config.mcp.baseDir ?? ".");
|
|
4386
|
+
template = config.mcp.output.types;
|
|
4387
|
+
suffix = ".ts";
|
|
4388
|
+
} else if (config.server?.zod && config.server.output?.types) {
|
|
4389
|
+
base = resolve2(rootDir, config.server.baseDir ?? ".");
|
|
4390
|
+
template = config.server.output.types;
|
|
4391
|
+
suffix = ".ts";
|
|
4392
|
+
} else if (config.zod) {
|
|
4393
|
+
base = resolve2(rootDir, config.zod.baseDir ?? ".");
|
|
4394
|
+
template = config.zod.output;
|
|
4395
|
+
suffix = ".schema.ts";
|
|
4396
|
+
} else {
|
|
4397
|
+
return map;
|
|
4398
|
+
}
|
|
4399
|
+
for (const ast of contractRoots) {
|
|
4400
|
+
const outPath = computeContractOutPath(ast.file, base, template, suffix, commonRoot, ast.meta);
|
|
4401
|
+
for (const model of ast.models) {
|
|
4402
|
+
map.set(model.name, outPath);
|
|
4403
|
+
if (modelsWithInput.has(model.name)) map.set(`${model.name}Input`, outPath);
|
|
4404
|
+
if (modelsWithOutput.has(model.name)) map.set(`${model.name}Output`, outPath);
|
|
4405
|
+
}
|
|
4406
|
+
}
|
|
4407
|
+
return map;
|
|
4408
|
+
}
|
|
4409
|
+
__name(resolveMcpModelOutPaths, "resolveMcpModelOutPaths");
|
|
4410
|
+
function collectMcpOutput(config, fullConfig, rootDir, inputs, units, globalFiles) {
|
|
4411
|
+
const mcpBase = resolve2(rootDir, config.baseDir ?? ".");
|
|
4412
|
+
const modelsWithInput = inputs.modelsWithInput;
|
|
4413
|
+
const modelsWithOutput = inputs.modelsWithOutput;
|
|
4414
|
+
const modelMap = buildModelMap(inputs.contractRoots);
|
|
4415
|
+
const allFiles = [
|
|
4416
|
+
...inputs.contractRoots.map((r) => r.file),
|
|
4417
|
+
...inputs.opRoots.map((r) => r.file)
|
|
4418
|
+
];
|
|
4419
|
+
const commonRoot = commonDir(allFiles, rootDir);
|
|
4420
|
+
const subConfigKey = stableSubConfig(config);
|
|
4421
|
+
const includeInternal = config.includeInternal ?? false;
|
|
4422
|
+
const modelOutPaths = resolveMcpModelOutPaths(fullConfig, rootDir, inputs.contractRoots, commonRoot, modelsWithInput, modelsWithOutput);
|
|
4423
|
+
const entries = [];
|
|
4424
|
+
for (const ast of inputs.opRoots) {
|
|
4425
|
+
if (!hasMcpOperations(ast, includeInternal)) continue;
|
|
4426
|
+
const outPath = computeOpOutPath(ast.file, mcpBase, config.output?.tools, ".mcp.ts", commonRoot, ast.meta);
|
|
4427
|
+
const refs = collectOpRootRefs(ast, modelMap);
|
|
4428
|
+
const fingerprint = hashFingerprint({
|
|
4429
|
+
kind: "mcp-tools",
|
|
4430
|
+
v: TYPESCRIPT_CODEGEN_VERSION,
|
|
4431
|
+
outPath,
|
|
4432
|
+
root: ast,
|
|
4433
|
+
outPathSlice: sliceOutPathMap(refs, modelOutPaths, modelsWithInput, modelsWithOutput),
|
|
4434
|
+
modelsWithInput: sliceModelSet(refs, /* @__PURE__ */ new Set(), modelsWithInput),
|
|
4435
|
+
modelsWithOutput: sliceModelSet(refs, /* @__PURE__ */ new Set(), modelsWithOutput),
|
|
4436
|
+
servicePathTemplate: config.servicePathTemplate ?? null,
|
|
4437
|
+
includeInternal,
|
|
4438
|
+
sub: subConfigKey
|
|
4439
|
+
});
|
|
4440
|
+
units.push({
|
|
4441
|
+
key: `mcp-tools::${outPath}`,
|
|
4442
|
+
fingerprint,
|
|
4443
|
+
render: /* @__PURE__ */ __name(() => [
|
|
4444
|
+
{
|
|
4445
|
+
relativePath: outPath,
|
|
4446
|
+
content: generateMcpFile(ast, {
|
|
4447
|
+
outPath,
|
|
4448
|
+
modelOutPaths,
|
|
4449
|
+
modelsWithInput,
|
|
4450
|
+
modelsWithOutput,
|
|
4451
|
+
servicePathTemplate: config.servicePathTemplate,
|
|
4452
|
+
includeInternal
|
|
4453
|
+
})
|
|
4454
|
+
}
|
|
4455
|
+
], "render")
|
|
4456
|
+
});
|
|
4457
|
+
entries.push({
|
|
4458
|
+
outPath,
|
|
4459
|
+
registerFn: deriveMcpRegisterFnName(ast.file)
|
|
4460
|
+
});
|
|
4461
|
+
}
|
|
4462
|
+
if (entries.length === 0) return;
|
|
4463
|
+
const indexPath = join2(mcpBase, config.output?.index ?? "mcp.tools.ts");
|
|
4464
|
+
const aggregatorEntries = entries.map((e) => {
|
|
4465
|
+
let rel = relative7(dirname7(indexPath), e.outPath).replace(/\.ts$/, ".js");
|
|
4466
|
+
if (!rel.startsWith(".")) rel = "./" + rel;
|
|
4467
|
+
return {
|
|
4468
|
+
registerFn: e.registerFn,
|
|
4469
|
+
importPath: rel
|
|
4470
|
+
};
|
|
4471
|
+
}).sort((a, b) => a.registerFn.localeCompare(b.registerFn));
|
|
4472
|
+
globalFiles.push({
|
|
4473
|
+
relativePath: indexPath,
|
|
4474
|
+
content: generateMcpAggregator(aggregatorEntries)
|
|
4475
|
+
});
|
|
4476
|
+
if (config.emitRouter !== false) {
|
|
4477
|
+
const routerPath = join2(mcpBase, config.output?.router ?? "mcp.router.ts");
|
|
4478
|
+
globalFiles.push({
|
|
4479
|
+
relativePath: routerPath,
|
|
4480
|
+
content: generateMcpRouter({
|
|
4481
|
+
path: config.path
|
|
4482
|
+
})
|
|
4483
|
+
});
|
|
4484
|
+
}
|
|
4485
|
+
}
|
|
4486
|
+
__name(collectMcpOutput, "collectMcpOutput");
|
|
3923
4487
|
function readManifest(manifestPath) {
|
|
3924
4488
|
if (!existsSync(manifestPath)) return emptyIncrementalManifest(TYPESCRIPT_CODEGEN_VERSION);
|
|
3925
4489
|
try {
|
|
@@ -3931,7 +4495,7 @@ function readManifest(manifestPath) {
|
|
|
3931
4495
|
__name(readManifest, "readManifest");
|
|
3932
4496
|
function writeManifest(manifestPath, manifest) {
|
|
3933
4497
|
try {
|
|
3934
|
-
mkdirSync(
|
|
4498
|
+
mkdirSync(dirname7(manifestPath), {
|
|
3935
4499
|
recursive: true
|
|
3936
4500
|
});
|
|
3937
4501
|
writeFileSync(manifestPath, serializeIncrementalManifest(manifest), "utf-8");
|
|
@@ -3947,7 +4511,7 @@ function deleteStalePaths(absPaths) {
|
|
|
3947
4511
|
rmSync(abs, {
|
|
3948
4512
|
force: true
|
|
3949
4513
|
});
|
|
3950
|
-
removedDirs.add(
|
|
4514
|
+
removedDirs.add(dirname7(abs));
|
|
3951
4515
|
}
|
|
3952
4516
|
}
|
|
3953
4517
|
for (const dir of removedDirs) {
|
|
@@ -3956,7 +4520,7 @@ function deleteStalePaths(absPaths) {
|
|
|
3956
4520
|
try {
|
|
3957
4521
|
if (readdirSync(current).length === 0) {
|
|
3958
4522
|
rmdirSync(current);
|
|
3959
|
-
current =
|
|
4523
|
+
current = dirname7(current);
|
|
3960
4524
|
} else {
|
|
3961
4525
|
break;
|
|
3962
4526
|
}
|