@faapi/faapi 4.1.0 → 4.2.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/cli/index.js +176 -52
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +16 -1
- package/dist/index.js +200 -79
- package/dist/index.js.map +1 -1
- package/dist/testing.js +21 -9
- package/dist/testing.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -120,8 +120,23 @@ interface Http2Options {
|
|
|
120
120
|
|
|
121
121
|
/**
|
|
122
122
|
* 生命周期钩子
|
|
123
|
+
*
|
|
124
|
+
* 执行时序:onBoot(.listen 调用前)→ server.listen → listen 回调内 onReady → 运行期 onError → 关闭时 onClose
|
|
123
125
|
*/
|
|
124
126
|
interface LifecycleHooks {
|
|
127
|
+
/**
|
|
128
|
+
* 服务器 listen 之前调用(启动校验钩子)
|
|
129
|
+
*
|
|
130
|
+
* 时机:`app.listen()` 内、`server.listen` 调用之前——server 已创建但未监听
|
|
131
|
+
* (`server.listening === false`),路由/tool/agent 清单已水合、插件已加载。
|
|
132
|
+
*
|
|
133
|
+
* 适合启动校验(环境变量、下游依赖可达性)、DB 迁移等**失败即不该暴露端口**的逻辑:
|
|
134
|
+
* 钩子抛错 → `listen()` 以原始错误 reject,`server.listen` 不会被调用,端口不暴露。
|
|
135
|
+
*
|
|
136
|
+
* 与 onReady 的差异:onReady 在 listen 回调内执行,失败时端口已开,
|
|
137
|
+
* 存在"接受连接但不服务"的窗口——启动校验请用 onBoot,资源初始化用 onReady。
|
|
138
|
+
*/
|
|
139
|
+
onBoot?: (ctx: LifecycleContext) => Promise<void> | void;
|
|
125
140
|
/** 服务器启动后调用(适合初始化数据库连接等) */
|
|
126
141
|
onReady?: (ctx: LifecycleContext) => Promise<void> | void;
|
|
127
142
|
/** 服务器关闭时调用(适合清理资源、优雅关闭) */
|
|
@@ -149,7 +164,7 @@ interface LifecycleContext {
|
|
|
149
164
|
rootDir: string;
|
|
150
165
|
/** 当前路由清单 */
|
|
151
166
|
routes: RouteManifest;
|
|
152
|
-
/**
|
|
167
|
+
/** 服务器实例(onBoot 钩子触发时已创建但未监听,`listening === false`) */
|
|
153
168
|
server: node_http.Server;
|
|
154
169
|
/** app 级注册表——skill 等运行时动态注册路径(`registries.skill.upsert(...)`) */
|
|
155
170
|
registries: AppRegistries;
|
package/dist/index.js
CHANGED
|
@@ -2036,6 +2036,15 @@ function toStrippedProdImportPath(sourceFile, rootDir) {
|
|
|
2036
2036
|
if (!rel.startsWith(".")) rel = "./" + rel;
|
|
2037
2037
|
return toProdExtension(rel);
|
|
2038
2038
|
}
|
|
2039
|
+
function toProdImportFromImporter(importer, rootDir, relFromDist) {
|
|
2040
|
+
const importerRel = path5.relative(toRealPath(path5.resolve(rootDir)), toRealPath(importer)).split(path5.sep).join("/");
|
|
2041
|
+
const importerProdDir = path5.posix.dirname(toProdExtension(importerRel));
|
|
2042
|
+
if (importerProdDir === ".") return relFromDist;
|
|
2043
|
+
const target = relFromDist.replace(/^\.\//, "");
|
|
2044
|
+
let rel = path5.posix.relative(importerProdDir, target);
|
|
2045
|
+
if (!rel.startsWith(".")) rel = "./" + rel;
|
|
2046
|
+
return rel;
|
|
2047
|
+
}
|
|
2039
2048
|
var PROD_EXTS = [".js", ".mjs", ".cjs"];
|
|
2040
2049
|
var SOURCE_EXTS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
|
|
2041
2050
|
var INDEX_EXTS = [
|
|
@@ -2093,9 +2102,10 @@ function createAliasPlugin(config, options) {
|
|
|
2093
2102
|
}
|
|
2094
2103
|
if (appDirAbs && importerOutsideAppDir && isInsideDir(resolved, appDirAbs)) {
|
|
2095
2104
|
modified = true;
|
|
2096
|
-
return `${prefix}${quote}${
|
|
2097
|
-
|
|
2098
|
-
options.rootDir
|
|
2105
|
+
return `${prefix}${quote}${toProdImportFromImporter(
|
|
2106
|
+
importer,
|
|
2107
|
+
options.rootDir,
|
|
2108
|
+
toStrippedProdImportPath(resolved, options.rootDir)
|
|
2099
2109
|
)}${quote}`;
|
|
2100
2110
|
}
|
|
2101
2111
|
modified = true;
|
|
@@ -2110,9 +2120,10 @@ function createAliasPlugin(config, options) {
|
|
|
2110
2120
|
if (fs4.existsSync(file)) {
|
|
2111
2121
|
modified = true;
|
|
2112
2122
|
if (appDirAbs && importerOutsideAppDir && isInsideDir(file, appDirAbs)) {
|
|
2113
|
-
return `${prefix}${quote}${
|
|
2114
|
-
|
|
2115
|
-
options.rootDir
|
|
2123
|
+
return `${prefix}${quote}${toProdImportFromImporter(
|
|
2124
|
+
importer,
|
|
2125
|
+
options.rootDir,
|
|
2126
|
+
toStrippedProdImportPath(file, options.rootDir)
|
|
2116
2127
|
)}${quote}`;
|
|
2117
2128
|
}
|
|
2118
2129
|
return `${prefix}${quote}${toProdImportPath(file, importer)}${quote}`;
|
|
@@ -2123,9 +2134,10 @@ function createAliasPlugin(config, options) {
|
|
|
2123
2134
|
if (fs4.existsSync(file)) {
|
|
2124
2135
|
modified = true;
|
|
2125
2136
|
if (appDirAbs && importerOutsideAppDir && isInsideDir(file, appDirAbs)) {
|
|
2126
|
-
return `${prefix}${quote}${
|
|
2127
|
-
|
|
2128
|
-
options.rootDir
|
|
2137
|
+
return `${prefix}${quote}${toProdImportFromImporter(
|
|
2138
|
+
importer,
|
|
2139
|
+
options.rootDir,
|
|
2140
|
+
toStrippedProdImportPath(file, options.rootDir)
|
|
2129
2141
|
)}${quote}`;
|
|
2130
2142
|
}
|
|
2131
2143
|
return `${prefix}${quote}${toProdImportPath(file, importer)}${quote}`;
|
|
@@ -3096,14 +3108,14 @@ var ValidationError = class extends FaapiError {
|
|
|
3096
3108
|
issues;
|
|
3097
3109
|
};
|
|
3098
3110
|
var RouteNotFoundError = class extends FaapiError {
|
|
3099
|
-
constructor(
|
|
3100
|
-
super("ROUTE_NOT_FOUND", `Route not found: ${
|
|
3111
|
+
constructor(path24) {
|
|
3112
|
+
super("ROUTE_NOT_FOUND", `Route not found: ${path24}`, 404);
|
|
3101
3113
|
this.name = "RouteNotFoundError";
|
|
3102
3114
|
}
|
|
3103
3115
|
};
|
|
3104
3116
|
var MethodNotAllowedError = class extends FaapiError {
|
|
3105
|
-
constructor(method,
|
|
3106
|
-
super("METHOD_NOT_ALLOWED", `Method ${method} not allowed for ${
|
|
3117
|
+
constructor(method, path24, allowedMethods) {
|
|
3118
|
+
super("METHOD_NOT_ALLOWED", `Method ${method} not allowed for ${path24}`, 405);
|
|
3107
3119
|
this.allowedMethods = allowedMethods;
|
|
3108
3120
|
this.name = "MethodNotAllowedError";
|
|
3109
3121
|
}
|
|
@@ -3129,8 +3141,8 @@ var PayloadTooLargeError = class extends FaapiError {
|
|
|
3129
3141
|
};
|
|
3130
3142
|
|
|
3131
3143
|
// src/cli/createAppCore.ts
|
|
3132
|
-
import
|
|
3133
|
-
import
|
|
3144
|
+
import fs17 from "fs";
|
|
3145
|
+
import path20 from "path";
|
|
3134
3146
|
import { PassThrough, Readable as Readable3 } from "stream";
|
|
3135
3147
|
|
|
3136
3148
|
// src/router/sortRoutes.ts
|
|
@@ -3225,18 +3237,18 @@ function getWsIndex(routes) {
|
|
|
3225
3237
|
wsIndexCache.set(routes, index);
|
|
3226
3238
|
return index;
|
|
3227
3239
|
}
|
|
3228
|
-
function matchRoute(routes, method,
|
|
3240
|
+
function matchRoute(routes, method, path24) {
|
|
3229
3241
|
const index = getHttpIndex(routes);
|
|
3230
3242
|
const upper = method.toUpperCase();
|
|
3231
|
-
const hit = matchByMethod(index, upper,
|
|
3243
|
+
const hit = matchByMethod(index, upper, path24);
|
|
3232
3244
|
if (hit) return hit;
|
|
3233
3245
|
if (upper === "HEAD") {
|
|
3234
|
-
return matchByMethod(index, "GET",
|
|
3246
|
+
return matchByMethod(index, "GET", path24);
|
|
3235
3247
|
}
|
|
3236
3248
|
return null;
|
|
3237
3249
|
}
|
|
3238
|
-
function matchByMethod(index, method,
|
|
3239
|
-
const staticHit = index.static.get(`${method}|${
|
|
3250
|
+
function matchByMethod(index, method, path24) {
|
|
3251
|
+
const staticHit = index.static.get(`${method}|${path24}`);
|
|
3240
3252
|
if (staticHit) {
|
|
3241
3253
|
return { route: staticHit, params: {} };
|
|
3242
3254
|
}
|
|
@@ -3245,31 +3257,31 @@ function matchByMethod(index, method, path23) {
|
|
|
3245
3257
|
if (route.method !== method) {
|
|
3246
3258
|
continue;
|
|
3247
3259
|
}
|
|
3248
|
-
const params = matchSegments(entry.segments,
|
|
3260
|
+
const params = matchSegments(entry.segments, path24, route.paramNames, route.isCatchAll);
|
|
3249
3261
|
if (params !== null) {
|
|
3250
3262
|
return { route, params };
|
|
3251
3263
|
}
|
|
3252
3264
|
}
|
|
3253
3265
|
return null;
|
|
3254
3266
|
}
|
|
3255
|
-
function matchWsRoute(wsRoutes,
|
|
3267
|
+
function matchWsRoute(wsRoutes, path24) {
|
|
3256
3268
|
const index = getWsIndex(wsRoutes);
|
|
3257
|
-
const staticHit = index.static.get(
|
|
3269
|
+
const staticHit = index.static.get(path24);
|
|
3258
3270
|
if (staticHit) {
|
|
3259
3271
|
return { route: staticHit, params: {} };
|
|
3260
3272
|
}
|
|
3261
3273
|
for (const route of index.dynamics) {
|
|
3262
|
-
const params = matchDynamicPath(route.urlPath,
|
|
3274
|
+
const params = matchDynamicPath(route.urlPath, path24, route.paramNames, route.isCatchAll);
|
|
3263
3275
|
if (params !== null) {
|
|
3264
3276
|
return { route, params };
|
|
3265
3277
|
}
|
|
3266
3278
|
}
|
|
3267
3279
|
return null;
|
|
3268
3280
|
}
|
|
3269
|
-
function findAllowedMethods(routes,
|
|
3281
|
+
function findAllowedMethods(routes, path24) {
|
|
3270
3282
|
const index = getHttpIndex(routes);
|
|
3271
3283
|
const methods = /* @__PURE__ */ new Set();
|
|
3272
|
-
const staticMethods = index.methodsByStaticPath.get(
|
|
3284
|
+
const staticMethods = index.methodsByStaticPath.get(path24);
|
|
3273
3285
|
if (staticMethods) {
|
|
3274
3286
|
for (const method of staticMethods) {
|
|
3275
3287
|
methods.add(method);
|
|
@@ -3278,7 +3290,7 @@ function findAllowedMethods(routes, path23) {
|
|
|
3278
3290
|
for (const entry of index.dynamics) {
|
|
3279
3291
|
const params = matchSegments(
|
|
3280
3292
|
entry.segments,
|
|
3281
|
-
|
|
3293
|
+
path24,
|
|
3282
3294
|
entry.route.paramNames,
|
|
3283
3295
|
entry.route.isCatchAll
|
|
3284
3296
|
);
|
|
@@ -3291,11 +3303,11 @@ function findAllowedMethods(routes, path23) {
|
|
|
3291
3303
|
}
|
|
3292
3304
|
return Array.from(methods);
|
|
3293
3305
|
}
|
|
3294
|
-
function matchDynamicPath(pattern,
|
|
3295
|
-
return matchSegments(pattern.split("/").filter(Boolean),
|
|
3306
|
+
function matchDynamicPath(pattern, path24, paramNames, isCatchAll) {
|
|
3307
|
+
return matchSegments(pattern.split("/").filter(Boolean), path24, paramNames, isCatchAll);
|
|
3296
3308
|
}
|
|
3297
|
-
function matchSegments(patternSegments,
|
|
3298
|
-
const pathSegments =
|
|
3309
|
+
function matchSegments(patternSegments, path24, paramNames, isCatchAll) {
|
|
3310
|
+
const pathSegments = path24.split("/").filter(Boolean);
|
|
3299
3311
|
if (isCatchAll) {
|
|
3300
3312
|
const nonCatchAllCount = patternSegments.length - 1;
|
|
3301
3313
|
if (pathSegments.length <= nonCatchAllCount) {
|
|
@@ -4183,9 +4195,9 @@ async function validateInput(schemaPath, method, inputType, input) {
|
|
|
4183
4195
|
function mapZodIssues(error) {
|
|
4184
4196
|
return error.issues.map((issue) => {
|
|
4185
4197
|
const code = mapZodCode(issue);
|
|
4186
|
-
const
|
|
4198
|
+
const path24 = issue.path.map(String).join(".") || "";
|
|
4187
4199
|
return {
|
|
4188
|
-
path:
|
|
4200
|
+
path: path24,
|
|
4189
4201
|
code,
|
|
4190
4202
|
expected: issue.expected ?? mapExpectedFromMessage(issue.message),
|
|
4191
4203
|
received: issue.received ?? mapReceivedFromMessage(issue.message),
|
|
@@ -5286,9 +5298,50 @@ async function generateAgentArtifacts(agents, rootDir, dist) {
|
|
|
5286
5298
|
}
|
|
5287
5299
|
|
|
5288
5300
|
// src/cli/loadPlugins.ts
|
|
5289
|
-
import
|
|
5301
|
+
import path19 from "path";
|
|
5302
|
+
import fs16 from "fs";
|
|
5290
5303
|
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
5291
|
-
|
|
5304
|
+
|
|
5305
|
+
// src/cli/compileConfig.ts
|
|
5306
|
+
import path18 from "path";
|
|
5307
|
+
import fs15 from "fs";
|
|
5308
|
+
async function compileProjectModules(entryPoints, rootDir, dist) {
|
|
5309
|
+
const { insideFiles, outsideFiles } = await collectRelativeImports(entryPoints, rootDir);
|
|
5310
|
+
const esbuild = await import("esbuild");
|
|
5311
|
+
const aliasPlugins = buildAliasPlugins(rootDir);
|
|
5312
|
+
const absDist = path18.resolve(rootDir, dist);
|
|
5313
|
+
await esbuild.build({
|
|
5314
|
+
entryPoints: [...entryPoints, ...outsideFiles],
|
|
5315
|
+
outdir: absDist,
|
|
5316
|
+
outbase: rootDir,
|
|
5317
|
+
bundle: false,
|
|
5318
|
+
platform: "node",
|
|
5319
|
+
format: "esm",
|
|
5320
|
+
sourcemap: true,
|
|
5321
|
+
packages: "external",
|
|
5322
|
+
plugins: aliasPlugins,
|
|
5323
|
+
logLevel: "silent"
|
|
5324
|
+
});
|
|
5325
|
+
if (insideFiles.length > 0) {
|
|
5326
|
+
const appOutbase = path18.resolve(rootDir, "src");
|
|
5327
|
+
await esbuild.build({
|
|
5328
|
+
entryPoints: insideFiles,
|
|
5329
|
+
outdir: absDist,
|
|
5330
|
+
outbase: appOutbase,
|
|
5331
|
+
bundle: false,
|
|
5332
|
+
platform: "node",
|
|
5333
|
+
format: "esm",
|
|
5334
|
+
sourcemap: true,
|
|
5335
|
+
packages: "external",
|
|
5336
|
+
plugins: aliasPlugins,
|
|
5337
|
+
logLevel: "silent"
|
|
5338
|
+
});
|
|
5339
|
+
}
|
|
5340
|
+
return { insideFiles, outsideFiles };
|
|
5341
|
+
}
|
|
5342
|
+
|
|
5343
|
+
// src/cli/loadPlugins.ts
|
|
5344
|
+
async function loadPlugins(declarations, ctx, rootDir, dist) {
|
|
5292
5345
|
const handlerWrappers = [];
|
|
5293
5346
|
const upgradeWrappers = [];
|
|
5294
5347
|
const failures = [];
|
|
@@ -5325,7 +5378,7 @@ async function loadPlugins(declarations, ctx, rootDir) {
|
|
|
5325
5378
|
}
|
|
5326
5379
|
loaded.add(specifier);
|
|
5327
5380
|
try {
|
|
5328
|
-
const mod = await
|
|
5381
|
+
const mod = await importPluginModule(specifier, rootDir ?? process.cwd(), dist);
|
|
5329
5382
|
const plugin = mod.default ?? mod;
|
|
5330
5383
|
if (typeof plugin.setup !== "function") {
|
|
5331
5384
|
failures.push({ specifier, reason: "plugin has no setup function" });
|
|
@@ -5348,15 +5401,72 @@ async function loadPlugins(declarations, ctx, rootDir) {
|
|
|
5348
5401
|
}
|
|
5349
5402
|
return { handlerWrappers, upgradeWrappers, failures };
|
|
5350
5403
|
}
|
|
5351
|
-
|
|
5352
|
-
|
|
5353
|
-
|
|
5354
|
-
|
|
5404
|
+
var TS_EXTS = /\.(ts|tsx|mts|cts)$/;
|
|
5405
|
+
var JS_EXTS = /\.(js|mjs|cjs|jsx)$/;
|
|
5406
|
+
function resolveLocalPluginSource(specifier, baseDir) {
|
|
5407
|
+
const base = path19.isAbsolute(specifier) ? specifier : path19.resolve(baseDir, specifier);
|
|
5408
|
+
if ((TS_EXTS.test(base) || JS_EXTS.test(base)) && fs16.existsSync(base)) return base;
|
|
5409
|
+
for (const ext of [".ts", ".js"]) {
|
|
5410
|
+
if (fs16.existsSync(base + ext)) return base + ext;
|
|
5355
5411
|
}
|
|
5356
|
-
|
|
5357
|
-
|
|
5412
|
+
for (const indexExt of ["/index.ts", "/index.js"]) {
|
|
5413
|
+
if (fs16.existsSync(base + indexExt)) return base + indexExt;
|
|
5358
5414
|
}
|
|
5359
|
-
return
|
|
5415
|
+
return null;
|
|
5416
|
+
}
|
|
5417
|
+
function mirrorProductPath(sourcePath, baseDir, distDir) {
|
|
5418
|
+
let rel = path19.relative(baseDir, sourcePath).replace(/\\/g, "/");
|
|
5419
|
+
if (rel.startsWith("src/")) rel = rel.slice(4);
|
|
5420
|
+
return path19.resolve(distDir, rel.replace(TS_EXTS, ".js"));
|
|
5421
|
+
}
|
|
5422
|
+
function isSourceNewer(sourcePath, productPath) {
|
|
5423
|
+
try {
|
|
5424
|
+
return fs16.statSync(sourcePath).mtimeMs > fs16.statSync(productPath).mtimeMs;
|
|
5425
|
+
} catch {
|
|
5426
|
+
return true;
|
|
5427
|
+
}
|
|
5428
|
+
}
|
|
5429
|
+
async function importPluginModule(specifier, baseDir, dist) {
|
|
5430
|
+
const isRelative = specifier.startsWith("./") || specifier.startsWith("../");
|
|
5431
|
+
if (!isRelative && !path19.isAbsolute(specifier)) {
|
|
5432
|
+
return import(specifier);
|
|
5433
|
+
}
|
|
5434
|
+
const distDir = dist ? path19.resolve(baseDir, dist) : null;
|
|
5435
|
+
const sourcePath = resolveLocalPluginSource(specifier, baseDir);
|
|
5436
|
+
const productPath = sourcePath && distDir ? mirrorProductPath(sourcePath, baseDir, distDir) : null;
|
|
5437
|
+
if (productPath !== null && fs16.existsSync(productPath) && (sourcePath === null || !isSourceNewer(sourcePath, productPath))) {
|
|
5438
|
+
return import(pathToFileURL2(productPath).href);
|
|
5439
|
+
}
|
|
5440
|
+
if (sourcePath && TS_EXTS.test(sourcePath)) {
|
|
5441
|
+
if (!isDevOnDemandEnabled() || !distDir) {
|
|
5442
|
+
throw new Error(
|
|
5443
|
+
`Local plugin "${specifier}" has no up-to-date build artifact (dist is stale or missing). Run "faapi build" first, or use "faapi dev" for on-demand compilation.`
|
|
5444
|
+
);
|
|
5445
|
+
}
|
|
5446
|
+
const relFromRoot = path19.relative(baseDir, sourcePath).replace(/\\/g, "/");
|
|
5447
|
+
if (relFromRoot.startsWith("src/")) {
|
|
5448
|
+
await ensureCompiled(sourcePath, baseDir, dist);
|
|
5449
|
+
} else {
|
|
5450
|
+
await compileProjectModules([sourcePath], baseDir, dist);
|
|
5451
|
+
}
|
|
5452
|
+
if (productPath && fs16.existsSync(productPath)) {
|
|
5453
|
+
return import(pathToFileURL2(productPath).href);
|
|
5454
|
+
}
|
|
5455
|
+
}
|
|
5456
|
+
if (sourcePath && JS_EXTS.test(sourcePath)) {
|
|
5457
|
+
return import(pathToFileURL2(sourcePath).href);
|
|
5458
|
+
}
|
|
5459
|
+
const candidates = [
|
|
5460
|
+
`${specifier}.ts`,
|
|
5461
|
+
`${specifier}/index.ts`,
|
|
5462
|
+
`${specifier}.js`,
|
|
5463
|
+
`${specifier}/index.js`
|
|
5464
|
+
].map((c) => path19.isAbsolute(c) ? c : path19.join(baseDir, c));
|
|
5465
|
+
throw new Error(
|
|
5466
|
+
`Cannot find local plugin "${specifier}" (resolved from ${baseDir}). Expected one of:
|
|
5467
|
+
${candidates.join("\n ")}
|
|
5468
|
+
Create the plugin file (export default { name, setup(ctx) {...} }) or fix the path.`
|
|
5469
|
+
);
|
|
5360
5470
|
}
|
|
5361
5471
|
function resolveDeclaration(decl) {
|
|
5362
5472
|
if (typeof decl === "string") {
|
|
@@ -5382,8 +5492,8 @@ var ROUTES_FILE = "faapi-routes.js";
|
|
|
5382
5492
|
var TOOLS_FILE2 = "faapi-tools.js";
|
|
5383
5493
|
var AGENTS_FILE2 = "faapi-agents.js";
|
|
5384
5494
|
async function loadAndHydrateTools(rootDir, dist, registries = defaultRegistries) {
|
|
5385
|
-
const toolsPath =
|
|
5386
|
-
if (!
|
|
5495
|
+
const toolsPath = path20.resolve(rootDir, dist, TOOLS_FILE2);
|
|
5496
|
+
if (!fs17.existsSync(toolsPath)) {
|
|
5387
5497
|
return [];
|
|
5388
5498
|
}
|
|
5389
5499
|
const serialized = await importWithCacheBust(toolsPath);
|
|
@@ -5392,8 +5502,8 @@ async function loadAndHydrateTools(rootDir, dist, registries = defaultRegistries
|
|
|
5392
5502
|
return hydrated;
|
|
5393
5503
|
}
|
|
5394
5504
|
async function loadAndHydrateAgents(rootDir, dist, registries = defaultRegistries) {
|
|
5395
|
-
const agentsPath =
|
|
5396
|
-
if (!
|
|
5505
|
+
const agentsPath = path20.resolve(rootDir, dist, AGENTS_FILE2);
|
|
5506
|
+
if (!fs17.existsSync(agentsPath)) {
|
|
5397
5507
|
return [];
|
|
5398
5508
|
}
|
|
5399
5509
|
const serialized = await importWithCacheBust(agentsPath);
|
|
@@ -5460,8 +5570,8 @@ function isFaapiConfigKey(key) {
|
|
|
5460
5570
|
async function createAppBase(options) {
|
|
5461
5571
|
const rootDir = options?.rootDir ?? process.cwd();
|
|
5462
5572
|
const dist = options?.dist ?? process.env.FAAPI_DIST ?? DEFAULT_DIST;
|
|
5463
|
-
const routesPath =
|
|
5464
|
-
if (!
|
|
5573
|
+
const routesPath = path20.resolve(rootDir, dist, ROUTES_FILE);
|
|
5574
|
+
if (!fs17.existsSync(routesPath)) {
|
|
5465
5575
|
throw new Error(
|
|
5466
5576
|
`[faapi] ${dist}/${ROUTES_FILE} \u4E0D\u5B58\u5728\uFF0C\u8BF7\u5148\u6267\u884C \`faapi build\`\uFF08\u6216 \`faapi dev\`\uFF09\u751F\u6210\u4EA7\u7269\u3002`
|
|
5467
5577
|
);
|
|
@@ -5513,7 +5623,8 @@ async function createAppBase(options) {
|
|
|
5513
5623
|
server,
|
|
5514
5624
|
config: pluginConfig
|
|
5515
5625
|
},
|
|
5516
|
-
rootDir
|
|
5626
|
+
rootDir,
|
|
5627
|
+
dist
|
|
5517
5628
|
);
|
|
5518
5629
|
applyPluginWrappers(server, handlerWrappers, upgradeWrappers);
|
|
5519
5630
|
let closed = false;
|
|
@@ -5526,6 +5637,16 @@ async function createAppBase(options) {
|
|
|
5526
5637
|
async listen(listenPort) {
|
|
5527
5638
|
const envPort = process.env.PORT ? Number(process.env.PORT) : void 0;
|
|
5528
5639
|
const actualPort = listenPort ?? options?.port ?? envPort ?? DEFAULT_PORT;
|
|
5640
|
+
if (config?.lifecycle?.onBoot) {
|
|
5641
|
+
try {
|
|
5642
|
+
await config.lifecycle.onBoot({ rootDir, routes: sorted, server, registries });
|
|
5643
|
+
console.log("- onBoot hook executed");
|
|
5644
|
+
} catch (err) {
|
|
5645
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
5646
|
+
console.error(`[faapi] onBoot hook failed: ${message}`);
|
|
5647
|
+
throw err;
|
|
5648
|
+
}
|
|
5649
|
+
}
|
|
5529
5650
|
return new Promise((resolve, reject) => {
|
|
5530
5651
|
const onListenError = (err) => {
|
|
5531
5652
|
if (err.code === "EADDRINUSE") {
|
|
@@ -5708,17 +5829,17 @@ async function createAppBase(options) {
|
|
|
5708
5829
|
|
|
5709
5830
|
// src/router/scanRoutes.ts
|
|
5710
5831
|
import fg2 from "fast-glob";
|
|
5711
|
-
import
|
|
5712
|
-
import
|
|
5832
|
+
import path21 from "path";
|
|
5833
|
+
import fs18 from "fs";
|
|
5713
5834
|
|
|
5714
5835
|
// src/router/constants.ts
|
|
5715
5836
|
var HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
|
|
5716
5837
|
var HTTP_METHOD_SET = new Set(HTTP_METHODS);
|
|
5717
5838
|
|
|
5718
5839
|
// src/utils/normalizePath.ts
|
|
5719
|
-
function normalizePath(
|
|
5720
|
-
if (!
|
|
5721
|
-
let result =
|
|
5840
|
+
function normalizePath(path24) {
|
|
5841
|
+
if (!path24) return "";
|
|
5842
|
+
let result = path24.replace(/\\/g, "/");
|
|
5722
5843
|
result = result.replace(/\/+/g, "/");
|
|
5723
5844
|
result = result.replace(/\/+$/, "");
|
|
5724
5845
|
if (result && !result.startsWith("/")) {
|
|
@@ -5779,34 +5900,34 @@ function extractExportsFromSource(source) {
|
|
|
5779
5900
|
return names;
|
|
5780
5901
|
}
|
|
5781
5902
|
function collectMiddlewarePaths(routeFilePath, rootDir, dist) {
|
|
5782
|
-
const routeDir =
|
|
5783
|
-
const resolvedRoot =
|
|
5903
|
+
const routeDir = path21.dirname(routeFilePath);
|
|
5904
|
+
const resolvedRoot = path21.resolve(rootDir);
|
|
5784
5905
|
const paths = [];
|
|
5785
|
-
let currentDir =
|
|
5906
|
+
let currentDir = path21.resolve(rootDir, routeDir);
|
|
5786
5907
|
while (true) {
|
|
5787
5908
|
if (dist) {
|
|
5788
|
-
const mwTsPath =
|
|
5789
|
-
const mwJsPath =
|
|
5790
|
-
const absTsPath =
|
|
5791
|
-
const absJsPath =
|
|
5792
|
-
const absMwPath =
|
|
5909
|
+
const mwTsPath = path21.join(currentDir, "middlewares.ts");
|
|
5910
|
+
const mwJsPath = path21.join(currentDir, "middlewares.js");
|
|
5911
|
+
const absTsPath = path21.resolve(rootDir, mwTsPath);
|
|
5912
|
+
const absJsPath = path21.resolve(rootDir, mwJsPath);
|
|
5913
|
+
const absMwPath = fs18.existsSync(absTsPath) ? absTsPath : fs18.existsSync(absJsPath) ? absJsPath : null;
|
|
5793
5914
|
if (absMwPath) {
|
|
5794
|
-
const relMwPath =
|
|
5795
|
-
const prodAbsPath =
|
|
5915
|
+
const relMwPath = path21.relative(rootDir, absMwPath);
|
|
5916
|
+
const prodAbsPath = path21.resolve(rootDir, toProdFilePath(relMwPath, dist));
|
|
5796
5917
|
paths.push(prodAbsPath);
|
|
5797
5918
|
}
|
|
5798
5919
|
} else {
|
|
5799
5920
|
for (const ext of [".ts", ".js"]) {
|
|
5800
|
-
const mwPath =
|
|
5801
|
-
const absMwPath =
|
|
5802
|
-
if (
|
|
5921
|
+
const mwPath = path21.join(currentDir, `middlewares${ext}`);
|
|
5922
|
+
const absMwPath = path21.resolve(rootDir, mwPath);
|
|
5923
|
+
if (fs18.existsSync(absMwPath)) {
|
|
5803
5924
|
paths.push(absMwPath);
|
|
5804
5925
|
break;
|
|
5805
5926
|
}
|
|
5806
5927
|
}
|
|
5807
5928
|
}
|
|
5808
5929
|
if (currentDir === resolvedRoot) break;
|
|
5809
|
-
const parentDir =
|
|
5930
|
+
const parentDir = path21.dirname(currentDir);
|
|
5810
5931
|
if (parentDir === currentDir) break;
|
|
5811
5932
|
currentDir = parentDir;
|
|
5812
5933
|
}
|
|
@@ -5825,7 +5946,7 @@ async function scanRoutes(rootDir, patterns, dist) {
|
|
|
5825
5946
|
const normalizedFile = file.replace(/\\/g, "/");
|
|
5826
5947
|
const fileName = normalizedFile.split("/").pop();
|
|
5827
5948
|
if (fileName === "handler.ts" || fileName === "handler.js") {
|
|
5828
|
-
const absPath =
|
|
5949
|
+
const absPath = path21.resolve(rootDir, normalizedFile);
|
|
5829
5950
|
const urlPath = filePathToUrlPath(normalizedFile);
|
|
5830
5951
|
const paramNames = extractParamNames(urlPath);
|
|
5831
5952
|
const isDynamic = paramNames.length > 0;
|
|
@@ -5838,7 +5959,7 @@ async function scanRoutes(rootDir, patterns, dist) {
|
|
|
5838
5959
|
const mwPaths = collectMiddlewarePaths(normalizedFile, rootDir);
|
|
5839
5960
|
middlewareBundle = await loadMergedMiddlewares(mwPaths);
|
|
5840
5961
|
}
|
|
5841
|
-
const source = await
|
|
5962
|
+
const source = await fs18.promises.readFile(absPath, "utf8").catch(() => "");
|
|
5842
5963
|
const exportNames = extractExportsFromSource(source);
|
|
5843
5964
|
const methods = HTTP_METHODS.filter((m) => exportNames.has(m));
|
|
5844
5965
|
for (const method of methods) {
|
|
@@ -5874,8 +5995,8 @@ async function scanRoutes(rootDir, patterns, dist) {
|
|
|
5874
5995
|
|
|
5875
5996
|
// src/tools/scanTools.ts
|
|
5876
5997
|
import fg3 from "fast-glob";
|
|
5877
|
-
import
|
|
5878
|
-
import
|
|
5998
|
+
import path22 from "path";
|
|
5999
|
+
import fs19 from "fs";
|
|
5879
6000
|
var TOOL_PATTERNS = ["src/tools/**/*.ts"];
|
|
5880
6001
|
var TOOL_EXPORT_RE = new RegExp(
|
|
5881
6002
|
String.raw`export\s+(?:async\s+)?(?:function\s+|const\s+)([A-Za-z_$][\w$]*)\s*(?:\(|=)`,
|
|
@@ -5925,8 +6046,8 @@ async function scanTools(rootDir, patterns) {
|
|
|
5925
6046
|
if (fileName !== "handler.ts" && fileName !== "handler.js") {
|
|
5926
6047
|
continue;
|
|
5927
6048
|
}
|
|
5928
|
-
const absPath =
|
|
5929
|
-
const source = await
|
|
6049
|
+
const absPath = path22.resolve(rootDir, normalizedFile);
|
|
6050
|
+
const source = await fs19.promises.readFile(absPath, "utf8").catch(() => "");
|
|
5930
6051
|
const exportNames = extractToolExportsFromSource(source);
|
|
5931
6052
|
const namespace = filePathToToolNamespace(normalizedFile);
|
|
5932
6053
|
for (const fnName of exportNames) {
|
|
@@ -5950,8 +6071,8 @@ async function scanTools(rootDir, patterns) {
|
|
|
5950
6071
|
|
|
5951
6072
|
// src/agents/scanAgents.ts
|
|
5952
6073
|
import fg4 from "fast-glob";
|
|
5953
|
-
import
|
|
5954
|
-
import
|
|
6074
|
+
import path23 from "path";
|
|
6075
|
+
import fs20 from "fs";
|
|
5955
6076
|
var DEFAULT_AGENT_PATTERNS = ["src/agents/*/handler.ts"];
|
|
5956
6077
|
var RUN_EXPORT_RE = /export\s+(?:async\s+)?(?:function\s+|const\s+)run\b/;
|
|
5957
6078
|
function extractAgentNameFromPath(filePath) {
|
|
@@ -5983,8 +6104,8 @@ async function scanAgents(rootDir, patterns) {
|
|
|
5983
6104
|
if (fileName !== "handler.ts" && fileName !== "handler.js") {
|
|
5984
6105
|
continue;
|
|
5985
6106
|
}
|
|
5986
|
-
const absPath =
|
|
5987
|
-
const source = await
|
|
6107
|
+
const absPath = path23.resolve(rootDir, normalizedFile);
|
|
6108
|
+
const source = await fs20.promises.readFile(absPath, "utf8").catch(() => "");
|
|
5988
6109
|
const { hasRun } = detectAgentExports(source);
|
|
5989
6110
|
const name = extractAgentNameFromPath(normalizedFile);
|
|
5990
6111
|
const prevFile = seen.get(name);
|