@faapi/faapi 0.0.0-canary.22b65a2 → 0.0.0-canary.a3f7014

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 CHANGED
@@ -9,351 +9,277 @@ var __export = (target, all) => {
9
9
  __defProp(target, name, { get: all[name], enumerable: true });
10
10
  };
11
11
 
12
- // src/router/constants.ts
13
- function isHttpMethod(value) {
14
- return HTTP_METHOD_SET.has(value);
12
+ // src/utils/resolveAlias.ts
13
+ function resolveAlias(specifier, config) {
14
+ const candidates = [];
15
+ for (const [pattern, targets] of Object.entries(config.paths)) {
16
+ const wildcardIndex = pattern.indexOf("*");
17
+ if (wildcardIndex === -1) {
18
+ if (specifier === pattern) {
19
+ candidates.push(...targets);
20
+ }
21
+ continue;
22
+ }
23
+ const prefix = pattern.slice(0, wildcardIndex);
24
+ const suffix = pattern.slice(wildcardIndex + 1);
25
+ if (specifier.startsWith(prefix) && specifier.endsWith(suffix) && specifier.length >= prefix.length + suffix.length) {
26
+ const captured = specifier.slice(prefix.length, specifier.length - suffix.length);
27
+ for (const target of targets) {
28
+ candidates.push(target.replace("*", captured));
29
+ }
30
+ }
31
+ }
32
+ return candidates;
15
33
  }
16
- var HTTP_METHODS, HTTP_METHOD_SET;
17
- var init_constants = __esm({
18
- "src/router/constants.ts"() {
34
+ var init_resolveAlias = __esm({
35
+ "src/utils/resolveAlias.ts"() {
19
36
  "use strict";
20
- HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
21
- HTTP_METHOD_SET = new Set(HTTP_METHODS);
22
37
  }
23
38
  });
24
39
 
25
- // src/utils/normalizePath.ts
26
- function normalizePath(path17) {
27
- if (!path17) return "";
28
- let result = path17.replace(/\\/g, "/");
29
- result = result.replace(/\/+/g, "/");
30
- result = result.replace(/\/+$/, "");
31
- if (result && !result.startsWith("/")) {
32
- result = "/" + result;
40
+ // src/utils/readTsconfig.ts
41
+ import ts from "typescript";
42
+ import path from "path";
43
+ import fs from "fs";
44
+ function readTsconfig(rootDir) {
45
+ const tsconfigPath = path.resolve(rootDir, "tsconfig.json");
46
+ if (!fs.existsSync(tsconfigPath)) return null;
47
+ const configFile = ts.readConfigFile(tsconfigPath, ts.sys.readFile);
48
+ if (configFile.error || !configFile.config) return null;
49
+ const parsed = ts.parseJsonConfigFileContent(configFile.config, ts.sys, rootDir);
50
+ const baseUrl = parsed.options.baseUrl ?? rootDir;
51
+ const rawPaths = parsed.options.paths;
52
+ if (!rawPaths) return null;
53
+ const paths = {};
54
+ for (const [pattern, targets] of Object.entries(rawPaths)) {
55
+ paths[pattern] = targets.map((t) => path.resolve(baseUrl, t));
33
56
  }
34
- return result;
57
+ return { baseUrl, paths };
35
58
  }
36
- var init_normalizePath = __esm({
37
- "src/utils/normalizePath.ts"() {
59
+ var init_readTsconfig = __esm({
60
+ "src/utils/readTsconfig.ts"() {
38
61
  "use strict";
39
62
  }
40
63
  });
41
64
 
42
- // src/router/parseRouteFile.ts
43
- function dynamicSegmentToParam(segment) {
44
- const match = segment.match(/^\[(.+)\]$/);
45
- if (match) {
46
- return ":" + match[1];
47
- }
48
- return segment;
65
+ // src/cli/aliasPlugin.ts
66
+ import path2 from "path";
67
+ import fs2 from "fs";
68
+ function toProdExtension(filePath) {
69
+ if (filePath.endsWith(".ts")) return filePath.slice(0, -3) + ".js";
70
+ if (filePath.endsWith(".tsx")) return filePath.slice(0, -4) + ".js";
71
+ if (filePath.endsWith(".jsx")) return filePath.slice(0, -4) + ".js";
72
+ return filePath;
49
73
  }
50
- function extractParamNames(urlPath) {
51
- const params = [];
52
- const segments = urlPath.split("/");
53
- for (const segment of segments) {
54
- if (segment.startsWith(":...")) {
55
- params.push(segment.slice(4));
56
- } else if (segment.startsWith(":")) {
57
- params.push(segment.slice(1));
58
- }
59
- }
60
- return params;
74
+ function toProdImportPath(sourceFile, importer) {
75
+ const importerDir = path2.dirname(importer);
76
+ let rel = path2.relative(importerDir, sourceFile);
77
+ rel = rel.split(path2.sep).join("/");
78
+ if (!rel.startsWith(".")) rel = "./" + rel;
79
+ return toProdExtension(rel);
61
80
  }
62
- function isCatchAllSegment(segment) {
63
- return /^\[\.\.\..+\]$/.test(segment);
81
+ function createAliasPlugin(config) {
82
+ const EXTS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
83
+ const INDEX_EXTS = [
84
+ "/index.ts",
85
+ "/index.tsx",
86
+ "/index.js",
87
+ "/index.jsx",
88
+ "/index.mjs",
89
+ "/index.cjs"
90
+ ];
91
+ const SPEC_RE = /(\bfrom\s*|import\s*\(\s*)(['"])([^'"]+)\2/g;
92
+ return {
93
+ name: "faapi-alias",
94
+ setup(build) {
95
+ build.onLoad({ filter: /\.(ts|tsx|js|jsx|mjs|cjs)$/ }, (args) => {
96
+ let source;
97
+ try {
98
+ source = fs2.readFileSync(args.path, "utf8");
99
+ } catch {
100
+ return void 0;
101
+ }
102
+ const importer = args.path;
103
+ let modified = false;
104
+ const newSource = source.replace(SPEC_RE, (full, prefix, quote, specifier) => {
105
+ if (specifier.startsWith(".") || specifier.startsWith("/") || specifier.startsWith("file:") || specifier.startsWith("node:")) {
106
+ return full;
107
+ }
108
+ const candidates = resolveAlias(specifier, config);
109
+ for (const candidate of candidates) {
110
+ for (const ext of EXTS) {
111
+ const file = candidate + ext;
112
+ if (fs2.existsSync(file)) {
113
+ modified = true;
114
+ return `${prefix}${quote}${toProdImportPath(file, importer)}${quote}`;
115
+ }
116
+ }
117
+ for (const indexExt of INDEX_EXTS) {
118
+ const file = candidate + indexExt;
119
+ if (fs2.existsSync(file)) {
120
+ modified = true;
121
+ return `${prefix}${quote}${toProdImportPath(file, importer)}${quote}`;
122
+ }
123
+ }
124
+ }
125
+ return full;
126
+ });
127
+ if (!modified) return void 0;
128
+ return { contents: newSource, loader: "default" };
129
+ });
130
+ }
131
+ };
64
132
  }
65
- function isRouteGroup(segment) {
66
- return /^\(.+\)$/.test(segment);
133
+ function buildAliasPlugins(rootDir) {
134
+ const tsconfig = readTsconfig(rootDir);
135
+ return tsconfig ? [createAliasPlugin(tsconfig)] : [];
67
136
  }
68
- function filePathToUrlPath(filePath, appDir = ".") {
69
- const withoutPrefix = filePath.startsWith(appDir + "/") ? filePath.slice(appDir.length + 1) : filePath;
70
- const lastSlashIndex = withoutPrefix.lastIndexOf("/");
71
- const dirPath = lastSlashIndex === -1 ? "" : withoutPrefix.slice(0, lastSlashIndex);
72
- if (!dirPath) {
73
- return "";
137
+ var init_aliasPlugin = __esm({
138
+ "src/cli/aliasPlugin.ts"() {
139
+ "use strict";
140
+ init_resolveAlias();
141
+ init_readTsconfig();
74
142
  }
75
- const segments = dirPath.split("/").filter((s) => !isRouteGroup(s)).map(dynamicSegmentToParam);
76
- return normalizePath(segments.join("/"));
143
+ });
144
+
145
+ // src/cli/compileDevRoutes.ts
146
+ import path3 from "path";
147
+ import fs3 from "fs";
148
+ import fg from "fast-glob";
149
+ async function compileDevRoutes(options) {
150
+ const { rootDir, appDir, outDir, files, logLevel = "silent" } = options;
151
+ const entryPoints = files ?? await fg([`${appDir}/**/*.ts`], {
152
+ cwd: rootDir,
153
+ onlyFiles: true,
154
+ absolute: true,
155
+ ignore: ["**/*.test.ts", "**/*.e2e.test.ts", "**/*.d.ts"]
156
+ });
157
+ if (entryPoints.length === 0) {
158
+ return { compiledFiles: [] };
159
+ }
160
+ const absOutDir = path3.resolve(rootDir, outDir);
161
+ await fs3.promises.mkdir(absOutDir, { recursive: true });
162
+ const plugins = buildAliasPlugins(rootDir);
163
+ const esbuild = await import("esbuild");
164
+ const outbase = appDir === "." ? rootDir : path3.resolve(rootDir, appDir);
165
+ await esbuild.build({
166
+ entryPoints,
167
+ outdir: absOutDir,
168
+ outbase,
169
+ bundle: false,
170
+ platform: "node",
171
+ format: "esm",
172
+ sourcemap: true,
173
+ packages: "external",
174
+ plugins,
175
+ logLevel
176
+ });
177
+ return { compiledFiles: entryPoints };
77
178
  }
78
- var init_parseRouteFile = __esm({
79
- "src/router/parseRouteFile.ts"() {
179
+ var init_compileDevRoutes = __esm({
180
+ "src/cli/compileDevRoutes.ts"() {
80
181
  "use strict";
81
- init_normalizePath();
182
+ init_aliasPlugin();
82
183
  }
83
184
  });
84
185
 
85
- // src/utils/importWithCacheBust.ts
86
- import { pathToFileURL } from "url";
87
- function setLoadTimestamp(ts7) {
88
- loadTs = ts7;
89
- }
90
- async function importWithCacheBust(filePath) {
91
- let url = pathToFileURL(filePath).href;
92
- if (loadTs !== void 0) {
93
- url += `?t=${loadTs}`;
186
+ // src/config/deepMerge.ts
187
+ function deepMerge(base, override) {
188
+ const result = { ...base };
189
+ for (const key of Object.keys(override)) {
190
+ const baseVal = base[key];
191
+ const overVal = override[key];
192
+ if (baseVal instanceof Date || overVal instanceof Date || baseVal instanceof RegExp || overVal instanceof RegExp || baseVal instanceof Map || overVal instanceof Map || baseVal instanceof Set || overVal instanceof Set) {
193
+ result[key] = overVal;
194
+ continue;
195
+ }
196
+ if (baseVal !== null && overVal !== null && typeof baseVal === "object" && typeof overVal === "object" && !Array.isArray(baseVal) && !Array.isArray(overVal) && !(baseVal instanceof Function) && !(overVal instanceof Function)) {
197
+ result[key] = deepMerge(
198
+ baseVal,
199
+ overVal
200
+ );
201
+ } else {
202
+ result[key] = overVal;
203
+ }
94
204
  }
95
- return await import(url);
205
+ return result;
96
206
  }
97
- var loadTs;
98
- var init_importWithCacheBust = __esm({
99
- "src/utils/importWithCacheBust.ts"() {
207
+ var DEEP_MERGE_SOURCE;
208
+ var init_deepMerge = __esm({
209
+ "src/config/deepMerge.ts"() {
100
210
  "use strict";
211
+ DEEP_MERGE_SOURCE = `const deepMerge = ${deepMerge.toString()};`;
101
212
  }
102
213
  });
103
214
 
104
- // src/middleware/loadMiddlewares.ts
105
- function invalidateMiddlewareCache() {
106
- middlewareCache.clear();
107
- }
108
- function getCachedMiddlewares(absPath) {
109
- return middlewareCache.get(absPath);
215
+ // src/cli/compileConfig.ts
216
+ import path4 from "path";
217
+ import fs4 from "fs";
218
+ function getEnv() {
219
+ return process.env.FAAPI_ENV || process.env.NODE_ENV || "development";
110
220
  }
111
- function setCachedMiddlewares(absPath, bundle) {
112
- middlewareCache.set(absPath, bundle);
221
+ function findBaseConfig(rootDir) {
222
+ for (const f of BASE_CONFIG_FILES) {
223
+ if (fs4.existsSync(path4.join(rootDir, f))) {
224
+ return f.replace(/\.(ts|js)$/, "");
225
+ }
226
+ }
227
+ return null;
113
228
  }
114
- async function loadMiddlewaresFile(filePath) {
115
- try {
116
- const module = await importWithCacheBust(filePath);
117
- const middlewares = module.default ?? module.middlewares ?? [];
118
- if (!Array.isArray(middlewares)) {
119
- console.warn(`[faapi] middlewares.ts \u5E94\u5BFC\u51FA\u6570\u7EC4\uFF0C\u5DF2\u5FFD\u7565: ${filePath}`);
120
- return { middlewares: [], injectors: {} };
229
+ function findEnvConfig(rootDir, env) {
230
+ for (const ext of ENV_CONFIG_EXTS) {
231
+ const f = `faapi.config.${env}${ext}`;
232
+ if (fs4.existsSync(path4.join(rootDir, f))) {
233
+ return `faapi.config.${env}`;
121
234
  }
122
- const validMiddlewares = middlewares.filter((m) => {
123
- if (typeof m !== "function") {
124
- console.warn(`[faapi] \u65E0\u6548\u7684\u4E2D\u95F4\u4EF6\u9879\uFF08\u5E94\u4E3A\u51FD\u6570\uFF09\uFF0C\u5DF2\u5FFD\u7565: ${typeof m}`);
125
- return false;
126
- }
127
- return true;
128
- });
129
- const injectors = module.injectors ?? {};
130
- if (typeof injectors !== "object" || injectors === null) {
131
- console.warn(`[faapi] injectors \u5E94\u5BFC\u51FA\u5BF9\u8C61\uFF0C\u5DF2\u5FFD\u7565: ${filePath}`);
132
- return { middlewares: validMiddlewares, injectors: {} };
133
- }
134
- const validInjectors = {};
135
- for (const [name, injector] of Object.entries(injectors)) {
136
- if (typeof injector !== "function") {
137
- console.warn(`[faapi] \u6CE8\u5165\u5668 ${name} \u5E94\u4E3A\u51FD\u6570\uFF0C\u5DF2\u5FFD\u7565`);
138
- continue;
139
- }
140
- validInjectors[name] = injector;
141
- }
142
- return { middlewares: validMiddlewares, injectors: validInjectors };
143
- } catch {
144
- return { middlewares: [], injectors: {} };
145
- }
146
- }
147
- var middlewareCache;
148
- var init_loadMiddlewares = __esm({
149
- "src/middleware/loadMiddlewares.ts"() {
150
- "use strict";
151
- init_importWithCacheBust();
152
- middlewareCache = /* @__PURE__ */ new Map();
153
- }
154
- });
155
-
156
- // src/router/scanRoutes.ts
157
- import fg from "fast-glob";
158
- import path from "path";
159
- import fs from "fs";
160
- function toProdAbsPath(sourceAbsPath, rootDir, appDir, prodDir) {
161
- let rel = path.relative(rootDir, sourceAbsPath).replace(/\\/g, "/");
162
- if (appDir !== "." && rel.startsWith(`${appDir}/`)) {
163
- rel = rel.slice(appDir.length + 1);
164
- }
165
- const prodRel = `${prodDir}/${rel.replace(/\.ts$/, ".js")}`;
166
- return path.resolve(rootDir, prodRel);
167
- }
168
- async function findMergedMiddlewares(routeFilePath, rootDir, appDir, prodDir) {
169
- const routeDir = path.dirname(routeFilePath);
170
- const resolvedRoot = path.resolve(rootDir);
171
- const mwPaths = [];
172
- let currentDir = path.resolve(rootDir, routeDir);
173
- while (true) {
174
- if (prodDir) {
175
- const mwPath = path.join(currentDir, "middlewares.js");
176
- const absMwPath = path.resolve(rootDir, mwPath);
177
- const prodAbsMwPath = toProdAbsPath(absMwPath, rootDir, appDir, prodDir);
178
- if (fs.existsSync(prodAbsMwPath)) {
179
- mwPaths.push(prodAbsMwPath);
180
- }
181
- } else {
182
- for (const ext of [".ts", ".js"]) {
183
- const mwPath = path.join(currentDir, `middlewares${ext}`);
184
- const absMwPath = path.resolve(rootDir, mwPath);
185
- if (fs.existsSync(absMwPath)) {
186
- mwPaths.push(absMwPath);
187
- break;
188
- }
189
- }
190
- }
191
- if (currentDir === resolvedRoot) break;
192
- const parentDir = path.dirname(currentDir);
193
- if (parentDir === currentDir) break;
194
- currentDir = parentDir;
195
- }
196
- if (mwPaths.length === 0) return void 0;
197
- mwPaths.reverse();
198
- const mergedMiddlewares = [];
199
- const mergedInjectors = {};
200
- for (const absMwPath of mwPaths) {
201
- let bundle = getCachedMiddlewares(absMwPath);
202
- if (bundle === void 0) {
203
- bundle = await loadMiddlewaresFile(absMwPath);
204
- setCachedMiddlewares(absMwPath, bundle);
205
- }
206
- mergedMiddlewares.push(...bundle.middlewares);
207
- for (const [name, injector] of Object.entries(bundle.injectors)) {
208
- mergedInjectors[name] = injector;
209
- }
210
- }
211
- if (mergedMiddlewares.length === 0 && Object.keys(mergedInjectors).length === 0) {
212
- return void 0;
213
- }
214
- return { middlewares: mergedMiddlewares, injectors: mergedInjectors };
215
- }
216
- async function extractMethodsFromHandler(absPath) {
217
- try {
218
- const module = await importWithCacheBust(absPath);
219
- const methods = [];
220
- for (const key of Object.keys(module)) {
221
- if (isHttpMethod(key) && typeof module[key] === "function") {
222
- methods.push(key);
223
- }
224
- }
225
- return methods;
226
- } catch (err) {
227
- const reason = err instanceof Error ? err.message : String(err);
228
- console.warn(`[faapi] \u52A0\u8F7D\u8DEF\u7531\u6587\u4EF6\u5931\u8D25 ${absPath}: ${reason}`);
229
- return [];
230
- }
231
- }
232
- async function hasWsExport(absPath) {
233
- try {
234
- const module = await importWithCacheBust(absPath);
235
- return typeof module["WS"] === "function";
236
- } catch (err) {
237
- const reason = err instanceof Error ? err.message : String(err);
238
- console.warn(`[faapi] \u52A0\u8F7D\u8DEF\u7531\u6587\u4EF6\u5931\u8D25\uFF08WS \u68C0\u6D4B\uFF09${absPath}: ${reason}`);
239
- return false;
240
235
  }
236
+ return null;
241
237
  }
242
- async function scanRoutes(rootDir, patterns, appDir, prodDir) {
243
- const dir = appDir ?? ".";
244
- const files = await fg(patterns, {
245
- cwd: rootDir,
246
- onlyFiles: true,
247
- absolute: false
248
- });
249
- const routes = [];
250
- const wsRoutes = [];
251
- for (const file of files) {
252
- const normalizedFile = file.replace(/\\/g, "/");
253
- const fileName = normalizedFile.split("/").pop();
254
- if (fileName === "handler.ts" || fileName === "handler.js") {
255
- const absPath = path.resolve(rootDir, normalizedFile);
256
- const importPath = prodDir ? toProdAbsPath(absPath, rootDir, dir, prodDir) : absPath;
257
- const urlPath = filePathToUrlPath(normalizedFile, dir);
258
- const paramNames = extractParamNames(urlPath);
259
- const isDynamic = paramNames.length > 0;
260
- const isCatchAll = normalizedFile.split("/").some(isCatchAllSegment);
261
- const middlewareBundle = await findMergedMiddlewares(normalizedFile, rootDir, dir, prodDir);
262
- const methods = await extractMethodsFromHandler(importPath);
263
- for (const method of methods) {
264
- routes.push({
265
- method,
266
- urlPath,
267
- filePath: normalizedFile,
268
- paramNames,
269
- isDynamic,
270
- isCatchAll: isCatchAll || void 0,
271
- middlewares: middlewareBundle?.middlewares,
272
- injectors: middlewareBundle?.injectors
273
- });
274
- }
275
- const hasWs = await hasWsExport(importPath);
276
- if (hasWs) {
277
- wsRoutes.push({
278
- urlPath,
279
- filePath: normalizedFile,
280
- paramNames,
281
- isDynamic,
282
- isCatchAll: isCatchAll || void 0,
283
- middlewares: middlewareBundle?.middlewares,
284
- injectors: middlewareBundle?.injectors
285
- });
286
- }
287
- continue;
288
- }
238
+ async function compileConfig(options) {
239
+ const { rootDir, outDir } = options;
240
+ const baseConfig = findBaseConfig(rootDir);
241
+ if (!baseConfig) {
242
+ return { generated: false, outputFile: "" };
289
243
  }
290
- return { routes, wsRoutes };
291
- }
292
- var init_scanRoutes = __esm({
293
- "src/router/scanRoutes.ts"() {
294
- "use strict";
295
- init_constants();
296
- init_parseRouteFile();
297
- init_loadMiddlewares();
298
- init_importWithCacheBust();
244
+ const env = getEnv();
245
+ const envConfig = findEnvConfig(rootDir, env);
246
+ const imports = [`import base from './${baseConfig}';`];
247
+ let exportDefault;
248
+ if (envConfig) {
249
+ imports.push(`import env from './${envConfig}';`);
250
+ exportDefault = "export default deepMerge(base, env);";
251
+ } else {
252
+ exportDefault = "export default base;";
299
253
  }
300
- });
301
-
302
- // src/router/sortRoutes.ts
303
- function sortRoutes(routes) {
304
- return [...routes].sort((a, b) => {
305
- if (a.isDynamic !== b.isDynamic) {
306
- return a.isDynamic ? 1 : -1;
307
- }
308
- if (a.isCatchAll !== b.isCatchAll) {
309
- return a.isCatchAll ? 1 : -1;
310
- }
311
- const aSegments = a.urlPath.split("/").filter(Boolean).length;
312
- const bSegments = b.urlPath.split("/").filter(Boolean).length;
313
- if (aSegments !== bSegments) {
314
- return aSegments - bSegments;
315
- }
316
- return a.urlPath.localeCompare(b.urlPath);
254
+ const entryCode = [...imports, DEEP_MERGE_SOURCE, exportDefault].join("\n");
255
+ const outputFile = path4.resolve(rootDir, outDir, "faapi-config.js");
256
+ await fs4.promises.mkdir(path4.dirname(outputFile), { recursive: true });
257
+ const esbuild = await import("esbuild");
258
+ await esbuild.build({
259
+ stdin: { contents: entryCode, resolveDir: rootDir, loader: "ts" },
260
+ outfile: outputFile,
261
+ bundle: true,
262
+ format: "esm",
263
+ platform: "node",
264
+ target: "node20",
265
+ sourcemap: true,
266
+ packages: "external",
267
+ logLevel: "silent"
317
268
  });
269
+ return { generated: true, outputFile };
318
270
  }
319
- var init_sortRoutes = __esm({
320
- "src/router/sortRoutes.ts"() {
321
- "use strict";
322
- }
323
- });
324
-
325
- // src/router/detectRouteConflicts.ts
326
- function detectRouteConflicts(routes) {
327
- const map = /* @__PURE__ */ new Map();
328
- for (const route of routes) {
329
- const key = `${route.method} ${route.urlPath}`;
330
- const existing = map.get(key);
331
- if (existing) {
332
- existing.files.push(route.filePath);
333
- } else {
334
- map.set(key, {
335
- method: route.method,
336
- urlPath: route.urlPath,
337
- files: [route.filePath]
338
- });
339
- }
340
- }
341
- const conflicts = [];
342
- for (const conflict of map.values()) {
343
- if (conflict.files.length > 1) {
344
- conflicts.push(conflict);
345
- }
346
- }
347
- return conflicts;
348
- }
349
- var init_detectRouteConflicts = __esm({
350
- "src/router/detectRouteConflicts.ts"() {
271
+ var BASE_CONFIG_FILES, ENV_CONFIG_EXTS;
272
+ var init_compileConfig = __esm({
273
+ "src/cli/compileConfig.ts"() {
351
274
  "use strict";
275
+ init_deepMerge();
276
+ BASE_CONFIG_FILES = ["faapi.config.ts", "faapi.config.js"];
277
+ ENV_CONFIG_EXTS = [".ts", ".js"];
352
278
  }
353
279
  });
354
280
 
355
281
  // src/ast/createProgram.ts
356
- import ts from "typescript";
282
+ import ts2 from "typescript";
357
283
  function invalidateProgramCache() {
358
284
  programCache.clear();
359
285
  }
@@ -362,11 +288,11 @@ function createProgram(filePath) {
362
288
  if (cached) {
363
289
  return cached;
364
290
  }
365
- const program = ts.createProgram([filePath], {
291
+ const program = ts2.createProgram([filePath], {
366
292
  strict: true,
367
- target: ts.ScriptTarget.ES2022,
368
- module: ts.ModuleKind.NodeNext,
369
- moduleResolution: ts.ModuleResolutionKind.NodeNext,
293
+ target: ts2.ScriptTarget.ES2022,
294
+ module: ts2.ModuleKind.NodeNext,
295
+ moduleResolution: ts2.ModuleResolutionKind.NodeNext,
370
296
  skipLibCheck: true,
371
297
  noEmit: true
372
298
  });
@@ -382,83 +308,83 @@ var init_createProgram = __esm({
382
308
  });
383
309
 
384
310
  // src/ast/resolveTypeNode.ts
385
- import ts2 from "typescript";
311
+ import ts3 from "typescript";
386
312
  function resolveTypeNode(typeNode, checker, visited = /* @__PURE__ */ new Set()) {
387
313
  const kind = typeNode.kind;
388
314
  switch (kind) {
389
- case ts2.SyntaxKind.StringKeyword:
315
+ case ts3.SyntaxKind.StringKeyword:
390
316
  return { kind: "string" };
391
- case ts2.SyntaxKind.NumberKeyword:
317
+ case ts3.SyntaxKind.NumberKeyword:
392
318
  return { kind: "number" };
393
- case ts2.SyntaxKind.BooleanKeyword:
319
+ case ts3.SyntaxKind.BooleanKeyword:
394
320
  return { kind: "boolean" };
395
- case ts2.SyntaxKind.BigIntKeyword:
321
+ case ts3.SyntaxKind.BigIntKeyword:
396
322
  throw new SchemaExtractionError(
397
323
  typeNode.getText(),
398
324
  "bigint \u65E0\u6CD5\u901A\u8FC7 HTTP/JSON \u4F20\u8F93,\u8BF7\u6539\u7528 string \u6216 number"
399
325
  );
400
- case ts2.SyntaxKind.SymbolKeyword:
326
+ case ts3.SyntaxKind.SymbolKeyword:
401
327
  throw new SchemaExtractionError(typeNode.getText(), "symbol \u65E0\u6CD5\u901A\u8FC7 HTTP/JSON \u4F20\u8F93");
402
- case ts2.SyntaxKind.NullKeyword:
328
+ case ts3.SyntaxKind.NullKeyword:
403
329
  return { kind: "null" };
404
- case ts2.SyntaxKind.UndefinedKeyword:
330
+ case ts3.SyntaxKind.UndefinedKeyword:
405
331
  return { kind: "undefined" };
406
- case ts2.SyntaxKind.UnknownKeyword:
332
+ case ts3.SyntaxKind.UnknownKeyword:
407
333
  return { kind: "any" };
408
- case ts2.SyntaxKind.AnyKeyword:
334
+ case ts3.SyntaxKind.AnyKeyword:
409
335
  throw new SchemaExtractionError(typeNode.getText(), "any \u4E0D\u652F\u6301\uFF0C\u8BF7\u4F7F\u7528 unknown \u8868\u793A\u4E0D\u6821\u9A8C");
410
- case ts2.SyntaxKind.VoidKeyword:
336
+ case ts3.SyntaxKind.VoidKeyword:
411
337
  throw new SchemaExtractionError(typeNode.getText(), "void \u4E0D\u652F\u6301\u8FD0\u884C\u65F6\u6821\u9A8C");
412
- case ts2.SyntaxKind.NeverKeyword:
338
+ case ts3.SyntaxKind.NeverKeyword:
413
339
  throw new SchemaExtractionError(typeNode.getText(), "never \u4E0D\u652F\u6301\u8FD0\u884C\u65F6\u6821\u9A8C");
414
- case ts2.SyntaxKind.ObjectKeyword:
340
+ case ts3.SyntaxKind.ObjectKeyword:
415
341
  throw new SchemaExtractionError(
416
342
  typeNode.getText(),
417
343
  "object \u4E0D\u652F\u6301\uFF0C\u8BF7\u4F7F\u7528\u5177\u4F53\u5BF9\u8C61\u7C7B\u578B\u6216 unknown"
418
344
  );
419
345
  }
420
- if (ts2.isLiteralTypeNode(typeNode)) {
346
+ if (ts3.isLiteralTypeNode(typeNode)) {
421
347
  const literal = typeNode.literal;
422
- if (ts2.isStringLiteral(literal)) {
348
+ if (ts3.isStringLiteral(literal)) {
423
349
  return { kind: "literal", value: literal.text };
424
350
  }
425
- if (ts2.isNumericLiteral(literal)) {
351
+ if (ts3.isNumericLiteral(literal)) {
426
352
  return { kind: "literal", value: Number(literal.text) };
427
353
  }
428
- if (literal.kind === ts2.SyntaxKind.TrueKeyword) {
354
+ if (literal.kind === ts3.SyntaxKind.TrueKeyword) {
429
355
  return { kind: "literal", value: true };
430
356
  }
431
- if (literal.kind === ts2.SyntaxKind.FalseKeyword) {
357
+ if (literal.kind === ts3.SyntaxKind.FalseKeyword) {
432
358
  return { kind: "literal", value: false };
433
359
  }
434
- if (literal.kind === ts2.SyntaxKind.NullKeyword) {
360
+ if (literal.kind === ts3.SyntaxKind.NullKeyword) {
435
361
  return { kind: "null" };
436
362
  }
437
363
  throw new SchemaExtractionError(typeNode.getText(), "\u4E0D\u652F\u6301\u7684\u5B57\u9762\u91CF\u7C7B\u578B");
438
364
  }
439
- if (ts2.isArrayTypeNode(typeNode)) {
365
+ if (ts3.isArrayTypeNode(typeNode)) {
440
366
  return {
441
367
  kind: "array",
442
368
  element: resolveTypeNode(typeNode.elementType, checker, visited)
443
369
  };
444
370
  }
445
- if (ts2.isTupleTypeNode(typeNode)) {
371
+ if (ts3.isTupleTypeNode(typeNode)) {
446
372
  const elements = typeNode.elements.map((e) => {
447
- if (ts2.isRestTypeNode(e)) {
373
+ if (ts3.isRestTypeNode(e)) {
448
374
  const inner = resolveTypeNode(e.type, checker, visited);
449
375
  if (inner.kind === "array") {
450
376
  return { type: inner.element, optional: false, rest: true };
451
377
  }
452
378
  return { type: inner, optional: false, rest: true };
453
379
  }
454
- if (ts2.isNamedTupleMember(e)) {
380
+ if (ts3.isNamedTupleMember(e)) {
455
381
  return {
456
382
  type: resolveTypeNode(e.type, checker, visited),
457
383
  optional: !!e.questionToken,
458
384
  rest: false
459
385
  };
460
386
  }
461
- if (ts2.isOptionalTypeNode(e)) {
387
+ if (ts3.isOptionalTypeNode(e)) {
462
388
  return {
463
389
  type: resolveTypeNode(e.type, checker, visited),
464
390
  optional: true,
@@ -473,11 +399,11 @@ function resolveTypeNode(typeNode, checker, visited = /* @__PURE__ */ new Set())
473
399
  });
474
400
  return { kind: "tuple", elements };
475
401
  }
476
- if (ts2.isUnionTypeNode(typeNode)) {
402
+ if (ts3.isUnionTypeNode(typeNode)) {
477
403
  const members = typeNode.types.map((t) => resolveTypeNode(t, checker, visited));
478
404
  return { kind: "union", members };
479
405
  }
480
- if (ts2.isIntersectionTypeNode(typeNode)) {
406
+ if (ts3.isIntersectionTypeNode(typeNode)) {
481
407
  const properties = [];
482
408
  for (const t of typeNode.types) {
483
409
  const resolved = resolveTypeNode(t, checker, visited);
@@ -487,13 +413,16 @@ function resolveTypeNode(typeNode, checker, visited = /* @__PURE__ */ new Set())
487
413
  }
488
414
  return { kind: "object", properties };
489
415
  }
490
- if (ts2.isTypeLiteralNode(typeNode)) {
416
+ if (ts3.isTypeLiteralNode(typeNode)) {
491
417
  return resolveTypeLiteral(typeNode, checker, visited);
492
418
  }
493
- if (ts2.isTypeOperatorNode(typeNode) && typeNode.operator === ts2.SyntaxKind.KeyOfKeyword) {
419
+ if (ts3.isTypeOperatorNode(typeNode) && typeNode.operator === ts3.SyntaxKind.KeyOfKeyword) {
494
420
  return resolveKeyOf(typeNode, checker);
495
421
  }
496
- if (ts2.isTypeReferenceNode(typeNode)) {
422
+ if (ts3.isTypeOperatorNode(typeNode) && typeNode.operator === ts3.SyntaxKind.ReadonlyKeyword) {
423
+ return resolveTypeNode(typeNode.type, checker, visited);
424
+ }
425
+ if (ts3.isTypeReferenceNode(typeNode)) {
497
426
  return resolveTypeReference(typeNode, checker, visited);
498
427
  }
499
428
  throw new SchemaExtractionError(typeNode.getText(), "\u4E0D\u652F\u6301\u7684\u7C7B\u578B\u8BED\u6CD5");
@@ -501,13 +430,17 @@ function resolveTypeNode(typeNode, checker, visited = /* @__PURE__ */ new Set())
501
430
  function resolveTypeLiteral(typeNode, checker, visited = /* @__PURE__ */ new Set()) {
502
431
  const properties = [];
503
432
  for (const member of typeNode.members) {
504
- if (ts2.isPropertySignature(member) && member.name) {
433
+ if (ts3.isPropertySignature(member) && member.name) {
505
434
  const name = member.name.getText();
506
435
  const optional = !!member.questionToken;
507
436
  const type = member.type ? resolveTypeNode(member.type, checker, visited) : { kind: "any" };
508
- properties.push({ name, type, optional });
437
+ const constraints = extractConstraintsFromJsDoc(member, name);
438
+ validateConstraints(constraints, type, name);
439
+ properties.push(
440
+ constraints.length > 0 ? { name, type, optional, constraints } : { name, type, optional }
441
+ );
509
442
  }
510
- if (ts2.isIndexSignatureDeclaration(member)) {
443
+ if (ts3.isIndexSignatureDeclaration(member)) {
511
444
  const keyType = member.parameters[0]?.type ? resolveTypeNode(member.parameters[0].type, checker, visited) : { kind: "any" };
512
445
  const valueType = member.type ? resolveTypeNode(member.type, checker, visited) : { kind: "any" };
513
446
  return { kind: "record", key: keyType, value: valueType };
@@ -587,7 +520,7 @@ function resolveTypeReference(typeNode, checker, visited = /* @__PURE__ */ new S
587
520
  if (typeName === "Date") {
588
521
  return { kind: "date" };
589
522
  }
590
- if (typeName === "Array" && typeNode.typeArguments?.length === 1) {
523
+ if ((typeName === "Array" || typeName === "ReadonlyArray") && typeNode.typeArguments?.length === 1) {
591
524
  return {
592
525
  kind: "array",
593
526
  element: resolveTypeNode(typeNode.typeArguments[0], checker, visited)
@@ -650,17 +583,17 @@ function resolveTypeReference(typeNode, checker, visited = /* @__PURE__ */ new S
650
583
  }
651
584
  visited.add(typeName);
652
585
  if (checker) {
653
- const symbol = typeNode.typeName.kind === ts2.SyntaxKind.Identifier ? checker.getSymbolAtLocation(typeNode.typeName) : void 0;
586
+ const symbol = typeNode.typeName.kind === ts3.SyntaxKind.Identifier ? checker.getSymbolAtLocation(typeNode.typeName) : void 0;
654
587
  if (symbol) {
655
588
  const declaration = symbol.declarations?.[0];
656
589
  if (declaration) {
657
- if (ts2.isInterfaceDeclaration(declaration)) {
590
+ if (ts3.isInterfaceDeclaration(declaration)) {
658
591
  return resolveInterfaceDeclaration(declaration, checker, visited);
659
592
  }
660
- if (ts2.isTypeAliasDeclaration(declaration)) {
593
+ if (ts3.isTypeAliasDeclaration(declaration)) {
661
594
  return resolveTypeNode(declaration.type, checker, visited);
662
595
  }
663
- if (ts2.isEnumDeclaration(declaration)) {
596
+ if (ts3.isEnumDeclaration(declaration)) {
664
597
  return resolveEnumDeclaration(declaration);
665
598
  }
666
599
  }
@@ -673,9 +606,9 @@ function resolveEnumDeclaration(node) {
673
606
  let nextNumericValue = 0;
674
607
  for (const member of node.members) {
675
608
  if (member.initializer) {
676
- if (ts2.isStringLiteral(member.initializer)) {
609
+ if (ts3.isStringLiteral(member.initializer)) {
677
610
  members.push({ kind: "literal", value: member.initializer.text });
678
- } else if (ts2.isNumericLiteral(member.initializer)) {
611
+ } else if (ts3.isNumericLiteral(member.initializer)) {
679
612
  const num = Number(member.initializer.text);
680
613
  members.push({ kind: "literal", value: num });
681
614
  nextNumericValue = num + 1;
@@ -696,7 +629,7 @@ function resolveInterfaceDeclaration(node, checker, visited = /* @__PURE__ */ ne
696
629
  const properties = [];
697
630
  const propMap = /* @__PURE__ */ new Map();
698
631
  for (const heritageClause of node.heritageClauses ?? []) {
699
- if (heritageClause.token === ts2.SyntaxKind.ExtendsKeyword) {
632
+ if (heritageClause.token === ts3.SyntaxKind.ExtendsKeyword) {
700
633
  for (const expr of heritageClause.types) {
701
634
  const parentType = resolveTypeNode(expr, checker, visited);
702
635
  if (parentType.kind === "object") {
@@ -708,13 +641,18 @@ function resolveInterfaceDeclaration(node, checker, visited = /* @__PURE__ */ ne
708
641
  }
709
642
  }
710
643
  for (const member of node.members) {
711
- if (ts2.isPropertySignature(member) && member.name) {
644
+ if (ts3.isPropertySignature(member) && member.name) {
712
645
  const name = member.name.getText();
713
646
  const optional = !!member.questionToken;
714
647
  const type = member.type ? resolveTypeNode(member.type, checker, visited) : { kind: "any" };
715
- propMap.set(name, { name, type, optional });
648
+ const constraints = extractConstraintsFromJsDoc(member, name);
649
+ validateConstraints(constraints, type, name);
650
+ propMap.set(
651
+ name,
652
+ constraints.length > 0 ? { name, type, optional, constraints } : { name, type, optional }
653
+ );
716
654
  }
717
- if (ts2.isIndexSignatureDeclaration(member)) {
655
+ if (ts3.isIndexSignatureDeclaration(member)) {
718
656
  const keyType = member.parameters[0]?.type ? resolveTypeNode(member.parameters[0].type, checker, visited) : { kind: "any" };
719
657
  const valueType = member.type ? resolveTypeNode(member.type, checker, visited) : { kind: "any" };
720
658
  return { kind: "record", key: keyType, value: valueType };
@@ -725,33 +663,169 @@ function resolveInterfaceDeclaration(node, checker, visited = /* @__PURE__ */ ne
725
663
  }
726
664
  return { kind: "object", properties };
727
665
  }
728
- var SchemaExtractionError;
729
- var init_resolveTypeNode = __esm({
730
- "src/ast/resolveTypeNode.ts"() {
731
- "use strict";
732
- SchemaExtractionError = class extends Error {
733
- constructor(typeText, reason, options) {
734
- super(`\u65E0\u6CD5\u89E3\u6790\u7C7B\u578B "${typeText}": ${reason}`, options);
735
- this.typeText = typeText;
666
+ function extractConstraintsFromJsDoc(node, fieldName) {
667
+ const jsDocs = ts3.getJSDocCommentsAndTags(node).filter((entry) => ts3.isJSDoc(entry));
668
+ if (jsDocs.length === 0) return [];
669
+ const constraints = [];
670
+ for (const jsDoc of jsDocs) {
671
+ if (!jsDoc.tags) continue;
672
+ for (const tag of jsDoc.tags) {
673
+ const constraint = parseJsDocTag(tag, fieldName);
674
+ if (constraint) constraints.push(constraint);
675
+ }
676
+ }
677
+ return constraints;
678
+ }
679
+ function getTagCommentText(tag) {
680
+ const comment = tag.comment;
681
+ if (typeof comment === "string") return comment;
682
+ return void 0;
683
+ }
684
+ function parseJsDocTag(tag, fieldName) {
685
+ const tagName = tag.tagName.text;
686
+ switch (tagName) {
687
+ // 数值约束(带值)
688
+ case "max":
689
+ case "min": {
690
+ const value = parseNumberValue(tag, fieldName, tagName);
691
+ return { kind: tagName, value };
692
+ }
693
+ // 长度约束(带值)
694
+ case "maxLength":
695
+ case "minLength":
696
+ case "length": {
697
+ const value = parseNumberValue(tag, fieldName, tagName);
698
+ return { kind: tagName, value };
699
+ }
700
+ // 正则约束(带 /pattern/flags 值)
701
+ case "regex":
702
+ case "pattern": {
703
+ const text = getTagCommentText(tag);
704
+ if (!text) {
705
+ throw new SchemaExtractionError(fieldName, `@${tagName} \u6807\u7B7E\u9700\u8981 /pattern/flags \u5F62\u5F0F\u7684\u503C`);
706
+ }
707
+ const regex = parseRegexLiteral(text.trim(), fieldName);
708
+ return { kind: "regex", pattern: regex.pattern, flags: regex.flags };
709
+ }
710
+ // 数值约束(无值)
711
+ case "int":
712
+ case "positive":
713
+ case "negative":
714
+ case "nonnegative":
715
+ case "nonpositive":
716
+ return { kind: tagName };
717
+ // 字符串格式约束(无值)
718
+ case "email":
719
+ case "url":
720
+ case "uuid":
721
+ return { kind: tagName };
722
+ default:
723
+ return null;
724
+ }
725
+ }
726
+ function parseNumberValue(tag, fieldName, tagName) {
727
+ const text = getTagCommentText(tag);
728
+ if (!text) {
729
+ throw new SchemaExtractionError(fieldName, `@${tagName} \u6807\u7B7E\u9700\u8981\u4E00\u4E2A\u6570\u5B57\u503C`);
730
+ }
731
+ const trimmed = text.trim();
732
+ const num = Number(trimmed);
733
+ if (!Number.isFinite(num)) {
734
+ throw new SchemaExtractionError(fieldName, `@${tagName} \u6807\u7B7E\u7684\u503C "${trimmed}" \u4E0D\u662F\u6709\u6548\u6570\u5B57`);
735
+ }
736
+ return num;
737
+ }
738
+ function parseRegexLiteral(text, fieldName) {
739
+ const match = /^\/(.+)\/([gimsuy]*)$/.exec(text);
740
+ if (!match) {
741
+ throw new SchemaExtractionError(fieldName, `\u6B63\u5219\u503C "${text}" \u4E0D\u662F /pattern/flags \u5F62\u5F0F`);
742
+ }
743
+ const [, pattern, flags] = match;
744
+ if (!pattern) {
745
+ throw new SchemaExtractionError(fieldName, `\u6B63\u5219\u503C "${text}" \u7684 pattern \u90E8\u5206\u4E3A\u7A7A`);
746
+ }
747
+ return flags ? { pattern, flags } : { pattern };
748
+ }
749
+ function validateConstraints(constraints, type, fieldName) {
750
+ if (constraints.length === 0) return;
751
+ for (const constraint of constraints) {
752
+ const kind = constraint.kind;
753
+ if (NUMBER_CONSTRAINT_KINDS.has(kind)) {
754
+ if (type.kind !== "number") {
755
+ throw new SchemaExtractionError(
756
+ fieldName,
757
+ `@${kind} \u7EA6\u675F\u4EC5\u9002\u7528\u4E8E number \u5B57\u6BB5\uFF0C\u5B9E\u9645\u4E3A ${type.kind}`
758
+ );
759
+ }
760
+ continue;
761
+ }
762
+ if (LENGTH_CONSTRAINT_KINDS.has(kind)) {
763
+ if (type.kind !== "string" && type.kind !== "array") {
764
+ throw new SchemaExtractionError(
765
+ fieldName,
766
+ `@${kind} \u7EA6\u675F\u4EC5\u9002\u7528\u4E8E string \u6216 array \u5B57\u6BB5\uFF0C\u5B9E\u9645\u4E3A ${type.kind}`
767
+ );
768
+ }
769
+ continue;
770
+ }
771
+ if (STRING_FORMAT_CONSTRAINT_KINDS.has(kind)) {
772
+ if (type.kind !== "string") {
773
+ throw new SchemaExtractionError(
774
+ fieldName,
775
+ `@${kind} \u7EA6\u675F\u4EC5\u9002\u7528\u4E8E string \u5B57\u6BB5\uFF0C\u5B9E\u9645\u4E3A ${type.kind}`
776
+ );
777
+ }
778
+ continue;
779
+ }
780
+ }
781
+ }
782
+ var SchemaExtractionError, NUMBER_CONSTRAINT_KINDS, LENGTH_CONSTRAINT_KINDS, STRING_FORMAT_CONSTRAINT_KINDS;
783
+ var init_resolveTypeNode = __esm({
784
+ "src/ast/resolveTypeNode.ts"() {
785
+ "use strict";
786
+ SchemaExtractionError = class extends Error {
787
+ constructor(typeText, reason, options) {
788
+ super(`\u65E0\u6CD5\u89E3\u6790\u7C7B\u578B "${typeText}": ${reason}`, options);
789
+ this.typeText = typeText;
736
790
  this.reason = reason;
737
791
  this.name = "SchemaExtractionError";
738
792
  }
739
793
  typeText;
740
794
  reason;
741
795
  };
796
+ NUMBER_CONSTRAINT_KINDS = /* @__PURE__ */ new Set([
797
+ "max",
798
+ "min",
799
+ "int",
800
+ "positive",
801
+ "negative",
802
+ "nonnegative",
803
+ "nonpositive"
804
+ ]);
805
+ LENGTH_CONSTRAINT_KINDS = /* @__PURE__ */ new Set([
806
+ "maxLength",
807
+ "minLength",
808
+ "length"
809
+ ]);
810
+ STRING_FORMAT_CONSTRAINT_KINDS = /* @__PURE__ */ new Set([
811
+ "regex",
812
+ "email",
813
+ "url",
814
+ "uuid"
815
+ ]);
742
816
  }
743
817
  });
744
818
 
745
819
  // src/ast/extractHandlerTypes.ts
746
- import ts3 from "typescript";
820
+ import ts4 from "typescript";
747
821
  function extractTypeInfo(program, filePath, typeName) {
748
822
  const sourceFile = program.getSourceFile(filePath);
749
823
  if (!sourceFile) return null;
750
824
  const checker = program.getTypeChecker();
751
825
  let result = null;
752
- ts3.forEachChild(sourceFile, (node) => {
826
+ ts4.forEachChild(sourceFile, (node) => {
753
827
  if (result) return;
754
- if (ts3.isInterfaceDeclaration(node) && node.name.text === typeName) {
828
+ if (ts4.isInterfaceDeclaration(node) && node.name.text === typeName) {
755
829
  const visited = /* @__PURE__ */ new Set();
756
830
  visited.add(typeName);
757
831
  const runtimeType = withFileContext(
@@ -766,7 +840,7 @@ function extractTypeInfo(program, filePath, typeName) {
766
840
  };
767
841
  return;
768
842
  }
769
- if (ts3.isTypeAliasDeclaration(node) && node.name.text === typeName) {
843
+ if (ts4.isTypeAliasDeclaration(node) && node.name.text === typeName) {
770
844
  const visited = /* @__PURE__ */ new Set();
771
845
  visited.add(typeName);
772
846
  const runtimeType = withFileContext(
@@ -789,8 +863,8 @@ function extractAllTypes(program, filePath) {
789
863
  if (!sourceFile) return /* @__PURE__ */ new Map();
790
864
  const checker = program.getTypeChecker();
791
865
  const result = /* @__PURE__ */ new Map();
792
- ts3.forEachChild(sourceFile, (node) => {
793
- if (ts3.isInterfaceDeclaration(node)) {
866
+ ts4.forEachChild(sourceFile, (node) => {
867
+ if (ts4.isInterfaceDeclaration(node)) {
794
868
  const visited = /* @__PURE__ */ new Set();
795
869
  visited.add(node.name.text);
796
870
  const runtimeType = withFileContext(
@@ -805,7 +879,7 @@ function extractAllTypes(program, filePath) {
805
879
  });
806
880
  return;
807
881
  }
808
- if (ts3.isTypeAliasDeclaration(node)) {
882
+ if (ts4.isTypeAliasDeclaration(node)) {
809
883
  const visited = /* @__PURE__ */ new Set();
810
884
  visited.add(node.name.text);
811
885
  const runtimeType = withFileContext(
@@ -875,7 +949,7 @@ var init_schemaName = __esm({
875
949
  });
876
950
 
877
951
  // src/injection/resolveInjection.ts
878
- import ts4 from "typescript";
952
+ import ts5 from "typescript";
879
953
  function resolveInjection(fn) {
880
954
  const fnStr = fn.toString();
881
955
  const params = extractParamsWithAst(fnStr);
@@ -890,53 +964,53 @@ function resolveInjection(fn) {
890
964
  });
891
965
  }
892
966
  function extractParamsWithAst(fnStr) {
893
- const sourceFile = ts4.createSourceFile(
967
+ const sourceFile = ts5.createSourceFile(
894
968
  "__faapi_injection__.ts",
895
969
  fnStr,
896
- ts4.ScriptTarget.Latest,
970
+ ts5.ScriptTarget.Latest,
897
971
  true
898
972
  );
899
973
  const paramNames = [];
900
974
  function visit(node) {
901
- if (ts4.isFunctionDeclaration(node) && node.parameters.length > 0) {
975
+ if (ts5.isFunctionDeclaration(node) && node.parameters.length > 0) {
902
976
  for (const param of node.parameters) {
903
977
  extractParamName(param, paramNames);
904
978
  }
905
979
  return;
906
980
  }
907
- if ((ts4.isArrowFunction(node) || ts4.isFunctionExpression(node)) && node.parameters.length > 0) {
981
+ if ((ts5.isArrowFunction(node) || ts5.isFunctionExpression(node)) && node.parameters.length > 0) {
908
982
  for (const param of node.parameters) {
909
983
  extractParamName(param, paramNames);
910
984
  }
911
985
  return;
912
986
  }
913
- ts4.forEachChild(node, visit);
987
+ ts5.forEachChild(node, visit);
914
988
  }
915
989
  visit(sourceFile);
916
990
  return paramNames.map((name) => ({ name }));
917
991
  }
918
992
  function extractParamName(param, names) {
919
993
  const name = param.name;
920
- if (ts4.isIdentifier(name)) {
994
+ if (ts5.isIdentifier(name)) {
921
995
  names.push(name.text);
922
996
  return;
923
997
  }
924
- if (ts4.isObjectBindingPattern(name)) {
998
+ if (ts5.isObjectBindingPattern(name)) {
925
999
  for (const element of name.elements) {
926
- if (ts4.isBindingElement(element)) {
1000
+ if (ts5.isBindingElement(element)) {
927
1001
  const elemName = element.name;
928
- if (ts4.isIdentifier(elemName)) {
1002
+ if (ts5.isIdentifier(elemName)) {
929
1003
  names.push(elemName.text);
930
1004
  }
931
1005
  }
932
1006
  }
933
1007
  return;
934
1008
  }
935
- if (ts4.isArrayBindingPattern(name)) {
1009
+ if (ts5.isArrayBindingPattern(name)) {
936
1010
  for (const element of name.elements) {
937
- if (element && ts4.isBindingElement(element)) {
1011
+ if (element && ts5.isBindingElement(element)) {
938
1012
  const elemName = element.name;
939
- if (ts4.isIdentifier(elemName)) {
1013
+ if (ts5.isIdentifier(elemName)) {
940
1014
  names.push(elemName.text);
941
1015
  }
942
1016
  }
@@ -965,12 +1039,12 @@ var init_resolveInjection = __esm({
965
1039
  });
966
1040
 
967
1041
  // src/injection/analyzeInjection.ts
968
- import ts5 from "typescript";
1042
+ import ts6 from "typescript";
969
1043
  function analyzeInjection(code, functionName) {
970
- const sourceFile = ts5.createSourceFile("temp.ts", code, ts5.ScriptTarget.Latest, true);
1044
+ const sourceFile = ts6.createSourceFile("temp.ts", code, ts6.ScriptTarget.Latest, true);
971
1045
  const params = [];
972
- ts5.forEachChild(sourceFile, (node) => {
973
- if (ts5.isFunctionDeclaration(node) && node.name?.text === functionName) {
1046
+ ts6.forEachChild(sourceFile, (node) => {
1047
+ if (ts6.isFunctionDeclaration(node) && node.name?.text === functionName) {
974
1048
  for (const param of node.parameters) {
975
1049
  const paramMeta = analyzeParam(param, sourceFile);
976
1050
  params.push(paramMeta);
@@ -984,9 +1058,9 @@ function analyzeParam(param, sourceFile) {
984
1058
  const type = PARAM_TYPE_MAP[name] || "unknown";
985
1059
  const result = { name, type };
986
1060
  if (param.type) {
987
- if (ts5.isTypeReferenceNode(param.type)) {
1061
+ if (ts6.isTypeReferenceNode(param.type)) {
988
1062
  result.typeName = param.type.typeName.getText(sourceFile);
989
- } else if (ts5.isTypeLiteralNode(param.type)) {
1063
+ } else if (ts6.isTypeLiteralNode(param.type)) {
990
1064
  result.schema = extractSchema(param.type, sourceFile);
991
1065
  }
992
1066
  }
@@ -995,7 +1069,7 @@ function analyzeParam(param, sourceFile) {
995
1069
  function extractSchema(typeNode, sourceFile) {
996
1070
  const schema = [];
997
1071
  for (const member of typeNode.members) {
998
- if (ts5.isPropertySignature(member) && member.name && ts5.isIdentifier(member.name)) {
1072
+ if (ts6.isPropertySignature(member) && member.name && ts6.isIdentifier(member.name)) {
999
1073
  const propName = member.name.text;
1000
1074
  const optional = !!member.questionToken;
1001
1075
  const propType = member.type?.getText(sourceFile) || "unknown";
@@ -1016,11 +1090,11 @@ var init_analyzeInjection = __esm({
1016
1090
  });
1017
1091
 
1018
1092
  // src/cli/collectRouteSchemaSources.ts
1019
- import path2 from "path";
1093
+ import path5 from "path";
1020
1094
  function collectRouteSchemaSources(routes, rootDir) {
1021
1095
  const methodsByFile = /* @__PURE__ */ new Map();
1022
1096
  for (const route of routes) {
1023
- const filePath = rootDir ? path2.resolve(rootDir, route.filePath) : route.filePath;
1097
+ const filePath = rootDir ? path5.resolve(rootDir, route.filePath) : route.filePath;
1024
1098
  let entry = methodsByFile.get(filePath);
1025
1099
  if (!entry) {
1026
1100
  entry = { urlPath: route.urlPath, methods: /* @__PURE__ */ new Set() };
@@ -1115,12 +1189,51 @@ function collectNamedTypes(type, ctx) {
1115
1189
  }
1116
1190
  }
1117
1191
  }
1118
- function runtimeTypeToZodExpression(type, ctx) {
1192
+ function runtimeTypeToZodExpression(type, ctx, constraints) {
1119
1193
  const expr = baseExpression(type, ctx);
1194
+ const withConstraints = constraints && constraints.length > 0 ? applyConstraints(expr, constraints, type.kind) : expr;
1120
1195
  if (ctx.coerce && (type.kind === "number" || type.kind === "boolean")) {
1121
- return wrapCoercePreprocess(type.kind, expr);
1196
+ return wrapCoercePreprocess(type.kind, withConstraints);
1197
+ }
1198
+ return withConstraints;
1199
+ }
1200
+ function applyConstraints(baseExpr, constraints, typeKind) {
1201
+ const suffix = constraints.map((c) => constraintToZodChain(c, typeKind)).join("");
1202
+ return `${baseExpr}${suffix}`;
1203
+ }
1204
+ function constraintToZodChain(constraint, _typeKind) {
1205
+ switch (constraint.kind) {
1206
+ case "max":
1207
+ return `.max(${constraint.value})`;
1208
+ case "min":
1209
+ return `.min(${constraint.value})`;
1210
+ case "int":
1211
+ return ".int()";
1212
+ case "positive":
1213
+ return ".positive()";
1214
+ case "negative":
1215
+ return ".negative()";
1216
+ case "nonnegative":
1217
+ return ".nonnegative()";
1218
+ case "nonpositive":
1219
+ return ".nonpositive()";
1220
+ case "maxLength":
1221
+ return `.max(${constraint.value})`;
1222
+ case "minLength":
1223
+ return `.min(${constraint.value})`;
1224
+ case "length":
1225
+ return `.length(${constraint.value})`;
1226
+ case "regex": {
1227
+ const flags = constraint.flags ?? "";
1228
+ return `.regex(new RegExp(${JSON.stringify(constraint.pattern)}${flags ? `, ${JSON.stringify(flags)}` : ""}))`;
1229
+ }
1230
+ case "email":
1231
+ return ".email()";
1232
+ case "url":
1233
+ return ".url()";
1234
+ case "uuid":
1235
+ return ".uuid()";
1122
1236
  }
1123
- return expr;
1124
1237
  }
1125
1238
  function baseExpression(type, ctx) {
1126
1239
  switch (type.kind) {
@@ -1215,8 +1328,9 @@ function generateTupleExpression(elements, ctx) {
1215
1328
  }
1216
1329
  function generateObjectExpression(properties, ctx) {
1217
1330
  const fields = properties.map((prop) => {
1218
- const expr = runtimeTypeToZodExpression(prop.type, ctx);
1219
- return `${JSON.stringify(prop.name)}: ${prop.optional ? `${expr}.optional()` : expr}`;
1331
+ const expr = runtimeTypeToZodExpression(prop.type, ctx, prop.constraints);
1332
+ const finalExpr = prop.optional ? `${expr}.optional()` : expr;
1333
+ return `${JSON.stringify(prop.name)}: ${finalExpr}`;
1220
1334
  });
1221
1335
  return `z.object({ ${fields.join(", ")} })`;
1222
1336
  }
@@ -1314,8 +1428,8 @@ var init_generateZodSchema = __esm({
1314
1428
  });
1315
1429
 
1316
1430
  // src/cli/generateSchemaFiles.ts
1317
- import path3 from "path";
1318
- import fs2 from "fs/promises";
1431
+ import path6 from "path";
1432
+ import fs5 from "fs/promises";
1319
1433
  function getSchemaOutputPath(sourceFile, appDir, outDir, rootDir) {
1320
1434
  let rel = sourceFile.replace(/\\/g, "/");
1321
1435
  if (appDir !== "." && rel.startsWith(`${appDir}/`)) {
@@ -1323,7 +1437,7 @@ function getSchemaOutputPath(sourceFile, appDir, outDir, rootDir) {
1323
1437
  }
1324
1438
  const idx = rel.lastIndexOf("/");
1325
1439
  const relDir = idx >= 0 ? rel.slice(0, idx) : "";
1326
- return path3.resolve(rootDir, outDir, relDir, "zod.js");
1440
+ return path6.resolve(rootDir, outDir, relDir, "zod.js");
1327
1441
  }
1328
1442
  function getRuntimeSchemaPath(filePath, appDir, outDir, rootDir) {
1329
1443
  let rel = filePath.replace(/\\/g, "/");
@@ -1334,7 +1448,7 @@ function getRuntimeSchemaPath(filePath, appDir, outDir, rootDir) {
1334
1448
  }
1335
1449
  const idx = rel.lastIndexOf("/");
1336
1450
  const relDir = idx >= 0 ? rel.slice(0, idx) : "";
1337
- return path3.resolve(rootDir, outDir, relDir, "zod.js");
1451
+ return path6.resolve(rootDir, outDir, relDir, "zod.js");
1338
1452
  }
1339
1453
  function getHelpersImportPath(relDir) {
1340
1454
  if (!relDir) return `./${HELPERS_FILENAME}`;
@@ -1382,7 +1496,7 @@ async function generateSchemaFiles(routes, rootDir, appDir, outDir) {
1382
1496
  }
1383
1497
  const fileEntries = [];
1384
1498
  for (const [filePath, fileSources] of sourcesByFile) {
1385
- const relFile = path3.relative(rootDir, filePath).replace(/\\/g, "/");
1499
+ const relFile = path6.relative(rootDir, filePath).replace(/\\/g, "/");
1386
1500
  const outputPath = getSchemaOutputPath(relFile, appDir, outDir, rootDir);
1387
1501
  const allTypes = allTypesByFile.get(filePath) ?? /* @__PURE__ */ new Map();
1388
1502
  let relForDir = relFile;
@@ -1397,7 +1511,7 @@ async function generateSchemaFiles(routes, rootDir, appDir, outDir) {
1397
1511
  }
1398
1512
  const allSourceCode = fileEntries.map((e) => e.source).join("\n");
1399
1513
  if (usesCoerceHelpers(allSourceCode)) {
1400
- const helpersPath = path3.resolve(rootDir, outDir, HELPERS_FILENAME);
1514
+ const helpersPath = path6.resolve(rootDir, outDir, HELPERS_FILENAME);
1401
1515
  await writeSchemaFile(helpersPath, generateHelpersFileSource());
1402
1516
  }
1403
1517
  await Promise.all(
@@ -1405,8 +1519,8 @@ async function generateSchemaFiles(routes, rootDir, appDir, outDir) {
1405
1519
  );
1406
1520
  }
1407
1521
  async function writeSchemaFile(outputPath, source) {
1408
- await fs2.mkdir(path3.dirname(outputPath), { recursive: true });
1409
- await fs2.writeFile(outputPath, source, "utf-8");
1522
+ await fs5.mkdir(path6.dirname(outputPath), { recursive: true });
1523
+ await fs5.writeFile(outputPath, source, "utf-8");
1410
1524
  }
1411
1525
  var init_generateSchemaFiles = __esm({
1412
1526
  "src/cli/generateSchemaFiles.ts"() {
@@ -1416,9 +1530,80 @@ var init_generateSchemaFiles = __esm({
1416
1530
  }
1417
1531
  });
1418
1532
 
1533
+ // src/utils/importWithCacheBust.ts
1534
+ import { pathToFileURL } from "url";
1535
+ function setLoadTimestamp(ts7) {
1536
+ loadTs = ts7;
1537
+ }
1538
+ async function importWithCacheBust(filePath) {
1539
+ let url = pathToFileURL(filePath).href;
1540
+ if (loadTs !== void 0) {
1541
+ url += `?t=${loadTs}`;
1542
+ }
1543
+ return await import(url);
1544
+ }
1545
+ var loadTs;
1546
+ var init_importWithCacheBust = __esm({
1547
+ "src/utils/importWithCacheBust.ts"() {
1548
+ "use strict";
1549
+ }
1550
+ });
1551
+
1552
+ // src/middleware/loadMiddlewares.ts
1553
+ function invalidateMiddlewareCache() {
1554
+ middlewareCache.clear();
1555
+ }
1556
+ function getCachedMiddlewares(absPath) {
1557
+ return middlewareCache.get(absPath);
1558
+ }
1559
+ function setCachedMiddlewares(absPath, bundle) {
1560
+ middlewareCache.set(absPath, bundle);
1561
+ }
1562
+ async function loadMiddlewaresFile(filePath) {
1563
+ try {
1564
+ const module = await importWithCacheBust(filePath);
1565
+ const middlewares = module.default ?? module.middlewares ?? [];
1566
+ if (!Array.isArray(middlewares)) {
1567
+ console.warn(`[faapi] middlewares.ts \u5E94\u5BFC\u51FA\u6570\u7EC4\uFF0C\u5DF2\u5FFD\u7565: ${filePath}`);
1568
+ return { middlewares: [], injectors: {} };
1569
+ }
1570
+ const validMiddlewares = middlewares.filter((m) => {
1571
+ if (typeof m !== "function") {
1572
+ console.warn(`[faapi] \u65E0\u6548\u7684\u4E2D\u95F4\u4EF6\u9879\uFF08\u5E94\u4E3A\u51FD\u6570\uFF09\uFF0C\u5DF2\u5FFD\u7565: ${typeof m}`);
1573
+ return false;
1574
+ }
1575
+ return true;
1576
+ });
1577
+ const injectors = module.injectors ?? {};
1578
+ if (typeof injectors !== "object" || injectors === null) {
1579
+ console.warn(`[faapi] injectors \u5E94\u5BFC\u51FA\u5BF9\u8C61\uFF0C\u5DF2\u5FFD\u7565: ${filePath}`);
1580
+ return { middlewares: validMiddlewares, injectors: {} };
1581
+ }
1582
+ const validInjectors = {};
1583
+ for (const [name, injector] of Object.entries(injectors)) {
1584
+ if (typeof injector !== "function") {
1585
+ console.warn(`[faapi] \u6CE8\u5165\u5668 ${name} \u5E94\u4E3A\u51FD\u6570\uFF0C\u5DF2\u5FFD\u7565`);
1586
+ continue;
1587
+ }
1588
+ validInjectors[name] = injector;
1589
+ }
1590
+ return { middlewares: validMiddlewares, injectors: validInjectors };
1591
+ } catch {
1592
+ return { middlewares: [], injectors: {} };
1593
+ }
1594
+ }
1595
+ var middlewareCache;
1596
+ var init_loadMiddlewares = __esm({
1597
+ "src/middleware/loadMiddlewares.ts"() {
1598
+ "use strict";
1599
+ init_importWithCacheBust();
1600
+ middlewareCache = /* @__PURE__ */ new Map();
1601
+ }
1602
+ });
1603
+
1419
1604
  // src/cli/generateRoutes.ts
1420
- import fs3 from "fs";
1421
- import path4 from "path";
1605
+ import fs6 from "fs";
1606
+ import path7 from "path";
1422
1607
  function toProdFilePath(filePath, appDir, prodDir) {
1423
1608
  let rel = filePath.replace(/\\/g, "/");
1424
1609
  if (appDir !== "." && rel.startsWith(`${appDir}/`)) {
@@ -1449,23 +1634,23 @@ function serializeRoutes(routes, wsRoutes, rootDir, appDir = "src", prodDir = "d
1449
1634
  };
1450
1635
  }
1451
1636
  function extractMiddlewarePaths(routeFilePath, rootDir, appDir, prodDir) {
1452
- const routeDir = path4.dirname(routeFilePath);
1453
- const resolvedRoot = path4.resolve(rootDir);
1637
+ const routeDir = path7.dirname(routeFilePath);
1638
+ const resolvedRoot = path7.resolve(rootDir);
1454
1639
  const paths = [];
1455
- let currentDir = path4.resolve(rootDir, routeDir);
1640
+ let currentDir = path7.resolve(rootDir, routeDir);
1456
1641
  while (true) {
1457
- const mwTsPath = path4.join(currentDir, "middlewares.ts");
1458
- const mwJsPath = path4.join(currentDir, "middlewares.js");
1459
- const absTsPath = path4.resolve(rootDir, mwTsPath);
1460
- const absJsPath = path4.resolve(rootDir, mwJsPath);
1461
- const absMwPath = fs3.existsSync(absTsPath) ? absTsPath : fs3.existsSync(absJsPath) ? absJsPath : null;
1642
+ const mwTsPath = path7.join(currentDir, "middlewares.ts");
1643
+ const mwJsPath = path7.join(currentDir, "middlewares.js");
1644
+ const absTsPath = path7.resolve(rootDir, mwTsPath);
1645
+ const absJsPath = path7.resolve(rootDir, mwJsPath);
1646
+ const absMwPath = fs6.existsSync(absTsPath) ? absTsPath : fs6.existsSync(absJsPath) ? absJsPath : null;
1462
1647
  if (absMwPath) {
1463
- const relMwPath = path4.relative(rootDir, absMwPath);
1464
- const prodAbsPath = path4.resolve(rootDir, toProdFilePath(relMwPath, appDir, prodDir));
1648
+ const relMwPath = path7.relative(rootDir, absMwPath);
1649
+ const prodAbsPath = path7.resolve(rootDir, toProdFilePath(relMwPath, appDir, prodDir));
1465
1650
  paths.push(prodAbsPath);
1466
1651
  }
1467
1652
  if (currentDir === resolvedRoot) break;
1468
- const parentDir = path4.dirname(currentDir);
1653
+ const parentDir = path7.dirname(currentDir);
1469
1654
  if (parentDir === currentDir) break;
1470
1655
  currentDir = parentDir;
1471
1656
  }
@@ -1473,13 +1658,13 @@ function extractMiddlewarePaths(routeFilePath, rootDir, appDir, prodDir) {
1473
1658
  return paths;
1474
1659
  }
1475
1660
  async function writeRoutesModule(manifest, outputPath) {
1476
- const dir = path4.dirname(outputPath);
1477
- await fs3.promises.mkdir(dir, { recursive: true });
1661
+ const dir = path7.dirname(outputPath);
1662
+ await fs6.promises.mkdir(dir, { recursive: true });
1478
1663
  const content = `// \u81EA\u52A8\u751F\u6210\uFF0C\u8BF7\u52FF\u624B\u52A8\u7F16\u8F91\uFF08faapi build \u4EA7\u7269\uFF09
1479
1664
  export const routes = ${JSON.stringify(manifest.routes, null, 2)};
1480
1665
  export const wsRoutes = ${JSON.stringify(manifest.wsRoutes, null, 2)};
1481
1666
  `;
1482
- await fs3.promises.writeFile(outputPath, content, "utf-8");
1667
+ await fs6.promises.writeFile(outputPath, content, "utf-8");
1483
1668
  }
1484
1669
  async function hydrateRoutes(manifest) {
1485
1670
  const hydrateRoute = async (serialized) => {
@@ -1535,281 +1720,249 @@ var init_generateRoutes = __esm({
1535
1720
  }
1536
1721
  });
1537
1722
 
1538
- // src/utils/resolveAlias.ts
1539
- function resolveAlias(specifier, config) {
1540
- const candidates = [];
1541
- for (const [pattern, targets] of Object.entries(config.paths)) {
1542
- const wildcardIndex = pattern.indexOf("*");
1543
- if (wildcardIndex === -1) {
1544
- if (specifier === pattern) {
1545
- candidates.push(...targets);
1546
- }
1547
- continue;
1548
- }
1549
- const prefix = pattern.slice(0, wildcardIndex);
1550
- const suffix = pattern.slice(wildcardIndex + 1);
1551
- if (specifier.startsWith(prefix) && specifier.endsWith(suffix) && specifier.length >= prefix.length + suffix.length) {
1552
- const captured = specifier.slice(prefix.length, specifier.length - suffix.length);
1553
- for (const target of targets) {
1554
- candidates.push(target.replace("*", captured));
1555
- }
1556
- }
1557
- }
1558
- return candidates;
1723
+ // src/router/constants.ts
1724
+ function isHttpMethod(value) {
1725
+ return HTTP_METHOD_SET.has(value);
1559
1726
  }
1560
- var init_resolveAlias = __esm({
1561
- "src/utils/resolveAlias.ts"() {
1727
+ var HTTP_METHODS, HTTP_METHOD_SET;
1728
+ var init_constants = __esm({
1729
+ "src/router/constants.ts"() {
1562
1730
  "use strict";
1731
+ HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
1732
+ HTTP_METHOD_SET = new Set(HTTP_METHODS);
1563
1733
  }
1564
1734
  });
1565
1735
 
1566
- // src/utils/readTsconfig.ts
1567
- import ts6 from "typescript";
1568
- import path5 from "path";
1569
- import fs4 from "fs";
1570
- function readTsconfig(rootDir) {
1571
- const tsconfigPath = path5.resolve(rootDir, "tsconfig.json");
1572
- if (!fs4.existsSync(tsconfigPath)) return null;
1573
- const configFile = ts6.readConfigFile(tsconfigPath, ts6.sys.readFile);
1574
- if (configFile.error || !configFile.config) return null;
1575
- const parsed = ts6.parseJsonConfigFileContent(configFile.config, ts6.sys, rootDir);
1576
- const baseUrl = parsed.options.baseUrl ?? rootDir;
1577
- const rawPaths = parsed.options.paths;
1578
- if (!rawPaths) return null;
1579
- const paths = {};
1580
- for (const [pattern, targets] of Object.entries(rawPaths)) {
1581
- paths[pattern] = targets.map((t) => path5.resolve(baseUrl, t));
1736
+ // src/utils/normalizePath.ts
1737
+ function normalizePath(path17) {
1738
+ if (!path17) return "";
1739
+ let result = path17.replace(/\\/g, "/");
1740
+ result = result.replace(/\/+/g, "/");
1741
+ result = result.replace(/\/+$/, "");
1742
+ if (result && !result.startsWith("/")) {
1743
+ result = "/" + result;
1582
1744
  }
1583
- return { baseUrl, paths };
1745
+ return result;
1584
1746
  }
1585
- var init_readTsconfig = __esm({
1586
- "src/utils/readTsconfig.ts"() {
1747
+ var init_normalizePath = __esm({
1748
+ "src/utils/normalizePath.ts"() {
1587
1749
  "use strict";
1588
1750
  }
1589
1751
  });
1590
1752
 
1591
- // src/cli/aliasPlugin.ts
1592
- import path6 from "path";
1593
- import fs5 from "fs";
1594
- function toProdExtension(filePath) {
1595
- if (filePath.endsWith(".ts")) return filePath.slice(0, -3) + ".js";
1596
- if (filePath.endsWith(".tsx")) return filePath.slice(0, -4) + ".js";
1597
- if (filePath.endsWith(".jsx")) return filePath.slice(0, -4) + ".js";
1598
- return filePath;
1599
- }
1600
- function toProdImportPath(sourceFile, importer) {
1601
- const importerDir = path6.dirname(importer);
1602
- let rel = path6.relative(importerDir, sourceFile);
1603
- rel = rel.split(path6.sep).join("/");
1604
- if (!rel.startsWith(".")) rel = "./" + rel;
1605
- return toProdExtension(rel);
1753
+ // src/router/parseRouteFile.ts
1754
+ function dynamicSegmentToParam(segment) {
1755
+ const match = segment.match(/^\[(.+)\]$/);
1756
+ if (match) {
1757
+ return ":" + match[1];
1758
+ }
1759
+ return segment;
1606
1760
  }
1607
- function createAliasPlugin(config) {
1608
- const EXTS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
1609
- const INDEX_EXTS = [
1610
- "/index.ts",
1611
- "/index.tsx",
1612
- "/index.js",
1613
- "/index.jsx",
1614
- "/index.mjs",
1615
- "/index.cjs"
1616
- ];
1617
- const SPEC_RE = /(\bfrom\s*|import\s*\(\s*)(['"])([^'"]+)\2/g;
1618
- return {
1619
- name: "faapi-alias",
1620
- setup(build) {
1621
- build.onLoad({ filter: /\.(ts|tsx|js|jsx|mjs|cjs)$/ }, (args) => {
1622
- let source;
1623
- try {
1624
- source = fs5.readFileSync(args.path, "utf8");
1625
- } catch {
1626
- return void 0;
1627
- }
1628
- const importer = args.path;
1629
- let modified = false;
1630
- const newSource = source.replace(SPEC_RE, (full, prefix, quote, specifier) => {
1631
- if (specifier.startsWith(".") || specifier.startsWith("/") || specifier.startsWith("file:") || specifier.startsWith("node:")) {
1632
- return full;
1633
- }
1634
- const candidates = resolveAlias(specifier, config);
1635
- for (const candidate of candidates) {
1636
- for (const ext of EXTS) {
1637
- const file = candidate + ext;
1638
- if (fs5.existsSync(file)) {
1639
- modified = true;
1640
- return `${prefix}${quote}${toProdImportPath(file, importer)}${quote}`;
1641
- }
1642
- }
1643
- for (const indexExt of INDEX_EXTS) {
1644
- const file = candidate + indexExt;
1645
- if (fs5.existsSync(file)) {
1646
- modified = true;
1647
- return `${prefix}${quote}${toProdImportPath(file, importer)}${quote}`;
1648
- }
1649
- }
1650
- }
1651
- return full;
1652
- });
1653
- if (!modified) return void 0;
1654
- return { contents: newSource, loader: "default" };
1655
- });
1761
+ function extractParamNames(urlPath) {
1762
+ const params = [];
1763
+ const segments = urlPath.split("/");
1764
+ for (const segment of segments) {
1765
+ if (segment.startsWith(":...")) {
1766
+ params.push(segment.slice(4));
1767
+ } else if (segment.startsWith(":")) {
1768
+ params.push(segment.slice(1));
1656
1769
  }
1657
- };
1770
+ }
1771
+ return params;
1658
1772
  }
1659
- function buildAliasPlugins(rootDir) {
1660
- const tsconfig = readTsconfig(rootDir);
1661
- return tsconfig ? [createAliasPlugin(tsconfig)] : [];
1773
+ function isCatchAllSegment(segment) {
1774
+ return /^\[\.\.\..+\]$/.test(segment);
1662
1775
  }
1663
- var init_aliasPlugin = __esm({
1664
- "src/cli/aliasPlugin.ts"() {
1665
- "use strict";
1666
- init_resolveAlias();
1667
- init_readTsconfig();
1668
- }
1669
- });
1670
-
1671
- // src/cli/compileBuildRoutes.ts
1672
- import path7 from "path";
1673
- import fs6 from "fs";
1674
- async function compileBuildRoutes(options) {
1675
- const {
1676
- rootDir,
1677
- appDir,
1678
- outDir,
1679
- entries,
1680
- splitting = true,
1681
- define,
1682
- minifySyntax = true,
1683
- logLevel = "silent"
1684
- } = options;
1685
- if (entries.length === 0) {
1686
- return { compiledFiles: [] };
1687
- }
1688
- const absOutDir = path7.resolve(rootDir, outDir);
1689
- await fs6.promises.mkdir(absOutDir, { recursive: true });
1690
- const plugins = buildAliasPlugins(rootDir);
1691
- const esbuild = await import("esbuild");
1692
- const outbase = appDir === "." ? rootDir : path7.resolve(rootDir, appDir);
1693
- await esbuild.build({
1694
- entryPoints: entries,
1695
- outdir: absOutDir,
1696
- outbase,
1697
- bundle: true,
1698
- splitting,
1699
- platform: "node",
1700
- format: "esm",
1701
- sourcemap: true,
1702
- packages: "external",
1703
- plugins,
1704
- define,
1705
- minifySyntax,
1706
- logLevel
1707
- });
1708
- return { compiledFiles: entries };
1776
+ function isRouteGroup(segment) {
1777
+ return /^\(.+\)$/.test(segment);
1709
1778
  }
1710
- var init_compileBuildRoutes = __esm({
1711
- "src/cli/compileBuildRoutes.ts"() {
1712
- "use strict";
1713
- init_aliasPlugin();
1714
- }
1715
- });
1716
-
1717
- // src/config/deepMerge.ts
1718
- function deepMerge(base, override) {
1719
- const result = { ...base };
1720
- for (const key of Object.keys(override)) {
1721
- const baseVal = base[key];
1722
- const overVal = override[key];
1723
- if (baseVal instanceof Date || overVal instanceof Date || baseVal instanceof RegExp || overVal instanceof RegExp || baseVal instanceof Map || overVal instanceof Map || baseVal instanceof Set || overVal instanceof Set) {
1724
- result[key] = overVal;
1725
- continue;
1726
- }
1727
- if (baseVal !== null && overVal !== null && typeof baseVal === "object" && typeof overVal === "object" && !Array.isArray(baseVal) && !Array.isArray(overVal) && !(baseVal instanceof Function) && !(overVal instanceof Function)) {
1728
- result[key] = deepMerge(
1729
- baseVal,
1730
- overVal
1731
- );
1732
- } else {
1733
- result[key] = overVal;
1734
- }
1779
+ function filePathToUrlPath(filePath, appDir = ".") {
1780
+ const withoutPrefix = filePath.startsWith(appDir + "/") ? filePath.slice(appDir.length + 1) : filePath;
1781
+ const lastSlashIndex = withoutPrefix.lastIndexOf("/");
1782
+ const dirPath = lastSlashIndex === -1 ? "" : withoutPrefix.slice(0, lastSlashIndex);
1783
+ if (!dirPath) {
1784
+ return "";
1735
1785
  }
1736
- return result;
1786
+ const segments = dirPath.split("/").filter((s) => !isRouteGroup(s)).map(dynamicSegmentToParam);
1787
+ return normalizePath(segments.join("/"));
1737
1788
  }
1738
- var DEEP_MERGE_SOURCE;
1739
- var init_deepMerge = __esm({
1740
- "src/config/deepMerge.ts"() {
1789
+ var init_parseRouteFile = __esm({
1790
+ "src/router/parseRouteFile.ts"() {
1741
1791
  "use strict";
1742
- DEEP_MERGE_SOURCE = `const deepMerge = ${deepMerge.toString()};`;
1792
+ init_normalizePath();
1743
1793
  }
1744
1794
  });
1745
1795
 
1746
- // src/cli/compileConfig.ts
1796
+ // src/router/scanRoutes.ts
1797
+ import fg2 from "fast-glob";
1747
1798
  import path8 from "path";
1748
1799
  import fs7 from "fs";
1749
- function getEnv() {
1750
- return process.env.FAAPI_ENV || process.env.NODE_ENV || "development";
1800
+ function toProdAbsPath(sourceAbsPath, rootDir, appDir, prodDir) {
1801
+ let rel = path8.relative(rootDir, sourceAbsPath).replace(/\\/g, "/");
1802
+ if (appDir !== "." && rel.startsWith(`${appDir}/`)) {
1803
+ rel = rel.slice(appDir.length + 1);
1804
+ }
1805
+ const prodRel = `${prodDir}/${rel.replace(/\.ts$/, ".js")}`;
1806
+ return path8.resolve(rootDir, prodRel);
1751
1807
  }
1752
- function findBaseConfig(rootDir) {
1753
- for (const f of BASE_CONFIG_FILES) {
1754
- if (fs7.existsSync(path8.join(rootDir, f))) {
1755
- return f.replace(/\.(ts|js)$/, "");
1808
+ async function findMergedMiddlewares(routeFilePath, rootDir, appDir, prodDir) {
1809
+ const routeDir = path8.dirname(routeFilePath);
1810
+ const resolvedRoot = path8.resolve(rootDir);
1811
+ const mwPaths = [];
1812
+ let currentDir = path8.resolve(rootDir, routeDir);
1813
+ while (true) {
1814
+ if (prodDir) {
1815
+ const mwPath = path8.join(currentDir, "middlewares.js");
1816
+ const absMwPath = path8.resolve(rootDir, mwPath);
1817
+ const prodAbsMwPath = toProdAbsPath(absMwPath, rootDir, appDir, prodDir);
1818
+ if (fs7.existsSync(prodAbsMwPath)) {
1819
+ mwPaths.push(prodAbsMwPath);
1820
+ }
1821
+ } else {
1822
+ for (const ext of [".ts", ".js"]) {
1823
+ const mwPath = path8.join(currentDir, `middlewares${ext}`);
1824
+ const absMwPath = path8.resolve(rootDir, mwPath);
1825
+ if (fs7.existsSync(absMwPath)) {
1826
+ mwPaths.push(absMwPath);
1827
+ break;
1828
+ }
1829
+ }
1756
1830
  }
1831
+ if (currentDir === resolvedRoot) break;
1832
+ const parentDir = path8.dirname(currentDir);
1833
+ if (parentDir === currentDir) break;
1834
+ currentDir = parentDir;
1757
1835
  }
1758
- return null;
1759
- }
1760
- function findEnvConfig(rootDir, env) {
1761
- for (const ext of ENV_CONFIG_EXTS) {
1762
- const f = `faapi.config.${env}${ext}`;
1763
- if (fs7.existsSync(path8.join(rootDir, f))) {
1764
- return `faapi.config.${env}`;
1836
+ if (mwPaths.length === 0) return void 0;
1837
+ mwPaths.reverse();
1838
+ const mergedMiddlewares = [];
1839
+ const mergedInjectors = {};
1840
+ for (const absMwPath of mwPaths) {
1841
+ let bundle = getCachedMiddlewares(absMwPath);
1842
+ if (bundle === void 0) {
1843
+ bundle = await loadMiddlewaresFile(absMwPath);
1844
+ setCachedMiddlewares(absMwPath, bundle);
1845
+ }
1846
+ mergedMiddlewares.push(...bundle.middlewares);
1847
+ for (const [name, injector] of Object.entries(bundle.injectors)) {
1848
+ mergedInjectors[name] = injector;
1765
1849
  }
1766
1850
  }
1767
- return null;
1851
+ if (mergedMiddlewares.length === 0 && Object.keys(mergedInjectors).length === 0) {
1852
+ return void 0;
1853
+ }
1854
+ return { middlewares: mergedMiddlewares, injectors: mergedInjectors };
1768
1855
  }
1769
- async function compileConfig(options) {
1770
- const { rootDir, outDir } = options;
1771
- const baseConfig = findBaseConfig(rootDir);
1772
- if (!baseConfig) {
1773
- return { generated: false, outputFile: "" };
1856
+ async function extractMethodsFromHandler(absPath) {
1857
+ try {
1858
+ const module = await importWithCacheBust(absPath);
1859
+ const methods = [];
1860
+ for (const key of Object.keys(module)) {
1861
+ if (isHttpMethod(key) && typeof module[key] === "function") {
1862
+ methods.push(key);
1863
+ }
1864
+ }
1865
+ return methods;
1866
+ } catch (err) {
1867
+ const reason = err instanceof Error ? err.message : String(err);
1868
+ console.warn(`[faapi] \u52A0\u8F7D\u8DEF\u7531\u6587\u4EF6\u5931\u8D25 ${absPath}: ${reason}`);
1869
+ return [];
1774
1870
  }
1775
- const env = getEnv();
1776
- const envConfig = findEnvConfig(rootDir, env);
1777
- const imports = [`import base from './${baseConfig}';`];
1778
- let exportDefault;
1779
- if (envConfig) {
1780
- imports.push(`import env from './${envConfig}';`);
1781
- exportDefault = "export default deepMerge(base, env);";
1782
- } else {
1783
- exportDefault = "export default base;";
1871
+ }
1872
+ async function hasWsExport(absPath) {
1873
+ try {
1874
+ const module = await importWithCacheBust(absPath);
1875
+ return typeof module["WS"] === "function";
1876
+ } catch (err) {
1877
+ const reason = err instanceof Error ? err.message : String(err);
1878
+ console.warn(`[faapi] \u52A0\u8F7D\u8DEF\u7531\u6587\u4EF6\u5931\u8D25\uFF08WS \u68C0\u6D4B\uFF09${absPath}: ${reason}`);
1879
+ return false;
1784
1880
  }
1785
- const entryCode = [...imports, DEEP_MERGE_SOURCE, exportDefault].join("\n");
1786
- const outputFile = path8.resolve(rootDir, outDir, "faapi-config.js");
1787
- await fs7.promises.mkdir(path8.dirname(outputFile), { recursive: true });
1788
- const esbuild = await import("esbuild");
1789
- await esbuild.build({
1790
- stdin: { contents: entryCode, resolveDir: rootDir, loader: "ts" },
1791
- outfile: outputFile,
1792
- bundle: true,
1793
- format: "esm",
1794
- platform: "node",
1795
- target: "node20",
1796
- sourcemap: true,
1797
- packages: "external",
1798
- logLevel: "silent"
1881
+ }
1882
+ async function scanRoutes(rootDir, patterns, appDir, prodDir) {
1883
+ const dir = appDir ?? ".";
1884
+ const files = await fg2(patterns, {
1885
+ cwd: rootDir,
1886
+ onlyFiles: true,
1887
+ absolute: false
1799
1888
  });
1800
- return { generated: true, outputFile };
1889
+ const routes = [];
1890
+ const wsRoutes = [];
1891
+ for (const file of files) {
1892
+ const normalizedFile = file.replace(/\\/g, "/");
1893
+ const fileName = normalizedFile.split("/").pop();
1894
+ if (fileName === "handler.ts" || fileName === "handler.js") {
1895
+ const absPath = path8.resolve(rootDir, normalizedFile);
1896
+ const importPath = prodDir ? toProdAbsPath(absPath, rootDir, dir, prodDir) : absPath;
1897
+ const urlPath = filePathToUrlPath(normalizedFile, dir);
1898
+ const paramNames = extractParamNames(urlPath);
1899
+ const isDynamic = paramNames.length > 0;
1900
+ const isCatchAll = normalizedFile.split("/").some(isCatchAllSegment);
1901
+ const middlewareBundle = await findMergedMiddlewares(normalizedFile, rootDir, dir, prodDir);
1902
+ const methods = await extractMethodsFromHandler(importPath);
1903
+ for (const method of methods) {
1904
+ routes.push({
1905
+ method,
1906
+ urlPath,
1907
+ filePath: normalizedFile,
1908
+ paramNames,
1909
+ isDynamic,
1910
+ isCatchAll: isCatchAll || void 0,
1911
+ middlewares: middlewareBundle?.middlewares,
1912
+ injectors: middlewareBundle?.injectors
1913
+ });
1914
+ }
1915
+ const hasWs = await hasWsExport(importPath);
1916
+ if (hasWs) {
1917
+ wsRoutes.push({
1918
+ urlPath,
1919
+ filePath: normalizedFile,
1920
+ paramNames,
1921
+ isDynamic,
1922
+ isCatchAll: isCatchAll || void 0,
1923
+ middlewares: middlewareBundle?.middlewares,
1924
+ injectors: middlewareBundle?.injectors
1925
+ });
1926
+ }
1927
+ continue;
1928
+ }
1929
+ }
1930
+ return { routes, wsRoutes };
1801
1931
  }
1802
- var BASE_CONFIG_FILES, ENV_CONFIG_EXTS;
1803
- var init_compileConfig = __esm({
1804
- "src/cli/compileConfig.ts"() {
1932
+ var init_scanRoutes = __esm({
1933
+ "src/router/scanRoutes.ts"() {
1805
1934
  "use strict";
1806
- init_deepMerge();
1807
- BASE_CONFIG_FILES = ["faapi.config.ts", "faapi.config.js"];
1808
- ENV_CONFIG_EXTS = [".ts", ".js"];
1935
+ init_constants();
1936
+ init_parseRouteFile();
1937
+ init_loadMiddlewares();
1938
+ init_importWithCacheBust();
1809
1939
  }
1810
1940
  });
1811
1941
 
1812
- // src/config/loadConfig.ts
1942
+ // src/router/sortRoutes.ts
1943
+ function sortRoutes(routes) {
1944
+ return [...routes].sort((a, b) => {
1945
+ if (a.isDynamic !== b.isDynamic) {
1946
+ return a.isDynamic ? 1 : -1;
1947
+ }
1948
+ if (a.isCatchAll !== b.isCatchAll) {
1949
+ return a.isCatchAll ? 1 : -1;
1950
+ }
1951
+ const aSegments = a.urlPath.split("/").filter(Boolean).length;
1952
+ const bSegments = b.urlPath.split("/").filter(Boolean).length;
1953
+ if (aSegments !== bSegments) {
1954
+ return aSegments - bSegments;
1955
+ }
1956
+ return a.urlPath.localeCompare(b.urlPath);
1957
+ });
1958
+ }
1959
+ var init_sortRoutes = __esm({
1960
+ "src/router/sortRoutes.ts"() {
1961
+ "use strict";
1962
+ }
1963
+ });
1964
+
1965
+ // src/config/loadConfig.ts
1813
1966
  import path9 from "path";
1814
1967
  import fs8 from "fs";
1815
1968
  async function loadConfig(rootDir, outDir) {
@@ -1835,162 +1988,6 @@ var init_loadConfig = __esm({
1835
1988
  }
1836
1989
  });
1837
1990
 
1838
- // src/cli/buildCommand.ts
1839
- var buildCommand_exports = {};
1840
- __export(buildCommand_exports, {
1841
- buildCommand: () => buildCommand
1842
- });
1843
- import path10 from "path";
1844
- import fs9 from "fs";
1845
- import fg2 from "fast-glob";
1846
- async function collectBundleEntries(rootDir, patterns, appDir) {
1847
- const entries = /* @__PURE__ */ new Set();
1848
- const handlerFiles = await fg2(patterns, {
1849
- cwd: rootDir,
1850
- onlyFiles: true,
1851
- absolute: true
1852
- });
1853
- for (const f of handlerFiles) {
1854
- if (f.endsWith("handler.ts")) entries.add(f);
1855
- }
1856
- const mwGlob = appDir === "." ? "**/middlewares.ts" : appDir + "/**/middlewares.ts";
1857
- const mwFiles = await fg2([mwGlob], {
1858
- cwd: rootDir,
1859
- onlyFiles: true,
1860
- absolute: true,
1861
- ignore: ["**/*.test.ts", "**/*.e2e.test.ts", "**/*.d.ts"]
1862
- });
1863
- for (const f of mwFiles) entries.add(f);
1864
- return Array.from(entries);
1865
- }
1866
- async function buildCommand(options) {
1867
- const rootDir = options?.rootDir ?? process.cwd();
1868
- const outdir = PROD_OUT_DIR;
1869
- await compileConfig({ rootDir, outDir: outdir });
1870
- const _config = await loadConfig(rootDir, outdir);
1871
- const appDir = process.env.FAAPI_APP_DIR ?? "src";
1872
- const patterns = appDir === "." ? ["api/**/*.ts"] : [`${appDir}/api/**/*.ts`];
1873
- console.log("faapi build started");
1874
- console.log(`- Root: ${rootDir}`);
1875
- console.log(`- AppDir: ${appDir}`);
1876
- console.log(`- Output: ${outdir}`);
1877
- console.log("\n[1/7] Collecting bundle entries...");
1878
- const entries = await collectBundleEntries(rootDir, patterns, appDir);
1879
- console.log(` ${entries.length} entry file(s)`);
1880
- if (entries.length === 0) {
1881
- console.warn(" ! No entry files found, nothing to build");
1882
- return;
1883
- }
1884
- console.log("\n[2/7] Compiling TypeScript (bundle mode)...");
1885
- const result = await compileBuildRoutes({
1886
- rootDir,
1887
- appDir,
1888
- outDir: outdir,
1889
- entries,
1890
- splitting: true,
1891
- define: { "process.env.NODE_ENV": JSON.stringify("production") },
1892
- minifySyntax: true,
1893
- logLevel: "silent"
1894
- });
1895
- console.log(` Compiled ${result.compiledFiles.length} entry file(s)`);
1896
- console.log("\n[3/7] Compiling config...");
1897
- const configResult = await compileConfig({ rootDir, outDir: outdir });
1898
- if (configResult.generated) {
1899
- console.log(` Written to ${configResult.outputFile}`);
1900
- } else {
1901
- console.log(" No config file found, skipped");
1902
- }
1903
- console.log("\n[4/7] Scanning routes...");
1904
- const { routes, wsRoutes } = await scanRoutes(rootDir, patterns, appDir, outdir);
1905
- const sorted = sortRoutes(routes);
1906
- console.log(` Found ${sorted.length} routes, ${wsRoutes.length} WS routes`);
1907
- const conflicts = detectRouteConflicts(sorted);
1908
- if (conflicts.length > 0) {
1909
- console.warn("! \u68C0\u6D4B\u5230\u8DEF\u7531\u51B2\u7A81\uFF1A");
1910
- for (const conflict of conflicts) {
1911
- console.warn(` ${conflict.method} ${conflict.urlPath}`);
1912
- for (const file of conflict.files) {
1913
- console.warn(` - ${file}`);
1914
- }
1915
- }
1916
- }
1917
- console.log("\n[5/7] Generating schema...");
1918
- await generateSchemaFiles(sorted, rootDir, appDir, outdir);
1919
- console.log(` Schema: zod.js files under ${path10.resolve(rootDir, outdir)}`);
1920
- console.log("\n[6/7] Generating routes manifest...");
1921
- const routesPath = path10.resolve(rootDir, outdir, "faapi-routes.js");
1922
- const serialized = serializeRoutes(sorted, wsRoutes, rootDir, appDir, outdir);
1923
- await writeRoutesModule(serialized, routesPath);
1924
- console.log(` Written to ${routesPath}`);
1925
- console.log("\n[7/7] Generating entry file...");
1926
- const mainPath = path10.resolve(rootDir, outdir, "main.js");
1927
- const mainContent = `// \u7531 faapi build \u81EA\u52A8\u751F\u6210\uFF0C\u8BF7\u52FF\u624B\u52A8\u7F16\u8F91
1928
- import { createProdApp } from '@faapi/faapi';
1929
-
1930
- const app = await createProdApp();
1931
- await app.listen();
1932
- `;
1933
- await fs9.promises.writeFile(mainPath, mainContent, "utf-8");
1934
- console.log(` Written to ${mainPath}`);
1935
- console.log("\nfaapi build completed");
1936
- }
1937
- var PROD_OUT_DIR;
1938
- var init_buildCommand = __esm({
1939
- "src/cli/buildCommand.ts"() {
1940
- "use strict";
1941
- init_scanRoutes();
1942
- init_sortRoutes();
1943
- init_detectRouteConflicts();
1944
- init_generateSchemaFiles();
1945
- init_generateRoutes();
1946
- init_compileBuildRoutes();
1947
- init_compileConfig();
1948
- init_loadConfig();
1949
- PROD_OUT_DIR = "dist";
1950
- }
1951
- });
1952
-
1953
- // src/cli/compileDevRoutes.ts
1954
- import path11 from "path";
1955
- import fs10 from "fs";
1956
- import fg3 from "fast-glob";
1957
- async function compileDevRoutes(options) {
1958
- const { rootDir, appDir, outDir, files, logLevel = "silent" } = options;
1959
- const entryPoints = files ?? await fg3([`${appDir}/**/*.ts`], {
1960
- cwd: rootDir,
1961
- onlyFiles: true,
1962
- absolute: true,
1963
- ignore: ["**/*.test.ts", "**/*.e2e.test.ts", "**/*.d.ts"]
1964
- });
1965
- if (entryPoints.length === 0) {
1966
- return { compiledFiles: [] };
1967
- }
1968
- const absOutDir = path11.resolve(rootDir, outDir);
1969
- await fs10.promises.mkdir(absOutDir, { recursive: true });
1970
- const plugins = buildAliasPlugins(rootDir);
1971
- const esbuild = await import("esbuild");
1972
- const outbase = appDir === "." ? rootDir : path11.resolve(rootDir, appDir);
1973
- await esbuild.build({
1974
- entryPoints,
1975
- outdir: absOutDir,
1976
- outbase,
1977
- bundle: false,
1978
- platform: "node",
1979
- format: "esm",
1980
- sourcemap: true,
1981
- packages: "external",
1982
- plugins,
1983
- logLevel
1984
- });
1985
- return { compiledFiles: entryPoints };
1986
- }
1987
- var init_compileDevRoutes = __esm({
1988
- "src/cli/compileDevRoutes.ts"() {
1989
- "use strict";
1990
- init_aliasPlugin();
1991
- }
1992
- });
1993
-
1994
1991
  // ../../node_modules/.pnpm/readdirp@4.1.2/node_modules/readdirp/esm/index.js
1995
1992
  import { stat, lstat, readdir, realpath } from "fs/promises";
1996
1993
  import { Readable } from "stream";
@@ -2542,10 +2539,10 @@ var init_handler = __esm({
2542
2539
  fn(val);
2543
2540
  }
2544
2541
  };
2545
- addAndConvert = (main2, prop, item) => {
2546
- let container = main2[prop];
2542
+ addAndConvert = (main, prop, item) => {
2543
+ let container = main[prop];
2547
2544
  if (!(container instanceof Set)) {
2548
- main2[prop] = container = /* @__PURE__ */ new Set([container]);
2545
+ main[prop] = container = /* @__PURE__ */ new Set([container]);
2549
2546
  }
2550
2547
  container.add(item);
2551
2548
  };
@@ -2557,12 +2554,12 @@ var init_handler = __esm({
2557
2554
  delete cont[key];
2558
2555
  }
2559
2556
  };
2560
- delFromSet = (main2, prop, item) => {
2561
- const container = main2[prop];
2557
+ delFromSet = (main, prop, item) => {
2558
+ const container = main[prop];
2562
2559
  if (container instanceof Set) {
2563
2560
  container.delete(item);
2564
2561
  } else if (container === item) {
2565
- delete main2[prop];
2562
+ delete main[prop];
2566
2563
  }
2567
2564
  };
2568
2565
  isEmptySet = (val) => val instanceof Set ? val.size === 0 : !val;
@@ -2981,7 +2978,7 @@ var init_handler = __esm({
2981
2978
  // ../../node_modules/.pnpm/chokidar@4.0.3/node_modules/chokidar/esm/index.js
2982
2979
  import { stat as statcb } from "fs";
2983
2980
  import { stat as stat3, readdir as readdir2 } from "fs/promises";
2984
- import { EventEmitter } from "events";
2981
+ import { EventEmitter as EventEmitter2 } from "events";
2985
2982
  import * as sysPath2 from "path";
2986
2983
  function arrify(item) {
2987
2984
  return Array.isArray(item) ? item : [item];
@@ -3184,7 +3181,7 @@ var init_esm2 = __esm({
3184
3181
  return this.fsw._isntIgnored(this.entryPath(entry), entry.stats);
3185
3182
  }
3186
3183
  };
3187
- FSWatcher = class extends EventEmitter {
3184
+ FSWatcher = class extends EventEmitter2 {
3188
3185
  // Not indenting methods for history sake; for now.
3189
3186
  constructor(_opts = {}) {
3190
3187
  super();
@@ -3701,7 +3698,7 @@ var init_esm2 = __esm({
3701
3698
  });
3702
3699
 
3703
3700
  // src/cli/watcher.ts
3704
- import path12 from "path";
3701
+ import path10 from "path";
3705
3702
  function startWatcher(options) {
3706
3703
  const { rootDir, appDir, app } = options;
3707
3704
  let rebuildTimer = null;
@@ -3755,11 +3752,11 @@ function startWatcher(options) {
3755
3752
  }
3756
3753
  });
3757
3754
  watcher.on("add", (file) => {
3758
- pendingFiles.add(path12.resolve(rootDir, file));
3755
+ pendingFiles.add(path10.resolve(rootDir, file));
3759
3756
  scheduleRebuild();
3760
3757
  });
3761
3758
  watcher.on("change", (file) => {
3762
- pendingFiles.add(path12.resolve(rootDir, file));
3759
+ pendingFiles.add(path10.resolve(rootDir, file));
3763
3760
  scheduleRebuild();
3764
3761
  });
3765
3762
  watcher.on("unlink", () => {
@@ -3787,6 +3784,36 @@ var init_watcher = __esm({
3787
3784
  }
3788
3785
  });
3789
3786
 
3787
+ // src/router/detectRouteConflicts.ts
3788
+ function detectRouteConflicts(routes) {
3789
+ const map = /* @__PURE__ */ new Map();
3790
+ for (const route of routes) {
3791
+ const key = `${route.method} ${route.urlPath}`;
3792
+ const existing = map.get(key);
3793
+ if (existing) {
3794
+ existing.files.push(route.filePath);
3795
+ } else {
3796
+ map.set(key, {
3797
+ method: route.method,
3798
+ urlPath: route.urlPath,
3799
+ files: [route.filePath]
3800
+ });
3801
+ }
3802
+ }
3803
+ const conflicts = [];
3804
+ for (const conflict of map.values()) {
3805
+ if (conflict.files.length > 1) {
3806
+ conflicts.push(conflict);
3807
+ }
3808
+ }
3809
+ return conflicts;
3810
+ }
3811
+ var init_detectRouteConflicts = __esm({
3812
+ "src/router/detectRouteConflicts.ts"() {
3813
+ "use strict";
3814
+ }
3815
+ });
3816
+
3790
3817
  // src/router/matchRoute.ts
3791
3818
  function matchRoute(routes, method, path17) {
3792
3819
  for (const route of routes) {
@@ -4335,10 +4362,6 @@ var init_isPlainObject = __esm({
4335
4362
  });
4336
4363
 
4337
4364
  // src/response/toResponse.ts
4338
- var toResponse_exports = {};
4339
- __export(toResponse_exports, {
4340
- toResponse: () => toResponse
4341
- });
4342
4365
  async function toResponse(value, meta) {
4343
4366
  if (value instanceof Promise) {
4344
4367
  return toResponse(await value, meta);
@@ -4672,16 +4695,13 @@ function mapZodCode(zodCode, message) {
4672
4695
  return "TYPE_MISMATCH";
4673
4696
  case "unrecognized_keys":
4674
4697
  return "INVALID_FORMAT";
4675
- case "invalid_enum_value":
4698
+ case "invalid_value":
4676
4699
  case "invalid_string":
4677
- case "invalid_date":
4678
4700
  case "too_small":
4679
4701
  case "too_big":
4680
4702
  case "invalid_intersection_types":
4681
4703
  case "not_multiple_of":
4682
4704
  return "INVALID_VALUE";
4683
- case "not_finite":
4684
- return "COERCE_FAILED";
4685
4705
  case "custom":
4686
4706
  return "INVALID_VALUE";
4687
4707
  default:
@@ -4717,7 +4737,7 @@ function getClientIp(req) {
4717
4737
  const first = xff.split(",")[0]?.trim();
4718
4738
  if (first) return first;
4719
4739
  }
4720
- const remote = req.socket.remoteAddress;
4740
+ const remote = req.socket?.remoteAddress;
4721
4741
  if (remote) {
4722
4742
  if (remote.startsWith("::ffff:")) {
4723
4743
  return remote.slice(7);
@@ -4937,14 +4957,7 @@ function nodeHttpToWebHeaders(req) {
4937
4957
  }
4938
4958
  return headers;
4939
4959
  }
4940
- function buildErrorResponse(err, ctx, errorFormat) {
4941
- if (errorFormat) {
4942
- try {
4943
- const res = errorFormat(err, ctx);
4944
- if (res) return res;
4945
- } catch {
4946
- }
4947
- }
4960
+ function buildErrorResponse(err) {
4948
4961
  try {
4949
4962
  return formatErrorResponse(err);
4950
4963
  } catch {
@@ -4987,7 +5000,7 @@ var init_wsHandler = __esm({
4987
5000
 
4988
5001
  // src/server/handleWsUpgrade.ts
4989
5002
  import { WebSocketServer, WebSocket } from "ws";
4990
- import path13 from "path";
5003
+ import path11 from "path";
4991
5004
  function getPathname(req) {
4992
5005
  const url = req.url ?? "/";
4993
5006
  const idx = url.indexOf("?");
@@ -5047,7 +5060,7 @@ async function sendResponseToSocket(socket, response) {
5047
5060
  socket.destroy();
5048
5061
  }
5049
5062
  function attachWebSocket(options) {
5050
- const { server, routesRef, rootDir, config, errorFormat, globalMiddlewares } = options;
5063
+ const { server, routesRef, rootDir, config, globalMiddlewares } = options;
5051
5064
  const wss = new WebSocketServer({ noServer: true });
5052
5065
  server.on("upgrade", async (req, socket, head) => {
5053
5066
  const currentWsRoutes = routesRef.wsCurrent;
@@ -5069,7 +5082,7 @@ function attachWebSocket(options) {
5069
5082
  const finalHandler = async () => {
5070
5083
  let handlers;
5071
5084
  try {
5072
- const absoluteFilePath = path13.resolve(rootDir, route.filePath);
5085
+ const absoluteFilePath = path11.resolve(rootDir, route.filePath);
5073
5086
  handlers = await loadWsHandler(absoluteFilePath, ctx);
5074
5087
  } catch (err) {
5075
5088
  const reason = err instanceof Error ? err.message : String(err);
@@ -5104,7 +5117,7 @@ function attachWebSocket(options) {
5104
5117
  console.error("[faapi] WS \u63E1\u624B\u540E\u4E2D\u95F4\u4EF6\u629B\u9519:", err);
5105
5118
  return;
5106
5119
  }
5107
- response = buildErrorResponse(err, ctx, errorFormat);
5120
+ response = buildErrorResponse(err);
5108
5121
  }
5109
5122
  if (upgraded) {
5110
5123
  return;
@@ -5133,7 +5146,7 @@ import {
5133
5146
  import { createSecureServer as createHttp2SecureServer } from "http2";
5134
5147
  import { readFileSync } from "fs";
5135
5148
  import { Readable as Readable3 } from "stream";
5136
- import path14 from "path";
5149
+ import path12 from "path";
5137
5150
  function toWebRequest(req, bodyLimit = DEFAULT_BODY_LIMIT) {
5138
5151
  const forwardedProto = req.headers["x-forwarded-proto"];
5139
5152
  const protocol = Array.isArray(forwardedProto) ? forwardedProto[0]?.split(",")[0]?.trim() ?? "http" : forwardedProto?.split(",")[0]?.trim() ?? "http";
@@ -5200,8 +5213,6 @@ function createServer(options) {
5200
5213
  appDir,
5201
5214
  outDir,
5202
5215
  cors: corsOption,
5203
- responseFormat,
5204
- errorFormat,
5205
5216
  onError,
5206
5217
  config,
5207
5218
  wsRoutes,
@@ -5240,8 +5251,6 @@ function createServer(options) {
5240
5251
  req,
5241
5252
  res,
5242
5253
  configMiddlewares,
5243
- responseFormat,
5244
- errorFormat,
5245
5254
  onError,
5246
5255
  config,
5247
5256
  globalMiddlewares,
@@ -5253,11 +5262,11 @@ function createServer(options) {
5253
5262
  });
5254
5263
  });
5255
5264
  if (routesRef.wsCurrent.length > 0) {
5256
- attachWebSocket({ server, routesRef, rootDir, config, errorFormat, globalMiddlewares });
5265
+ attachWebSocket({ server, routesRef, rootDir, config, globalMiddlewares });
5257
5266
  }
5258
5267
  return { server, routesRef };
5259
5268
  }
5260
- async function handleRequest(routes, rootDir, appDir, outDir, req, res, configMiddlewares, responseFormat, errorFormat, onError, config, globalMiddlewares, globalInjectors, bodyLimit) {
5269
+ async function handleRequest(routes, rootDir, appDir, outDir, req, res, configMiddlewares, onError, config, globalMiddlewares, globalInjectors, bodyLimit) {
5261
5270
  const request = toWebRequest(req, bodyLimit);
5262
5271
  const method = request.method.toUpperCase();
5263
5272
  const urlPath = new URL(request.url).pathname;
@@ -5274,7 +5283,7 @@ async function handleRequest(routes, rootDir, appDir, outDir, req, res, configMi
5274
5283
  }
5275
5284
  ctx.params = match.params;
5276
5285
  const { route } = match;
5277
- const absoluteFilePath = path14.resolve(rootDir, route.filePath);
5286
+ const absoluteFilePath = path12.resolve(rootDir, route.filePath);
5278
5287
  const routeModule = await loadRouteModule(absoluteFilePath, route.method);
5279
5288
  const input = await resolveInput(route.method, request);
5280
5289
  const inputType = getInputTypeForMethod(route.method);
@@ -5285,22 +5294,13 @@ async function handleRequest(routes, rootDir, appDir, outDir, req, res, configMi
5285
5294
  }
5286
5295
  const body = hasBody(route.method) ? result.data : void 0;
5287
5296
  const mergedInjectors = globalInjectors ? { ...globalInjectors, ...route.injectors } : route.injectors;
5288
- let response = await invokeHandler(
5297
+ const response = await invokeHandler(
5289
5298
  routeModule.handler,
5290
5299
  ctx,
5291
5300
  body,
5292
5301
  route.middlewares,
5293
5302
  mergedInjectors
5294
5303
  );
5295
- if (responseFormat && response.status >= 200 && response.status < 300) {
5296
- const contentType = response.headers.get("Content-Type") ?? "";
5297
- if (contentType.includes("application/json")) {
5298
- const data = await response.json();
5299
- const formatted = responseFormat(data, ctx);
5300
- const { toResponse: toResponse2 } = await Promise.resolve().then(() => (init_toResponse(), toResponse_exports));
5301
- response = await toResponse2(formatted, meta);
5302
- }
5303
- }
5304
5304
  return response;
5305
5305
  };
5306
5306
  try {
@@ -5317,7 +5317,7 @@ async function handleRequest(routes, rootDir, appDir, outDir, req, res, configMi
5317
5317
  }
5318
5318
  await sendNodeResponse(response, res);
5319
5319
  } catch (err) {
5320
- const errorResponse = buildErrorResponse(err, ctx, errorFormat);
5320
+ const errorResponse = buildErrorResponse(err);
5321
5321
  await sendNodeResponse(mergeMeta(errorResponse, meta), res);
5322
5322
  if (onError) {
5323
5323
  try {
@@ -5448,8 +5448,8 @@ var init_loadPlugins = __esm({
5448
5448
  });
5449
5449
 
5450
5450
  // src/cli/createAppCore.ts
5451
- import fs11 from "fs";
5452
- import path15 from "path";
5451
+ import fs9 from "fs";
5452
+ import path13 from "path";
5453
5453
  import { PassThrough } from "stream";
5454
5454
  function isFaapiConfigKey(key) {
5455
5455
  return FAAPI_CONFIG_KEYS.has(key);
@@ -5457,8 +5457,8 @@ function isFaapiConfigKey(key) {
5457
5457
  async function createAppBase(options) {
5458
5458
  const rootDir = options?.rootDir ?? process.cwd();
5459
5459
  const outDir = process.env.FAAPI_OUT_DIR ?? DEFAULT_OUT_DIR;
5460
- const routesPath = path15.resolve(rootDir, outDir, ROUTES_FILE);
5461
- if (!fs11.existsSync(routesPath)) {
5460
+ const routesPath = path13.resolve(rootDir, outDir, ROUTES_FILE);
5461
+ if (!fs9.existsSync(routesPath)) {
5462
5462
  throw new Error(
5463
5463
  `[faapi] ${outDir}/${ROUTES_FILE} \u4E0D\u5B58\u5728\uFF0C\u8BF7\u5148\u6267\u884C \`faapi build\`\uFF08\u6216 \`faapi dev\`\uFF09\u751F\u6210\u4EA7\u7269\u3002`
5464
5464
  );
@@ -5486,8 +5486,6 @@ async function createAppBase(options) {
5486
5486
  appDir,
5487
5487
  outDir,
5488
5488
  cors: config?.cors ?? true,
5489
- responseFormat: config?.responseFormat,
5490
- errorFormat: config?.errorFormat,
5491
5489
  onError: config?.lifecycle?.onError,
5492
5490
  config: config ?? void 0,
5493
5491
  wsRoutes,
@@ -5500,6 +5498,7 @@ async function createAppBase(options) {
5500
5498
  const { handlerWrappers, upgradeWrappers } = await loadPlugins(config?.plugins, {
5501
5499
  rootDir,
5502
5500
  routes: sorted,
5501
+ getRoutes: () => sorted,
5503
5502
  server,
5504
5503
  config: pluginConfig
5505
5504
  });
@@ -5565,6 +5564,11 @@ async function createAppBase(options) {
5565
5564
  setHeader(name, value) {
5566
5565
  this._headers[name.toLowerCase()] = value;
5567
5566
  },
5567
+ appendHeader(name, value) {
5568
+ const key = name.toLowerCase();
5569
+ const existing = this._headers[key];
5570
+ this._headers[key] = existing ? `${existing}, ${value}` : value;
5571
+ },
5568
5572
  writeHead(status, headers) {
5569
5573
  this.statusCode = status;
5570
5574
  if (headers) {
@@ -5606,6 +5610,7 @@ async function createAppBase(options) {
5606
5610
  host: "localhost",
5607
5611
  "content-type": body !== void 0 ? "application/json" : void 0
5608
5612
  };
5613
+ mockReq.socket = { remoteAddress: "127.0.0.1" };
5609
5614
  if (body !== void 0) {
5610
5615
  mockReq.push(JSON.stringify(body));
5611
5616
  }
@@ -5618,8 +5623,13 @@ async function createAppBase(options) {
5618
5623
  async close() {
5619
5624
  if (closed) return;
5620
5625
  closed = true;
5621
- server.closeIdleConnections?.();
5622
- server.closeAllConnections?.();
5626
+ const s = server;
5627
+ if (typeof s.closeIdleConnections === "function") {
5628
+ s.closeIdleConnections();
5629
+ }
5630
+ if (typeof s.closeAllConnections === "function") {
5631
+ s.closeAllConnections();
5632
+ }
5623
5633
  if (config?.lifecycle?.onClose) {
5624
5634
  await config.lifecycle.onClose({ rootDir, routes: sorted, server });
5625
5635
  }
@@ -5669,8 +5679,6 @@ var init_createAppCore = __esm({
5669
5679
  ROUTES_FILE = "faapi-routes.js";
5670
5680
  FAAPI_CONFIG_KEYS = /* @__PURE__ */ new Set([
5671
5681
  "cors",
5672
- "responseFormat",
5673
- "errorFormat",
5674
5682
  "lifecycle",
5675
5683
  "middlewares",
5676
5684
  "injectors",
@@ -5720,7 +5728,7 @@ __export(devCommand_exports, {
5720
5728
  devCommand: () => devCommand,
5721
5729
  generateRouteArtifacts: () => generateRouteArtifacts
5722
5730
  });
5723
- import path16 from "path";
5731
+ import path14 from "path";
5724
5732
  async function devCommand() {
5725
5733
  const rootDir = process.cwd();
5726
5734
  process.env.FAAPI_OUT_DIR = DEV_OUT_DIR2;
@@ -5743,7 +5751,7 @@ async function devCommand() {
5743
5751
  async function generateRouteArtifacts(rootDir, appDir, patterns) {
5744
5752
  const { routes, wsRoutes } = await scanRoutes(rootDir, patterns, appDir, DEV_OUT_DIR2);
5745
5753
  const sorted = sortRoutes(routes);
5746
- const routesPath = path16.resolve(rootDir, DEV_OUT_DIR2, ROUTES_FILE2);
5754
+ const routesPath = path14.resolve(rootDir, DEV_OUT_DIR2, ROUTES_FILE2);
5747
5755
  const serialized = serializeRoutes(sorted, wsRoutes, rootDir, appDir, DEV_OUT_DIR2);
5748
5756
  await writeRoutesModule(serialized, routesPath);
5749
5757
  await generateSchemaFiles(sorted, rootDir, appDir, DEV_OUT_DIR2);
@@ -5766,22 +5774,764 @@ var init_devCommand = __esm({
5766
5774
  }
5767
5775
  });
5768
5776
 
5769
- // src/cli/index.ts
5770
- async function main() {
5771
- const argv = process.argv.slice(2);
5772
- const firstArg = argv[0];
5773
- if (firstArg === "build") {
5774
- const { buildCommand: buildCommand2 } = await Promise.resolve().then(() => (init_buildCommand(), buildCommand_exports));
5775
- await buildCommand2();
5777
+ // src/cli/compileBuildRoutes.ts
5778
+ import path15 from "path";
5779
+ import fs10 from "fs";
5780
+ async function compileBuildRoutes(options) {
5781
+ const {
5782
+ rootDir,
5783
+ appDir,
5784
+ outDir,
5785
+ entries,
5786
+ splitting = true,
5787
+ define,
5788
+ minifySyntax = true,
5789
+ logLevel = "silent"
5790
+ } = options;
5791
+ if (entries.length === 0) {
5792
+ return { compiledFiles: [] };
5793
+ }
5794
+ const absOutDir = path15.resolve(rootDir, outDir);
5795
+ await fs10.promises.mkdir(absOutDir, { recursive: true });
5796
+ const plugins = buildAliasPlugins(rootDir);
5797
+ const esbuild = await import("esbuild");
5798
+ const outbase = appDir === "." ? rootDir : path15.resolve(rootDir, appDir);
5799
+ await esbuild.build({
5800
+ entryPoints: entries,
5801
+ outdir: absOutDir,
5802
+ outbase,
5803
+ bundle: true,
5804
+ splitting,
5805
+ platform: "node",
5806
+ format: "esm",
5807
+ sourcemap: true,
5808
+ packages: "external",
5809
+ plugins,
5810
+ define,
5811
+ minifySyntax,
5812
+ logLevel
5813
+ });
5814
+ return { compiledFiles: entries };
5815
+ }
5816
+ var init_compileBuildRoutes = __esm({
5817
+ "src/cli/compileBuildRoutes.ts"() {
5818
+ "use strict";
5819
+ init_aliasPlugin();
5820
+ }
5821
+ });
5822
+
5823
+ // src/cli/buildCommand.ts
5824
+ var buildCommand_exports = {};
5825
+ __export(buildCommand_exports, {
5826
+ buildCommand: () => buildCommand
5827
+ });
5828
+ import path16 from "path";
5829
+ import fs11 from "fs";
5830
+ import fg3 from "fast-glob";
5831
+ async function collectBundleEntries(rootDir, patterns, appDir) {
5832
+ const entries = /* @__PURE__ */ new Set();
5833
+ const handlerFiles = await fg3(patterns, {
5834
+ cwd: rootDir,
5835
+ onlyFiles: true,
5836
+ absolute: true
5837
+ });
5838
+ for (const f of handlerFiles) {
5839
+ if (f.endsWith("handler.ts")) entries.add(f);
5840
+ }
5841
+ const mwGlob = appDir === "." ? "**/middlewares.ts" : appDir + "/**/middlewares.ts";
5842
+ const mwFiles = await fg3([mwGlob], {
5843
+ cwd: rootDir,
5844
+ onlyFiles: true,
5845
+ absolute: true,
5846
+ ignore: ["**/*.test.ts", "**/*.e2e.test.ts", "**/*.d.ts"]
5847
+ });
5848
+ for (const f of mwFiles) entries.add(f);
5849
+ return Array.from(entries);
5850
+ }
5851
+ async function buildCommand(options) {
5852
+ const rootDir = options?.rootDir ?? process.cwd();
5853
+ const outdir = PROD_OUT_DIR;
5854
+ await compileConfig({ rootDir, outDir: outdir });
5855
+ const _config = await loadConfig(rootDir, outdir);
5856
+ const appDir = process.env.FAAPI_APP_DIR ?? "src";
5857
+ const patterns = appDir === "." ? ["api/**/*.ts"] : [`${appDir}/api/**/*.ts`];
5858
+ console.log("faapi build started");
5859
+ console.log(`- Root: ${rootDir}`);
5860
+ console.log(`- AppDir: ${appDir}`);
5861
+ console.log(`- Output: ${outdir}`);
5862
+ console.log("\n[1/7] Collecting bundle entries...");
5863
+ const entries = await collectBundleEntries(rootDir, patterns, appDir);
5864
+ console.log(` ${entries.length} entry file(s)`);
5865
+ if (entries.length === 0) {
5866
+ console.warn(" ! No entry files found, nothing to build");
5776
5867
  return;
5777
5868
  }
5869
+ console.log("\n[2/7] Compiling TypeScript (bundle mode)...");
5870
+ const result = await compileBuildRoutes({
5871
+ rootDir,
5872
+ appDir,
5873
+ outDir: outdir,
5874
+ entries,
5875
+ splitting: true,
5876
+ define: { "process.env.NODE_ENV": JSON.stringify("production") },
5877
+ minifySyntax: true,
5878
+ logLevel: "silent"
5879
+ });
5880
+ console.log(` Compiled ${result.compiledFiles.length} entry file(s)`);
5881
+ console.log("\n[3/7] Compiling config...");
5882
+ const configResult = await compileConfig({ rootDir, outDir: outdir });
5883
+ if (configResult.generated) {
5884
+ console.log(` Written to ${configResult.outputFile}`);
5885
+ } else {
5886
+ console.log(" No config file found, skipped");
5887
+ }
5888
+ console.log("\n[4/7] Scanning routes...");
5889
+ const { routes, wsRoutes } = await scanRoutes(rootDir, patterns, appDir, outdir);
5890
+ const sorted = sortRoutes(routes);
5891
+ console.log(` Found ${sorted.length} routes, ${wsRoutes.length} WS routes`);
5892
+ const conflicts = detectRouteConflicts(sorted);
5893
+ if (conflicts.length > 0) {
5894
+ console.warn("! \u68C0\u6D4B\u5230\u8DEF\u7531\u51B2\u7A81\uFF1A");
5895
+ for (const conflict of conflicts) {
5896
+ console.warn(` ${conflict.method} ${conflict.urlPath}`);
5897
+ for (const file of conflict.files) {
5898
+ console.warn(` - ${file}`);
5899
+ }
5900
+ }
5901
+ }
5902
+ console.log("\n[5/7] Generating schema...");
5903
+ await generateSchemaFiles(sorted, rootDir, appDir, outdir);
5904
+ console.log(` Schema: zod.js files under ${path16.resolve(rootDir, outdir)}`);
5905
+ console.log("\n[6/7] Generating routes manifest...");
5906
+ const routesPath = path16.resolve(rootDir, outdir, "faapi-routes.js");
5907
+ const serialized = serializeRoutes(sorted, wsRoutes, rootDir, appDir, outdir);
5908
+ await writeRoutesModule(serialized, routesPath);
5909
+ console.log(` Written to ${routesPath}`);
5910
+ console.log("\n[7/7] Generating entry file...");
5911
+ const mainPath = path16.resolve(rootDir, outdir, "main.js");
5912
+ const mainContent = `// \u7531 faapi build \u81EA\u52A8\u751F\u6210\uFF0C\u8BF7\u52FF\u624B\u52A8\u7F16\u8F91
5913
+ import { createProdApp } from '@faapi/faapi';
5914
+
5915
+ const app = await createProdApp();
5916
+ await app.listen();
5917
+ `;
5918
+ await fs11.promises.writeFile(mainPath, mainContent, "utf-8");
5919
+ console.log(` Written to ${mainPath}`);
5920
+ console.log("\nfaapi build completed");
5921
+ }
5922
+ var PROD_OUT_DIR;
5923
+ var init_buildCommand = __esm({
5924
+ "src/cli/buildCommand.ts"() {
5925
+ "use strict";
5926
+ init_scanRoutes();
5927
+ init_sortRoutes();
5928
+ init_detectRouteConflicts();
5929
+ init_generateSchemaFiles();
5930
+ init_generateRoutes();
5931
+ init_compileBuildRoutes();
5932
+ init_compileConfig();
5933
+ init_loadConfig();
5934
+ PROD_OUT_DIR = "dist";
5935
+ }
5936
+ });
5937
+
5938
+ // ../../node_modules/.pnpm/cac@6.7.14/node_modules/cac/dist/index.mjs
5939
+ import { EventEmitter } from "events";
5940
+ function toArr(any) {
5941
+ return any == null ? [] : Array.isArray(any) ? any : [any];
5942
+ }
5943
+ function toVal(out, key, val, opts) {
5944
+ var x, old = out[key], nxt = !!~opts.string.indexOf(key) ? val == null || val === true ? "" : String(val) : typeof val === "boolean" ? val : !!~opts.boolean.indexOf(key) ? val === "false" ? false : val === "true" || (out._.push((x = +val, x * 0 === 0) ? x : val), !!val) : (x = +val, x * 0 === 0) ? x : val;
5945
+ out[key] = old == null ? nxt : Array.isArray(old) ? old.concat(nxt) : [old, nxt];
5946
+ }
5947
+ function mri2(args, opts) {
5948
+ args = args || [];
5949
+ opts = opts || {};
5950
+ var k, arr, arg, name, val, out = { _: [] };
5951
+ var i = 0, j = 0, idx = 0, len = args.length;
5952
+ const alibi = opts.alias !== void 0;
5953
+ const strict = opts.unknown !== void 0;
5954
+ const defaults = opts.default !== void 0;
5955
+ opts.alias = opts.alias || {};
5956
+ opts.string = toArr(opts.string);
5957
+ opts.boolean = toArr(opts.boolean);
5958
+ if (alibi) {
5959
+ for (k in opts.alias) {
5960
+ arr = opts.alias[k] = toArr(opts.alias[k]);
5961
+ for (i = 0; i < arr.length; i++) {
5962
+ (opts.alias[arr[i]] = arr.concat(k)).splice(i, 1);
5963
+ }
5964
+ }
5965
+ }
5966
+ for (i = opts.boolean.length; i-- > 0; ) {
5967
+ arr = opts.alias[opts.boolean[i]] || [];
5968
+ for (j = arr.length; j-- > 0; ) opts.boolean.push(arr[j]);
5969
+ }
5970
+ for (i = opts.string.length; i-- > 0; ) {
5971
+ arr = opts.alias[opts.string[i]] || [];
5972
+ for (j = arr.length; j-- > 0; ) opts.string.push(arr[j]);
5973
+ }
5974
+ if (defaults) {
5975
+ for (k in opts.default) {
5976
+ name = typeof opts.default[k];
5977
+ arr = opts.alias[k] = opts.alias[k] || [];
5978
+ if (opts[name] !== void 0) {
5979
+ opts[name].push(k);
5980
+ for (i = 0; i < arr.length; i++) {
5981
+ opts[name].push(arr[i]);
5982
+ }
5983
+ }
5984
+ }
5985
+ }
5986
+ const keys = strict ? Object.keys(opts.alias) : [];
5987
+ for (i = 0; i < len; i++) {
5988
+ arg = args[i];
5989
+ if (arg === "--") {
5990
+ out._ = out._.concat(args.slice(++i));
5991
+ break;
5992
+ }
5993
+ for (j = 0; j < arg.length; j++) {
5994
+ if (arg.charCodeAt(j) !== 45) break;
5995
+ }
5996
+ if (j === 0) {
5997
+ out._.push(arg);
5998
+ } else if (arg.substring(j, j + 3) === "no-") {
5999
+ name = arg.substring(j + 3);
6000
+ if (strict && !~keys.indexOf(name)) {
6001
+ return opts.unknown(arg);
6002
+ }
6003
+ out[name] = false;
6004
+ } else {
6005
+ for (idx = j + 1; idx < arg.length; idx++) {
6006
+ if (arg.charCodeAt(idx) === 61) break;
6007
+ }
6008
+ name = arg.substring(j, idx);
6009
+ val = arg.substring(++idx) || (i + 1 === len || ("" + args[i + 1]).charCodeAt(0) === 45 || args[++i]);
6010
+ arr = j === 2 ? [name] : name;
6011
+ for (idx = 0; idx < arr.length; idx++) {
6012
+ name = arr[idx];
6013
+ if (strict && !~keys.indexOf(name)) return opts.unknown("-".repeat(j) + name);
6014
+ toVal(out, name, idx + 1 < arr.length || val, opts);
6015
+ }
6016
+ }
6017
+ }
6018
+ if (defaults) {
6019
+ for (k in opts.default) {
6020
+ if (out[k] === void 0) {
6021
+ out[k] = opts.default[k];
6022
+ }
6023
+ }
6024
+ }
6025
+ if (alibi) {
6026
+ for (k in out) {
6027
+ arr = opts.alias[k] || [];
6028
+ while (arr.length > 0) {
6029
+ out[arr.shift()] = out[k];
6030
+ }
6031
+ }
6032
+ }
6033
+ return out;
6034
+ }
6035
+ var removeBrackets = (v) => v.replace(/[<[].+/, "").trim();
6036
+ var findAllBrackets = (v) => {
6037
+ const ANGLED_BRACKET_RE_GLOBAL = /<([^>]+)>/g;
6038
+ const SQUARE_BRACKET_RE_GLOBAL = /\[([^\]]+)\]/g;
6039
+ const res = [];
6040
+ const parse = (match) => {
6041
+ let variadic = false;
6042
+ let value = match[1];
6043
+ if (value.startsWith("...")) {
6044
+ value = value.slice(3);
6045
+ variadic = true;
6046
+ }
6047
+ return {
6048
+ required: match[0].startsWith("<"),
6049
+ value,
6050
+ variadic
6051
+ };
6052
+ };
6053
+ let angledMatch;
6054
+ while (angledMatch = ANGLED_BRACKET_RE_GLOBAL.exec(v)) {
6055
+ res.push(parse(angledMatch));
6056
+ }
6057
+ let squareMatch;
6058
+ while (squareMatch = SQUARE_BRACKET_RE_GLOBAL.exec(v)) {
6059
+ res.push(parse(squareMatch));
6060
+ }
6061
+ return res;
6062
+ };
6063
+ var getMriOptions = (options) => {
6064
+ const result = { alias: {}, boolean: [] };
6065
+ for (const [index, option] of options.entries()) {
6066
+ if (option.names.length > 1) {
6067
+ result.alias[option.names[0]] = option.names.slice(1);
6068
+ }
6069
+ if (option.isBoolean) {
6070
+ if (option.negated) {
6071
+ const hasStringTypeOption = options.some((o, i) => {
6072
+ return i !== index && o.names.some((name) => option.names.includes(name)) && typeof o.required === "boolean";
6073
+ });
6074
+ if (!hasStringTypeOption) {
6075
+ result.boolean.push(option.names[0]);
6076
+ }
6077
+ } else {
6078
+ result.boolean.push(option.names[0]);
6079
+ }
6080
+ }
6081
+ }
6082
+ return result;
6083
+ };
6084
+ var findLongest = (arr) => {
6085
+ return arr.sort((a, b) => {
6086
+ return a.length > b.length ? -1 : 1;
6087
+ })[0];
6088
+ };
6089
+ var padRight = (str, length) => {
6090
+ return str.length >= length ? str : `${str}${" ".repeat(length - str.length)}`;
6091
+ };
6092
+ var camelcase = (input) => {
6093
+ return input.replace(/([a-z])-([a-z])/g, (_, p1, p2) => {
6094
+ return p1 + p2.toUpperCase();
6095
+ });
6096
+ };
6097
+ var setDotProp = (obj, keys, val) => {
6098
+ let i = 0;
6099
+ let length = keys.length;
6100
+ let t = obj;
6101
+ let x;
6102
+ for (; i < length; ++i) {
6103
+ x = t[keys[i]];
6104
+ t = t[keys[i]] = i === length - 1 ? val : x != null ? x : !!~keys[i + 1].indexOf(".") || !(+keys[i + 1] > -1) ? {} : [];
6105
+ }
6106
+ };
6107
+ var setByType = (obj, transforms) => {
6108
+ for (const key of Object.keys(transforms)) {
6109
+ const transform = transforms[key];
6110
+ if (transform.shouldTransform) {
6111
+ obj[key] = Array.prototype.concat.call([], obj[key]);
6112
+ if (typeof transform.transformFunction === "function") {
6113
+ obj[key] = obj[key].map(transform.transformFunction);
6114
+ }
6115
+ }
6116
+ }
6117
+ };
6118
+ var getFileName = (input) => {
6119
+ const m = /([^\\\/]+)$/.exec(input);
6120
+ return m ? m[1] : "";
6121
+ };
6122
+ var camelcaseOptionName = (name) => {
6123
+ return name.split(".").map((v, i) => {
6124
+ return i === 0 ? camelcase(v) : v;
6125
+ }).join(".");
6126
+ };
6127
+ var CACError = class extends Error {
6128
+ constructor(message) {
6129
+ super(message);
6130
+ this.name = this.constructor.name;
6131
+ if (typeof Error.captureStackTrace === "function") {
6132
+ Error.captureStackTrace(this, this.constructor);
6133
+ } else {
6134
+ this.stack = new Error(message).stack;
6135
+ }
6136
+ }
6137
+ };
6138
+ var Option = class {
6139
+ constructor(rawName, description, config) {
6140
+ this.rawName = rawName;
6141
+ this.description = description;
6142
+ this.config = Object.assign({}, config);
6143
+ rawName = rawName.replace(/\.\*/g, "");
6144
+ this.negated = false;
6145
+ this.names = removeBrackets(rawName).split(",").map((v) => {
6146
+ let name = v.trim().replace(/^-{1,2}/, "");
6147
+ if (name.startsWith("no-")) {
6148
+ this.negated = true;
6149
+ name = name.replace(/^no-/, "");
6150
+ }
6151
+ return camelcaseOptionName(name);
6152
+ }).sort((a, b) => a.length > b.length ? 1 : -1);
6153
+ this.name = this.names[this.names.length - 1];
6154
+ if (this.negated && this.config.default == null) {
6155
+ this.config.default = true;
6156
+ }
6157
+ if (rawName.includes("<")) {
6158
+ this.required = true;
6159
+ } else if (rawName.includes("[")) {
6160
+ this.required = false;
6161
+ } else {
6162
+ this.isBoolean = true;
6163
+ }
6164
+ }
6165
+ };
6166
+ var processArgs = process.argv;
6167
+ var platformInfo = `${process.platform}-${process.arch} node-${process.version}`;
6168
+ var Command = class {
6169
+ constructor(rawName, description, config = {}, cli2) {
6170
+ this.rawName = rawName;
6171
+ this.description = description;
6172
+ this.config = config;
6173
+ this.cli = cli2;
6174
+ this.options = [];
6175
+ this.aliasNames = [];
6176
+ this.name = removeBrackets(rawName);
6177
+ this.args = findAllBrackets(rawName);
6178
+ this.examples = [];
6179
+ }
6180
+ usage(text) {
6181
+ this.usageText = text;
6182
+ return this;
6183
+ }
6184
+ allowUnknownOptions() {
6185
+ this.config.allowUnknownOptions = true;
6186
+ return this;
6187
+ }
6188
+ ignoreOptionDefaultValue() {
6189
+ this.config.ignoreOptionDefaultValue = true;
6190
+ return this;
6191
+ }
6192
+ version(version, customFlags = "-v, --version") {
6193
+ this.versionNumber = version;
6194
+ this.option(customFlags, "Display version number");
6195
+ return this;
6196
+ }
6197
+ example(example) {
6198
+ this.examples.push(example);
6199
+ return this;
6200
+ }
6201
+ option(rawName, description, config) {
6202
+ const option = new Option(rawName, description, config);
6203
+ this.options.push(option);
6204
+ return this;
6205
+ }
6206
+ alias(name) {
6207
+ this.aliasNames.push(name);
6208
+ return this;
6209
+ }
6210
+ action(callback) {
6211
+ this.commandAction = callback;
6212
+ return this;
6213
+ }
6214
+ isMatched(name) {
6215
+ return this.name === name || this.aliasNames.includes(name);
6216
+ }
6217
+ get isDefaultCommand() {
6218
+ return this.name === "" || this.aliasNames.includes("!");
6219
+ }
6220
+ get isGlobalCommand() {
6221
+ return this instanceof GlobalCommand;
6222
+ }
6223
+ hasOption(name) {
6224
+ name = name.split(".")[0];
6225
+ return this.options.find((option) => {
6226
+ return option.names.includes(name);
6227
+ });
6228
+ }
6229
+ outputHelp() {
6230
+ const { name, commands } = this.cli;
6231
+ const {
6232
+ versionNumber,
6233
+ options: globalOptions,
6234
+ helpCallback
6235
+ } = this.cli.globalCommand;
6236
+ let sections = [
6237
+ {
6238
+ body: `${name}${versionNumber ? `/${versionNumber}` : ""}`
6239
+ }
6240
+ ];
6241
+ sections.push({
6242
+ title: "Usage",
6243
+ body: ` $ ${name} ${this.usageText || this.rawName}`
6244
+ });
6245
+ const showCommands = (this.isGlobalCommand || this.isDefaultCommand) && commands.length > 0;
6246
+ if (showCommands) {
6247
+ const longestCommandName = findLongest(commands.map((command) => command.rawName));
6248
+ sections.push({
6249
+ title: "Commands",
6250
+ body: commands.map((command) => {
6251
+ return ` ${padRight(command.rawName, longestCommandName.length)} ${command.description}`;
6252
+ }).join("\n")
6253
+ });
6254
+ sections.push({
6255
+ title: `For more info, run any command with the \`--help\` flag`,
6256
+ body: commands.map((command) => ` $ ${name}${command.name === "" ? "" : ` ${command.name}`} --help`).join("\n")
6257
+ });
6258
+ }
6259
+ let options = this.isGlobalCommand ? globalOptions : [...this.options, ...globalOptions || []];
6260
+ if (!this.isGlobalCommand && !this.isDefaultCommand) {
6261
+ options = options.filter((option) => option.name !== "version");
6262
+ }
6263
+ if (options.length > 0) {
6264
+ const longestOptionName = findLongest(options.map((option) => option.rawName));
6265
+ sections.push({
6266
+ title: "Options",
6267
+ body: options.map((option) => {
6268
+ return ` ${padRight(option.rawName, longestOptionName.length)} ${option.description} ${option.config.default === void 0 ? "" : `(default: ${option.config.default})`}`;
6269
+ }).join("\n")
6270
+ });
6271
+ }
6272
+ if (this.examples.length > 0) {
6273
+ sections.push({
6274
+ title: "Examples",
6275
+ body: this.examples.map((example) => {
6276
+ if (typeof example === "function") {
6277
+ return example(name);
6278
+ }
6279
+ return example;
6280
+ }).join("\n")
6281
+ });
6282
+ }
6283
+ if (helpCallback) {
6284
+ sections = helpCallback(sections) || sections;
6285
+ }
6286
+ console.log(sections.map((section) => {
6287
+ return section.title ? `${section.title}:
6288
+ ${section.body}` : section.body;
6289
+ }).join("\n\n"));
6290
+ }
6291
+ outputVersion() {
6292
+ const { name } = this.cli;
6293
+ const { versionNumber } = this.cli.globalCommand;
6294
+ if (versionNumber) {
6295
+ console.log(`${name}/${versionNumber} ${platformInfo}`);
6296
+ }
6297
+ }
6298
+ checkRequiredArgs() {
6299
+ const minimalArgsCount = this.args.filter((arg) => arg.required).length;
6300
+ if (this.cli.args.length < minimalArgsCount) {
6301
+ throw new CACError(`missing required args for command \`${this.rawName}\``);
6302
+ }
6303
+ }
6304
+ checkUnknownOptions() {
6305
+ const { options, globalCommand } = this.cli;
6306
+ if (!this.config.allowUnknownOptions) {
6307
+ for (const name of Object.keys(options)) {
6308
+ if (name !== "--" && !this.hasOption(name) && !globalCommand.hasOption(name)) {
6309
+ throw new CACError(`Unknown option \`${name.length > 1 ? `--${name}` : `-${name}`}\``);
6310
+ }
6311
+ }
6312
+ }
6313
+ }
6314
+ checkOptionValue() {
6315
+ const { options: parsedOptions, globalCommand } = this.cli;
6316
+ const options = [...globalCommand.options, ...this.options];
6317
+ for (const option of options) {
6318
+ const value = parsedOptions[option.name.split(".")[0]];
6319
+ if (option.required) {
6320
+ const hasNegated = options.some((o) => o.negated && o.names.includes(option.name));
6321
+ if (value === true || value === false && !hasNegated) {
6322
+ throw new CACError(`option \`${option.rawName}\` value is missing`);
6323
+ }
6324
+ }
6325
+ }
6326
+ }
6327
+ };
6328
+ var GlobalCommand = class extends Command {
6329
+ constructor(cli2) {
6330
+ super("@@global@@", "", {}, cli2);
6331
+ }
6332
+ };
6333
+ var __assign = Object.assign;
6334
+ var CAC = class extends EventEmitter {
6335
+ constructor(name = "") {
6336
+ super();
6337
+ this.name = name;
6338
+ this.commands = [];
6339
+ this.rawArgs = [];
6340
+ this.args = [];
6341
+ this.options = {};
6342
+ this.globalCommand = new GlobalCommand(this);
6343
+ this.globalCommand.usage("<command> [options]");
6344
+ }
6345
+ usage(text) {
6346
+ this.globalCommand.usage(text);
6347
+ return this;
6348
+ }
6349
+ command(rawName, description, config) {
6350
+ const command = new Command(rawName, description || "", config, this);
6351
+ command.globalCommand = this.globalCommand;
6352
+ this.commands.push(command);
6353
+ return command;
6354
+ }
6355
+ option(rawName, description, config) {
6356
+ this.globalCommand.option(rawName, description, config);
6357
+ return this;
6358
+ }
6359
+ help(callback) {
6360
+ this.globalCommand.option("-h, --help", "Display this message");
6361
+ this.globalCommand.helpCallback = callback;
6362
+ this.showHelpOnExit = true;
6363
+ return this;
6364
+ }
6365
+ version(version, customFlags = "-v, --version") {
6366
+ this.globalCommand.version(version, customFlags);
6367
+ this.showVersionOnExit = true;
6368
+ return this;
6369
+ }
6370
+ example(example) {
6371
+ this.globalCommand.example(example);
6372
+ return this;
6373
+ }
6374
+ outputHelp() {
6375
+ if (this.matchedCommand) {
6376
+ this.matchedCommand.outputHelp();
6377
+ } else {
6378
+ this.globalCommand.outputHelp();
6379
+ }
6380
+ }
6381
+ outputVersion() {
6382
+ this.globalCommand.outputVersion();
6383
+ }
6384
+ setParsedInfo({ args, options }, matchedCommand, matchedCommandName) {
6385
+ this.args = args;
6386
+ this.options = options;
6387
+ if (matchedCommand) {
6388
+ this.matchedCommand = matchedCommand;
6389
+ }
6390
+ if (matchedCommandName) {
6391
+ this.matchedCommandName = matchedCommandName;
6392
+ }
6393
+ return this;
6394
+ }
6395
+ unsetMatchedCommand() {
6396
+ this.matchedCommand = void 0;
6397
+ this.matchedCommandName = void 0;
6398
+ }
6399
+ parse(argv = processArgs, {
6400
+ run = true
6401
+ } = {}) {
6402
+ this.rawArgs = argv;
6403
+ if (!this.name) {
6404
+ this.name = argv[1] ? getFileName(argv[1]) : "cli";
6405
+ }
6406
+ let shouldParse = true;
6407
+ for (const command of this.commands) {
6408
+ const parsed = this.mri(argv.slice(2), command);
6409
+ const commandName = parsed.args[0];
6410
+ if (command.isMatched(commandName)) {
6411
+ shouldParse = false;
6412
+ const parsedInfo = __assign(__assign({}, parsed), {
6413
+ args: parsed.args.slice(1)
6414
+ });
6415
+ this.setParsedInfo(parsedInfo, command, commandName);
6416
+ this.emit(`command:${commandName}`, command);
6417
+ }
6418
+ }
6419
+ if (shouldParse) {
6420
+ for (const command of this.commands) {
6421
+ if (command.name === "") {
6422
+ shouldParse = false;
6423
+ const parsed = this.mri(argv.slice(2), command);
6424
+ this.setParsedInfo(parsed, command);
6425
+ this.emit(`command:!`, command);
6426
+ }
6427
+ }
6428
+ }
6429
+ if (shouldParse) {
6430
+ const parsed = this.mri(argv.slice(2));
6431
+ this.setParsedInfo(parsed);
6432
+ }
6433
+ if (this.options.help && this.showHelpOnExit) {
6434
+ this.outputHelp();
6435
+ run = false;
6436
+ this.unsetMatchedCommand();
6437
+ }
6438
+ if (this.options.version && this.showVersionOnExit && this.matchedCommandName == null) {
6439
+ this.outputVersion();
6440
+ run = false;
6441
+ this.unsetMatchedCommand();
6442
+ }
6443
+ const parsedArgv = { args: this.args, options: this.options };
6444
+ if (run) {
6445
+ this.runMatchedCommand();
6446
+ }
6447
+ if (!this.matchedCommand && this.args[0]) {
6448
+ this.emit("command:*");
6449
+ }
6450
+ return parsedArgv;
6451
+ }
6452
+ mri(argv, command) {
6453
+ const cliOptions = [
6454
+ ...this.globalCommand.options,
6455
+ ...command ? command.options : []
6456
+ ];
6457
+ const mriOptions = getMriOptions(cliOptions);
6458
+ let argsAfterDoubleDashes = [];
6459
+ const doubleDashesIndex = argv.indexOf("--");
6460
+ if (doubleDashesIndex > -1) {
6461
+ argsAfterDoubleDashes = argv.slice(doubleDashesIndex + 1);
6462
+ argv = argv.slice(0, doubleDashesIndex);
6463
+ }
6464
+ let parsed = mri2(argv, mriOptions);
6465
+ parsed = Object.keys(parsed).reduce((res, name) => {
6466
+ return __assign(__assign({}, res), {
6467
+ [camelcaseOptionName(name)]: parsed[name]
6468
+ });
6469
+ }, { _: [] });
6470
+ const args = parsed._;
6471
+ const options = {
6472
+ "--": argsAfterDoubleDashes
6473
+ };
6474
+ const ignoreDefault = command && command.config.ignoreOptionDefaultValue ? command.config.ignoreOptionDefaultValue : this.globalCommand.config.ignoreOptionDefaultValue;
6475
+ let transforms = /* @__PURE__ */ Object.create(null);
6476
+ for (const cliOption of cliOptions) {
6477
+ if (!ignoreDefault && cliOption.config.default !== void 0) {
6478
+ for (const name of cliOption.names) {
6479
+ options[name] = cliOption.config.default;
6480
+ }
6481
+ }
6482
+ if (Array.isArray(cliOption.config.type)) {
6483
+ if (transforms[cliOption.name] === void 0) {
6484
+ transforms[cliOption.name] = /* @__PURE__ */ Object.create(null);
6485
+ transforms[cliOption.name]["shouldTransform"] = true;
6486
+ transforms[cliOption.name]["transformFunction"] = cliOption.config.type[0];
6487
+ }
6488
+ }
6489
+ }
6490
+ for (const key of Object.keys(parsed)) {
6491
+ if (key !== "_") {
6492
+ const keys = key.split(".");
6493
+ setDotProp(options, keys, parsed[key]);
6494
+ setByType(options, transforms);
6495
+ }
6496
+ }
6497
+ return {
6498
+ args,
6499
+ options
6500
+ };
6501
+ }
6502
+ runMatchedCommand() {
6503
+ const { args, options, matchedCommand: command } = this;
6504
+ if (!command || !command.commandAction)
6505
+ return;
6506
+ command.checkUnknownOptions();
6507
+ command.checkOptionValue();
6508
+ command.checkRequiredArgs();
6509
+ const actionArgs = [];
6510
+ command.args.forEach((arg, index) => {
6511
+ if (arg.variadic) {
6512
+ actionArgs.push(args.slice(index));
6513
+ } else {
6514
+ actionArgs.push(args[index]);
6515
+ }
6516
+ });
6517
+ actionArgs.push(options);
6518
+ return command.commandAction.apply(this, actionArgs);
6519
+ }
6520
+ };
6521
+ var cac = (name = "") => new CAC(name);
6522
+
6523
+ // src/cli/index.ts
6524
+ var cli = cac("faapi");
6525
+ cli.command("").alias("dev").action(async () => {
5778
6526
  const { devCommand: devCommand2 } = await Promise.resolve().then(() => (init_devCommand(), devCommand_exports));
5779
6527
  await devCommand2();
5780
- }
5781
- main().catch((err) => {
5782
- console.error(err);
5783
- process.exit(1);
5784
6528
  });
6529
+ cli.command("build", "Build for production").action(async () => {
6530
+ const { buildCommand: buildCommand2 } = await Promise.resolve().then(() => (init_buildCommand(), buildCommand_exports));
6531
+ await buildCommand2();
6532
+ });
6533
+ cli.help();
6534
+ cli.parse();
5785
6535
  /*! Bundled license information:
5786
6536
 
5787
6537
  chokidar/esm/index.js: