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.
Files changed (151) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/esm/src/constant.d.ts +2 -0
  3. package/dist/esm/src/constant.js +2 -0
  4. package/dist/esm/src/core.d.ts +30 -0
  5. package/dist/esm/src/core.js +162 -53
  6. package/dist/esm/src/exception/abort.exception.d.ts +3 -0
  7. package/dist/esm/src/exception/abort.exception.js +6 -0
  8. package/dist/esm/src/index.d.ts +1 -0
  9. package/dist/esm/src/index.js +1 -0
  10. package/dist/esm/src/is/is-abort-signal.d.ts +1 -0
  11. package/dist/esm/src/is/is-abort-signal.js +8 -0
  12. package/dist/esm/src/middlewares/fetch-arguments-middleware.js +5 -1
  13. package/dist/esm/src/middlewares/retry-middleware.js +20 -3
  14. package/dist/esm/src/middlewares/timeout-middleware.js +3 -1
  15. package/dist/esm/src/types/keq-context.d.ts +10 -1
  16. package/dist/esm/src/types/keq-context.js +1 -1
  17. package/dist/umd/src/constant.d.ts +2 -0
  18. package/dist/umd/src/constant.js +3 -1
  19. package/dist/umd/src/core.d.ts +30 -0
  20. package/dist/umd/src/core.js +162 -53
  21. package/dist/umd/src/exception/abort.exception.d.ts +3 -0
  22. package/dist/umd/src/exception/abort.exception.js +20 -0
  23. package/dist/umd/src/index.d.ts +1 -0
  24. package/dist/umd/src/index.js +4 -2
  25. package/dist/umd/src/is/is-abort-signal.d.ts +1 -0
  26. package/dist/umd/src/is/is-abort-signal.js +22 -0
  27. package/dist/umd/src/middlewares/fetch-arguments-middleware.js +4 -0
  28. package/dist/umd/src/middlewares/retry-middleware.js +21 -4
  29. package/dist/umd/src/middlewares/timeout-middleware.js +4 -2
  30. package/dist/umd/src/types/keq-context.d.ts +10 -1
  31. package/package.json +10 -13
  32. package/.claude/settings.local.json +0 -26
  33. package/packages/cache/.turbo/turbo-build.log +0 -29
  34. package/packages/cache/dist/index.d.mts +0 -354
  35. package/packages/cache/dist/index.d.mts.map +0 -1
  36. package/packages/cache/dist/index.d.ts +0 -353
  37. package/packages/cache/dist/index.d.ts.map +0 -1
  38. package/packages/cache/dist/index.js +0 -1271
  39. package/packages/cache/dist/index.js.map +0 -1
  40. package/packages/cache/dist/index.mjs +0 -1237
  41. package/packages/cache/dist/index.mjs.map +0 -1
  42. package/packages/cli/.turbo/turbo-build.log +0 -91
  43. package/packages/cli/dist/cli.cjs +0 -1364
  44. package/packages/cli/dist/cli.cjs.map +0 -1
  45. package/packages/cli/dist/cli.d.cts +0 -1
  46. package/packages/cli/dist/cli.d.mts +0 -1
  47. package/packages/cli/dist/cli.mjs +0 -1359
  48. package/packages/cli/dist/cli.mjs.map +0 -1
  49. package/packages/cli/dist/compiler-0Kli6UCr.cjs +0 -738
  50. package/packages/cli/dist/compiler-0Kli6UCr.cjs.map +0 -1
  51. package/packages/cli/dist/compiler-C3AE8j54.mjs +0 -715
  52. package/packages/cli/dist/compiler-C3AE8j54.mjs.map +0 -1
  53. package/packages/cli/dist/index.cjs +0 -20
  54. package/packages/cli/dist/index.cjs.map +0 -1
  55. package/packages/cli/dist/index.d.cts +0 -17
  56. package/packages/cli/dist/index.d.cts.map +0 -1
  57. package/packages/cli/dist/index.d.mts +0 -17
  58. package/packages/cli/dist/index.d.mts.map +0 -1
  59. package/packages/cli/dist/index.mjs +0 -10
  60. package/packages/cli/dist/index.mjs.map +0 -1
  61. package/packages/cli/dist/init-BNhD2oCI.mjs +0 -168
  62. package/packages/cli/dist/init-BNhD2oCI.mjs.map +0 -1
  63. package/packages/cli/dist/init-DqxLcRSQ.cjs +0 -170
  64. package/packages/cli/dist/init-DqxLcRSQ.cjs.map +0 -1
  65. package/packages/cli/dist/logger-D7PyVZo3.cjs +0 -46
  66. package/packages/cli/dist/logger-D7PyVZo3.cjs.map +0 -1
  67. package/packages/cli/dist/logger-QLBHySQ3.mjs +0 -12
  68. package/packages/cli/dist/logger-QLBHySQ3.mjs.map +0 -1
  69. package/packages/cli/dist/micro-function.translator-BmJZzkKh.cjs +0 -16
  70. package/packages/cli/dist/micro-function.translator-BmJZzkKh.cjs.map +0 -1
  71. package/packages/cli/dist/micro-function.translator-CDfkj4Tt.mjs +0 -11
  72. package/packages/cli/dist/micro-function.translator-CDfkj4Tt.mjs.map +0 -1
  73. package/packages/cli/dist/mock-command-Bp1LxSls.mjs +0 -350
  74. package/packages/cli/dist/mock-command-Bp1LxSls.mjs.map +0 -1
  75. package/packages/cli/dist/mock-command-WZcfy5Iq.cjs +0 -353
  76. package/packages/cli/dist/mock-command-WZcfy5Iq.cjs.map +0 -1
  77. package/packages/cli/dist/plugins-CgMhaNZq.cjs +0 -3558
  78. package/packages/cli/dist/plugins-CgMhaNZq.cjs.map +0 -1
  79. package/packages/cli/dist/plugins-DCvJ8iHR.mjs +0 -3280
  80. package/packages/cli/dist/plugins-DCvJ8iHR.mjs.map +0 -1
  81. package/packages/cli/dist/plugins.cjs +0 -36
  82. package/packages/cli/dist/plugins.d.cts +0 -2
  83. package/packages/cli/dist/plugins.d.mts +0 -2
  84. package/packages/cli/dist/plugins.mjs +0 -2
  85. package/packages/cli/dist/runtime-config-BWQlPhiB.d.cts +0 -780
  86. package/packages/cli/dist/runtime-config-BWQlPhiB.d.cts.map +0 -1
  87. package/packages/cli/dist/runtime-config-CrzK-Ucf.d.mts +0 -780
  88. package/packages/cli/dist/runtime-config-CrzK-Ucf.d.mts.map +0 -1
  89. package/packages/cli/dist/translators.cjs +0 -14
  90. package/packages/cli/dist/translators.cjs.map +0 -1
  91. package/packages/cli/dist/translators.d.cts +0 -14
  92. package/packages/cli/dist/translators.d.cts.map +0 -1
  93. package/packages/cli/dist/translators.d.mts +0 -14
  94. package/packages/cli/dist/translators.d.mts.map +0 -1
  95. package/packages/cli/dist/translators.mjs +0 -12
  96. package/packages/cli/dist/translators.mjs.map +0 -1
  97. package/packages/cli/node_modules/.bin/eslint +0 -21
  98. package/packages/cli/node_modules/.bin/jiti +0 -21
  99. package/packages/cli/node_modules/.bin/js-yaml +0 -21
  100. package/packages/cli/node_modules/.bin/jsf +0 -21
  101. package/packages/cli/node_modules/.bin/jsonpath +0 -21
  102. package/packages/cli/node_modules/.bin/jsonpath-plus +0 -21
  103. package/packages/cli/node_modules/.bin/semver +0 -21
  104. package/packages/cli/node_modules/.bin/tsc +0 -21
  105. package/packages/cli/node_modules/.bin/tsserver +0 -21
  106. package/packages/exception/.turbo/turbo-build.log +0 -29
  107. package/packages/exception/dist/index.d.mts +0 -21
  108. package/packages/exception/dist/index.d.mts.map +0 -1
  109. package/packages/exception/dist/index.d.ts +0 -21
  110. package/packages/exception/dist/index.d.ts.map +0 -1
  111. package/packages/exception/dist/index.js +0 -104
  112. package/packages/exception/dist/index.js.map +0 -1
  113. package/packages/exception/dist/index.mjs +0 -99
  114. package/packages/exception/dist/index.mjs.map +0 -1
  115. package/packages/headers/.turbo/turbo-build.log +0 -29
  116. package/packages/headers/dist/index.d.mts +0 -46
  117. package/packages/headers/dist/index.d.mts.map +0 -1
  118. package/packages/headers/dist/index.d.ts +0 -46
  119. package/packages/headers/dist/index.d.ts.map +0 -1
  120. package/packages/headers/dist/index.js +0 -81
  121. package/packages/headers/dist/index.js.map +0 -1
  122. package/packages/headers/dist/index.mjs +0 -75
  123. package/packages/headers/dist/index.mjs.map +0 -1
  124. package/packages/keq/.turbo/turbo-build.log +0 -29
  125. package/packages/keq/dist/index.d.mts +0 -962
  126. package/packages/keq/dist/index.d.mts.map +0 -1
  127. package/packages/keq/dist/index.d.ts +0 -962
  128. package/packages/keq/dist/index.d.ts.map +0 -1
  129. package/packages/keq/dist/index.js +0 -1876
  130. package/packages/keq/dist/index.js.map +0 -1
  131. package/packages/keq/dist/index.mjs +0 -1814
  132. package/packages/keq/dist/index.mjs.map +0 -1
  133. package/packages/nestjs/.turbo/turbo-build.log +0 -29
  134. package/packages/nestjs/dist/index.d.mts +0 -52
  135. package/packages/nestjs/dist/index.d.mts.map +0 -1
  136. package/packages/nestjs/dist/index.d.ts +0 -52
  137. package/packages/nestjs/dist/index.d.ts.map +0 -1
  138. package/packages/nestjs/dist/index.js +0 -166
  139. package/packages/nestjs/dist/index.js.map +0 -1
  140. package/packages/nestjs/dist/index.mjs +0 -136
  141. package/packages/nestjs/dist/index.mjs.map +0 -1
  142. package/packages/test/.turbo/turbo-build.log +0 -6
  143. package/packages/url/.turbo/turbo-build.log +0 -29
  144. package/packages/url/dist/index.d.mts +0 -9
  145. package/packages/url/dist/index.d.mts.map +0 -1
  146. package/packages/url/dist/index.d.ts +0 -9
  147. package/packages/url/dist/index.d.ts.map +0 -1
  148. package/packages/url/dist/index.js +0 -43
  149. package/packages/url/dist/index.js.map +0 -1
  150. package/packages/url/dist/index.mjs +0 -40
  151. 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