@exposify/express 0.0.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/dist/index.cjs +380 -0
- package/dist/index.d.cts +35 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.js +362 -0
- package/package.json +42 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
aiReady: () => aiReady,
|
|
24
|
+
describe: () => import_core3.describe,
|
|
25
|
+
exposed: () => import_core3.exposed,
|
|
26
|
+
hidden: () => import_core3.hidden,
|
|
27
|
+
routesFromExpress: () => routesFromExpress
|
|
28
|
+
});
|
|
29
|
+
module.exports = __toCommonJS(index_exports);
|
|
30
|
+
var import_node_path2 = require("path");
|
|
31
|
+
var import_core2 = require("@exposify/core");
|
|
32
|
+
var import_node2 = require("@exposify/core/node");
|
|
33
|
+
|
|
34
|
+
// src/surface.ts
|
|
35
|
+
var import_promises = require("fs/promises");
|
|
36
|
+
var import_node_path = require("path");
|
|
37
|
+
var import_light_my_request = require("light-my-request");
|
|
38
|
+
var import_node = require("@exposify/core/node");
|
|
39
|
+
|
|
40
|
+
// src/introspect.ts
|
|
41
|
+
var import_core = require("@exposify/core");
|
|
42
|
+
|
|
43
|
+
// src/mount-registry.ts
|
|
44
|
+
var import_node_module = require("module");
|
|
45
|
+
var import_meta = {};
|
|
46
|
+
var recordedMounts = /* @__PURE__ */ new WeakMap();
|
|
47
|
+
var patchedTargets = /* @__PURE__ */ new WeakSet();
|
|
48
|
+
function installMountRecorder(routerCtor) {
|
|
49
|
+
try {
|
|
50
|
+
const RouterCtor = routerCtor ?? resolveExpressRouter();
|
|
51
|
+
if (typeof RouterCtor !== "function") return;
|
|
52
|
+
const target = resolvePatchTarget(RouterCtor);
|
|
53
|
+
if (!target) return;
|
|
54
|
+
if (patchedTargets.has(target)) return;
|
|
55
|
+
patchedTargets.add(target);
|
|
56
|
+
const original = target.use;
|
|
57
|
+
if (typeof original !== "function") return;
|
|
58
|
+
const originalUse = original;
|
|
59
|
+
const wrapper = function(...args) {
|
|
60
|
+
try {
|
|
61
|
+
recordUse(this, args);
|
|
62
|
+
} catch {
|
|
63
|
+
}
|
|
64
|
+
return originalUse.apply(this, args);
|
|
65
|
+
};
|
|
66
|
+
Object.defineProperty(wrapper, "name", { value: "use" });
|
|
67
|
+
target.use = wrapper;
|
|
68
|
+
} catch {
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function getRecordedMounts(handle) {
|
|
72
|
+
if (typeof handle !== "function") return void 0;
|
|
73
|
+
return recordedMounts.get(handle);
|
|
74
|
+
}
|
|
75
|
+
function resolveExpressRouter() {
|
|
76
|
+
const req = resolveRequireFn();
|
|
77
|
+
if (!req) return void 0;
|
|
78
|
+
const expressModule = req("express");
|
|
79
|
+
return expressModule.Router;
|
|
80
|
+
}
|
|
81
|
+
function resolveRequireFn() {
|
|
82
|
+
try {
|
|
83
|
+
if (typeof require === "function" && typeof require.resolve === "function") return require;
|
|
84
|
+
return (0, import_node_module.createRequire)(import_meta.url);
|
|
85
|
+
} catch {
|
|
86
|
+
return void 0;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function resolvePatchTarget(RouterCtor) {
|
|
90
|
+
const proto = RouterCtor.prototype;
|
|
91
|
+
if (proto && typeof proto.use === "function") {
|
|
92
|
+
return proto;
|
|
93
|
+
}
|
|
94
|
+
if (typeof RouterCtor.use === "function") {
|
|
95
|
+
return RouterCtor;
|
|
96
|
+
}
|
|
97
|
+
return void 0;
|
|
98
|
+
}
|
|
99
|
+
function classifyFirstArg(first) {
|
|
100
|
+
let arg = first;
|
|
101
|
+
while (Array.isArray(arg) && arg.length !== 0) arg = arg[0];
|
|
102
|
+
if (typeof arg === "function") {
|
|
103
|
+
return { paths: ["/"], handlerOffset: 0 };
|
|
104
|
+
}
|
|
105
|
+
if (typeof first === "string") {
|
|
106
|
+
return { paths: [first], handlerOffset: 1 };
|
|
107
|
+
}
|
|
108
|
+
if (Array.isArray(first) && first.every((p) => typeof p === "string")) {
|
|
109
|
+
return { paths: first, handlerOffset: 1 };
|
|
110
|
+
}
|
|
111
|
+
return { paths: null, handlerOffset: 1 };
|
|
112
|
+
}
|
|
113
|
+
function recordUse(parent, args) {
|
|
114
|
+
if (args.length === 0) return;
|
|
115
|
+
const { paths, handlerOffset } = classifyFirstArg(args[0]);
|
|
116
|
+
if (paths === null) return;
|
|
117
|
+
const rawHandlers = args.slice(handlerOffset);
|
|
118
|
+
for (const fn of flattenHandlers(rawHandlers)) {
|
|
119
|
+
if (typeof fn !== "function") continue;
|
|
120
|
+
if (!Array.isArray(fn.stack)) continue;
|
|
121
|
+
const existing = recordedMounts.get(fn) ?? [];
|
|
122
|
+
for (const p of paths) {
|
|
123
|
+
if (!existing.some((e) => e.parent === parent && e.path === p)) {
|
|
124
|
+
existing.push({ parent, path: p });
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
recordedMounts.set(fn, existing);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function flattenHandlers(items) {
|
|
131
|
+
return items.flatMap((item) => Array.isArray(item) ? flattenHandlers(item) : [item]);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// src/introspect.ts
|
|
135
|
+
function routesFromExpress(app, opts) {
|
|
136
|
+
const router = routerOf(app);
|
|
137
|
+
const out = [];
|
|
138
|
+
const walked = /* @__PURE__ */ new Map();
|
|
139
|
+
const unresolved = /* @__PURE__ */ new Set();
|
|
140
|
+
if (router?.stack) {
|
|
141
|
+
walk(router.stack, "", router, out, walked, unresolved);
|
|
142
|
+
}
|
|
143
|
+
if (opts?.mounts) {
|
|
144
|
+
for (const [prefix, handle] of Object.entries(opts.mounts)) {
|
|
145
|
+
try {
|
|
146
|
+
const resolved = ownerAndStackOf(handle);
|
|
147
|
+
if (!resolved) continue;
|
|
148
|
+
unresolved.delete(handle);
|
|
149
|
+
walkOnce(handle, prefix, resolved.stack, resolved.owner, out, walked, unresolved);
|
|
150
|
+
} catch {
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
if (unresolved.size > 0) warnMountSkipped();
|
|
155
|
+
return out;
|
|
156
|
+
}
|
|
157
|
+
var warnedAboutSkippedMount = false;
|
|
158
|
+
function warnMountSkipped() {
|
|
159
|
+
if (warnedAboutSkippedMount) return;
|
|
160
|
+
warnedAboutSkippedMount = true;
|
|
161
|
+
try {
|
|
162
|
+
console.warn(
|
|
163
|
+
`[agentready] skipped one or more mounted routers: their mount path could not be determined automatically, so routes under them will not be exposed as tools. If aiReady() is registered before they're mounted, this is usually automatic (Express 5's mount recorder); otherwise pass them explicitly via aiReady({ mounts: { "/path": router } }).`
|
|
164
|
+
);
|
|
165
|
+
} catch {
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
function routerOf(app) {
|
|
169
|
+
if (!app || typeof app !== "object" && typeof app !== "function") return void 0;
|
|
170
|
+
const withRouter = app;
|
|
171
|
+
if (withRouter._router?.stack) return withRouter._router;
|
|
172
|
+
try {
|
|
173
|
+
return withRouter.router;
|
|
174
|
+
} catch {
|
|
175
|
+
return void 0;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
function walk(stack, prefix, owner, out, walked, unresolved) {
|
|
179
|
+
for (const layer of stack) {
|
|
180
|
+
try {
|
|
181
|
+
if (layer.route) {
|
|
182
|
+
const path = joinPaths(prefix, layer.route.path);
|
|
183
|
+
const handlers = layer.route.stack.map((s) => s.handle);
|
|
184
|
+
const marker = handlers.map(import_core.getMarker).find(Boolean);
|
|
185
|
+
for (const [method, enabled] of Object.entries(layer.route.methods)) {
|
|
186
|
+
if (!enabled || method === "_all") continue;
|
|
187
|
+
out.push({
|
|
188
|
+
method: method.toUpperCase(),
|
|
189
|
+
path,
|
|
190
|
+
params: extractParams(path),
|
|
191
|
+
marker,
|
|
192
|
+
description: marker?.kind === "described" ? marker.description : void 0
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
const handle = layer.handle;
|
|
198
|
+
if (handle === void 0 || handle === null) continue;
|
|
199
|
+
const mountPaths = /* @__PURE__ */ new Set();
|
|
200
|
+
const recorded = getRecordedMountPaths(handle, owner);
|
|
201
|
+
if (recorded) for (const p of recorded) mountPaths.add(p);
|
|
202
|
+
const regexpMount = mountPathOf(layer);
|
|
203
|
+
if (regexpMount !== null) mountPaths.add(regexpMount);
|
|
204
|
+
const subAppMount = subAppMountOf(handle);
|
|
205
|
+
if (subAppMount !== null) mountPaths.add(subAppMount);
|
|
206
|
+
const resolved = mountPaths.size > 0 && walkEachVia([...mountPaths], handle, prefix, out, walked, unresolved);
|
|
207
|
+
if (resolved) continue;
|
|
208
|
+
if (looksRouterish(handle) || looksLikeExpressApp(handle)) {
|
|
209
|
+
unresolved.add(handle);
|
|
210
|
+
}
|
|
211
|
+
} catch {
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
function getRecordedMountPaths(handle, owner) {
|
|
216
|
+
const recorded = getRecordedMounts(handle);
|
|
217
|
+
if (!recorded) return null;
|
|
218
|
+
const paths = recorded.filter((entry) => entry.parent === owner).map((entry) => entry.path);
|
|
219
|
+
return paths.length > 0 ? paths : null;
|
|
220
|
+
}
|
|
221
|
+
function walkEachVia(mounts, handle, prefix, out, walked, unresolved) {
|
|
222
|
+
const resolved = ownerAndStackOf(handle);
|
|
223
|
+
if (!resolved) return false;
|
|
224
|
+
unresolved.delete(handle);
|
|
225
|
+
for (const mount of mounts) {
|
|
226
|
+
walkOnce(handle, joinPaths(prefix, mount), resolved.stack, resolved.owner, out, walked, unresolved);
|
|
227
|
+
}
|
|
228
|
+
return true;
|
|
229
|
+
}
|
|
230
|
+
function walkOnce(handle, prefix, stack, owner, out, walked, unresolved) {
|
|
231
|
+
let prefixes = walked.get(handle);
|
|
232
|
+
if (prefixes?.has(prefix)) return;
|
|
233
|
+
if (!prefixes) {
|
|
234
|
+
prefixes = /* @__PURE__ */ new Set();
|
|
235
|
+
walked.set(handle, prefixes);
|
|
236
|
+
}
|
|
237
|
+
prefixes.add(prefix);
|
|
238
|
+
walk(stack, prefix, owner, out, walked, unresolved);
|
|
239
|
+
}
|
|
240
|
+
function mountPathOf(layer) {
|
|
241
|
+
if (layer.regexp?.fast_slash) return "";
|
|
242
|
+
const source = layer.regexp?.source;
|
|
243
|
+
if (!source) return null;
|
|
244
|
+
const match = /^\^((?:\\\/[A-Za-z0-9._~-]+)+)\\\/\?(?:\(\?=\\\/\|\$\)|\$)/.exec(source);
|
|
245
|
+
if (!match) return null;
|
|
246
|
+
return match[1].replace(/\\\//g, "/");
|
|
247
|
+
}
|
|
248
|
+
function joinPaths(a, b) {
|
|
249
|
+
const joined = `${a}${b.startsWith("/") ? b : `/${b}`}`;
|
|
250
|
+
return joined.replace(/\/+/g, "/").replace(/(.)\/$/, "$1") || "/";
|
|
251
|
+
}
|
|
252
|
+
function extractParams(path) {
|
|
253
|
+
return [...path.matchAll(/:([A-Za-z0-9_]+)/g)].map((m) => m[1]);
|
|
254
|
+
}
|
|
255
|
+
function looksRouterish(handle) {
|
|
256
|
+
return Array.isArray(handle?.stack);
|
|
257
|
+
}
|
|
258
|
+
function looksLikeExpressApp(handle) {
|
|
259
|
+
if (typeof handle !== "function") return false;
|
|
260
|
+
const h = handle;
|
|
261
|
+
return typeof h.handle === "function" && typeof h.set === "function";
|
|
262
|
+
}
|
|
263
|
+
function subAppMountOf(handle) {
|
|
264
|
+
if (!looksLikeExpressApp(handle)) return null;
|
|
265
|
+
const mountpath = handle.mountpath;
|
|
266
|
+
const value = Array.isArray(mountpath) ? mountpath[0] : mountpath;
|
|
267
|
+
if (typeof value !== "string" || value === "/") return null;
|
|
268
|
+
return value;
|
|
269
|
+
}
|
|
270
|
+
function ownerAndStackOf(handle) {
|
|
271
|
+
const direct = handle?.stack;
|
|
272
|
+
if (Array.isArray(direct)) return { owner: handle, stack: direct };
|
|
273
|
+
const sub = routerOf(handle);
|
|
274
|
+
if (sub?.stack) return { owner: sub, stack: sub.stack };
|
|
275
|
+
return void 0;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// src/surface.ts
|
|
279
|
+
async function buildExpressSurface(app, cfg, cwd, opts) {
|
|
280
|
+
const info = await readPackageInfo(cwd);
|
|
281
|
+
const appish = app;
|
|
282
|
+
if (!appish.request || !appish.response) {
|
|
283
|
+
console.warn(
|
|
284
|
+
"[agentready] express request/response prototypes not found; light-my-request corruption guard inactive"
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
const reqProto = appish.request ? Object.getPrototypeOf(appish.request) : void 0;
|
|
288
|
+
const resProto = appish.response ? Object.getPrototypeOf(appish.response) : void 0;
|
|
289
|
+
const pristineReqProto = reqProto ? Object.getPrototypeOf(reqProto) : void 0;
|
|
290
|
+
const pristineResProto = resProto ? Object.getPrototypeOf(resProto) : void 0;
|
|
291
|
+
return {
|
|
292
|
+
info,
|
|
293
|
+
routes: routesFromExpress(app, opts),
|
|
294
|
+
docs: await (0, import_node.loadDocsFromFs)(cfg.docs, cwd),
|
|
295
|
+
async dispatch(req) {
|
|
296
|
+
const url = new URL(req.url);
|
|
297
|
+
try {
|
|
298
|
+
const res = await (0, import_light_my_request.inject)(app, {
|
|
299
|
+
method: req.method,
|
|
300
|
+
url: url.pathname + url.search,
|
|
301
|
+
headers: Object.fromEntries(req.headers.entries()),
|
|
302
|
+
payload: req.body ? await req.text() : void 0
|
|
303
|
+
});
|
|
304
|
+
return new Response(res.body, {
|
|
305
|
+
status: res.statusCode,
|
|
306
|
+
headers: res.headers
|
|
307
|
+
});
|
|
308
|
+
} finally {
|
|
309
|
+
if (pristineReqProto && reqProto) Object.setPrototypeOf(reqProto, pristineReqProto);
|
|
310
|
+
if (pristineResProto && resProto) Object.setPrototypeOf(resProto, pristineResProto);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
async function readPackageInfo(cwd) {
|
|
316
|
+
try {
|
|
317
|
+
const pkg = JSON.parse(await (0, import_promises.readFile)((0, import_node_path.join)(cwd, "package.json"), "utf8"));
|
|
318
|
+
return { name: pkg.name ?? "app", version: pkg.version ?? "0.0.0" };
|
|
319
|
+
} catch {
|
|
320
|
+
return { name: "app", version: "0.0.0" };
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// src/index.ts
|
|
325
|
+
var import_core3 = require("@exposify/core");
|
|
326
|
+
var MCP_PATH = "/mcp";
|
|
327
|
+
function aiReady(options = {}) {
|
|
328
|
+
const cfg = (0, import_core2.resolveOptions)(options);
|
|
329
|
+
const cwd = options.cwd ?? process.cwd();
|
|
330
|
+
const mounts = options.mounts;
|
|
331
|
+
try {
|
|
332
|
+
installMountRecorder();
|
|
333
|
+
} catch {
|
|
334
|
+
}
|
|
335
|
+
let handlerPromise = null;
|
|
336
|
+
let disabled = false;
|
|
337
|
+
let warned = false;
|
|
338
|
+
return (req, res, next) => {
|
|
339
|
+
const path = req.path ?? req.url.split("?")[0];
|
|
340
|
+
if (path !== MCP_PATH && !path.startsWith(`${MCP_PATH}/`)) return next();
|
|
341
|
+
if (disabled) {
|
|
342
|
+
res.writeHead(503).end("agentready is disabled due to an internal error");
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
handlerPromise ??= initialize(req.app, cfg, cwd, mounts);
|
|
346
|
+
handlerPromise.then((handler) => handler(req, res)).catch((err) => {
|
|
347
|
+
disabled = true;
|
|
348
|
+
handlerPromise = null;
|
|
349
|
+
if (!warned) {
|
|
350
|
+
warned = true;
|
|
351
|
+
console.warn("[agentready] disabled after internal error:", err);
|
|
352
|
+
}
|
|
353
|
+
if (!res.headersSent) res.writeHead(503).end("agentready failed to initialize");
|
|
354
|
+
});
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
async function initialize(app, cfg, cwd, mounts) {
|
|
358
|
+
let surface = null;
|
|
359
|
+
let kbPromise = null;
|
|
360
|
+
const getSurface = async () => {
|
|
361
|
+
surface ??= await buildExpressSurface(app, cfg, cwd, { mounts });
|
|
362
|
+
return surface;
|
|
363
|
+
};
|
|
364
|
+
const getKb = async () => {
|
|
365
|
+
const s = await getSurface();
|
|
366
|
+
kbPromise ??= (0, import_node2.loadOrBuildKb)(s.docs, (0, import_node_path2.join)(cwd, ".agentready"));
|
|
367
|
+
return kbPromise;
|
|
368
|
+
};
|
|
369
|
+
await getSurface();
|
|
370
|
+
await getKb();
|
|
371
|
+
return (0, import_node2.createNodeMcpHandler)({ getSurface, getKb, cfg });
|
|
372
|
+
}
|
|
373
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
374
|
+
0 && (module.exports = {
|
|
375
|
+
aiReady,
|
|
376
|
+
describe,
|
|
377
|
+
exposed,
|
|
378
|
+
hidden,
|
|
379
|
+
routesFromExpress
|
|
380
|
+
});
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { RouteInfo, AgentReadyOptions } from '@exposify/core';
|
|
2
|
+
export { describe, exposed, hidden } from '@exposify/core';
|
|
3
|
+
|
|
4
|
+
interface RoutesFromExpressOptions {
|
|
5
|
+
/**
|
|
6
|
+
* Explicit fallback for router (or sub-app) mounts the automatic
|
|
7
|
+
* mechanisms can't resolve -- most commonly a sub-app mounted via
|
|
8
|
+
* `app.use(path, subApp)`, whose real handle Express wraps in an opaque
|
|
9
|
+
* dispatch closure before storing it as `layer.handle` (see
|
|
10
|
+
* subAppMountOf's doc comment), so there is no reflectable trace of it on
|
|
11
|
+
* the app's router stack at all. Keyed by the mount prefix, valued by the
|
|
12
|
+
* exact router/app object registered at that prefix.
|
|
13
|
+
*/
|
|
14
|
+
mounts?: Record<string, unknown>;
|
|
15
|
+
}
|
|
16
|
+
declare function routesFromExpress(app: unknown, opts?: RoutesFromExpressOptions): RouteInfo[];
|
|
17
|
+
|
|
18
|
+
interface ExpressAiReadyOptions extends AgentReadyOptions {
|
|
19
|
+
/** Where docs globs and the .agentready cache resolve. Default: process.cwd() */
|
|
20
|
+
cwd?: string;
|
|
21
|
+
/**
|
|
22
|
+
* Explicit mount paths for routers (or sub-apps) introspection can't
|
|
23
|
+
* discover on its own -- most commonly a sub-app mounted via
|
|
24
|
+
* `app.use(path, subApp)`. On Express 5, an ordinary `app.use(path,
|
|
25
|
+
* router)` mount is discovered automatically as long as `aiReady()` is
|
|
26
|
+
* registered before it (the standard layout); this option is the
|
|
27
|
+
* fallback for anything mounted earlier, or that automatic discovery
|
|
28
|
+
* otherwise can't reach. Keyed by mount prefix, valued by the exact
|
|
29
|
+
* router/app object registered at that prefix.
|
|
30
|
+
*/
|
|
31
|
+
mounts?: Record<string, unknown>;
|
|
32
|
+
}
|
|
33
|
+
declare function aiReady(options?: ExpressAiReadyOptions): (req: any, res: any, next: any) => void;
|
|
34
|
+
|
|
35
|
+
export { type ExpressAiReadyOptions, aiReady, routesFromExpress };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { RouteInfo, AgentReadyOptions } from '@exposify/core';
|
|
2
|
+
export { describe, exposed, hidden } from '@exposify/core';
|
|
3
|
+
|
|
4
|
+
interface RoutesFromExpressOptions {
|
|
5
|
+
/**
|
|
6
|
+
* Explicit fallback for router (or sub-app) mounts the automatic
|
|
7
|
+
* mechanisms can't resolve -- most commonly a sub-app mounted via
|
|
8
|
+
* `app.use(path, subApp)`, whose real handle Express wraps in an opaque
|
|
9
|
+
* dispatch closure before storing it as `layer.handle` (see
|
|
10
|
+
* subAppMountOf's doc comment), so there is no reflectable trace of it on
|
|
11
|
+
* the app's router stack at all. Keyed by the mount prefix, valued by the
|
|
12
|
+
* exact router/app object registered at that prefix.
|
|
13
|
+
*/
|
|
14
|
+
mounts?: Record<string, unknown>;
|
|
15
|
+
}
|
|
16
|
+
declare function routesFromExpress(app: unknown, opts?: RoutesFromExpressOptions): RouteInfo[];
|
|
17
|
+
|
|
18
|
+
interface ExpressAiReadyOptions extends AgentReadyOptions {
|
|
19
|
+
/** Where docs globs and the .agentready cache resolve. Default: process.cwd() */
|
|
20
|
+
cwd?: string;
|
|
21
|
+
/**
|
|
22
|
+
* Explicit mount paths for routers (or sub-apps) introspection can't
|
|
23
|
+
* discover on its own -- most commonly a sub-app mounted via
|
|
24
|
+
* `app.use(path, subApp)`. On Express 5, an ordinary `app.use(path,
|
|
25
|
+
* router)` mount is discovered automatically as long as `aiReady()` is
|
|
26
|
+
* registered before it (the standard layout); this option is the
|
|
27
|
+
* fallback for anything mounted earlier, or that automatic discovery
|
|
28
|
+
* otherwise can't reach. Keyed by mount prefix, valued by the exact
|
|
29
|
+
* router/app object registered at that prefix.
|
|
30
|
+
*/
|
|
31
|
+
mounts?: Record<string, unknown>;
|
|
32
|
+
}
|
|
33
|
+
declare function aiReady(options?: ExpressAiReadyOptions): (req: any, res: any, next: any) => void;
|
|
34
|
+
|
|
35
|
+
export { type ExpressAiReadyOptions, aiReady, routesFromExpress };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
+
}) : x)(function(x) {
|
|
4
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
5
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
// src/index.ts
|
|
9
|
+
import { join as join2 } from "path";
|
|
10
|
+
import {
|
|
11
|
+
resolveOptions
|
|
12
|
+
} from "@exposify/core";
|
|
13
|
+
import {
|
|
14
|
+
createNodeMcpHandler,
|
|
15
|
+
loadOrBuildKb
|
|
16
|
+
} from "@exposify/core/node";
|
|
17
|
+
|
|
18
|
+
// src/surface.ts
|
|
19
|
+
import { readFile } from "fs/promises";
|
|
20
|
+
import { join } from "path";
|
|
21
|
+
import { inject } from "light-my-request";
|
|
22
|
+
import { loadDocsFromFs } from "@exposify/core/node";
|
|
23
|
+
|
|
24
|
+
// src/introspect.ts
|
|
25
|
+
import { getMarker } from "@exposify/core";
|
|
26
|
+
|
|
27
|
+
// src/mount-registry.ts
|
|
28
|
+
import { createRequire } from "module";
|
|
29
|
+
var recordedMounts = /* @__PURE__ */ new WeakMap();
|
|
30
|
+
var patchedTargets = /* @__PURE__ */ new WeakSet();
|
|
31
|
+
function installMountRecorder(routerCtor) {
|
|
32
|
+
try {
|
|
33
|
+
const RouterCtor = routerCtor ?? resolveExpressRouter();
|
|
34
|
+
if (typeof RouterCtor !== "function") return;
|
|
35
|
+
const target = resolvePatchTarget(RouterCtor);
|
|
36
|
+
if (!target) return;
|
|
37
|
+
if (patchedTargets.has(target)) return;
|
|
38
|
+
patchedTargets.add(target);
|
|
39
|
+
const original = target.use;
|
|
40
|
+
if (typeof original !== "function") return;
|
|
41
|
+
const originalUse = original;
|
|
42
|
+
const wrapper = function(...args) {
|
|
43
|
+
try {
|
|
44
|
+
recordUse(this, args);
|
|
45
|
+
} catch {
|
|
46
|
+
}
|
|
47
|
+
return originalUse.apply(this, args);
|
|
48
|
+
};
|
|
49
|
+
Object.defineProperty(wrapper, "name", { value: "use" });
|
|
50
|
+
target.use = wrapper;
|
|
51
|
+
} catch {
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function getRecordedMounts(handle) {
|
|
55
|
+
if (typeof handle !== "function") return void 0;
|
|
56
|
+
return recordedMounts.get(handle);
|
|
57
|
+
}
|
|
58
|
+
function resolveExpressRouter() {
|
|
59
|
+
const req = resolveRequireFn();
|
|
60
|
+
if (!req) return void 0;
|
|
61
|
+
const expressModule = req("express");
|
|
62
|
+
return expressModule.Router;
|
|
63
|
+
}
|
|
64
|
+
function resolveRequireFn() {
|
|
65
|
+
try {
|
|
66
|
+
if (typeof __require === "function" && typeof __require.resolve === "function") return __require;
|
|
67
|
+
return createRequire(import.meta.url);
|
|
68
|
+
} catch {
|
|
69
|
+
return void 0;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function resolvePatchTarget(RouterCtor) {
|
|
73
|
+
const proto = RouterCtor.prototype;
|
|
74
|
+
if (proto && typeof proto.use === "function") {
|
|
75
|
+
return proto;
|
|
76
|
+
}
|
|
77
|
+
if (typeof RouterCtor.use === "function") {
|
|
78
|
+
return RouterCtor;
|
|
79
|
+
}
|
|
80
|
+
return void 0;
|
|
81
|
+
}
|
|
82
|
+
function classifyFirstArg(first) {
|
|
83
|
+
let arg = first;
|
|
84
|
+
while (Array.isArray(arg) && arg.length !== 0) arg = arg[0];
|
|
85
|
+
if (typeof arg === "function") {
|
|
86
|
+
return { paths: ["/"], handlerOffset: 0 };
|
|
87
|
+
}
|
|
88
|
+
if (typeof first === "string") {
|
|
89
|
+
return { paths: [first], handlerOffset: 1 };
|
|
90
|
+
}
|
|
91
|
+
if (Array.isArray(first) && first.every((p) => typeof p === "string")) {
|
|
92
|
+
return { paths: first, handlerOffset: 1 };
|
|
93
|
+
}
|
|
94
|
+
return { paths: null, handlerOffset: 1 };
|
|
95
|
+
}
|
|
96
|
+
function recordUse(parent, args) {
|
|
97
|
+
if (args.length === 0) return;
|
|
98
|
+
const { paths, handlerOffset } = classifyFirstArg(args[0]);
|
|
99
|
+
if (paths === null) return;
|
|
100
|
+
const rawHandlers = args.slice(handlerOffset);
|
|
101
|
+
for (const fn of flattenHandlers(rawHandlers)) {
|
|
102
|
+
if (typeof fn !== "function") continue;
|
|
103
|
+
if (!Array.isArray(fn.stack)) continue;
|
|
104
|
+
const existing = recordedMounts.get(fn) ?? [];
|
|
105
|
+
for (const p of paths) {
|
|
106
|
+
if (!existing.some((e) => e.parent === parent && e.path === p)) {
|
|
107
|
+
existing.push({ parent, path: p });
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
recordedMounts.set(fn, existing);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function flattenHandlers(items) {
|
|
114
|
+
return items.flatMap((item) => Array.isArray(item) ? flattenHandlers(item) : [item]);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// src/introspect.ts
|
|
118
|
+
function routesFromExpress(app, opts) {
|
|
119
|
+
const router = routerOf(app);
|
|
120
|
+
const out = [];
|
|
121
|
+
const walked = /* @__PURE__ */ new Map();
|
|
122
|
+
const unresolved = /* @__PURE__ */ new Set();
|
|
123
|
+
if (router?.stack) {
|
|
124
|
+
walk(router.stack, "", router, out, walked, unresolved);
|
|
125
|
+
}
|
|
126
|
+
if (opts?.mounts) {
|
|
127
|
+
for (const [prefix, handle] of Object.entries(opts.mounts)) {
|
|
128
|
+
try {
|
|
129
|
+
const resolved = ownerAndStackOf(handle);
|
|
130
|
+
if (!resolved) continue;
|
|
131
|
+
unresolved.delete(handle);
|
|
132
|
+
walkOnce(handle, prefix, resolved.stack, resolved.owner, out, walked, unresolved);
|
|
133
|
+
} catch {
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (unresolved.size > 0) warnMountSkipped();
|
|
138
|
+
return out;
|
|
139
|
+
}
|
|
140
|
+
var warnedAboutSkippedMount = false;
|
|
141
|
+
function warnMountSkipped() {
|
|
142
|
+
if (warnedAboutSkippedMount) return;
|
|
143
|
+
warnedAboutSkippedMount = true;
|
|
144
|
+
try {
|
|
145
|
+
console.warn(
|
|
146
|
+
`[agentready] skipped one or more mounted routers: their mount path could not be determined automatically, so routes under them will not be exposed as tools. If aiReady() is registered before they're mounted, this is usually automatic (Express 5's mount recorder); otherwise pass them explicitly via aiReady({ mounts: { "/path": router } }).`
|
|
147
|
+
);
|
|
148
|
+
} catch {
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
function routerOf(app) {
|
|
152
|
+
if (!app || typeof app !== "object" && typeof app !== "function") return void 0;
|
|
153
|
+
const withRouter = app;
|
|
154
|
+
if (withRouter._router?.stack) return withRouter._router;
|
|
155
|
+
try {
|
|
156
|
+
return withRouter.router;
|
|
157
|
+
} catch {
|
|
158
|
+
return void 0;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
function walk(stack, prefix, owner, out, walked, unresolved) {
|
|
162
|
+
for (const layer of stack) {
|
|
163
|
+
try {
|
|
164
|
+
if (layer.route) {
|
|
165
|
+
const path = joinPaths(prefix, layer.route.path);
|
|
166
|
+
const handlers = layer.route.stack.map((s) => s.handle);
|
|
167
|
+
const marker = handlers.map(getMarker).find(Boolean);
|
|
168
|
+
for (const [method, enabled] of Object.entries(layer.route.methods)) {
|
|
169
|
+
if (!enabled || method === "_all") continue;
|
|
170
|
+
out.push({
|
|
171
|
+
method: method.toUpperCase(),
|
|
172
|
+
path,
|
|
173
|
+
params: extractParams(path),
|
|
174
|
+
marker,
|
|
175
|
+
description: marker?.kind === "described" ? marker.description : void 0
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
const handle = layer.handle;
|
|
181
|
+
if (handle === void 0 || handle === null) continue;
|
|
182
|
+
const mountPaths = /* @__PURE__ */ new Set();
|
|
183
|
+
const recorded = getRecordedMountPaths(handle, owner);
|
|
184
|
+
if (recorded) for (const p of recorded) mountPaths.add(p);
|
|
185
|
+
const regexpMount = mountPathOf(layer);
|
|
186
|
+
if (regexpMount !== null) mountPaths.add(regexpMount);
|
|
187
|
+
const subAppMount = subAppMountOf(handle);
|
|
188
|
+
if (subAppMount !== null) mountPaths.add(subAppMount);
|
|
189
|
+
const resolved = mountPaths.size > 0 && walkEachVia([...mountPaths], handle, prefix, out, walked, unresolved);
|
|
190
|
+
if (resolved) continue;
|
|
191
|
+
if (looksRouterish(handle) || looksLikeExpressApp(handle)) {
|
|
192
|
+
unresolved.add(handle);
|
|
193
|
+
}
|
|
194
|
+
} catch {
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
function getRecordedMountPaths(handle, owner) {
|
|
199
|
+
const recorded = getRecordedMounts(handle);
|
|
200
|
+
if (!recorded) return null;
|
|
201
|
+
const paths = recorded.filter((entry) => entry.parent === owner).map((entry) => entry.path);
|
|
202
|
+
return paths.length > 0 ? paths : null;
|
|
203
|
+
}
|
|
204
|
+
function walkEachVia(mounts, handle, prefix, out, walked, unresolved) {
|
|
205
|
+
const resolved = ownerAndStackOf(handle);
|
|
206
|
+
if (!resolved) return false;
|
|
207
|
+
unresolved.delete(handle);
|
|
208
|
+
for (const mount of mounts) {
|
|
209
|
+
walkOnce(handle, joinPaths(prefix, mount), resolved.stack, resolved.owner, out, walked, unresolved);
|
|
210
|
+
}
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
function walkOnce(handle, prefix, stack, owner, out, walked, unresolved) {
|
|
214
|
+
let prefixes = walked.get(handle);
|
|
215
|
+
if (prefixes?.has(prefix)) return;
|
|
216
|
+
if (!prefixes) {
|
|
217
|
+
prefixes = /* @__PURE__ */ new Set();
|
|
218
|
+
walked.set(handle, prefixes);
|
|
219
|
+
}
|
|
220
|
+
prefixes.add(prefix);
|
|
221
|
+
walk(stack, prefix, owner, out, walked, unresolved);
|
|
222
|
+
}
|
|
223
|
+
function mountPathOf(layer) {
|
|
224
|
+
if (layer.regexp?.fast_slash) return "";
|
|
225
|
+
const source = layer.regexp?.source;
|
|
226
|
+
if (!source) return null;
|
|
227
|
+
const match = /^\^((?:\\\/[A-Za-z0-9._~-]+)+)\\\/\?(?:\(\?=\\\/\|\$\)|\$)/.exec(source);
|
|
228
|
+
if (!match) return null;
|
|
229
|
+
return match[1].replace(/\\\//g, "/");
|
|
230
|
+
}
|
|
231
|
+
function joinPaths(a, b) {
|
|
232
|
+
const joined = `${a}${b.startsWith("/") ? b : `/${b}`}`;
|
|
233
|
+
return joined.replace(/\/+/g, "/").replace(/(.)\/$/, "$1") || "/";
|
|
234
|
+
}
|
|
235
|
+
function extractParams(path) {
|
|
236
|
+
return [...path.matchAll(/:([A-Za-z0-9_]+)/g)].map((m) => m[1]);
|
|
237
|
+
}
|
|
238
|
+
function looksRouterish(handle) {
|
|
239
|
+
return Array.isArray(handle?.stack);
|
|
240
|
+
}
|
|
241
|
+
function looksLikeExpressApp(handle) {
|
|
242
|
+
if (typeof handle !== "function") return false;
|
|
243
|
+
const h = handle;
|
|
244
|
+
return typeof h.handle === "function" && typeof h.set === "function";
|
|
245
|
+
}
|
|
246
|
+
function subAppMountOf(handle) {
|
|
247
|
+
if (!looksLikeExpressApp(handle)) return null;
|
|
248
|
+
const mountpath = handle.mountpath;
|
|
249
|
+
const value = Array.isArray(mountpath) ? mountpath[0] : mountpath;
|
|
250
|
+
if (typeof value !== "string" || value === "/") return null;
|
|
251
|
+
return value;
|
|
252
|
+
}
|
|
253
|
+
function ownerAndStackOf(handle) {
|
|
254
|
+
const direct = handle?.stack;
|
|
255
|
+
if (Array.isArray(direct)) return { owner: handle, stack: direct };
|
|
256
|
+
const sub = routerOf(handle);
|
|
257
|
+
if (sub?.stack) return { owner: sub, stack: sub.stack };
|
|
258
|
+
return void 0;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// src/surface.ts
|
|
262
|
+
async function buildExpressSurface(app, cfg, cwd, opts) {
|
|
263
|
+
const info = await readPackageInfo(cwd);
|
|
264
|
+
const appish = app;
|
|
265
|
+
if (!appish.request || !appish.response) {
|
|
266
|
+
console.warn(
|
|
267
|
+
"[agentready] express request/response prototypes not found; light-my-request corruption guard inactive"
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
const reqProto = appish.request ? Object.getPrototypeOf(appish.request) : void 0;
|
|
271
|
+
const resProto = appish.response ? Object.getPrototypeOf(appish.response) : void 0;
|
|
272
|
+
const pristineReqProto = reqProto ? Object.getPrototypeOf(reqProto) : void 0;
|
|
273
|
+
const pristineResProto = resProto ? Object.getPrototypeOf(resProto) : void 0;
|
|
274
|
+
return {
|
|
275
|
+
info,
|
|
276
|
+
routes: routesFromExpress(app, opts),
|
|
277
|
+
docs: await loadDocsFromFs(cfg.docs, cwd),
|
|
278
|
+
async dispatch(req) {
|
|
279
|
+
const url = new URL(req.url);
|
|
280
|
+
try {
|
|
281
|
+
const res = await inject(app, {
|
|
282
|
+
method: req.method,
|
|
283
|
+
url: url.pathname + url.search,
|
|
284
|
+
headers: Object.fromEntries(req.headers.entries()),
|
|
285
|
+
payload: req.body ? await req.text() : void 0
|
|
286
|
+
});
|
|
287
|
+
return new Response(res.body, {
|
|
288
|
+
status: res.statusCode,
|
|
289
|
+
headers: res.headers
|
|
290
|
+
});
|
|
291
|
+
} finally {
|
|
292
|
+
if (pristineReqProto && reqProto) Object.setPrototypeOf(reqProto, pristineReqProto);
|
|
293
|
+
if (pristineResProto && resProto) Object.setPrototypeOf(resProto, pristineResProto);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
async function readPackageInfo(cwd) {
|
|
299
|
+
try {
|
|
300
|
+
const pkg = JSON.parse(await readFile(join(cwd, "package.json"), "utf8"));
|
|
301
|
+
return { name: pkg.name ?? "app", version: pkg.version ?? "0.0.0" };
|
|
302
|
+
} catch {
|
|
303
|
+
return { name: "app", version: "0.0.0" };
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// src/index.ts
|
|
308
|
+
import { exposed, hidden, describe } from "@exposify/core";
|
|
309
|
+
var MCP_PATH = "/mcp";
|
|
310
|
+
function aiReady(options = {}) {
|
|
311
|
+
const cfg = resolveOptions(options);
|
|
312
|
+
const cwd = options.cwd ?? process.cwd();
|
|
313
|
+
const mounts = options.mounts;
|
|
314
|
+
try {
|
|
315
|
+
installMountRecorder();
|
|
316
|
+
} catch {
|
|
317
|
+
}
|
|
318
|
+
let handlerPromise = null;
|
|
319
|
+
let disabled = false;
|
|
320
|
+
let warned = false;
|
|
321
|
+
return (req, res, next) => {
|
|
322
|
+
const path = req.path ?? req.url.split("?")[0];
|
|
323
|
+
if (path !== MCP_PATH && !path.startsWith(`${MCP_PATH}/`)) return next();
|
|
324
|
+
if (disabled) {
|
|
325
|
+
res.writeHead(503).end("agentready is disabled due to an internal error");
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
handlerPromise ??= initialize(req.app, cfg, cwd, mounts);
|
|
329
|
+
handlerPromise.then((handler) => handler(req, res)).catch((err) => {
|
|
330
|
+
disabled = true;
|
|
331
|
+
handlerPromise = null;
|
|
332
|
+
if (!warned) {
|
|
333
|
+
warned = true;
|
|
334
|
+
console.warn("[agentready] disabled after internal error:", err);
|
|
335
|
+
}
|
|
336
|
+
if (!res.headersSent) res.writeHead(503).end("agentready failed to initialize");
|
|
337
|
+
});
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
async function initialize(app, cfg, cwd, mounts) {
|
|
341
|
+
let surface = null;
|
|
342
|
+
let kbPromise = null;
|
|
343
|
+
const getSurface = async () => {
|
|
344
|
+
surface ??= await buildExpressSurface(app, cfg, cwd, { mounts });
|
|
345
|
+
return surface;
|
|
346
|
+
};
|
|
347
|
+
const getKb = async () => {
|
|
348
|
+
const s = await getSurface();
|
|
349
|
+
kbPromise ??= loadOrBuildKb(s.docs, join2(cwd, ".agentready"));
|
|
350
|
+
return kbPromise;
|
|
351
|
+
};
|
|
352
|
+
await getSurface();
|
|
353
|
+
await getKb();
|
|
354
|
+
return createNodeMcpHandler({ getSurface, getKb, cfg });
|
|
355
|
+
}
|
|
356
|
+
export {
|
|
357
|
+
aiReady,
|
|
358
|
+
describe,
|
|
359
|
+
exposed,
|
|
360
|
+
hidden,
|
|
361
|
+
routesFromExpress
|
|
362
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@exposify/express",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"require": "./dist/index.cjs"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsup src/index.ts --format esm,cjs --dts --clean",
|
|
21
|
+
"test": "vitest run",
|
|
22
|
+
"typecheck": "tsc --noEmit"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@exposify/core": "0.0.0",
|
|
26
|
+
"light-my-request": "^6.0.0"
|
|
27
|
+
},
|
|
28
|
+
"peerDependencies": {
|
|
29
|
+
"express": ">=4 <6"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
33
|
+
"@types/express": "^5.0.0",
|
|
34
|
+
"@types/node": "^22.0.0",
|
|
35
|
+
"express": "npm:express@^5.1.0",
|
|
36
|
+
"express4": "npm:express@^4.19.0",
|
|
37
|
+
"express5": "npm:express@^5.1.0",
|
|
38
|
+
"tsup": "^8.3.0",
|
|
39
|
+
"typescript": "^5.6.0",
|
|
40
|
+
"vitest": "^3.0.0"
|
|
41
|
+
}
|
|
42
|
+
}
|