@faapi/faapi 4.0.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/README.md +5 -0
- package/dist/cli/index.js +197 -58
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +16 -1
- package/dist/index.js +221 -85
- package/dist/index.js.map +1 -1
- package/dist/testing.js +42 -15
- package/dist/testing.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -48,6 +48,11 @@ node dist/main # 启动生产服务器
|
|
|
48
48
|
- 洋葱模型中间件:`(ctx, next) => {}`
|
|
49
49
|
- 依赖注入:注入器按参数名匹配 handler 参数
|
|
50
50
|
- WebSocket 路由:导出 `WS` 函数声明
|
|
51
|
+
- SSE 流式响应:`ctx.sse()` 流式推送(LLM 输出、通知推送)
|
|
52
|
+
- 响应压缩:`compression: true`,gzip/deflate/br 协商,SSE 自动跳过
|
|
53
|
+
- ETag/304 协商:`etag: true`,GET/HEAD 弱 ETag 自动生成与协商
|
|
54
|
+
- 优雅停机:SIGTERM/SIGINT 默认注册,drain 在途请求(`FAAPI_SHUTDOWN_TIMEOUT_MS` 可调)
|
|
55
|
+
- app 级注册表:tool/agent/skill 注册表随 app 实例创建与销毁,多 app 同进程隔离
|
|
51
56
|
- SSE 流式响应:`ctx.sse()` 推送
|
|
52
57
|
- 动态路由:`[id]` / `[...slug]` / `(group)`
|
|
53
58
|
- MCP 集成:LLM 可查询路由 schema
|
package/dist/cli/index.js
CHANGED
|
@@ -132,6 +132,15 @@ function toStrippedProdImportPath(sourceFile, rootDir) {
|
|
|
132
132
|
if (!rel.startsWith(".")) rel = "./" + rel;
|
|
133
133
|
return toProdExtension(rel);
|
|
134
134
|
}
|
|
135
|
+
function toProdImportFromImporter(importer, rootDir, relFromDist) {
|
|
136
|
+
const importerRel = path3.relative(toRealPath(path3.resolve(rootDir)), toRealPath(importer)).split(path3.sep).join("/");
|
|
137
|
+
const importerProdDir = path3.posix.dirname(toProdExtension(importerRel));
|
|
138
|
+
if (importerProdDir === ".") return relFromDist;
|
|
139
|
+
const target = relFromDist.replace(/^\.\//, "");
|
|
140
|
+
let rel = path3.posix.relative(importerProdDir, target);
|
|
141
|
+
if (!rel.startsWith(".")) rel = "./" + rel;
|
|
142
|
+
return rel;
|
|
143
|
+
}
|
|
135
144
|
function resolveRelativeSpecifier(importer, specifier) {
|
|
136
145
|
const importerDir = path3.dirname(importer);
|
|
137
146
|
const base = path3.resolve(importerDir, specifier);
|
|
@@ -179,9 +188,10 @@ function createAliasPlugin(config, options) {
|
|
|
179
188
|
}
|
|
180
189
|
if (appDirAbs && importerOutsideAppDir && isInsideDir(resolved, appDirAbs)) {
|
|
181
190
|
modified = true;
|
|
182
|
-
return `${prefix}${quote}${
|
|
183
|
-
|
|
184
|
-
options.rootDir
|
|
191
|
+
return `${prefix}${quote}${toProdImportFromImporter(
|
|
192
|
+
importer,
|
|
193
|
+
options.rootDir,
|
|
194
|
+
toStrippedProdImportPath(resolved, options.rootDir)
|
|
185
195
|
)}${quote}`;
|
|
186
196
|
}
|
|
187
197
|
modified = true;
|
|
@@ -196,9 +206,10 @@ function createAliasPlugin(config, options) {
|
|
|
196
206
|
if (fs3.existsSync(file)) {
|
|
197
207
|
modified = true;
|
|
198
208
|
if (appDirAbs && importerOutsideAppDir && isInsideDir(file, appDirAbs)) {
|
|
199
|
-
return `${prefix}${quote}${
|
|
200
|
-
|
|
201
|
-
options.rootDir
|
|
209
|
+
return `${prefix}${quote}${toProdImportFromImporter(
|
|
210
|
+
importer,
|
|
211
|
+
options.rootDir,
|
|
212
|
+
toStrippedProdImportPath(file, options.rootDir)
|
|
202
213
|
)}${quote}`;
|
|
203
214
|
}
|
|
204
215
|
return `${prefix}${quote}${toProdImportPath(file, importer)}${quote}`;
|
|
@@ -209,9 +220,10 @@ function createAliasPlugin(config, options) {
|
|
|
209
220
|
if (fs3.existsSync(file)) {
|
|
210
221
|
modified = true;
|
|
211
222
|
if (appDirAbs && importerOutsideAppDir && isInsideDir(file, appDirAbs)) {
|
|
212
|
-
return `${prefix}${quote}${
|
|
213
|
-
|
|
214
|
-
options.rootDir
|
|
223
|
+
return `${prefix}${quote}${toProdImportFromImporter(
|
|
224
|
+
importer,
|
|
225
|
+
options.rootDir,
|
|
226
|
+
toStrippedProdImportPath(file, options.rootDir)
|
|
215
227
|
)}${quote}`;
|
|
216
228
|
}
|
|
217
229
|
return `${prefix}${quote}${toProdImportPath(file, importer)}${quote}`;
|
|
@@ -368,31 +380,13 @@ function createExternalRelativePlugin() {
|
|
|
368
380
|
}
|
|
369
381
|
};
|
|
370
382
|
}
|
|
371
|
-
async function
|
|
372
|
-
const {
|
|
373
|
-
const baseConfigName = findBaseConfig(rootDir);
|
|
374
|
-
if (!baseConfigName) {
|
|
375
|
-
return { generated: false, outputFile: "", skipped: false };
|
|
376
|
-
}
|
|
377
|
-
const absDist = path5.resolve(rootDir, dist);
|
|
378
|
-
await fs5.promises.mkdir(absDist, { recursive: true });
|
|
379
|
-
const outputFile = path5.resolve(absDist, "faapi-config.js");
|
|
380
|
-
const cacheKey = `${toRealPath(rootDir)}::${dist}`;
|
|
381
|
-
const cached = compileConfigCache.get(cacheKey);
|
|
382
|
-
if (cached && await isCacheFresh(cached)) {
|
|
383
|
-
return { generated: true, outputFile: cached.outputFile, skipped: true };
|
|
384
|
-
}
|
|
385
|
-
compileConfigCache.delete(cacheKey);
|
|
386
|
-
const configEntryPoints = [path5.resolve(rootDir, baseConfigName)];
|
|
387
|
-
const { insideFiles: appDirFiles, outsideFiles: nonAppDirFiles } = await collectRelativeImports(
|
|
388
|
-
configEntryPoints,
|
|
389
|
-
rootDir
|
|
390
|
-
);
|
|
383
|
+
async function compileProjectModules(entryPoints, rootDir, dist) {
|
|
384
|
+
const { insideFiles, outsideFiles } = await collectRelativeImports(entryPoints, rootDir);
|
|
391
385
|
const esbuild = await import("esbuild");
|
|
392
386
|
const aliasPlugins = buildAliasPlugins(rootDir);
|
|
393
|
-
const
|
|
387
|
+
const absDist = path5.resolve(rootDir, dist);
|
|
394
388
|
await esbuild.build({
|
|
395
|
-
entryPoints:
|
|
389
|
+
entryPoints: [...entryPoints, ...outsideFiles],
|
|
396
390
|
outdir: absDist,
|
|
397
391
|
outbase: rootDir,
|
|
398
392
|
bundle: false,
|
|
@@ -403,10 +397,10 @@ async function compileConfig(options) {
|
|
|
403
397
|
plugins: aliasPlugins,
|
|
404
398
|
logLevel: "silent"
|
|
405
399
|
});
|
|
406
|
-
if (
|
|
400
|
+
if (insideFiles.length > 0) {
|
|
407
401
|
const appOutbase = path5.resolve(rootDir, "src");
|
|
408
402
|
await esbuild.build({
|
|
409
|
-
entryPoints:
|
|
403
|
+
entryPoints: insideFiles,
|
|
410
404
|
outdir: absDist,
|
|
411
405
|
outbase: appOutbase,
|
|
412
406
|
bundle: false,
|
|
@@ -418,9 +412,33 @@ async function compileConfig(options) {
|
|
|
418
412
|
logLevel: "silent"
|
|
419
413
|
});
|
|
420
414
|
}
|
|
415
|
+
return { insideFiles, outsideFiles };
|
|
416
|
+
}
|
|
417
|
+
async function compileConfig(options) {
|
|
418
|
+
const { rootDir, dist } = options;
|
|
419
|
+
const baseConfigName = findBaseConfig(rootDir);
|
|
420
|
+
if (!baseConfigName) {
|
|
421
|
+
return { generated: false, outputFile: "", skipped: false };
|
|
422
|
+
}
|
|
423
|
+
const absDist = path5.resolve(rootDir, dist);
|
|
424
|
+
await fs5.promises.mkdir(absDist, { recursive: true });
|
|
425
|
+
const outputFile = path5.resolve(absDist, "faapi-config.js");
|
|
426
|
+
const cacheKey = `${toRealPath(rootDir)}::${dist}`;
|
|
427
|
+
const cached = compileConfigCache.get(cacheKey);
|
|
428
|
+
if (cached && await isCacheFresh(cached)) {
|
|
429
|
+
return { generated: true, outputFile: cached.outputFile, skipped: true };
|
|
430
|
+
}
|
|
431
|
+
compileConfigCache.delete(cacheKey);
|
|
432
|
+
const configEntryPoints = [path5.resolve(rootDir, baseConfigName)];
|
|
433
|
+
const { insideFiles: appDirFiles, outsideFiles: nonAppDirFiles } = await compileProjectModules(
|
|
434
|
+
configEntryPoints,
|
|
435
|
+
rootDir,
|
|
436
|
+
dist
|
|
437
|
+
);
|
|
421
438
|
const baseImport = `import base from './${toProdImport(baseConfigName)}';`;
|
|
422
439
|
const exportDefault = "export default base;";
|
|
423
440
|
const entryCode = [baseImport, exportDefault].join("\n");
|
|
441
|
+
const esbuild = await import("esbuild");
|
|
424
442
|
await esbuild.build({
|
|
425
443
|
stdin: { contents: entryCode, resolveDir: absDist, loader: "ts" },
|
|
426
444
|
outfile: outputFile,
|
|
@@ -7519,7 +7537,7 @@ import { createSecureServer as createHttp2SecureServer } from "http2";
|
|
|
7519
7537
|
import { readFileSync } from "fs";
|
|
7520
7538
|
import { Readable as Readable3 } from "stream";
|
|
7521
7539
|
import path22 from "path";
|
|
7522
|
-
function toWebRequest(req, bodyLimit = DEFAULT_BODY_LIMIT) {
|
|
7540
|
+
function toWebRequest(req, bodyLimit = DEFAULT_BODY_LIMIT, requestSignal) {
|
|
7523
7541
|
const forwardedProto = req.headers["x-forwarded-proto"];
|
|
7524
7542
|
const protocol = Array.isArray(forwardedProto) ? forwardedProto[0]?.split(",")[0]?.trim() ?? "http" : forwardedProto?.split(",")[0]?.trim() ?? "http";
|
|
7525
7543
|
const host = req.headers.host ?? "localhost";
|
|
@@ -7527,7 +7545,10 @@ function toWebRequest(req, bodyLimit = DEFAULT_BODY_LIMIT) {
|
|
|
7527
7545
|
const headers = nodeHttpToWebHeaders(req);
|
|
7528
7546
|
const method = req.method ?? "GET";
|
|
7529
7547
|
if (method === "GET" || method === "HEAD") {
|
|
7530
|
-
return {
|
|
7548
|
+
return {
|
|
7549
|
+
request: new Request(url.toString(), { method, headers, signal: requestSignal }),
|
|
7550
|
+
url
|
|
7551
|
+
};
|
|
7531
7552
|
}
|
|
7532
7553
|
const contentLength = req.headers["content-length"];
|
|
7533
7554
|
if (contentLength !== void 0) {
|
|
@@ -7543,7 +7564,8 @@ function toWebRequest(req, bodyLimit = DEFAULT_BODY_LIMIT) {
|
|
|
7543
7564
|
method,
|
|
7544
7565
|
headers,
|
|
7545
7566
|
body: limitedStream,
|
|
7546
|
-
duplex: "half"
|
|
7567
|
+
duplex: "half",
|
|
7568
|
+
signal: requestSignal
|
|
7547
7569
|
}),
|
|
7548
7570
|
url
|
|
7549
7571
|
};
|
|
@@ -7682,8 +7704,8 @@ function createServer(options) {
|
|
|
7682
7704
|
});
|
|
7683
7705
|
return { server, routesRef };
|
|
7684
7706
|
}
|
|
7685
|
-
function prepareRequest(req, config, bodyLimit, trustedProxy, registries) {
|
|
7686
|
-
const { request, url } = toWebRequest(req, bodyLimit);
|
|
7707
|
+
function prepareRequest(req, config, bodyLimit, trustedProxy, registries, requestSignal) {
|
|
7708
|
+
const { request, url } = toWebRequest(req, bodyLimit, requestSignal);
|
|
7687
7709
|
const method = request.method.toUpperCase();
|
|
7688
7710
|
const urlPath = url.pathname;
|
|
7689
7711
|
const ctx = createContextFromUrl(
|
|
@@ -7768,8 +7790,19 @@ async function sendErrorResponse(err, meta, res, onError, ctx) {
|
|
|
7768
7790
|
async function handleRequest(routes, rootDir, dist, req, res, outerMiddlewares, onError, config, globalInjectors, bodyLimit, trustedProxy, registries) {
|
|
7769
7791
|
let meta = { headers: {}, setCookies: [] };
|
|
7770
7792
|
let ctx;
|
|
7793
|
+
const abortController = new AbortController();
|
|
7794
|
+
res.on("close", () => {
|
|
7795
|
+
if (!res.writableEnded) abortController.abort();
|
|
7796
|
+
});
|
|
7771
7797
|
try {
|
|
7772
|
-
const prepared = prepareRequest(
|
|
7798
|
+
const prepared = prepareRequest(
|
|
7799
|
+
req,
|
|
7800
|
+
config,
|
|
7801
|
+
bodyLimit,
|
|
7802
|
+
trustedProxy,
|
|
7803
|
+
registries,
|
|
7804
|
+
abortController.signal
|
|
7805
|
+
);
|
|
7773
7806
|
ctx = prepared.ctx;
|
|
7774
7807
|
meta = prepared.meta;
|
|
7775
7808
|
const { request, url, method, urlPath } = prepared;
|
|
@@ -7855,8 +7888,9 @@ var init_startServer = __esm({
|
|
|
7855
7888
|
|
|
7856
7889
|
// src/cli/loadPlugins.ts
|
|
7857
7890
|
import path23 from "path";
|
|
7891
|
+
import fs17 from "fs";
|
|
7858
7892
|
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
7859
|
-
async function loadPlugins(declarations, ctx, rootDir) {
|
|
7893
|
+
async function loadPlugins(declarations, ctx, rootDir, dist) {
|
|
7860
7894
|
const handlerWrappers = [];
|
|
7861
7895
|
const upgradeWrappers = [];
|
|
7862
7896
|
const failures = [];
|
|
@@ -7893,7 +7927,7 @@ async function loadPlugins(declarations, ctx, rootDir) {
|
|
|
7893
7927
|
}
|
|
7894
7928
|
loaded.add(specifier);
|
|
7895
7929
|
try {
|
|
7896
|
-
const mod = await
|
|
7930
|
+
const mod = await importPluginModule(specifier, rootDir ?? process.cwd(), dist);
|
|
7897
7931
|
const plugin = mod.default ?? mod;
|
|
7898
7932
|
if (typeof plugin.setup !== "function") {
|
|
7899
7933
|
failures.push({ specifier, reason: "plugin has no setup function" });
|
|
@@ -7916,15 +7950,70 @@ async function loadPlugins(declarations, ctx, rootDir) {
|
|
|
7916
7950
|
}
|
|
7917
7951
|
return { handlerWrappers, upgradeWrappers, failures };
|
|
7918
7952
|
}
|
|
7919
|
-
function
|
|
7920
|
-
|
|
7921
|
-
|
|
7922
|
-
|
|
7953
|
+
function resolveLocalPluginSource(specifier, baseDir) {
|
|
7954
|
+
const base = path23.isAbsolute(specifier) ? specifier : path23.resolve(baseDir, specifier);
|
|
7955
|
+
if ((TS_EXTS.test(base) || JS_EXTS.test(base)) && fs17.existsSync(base)) return base;
|
|
7956
|
+
for (const ext of [".ts", ".js"]) {
|
|
7957
|
+
if (fs17.existsSync(base + ext)) return base + ext;
|
|
7958
|
+
}
|
|
7959
|
+
for (const indexExt of ["/index.ts", "/index.js"]) {
|
|
7960
|
+
if (fs17.existsSync(base + indexExt)) return base + indexExt;
|
|
7961
|
+
}
|
|
7962
|
+
return null;
|
|
7963
|
+
}
|
|
7964
|
+
function mirrorProductPath(sourcePath, baseDir, distDir) {
|
|
7965
|
+
let rel = path23.relative(baseDir, sourcePath).replace(/\\/g, "/");
|
|
7966
|
+
if (rel.startsWith("src/")) rel = rel.slice(4);
|
|
7967
|
+
return path23.resolve(distDir, rel.replace(TS_EXTS, ".js"));
|
|
7968
|
+
}
|
|
7969
|
+
function isSourceNewer(sourcePath, productPath) {
|
|
7970
|
+
try {
|
|
7971
|
+
return fs17.statSync(sourcePath).mtimeMs > fs17.statSync(productPath).mtimeMs;
|
|
7972
|
+
} catch {
|
|
7973
|
+
return true;
|
|
7974
|
+
}
|
|
7975
|
+
}
|
|
7976
|
+
async function importPluginModule(specifier, baseDir, dist) {
|
|
7977
|
+
const isRelative = specifier.startsWith("./") || specifier.startsWith("../");
|
|
7978
|
+
if (!isRelative && !path23.isAbsolute(specifier)) {
|
|
7979
|
+
return import(specifier);
|
|
7980
|
+
}
|
|
7981
|
+
const distDir = dist ? path23.resolve(baseDir, dist) : null;
|
|
7982
|
+
const sourcePath = resolveLocalPluginSource(specifier, baseDir);
|
|
7983
|
+
const productPath = sourcePath && distDir ? mirrorProductPath(sourcePath, baseDir, distDir) : null;
|
|
7984
|
+
if (productPath !== null && fs17.existsSync(productPath) && (sourcePath === null || !isSourceNewer(sourcePath, productPath))) {
|
|
7985
|
+
return import(pathToFileURL2(productPath).href);
|
|
7986
|
+
}
|
|
7987
|
+
if (sourcePath && TS_EXTS.test(sourcePath)) {
|
|
7988
|
+
if (!isDevOnDemandEnabled() || !distDir) {
|
|
7989
|
+
throw new Error(
|
|
7990
|
+
`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.`
|
|
7991
|
+
);
|
|
7992
|
+
}
|
|
7993
|
+
const relFromRoot = path23.relative(baseDir, sourcePath).replace(/\\/g, "/");
|
|
7994
|
+
if (relFromRoot.startsWith("src/")) {
|
|
7995
|
+
await ensureCompiled(sourcePath, baseDir, dist);
|
|
7996
|
+
} else {
|
|
7997
|
+
await compileProjectModules([sourcePath], baseDir, dist);
|
|
7998
|
+
}
|
|
7999
|
+
if (productPath && fs17.existsSync(productPath)) {
|
|
8000
|
+
return import(pathToFileURL2(productPath).href);
|
|
8001
|
+
}
|
|
7923
8002
|
}
|
|
7924
|
-
if (
|
|
7925
|
-
return pathToFileURL2(
|
|
8003
|
+
if (sourcePath && JS_EXTS.test(sourcePath)) {
|
|
8004
|
+
return import(pathToFileURL2(sourcePath).href);
|
|
7926
8005
|
}
|
|
7927
|
-
|
|
8006
|
+
const candidates = [
|
|
8007
|
+
`${specifier}.ts`,
|
|
8008
|
+
`${specifier}/index.ts`,
|
|
8009
|
+
`${specifier}.js`,
|
|
8010
|
+
`${specifier}/index.js`
|
|
8011
|
+
].map((c) => path23.isAbsolute(c) ? c : path23.join(baseDir, c));
|
|
8012
|
+
throw new Error(
|
|
8013
|
+
`Cannot find local plugin "${specifier}" (resolved from ${baseDir}). Expected one of:
|
|
8014
|
+
${candidates.join("\n ")}
|
|
8015
|
+
Create the plugin file (export default { name, setup(ctx) {...} }) or fix the path.`
|
|
8016
|
+
);
|
|
7928
8017
|
}
|
|
7929
8018
|
function resolveDeclaration(decl) {
|
|
7930
8019
|
if (typeof decl === "string") {
|
|
@@ -7942,19 +8031,24 @@ function resolveDeclaration(decl) {
|
|
|
7942
8031
|
}
|
|
7943
8032
|
throw new Error(`Invalid plugin declaration: ${JSON.stringify(decl)}`);
|
|
7944
8033
|
}
|
|
8034
|
+
var TS_EXTS, JS_EXTS;
|
|
7945
8035
|
var init_loadPlugins = __esm({
|
|
7946
8036
|
"src/cli/loadPlugins.ts"() {
|
|
7947
8037
|
"use strict";
|
|
8038
|
+
init_compileOnDemand();
|
|
8039
|
+
init_compileConfig();
|
|
8040
|
+
TS_EXTS = /\.(ts|tsx|mts|cts)$/;
|
|
8041
|
+
JS_EXTS = /\.(js|mjs|cjs|jsx)$/;
|
|
7948
8042
|
}
|
|
7949
8043
|
});
|
|
7950
8044
|
|
|
7951
8045
|
// src/cli/createAppCore.ts
|
|
7952
|
-
import
|
|
8046
|
+
import fs18 from "fs";
|
|
7953
8047
|
import path24 from "path";
|
|
7954
8048
|
import { PassThrough, Readable as Readable4 } from "stream";
|
|
7955
8049
|
async function loadAndHydrateTools(rootDir, dist, registries = defaultRegistries) {
|
|
7956
8050
|
const toolsPath = path24.resolve(rootDir, dist, TOOLS_FILE2);
|
|
7957
|
-
if (!
|
|
8051
|
+
if (!fs18.existsSync(toolsPath)) {
|
|
7958
8052
|
return [];
|
|
7959
8053
|
}
|
|
7960
8054
|
const serialized = await importWithCacheBust(toolsPath);
|
|
@@ -7964,7 +8058,7 @@ async function loadAndHydrateTools(rootDir, dist, registries = defaultRegistries
|
|
|
7964
8058
|
}
|
|
7965
8059
|
async function loadAndHydrateAgents(rootDir, dist, registries = defaultRegistries) {
|
|
7966
8060
|
const agentsPath = path24.resolve(rootDir, dist, AGENTS_FILE2);
|
|
7967
|
-
if (!
|
|
8061
|
+
if (!fs18.existsSync(agentsPath)) {
|
|
7968
8062
|
return [];
|
|
7969
8063
|
}
|
|
7970
8064
|
const serialized = await importWithCacheBust(agentsPath);
|
|
@@ -8005,7 +8099,7 @@ async function createAppBase(options) {
|
|
|
8005
8099
|
const rootDir = options?.rootDir ?? process.cwd();
|
|
8006
8100
|
const dist = options?.dist ?? process.env.FAAPI_DIST ?? DEFAULT_DIST;
|
|
8007
8101
|
const routesPath = path24.resolve(rootDir, dist, ROUTES_FILE);
|
|
8008
|
-
if (!
|
|
8102
|
+
if (!fs18.existsSync(routesPath)) {
|
|
8009
8103
|
throw new Error(
|
|
8010
8104
|
`[faapi] ${dist}/${ROUTES_FILE} \u4E0D\u5B58\u5728\uFF0C\u8BF7\u5148\u6267\u884C \`faapi build\`\uFF08\u6216 \`faapi dev\`\uFF09\u751F\u6210\u4EA7\u7269\u3002`
|
|
8011
8105
|
);
|
|
@@ -8057,7 +8151,8 @@ async function createAppBase(options) {
|
|
|
8057
8151
|
server,
|
|
8058
8152
|
config: pluginConfig
|
|
8059
8153
|
},
|
|
8060
|
-
rootDir
|
|
8154
|
+
rootDir,
|
|
8155
|
+
dist
|
|
8061
8156
|
);
|
|
8062
8157
|
applyPluginWrappers(server, handlerWrappers, upgradeWrappers);
|
|
8063
8158
|
let closed = false;
|
|
@@ -8070,6 +8165,16 @@ async function createAppBase(options) {
|
|
|
8070
8165
|
async listen(listenPort) {
|
|
8071
8166
|
const envPort = process.env.PORT ? Number(process.env.PORT) : void 0;
|
|
8072
8167
|
const actualPort = listenPort ?? options?.port ?? envPort ?? DEFAULT_PORT;
|
|
8168
|
+
if (config?.lifecycle?.onBoot) {
|
|
8169
|
+
try {
|
|
8170
|
+
await config.lifecycle.onBoot({ rootDir, routes: sorted, server, registries });
|
|
8171
|
+
console.log("- onBoot hook executed");
|
|
8172
|
+
} catch (err) {
|
|
8173
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
8174
|
+
console.error(`[faapi] onBoot hook failed: ${message}`);
|
|
8175
|
+
throw err;
|
|
8176
|
+
}
|
|
8177
|
+
}
|
|
8073
8178
|
return new Promise((resolve3, reject) => {
|
|
8074
8179
|
const onListenError = (err) => {
|
|
8075
8180
|
if (err.code === "EADDRINUSE") {
|
|
@@ -8447,14 +8552,14 @@ __export(buildCommand_exports, {
|
|
|
8447
8552
|
buildCommand: () => buildCommand
|
|
8448
8553
|
});
|
|
8449
8554
|
import path26 from "path";
|
|
8450
|
-
import
|
|
8555
|
+
import fs19 from "fs";
|
|
8451
8556
|
async function buildCommand(options) {
|
|
8452
8557
|
const rootDir = options?.rootDir ?? process.cwd();
|
|
8453
8558
|
const outdir = options?.dist ?? DEFAULT_DIST2;
|
|
8454
8559
|
const absOut = path26.resolve(rootDir, outdir);
|
|
8455
8560
|
const realRoot = toRealPath(path26.resolve(rootDir));
|
|
8456
8561
|
if (isInsideDir(toRealPath(absOut), realRoot)) {
|
|
8457
|
-
|
|
8562
|
+
fs19.rmSync(absOut, { recursive: true, force: true });
|
|
8458
8563
|
} else {
|
|
8459
8564
|
console.warn(
|
|
8460
8565
|
`! Output directory "${outdir}" is outside the project root, skipping clean (stale artifacts may remain)`
|
|
@@ -8463,9 +8568,9 @@ async function buildCommand(options) {
|
|
|
8463
8568
|
await compileConfig({ rootDir, dist: outdir });
|
|
8464
8569
|
const _config = await loadConfig(rootDir, outdir);
|
|
8465
8570
|
const pkgPath = path26.resolve(rootDir, "package.json");
|
|
8466
|
-
if (
|
|
8571
|
+
if (fs19.existsSync(pkgPath)) {
|
|
8467
8572
|
try {
|
|
8468
|
-
const pkg = JSON.parse(
|
|
8573
|
+
const pkg = JSON.parse(fs19.readFileSync(pkgPath, "utf-8"));
|
|
8469
8574
|
if (pkg.type !== "module") {
|
|
8470
8575
|
console.warn(
|
|
8471
8576
|
'! package.json is missing "type": "module" \u2014 build output is ESM and `node dist/main` will fail without it'
|
|
@@ -8489,6 +8594,18 @@ async function buildCommand(options) {
|
|
|
8489
8594
|
console.warn(" ! No source files found, nothing to build");
|
|
8490
8595
|
return;
|
|
8491
8596
|
}
|
|
8597
|
+
const localPluginSources = extractLocalPluginSources(_config?.plugins, rootDir);
|
|
8598
|
+
if (localPluginSources.length > 0) {
|
|
8599
|
+
console.log("\n[2.5/8] Compiling local plugins...");
|
|
8600
|
+
const outside = localPluginSources.filter((p) => {
|
|
8601
|
+
const rel = path26.relative(rootDir, p).replace(/\\/g, "/");
|
|
8602
|
+
return !rel.startsWith("src/");
|
|
8603
|
+
});
|
|
8604
|
+
if (outside.length > 0) {
|
|
8605
|
+
await compileProjectModules(outside, rootDir, outdir);
|
|
8606
|
+
}
|
|
8607
|
+
console.log(` Compiled ${localPluginSources.length} local plugin(s)`);
|
|
8608
|
+
}
|
|
8492
8609
|
console.log("\n[3/8] Scanning routes...");
|
|
8493
8610
|
const { routes, wsRoutes } = await scanRoutes(rootDir, ROUTE_PATTERNS, outdir);
|
|
8494
8611
|
const sorted = sortRoutes(routes);
|
|
@@ -8534,10 +8651,31 @@ loadEnv(process.cwd());
|
|
|
8534
8651
|
const app = await createProdApp(${createProdAppArgs});
|
|
8535
8652
|
await app.listen();
|
|
8536
8653
|
`;
|
|
8537
|
-
await
|
|
8654
|
+
await fs19.promises.writeFile(mainPath, mainContent, "utf-8");
|
|
8538
8655
|
console.log(` Written to ${mainPath}`);
|
|
8539
8656
|
console.log("\nfaapi build completed");
|
|
8540
8657
|
}
|
|
8658
|
+
function extractLocalPluginSources(declarations, rootDir) {
|
|
8659
|
+
if (!declarations || declarations.length === 0) return [];
|
|
8660
|
+
const sources = [];
|
|
8661
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8662
|
+
for (const decl of declarations) {
|
|
8663
|
+
let specifier;
|
|
8664
|
+
if (typeof decl === "string") specifier = decl;
|
|
8665
|
+
else if (Array.isArray(decl)) specifier = decl[0];
|
|
8666
|
+
else if ("path" in decl) specifier = decl.path;
|
|
8667
|
+
if (!specifier) continue;
|
|
8668
|
+
if (!specifier.startsWith("./") && !specifier.startsWith("../") && !path26.isAbsolute(specifier)) {
|
|
8669
|
+
continue;
|
|
8670
|
+
}
|
|
8671
|
+
const source = resolveLocalPluginSource(specifier, rootDir);
|
|
8672
|
+
if (source && !seen.has(source)) {
|
|
8673
|
+
seen.add(source);
|
|
8674
|
+
sources.push(source);
|
|
8675
|
+
}
|
|
8676
|
+
}
|
|
8677
|
+
return sources;
|
|
8678
|
+
}
|
|
8541
8679
|
var DEFAULT_DIST2;
|
|
8542
8680
|
var init_buildCommand = __esm({
|
|
8543
8681
|
"src/cli/buildCommand.ts"() {
|
|
@@ -8557,6 +8695,7 @@ var init_buildCommand = __esm({
|
|
|
8557
8695
|
init_prodPaths();
|
|
8558
8696
|
init_compileConfig();
|
|
8559
8697
|
init_loadConfig();
|
|
8698
|
+
init_loadPlugins();
|
|
8560
8699
|
init_prodPaths();
|
|
8561
8700
|
DEFAULT_DIST2 = "dist";
|
|
8562
8701
|
}
|