@faapi/faapi 6.17.0 → 6.18.1
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 +1718 -1523
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +17 -3
- package/dist/index.js +1100 -941
- package/dist/index.js.map +1 -1
- package/dist/{routeTypes-C7yHYxPg.d.ts → routeTypes-BAhPIzcG.d.ts} +26 -2
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +458 -271
- package/dist/testing.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1234,21 +1234,6 @@ var init_collectRouteSchemaSources = __esm({
|
|
|
1234
1234
|
}
|
|
1235
1235
|
});
|
|
1236
1236
|
|
|
1237
|
-
// src/utils/atomicWrite.ts
|
|
1238
|
-
import path10 from "path";
|
|
1239
|
-
import fs8 from "fs";
|
|
1240
|
-
async function atomicWriteFile(outputPath, content) {
|
|
1241
|
-
await fs8.promises.mkdir(path10.dirname(outputPath), { recursive: true });
|
|
1242
|
-
const tmp = `${outputPath}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
|
|
1243
|
-
await fs8.promises.writeFile(tmp, content, "utf-8");
|
|
1244
|
-
await fs8.promises.rename(tmp, outputPath);
|
|
1245
|
-
}
|
|
1246
|
-
var init_atomicWrite = __esm({
|
|
1247
|
-
"src/utils/atomicWrite.ts"() {
|
|
1248
|
-
"use strict";
|
|
1249
|
-
}
|
|
1250
|
-
});
|
|
1251
|
-
|
|
1252
1237
|
// src/ast/generateZodSchema.ts
|
|
1253
1238
|
function collectNamedTypes(type, ctx) {
|
|
1254
1239
|
switch (type.kind) {
|
|
@@ -1579,6 +1564,74 @@ var init_generateZodSchema = __esm({
|
|
|
1579
1564
|
}
|
|
1580
1565
|
});
|
|
1581
1566
|
|
|
1567
|
+
// src/utils/atomicWrite.ts
|
|
1568
|
+
import path10 from "path";
|
|
1569
|
+
import fs9 from "fs";
|
|
1570
|
+
async function atomicWriteFile(outputPath, content) {
|
|
1571
|
+
await fs9.promises.mkdir(path10.dirname(outputPath), { recursive: true });
|
|
1572
|
+
const tmp = `${outputPath}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
|
|
1573
|
+
await fs9.promises.writeFile(tmp, content, "utf-8");
|
|
1574
|
+
await fs9.promises.rename(tmp, outputPath);
|
|
1575
|
+
}
|
|
1576
|
+
var init_atomicWrite = __esm({
|
|
1577
|
+
"src/utils/atomicWrite.ts"() {
|
|
1578
|
+
"use strict";
|
|
1579
|
+
}
|
|
1580
|
+
});
|
|
1581
|
+
|
|
1582
|
+
// src/cli/generateZodArtifacts.ts
|
|
1583
|
+
import path11 from "path";
|
|
1584
|
+
import { existsSync } from "fs";
|
|
1585
|
+
async function generateZodArtifacts(sources, options) {
|
|
1586
|
+
const { generateFileSource, resolversByFile, rootDir, dist } = options;
|
|
1587
|
+
if (sources.length === 0) return;
|
|
1588
|
+
const sourcesByFile = /* @__PURE__ */ new Map();
|
|
1589
|
+
for (const source of sources) {
|
|
1590
|
+
let list = sourcesByFile.get(source.filePath);
|
|
1591
|
+
if (!list) {
|
|
1592
|
+
list = [];
|
|
1593
|
+
sourcesByFile.set(source.filePath, list);
|
|
1594
|
+
}
|
|
1595
|
+
list.push(source);
|
|
1596
|
+
}
|
|
1597
|
+
const fileEntries = [];
|
|
1598
|
+
for (const [filePath, fileSources] of sourcesByFile) {
|
|
1599
|
+
const relFile = path11.relative(rootDir, filePath).replace(/\\/g, "/");
|
|
1600
|
+
const outputPath = getSchemaOutputPath(relFile, dist, rootDir);
|
|
1601
|
+
const resolver = resolversByFile.get(filePath);
|
|
1602
|
+
let relForDir = relFile;
|
|
1603
|
+
if (relForDir.startsWith("src/")) {
|
|
1604
|
+
relForDir = relForDir.slice(4);
|
|
1605
|
+
}
|
|
1606
|
+
const dirIdx = relForDir.lastIndexOf("/");
|
|
1607
|
+
const zodRelDir = dirIdx >= 0 ? relForDir.slice(0, dirIdx) : "";
|
|
1608
|
+
const source = generateFileSource(
|
|
1609
|
+
fileSources,
|
|
1610
|
+
(name) => resolver?.resolve(name)?.runtimeType,
|
|
1611
|
+
getHelpersImportPath(zodRelDir)
|
|
1612
|
+
);
|
|
1613
|
+
fileEntries.push({ outputPath, source });
|
|
1614
|
+
}
|
|
1615
|
+
const allSourceCode = fileEntries.map((e) => e.source).join("\n");
|
|
1616
|
+
if (usesCoerceHelpers(allSourceCode)) {
|
|
1617
|
+
const helpersPath = path11.resolve(rootDir, dist, HELPERS_FILENAME);
|
|
1618
|
+
if (!existsSync(helpersPath)) {
|
|
1619
|
+
await atomicWriteFile(helpersPath, generateHelpersFileSource());
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
await Promise.all(
|
|
1623
|
+
fileEntries.map(({ outputPath, source }) => atomicWriteFile(outputPath, source))
|
|
1624
|
+
);
|
|
1625
|
+
}
|
|
1626
|
+
var init_generateZodArtifacts = __esm({
|
|
1627
|
+
"src/cli/generateZodArtifacts.ts"() {
|
|
1628
|
+
"use strict";
|
|
1629
|
+
init_atomicWrite();
|
|
1630
|
+
init_generateZodSchema();
|
|
1631
|
+
init_generateSchemaFiles();
|
|
1632
|
+
}
|
|
1633
|
+
});
|
|
1634
|
+
|
|
1582
1635
|
// src/cli/generateSchemaFiles.ts
|
|
1583
1636
|
var generateSchemaFiles_exports = {};
|
|
1584
1637
|
__export(generateSchemaFiles_exports, {
|
|
@@ -1588,7 +1641,7 @@ __export(generateSchemaFiles_exports, {
|
|
|
1588
1641
|
getRuntimeSchemaPath: () => getRuntimeSchemaPath,
|
|
1589
1642
|
getSchemaOutputPath: () => getSchemaOutputPath
|
|
1590
1643
|
});
|
|
1591
|
-
import
|
|
1644
|
+
import path12 from "path";
|
|
1592
1645
|
function getSchemaOutputPath(sourceFile, dist, rootDir) {
|
|
1593
1646
|
let rel = sourceFile.replace(/\\/g, "/");
|
|
1594
1647
|
if (rel.startsWith("src/")) {
|
|
@@ -1596,7 +1649,7 @@ function getSchemaOutputPath(sourceFile, dist, rootDir) {
|
|
|
1596
1649
|
}
|
|
1597
1650
|
const idx = rel.lastIndexOf("/");
|
|
1598
1651
|
const relDir = idx >= 0 ? rel.slice(0, idx) : "";
|
|
1599
|
-
return
|
|
1652
|
+
return path12.resolve(rootDir, dist, relDir, "zod.js");
|
|
1600
1653
|
}
|
|
1601
1654
|
function getRuntimeSchemaPath(filePath, dist, rootDir) {
|
|
1602
1655
|
let rel = filePath.replace(/\\/g, "/");
|
|
@@ -1607,7 +1660,7 @@ function getRuntimeSchemaPath(filePath, dist, rootDir) {
|
|
|
1607
1660
|
}
|
|
1608
1661
|
const idx = rel.lastIndexOf("/");
|
|
1609
1662
|
const relDir = idx >= 0 ? rel.slice(0, idx) : "";
|
|
1610
|
-
return
|
|
1663
|
+
return path12.resolve(rootDir, dist, relDir, "zod.js");
|
|
1611
1664
|
}
|
|
1612
1665
|
function getHelpersImportPath(relDir) {
|
|
1613
1666
|
if (!relDir) return `./${HELPERS_FILENAME}`;
|
|
@@ -1655,52 +1708,19 @@ function generateSchemaFileSource(sources, resolveType, helpersImportPath) {
|
|
|
1655
1708
|
async function generateSchemaFiles(routes, rootDir, dist) {
|
|
1656
1709
|
if (routes.length === 0) return;
|
|
1657
1710
|
const { sources, resolversByFile } = collectRouteSchemaSources(routes, rootDir);
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
}
|
|
1665
|
-
list.push(source);
|
|
1666
|
-
}
|
|
1667
|
-
const fileEntries = [];
|
|
1668
|
-
for (const [filePath, fileSources] of sourcesByFile) {
|
|
1669
|
-
const relFile = path11.relative(rootDir, filePath).replace(/\\/g, "/");
|
|
1670
|
-
const outputPath = getSchemaOutputPath(relFile, dist, rootDir);
|
|
1671
|
-
let relForDir = relFile;
|
|
1672
|
-
if (relForDir.startsWith("src/")) {
|
|
1673
|
-
relForDir = relForDir.slice(4);
|
|
1674
|
-
}
|
|
1675
|
-
const dirIdx = relForDir.lastIndexOf("/");
|
|
1676
|
-
const zodRelDir = dirIdx >= 0 ? relForDir.slice(0, dirIdx) : "";
|
|
1677
|
-
const helpersImportPath = getHelpersImportPath(zodRelDir);
|
|
1678
|
-
const resolver = resolversByFile.get(filePath);
|
|
1679
|
-
const source = generateSchemaFileSource(
|
|
1680
|
-
fileSources,
|
|
1681
|
-
(name) => resolver?.resolve(name)?.runtimeType,
|
|
1682
|
-
helpersImportPath
|
|
1683
|
-
);
|
|
1684
|
-
fileEntries.push({ outputPath, source });
|
|
1685
|
-
}
|
|
1686
|
-
const allSourceCode = fileEntries.map((e) => e.source).join("\n");
|
|
1687
|
-
if (usesCoerceHelpers(allSourceCode)) {
|
|
1688
|
-
const helpersPath = path11.resolve(rootDir, dist, HELPERS_FILENAME);
|
|
1689
|
-
await writeSchemaFile(helpersPath, generateHelpersFileSource());
|
|
1690
|
-
}
|
|
1691
|
-
await Promise.all(
|
|
1692
|
-
fileEntries.map(({ outputPath, source }) => writeSchemaFile(outputPath, source))
|
|
1693
|
-
);
|
|
1694
|
-
}
|
|
1695
|
-
async function writeSchemaFile(outputPath, source) {
|
|
1696
|
-
await atomicWriteFile(outputPath, source);
|
|
1711
|
+
await generateZodArtifacts(sources, {
|
|
1712
|
+
generateFileSource: generateSchemaFileSource,
|
|
1713
|
+
resolversByFile,
|
|
1714
|
+
rootDir,
|
|
1715
|
+
dist
|
|
1716
|
+
});
|
|
1697
1717
|
}
|
|
1698
1718
|
var init_generateSchemaFiles = __esm({
|
|
1699
1719
|
"src/cli/generateSchemaFiles.ts"() {
|
|
1700
1720
|
"use strict";
|
|
1701
|
-
init_atomicWrite();
|
|
1702
1721
|
init_collectRouteSchemaSources();
|
|
1703
1722
|
init_generateZodSchema();
|
|
1723
|
+
init_generateZodArtifacts();
|
|
1704
1724
|
}
|
|
1705
1725
|
});
|
|
1706
1726
|
|
|
@@ -1945,14 +1965,14 @@ var ValidationError = class extends FaapiError {
|
|
|
1945
1965
|
issues;
|
|
1946
1966
|
};
|
|
1947
1967
|
var RouteNotFoundError = class extends FaapiError {
|
|
1948
|
-
constructor(
|
|
1949
|
-
super("ROUTE_NOT_FOUND", `Route not found: ${
|
|
1968
|
+
constructor(path31) {
|
|
1969
|
+
super("ROUTE_NOT_FOUND", `Route not found: ${path31}`, 404);
|
|
1950
1970
|
this.name = "RouteNotFoundError";
|
|
1951
1971
|
}
|
|
1952
1972
|
};
|
|
1953
1973
|
var MethodNotAllowedError = class extends FaapiError {
|
|
1954
|
-
constructor(method,
|
|
1955
|
-
super("METHOD_NOT_ALLOWED", `Method ${method} not allowed for ${
|
|
1974
|
+
constructor(method, path31, allowedMethods) {
|
|
1975
|
+
super("METHOD_NOT_ALLOWED", `Method ${method} not allowed for ${path31}`, 405);
|
|
1956
1976
|
this.allowedMethods = allowedMethods;
|
|
1957
1977
|
this.name = "MethodNotAllowedError";
|
|
1958
1978
|
}
|
|
@@ -2352,15 +2372,19 @@ function createFileLogSink(options) {
|
|
|
2352
2372
|
}
|
|
2353
2373
|
drainWaiters.push(resolve);
|
|
2354
2374
|
});
|
|
2355
|
-
|
|
2356
|
-
|
|
2375
|
+
let closed = false;
|
|
2376
|
+
const close = () => {
|
|
2377
|
+
if (closed) return Promise.resolve();
|
|
2378
|
+
closed = true;
|
|
2379
|
+
const closing = [...streams.values()].map(
|
|
2357
2380
|
(stream) => new Promise((resolve) => {
|
|
2358
2381
|
stream.end(() => resolve());
|
|
2359
2382
|
})
|
|
2360
|
-
)
|
|
2361
|
-
).then(() => {
|
|
2383
|
+
);
|
|
2362
2384
|
streams.clear();
|
|
2363
|
-
|
|
2385
|
+
return Promise.all(closing).then(() => {
|
|
2386
|
+
});
|
|
2387
|
+
};
|
|
2364
2388
|
return { write, flush, close };
|
|
2365
2389
|
}
|
|
2366
2390
|
|
|
@@ -2692,7 +2716,11 @@ function createTaskQueue(deps) {
|
|
|
2692
2716
|
const id = await driver.enqueue(name, data, {
|
|
2693
2717
|
delayMs: opts?.delayMs,
|
|
2694
2718
|
retries: meta.retries ?? 0,
|
|
2695
|
-
dedupId: opts?.dedupId
|
|
2719
|
+
dedupId: opts?.dedupId,
|
|
2720
|
+
// 执行硬限预算透传驱动(pgboss 映射 expireInSeconds):未声明 timeoutMs 时
|
|
2721
|
+
// 两字段为 undefined,驱动用自身兜底
|
|
2722
|
+
timeoutMs: meta.timeoutMs,
|
|
2723
|
+
graceMs: meta.graceMs
|
|
2696
2724
|
});
|
|
2697
2725
|
if (!records.has(id)) {
|
|
2698
2726
|
records.set(id, {
|
|
@@ -2841,16 +2869,19 @@ function createCronScheduler(registry, enqueue) {
|
|
|
2841
2869
|
this.stop();
|
|
2842
2870
|
for (const task of registry.list()) {
|
|
2843
2871
|
if (!task.cron) continue;
|
|
2872
|
+
let pendingSlot = null;
|
|
2844
2873
|
const schedule = new Cron(task.cron, () => {
|
|
2845
|
-
const
|
|
2874
|
+
const slot = pendingSlot;
|
|
2875
|
+
pendingSlot = schedule.nextRun();
|
|
2846
2876
|
void Promise.resolve().then(
|
|
2847
2877
|
() => enqueue(task.name, {
|
|
2848
|
-
dedupId:
|
|
2878
|
+
dedupId: slot ? `cron:${task.name}:${slot.toISOString()}` : void 0
|
|
2849
2879
|
})
|
|
2850
2880
|
).catch((err) => {
|
|
2851
2881
|
console.error(`[faapi] Cron enqueue failed for task "${task.name}":`, err);
|
|
2852
2882
|
});
|
|
2853
2883
|
});
|
|
2884
|
+
pendingSlot = schedule.nextRun();
|
|
2854
2885
|
schedules.push(schedule);
|
|
2855
2886
|
}
|
|
2856
2887
|
},
|
|
@@ -2975,7 +3006,7 @@ function listSkills() {
|
|
|
2975
3006
|
}
|
|
2976
3007
|
|
|
2977
3008
|
// src/loader/loadAgentModule.ts
|
|
2978
|
-
import
|
|
3009
|
+
import fs12 from "fs";
|
|
2979
3010
|
|
|
2980
3011
|
// src/loader/resolveExports.ts
|
|
2981
3012
|
function resolveExport(module, exportName) {
|
|
@@ -2993,6 +3024,7 @@ function resolveExport(module, exportName) {
|
|
|
2993
3024
|
}
|
|
2994
3025
|
|
|
2995
3026
|
// src/utils/importWithCacheBust.ts
|
|
3027
|
+
import fs4 from "fs";
|
|
2996
3028
|
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
2997
3029
|
var loadTs;
|
|
2998
3030
|
function setLoadTimestamp(ts11) {
|
|
@@ -3003,13 +3035,26 @@ function getVitestImportActual() {
|
|
|
3003
3035
|
if (typeof vi?.importActual !== "function") return void 0;
|
|
3004
3036
|
return vi.importActual.bind(vi);
|
|
3005
3037
|
}
|
|
3038
|
+
var bustCache = /* @__PURE__ */ new Map();
|
|
3039
|
+
var bustCounter = 0;
|
|
3006
3040
|
async function importWithCacheBust(filePath, bustViteCache = false) {
|
|
3007
3041
|
const importActual = getVitestImportActual();
|
|
3008
3042
|
if (importActual) {
|
|
3009
3043
|
if (bustViteCache) {
|
|
3044
|
+
let mtimeMs = 0;
|
|
3045
|
+
try {
|
|
3046
|
+
mtimeMs = fs4.statSync(filePath).mtimeMs;
|
|
3047
|
+
} catch {
|
|
3048
|
+
}
|
|
3049
|
+
const cached = bustCache.get(filePath);
|
|
3050
|
+
if (cached && cached.mtimeMs === mtimeMs) {
|
|
3051
|
+
return cached.module;
|
|
3052
|
+
}
|
|
3010
3053
|
let url2 = pathToFileURL2(filePath).href;
|
|
3011
|
-
url2 += `?t=${Date.now()}`;
|
|
3012
|
-
|
|
3054
|
+
url2 += `?t=${Date.now()}-${++bustCounter}`;
|
|
3055
|
+
const module = await import(url2);
|
|
3056
|
+
bustCache.set(filePath, { mtimeMs, module });
|
|
3057
|
+
return module;
|
|
3013
3058
|
}
|
|
3014
3059
|
return await importActual(filePath);
|
|
3015
3060
|
}
|
|
@@ -3021,18 +3066,18 @@ async function importWithCacheBust(filePath, bustViteCache = false) {
|
|
|
3021
3066
|
}
|
|
3022
3067
|
|
|
3023
3068
|
// src/cli/compileOnDemand.ts
|
|
3024
|
-
import
|
|
3025
|
-
import
|
|
3069
|
+
import path14 from "path";
|
|
3070
|
+
import fs11 from "fs";
|
|
3026
3071
|
|
|
3027
3072
|
// src/cli/compileSourceFiles.ts
|
|
3028
3073
|
import path9 from "path";
|
|
3029
|
-
import
|
|
3074
|
+
import fs8 from "fs";
|
|
3030
3075
|
import fg2 from "fast-glob";
|
|
3031
3076
|
|
|
3032
3077
|
// src/cli/aliasPlugin.ts
|
|
3033
3078
|
import ts7 from "typescript";
|
|
3034
3079
|
import path8 from "path";
|
|
3035
|
-
import
|
|
3080
|
+
import fs7 from "fs";
|
|
3036
3081
|
|
|
3037
3082
|
// src/utils/resolveAlias.ts
|
|
3038
3083
|
function resolveAlias(specifier, config) {
|
|
@@ -3059,7 +3104,7 @@ function resolveAlias(specifier, config) {
|
|
|
3059
3104
|
|
|
3060
3105
|
// src/utils/prodPaths.ts
|
|
3061
3106
|
import path6 from "path";
|
|
3062
|
-
import
|
|
3107
|
+
import fs5 from "fs";
|
|
3063
3108
|
var APP_DIR = "src";
|
|
3064
3109
|
var ROUTE_PATTERNS = ["src/api/**/*.ts"];
|
|
3065
3110
|
function toProdFilePath(filePath, dist) {
|
|
@@ -3078,7 +3123,7 @@ function toProdExtension(filePath) {
|
|
|
3078
3123
|
}
|
|
3079
3124
|
function toRealPath(p) {
|
|
3080
3125
|
try {
|
|
3081
|
-
return
|
|
3126
|
+
return fs5.realpathSync(p);
|
|
3082
3127
|
} catch {
|
|
3083
3128
|
return p;
|
|
3084
3129
|
}
|
|
@@ -3091,14 +3136,14 @@ function isInsideDir(filePath, dir) {
|
|
|
3091
3136
|
// src/utils/readTsconfig.ts
|
|
3092
3137
|
import ts6 from "typescript";
|
|
3093
3138
|
import path7 from "path";
|
|
3094
|
-
import
|
|
3139
|
+
import fs6 from "fs";
|
|
3095
3140
|
var tsconfigCache = /* @__PURE__ */ new Map();
|
|
3096
3141
|
function readTsconfig(rootDir) {
|
|
3097
3142
|
const tsconfigPath = path7.resolve(rootDir, "tsconfig.json");
|
|
3098
|
-
if (!
|
|
3143
|
+
if (!fs6.existsSync(tsconfigPath)) return null;
|
|
3099
3144
|
let mtimeMs;
|
|
3100
3145
|
try {
|
|
3101
|
-
mtimeMs =
|
|
3146
|
+
mtimeMs = fs6.statSync(tsconfigPath).mtimeMs;
|
|
3102
3147
|
} catch {
|
|
3103
3148
|
return null;
|
|
3104
3149
|
}
|
|
@@ -3161,25 +3206,25 @@ function resolveRelativeSpecifier(importer, specifier) {
|
|
|
3161
3206
|
const importerDir = path8.dirname(importer);
|
|
3162
3207
|
const base = path8.resolve(importerDir, specifier);
|
|
3163
3208
|
if (PROD_EXTS.some((ext) => specifier.endsWith(ext))) {
|
|
3164
|
-
if (
|
|
3209
|
+
if (fs7.existsSync(base)) return base;
|
|
3165
3210
|
if (base.endsWith(".js")) {
|
|
3166
3211
|
for (const ext of [".ts", ".tsx", ".jsx"]) {
|
|
3167
3212
|
const file = base.slice(0, -3) + ext;
|
|
3168
|
-
if (
|
|
3213
|
+
if (fs7.existsSync(file)) return file;
|
|
3169
3214
|
}
|
|
3170
3215
|
}
|
|
3171
3216
|
return null;
|
|
3172
3217
|
}
|
|
3173
3218
|
if (/\.(ts|tsx|jsx)$/.test(specifier)) {
|
|
3174
|
-
return
|
|
3219
|
+
return fs7.existsSync(base) ? base : null;
|
|
3175
3220
|
}
|
|
3176
3221
|
for (const ext of SOURCE_EXTS) {
|
|
3177
3222
|
const file = base + ext;
|
|
3178
|
-
if (
|
|
3223
|
+
if (fs7.existsSync(file)) return file;
|
|
3179
3224
|
}
|
|
3180
3225
|
for (const indexExt of INDEX_EXTS) {
|
|
3181
3226
|
const file = base + indexExt;
|
|
3182
|
-
if (
|
|
3227
|
+
if (fs7.existsSync(file)) return file;
|
|
3183
3228
|
}
|
|
3184
3229
|
return null;
|
|
3185
3230
|
}
|
|
@@ -3188,11 +3233,11 @@ function createAliasPlugin(config, options) {
|
|
|
3188
3233
|
const probeCandidateFile = (candidate) => {
|
|
3189
3234
|
for (const ext of SOURCE_EXTS) {
|
|
3190
3235
|
const file = candidate + ext;
|
|
3191
|
-
if (
|
|
3236
|
+
if (fs7.existsSync(file)) return file;
|
|
3192
3237
|
}
|
|
3193
3238
|
for (const indexExt of INDEX_EXTS) {
|
|
3194
3239
|
const file = candidate + indexExt;
|
|
3195
|
-
if (
|
|
3240
|
+
if (fs7.existsSync(file)) return file;
|
|
3196
3241
|
}
|
|
3197
3242
|
return null;
|
|
3198
3243
|
};
|
|
@@ -3202,7 +3247,7 @@ function createAliasPlugin(config, options) {
|
|
|
3202
3247
|
build.onLoad({ filter: /\.(ts|tsx|js|jsx|mjs|cjs)$/ }, (args) => {
|
|
3203
3248
|
let source;
|
|
3204
3249
|
try {
|
|
3205
|
-
source =
|
|
3250
|
+
source = fs7.readFileSync(args.path, "utf8");
|
|
3206
3251
|
} catch {
|
|
3207
3252
|
return void 0;
|
|
3208
3253
|
}
|
|
@@ -3328,7 +3373,7 @@ async function compileSourceFiles(options) {
|
|
|
3328
3373
|
return { compiledFiles: [] };
|
|
3329
3374
|
}
|
|
3330
3375
|
const absDist = path9.resolve(rootDir, dist);
|
|
3331
|
-
await
|
|
3376
|
+
await fs8.promises.mkdir(absDist, { recursive: true });
|
|
3332
3377
|
const plugins = buildAliasPlugins(rootDir);
|
|
3333
3378
|
const esbuild = await import("esbuild");
|
|
3334
3379
|
const outbase = path9.resolve(rootDir, APP_DIR);
|
|
@@ -3351,10 +3396,10 @@ async function compileSourceFiles(options) {
|
|
|
3351
3396
|
if (atomicWrite && result.outputFiles) {
|
|
3352
3397
|
await Promise.all(
|
|
3353
3398
|
result.outputFiles.map(async (file) => {
|
|
3354
|
-
await
|
|
3399
|
+
await fs8.promises.mkdir(path9.dirname(file.path), { recursive: true });
|
|
3355
3400
|
const tmp = `${file.path}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
|
|
3356
|
-
await
|
|
3357
|
-
await
|
|
3401
|
+
await fs8.promises.writeFile(tmp, file.contents);
|
|
3402
|
+
await fs8.promises.rename(tmp, file.path);
|
|
3358
3403
|
})
|
|
3359
3404
|
);
|
|
3360
3405
|
}
|
|
@@ -3371,8 +3416,8 @@ init_generateSchemaFiles();
|
|
|
3371
3416
|
init_generateSchemaFiles();
|
|
3372
3417
|
|
|
3373
3418
|
// src/cli/collectImports.ts
|
|
3374
|
-
import
|
|
3375
|
-
import
|
|
3419
|
+
import path13 from "path";
|
|
3420
|
+
import fs10 from "fs";
|
|
3376
3421
|
var SPEC_RE = /(\bfrom\s*|import\s*\(\s*)(['"])([^'"]+)\2/g;
|
|
3377
3422
|
function extractImportSpecifiers(source) {
|
|
3378
3423
|
const specifiers = [];
|
|
@@ -3384,10 +3429,10 @@ function extractImportSpecifiers(source) {
|
|
|
3384
3429
|
return specifiers;
|
|
3385
3430
|
}
|
|
3386
3431
|
function resolveSpecifierFromDir(dir, specifier) {
|
|
3387
|
-
return resolveRelativeSpecifier(
|
|
3432
|
+
return resolveRelativeSpecifier(path13.join(dir, "__faapi_probe__.ts"), specifier);
|
|
3388
3433
|
}
|
|
3389
3434
|
async function collectRelativeImports(entryFiles, rootDir) {
|
|
3390
|
-
const appDirAbs = toRealPath(
|
|
3435
|
+
const appDirAbs = toRealPath(path13.resolve(rootDir, "src"));
|
|
3391
3436
|
const tsconfig = readTsconfig(rootDir);
|
|
3392
3437
|
const visited = /* @__PURE__ */ new Set();
|
|
3393
3438
|
const insideFiles = /* @__PURE__ */ new Set();
|
|
@@ -3397,7 +3442,7 @@ async function collectRelativeImports(entryFiles, rootDir) {
|
|
|
3397
3442
|
visited.add(filePath);
|
|
3398
3443
|
let source;
|
|
3399
3444
|
try {
|
|
3400
|
-
source = await
|
|
3445
|
+
source = await fs10.promises.readFile(filePath, "utf8");
|
|
3401
3446
|
} catch {
|
|
3402
3447
|
return;
|
|
3403
3448
|
}
|
|
@@ -3419,7 +3464,7 @@ async function collectRelativeImports(entryFiles, rootDir) {
|
|
|
3419
3464
|
resolved = null;
|
|
3420
3465
|
}
|
|
3421
3466
|
if (!resolved) continue;
|
|
3422
|
-
if (!isInsideDir(toRealPath(resolved), toRealPath(
|
|
3467
|
+
if (!isInsideDir(toRealPath(resolved), toRealPath(path13.resolve(rootDir)))) continue;
|
|
3423
3468
|
if (isInsideDir(toRealPath(resolved), appDirAbs)) {
|
|
3424
3469
|
insideFiles.add(resolved);
|
|
3425
3470
|
} else {
|
|
@@ -3440,8 +3485,8 @@ async function collectRelativeImports(entryFiles, rootDir) {
|
|
|
3440
3485
|
// src/cli/compileOnDemand.ts
|
|
3441
3486
|
function isProductFresh(sourceAbsPath, productAbsPath) {
|
|
3442
3487
|
try {
|
|
3443
|
-
const srcStat =
|
|
3444
|
-
const prodStat =
|
|
3488
|
+
const srcStat = fs11.statSync(sourceAbsPath);
|
|
3489
|
+
const prodStat = fs11.statSync(productAbsPath);
|
|
3445
3490
|
return prodStat.mtimeMs >= srcStat.mtimeMs;
|
|
3446
3491
|
} catch {
|
|
3447
3492
|
return false;
|
|
@@ -3466,14 +3511,13 @@ function clearCompiledFiles() {
|
|
|
3466
3511
|
async function ensureCompiled(sourceAbsPath, rootDir, dist) {
|
|
3467
3512
|
const inFlight2 = state.inFlightCompilations.get(sourceAbsPath);
|
|
3468
3513
|
if (inFlight2) {
|
|
3469
|
-
await inFlight2
|
|
3470
|
-
});
|
|
3514
|
+
await inFlight2;
|
|
3471
3515
|
return false;
|
|
3472
3516
|
}
|
|
3473
3517
|
if (state.compiledFiles.has(sourceAbsPath)) {
|
|
3474
3518
|
return false;
|
|
3475
3519
|
}
|
|
3476
|
-
if (!
|
|
3520
|
+
if (!fs11.existsSync(sourceAbsPath)) {
|
|
3477
3521
|
return false;
|
|
3478
3522
|
}
|
|
3479
3523
|
const productPath = prodSourcePathToProductPath(sourceAbsPath, rootDir, dist);
|
|
@@ -3523,11 +3567,11 @@ async function ensureMiddlewaresCompiled(middlewarePaths, rootDir) {
|
|
|
3523
3567
|
}
|
|
3524
3568
|
}
|
|
3525
3569
|
function prodSourcePathToProductPath(sourceAbsPath, rootDir, dist) {
|
|
3526
|
-
const rel =
|
|
3570
|
+
const rel = path14.relative(rootDir, sourceAbsPath).replace(/\\/g, "/");
|
|
3527
3571
|
if (!rel.startsWith("src/")) return null;
|
|
3528
3572
|
const relWithoutSrc = rel.slice(4);
|
|
3529
3573
|
const jsRel = relWithoutSrc.replace(/\.ts$/, ".js");
|
|
3530
|
-
return
|
|
3574
|
+
return path14.resolve(rootDir, dist, jsRel);
|
|
3531
3575
|
}
|
|
3532
3576
|
function clearGeneratedSchemas() {
|
|
3533
3577
|
state.generatedSchemas.clear();
|
|
@@ -3536,16 +3580,15 @@ function clearGeneratedSchemas() {
|
|
|
3536
3580
|
async function ensureSchemaGenerated(schemaPath, routeFilePath, routes, rootDir, dist) {
|
|
3537
3581
|
const inFlight2 = state.inFlightSchemaGenerations.get(schemaPath);
|
|
3538
3582
|
if (inFlight2) {
|
|
3539
|
-
await inFlight2
|
|
3540
|
-
});
|
|
3583
|
+
await inFlight2;
|
|
3541
3584
|
return false;
|
|
3542
3585
|
}
|
|
3543
3586
|
if (state.generatedSchemas.has(schemaPath)) {
|
|
3544
3587
|
return false;
|
|
3545
3588
|
}
|
|
3546
|
-
const prodAbsPath =
|
|
3589
|
+
const prodAbsPath = path14.resolve(rootDir, routeFilePath);
|
|
3547
3590
|
const sourceAbsPath = prodPathToSourcePath(prodAbsPath, rootDir, dist);
|
|
3548
|
-
if (!
|
|
3591
|
+
if (!fs11.existsSync(sourceAbsPath)) {
|
|
3549
3592
|
return false;
|
|
3550
3593
|
}
|
|
3551
3594
|
if (isProductFresh(sourceAbsPath, schemaPath)) {
|
|
@@ -3556,7 +3599,7 @@ async function ensureSchemaGenerated(schemaPath, routeFilePath, routes, rootDir,
|
|
|
3556
3599
|
if (fileRoutes.length === 0) {
|
|
3557
3600
|
return false;
|
|
3558
3601
|
}
|
|
3559
|
-
const sourceRelPath =
|
|
3602
|
+
const sourceRelPath = path14.relative(rootDir, sourceAbsPath).replace(/\\/g, "/");
|
|
3560
3603
|
const sourceRoutes = fileRoutes.map((r) => ({ ...r, filePath: sourceRelPath }));
|
|
3561
3604
|
const generatePromise = (async () => {
|
|
3562
3605
|
await generateSchemaFiles(sourceRoutes, rootDir, dist);
|
|
@@ -3577,7 +3620,7 @@ async function deleteSchemaFiles(routes, rootDir, dist) {
|
|
|
3577
3620
|
if (deleted.has(schemaPath)) continue;
|
|
3578
3621
|
deleted.add(schemaPath);
|
|
3579
3622
|
try {
|
|
3580
|
-
await
|
|
3623
|
+
await fs11.promises.unlink(schemaPath);
|
|
3581
3624
|
} catch {
|
|
3582
3625
|
}
|
|
3583
3626
|
}
|
|
@@ -3586,19 +3629,19 @@ var sourcePathCache = /* @__PURE__ */ new Map();
|
|
|
3586
3629
|
function prodPathToSourcePath(prodAbsPath, rootDir, dist) {
|
|
3587
3630
|
const cached = sourcePathCache.get(prodAbsPath);
|
|
3588
3631
|
if (cached) return cached;
|
|
3589
|
-
const rel =
|
|
3632
|
+
const rel = path14.relative(rootDir, prodAbsPath).replace(/\\/g, "/");
|
|
3590
3633
|
let relWithoutDist = rel;
|
|
3591
3634
|
if (relWithoutDist.startsWith(`${dist}/`)) {
|
|
3592
3635
|
relWithoutDist = relWithoutDist.slice(dist.length + 1);
|
|
3593
3636
|
}
|
|
3594
3637
|
const srcRel = `src/${relWithoutDist}`;
|
|
3595
3638
|
const tsRel = srcRel.replace(/\.js$/, ".ts");
|
|
3596
|
-
const tsAbs =
|
|
3639
|
+
const tsAbs = path14.resolve(rootDir, tsRel);
|
|
3597
3640
|
let result;
|
|
3598
|
-
if (
|
|
3641
|
+
if (fs11.existsSync(tsAbs)) {
|
|
3599
3642
|
result = tsAbs;
|
|
3600
3643
|
} else {
|
|
3601
|
-
result =
|
|
3644
|
+
result = path14.resolve(rootDir, srcRel);
|
|
3602
3645
|
}
|
|
3603
3646
|
sourcePathCache.set(prodAbsPath, result);
|
|
3604
3647
|
return result;
|
|
@@ -3616,7 +3659,7 @@ async function loadAgentModule(filePath, hasRun, rootDir) {
|
|
|
3616
3659
|
const dist = getDevDist();
|
|
3617
3660
|
if (dist) {
|
|
3618
3661
|
const sourcePath = prodPathToSourcePath(filePath, rootDir, dist);
|
|
3619
|
-
if (sourcePath &&
|
|
3662
|
+
if (sourcePath && fs12.existsSync(sourcePath)) {
|
|
3620
3663
|
try {
|
|
3621
3664
|
await ensureCompiled(sourcePath, rootDir, dist);
|
|
3622
3665
|
} catch (compileErr) {
|
|
@@ -3649,13 +3692,13 @@ async function loadAgentModule(filePath, hasRun, rootDir) {
|
|
|
3649
3692
|
}
|
|
3650
3693
|
|
|
3651
3694
|
// src/loader/loadToolModule.ts
|
|
3652
|
-
import
|
|
3695
|
+
import fs13 from "fs";
|
|
3653
3696
|
async function loadToolModule(filePath, functionName, rootDir) {
|
|
3654
3697
|
if (isDevOnDemandEnabled() && rootDir) {
|
|
3655
3698
|
const dist = getDevDist();
|
|
3656
3699
|
if (dist) {
|
|
3657
3700
|
const sourcePath = prodPathToSourcePath(filePath, rootDir, dist);
|
|
3658
|
-
if (sourcePath &&
|
|
3701
|
+
if (sourcePath && fs13.existsSync(sourcePath)) {
|
|
3659
3702
|
try {
|
|
3660
3703
|
await ensureCompiled(sourcePath, rootDir, dist);
|
|
3661
3704
|
} catch (compileErr) {
|
|
@@ -3687,8 +3730,8 @@ async function loadToolModule(filePath, functionName, rootDir) {
|
|
|
3687
3730
|
import { existsSync as existsSync2 } from "fs";
|
|
3688
3731
|
|
|
3689
3732
|
// src/cli/generateToolArtifacts.ts
|
|
3690
|
-
|
|
3691
|
-
import
|
|
3733
|
+
init_generateZodArtifacts();
|
|
3734
|
+
import path15 from "path";
|
|
3692
3735
|
|
|
3693
3736
|
// src/ast/extractToolMetadata.ts
|
|
3694
3737
|
import ts9 from "typescript";
|
|
@@ -3777,11 +3820,9 @@ function getFirstParamTypeName(fn, sourceFile) {
|
|
|
3777
3820
|
// src/cli/generateToolArtifacts.ts
|
|
3778
3821
|
init_createProgram();
|
|
3779
3822
|
init_atomicWrite();
|
|
3780
|
-
init_generateSchemaFiles();
|
|
3781
3823
|
init_extractHandlerTypes();
|
|
3782
3824
|
init_generateZodSchema();
|
|
3783
3825
|
init_generateSchemaFiles();
|
|
3784
|
-
init_generateSchemaFiles();
|
|
3785
3826
|
var TOOLS_FILE = "faapi-tools.js";
|
|
3786
3827
|
function serializeTools(tools, dist = "dist") {
|
|
3787
3828
|
return tools.map((t) => ({
|
|
@@ -3811,7 +3852,7 @@ function collectToolSchemaSources(tools, rootDir) {
|
|
|
3811
3852
|
const toolsByFile = /* @__PURE__ */ new Map();
|
|
3812
3853
|
for (const tool of tools) {
|
|
3813
3854
|
if (!tool.inputTypeName) continue;
|
|
3814
|
-
const absPath =
|
|
3855
|
+
const absPath = path15.resolve(rootDir, tool.filePath);
|
|
3815
3856
|
let list = toolsByFile.get(absPath);
|
|
3816
3857
|
if (!list) {
|
|
3817
3858
|
list = [];
|
|
@@ -3869,17 +3910,11 @@ function generateToolSchemaFileSource(sources, resolveType, helpersImportPath) {
|
|
|
3869
3910
|
lines.push(...schemaBlocks);
|
|
3870
3911
|
return lines.join("\n").replace(/\n+$/, "\n");
|
|
3871
3912
|
}
|
|
3872
|
-
async function maybeGenerateHelpers(allSourceCode, distDir) {
|
|
3873
|
-
if (!usesCoerceHelpers(allSourceCode)) return;
|
|
3874
|
-
const helpersPath = path14.resolve(distDir, HELPERS_FILENAME);
|
|
3875
|
-
if (existsSync(helpersPath)) return;
|
|
3876
|
-
await atomicWriteFile(helpersPath, generateHelpersFileSource());
|
|
3877
|
-
}
|
|
3878
3913
|
async function generateToolArtifacts(tools, rootDir, dist, options) {
|
|
3879
3914
|
const metadata = [];
|
|
3880
|
-
const programByFile = createPrograms(tools.map((m) =>
|
|
3915
|
+
const programByFile = createPrograms(tools.map((m) => path15.resolve(rootDir, m.filePath)));
|
|
3881
3916
|
for (const manifest of tools) {
|
|
3882
|
-
const absPath =
|
|
3917
|
+
const absPath = path15.resolve(rootDir, manifest.filePath);
|
|
3883
3918
|
const program = programByFile.get(absPath);
|
|
3884
3919
|
const result = extractToolMetadata(program, absPath, manifest.functionName, {
|
|
3885
3920
|
name: manifest.name,
|
|
@@ -3890,7 +3925,7 @@ async function generateToolArtifacts(tools, rootDir, dist, options) {
|
|
|
3890
3925
|
}
|
|
3891
3926
|
}
|
|
3892
3927
|
const serialized = serializeTools(metadata, dist);
|
|
3893
|
-
const toolsPath =
|
|
3928
|
+
const toolsPath = path15.resolve(rootDir, dist, TOOLS_FILE);
|
|
3894
3929
|
await writeToolsModule(serialized, toolsPath);
|
|
3895
3930
|
if (options?.skipSchema) {
|
|
3896
3931
|
return metadata;
|
|
@@ -3902,45 +3937,14 @@ async function generateToolArtifacts(tools, rootDir, dist, options) {
|
|
|
3902
3937
|
if (sources.length === 0) {
|
|
3903
3938
|
return metadata;
|
|
3904
3939
|
}
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
}
|
|
3912
|
-
list.push(source);
|
|
3913
|
-
}
|
|
3914
|
-
const fileEntries = [];
|
|
3915
|
-
for (const [filePath, fileSources] of sourcesByFile) {
|
|
3916
|
-
const relFile = path14.relative(rootDir, filePath).replace(/\\/g, "/");
|
|
3917
|
-
const outputPath = getSchemaOutputPath(relFile, dist, rootDir);
|
|
3918
|
-
const resolver = resolversByFile.get(filePath);
|
|
3919
|
-
let relForDir = relFile;
|
|
3920
|
-
if (relForDir.startsWith("src/")) {
|
|
3921
|
-
relForDir = relForDir.slice(4);
|
|
3922
|
-
}
|
|
3923
|
-
const dirIdx = relForDir.lastIndexOf("/");
|
|
3924
|
-
const zodRelDir = dirIdx >= 0 ? relForDir.slice(0, dirIdx) : "";
|
|
3925
|
-
const helpersImportPath = getHelpersImportPath(zodRelDir);
|
|
3926
|
-
const source = generateToolSchemaFileSource(
|
|
3927
|
-
fileSources,
|
|
3928
|
-
(name) => resolver?.resolve(name)?.runtimeType,
|
|
3929
|
-
helpersImportPath
|
|
3930
|
-
);
|
|
3931
|
-
fileEntries.push({ outputPath, source });
|
|
3932
|
-
}
|
|
3933
|
-
const allSourceCode = fileEntries.map((e) => e.source).join("\n");
|
|
3934
|
-
const distDir = path14.resolve(rootDir, dist);
|
|
3935
|
-
await maybeGenerateHelpers(allSourceCode, distDir);
|
|
3936
|
-
await Promise.all(
|
|
3937
|
-
fileEntries.map(({ outputPath, source }) => writeToolSchemaFile(outputPath, source))
|
|
3938
|
-
);
|
|
3940
|
+
await generateZodArtifacts(sources, {
|
|
3941
|
+
generateFileSource: generateToolSchemaFileSource,
|
|
3942
|
+
resolversByFile,
|
|
3943
|
+
rootDir,
|
|
3944
|
+
dist
|
|
3945
|
+
});
|
|
3939
3946
|
return metadata;
|
|
3940
3947
|
}
|
|
3941
|
-
async function writeToolSchemaFile(outputPath, source) {
|
|
3942
|
-
await atomicWriteFile(outputPath, source);
|
|
3943
|
-
}
|
|
3944
3948
|
|
|
3945
3949
|
// src/loader/loadToolSchema.ts
|
|
3946
3950
|
function getDist() {
|
|
@@ -3979,6 +3983,20 @@ function clearAgentHandleFactory() {
|
|
|
3979
3983
|
defaultRegistries.agentHandle.clear();
|
|
3980
3984
|
}
|
|
3981
3985
|
|
|
3986
|
+
// src/middleware/mergeVary.ts
|
|
3987
|
+
function mergeVary(meta, value) {
|
|
3988
|
+
const existing = meta.headers["Vary"] ?? meta.headers["vary"];
|
|
3989
|
+
if (!existing) {
|
|
3990
|
+
meta.headers["Vary"] = value;
|
|
3991
|
+
return;
|
|
3992
|
+
}
|
|
3993
|
+
const segments = existing.split(",").map((s) => s.trim().toLowerCase());
|
|
3994
|
+
if (!segments.includes(value.toLowerCase())) {
|
|
3995
|
+
const key = meta.headers["Vary"] !== void 0 ? "Vary" : "vary";
|
|
3996
|
+
meta.headers[key] = `${existing}, ${value}`;
|
|
3997
|
+
}
|
|
3998
|
+
}
|
|
3999
|
+
|
|
3982
4000
|
// src/middleware/cors.ts
|
|
3983
4001
|
var DEFAULT_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
|
|
3984
4002
|
function cors(options = {}) {
|
|
@@ -3991,6 +4009,7 @@ function cors(options = {}) {
|
|
|
3991
4009
|
maxAge
|
|
3992
4010
|
} = options;
|
|
3993
4011
|
return async (ctx, next) => {
|
|
4012
|
+
const meta = ctx.meta;
|
|
3994
4013
|
const reqOrigin = ctx.headers.get("origin");
|
|
3995
4014
|
if (!reqOrigin) {
|
|
3996
4015
|
await next();
|
|
@@ -4005,14 +4024,7 @@ function cors(options = {}) {
|
|
|
4005
4024
|
allowOrigin = origin.includes(reqOrigin) ? reqOrigin : null;
|
|
4006
4025
|
}
|
|
4007
4026
|
if (origin === true || Array.isArray(origin)) {
|
|
4008
|
-
|
|
4009
|
-
if (existingVary) {
|
|
4010
|
-
if (!existingVary.toLowerCase().includes("origin")) {
|
|
4011
|
-
ctx.setHeader("Vary", `${existingVary}, Origin`);
|
|
4012
|
-
}
|
|
4013
|
-
} else {
|
|
4014
|
-
ctx.setHeader("Vary", "Origin");
|
|
4015
|
-
}
|
|
4027
|
+
mergeVary(meta, "Origin");
|
|
4016
4028
|
}
|
|
4017
4029
|
if (!allowOrigin) {
|
|
4018
4030
|
await next();
|
|
@@ -4157,16 +4169,16 @@ function helmet(options = {}) {
|
|
|
4157
4169
|
}
|
|
4158
4170
|
|
|
4159
4171
|
// src/config/loadConfig.ts
|
|
4160
|
-
import
|
|
4161
|
-
import
|
|
4172
|
+
import path16 from "path";
|
|
4173
|
+
import fs14 from "fs";
|
|
4162
4174
|
var CONFIG_PRODUCT_FILE = "faapi-config.js";
|
|
4163
4175
|
async function loadConfig(rootDir, dist) {
|
|
4164
|
-
const configProductPath =
|
|
4165
|
-
if (
|
|
4176
|
+
const configProductPath = path16.resolve(rootDir, dist, CONFIG_PRODUCT_FILE);
|
|
4177
|
+
if (fs14.existsSync(configProductPath)) {
|
|
4166
4178
|
const module = await importWithCacheBust(configProductPath);
|
|
4167
4179
|
return module.default ?? {};
|
|
4168
4180
|
}
|
|
4169
|
-
const hasSourceConfig =
|
|
4181
|
+
const hasSourceConfig = fs14.existsSync(path16.join(rootDir, "faapi.config.ts")) || fs14.existsSync(path16.join(rootDir, "faapi.config.js"));
|
|
4170
4182
|
if (hasSourceConfig) {
|
|
4171
4183
|
throw new Error(
|
|
4172
4184
|
`[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`
|
|
@@ -4176,8 +4188,8 @@ async function loadConfig(rootDir, dist) {
|
|
|
4176
4188
|
}
|
|
4177
4189
|
|
|
4178
4190
|
// src/cli/loadEnv.ts
|
|
4179
|
-
import
|
|
4180
|
-
import
|
|
4191
|
+
import fs15 from "fs";
|
|
4192
|
+
import path17 from "path";
|
|
4181
4193
|
function resolveEnv() {
|
|
4182
4194
|
return process.env.NODE_ENV || "development";
|
|
4183
4195
|
}
|
|
@@ -4244,9 +4256,9 @@ function loadEnv(rootDir) {
|
|
|
4244
4256
|
const files = getEnvFiles(env);
|
|
4245
4257
|
const merged = {};
|
|
4246
4258
|
for (const file of files) {
|
|
4247
|
-
const filePath =
|
|
4248
|
-
if (!
|
|
4249
|
-
const content =
|
|
4259
|
+
const filePath = path17.join(rootDir, file);
|
|
4260
|
+
if (!fs15.existsSync(filePath)) continue;
|
|
4261
|
+
const content = fs15.readFileSync(filePath, "utf-8");
|
|
4250
4262
|
const parsed = parseEnvFile(content, merged);
|
|
4251
4263
|
Object.assign(merged, parsed);
|
|
4252
4264
|
}
|
|
@@ -4258,9 +4270,8 @@ function loadEnv(rootDir) {
|
|
|
4258
4270
|
}
|
|
4259
4271
|
|
|
4260
4272
|
// src/cli/createAppCore.ts
|
|
4261
|
-
import
|
|
4262
|
-
import
|
|
4263
|
-
import { PassThrough, Readable as Readable3 } from "stream";
|
|
4273
|
+
import fs21 from "fs";
|
|
4274
|
+
import path26 from "path";
|
|
4264
4275
|
|
|
4265
4276
|
// src/router/sortRoutes.ts
|
|
4266
4277
|
function sortRoutes(routes) {
|
|
@@ -4304,6 +4315,18 @@ function detectRouteConflicts(routes) {
|
|
|
4304
4315
|
}
|
|
4305
4316
|
return conflicts;
|
|
4306
4317
|
}
|
|
4318
|
+
function reportRouteConflicts(routes) {
|
|
4319
|
+
const conflicts = detectRouteConflicts(routes);
|
|
4320
|
+
if (conflicts.length === 0) return false;
|
|
4321
|
+
console.warn("! \u8DEF\u7531\u51B2\u7A81:");
|
|
4322
|
+
for (const conflict of conflicts) {
|
|
4323
|
+
console.warn(` ${conflict.method} ${conflict.urlPath}`);
|
|
4324
|
+
for (const file of conflict.files) {
|
|
4325
|
+
console.warn(` - ${file}`);
|
|
4326
|
+
}
|
|
4327
|
+
}
|
|
4328
|
+
return true;
|
|
4329
|
+
}
|
|
4307
4330
|
|
|
4308
4331
|
// src/server/createServer.ts
|
|
4309
4332
|
import {
|
|
@@ -4312,7 +4335,7 @@ import {
|
|
|
4312
4335
|
import { createSecureServer as createHttp2SecureServer } from "http2";
|
|
4313
4336
|
import { readFileSync } from "fs";
|
|
4314
4337
|
import { Readable as Readable2 } from "stream";
|
|
4315
|
-
import
|
|
4338
|
+
import path19 from "path";
|
|
4316
4339
|
|
|
4317
4340
|
// src/router/matchRoute.ts
|
|
4318
4341
|
var httpIndexCache = /* @__PURE__ */ new WeakMap();
|
|
@@ -4354,65 +4377,72 @@ function getWsIndex(routes) {
|
|
|
4354
4377
|
wsIndexCache.set(routes, index);
|
|
4355
4378
|
return index;
|
|
4356
4379
|
}
|
|
4357
|
-
function matchRoute(routes, method,
|
|
4380
|
+
function matchRoute(routes, method, path31) {
|
|
4358
4381
|
const index = getHttpIndex(routes);
|
|
4359
4382
|
const upper = method.toUpperCase();
|
|
4360
|
-
const hit = matchByMethod(index, upper,
|
|
4383
|
+
const hit = matchByMethod(index, upper, path31);
|
|
4361
4384
|
if (hit) return hit;
|
|
4362
4385
|
if (upper === "HEAD") {
|
|
4363
|
-
return matchByMethod(index, "GET",
|
|
4386
|
+
return matchByMethod(index, "GET", path31);
|
|
4364
4387
|
}
|
|
4365
4388
|
return null;
|
|
4366
4389
|
}
|
|
4367
|
-
function matchByMethod(index, method,
|
|
4368
|
-
const staticHit = index.static.get(`${method}|${
|
|
4390
|
+
function matchByMethod(index, method, path31) {
|
|
4391
|
+
const staticHit = index.static.get(`${method}|${path31}`);
|
|
4369
4392
|
if (staticHit) {
|
|
4370
4393
|
return { route: staticHit, params: {} };
|
|
4371
4394
|
}
|
|
4395
|
+
if (index.dynamics.length === 0) {
|
|
4396
|
+
return null;
|
|
4397
|
+
}
|
|
4398
|
+
const pathSegments = path31.split("/").filter(Boolean);
|
|
4372
4399
|
for (const entry of index.dynamics) {
|
|
4373
4400
|
const route = entry.route;
|
|
4374
4401
|
if (route.method !== method) {
|
|
4375
4402
|
continue;
|
|
4376
4403
|
}
|
|
4377
|
-
const params = matchSegments(entry.segments,
|
|
4404
|
+
const params = matchSegments(entry.segments, pathSegments, route.paramNames, route.isCatchAll);
|
|
4378
4405
|
if (params !== null) {
|
|
4379
4406
|
return { route, params };
|
|
4380
4407
|
}
|
|
4381
4408
|
}
|
|
4382
4409
|
return null;
|
|
4383
4410
|
}
|
|
4384
|
-
function matchWsRoute(wsRoutes,
|
|
4411
|
+
function matchWsRoute(wsRoutes, path31) {
|
|
4385
4412
|
const index = getWsIndex(wsRoutes);
|
|
4386
|
-
const staticHit = index.static.get(
|
|
4413
|
+
const staticHit = index.static.get(path31);
|
|
4387
4414
|
if (staticHit) {
|
|
4388
4415
|
return { route: staticHit, params: {} };
|
|
4389
4416
|
}
|
|
4390
4417
|
for (const route of index.dynamics) {
|
|
4391
|
-
const params = matchDynamicPath(route.urlPath,
|
|
4418
|
+
const params = matchDynamicPath(route.urlPath, path31, route.paramNames, route.isCatchAll);
|
|
4392
4419
|
if (params !== null) {
|
|
4393
4420
|
return { route, params };
|
|
4394
4421
|
}
|
|
4395
4422
|
}
|
|
4396
4423
|
return null;
|
|
4397
4424
|
}
|
|
4398
|
-
function findAllowedMethods(routes,
|
|
4425
|
+
function findAllowedMethods(routes, path31) {
|
|
4399
4426
|
const index = getHttpIndex(routes);
|
|
4400
4427
|
const methods = /* @__PURE__ */ new Set();
|
|
4401
|
-
const staticMethods = index.methodsByStaticPath.get(
|
|
4428
|
+
const staticMethods = index.methodsByStaticPath.get(path31);
|
|
4402
4429
|
if (staticMethods) {
|
|
4403
4430
|
for (const method of staticMethods) {
|
|
4404
4431
|
methods.add(method);
|
|
4405
4432
|
}
|
|
4406
4433
|
}
|
|
4407
|
-
|
|
4408
|
-
const
|
|
4409
|
-
|
|
4410
|
-
|
|
4411
|
-
|
|
4412
|
-
|
|
4413
|
-
|
|
4414
|
-
|
|
4415
|
-
|
|
4434
|
+
if (index.dynamics.length > 0) {
|
|
4435
|
+
const pathSegments = path31.split("/").filter(Boolean);
|
|
4436
|
+
for (const entry of index.dynamics) {
|
|
4437
|
+
const params = matchSegments(
|
|
4438
|
+
entry.segments,
|
|
4439
|
+
pathSegments,
|
|
4440
|
+
entry.route.paramNames,
|
|
4441
|
+
entry.route.isCatchAll
|
|
4442
|
+
);
|
|
4443
|
+
if (params !== null) {
|
|
4444
|
+
methods.add(entry.route.method);
|
|
4445
|
+
}
|
|
4416
4446
|
}
|
|
4417
4447
|
}
|
|
4418
4448
|
if (methods.has("GET")) {
|
|
@@ -4420,11 +4450,15 @@ function findAllowedMethods(routes, path29) {
|
|
|
4420
4450
|
}
|
|
4421
4451
|
return Array.from(methods);
|
|
4422
4452
|
}
|
|
4423
|
-
function matchDynamicPath(pattern,
|
|
4424
|
-
return matchSegments(
|
|
4453
|
+
function matchDynamicPath(pattern, path31, paramNames, isCatchAll) {
|
|
4454
|
+
return matchSegments(
|
|
4455
|
+
pattern.split("/").filter(Boolean),
|
|
4456
|
+
path31.split("/").filter(Boolean),
|
|
4457
|
+
paramNames,
|
|
4458
|
+
isCatchAll
|
|
4459
|
+
);
|
|
4425
4460
|
}
|
|
4426
|
-
function matchSegments(patternSegments,
|
|
4427
|
-
const pathSegments = path29.split("/").filter(Boolean);
|
|
4461
|
+
function matchSegments(patternSegments, pathSegments, paramNames, isCatchAll) {
|
|
4428
4462
|
if (isCatchAll) {
|
|
4429
4463
|
const nonCatchAllCount = patternSegments.length - 1;
|
|
4430
4464
|
if (pathSegments.length <= nonCatchAllCount) {
|
|
@@ -4508,6 +4542,15 @@ async function loadRouteModule(filePath, method, rootDir) {
|
|
|
4508
4542
|
return { handler, method };
|
|
4509
4543
|
}
|
|
4510
4544
|
|
|
4545
|
+
// src/response/bufferedBody.ts
|
|
4546
|
+
var bufferedBodyResponses = /* @__PURE__ */ new WeakSet();
|
|
4547
|
+
function markBufferedBody(response) {
|
|
4548
|
+
bufferedBodyResponses.add(response);
|
|
4549
|
+
}
|
|
4550
|
+
function hasBufferedBody(response) {
|
|
4551
|
+
return bufferedBodyResponses.has(response);
|
|
4552
|
+
}
|
|
4553
|
+
|
|
4511
4554
|
// src/utils/isPlainObject.ts
|
|
4512
4555
|
function isPlainObject(value) {
|
|
4513
4556
|
if (value === null || typeof value !== "object") {
|
|
@@ -4522,8 +4565,50 @@ function isPlainObject(value) {
|
|
|
4522
4565
|
|
|
4523
4566
|
// src/utils/stringifyJson.ts
|
|
4524
4567
|
function stringifyJson(value) {
|
|
4568
|
+
if (!needsConversion(value, /* @__PURE__ */ new Set())) {
|
|
4569
|
+
return JSON.stringify(value);
|
|
4570
|
+
}
|
|
4525
4571
|
return JSON.stringify(convertValue(value, /* @__PURE__ */ new Set()));
|
|
4526
4572
|
}
|
|
4573
|
+
function needsConversion(value, ancestors) {
|
|
4574
|
+
if (value === null) return false;
|
|
4575
|
+
const type = typeof value;
|
|
4576
|
+
if (type === "bigint") return true;
|
|
4577
|
+
if (type === "number") return !Number.isFinite(value);
|
|
4578
|
+
if (type !== "object") return false;
|
|
4579
|
+
const obj = value;
|
|
4580
|
+
if (ancestors.has(obj)) {
|
|
4581
|
+
throw new TypeError("[faapi] Converting circular structure to JSON");
|
|
4582
|
+
}
|
|
4583
|
+
if (obj instanceof Date || obj instanceof Map || obj instanceof Set || obj instanceof RegExp) {
|
|
4584
|
+
return true;
|
|
4585
|
+
}
|
|
4586
|
+
if (typeof obj.toJSON === "function") return true;
|
|
4587
|
+
if (Array.isArray(obj)) {
|
|
4588
|
+
ancestors.add(obj);
|
|
4589
|
+
for (let i = 0; i < obj.length; i++) {
|
|
4590
|
+
if (needsConversion(obj[i], ancestors)) {
|
|
4591
|
+
ancestors.delete(obj);
|
|
4592
|
+
return true;
|
|
4593
|
+
}
|
|
4594
|
+
}
|
|
4595
|
+
ancestors.delete(obj);
|
|
4596
|
+
return false;
|
|
4597
|
+
}
|
|
4598
|
+
if (isPlainObject(obj)) {
|
|
4599
|
+
ancestors.add(obj);
|
|
4600
|
+
for (const key in obj) {
|
|
4601
|
+
if (!Object.prototype.hasOwnProperty.call(obj, key)) continue;
|
|
4602
|
+
if (needsConversion(obj[key], ancestors)) {
|
|
4603
|
+
ancestors.delete(obj);
|
|
4604
|
+
return true;
|
|
4605
|
+
}
|
|
4606
|
+
}
|
|
4607
|
+
ancestors.delete(obj);
|
|
4608
|
+
return false;
|
|
4609
|
+
}
|
|
4610
|
+
return false;
|
|
4611
|
+
}
|
|
4527
4612
|
function convertValue(value, ancestors) {
|
|
4528
4613
|
if (value === null) return value;
|
|
4529
4614
|
const type = typeof value;
|
|
@@ -4618,16 +4703,30 @@ function createSseWriter() {
|
|
|
4618
4703
|
let controller = null;
|
|
4619
4704
|
let closed = false;
|
|
4620
4705
|
let aborted = false;
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
|
|
4706
|
+
let drainWaiters = [];
|
|
4707
|
+
const resolveDrainWaiters = () => {
|
|
4708
|
+
const waiters = drainWaiters;
|
|
4709
|
+
drainWaiters = [];
|
|
4710
|
+
for (const resolve of waiters) resolve();
|
|
4711
|
+
};
|
|
4712
|
+
const stream = new ReadableStream(
|
|
4713
|
+
{
|
|
4714
|
+
start(c) {
|
|
4715
|
+
controller = c;
|
|
4716
|
+
},
|
|
4717
|
+
// 消费者排空缓冲后再要数据 → 背压解除,唤醒 waitForDrain 等待方
|
|
4718
|
+
pull() {
|
|
4719
|
+
resolveDrainWaiters();
|
|
4720
|
+
},
|
|
4721
|
+
cancel() {
|
|
4722
|
+
aborted = true;
|
|
4723
|
+
closed = true;
|
|
4724
|
+
controller = null;
|
|
4725
|
+
resolveDrainWaiters();
|
|
4726
|
+
}
|
|
4624
4727
|
},
|
|
4625
|
-
|
|
4626
|
-
|
|
4627
|
-
closed = true;
|
|
4628
|
-
controller = null;
|
|
4629
|
-
}
|
|
4630
|
-
});
|
|
4728
|
+
{ highWaterMark: 16 }
|
|
4729
|
+
);
|
|
4631
4730
|
const response = new Response(stream, {
|
|
4632
4731
|
status: 200,
|
|
4633
4732
|
headers: {
|
|
@@ -4662,6 +4761,7 @@ function createSseWriter() {
|
|
|
4662
4761
|
close() {
|
|
4663
4762
|
if (closed) return;
|
|
4664
4763
|
closed = true;
|
|
4764
|
+
resolveDrainWaiters();
|
|
4665
4765
|
if (controller) {
|
|
4666
4766
|
try {
|
|
4667
4767
|
controller.close();
|
|
@@ -4673,8 +4773,18 @@ function createSseWriter() {
|
|
|
4673
4773
|
get closed() {
|
|
4674
4774
|
return closed;
|
|
4675
4775
|
},
|
|
4676
|
-
get
|
|
4677
|
-
return
|
|
4776
|
+
get desiredSize() {
|
|
4777
|
+
return controller ? controller.desiredSize : null;
|
|
4778
|
+
},
|
|
4779
|
+
waitForDrain() {
|
|
4780
|
+
if (closed || !controller) return Promise.resolve();
|
|
4781
|
+
if ((controller.desiredSize ?? 0) > 0) return Promise.resolve();
|
|
4782
|
+
return new Promise((resolve) => {
|
|
4783
|
+
drainWaiters.push(resolve);
|
|
4784
|
+
});
|
|
4785
|
+
},
|
|
4786
|
+
get aborted() {
|
|
4787
|
+
return aborted;
|
|
4678
4788
|
},
|
|
4679
4789
|
get response() {
|
|
4680
4790
|
return response;
|
|
@@ -4710,7 +4820,9 @@ function jsonRaw(body, status, extraHeaders) {
|
|
|
4710
4820
|
const extra = new Headers(extraHeaders);
|
|
4711
4821
|
extra.forEach((value, key) => headers.set(key, value));
|
|
4712
4822
|
}
|
|
4713
|
-
|
|
4823
|
+
const response = new Response(stringifyJson(body), { status, headers });
|
|
4824
|
+
markBufferedBody(response);
|
|
4825
|
+
return response;
|
|
4714
4826
|
}
|
|
4715
4827
|
function wrapOkResult(result, config) {
|
|
4716
4828
|
if (result instanceof Response) return result;
|
|
@@ -4727,12 +4839,9 @@ function formatFailResponse(options, config) {
|
|
|
4727
4839
|
}
|
|
4728
4840
|
function formatErrorResponse(error, config) {
|
|
4729
4841
|
const failFn = resolveFailFn(config);
|
|
4842
|
+
const buildFailBody = (e) => failFn({ status: e.statusCode, code: e.code, message: e.message });
|
|
4730
4843
|
if (error instanceof ValidationError) {
|
|
4731
|
-
const body2 =
|
|
4732
|
-
status: error.statusCode,
|
|
4733
|
-
code: error.code,
|
|
4734
|
-
message: error.message
|
|
4735
|
-
});
|
|
4844
|
+
const body2 = buildFailBody(error);
|
|
4736
4845
|
const bodyObj = typeof body2 === "object" && body2 !== null ? { ...body2 } : { error: body2 };
|
|
4737
4846
|
const existingError = bodyObj.error;
|
|
4738
4847
|
if (existingError && typeof existingError === "object") {
|
|
@@ -4745,28 +4854,15 @@ function formatErrorResponse(error, config) {
|
|
|
4745
4854
|
return jsonOk(bodyObj, error.statusCode);
|
|
4746
4855
|
}
|
|
4747
4856
|
if (error instanceof MethodNotAllowedError) {
|
|
4748
|
-
|
|
4749
|
-
|
|
4750
|
-
code: error.code,
|
|
4751
|
-
message: error.message
|
|
4857
|
+
return jsonOk(buildFailBody(error), error.statusCode, {
|
|
4858
|
+
Allow: error.allowedMethods.join(", ")
|
|
4752
4859
|
});
|
|
4753
|
-
return jsonOk(body2, error.statusCode, { Allow: error.allowedMethods.join(", ") });
|
|
4754
4860
|
}
|
|
4755
4861
|
if (error instanceof PayloadTooLargeError) {
|
|
4756
|
-
|
|
4757
|
-
status: error.statusCode,
|
|
4758
|
-
code: error.code,
|
|
4759
|
-
message: error.message
|
|
4760
|
-
});
|
|
4761
|
-
return jsonOk(body2, error.statusCode);
|
|
4862
|
+
return jsonOk(buildFailBody(error), error.statusCode);
|
|
4762
4863
|
}
|
|
4763
4864
|
if (error instanceof FaapiError) {
|
|
4764
|
-
|
|
4765
|
-
status: error.statusCode,
|
|
4766
|
-
code: error.code,
|
|
4767
|
-
message: error.message
|
|
4768
|
-
});
|
|
4769
|
-
return jsonOk(body2, error.statusCode);
|
|
4865
|
+
return jsonOk(buildFailBody(error), error.statusCode);
|
|
4770
4866
|
}
|
|
4771
4867
|
const message = error instanceof Error ? error.message : "An unknown error occurred";
|
|
4772
4868
|
const body = failFn({ status: 500, code: "INTERNAL_ERROR", message });
|
|
@@ -4851,10 +4947,12 @@ function createContextFromUrl(request, url, params, config = {}, ip = "", regist
|
|
|
4851
4947
|
},
|
|
4852
4948
|
html(html, status) {
|
|
4853
4949
|
const headers = { "Content-Type": "text/html; charset=utf-8" };
|
|
4854
|
-
|
|
4950
|
+
const response = new Response(html, {
|
|
4855
4951
|
status: status ?? 200,
|
|
4856
4952
|
headers
|
|
4857
4953
|
});
|
|
4954
|
+
markBufferedBody(response);
|
|
4955
|
+
return response;
|
|
4858
4956
|
},
|
|
4859
4957
|
getCookie(name) {
|
|
4860
4958
|
return parsedCookies.get(name);
|
|
@@ -4921,7 +5019,10 @@ function createContextFromUrl(request, url, params, config = {}, ip = "", regist
|
|
|
4921
5019
|
}
|
|
4922
5020
|
|
|
4923
5021
|
// src/utils/queryToObject.ts
|
|
5022
|
+
var queryCache = /* @__PURE__ */ new WeakMap();
|
|
4924
5023
|
function queryToObject(params) {
|
|
5024
|
+
const cached = queryCache.get(params);
|
|
5025
|
+
if (cached) return cached;
|
|
4925
5026
|
const result = {};
|
|
4926
5027
|
for (const [key, value] of params) {
|
|
4927
5028
|
const existing = result[key];
|
|
@@ -4933,6 +5034,7 @@ function queryToObject(params) {
|
|
|
4933
5034
|
result[key] = [existing, value];
|
|
4934
5035
|
}
|
|
4935
5036
|
}
|
|
5037
|
+
queryCache.set(params, result);
|
|
4936
5038
|
return result;
|
|
4937
5039
|
}
|
|
4938
5040
|
|
|
@@ -5101,51 +5203,67 @@ async function toResponse(value, meta) {
|
|
|
5101
5203
|
if (typeof Buffer !== "undefined" && Buffer.isBuffer(value)) {
|
|
5102
5204
|
const headers2 = new Headers({ "Content-Type": "application/octet-stream" });
|
|
5103
5205
|
applyMeta(headers2);
|
|
5104
|
-
return
|
|
5105
|
-
|
|
5106
|
-
|
|
5107
|
-
|
|
5206
|
+
return markBuffered(
|
|
5207
|
+
new Response(value, {
|
|
5208
|
+
status: meta?.status ?? 200,
|
|
5209
|
+
headers: headers2
|
|
5210
|
+
})
|
|
5211
|
+
);
|
|
5108
5212
|
}
|
|
5109
5213
|
if (value instanceof Uint8Array) {
|
|
5110
5214
|
const headers2 = new Headers({ "Content-Type": "application/octet-stream" });
|
|
5111
5215
|
applyMeta(headers2);
|
|
5112
|
-
return
|
|
5113
|
-
|
|
5114
|
-
|
|
5115
|
-
|
|
5216
|
+
return markBuffered(
|
|
5217
|
+
new Response(value, {
|
|
5218
|
+
status: meta?.status ?? 200,
|
|
5219
|
+
headers: headers2
|
|
5220
|
+
})
|
|
5221
|
+
);
|
|
5116
5222
|
}
|
|
5117
5223
|
if (isPlainObject(value) || Array.isArray(value)) {
|
|
5118
5224
|
const body2 = stringifyJson(value);
|
|
5119
5225
|
const headers2 = new Headers({ "Content-Type": "application/json" });
|
|
5120
5226
|
applyMeta(headers2);
|
|
5121
|
-
return
|
|
5122
|
-
|
|
5123
|
-
|
|
5124
|
-
|
|
5227
|
+
return markBuffered(
|
|
5228
|
+
new Response(body2, {
|
|
5229
|
+
status: meta?.status ?? 200,
|
|
5230
|
+
headers: headers2
|
|
5231
|
+
})
|
|
5232
|
+
);
|
|
5125
5233
|
}
|
|
5126
5234
|
if (typeof value === "string") {
|
|
5127
5235
|
const headers2 = new Headers({ "Content-Type": "text/plain" });
|
|
5128
5236
|
applyMeta(headers2);
|
|
5129
|
-
return
|
|
5130
|
-
|
|
5131
|
-
|
|
5132
|
-
|
|
5237
|
+
return markBuffered(
|
|
5238
|
+
new Response(value, {
|
|
5239
|
+
status: meta?.status ?? 200,
|
|
5240
|
+
headers: headers2
|
|
5241
|
+
})
|
|
5242
|
+
);
|
|
5133
5243
|
}
|
|
5134
5244
|
if (typeof value === "number" || typeof value === "boolean") {
|
|
5135
5245
|
const headers2 = new Headers({ "Content-Type": "text/plain" });
|
|
5136
5246
|
applyMeta(headers2);
|
|
5137
|
-
return
|
|
5138
|
-
|
|
5139
|
-
|
|
5140
|
-
|
|
5247
|
+
return markBuffered(
|
|
5248
|
+
new Response(String(value), {
|
|
5249
|
+
status: meta?.status ?? 200,
|
|
5250
|
+
headers: headers2
|
|
5251
|
+
})
|
|
5252
|
+
);
|
|
5141
5253
|
}
|
|
5142
5254
|
const body = stringifyJson(value);
|
|
5143
5255
|
const headers = new Headers({ "Content-Type": "application/json" });
|
|
5144
5256
|
applyMeta(headers);
|
|
5145
|
-
return
|
|
5146
|
-
|
|
5147
|
-
|
|
5148
|
-
|
|
5257
|
+
return markBuffered(
|
|
5258
|
+
new Response(body, {
|
|
5259
|
+
status: meta?.status ?? 200,
|
|
5260
|
+
headers
|
|
5261
|
+
})
|
|
5262
|
+
);
|
|
5263
|
+
}
|
|
5264
|
+
function markBuffered(response) {
|
|
5265
|
+
markBufferedBody(response);
|
|
5266
|
+
return response;
|
|
5149
5267
|
}
|
|
5150
5268
|
|
|
5151
5269
|
// src/injection/injectParams.ts
|
|
@@ -5285,18 +5403,7 @@ async function invokeHandler(handler, ctx, body, middlewares, injectors) {
|
|
|
5285
5403
|
sseWriter.close();
|
|
5286
5404
|
}
|
|
5287
5405
|
};
|
|
5288
|
-
|
|
5289
|
-
try {
|
|
5290
|
-
const result = await injectParamsAsync(handler, ctx, body, injectors);
|
|
5291
|
-
const sseResponse = pickSseAndAutoClose();
|
|
5292
|
-
if (sseResponse) return sseResponse;
|
|
5293
|
-
return toResponse(wrapResult(result, ctx), meta);
|
|
5294
|
-
} catch (err) {
|
|
5295
|
-
autoCloseSseOnError();
|
|
5296
|
-
throw err;
|
|
5297
|
-
}
|
|
5298
|
-
}
|
|
5299
|
-
const finalHandler = async () => {
|
|
5406
|
+
const executeAndFormat = async () => {
|
|
5300
5407
|
try {
|
|
5301
5408
|
const result = await injectParamsAsync(handler, ctx, body, injectors);
|
|
5302
5409
|
const sseResponse = pickSseAndAutoClose();
|
|
@@ -5307,6 +5414,10 @@ async function invokeHandler(handler, ctx, body, middlewares, injectors) {
|
|
|
5307
5414
|
throw err;
|
|
5308
5415
|
}
|
|
5309
5416
|
};
|
|
5417
|
+
if (!middlewares || middlewares.length === 0) {
|
|
5418
|
+
return executeAndFormat();
|
|
5419
|
+
}
|
|
5420
|
+
const finalHandler = executeAndFormat;
|
|
5310
5421
|
return await compose(middlewares, ctx, finalHandler);
|
|
5311
5422
|
}
|
|
5312
5423
|
|
|
@@ -5328,6 +5439,20 @@ async function sendNodeResponse(response, res) {
|
|
|
5328
5439
|
}
|
|
5329
5440
|
}
|
|
5330
5441
|
if (response.body) {
|
|
5442
|
+
if (hasBufferedBody(response)) {
|
|
5443
|
+
const buffer = await readBufferedBody(response.body);
|
|
5444
|
+
await new Promise((resolve) => {
|
|
5445
|
+
res.on("close", () => {
|
|
5446
|
+
if (!res.writableEnded) res.destroy();
|
|
5447
|
+
});
|
|
5448
|
+
if (res.destroyed) {
|
|
5449
|
+
resolve();
|
|
5450
|
+
return;
|
|
5451
|
+
}
|
|
5452
|
+
res.end(buffer, () => resolve());
|
|
5453
|
+
});
|
|
5454
|
+
return;
|
|
5455
|
+
}
|
|
5331
5456
|
const nodeStream = Readable.fromWeb(response.body);
|
|
5332
5457
|
await new Promise((resolve, reject) => {
|
|
5333
5458
|
nodeStream.on("error", reject);
|
|
@@ -5346,6 +5471,25 @@ async function sendNodeResponse(response, res) {
|
|
|
5346
5471
|
}
|
|
5347
5472
|
res.end();
|
|
5348
5473
|
}
|
|
5474
|
+
async function readBufferedBody(body) {
|
|
5475
|
+
const reader = body.getReader();
|
|
5476
|
+
const chunks = [];
|
|
5477
|
+
try {
|
|
5478
|
+
for (; ; ) {
|
|
5479
|
+
const { done, value } = await reader.read();
|
|
5480
|
+
if (done) break;
|
|
5481
|
+
chunks.push(value);
|
|
5482
|
+
}
|
|
5483
|
+
} finally {
|
|
5484
|
+
try {
|
|
5485
|
+
reader.releaseLock();
|
|
5486
|
+
} catch {
|
|
5487
|
+
}
|
|
5488
|
+
}
|
|
5489
|
+
if (chunks.length === 0) return new Uint8Array(0);
|
|
5490
|
+
if (chunks.length === 1) return chunks[0];
|
|
5491
|
+
return Buffer.concat(chunks);
|
|
5492
|
+
}
|
|
5349
5493
|
|
|
5350
5494
|
// src/validator/validateInput.ts
|
|
5351
5495
|
init_schemaName();
|
|
@@ -5389,9 +5533,9 @@ async function validateInput(schemaPath, method, inputType, input) {
|
|
|
5389
5533
|
function mapZodIssues(error) {
|
|
5390
5534
|
return error.issues.map((issue) => {
|
|
5391
5535
|
const code = mapZodCode(issue);
|
|
5392
|
-
const
|
|
5536
|
+
const path31 = issue.path.map(String).join(".") || "";
|
|
5393
5537
|
return {
|
|
5394
|
-
path:
|
|
5538
|
+
path: path31,
|
|
5395
5539
|
code,
|
|
5396
5540
|
expected: issue.expected ?? mapExpectedFromMessage(issue.message),
|
|
5397
5541
|
received: issue.received ?? mapReceivedFromMessage(issue.message),
|
|
@@ -5513,17 +5657,6 @@ async function compressBody(encoding, body) {
|
|
|
5513
5657
|
return deflateAsync(buf);
|
|
5514
5658
|
}
|
|
5515
5659
|
}
|
|
5516
|
-
function mergeVary(meta, value) {
|
|
5517
|
-
const existing = meta.headers["Vary"] ?? meta.headers["vary"];
|
|
5518
|
-
if (!existing) {
|
|
5519
|
-
meta.headers["Vary"] = value;
|
|
5520
|
-
return;
|
|
5521
|
-
}
|
|
5522
|
-
if (!existing.toLowerCase().includes(value.toLowerCase())) {
|
|
5523
|
-
const key = meta.headers["Vary"] !== void 0 ? "Vary" : "vary";
|
|
5524
|
-
meta.headers[key] = `${existing}, ${value}`;
|
|
5525
|
-
}
|
|
5526
|
-
}
|
|
5527
5660
|
function compression(options = {}) {
|
|
5528
5661
|
const threshold = options.threshold ?? DEFAULT_THRESHOLD;
|
|
5529
5662
|
return async (ctx, next) => {
|
|
@@ -5623,9 +5756,9 @@ function etag(options = {}) {
|
|
|
5623
5756
|
}
|
|
5624
5757
|
|
|
5625
5758
|
// src/server/handleWsUpgrade.ts
|
|
5626
|
-
import
|
|
5759
|
+
import fs16 from "fs";
|
|
5627
5760
|
import { WebSocketServer, WebSocket } from "ws";
|
|
5628
|
-
import
|
|
5761
|
+
import path18 from "path";
|
|
5629
5762
|
|
|
5630
5763
|
// src/server/serverUtils.ts
|
|
5631
5764
|
function nodeHttpToWebHeaders(req) {
|
|
@@ -5644,13 +5777,15 @@ function buildErrorResponse(err, config) {
|
|
|
5644
5777
|
try {
|
|
5645
5778
|
return formatErrorResponse(err, config);
|
|
5646
5779
|
} catch {
|
|
5647
|
-
|
|
5780
|
+
const fallback = new Response(
|
|
5648
5781
|
JSON.stringify({ error: { code: "INTERNAL_ERROR", message: "Internal Server Error" } }),
|
|
5649
5782
|
{
|
|
5650
5783
|
status: 500,
|
|
5651
5784
|
headers: { "Content-Type": "application/json" }
|
|
5652
5785
|
}
|
|
5653
5786
|
);
|
|
5787
|
+
markBufferedBody(fallback);
|
|
5788
|
+
return fallback;
|
|
5654
5789
|
}
|
|
5655
5790
|
}
|
|
5656
5791
|
|
|
@@ -5760,7 +5895,7 @@ async function loadWsHandler(filePath, ctx, rootDir) {
|
|
|
5760
5895
|
const dist = getDevDist();
|
|
5761
5896
|
if (dist) {
|
|
5762
5897
|
const sourcePath = prodPathToSourcePath(filePath, rootDir, dist);
|
|
5763
|
-
if (sourcePath &&
|
|
5898
|
+
if (sourcePath && fs16.existsSync(sourcePath)) {
|
|
5764
5899
|
await ensureCompiled(sourcePath, rootDir, dist);
|
|
5765
5900
|
}
|
|
5766
5901
|
}
|
|
@@ -5775,27 +5910,47 @@ async function loadWsHandler(filePath, ctx, rootDir) {
|
|
|
5775
5910
|
function bindEvents(rawSocket, handlers) {
|
|
5776
5911
|
if (!handlers) return;
|
|
5777
5912
|
const ws = wrapWsSocket(rawSocket);
|
|
5913
|
+
const guard = (event, fn) => {
|
|
5914
|
+
try {
|
|
5915
|
+
fn();
|
|
5916
|
+
} catch (err) {
|
|
5917
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
5918
|
+
if (handlers.onError) {
|
|
5919
|
+
try {
|
|
5920
|
+
handlers.onError(ws, error);
|
|
5921
|
+
} catch (hookErr) {
|
|
5922
|
+
console.error(`[faapi] WS onError \u56DE\u8C03\u629B\u9519 (after ${event}):`, hookErr);
|
|
5923
|
+
}
|
|
5924
|
+
} else {
|
|
5925
|
+
console.error(`[faapi] WS ${event} \u56DE\u8C03\u629B\u9519:`, error);
|
|
5926
|
+
}
|
|
5927
|
+
}
|
|
5928
|
+
};
|
|
5778
5929
|
if (handlers.onOpen) {
|
|
5779
5930
|
if (rawSocket.readyState === WebSocket.OPEN) {
|
|
5780
|
-
handlers.onOpen(ws);
|
|
5931
|
+
guard("onOpen", () => handlers.onOpen(ws));
|
|
5781
5932
|
} else {
|
|
5782
|
-
rawSocket.once("open", () => handlers.onOpen(ws));
|
|
5933
|
+
rawSocket.once("open", () => guard("onOpen", () => handlers.onOpen(ws)));
|
|
5783
5934
|
}
|
|
5784
5935
|
}
|
|
5785
5936
|
if (handlers.onMessage) {
|
|
5786
5937
|
rawSocket.on("message", (data, isBinary) => {
|
|
5787
5938
|
const buf = toWsBuffer(data);
|
|
5788
|
-
handlers.onMessage(ws, isBinary ? buf : buf.toString("utf8"));
|
|
5939
|
+
guard("onMessage", () => handlers.onMessage(ws, isBinary ? buf : buf.toString("utf8")));
|
|
5789
5940
|
});
|
|
5790
5941
|
}
|
|
5791
5942
|
if (handlers.onClose) {
|
|
5792
5943
|
rawSocket.on("close", (code, reason) => {
|
|
5793
|
-
handlers.onClose(ws, code, reason.toString("utf8"));
|
|
5944
|
+
guard("onClose", () => handlers.onClose(ws, code, reason.toString("utf8")));
|
|
5794
5945
|
});
|
|
5795
5946
|
}
|
|
5796
5947
|
if (handlers.onError) {
|
|
5797
5948
|
rawSocket.on("error", (err) => {
|
|
5798
|
-
|
|
5949
|
+
try {
|
|
5950
|
+
handlers.onError(ws, err);
|
|
5951
|
+
} catch (hookErr) {
|
|
5952
|
+
console.error("[faapi] WS onError \u56DE\u8C03\u629B\u9519 (after onError):", hookErr);
|
|
5953
|
+
}
|
|
5799
5954
|
});
|
|
5800
5955
|
}
|
|
5801
5956
|
}
|
|
@@ -5874,7 +6029,7 @@ function attachWebSocket(options) {
|
|
|
5874
6029
|
const finalHandler = async () => {
|
|
5875
6030
|
let handlers;
|
|
5876
6031
|
try {
|
|
5877
|
-
const absoluteFilePath =
|
|
6032
|
+
const absoluteFilePath = path18.resolve(rootDir, route.filePath);
|
|
5878
6033
|
handlers = await loadWsHandler(absoluteFilePath, ctx, rootDir);
|
|
5879
6034
|
} catch (err) {
|
|
5880
6035
|
const reason = err instanceof Error ? err.message : String(err);
|
|
@@ -6080,9 +6235,17 @@ function createServer(options) {
|
|
|
6080
6235
|
bodyLimit,
|
|
6081
6236
|
trustedProxy,
|
|
6082
6237
|
registries
|
|
6083
|
-
).catch(() => {
|
|
6084
|
-
|
|
6085
|
-
|
|
6238
|
+
).catch((err) => {
|
|
6239
|
+
console.error("[faapi] Request pipeline failed after error response attempt:", err);
|
|
6240
|
+
try {
|
|
6241
|
+
if (!res.headersSent) {
|
|
6242
|
+
res.statusCode = 500;
|
|
6243
|
+
res.end();
|
|
6244
|
+
} else if (!res.writableEnded) {
|
|
6245
|
+
res.end();
|
|
6246
|
+
}
|
|
6247
|
+
} catch {
|
|
6248
|
+
}
|
|
6086
6249
|
});
|
|
6087
6250
|
});
|
|
6088
6251
|
attachWebSocket({
|
|
@@ -6125,13 +6288,14 @@ function getRoutePaths(route, rootDir, dist) {
|
|
|
6125
6288
|
let cached = routePathCache.get(route);
|
|
6126
6289
|
if (!cached) {
|
|
6127
6290
|
cached = {
|
|
6128
|
-
absFilePath:
|
|
6291
|
+
absFilePath: path19.resolve(rootDir, route.filePath),
|
|
6129
6292
|
schemaPath: getRuntimeSchemaPath(route.filePath, dist, rootDir)
|
|
6130
6293
|
};
|
|
6131
6294
|
routePathCache.set(route, cached);
|
|
6132
6295
|
}
|
|
6133
6296
|
return cached;
|
|
6134
6297
|
}
|
|
6298
|
+
var mergedInjectorsCache = /* @__PURE__ */ new WeakMap();
|
|
6135
6299
|
function createRoutePipeline(opts) {
|
|
6136
6300
|
const { routes, method, urlPath, url, ctx, request, rootDir, dist, globalInjectors } = opts;
|
|
6137
6301
|
return async () => {
|
|
@@ -6164,7 +6328,15 @@ function createRoutePipeline(opts) {
|
|
|
6164
6328
|
route.injectors = {};
|
|
6165
6329
|
}
|
|
6166
6330
|
}
|
|
6167
|
-
|
|
6331
|
+
let mergedInjectors = route.injectors;
|
|
6332
|
+
if (globalInjectors) {
|
|
6333
|
+
let merged = mergedInjectorsCache.get(route);
|
|
6334
|
+
if (!merged) {
|
|
6335
|
+
merged = { ...globalInjectors, ...route.injectors };
|
|
6336
|
+
mergedInjectorsCache.set(route, merged);
|
|
6337
|
+
}
|
|
6338
|
+
mergedInjectors = merged;
|
|
6339
|
+
}
|
|
6168
6340
|
return await invokeHandler(routeModule.handler, ctx, body, route.middlewares, mergedInjectors);
|
|
6169
6341
|
};
|
|
6170
6342
|
}
|
|
@@ -6214,6 +6386,10 @@ async function handleRequest(routes, rootDir, dist, req, res, outerMiddlewares,
|
|
|
6214
6386
|
await sendSuccessResponse(response, res);
|
|
6215
6387
|
} catch (err) {
|
|
6216
6388
|
if (res.destroyed || res.writableEnded) return;
|
|
6389
|
+
if (err instanceof PayloadTooLargeError) {
|
|
6390
|
+
res.setHeader("Connection", "close");
|
|
6391
|
+
res.once("finish", () => req.destroy());
|
|
6392
|
+
}
|
|
6217
6393
|
await sendErrorResponse(err, meta, res, onError, ctx);
|
|
6218
6394
|
}
|
|
6219
6395
|
}
|
|
@@ -6247,8 +6423,8 @@ function applyPluginWrappers(server, handlerWrappers, upgradeWrappers) {
|
|
|
6247
6423
|
}
|
|
6248
6424
|
|
|
6249
6425
|
// src/cli/generateRoutes.ts
|
|
6250
|
-
import
|
|
6251
|
-
import
|
|
6426
|
+
import fs17 from "fs";
|
|
6427
|
+
import path20 from "path";
|
|
6252
6428
|
async function hydrateRoutes(manifest) {
|
|
6253
6429
|
const hydrateRoute = (serialized) => ({
|
|
6254
6430
|
method: serialized.method,
|
|
@@ -6272,102 +6448,306 @@ async function hydrateRoutes(manifest) {
|
|
|
6272
6448
|
return { routes, wsRoutes };
|
|
6273
6449
|
}
|
|
6274
6450
|
|
|
6275
|
-
// src/
|
|
6276
|
-
|
|
6277
|
-
|
|
6278
|
-
// src/ast/extractAgentMetadata.ts
|
|
6279
|
-
import ts10 from "typescript";
|
|
6280
|
-
init_resolveTypeNode();
|
|
6281
|
-
function extractAgentMetadata(program, filePath, pathMeta) {
|
|
6282
|
-
const sourceFile = program.getSourceFile(filePath);
|
|
6283
|
-
if (!sourceFile) return null;
|
|
6284
|
-
const configFound = findConfigExport(sourceFile);
|
|
6285
|
-
if (!configFound) {
|
|
6286
|
-
throw SchemaExtractionError.at(
|
|
6287
|
-
sourceFile,
|
|
6288
|
-
"config.systemPrompt",
|
|
6289
|
-
"agent \u5FC5\u586B\u2014\u2014\u8BF7\u5728 config \u5757\u58F0\u660E\u7CFB\u7EDF\u63D0\u793A\u8BCD\uFF08\u5B57\u7B26\u4E32\u5B57\u9762\u91CF\u3001\u65E0\u63D2\u503C\u6A21\u677F\u5B57\u7B26\u4E32\u6216\u5176 + \u62FC\u63A5\uFF09",
|
|
6290
|
-
sourceFile
|
|
6291
|
-
);
|
|
6292
|
-
}
|
|
6293
|
-
if (!configFound.objectLiteral) {
|
|
6294
|
-
throw SchemaExtractionError.at(
|
|
6295
|
-
configFound.jsDocOwner,
|
|
6296
|
-
"config",
|
|
6297
|
-
"\u4EC5\u652F\u6301\u5BF9\u8C61\u5B57\u9762\u91CF\u3001\u7BAD\u5934\u51FD\u6570\u8FD4\u56DE\u5BF9\u8C61\u5B57\u9762\u91CF\u6216\u51FD\u6570\u8FD4\u56DE\u5BF9\u8C61\u5B57\u9762\u91CF",
|
|
6298
|
-
sourceFile
|
|
6299
|
-
);
|
|
6300
|
-
}
|
|
6301
|
-
const objectLiteral = configFound.objectLiteral;
|
|
6302
|
-
const jsDoc = getJSDocFromNode(configFound.jsDocOwner);
|
|
6303
|
-
const description = extractDescription(jsDoc);
|
|
6304
|
-
const agentNameOverride = extractJSDocTagValue(jsDoc, "agent");
|
|
6305
|
-
const { systemPrompt, tools, agents, model, maxTurns, inputDescription } = extractConfigFields(
|
|
6306
|
-
objectLiteral,
|
|
6307
|
-
sourceFile
|
|
6308
|
-
);
|
|
6451
|
+
// src/task/idleTaskDriver.ts
|
|
6452
|
+
function createIdleTaskDriver() {
|
|
6309
6453
|
return {
|
|
6310
|
-
|
|
6311
|
-
|
|
6312
|
-
|
|
6313
|
-
|
|
6314
|
-
|
|
6315
|
-
|
|
6316
|
-
|
|
6317
|
-
|
|
6318
|
-
|
|
6319
|
-
|
|
6454
|
+
enqueue() {
|
|
6455
|
+
return Promise.reject(
|
|
6456
|
+
new Error(
|
|
6457
|
+
"[faapi] New task(s) were registered without a queue driver. Set config.task.driver to 'pgboss' or 'bullmq' (install the matching @faapi/task-* package) and restart the server."
|
|
6458
|
+
)
|
|
6459
|
+
);
|
|
6460
|
+
},
|
|
6461
|
+
startWorker() {
|
|
6462
|
+
},
|
|
6463
|
+
async stop() {
|
|
6464
|
+
},
|
|
6465
|
+
async stopWorkers() {
|
|
6466
|
+
}
|
|
6320
6467
|
};
|
|
6321
6468
|
}
|
|
6322
|
-
|
|
6323
|
-
|
|
6324
|
-
|
|
6325
|
-
|
|
6326
|
-
|
|
6327
|
-
|
|
6328
|
-
|
|
6329
|
-
|
|
6330
|
-
|
|
6331
|
-
|
|
6332
|
-
|
|
6333
|
-
|
|
6334
|
-
|
|
6335
|
-
|
|
6336
|
-
|
|
6337
|
-
|
|
6338
|
-
|
|
6339
|
-
|
|
6340
|
-
|
|
6341
|
-
|
|
6342
|
-
|
|
6343
|
-
|
|
6469
|
+
|
|
6470
|
+
// src/cli/loadPlugins.ts
|
|
6471
|
+
import path22 from "path";
|
|
6472
|
+
import fs19 from "fs";
|
|
6473
|
+
import { pathToFileURL as pathToFileURL3 } from "url";
|
|
6474
|
+
|
|
6475
|
+
// src/cli/compileConfig.ts
|
|
6476
|
+
import path21 from "path";
|
|
6477
|
+
import fs18 from "fs";
|
|
6478
|
+
async function compileProjectModules(entryPoints, rootDir, dist) {
|
|
6479
|
+
const { insideFiles, outsideFiles } = await collectRelativeImports(entryPoints, rootDir);
|
|
6480
|
+
const esbuild = await import("esbuild");
|
|
6481
|
+
const aliasPlugins = buildAliasPlugins(rootDir);
|
|
6482
|
+
const absDist = path21.resolve(rootDir, dist);
|
|
6483
|
+
await esbuild.build({
|
|
6484
|
+
entryPoints: [...entryPoints, ...outsideFiles],
|
|
6485
|
+
outdir: absDist,
|
|
6486
|
+
outbase: rootDir,
|
|
6487
|
+
bundle: false,
|
|
6488
|
+
platform: "node",
|
|
6489
|
+
format: "esm",
|
|
6490
|
+
sourcemap: true,
|
|
6491
|
+
packages: "external",
|
|
6492
|
+
plugins: aliasPlugins,
|
|
6493
|
+
logLevel: "silent"
|
|
6344
6494
|
});
|
|
6345
|
-
|
|
6346
|
-
|
|
6347
|
-
|
|
6348
|
-
|
|
6349
|
-
|
|
6350
|
-
|
|
6351
|
-
|
|
6352
|
-
|
|
6353
|
-
|
|
6354
|
-
|
|
6355
|
-
|
|
6356
|
-
|
|
6357
|
-
|
|
6358
|
-
}
|
|
6495
|
+
if (insideFiles.length > 0) {
|
|
6496
|
+
const appOutbase = path21.resolve(rootDir, "src");
|
|
6497
|
+
await esbuild.build({
|
|
6498
|
+
entryPoints: insideFiles,
|
|
6499
|
+
outdir: absDist,
|
|
6500
|
+
outbase: appOutbase,
|
|
6501
|
+
bundle: false,
|
|
6502
|
+
platform: "node",
|
|
6503
|
+
format: "esm",
|
|
6504
|
+
sourcemap: true,
|
|
6505
|
+
packages: "external",
|
|
6506
|
+
plugins: aliasPlugins,
|
|
6507
|
+
logLevel: "silent"
|
|
6508
|
+
});
|
|
6359
6509
|
}
|
|
6360
|
-
return
|
|
6510
|
+
return { insideFiles, outsideFiles };
|
|
6361
6511
|
}
|
|
6362
|
-
|
|
6363
|
-
|
|
6364
|
-
|
|
6365
|
-
|
|
6366
|
-
|
|
6367
|
-
|
|
6368
|
-
|
|
6369
|
-
|
|
6370
|
-
|
|
6512
|
+
|
|
6513
|
+
// src/cli/loadPlugins.ts
|
|
6514
|
+
async function loadPlugins(declarations, ctx, rootDir, dist) {
|
|
6515
|
+
const handlerWrappers = [];
|
|
6516
|
+
const upgradeWrappers = [];
|
|
6517
|
+
const failures = [];
|
|
6518
|
+
if (!declarations || declarations.length === 0) {
|
|
6519
|
+
return { handlerWrappers, upgradeWrappers, failures };
|
|
6520
|
+
}
|
|
6521
|
+
const fullCtx = {
|
|
6522
|
+
...ctx,
|
|
6523
|
+
wrapHandler: (fn) => {
|
|
6524
|
+
handlerWrappers.push(fn);
|
|
6525
|
+
},
|
|
6526
|
+
wrapUpgradeHandler: (fn) => {
|
|
6527
|
+
upgradeWrappers.push(fn);
|
|
6528
|
+
}
|
|
6529
|
+
};
|
|
6530
|
+
const loaded = /* @__PURE__ */ new Set();
|
|
6531
|
+
for (const decl of declarations) {
|
|
6532
|
+
let specifier;
|
|
6533
|
+
let options;
|
|
6534
|
+
let enable;
|
|
6535
|
+
try {
|
|
6536
|
+
({ specifier, options, enable } = resolveDeclaration(decl));
|
|
6537
|
+
} catch (err) {
|
|
6538
|
+
failures.push({
|
|
6539
|
+
specifier: JSON.stringify(decl),
|
|
6540
|
+
reason: err instanceof Error ? err.message : String(err)
|
|
6541
|
+
});
|
|
6542
|
+
continue;
|
|
6543
|
+
}
|
|
6544
|
+
if (enable === false) continue;
|
|
6545
|
+
if (loaded.has(specifier)) {
|
|
6546
|
+
console.warn(`! Plugin already loaded: ${specifier}, skipping`);
|
|
6547
|
+
continue;
|
|
6548
|
+
}
|
|
6549
|
+
loaded.add(specifier);
|
|
6550
|
+
try {
|
|
6551
|
+
const mod = await importPluginModule(specifier, rootDir ?? process.cwd(), dist);
|
|
6552
|
+
const plugin = mod.default ?? mod;
|
|
6553
|
+
if (typeof plugin.setup !== "function") {
|
|
6554
|
+
failures.push({ specifier, reason: "plugin has no setup function" });
|
|
6555
|
+
continue;
|
|
6556
|
+
}
|
|
6557
|
+
await plugin.setup({ ...fullCtx, options });
|
|
6558
|
+
console.log(`- Plugin loaded: ${plugin.name ?? specifier}`);
|
|
6559
|
+
} catch (err) {
|
|
6560
|
+
failures.push({
|
|
6561
|
+
specifier,
|
|
6562
|
+
reason: err instanceof Error ? err.message : String(err)
|
|
6563
|
+
});
|
|
6564
|
+
}
|
|
6565
|
+
}
|
|
6566
|
+
if (failures.length > 0) {
|
|
6567
|
+
console.error(
|
|
6568
|
+
`[faapi] ${failures.length} plugin(s) failed to load:
|
|
6569
|
+
` + failures.map((f) => ` - ${f.specifier}: ${f.reason}`).join("\n")
|
|
6570
|
+
);
|
|
6571
|
+
}
|
|
6572
|
+
return { handlerWrappers, upgradeWrappers, failures };
|
|
6573
|
+
}
|
|
6574
|
+
var TS_EXTS = /\.(ts|tsx|mts|cts)$/;
|
|
6575
|
+
var JS_EXTS = /\.(js|mjs|cjs|jsx)$/;
|
|
6576
|
+
function resolveLocalPluginSource(specifier, baseDir) {
|
|
6577
|
+
const base = path22.isAbsolute(specifier) ? specifier : path22.resolve(baseDir, specifier);
|
|
6578
|
+
if ((TS_EXTS.test(base) || JS_EXTS.test(base)) && fs19.existsSync(base)) return base;
|
|
6579
|
+
for (const ext of [".ts", ".js"]) {
|
|
6580
|
+
if (fs19.existsSync(base + ext)) return base + ext;
|
|
6581
|
+
}
|
|
6582
|
+
for (const indexExt of ["/index.ts", "/index.js"]) {
|
|
6583
|
+
if (fs19.existsSync(base + indexExt)) return base + indexExt;
|
|
6584
|
+
}
|
|
6585
|
+
return null;
|
|
6586
|
+
}
|
|
6587
|
+
function mirrorProductPath(sourcePath, baseDir, distDir) {
|
|
6588
|
+
let rel = path22.relative(baseDir, sourcePath).replace(/\\/g, "/");
|
|
6589
|
+
if (rel.startsWith("src/")) rel = rel.slice(4);
|
|
6590
|
+
return path22.resolve(distDir, rel.replace(TS_EXTS, ".js"));
|
|
6591
|
+
}
|
|
6592
|
+
async function importPluginModule(specifier, baseDir, dist) {
|
|
6593
|
+
const isRelative = specifier.startsWith("./") || specifier.startsWith("../");
|
|
6594
|
+
if (!isRelative && !path22.isAbsolute(specifier)) {
|
|
6595
|
+
return import(specifier);
|
|
6596
|
+
}
|
|
6597
|
+
const distDir = dist ? path22.resolve(baseDir, dist) : null;
|
|
6598
|
+
const sourcePath = resolveLocalPluginSource(specifier, baseDir);
|
|
6599
|
+
const productPath = sourcePath && distDir ? mirrorProductPath(sourcePath, baseDir, distDir) : null;
|
|
6600
|
+
if (productPath !== null && fs19.existsSync(productPath) && (sourcePath === null || isProductFresh(sourcePath, productPath))) {
|
|
6601
|
+
return import(pathToFileURL3(productPath).href);
|
|
6602
|
+
}
|
|
6603
|
+
if (sourcePath && TS_EXTS.test(sourcePath)) {
|
|
6604
|
+
if (!isDevOnDemandEnabled() || !distDir) {
|
|
6605
|
+
throw new Error(
|
|
6606
|
+
`Local plugin "${specifier}" has no up-to-date build artifact (dist is stale or missing). Run "faapi build" first, or use "faapi dev" for on-demand compilation.`
|
|
6607
|
+
);
|
|
6608
|
+
}
|
|
6609
|
+
const relFromRoot = path22.relative(baseDir, sourcePath).replace(/\\/g, "/");
|
|
6610
|
+
if (relFromRoot.startsWith("src/")) {
|
|
6611
|
+
await ensureCompiled(sourcePath, baseDir, dist);
|
|
6612
|
+
} else {
|
|
6613
|
+
await compileProjectModules([sourcePath], baseDir, dist);
|
|
6614
|
+
}
|
|
6615
|
+
if (productPath && fs19.existsSync(productPath)) {
|
|
6616
|
+
return import(pathToFileURL3(productPath).href);
|
|
6617
|
+
}
|
|
6618
|
+
}
|
|
6619
|
+
if (sourcePath && JS_EXTS.test(sourcePath)) {
|
|
6620
|
+
return import(pathToFileURL3(sourcePath).href);
|
|
6621
|
+
}
|
|
6622
|
+
const candidates = [
|
|
6623
|
+
`${specifier}.ts`,
|
|
6624
|
+
`${specifier}/index.ts`,
|
|
6625
|
+
`${specifier}.js`,
|
|
6626
|
+
`${specifier}/index.js`
|
|
6627
|
+
].map((c) => path22.isAbsolute(c) ? c : path22.join(baseDir, c));
|
|
6628
|
+
throw new Error(
|
|
6629
|
+
`Cannot find local plugin "${specifier}" (resolved from ${baseDir}). Expected one of:
|
|
6630
|
+
${candidates.join("\n ")}
|
|
6631
|
+
Create the plugin file (export default { name, setup(ctx) {...} }) or fix the path.`
|
|
6632
|
+
);
|
|
6633
|
+
}
|
|
6634
|
+
function resolveDeclaration(decl) {
|
|
6635
|
+
if (typeof decl === "string") {
|
|
6636
|
+
return { specifier: decl };
|
|
6637
|
+
}
|
|
6638
|
+
if (Array.isArray(decl)) {
|
|
6639
|
+
const [specifier, options] = decl;
|
|
6640
|
+
return { specifier, options };
|
|
6641
|
+
}
|
|
6642
|
+
if ("package" in decl) {
|
|
6643
|
+
return { specifier: decl.package, options: decl.options, enable: decl.enable };
|
|
6644
|
+
}
|
|
6645
|
+
if ("path" in decl) {
|
|
6646
|
+
return { specifier: decl.path, options: decl.options, enable: decl.enable };
|
|
6647
|
+
}
|
|
6648
|
+
throw new Error(`Invalid plugin declaration: ${JSON.stringify(decl)}`);
|
|
6649
|
+
}
|
|
6650
|
+
|
|
6651
|
+
// src/cli/manifestLoader.ts
|
|
6652
|
+
import fs20 from "fs";
|
|
6653
|
+
import path25 from "path";
|
|
6654
|
+
|
|
6655
|
+
// src/cli/generateAgentArtifacts.ts
|
|
6656
|
+
import path23 from "path";
|
|
6657
|
+
|
|
6658
|
+
// src/ast/extractAgentMetadata.ts
|
|
6659
|
+
import ts10 from "typescript";
|
|
6660
|
+
init_resolveTypeNode();
|
|
6661
|
+
function extractAgentMetadata(program, filePath, pathMeta) {
|
|
6662
|
+
const sourceFile = program.getSourceFile(filePath);
|
|
6663
|
+
if (!sourceFile) return null;
|
|
6664
|
+
const configFound = findConfigExport(sourceFile);
|
|
6665
|
+
if (!configFound) {
|
|
6666
|
+
throw SchemaExtractionError.at(
|
|
6667
|
+
sourceFile,
|
|
6668
|
+
"config.systemPrompt",
|
|
6669
|
+
"agent \u5FC5\u586B\u2014\u2014\u8BF7\u5728 config \u5757\u58F0\u660E\u7CFB\u7EDF\u63D0\u793A\u8BCD\uFF08\u5B57\u7B26\u4E32\u5B57\u9762\u91CF\u3001\u65E0\u63D2\u503C\u6A21\u677F\u5B57\u7B26\u4E32\u6216\u5176 + \u62FC\u63A5\uFF09",
|
|
6670
|
+
sourceFile
|
|
6671
|
+
);
|
|
6672
|
+
}
|
|
6673
|
+
if (!configFound.objectLiteral) {
|
|
6674
|
+
throw SchemaExtractionError.at(
|
|
6675
|
+
configFound.jsDocOwner,
|
|
6676
|
+
"config",
|
|
6677
|
+
"\u4EC5\u652F\u6301\u5BF9\u8C61\u5B57\u9762\u91CF\u3001\u7BAD\u5934\u51FD\u6570\u8FD4\u56DE\u5BF9\u8C61\u5B57\u9762\u91CF\u6216\u51FD\u6570\u8FD4\u56DE\u5BF9\u8C61\u5B57\u9762\u91CF",
|
|
6678
|
+
sourceFile
|
|
6679
|
+
);
|
|
6680
|
+
}
|
|
6681
|
+
const objectLiteral = configFound.objectLiteral;
|
|
6682
|
+
const jsDoc = getJSDocFromNode(configFound.jsDocOwner);
|
|
6683
|
+
const description = extractDescription(jsDoc);
|
|
6684
|
+
const agentNameOverride = extractJSDocTagValue(jsDoc, "agent");
|
|
6685
|
+
const { systemPrompt, tools, agents, model, maxTurns, inputDescription } = extractConfigFields(
|
|
6686
|
+
objectLiteral,
|
|
6687
|
+
sourceFile
|
|
6688
|
+
);
|
|
6689
|
+
return {
|
|
6690
|
+
name: agentNameOverride ?? pathMeta.name,
|
|
6691
|
+
description,
|
|
6692
|
+
filePath: pathMeta.filePath,
|
|
6693
|
+
hasRun: pathMeta.hasRun,
|
|
6694
|
+
systemPrompt,
|
|
6695
|
+
tools,
|
|
6696
|
+
agents,
|
|
6697
|
+
model,
|
|
6698
|
+
maxTurns,
|
|
6699
|
+
inputDescription
|
|
6700
|
+
};
|
|
6701
|
+
}
|
|
6702
|
+
function findConfigExport(sourceFile) {
|
|
6703
|
+
let result = null;
|
|
6704
|
+
ts10.forEachChild(sourceFile, (node) => {
|
|
6705
|
+
if (result) return;
|
|
6706
|
+
if (ts10.isVariableStatement(node) && hasExportModifier(node)) {
|
|
6707
|
+
for (const decl of node.declarationList.declarations) {
|
|
6708
|
+
if (result) break;
|
|
6709
|
+
const nameText = ts10.isIdentifier(decl.name) ? decl.name.text : "";
|
|
6710
|
+
if (nameText !== "config" || !decl.initializer) continue;
|
|
6711
|
+
if (ts10.isObjectLiteralExpression(decl.initializer)) {
|
|
6712
|
+
result = { jsDocOwner: node, objectLiteral: decl.initializer };
|
|
6713
|
+
} else if (ts10.isArrowFunction(decl.initializer)) {
|
|
6714
|
+
result = { jsDocOwner: node, objectLiteral: getReturnObjectLiteral(decl.initializer) };
|
|
6715
|
+
} else {
|
|
6716
|
+
result = { jsDocOwner: node, objectLiteral: null };
|
|
6717
|
+
}
|
|
6718
|
+
}
|
|
6719
|
+
}
|
|
6720
|
+
if (ts10.isFunctionDeclaration(node) && hasExportModifier(node) && node.name?.text === "config") {
|
|
6721
|
+
const returnObj = getReturnObjectLiteral(node);
|
|
6722
|
+
result = { jsDocOwner: node, objectLiteral: returnObj };
|
|
6723
|
+
}
|
|
6724
|
+
});
|
|
6725
|
+
return result;
|
|
6726
|
+
}
|
|
6727
|
+
function getReturnObjectLiteral(fn) {
|
|
6728
|
+
const body = fn.body;
|
|
6729
|
+
if (!body) return null;
|
|
6730
|
+
if (ts10.isObjectLiteralExpression(body)) {
|
|
6731
|
+
return body;
|
|
6732
|
+
}
|
|
6733
|
+
if (ts10.isBlock(body)) {
|
|
6734
|
+
for (const stmt of body.statements) {
|
|
6735
|
+
if (ts10.isReturnStatement(stmt) && stmt.expression && ts10.isObjectLiteralExpression(stmt.expression)) {
|
|
6736
|
+
return stmt.expression;
|
|
6737
|
+
}
|
|
6738
|
+
}
|
|
6739
|
+
}
|
|
6740
|
+
return null;
|
|
6741
|
+
}
|
|
6742
|
+
function extractConfigFields(objLit, sourceFile) {
|
|
6743
|
+
let systemPrompt;
|
|
6744
|
+
let tools;
|
|
6745
|
+
let agents;
|
|
6746
|
+
let model;
|
|
6747
|
+
let maxTurns;
|
|
6748
|
+
let inputDescription;
|
|
6749
|
+
for (const prop of objLit.properties) {
|
|
6750
|
+
if (ts10.isSpreadAssignment(prop)) continue;
|
|
6371
6751
|
if (!ts10.isPropertyAssignment(prop)) {
|
|
6372
6752
|
throw SchemaExtractionError.at(
|
|
6373
6753
|
prop,
|
|
@@ -6553,9 +6933,9 @@ function validateAgentList(metadata) {
|
|
|
6553
6933
|
}
|
|
6554
6934
|
async function generateAgentArtifacts(agents, rootDir, dist) {
|
|
6555
6935
|
const metadata = [];
|
|
6556
|
-
const programByFile = createPrograms(agents.map((m) =>
|
|
6936
|
+
const programByFile = createPrograms(agents.map((m) => path23.resolve(rootDir, m.filePath)));
|
|
6557
6937
|
for (const manifest of agents) {
|
|
6558
|
-
const absPath =
|
|
6938
|
+
const absPath = path23.resolve(rootDir, manifest.filePath);
|
|
6559
6939
|
const program = programByFile.get(absPath);
|
|
6560
6940
|
const result = extractAgentMetadata(program, absPath, {
|
|
6561
6941
|
name: manifest.name,
|
|
@@ -6565,394 +6945,151 @@ async function generateAgentArtifacts(agents, rootDir, dist) {
|
|
|
6565
6945
|
if (!result) {
|
|
6566
6946
|
throw new Error(
|
|
6567
6947
|
`agent "${manifest.name}" \u6E90\u6587\u4EF6\u4E0D\u5728 Program \u4E2D: ${manifest.filePath}\u2014\u2014\u65E0\u6CD5\u751F\u6210\u6E05\u5355`
|
|
6568
|
-
);
|
|
6569
|
-
}
|
|
6570
|
-
metadata.push(result);
|
|
6571
|
-
}
|
|
6572
|
-
validateAgentList(metadata);
|
|
6573
|
-
const serialized = serializeAgents(metadata, dist);
|
|
6574
|
-
const agentsPath = path20.resolve(rootDir, dist, AGENTS_FILE);
|
|
6575
|
-
await writeAgentsModule(serialized, agentsPath);
|
|
6576
|
-
return metadata;
|
|
6577
|
-
}
|
|
6578
|
-
|
|
6579
|
-
// src/cli/generateTaskArtifacts.ts
|
|
6580
|
-
import path21 from "path";
|
|
6581
|
-
init_createProgram();
|
|
6582
|
-
init_atomicWrite();
|
|
6583
|
-
init_generateSchemaFiles();
|
|
6584
|
-
init_extractHandlerTypes();
|
|
6585
|
-
init_generateZodSchema();
|
|
6586
|
-
var TASKS_FILE = "faapi-tasks.js";
|
|
6587
|
-
function serializeTasks(tasks, dist = "dist") {
|
|
6588
|
-
return tasks.map((t) => ({
|
|
6589
|
-
name: t.name,
|
|
6590
|
-
filePath: toProdFilePath(t.filePath, dist),
|
|
6591
|
-
...t.cron !== void 0 ? { cron: t.cron } : {},
|
|
6592
|
-
...t.concurrency !== void 0 ? { concurrency: t.concurrency } : {},
|
|
6593
|
-
...t.retries !== void 0 ? { retries: t.retries } : {},
|
|
6594
|
-
...t.timeoutMs !== void 0 ? { timeoutMs: t.timeoutMs } : {},
|
|
6595
|
-
...t.graceMs !== void 0 ? { graceMs: t.graceMs } : {}
|
|
6596
|
-
}));
|
|
6597
|
-
}
|
|
6598
|
-
async function writeTasksModule(manifest, outputPath) {
|
|
6599
|
-
const content = `// \u81EA\u52A8\u751F\u6210,\u8BF7\u52FF\u624B\u52A8\u7F16\u8F91(faapi build/dev \u4EA7\u7269)
|
|
6600
|
-
export const tasks = ${JSON.stringify(manifest, null, 2)};
|
|
6601
|
-
`;
|
|
6602
|
-
await atomicWriteFile(outputPath, content);
|
|
6603
|
-
}
|
|
6604
|
-
function hydrateTasks(manifest) {
|
|
6605
|
-
return manifest.map((t) => ({
|
|
6606
|
-
name: t.name,
|
|
6607
|
-
filePath: t.filePath,
|
|
6608
|
-
cron: t.cron ?? void 0,
|
|
6609
|
-
concurrency: t.concurrency ?? void 0,
|
|
6610
|
-
retries: t.retries ?? void 0,
|
|
6611
|
-
timeoutMs: t.timeoutMs ?? void 0,
|
|
6612
|
-
graceMs: t.graceMs ?? void 0
|
|
6613
|
-
}));
|
|
6614
|
-
}
|
|
6615
|
-
function collectTaskSchemaSources(tasks, rootDir) {
|
|
6616
|
-
const tasksByFile = /* @__PURE__ */ new Map();
|
|
6617
|
-
for (const task of tasks) {
|
|
6618
|
-
if (!task.inputTypeName) continue;
|
|
6619
|
-
const absPath = path21.resolve(rootDir, task.filePath);
|
|
6620
|
-
let list = tasksByFile.get(absPath);
|
|
6621
|
-
if (!list) {
|
|
6622
|
-
list = [];
|
|
6623
|
-
tasksByFile.set(absPath, list);
|
|
6624
|
-
}
|
|
6625
|
-
list.push(task);
|
|
6626
|
-
}
|
|
6627
|
-
const programByFile = createPrograms([...tasksByFile.keys()]);
|
|
6628
|
-
const resolversByFile = /* @__PURE__ */ new Map();
|
|
6629
|
-
for (const filePath of tasksByFile.keys()) {
|
|
6630
|
-
resolversByFile.set(filePath, createLazyTypeResolver(programByFile.get(filePath), filePath));
|
|
6631
|
-
}
|
|
6632
|
-
const sources = [];
|
|
6633
|
-
for (const [filePath, fileTasks] of tasksByFile) {
|
|
6634
|
-
const program = programByFile.get(filePath);
|
|
6635
|
-
for (const task of fileTasks) {
|
|
6636
|
-
const typeInfo = extractTypeInfo(program, filePath, task.inputTypeName);
|
|
6637
|
-
sources.push({
|
|
6638
|
-
name: task.taskName,
|
|
6639
|
-
filePath,
|
|
6640
|
-
schemaName: task.inputTypeName,
|
|
6641
|
-
typeInfo
|
|
6642
|
-
});
|
|
6643
|
-
}
|
|
6644
|
-
}
|
|
6645
|
-
return { sources, resolversByFile };
|
|
6646
|
-
}
|
|
6647
|
-
function generateTaskSchemaFileSource(sources, resolveType, helpersImportPath) {
|
|
6648
|
-
const lines = ["import { z } from 'zod';"];
|
|
6649
|
-
const schemaBlocks = [];
|
|
6650
|
-
for (const source of sources) {
|
|
6651
|
-
if (!source.typeInfo) continue;
|
|
6652
|
-
const block = [`// task ${source.name} \u2192 ${source.schemaName}`];
|
|
6653
|
-
const schemaCode = generateZodSchemaSource(
|
|
6654
|
-
source.typeInfo,
|
|
6655
|
-
resolveType,
|
|
6656
|
-
source.schemaName,
|
|
6657
|
-
false
|
|
6658
|
-
).replace(/^import \{ z \} from 'zod';\s*\n\s*\n/, "");
|
|
6659
|
-
block.push(schemaCode);
|
|
6660
|
-
block.push("");
|
|
6661
|
-
schemaBlocks.push(block.join("\n"));
|
|
6662
|
-
}
|
|
6663
|
-
const allSchemaCode = schemaBlocks.join("\n");
|
|
6664
|
-
if (helpersImportPath && usesCoerceHelpers(allSchemaCode)) {
|
|
6665
|
-
lines.push(
|
|
6666
|
-
`import { coerceNumber, coerceBoolean, coerceMap, coerceSet } from '${helpersImportPath}';`
|
|
6667
|
-
);
|
|
6668
|
-
}
|
|
6669
|
-
lines.push("");
|
|
6670
|
-
lines.push(...schemaBlocks);
|
|
6671
|
-
return lines.join("\n").replace(/\n+$/, "\n");
|
|
6672
|
-
}
|
|
6673
|
-
async function generateTaskArtifacts(manifests, rootDir, dist) {
|
|
6674
|
-
const metadata = [];
|
|
6675
|
-
if (manifests.length > 0) {
|
|
6676
|
-
const programByFile = createPrograms(manifests.map((m) => path21.resolve(rootDir, m.filePath)));
|
|
6677
|
-
for (const manifest of manifests) {
|
|
6678
|
-
const absPath = path21.resolve(rootDir, manifest.filePath);
|
|
6679
|
-
const result = extractToolMetadata(programByFile.get(absPath), absPath, "run", {
|
|
6680
|
-
name: manifest.name,
|
|
6681
|
-
filePath: manifest.filePath
|
|
6682
|
-
});
|
|
6683
|
-
if (result) {
|
|
6684
|
-
metadata.push({ ...result, taskName: manifest.name });
|
|
6685
|
-
}
|
|
6686
|
-
}
|
|
6687
|
-
}
|
|
6688
|
-
const serialized = serializeTasks(manifests, dist);
|
|
6689
|
-
const tasksPath = path21.resolve(rootDir, dist, TASKS_FILE);
|
|
6690
|
-
await writeTasksModule(serialized, tasksPath);
|
|
6691
|
-
if (metadata.length === 0) {
|
|
6692
|
-
return hydrateTasks(serialized);
|
|
6693
|
-
}
|
|
6694
|
-
const { sources, resolversByFile } = collectTaskSchemaSources(metadata, rootDir);
|
|
6695
|
-
if (sources.length === 0) {
|
|
6696
|
-
return hydrateTasks(serialized);
|
|
6697
|
-
}
|
|
6698
|
-
const sourcesByFile = /* @__PURE__ */ new Map();
|
|
6699
|
-
for (const source of sources) {
|
|
6700
|
-
let list = sourcesByFile.get(source.filePath);
|
|
6701
|
-
if (!list) {
|
|
6702
|
-
list = [];
|
|
6703
|
-
sourcesByFile.set(source.filePath, list);
|
|
6704
|
-
}
|
|
6705
|
-
list.push(source);
|
|
6706
|
-
}
|
|
6707
|
-
const fileEntries = [];
|
|
6708
|
-
for (const [filePath, fileSources] of sourcesByFile) {
|
|
6709
|
-
const relFile = path21.relative(rootDir, filePath).replace(/\\/g, "/");
|
|
6710
|
-
const outputPath = getSchemaOutputPath(relFile, dist, rootDir);
|
|
6711
|
-
const resolver = resolversByFile.get(filePath);
|
|
6712
|
-
let relForDir = relFile;
|
|
6713
|
-
if (relForDir.startsWith("src/")) {
|
|
6714
|
-
relForDir = relForDir.slice(4);
|
|
6715
|
-
}
|
|
6716
|
-
const dirIdx = relForDir.lastIndexOf("/");
|
|
6717
|
-
const zodRelDir = dirIdx >= 0 ? relForDir.slice(0, dirIdx) : "";
|
|
6718
|
-
const helpersImportPath = getHelpersImportPath(zodRelDir);
|
|
6719
|
-
const source = generateTaskSchemaFileSource(
|
|
6720
|
-
fileSources,
|
|
6721
|
-
(name) => resolver?.resolve(name)?.runtimeType,
|
|
6722
|
-
helpersImportPath
|
|
6723
|
-
);
|
|
6724
|
-
fileEntries.push({ outputPath, source });
|
|
6725
|
-
}
|
|
6726
|
-
const allSourceCode = fileEntries.map((e) => e.source).join("\n");
|
|
6727
|
-
if (usesCoerceHelpers(allSourceCode)) {
|
|
6728
|
-
const helpersPath = path21.resolve(rootDir, dist, HELPERS_FILENAME);
|
|
6729
|
-
const { existsSync: existsSync3 } = await import("fs");
|
|
6730
|
-
if (!existsSync3(helpersPath)) {
|
|
6731
|
-
await atomicWriteFile(helpersPath, generateHelpersFileSource());
|
|
6732
|
-
}
|
|
6733
|
-
}
|
|
6734
|
-
await Promise.all(
|
|
6735
|
-
fileEntries.map(({ outputPath, source }) => atomicWriteFile(outputPath, source))
|
|
6736
|
-
);
|
|
6737
|
-
return hydrateTasks(serialized);
|
|
6738
|
-
}
|
|
6739
|
-
|
|
6740
|
-
// src/task/idleTaskDriver.ts
|
|
6741
|
-
function createIdleTaskDriver() {
|
|
6742
|
-
return {
|
|
6743
|
-
enqueue() {
|
|
6744
|
-
return Promise.reject(
|
|
6745
|
-
new Error(
|
|
6746
|
-
"[faapi] New task(s) were registered without a queue driver. Set config.task.driver to 'pgboss' or 'bullmq' (install the matching @faapi/task-* package) and restart the server."
|
|
6747
|
-
)
|
|
6748
|
-
);
|
|
6749
|
-
},
|
|
6750
|
-
startWorker() {
|
|
6751
|
-
},
|
|
6752
|
-
async stop() {
|
|
6753
|
-
},
|
|
6754
|
-
async stopWorkers() {
|
|
6755
|
-
}
|
|
6756
|
-
};
|
|
6757
|
-
}
|
|
6758
|
-
|
|
6759
|
-
// src/cli/loadPlugins.ts
|
|
6760
|
-
import path23 from "path";
|
|
6761
|
-
import fs18 from "fs";
|
|
6762
|
-
import { pathToFileURL as pathToFileURL3 } from "url";
|
|
6763
|
-
|
|
6764
|
-
// src/cli/compileConfig.ts
|
|
6765
|
-
import path22 from "path";
|
|
6766
|
-
import fs17 from "fs";
|
|
6767
|
-
async function compileProjectModules(entryPoints, rootDir, dist) {
|
|
6768
|
-
const { insideFiles, outsideFiles } = await collectRelativeImports(entryPoints, rootDir);
|
|
6769
|
-
const esbuild = await import("esbuild");
|
|
6770
|
-
const aliasPlugins = buildAliasPlugins(rootDir);
|
|
6771
|
-
const absDist = path22.resolve(rootDir, dist);
|
|
6772
|
-
await esbuild.build({
|
|
6773
|
-
entryPoints: [...entryPoints, ...outsideFiles],
|
|
6774
|
-
outdir: absDist,
|
|
6775
|
-
outbase: rootDir,
|
|
6776
|
-
bundle: false,
|
|
6777
|
-
platform: "node",
|
|
6778
|
-
format: "esm",
|
|
6779
|
-
sourcemap: true,
|
|
6780
|
-
packages: "external",
|
|
6781
|
-
plugins: aliasPlugins,
|
|
6782
|
-
logLevel: "silent"
|
|
6783
|
-
});
|
|
6784
|
-
if (insideFiles.length > 0) {
|
|
6785
|
-
const appOutbase = path22.resolve(rootDir, "src");
|
|
6786
|
-
await esbuild.build({
|
|
6787
|
-
entryPoints: insideFiles,
|
|
6788
|
-
outdir: absDist,
|
|
6789
|
-
outbase: appOutbase,
|
|
6790
|
-
bundle: false,
|
|
6791
|
-
platform: "node",
|
|
6792
|
-
format: "esm",
|
|
6793
|
-
sourcemap: true,
|
|
6794
|
-
packages: "external",
|
|
6795
|
-
plugins: aliasPlugins,
|
|
6796
|
-
logLevel: "silent"
|
|
6797
|
-
});
|
|
6798
|
-
}
|
|
6799
|
-
return { insideFiles, outsideFiles };
|
|
6800
|
-
}
|
|
6801
|
-
|
|
6802
|
-
// src/cli/loadPlugins.ts
|
|
6803
|
-
async function loadPlugins(declarations, ctx, rootDir, dist) {
|
|
6804
|
-
const handlerWrappers = [];
|
|
6805
|
-
const upgradeWrappers = [];
|
|
6806
|
-
const failures = [];
|
|
6807
|
-
if (!declarations || declarations.length === 0) {
|
|
6808
|
-
return { handlerWrappers, upgradeWrappers, failures };
|
|
6809
|
-
}
|
|
6810
|
-
const fullCtx = {
|
|
6811
|
-
...ctx,
|
|
6812
|
-
wrapHandler: (fn) => {
|
|
6813
|
-
handlerWrappers.push(fn);
|
|
6814
|
-
},
|
|
6815
|
-
wrapUpgradeHandler: (fn) => {
|
|
6816
|
-
upgradeWrappers.push(fn);
|
|
6817
|
-
}
|
|
6818
|
-
};
|
|
6819
|
-
const loaded = /* @__PURE__ */ new Set();
|
|
6820
|
-
for (const decl of declarations) {
|
|
6821
|
-
let specifier;
|
|
6822
|
-
let options;
|
|
6823
|
-
let enable;
|
|
6824
|
-
try {
|
|
6825
|
-
({ specifier, options, enable } = resolveDeclaration(decl));
|
|
6826
|
-
} catch (err) {
|
|
6827
|
-
failures.push({
|
|
6828
|
-
specifier: JSON.stringify(decl),
|
|
6829
|
-
reason: err instanceof Error ? err.message : String(err)
|
|
6830
|
-
});
|
|
6831
|
-
continue;
|
|
6832
|
-
}
|
|
6833
|
-
if (enable === false) continue;
|
|
6834
|
-
if (loaded.has(specifier)) {
|
|
6835
|
-
console.warn(`! Plugin already loaded: ${specifier}, skipping`);
|
|
6836
|
-
continue;
|
|
6837
|
-
}
|
|
6838
|
-
loaded.add(specifier);
|
|
6839
|
-
try {
|
|
6840
|
-
const mod = await importPluginModule(specifier, rootDir ?? process.cwd(), dist);
|
|
6841
|
-
const plugin = mod.default ?? mod;
|
|
6842
|
-
if (typeof plugin.setup !== "function") {
|
|
6843
|
-
failures.push({ specifier, reason: "plugin has no setup function" });
|
|
6844
|
-
continue;
|
|
6845
|
-
}
|
|
6846
|
-
await plugin.setup({ ...fullCtx, options });
|
|
6847
|
-
console.log(`- Plugin loaded: ${plugin.name ?? specifier}`);
|
|
6848
|
-
} catch (err) {
|
|
6849
|
-
failures.push({
|
|
6850
|
-
specifier,
|
|
6851
|
-
reason: err instanceof Error ? err.message : String(err)
|
|
6852
|
-
});
|
|
6853
|
-
}
|
|
6854
|
-
}
|
|
6855
|
-
if (failures.length > 0) {
|
|
6856
|
-
console.error(
|
|
6857
|
-
`[faapi] ${failures.length} plugin(s) failed to load:
|
|
6858
|
-
` + failures.map((f) => ` - ${f.specifier}: ${f.reason}`).join("\n")
|
|
6859
|
-
);
|
|
6860
|
-
}
|
|
6861
|
-
return { handlerWrappers, upgradeWrappers, failures };
|
|
6862
|
-
}
|
|
6863
|
-
var TS_EXTS = /\.(ts|tsx|mts|cts)$/;
|
|
6864
|
-
var JS_EXTS = /\.(js|mjs|cjs|jsx)$/;
|
|
6865
|
-
function resolveLocalPluginSource(specifier, baseDir) {
|
|
6866
|
-
const base = path23.isAbsolute(specifier) ? specifier : path23.resolve(baseDir, specifier);
|
|
6867
|
-
if ((TS_EXTS.test(base) || JS_EXTS.test(base)) && fs18.existsSync(base)) return base;
|
|
6868
|
-
for (const ext of [".ts", ".js"]) {
|
|
6869
|
-
if (fs18.existsSync(base + ext)) return base + ext;
|
|
6870
|
-
}
|
|
6871
|
-
for (const indexExt of ["/index.ts", "/index.js"]) {
|
|
6872
|
-
if (fs18.existsSync(base + indexExt)) return base + indexExt;
|
|
6948
|
+
);
|
|
6949
|
+
}
|
|
6950
|
+
metadata.push(result);
|
|
6873
6951
|
}
|
|
6874
|
-
|
|
6952
|
+
validateAgentList(metadata);
|
|
6953
|
+
const serialized = serializeAgents(metadata, dist);
|
|
6954
|
+
const agentsPath = path23.resolve(rootDir, dist, AGENTS_FILE);
|
|
6955
|
+
await writeAgentsModule(serialized, agentsPath);
|
|
6956
|
+
return metadata;
|
|
6875
6957
|
}
|
|
6876
|
-
|
|
6877
|
-
|
|
6878
|
-
|
|
6879
|
-
|
|
6958
|
+
|
|
6959
|
+
// src/cli/generateTaskArtifacts.ts
|
|
6960
|
+
init_generateZodArtifacts();
|
|
6961
|
+
import path24 from "path";
|
|
6962
|
+
init_createProgram();
|
|
6963
|
+
init_atomicWrite();
|
|
6964
|
+
init_extractHandlerTypes();
|
|
6965
|
+
init_generateZodSchema();
|
|
6966
|
+
var TASKS_FILE = "faapi-tasks.js";
|
|
6967
|
+
function serializeTasks(tasks, dist = "dist") {
|
|
6968
|
+
return tasks.map((t) => ({
|
|
6969
|
+
name: t.name,
|
|
6970
|
+
filePath: toProdFilePath(t.filePath, dist),
|
|
6971
|
+
...t.cron !== void 0 ? { cron: t.cron } : {},
|
|
6972
|
+
...t.concurrency !== void 0 ? { concurrency: t.concurrency } : {},
|
|
6973
|
+
...t.retries !== void 0 ? { retries: t.retries } : {},
|
|
6974
|
+
...t.timeoutMs !== void 0 ? { timeoutMs: t.timeoutMs } : {},
|
|
6975
|
+
...t.graceMs !== void 0 ? { graceMs: t.graceMs } : {}
|
|
6976
|
+
}));
|
|
6880
6977
|
}
|
|
6881
|
-
function
|
|
6882
|
-
|
|
6883
|
-
|
|
6884
|
-
|
|
6885
|
-
|
|
6886
|
-
}
|
|
6978
|
+
async function writeTasksModule(manifest, outputPath) {
|
|
6979
|
+
const content = `// \u81EA\u52A8\u751F\u6210,\u8BF7\u52FF\u624B\u52A8\u7F16\u8F91(faapi build/dev \u4EA7\u7269)
|
|
6980
|
+
export const tasks = ${JSON.stringify(manifest, null, 2)};
|
|
6981
|
+
`;
|
|
6982
|
+
await atomicWriteFile(outputPath, content);
|
|
6887
6983
|
}
|
|
6888
|
-
|
|
6889
|
-
|
|
6890
|
-
|
|
6891
|
-
|
|
6984
|
+
function hydrateTasks(manifest) {
|
|
6985
|
+
return manifest.map((t) => ({
|
|
6986
|
+
name: t.name,
|
|
6987
|
+
filePath: t.filePath,
|
|
6988
|
+
cron: t.cron ?? void 0,
|
|
6989
|
+
concurrency: t.concurrency ?? void 0,
|
|
6990
|
+
retries: t.retries ?? void 0,
|
|
6991
|
+
timeoutMs: t.timeoutMs ?? void 0,
|
|
6992
|
+
graceMs: t.graceMs ?? void 0
|
|
6993
|
+
}));
|
|
6994
|
+
}
|
|
6995
|
+
function collectTaskSchemaSources(tasks, rootDir) {
|
|
6996
|
+
const tasksByFile = /* @__PURE__ */ new Map();
|
|
6997
|
+
for (const task of tasks) {
|
|
6998
|
+
if (!task.inputTypeName) continue;
|
|
6999
|
+
const absPath = path24.resolve(rootDir, task.filePath);
|
|
7000
|
+
let list = tasksByFile.get(absPath);
|
|
7001
|
+
if (!list) {
|
|
7002
|
+
list = [];
|
|
7003
|
+
tasksByFile.set(absPath, list);
|
|
7004
|
+
}
|
|
7005
|
+
list.push(task);
|
|
6892
7006
|
}
|
|
6893
|
-
const
|
|
6894
|
-
const
|
|
6895
|
-
const
|
|
6896
|
-
|
|
6897
|
-
return import(pathToFileURL3(productPath).href);
|
|
7007
|
+
const programByFile = createPrograms([...tasksByFile.keys()]);
|
|
7008
|
+
const resolversByFile = /* @__PURE__ */ new Map();
|
|
7009
|
+
for (const filePath of tasksByFile.keys()) {
|
|
7010
|
+
resolversByFile.set(filePath, createLazyTypeResolver(programByFile.get(filePath), filePath));
|
|
6898
7011
|
}
|
|
6899
|
-
|
|
6900
|
-
|
|
6901
|
-
|
|
6902
|
-
|
|
6903
|
-
);
|
|
6904
|
-
|
|
6905
|
-
|
|
6906
|
-
|
|
6907
|
-
|
|
6908
|
-
|
|
6909
|
-
|
|
6910
|
-
}
|
|
6911
|
-
if (productPath && fs18.existsSync(productPath)) {
|
|
6912
|
-
return import(pathToFileURL3(productPath).href);
|
|
7012
|
+
const sources = [];
|
|
7013
|
+
for (const [filePath, fileTasks] of tasksByFile) {
|
|
7014
|
+
const program = programByFile.get(filePath);
|
|
7015
|
+
for (const task of fileTasks) {
|
|
7016
|
+
const typeInfo = extractTypeInfo(program, filePath, task.inputTypeName);
|
|
7017
|
+
sources.push({
|
|
7018
|
+
name: task.taskName,
|
|
7019
|
+
filePath,
|
|
7020
|
+
schemaName: task.inputTypeName,
|
|
7021
|
+
typeInfo
|
|
7022
|
+
});
|
|
6913
7023
|
}
|
|
6914
7024
|
}
|
|
6915
|
-
|
|
6916
|
-
return import(pathToFileURL3(sourcePath).href);
|
|
6917
|
-
}
|
|
6918
|
-
const candidates = [
|
|
6919
|
-
`${specifier}.ts`,
|
|
6920
|
-
`${specifier}/index.ts`,
|
|
6921
|
-
`${specifier}.js`,
|
|
6922
|
-
`${specifier}/index.js`
|
|
6923
|
-
].map((c) => path23.isAbsolute(c) ? c : path23.join(baseDir, c));
|
|
6924
|
-
throw new Error(
|
|
6925
|
-
`Cannot find local plugin "${specifier}" (resolved from ${baseDir}). Expected one of:
|
|
6926
|
-
${candidates.join("\n ")}
|
|
6927
|
-
Create the plugin file (export default { name, setup(ctx) {...} }) or fix the path.`
|
|
6928
|
-
);
|
|
7025
|
+
return { sources, resolversByFile };
|
|
6929
7026
|
}
|
|
6930
|
-
function
|
|
6931
|
-
|
|
6932
|
-
|
|
7027
|
+
function generateTaskSchemaFileSource(sources, resolveType, helpersImportPath) {
|
|
7028
|
+
const lines = ["import { z } from 'zod';"];
|
|
7029
|
+
const schemaBlocks = [];
|
|
7030
|
+
for (const source of sources) {
|
|
7031
|
+
if (!source.typeInfo) continue;
|
|
7032
|
+
const block = [`// task ${source.name} \u2192 ${source.schemaName}`];
|
|
7033
|
+
const schemaCode = generateZodSchemaSource(
|
|
7034
|
+
source.typeInfo,
|
|
7035
|
+
resolveType,
|
|
7036
|
+
source.schemaName,
|
|
7037
|
+
false
|
|
7038
|
+
).replace(/^import \{ z \} from 'zod';\s*\n\s*\n/, "");
|
|
7039
|
+
block.push(schemaCode);
|
|
7040
|
+
block.push("");
|
|
7041
|
+
schemaBlocks.push(block.join("\n"));
|
|
6933
7042
|
}
|
|
6934
|
-
|
|
6935
|
-
|
|
6936
|
-
|
|
7043
|
+
const allSchemaCode = schemaBlocks.join("\n");
|
|
7044
|
+
if (helpersImportPath && usesCoerceHelpers(allSchemaCode)) {
|
|
7045
|
+
lines.push(
|
|
7046
|
+
`import { coerceNumber, coerceBoolean, coerceMap, coerceSet } from '${helpersImportPath}';`
|
|
7047
|
+
);
|
|
6937
7048
|
}
|
|
6938
|
-
|
|
6939
|
-
|
|
7049
|
+
lines.push("");
|
|
7050
|
+
lines.push(...schemaBlocks);
|
|
7051
|
+
return lines.join("\n").replace(/\n+$/, "\n");
|
|
7052
|
+
}
|
|
7053
|
+
async function generateTaskArtifacts(manifests, rootDir, dist) {
|
|
7054
|
+
const metadata = [];
|
|
7055
|
+
if (manifests.length > 0) {
|
|
7056
|
+
const programByFile = createPrograms(manifests.map((m) => path24.resolve(rootDir, m.filePath)));
|
|
7057
|
+
for (const manifest of manifests) {
|
|
7058
|
+
const absPath = path24.resolve(rootDir, manifest.filePath);
|
|
7059
|
+
const result = extractToolMetadata(programByFile.get(absPath), absPath, "run", {
|
|
7060
|
+
name: manifest.name,
|
|
7061
|
+
filePath: manifest.filePath
|
|
7062
|
+
});
|
|
7063
|
+
if (result) {
|
|
7064
|
+
metadata.push({ ...result, taskName: manifest.name });
|
|
7065
|
+
}
|
|
7066
|
+
}
|
|
6940
7067
|
}
|
|
6941
|
-
|
|
6942
|
-
|
|
7068
|
+
const serialized = serializeTasks(manifests, dist);
|
|
7069
|
+
const tasksPath = path24.resolve(rootDir, dist, TASKS_FILE);
|
|
7070
|
+
await writeTasksModule(serialized, tasksPath);
|
|
7071
|
+
if (metadata.length === 0) {
|
|
7072
|
+
return hydrateTasks(serialized);
|
|
6943
7073
|
}
|
|
6944
|
-
|
|
7074
|
+
const { sources, resolversByFile } = collectTaskSchemaSources(metadata, rootDir);
|
|
7075
|
+
if (sources.length === 0) {
|
|
7076
|
+
return hydrateTasks(serialized);
|
|
7077
|
+
}
|
|
7078
|
+
await generateZodArtifacts(sources, {
|
|
7079
|
+
generateFileSource: generateTaskSchemaFileSource,
|
|
7080
|
+
resolversByFile,
|
|
7081
|
+
rootDir,
|
|
7082
|
+
dist
|
|
7083
|
+
});
|
|
7084
|
+
return hydrateTasks(serialized);
|
|
6945
7085
|
}
|
|
6946
7086
|
|
|
6947
|
-
// src/cli/
|
|
6948
|
-
var DEFAULT_DIST = "dist";
|
|
6949
|
-
var DEFAULT_PORT = 3e3;
|
|
6950
|
-
var ROUTES_FILE = "faapi-routes.js";
|
|
7087
|
+
// src/cli/manifestLoader.ts
|
|
6951
7088
|
var TOOLS_FILE2 = "faapi-tools.js";
|
|
6952
7089
|
var AGENTS_FILE2 = "faapi-agents.js";
|
|
6953
7090
|
async function loadAndHydrateTools(rootDir, dist, registries = defaultRegistries) {
|
|
6954
|
-
const toolsPath =
|
|
6955
|
-
if (!
|
|
7091
|
+
const toolsPath = path25.resolve(rootDir, dist, TOOLS_FILE2);
|
|
7092
|
+
if (!fs20.existsSync(toolsPath)) {
|
|
6956
7093
|
return [];
|
|
6957
7094
|
}
|
|
6958
7095
|
const serialized = await importWithCacheBust(toolsPath);
|
|
@@ -6961,8 +7098,8 @@ async function loadAndHydrateTools(rootDir, dist, registries = defaultRegistries
|
|
|
6961
7098
|
return hydrated;
|
|
6962
7099
|
}
|
|
6963
7100
|
async function loadAndHydrateAgents(rootDir, dist, registries = defaultRegistries) {
|
|
6964
|
-
const agentsPath =
|
|
6965
|
-
if (!
|
|
7101
|
+
const agentsPath = path25.resolve(rootDir, dist, AGENTS_FILE2);
|
|
7102
|
+
if (!fs20.existsSync(agentsPath)) {
|
|
6966
7103
|
return [];
|
|
6967
7104
|
}
|
|
6968
7105
|
const serialized = await importWithCacheBust(agentsPath);
|
|
@@ -6977,8 +7114,8 @@ function getTaskDriverOptions(config) {
|
|
|
6977
7114
|
return void 0;
|
|
6978
7115
|
}
|
|
6979
7116
|
async function loadAndHydrateTasks(rootDir, dist, registries = defaultRegistries) {
|
|
6980
|
-
const tasksPath =
|
|
6981
|
-
if (!
|
|
7117
|
+
const tasksPath = path25.resolve(rootDir, dist, TASKS_FILE);
|
|
7118
|
+
if (!fs20.existsSync(tasksPath)) {
|
|
6982
7119
|
return [];
|
|
6983
7120
|
}
|
|
6984
7121
|
const serialized = await importWithCacheBust(tasksPath);
|
|
@@ -6986,6 +7123,8 @@ async function loadAndHydrateTasks(rootDir, dist, registries = defaultRegistries
|
|
|
6986
7123
|
registries.task.hydrate(hydrated);
|
|
6987
7124
|
return hydrated;
|
|
6988
7125
|
}
|
|
7126
|
+
|
|
7127
|
+
// src/cli/appSingleton.ts
|
|
6989
7128
|
var APP_INSTANCE_KEY = /* @__PURE__ */ Symbol.for("faapi.app.instance");
|
|
6990
7129
|
function getCurrentApp() {
|
|
6991
7130
|
return globalThis[APP_INSTANCE_KEY] ?? null;
|
|
@@ -7023,6 +7162,94 @@ function getApp() {
|
|
|
7023
7162
|
}
|
|
7024
7163
|
return app;
|
|
7025
7164
|
}
|
|
7165
|
+
|
|
7166
|
+
// src/cli/injectMock.ts
|
|
7167
|
+
import { PassThrough, Readable as Readable3 } from "stream";
|
|
7168
|
+
function performInject(server, injectOpts) {
|
|
7169
|
+
const {
|
|
7170
|
+
method = "GET",
|
|
7171
|
+
path: reqPath = "/",
|
|
7172
|
+
headers: reqHeaders = {},
|
|
7173
|
+
query,
|
|
7174
|
+
body
|
|
7175
|
+
} = injectOpts ?? {};
|
|
7176
|
+
const queryStr = query ? "?" + new URLSearchParams(Object.entries(query).map(([k, v]) => [k, String(v)])).toString() : "";
|
|
7177
|
+
return new Promise((resolve, reject) => {
|
|
7178
|
+
const chunks = [];
|
|
7179
|
+
const mockRes = new PassThrough();
|
|
7180
|
+
mockRes.statusCode = 200;
|
|
7181
|
+
mockRes._headers = {};
|
|
7182
|
+
mockRes.setHeader = function(name, value) {
|
|
7183
|
+
this._headers[name.toLowerCase()] = value;
|
|
7184
|
+
};
|
|
7185
|
+
mockRes.appendHeader = function(name, value) {
|
|
7186
|
+
const key = name.toLowerCase();
|
|
7187
|
+
const existing = this._headers[key];
|
|
7188
|
+
this._headers[key] = existing ? `${existing}, ${value}` : value;
|
|
7189
|
+
};
|
|
7190
|
+
mockRes.writeHead = function(status, headers) {
|
|
7191
|
+
this.statusCode = status;
|
|
7192
|
+
if (headers) {
|
|
7193
|
+
Object.assign(this._headers, headers);
|
|
7194
|
+
}
|
|
7195
|
+
};
|
|
7196
|
+
mockRes.on("data", (chunk) => chunks.push(chunk));
|
|
7197
|
+
mockRes.on("error", reject);
|
|
7198
|
+
mockRes.on("finish", () => {
|
|
7199
|
+
const bodyBuf = Buffer.concat(chunks);
|
|
7200
|
+
let parsed;
|
|
7201
|
+
try {
|
|
7202
|
+
parsed = JSON.parse(bodyBuf.toString());
|
|
7203
|
+
} catch {
|
|
7204
|
+
parsed = bodyBuf.toString();
|
|
7205
|
+
}
|
|
7206
|
+
resolve({
|
|
7207
|
+
status: mockRes.statusCode,
|
|
7208
|
+
headers: new Headers(mockRes._headers),
|
|
7209
|
+
body: parsed
|
|
7210
|
+
});
|
|
7211
|
+
});
|
|
7212
|
+
const listeners = server.listeners("request");
|
|
7213
|
+
const handler = listeners[listeners.length - 1];
|
|
7214
|
+
if (typeof handler !== "function") {
|
|
7215
|
+
reject(new Error("No request handler found"));
|
|
7216
|
+
return;
|
|
7217
|
+
}
|
|
7218
|
+
let payload;
|
|
7219
|
+
let defaultContentType;
|
|
7220
|
+
if (body !== void 0) {
|
|
7221
|
+
if (typeof body === "string") {
|
|
7222
|
+
payload = Buffer.from(body, "utf-8");
|
|
7223
|
+
defaultContentType = "text/plain";
|
|
7224
|
+
} else if (body instanceof Uint8Array) {
|
|
7225
|
+
payload = Buffer.from(body);
|
|
7226
|
+
defaultContentType = "application/octet-stream";
|
|
7227
|
+
} else {
|
|
7228
|
+
payload = Buffer.from(JSON.stringify(body));
|
|
7229
|
+
defaultContentType = "application/json";
|
|
7230
|
+
}
|
|
7231
|
+
}
|
|
7232
|
+
const mockReq = Readable3.from(payload !== void 0 ? [payload] : []);
|
|
7233
|
+
mockReq.method = method;
|
|
7234
|
+
mockReq.url = `${reqPath}${queryStr}`;
|
|
7235
|
+
const hasCallerContentType = "content-type" in reqHeaders || "Content-Type" in reqHeaders;
|
|
7236
|
+
mockReq.headers = {
|
|
7237
|
+
...reqHeaders,
|
|
7238
|
+
host: "localhost",
|
|
7239
|
+
...payload !== void 0 && !hasCallerContentType ? { "content-type": defaultContentType } : {}
|
|
7240
|
+
};
|
|
7241
|
+
mockReq.socket = { remoteAddress: "127.0.0.1" };
|
|
7242
|
+
handler(
|
|
7243
|
+
mockReq,
|
|
7244
|
+
mockRes
|
|
7245
|
+
);
|
|
7246
|
+
});
|
|
7247
|
+
}
|
|
7248
|
+
|
|
7249
|
+
// src/cli/createAppCore.ts
|
|
7250
|
+
var DEFAULT_DIST = "dist";
|
|
7251
|
+
var DEFAULT_PORT = 3e3;
|
|
7252
|
+
var ROUTES_FILE = "faapi-routes.js";
|
|
7026
7253
|
var FAAPI_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
7027
7254
|
"cors",
|
|
7028
7255
|
"lifecycle",
|
|
@@ -7043,11 +7270,45 @@ var FAAPI_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
|
7043
7270
|
function isFaapiConfigKey(key) {
|
|
7044
7271
|
return FAAPI_CONFIG_KEYS.has(key);
|
|
7045
7272
|
}
|
|
7273
|
+
function logStartupSummary(routes, wsRoutes, tools, agents, taskMetas, p) {
|
|
7274
|
+
console.log("faapi server started");
|
|
7275
|
+
console.log(`- Local: http://localhost:${p}`);
|
|
7276
|
+
console.log("- Loaded routes:");
|
|
7277
|
+
for (const route of routes) {
|
|
7278
|
+
console.log(` ${route.method.padEnd(6)}${route.urlPath} ${route.filePath}`);
|
|
7279
|
+
}
|
|
7280
|
+
if (wsRoutes.length > 0) {
|
|
7281
|
+
console.log("- WebSocket routes:");
|
|
7282
|
+
for (const route of wsRoutes) {
|
|
7283
|
+
console.log(` WS ${route.urlPath} ${route.filePath}`);
|
|
7284
|
+
}
|
|
7285
|
+
}
|
|
7286
|
+
if (tools.length > 0) {
|
|
7287
|
+
console.log(`- Loaded ${tools.length} tool(s):`);
|
|
7288
|
+
for (const tool of tools) {
|
|
7289
|
+
console.log(` ${tool.name} ${tool.filePath}`);
|
|
7290
|
+
}
|
|
7291
|
+
}
|
|
7292
|
+
if (agents.length > 0) {
|
|
7293
|
+
console.log(`- Loaded ${agents.length} agent(s):`);
|
|
7294
|
+
for (const agent of agents) {
|
|
7295
|
+
const exports = agent.hasRun ? "run" : "-";
|
|
7296
|
+
console.log(` ${agent.name} [${exports}] ${agent.filePath}`);
|
|
7297
|
+
}
|
|
7298
|
+
}
|
|
7299
|
+
if (taskMetas.length > 0) {
|
|
7300
|
+
console.log(`- Loaded ${taskMetas.length} task(s):`);
|
|
7301
|
+
for (const taskMeta of taskMetas) {
|
|
7302
|
+
const schedule = taskMeta.cron ? ` cron=${taskMeta.cron}` : "";
|
|
7303
|
+
console.log(` ${taskMeta.name}${schedule} ${taskMeta.filePath}`);
|
|
7304
|
+
}
|
|
7305
|
+
}
|
|
7306
|
+
}
|
|
7046
7307
|
async function createAppBase(options) {
|
|
7047
7308
|
const rootDir = options?.rootDir ?? process.cwd();
|
|
7048
7309
|
const dist = options?.dist ?? process.env.FAAPI_DIST ?? DEFAULT_DIST;
|
|
7049
|
-
const routesPath =
|
|
7050
|
-
if (!
|
|
7310
|
+
const routesPath = path26.resolve(rootDir, dist, ROUTES_FILE);
|
|
7311
|
+
if (!fs21.existsSync(routesPath)) {
|
|
7051
7312
|
throw new Error(
|
|
7052
7313
|
`[faapi] ${dist}/${ROUTES_FILE} \u4E0D\u5B58\u5728\uFF0C\u8BF7\u5148\u6267\u884C \`faapi build\`\uFF08\u6216 \`faapi dev\`\uFF09\u751F\u6210\u4EA7\u7269\u3002`
|
|
7053
7314
|
);
|
|
@@ -7058,15 +7319,7 @@ async function createAppBase(options) {
|
|
|
7058
7319
|
const hydrated = await hydrateRoutes(serialized);
|
|
7059
7320
|
let sorted = sortRoutes(hydrated.routes);
|
|
7060
7321
|
let wsRoutes = hydrated.wsRoutes;
|
|
7061
|
-
|
|
7062
|
-
if (conflicts.length > 0) {
|
|
7063
|
-
for (const conflict of conflicts) {
|
|
7064
|
-
console.warn(`! \u8DEF\u7531\u51B2\u7A81: ${conflict.method} ${conflict.urlPath}`);
|
|
7065
|
-
for (const file of conflict.files) {
|
|
7066
|
-
console.warn(` - ${file}`);
|
|
7067
|
-
}
|
|
7068
|
-
}
|
|
7069
|
-
}
|
|
7322
|
+
reportRouteConflicts(sorted);
|
|
7070
7323
|
const registries = createAppRegistries();
|
|
7071
7324
|
const tools = await loadAndHydrateTools(rootDir, dist, registries);
|
|
7072
7325
|
const agents = await loadAndHydrateAgents(rootDir, dist, registries);
|
|
@@ -7172,38 +7425,7 @@ async function createAppBase(options) {
|
|
|
7172
7425
|
server.off("error", onListenError);
|
|
7173
7426
|
const address = server.address();
|
|
7174
7427
|
const p = typeof address === "object" && address !== null ? address.port : actualPort;
|
|
7175
|
-
|
|
7176
|
-
console.log(`- Local: http://localhost:${p}`);
|
|
7177
|
-
console.log("- Loaded routes:");
|
|
7178
|
-
for (const route of sorted) {
|
|
7179
|
-
console.log(` ${route.method.padEnd(6)}${route.urlPath} ${route.filePath}`);
|
|
7180
|
-
}
|
|
7181
|
-
if (wsRoutes.length > 0) {
|
|
7182
|
-
console.log("- WebSocket routes:");
|
|
7183
|
-
for (const route of wsRoutes) {
|
|
7184
|
-
console.log(` WS ${route.urlPath} ${route.filePath}`);
|
|
7185
|
-
}
|
|
7186
|
-
}
|
|
7187
|
-
if (tools.length > 0) {
|
|
7188
|
-
console.log(`- Loaded ${tools.length} tool(s):`);
|
|
7189
|
-
for (const tool of tools) {
|
|
7190
|
-
console.log(` ${tool.name} ${tool.filePath}`);
|
|
7191
|
-
}
|
|
7192
|
-
}
|
|
7193
|
-
if (agents.length > 0) {
|
|
7194
|
-
console.log(`- Loaded ${agents.length} agent(s):`);
|
|
7195
|
-
for (const agent of agents) {
|
|
7196
|
-
const exports = agent.hasRun ? "run" : "-";
|
|
7197
|
-
console.log(` ${agent.name} [${exports}] ${agent.filePath}`);
|
|
7198
|
-
}
|
|
7199
|
-
}
|
|
7200
|
-
if (taskMetas.length > 0) {
|
|
7201
|
-
console.log(`- Loaded ${taskMetas.length} task(s):`);
|
|
7202
|
-
for (const taskMeta of taskMetas) {
|
|
7203
|
-
const schedule = taskMeta.cron ? ` cron=${taskMeta.cron}` : "";
|
|
7204
|
-
console.log(` ${taskMeta.name}${schedule} ${taskMeta.filePath}`);
|
|
7205
|
-
}
|
|
7206
|
-
}
|
|
7428
|
+
logStartupSummary(sorted, wsRoutes, tools, agents, taskMetas, p);
|
|
7207
7429
|
registerDefaultShutdownHandlers();
|
|
7208
7430
|
if (config?.lifecycle?.onReady) {
|
|
7209
7431
|
await config.lifecycle.onReady({
|
|
@@ -7220,85 +7442,8 @@ async function createAppBase(options) {
|
|
|
7220
7442
|
});
|
|
7221
7443
|
});
|
|
7222
7444
|
},
|
|
7223
|
-
|
|
7224
|
-
|
|
7225
|
-
method = "GET",
|
|
7226
|
-
path: reqPath = "/",
|
|
7227
|
-
headers: reqHeaders = {},
|
|
7228
|
-
query,
|
|
7229
|
-
body
|
|
7230
|
-
} = injectOpts ?? {};
|
|
7231
|
-
const queryStr = query ? "?" + new URLSearchParams(Object.entries(query).map(([k, v]) => [k, String(v)])).toString() : "";
|
|
7232
|
-
return new Promise((resolve, reject) => {
|
|
7233
|
-
const chunks = [];
|
|
7234
|
-
const mockRes = new PassThrough();
|
|
7235
|
-
mockRes.statusCode = 200;
|
|
7236
|
-
mockRes._headers = {};
|
|
7237
|
-
mockRes.setHeader = function(name, value) {
|
|
7238
|
-
this._headers[name.toLowerCase()] = value;
|
|
7239
|
-
};
|
|
7240
|
-
mockRes.appendHeader = function(name, value) {
|
|
7241
|
-
const key = name.toLowerCase();
|
|
7242
|
-
const existing = this._headers[key];
|
|
7243
|
-
this._headers[key] = existing ? `${existing}, ${value}` : value;
|
|
7244
|
-
};
|
|
7245
|
-
mockRes.writeHead = function(status, headers) {
|
|
7246
|
-
this.statusCode = status;
|
|
7247
|
-
if (headers) {
|
|
7248
|
-
Object.assign(this._headers, headers);
|
|
7249
|
-
}
|
|
7250
|
-
};
|
|
7251
|
-
mockRes.on("data", (chunk) => chunks.push(chunk));
|
|
7252
|
-
mockRes.on("error", reject);
|
|
7253
|
-
mockRes.on("finish", () => {
|
|
7254
|
-
const body2 = Buffer.concat(chunks);
|
|
7255
|
-
let parsed;
|
|
7256
|
-
try {
|
|
7257
|
-
parsed = JSON.parse(body2.toString());
|
|
7258
|
-
} catch {
|
|
7259
|
-
parsed = body2.toString();
|
|
7260
|
-
}
|
|
7261
|
-
resolve({
|
|
7262
|
-
status: mockRes.statusCode,
|
|
7263
|
-
headers: new Headers(mockRes._headers),
|
|
7264
|
-
body: parsed
|
|
7265
|
-
});
|
|
7266
|
-
});
|
|
7267
|
-
const listeners = server.listeners("request");
|
|
7268
|
-
const handler = listeners[listeners.length - 1];
|
|
7269
|
-
if (typeof handler !== "function") {
|
|
7270
|
-
reject(new Error("No request handler found"));
|
|
7271
|
-
return;
|
|
7272
|
-
}
|
|
7273
|
-
let payload;
|
|
7274
|
-
let defaultContentType;
|
|
7275
|
-
if (body !== void 0) {
|
|
7276
|
-
if (typeof body === "string") {
|
|
7277
|
-
payload = Buffer.from(body, "utf-8");
|
|
7278
|
-
defaultContentType = "text/plain";
|
|
7279
|
-
} else if (body instanceof Uint8Array) {
|
|
7280
|
-
payload = Buffer.from(body);
|
|
7281
|
-
defaultContentType = "application/octet-stream";
|
|
7282
|
-
} else {
|
|
7283
|
-
payload = Buffer.from(JSON.stringify(body));
|
|
7284
|
-
defaultContentType = "application/json";
|
|
7285
|
-
}
|
|
7286
|
-
}
|
|
7287
|
-
const mockReq = Readable3.from(payload !== void 0 ? [payload] : []);
|
|
7288
|
-
mockReq.method = method;
|
|
7289
|
-
mockReq.url = `${reqPath}${queryStr}`;
|
|
7290
|
-
const hasCallerContentType = "content-type" in reqHeaders || "Content-Type" in reqHeaders;
|
|
7291
|
-
mockReq.headers = {
|
|
7292
|
-
...reqHeaders,
|
|
7293
|
-
host: "localhost",
|
|
7294
|
-
...payload !== void 0 && !hasCallerContentType ? { "content-type": defaultContentType } : {}
|
|
7295
|
-
};
|
|
7296
|
-
mockReq.socket = { remoteAddress: "127.0.0.1" };
|
|
7297
|
-
handler(
|
|
7298
|
-
mockReq,
|
|
7299
|
-
mockRes
|
|
7300
|
-
);
|
|
7301
|
-
});
|
|
7445
|
+
inject(injectOpts) {
|
|
7446
|
+
return performInject(server, injectOpts);
|
|
7302
7447
|
},
|
|
7303
7448
|
async close() {
|
|
7304
7449
|
if (closed) return;
|
|
@@ -7374,21 +7519,21 @@ async function createAppBase(options) {
|
|
|
7374
7519
|
}
|
|
7375
7520
|
|
|
7376
7521
|
// src/cli/createDevApp.ts
|
|
7377
|
-
import
|
|
7522
|
+
import path30 from "path";
|
|
7378
7523
|
|
|
7379
7524
|
// src/router/scanRoutes.ts
|
|
7380
7525
|
import fg3 from "fast-glob";
|
|
7381
|
-
import
|
|
7382
|
-
import
|
|
7526
|
+
import path27 from "path";
|
|
7527
|
+
import fs22 from "fs";
|
|
7383
7528
|
|
|
7384
7529
|
// src/router/constants.ts
|
|
7385
7530
|
var HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
|
|
7386
7531
|
var HTTP_METHOD_SET = new Set(HTTP_METHODS);
|
|
7387
7532
|
|
|
7388
7533
|
// src/utils/normalizePath.ts
|
|
7389
|
-
function normalizePath(
|
|
7390
|
-
if (!
|
|
7391
|
-
let result =
|
|
7534
|
+
function normalizePath(path31) {
|
|
7535
|
+
if (!path31) return "";
|
|
7536
|
+
let result = path31.replace(/\\/g, "/");
|
|
7392
7537
|
result = result.replace(/\/+/g, "/");
|
|
7393
7538
|
result = result.replace(/\/+$/, "");
|
|
7394
7539
|
if (result && !result.startsWith("/")) {
|
|
@@ -7449,34 +7594,34 @@ function extractExportsFromSource(source) {
|
|
|
7449
7594
|
return names;
|
|
7450
7595
|
}
|
|
7451
7596
|
function collectMiddlewarePaths(routeFilePath, rootDir, dist) {
|
|
7452
|
-
const routeDir =
|
|
7453
|
-
const resolvedRoot =
|
|
7597
|
+
const routeDir = path27.dirname(routeFilePath);
|
|
7598
|
+
const resolvedRoot = path27.resolve(rootDir);
|
|
7454
7599
|
const paths = [];
|
|
7455
|
-
let currentDir =
|
|
7600
|
+
let currentDir = path27.resolve(rootDir, routeDir);
|
|
7456
7601
|
while (true) {
|
|
7457
7602
|
if (dist) {
|
|
7458
|
-
const mwTsPath =
|
|
7459
|
-
const mwJsPath =
|
|
7460
|
-
const absTsPath =
|
|
7461
|
-
const absJsPath =
|
|
7462
|
-
const absMwPath =
|
|
7603
|
+
const mwTsPath = path27.join(currentDir, "middlewares.ts");
|
|
7604
|
+
const mwJsPath = path27.join(currentDir, "middlewares.js");
|
|
7605
|
+
const absTsPath = path27.resolve(rootDir, mwTsPath);
|
|
7606
|
+
const absJsPath = path27.resolve(rootDir, mwJsPath);
|
|
7607
|
+
const absMwPath = fs22.existsSync(absTsPath) ? absTsPath : fs22.existsSync(absJsPath) ? absJsPath : null;
|
|
7463
7608
|
if (absMwPath) {
|
|
7464
|
-
const relMwPath =
|
|
7465
|
-
const prodAbsPath =
|
|
7609
|
+
const relMwPath = path27.relative(rootDir, absMwPath);
|
|
7610
|
+
const prodAbsPath = path27.resolve(rootDir, toProdFilePath(relMwPath, dist));
|
|
7466
7611
|
paths.push(prodAbsPath);
|
|
7467
7612
|
}
|
|
7468
7613
|
} else {
|
|
7469
7614
|
for (const ext of [".ts", ".js"]) {
|
|
7470
|
-
const mwPath =
|
|
7471
|
-
const absMwPath =
|
|
7472
|
-
if (
|
|
7615
|
+
const mwPath = path27.join(currentDir, `middlewares${ext}`);
|
|
7616
|
+
const absMwPath = path27.resolve(rootDir, mwPath);
|
|
7617
|
+
if (fs22.existsSync(absMwPath)) {
|
|
7473
7618
|
paths.push(absMwPath);
|
|
7474
7619
|
break;
|
|
7475
7620
|
}
|
|
7476
7621
|
}
|
|
7477
7622
|
}
|
|
7478
7623
|
if (currentDir === resolvedRoot) break;
|
|
7479
|
-
const parentDir =
|
|
7624
|
+
const parentDir = path27.dirname(currentDir);
|
|
7480
7625
|
if (parentDir === currentDir) break;
|
|
7481
7626
|
currentDir = parentDir;
|
|
7482
7627
|
}
|
|
@@ -7495,7 +7640,7 @@ async function scanRoutes(rootDir, patterns, dist) {
|
|
|
7495
7640
|
const normalizedFile = file.replace(/\\/g, "/");
|
|
7496
7641
|
const fileName = normalizedFile.split("/").pop();
|
|
7497
7642
|
if (fileName === "handler.ts" || fileName === "handler.js") {
|
|
7498
|
-
const absPath =
|
|
7643
|
+
const absPath = path27.resolve(rootDir, normalizedFile);
|
|
7499
7644
|
const urlPath = filePathToUrlPath(normalizedFile);
|
|
7500
7645
|
const paramNames = extractParamNames(urlPath);
|
|
7501
7646
|
const isDynamic = paramNames.length > 0;
|
|
@@ -7508,7 +7653,7 @@ async function scanRoutes(rootDir, patterns, dist) {
|
|
|
7508
7653
|
const mwPaths = collectMiddlewarePaths(normalizedFile, rootDir);
|
|
7509
7654
|
middlewareBundle = await loadMergedMiddlewares(mwPaths);
|
|
7510
7655
|
}
|
|
7511
|
-
const source = await
|
|
7656
|
+
const source = await fs22.promises.readFile(absPath, "utf8").catch(() => "");
|
|
7512
7657
|
const exportNames = extractExportsFromSource(source);
|
|
7513
7658
|
const methods = HTTP_METHODS.filter((m) => exportNames.has(m));
|
|
7514
7659
|
for (const method of methods) {
|
|
@@ -7544,8 +7689,8 @@ async function scanRoutes(rootDir, patterns, dist) {
|
|
|
7544
7689
|
|
|
7545
7690
|
// src/tools/scanTools.ts
|
|
7546
7691
|
import fg4 from "fast-glob";
|
|
7547
|
-
import
|
|
7548
|
-
import
|
|
7692
|
+
import path28 from "path";
|
|
7693
|
+
import fs23 from "fs";
|
|
7549
7694
|
var TOOL_PATTERNS = ["src/tools/**/*.ts"];
|
|
7550
7695
|
var TOOL_EXPORT_RE = new RegExp(
|
|
7551
7696
|
String.raw`export\s+(?:async\s+)?(?:function\s+|const\s+)([A-Za-z_$][\w$]*)\s*(?:\(|=)`,
|
|
@@ -7595,8 +7740,8 @@ async function scanTools(rootDir, patterns) {
|
|
|
7595
7740
|
if (fileName !== "handler.ts" && fileName !== "handler.js") {
|
|
7596
7741
|
continue;
|
|
7597
7742
|
}
|
|
7598
|
-
const absPath =
|
|
7599
|
-
const source = await
|
|
7743
|
+
const absPath = path28.resolve(rootDir, normalizedFile);
|
|
7744
|
+
const source = await fs23.promises.readFile(absPath, "utf8").catch(() => "");
|
|
7600
7745
|
const exportNames = extractToolExportsFromSource(source);
|
|
7601
7746
|
const namespace = filePathToToolNamespace(normalizedFile);
|
|
7602
7747
|
for (const fnName of exportNames) {
|
|
@@ -7620,8 +7765,8 @@ async function scanTools(rootDir, patterns) {
|
|
|
7620
7765
|
|
|
7621
7766
|
// src/agents/scanAgents.ts
|
|
7622
7767
|
import fg5 from "fast-glob";
|
|
7623
|
-
import
|
|
7624
|
-
import
|
|
7768
|
+
import path29 from "path";
|
|
7769
|
+
import fs24 from "fs";
|
|
7625
7770
|
var DEFAULT_AGENT_PATTERNS = ["src/agents/**/handler.ts"];
|
|
7626
7771
|
var RUN_EXPORT_RE = /export\s+(?:async\s+)?(?:function\s+|const\s+)run\b/;
|
|
7627
7772
|
function extractAgentNameFromPath(filePath) {
|
|
@@ -7653,8 +7798,8 @@ async function scanAgents(rootDir, patterns) {
|
|
|
7653
7798
|
if (fileName !== "handler.ts" && fileName !== "handler.js") {
|
|
7654
7799
|
continue;
|
|
7655
7800
|
}
|
|
7656
|
-
const absPath =
|
|
7657
|
-
const source = await
|
|
7801
|
+
const absPath = path29.resolve(rootDir, normalizedFile);
|
|
7802
|
+
const source = await fs24.promises.readFile(absPath, "utf8").catch(() => "");
|
|
7658
7803
|
const { hasRun } = detectAgentExports(source);
|
|
7659
7804
|
const name = extractAgentNameFromPath(normalizedFile);
|
|
7660
7805
|
const prevFile = seen.get(name);
|
|
@@ -7675,13 +7820,14 @@ async function scanAgents(rootDir, patterns) {
|
|
|
7675
7820
|
|
|
7676
7821
|
// src/cli/createDevApp.ts
|
|
7677
7822
|
init_createProgram();
|
|
7823
|
+
var suppressProgramInvalidation = false;
|
|
7678
7824
|
async function createDevApp(options) {
|
|
7679
7825
|
const { app, ctx } = await createAppBase(options);
|
|
7680
7826
|
const devApp = app;
|
|
7681
7827
|
devApp.reloadRoutes = async () => {
|
|
7682
7828
|
setLoadTimestamp(Date.now());
|
|
7683
7829
|
invalidateMiddlewareCache();
|
|
7684
|
-
invalidateProgramCache();
|
|
7830
|
+
if (!suppressProgramInvalidation) invalidateProgramCache();
|
|
7685
7831
|
invalidateSchemaCache();
|
|
7686
7832
|
clearCompiledFiles();
|
|
7687
7833
|
const reScanned = await scanRoutes(ctx.rootDir, ctx.patterns, ctx.dist);
|
|
@@ -7705,7 +7851,7 @@ async function createDevApp(options) {
|
|
|
7705
7851
|
};
|
|
7706
7852
|
devApp.reloadTools = async () => {
|
|
7707
7853
|
setLoadTimestamp(Date.now());
|
|
7708
|
-
invalidateProgramCache();
|
|
7854
|
+
if (!suppressProgramInvalidation) invalidateProgramCache();
|
|
7709
7855
|
const tools = await scanTools(ctx.rootDir, TOOL_PATTERNS);
|
|
7710
7856
|
await generateToolArtifacts(tools, ctx.rootDir, ctx.dist, {
|
|
7711
7857
|
skipSchema: isDevOnDemandEnabled()
|
|
@@ -7714,17 +7860,17 @@ async function createDevApp(options) {
|
|
|
7714
7860
|
};
|
|
7715
7861
|
devApp.reloadAgents = async () => {
|
|
7716
7862
|
setLoadTimestamp(Date.now());
|
|
7717
|
-
invalidateProgramCache();
|
|
7863
|
+
if (!suppressProgramInvalidation) invalidateProgramCache();
|
|
7718
7864
|
const agents = await scanAgents(ctx.rootDir, DEFAULT_AGENT_PATTERNS);
|
|
7719
7865
|
await generateAgentArtifacts(agents, ctx.rootDir, ctx.dist);
|
|
7720
7866
|
await loadAndHydrateAgents(ctx.rootDir, ctx.dist, ctx.registries);
|
|
7721
7867
|
};
|
|
7722
7868
|
devApp.reloadTasks = async () => {
|
|
7723
7869
|
setLoadTimestamp(Date.now());
|
|
7724
|
-
invalidateProgramCache();
|
|
7870
|
+
if (!suppressProgramInvalidation) invalidateProgramCache();
|
|
7725
7871
|
const tasks = await scanTasks(ctx.rootDir, TASK_PATTERNS);
|
|
7726
7872
|
for (const task of tasks) {
|
|
7727
|
-
await ensureCompiled(
|
|
7873
|
+
await ensureCompiled(path30.resolve(ctx.rootDir, task.filePath), ctx.rootDir, ctx.dist);
|
|
7728
7874
|
}
|
|
7729
7875
|
await generateTaskArtifacts(tasks, ctx.rootDir, ctx.dist);
|
|
7730
7876
|
ctx.taskQueue.invalidateModules();
|
|
@@ -7732,6 +7878,19 @@ async function createDevApp(options) {
|
|
|
7732
7878
|
await ctx.taskQueue.reload();
|
|
7733
7879
|
await loadAndHydrateTasks(ctx.rootDir, ctx.dist, ctx.registries);
|
|
7734
7880
|
};
|
|
7881
|
+
devApp.reloadAll = async () => {
|
|
7882
|
+
invalidateProgramCache();
|
|
7883
|
+
const prev = suppressProgramInvalidation;
|
|
7884
|
+
suppressProgramInvalidation = true;
|
|
7885
|
+
try {
|
|
7886
|
+
await devApp.reloadRoutes();
|
|
7887
|
+
await devApp.reloadTools();
|
|
7888
|
+
await devApp.reloadAgents();
|
|
7889
|
+
await devApp.reloadTasks();
|
|
7890
|
+
} finally {
|
|
7891
|
+
suppressProgramInvalidation = prev;
|
|
7892
|
+
}
|
|
7893
|
+
};
|
|
7735
7894
|
return devApp;
|
|
7736
7895
|
}
|
|
7737
7896
|
|