@faapi/faapi 2.0.1 → 3.1.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 +4536 -3525
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +618 -3
- package/dist/index.js +1988 -927
- package/dist/index.js.map +1 -1
- package/dist/testing.js +364 -221
- package/dist/testing.js.map +1 -1
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -790,7 +790,11 @@ var init_resolveInjection = __esm({
|
|
|
790
790
|
ip: "ip",
|
|
791
791
|
ua: "ua",
|
|
792
792
|
files: "files",
|
|
793
|
-
fields: "fields"
|
|
793
|
+
fields: "fields",
|
|
794
|
+
agent: "agent",
|
|
795
|
+
// Phase 2.3
|
|
796
|
+
agents: "agents"
|
|
797
|
+
// Phase 2.3
|
|
794
798
|
};
|
|
795
799
|
}
|
|
796
800
|
});
|
|
@@ -1219,8 +1223,8 @@ __export(generateSchemaFiles_exports, {
|
|
|
1219
1223
|
getRuntimeSchemaPath: () => getRuntimeSchemaPath,
|
|
1220
1224
|
getSchemaOutputPath: () => getSchemaOutputPath
|
|
1221
1225
|
});
|
|
1222
|
-
import
|
|
1223
|
-
import
|
|
1226
|
+
import path5 from "path";
|
|
1227
|
+
import fs4 from "fs/promises";
|
|
1224
1228
|
function getSchemaOutputPath(sourceFile, dist, rootDir) {
|
|
1225
1229
|
let rel = sourceFile.replace(/\\/g, "/");
|
|
1226
1230
|
if (rel.startsWith("src/")) {
|
|
@@ -1228,7 +1232,7 @@ function getSchemaOutputPath(sourceFile, dist, rootDir) {
|
|
|
1228
1232
|
}
|
|
1229
1233
|
const idx = rel.lastIndexOf("/");
|
|
1230
1234
|
const relDir = idx >= 0 ? rel.slice(0, idx) : "";
|
|
1231
|
-
return
|
|
1235
|
+
return path5.resolve(rootDir, dist, relDir, "zod.js");
|
|
1232
1236
|
}
|
|
1233
1237
|
function getRuntimeSchemaPath(filePath, dist, rootDir) {
|
|
1234
1238
|
let rel = filePath.replace(/\\/g, "/");
|
|
@@ -1239,7 +1243,7 @@ function getRuntimeSchemaPath(filePath, dist, rootDir) {
|
|
|
1239
1243
|
}
|
|
1240
1244
|
const idx = rel.lastIndexOf("/");
|
|
1241
1245
|
const relDir = idx >= 0 ? rel.slice(0, idx) : "";
|
|
1242
|
-
return
|
|
1246
|
+
return path5.resolve(rootDir, dist, relDir, "zod.js");
|
|
1243
1247
|
}
|
|
1244
1248
|
function getHelpersImportPath(relDir) {
|
|
1245
1249
|
if (!relDir) return `./${HELPERS_FILENAME}`;
|
|
@@ -1289,7 +1293,7 @@ async function generateSchemaFiles(routes, rootDir, dist) {
|
|
|
1289
1293
|
}
|
|
1290
1294
|
const fileEntries = [];
|
|
1291
1295
|
for (const [filePath, fileSources] of sourcesByFile) {
|
|
1292
|
-
const relFile =
|
|
1296
|
+
const relFile = path5.relative(rootDir, filePath).replace(/\\/g, "/");
|
|
1293
1297
|
const outputPath = getSchemaOutputPath(relFile, dist, rootDir);
|
|
1294
1298
|
const allTypes = allTypesByFile.get(filePath) ?? /* @__PURE__ */ new Map();
|
|
1295
1299
|
let relForDir = relFile;
|
|
@@ -1304,7 +1308,7 @@ async function generateSchemaFiles(routes, rootDir, dist) {
|
|
|
1304
1308
|
}
|
|
1305
1309
|
const allSourceCode = fileEntries.map((e) => e.source).join("\n");
|
|
1306
1310
|
if (usesCoerceHelpers(allSourceCode)) {
|
|
1307
|
-
const helpersPath =
|
|
1311
|
+
const helpersPath = path5.resolve(rootDir, dist, HELPERS_FILENAME);
|
|
1308
1312
|
await writeSchemaFile(helpersPath, generateHelpersFileSource());
|
|
1309
1313
|
}
|
|
1310
1314
|
await Promise.all(
|
|
@@ -1312,8 +1316,8 @@ async function generateSchemaFiles(routes, rootDir, dist) {
|
|
|
1312
1316
|
);
|
|
1313
1317
|
}
|
|
1314
1318
|
async function writeSchemaFile(outputPath, source) {
|
|
1315
|
-
await
|
|
1316
|
-
await
|
|
1319
|
+
await fs4.mkdir(path5.dirname(outputPath), { recursive: true });
|
|
1320
|
+
await fs4.writeFile(outputPath, source, "utf-8");
|
|
1317
1321
|
}
|
|
1318
1322
|
var init_generateSchemaFiles = __esm({
|
|
1319
1323
|
"src/cli/generateSchemaFiles.ts"() {
|
|
@@ -1330,175 +1334,117 @@ init_resolveTypeNode();
|
|
|
1330
1334
|
init_inputType();
|
|
1331
1335
|
init_collectRouteSchemaSources();
|
|
1332
1336
|
|
|
1333
|
-
// src/
|
|
1334
|
-
var
|
|
1335
|
-
function
|
|
1336
|
-
const
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
await next();
|
|
1348
|
-
return;
|
|
1349
|
-
}
|
|
1350
|
-
let allowOrigin = null;
|
|
1351
|
-
if (origin === true) {
|
|
1352
|
-
allowOrigin = reqOrigin;
|
|
1353
|
-
} else if (typeof origin === "string") {
|
|
1354
|
-
allowOrigin = reqOrigin === origin ? origin : null;
|
|
1355
|
-
} else if (Array.isArray(origin)) {
|
|
1356
|
-
allowOrigin = origin.includes(reqOrigin) ? reqOrigin : null;
|
|
1357
|
-
}
|
|
1358
|
-
if (!allowOrigin) {
|
|
1359
|
-
await next();
|
|
1360
|
-
return;
|
|
1361
|
-
}
|
|
1362
|
-
ctx.setHeader("Access-Control-Allow-Origin", allowOrigin);
|
|
1363
|
-
if (origin === true || Array.isArray(origin)) {
|
|
1364
|
-
const existingVary = ctx.headers.get("vary");
|
|
1365
|
-
if (existingVary) {
|
|
1366
|
-
if (!existingVary.toLowerCase().includes("origin")) {
|
|
1367
|
-
ctx.setHeader("Vary", `${existingVary}, Origin`);
|
|
1368
|
-
}
|
|
1369
|
-
} else {
|
|
1370
|
-
ctx.setHeader("Vary", "Origin");
|
|
1371
|
-
}
|
|
1372
|
-
}
|
|
1373
|
-
ctx.setHeader("Access-Control-Allow-Methods", methods.join(", "));
|
|
1374
|
-
if (allowedHeaders) {
|
|
1375
|
-
ctx.setHeader("Access-Control-Allow-Headers", allowedHeaders.join(", "));
|
|
1376
|
-
} else {
|
|
1377
|
-
const requestHeaders = ctx.headers.get("access-control-request-headers");
|
|
1378
|
-
if (requestHeaders) {
|
|
1379
|
-
ctx.setHeader("Access-Control-Allow-Headers", requestHeaders);
|
|
1380
|
-
}
|
|
1381
|
-
}
|
|
1382
|
-
if (exposeHeaders && exposeHeaders.length > 0) {
|
|
1383
|
-
ctx.setHeader("Access-Control-Expose-Headers", exposeHeaders.join(", "));
|
|
1384
|
-
}
|
|
1385
|
-
if (credentials) {
|
|
1386
|
-
ctx.setHeader("Access-Control-Allow-Credentials", "true");
|
|
1387
|
-
}
|
|
1388
|
-
if (maxAge !== void 0) {
|
|
1389
|
-
ctx.setHeader("Access-Control-Max-Age", String(maxAge));
|
|
1390
|
-
}
|
|
1391
|
-
if (ctx.method === "OPTIONS") {
|
|
1392
|
-
return new Response(null, { status: 204 });
|
|
1393
|
-
}
|
|
1394
|
-
await next();
|
|
1395
|
-
};
|
|
1337
|
+
// src/injection/toolRegistry.ts
|
|
1338
|
+
var registry = /* @__PURE__ */ new Map();
|
|
1339
|
+
function hydrateToolRegistry(tools) {
|
|
1340
|
+
const next = /* @__PURE__ */ new Map();
|
|
1341
|
+
for (const tool of tools) {
|
|
1342
|
+
next.set(tool.name, tool);
|
|
1343
|
+
}
|
|
1344
|
+
registry = next;
|
|
1345
|
+
}
|
|
1346
|
+
function clearToolRegistry() {
|
|
1347
|
+
registry = /* @__PURE__ */ new Map();
|
|
1348
|
+
}
|
|
1349
|
+
function getTool(name) {
|
|
1350
|
+
return registry.get(name);
|
|
1396
1351
|
}
|
|
1397
1352
|
|
|
1398
|
-
// src/
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
status,
|
|
1422
|
-
durationMs: duration,
|
|
1423
|
-
error: message
|
|
1424
|
-
};
|
|
1425
|
-
log(entry, `${ctx.method} ${ctx.path} ${status} ${duration}ms - ${message}`);
|
|
1426
|
-
throw err;
|
|
1427
|
-
}
|
|
1428
|
-
};
|
|
1353
|
+
// src/injection/skillRegistry.ts
|
|
1354
|
+
var registry2 = /* @__PURE__ */ new Map();
|
|
1355
|
+
function hydrateSkillRegistry(skills) {
|
|
1356
|
+
const next = /* @__PURE__ */ new Map();
|
|
1357
|
+
for (const skill of skills) {
|
|
1358
|
+
next.set(skill.name, skill);
|
|
1359
|
+
}
|
|
1360
|
+
registry2 = next;
|
|
1361
|
+
}
|
|
1362
|
+
function clearSkillRegistry() {
|
|
1363
|
+
registry2 = /* @__PURE__ */ new Map();
|
|
1364
|
+
}
|
|
1365
|
+
function upsertSkill(core) {
|
|
1366
|
+
registry2.set(core.name, core);
|
|
1367
|
+
}
|
|
1368
|
+
function removeSkill(name) {
|
|
1369
|
+
registry2.delete(name);
|
|
1370
|
+
}
|
|
1371
|
+
function getSkill(name) {
|
|
1372
|
+
return registry2.get(name);
|
|
1373
|
+
}
|
|
1374
|
+
function listSkills() {
|
|
1375
|
+
return Array.from(registry2.values());
|
|
1429
1376
|
}
|
|
1430
1377
|
|
|
1431
|
-
// src/
|
|
1432
|
-
var
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
ctx.setHeader("Strict-Transport-Security", opts.strictTransportSecurity);
|
|
1464
|
-
}
|
|
1465
|
-
if (opts.xDnsPrefetchControl) {
|
|
1466
|
-
ctx.setHeader("X-DNS-Prefetch-Control", "off");
|
|
1467
|
-
}
|
|
1468
|
-
if (opts.xDownloadOptions) {
|
|
1469
|
-
ctx.setHeader("X-Download-Options", "noopen");
|
|
1470
|
-
}
|
|
1471
|
-
if (opts.xPermittedCrossDomainPolicies !== false) {
|
|
1472
|
-
ctx.setHeader("X-Permitted-Cross-Domain-Policies", opts.xPermittedCrossDomainPolicies);
|
|
1473
|
-
}
|
|
1474
|
-
if (opts.crossOriginOpenerPolicy !== false) {
|
|
1475
|
-
ctx.setHeader("Cross-Origin-Opener-Policy", opts.crossOriginOpenerPolicy);
|
|
1476
|
-
}
|
|
1477
|
-
if (opts.crossOriginResourcePolicy !== false) {
|
|
1478
|
-
ctx.setHeader("Cross-Origin-Resource-Policy", opts.crossOriginResourcePolicy);
|
|
1479
|
-
}
|
|
1480
|
-
if (opts.crossOriginEmbedderPolicy !== false) {
|
|
1481
|
-
ctx.setHeader("Cross-Origin-Embedder-Policy", opts.crossOriginEmbedderPolicy);
|
|
1482
|
-
}
|
|
1483
|
-
if (opts.originAgentCluster) {
|
|
1484
|
-
ctx.setHeader("Origin-Agent-Cluster", "?1");
|
|
1485
|
-
}
|
|
1486
|
-
if (opts.xPoweredBy) {
|
|
1487
|
-
ctx.setHeader("X-Powered-By", "faapi");
|
|
1378
|
+
// src/injection/agentRegistry.ts
|
|
1379
|
+
var registry3 = /* @__PURE__ */ new Map();
|
|
1380
|
+
function hydrateAgentRegistry(agents) {
|
|
1381
|
+
const next = /* @__PURE__ */ new Map();
|
|
1382
|
+
for (const agent of agents) {
|
|
1383
|
+
next.set(agent.name, agent);
|
|
1384
|
+
}
|
|
1385
|
+
registry3 = next;
|
|
1386
|
+
}
|
|
1387
|
+
function clearAgentRegistry() {
|
|
1388
|
+
registry3 = /* @__PURE__ */ new Map();
|
|
1389
|
+
}
|
|
1390
|
+
function getAgent(name) {
|
|
1391
|
+
return getSkill(name) ?? registry3.get(name);
|
|
1392
|
+
}
|
|
1393
|
+
function getAgentEntry(name) {
|
|
1394
|
+
return registry3.get(name);
|
|
1395
|
+
}
|
|
1396
|
+
function listAgents() {
|
|
1397
|
+
const merged = /* @__PURE__ */ new Map();
|
|
1398
|
+
for (const agent of registry3.values()) merged.set(agent.name, agent);
|
|
1399
|
+
for (const skill of listSkills()) merged.set(skill.name, skill);
|
|
1400
|
+
return Array.from(merged.values());
|
|
1401
|
+
}
|
|
1402
|
+
function resolveAgentTools(name) {
|
|
1403
|
+
const agent = getAgent(name);
|
|
1404
|
+
if (!agent) return [];
|
|
1405
|
+
const result = /* @__PURE__ */ new Map();
|
|
1406
|
+
if (agent.tools) {
|
|
1407
|
+
for (const toolName of agent.tools) {
|
|
1408
|
+
const tool = getTool(toolName);
|
|
1409
|
+
if (tool) result.set(tool.name, tool);
|
|
1488
1410
|
}
|
|
1489
|
-
|
|
1490
|
-
|
|
1411
|
+
}
|
|
1412
|
+
return Array.from(result.values());
|
|
1413
|
+
}
|
|
1414
|
+
function resolveSubAgents(name) {
|
|
1415
|
+
const agent = getAgent(name);
|
|
1416
|
+
if (!agent || !agent.agents) return [];
|
|
1417
|
+
const result = [];
|
|
1418
|
+
for (const agentName of agent.agents) {
|
|
1419
|
+
const subAgent = getAgent(agentName);
|
|
1420
|
+
if (subAgent) result.push(subAgent);
|
|
1421
|
+
}
|
|
1422
|
+
return result;
|
|
1491
1423
|
}
|
|
1492
1424
|
|
|
1493
|
-
// src/
|
|
1494
|
-
import
|
|
1495
|
-
|
|
1425
|
+
// src/loader/loadAgentModule.ts
|
|
1426
|
+
import fs6 from "fs";
|
|
1427
|
+
|
|
1428
|
+
// src/loader/resolveExports.ts
|
|
1429
|
+
function resolveExport(module, exportName) {
|
|
1430
|
+
if (exportName in module && typeof module[exportName] !== "undefined") {
|
|
1431
|
+
return module[exportName];
|
|
1432
|
+
}
|
|
1433
|
+
const defaultExport = module.default;
|
|
1434
|
+
if (defaultExport !== null && typeof defaultExport === "object") {
|
|
1435
|
+
const value = defaultExport[exportName];
|
|
1436
|
+
if (value !== void 0) {
|
|
1437
|
+
return value;
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
return void 0;
|
|
1441
|
+
}
|
|
1496
1442
|
|
|
1497
1443
|
// src/utils/importWithCacheBust.ts
|
|
1498
1444
|
import { pathToFileURL } from "url";
|
|
1499
1445
|
var loadTs;
|
|
1500
|
-
function setLoadTimestamp(
|
|
1501
|
-
loadTs =
|
|
1446
|
+
function setLoadTimestamp(ts9) {
|
|
1447
|
+
loadTs = ts9;
|
|
1502
1448
|
}
|
|
1503
1449
|
function getVitestImportActual() {
|
|
1504
1450
|
const vi = globalThis.vi;
|
|
@@ -1522,666 +1468,1246 @@ async function importWithCacheBust(filePath, bustViteCache = false) {
|
|
|
1522
1468
|
return await import(url);
|
|
1523
1469
|
}
|
|
1524
1470
|
|
|
1525
|
-
// src/
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1471
|
+
// src/cli/compileOnDemand.ts
|
|
1472
|
+
import path6 from "path";
|
|
1473
|
+
import fs5 from "fs";
|
|
1474
|
+
|
|
1475
|
+
// src/cli/compileDevRoutes.ts
|
|
1476
|
+
import path4 from "path";
|
|
1477
|
+
import fs3 from "fs";
|
|
1478
|
+
import fg from "fast-glob";
|
|
1479
|
+
|
|
1480
|
+
// src/cli/aliasPlugin.ts
|
|
1481
|
+
import path3 from "path";
|
|
1482
|
+
import fs2 from "fs";
|
|
1483
|
+
|
|
1484
|
+
// src/utils/resolveAlias.ts
|
|
1485
|
+
function resolveAlias(specifier, config) {
|
|
1486
|
+
const candidates = [];
|
|
1487
|
+
for (const [pattern, targets] of Object.entries(config.paths)) {
|
|
1488
|
+
const wildcardIndex = pattern.indexOf("*");
|
|
1489
|
+
if (wildcardIndex === -1) {
|
|
1490
|
+
if (specifier === pattern) {
|
|
1491
|
+
candidates.push(...targets);
|
|
1492
|
+
}
|
|
1493
|
+
continue;
|
|
1494
|
+
}
|
|
1495
|
+
const prefix = pattern.slice(0, wildcardIndex);
|
|
1496
|
+
const suffix = pattern.slice(wildcardIndex + 1);
|
|
1497
|
+
if (specifier.startsWith(prefix) && specifier.endsWith(suffix) && specifier.length >= prefix.length + suffix.length) {
|
|
1498
|
+
const captured = specifier.slice(prefix.length, specifier.length - suffix.length);
|
|
1499
|
+
for (const target of targets) {
|
|
1500
|
+
candidates.push(target.replace("*", captured));
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1532
1503
|
}
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1504
|
+
return candidates;
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
// src/utils/readTsconfig.ts
|
|
1508
|
+
import ts6 from "typescript";
|
|
1509
|
+
import path2 from "path";
|
|
1510
|
+
import fs from "fs";
|
|
1511
|
+
function readTsconfig(rootDir) {
|
|
1512
|
+
const tsconfigPath = path2.resolve(rootDir, "tsconfig.json");
|
|
1513
|
+
if (!fs.existsSync(tsconfigPath)) return null;
|
|
1514
|
+
const configFile = ts6.readConfigFile(tsconfigPath, ts6.sys.readFile);
|
|
1515
|
+
if (configFile.error || !configFile.config) return null;
|
|
1516
|
+
const parsed = ts6.parseJsonConfigFileContent(configFile.config, ts6.sys, rootDir);
|
|
1517
|
+
const baseUrl = parsed.options.baseUrl ?? rootDir;
|
|
1518
|
+
const rawPaths = parsed.options.paths;
|
|
1519
|
+
if (!rawPaths) return null;
|
|
1520
|
+
const paths = {};
|
|
1521
|
+
for (const [pattern, targets] of Object.entries(rawPaths)) {
|
|
1522
|
+
paths[pattern] = targets.map((t) => path2.resolve(baseUrl, t));
|
|
1538
1523
|
}
|
|
1539
|
-
return
|
|
1524
|
+
return { baseUrl, paths };
|
|
1540
1525
|
}
|
|
1541
1526
|
|
|
1542
|
-
// src/cli/
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1527
|
+
// src/cli/aliasPlugin.ts
|
|
1528
|
+
function toProdExtension(filePath) {
|
|
1529
|
+
if (filePath.endsWith(".ts")) return filePath.slice(0, -3) + ".js";
|
|
1530
|
+
if (filePath.endsWith(".tsx")) return filePath.slice(0, -4) + ".js";
|
|
1531
|
+
if (filePath.endsWith(".jsx")) return filePath.slice(0, -4) + ".js";
|
|
1532
|
+
return filePath;
|
|
1547
1533
|
}
|
|
1548
|
-
function
|
|
1549
|
-
|
|
1534
|
+
function toProdImportPath(sourceFile, importer) {
|
|
1535
|
+
const importerDir = path3.dirname(importer);
|
|
1536
|
+
let rel = path3.relative(importerDir, sourceFile);
|
|
1537
|
+
rel = rel.split(path3.sep).join("/");
|
|
1538
|
+
if (!rel.startsWith(".")) rel = "./" + rel;
|
|
1539
|
+
return toProdExtension(rel);
|
|
1550
1540
|
}
|
|
1551
|
-
function
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
1557
|
-
const match = /^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/.exec(trimmed);
|
|
1558
|
-
if (!match) continue;
|
|
1559
|
-
const [, key, rawValue] = match;
|
|
1560
|
-
const value = parseValue(rawValue, { ...fileVars, ...result });
|
|
1561
|
-
result[key] = value;
|
|
1541
|
+
function toRealPath(p) {
|
|
1542
|
+
try {
|
|
1543
|
+
return fs2.realpathSync(p);
|
|
1544
|
+
} catch {
|
|
1545
|
+
return p;
|
|
1562
1546
|
}
|
|
1563
|
-
return result;
|
|
1564
1547
|
}
|
|
1565
|
-
function
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1548
|
+
function isInsideDir(filePath, dir) {
|
|
1549
|
+
const rel = path3.relative(dir, filePath);
|
|
1550
|
+
return rel !== "" && !rel.startsWith("..") && !path3.isAbsolute(rel);
|
|
1551
|
+
}
|
|
1552
|
+
var APP_DIR = "src";
|
|
1553
|
+
function toStrippedProdImportPath(sourceFile, rootDir) {
|
|
1554
|
+
const appDirAbs = toRealPath(path3.resolve(rootDir, APP_DIR));
|
|
1555
|
+
const sourceReal = toRealPath(sourceFile);
|
|
1556
|
+
let rel = path3.relative(appDirAbs, sourceReal);
|
|
1557
|
+
rel = rel.split(path3.sep).join("/");
|
|
1558
|
+
if (!rel.startsWith(".")) rel = "./" + rel;
|
|
1559
|
+
return toProdExtension(rel);
|
|
1560
|
+
}
|
|
1561
|
+
var PROD_EXTS = [".js", ".mjs", ".cjs"];
|
|
1562
|
+
var SOURCE_EXTS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
|
|
1563
|
+
var INDEX_EXTS = [
|
|
1564
|
+
"/index.ts",
|
|
1565
|
+
"/index.tsx",
|
|
1566
|
+
"/index.js",
|
|
1567
|
+
"/index.jsx",
|
|
1568
|
+
"/index.mjs",
|
|
1569
|
+
"/index.cjs"
|
|
1570
|
+
];
|
|
1571
|
+
function resolveRelativeSpecifier(importer, specifier) {
|
|
1572
|
+
const importerDir = path3.dirname(importer);
|
|
1573
|
+
const base = path3.resolve(importerDir, specifier);
|
|
1574
|
+
if (PROD_EXTS.some((ext) => specifier.endsWith(ext))) {
|
|
1575
|
+
return fs2.existsSync(base) ? base : null;
|
|
1570
1576
|
}
|
|
1571
|
-
if (
|
|
1572
|
-
|
|
1573
|
-
const inner = match ? match[1] : raw.slice(1);
|
|
1574
|
-
return expandEscapesAndVars(inner, env);
|
|
1577
|
+
if (/\.(ts|tsx|jsx)$/.test(specifier)) {
|
|
1578
|
+
return fs2.existsSync(base) ? base : null;
|
|
1575
1579
|
}
|
|
1576
|
-
const
|
|
1577
|
-
|
|
1578
|
-
|
|
1580
|
+
for (const ext of SOURCE_EXTS) {
|
|
1581
|
+
const file = base + ext;
|
|
1582
|
+
if (fs2.existsSync(file)) return file;
|
|
1583
|
+
}
|
|
1584
|
+
for (const indexExt of INDEX_EXTS) {
|
|
1585
|
+
const file = base + indexExt;
|
|
1586
|
+
if (fs2.existsSync(file)) return file;
|
|
1587
|
+
}
|
|
1588
|
+
return null;
|
|
1579
1589
|
}
|
|
1580
|
-
function
|
|
1581
|
-
const
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1590
|
+
function createAliasPlugin(config, options) {
|
|
1591
|
+
const SPEC_RE = /(\bfrom\s*|import\s*\(\s*)(['"])([^'"]+)\2/g;
|
|
1592
|
+
const appDirAbs = options?.rootDir ? toRealPath(path3.resolve(options.rootDir, APP_DIR)) : null;
|
|
1593
|
+
return {
|
|
1594
|
+
name: "faapi-alias",
|
|
1595
|
+
setup(build) {
|
|
1596
|
+
build.onLoad({ filter: /\.(ts|tsx|js|jsx|mjs|cjs)$/ }, (args) => {
|
|
1597
|
+
let source;
|
|
1598
|
+
try {
|
|
1599
|
+
source = fs2.readFileSync(args.path, "utf8");
|
|
1600
|
+
} catch {
|
|
1601
|
+
return void 0;
|
|
1602
|
+
}
|
|
1603
|
+
const importer = args.path;
|
|
1604
|
+
const importerOutsideAppDir = appDirAbs ? !isInsideDir(importer, appDirAbs) : false;
|
|
1605
|
+
let modified = false;
|
|
1606
|
+
const newSource = source.replace(SPEC_RE, (full, prefix, quote, specifier) => {
|
|
1607
|
+
if (specifier.startsWith("/") || specifier.startsWith("file:") || specifier.startsWith("node:")) {
|
|
1608
|
+
return full;
|
|
1609
|
+
}
|
|
1610
|
+
if (specifier.startsWith("./") || specifier.startsWith("../")) {
|
|
1611
|
+
const resolved = resolveRelativeSpecifier(importer, specifier);
|
|
1612
|
+
if (resolved) {
|
|
1613
|
+
if (PROD_EXTS.some((ext) => specifier.endsWith(ext))) {
|
|
1614
|
+
return full;
|
|
1615
|
+
}
|
|
1616
|
+
if (appDirAbs && importerOutsideAppDir && isInsideDir(resolved, appDirAbs)) {
|
|
1617
|
+
modified = true;
|
|
1618
|
+
return `${prefix}${quote}${toStrippedProdImportPath(
|
|
1619
|
+
resolved,
|
|
1620
|
+
options.rootDir
|
|
1621
|
+
)}${quote}`;
|
|
1622
|
+
}
|
|
1623
|
+
modified = true;
|
|
1624
|
+
return `${prefix}${quote}${toProdImportPath(resolved, importer)}${quote}`;
|
|
1625
|
+
}
|
|
1626
|
+
return full;
|
|
1627
|
+
}
|
|
1628
|
+
const candidates = resolveAlias(specifier, config);
|
|
1629
|
+
for (const candidate of candidates) {
|
|
1630
|
+
for (const ext of SOURCE_EXTS) {
|
|
1631
|
+
const file = candidate + ext;
|
|
1632
|
+
if (fs2.existsSync(file)) {
|
|
1633
|
+
modified = true;
|
|
1634
|
+
if (appDirAbs && importerOutsideAppDir && isInsideDir(file, appDirAbs)) {
|
|
1635
|
+
return `${prefix}${quote}${toStrippedProdImportPath(
|
|
1636
|
+
file,
|
|
1637
|
+
options.rootDir
|
|
1638
|
+
)}${quote}`;
|
|
1639
|
+
}
|
|
1640
|
+
return `${prefix}${quote}${toProdImportPath(file, importer)}${quote}`;
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
for (const indexExt of INDEX_EXTS) {
|
|
1644
|
+
const file = candidate + indexExt;
|
|
1645
|
+
if (fs2.existsSync(file)) {
|
|
1646
|
+
modified = true;
|
|
1647
|
+
if (appDirAbs && importerOutsideAppDir && isInsideDir(file, appDirAbs)) {
|
|
1648
|
+
return `${prefix}${quote}${toStrippedProdImportPath(
|
|
1649
|
+
file,
|
|
1650
|
+
options.rootDir
|
|
1651
|
+
)}${quote}`;
|
|
1652
|
+
}
|
|
1653
|
+
return `${prefix}${quote}${toProdImportPath(file, importer)}${quote}`;
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
return full;
|
|
1658
|
+
});
|
|
1659
|
+
if (!modified) return void 0;
|
|
1660
|
+
return { contents: newSource, loader: "default" };
|
|
1661
|
+
});
|
|
1602
1662
|
}
|
|
1603
|
-
|
|
1663
|
+
};
|
|
1604
1664
|
}
|
|
1605
|
-
function
|
|
1606
|
-
const
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1665
|
+
function buildAliasPlugins(rootDir) {
|
|
1666
|
+
const tsconfig = readTsconfig(rootDir);
|
|
1667
|
+
return [createAliasPlugin(tsconfig ?? { baseUrl: ".", paths: {} }, { rootDir })];
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1670
|
+
// src/cli/compileDevRoutes.ts
|
|
1671
|
+
var APP_DIR2 = "src";
|
|
1672
|
+
async function compileDevRoutes(options) {
|
|
1673
|
+
const { rootDir, dist, files, logLevel = "silent" } = options;
|
|
1674
|
+
const entryPoints = files ?? await fg([`${APP_DIR2}/**/*.ts`], {
|
|
1675
|
+
cwd: rootDir,
|
|
1676
|
+
onlyFiles: true,
|
|
1677
|
+
absolute: true,
|
|
1678
|
+
ignore: ["**/*.test.ts", "**/*.e2e.test.ts", "**/*.d.ts"]
|
|
1679
|
+
});
|
|
1680
|
+
if (entryPoints.length === 0) {
|
|
1681
|
+
return { compiledFiles: [] };
|
|
1615
1682
|
}
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1683
|
+
const absDist = path4.resolve(rootDir, dist);
|
|
1684
|
+
await fs3.promises.mkdir(absDist, { recursive: true });
|
|
1685
|
+
const plugins = buildAliasPlugins(rootDir);
|
|
1686
|
+
const esbuild = await import("esbuild");
|
|
1687
|
+
const outbase = path4.resolve(rootDir, APP_DIR2);
|
|
1688
|
+
const result = await esbuild.build({
|
|
1689
|
+
entryPoints,
|
|
1690
|
+
outdir: absDist,
|
|
1691
|
+
outbase,
|
|
1692
|
+
bundle: false,
|
|
1693
|
+
platform: "node",
|
|
1694
|
+
format: "esm",
|
|
1695
|
+
sourcemap: true,
|
|
1696
|
+
packages: "external",
|
|
1697
|
+
plugins,
|
|
1698
|
+
logLevel,
|
|
1699
|
+
write: false
|
|
1700
|
+
});
|
|
1701
|
+
if (result.outputFiles) {
|
|
1702
|
+
await Promise.all(
|
|
1703
|
+
result.outputFiles.map(async (file) => {
|
|
1704
|
+
await fs3.promises.mkdir(path4.dirname(file.path), { recursive: true });
|
|
1705
|
+
const tmp = `${file.path}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
|
|
1706
|
+
await fs3.promises.writeFile(tmp, file.contents);
|
|
1707
|
+
await fs3.promises.rename(tmp, file.path);
|
|
1708
|
+
})
|
|
1709
|
+
);
|
|
1620
1710
|
}
|
|
1711
|
+
return { compiledFiles: entryPoints };
|
|
1621
1712
|
}
|
|
1622
1713
|
|
|
1623
|
-
// src/
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1714
|
+
// src/cli/compileOnDemand.ts
|
|
1715
|
+
init_generateSchemaFiles();
|
|
1716
|
+
init_generateSchemaFiles();
|
|
1717
|
+
function isProductFresh(sourceAbsPath, productAbsPath) {
|
|
1718
|
+
try {
|
|
1719
|
+
const srcStat = fs5.statSync(sourceAbsPath);
|
|
1720
|
+
const prodStat = fs5.statSync(productAbsPath);
|
|
1721
|
+
return prodStat.mtimeMs >= srcStat.mtimeMs;
|
|
1722
|
+
} catch {
|
|
1723
|
+
return false;
|
|
1630
1724
|
}
|
|
1631
|
-
code;
|
|
1632
|
-
statusCode;
|
|
1633
|
-
};
|
|
1634
|
-
|
|
1635
|
-
// src/errors/httpErrors.ts
|
|
1636
|
-
function deriveStatusCode(issues) {
|
|
1637
|
-
const has400 = issues.some((i) => i.code === "INVALID_FORMAT" || i.code === "MISSING_FIELD");
|
|
1638
|
-
return has400 ? 400 : 422;
|
|
1639
1725
|
}
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1726
|
+
function createDevOnDemandState() {
|
|
1727
|
+
return {
|
|
1728
|
+
enabled: false,
|
|
1729
|
+
distDir: void 0,
|
|
1730
|
+
compiledFiles: /* @__PURE__ */ new Set(),
|
|
1731
|
+
generatedSchemas: /* @__PURE__ */ new Set(),
|
|
1732
|
+
inFlightCompilations: /* @__PURE__ */ new Map(),
|
|
1733
|
+
inFlightSchemaGenerations: /* @__PURE__ */ new Map()
|
|
1734
|
+
};
|
|
1735
|
+
}
|
|
1736
|
+
var state = createDevOnDemandState();
|
|
1737
|
+
function clearCompiledFiles() {
|
|
1738
|
+
state.compiledFiles.clear();
|
|
1739
|
+
state.inFlightCompilations.clear();
|
|
1740
|
+
}
|
|
1741
|
+
async function ensureCompiled(sourceAbsPath, rootDir, dist) {
|
|
1742
|
+
const inFlight = state.inFlightCompilations.get(sourceAbsPath);
|
|
1743
|
+
if (inFlight) {
|
|
1744
|
+
await inFlight.catch(() => {
|
|
1745
|
+
});
|
|
1746
|
+
return false;
|
|
1645
1747
|
}
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
var RouteNotFoundError = class extends FaapiError {
|
|
1649
|
-
constructor(path14) {
|
|
1650
|
-
super("ROUTE_NOT_FOUND", `Route not found: ${path14}`, 404);
|
|
1651
|
-
this.name = "RouteNotFoundError";
|
|
1748
|
+
if (state.compiledFiles.has(sourceAbsPath)) {
|
|
1749
|
+
return false;
|
|
1652
1750
|
}
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
constructor(method, path14, allowedMethods) {
|
|
1656
|
-
super("METHOD_NOT_ALLOWED", `Method ${method} not allowed for ${path14}`, 405);
|
|
1657
|
-
this.allowedMethods = allowedMethods;
|
|
1658
|
-
this.name = "MethodNotAllowedError";
|
|
1751
|
+
if (!fs5.existsSync(sourceAbsPath)) {
|
|
1752
|
+
return false;
|
|
1659
1753
|
}
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
super("INTERNAL_ERROR", message, 500);
|
|
1665
|
-
this.name = "InternalError";
|
|
1754
|
+
const productPath = prodSourcePathToProductPath(sourceAbsPath, rootDir, dist);
|
|
1755
|
+
if (productPath && isProductFresh(sourceAbsPath, productPath)) {
|
|
1756
|
+
state.compiledFiles.add(sourceAbsPath);
|
|
1757
|
+
return false;
|
|
1666
1758
|
}
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1759
|
+
const compilePromise = (async () => {
|
|
1760
|
+
await compileDevRoutes({
|
|
1761
|
+
rootDir,
|
|
1762
|
+
dist,
|
|
1763
|
+
files: [sourceAbsPath],
|
|
1764
|
+
logLevel: "silent"
|
|
1765
|
+
});
|
|
1766
|
+
state.compiledFiles.add(sourceAbsPath);
|
|
1767
|
+
})();
|
|
1768
|
+
state.inFlightCompilations.set(sourceAbsPath, compilePromise);
|
|
1769
|
+
try {
|
|
1770
|
+
await compilePromise;
|
|
1771
|
+
return true;
|
|
1772
|
+
} finally {
|
|
1773
|
+
state.inFlightCompilations.delete(sourceAbsPath);
|
|
1672
1774
|
}
|
|
1673
|
-
};
|
|
1674
|
-
|
|
1675
|
-
// src/cli/createAppCore.ts
|
|
1676
|
-
import fs11 from "fs";
|
|
1677
|
-
import path12 from "path";
|
|
1678
|
-
import { PassThrough, Readable as Readable3 } from "stream";
|
|
1679
|
-
|
|
1680
|
-
// src/router/sortRoutes.ts
|
|
1681
|
-
function sortRoutes(routes) {
|
|
1682
|
-
return [...routes].sort((a, b) => {
|
|
1683
|
-
if (a.isDynamic !== b.isDynamic) {
|
|
1684
|
-
return a.isDynamic ? 1 : -1;
|
|
1685
|
-
}
|
|
1686
|
-
if (a.isCatchAll !== b.isCatchAll) {
|
|
1687
|
-
return a.isCatchAll ? 1 : -1;
|
|
1688
|
-
}
|
|
1689
|
-
const aSegments = a.urlPath.split("/").filter(Boolean).length;
|
|
1690
|
-
const bSegments = b.urlPath.split("/").filter(Boolean).length;
|
|
1691
|
-
if (aSegments !== bSegments) {
|
|
1692
|
-
return aSegments - bSegments;
|
|
1693
|
-
}
|
|
1694
|
-
return a.urlPath.localeCompare(b.urlPath);
|
|
1695
|
-
});
|
|
1696
1775
|
}
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
const
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
}
|
|
1776
|
+
function prodSourcePathToProductPath(sourceAbsPath, rootDir, dist) {
|
|
1777
|
+
const rel = path6.relative(rootDir, sourceAbsPath).replace(/\\/g, "/");
|
|
1778
|
+
if (!rel.startsWith("src/")) return null;
|
|
1779
|
+
const relWithoutSrc = rel.slice(4);
|
|
1780
|
+
const jsRel = relWithoutSrc.replace(/\.ts$/, ".js");
|
|
1781
|
+
return path6.resolve(rootDir, dist, jsRel);
|
|
1782
|
+
}
|
|
1783
|
+
function clearGeneratedSchemas() {
|
|
1784
|
+
state.generatedSchemas.clear();
|
|
1785
|
+
state.inFlightSchemaGenerations.clear();
|
|
1786
|
+
}
|
|
1787
|
+
async function ensureSchemaGenerated(schemaPath, routeFilePath, routes, rootDir, dist) {
|
|
1788
|
+
const inFlight = state.inFlightSchemaGenerations.get(schemaPath);
|
|
1789
|
+
if (inFlight) {
|
|
1790
|
+
await inFlight.catch(() => {
|
|
1791
|
+
});
|
|
1792
|
+
return false;
|
|
1713
1793
|
}
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1794
|
+
if (state.generatedSchemas.has(schemaPath)) {
|
|
1795
|
+
return false;
|
|
1796
|
+
}
|
|
1797
|
+
const prodAbsPath = path6.resolve(rootDir, routeFilePath);
|
|
1798
|
+
const sourceAbsPath = prodPathToSourcePath(prodAbsPath, rootDir, dist);
|
|
1799
|
+
if (!fs5.existsSync(sourceAbsPath)) {
|
|
1800
|
+
return false;
|
|
1801
|
+
}
|
|
1802
|
+
if (isProductFresh(sourceAbsPath, schemaPath)) {
|
|
1803
|
+
state.generatedSchemas.add(schemaPath);
|
|
1804
|
+
return false;
|
|
1805
|
+
}
|
|
1806
|
+
const fileRoutes = routes.filter((r) => r.filePath === routeFilePath);
|
|
1807
|
+
if (fileRoutes.length === 0) {
|
|
1808
|
+
return false;
|
|
1809
|
+
}
|
|
1810
|
+
const sourceRelPath = path6.relative(rootDir, sourceAbsPath).replace(/\\/g, "/");
|
|
1811
|
+
const sourceRoutes = fileRoutes.map((r) => ({ ...r, filePath: sourceRelPath }));
|
|
1812
|
+
const generatePromise = (async () => {
|
|
1813
|
+
await generateSchemaFiles(sourceRoutes, rootDir, dist);
|
|
1814
|
+
state.generatedSchemas.add(schemaPath);
|
|
1815
|
+
})();
|
|
1816
|
+
state.inFlightSchemaGenerations.set(schemaPath, generatePromise);
|
|
1817
|
+
try {
|
|
1818
|
+
await generatePromise;
|
|
1819
|
+
return true;
|
|
1820
|
+
} finally {
|
|
1821
|
+
state.inFlightSchemaGenerations.delete(schemaPath);
|
|
1719
1822
|
}
|
|
1720
|
-
return conflicts;
|
|
1721
1823
|
}
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
import {
|
|
1725
|
-
createServer as createHttpServer
|
|
1726
|
-
} from "http";
|
|
1727
|
-
import { createSecureServer as createHttp2SecureServer } from "http2";
|
|
1728
|
-
import { readFileSync } from "fs";
|
|
1729
|
-
import { Readable as Readable2 } from "stream";
|
|
1730
|
-
import path10 from "path";
|
|
1731
|
-
|
|
1732
|
-
// src/router/matchRoute.ts
|
|
1733
|
-
function matchRoute(routes, method, path14) {
|
|
1824
|
+
async function deleteSchemaFiles(routes, rootDir, dist) {
|
|
1825
|
+
const deleted = /* @__PURE__ */ new Set();
|
|
1734
1826
|
for (const route of routes) {
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
}
|
|
1742
|
-
continue;
|
|
1743
|
-
}
|
|
1744
|
-
const params = matchDynamicPath(route.urlPath, path14, route.paramNames, route.isCatchAll);
|
|
1745
|
-
if (params !== null) {
|
|
1746
|
-
return { route, params };
|
|
1827
|
+
const schemaPath = getRuntimeSchemaPath(route.filePath, dist, rootDir);
|
|
1828
|
+
if (deleted.has(schemaPath)) continue;
|
|
1829
|
+
deleted.add(schemaPath);
|
|
1830
|
+
try {
|
|
1831
|
+
await fs5.promises.unlink(schemaPath);
|
|
1832
|
+
} catch {
|
|
1747
1833
|
}
|
|
1748
1834
|
}
|
|
1749
|
-
return null;
|
|
1750
1835
|
}
|
|
1751
|
-
function
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1836
|
+
function prodPathToSourcePath(prodAbsPath, rootDir, dist) {
|
|
1837
|
+
const rel = path6.relative(rootDir, prodAbsPath).replace(/\\/g, "/");
|
|
1838
|
+
let relWithoutDist = rel;
|
|
1839
|
+
if (relWithoutDist.startsWith(`${dist}/`)) {
|
|
1840
|
+
relWithoutDist = relWithoutDist.slice(dist.length + 1);
|
|
1841
|
+
}
|
|
1842
|
+
const srcRel = `src/${relWithoutDist}`;
|
|
1843
|
+
const tsRel = srcRel.replace(/\.js$/, ".ts");
|
|
1844
|
+
const tsAbs = path6.resolve(rootDir, tsRel);
|
|
1845
|
+
if (fs5.existsSync(tsAbs)) return tsAbs;
|
|
1846
|
+
return path6.resolve(rootDir, srcRel);
|
|
1847
|
+
}
|
|
1848
|
+
function isDevOnDemandEnabled() {
|
|
1849
|
+
return state.enabled;
|
|
1850
|
+
}
|
|
1851
|
+
function getDevDist() {
|
|
1852
|
+
return state.distDir;
|
|
1853
|
+
}
|
|
1854
|
+
|
|
1855
|
+
// src/loader/loadAgentModule.ts
|
|
1856
|
+
async function loadAgentModule(filePath, hasRun, rootDir) {
|
|
1857
|
+
if (isDevOnDemandEnabled() && rootDir) {
|
|
1858
|
+
const dist = getDevDist();
|
|
1859
|
+
if (dist) {
|
|
1860
|
+
const sourcePath = prodPathToSourcePath(filePath, rootDir, dist);
|
|
1861
|
+
if (sourcePath && fs6.existsSync(sourcePath)) {
|
|
1862
|
+
try {
|
|
1863
|
+
await ensureCompiled(sourcePath, rootDir, dist);
|
|
1864
|
+
} catch (compileErr) {
|
|
1865
|
+
const reason = compileErr instanceof Error ? compileErr.message : String(compileErr);
|
|
1866
|
+
throw new Error(`Failed to compile agent module "${sourcePath}": ${reason}`, {
|
|
1867
|
+
cause: compileErr
|
|
1868
|
+
});
|
|
1869
|
+
}
|
|
1756
1870
|
}
|
|
1757
|
-
continue;
|
|
1758
1871
|
}
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1872
|
+
}
|
|
1873
|
+
let module;
|
|
1874
|
+
try {
|
|
1875
|
+
module = await importWithCacheBust(filePath, isDevOnDemandEnabled());
|
|
1876
|
+
} catch (err) {
|
|
1877
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
1878
|
+
throw new Error(`Failed to load agent module "${filePath}": ${reason}`, { cause: err });
|
|
1879
|
+
}
|
|
1880
|
+
let run;
|
|
1881
|
+
if (hasRun) {
|
|
1882
|
+
const runExport = resolveExport(module, "run");
|
|
1883
|
+
if (typeof runExport !== "function") {
|
|
1884
|
+
throw new Error(
|
|
1885
|
+
`Agent module "${filePath}" does not export a valid "run" function (hasRun=true). Expected a function, got ${runExport === void 0 ? "undefined" : typeof runExport}.`
|
|
1886
|
+
);
|
|
1762
1887
|
}
|
|
1888
|
+
run = runExport;
|
|
1763
1889
|
}
|
|
1764
|
-
return
|
|
1890
|
+
return { run };
|
|
1765
1891
|
}
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1892
|
+
|
|
1893
|
+
// src/loader/loadToolModule.ts
|
|
1894
|
+
import fs7 from "fs";
|
|
1895
|
+
async function loadToolModule(filePath, functionName, rootDir) {
|
|
1896
|
+
if (isDevOnDemandEnabled() && rootDir) {
|
|
1897
|
+
const dist = getDevDist();
|
|
1898
|
+
if (dist) {
|
|
1899
|
+
const sourcePath = prodPathToSourcePath(filePath, rootDir, dist);
|
|
1900
|
+
if (sourcePath && fs7.existsSync(sourcePath)) {
|
|
1901
|
+
try {
|
|
1902
|
+
await ensureCompiled(sourcePath, rootDir, dist);
|
|
1903
|
+
} catch (compileErr) {
|
|
1904
|
+
const reason = compileErr instanceof Error ? compileErr.message : String(compileErr);
|
|
1905
|
+
throw new Error(`Failed to compile tool module "${sourcePath}": ${reason}`, {
|
|
1906
|
+
cause: compileErr
|
|
1907
|
+
});
|
|
1908
|
+
}
|
|
1783
1909
|
}
|
|
1784
1910
|
}
|
|
1785
|
-
const catchAllValue = pathSegments.slice(nonCatchAllCount).join("/");
|
|
1786
|
-
const catchAllParamName = patternSegments[nonCatchAllCount].slice(4);
|
|
1787
|
-
params2[catchAllParamName] = catchAllValue;
|
|
1788
|
-
if (Object.keys(params2).length !== paramNames.length) {
|
|
1789
|
-
return null;
|
|
1790
|
-
}
|
|
1791
|
-
return params2;
|
|
1792
|
-
}
|
|
1793
|
-
if (patternSegments.length !== pathSegments.length) {
|
|
1794
|
-
return null;
|
|
1795
1911
|
}
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
params[paramName] = pathSeg;
|
|
1803
|
-
} else if (patternSeg !== pathSeg) {
|
|
1804
|
-
return null;
|
|
1805
|
-
}
|
|
1912
|
+
let module;
|
|
1913
|
+
try {
|
|
1914
|
+
module = await importWithCacheBust(filePath, isDevOnDemandEnabled());
|
|
1915
|
+
} catch (err) {
|
|
1916
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
1917
|
+
throw new Error(`Failed to load tool module "${filePath}": ${reason}`, { cause: err });
|
|
1806
1918
|
}
|
|
1807
|
-
|
|
1808
|
-
|
|
1919
|
+
const handler = resolveExport(module, functionName);
|
|
1920
|
+
if (typeof handler !== "function") {
|
|
1921
|
+
throw new Error(
|
|
1922
|
+
`Tool module "${filePath}" does not export a valid function for "${functionName}". Expected a function, got ${handler === void 0 ? "undefined" : typeof handler}.`
|
|
1923
|
+
);
|
|
1809
1924
|
}
|
|
1810
|
-
return
|
|
1925
|
+
return { handler, functionName };
|
|
1811
1926
|
}
|
|
1812
1927
|
|
|
1813
|
-
// src/loader/
|
|
1814
|
-
import
|
|
1928
|
+
// src/loader/loadToolSchema.ts
|
|
1929
|
+
import { existsSync as existsSync2 } from "fs";
|
|
1815
1930
|
|
|
1816
|
-
// src/
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1931
|
+
// src/cli/generateToolArtifacts.ts
|
|
1932
|
+
import path7 from "path";
|
|
1933
|
+
import fs8 from "fs/promises";
|
|
1934
|
+
import { existsSync } from "fs";
|
|
1935
|
+
|
|
1936
|
+
// src/ast/extractToolMetadata.ts
|
|
1937
|
+
import ts7 from "typescript";
|
|
1938
|
+
function extractToolMetadata(program, filePath, functionName, pathMeta) {
|
|
1939
|
+
const sourceFile = program.getSourceFile(filePath);
|
|
1940
|
+
if (!sourceFile) return null;
|
|
1941
|
+
const found = findExportedFunction(sourceFile, functionName);
|
|
1942
|
+
if (!found) return null;
|
|
1943
|
+
const { fn, jsDocOwner } = found;
|
|
1944
|
+
const jsDoc = getJSDocFromNode(jsDocOwner);
|
|
1945
|
+
const description = extractDescription(jsDoc);
|
|
1946
|
+
const toolNameOverride = extractToolTagValue(jsDoc);
|
|
1947
|
+
const inputTypeName = getFirstParamTypeName(fn, sourceFile);
|
|
1948
|
+
return {
|
|
1949
|
+
name: toolNameOverride ?? pathMeta.name,
|
|
1950
|
+
description,
|
|
1951
|
+
inputTypeName,
|
|
1952
|
+
filePath: pathMeta.filePath,
|
|
1953
|
+
functionName
|
|
1954
|
+
};
|
|
1955
|
+
}
|
|
1956
|
+
function findExportedFunction(sourceFile, functionName) {
|
|
1957
|
+
let result = null;
|
|
1958
|
+
ts7.forEachChild(sourceFile, (node) => {
|
|
1959
|
+
if (result) return;
|
|
1960
|
+
if (ts7.isFunctionDeclaration(node) && hasExportModifier(node) && node.name?.text === functionName) {
|
|
1961
|
+
result = { fn: node, jsDocOwner: node };
|
|
1962
|
+
return;
|
|
1826
1963
|
}
|
|
1827
|
-
|
|
1964
|
+
if (ts7.isVariableStatement(node) && hasExportModifier(node)) {
|
|
1965
|
+
for (const decl of node.declarationList.declarations) {
|
|
1966
|
+
if (result) break;
|
|
1967
|
+
const nameText = ts7.isIdentifier(decl.name) ? decl.name.text : decl.name.getText(sourceFile);
|
|
1968
|
+
if (nameText !== functionName || !decl.initializer) continue;
|
|
1969
|
+
if (ts7.isArrowFunction(decl.initializer) || ts7.isFunctionExpression(decl.initializer)) {
|
|
1970
|
+
result = { fn: decl.initializer, jsDocOwner: node };
|
|
1971
|
+
}
|
|
1972
|
+
}
|
|
1973
|
+
}
|
|
1974
|
+
});
|
|
1975
|
+
return result;
|
|
1976
|
+
}
|
|
1977
|
+
function hasExportModifier(node) {
|
|
1978
|
+
if (!ts7.canHaveModifiers(node)) return false;
|
|
1979
|
+
const modifiers = ts7.getModifiers(node);
|
|
1980
|
+
return !!modifiers?.some((m) => m.kind === ts7.SyntaxKind.ExportKeyword);
|
|
1981
|
+
}
|
|
1982
|
+
function getJSDocFromNode(node) {
|
|
1983
|
+
const apiDocs = ts7.getJSDocCommentsAndTags(node).filter((entry) => ts7.isJSDoc(entry));
|
|
1984
|
+
if (apiDocs.length > 0) return apiDocs[0];
|
|
1985
|
+
const directDocs = node.jsDoc;
|
|
1986
|
+
if (directDocs && directDocs.length > 0) return directDocs[0];
|
|
1828
1987
|
return void 0;
|
|
1829
1988
|
}
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1989
|
+
function extractDescription(jsDoc) {
|
|
1990
|
+
if (!jsDoc) return void 0;
|
|
1991
|
+
if (typeof jsDoc.comment !== "string") return void 0;
|
|
1992
|
+
const trimmed = jsDoc.comment.trim();
|
|
1993
|
+
return trimmed || void 0;
|
|
1994
|
+
}
|
|
1995
|
+
function extractToolTagValue(jsDoc) {
|
|
1996
|
+
if (!jsDoc || !jsDoc.tags) return void 0;
|
|
1997
|
+
for (const tag of jsDoc.tags) {
|
|
1998
|
+
if (tag.tagName.text !== "tool") continue;
|
|
1999
|
+
if (typeof tag.comment !== "string") return void 0;
|
|
2000
|
+
const text = tag.comment.trim();
|
|
2001
|
+
if (!text) return void 0;
|
|
2002
|
+
const cleaned = text.replace(/^\{|\}$/g, "").trim();
|
|
2003
|
+
return cleaned || void 0;
|
|
1837
2004
|
}
|
|
2005
|
+
return void 0;
|
|
2006
|
+
}
|
|
2007
|
+
function getFirstParamTypeName(fn, sourceFile) {
|
|
2008
|
+
const firstParam = fn.parameters[0];
|
|
2009
|
+
if (!firstParam) return void 0;
|
|
2010
|
+
if (!firstParam.type) return void 0;
|
|
2011
|
+
if (!ts7.isTypeReferenceNode(firstParam.type)) return void 0;
|
|
2012
|
+
return firstParam.type.typeName.getText(sourceFile);
|
|
1838
2013
|
}
|
|
1839
2014
|
|
|
1840
|
-
// src/cli/
|
|
1841
|
-
|
|
1842
|
-
|
|
2015
|
+
// src/cli/generateToolArtifacts.ts
|
|
2016
|
+
init_createProgram();
|
|
2017
|
+
init_extractHandlerTypes();
|
|
2018
|
+
init_generateZodSchema();
|
|
2019
|
+
init_generateSchemaFiles();
|
|
2020
|
+
var TOOLS_FILE = "faapi-tools.js";
|
|
2021
|
+
function getToolSchemaOutputPath(sourceFile, dist, rootDir) {
|
|
2022
|
+
let rel = sourceFile.replace(/\\/g, "/");
|
|
2023
|
+
if (rel.startsWith("src/")) {
|
|
2024
|
+
rel = rel.slice(4);
|
|
2025
|
+
}
|
|
2026
|
+
const idx = rel.lastIndexOf("/");
|
|
2027
|
+
const relDir = idx >= 0 ? rel.slice(0, idx) : "";
|
|
2028
|
+
return path7.resolve(rootDir, dist, relDir, "zod.js");
|
|
2029
|
+
}
|
|
2030
|
+
function getRuntimeToolSchemaPath(filePath, dist, rootDir) {
|
|
2031
|
+
let rel = filePath.replace(/\\/g, "/");
|
|
2032
|
+
if (rel.startsWith("src/")) {
|
|
2033
|
+
rel = rel.slice(4);
|
|
2034
|
+
} else if (rel.startsWith(`${dist}/`)) {
|
|
2035
|
+
rel = rel.slice(dist.length + 1);
|
|
2036
|
+
}
|
|
2037
|
+
const idx = rel.lastIndexOf("/");
|
|
2038
|
+
const relDir = idx >= 0 ? rel.slice(0, idx) : "";
|
|
2039
|
+
return path7.resolve(rootDir, dist, relDir, "zod.js");
|
|
2040
|
+
}
|
|
2041
|
+
function toProdFilePath(filePath, dist) {
|
|
2042
|
+
let rel = filePath.replace(/\\/g, "/");
|
|
2043
|
+
if (rel.startsWith("src/")) {
|
|
2044
|
+
rel = rel.slice(4);
|
|
2045
|
+
}
|
|
2046
|
+
const jsPath = rel.replace(/\.ts$/, ".js");
|
|
2047
|
+
return jsPath.startsWith(`${dist}/`) ? jsPath : `${dist}/${jsPath}`;
|
|
2048
|
+
}
|
|
2049
|
+
function serializeTools(tools, dist = "dist") {
|
|
2050
|
+
return tools.map((t) => ({
|
|
2051
|
+
name: t.name,
|
|
2052
|
+
functionName: t.functionName,
|
|
2053
|
+
description: t.description,
|
|
2054
|
+
inputTypeName: t.inputTypeName,
|
|
2055
|
+
filePath: toProdFilePath(t.filePath, dist)
|
|
2056
|
+
}));
|
|
2057
|
+
}
|
|
2058
|
+
async function writeToolsModule(manifest, outputPath) {
|
|
2059
|
+
const dir = path7.dirname(outputPath);
|
|
2060
|
+
await fs8.mkdir(dir, { recursive: true });
|
|
2061
|
+
const content = `// \u81EA\u52A8\u751F\u6210,\u8BF7\u52FF\u624B\u52A8\u7F16\u8F91(faapi build/dev \u4EA7\u7269)
|
|
2062
|
+
export const tools = ${JSON.stringify(manifest, null, 2)};
|
|
2063
|
+
`;
|
|
2064
|
+
await fs8.writeFile(outputPath, content, "utf-8");
|
|
2065
|
+
}
|
|
2066
|
+
function hydrateTools(manifest) {
|
|
2067
|
+
return manifest.map((t) => ({
|
|
2068
|
+
name: t.name,
|
|
2069
|
+
functionName: t.functionName,
|
|
2070
|
+
description: t.description ?? void 0,
|
|
2071
|
+
inputTypeName: t.inputTypeName ?? void 0,
|
|
2072
|
+
filePath: t.filePath
|
|
2073
|
+
}));
|
|
2074
|
+
}
|
|
2075
|
+
function collectToolSchemaSources(tools, rootDir) {
|
|
2076
|
+
const toolsByFile = /* @__PURE__ */ new Map();
|
|
2077
|
+
for (const tool of tools) {
|
|
2078
|
+
if (!tool.inputTypeName) continue;
|
|
2079
|
+
const absPath = path7.resolve(rootDir, tool.filePath);
|
|
2080
|
+
let list = toolsByFile.get(absPath);
|
|
2081
|
+
if (!list) {
|
|
2082
|
+
list = [];
|
|
2083
|
+
toolsByFile.set(absPath, list);
|
|
2084
|
+
}
|
|
2085
|
+
list.push(tool);
|
|
2086
|
+
}
|
|
2087
|
+
const allTypesByFile = /* @__PURE__ */ new Map();
|
|
2088
|
+
for (const filePath of toolsByFile.keys()) {
|
|
2089
|
+
const program = createProgram(filePath);
|
|
2090
|
+
const allTypes = extractAllTypes(program, filePath);
|
|
2091
|
+
allTypesByFile.set(filePath, allTypes);
|
|
2092
|
+
}
|
|
2093
|
+
const sources = [];
|
|
2094
|
+
for (const [filePath, fileTools] of toolsByFile) {
|
|
2095
|
+
const program = createProgram(filePath);
|
|
2096
|
+
for (const tool of fileTools) {
|
|
2097
|
+
const inputTypeName = tool.inputTypeName;
|
|
2098
|
+
const typeInfo = extractTypeInfo(program, filePath, inputTypeName);
|
|
2099
|
+
sources.push({
|
|
2100
|
+
name: tool.name,
|
|
2101
|
+
filePath,
|
|
2102
|
+
schemaName: inputTypeName,
|
|
2103
|
+
// schema 名 = inputTypeName
|
|
2104
|
+
typeInfo
|
|
2105
|
+
});
|
|
2106
|
+
}
|
|
2107
|
+
}
|
|
2108
|
+
return { sources, allTypesByFile };
|
|
2109
|
+
}
|
|
2110
|
+
function generateToolSchemaFileSource(sources, allTypes, helpersImportPath) {
|
|
2111
|
+
const resolveType = (name) => allTypes.get(name)?.runtimeType;
|
|
2112
|
+
const lines = ["import { z } from 'zod';"];
|
|
2113
|
+
const schemaBlocks = [];
|
|
2114
|
+
for (const source of sources) {
|
|
2115
|
+
const { schemaName, typeInfo } = source;
|
|
2116
|
+
if (!typeInfo) {
|
|
2117
|
+
continue;
|
|
2118
|
+
}
|
|
2119
|
+
const coerce = false;
|
|
2120
|
+
const block = [`// ${source.name} \u2192 ${schemaName}`];
|
|
2121
|
+
const schemaCode = generateZodSchemaSource(typeInfo, resolveType, schemaName, coerce).replace(
|
|
2122
|
+
/^import \{ z \} from 'zod';\s*\n\s*\n/,
|
|
2123
|
+
""
|
|
2124
|
+
);
|
|
2125
|
+
block.push(schemaCode);
|
|
2126
|
+
block.push("");
|
|
2127
|
+
schemaBlocks.push(block.join("\n"));
|
|
2128
|
+
}
|
|
2129
|
+
const allSchemaCode = schemaBlocks.join("\n");
|
|
2130
|
+
if (helpersImportPath && usesCoerceHelpers(allSchemaCode)) {
|
|
2131
|
+
lines.push(
|
|
2132
|
+
`import { coerceNumber, coerceBoolean, coerceMap, coerceSet } from '${helpersImportPath}';`
|
|
2133
|
+
);
|
|
2134
|
+
}
|
|
2135
|
+
lines.push("");
|
|
2136
|
+
lines.push(...schemaBlocks);
|
|
2137
|
+
return lines.join("\n").replace(/\n+$/, "\n");
|
|
2138
|
+
}
|
|
2139
|
+
async function maybeGenerateHelpers(allSourceCode, distDir) {
|
|
2140
|
+
if (!usesCoerceHelpers(allSourceCode)) return;
|
|
2141
|
+
const helpersPath = path7.resolve(distDir, HELPERS_FILENAME);
|
|
2142
|
+
if (existsSync(helpersPath)) return;
|
|
2143
|
+
await fs8.mkdir(path7.dirname(helpersPath), { recursive: true });
|
|
2144
|
+
await fs8.writeFile(helpersPath, generateHelpersFileSource(), "utf-8");
|
|
2145
|
+
}
|
|
2146
|
+
async function generateToolArtifacts(tools, rootDir, dist, options) {
|
|
2147
|
+
const metadata = [];
|
|
2148
|
+
for (const manifest of tools) {
|
|
2149
|
+
const absPath = path7.resolve(rootDir, manifest.filePath);
|
|
2150
|
+
const program = createProgram(absPath);
|
|
2151
|
+
const result = extractToolMetadata(program, absPath, manifest.functionName, {
|
|
2152
|
+
name: manifest.name,
|
|
2153
|
+
filePath: manifest.filePath
|
|
2154
|
+
});
|
|
2155
|
+
if (result) {
|
|
2156
|
+
metadata.push(result);
|
|
2157
|
+
}
|
|
2158
|
+
}
|
|
2159
|
+
const serialized = serializeTools(metadata, dist);
|
|
2160
|
+
const toolsPath = path7.resolve(rootDir, dist, TOOLS_FILE);
|
|
2161
|
+
await writeToolsModule(serialized, toolsPath);
|
|
2162
|
+
if (options?.skipSchema) {
|
|
2163
|
+
return metadata;
|
|
2164
|
+
}
|
|
2165
|
+
if (metadata.length === 0) {
|
|
2166
|
+
return metadata;
|
|
2167
|
+
}
|
|
2168
|
+
const { sources, allTypesByFile } = collectToolSchemaSources(metadata, rootDir);
|
|
2169
|
+
if (sources.length === 0) {
|
|
2170
|
+
return metadata;
|
|
2171
|
+
}
|
|
2172
|
+
const sourcesByFile = /* @__PURE__ */ new Map();
|
|
2173
|
+
for (const source of sources) {
|
|
2174
|
+
let list = sourcesByFile.get(source.filePath);
|
|
2175
|
+
if (!list) {
|
|
2176
|
+
list = [];
|
|
2177
|
+
sourcesByFile.set(source.filePath, list);
|
|
2178
|
+
}
|
|
2179
|
+
list.push(source);
|
|
2180
|
+
}
|
|
2181
|
+
const fileEntries = [];
|
|
2182
|
+
for (const [filePath, fileSources] of sourcesByFile) {
|
|
2183
|
+
const relFile = path7.relative(rootDir, filePath).replace(/\\/g, "/");
|
|
2184
|
+
const outputPath = getToolSchemaOutputPath(relFile, dist, rootDir);
|
|
2185
|
+
const allTypes = allTypesByFile.get(filePath) ?? /* @__PURE__ */ new Map();
|
|
2186
|
+
let relForDir = relFile;
|
|
2187
|
+
if (relForDir.startsWith("src/")) {
|
|
2188
|
+
relForDir = relForDir.slice(4);
|
|
2189
|
+
}
|
|
2190
|
+
const dirIdx = relForDir.lastIndexOf("/");
|
|
2191
|
+
const zodRelDir = dirIdx >= 0 ? relForDir.slice(0, dirIdx) : "";
|
|
2192
|
+
const helpersImportPath = getHelpersImportPath(zodRelDir);
|
|
2193
|
+
const source = generateToolSchemaFileSource(fileSources, allTypes, helpersImportPath);
|
|
2194
|
+
fileEntries.push({ outputPath, source });
|
|
2195
|
+
}
|
|
2196
|
+
const allSourceCode = fileEntries.map((e) => e.source).join("\n");
|
|
2197
|
+
const distDir = path7.resolve(rootDir, dist);
|
|
2198
|
+
await maybeGenerateHelpers(allSourceCode, distDir);
|
|
2199
|
+
await Promise.all(
|
|
2200
|
+
fileEntries.map(({ outputPath, source }) => writeToolSchemaFile(outputPath, source))
|
|
2201
|
+
);
|
|
2202
|
+
return metadata;
|
|
2203
|
+
}
|
|
2204
|
+
async function writeToolSchemaFile(outputPath, source) {
|
|
2205
|
+
await fs8.mkdir(path7.dirname(outputPath), { recursive: true });
|
|
2206
|
+
await fs8.writeFile(outputPath, source, "utf-8");
|
|
2207
|
+
}
|
|
1843
2208
|
|
|
1844
|
-
// src/
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
2209
|
+
// src/loader/loadToolSchema.ts
|
|
2210
|
+
function getDist() {
|
|
2211
|
+
if (isDevOnDemandEnabled()) {
|
|
2212
|
+
return getDevDist() ?? ".faapi";
|
|
2213
|
+
}
|
|
2214
|
+
return process.env.FAAPI_DIST ?? "dist";
|
|
2215
|
+
}
|
|
2216
|
+
async function loadToolSchema(tool, rootDir) {
|
|
2217
|
+
if (!tool.inputTypeName) return void 0;
|
|
2218
|
+
const schemaName = `${tool.inputTypeName}Schema`;
|
|
2219
|
+
const dist = getDist();
|
|
2220
|
+
const zodPath = getRuntimeToolSchemaPath(tool.filePath, dist, rootDir ?? process.cwd());
|
|
2221
|
+
if (!existsSync2(zodPath)) return void 0;
|
|
2222
|
+
try {
|
|
2223
|
+
const mod = await importWithCacheBust(zodPath, isDevOnDemandEnabled());
|
|
2224
|
+
const schema = mod[`${tool.inputTypeName}Schema`];
|
|
2225
|
+
if (!schema) return void 0;
|
|
2226
|
+
return { schema, schemaName };
|
|
2227
|
+
} catch {
|
|
2228
|
+
return void 0;
|
|
2229
|
+
}
|
|
2230
|
+
}
|
|
1848
2231
|
|
|
1849
|
-
// src/
|
|
1850
|
-
|
|
1851
|
-
|
|
2232
|
+
// src/injection/agentHandle.ts
|
|
2233
|
+
var currentFactory = null;
|
|
2234
|
+
function registerAgentHandleFactory(factory) {
|
|
2235
|
+
currentFactory = factory;
|
|
2236
|
+
}
|
|
2237
|
+
function getAgentHandle(ctx) {
|
|
2238
|
+
if (currentFactory === null) return void 0;
|
|
2239
|
+
return currentFactory(ctx);
|
|
2240
|
+
}
|
|
2241
|
+
function clearAgentHandleFactory() {
|
|
2242
|
+
currentFactory = null;
|
|
2243
|
+
}
|
|
1852
2244
|
|
|
1853
|
-
// src/
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
2245
|
+
// src/middleware/cors.ts
|
|
2246
|
+
var DEFAULT_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
|
|
2247
|
+
function cors(options = {}) {
|
|
2248
|
+
const {
|
|
2249
|
+
origin = true,
|
|
2250
|
+
methods = DEFAULT_METHODS,
|
|
2251
|
+
allowedHeaders,
|
|
2252
|
+
exposeHeaders,
|
|
2253
|
+
credentials = false,
|
|
2254
|
+
maxAge
|
|
2255
|
+
} = options;
|
|
2256
|
+
return async (ctx, next) => {
|
|
2257
|
+
const reqOrigin = ctx.headers.get("origin");
|
|
2258
|
+
if (!reqOrigin) {
|
|
2259
|
+
await next();
|
|
2260
|
+
return;
|
|
2261
|
+
}
|
|
2262
|
+
let allowOrigin = null;
|
|
2263
|
+
if (origin === true) {
|
|
2264
|
+
allowOrigin = reqOrigin;
|
|
2265
|
+
} else if (typeof origin === "string") {
|
|
2266
|
+
allowOrigin = reqOrigin === origin ? origin : null;
|
|
2267
|
+
} else if (Array.isArray(origin)) {
|
|
2268
|
+
allowOrigin = origin.includes(reqOrigin) ? reqOrigin : null;
|
|
2269
|
+
}
|
|
2270
|
+
if (!allowOrigin) {
|
|
2271
|
+
await next();
|
|
2272
|
+
return;
|
|
2273
|
+
}
|
|
2274
|
+
ctx.setHeader("Access-Control-Allow-Origin", allowOrigin);
|
|
2275
|
+
if (origin === true || Array.isArray(origin)) {
|
|
2276
|
+
const existingVary = ctx.headers.get("vary");
|
|
2277
|
+
if (existingVary) {
|
|
2278
|
+
if (!existingVary.toLowerCase().includes("origin")) {
|
|
2279
|
+
ctx.setHeader("Vary", `${existingVary}, Origin`);
|
|
2280
|
+
}
|
|
2281
|
+
} else {
|
|
2282
|
+
ctx.setHeader("Vary", "Origin");
|
|
1861
2283
|
}
|
|
1862
|
-
continue;
|
|
1863
2284
|
}
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
2285
|
+
ctx.setHeader("Access-Control-Allow-Methods", methods.join(", "));
|
|
2286
|
+
if (allowedHeaders) {
|
|
2287
|
+
ctx.setHeader("Access-Control-Allow-Headers", allowedHeaders.join(", "));
|
|
2288
|
+
} else {
|
|
2289
|
+
const requestHeaders = ctx.headers.get("access-control-request-headers");
|
|
2290
|
+
if (requestHeaders) {
|
|
2291
|
+
ctx.setHeader("Access-Control-Allow-Headers", requestHeaders);
|
|
2292
|
+
}
|
|
2293
|
+
}
|
|
2294
|
+
if (exposeHeaders && exposeHeaders.length > 0) {
|
|
2295
|
+
ctx.setHeader("Access-Control-Expose-Headers", exposeHeaders.join(", "));
|
|
2296
|
+
}
|
|
2297
|
+
if (credentials) {
|
|
2298
|
+
ctx.setHeader("Access-Control-Allow-Credentials", "true");
|
|
2299
|
+
}
|
|
2300
|
+
if (maxAge !== void 0) {
|
|
2301
|
+
ctx.setHeader("Access-Control-Max-Age", String(maxAge));
|
|
2302
|
+
}
|
|
2303
|
+
if (ctx.method === "OPTIONS") {
|
|
2304
|
+
return new Response(null, { status: 204 });
|
|
2305
|
+
}
|
|
2306
|
+
await next();
|
|
2307
|
+
};
|
|
2308
|
+
}
|
|
2309
|
+
|
|
2310
|
+
// src/middleware/logger.ts
|
|
2311
|
+
function logger(options = {}) {
|
|
2312
|
+
return async (ctx, next) => {
|
|
2313
|
+
const log = options.log ?? console.log;
|
|
2314
|
+
const start = Date.now();
|
|
2315
|
+
try {
|
|
2316
|
+
const response = await next();
|
|
2317
|
+
const duration = Date.now() - start;
|
|
2318
|
+
const entry = {
|
|
2319
|
+
method: ctx.method,
|
|
2320
|
+
path: ctx.path,
|
|
2321
|
+
status: response.status,
|
|
2322
|
+
durationMs: duration
|
|
2323
|
+
};
|
|
2324
|
+
log(entry, `${ctx.method} ${ctx.path} ${response.status} ${duration}ms`);
|
|
2325
|
+
return response;
|
|
2326
|
+
} catch (err) {
|
|
2327
|
+
const duration = Date.now() - start;
|
|
2328
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2329
|
+
const status = err?.statusCode ?? 500;
|
|
2330
|
+
const entry = {
|
|
2331
|
+
method: ctx.method,
|
|
2332
|
+
path: ctx.path,
|
|
2333
|
+
status,
|
|
2334
|
+
durationMs: duration,
|
|
2335
|
+
error: message
|
|
2336
|
+
};
|
|
2337
|
+
log(entry, `${ctx.method} ${ctx.path} ${status} ${duration}ms - ${message}`);
|
|
2338
|
+
throw err;
|
|
2339
|
+
}
|
|
2340
|
+
};
|
|
2341
|
+
}
|
|
2342
|
+
|
|
2343
|
+
// src/middleware/helmet.ts
|
|
2344
|
+
var DEFAULTS = {
|
|
2345
|
+
contentSecurityPolicy: "default-src 'self'",
|
|
2346
|
+
xFrameOptions: "SAMEORIGIN",
|
|
2347
|
+
xContentTypeOptions: true,
|
|
2348
|
+
referrerPolicy: "no-referrer",
|
|
2349
|
+
strictTransportSecurity: "max-age=31536000; includeSubDomains",
|
|
2350
|
+
xDnsPrefetchControl: true,
|
|
2351
|
+
xDownloadOptions: true,
|
|
2352
|
+
xPermittedCrossDomainPolicies: "none",
|
|
2353
|
+
crossOriginOpenerPolicy: "same-origin",
|
|
2354
|
+
crossOriginResourcePolicy: "same-origin",
|
|
2355
|
+
crossOriginEmbedderPolicy: false,
|
|
2356
|
+
originAgentCluster: true,
|
|
2357
|
+
xPoweredBy: true
|
|
2358
|
+
};
|
|
2359
|
+
function helmet(options = {}) {
|
|
2360
|
+
const opts = { ...DEFAULTS, ...options };
|
|
2361
|
+
return async (ctx, next) => {
|
|
2362
|
+
if (opts.contentSecurityPolicy !== false) {
|
|
2363
|
+
ctx.setHeader("Content-Security-Policy", opts.contentSecurityPolicy);
|
|
2364
|
+
}
|
|
2365
|
+
if (opts.xFrameOptions !== false) {
|
|
2366
|
+
ctx.setHeader("X-Frame-Options", opts.xFrameOptions);
|
|
2367
|
+
}
|
|
2368
|
+
if (opts.xContentTypeOptions) {
|
|
2369
|
+
ctx.setHeader("X-Content-Type-Options", "nosniff");
|
|
2370
|
+
}
|
|
2371
|
+
if (opts.referrerPolicy !== false) {
|
|
2372
|
+
ctx.setHeader("Referrer-Policy", opts.referrerPolicy);
|
|
2373
|
+
}
|
|
2374
|
+
if (opts.strictTransportSecurity !== false) {
|
|
2375
|
+
ctx.setHeader("Strict-Transport-Security", opts.strictTransportSecurity);
|
|
2376
|
+
}
|
|
2377
|
+
if (opts.xDnsPrefetchControl) {
|
|
2378
|
+
ctx.setHeader("X-DNS-Prefetch-Control", "off");
|
|
2379
|
+
}
|
|
2380
|
+
if (opts.xDownloadOptions) {
|
|
2381
|
+
ctx.setHeader("X-Download-Options", "noopen");
|
|
2382
|
+
}
|
|
2383
|
+
if (opts.xPermittedCrossDomainPolicies !== false) {
|
|
2384
|
+
ctx.setHeader("X-Permitted-Cross-Domain-Policies", opts.xPermittedCrossDomainPolicies);
|
|
2385
|
+
}
|
|
2386
|
+
if (opts.crossOriginOpenerPolicy !== false) {
|
|
2387
|
+
ctx.setHeader("Cross-Origin-Opener-Policy", opts.crossOriginOpenerPolicy);
|
|
2388
|
+
}
|
|
2389
|
+
if (opts.crossOriginResourcePolicy !== false) {
|
|
2390
|
+
ctx.setHeader("Cross-Origin-Resource-Policy", opts.crossOriginResourcePolicy);
|
|
2391
|
+
}
|
|
2392
|
+
if (opts.crossOriginEmbedderPolicy !== false) {
|
|
2393
|
+
ctx.setHeader("Cross-Origin-Embedder-Policy", opts.crossOriginEmbedderPolicy);
|
|
1871
2394
|
}
|
|
1872
|
-
|
|
1873
|
-
|
|
2395
|
+
if (opts.originAgentCluster) {
|
|
2396
|
+
ctx.setHeader("Origin-Agent-Cluster", "?1");
|
|
2397
|
+
}
|
|
2398
|
+
if (opts.xPoweredBy) {
|
|
2399
|
+
ctx.setHeader("X-Powered-By", "faapi");
|
|
2400
|
+
}
|
|
2401
|
+
return await next();
|
|
2402
|
+
};
|
|
1874
2403
|
}
|
|
1875
2404
|
|
|
1876
|
-
// src/
|
|
1877
|
-
import
|
|
1878
|
-
import
|
|
1879
|
-
|
|
1880
|
-
function
|
|
1881
|
-
const
|
|
1882
|
-
if (
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
const parsed = ts6.parseJsonConfigFileContent(configFile.config, ts6.sys, rootDir);
|
|
1886
|
-
const baseUrl = parsed.options.baseUrl ?? rootDir;
|
|
1887
|
-
const rawPaths = parsed.options.paths;
|
|
1888
|
-
if (!rawPaths) return null;
|
|
1889
|
-
const paths = {};
|
|
1890
|
-
for (const [pattern, targets] of Object.entries(rawPaths)) {
|
|
1891
|
-
paths[pattern] = targets.map((t) => path4.resolve(baseUrl, t));
|
|
2405
|
+
// src/config/loadConfig.ts
|
|
2406
|
+
import path8 from "path";
|
|
2407
|
+
import fs9 from "fs";
|
|
2408
|
+
var CONFIG_PRODUCT_FILE = "faapi-config.js";
|
|
2409
|
+
async function loadConfig(rootDir, dist) {
|
|
2410
|
+
const configProductPath = path8.resolve(rootDir, dist, CONFIG_PRODUCT_FILE);
|
|
2411
|
+
if (fs9.existsSync(configProductPath)) {
|
|
2412
|
+
const module = await importWithCacheBust(configProductPath);
|
|
2413
|
+
return module.default ?? {};
|
|
1892
2414
|
}
|
|
1893
|
-
|
|
2415
|
+
const hasSourceConfig = fs9.existsSync(path8.join(rootDir, "faapi.config.ts")) || fs9.existsSync(path8.join(rootDir, "faapi.config.js"));
|
|
2416
|
+
if (hasSourceConfig) {
|
|
2417
|
+
throw new Error(
|
|
2418
|
+
`[faapi] ${dist}/${CONFIG_PRODUCT_FILE} \u4E0D\u5B58\u5728\uFF0C\u8BF7\u5148\u6267\u884C \`faapi build\`\uFF08\u6216 \`faapi dev\`\uFF09\u751F\u6210\u4EA7\u7269\u3002`
|
|
2419
|
+
);
|
|
2420
|
+
}
|
|
2421
|
+
return null;
|
|
1894
2422
|
}
|
|
1895
2423
|
|
|
1896
|
-
// src/cli/
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
return filePath;
|
|
2424
|
+
// src/cli/loadEnv.ts
|
|
2425
|
+
import fs10 from "fs";
|
|
2426
|
+
import path9 from "path";
|
|
2427
|
+
function resolveEnv() {
|
|
2428
|
+
return process.env.NODE_ENV || "development";
|
|
1902
2429
|
}
|
|
1903
|
-
function
|
|
1904
|
-
|
|
1905
|
-
let rel = path5.relative(importerDir, sourceFile);
|
|
1906
|
-
rel = rel.split(path5.sep).join("/");
|
|
1907
|
-
if (!rel.startsWith(".")) rel = "./" + rel;
|
|
1908
|
-
return toProdExtension(rel);
|
|
2430
|
+
function getEnvFiles(env) {
|
|
2431
|
+
return [".env", ".env.local", `.env.${env}`, `.env.${env}.local`];
|
|
1909
2432
|
}
|
|
1910
|
-
function
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
2433
|
+
function parseEnvFile(content, fileVars) {
|
|
2434
|
+
const result = {};
|
|
2435
|
+
const lines = content.replace(/\r\n/g, "\n").split("\n");
|
|
2436
|
+
for (const line of lines) {
|
|
2437
|
+
const trimmed = line.trim();
|
|
2438
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
2439
|
+
const match = /^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/.exec(trimmed);
|
|
2440
|
+
if (!match) continue;
|
|
2441
|
+
const [, key, rawValue] = match;
|
|
2442
|
+
const value = parseValue(rawValue, { ...fileVars, ...result });
|
|
2443
|
+
result[key] = value;
|
|
1915
2444
|
}
|
|
2445
|
+
return result;
|
|
1916
2446
|
}
|
|
1917
|
-
function
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
function toStrippedProdImportPath(sourceFile, rootDir) {
|
|
1923
|
-
const appDirAbs = toRealPath(path5.resolve(rootDir, APP_DIR));
|
|
1924
|
-
const sourceReal = toRealPath(sourceFile);
|
|
1925
|
-
let rel = path5.relative(appDirAbs, sourceReal);
|
|
1926
|
-
rel = rel.split(path5.sep).join("/");
|
|
1927
|
-
if (!rel.startsWith(".")) rel = "./" + rel;
|
|
1928
|
-
return toProdExtension(rel);
|
|
1929
|
-
}
|
|
1930
|
-
var PROD_EXTS = [".js", ".mjs", ".cjs"];
|
|
1931
|
-
var SOURCE_EXTS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
|
|
1932
|
-
var INDEX_EXTS = [
|
|
1933
|
-
"/index.ts",
|
|
1934
|
-
"/index.tsx",
|
|
1935
|
-
"/index.js",
|
|
1936
|
-
"/index.jsx",
|
|
1937
|
-
"/index.mjs",
|
|
1938
|
-
"/index.cjs"
|
|
1939
|
-
];
|
|
1940
|
-
function resolveRelativeSpecifier(importer, specifier) {
|
|
1941
|
-
const importerDir = path5.dirname(importer);
|
|
1942
|
-
const base = path5.resolve(importerDir, specifier);
|
|
1943
|
-
if (PROD_EXTS.some((ext) => specifier.endsWith(ext))) {
|
|
1944
|
-
return fs4.existsSync(base) ? base : null;
|
|
1945
|
-
}
|
|
1946
|
-
if (/\.(ts|tsx|jsx)$/.test(specifier)) {
|
|
1947
|
-
return fs4.existsSync(base) ? base : null;
|
|
1948
|
-
}
|
|
1949
|
-
for (const ext of SOURCE_EXTS) {
|
|
1950
|
-
const file = base + ext;
|
|
1951
|
-
if (fs4.existsSync(file)) return file;
|
|
2447
|
+
function parseValue(raw, env) {
|
|
2448
|
+
if (raw === "") return "";
|
|
2449
|
+
if (raw[0] === "'") {
|
|
2450
|
+
const end = raw.indexOf("'", 1);
|
|
2451
|
+
return end === -1 ? raw.slice(1) : raw.slice(1, end);
|
|
1952
2452
|
}
|
|
1953
|
-
|
|
1954
|
-
const
|
|
1955
|
-
|
|
2453
|
+
if (raw[0] === '"') {
|
|
2454
|
+
const match = /^"((?:\\.|[^"\\])*)"/.exec(raw);
|
|
2455
|
+
const inner = match ? match[1] : raw.slice(1);
|
|
2456
|
+
return expandEscapesAndVars(inner, env);
|
|
1956
2457
|
}
|
|
1957
|
-
|
|
2458
|
+
const commentMatch = /^(.*?)(\s+#.*)$/.exec(raw);
|
|
2459
|
+
const value = commentMatch ? commentMatch[1] : raw;
|
|
2460
|
+
return value.trim();
|
|
1958
2461
|
}
|
|
1959
|
-
function
|
|
1960
|
-
const
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
let modified = false;
|
|
1975
|
-
const newSource = source.replace(SPEC_RE, (full, prefix, quote, specifier) => {
|
|
1976
|
-
if (specifier.startsWith("/") || specifier.startsWith("file:") || specifier.startsWith("node:")) {
|
|
1977
|
-
return full;
|
|
1978
|
-
}
|
|
1979
|
-
if (specifier.startsWith("./") || specifier.startsWith("../")) {
|
|
1980
|
-
const resolved = resolveRelativeSpecifier(importer, specifier);
|
|
1981
|
-
if (resolved) {
|
|
1982
|
-
if (PROD_EXTS.some((ext) => specifier.endsWith(ext))) {
|
|
1983
|
-
return full;
|
|
1984
|
-
}
|
|
1985
|
-
if (appDirAbs && importerOutsideAppDir && isInsideDir(resolved, appDirAbs)) {
|
|
1986
|
-
modified = true;
|
|
1987
|
-
return `${prefix}${quote}${toStrippedProdImportPath(
|
|
1988
|
-
resolved,
|
|
1989
|
-
options.rootDir
|
|
1990
|
-
)}${quote}`;
|
|
1991
|
-
}
|
|
1992
|
-
modified = true;
|
|
1993
|
-
return `${prefix}${quote}${toProdImportPath(resolved, importer)}${quote}`;
|
|
1994
|
-
}
|
|
1995
|
-
return full;
|
|
1996
|
-
}
|
|
1997
|
-
const candidates = resolveAlias(specifier, config);
|
|
1998
|
-
for (const candidate of candidates) {
|
|
1999
|
-
for (const ext of SOURCE_EXTS) {
|
|
2000
|
-
const file = candidate + ext;
|
|
2001
|
-
if (fs4.existsSync(file)) {
|
|
2002
|
-
modified = true;
|
|
2003
|
-
if (appDirAbs && importerOutsideAppDir && isInsideDir(file, appDirAbs)) {
|
|
2004
|
-
return `${prefix}${quote}${toStrippedProdImportPath(
|
|
2005
|
-
file,
|
|
2006
|
-
options.rootDir
|
|
2007
|
-
)}${quote}`;
|
|
2008
|
-
}
|
|
2009
|
-
return `${prefix}${quote}${toProdImportPath(file, importer)}${quote}`;
|
|
2010
|
-
}
|
|
2011
|
-
}
|
|
2012
|
-
for (const indexExt of INDEX_EXTS) {
|
|
2013
|
-
const file = candidate + indexExt;
|
|
2014
|
-
if (fs4.existsSync(file)) {
|
|
2015
|
-
modified = true;
|
|
2016
|
-
if (appDirAbs && importerOutsideAppDir && isInsideDir(file, appDirAbs)) {
|
|
2017
|
-
return `${prefix}${quote}${toStrippedProdImportPath(
|
|
2018
|
-
file,
|
|
2019
|
-
options.rootDir
|
|
2020
|
-
)}${quote}`;
|
|
2021
|
-
}
|
|
2022
|
-
return `${prefix}${quote}${toProdImportPath(file, importer)}${quote}`;
|
|
2023
|
-
}
|
|
2024
|
-
}
|
|
2025
|
-
}
|
|
2026
|
-
return full;
|
|
2027
|
-
});
|
|
2028
|
-
if (!modified) return void 0;
|
|
2029
|
-
return { contents: newSource, loader: "default" };
|
|
2030
|
-
});
|
|
2462
|
+
function expandEscapesAndVars(str, env) {
|
|
2463
|
+
const escaped = str.replace(/\\(.)/g, (_, ch) => {
|
|
2464
|
+
switch (ch) {
|
|
2465
|
+
case "n":
|
|
2466
|
+
return "\n";
|
|
2467
|
+
case "r":
|
|
2468
|
+
return "\r";
|
|
2469
|
+
case "t":
|
|
2470
|
+
return " ";
|
|
2471
|
+
case "\\":
|
|
2472
|
+
return "\\";
|
|
2473
|
+
case '"':
|
|
2474
|
+
return '"';
|
|
2475
|
+
default:
|
|
2476
|
+
return ch;
|
|
2031
2477
|
}
|
|
2032
|
-
};
|
|
2033
|
-
}
|
|
2034
|
-
function buildAliasPlugins(rootDir) {
|
|
2035
|
-
const tsconfig = readTsconfig(rootDir);
|
|
2036
|
-
return [createAliasPlugin(tsconfig ?? { baseUrl: ".", paths: {} }, { rootDir })];
|
|
2037
|
-
}
|
|
2038
|
-
|
|
2039
|
-
// src/cli/compileDevRoutes.ts
|
|
2040
|
-
var APP_DIR2 = "src";
|
|
2041
|
-
async function compileDevRoutes(options) {
|
|
2042
|
-
const { rootDir, dist, files, logLevel = "silent" } = options;
|
|
2043
|
-
const entryPoints = files ?? await fg([`${APP_DIR2}/**/*.ts`], {
|
|
2044
|
-
cwd: rootDir,
|
|
2045
|
-
onlyFiles: true,
|
|
2046
|
-
absolute: true,
|
|
2047
|
-
ignore: ["**/*.test.ts", "**/*.e2e.test.ts", "**/*.d.ts"]
|
|
2048
|
-
});
|
|
2049
|
-
if (entryPoints.length === 0) {
|
|
2050
|
-
return { compiledFiles: [] };
|
|
2051
|
-
}
|
|
2052
|
-
const absDist = path6.resolve(rootDir, dist);
|
|
2053
|
-
await fs5.promises.mkdir(absDist, { recursive: true });
|
|
2054
|
-
const plugins = buildAliasPlugins(rootDir);
|
|
2055
|
-
const esbuild = await import("esbuild");
|
|
2056
|
-
const outbase = path6.resolve(rootDir, APP_DIR2);
|
|
2057
|
-
const result = await esbuild.build({
|
|
2058
|
-
entryPoints,
|
|
2059
|
-
outdir: absDist,
|
|
2060
|
-
outbase,
|
|
2061
|
-
bundle: false,
|
|
2062
|
-
platform: "node",
|
|
2063
|
-
format: "esm",
|
|
2064
|
-
sourcemap: true,
|
|
2065
|
-
packages: "external",
|
|
2066
|
-
plugins,
|
|
2067
|
-
logLevel,
|
|
2068
|
-
write: false
|
|
2069
2478
|
});
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2479
|
+
return escaped.replace(
|
|
2480
|
+
/\$\{([A-Za-z_][A-Za-z0-9_]*)\}|\$([A-Za-z_][A-Za-z0-9_]*)/g,
|
|
2481
|
+
(_, braced, plain) => {
|
|
2482
|
+
const varName = braced || plain;
|
|
2483
|
+
return env[varName] ?? process.env[varName] ?? "";
|
|
2484
|
+
}
|
|
2485
|
+
);
|
|
2486
|
+
}
|
|
2487
|
+
function loadEnv(rootDir) {
|
|
2488
|
+
const env = resolveEnv();
|
|
2489
|
+
const files = getEnvFiles(env);
|
|
2490
|
+
const merged = {};
|
|
2491
|
+
for (const file of files) {
|
|
2492
|
+
const filePath = path9.join(rootDir, file);
|
|
2493
|
+
if (!fs10.existsSync(filePath)) continue;
|
|
2494
|
+
const content = fs10.readFileSync(filePath, "utf-8");
|
|
2495
|
+
const parsed = parseEnvFile(content, merged);
|
|
2496
|
+
Object.assign(merged, parsed);
|
|
2497
|
+
}
|
|
2498
|
+
for (const [key, value] of Object.entries(merged)) {
|
|
2499
|
+
if (process.env[key] === void 0) {
|
|
2500
|
+
process.env[key] = value;
|
|
2501
|
+
}
|
|
2079
2502
|
}
|
|
2080
|
-
return { compiledFiles: entryPoints };
|
|
2081
2503
|
}
|
|
2082
2504
|
|
|
2083
|
-
// src/
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
return prodStat.mtimeMs >= srcStat.mtimeMs;
|
|
2091
|
-
} catch {
|
|
2092
|
-
return false;
|
|
2505
|
+
// src/errors/FaapiError.ts
|
|
2506
|
+
var FaapiError = class extends Error {
|
|
2507
|
+
constructor(code, message, statusCode) {
|
|
2508
|
+
super(message);
|
|
2509
|
+
this.code = code;
|
|
2510
|
+
this.statusCode = statusCode;
|
|
2511
|
+
this.name = "FaapiError";
|
|
2093
2512
|
}
|
|
2513
|
+
code;
|
|
2514
|
+
statusCode;
|
|
2515
|
+
};
|
|
2516
|
+
|
|
2517
|
+
// src/errors/httpErrors.ts
|
|
2518
|
+
function deriveStatusCode(issues) {
|
|
2519
|
+
const has400 = issues.some((i) => i.code === "INVALID_FORMAT" || i.code === "MISSING_FIELD");
|
|
2520
|
+
return has400 ? 400 : 422;
|
|
2094
2521
|
}
|
|
2095
|
-
var
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
if (compiledFiles.has(sourceAbsPath)) {
|
|
2101
|
-
return false;
|
|
2522
|
+
var ValidationError = class extends FaapiError {
|
|
2523
|
+
constructor(message, issues) {
|
|
2524
|
+
super("VALIDATION_ERROR", message, deriveStatusCode(issues));
|
|
2525
|
+
this.issues = issues;
|
|
2526
|
+
this.name = "ValidationError";
|
|
2102
2527
|
}
|
|
2103
|
-
|
|
2104
|
-
|
|
2528
|
+
issues;
|
|
2529
|
+
};
|
|
2530
|
+
var RouteNotFoundError = class extends FaapiError {
|
|
2531
|
+
constructor(path18) {
|
|
2532
|
+
super("ROUTE_NOT_FOUND", `Route not found: ${path18}`, 404);
|
|
2533
|
+
this.name = "RouteNotFoundError";
|
|
2105
2534
|
}
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2535
|
+
};
|
|
2536
|
+
var MethodNotAllowedError = class extends FaapiError {
|
|
2537
|
+
constructor(method, path18, allowedMethods) {
|
|
2538
|
+
super("METHOD_NOT_ALLOWED", `Method ${method} not allowed for ${path18}`, 405);
|
|
2539
|
+
this.allowedMethods = allowedMethods;
|
|
2540
|
+
this.name = "MethodNotAllowedError";
|
|
2110
2541
|
}
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
compiledFiles.add(sourceAbsPath);
|
|
2118
|
-
return true;
|
|
2119
|
-
}
|
|
2120
|
-
function prodSourcePathToProductPath(sourceAbsPath, rootDir, dist) {
|
|
2121
|
-
const rel = path8.relative(rootDir, sourceAbsPath).replace(/\\/g, "/");
|
|
2122
|
-
if (!rel.startsWith("src/")) return null;
|
|
2123
|
-
const relWithoutSrc = rel.slice(4);
|
|
2124
|
-
const jsRel = relWithoutSrc.replace(/\.ts$/, ".js");
|
|
2125
|
-
return path8.resolve(rootDir, dist, jsRel);
|
|
2126
|
-
}
|
|
2127
|
-
var generatedSchemas = /* @__PURE__ */ new Set();
|
|
2128
|
-
function clearGeneratedSchemas() {
|
|
2129
|
-
generatedSchemas.clear();
|
|
2130
|
-
}
|
|
2131
|
-
async function ensureSchemaGenerated(schemaPath, routeFilePath, routes, rootDir, dist) {
|
|
2132
|
-
if (generatedSchemas.has(schemaPath)) {
|
|
2133
|
-
return false;
|
|
2542
|
+
allowedMethods;
|
|
2543
|
+
};
|
|
2544
|
+
var InternalError = class extends FaapiError {
|
|
2545
|
+
constructor(message) {
|
|
2546
|
+
super("INTERNAL_ERROR", message, 500);
|
|
2547
|
+
this.name = "InternalError";
|
|
2134
2548
|
}
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2549
|
+
};
|
|
2550
|
+
var ModuleLoadError = class extends FaapiError {
|
|
2551
|
+
constructor(filePath, reason) {
|
|
2552
|
+
super("MODULE_LOAD_ERROR", `Failed to load module ${filePath}: ${reason}`, 500);
|
|
2553
|
+
this.name = "ModuleLoadError";
|
|
2139
2554
|
}
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2555
|
+
};
|
|
2556
|
+
var PayloadTooLargeError = class extends FaapiError {
|
|
2557
|
+
constructor(maxSize) {
|
|
2558
|
+
super("PAYLOAD_TOO_LARGE", `Request body exceeds size limit of ${maxSize} bytes`, 413);
|
|
2559
|
+
this.name = "PayloadTooLargeError";
|
|
2143
2560
|
}
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2561
|
+
};
|
|
2562
|
+
|
|
2563
|
+
// src/cli/createAppCore.ts
|
|
2564
|
+
import fs15 from "fs";
|
|
2565
|
+
import path14 from "path";
|
|
2566
|
+
import { PassThrough, Readable as Readable3 } from "stream";
|
|
2567
|
+
|
|
2568
|
+
// src/router/sortRoutes.ts
|
|
2569
|
+
function sortRoutes(routes) {
|
|
2570
|
+
return [...routes].sort((a, b) => {
|
|
2571
|
+
if (a.isDynamic !== b.isDynamic) {
|
|
2572
|
+
return a.isDynamic ? 1 : -1;
|
|
2573
|
+
}
|
|
2574
|
+
if (a.isCatchAll !== b.isCatchAll) {
|
|
2575
|
+
return a.isCatchAll ? 1 : -1;
|
|
2576
|
+
}
|
|
2577
|
+
const aSegments = a.urlPath.split("/").filter(Boolean).length;
|
|
2578
|
+
const bSegments = b.urlPath.split("/").filter(Boolean).length;
|
|
2579
|
+
if (aSegments !== bSegments) {
|
|
2580
|
+
return aSegments - bSegments;
|
|
2581
|
+
}
|
|
2582
|
+
return a.urlPath.localeCompare(b.urlPath);
|
|
2583
|
+
});
|
|
2584
|
+
}
|
|
2585
|
+
|
|
2586
|
+
// src/router/detectRouteConflicts.ts
|
|
2587
|
+
function detectRouteConflicts(routes) {
|
|
2588
|
+
const map = /* @__PURE__ */ new Map();
|
|
2589
|
+
for (const route of routes) {
|
|
2590
|
+
const key = `${route.method} ${route.urlPath}`;
|
|
2591
|
+
const existing = map.get(key);
|
|
2592
|
+
if (existing) {
|
|
2593
|
+
existing.files.push(route.filePath);
|
|
2594
|
+
} else {
|
|
2595
|
+
map.set(key, {
|
|
2596
|
+
method: route.method,
|
|
2597
|
+
urlPath: route.urlPath,
|
|
2598
|
+
files: [route.filePath]
|
|
2599
|
+
});
|
|
2600
|
+
}
|
|
2147
2601
|
}
|
|
2148
|
-
const
|
|
2149
|
-
const
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2602
|
+
const conflicts = [];
|
|
2603
|
+
for (const conflict of map.values()) {
|
|
2604
|
+
if (conflict.files.length > 1) {
|
|
2605
|
+
conflicts.push(conflict);
|
|
2606
|
+
}
|
|
2607
|
+
}
|
|
2608
|
+
return conflicts;
|
|
2153
2609
|
}
|
|
2154
|
-
|
|
2155
|
-
|
|
2610
|
+
|
|
2611
|
+
// src/server/createServer.ts
|
|
2612
|
+
import {
|
|
2613
|
+
createServer as createHttpServer
|
|
2614
|
+
} from "http";
|
|
2615
|
+
import { createSecureServer as createHttp2SecureServer } from "http2";
|
|
2616
|
+
import { readFileSync } from "fs";
|
|
2617
|
+
import { Readable as Readable2 } from "stream";
|
|
2618
|
+
import path11 from "path";
|
|
2619
|
+
|
|
2620
|
+
// src/router/matchRoute.ts
|
|
2621
|
+
function matchRoute(routes, method, path18) {
|
|
2156
2622
|
for (const route of routes) {
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2623
|
+
if (route.method !== method) {
|
|
2624
|
+
continue;
|
|
2625
|
+
}
|
|
2626
|
+
if (!route.isDynamic) {
|
|
2627
|
+
if (route.urlPath === path18) {
|
|
2628
|
+
return { route, params: {} };
|
|
2629
|
+
}
|
|
2630
|
+
continue;
|
|
2631
|
+
}
|
|
2632
|
+
const params = matchDynamicPath(route.urlPath, path18, route.paramNames, route.isCatchAll);
|
|
2633
|
+
if (params !== null) {
|
|
2634
|
+
return { route, params };
|
|
2163
2635
|
}
|
|
2164
2636
|
}
|
|
2637
|
+
return null;
|
|
2165
2638
|
}
|
|
2166
|
-
function
|
|
2167
|
-
const
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2639
|
+
function matchWsRoute(wsRoutes, path18) {
|
|
2640
|
+
for (const route of wsRoutes) {
|
|
2641
|
+
if (!route.isDynamic) {
|
|
2642
|
+
if (route.urlPath === path18) {
|
|
2643
|
+
return { route, params: {} };
|
|
2644
|
+
}
|
|
2645
|
+
continue;
|
|
2646
|
+
}
|
|
2647
|
+
const params = matchDynamicPath(route.urlPath, path18, route.paramNames, route.isCatchAll);
|
|
2648
|
+
if (params !== null) {
|
|
2649
|
+
return { route, params };
|
|
2650
|
+
}
|
|
2171
2651
|
}
|
|
2172
|
-
|
|
2173
|
-
const tsRel = srcRel.replace(/\.js$/, ".ts");
|
|
2174
|
-
const tsAbs = path8.resolve(rootDir, tsRel);
|
|
2175
|
-
if (fs7.existsSync(tsAbs)) return tsAbs;
|
|
2176
|
-
return path8.resolve(rootDir, srcRel);
|
|
2652
|
+
return null;
|
|
2177
2653
|
}
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2654
|
+
function matchDynamicPath(pattern, path18, paramNames, isCatchAll) {
|
|
2655
|
+
const patternSegments = pattern.split("/").filter(Boolean);
|
|
2656
|
+
const pathSegments = path18.split("/").filter(Boolean);
|
|
2657
|
+
if (isCatchAll) {
|
|
2658
|
+
const nonCatchAllCount = patternSegments.length - 1;
|
|
2659
|
+
if (pathSegments.length <= nonCatchAllCount) {
|
|
2660
|
+
return null;
|
|
2661
|
+
}
|
|
2662
|
+
const params2 = {};
|
|
2663
|
+
for (let i = 0; i < nonCatchAllCount; i++) {
|
|
2664
|
+
const patternSeg = patternSegments[i];
|
|
2665
|
+
const pathSeg = pathSegments[i];
|
|
2666
|
+
if (patternSeg.startsWith(":")) {
|
|
2667
|
+
const paramName = patternSeg.slice(1);
|
|
2668
|
+
params2[paramName] = pathSeg;
|
|
2669
|
+
} else if (patternSeg !== pathSeg) {
|
|
2670
|
+
return null;
|
|
2671
|
+
}
|
|
2672
|
+
}
|
|
2673
|
+
const catchAllValue = pathSegments.slice(nonCatchAllCount).join("/");
|
|
2674
|
+
const catchAllParamName = patternSegments[nonCatchAllCount].slice(4);
|
|
2675
|
+
params2[catchAllParamName] = catchAllValue;
|
|
2676
|
+
if (Object.keys(params2).length !== paramNames.length) {
|
|
2677
|
+
return null;
|
|
2678
|
+
}
|
|
2679
|
+
return params2;
|
|
2680
|
+
}
|
|
2681
|
+
if (patternSegments.length !== pathSegments.length) {
|
|
2682
|
+
return null;
|
|
2683
|
+
}
|
|
2684
|
+
const params = {};
|
|
2685
|
+
for (let i = 0; i < patternSegments.length; i++) {
|
|
2686
|
+
const patternSeg = patternSegments[i];
|
|
2687
|
+
const pathSeg = pathSegments[i];
|
|
2688
|
+
if (patternSeg.startsWith(":")) {
|
|
2689
|
+
const paramName = patternSeg.slice(1);
|
|
2690
|
+
params[paramName] = pathSeg;
|
|
2691
|
+
} else if (patternSeg !== pathSeg) {
|
|
2692
|
+
return null;
|
|
2693
|
+
}
|
|
2694
|
+
}
|
|
2695
|
+
if (Object.keys(params).length !== paramNames.length) {
|
|
2696
|
+
return null;
|
|
2697
|
+
}
|
|
2698
|
+
return params;
|
|
2181
2699
|
}
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2700
|
+
|
|
2701
|
+
// src/loader/loadRouteModule.ts
|
|
2702
|
+
import fs11 from "fs";
|
|
2703
|
+
|
|
2704
|
+
// src/loader/validateRouteModule.ts
|
|
2705
|
+
function validateRouteModule(value, method, filePath) {
|
|
2706
|
+
if (typeof value !== "function") {
|
|
2707
|
+
throw new Error(
|
|
2708
|
+
`Route module "${filePath}" does not export a valid handler for method "${method}". Expected a function, got ${typeof value}.`
|
|
2709
|
+
);
|
|
2710
|
+
}
|
|
2185
2711
|
}
|
|
2186
2712
|
|
|
2187
2713
|
// src/loader/loadRouteModule.ts
|
|
@@ -2190,7 +2716,7 @@ async function loadRouteModule(filePath, method, rootDir) {
|
|
|
2190
2716
|
const dist = getDevDist();
|
|
2191
2717
|
if (dist) {
|
|
2192
2718
|
const sourcePath = prodPathToSourcePath(filePath, rootDir, dist);
|
|
2193
|
-
if (sourcePath &&
|
|
2719
|
+
if (sourcePath && fs11.existsSync(sourcePath)) {
|
|
2194
2720
|
try {
|
|
2195
2721
|
await ensureCompiled(sourcePath, rootDir, dist);
|
|
2196
2722
|
} catch (compileErr) {
|
|
@@ -2319,6 +2845,92 @@ function createSseWriter() {
|
|
|
2319
2845
|
return writer;
|
|
2320
2846
|
}
|
|
2321
2847
|
|
|
2848
|
+
// src/response/responseFormatter.ts
|
|
2849
|
+
function defaultOk(data) {
|
|
2850
|
+
return { data };
|
|
2851
|
+
}
|
|
2852
|
+
function defaultFail(e) {
|
|
2853
|
+
const error = { message: e.message };
|
|
2854
|
+
if (e.code !== void 0) error.code = e.code;
|
|
2855
|
+
return { error };
|
|
2856
|
+
}
|
|
2857
|
+
function getResponseConfig(config) {
|
|
2858
|
+
return config?.response;
|
|
2859
|
+
}
|
|
2860
|
+
function resolveOkFn(config) {
|
|
2861
|
+
return getResponseConfig(config)?.ok ?? defaultOk;
|
|
2862
|
+
}
|
|
2863
|
+
function resolveFailFn(config) {
|
|
2864
|
+
return getResponseConfig(config)?.fail ?? defaultFail;
|
|
2865
|
+
}
|
|
2866
|
+
function jsonOk(body, status = 200, extraHeaders) {
|
|
2867
|
+
return jsonRaw(body, status, extraHeaders);
|
|
2868
|
+
}
|
|
2869
|
+
function jsonRaw(body, status, extraHeaders) {
|
|
2870
|
+
const headers = new Headers({ "Content-Type": "application/json" });
|
|
2871
|
+
if (extraHeaders) {
|
|
2872
|
+
const extra = new Headers(extraHeaders);
|
|
2873
|
+
extra.forEach((value, key) => headers.set(key, value));
|
|
2874
|
+
}
|
|
2875
|
+
return new Response(JSON.stringify(body), { status, headers });
|
|
2876
|
+
}
|
|
2877
|
+
function wrapOkResult(result, config) {
|
|
2878
|
+
if (result instanceof Response) return result;
|
|
2879
|
+
return resolveOkFn(config)(result);
|
|
2880
|
+
}
|
|
2881
|
+
function formatFailResponse(options, config) {
|
|
2882
|
+
const failFn = resolveFailFn(config);
|
|
2883
|
+
const body = failFn({
|
|
2884
|
+
status: options.status,
|
|
2885
|
+
code: options.code,
|
|
2886
|
+
message: options.message
|
|
2887
|
+
});
|
|
2888
|
+
return jsonOk(body, options.status ?? 500);
|
|
2889
|
+
}
|
|
2890
|
+
function formatErrorResponse(error, config) {
|
|
2891
|
+
const failFn = resolveFailFn(config);
|
|
2892
|
+
if (error instanceof ValidationError) {
|
|
2893
|
+
const body2 = failFn({
|
|
2894
|
+
status: error.statusCode,
|
|
2895
|
+
code: error.code,
|
|
2896
|
+
message: error.message
|
|
2897
|
+
});
|
|
2898
|
+
const bodyObj = typeof body2 === "object" && body2 !== null ? body2 : { error: body2 };
|
|
2899
|
+
const errorObj = bodyObj.error ?? bodyObj;
|
|
2900
|
+
if (errorObj) {
|
|
2901
|
+
errorObj.issues = error.issues;
|
|
2902
|
+
}
|
|
2903
|
+
return jsonOk(bodyObj, error.statusCode);
|
|
2904
|
+
}
|
|
2905
|
+
if (error instanceof MethodNotAllowedError) {
|
|
2906
|
+
const body2 = failFn({
|
|
2907
|
+
status: error.statusCode,
|
|
2908
|
+
code: error.code,
|
|
2909
|
+
message: error.message
|
|
2910
|
+
});
|
|
2911
|
+
return jsonOk(body2, error.statusCode, { Allow: error.allowedMethods.join(", ") });
|
|
2912
|
+
}
|
|
2913
|
+
if (error instanceof PayloadTooLargeError) {
|
|
2914
|
+
const body2 = failFn({
|
|
2915
|
+
status: error.statusCode,
|
|
2916
|
+
code: error.code,
|
|
2917
|
+
message: error.message
|
|
2918
|
+
});
|
|
2919
|
+
return jsonOk(body2, error.statusCode);
|
|
2920
|
+
}
|
|
2921
|
+
if (error instanceof FaapiError) {
|
|
2922
|
+
const body2 = failFn({
|
|
2923
|
+
status: error.statusCode,
|
|
2924
|
+
code: error.code,
|
|
2925
|
+
message: error.message
|
|
2926
|
+
});
|
|
2927
|
+
return jsonOk(body2, error.statusCode);
|
|
2928
|
+
}
|
|
2929
|
+
const message = error instanceof Error ? error.message : "An unknown error occurred";
|
|
2930
|
+
const body = failFn({ status: 500, code: "INTERNAL_ERROR", message });
|
|
2931
|
+
return jsonOk(body, 500);
|
|
2932
|
+
}
|
|
2933
|
+
|
|
2322
2934
|
// src/runtime/createContext.ts
|
|
2323
2935
|
function parseCookies(cookieHeader) {
|
|
2324
2936
|
const cookies = /* @__PURE__ */ new Map();
|
|
@@ -2379,11 +2991,7 @@ function createContext(request, params, config = {}, ip = "") {
|
|
|
2379
2991
|
});
|
|
2380
2992
|
},
|
|
2381
2993
|
json(data, status) {
|
|
2382
|
-
|
|
2383
|
-
return new Response(JSON.stringify(data), {
|
|
2384
|
-
status: status ?? 200,
|
|
2385
|
-
headers
|
|
2386
|
-
});
|
|
2994
|
+
return jsonOk(data, status ?? 200);
|
|
2387
2995
|
},
|
|
2388
2996
|
html(html, status) {
|
|
2389
2997
|
const headers = { "Content-Type": "text/html; charset=utf-8" };
|
|
@@ -2419,18 +3027,22 @@ function createContext(request, params, config = {}, ip = "") {
|
|
|
2419
3027
|
/**
|
|
2420
3028
|
* 显式包装成功响应(返回 Response,不会被自动包裹再次包装)
|
|
2421
3029
|
*
|
|
3030
|
+
* 实现委托给 [responseFormatter.wrapOkResult](../response/responseFormatter.ts),
|
|
3031
|
+
* 与 handler `return data` 走的自动包裹路径共享同一套 ok 函数。
|
|
3032
|
+
*
|
|
2422
3033
|
* 用 config.response.ok(或默认 (data) => ({ data })) 包裹 data 并返回 JSON Response。
|
|
2423
3034
|
* handler 也可直接 return data,框架会自动用 ok 包裹,两者等价。
|
|
2424
3035
|
*/
|
|
2425
3036
|
ok(data) {
|
|
2426
|
-
const
|
|
2427
|
-
|
|
2428
|
-
const body = okFn(data);
|
|
2429
|
-
return ctx.json(body);
|
|
3037
|
+
const body = wrapOkResult(data, config);
|
|
3038
|
+
return jsonOk(body, 200);
|
|
2430
3039
|
},
|
|
2431
3040
|
/**
|
|
2432
3041
|
* 返回错误响应(对象形式参数,status 和 code 均可省略)
|
|
2433
3042
|
*
|
|
3043
|
+
* 实现委托给 [responseFormatter.formatFailResponse](../response/responseFormatter.ts),
|
|
3044
|
+
* 与 formatErrorResponse(handler 抛错兜底)共享同一套 fail 函数,确保错误格式一致。
|
|
3045
|
+
*
|
|
2434
3046
|
* - status 省略时 HTTP 状态码默认 500
|
|
2435
3047
|
* - code 省略时响应 body 里不含 code 字段(默认 fail 函数只放非 undefined 的字段)
|
|
2436
3048
|
* - status 和 code 独立无关联
|
|
@@ -2438,18 +3050,7 @@ function createContext(request, params, config = {}, ip = "") {
|
|
|
2438
3050
|
* body 用 config.response.fail(或默认实现)包装。
|
|
2439
3051
|
*/
|
|
2440
3052
|
fail(options) {
|
|
2441
|
-
|
|
2442
|
-
const failFn = responseConfig?.fail ?? ((e) => {
|
|
2443
|
-
const error = { message: e.message };
|
|
2444
|
-
if (e.code !== void 0) error.code = e.code;
|
|
2445
|
-
return { error };
|
|
2446
|
-
});
|
|
2447
|
-
const body = failFn({
|
|
2448
|
-
status: options.status,
|
|
2449
|
-
code: options.code,
|
|
2450
|
-
message: options.message
|
|
2451
|
-
});
|
|
2452
|
-
return ctx.json(body, options.status ?? 500);
|
|
3053
|
+
return formatFailResponse(options, config);
|
|
2453
3054
|
}
|
|
2454
3055
|
};
|
|
2455
3056
|
const extend = config?.extendContext;
|
|
@@ -2684,6 +3285,12 @@ function getBuiltinInjectionValue(type, ctx, body) {
|
|
|
2684
3285
|
return body.fields;
|
|
2685
3286
|
}
|
|
2686
3287
|
return {};
|
|
3288
|
+
// Phase 2.3:注入所有已注册 agent 元数据列表
|
|
3289
|
+
case "agents":
|
|
3290
|
+
return listAgents();
|
|
3291
|
+
// Phase 3.5:调 @faapi/agent 插件注册的工厂获取 AgentHandle
|
|
3292
|
+
case "agent":
|
|
3293
|
+
return getAgentHandle(ctx);
|
|
2687
3294
|
default:
|
|
2688
3295
|
return void 0;
|
|
2689
3296
|
}
|
|
@@ -2709,10 +3316,7 @@ async function injectParamsAsync(handler, ctx, body, injectors) {
|
|
|
2709
3316
|
|
|
2710
3317
|
// src/runtime/invokeHandler.ts
|
|
2711
3318
|
function wrapResult(result, ctx) {
|
|
2712
|
-
|
|
2713
|
-
const responseConfig = ctx.config.response;
|
|
2714
|
-
const okFn = responseConfig?.ok ?? ((d) => ({ data: d }));
|
|
2715
|
-
return okFn(result);
|
|
3319
|
+
return wrapOkResult(result, ctx.config);
|
|
2716
3320
|
}
|
|
2717
3321
|
function mergeMeta(response, meta) {
|
|
2718
3322
|
const hasMeta = meta.status !== void 0 || Object.keys(meta.headers).length > 0 || meta.setCookies.length > 0;
|
|
@@ -2867,9 +3471,9 @@ async function validateInput(schemaPath, method, inputType, input) {
|
|
|
2867
3471
|
function mapZodIssues(error) {
|
|
2868
3472
|
return error.issues.map((issue) => {
|
|
2869
3473
|
const code = mapZodCode(issue.code, issue.message);
|
|
2870
|
-
const
|
|
3474
|
+
const path18 = issue.path.map(String).join(".") || "";
|
|
2871
3475
|
return {
|
|
2872
|
-
path:
|
|
3476
|
+
path: path18,
|
|
2873
3477
|
code,
|
|
2874
3478
|
expected: issue.expected ?? mapExpectedFromMessage(issue.message),
|
|
2875
3479
|
received: issue.received ?? mapReceivedFromMessage(issue.message),
|
|
@@ -2931,55 +3535,9 @@ function getClientIp(req) {
|
|
|
2931
3535
|
}
|
|
2932
3536
|
|
|
2933
3537
|
// src/server/handleWsUpgrade.ts
|
|
2934
|
-
import
|
|
3538
|
+
import fs12 from "fs";
|
|
2935
3539
|
import { WebSocketServer, WebSocket } from "ws";
|
|
2936
|
-
import
|
|
2937
|
-
|
|
2938
|
-
// src/errors/formatErrorResponse.ts
|
|
2939
|
-
function formatErrorResponse(error) {
|
|
2940
|
-
if (error instanceof ValidationError) {
|
|
2941
|
-
const body2 = {
|
|
2942
|
-
code: error.code,
|
|
2943
|
-
message: error.message,
|
|
2944
|
-
issues: error.issues
|
|
2945
|
-
};
|
|
2946
|
-
return new Response(JSON.stringify({ error: body2 }), {
|
|
2947
|
-
status: error.statusCode,
|
|
2948
|
-
headers: { "Content-Type": "application/json" }
|
|
2949
|
-
});
|
|
2950
|
-
}
|
|
2951
|
-
if (error instanceof MethodNotAllowedError) {
|
|
2952
|
-
const body2 = {
|
|
2953
|
-
code: error.code,
|
|
2954
|
-
message: error.message
|
|
2955
|
-
};
|
|
2956
|
-
return new Response(JSON.stringify({ error: body2 }), {
|
|
2957
|
-
status: error.statusCode,
|
|
2958
|
-
headers: {
|
|
2959
|
-
"Content-Type": "application/json",
|
|
2960
|
-
Allow: error.allowedMethods.join(", ")
|
|
2961
|
-
}
|
|
2962
|
-
});
|
|
2963
|
-
}
|
|
2964
|
-
if (error instanceof FaapiError) {
|
|
2965
|
-
const body2 = {
|
|
2966
|
-
code: error.code,
|
|
2967
|
-
message: error.message
|
|
2968
|
-
};
|
|
2969
|
-
return new Response(JSON.stringify({ error: body2 }), {
|
|
2970
|
-
status: error.statusCode,
|
|
2971
|
-
headers: { "Content-Type": "application/json" }
|
|
2972
|
-
});
|
|
2973
|
-
}
|
|
2974
|
-
const body = {
|
|
2975
|
-
code: "INTERNAL_ERROR",
|
|
2976
|
-
message: error instanceof Error ? error.message : "An unknown error occurred"
|
|
2977
|
-
};
|
|
2978
|
-
return new Response(JSON.stringify({ error: body }), {
|
|
2979
|
-
status: 500,
|
|
2980
|
-
headers: { "Content-Type": "application/json" }
|
|
2981
|
-
});
|
|
2982
|
-
}
|
|
3540
|
+
import path10 from "path";
|
|
2983
3541
|
|
|
2984
3542
|
// src/server/serverUtils.ts
|
|
2985
3543
|
function nodeHttpToWebHeaders(req) {
|
|
@@ -2994,9 +3552,9 @@ function nodeHttpToWebHeaders(req) {
|
|
|
2994
3552
|
}
|
|
2995
3553
|
return headers;
|
|
2996
3554
|
}
|
|
2997
|
-
function buildErrorResponse(err) {
|
|
3555
|
+
function buildErrorResponse(err, config) {
|
|
2998
3556
|
try {
|
|
2999
|
-
return formatErrorResponse(err);
|
|
3557
|
+
return formatErrorResponse(err, config);
|
|
3000
3558
|
} catch {
|
|
3001
3559
|
return new Response(
|
|
3002
3560
|
JSON.stringify({ error: { code: "INTERNAL_ERROR", message: "Internal Server Error" } }),
|
|
@@ -3020,37 +3578,42 @@ function setCachedMiddlewares(absPath, bundle) {
|
|
|
3020
3578
|
middlewareCache.set(absPath, bundle);
|
|
3021
3579
|
}
|
|
3022
3580
|
async function loadMiddlewaresFile(filePath) {
|
|
3581
|
+
let module;
|
|
3023
3582
|
try {
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
const validMiddlewares = middlewares.filter((m) => {
|
|
3031
|
-
if (typeof m !== "function") {
|
|
3032
|
-
console.warn(`[faapi] \u65E0\u6548\u7684\u4E2D\u95F4\u4EF6\u9879\uFF08\u5E94\u4E3A\u51FD\u6570\uFF09\uFF0C\u5DF2\u5FFD\u7565: ${typeof m}`);
|
|
3033
|
-
return false;
|
|
3034
|
-
}
|
|
3035
|
-
return true;
|
|
3036
|
-
});
|
|
3037
|
-
const injectors = module.injectors ?? {};
|
|
3038
|
-
if (typeof injectors !== "object" || injectors === null) {
|
|
3039
|
-
console.warn(`[faapi] injectors \u5E94\u5BFC\u51FA\u5BF9\u8C61\uFF0C\u5DF2\u5FFD\u7565: ${filePath}`);
|
|
3040
|
-
return { middlewares: validMiddlewares, injectors: {} };
|
|
3041
|
-
}
|
|
3042
|
-
const validInjectors = {};
|
|
3043
|
-
for (const [name, injector] of Object.entries(injectors)) {
|
|
3044
|
-
if (typeof injector !== "function") {
|
|
3045
|
-
console.warn(`[faapi] \u6CE8\u5165\u5668 ${name} \u5E94\u4E3A\u51FD\u6570\uFF0C\u5DF2\u5FFD\u7565`);
|
|
3046
|
-
continue;
|
|
3047
|
-
}
|
|
3048
|
-
validInjectors[name] = injector;
|
|
3049
|
-
}
|
|
3050
|
-
return { middlewares: validMiddlewares, injectors: validInjectors };
|
|
3051
|
-
} catch {
|
|
3583
|
+
module = await importWithCacheBust(filePath);
|
|
3584
|
+
} catch (err) {
|
|
3585
|
+
console.error(
|
|
3586
|
+
`[faapi] Failed to load middlewares from ${filePath}:`,
|
|
3587
|
+
err instanceof Error ? err.stack ?? err.message : err
|
|
3588
|
+
);
|
|
3052
3589
|
return { middlewares: [], injectors: {} };
|
|
3053
3590
|
}
|
|
3591
|
+
const middlewares = module.default ?? module.middlewares ?? [];
|
|
3592
|
+
if (!Array.isArray(middlewares)) {
|
|
3593
|
+
console.warn(`[faapi] middlewares.ts \u5E94\u5BFC\u51FA\u6570\u7EC4\uFF0C\u5DF2\u5FFD\u7565: ${filePath}`);
|
|
3594
|
+
return { middlewares: [], injectors: {} };
|
|
3595
|
+
}
|
|
3596
|
+
const validMiddlewares = middlewares.filter((m) => {
|
|
3597
|
+
if (typeof m !== "function") {
|
|
3598
|
+
console.warn(`[faapi] \u65E0\u6548\u7684\u4E2D\u95F4\u4EF6\u9879\uFF08\u5E94\u4E3A\u51FD\u6570\uFF09\uFF0C\u5DF2\u5FFD\u7565: ${typeof m}`);
|
|
3599
|
+
return false;
|
|
3600
|
+
}
|
|
3601
|
+
return true;
|
|
3602
|
+
});
|
|
3603
|
+
const injectors = module.injectors ?? {};
|
|
3604
|
+
if (typeof injectors !== "object" || injectors === null) {
|
|
3605
|
+
console.warn(`[faapi] injectors \u5E94\u5BFC\u51FA\u5BF9\u8C61\uFF0C\u5DF2\u5FFD\u7565: ${filePath}`);
|
|
3606
|
+
return { middlewares: validMiddlewares, injectors: {} };
|
|
3607
|
+
}
|
|
3608
|
+
const validInjectors = {};
|
|
3609
|
+
for (const [name, injector] of Object.entries(injectors)) {
|
|
3610
|
+
if (typeof injector !== "function") {
|
|
3611
|
+
console.warn(`[faapi] \u6CE8\u5165\u5668 ${name} \u5E94\u4E3A\u51FD\u6570\uFF0C\u5DF2\u5FFD\u7565`);
|
|
3612
|
+
continue;
|
|
3613
|
+
}
|
|
3614
|
+
validInjectors[name] = injector;
|
|
3615
|
+
}
|
|
3616
|
+
return { middlewares: validMiddlewares, injectors: validInjectors };
|
|
3054
3617
|
}
|
|
3055
3618
|
async function loadMergedMiddlewares(middlewarePaths) {
|
|
3056
3619
|
if (middlewarePaths.length === 0) return void 0;
|
|
@@ -3100,7 +3663,7 @@ async function loadWsHandler(filePath, ctx, rootDir) {
|
|
|
3100
3663
|
const dist = getDevDist();
|
|
3101
3664
|
if (dist) {
|
|
3102
3665
|
const sourcePath = prodPathToSourcePath(filePath, rootDir, dist);
|
|
3103
|
-
if (sourcePath &&
|
|
3666
|
+
if (sourcePath && fs12.existsSync(sourcePath)) {
|
|
3104
3667
|
await ensureCompiled(sourcePath, rootDir, dist);
|
|
3105
3668
|
}
|
|
3106
3669
|
}
|
|
@@ -3180,7 +3743,7 @@ function attachWebSocket(options) {
|
|
|
3180
3743
|
const finalHandler = async () => {
|
|
3181
3744
|
let handlers;
|
|
3182
3745
|
try {
|
|
3183
|
-
const absoluteFilePath =
|
|
3746
|
+
const absoluteFilePath = path10.resolve(rootDir, route.filePath);
|
|
3184
3747
|
handlers = await loadWsHandler(absoluteFilePath, ctx, rootDir);
|
|
3185
3748
|
} catch (err) {
|
|
3186
3749
|
const reason = err instanceof Error ? err.message : String(err);
|
|
@@ -3257,37 +3820,63 @@ function toWebRequest(req, bodyLimit = DEFAULT_BODY_LIMIT) {
|
|
|
3257
3820
|
}
|
|
3258
3821
|
function limitStreamSize(stream, maxSize) {
|
|
3259
3822
|
let totalSize = 0;
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
controller.close();
|
|
3823
|
+
let reader;
|
|
3824
|
+
let errored = false;
|
|
3825
|
+
const releaseReader = () => {
|
|
3826
|
+
if (reader) {
|
|
3827
|
+
try {
|
|
3266
3828
|
reader.releaseLock();
|
|
3267
|
-
|
|
3829
|
+
} catch {
|
|
3268
3830
|
}
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3831
|
+
reader = void 0;
|
|
3832
|
+
}
|
|
3833
|
+
};
|
|
3834
|
+
const failStream = (controller, err) => {
|
|
3835
|
+
if (errored) return;
|
|
3836
|
+
errored = true;
|
|
3837
|
+
controller.error(err instanceof Error ? err : new Error(String(err)));
|
|
3838
|
+
releaseReader();
|
|
3839
|
+
};
|
|
3840
|
+
return new ReadableStream({
|
|
3841
|
+
async pull(controller) {
|
|
3842
|
+
if (!reader) reader = stream.getReader();
|
|
3843
|
+
try {
|
|
3844
|
+
const { done, value } = await reader.read();
|
|
3845
|
+
if (done) {
|
|
3846
|
+
controller.close();
|
|
3847
|
+
releaseReader();
|
|
3848
|
+
return;
|
|
3849
|
+
}
|
|
3850
|
+
totalSize += value.byteLength;
|
|
3851
|
+
if (totalSize > maxSize) {
|
|
3852
|
+
failStream(controller, new PayloadTooLargeError(maxSize));
|
|
3853
|
+
return;
|
|
3854
|
+
}
|
|
3855
|
+
controller.enqueue(value);
|
|
3856
|
+
} catch (err) {
|
|
3857
|
+
failStream(controller, err);
|
|
3274
3858
|
}
|
|
3275
|
-
controller.enqueue(value);
|
|
3276
3859
|
},
|
|
3277
3860
|
cancel(reason) {
|
|
3278
|
-
reader
|
|
3861
|
+
if (reader) {
|
|
3862
|
+
try {
|
|
3863
|
+
reader.cancel(reason);
|
|
3864
|
+
} catch {
|
|
3865
|
+
}
|
|
3866
|
+
releaseReader();
|
|
3867
|
+
}
|
|
3279
3868
|
}
|
|
3280
3869
|
});
|
|
3281
3870
|
}
|
|
3282
|
-
function findAllowedMethods(routes,
|
|
3871
|
+
function findAllowedMethods(routes, path18) {
|
|
3283
3872
|
const methods = /* @__PURE__ */ new Set();
|
|
3284
3873
|
for (const route of routes) {
|
|
3285
|
-
if (route.urlPath ===
|
|
3874
|
+
if (route.urlPath === path18) {
|
|
3286
3875
|
methods.add(route.method);
|
|
3287
3876
|
continue;
|
|
3288
3877
|
}
|
|
3289
3878
|
if (route.isDynamic) {
|
|
3290
|
-
const params = matchDynamicPath(route.urlPath,
|
|
3879
|
+
const params = matchDynamicPath(route.urlPath, path18, route.paramNames, route.isCatchAll);
|
|
3291
3880
|
if (params !== null) {
|
|
3292
3881
|
methods.add(route.method);
|
|
3293
3882
|
}
|
|
@@ -3356,24 +3945,30 @@ function createServer(options) {
|
|
|
3356
3945
|
}
|
|
3357
3946
|
return { server, routesRef };
|
|
3358
3947
|
}
|
|
3359
|
-
|
|
3948
|
+
function prepareRequest(req, config, bodyLimit) {
|
|
3360
3949
|
const request = toWebRequest(req, bodyLimit);
|
|
3361
3950
|
const method = request.method.toUpperCase();
|
|
3362
3951
|
const urlPath = new URL(request.url).pathname;
|
|
3363
3952
|
const ctx = createContext(request, {}, config, getClientIp(req));
|
|
3364
3953
|
const meta = ctx.meta;
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3954
|
+
return { request, ctx, meta, method, urlPath };
|
|
3955
|
+
}
|
|
3956
|
+
function resolveRouteOrThrow(routes, method, urlPath) {
|
|
3957
|
+
const match = matchRoute(routes, method, urlPath);
|
|
3958
|
+
if (match) return match;
|
|
3959
|
+
const allowedMethods = findAllowedMethods(routes, urlPath);
|
|
3960
|
+
if (allowedMethods.length > 0) {
|
|
3961
|
+
throw new MethodNotAllowedError(method, urlPath, allowedMethods);
|
|
3962
|
+
}
|
|
3963
|
+
throw new RouteNotFoundError(urlPath);
|
|
3964
|
+
}
|
|
3965
|
+
function createRoutePipeline(opts) {
|
|
3966
|
+
const { routes, method, urlPath, ctx, request, rootDir, dist, globalInjectors } = opts;
|
|
3967
|
+
return async () => {
|
|
3968
|
+
const match = resolveRouteOrThrow(routes, method, urlPath);
|
|
3374
3969
|
ctx.params = match.params;
|
|
3375
3970
|
const { route } = match;
|
|
3376
|
-
const absoluteFilePath =
|
|
3971
|
+
const absoluteFilePath = path11.resolve(rootDir, route.filePath);
|
|
3377
3972
|
const routeModule = await loadRouteModule(absoluteFilePath, route.method, rootDir);
|
|
3378
3973
|
const input = await resolveInput(route.method, request);
|
|
3379
3974
|
const inputType = getInputTypeForMethod(route.method);
|
|
@@ -3400,37 +3995,44 @@ async function handleRequest(routes, rootDir, dist, req, res, configMiddlewares,
|
|
|
3400
3995
|
}
|
|
3401
3996
|
}
|
|
3402
3997
|
const mergedInjectors = globalInjectors ? { ...globalInjectors, ...route.injectors } : route.injectors;
|
|
3403
|
-
|
|
3404
|
-
routeModule.handler,
|
|
3405
|
-
ctx,
|
|
3406
|
-
body,
|
|
3407
|
-
route.middlewares,
|
|
3408
|
-
mergedInjectors
|
|
3409
|
-
);
|
|
3410
|
-
return response;
|
|
3998
|
+
return await invokeHandler(routeModule.handler, ctx, body, route.middlewares, mergedInjectors);
|
|
3411
3999
|
};
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
}
|
|
3422
|
-
response = await routePipeline();
|
|
4000
|
+
}
|
|
4001
|
+
async function sendSuccessResponse(response, res) {
|
|
4002
|
+
await sendNodeResponse(response, res);
|
|
4003
|
+
}
|
|
4004
|
+
async function sendErrorResponse(err, meta, res, onError, ctx) {
|
|
4005
|
+
await sendNodeResponse(mergeMeta(buildErrorResponse(err, ctx.config), meta), res);
|
|
4006
|
+
if (onError) {
|
|
4007
|
+
try {
|
|
4008
|
+
await onError(err, ctx);
|
|
4009
|
+
} catch {
|
|
3423
4010
|
}
|
|
3424
|
-
|
|
4011
|
+
}
|
|
4012
|
+
}
|
|
4013
|
+
async function handleRequest(routes, rootDir, dist, req, res, configMiddlewares, onError, config, globalMiddlewares, globalInjectors, bodyLimit) {
|
|
4014
|
+
const { request, ctx, meta, method, urlPath } = prepareRequest(req, config, bodyLimit);
|
|
4015
|
+
const routePipeline = createRoutePipeline({
|
|
4016
|
+
routes,
|
|
4017
|
+
method,
|
|
4018
|
+
urlPath,
|
|
4019
|
+
ctx,
|
|
4020
|
+
request,
|
|
4021
|
+
rootDir,
|
|
4022
|
+
dist,
|
|
4023
|
+
globalMiddlewares,
|
|
4024
|
+
globalInjectors
|
|
4025
|
+
});
|
|
4026
|
+
const outerMiddlewares = [];
|
|
4027
|
+
if (configMiddlewares.length > 0) outerMiddlewares.push(...configMiddlewares);
|
|
4028
|
+
if (globalMiddlewares && globalMiddlewares.length > 0) {
|
|
4029
|
+
outerMiddlewares.push(...globalMiddlewares);
|
|
4030
|
+
}
|
|
4031
|
+
try {
|
|
4032
|
+
const response = outerMiddlewares.length > 0 ? await compose(outerMiddlewares, ctx, routePipeline) : await routePipeline();
|
|
4033
|
+
await sendSuccessResponse(response, res);
|
|
3425
4034
|
} catch (err) {
|
|
3426
|
-
|
|
3427
|
-
await sendNodeResponse(mergeMeta(errorResponse, meta), res);
|
|
3428
|
-
if (onError) {
|
|
3429
|
-
try {
|
|
3430
|
-
await onError(err, ctx);
|
|
3431
|
-
} catch {
|
|
3432
|
-
}
|
|
3433
|
-
}
|
|
4035
|
+
await sendErrorResponse(err, meta, res, onError, ctx);
|
|
3434
4036
|
}
|
|
3435
4037
|
}
|
|
3436
4038
|
|
|
@@ -3463,8 +4065,8 @@ function applyPluginWrappers(server, handlerWrappers, upgradeWrappers) {
|
|
|
3463
4065
|
}
|
|
3464
4066
|
|
|
3465
4067
|
// src/cli/generateRoutes.ts
|
|
3466
|
-
import
|
|
3467
|
-
import
|
|
4068
|
+
import fs13 from "fs";
|
|
4069
|
+
import path12 from "path";
|
|
3468
4070
|
async function hydrateRoutes(manifest) {
|
|
3469
4071
|
const hydrateRoute = (serialized) => ({
|
|
3470
4072
|
method: serialized.method,
|
|
@@ -3488,6 +4090,262 @@ async function hydrateRoutes(manifest) {
|
|
|
3488
4090
|
return { routes, wsRoutes };
|
|
3489
4091
|
}
|
|
3490
4092
|
|
|
4093
|
+
// src/cli/generateAgentArtifacts.ts
|
|
4094
|
+
import path13 from "path";
|
|
4095
|
+
import fs14 from "fs/promises";
|
|
4096
|
+
|
|
4097
|
+
// src/ast/extractAgentMetadata.ts
|
|
4098
|
+
import ts8 from "typescript";
|
|
4099
|
+
function extractAgentMetadata(program, filePath, pathMeta) {
|
|
4100
|
+
const sourceFile = program.getSourceFile(filePath);
|
|
4101
|
+
if (!sourceFile) return null;
|
|
4102
|
+
const configFound = findConfigExport(sourceFile);
|
|
4103
|
+
let jsDocOwner = null;
|
|
4104
|
+
let objectLiteral = null;
|
|
4105
|
+
if (configFound) {
|
|
4106
|
+
jsDocOwner = configFound.jsDocOwner;
|
|
4107
|
+
objectLiteral = configFound.objectLiteral;
|
|
4108
|
+
} else if (pathMeta.hasRun) {
|
|
4109
|
+
const runNode = findRunExport(sourceFile);
|
|
4110
|
+
if (runNode) {
|
|
4111
|
+
jsDocOwner = runNode;
|
|
4112
|
+
}
|
|
4113
|
+
}
|
|
4114
|
+
const jsDoc = jsDocOwner ? getJSDocFromNode2(jsDocOwner) : void 0;
|
|
4115
|
+
const description = extractDescription2(jsDoc);
|
|
4116
|
+
const agentNameOverride = extractAgentTagValue(jsDoc);
|
|
4117
|
+
let systemPrompt;
|
|
4118
|
+
let tools;
|
|
4119
|
+
let agents;
|
|
4120
|
+
let model;
|
|
4121
|
+
let maxTurns;
|
|
4122
|
+
if (objectLiteral) {
|
|
4123
|
+
const fields = extractConfigFields(objectLiteral);
|
|
4124
|
+
systemPrompt = fields.systemPrompt;
|
|
4125
|
+
tools = fields.tools;
|
|
4126
|
+
agents = fields.agents;
|
|
4127
|
+
model = fields.model;
|
|
4128
|
+
maxTurns = fields.maxTurns;
|
|
4129
|
+
}
|
|
4130
|
+
return {
|
|
4131
|
+
name: agentNameOverride ?? pathMeta.name,
|
|
4132
|
+
description,
|
|
4133
|
+
filePath: pathMeta.filePath,
|
|
4134
|
+
hasRun: pathMeta.hasRun,
|
|
4135
|
+
systemPrompt,
|
|
4136
|
+
tools,
|
|
4137
|
+
agents,
|
|
4138
|
+
model,
|
|
4139
|
+
maxTurns
|
|
4140
|
+
};
|
|
4141
|
+
}
|
|
4142
|
+
function findConfigExport(sourceFile) {
|
|
4143
|
+
let result = null;
|
|
4144
|
+
ts8.forEachChild(sourceFile, (node) => {
|
|
4145
|
+
if (result) return;
|
|
4146
|
+
if (ts8.isVariableStatement(node) && hasExportModifier2(node)) {
|
|
4147
|
+
for (const decl of node.declarationList.declarations) {
|
|
4148
|
+
if (result) break;
|
|
4149
|
+
const nameText = ts8.isIdentifier(decl.name) ? decl.name.text : "";
|
|
4150
|
+
if (nameText !== "config" || !decl.initializer) continue;
|
|
4151
|
+
if (ts8.isObjectLiteralExpression(decl.initializer)) {
|
|
4152
|
+
result = { jsDocOwner: node, objectLiteral: decl.initializer };
|
|
4153
|
+
} else if (ts8.isArrowFunction(decl.initializer)) {
|
|
4154
|
+
const returnObj = getReturnObjectLiteral(decl.initializer);
|
|
4155
|
+
result = { jsDocOwner: node, objectLiteral: returnObj };
|
|
4156
|
+
}
|
|
4157
|
+
}
|
|
4158
|
+
}
|
|
4159
|
+
if (ts8.isFunctionDeclaration(node) && hasExportModifier2(node) && node.name?.text === "config") {
|
|
4160
|
+
const returnObj = getReturnObjectLiteral(node);
|
|
4161
|
+
result = { jsDocOwner: node, objectLiteral: returnObj };
|
|
4162
|
+
}
|
|
4163
|
+
});
|
|
4164
|
+
return result;
|
|
4165
|
+
}
|
|
4166
|
+
function findRunExport(sourceFile) {
|
|
4167
|
+
let result = null;
|
|
4168
|
+
ts8.forEachChild(sourceFile, (node) => {
|
|
4169
|
+
if (result) return;
|
|
4170
|
+
if (ts8.isFunctionDeclaration(node) && hasExportModifier2(node) && node.name?.text === "run") {
|
|
4171
|
+
result = node;
|
|
4172
|
+
return;
|
|
4173
|
+
}
|
|
4174
|
+
if (ts8.isVariableStatement(node) && hasExportModifier2(node)) {
|
|
4175
|
+
for (const decl of node.declarationList.declarations) {
|
|
4176
|
+
if (result) break;
|
|
4177
|
+
const nameText = ts8.isIdentifier(decl.name) ? decl.name.text : "";
|
|
4178
|
+
if (nameText !== "run" || !decl.initializer) continue;
|
|
4179
|
+
if (ts8.isArrowFunction(decl.initializer) || ts8.isFunctionExpression(decl.initializer)) {
|
|
4180
|
+
result = node;
|
|
4181
|
+
}
|
|
4182
|
+
}
|
|
4183
|
+
}
|
|
4184
|
+
});
|
|
4185
|
+
return result;
|
|
4186
|
+
}
|
|
4187
|
+
function getReturnObjectLiteral(fn) {
|
|
4188
|
+
const body = fn.body;
|
|
4189
|
+
if (!body) return null;
|
|
4190
|
+
if (ts8.isObjectLiteralExpression(body)) {
|
|
4191
|
+
return body;
|
|
4192
|
+
}
|
|
4193
|
+
if (ts8.isBlock(body)) {
|
|
4194
|
+
for (const stmt of body.statements) {
|
|
4195
|
+
if (ts8.isReturnStatement(stmt) && stmt.expression && ts8.isObjectLiteralExpression(stmt.expression)) {
|
|
4196
|
+
return stmt.expression;
|
|
4197
|
+
}
|
|
4198
|
+
}
|
|
4199
|
+
}
|
|
4200
|
+
return null;
|
|
4201
|
+
}
|
|
4202
|
+
function hasExportModifier2(node) {
|
|
4203
|
+
if (!ts8.canHaveModifiers(node)) return false;
|
|
4204
|
+
const modifiers = ts8.getModifiers(node);
|
|
4205
|
+
return !!modifiers?.some((m) => m.kind === ts8.SyntaxKind.ExportKeyword);
|
|
4206
|
+
}
|
|
4207
|
+
function getJSDocFromNode2(node) {
|
|
4208
|
+
const apiDocs = ts8.getJSDocCommentsAndTags(node).filter((entry) => ts8.isJSDoc(entry));
|
|
4209
|
+
if (apiDocs.length > 0) return apiDocs[0];
|
|
4210
|
+
const directDocs = node.jsDoc;
|
|
4211
|
+
if (directDocs && directDocs.length > 0) return directDocs[0];
|
|
4212
|
+
return void 0;
|
|
4213
|
+
}
|
|
4214
|
+
function extractDescription2(jsDoc) {
|
|
4215
|
+
if (!jsDoc) return void 0;
|
|
4216
|
+
if (typeof jsDoc.comment !== "string") return void 0;
|
|
4217
|
+
const trimmed = jsDoc.comment.trim();
|
|
4218
|
+
return trimmed || void 0;
|
|
4219
|
+
}
|
|
4220
|
+
function extractAgentTagValue(jsDoc) {
|
|
4221
|
+
if (!jsDoc || !jsDoc.tags) return void 0;
|
|
4222
|
+
for (const tag of jsDoc.tags) {
|
|
4223
|
+
if (tag.tagName.text !== "agent") continue;
|
|
4224
|
+
if (typeof tag.comment !== "string") return void 0;
|
|
4225
|
+
const text = tag.comment.trim();
|
|
4226
|
+
if (!text) return void 0;
|
|
4227
|
+
const cleaned = text.replace(/^\{|\}$/g, "").trim();
|
|
4228
|
+
return cleaned || void 0;
|
|
4229
|
+
}
|
|
4230
|
+
return void 0;
|
|
4231
|
+
}
|
|
4232
|
+
function extractConfigFields(objLit) {
|
|
4233
|
+
const result = {};
|
|
4234
|
+
for (const prop of objLit.properties) {
|
|
4235
|
+
if (!ts8.isPropertyAssignment(prop)) continue;
|
|
4236
|
+
const propName = getPropertyName(prop.name);
|
|
4237
|
+
if (!propName) continue;
|
|
4238
|
+
switch (propName) {
|
|
4239
|
+
case "systemPrompt":
|
|
4240
|
+
result.systemPrompt = extractStringValue(prop.initializer);
|
|
4241
|
+
break;
|
|
4242
|
+
case "tools":
|
|
4243
|
+
result.tools = extractStringArrayValue(prop.initializer);
|
|
4244
|
+
break;
|
|
4245
|
+
case "agents":
|
|
4246
|
+
result.agents = extractStringArrayValue(prop.initializer);
|
|
4247
|
+
break;
|
|
4248
|
+
case "model":
|
|
4249
|
+
result.model = extractStringValue(prop.initializer);
|
|
4250
|
+
break;
|
|
4251
|
+
case "maxTurns":
|
|
4252
|
+
result.maxTurns = extractNumberValue(prop.initializer);
|
|
4253
|
+
break;
|
|
4254
|
+
}
|
|
4255
|
+
}
|
|
4256
|
+
return result;
|
|
4257
|
+
}
|
|
4258
|
+
function getPropertyName(name) {
|
|
4259
|
+
if (ts8.isIdentifier(name)) return name.text;
|
|
4260
|
+
if (ts8.isStringLiteral(name)) return name.text;
|
|
4261
|
+
return null;
|
|
4262
|
+
}
|
|
4263
|
+
function extractStringValue(expr) {
|
|
4264
|
+
if (ts8.isStringLiteral(expr)) return expr.text;
|
|
4265
|
+
return void 0;
|
|
4266
|
+
}
|
|
4267
|
+
function extractNumberValue(expr) {
|
|
4268
|
+
if (ts8.isNumericLiteral(expr)) {
|
|
4269
|
+
const num = Number(expr.text);
|
|
4270
|
+
return Number.isNaN(num) ? void 0 : num;
|
|
4271
|
+
}
|
|
4272
|
+
return void 0;
|
|
4273
|
+
}
|
|
4274
|
+
function extractStringArrayValue(expr) {
|
|
4275
|
+
if (!ts8.isArrayLiteralExpression(expr)) return void 0;
|
|
4276
|
+
const values = [];
|
|
4277
|
+
for (const element of expr.elements) {
|
|
4278
|
+
if (!ts8.isStringLiteral(element)) return void 0;
|
|
4279
|
+
values.push(element.text);
|
|
4280
|
+
}
|
|
4281
|
+
return values;
|
|
4282
|
+
}
|
|
4283
|
+
|
|
4284
|
+
// src/cli/generateAgentArtifacts.ts
|
|
4285
|
+
init_createProgram();
|
|
4286
|
+
var AGENTS_FILE = "faapi-agents.js";
|
|
4287
|
+
function toProdFilePath2(filePath, dist) {
|
|
4288
|
+
let rel = filePath.replace(/\\/g, "/");
|
|
4289
|
+
if (rel.startsWith("src/")) {
|
|
4290
|
+
rel = rel.slice(4);
|
|
4291
|
+
}
|
|
4292
|
+
const jsPath = rel.replace(/\.ts$/, ".js");
|
|
4293
|
+
return jsPath.startsWith(`${dist}/`) ? jsPath : `${dist}/${jsPath}`;
|
|
4294
|
+
}
|
|
4295
|
+
function serializeAgents(agents, dist = "dist") {
|
|
4296
|
+
return agents.map((a) => ({
|
|
4297
|
+
name: a.name,
|
|
4298
|
+
description: a.description,
|
|
4299
|
+
hasRun: a.hasRun,
|
|
4300
|
+
systemPrompt: a.systemPrompt,
|
|
4301
|
+
tools: a.tools,
|
|
4302
|
+
agents: a.agents,
|
|
4303
|
+
model: a.model,
|
|
4304
|
+
maxTurns: a.maxTurns,
|
|
4305
|
+
filePath: toProdFilePath2(a.filePath, dist)
|
|
4306
|
+
}));
|
|
4307
|
+
}
|
|
4308
|
+
async function writeAgentsModule(manifest, outputPath) {
|
|
4309
|
+
const dir = path13.dirname(outputPath);
|
|
4310
|
+
await fs14.mkdir(dir, { recursive: true });
|
|
4311
|
+
const content = `// \u81EA\u52A8\u751F\u6210,\u8BF7\u52FF\u624B\u52A8\u7F16\u8F91(faapi build/dev \u4EA7\u7269)
|
|
4312
|
+
export const agents = ${JSON.stringify(manifest, null, 2)};
|
|
4313
|
+
`;
|
|
4314
|
+
await fs14.writeFile(outputPath, content, "utf-8");
|
|
4315
|
+
}
|
|
4316
|
+
function hydrateAgents(manifest) {
|
|
4317
|
+
return manifest.map((a) => ({
|
|
4318
|
+
name: a.name,
|
|
4319
|
+
description: a.description ?? void 0,
|
|
4320
|
+
filePath: a.filePath,
|
|
4321
|
+
hasRun: a.hasRun,
|
|
4322
|
+
systemPrompt: a.systemPrompt ?? void 0,
|
|
4323
|
+
tools: a.tools ?? void 0,
|
|
4324
|
+
agents: a.agents ?? void 0,
|
|
4325
|
+
model: a.model ?? void 0,
|
|
4326
|
+
maxTurns: a.maxTurns ?? void 0
|
|
4327
|
+
}));
|
|
4328
|
+
}
|
|
4329
|
+
async function generateAgentArtifacts(agents, rootDir, dist) {
|
|
4330
|
+
const metadata = [];
|
|
4331
|
+
for (const manifest of agents) {
|
|
4332
|
+
const absPath = path13.resolve(rootDir, manifest.filePath);
|
|
4333
|
+
const program = createProgram(absPath);
|
|
4334
|
+
const result = extractAgentMetadata(program, absPath, {
|
|
4335
|
+
name: manifest.name,
|
|
4336
|
+
filePath: manifest.filePath,
|
|
4337
|
+
hasRun: manifest.hasRun
|
|
4338
|
+
});
|
|
4339
|
+
if (result) {
|
|
4340
|
+
metadata.push(result);
|
|
4341
|
+
}
|
|
4342
|
+
}
|
|
4343
|
+
const serialized = serializeAgents(metadata, dist);
|
|
4344
|
+
const agentsPath = path13.resolve(rootDir, dist, AGENTS_FILE);
|
|
4345
|
+
await writeAgentsModule(serialized, agentsPath);
|
|
4346
|
+
return metadata;
|
|
4347
|
+
}
|
|
4348
|
+
|
|
3491
4349
|
// src/cli/loadPlugins.ts
|
|
3492
4350
|
async function loadPlugins(declarations, ctx) {
|
|
3493
4351
|
const handlerWrappers = [];
|
|
@@ -3551,7 +4409,29 @@ function resolveDeclaration(decl) {
|
|
|
3551
4409
|
var DEFAULT_DIST = "dist";
|
|
3552
4410
|
var DEFAULT_PORT = 3e3;
|
|
3553
4411
|
var ROUTES_FILE = "faapi-routes.js";
|
|
4412
|
+
var TOOLS_FILE2 = "faapi-tools.js";
|
|
4413
|
+
var AGENTS_FILE2 = "faapi-agents.js";
|
|
3554
4414
|
var PATTERNS = ["src/api/**/*.ts"];
|
|
4415
|
+
async function loadAndHydrateTools(rootDir, dist) {
|
|
4416
|
+
const toolsPath = path14.resolve(rootDir, dist, TOOLS_FILE2);
|
|
4417
|
+
if (!fs15.existsSync(toolsPath)) {
|
|
4418
|
+
return [];
|
|
4419
|
+
}
|
|
4420
|
+
const serialized = await importWithCacheBust(toolsPath);
|
|
4421
|
+
const hydrated = hydrateTools(serialized.tools ?? []);
|
|
4422
|
+
hydrateToolRegistry(hydrated);
|
|
4423
|
+
return hydrated;
|
|
4424
|
+
}
|
|
4425
|
+
async function loadAndHydrateAgents(rootDir, dist) {
|
|
4426
|
+
const agentsPath = path14.resolve(rootDir, dist, AGENTS_FILE2);
|
|
4427
|
+
if (!fs15.existsSync(agentsPath)) {
|
|
4428
|
+
return [];
|
|
4429
|
+
}
|
|
4430
|
+
const serialized = await importWithCacheBust(agentsPath);
|
|
4431
|
+
const hydrated = hydrateAgents(serialized.agents ?? []);
|
|
4432
|
+
hydrateAgentRegistry(hydrated);
|
|
4433
|
+
return hydrated;
|
|
4434
|
+
}
|
|
3555
4435
|
var APP_INSTANCE_KEY = /* @__PURE__ */ Symbol.for("faapi.app.instance");
|
|
3556
4436
|
function getCurrentApp() {
|
|
3557
4437
|
return globalThis[APP_INSTANCE_KEY] ?? null;
|
|
@@ -3591,8 +4471,8 @@ function isFaapiConfigKey(key) {
|
|
|
3591
4471
|
async function createAppBase(options) {
|
|
3592
4472
|
const rootDir = options?.rootDir ?? process.cwd();
|
|
3593
4473
|
const dist = options?.dist ?? process.env.FAAPI_DIST ?? DEFAULT_DIST;
|
|
3594
|
-
const routesPath =
|
|
3595
|
-
if (!
|
|
4474
|
+
const routesPath = path14.resolve(rootDir, dist, ROUTES_FILE);
|
|
4475
|
+
if (!fs15.existsSync(routesPath)) {
|
|
3596
4476
|
throw new Error(
|
|
3597
4477
|
`[faapi] ${dist}/${ROUTES_FILE} \u4E0D\u5B58\u5728\uFF0C\u8BF7\u5148\u6267\u884C \`faapi build\`\uFF08\u6216 \`faapi dev\`\uFF09\u751F\u6210\u4EA7\u7269\u3002`
|
|
3598
4478
|
);
|
|
@@ -3611,6 +4491,8 @@ async function createAppBase(options) {
|
|
|
3611
4491
|
}
|
|
3612
4492
|
}
|
|
3613
4493
|
}
|
|
4494
|
+
const tools = await loadAndHydrateTools(rootDir, dist);
|
|
4495
|
+
const agents = await loadAndHydrateAgents(rootDir, dist);
|
|
3614
4496
|
const pluginConfig = config ? Object.fromEntries(Object.entries(config).filter(([k]) => !isFaapiConfigKey(k))) : {};
|
|
3615
4497
|
const { server, routesRef } = createServer({
|
|
3616
4498
|
routes: sorted,
|
|
@@ -3660,6 +4542,19 @@ async function createAppBase(options) {
|
|
|
3660
4542
|
console.log(` WS ${route.urlPath} ${route.filePath}`);
|
|
3661
4543
|
}
|
|
3662
4544
|
}
|
|
4545
|
+
if (tools.length > 0) {
|
|
4546
|
+
console.log(`- Loaded ${tools.length} tool(s):`);
|
|
4547
|
+
for (const tool of tools) {
|
|
4548
|
+
console.log(` ${tool.name} ${tool.filePath}`);
|
|
4549
|
+
}
|
|
4550
|
+
}
|
|
4551
|
+
if (agents.length > 0) {
|
|
4552
|
+
console.log(`- Loaded ${agents.length} agent(s):`);
|
|
4553
|
+
for (const agent of agents) {
|
|
4554
|
+
const exports = agent.hasRun ? "run" : "-";
|
|
4555
|
+
console.log(` ${agent.name} [${exports}] ${agent.filePath}`);
|
|
4556
|
+
}
|
|
4557
|
+
}
|
|
3663
4558
|
if (config?.lifecycle?.onClose) {
|
|
3664
4559
|
const graceful = async (signal) => {
|
|
3665
4560
|
console.log(`
|
|
@@ -3757,6 +4652,10 @@ async function createAppBase(options) {
|
|
|
3757
4652
|
if (config?.lifecycle?.onClose) {
|
|
3758
4653
|
await config.lifecycle.onClose({ rootDir, routes: sorted, server });
|
|
3759
4654
|
}
|
|
4655
|
+
clearToolRegistry();
|
|
4656
|
+
clearAgentRegistry();
|
|
4657
|
+
clearSkillRegistry();
|
|
4658
|
+
clearAgentHandleFactory();
|
|
3760
4659
|
if (!server.listening) {
|
|
3761
4660
|
app.server = null;
|
|
3762
4661
|
if (getCurrentApp() === app) setCurrentApp(null);
|
|
@@ -3794,17 +4693,17 @@ async function createAppBase(options) {
|
|
|
3794
4693
|
|
|
3795
4694
|
// src/router/scanRoutes.ts
|
|
3796
4695
|
import fg2 from "fast-glob";
|
|
3797
|
-
import
|
|
3798
|
-
import
|
|
4696
|
+
import path15 from "path";
|
|
4697
|
+
import fs16 from "fs";
|
|
3799
4698
|
|
|
3800
4699
|
// src/router/constants.ts
|
|
3801
4700
|
var HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
|
|
3802
4701
|
var HTTP_METHOD_SET = new Set(HTTP_METHODS);
|
|
3803
4702
|
|
|
3804
4703
|
// src/utils/normalizePath.ts
|
|
3805
|
-
function normalizePath(
|
|
3806
|
-
if (!
|
|
3807
|
-
let result =
|
|
4704
|
+
function normalizePath(path18) {
|
|
4705
|
+
if (!path18) return "";
|
|
4706
|
+
let result = path18.replace(/\\/g, "/");
|
|
3808
4707
|
result = result.replace(/\/+/g, "/");
|
|
3809
4708
|
result = result.replace(/\/+$/, "");
|
|
3810
4709
|
if (result && !result.startsWith("/")) {
|
|
@@ -3865,41 +4764,41 @@ function extractExportsFromSource(source) {
|
|
|
3865
4764
|
return names;
|
|
3866
4765
|
}
|
|
3867
4766
|
function collectMiddlewarePaths(routeFilePath, rootDir, dist) {
|
|
3868
|
-
const routeDir =
|
|
3869
|
-
const resolvedRoot =
|
|
4767
|
+
const routeDir = path15.dirname(routeFilePath);
|
|
4768
|
+
const resolvedRoot = path15.resolve(rootDir);
|
|
3870
4769
|
const paths = [];
|
|
3871
|
-
let currentDir =
|
|
4770
|
+
let currentDir = path15.resolve(rootDir, routeDir);
|
|
3872
4771
|
while (true) {
|
|
3873
4772
|
if (dist) {
|
|
3874
|
-
const mwTsPath =
|
|
3875
|
-
const mwJsPath =
|
|
3876
|
-
const absTsPath =
|
|
3877
|
-
const absJsPath =
|
|
3878
|
-
const absMwPath =
|
|
4773
|
+
const mwTsPath = path15.join(currentDir, "middlewares.ts");
|
|
4774
|
+
const mwJsPath = path15.join(currentDir, "middlewares.js");
|
|
4775
|
+
const absTsPath = path15.resolve(rootDir, mwTsPath);
|
|
4776
|
+
const absJsPath = path15.resolve(rootDir, mwJsPath);
|
|
4777
|
+
const absMwPath = fs16.existsSync(absTsPath) ? absTsPath : fs16.existsSync(absJsPath) ? absJsPath : null;
|
|
3879
4778
|
if (absMwPath) {
|
|
3880
|
-
const relMwPath =
|
|
3881
|
-
const prodAbsPath =
|
|
4779
|
+
const relMwPath = path15.relative(rootDir, absMwPath);
|
|
4780
|
+
const prodAbsPath = path15.resolve(rootDir, toProdFilePath3(relMwPath, dist));
|
|
3882
4781
|
paths.push(prodAbsPath);
|
|
3883
4782
|
}
|
|
3884
4783
|
} else {
|
|
3885
4784
|
for (const ext of [".ts", ".js"]) {
|
|
3886
|
-
const mwPath =
|
|
3887
|
-
const absMwPath =
|
|
3888
|
-
if (
|
|
4785
|
+
const mwPath = path15.join(currentDir, `middlewares${ext}`);
|
|
4786
|
+
const absMwPath = path15.resolve(rootDir, mwPath);
|
|
4787
|
+
if (fs16.existsSync(absMwPath)) {
|
|
3889
4788
|
paths.push(absMwPath);
|
|
3890
4789
|
break;
|
|
3891
4790
|
}
|
|
3892
4791
|
}
|
|
3893
4792
|
}
|
|
3894
4793
|
if (currentDir === resolvedRoot) break;
|
|
3895
|
-
const parentDir =
|
|
4794
|
+
const parentDir = path15.dirname(currentDir);
|
|
3896
4795
|
if (parentDir === currentDir) break;
|
|
3897
4796
|
currentDir = parentDir;
|
|
3898
4797
|
}
|
|
3899
4798
|
paths.reverse();
|
|
3900
4799
|
return paths;
|
|
3901
4800
|
}
|
|
3902
|
-
function
|
|
4801
|
+
function toProdFilePath3(filePath, dist) {
|
|
3903
4802
|
let rel = filePath.replace(/\\/g, "/");
|
|
3904
4803
|
if (rel.startsWith("src/")) {
|
|
3905
4804
|
rel = rel.slice(4);
|
|
@@ -3919,7 +4818,7 @@ async function scanRoutes(rootDir, patterns, dist) {
|
|
|
3919
4818
|
const normalizedFile = file.replace(/\\/g, "/");
|
|
3920
4819
|
const fileName = normalizedFile.split("/").pop();
|
|
3921
4820
|
if (fileName === "handler.ts" || fileName === "handler.js") {
|
|
3922
|
-
const absPath =
|
|
4821
|
+
const absPath = path15.resolve(rootDir, normalizedFile);
|
|
3923
4822
|
const urlPath = filePathToUrlPath(normalizedFile);
|
|
3924
4823
|
const paramNames = extractParamNames(urlPath);
|
|
3925
4824
|
const isDynamic = paramNames.length > 0;
|
|
@@ -3932,7 +4831,7 @@ async function scanRoutes(rootDir, patterns, dist) {
|
|
|
3932
4831
|
const mwPaths = collectMiddlewarePaths(normalizedFile, rootDir);
|
|
3933
4832
|
middlewareBundle = await loadMergedMiddlewares(mwPaths);
|
|
3934
4833
|
}
|
|
3935
|
-
const source = await
|
|
4834
|
+
const source = await fs16.promises.readFile(absPath, "utf8").catch(() => "");
|
|
3936
4835
|
const exportNames = extractExportsFromSource(source);
|
|
3937
4836
|
const methods = HTTP_METHODS.filter((m) => exportNames.has(m));
|
|
3938
4837
|
for (const method of methods) {
|
|
@@ -3966,6 +4865,137 @@ async function scanRoutes(rootDir, patterns, dist) {
|
|
|
3966
4865
|
return { routes, wsRoutes };
|
|
3967
4866
|
}
|
|
3968
4867
|
|
|
4868
|
+
// src/tools/scanTools.ts
|
|
4869
|
+
import fg3 from "fast-glob";
|
|
4870
|
+
import path16 from "path";
|
|
4871
|
+
import fs17 from "fs";
|
|
4872
|
+
var TOOL_PATTERNS = ["src/tools/**/*.ts"];
|
|
4873
|
+
var TOOL_EXPORT_RE = new RegExp(
|
|
4874
|
+
String.raw`export\s+(?:async\s+)?(?:function\s+|const\s+)([A-Za-z_$][\w$]*)\s*(?:\(|=)`,
|
|
4875
|
+
"g"
|
|
4876
|
+
);
|
|
4877
|
+
var RESERVED_EXPORTS = /* @__PURE__ */ new Set(["default", "config", "run"]);
|
|
4878
|
+
function extractToolExportsFromSource(source) {
|
|
4879
|
+
const names = /* @__PURE__ */ new Set();
|
|
4880
|
+
let match;
|
|
4881
|
+
TOOL_EXPORT_RE.lastIndex = 0;
|
|
4882
|
+
while ((match = TOOL_EXPORT_RE.exec(source)) !== null) {
|
|
4883
|
+
const name = match[1];
|
|
4884
|
+
if (!RESERVED_EXPORTS.has(name)) {
|
|
4885
|
+
names.add(name);
|
|
4886
|
+
}
|
|
4887
|
+
}
|
|
4888
|
+
return names;
|
|
4889
|
+
}
|
|
4890
|
+
function extractNamespaceFromRelPath(relPath) {
|
|
4891
|
+
const lastSlash = relPath.lastIndexOf("/");
|
|
4892
|
+
const dirPath = lastSlash === -1 ? "" : relPath.slice(0, lastSlash);
|
|
4893
|
+
if (!dirPath) return "";
|
|
4894
|
+
return dirPath.split("/").join(".");
|
|
4895
|
+
}
|
|
4896
|
+
function filePathToToolNamespace(filePath) {
|
|
4897
|
+
const normalized = filePath.replace(/\\/g, "/");
|
|
4898
|
+
const toolsMatch = normalized.match(/(?:^|\/)tools\/(.+)$/);
|
|
4899
|
+
if (toolsMatch) {
|
|
4900
|
+
return extractNamespaceFromRelPath(toolsMatch[1]);
|
|
4901
|
+
}
|
|
4902
|
+
return "";
|
|
4903
|
+
}
|
|
4904
|
+
function buildToolName(namespace, functionName) {
|
|
4905
|
+
return namespace ? `${namespace}.${functionName}` : functionName;
|
|
4906
|
+
}
|
|
4907
|
+
async function scanTools(rootDir, patterns) {
|
|
4908
|
+
const files = await fg3(patterns, {
|
|
4909
|
+
cwd: rootDir,
|
|
4910
|
+
onlyFiles: true,
|
|
4911
|
+
absolute: false
|
|
4912
|
+
});
|
|
4913
|
+
const tools = [];
|
|
4914
|
+
const seen = /* @__PURE__ */ new Map();
|
|
4915
|
+
for (const file of files) {
|
|
4916
|
+
const normalizedFile = file.replace(/\\/g, "/");
|
|
4917
|
+
const fileName = normalizedFile.split("/").pop();
|
|
4918
|
+
if (fileName !== "handler.ts" && fileName !== "handler.js") {
|
|
4919
|
+
continue;
|
|
4920
|
+
}
|
|
4921
|
+
const absPath = path16.resolve(rootDir, normalizedFile);
|
|
4922
|
+
const source = await fs17.promises.readFile(absPath, "utf8").catch(() => "");
|
|
4923
|
+
const exportNames = extractToolExportsFromSource(source);
|
|
4924
|
+
const namespace = filePathToToolNamespace(normalizedFile);
|
|
4925
|
+
for (const fnName of exportNames) {
|
|
4926
|
+
const toolName = buildToolName(namespace, fnName);
|
|
4927
|
+
const prevFile = seen.get(toolName);
|
|
4928
|
+
if (prevFile) {
|
|
4929
|
+
throw new Error(
|
|
4930
|
+
`Tool conflict: "${toolName}" declared in both ${prevFile} and ${normalizedFile}`
|
|
4931
|
+
);
|
|
4932
|
+
}
|
|
4933
|
+
seen.set(toolName, normalizedFile);
|
|
4934
|
+
tools.push({
|
|
4935
|
+
name: toolName,
|
|
4936
|
+
functionName: fnName,
|
|
4937
|
+
filePath: normalizedFile
|
|
4938
|
+
});
|
|
4939
|
+
}
|
|
4940
|
+
}
|
|
4941
|
+
return tools;
|
|
4942
|
+
}
|
|
4943
|
+
|
|
4944
|
+
// src/agents/scanAgents.ts
|
|
4945
|
+
import fg4 from "fast-glob";
|
|
4946
|
+
import path17 from "path";
|
|
4947
|
+
import fs18 from "fs";
|
|
4948
|
+
var DEFAULT_AGENT_PATTERNS = ["src/agents/*/handler.ts"];
|
|
4949
|
+
var RUN_EXPORT_RE = /export\s+(?:async\s+)?(?:function\s+|const\s+)run\b/;
|
|
4950
|
+
function extractAgentNameFromPath(filePath) {
|
|
4951
|
+
const normalized = filePath.replace(/\\/g, "/");
|
|
4952
|
+
const match = normalized.match(/(?:^|\/)agents\/([^/]+)\/handler\.ts$/);
|
|
4953
|
+
if (!match) {
|
|
4954
|
+
throw new Error(
|
|
4955
|
+
`Not an agent path: "${filePath}". Expected pattern: src/agents/<name>/handler.ts`
|
|
4956
|
+
);
|
|
4957
|
+
}
|
|
4958
|
+
return match[1];
|
|
4959
|
+
}
|
|
4960
|
+
function detectAgentExports(source) {
|
|
4961
|
+
return {
|
|
4962
|
+
hasRun: RUN_EXPORT_RE.test(source)
|
|
4963
|
+
};
|
|
4964
|
+
}
|
|
4965
|
+
async function scanAgents(rootDir, patterns) {
|
|
4966
|
+
const files = await fg4(patterns, {
|
|
4967
|
+
cwd: rootDir,
|
|
4968
|
+
onlyFiles: true,
|
|
4969
|
+
absolute: false
|
|
4970
|
+
});
|
|
4971
|
+
const agents = [];
|
|
4972
|
+
const seen = /* @__PURE__ */ new Map();
|
|
4973
|
+
for (const file of files) {
|
|
4974
|
+
const normalizedFile = file.replace(/\\/g, "/");
|
|
4975
|
+
const fileName = normalizedFile.split("/").pop();
|
|
4976
|
+
if (fileName !== "handler.ts" && fileName !== "handler.js") {
|
|
4977
|
+
continue;
|
|
4978
|
+
}
|
|
4979
|
+
const absPath = path17.resolve(rootDir, normalizedFile);
|
|
4980
|
+
const source = await fs18.promises.readFile(absPath, "utf8").catch(() => "");
|
|
4981
|
+
const { hasRun } = detectAgentExports(source);
|
|
4982
|
+
const name = extractAgentNameFromPath(normalizedFile);
|
|
4983
|
+
const prevFile = seen.get(name);
|
|
4984
|
+
if (prevFile) {
|
|
4985
|
+
throw new Error(
|
|
4986
|
+
`Agent conflict: "${name}" declared in both ${prevFile} and ${normalizedFile}`
|
|
4987
|
+
);
|
|
4988
|
+
}
|
|
4989
|
+
seen.set(name, normalizedFile);
|
|
4990
|
+
agents.push({
|
|
4991
|
+
name,
|
|
4992
|
+
filePath: normalizedFile,
|
|
4993
|
+
hasRun
|
|
4994
|
+
});
|
|
4995
|
+
}
|
|
4996
|
+
return agents;
|
|
4997
|
+
}
|
|
4998
|
+
|
|
3969
4999
|
// src/cli/createDevApp.ts
|
|
3970
5000
|
init_createProgram();
|
|
3971
5001
|
async function createDevApp(options) {
|
|
@@ -3988,6 +5018,22 @@ async function createDevApp(options) {
|
|
|
3988
5018
|
}
|
|
3989
5019
|
ctx.updateRoutes(sorted, reScanned.wsRoutes);
|
|
3990
5020
|
};
|
|
5021
|
+
devApp.reloadTools = async () => {
|
|
5022
|
+
setLoadTimestamp(Date.now());
|
|
5023
|
+
invalidateProgramCache();
|
|
5024
|
+
const tools = await scanTools(ctx.rootDir, TOOL_PATTERNS);
|
|
5025
|
+
await generateToolArtifacts(tools, ctx.rootDir, ctx.dist, {
|
|
5026
|
+
skipSchema: isDevOnDemandEnabled()
|
|
5027
|
+
});
|
|
5028
|
+
await loadAndHydrateTools(ctx.rootDir, ctx.dist);
|
|
5029
|
+
};
|
|
5030
|
+
devApp.reloadAgents = async () => {
|
|
5031
|
+
setLoadTimestamp(Date.now());
|
|
5032
|
+
invalidateProgramCache();
|
|
5033
|
+
const agents = await scanAgents(ctx.rootDir, DEFAULT_AGENT_PATTERNS);
|
|
5034
|
+
await generateAgentArtifacts(agents, ctx.rootDir, ctx.dist);
|
|
5035
|
+
await loadAndHydrateAgents(ctx.rootDir, ctx.dist);
|
|
5036
|
+
};
|
|
3991
5037
|
return devApp;
|
|
3992
5038
|
}
|
|
3993
5039
|
|
|
@@ -4004,6 +5050,7 @@ export {
|
|
|
4004
5050
|
RouteNotFoundError,
|
|
4005
5051
|
SchemaExtractionError,
|
|
4006
5052
|
ValidationError,
|
|
5053
|
+
clearAgentHandleFactory,
|
|
4007
5054
|
collectRouteSchemaSources,
|
|
4008
5055
|
cors,
|
|
4009
5056
|
createProdApp as createApp,
|
|
@@ -4011,13 +5058,27 @@ export {
|
|
|
4011
5058
|
createProdApp,
|
|
4012
5059
|
createProgram,
|
|
4013
5060
|
extractTypeInfo,
|
|
5061
|
+
getAgent,
|
|
5062
|
+
getAgentEntry,
|
|
4014
5063
|
getApp,
|
|
4015
5064
|
getInputTypeForMethod,
|
|
5065
|
+
getSkill,
|
|
5066
|
+
getTool,
|
|
4016
5067
|
helmet,
|
|
5068
|
+
hydrateSkillRegistry,
|
|
4017
5069
|
invalidateProgramCache,
|
|
5070
|
+
listSkills,
|
|
5071
|
+
loadAgentModule,
|
|
4018
5072
|
loadConfig,
|
|
4019
5073
|
loadEnv,
|
|
5074
|
+
loadToolModule,
|
|
5075
|
+
loadToolSchema,
|
|
4020
5076
|
logger,
|
|
4021
|
-
|
|
5077
|
+
registerAgentHandleFactory,
|
|
5078
|
+
removeSkill,
|
|
5079
|
+
resolveAgentTools,
|
|
5080
|
+
resolveSubAgents,
|
|
5081
|
+
resolveTypeNode,
|
|
5082
|
+
upsertSkill
|
|
4022
5083
|
};
|
|
4023
5084
|
//# sourceMappingURL=index.js.map
|