keq 2.9.0 → 2.10.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/CHANGELOG.md +7 -0
- package/dist/esm/src/constant.d.ts +2 -0
- package/dist/esm/src/constant.js +2 -0
- package/dist/esm/src/core.d.ts +30 -0
- package/dist/esm/src/core.js +162 -53
- package/dist/esm/src/exception/abort.exception.d.ts +3 -0
- package/dist/esm/src/exception/abort.exception.js +6 -0
- package/dist/esm/src/index.d.ts +1 -0
- package/dist/esm/src/index.js +1 -0
- package/dist/esm/src/is/is-abort-signal.d.ts +1 -0
- package/dist/esm/src/is/is-abort-signal.js +8 -0
- package/dist/esm/src/middlewares/fetch-arguments-middleware.js +5 -1
- package/dist/esm/src/middlewares/retry-middleware.js +20 -3
- package/dist/esm/src/middlewares/timeout-middleware.js +3 -1
- package/dist/esm/src/types/keq-context.d.ts +10 -1
- package/dist/esm/src/types/keq-context.js +1 -1
- package/dist/umd/src/constant.d.ts +2 -0
- package/dist/umd/src/constant.js +3 -1
- package/dist/umd/src/core.d.ts +30 -0
- package/dist/umd/src/core.js +162 -53
- package/dist/umd/src/exception/abort.exception.d.ts +3 -0
- package/dist/umd/src/exception/abort.exception.js +20 -0
- package/dist/umd/src/index.d.ts +1 -0
- package/dist/umd/src/index.js +4 -2
- package/dist/umd/src/is/is-abort-signal.d.ts +1 -0
- package/dist/umd/src/is/is-abort-signal.js +22 -0
- package/dist/umd/src/middlewares/fetch-arguments-middleware.js +4 -0
- package/dist/umd/src/middlewares/retry-middleware.js +21 -4
- package/dist/umd/src/middlewares/timeout-middleware.js +4 -2
- package/dist/umd/src/types/keq-context.d.ts +10 -1
- package/package.json +10 -13
- package/.claude/settings.local.json +0 -26
- package/packages/cache/.turbo/turbo-build.log +0 -29
- package/packages/cache/dist/index.d.mts +0 -354
- package/packages/cache/dist/index.d.mts.map +0 -1
- package/packages/cache/dist/index.d.ts +0 -353
- package/packages/cache/dist/index.d.ts.map +0 -1
- package/packages/cache/dist/index.js +0 -1271
- package/packages/cache/dist/index.js.map +0 -1
- package/packages/cache/dist/index.mjs +0 -1237
- package/packages/cache/dist/index.mjs.map +0 -1
- package/packages/cli/.turbo/turbo-build.log +0 -91
- package/packages/cli/dist/cli.cjs +0 -1364
- package/packages/cli/dist/cli.cjs.map +0 -1
- package/packages/cli/dist/cli.d.cts +0 -1
- package/packages/cli/dist/cli.d.mts +0 -1
- package/packages/cli/dist/cli.mjs +0 -1359
- package/packages/cli/dist/cli.mjs.map +0 -1
- package/packages/cli/dist/compiler-0Kli6UCr.cjs +0 -738
- package/packages/cli/dist/compiler-0Kli6UCr.cjs.map +0 -1
- package/packages/cli/dist/compiler-C3AE8j54.mjs +0 -715
- package/packages/cli/dist/compiler-C3AE8j54.mjs.map +0 -1
- package/packages/cli/dist/index.cjs +0 -20
- package/packages/cli/dist/index.cjs.map +0 -1
- package/packages/cli/dist/index.d.cts +0 -17
- package/packages/cli/dist/index.d.cts.map +0 -1
- package/packages/cli/dist/index.d.mts +0 -17
- package/packages/cli/dist/index.d.mts.map +0 -1
- package/packages/cli/dist/index.mjs +0 -10
- package/packages/cli/dist/index.mjs.map +0 -1
- package/packages/cli/dist/init-BNhD2oCI.mjs +0 -168
- package/packages/cli/dist/init-BNhD2oCI.mjs.map +0 -1
- package/packages/cli/dist/init-DqxLcRSQ.cjs +0 -170
- package/packages/cli/dist/init-DqxLcRSQ.cjs.map +0 -1
- package/packages/cli/dist/logger-D7PyVZo3.cjs +0 -46
- package/packages/cli/dist/logger-D7PyVZo3.cjs.map +0 -1
- package/packages/cli/dist/logger-QLBHySQ3.mjs +0 -12
- package/packages/cli/dist/logger-QLBHySQ3.mjs.map +0 -1
- package/packages/cli/dist/micro-function.translator-BmJZzkKh.cjs +0 -16
- package/packages/cli/dist/micro-function.translator-BmJZzkKh.cjs.map +0 -1
- package/packages/cli/dist/micro-function.translator-CDfkj4Tt.mjs +0 -11
- package/packages/cli/dist/micro-function.translator-CDfkj4Tt.mjs.map +0 -1
- package/packages/cli/dist/mock-command-Bp1LxSls.mjs +0 -350
- package/packages/cli/dist/mock-command-Bp1LxSls.mjs.map +0 -1
- package/packages/cli/dist/mock-command-WZcfy5Iq.cjs +0 -353
- package/packages/cli/dist/mock-command-WZcfy5Iq.cjs.map +0 -1
- package/packages/cli/dist/plugins-CgMhaNZq.cjs +0 -3558
- package/packages/cli/dist/plugins-CgMhaNZq.cjs.map +0 -1
- package/packages/cli/dist/plugins-DCvJ8iHR.mjs +0 -3280
- package/packages/cli/dist/plugins-DCvJ8iHR.mjs.map +0 -1
- package/packages/cli/dist/plugins.cjs +0 -36
- package/packages/cli/dist/plugins.d.cts +0 -2
- package/packages/cli/dist/plugins.d.mts +0 -2
- package/packages/cli/dist/plugins.mjs +0 -2
- package/packages/cli/dist/runtime-config-BWQlPhiB.d.cts +0 -780
- package/packages/cli/dist/runtime-config-BWQlPhiB.d.cts.map +0 -1
- package/packages/cli/dist/runtime-config-CrzK-Ucf.d.mts +0 -780
- package/packages/cli/dist/runtime-config-CrzK-Ucf.d.mts.map +0 -1
- package/packages/cli/dist/translators.cjs +0 -14
- package/packages/cli/dist/translators.cjs.map +0 -1
- package/packages/cli/dist/translators.d.cts +0 -14
- package/packages/cli/dist/translators.d.cts.map +0 -1
- package/packages/cli/dist/translators.d.mts +0 -14
- package/packages/cli/dist/translators.d.mts.map +0 -1
- package/packages/cli/dist/translators.mjs +0 -12
- package/packages/cli/dist/translators.mjs.map +0 -1
- package/packages/cli/node_modules/.bin/eslint +0 -21
- package/packages/cli/node_modules/.bin/jiti +0 -21
- package/packages/cli/node_modules/.bin/js-yaml +0 -21
- package/packages/cli/node_modules/.bin/jsf +0 -21
- package/packages/cli/node_modules/.bin/jsonpath +0 -21
- package/packages/cli/node_modules/.bin/jsonpath-plus +0 -21
- package/packages/cli/node_modules/.bin/semver +0 -21
- package/packages/cli/node_modules/.bin/tsc +0 -21
- package/packages/cli/node_modules/.bin/tsserver +0 -21
- package/packages/exception/.turbo/turbo-build.log +0 -29
- package/packages/exception/dist/index.d.mts +0 -21
- package/packages/exception/dist/index.d.mts.map +0 -1
- package/packages/exception/dist/index.d.ts +0 -21
- package/packages/exception/dist/index.d.ts.map +0 -1
- package/packages/exception/dist/index.js +0 -104
- package/packages/exception/dist/index.js.map +0 -1
- package/packages/exception/dist/index.mjs +0 -99
- package/packages/exception/dist/index.mjs.map +0 -1
- package/packages/headers/.turbo/turbo-build.log +0 -29
- package/packages/headers/dist/index.d.mts +0 -46
- package/packages/headers/dist/index.d.mts.map +0 -1
- package/packages/headers/dist/index.d.ts +0 -46
- package/packages/headers/dist/index.d.ts.map +0 -1
- package/packages/headers/dist/index.js +0 -81
- package/packages/headers/dist/index.js.map +0 -1
- package/packages/headers/dist/index.mjs +0 -75
- package/packages/headers/dist/index.mjs.map +0 -1
- package/packages/keq/.turbo/turbo-build.log +0 -29
- package/packages/keq/dist/index.d.mts +0 -962
- package/packages/keq/dist/index.d.mts.map +0 -1
- package/packages/keq/dist/index.d.ts +0 -962
- package/packages/keq/dist/index.d.ts.map +0 -1
- package/packages/keq/dist/index.js +0 -1876
- package/packages/keq/dist/index.js.map +0 -1
- package/packages/keq/dist/index.mjs +0 -1814
- package/packages/keq/dist/index.mjs.map +0 -1
- package/packages/nestjs/.turbo/turbo-build.log +0 -29
- package/packages/nestjs/dist/index.d.mts +0 -52
- package/packages/nestjs/dist/index.d.mts.map +0 -1
- package/packages/nestjs/dist/index.d.ts +0 -52
- package/packages/nestjs/dist/index.d.ts.map +0 -1
- package/packages/nestjs/dist/index.js +0 -166
- package/packages/nestjs/dist/index.js.map +0 -1
- package/packages/nestjs/dist/index.mjs +0 -136
- package/packages/nestjs/dist/index.mjs.map +0 -1
- package/packages/test/.turbo/turbo-build.log +0 -6
- package/packages/url/.turbo/turbo-build.log +0 -29
- package/packages/url/dist/index.d.mts +0 -9
- package/packages/url/dist/index.d.mts.map +0 -1
- package/packages/url/dist/index.d.ts +0 -9
- package/packages/url/dist/index.d.ts.map +0 -1
- package/packages/url/dist/index.js +0 -43
- package/packages/url/dist/index.js.map +0 -1
- package/packages/url/dist/index.mjs +0 -40
- package/packages/url/dist/index.mjs.map +0 -1
|
@@ -1,1364 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
const require_logger = require("./logger-D7PyVZo3.cjs");
|
|
3
|
-
const require_compiler = require("./compiler-0Kli6UCr.cjs");
|
|
4
|
-
const require_plugins = require("./plugins-CgMhaNZq.cjs");
|
|
5
|
-
let fs_extra = require("fs-extra");
|
|
6
|
-
fs_extra = require_logger.__toESM(fs_extra, 1);
|
|
7
|
-
let path = require("path");
|
|
8
|
-
path = require_logger.__toESM(path, 1);
|
|
9
|
-
let cosmiconfig = require("cosmiconfig");
|
|
10
|
-
let ramda = require("ramda");
|
|
11
|
-
ramda = require_logger.__toESM(ramda, 1);
|
|
12
|
-
let picomatch = require("picomatch");
|
|
13
|
-
picomatch = require_logger.__toESM(picomatch, 1);
|
|
14
|
-
let js_yaml = require("js-yaml");
|
|
15
|
-
js_yaml = require_logger.__toESM(js_yaml, 1);
|
|
16
|
-
let semver = require("semver");
|
|
17
|
-
semver = require_logger.__toESM(semver, 1);
|
|
18
|
-
let commander = require("commander");
|
|
19
|
-
let os = require("os");
|
|
20
|
-
os = require_logger.__toESM(os, 1);
|
|
21
|
-
let _modelcontextprotocol_sdk_server_mcp_js = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
22
|
-
let _modelcontextprotocol_sdk_server_stdio_js = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
23
|
-
let zod = require("zod");
|
|
24
|
-
//#region src/utils/xprod-merge.ts
|
|
25
|
-
/**
|
|
26
|
-
* 对多个对象数组执行笛卡尔积合并
|
|
27
|
-
* @example
|
|
28
|
-
* xprodMerge([{ a: 1 }, { a: 2 }], [{b: 1}], [{ c: 1 }])
|
|
29
|
-
* // => [{ a: 1, b: 1, c: 1}, { a: 2, b:1, c: 1 }]
|
|
30
|
-
*/
|
|
31
|
-
function xprodMerge(...lists) {
|
|
32
|
-
if (lists.length === 0) return [];
|
|
33
|
-
if (lists.length === 1) return lists[0];
|
|
34
|
-
return lists.reduce((acc, curr) => ramda.chain((item1) => ramda.map((item2) => ramda.mergeAll([item1, item2]), curr), acc));
|
|
35
|
-
}
|
|
36
|
-
//#endregion
|
|
37
|
-
//#region src/commands/utils/xprod-filter-rules.ts
|
|
38
|
-
function xprodFilterRules(options) {
|
|
39
|
-
return xprodMerge((options.module || ["*"]).map((moduleName) => ({
|
|
40
|
-
deny: false,
|
|
41
|
-
persist: options.persist,
|
|
42
|
-
moduleName
|
|
43
|
-
})), options.method ? [{ operationMethod: options.method.toLowerCase() }] : [{ operationMethod: "*" }], options.pathname ? [{ operationPathname: options.pathname }] : [{ operationPathname: "/**" }]);
|
|
44
|
-
}
|
|
45
|
-
//#endregion
|
|
46
|
-
//#region src/commands/build.ts
|
|
47
|
-
function registerBuildCommand(program) {
|
|
48
|
-
program.command("build").description("Build and generate API client code from OpenAPI/Swagger specifications").option("-c --config <config>", "The keq-cli config file").option("--module <modules...>", "Filter module(s) to generate").option("--debug", "Print debug information").option("--tolerant", "Tolerate wrong swagger/openapi structure").option("--fresh", "Clean the output directory before building").option("-i --interactive", "Interactive select the scope of generation").addOption(new commander.Option("--method <method>", "Only generate files of the specified operation method").choices([...require_plugins.SupportedMethods, ...require_plugins.SupportedMethods.map((method) => method.toUpperCase())])).option("--pathname <pathname>", "Only generate files of the specified operation pathname").action(async (options) => {
|
|
49
|
-
const filterRules = [];
|
|
50
|
-
if (options.module || options.method || options.pathname) filterRules.push({
|
|
51
|
-
deny: true,
|
|
52
|
-
persist: false,
|
|
53
|
-
moduleName: "*",
|
|
54
|
-
operationMethod: "*",
|
|
55
|
-
operationPathname: "/**"
|
|
56
|
-
}, ...xprodFilterRules({
|
|
57
|
-
module: options.module,
|
|
58
|
-
method: options.method,
|
|
59
|
-
pathname: options.pathname,
|
|
60
|
-
persist: false
|
|
61
|
-
}));
|
|
62
|
-
else if (options.interactive) filterRules.push({
|
|
63
|
-
deny: true,
|
|
64
|
-
persist: false,
|
|
65
|
-
moduleName: "*",
|
|
66
|
-
operationMethod: "*",
|
|
67
|
-
operationPathname: "/**"
|
|
68
|
-
});
|
|
69
|
-
await new require_compiler.Compiler({
|
|
70
|
-
build: true,
|
|
71
|
-
persist: true,
|
|
72
|
-
fresh: !!options.fresh,
|
|
73
|
-
config: options.config,
|
|
74
|
-
debug: !!options.debug,
|
|
75
|
-
tolerant: !!options.tolerant,
|
|
76
|
-
interactive: !!options.interactive && { mode: "allow" },
|
|
77
|
-
filter: filterRules.length > 0 ? { rules: filterRules } : void 0
|
|
78
|
-
}).run();
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
//#endregion
|
|
82
|
-
//#region src/commands/filter.ts
|
|
83
|
-
function registerFilterCommand(program) {
|
|
84
|
-
program.command("filter").description("Manage filter rules for API generation using .keqfilter file").addArgument(new commander.Argument("<mode>", "The filter mode").choices([
|
|
85
|
-
"all",
|
|
86
|
-
"deny",
|
|
87
|
-
"allow"
|
|
88
|
-
]).argRequired()).option("-c --config <config>", "The keq-cli config file").option("--debug", "Print debug information").option("--tolerant", "Tolerate wrong swagger/openapi structure").option("--module <modules...>").option("--build", "Build after updating .keqfilter file").addOption(new commander.Option("--method <method>", "Only generate files of the specified operation method").choices([...require_plugins.SupportedMethods, ...require_plugins.SupportedMethods.map((method) => method.toUpperCase())])).option("--pathname <pathnames>", "Only generate files of the specified operation pathname").option("-i --interactive", "Interactive select the scope of generation").action(async (mode, options) => {
|
|
89
|
-
let compiler;
|
|
90
|
-
if (mode === "all") {
|
|
91
|
-
if (options.build) throw new Error("'--build' cannot be used with 'all' mode");
|
|
92
|
-
if (options.interactive) throw new Error("'--interactive' cannot be used with 'all' mode");
|
|
93
|
-
if (options.module) throw new Error("'--module' cannot be used with 'all' mode");
|
|
94
|
-
if (options.method) throw new Error("'--method' cannot be used with 'all' mode");
|
|
95
|
-
if (options.pathname) throw new Error("'--pathname' cannot be used with 'all' mode");
|
|
96
|
-
compiler = new require_compiler.Compiler({
|
|
97
|
-
build: false,
|
|
98
|
-
persist: true,
|
|
99
|
-
config: options.config,
|
|
100
|
-
debug: !!options.debug,
|
|
101
|
-
tolerant: !!options.tolerant,
|
|
102
|
-
interactive: false,
|
|
103
|
-
filter: { rules: [{
|
|
104
|
-
persist: true,
|
|
105
|
-
deny: true,
|
|
106
|
-
moduleName: "*",
|
|
107
|
-
operationMethod: "*",
|
|
108
|
-
operationPathname: "/**"
|
|
109
|
-
}] }
|
|
110
|
-
});
|
|
111
|
-
} else if (options.interactive) {
|
|
112
|
-
if (options.interactive) {
|
|
113
|
-
if (options.method) throw new Error("'--method' cannot be used with '--interactive'");
|
|
114
|
-
if (options.pathname) throw new Error("'--pathname' cannot be used with '--interactive'");
|
|
115
|
-
}
|
|
116
|
-
compiler = new require_compiler.Compiler({
|
|
117
|
-
build: !!options.build,
|
|
118
|
-
persist: true,
|
|
119
|
-
config: options.config,
|
|
120
|
-
debug: !!options.debug,
|
|
121
|
-
tolerant: !!options.tolerant,
|
|
122
|
-
interactive: {
|
|
123
|
-
mode,
|
|
124
|
-
persist: true
|
|
125
|
-
},
|
|
126
|
-
filter: { rules: [{
|
|
127
|
-
persist: false,
|
|
128
|
-
deny: true,
|
|
129
|
-
moduleName: "*",
|
|
130
|
-
operationMethod: "*",
|
|
131
|
-
operationPathname: "/**"
|
|
132
|
-
}] }
|
|
133
|
-
});
|
|
134
|
-
} else {
|
|
135
|
-
if (!options.method && !options.pathname) throw new Error("at least one of '-i --interactive', '--method' or '--pathname' must be specified");
|
|
136
|
-
const moduleNames = options.module || ["*"];
|
|
137
|
-
compiler = new require_compiler.Compiler({
|
|
138
|
-
build: !!options.build,
|
|
139
|
-
persist: true,
|
|
140
|
-
config: options.config,
|
|
141
|
-
debug: !!options.debug,
|
|
142
|
-
tolerant: !!options.tolerant,
|
|
143
|
-
filter: { rules: moduleNames.map((moduleNames) => ({
|
|
144
|
-
persist: true,
|
|
145
|
-
deny: mode === "deny",
|
|
146
|
-
moduleName: moduleNames,
|
|
147
|
-
operationMethod: options.method,
|
|
148
|
-
operationPathname: options.pathname
|
|
149
|
-
})) },
|
|
150
|
-
interactive: false
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
|
-
await compiler.run();
|
|
154
|
-
});
|
|
155
|
-
}
|
|
156
|
-
//#endregion
|
|
157
|
-
//#region src/utils/scan-generated-files.ts
|
|
158
|
-
/**
|
|
159
|
-
* Recursively find all files in a directory
|
|
160
|
-
*/
|
|
161
|
-
async function findAllFiles(dir, extensions) {
|
|
162
|
-
const results = [];
|
|
163
|
-
async function scan(currentDir) {
|
|
164
|
-
let entries;
|
|
165
|
-
try {
|
|
166
|
-
entries = await fs_extra.default.readdir(currentDir);
|
|
167
|
-
} catch (err) {
|
|
168
|
-
return;
|
|
169
|
-
}
|
|
170
|
-
for (const entry of entries) {
|
|
171
|
-
if (entry === "node_modules" || entry === ".git") continue;
|
|
172
|
-
const fullPath = path.join(currentDir, entry);
|
|
173
|
-
let stat;
|
|
174
|
-
try {
|
|
175
|
-
stat = await fs_extra.default.stat(fullPath);
|
|
176
|
-
} catch (err) {
|
|
177
|
-
continue;
|
|
178
|
-
}
|
|
179
|
-
if (stat.isDirectory()) await scan(fullPath);
|
|
180
|
-
else if (stat.isFile()) if (!extensions) results.push(fullPath);
|
|
181
|
-
else {
|
|
182
|
-
const ext = path.extname(fullPath);
|
|
183
|
-
if (extensions.includes(ext)) results.push(fullPath);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
await scan(dir);
|
|
188
|
-
return results;
|
|
189
|
-
}
|
|
190
|
-
/**
|
|
191
|
-
* Scan all files in the output directory
|
|
192
|
-
* @param outdir The output directory to scan
|
|
193
|
-
* @returns List of all files with their paths
|
|
194
|
-
*/
|
|
195
|
-
async function scanAllFiles(outdir) {
|
|
196
|
-
const absoluteOutdir = path.resolve(outdir);
|
|
197
|
-
if (!await fs_extra.default.exists(absoluteOutdir)) return [];
|
|
198
|
-
return (await findAllFiles(absoluteOutdir)).map((file) => ({
|
|
199
|
-
absolutePath: file,
|
|
200
|
-
relativePath: path.relative(absoluteOutdir, file)
|
|
201
|
-
}));
|
|
202
|
-
}
|
|
203
|
-
/**
|
|
204
|
-
* Find invalid files that are not in the current artifacts list
|
|
205
|
-
* This includes both non-generated files and outdated generated files
|
|
206
|
-
* @param outdir The output directory
|
|
207
|
-
* @param validFilePaths List of valid file paths (relative to outdir)
|
|
208
|
-
* @returns List of invalid files
|
|
209
|
-
*/
|
|
210
|
-
async function findInvalidFiles(outdir, validFilePaths) {
|
|
211
|
-
const allFiles = await scanAllFiles(outdir);
|
|
212
|
-
const validPathsSet = new Set(validFilePaths.map((p) => path.normalize(p)));
|
|
213
|
-
return allFiles.filter((file) => {
|
|
214
|
-
const normalizedPath = path.normalize(file.relativePath);
|
|
215
|
-
return !validPathsSet.has(normalizedPath);
|
|
216
|
-
});
|
|
217
|
-
}
|
|
218
|
-
//#endregion
|
|
219
|
-
//#region src/commands/list.ts
|
|
220
|
-
function registerListCommand(program) {
|
|
221
|
-
program.command("list").description("List generated files based on current configuration").option("-c --config <config>", "The keq-cli config file").option("--invalid", "List only invalid generated files (files not in current build)").option("--debug", "Print debug information").option("--tolerant", "Tolerate wrong swagger/openapi structure").action(async (options) => {
|
|
222
|
-
const compiler = new require_compiler.Compiler({
|
|
223
|
-
build: true,
|
|
224
|
-
persist: false,
|
|
225
|
-
silent: true,
|
|
226
|
-
config: options.config,
|
|
227
|
-
debug: !!options.debug,
|
|
228
|
-
tolerant: !!options.tolerant
|
|
229
|
-
});
|
|
230
|
-
await compiler.run();
|
|
231
|
-
const context = compiler.context;
|
|
232
|
-
if (!context.rc) throw new Error("Failed to load configuration");
|
|
233
|
-
if (options.invalid) {
|
|
234
|
-
const validFilePaths = (context.artifacts || []).map((artifact) => artifact.filepath);
|
|
235
|
-
const invalidFiles = await findInvalidFiles(context.rc.outdir, validFilePaths);
|
|
236
|
-
for (const file of invalidFiles) {
|
|
237
|
-
const fullPath = `./${path.join(context.rc.outdir, file.relativePath)}`;
|
|
238
|
-
console.log(fullPath);
|
|
239
|
-
}
|
|
240
|
-
} else {
|
|
241
|
-
const artifacts = context.artifacts || [];
|
|
242
|
-
for (const artifact of artifacts) {
|
|
243
|
-
const fullPath = `./${path.join(context.rc.outdir, artifact.filepath)}`;
|
|
244
|
-
console.log(fullPath);
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
});
|
|
248
|
-
}
|
|
249
|
-
//#endregion
|
|
250
|
-
//#region src/commands/apis.ts
|
|
251
|
-
function registerApisCommand(program) {
|
|
252
|
-
program.command("apis").description("List API operations and components from OpenAPI/Swagger specifications").option("-c --config <config>", "The keq-cli config file").option("--tolerant", "Tolerate wrong swagger/openapi structure").addOption(new commander.Option("--includes <includes...>", "Include specific parts").choices(["operations", "components"])).option("--module <modules...>", "Filter module(s) to list").addOption(new commander.Option("--method <method>", "Only generate files of the specified operation method").choices([...require_plugins.SupportedMethods, ...require_plugins.SupportedMethods.map((method) => method.toUpperCase())])).option("--pathname <pathnames>", "Only generate files of the specified operation pathname").addOption(new commander.Option("--format <format>", "Output format").choices(["compact", "json"])).option("--json", "Output in JSON format (shortcut for --format json)").option("--all", "Ignore .keqfilter rules and show all modules/operations").option("--debug", "Print debug information").action(async (options) => {
|
|
253
|
-
if (options.all && (options.module || options.method || options.pathname)) throw new Error("'--all' cannot be used with '--module', '--method', or '--pathname'");
|
|
254
|
-
const filterRules = [];
|
|
255
|
-
if (options.module || options.method || options.pathname) filterRules.push({
|
|
256
|
-
deny: true,
|
|
257
|
-
persist: false,
|
|
258
|
-
moduleName: "*",
|
|
259
|
-
operationMethod: "*",
|
|
260
|
-
operationPathname: "/**"
|
|
261
|
-
}, ...xprodFilterRules({
|
|
262
|
-
module: options.module,
|
|
263
|
-
method: options.method,
|
|
264
|
-
pathname: options.pathname,
|
|
265
|
-
persist: false
|
|
266
|
-
}));
|
|
267
|
-
const compiler = new require_compiler.Compiler({
|
|
268
|
-
build: true,
|
|
269
|
-
persist: false,
|
|
270
|
-
silent: true,
|
|
271
|
-
config: options.config,
|
|
272
|
-
debug: !!options.debug,
|
|
273
|
-
tolerant: !!options.tolerant,
|
|
274
|
-
filter: options.all ? false : filterRules.length > 0 ? { rules: filterRules } : void 0
|
|
275
|
-
});
|
|
276
|
-
await compiler.run();
|
|
277
|
-
const documents = compiler.context.documents || [];
|
|
278
|
-
const includesList = options.includes || ["operations", "components"];
|
|
279
|
-
const includeOperations = includesList.includes("operations");
|
|
280
|
-
const includeComponents = includesList.includes("components");
|
|
281
|
-
const result = documents.map((document) => {
|
|
282
|
-
const item = { module: document.module.name };
|
|
283
|
-
if (includeOperations) item.operations = document.operations.map((operation) => ({
|
|
284
|
-
method: operation.method.toUpperCase(),
|
|
285
|
-
path: operation.pathname,
|
|
286
|
-
operationId: operation.operationId,
|
|
287
|
-
summary: operation.operation.summary || "",
|
|
288
|
-
description: operation.operation.description || ""
|
|
289
|
-
}));
|
|
290
|
-
if (includeComponents) item.components = { schemas: document.schemas.map((schema) => {
|
|
291
|
-
const schemaObj = schema.schema;
|
|
292
|
-
return {
|
|
293
|
-
name: schema.name,
|
|
294
|
-
description: schemaObj.description || ""
|
|
295
|
-
};
|
|
296
|
-
}) };
|
|
297
|
-
return item;
|
|
298
|
-
});
|
|
299
|
-
const format = options.format || (options.json ? "json" : void 0);
|
|
300
|
-
if (format === "json") console.log(JSON.stringify(result, null, 2));
|
|
301
|
-
else if (format === "compact") {
|
|
302
|
-
for (const item of result) {
|
|
303
|
-
console.log(`\nModule: ${item.module}`);
|
|
304
|
-
console.log("─".repeat(70));
|
|
305
|
-
if (includeOperations) {
|
|
306
|
-
console.log("\n APIs:");
|
|
307
|
-
if (!item.operations || item.operations.length === 0) console.log(" (No APIs)");
|
|
308
|
-
else for (const operation of item.operations) {
|
|
309
|
-
const method = operation.method.padEnd(7);
|
|
310
|
-
const desc = operation.summary || operation.description;
|
|
311
|
-
const descText = desc ? ` ${desc}` : "";
|
|
312
|
-
console.log(` ${method} ${operation.path}${descText}`);
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
if (includeComponents) {
|
|
316
|
-
console.log("\n Schemas:");
|
|
317
|
-
if (!item.components || item.components.schemas.length === 0) console.log(" (No Schemas)");
|
|
318
|
-
else for (const schema of item.components.schemas) {
|
|
319
|
-
const desc = schema.description ? ` ${schema.description}` : "";
|
|
320
|
-
console.log(` ${schema.name}${desc}`);
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
const totalApis = result.reduce((sum, item) => sum + (item.operations?.length || 0), 0);
|
|
325
|
-
const totalSchemas = result.reduce((sum, item) => sum + (item.components?.schemas.length || 0), 0);
|
|
326
|
-
console.log("\n" + "─".repeat(70));
|
|
327
|
-
const parts = [`${result.length} modules`];
|
|
328
|
-
if (includeOperations) parts.push(`${totalApis} APIs`);
|
|
329
|
-
if (includeComponents) parts.push(`${totalSchemas} Schemas`);
|
|
330
|
-
console.log(`Total: ${parts.join(", ")}`);
|
|
331
|
-
} else {
|
|
332
|
-
for (const item of result) {
|
|
333
|
-
console.log(`\nModule: ${item.module}`);
|
|
334
|
-
console.log("─".repeat(70));
|
|
335
|
-
if (includeOperations) {
|
|
336
|
-
console.log("\n APIs:");
|
|
337
|
-
if (!item.operations || item.operations.length === 0) console.log(" (No APIs)");
|
|
338
|
-
else for (const operation of item.operations) {
|
|
339
|
-
const method = operation.method.padEnd(7);
|
|
340
|
-
console.log(` ${method} ${operation.path}`);
|
|
341
|
-
const desc = operation.summary || operation.description;
|
|
342
|
-
if (desc) console.log(` ${desc}`);
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
if (includeComponents) {
|
|
346
|
-
console.log("\n Schemas:");
|
|
347
|
-
if (!item.components || item.components.schemas.length === 0) console.log(" (No Schemas)");
|
|
348
|
-
else for (const schema of item.components.schemas) {
|
|
349
|
-
console.log(` ${schema.name}`);
|
|
350
|
-
if (schema.description) console.log(` ${schema.description}`);
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
const totalApis = result.reduce((sum, item) => sum + (item.operations?.length || 0), 0);
|
|
355
|
-
const totalSchemas = result.reduce((sum, item) => sum + (item.components?.schemas.length || 0), 0);
|
|
356
|
-
console.log("\n" + "─".repeat(70));
|
|
357
|
-
const parts = [`${result.length} modules`];
|
|
358
|
-
if (includeOperations) parts.push(`${totalApis} APIs`);
|
|
359
|
-
if (includeComponents) parts.push(`${totalSchemas} Schemas`);
|
|
360
|
-
console.log(`Total: ${parts.join(", ")}`);
|
|
361
|
-
}
|
|
362
|
-
});
|
|
363
|
-
}
|
|
364
|
-
//#endregion
|
|
365
|
-
//#region src/commands/mock.ts
|
|
366
|
-
function registerMockCommand(program) {
|
|
367
|
-
program.command("mock").description("Start a mock server based on OpenAPI specifications").option("-c --config <config>", "The keq-cli config file").option("-p --port <port>", "Port to listen on", "3939").option("--host <host>", "Host to bind to", "localhost").option("--module <modules...>", "Filter module(s) to mock").option("--cors", "Enable CORS headers", true).option("--delay <delay>", "Response delay in ms (or min-max range like \"100-500\")").option("--max-depth <depth>", "Max schema depth for mock data generation", "10").option("--ref-depth <depth>", "Max $ref recursion depth for circular schemas", "5").option("--debug", "Print debug information").option("--tolerant", "Tolerate wrong swagger/openapi structure").action(async (options) => {
|
|
368
|
-
const { mockCommand } = await Promise.resolve().then(() => require("./mock-command-WZcfy5Iq.cjs"));
|
|
369
|
-
await mockCommand(options);
|
|
370
|
-
});
|
|
371
|
-
}
|
|
372
|
-
//#endregion
|
|
373
|
-
//#region src/commands/init.ts
|
|
374
|
-
function registerInitCommand(program) {
|
|
375
|
-
program.command("init").description("Initialize keq configuration file").option("-f --force", "Force overwrite existing config file").action(async (options) => {
|
|
376
|
-
const { initConfig } = await Promise.resolve().then(() => require("./init-DqxLcRSQ.cjs"));
|
|
377
|
-
await initConfig(options);
|
|
378
|
-
});
|
|
379
|
-
}
|
|
380
|
-
//#endregion
|
|
381
|
-
//#region src/search/embedder.ts
|
|
382
|
-
const MODEL_NAME = "intfloat/multilingual-e5-small";
|
|
383
|
-
const CACHE_DIR = path.default.join(os.default.homedir(), ".cache", "keq", "models");
|
|
384
|
-
let transformers = null;
|
|
385
|
-
let loadError = null;
|
|
386
|
-
let extractor = null;
|
|
387
|
-
var EmbedderUnavailableError = class extends Error {
|
|
388
|
-
constructor(cause) {
|
|
389
|
-
super("@huggingface/transformers is not installed. Semantic search is unavailable.\nRun `pnpm add @huggingface/transformers` to enable this feature.");
|
|
390
|
-
this.name = "EmbedderUnavailableError";
|
|
391
|
-
if (cause) this.cause = cause;
|
|
392
|
-
}
|
|
393
|
-
};
|
|
394
|
-
async function loadTransformers() {
|
|
395
|
-
if (transformers) return transformers;
|
|
396
|
-
if (loadError) throw new EmbedderUnavailableError(loadError);
|
|
397
|
-
try {
|
|
398
|
-
transformers = await import("@huggingface/transformers");
|
|
399
|
-
return transformers;
|
|
400
|
-
} catch (e) {
|
|
401
|
-
loadError = e instanceof Error ? e : new Error(String(e));
|
|
402
|
-
throw new EmbedderUnavailableError(loadError);
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
async function getEmbedder() {
|
|
406
|
-
if (extractor) return extractor;
|
|
407
|
-
const { pipeline, env } = await loadTransformers();
|
|
408
|
-
env.cacheDir = CACHE_DIR;
|
|
409
|
-
env.allowLocalModels = true;
|
|
410
|
-
extractor = await pipeline("feature-extraction", MODEL_NAME, { dtype: "fp32" });
|
|
411
|
-
return extractor;
|
|
412
|
-
}
|
|
413
|
-
async function embed(texts) {
|
|
414
|
-
return (await (await getEmbedder())(texts.map((t) => `query: ${t}`), {
|
|
415
|
-
pooling: "mean",
|
|
416
|
-
normalize: true
|
|
417
|
-
})).tolist().map((vec) => new Float32Array(vec));
|
|
418
|
-
}
|
|
419
|
-
async function embedDocuments(texts) {
|
|
420
|
-
return (await (await getEmbedder())(texts.map((t) => `passage: ${t}`), {
|
|
421
|
-
pooling: "mean",
|
|
422
|
-
normalize: true
|
|
423
|
-
})).tolist().map((vec) => new Float32Array(vec));
|
|
424
|
-
}
|
|
425
|
-
function cosineSimilarity(a, b) {
|
|
426
|
-
let dot = 0;
|
|
427
|
-
for (let i = 0; i < a.length; i++) dot += a[i] * b[i];
|
|
428
|
-
return dot;
|
|
429
|
-
}
|
|
430
|
-
//#endregion
|
|
431
|
-
//#region src/search/search-engine.ts
|
|
432
|
-
var SearchEngine = class {
|
|
433
|
-
operations = [];
|
|
434
|
-
async buildIndex(documents) {
|
|
435
|
-
this.operations = [];
|
|
436
|
-
for (const document of documents) for (const op of document.operations) {
|
|
437
|
-
const tags = op.operation.tags || [];
|
|
438
|
-
const summary = op.operation.summary || "";
|
|
439
|
-
const description = op.operation.description || "";
|
|
440
|
-
const text = [
|
|
441
|
-
op.method.toUpperCase(),
|
|
442
|
-
op.pathname,
|
|
443
|
-
op.operationId,
|
|
444
|
-
summary,
|
|
445
|
-
description,
|
|
446
|
-
...tags
|
|
447
|
-
].filter(Boolean).join(" ");
|
|
448
|
-
this.operations.push({
|
|
449
|
-
id: `${document.module.name}:${op.method}:${op.pathname}`,
|
|
450
|
-
module: document.module.name,
|
|
451
|
-
method: op.method.toUpperCase(),
|
|
452
|
-
pathname: op.pathname,
|
|
453
|
-
operationId: op.operationId,
|
|
454
|
-
summary,
|
|
455
|
-
description,
|
|
456
|
-
tags,
|
|
457
|
-
text,
|
|
458
|
-
operation: op.operation
|
|
459
|
-
});
|
|
460
|
-
}
|
|
461
|
-
const embeddings = await embedDocuments(this.operations.map((op) => op.text));
|
|
462
|
-
for (let i = 0; i < this.operations.length; i++) this.operations[i].embedding = embeddings[i];
|
|
463
|
-
}
|
|
464
|
-
async search(query, options) {
|
|
465
|
-
const limit = options?.limit ?? 10;
|
|
466
|
-
const modules = options?.module;
|
|
467
|
-
let candidates = this.operations;
|
|
468
|
-
if (modules && modules.length > 0) candidates = candidates.filter((op) => modules.includes(op.module));
|
|
469
|
-
if (candidates.length === 0) return [];
|
|
470
|
-
const [queryEmbedding] = await embed([query]);
|
|
471
|
-
return candidates.filter((op) => op.embedding).map((op) => ({
|
|
472
|
-
op,
|
|
473
|
-
score: cosineSimilarity(queryEmbedding, op.embedding)
|
|
474
|
-
})).sort((a, b) => b.score - a.score).slice(0, limit).map(({ op, score }) => ({
|
|
475
|
-
score: Math.round(score * 1e3) / 1e3,
|
|
476
|
-
module: op.module,
|
|
477
|
-
method: op.method,
|
|
478
|
-
pathname: op.pathname,
|
|
479
|
-
operationId: op.operationId,
|
|
480
|
-
summary: op.summary,
|
|
481
|
-
description: op.description,
|
|
482
|
-
tags: op.tags
|
|
483
|
-
}));
|
|
484
|
-
}
|
|
485
|
-
getDetail(module, method, pathname) {
|
|
486
|
-
const op = this.operations.find((o) => o.module === module && o.method === method.toUpperCase() && o.pathname === pathname);
|
|
487
|
-
if (!op) return void 0;
|
|
488
|
-
const operation = op.operation;
|
|
489
|
-
return {
|
|
490
|
-
score: 1,
|
|
491
|
-
module: op.module,
|
|
492
|
-
method: op.method,
|
|
493
|
-
pathname: op.pathname,
|
|
494
|
-
operationId: op.operationId,
|
|
495
|
-
summary: op.summary,
|
|
496
|
-
description: op.description,
|
|
497
|
-
tags: op.tags,
|
|
498
|
-
parameters: operation.parameters || [],
|
|
499
|
-
requestBody: operation.requestBody || null,
|
|
500
|
-
responses: operation.responses || null
|
|
501
|
-
};
|
|
502
|
-
}
|
|
503
|
-
listModules() {
|
|
504
|
-
return [...new Set(this.operations.map((op) => op.module))];
|
|
505
|
-
}
|
|
506
|
-
};
|
|
507
|
-
//#endregion
|
|
508
|
-
//#region src/commands/search.ts
|
|
509
|
-
function registerSearchCommand(program) {
|
|
510
|
-
program.command("search").description("Semantic search for API operations using natural language").argument("<query>", "Natural language search query").option("-c --config <config>", "The keq-cli config file").option("--module <modules...>", "Filter module(s) to search").option("--limit <limit>", "Maximum number of results", "10").option("--detail", "Include full request/response schema in results").addOption(new commander.Option("--format <format>", "Output format").choices(["json", "compact"]).default("json")).option("--all", "Ignore .keqfilter rules").option("--tolerant", "Tolerate wrong swagger/openapi structure").option("--debug", "Print debug information").action(async (query, options) => {
|
|
511
|
-
const filterRules = [];
|
|
512
|
-
if (options.module && !options.all) filterRules.push({
|
|
513
|
-
deny: true,
|
|
514
|
-
persist: false,
|
|
515
|
-
moduleName: "*",
|
|
516
|
-
operationMethod: "*",
|
|
517
|
-
operationPathname: "/**"
|
|
518
|
-
}, ...xprodFilterRules({
|
|
519
|
-
module: options.module,
|
|
520
|
-
persist: false
|
|
521
|
-
}));
|
|
522
|
-
const compiler = new require_compiler.Compiler({
|
|
523
|
-
build: true,
|
|
524
|
-
persist: false,
|
|
525
|
-
silent: true,
|
|
526
|
-
config: options.config,
|
|
527
|
-
debug: !!options.debug,
|
|
528
|
-
tolerant: !!options.tolerant,
|
|
529
|
-
filter: options.all ? false : filterRules.length > 0 ? { rules: filterRules } : void 0
|
|
530
|
-
});
|
|
531
|
-
await compiler.run();
|
|
532
|
-
const documents = compiler.context.documents || [];
|
|
533
|
-
const engine = new SearchEngine();
|
|
534
|
-
try {
|
|
535
|
-
await engine.buildIndex(documents);
|
|
536
|
-
} catch (e) {
|
|
537
|
-
if (e instanceof EmbedderUnavailableError) {
|
|
538
|
-
console.error(e.message);
|
|
539
|
-
process.exit(1);
|
|
540
|
-
}
|
|
541
|
-
throw e;
|
|
542
|
-
}
|
|
543
|
-
const limit = parseInt(options.limit, 10);
|
|
544
|
-
const results = await engine.search(query, {
|
|
545
|
-
limit,
|
|
546
|
-
module: options.module
|
|
547
|
-
});
|
|
548
|
-
if (options.detail) printResults(results.map((r) => {
|
|
549
|
-
return engine.getDetail(r.module, r.method, r.pathname) || r;
|
|
550
|
-
}), options.format);
|
|
551
|
-
else printResults(results, options.format);
|
|
552
|
-
});
|
|
553
|
-
}
|
|
554
|
-
function printResults(results, format) {
|
|
555
|
-
if (format === "json") console.log(JSON.stringify({
|
|
556
|
-
results,
|
|
557
|
-
total: results.length
|
|
558
|
-
}, null, 2));
|
|
559
|
-
else {
|
|
560
|
-
for (const r of results) {
|
|
561
|
-
const method = String(r.method).padEnd(7);
|
|
562
|
-
const score = String(r.score);
|
|
563
|
-
console.log(` [${score}] ${method} ${String(r.pathname)}`);
|
|
564
|
-
if (typeof r.summary === "string") console.log(` ${r.summary}`);
|
|
565
|
-
console.log(` ${String(r.module)} | ${String(r.operationId)}`);
|
|
566
|
-
console.log();
|
|
567
|
-
}
|
|
568
|
-
console.log(`Total: ${results.length} results`);
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
//#endregion
|
|
572
|
-
//#region src/mcp/compiler-provider.ts
|
|
573
|
-
/**
|
|
574
|
-
* Manages the Compiler lifecycle with automatic lazy reload.
|
|
575
|
-
* Tracks .keqrc and .keqfilter for changes; getters transparently
|
|
576
|
-
* re-compile when config files are modified, with built-in concurrency protection.
|
|
577
|
-
*/
|
|
578
|
-
var CompilerProvider = class CompilerProvider {
|
|
579
|
-
tracker = new require_plugins.FileTracker();
|
|
580
|
-
_engine = new SearchEngine();
|
|
581
|
-
_engineAvailable = false;
|
|
582
|
-
_documents = [];
|
|
583
|
-
_context = {};
|
|
584
|
-
_matcher = new require_compiler.Matcher([]);
|
|
585
|
-
_keqfilterPath = "";
|
|
586
|
-
_configPath = "";
|
|
587
|
-
_loaded = false;
|
|
588
|
-
_loadPromise = null;
|
|
589
|
-
options;
|
|
590
|
-
constructor(options) {
|
|
591
|
-
this.options = options;
|
|
592
|
-
}
|
|
593
|
-
get configPath() {
|
|
594
|
-
return this._configPath;
|
|
595
|
-
}
|
|
596
|
-
static async init(options) {
|
|
597
|
-
const provider = new CompilerProvider(options);
|
|
598
|
-
const explore = (0, cosmiconfig.cosmiconfig)("keq");
|
|
599
|
-
const result = options.config ? await explore.load(options.config) : await explore.search();
|
|
600
|
-
if (!result) throw new Error("Cannot find config file.");
|
|
601
|
-
const configFilepath = result.filepath;
|
|
602
|
-
const keqfilterPath = path.default.resolve(path.default.dirname(configFilepath), ".keqfilter");
|
|
603
|
-
provider._keqfilterPath = keqfilterPath;
|
|
604
|
-
provider._configPath = configFilepath;
|
|
605
|
-
await provider.tracker.track(configFilepath);
|
|
606
|
-
await provider.tracker.track(keqfilterPath);
|
|
607
|
-
return provider;
|
|
608
|
-
}
|
|
609
|
-
async getDocuments() {
|
|
610
|
-
await this.ensureLoaded();
|
|
611
|
-
return this._documents;
|
|
612
|
-
}
|
|
613
|
-
async getEngine() {
|
|
614
|
-
await this.ensureLoaded();
|
|
615
|
-
if (!this._engineAvailable) throw new EmbedderUnavailableError();
|
|
616
|
-
return this._engine;
|
|
617
|
-
}
|
|
618
|
-
async getContext() {
|
|
619
|
-
await this.ensureLoaded();
|
|
620
|
-
return this._context;
|
|
621
|
-
}
|
|
622
|
-
async getMatcher() {
|
|
623
|
-
await this.ensureLoaded();
|
|
624
|
-
return this._matcher;
|
|
625
|
-
}
|
|
626
|
-
async invalidate() {
|
|
627
|
-
await this.tracker.forceReload(() => this.reload());
|
|
628
|
-
}
|
|
629
|
-
async ensureLoaded() {
|
|
630
|
-
if (this._loaded) {
|
|
631
|
-
await this.tracker.ensureFresh(() => this.reload());
|
|
632
|
-
return;
|
|
633
|
-
}
|
|
634
|
-
if (this._loadPromise) {
|
|
635
|
-
await this._loadPromise;
|
|
636
|
-
return;
|
|
637
|
-
}
|
|
638
|
-
this._loadPromise = this.reload().finally(() => {
|
|
639
|
-
this._loadPromise = null;
|
|
640
|
-
});
|
|
641
|
-
await this._loadPromise;
|
|
642
|
-
}
|
|
643
|
-
async reload() {
|
|
644
|
-
const { config, debug, tolerant } = this.options;
|
|
645
|
-
const compiler = new require_compiler.Compiler({
|
|
646
|
-
build: true,
|
|
647
|
-
persist: false,
|
|
648
|
-
silent: true,
|
|
649
|
-
config,
|
|
650
|
-
debug,
|
|
651
|
-
tolerant,
|
|
652
|
-
filter: false
|
|
653
|
-
});
|
|
654
|
-
await compiler.run();
|
|
655
|
-
this._context = compiler.context;
|
|
656
|
-
this._documents = compiler.context.documents || [];
|
|
657
|
-
try {
|
|
658
|
-
await this._engine.buildIndex(this._documents);
|
|
659
|
-
this._engineAvailable = true;
|
|
660
|
-
} catch (e) {
|
|
661
|
-
if (e instanceof EmbedderUnavailableError) this._engineAvailable = false;
|
|
662
|
-
else throw e;
|
|
663
|
-
}
|
|
664
|
-
if (await fs_extra.default.exists(this._keqfilterPath)) this._matcher = await require_compiler.Matcher.read(this._keqfilterPath);
|
|
665
|
-
else this._matcher = new require_compiler.Matcher([]);
|
|
666
|
-
await this.tracker.snapshot();
|
|
667
|
-
this._loaded = true;
|
|
668
|
-
}
|
|
669
|
-
};
|
|
670
|
-
//#endregion
|
|
671
|
-
//#region src/mcp/find-config-in-dir.ts
|
|
672
|
-
const CONFIG_FILENAMES = [
|
|
673
|
-
".keqrc",
|
|
674
|
-
".keqrc.ts",
|
|
675
|
-
".keqrc.js",
|
|
676
|
-
".keqrc.mjs",
|
|
677
|
-
".keqrc.cjs",
|
|
678
|
-
".keqrc.json",
|
|
679
|
-
".keqrc.yml",
|
|
680
|
-
".keqrc.yaml",
|
|
681
|
-
"keq.config.ts",
|
|
682
|
-
"keq.config.js",
|
|
683
|
-
"keq.config.mjs",
|
|
684
|
-
"keq.config.cjs",
|
|
685
|
-
"keq.config.json",
|
|
686
|
-
"keq.config.yml",
|
|
687
|
-
"keq.config.yaml"
|
|
688
|
-
];
|
|
689
|
-
async function findConfigInDir(dir) {
|
|
690
|
-
for (const filename of CONFIG_FILENAMES) {
|
|
691
|
-
const filePath = path.default.resolve(dir, filename);
|
|
692
|
-
if (await fs_extra.default.pathExists(filePath)) return filePath;
|
|
693
|
-
}
|
|
694
|
-
return null;
|
|
695
|
-
}
|
|
696
|
-
//#endregion
|
|
697
|
-
//#region src/mcp/compiler-provider-registry.ts
|
|
698
|
-
/**
|
|
699
|
-
* Manages multiple CompilerProvider instances for monorepo/multi-project support.
|
|
700
|
-
* Lazily initializes providers on first access and dynamically registers new projects
|
|
701
|
-
* when an unknown project directory is requested.
|
|
702
|
-
*/
|
|
703
|
-
var CompilerProviderRegistry = class {
|
|
704
|
-
entries = /* @__PURE__ */ new Map();
|
|
705
|
-
providers = /* @__PURE__ */ new Map();
|
|
706
|
-
options;
|
|
707
|
-
constructor(options) {
|
|
708
|
-
this.options = options;
|
|
709
|
-
}
|
|
710
|
-
register(entry) {
|
|
711
|
-
this.entries.set(entry.projectDir, entry);
|
|
712
|
-
}
|
|
713
|
-
has(projectDir) {
|
|
714
|
-
return this.entries.has(projectDir);
|
|
715
|
-
}
|
|
716
|
-
list() {
|
|
717
|
-
return [...this.entries.values()];
|
|
718
|
-
}
|
|
719
|
-
async resolve(projectDir) {
|
|
720
|
-
const entry = await this.resolveEntry(projectDir);
|
|
721
|
-
const cached = this.providers.get(entry.projectDir);
|
|
722
|
-
if (cached) return cached;
|
|
723
|
-
const provider = await CompilerProvider.init({
|
|
724
|
-
config: entry.configPath,
|
|
725
|
-
debug: this.options.debug,
|
|
726
|
-
tolerant: this.options.tolerant
|
|
727
|
-
});
|
|
728
|
-
this.providers.set(entry.projectDir, provider);
|
|
729
|
-
return provider;
|
|
730
|
-
}
|
|
731
|
-
getConfigPath(projectDir) {
|
|
732
|
-
return this.resolveEntrySync(projectDir).configPath;
|
|
733
|
-
}
|
|
734
|
-
async resolveEntry(projectDir) {
|
|
735
|
-
if (!projectDir) return this.resolveDefault();
|
|
736
|
-
if (!path.default.isAbsolute(projectDir)) throw new Error(`The 'project' parameter must be an absolute path. Received: ${projectDir}`);
|
|
737
|
-
const existing = this.entries.get(projectDir);
|
|
738
|
-
if (existing) return existing;
|
|
739
|
-
const configPath = await findConfigInDir(projectDir);
|
|
740
|
-
if (!configPath) throw new Error(`No keqrc config file found in ${projectDir}`);
|
|
741
|
-
const entry = {
|
|
742
|
-
configPath,
|
|
743
|
-
projectDir
|
|
744
|
-
};
|
|
745
|
-
this.register(entry);
|
|
746
|
-
return entry;
|
|
747
|
-
}
|
|
748
|
-
resolveEntrySync(projectDir) {
|
|
749
|
-
if (!projectDir) return this.resolveDefault();
|
|
750
|
-
if (!path.default.isAbsolute(projectDir)) throw new Error(`The 'project' parameter must be an absolute path. Received: ${projectDir}`);
|
|
751
|
-
const existing = this.entries.get(projectDir);
|
|
752
|
-
if (!existing) throw new Error(`No keqrc config file found in ${projectDir}`);
|
|
753
|
-
return existing;
|
|
754
|
-
}
|
|
755
|
-
resolveDefault() {
|
|
756
|
-
if (this.entries.size === 0) throw new Error("No keq project found. Please specify 'project' parameter with the absolute path to your project directory containing a keqrc config file.");
|
|
757
|
-
if (this.entries.size === 1) return this.entries.values().next().value;
|
|
758
|
-
const projects = [...this.entries.values()].map((e) => `- ${e.projectDir} (${path.default.basename(e.configPath)})`).join("\n");
|
|
759
|
-
throw new Error(`This is a monorepo with multiple keq projects. Please specify 'project' parameter with one of the following project directories:\n${projects}`);
|
|
760
|
-
}
|
|
761
|
-
};
|
|
762
|
-
//#endregion
|
|
763
|
-
//#region src/mcp/discover-projects.ts
|
|
764
|
-
async function discoverProjects(config) {
|
|
765
|
-
if (config) {
|
|
766
|
-
const configPath = path.default.resolve(config);
|
|
767
|
-
return [{
|
|
768
|
-
configPath,
|
|
769
|
-
projectDir: path.default.dirname(configPath)
|
|
770
|
-
}];
|
|
771
|
-
}
|
|
772
|
-
const monorepoEntries = await discoverFromMonorepo(process.cwd());
|
|
773
|
-
if (monorepoEntries.length > 0) return monorepoEntries;
|
|
774
|
-
const result = await (0, cosmiconfig.cosmiconfig)("keq").search();
|
|
775
|
-
if (result) return [{
|
|
776
|
-
configPath: result.filepath,
|
|
777
|
-
projectDir: path.default.dirname(result.filepath)
|
|
778
|
-
}];
|
|
779
|
-
return [];
|
|
780
|
-
}
|
|
781
|
-
async function discoverFromMonorepo(root) {
|
|
782
|
-
const workspaceGlobs = await getWorkspaceGlobs(root);
|
|
783
|
-
if (workspaceGlobs.length === 0) return [];
|
|
784
|
-
const dirs = [root, ...await expandWorkspaceGlobs(root, workspaceGlobs)];
|
|
785
|
-
const entries = [];
|
|
786
|
-
for (const dir of dirs) {
|
|
787
|
-
const configPath = await findConfigInDir(dir);
|
|
788
|
-
if (configPath) entries.push({
|
|
789
|
-
configPath,
|
|
790
|
-
projectDir: dir
|
|
791
|
-
});
|
|
792
|
-
}
|
|
793
|
-
return entries;
|
|
794
|
-
}
|
|
795
|
-
async function getWorkspaceGlobs(root) {
|
|
796
|
-
const pnpmWorkspacePath = path.default.join(root, "pnpm-workspace.yaml");
|
|
797
|
-
if (await fs_extra.default.pathExists(pnpmWorkspacePath)) {
|
|
798
|
-
const content = await fs_extra.default.readFile(pnpmWorkspacePath, "utf-8");
|
|
799
|
-
const parsed = js_yaml.load(content);
|
|
800
|
-
if (parsed?.packages) return parsed.packages;
|
|
801
|
-
}
|
|
802
|
-
const packageJsonPath = path.default.join(root, "package.json");
|
|
803
|
-
if (await fs_extra.default.pathExists(packageJsonPath)) {
|
|
804
|
-
const pkg = await fs_extra.default.readJson(packageJsonPath);
|
|
805
|
-
if (Array.isArray(pkg.workspaces)) return pkg.workspaces;
|
|
806
|
-
if (Array.isArray(pkg.workspaces?.packages)) return pkg.workspaces.packages;
|
|
807
|
-
}
|
|
808
|
-
return [];
|
|
809
|
-
}
|
|
810
|
-
async function expandWorkspaceGlobs(root, globs) {
|
|
811
|
-
const dirs = [];
|
|
812
|
-
for (const glob of globs) {
|
|
813
|
-
const parts = glob.split("/");
|
|
814
|
-
const baseParts = [];
|
|
815
|
-
let globPart = "";
|
|
816
|
-
for (let i = 0; i < parts.length; i++) {
|
|
817
|
-
if (parts[i].includes("*") || parts[i].includes("{") || parts[i].includes("?")) {
|
|
818
|
-
globPart = parts.slice(i).join("/");
|
|
819
|
-
break;
|
|
820
|
-
}
|
|
821
|
-
baseParts.push(parts[i]);
|
|
822
|
-
}
|
|
823
|
-
const baseDir = path.default.join(root, ...baseParts);
|
|
824
|
-
if (!await fs_extra.default.pathExists(baseDir)) continue;
|
|
825
|
-
if (!globPart) {
|
|
826
|
-
const fullPath = path.default.resolve(root, glob);
|
|
827
|
-
if (await fs_extra.default.pathExists(fullPath)) dirs.push(fullPath);
|
|
828
|
-
continue;
|
|
829
|
-
}
|
|
830
|
-
const matcher = (0, picomatch.default)(globPart);
|
|
831
|
-
const candidates = await fs_extra.default.readdir(baseDir, { withFileTypes: true });
|
|
832
|
-
for (const entry of candidates) {
|
|
833
|
-
if (!entry.isDirectory()) continue;
|
|
834
|
-
if (matcher(entry.name)) dirs.push(path.default.join(baseDir, entry.name));
|
|
835
|
-
}
|
|
836
|
-
}
|
|
837
|
-
return dirs;
|
|
838
|
-
}
|
|
839
|
-
//#endregion
|
|
840
|
-
//#region src/commands/mcp.ts
|
|
841
|
-
function getFilterAnnotation(matcher, op) {
|
|
842
|
-
if (op && matcher.isOperationDenied(op)) return "This API is excluded from the code generation list by .keqfilter rules";
|
|
843
|
-
return "This API is included in the code generation list";
|
|
844
|
-
}
|
|
845
|
-
function registerMcpCommand(program) {
|
|
846
|
-
program.command("mcp").description("Start keq MCP server").option("-c --config <config>", "The keq-cli config file").option("--tolerant", "Tolerate wrong swagger/openapi structure").option("--debug", "Print debug information").action(async (options) => {
|
|
847
|
-
const config = options.config;
|
|
848
|
-
const tolerant = !!options.tolerant;
|
|
849
|
-
const debug = !!options.debug;
|
|
850
|
-
const entries = await discoverProjects(config);
|
|
851
|
-
const registry = new CompilerProviderRegistry({
|
|
852
|
-
debug,
|
|
853
|
-
tolerant
|
|
854
|
-
});
|
|
855
|
-
for (const entry of entries) registry.register(entry);
|
|
856
|
-
const server = new _modelcontextprotocol_sdk_server_mcp_js.McpServer({
|
|
857
|
-
name: "keq",
|
|
858
|
-
version: "1.0.0",
|
|
859
|
-
description: "查询和搜索项目中对接的所有后端服务 API 接口。当用户想了解后端有哪些接口、查找某个功能的 API、查看接口的请求参数和响应格式时使用。支持自然语言语义搜索(中英文)。也可为接口生成 TypeScript 客户端代码。数据来源:项目配置的 Swagger/OpenAPI 规范文档。"
|
|
860
|
-
});
|
|
861
|
-
const projectParam = zod.z.string().optional().describe("项目目录绝对路径,用于 monorepo 中指定操作哪个子项目。非 monorepo 项目无需提供。");
|
|
862
|
-
server.registerTool("search_apis", {
|
|
863
|
-
description: "通过语义匹配搜索项目中的后端 API 接口。仅当用户描述了具体功能或业务场景(如\"用户登录\"、\"搜索商品\")来查找相关接口时使用。如果用户想查看所有接口或按模块/路径浏览接口列表,应使用 list_apis 而非此工具。支持中英文自然语言,返回按相关度排序的接口列表。",
|
|
864
|
-
inputSchema: {
|
|
865
|
-
project: projectParam,
|
|
866
|
-
query: zod.z.string().describe("自然语言搜索词,如\"用户登录\"、\"搜索商品\"、\"订单列表\"等"),
|
|
867
|
-
module: zod.z.array(zod.z.string()).optional().describe("按模块名筛选,限定在特定后端服务中搜索"),
|
|
868
|
-
limit: zod.z.number().optional().default(10).describe("返回结果数量上限")
|
|
869
|
-
},
|
|
870
|
-
annotations: {
|
|
871
|
-
readOnlyHint: true,
|
|
872
|
-
destructiveHint: false
|
|
873
|
-
}
|
|
874
|
-
}, async ({ project, query, module, limit }) => {
|
|
875
|
-
try {
|
|
876
|
-
const provider = await registry.resolve(project);
|
|
877
|
-
const engine = await provider.getEngine();
|
|
878
|
-
const matcher = await provider.getMatcher();
|
|
879
|
-
const documents = await provider.getDocuments();
|
|
880
|
-
const annotated = (await engine.search(query, {
|
|
881
|
-
limit,
|
|
882
|
-
module
|
|
883
|
-
})).map((result) => {
|
|
884
|
-
const { score, ...rest } = result;
|
|
885
|
-
const op = documents.find((d) => d.module.name === result.module)?.operations.find((o) => o.method.toLowerCase() === result.method.toLowerCase() && o.pathname === result.pathname);
|
|
886
|
-
return {
|
|
887
|
-
...rest,
|
|
888
|
-
"x-keq-score": score,
|
|
889
|
-
"x-keq-filter": getFilterAnnotation(matcher, op)
|
|
890
|
-
};
|
|
891
|
-
});
|
|
892
|
-
return { content: [{
|
|
893
|
-
type: "text",
|
|
894
|
-
text: JSON.stringify(annotated, null, 2)
|
|
895
|
-
}] };
|
|
896
|
-
} catch (error) {
|
|
897
|
-
return {
|
|
898
|
-
content: [{
|
|
899
|
-
type: "text",
|
|
900
|
-
text: `Error: ${error instanceof Error ? error.message : String(error)}`
|
|
901
|
-
}],
|
|
902
|
-
isError: true
|
|
903
|
-
};
|
|
904
|
-
}
|
|
905
|
-
});
|
|
906
|
-
server.registerTool("get_api_detail", {
|
|
907
|
-
description: "获取某个 API 接口的完整详情,包含请求参数、请求体、响应体的结构定义。当用户想知道某个接口怎么调用、需要传什么参数、返回什么数据时使用。需要先通过 search_apis 或 list_apis 获取接口的 module、method、pathname。",
|
|
908
|
-
inputSchema: {
|
|
909
|
-
project: projectParam,
|
|
910
|
-
module: zod.z.string().describe("Module name"),
|
|
911
|
-
method: zod.z.string().describe("HTTP method (GET, POST, PUT, DELETE, etc.)"),
|
|
912
|
-
pathname: zod.z.string().describe("API pathname (e.g. /api/v1/users)")
|
|
913
|
-
},
|
|
914
|
-
annotations: {
|
|
915
|
-
readOnlyHint: true,
|
|
916
|
-
destructiveHint: false
|
|
917
|
-
}
|
|
918
|
-
}, async ({ project, module, method, pathname }) => {
|
|
919
|
-
try {
|
|
920
|
-
const provider = await registry.resolve(project);
|
|
921
|
-
const engine = await provider.getEngine();
|
|
922
|
-
const matcher = await provider.getMatcher();
|
|
923
|
-
const documents = await provider.getDocuments();
|
|
924
|
-
const detail = engine.getDetail(module, method, pathname);
|
|
925
|
-
if (!detail) return {
|
|
926
|
-
content: [{
|
|
927
|
-
type: "text",
|
|
928
|
-
text: "API not found"
|
|
929
|
-
}],
|
|
930
|
-
isError: true
|
|
931
|
-
};
|
|
932
|
-
const op = documents.find((d) => d.module.name === module)?.operations.find((o) => o.method.toLowerCase() === method.toLowerCase() && o.pathname === pathname);
|
|
933
|
-
return { content: [{
|
|
934
|
-
type: "text",
|
|
935
|
-
text: JSON.stringify({
|
|
936
|
-
...detail,
|
|
937
|
-
"x-keq-filter": getFilterAnnotation(matcher, op)
|
|
938
|
-
}, null, 2)
|
|
939
|
-
}] };
|
|
940
|
-
} catch (error) {
|
|
941
|
-
return {
|
|
942
|
-
content: [{
|
|
943
|
-
type: "text",
|
|
944
|
-
text: `Error: ${error instanceof Error ? error.message : String(error)}`
|
|
945
|
-
}],
|
|
946
|
-
isError: true
|
|
947
|
-
};
|
|
948
|
-
}
|
|
949
|
-
});
|
|
950
|
-
server.registerTool("list_modules", {
|
|
951
|
-
description: "列出项目对接的所有后端服务模块。当用户想知道项目接入了哪些后端系统、有哪些服务可用时使用。每个模块对应一个后端服务的 API 文档。",
|
|
952
|
-
inputSchema: { project: projectParam },
|
|
953
|
-
annotations: {
|
|
954
|
-
readOnlyHint: true,
|
|
955
|
-
destructiveHint: false
|
|
956
|
-
}
|
|
957
|
-
}, async ({ project }) => {
|
|
958
|
-
try {
|
|
959
|
-
const modules = (await (await registry.resolve(project)).getEngine()).listModules();
|
|
960
|
-
return { content: [{
|
|
961
|
-
type: "text",
|
|
962
|
-
text: JSON.stringify(modules, null, 2)
|
|
963
|
-
}] };
|
|
964
|
-
} catch (error) {
|
|
965
|
-
return {
|
|
966
|
-
content: [{
|
|
967
|
-
type: "text",
|
|
968
|
-
text: `Error: ${error instanceof Error ? error.message : String(error)}`
|
|
969
|
-
}],
|
|
970
|
-
isError: true
|
|
971
|
-
};
|
|
972
|
-
}
|
|
973
|
-
});
|
|
974
|
-
server.registerTool("list_apis", {
|
|
975
|
-
description: "列出项目中所有可用的后端 API 接口。当用户想查看所有接口、浏览某个服务的全部接口、查询某个系统有哪些接口、或按条件筛选接口(如只看 GET 请求或某个路径下的接口)时使用。如果用户描述了具体功能来查找接口(如\"搜索商品的接口\"),应使用 search_apis。",
|
|
976
|
-
inputSchema: {
|
|
977
|
-
project: projectParam,
|
|
978
|
-
module: zod.z.array(zod.z.string()).optional().describe("Filter by module names"),
|
|
979
|
-
method: zod.z.string().optional().describe("Filter by HTTP method (GET, POST, PUT, DELETE, etc.)"),
|
|
980
|
-
pathname: zod.z.string().optional().describe("Filter by pathname pattern (glob supported)"),
|
|
981
|
-
includes: zod.z.array(zod.z.enum(["operations", "components"])).optional().default(["operations"]).describe("What to include in results")
|
|
982
|
-
},
|
|
983
|
-
annotations: {
|
|
984
|
-
readOnlyHint: true,
|
|
985
|
-
destructiveHint: false
|
|
986
|
-
}
|
|
987
|
-
}, async ({ project, module, method, pathname, includes }) => {
|
|
988
|
-
try {
|
|
989
|
-
const provider = await registry.resolve(project);
|
|
990
|
-
const documents = await provider.getDocuments();
|
|
991
|
-
const matcher = await provider.getMatcher();
|
|
992
|
-
const result = documents.filter((doc) => !module || module.includes(doc.module.name)).map((document) => {
|
|
993
|
-
const item = { module: document.module.name };
|
|
994
|
-
if (includes.includes("operations")) {
|
|
995
|
-
let ops = document.operations;
|
|
996
|
-
if (method) ops = ops.filter((op) => op.method.toLowerCase() === method.toLowerCase());
|
|
997
|
-
if (pathname) {
|
|
998
|
-
const match = (0, picomatch.default)(pathname);
|
|
999
|
-
ops = ops.filter((op) => match(op.pathname));
|
|
1000
|
-
}
|
|
1001
|
-
item.operations = ops.map((op) => ({
|
|
1002
|
-
method: op.method.toUpperCase(),
|
|
1003
|
-
path: op.pathname,
|
|
1004
|
-
operationId: op.operationId,
|
|
1005
|
-
summary: op.operation.summary || "",
|
|
1006
|
-
description: op.operation.description || "",
|
|
1007
|
-
"x-keq-filter": getFilterAnnotation(matcher, op)
|
|
1008
|
-
}));
|
|
1009
|
-
}
|
|
1010
|
-
if (includes.includes("components")) item.components = { schemas: document.schemas.map((schema) => {
|
|
1011
|
-
const schemaObj = schema.schema;
|
|
1012
|
-
return {
|
|
1013
|
-
name: schema.name,
|
|
1014
|
-
description: schemaObj.description || ""
|
|
1015
|
-
};
|
|
1016
|
-
}) };
|
|
1017
|
-
return item;
|
|
1018
|
-
});
|
|
1019
|
-
return { content: [{
|
|
1020
|
-
type: "text",
|
|
1021
|
-
text: JSON.stringify(result, null, 2)
|
|
1022
|
-
}] };
|
|
1023
|
-
} catch (error) {
|
|
1024
|
-
return {
|
|
1025
|
-
content: [{
|
|
1026
|
-
type: "text",
|
|
1027
|
-
text: `Error: ${error instanceof Error ? error.message : String(error)}`
|
|
1028
|
-
}],
|
|
1029
|
-
isError: true
|
|
1030
|
-
};
|
|
1031
|
-
}
|
|
1032
|
-
});
|
|
1033
|
-
server.registerTool("build_apis", {
|
|
1034
|
-
description: "为后端 API 接口生成类型安全的 TypeScript 请求函数代码。当用户想生成某个接口的调用代码、需要类型安全的 HTTP 客户端时使用。可按模块、方法、路径范围筛选要生成的接口。生成的文件写入项目配置的输出目录。",
|
|
1035
|
-
inputSchema: {
|
|
1036
|
-
project: projectParam,
|
|
1037
|
-
module: zod.z.array(zod.z.string()).optional().describe("Module(s) to generate code for"),
|
|
1038
|
-
method: zod.z.string().optional().describe("HTTP method filter (e.g. GET, POST)"),
|
|
1039
|
-
pathname: zod.z.string().optional().describe("Pathname filter (glob pattern supported, e.g. /api/v1/users/**)"),
|
|
1040
|
-
fresh: zod.z.boolean().optional().default(false).describe("Clean output directory before building")
|
|
1041
|
-
},
|
|
1042
|
-
annotations: {
|
|
1043
|
-
readOnlyHint: false,
|
|
1044
|
-
destructiveHint: false
|
|
1045
|
-
}
|
|
1046
|
-
}, async ({ project, module, method, pathname, fresh }) => {
|
|
1047
|
-
try {
|
|
1048
|
-
const provider = await registry.resolve(project);
|
|
1049
|
-
const configPath = registry.getConfigPath(project);
|
|
1050
|
-
const filterRules = [];
|
|
1051
|
-
if (module || method || pathname) filterRules.push({
|
|
1052
|
-
deny: true,
|
|
1053
|
-
persist: false,
|
|
1054
|
-
moduleName: "*",
|
|
1055
|
-
operationMethod: "*",
|
|
1056
|
-
operationPathname: "/**"
|
|
1057
|
-
}, ...xprodFilterRules({
|
|
1058
|
-
module,
|
|
1059
|
-
method,
|
|
1060
|
-
pathname,
|
|
1061
|
-
persist: false
|
|
1062
|
-
}));
|
|
1063
|
-
const buildCompiler = new require_compiler.Compiler({
|
|
1064
|
-
build: true,
|
|
1065
|
-
persist: true,
|
|
1066
|
-
silent: true,
|
|
1067
|
-
fresh,
|
|
1068
|
-
config: configPath,
|
|
1069
|
-
debug,
|
|
1070
|
-
tolerant,
|
|
1071
|
-
filter: filterRules.length > 0 ? { rules: filterRules } : void 0
|
|
1072
|
-
});
|
|
1073
|
-
await buildCompiler.run();
|
|
1074
|
-
const artifacts = buildCompiler.context.artifacts || [];
|
|
1075
|
-
const rc = buildCompiler.context.rc;
|
|
1076
|
-
const files = artifacts.map((a) => rc ? `./${path.join(rc.outdir, a.filepath)}` : a.filepath);
|
|
1077
|
-
await provider.invalidate();
|
|
1078
|
-
return { content: [{
|
|
1079
|
-
type: "text",
|
|
1080
|
-
text: JSON.stringify({
|
|
1081
|
-
generated: files.length,
|
|
1082
|
-
files
|
|
1083
|
-
}, null, 2)
|
|
1084
|
-
}] };
|
|
1085
|
-
} catch (error) {
|
|
1086
|
-
return {
|
|
1087
|
-
content: [{
|
|
1088
|
-
type: "text",
|
|
1089
|
-
text: `Error: ${error instanceof Error ? error.message : String(error)}`
|
|
1090
|
-
}],
|
|
1091
|
-
isError: true
|
|
1092
|
-
};
|
|
1093
|
-
}
|
|
1094
|
-
});
|
|
1095
|
-
server.registerTool("list_generated_files", {
|
|
1096
|
-
description: "列出已生成的 API 客户端代码文件。当用户想查看哪些接口已经生成了代码、或想找出过期/无效的生成文件时使用。",
|
|
1097
|
-
inputSchema: {
|
|
1098
|
-
project: projectParam,
|
|
1099
|
-
invalid: zod.z.boolean().optional().default(false).describe("If true, only list invalid/stale files not matching current build")
|
|
1100
|
-
},
|
|
1101
|
-
annotations: {
|
|
1102
|
-
readOnlyHint: true,
|
|
1103
|
-
destructiveHint: false
|
|
1104
|
-
}
|
|
1105
|
-
}, async ({ project, invalid }) => {
|
|
1106
|
-
try {
|
|
1107
|
-
const context = await (await registry.resolve(project)).getContext();
|
|
1108
|
-
const rc = context.rc;
|
|
1109
|
-
if (!rc) return {
|
|
1110
|
-
content: [{
|
|
1111
|
-
type: "text",
|
|
1112
|
-
text: "Error: Failed to load configuration"
|
|
1113
|
-
}],
|
|
1114
|
-
isError: true
|
|
1115
|
-
};
|
|
1116
|
-
if (invalid) {
|
|
1117
|
-
const validFilePaths = (context.artifacts || []).map((a) => a.filepath);
|
|
1118
|
-
const files = (await findInvalidFiles(rc.outdir, validFilePaths)).map((f) => `./${path.join(rc.outdir, f.relativePath)}`);
|
|
1119
|
-
return { content: [{
|
|
1120
|
-
type: "text",
|
|
1121
|
-
text: JSON.stringify({
|
|
1122
|
-
count: files.length,
|
|
1123
|
-
files
|
|
1124
|
-
}, null, 2)
|
|
1125
|
-
}] };
|
|
1126
|
-
}
|
|
1127
|
-
const files = (context.artifacts || []).map((a) => `./${path.join(rc.outdir, a.filepath)}`);
|
|
1128
|
-
return { content: [{
|
|
1129
|
-
type: "text",
|
|
1130
|
-
text: JSON.stringify({
|
|
1131
|
-
count: files.length,
|
|
1132
|
-
files
|
|
1133
|
-
}, null, 2)
|
|
1134
|
-
}] };
|
|
1135
|
-
} catch (error) {
|
|
1136
|
-
return {
|
|
1137
|
-
content: [{
|
|
1138
|
-
type: "text",
|
|
1139
|
-
text: `Error: ${error instanceof Error ? error.message : String(error)}`
|
|
1140
|
-
}],
|
|
1141
|
-
isError: true
|
|
1142
|
-
};
|
|
1143
|
-
}
|
|
1144
|
-
});
|
|
1145
|
-
server.registerTool("get_filter_rules", {
|
|
1146
|
-
description: "查看当前的接口过滤规则。过滤规则决定哪些 API 接口会被纳入代码生成范围。当用户想了解哪些接口被排除或包含在生成列表中时使用。",
|
|
1147
|
-
inputSchema: { project: projectParam },
|
|
1148
|
-
annotations: {
|
|
1149
|
-
readOnlyHint: true,
|
|
1150
|
-
destructiveHint: false
|
|
1151
|
-
}
|
|
1152
|
-
}, async ({ project }) => {
|
|
1153
|
-
try {
|
|
1154
|
-
await registry.resolve(project);
|
|
1155
|
-
const configPath = registry.getConfigPath(project);
|
|
1156
|
-
const filterPath = path.resolve(path.dirname(configPath), ".keqfilter");
|
|
1157
|
-
if (!await fs_extra.default.exists(filterPath)) return { content: [{
|
|
1158
|
-
type: "text",
|
|
1159
|
-
text: JSON.stringify({
|
|
1160
|
-
exists: false,
|
|
1161
|
-
content: ""
|
|
1162
|
-
}, null, 2)
|
|
1163
|
-
}] };
|
|
1164
|
-
const content = await fs_extra.default.readFile(filterPath, "utf-8");
|
|
1165
|
-
return { content: [{
|
|
1166
|
-
type: "text",
|
|
1167
|
-
text: JSON.stringify({
|
|
1168
|
-
exists: true,
|
|
1169
|
-
content
|
|
1170
|
-
}, null, 2)
|
|
1171
|
-
}] };
|
|
1172
|
-
} catch (error) {
|
|
1173
|
-
return {
|
|
1174
|
-
content: [{
|
|
1175
|
-
type: "text",
|
|
1176
|
-
text: `Error: ${error instanceof Error ? error.message : String(error)}`
|
|
1177
|
-
}],
|
|
1178
|
-
isError: true
|
|
1179
|
-
};
|
|
1180
|
-
}
|
|
1181
|
-
});
|
|
1182
|
-
server.registerTool("add_filter_rule", {
|
|
1183
|
-
description: "添加接口过滤规则,控制哪些 API 需要生成代码。当用户想排除某些不需要的接口、或只保留特定接口时使用。deny 模式排除接口,allow 模式包含接口。",
|
|
1184
|
-
inputSchema: {
|
|
1185
|
-
project: projectParam,
|
|
1186
|
-
mode: zod.z.enum(["deny", "allow"]).describe("Rule mode: deny excludes APIs, allow includes them"),
|
|
1187
|
-
module: zod.z.string().optional().default("*").describe("Module name pattern (glob supported)"),
|
|
1188
|
-
method: zod.z.string().optional().default("*").describe("HTTP method pattern (e.g. GET, POST, or * for all)"),
|
|
1189
|
-
pathname: zod.z.string().describe("Pathname pattern (glob supported, e.g. /api/v1/users/**)"),
|
|
1190
|
-
build: zod.z.boolean().optional().default(false).describe("Whether to rebuild after adding the rule")
|
|
1191
|
-
},
|
|
1192
|
-
annotations: {
|
|
1193
|
-
readOnlyHint: false,
|
|
1194
|
-
destructiveHint: false
|
|
1195
|
-
}
|
|
1196
|
-
}, async ({ project, mode, module, method, pathname, build }) => {
|
|
1197
|
-
try {
|
|
1198
|
-
const provider = await registry.resolve(project);
|
|
1199
|
-
await new require_compiler.Compiler({
|
|
1200
|
-
build,
|
|
1201
|
-
persist: true,
|
|
1202
|
-
silent: true,
|
|
1203
|
-
config: registry.getConfigPath(project),
|
|
1204
|
-
debug,
|
|
1205
|
-
tolerant,
|
|
1206
|
-
filter: { rules: [{
|
|
1207
|
-
persist: true,
|
|
1208
|
-
deny: mode === "deny",
|
|
1209
|
-
moduleName: module,
|
|
1210
|
-
operationMethod: method.toLowerCase(),
|
|
1211
|
-
operationPathname: pathname
|
|
1212
|
-
}] }
|
|
1213
|
-
}).run();
|
|
1214
|
-
if (build) await provider.invalidate();
|
|
1215
|
-
return { content: [{
|
|
1216
|
-
type: "text",
|
|
1217
|
-
text: JSON.stringify({
|
|
1218
|
-
success: true,
|
|
1219
|
-
rule: {
|
|
1220
|
-
mode,
|
|
1221
|
-
module,
|
|
1222
|
-
method,
|
|
1223
|
-
pathname
|
|
1224
|
-
}
|
|
1225
|
-
}, null, 2)
|
|
1226
|
-
}] };
|
|
1227
|
-
} catch (error) {
|
|
1228
|
-
return {
|
|
1229
|
-
content: [{
|
|
1230
|
-
type: "text",
|
|
1231
|
-
text: `Error: ${error instanceof Error ? error.message : String(error)}`
|
|
1232
|
-
}],
|
|
1233
|
-
isError: true
|
|
1234
|
-
};
|
|
1235
|
-
}
|
|
1236
|
-
});
|
|
1237
|
-
server.registerTool("remove_filter_rule", {
|
|
1238
|
-
description: "移除已有的接口过滤规则。当用户想恢复之前被排除的接口、或调整过滤策略时使用。",
|
|
1239
|
-
inputSchema: {
|
|
1240
|
-
project: projectParam,
|
|
1241
|
-
mode: zod.z.enum(["deny", "allow"]).describe("Which section the rule is in"),
|
|
1242
|
-
module: zod.z.string().describe("Exact module name pattern to match"),
|
|
1243
|
-
method: zod.z.string().describe("Exact method pattern to match"),
|
|
1244
|
-
pathname: zod.z.string().describe("Exact pathname pattern to match"),
|
|
1245
|
-
build: zod.z.boolean().optional().default(false).describe("Whether to rebuild after removing the rule")
|
|
1246
|
-
},
|
|
1247
|
-
annotations: {
|
|
1248
|
-
readOnlyHint: false,
|
|
1249
|
-
destructiveHint: false
|
|
1250
|
-
}
|
|
1251
|
-
}, async ({ project, mode, module, method, pathname, build }) => {
|
|
1252
|
-
try {
|
|
1253
|
-
const provider = await registry.resolve(project);
|
|
1254
|
-
const configPath = registry.getConfigPath(project);
|
|
1255
|
-
const filterPath = path.resolve(path.dirname(configPath), ".keqfilter");
|
|
1256
|
-
if (!await fs_extra.default.exists(filterPath)) return {
|
|
1257
|
-
content: [{
|
|
1258
|
-
type: "text",
|
|
1259
|
-
text: "Error: .keqfilter file not found"
|
|
1260
|
-
}],
|
|
1261
|
-
isError: true
|
|
1262
|
-
};
|
|
1263
|
-
const content = await fs_extra.default.readFile(filterPath, "utf-8");
|
|
1264
|
-
const isDeny = mode === "deny";
|
|
1265
|
-
const targetMethod = method.toLowerCase();
|
|
1266
|
-
const lines = content.split("\n");
|
|
1267
|
-
const resultLines = [];
|
|
1268
|
-
let currentSection = null;
|
|
1269
|
-
let skipNext = false;
|
|
1270
|
-
for (const line of lines) {
|
|
1271
|
-
const trimmed = line.replace(/#.*$/, "").trim();
|
|
1272
|
-
if (trimmed === "[deny]") {
|
|
1273
|
-
currentSection = "deny";
|
|
1274
|
-
resultLines.push(line);
|
|
1275
|
-
continue;
|
|
1276
|
-
}
|
|
1277
|
-
if (trimmed === "[allow]") {
|
|
1278
|
-
currentSection = "allow";
|
|
1279
|
-
resultLines.push(line);
|
|
1280
|
-
continue;
|
|
1281
|
-
}
|
|
1282
|
-
if (!trimmed) {
|
|
1283
|
-
if (!skipNext) resultLines.push(line);
|
|
1284
|
-
skipNext = false;
|
|
1285
|
-
continue;
|
|
1286
|
-
}
|
|
1287
|
-
const matched = trimmed.match(/^([^\s]+)\s+([^:\s]+):(.*)$/);
|
|
1288
|
-
if (matched && currentSection === (isDeny ? "deny" : "allow")) {
|
|
1289
|
-
const [, ruleMethod, ruleModule, rulePathname] = matched;
|
|
1290
|
-
if (ruleMethod.toLowerCase() === targetMethod && ruleModule === module && rulePathname === pathname) {
|
|
1291
|
-
skipNext = false;
|
|
1292
|
-
continue;
|
|
1293
|
-
}
|
|
1294
|
-
}
|
|
1295
|
-
resultLines.push(line);
|
|
1296
|
-
skipNext = false;
|
|
1297
|
-
}
|
|
1298
|
-
await fs_extra.default.writeFile(filterPath, resultLines.join("\n"), "utf-8");
|
|
1299
|
-
if (build) {
|
|
1300
|
-
await new require_compiler.Compiler({
|
|
1301
|
-
build: true,
|
|
1302
|
-
persist: true,
|
|
1303
|
-
silent: true,
|
|
1304
|
-
config: configPath,
|
|
1305
|
-
debug,
|
|
1306
|
-
tolerant
|
|
1307
|
-
}).run();
|
|
1308
|
-
await provider.invalidate();
|
|
1309
|
-
}
|
|
1310
|
-
return { content: [{
|
|
1311
|
-
type: "text",
|
|
1312
|
-
text: JSON.stringify({
|
|
1313
|
-
success: true,
|
|
1314
|
-
removed: {
|
|
1315
|
-
mode,
|
|
1316
|
-
module,
|
|
1317
|
-
method,
|
|
1318
|
-
pathname
|
|
1319
|
-
}
|
|
1320
|
-
}, null, 2)
|
|
1321
|
-
}] };
|
|
1322
|
-
} catch (error) {
|
|
1323
|
-
return {
|
|
1324
|
-
content: [{
|
|
1325
|
-
type: "text",
|
|
1326
|
-
text: `Error: ${error instanceof Error ? error.message : String(error)}`
|
|
1327
|
-
}],
|
|
1328
|
-
isError: true
|
|
1329
|
-
};
|
|
1330
|
-
}
|
|
1331
|
-
});
|
|
1332
|
-
const transport = new _modelcontextprotocol_sdk_server_stdio_js.StdioServerTransport();
|
|
1333
|
-
await server.connect(transport);
|
|
1334
|
-
if (registry.list().length === 1) (await registry.resolve()).getEngine().catch(() => {});
|
|
1335
|
-
});
|
|
1336
|
-
}
|
|
1337
|
-
//#endregion
|
|
1338
|
-
//#region src/cli.ts
|
|
1339
|
-
if (semver.default.lt(process.version, "20.0.0")) throw new Error("Node.js version must be greater than 20");
|
|
1340
|
-
const program = new commander.Command();
|
|
1341
|
-
registerBuildCommand(program);
|
|
1342
|
-
registerFilterCommand(program);
|
|
1343
|
-
registerListCommand(program);
|
|
1344
|
-
registerApisCommand(program);
|
|
1345
|
-
registerMockCommand(program);
|
|
1346
|
-
registerInitCommand(program);
|
|
1347
|
-
registerSearchCommand(program);
|
|
1348
|
-
registerMcpCommand(program);
|
|
1349
|
-
async function main() {
|
|
1350
|
-
program.on("command:*", function(operands) {
|
|
1351
|
-
throw new Error(`Unknown command '${String(operands[0])}'`);
|
|
1352
|
-
});
|
|
1353
|
-
try {
|
|
1354
|
-
await program.parseAsync(process.argv);
|
|
1355
|
-
} catch (err) {
|
|
1356
|
-
if (process.argv.includes("--debug")) console.error(err);
|
|
1357
|
-
else require_logger.logger.error(err instanceof Error ? err.message : String(err));
|
|
1358
|
-
process.exit(1);
|
|
1359
|
-
}
|
|
1360
|
-
}
|
|
1361
|
-
main();
|
|
1362
|
-
//#endregion
|
|
1363
|
-
|
|
1364
|
-
//# sourceMappingURL=cli.cjs.map
|