bini-router 1.0.4 → 1.0.6

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/index.cjs CHANGED
@@ -36,6 +36,8 @@ __export(index_exports, {
36
36
  module.exports = __toCommonJS(index_exports);
37
37
  var import_fs = __toESM(require("fs"), 1);
38
38
  var import_path = __toESM(require("path"), 1);
39
+ var import_child_process = require("child_process");
40
+ var import_url = require("url");
39
41
  var PAGE_FILES = ["page.tsx", "page.jsx", "page.ts", "page.js"];
40
42
  var LAYOUT_FILES = ["layout.tsx", "layout.jsx", "layout.ts", "layout.js"];
41
43
  var SUPPORTED_EXTS = [".tsx", ".jsx", ".ts", ".js"];
@@ -45,13 +47,13 @@ var API_EXTS = [".ts", ".js"];
45
47
  var DEBOUNCE_MS = 60;
46
48
  var EVENT_DEDUP_MS = 500;
47
49
  var EVENT_TTL_MS = 2e3;
50
+ var APP_DIR = import_path.default.join(process.cwd(), "src/app");
51
+ var API_DIR = import_path.default.join(process.cwd(), "src/app/api");
48
52
  function norm(p) {
49
53
  return p.replace(/\\/g, "/");
50
54
  }
51
55
  function isInDir(file, dir) {
52
- const nFile = norm(file);
53
- const nDir = norm(dir).replace(/\/$/, "");
54
- return nFile.startsWith(nDir + "/") || nFile === nDir;
56
+ return norm(file).startsWith(norm(dir));
55
57
  }
56
58
  function readTsconfigAliases() {
57
59
  const aliases = {};
@@ -104,20 +106,20 @@ function getAppFile() {
104
106
  const ts = import_path.default.join(process.cwd(), "src/App.tsx");
105
107
  return import_fs.default.existsSync(ts) ? ts : import_path.default.join(process.cwd(), "src/App.jsx");
106
108
  }
107
- function resolveLayoutChain(pageDir, appDir) {
109
+ function resolveLayoutChain(pageDir) {
108
110
  const chain = [];
109
111
  let current = pageDir;
110
112
  while (true) {
111
113
  const layout = findFile(current, LAYOUT_FILES);
112
114
  if (layout) chain.unshift(import_path.default.join(current, layout));
113
- if (import_path.default.resolve(current) === import_path.default.resolve(appDir)) break;
115
+ if (import_path.default.resolve(current) === import_path.default.resolve(APP_DIR)) break;
114
116
  const parent = import_path.default.dirname(current);
115
117
  if (parent === current) break;
116
118
  current = parent;
117
119
  }
118
120
  return chain;
119
121
  }
120
- function scanRoutes(dir, appDir, baseRoute = "") {
122
+ function scanRoutes(dir, baseRoute = "") {
121
123
  const routes = [];
122
124
  if (!import_fs.default.existsSync(dir)) return routes;
123
125
  let entries;
@@ -135,7 +137,7 @@ function scanRoutes(dir, appDir, baseRoute = "") {
135
137
  routes.push({
136
138
  routePath: `${baseRoute}/${base}`,
137
139
  filePath: import_path.default.join(dir, entry.name),
138
- layouts: resolveLayoutChain(dir, appDir),
140
+ layouts: resolveLayoutChain(dir),
139
141
  dynamic: false
140
142
  });
141
143
  }
@@ -151,11 +153,11 @@ function scanRoutes(dir, appDir, baseRoute = "") {
151
153
  routes.push({
152
154
  routePath,
153
155
  filePath: import_path.default.join(fullPath, pageFile),
154
- layouts: resolveLayoutChain(fullPath, appDir),
156
+ layouts: resolveLayoutChain(fullPath),
155
157
  dynamic: isDynamic
156
158
  });
157
159
  }
158
- routes.push(...scanRoutes(fullPath, appDir, routePath));
160
+ routes.push(...scanRoutes(fullPath, routePath));
159
161
  }
160
162
  return routes;
161
163
  }
@@ -211,15 +213,15 @@ function renderChain(layouts, routesInChain, layoutNames, pageNames, layoutTitle
211
213
  `${pad}</Route>`
212
214
  ].join("\n");
213
215
  }
214
- function generateApp(appDir) {
216
+ function generateApp() {
215
217
  const aliases = readTsconfigAliases();
216
- const routes = scanRoutes(appDir, appDir);
217
- const rootPage = findFile(appDir, PAGE_FILES);
218
+ const routes = scanRoutes(APP_DIR);
219
+ const rootPage = findFile(APP_DIR, PAGE_FILES);
218
220
  if (rootPage) {
219
221
  routes.unshift({
220
222
  routePath: "/",
221
- filePath: import_path.default.join(appDir, rootPage),
222
- layouts: resolveLayoutChain(appDir, appDir),
223
+ filePath: import_path.default.join(APP_DIR, rootPage),
224
+ layouts: resolveLayoutChain(APP_DIR),
223
225
  dynamic: false
224
226
  });
225
227
  }
@@ -234,8 +236,8 @@ function generateApp(appDir) {
234
236
  if (a.dynamic !== b.dynamic) return a.dynamic ? 1 : -1;
235
237
  return a.routePath.length - b.routePath.length;
236
238
  });
237
- const notFoundFile = NOT_FOUND_FILES.find((f) => import_fs.default.existsSync(import_path.default.join(appDir, f)));
238
- const notFound = notFoundFile && hasDefaultExport(import_path.default.join(appDir, notFoundFile)) ? notFoundFile : void 0;
239
+ const notFoundFile = NOT_FOUND_FILES.find((f) => import_fs.default.existsSync(import_path.default.join(APP_DIR, f)));
240
+ const notFound = notFoundFile && hasDefaultExport(import_path.default.join(APP_DIR, notFoundFile)) ? notFoundFile : void 0;
239
241
  const allLayouts = /* @__PURE__ */ new Set();
240
242
  for (const r of validRoutes) r.layouts.forEach((l) => {
241
243
  if (isUsableLayout(l)) allLayouts.add(l);
@@ -256,7 +258,7 @@ function generateApp(appDir) {
256
258
  for (const [fp, name] of layoutNames)
257
259
  lazyImports.push(`const ${name} = React.lazy(() => import('${toImportPath(fp, aliases)}'));`);
258
260
  if (notFound)
259
- lazyImports.push(`const NotFound = React.lazy(() => import('${toImportPath(import_path.default.join(appDir, notFound), aliases)}'));`);
261
+ lazyImports.push(`const NotFound = React.lazy(() => import('${toImportPath(import_path.default.join(APP_DIR, notFound), aliases)}'));`);
260
262
  const emittedPages = /* @__PURE__ */ new Set();
261
263
  for (const r of validRoutes) {
262
264
  if (emittedPages.has(r.filePath)) continue;
@@ -318,7 +320,6 @@ function Spinner() {
318
320
  }
319
321
 
320
322
  // Renders nothing \u2014 just sets document.title when the layout mounts.
321
- // Placed outside ErrorBoundary so a layout crash doesn't block the title update.
322
323
  function TitleSetter({ title }: { title: string }) {
323
324
  React.useEffect(() => { document.title = title; }, [title]);
324
325
  return null;
@@ -348,12 +349,12 @@ ${catchAll}
348
349
  }
349
350
  `;
350
351
  }
351
- function parseAppMetadata(appDir) {
352
- const layout = findFile(appDir, LAYOUT_FILES);
352
+ function parseAppMetadata() {
353
+ const layout = findFile(APP_DIR, LAYOUT_FILES);
353
354
  if (!layout) return {};
354
355
  let src = "";
355
356
  try {
356
- src = import_fs.default.readFileSync(import_path.default.join(appDir, layout), "utf8");
357
+ src = import_fs.default.readFileSync(import_path.default.join(APP_DIR, layout), "utf8");
357
358
  } catch {
358
359
  return {};
359
360
  }
@@ -515,7 +516,7 @@ function extractArrayRaw(source, key) {
515
516
  }
516
517
  return void 0;
517
518
  }
518
- function scanApiRoutes(dir, baseRoute = "") {
519
+ function scanApiRoutes(dir = API_DIR, baseRoute = "") {
519
520
  const routes = [];
520
521
  if (!import_fs.default.existsSync(dir)) return routes;
521
522
  let entries;
@@ -544,136 +545,34 @@ function scanApiRoutes(dir, baseRoute = "") {
544
545
  }
545
546
  return routes;
546
547
  }
547
- async function buildHonoApp(apiDir, enableCors) {
548
- const { Hono } = await import("hono");
549
- const app = new Hono();
550
- if (enableCors) {
551
- const { cors } = await import("hono/cors");
552
- app.use("*", cors({
553
- origin: "*",
554
- allowMethods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
555
- allowHeaders: ["Content-Type", "Authorization"]
556
- }));
557
- }
558
- for (const route of scanApiRoutes(apiDir)) {
559
- const filePath = route.filePath;
560
- app.all(route.routePath, async (c) => {
561
- try {
562
- const { pathToFileURL } = await import("url");
563
- const mod = await import(pathToFileURL(filePath).href + "?t=" + Date.now());
564
- const handler = mod.default;
565
- if (!handler) return c.json({ error: "No default export" }, 500);
566
- if (typeof handler.fetch === "function") return await handler.fetch(c.req.raw);
567
- if (typeof handler === "function") {
568
- return await handler(c.req.raw);
569
- }
570
- return c.json({ error: "Default export must be a function or Hono app" }, 500);
571
- } catch (e) {
572
- return c.json({ error: e.message }, 500);
573
- }
574
- });
575
- }
576
- return app;
577
- }
578
- async function handleApiRequest(req, res, next, apiDir, enableCors, getCache, setCache) {
579
- try {
580
- let app = getCache();
581
- if (!app) {
582
- app = await buildHonoApp(apiDir, enableCors);
583
- setCache(app);
584
- }
585
- const host = req.headers.host ?? "localhost";
586
- const url = `http://${host}/api${req.url}`;
587
- const chunks = [];
588
- for await (const chunk of req) chunks.push(chunk);
589
- const body = chunks.length > 0 ? Buffer.concat(chunks) : void 0;
590
- const webReq = new Request(url, {
591
- method: req.method,
592
- headers: req.headers,
593
- body: body?.length ? body : void 0
594
- });
595
- const webRes = await app.fetch(webReq);
596
- res.statusCode = webRes.status;
597
- webRes.headers.forEach((v, k) => res.setHeader(k, v));
598
- res.end(Buffer.from(await webRes.arrayBuffer()));
599
- } catch (e) {
600
- next(e);
601
- }
602
- }
603
- function buildNitroRoutes(srcApiDir, enableCors) {
604
- if (!import_fs.default.existsSync(srcApiDir)) return;
605
- const routes = scanApiRoutes(srcApiDir);
606
- if (!routes.length) return;
607
- const cwd = process.cwd();
608
- const nitroApiDir = import_path.default.join(cwd, "server", "routes", "api");
609
- if (import_fs.default.existsSync(nitroApiDir)) {
610
- import_fs.default.rmSync(nitroApiDir, { recursive: true, force: true });
611
- }
612
- import_fs.default.mkdirSync(nitroApiDir, { recursive: true });
613
- for (const route of routes) {
614
- const relFromApiDir = norm(import_path.default.relative(srcApiDir, route.filePath));
615
- const outFile = import_path.default.join(nitroApiDir, relFromApiDir);
616
- import_fs.default.mkdirSync(import_path.default.dirname(outFile), { recursive: true });
617
- const importRel = norm(import_path.default.relative(import_path.default.dirname(outFile), route.filePath)).replace(/\.(ts|tsx)$/, "");
618
- const importPath = importRel.startsWith(".") ? importRel : `./${importRel}`;
619
- const code = [
620
- `// Auto-generated by bini-router \u2014 DO NOT EDIT.`,
621
- `// Source: ${norm(import_path.default.relative(cwd, route.filePath))}`,
622
- `import { Hono } from 'hono';`,
623
- ...enableCors ? [`import { cors } from 'hono/cors';`] : [],
624
- `import { defineEventHandler, getRequestURL, getHeaders, readRawBody } from 'h3';`,
625
- `import sourceHandler from '${importPath}';`,
626
- ``,
627
- `// Wrap the user's handler so we get a standard fetch() interface.`,
628
- `// Supports:`,
629
- `// export default async function handler(req: Request) { ... } \u2190 web standard`,
630
- `// export default new Hono() \u2190 Hono app`,
631
- `function makeApp(): Hono {`,
632
- ` if (sourceHandler instanceof Hono) return sourceHandler;`,
633
- ` const app = new Hono();`,
634
- ...enableCors ? [` app.use('*', cors({ origin: '*', allowMethods: ['GET','POST','PUT','PATCH','DELETE','OPTIONS'], allowHeaders: ['Content-Type','Authorization'] }));`] : [],
635
- ` app.all('*', async c => {`,
636
- ` try {`,
637
- ` const res = await (sourceHandler as (req: Request) => Promise<Response>)(c.req.raw);`,
638
- ` return res;`,
639
- ` } catch (e: any) {`,
640
- ` return c.json({ error: e.message ?? 'Internal server error' }, 500);`,
641
- ` }`,
642
- ` });`,
643
- ` return app;`,
644
- `}`,
645
- ``,
646
- `const app = makeApp();`,
647
- ``,
648
- `export default defineEventHandler(async (event) => {`,
649
- ` // Manually reconstruct a Web Request from h3 event to avoid the`,
650
- ` // toWebRequest() body-stream bug on Netlify/Lambda (h3 issue #578).`,
651
- ` const url = getRequestURL(event);`,
652
- ` const headers = new Headers(getHeaders(event) as Record<string, string>);`,
653
- ` const method = (event.method ?? event.node?.req?.method ?? 'GET').toUpperCase();`,
654
- ` const hasBody = !['GET', 'HEAD', 'OPTIONS'].includes(method);`,
655
- ` let body: BodyInit | undefined;`,
656
- ` if (hasBody) {`,
657
- ` const raw = await readRawBody(event, false);`,
658
- ` if (raw && raw.length > 0) body = raw;`,
659
- ` }`,
660
- ` const req = new Request(url.toString(), { method, headers, body });`,
661
- ` return app.fetch(req);`,
662
- `});`,
663
- ``
664
- ].join("\n");
665
- import_fs.default.writeFileSync(outFile, code, "utf8");
666
- }
667
- console.log(`[bini-router] \u2713 Generated ${routes.length} Nitro route(s) in server/routes/api/
668
- `);
548
+ function generateNitroApiEntry() {
549
+ const routes = scanApiRoutes();
550
+ if (!routes.length) return "";
551
+ const imports = [];
552
+ const registrations = [];
553
+ routes.forEach((route, i) => {
554
+ const relPath = import_path.default.relative(
555
+ import_path.default.join(process.cwd(), "server"),
556
+ route.filePath
557
+ ).replace(/\\/g, "/").replace(/\.(ts|js)$/, "");
558
+ imports.push(`import handler${i} from './${relPath}';`);
559
+ registrations.push(
560
+ ` app.all('${route.routePath}', async (c) => { try { return await handler${i}(c.req.raw); } catch (e) { return c.json({ error: e.message }, 500); } });`
561
+ );
562
+ });
563
+ return `// Auto-generated by bini-router
564
+ import { Hono } from 'hono';
565
+ ${imports.join("\n")}
566
+
567
+ const app = new Hono();
568
+ ${registrations.join("\n")}
569
+
570
+ export default app;
571
+ `;
669
572
  }
670
- function biniroute(options = {}) {
671
- const { cors: enableCors = true } = options;
672
- const getAppDir = () => import_path.default.join(process.cwd(), options.appDir ?? "src/app");
673
- const getApiDir = () => import_path.default.join(process.cwd(), options.apiDir ?? "src/app/api");
573
+ function biniroute() {
674
574
  let debounceTimer = null;
675
575
  let lastGeneratedCode = "";
676
- let honoCache = null;
677
576
  const eventLog = /* @__PURE__ */ new Map();
678
577
  function shouldProcess(file, event) {
679
578
  const key = `${file}:${event}`;
@@ -688,19 +587,18 @@ function biniroute(options = {}) {
688
587
  const base = import_path.default.basename(f, import_path.default.extname(f));
689
588
  const ext = import_path.default.extname(f);
690
589
  if (!SUPPORTED_EXTS.includes(ext)) return false;
691
- if (!isInDir(nf, norm(getAppDir()))) return false;
692
- if (isInDir(nf, norm(getApiDir()))) return false;
590
+ if (!isInDir(nf, norm(APP_DIR))) return false;
591
+ if (isInDir(nf, norm(API_DIR))) return false;
693
592
  if (base.startsWith("_")) return false;
694
593
  return true;
695
594
  }
696
595
  function isApiFile(f) {
697
596
  const nf = norm(f);
698
- return isInDir(nf, norm(getApiDir())) && API_EXTS.includes(import_path.default.extname(f));
597
+ return isInDir(nf, norm(API_DIR)) && API_EXTS.includes(import_path.default.extname(f));
699
598
  }
700
599
  function applyApp() {
701
- const dir = getAppDir();
702
- if (!import_fs.default.existsSync(dir)) return null;
703
- const code = generateApp(dir);
600
+ if (!import_fs.default.existsSync(APP_DIR)) return null;
601
+ const code = generateApp();
704
602
  if (code === lastGeneratedCode) return null;
705
603
  import_fs.default.writeFileSync(getAppFile(), code, "utf8");
706
604
  lastGeneratedCode = code;
@@ -718,19 +616,53 @@ function biniroute(options = {}) {
718
616
  function addSpaFallback(server) {
719
617
  server.middlewares.use((req, res, next) => {
720
618
  const url = req.url;
721
- if (url.startsWith("/api") || url.includes(".")) return next();
619
+ if (url.includes(".")) return next();
722
620
  req.url = "/index.html";
723
621
  next();
724
622
  });
725
623
  }
624
+ function buildApiWithNitro() {
625
+ const routes = scanApiRoutes();
626
+ if (routes.length === 0) return;
627
+ console.log("\n\u{1F4E6} Building API routes with Nitro...");
628
+ const serverDir = import_path.default.join(process.cwd(), "server");
629
+ const apiDir = import_path.default.join(serverDir, "api");
630
+ import_fs.default.mkdirSync(apiDir, { recursive: true });
631
+ const entry = generateNitroApiEntry();
632
+ import_fs.default.writeFileSync(import_path.default.join(serverDir, "index.ts"), entry, "utf8");
633
+ routes.forEach((route) => {
634
+ const destPath = import_path.default.join(apiDir, import_path.default.basename(route.filePath));
635
+ import_fs.default.copyFileSync(route.filePath, destPath);
636
+ });
637
+ const nitroConfig = import_path.default.join(process.cwd(), "nitro.config.ts");
638
+ if (!import_fs.default.existsSync(nitroConfig)) {
639
+ const config = `import { defineNitroConfig } from 'nitropack/config';
640
+
641
+ export default defineNitroConfig({
642
+ handlers: [
643
+ { route: '/api/**', handler: '~/server/index' }
644
+ ]
645
+ });
646
+ `;
647
+ import_fs.default.writeFileSync(nitroConfig, config, "utf8");
648
+ }
649
+ try {
650
+ (0, import_child_process.execSync)("npx nitropack build", {
651
+ stdio: "inherit",
652
+ cwd: process.cwd()
653
+ });
654
+ console.log("\u2705 API routes built successfully");
655
+ } catch (error) {
656
+ console.error("\u274C API build failed:", error);
657
+ process.exit(1);
658
+ }
659
+ }
726
660
  return {
727
661
  name: "bini-router",
728
662
  enforce: "pre",
729
- // Strip `export const metadata = {...}` from all src/app files before
730
- // @vitejs/plugin-react's Fast Refresh transform sees them.
731
663
  transform(code, id) {
732
664
  const nid = norm(id);
733
- if (!isInDir(nid, norm(getAppDir()))) return;
665
+ if (!isInDir(nid, norm(APP_DIR))) return;
734
666
  const ext = import_path.default.extname(id);
735
667
  if (!SUPPORTED_EXTS.includes(ext)) return;
736
668
  if (!code.includes("export const metadata")) return;
@@ -765,89 +697,88 @@ function biniroute(options = {}) {
765
697
  buildStart() {
766
698
  applyApp();
767
699
  },
700
+ // This runs after Vite build is complete
768
701
  closeBundle() {
769
- buildNitroRoutes(getApiDir(), enableCors);
702
+ buildApiWithNitro();
770
703
  },
771
704
  buildEnd() {
772
- honoCache = null;
773
705
  if (debounceTimer) {
774
706
  clearTimeout(debounceTimer);
775
707
  debounceTimer = null;
776
708
  }
777
709
  },
778
710
  async configureServer(server) {
779
- const appDir = getAppDir();
780
- const apiDir = getApiDir();
781
- if (!import_fs.default.existsSync(appDir)) return;
782
- server.watcher.add(appDir);
711
+ if (!import_fs.default.existsSync(APP_DIR)) return;
712
+ server.watcher.add(APP_DIR);
783
713
  server.watcher.on("add", (f) => isPageFile(f) && shouldProcess(f, "add") && scheduleRegen(server, 300));
784
714
  server.watcher.on("unlink", (f) => isPageFile(f) && shouldProcess(f, "unlink") && scheduleRegen(server));
785
715
  server.watcher.on("change", (f) => isPageFile(f) && shouldProcess(f, "change") && scheduleRegen(server));
786
716
  server.watcher.on("change", (f) => {
787
717
  const base = import_path.default.basename(f, import_path.default.extname(f));
788
- const inAppRoot = import_path.default.resolve(import_path.default.dirname(f)) === import_path.default.resolve(appDir);
718
+ const inAppRoot = import_path.default.resolve(import_path.default.dirname(f)) === import_path.default.resolve(APP_DIR);
789
719
  if (!inAppRoot || base !== "layout") return;
790
720
  server.moduleGraph.invalidateAll();
791
721
  server.ws.send({ type: "full-reload", path: "*" });
792
722
  });
793
723
  server.watcher.on("addDir", (d) => {
794
- const nd = norm(d);
795
- if (!isInDir(nd, norm(appDir)) || d.includes("node_modules") || isInDir(nd, norm(apiDir))) return;
724
+ if (!isInDir(norm(d), norm(APP_DIR)) || d.includes("node_modules") || d.includes("api")) return;
796
725
  setTimeout(() => PAGE_FILES.some((f) => import_fs.default.existsSync(import_path.default.join(d, f))) && scheduleRegen(server), 300);
797
726
  });
798
727
  server.watcher.on("unlinkDir", (d) => {
799
- const nd = norm(d);
800
- if (isInDir(nd, norm(appDir)) && !d.includes("node_modules") && !isInDir(nd, norm(apiDir)))
728
+ if (isInDir(norm(d), norm(APP_DIR)) && !d.includes("node_modules") && !d.includes("api"))
801
729
  scheduleRegen(server);
802
730
  });
803
- if (import_fs.default.existsSync(apiDir)) {
804
- server.watcher.add(apiDir);
805
- const resetApi = () => {
806
- honoCache = null;
807
- server.ws.send({ type: "full-reload", path: "*" });
808
- };
809
- server.watcher.on("add", (f) => isApiFile(f) && resetApi());
810
- server.watcher.on("unlink", (f) => isApiFile(f) && resetApi());
811
- server.watcher.on("change", (f) => isApiFile(f) && resetApi());
812
- server.middlewares.use(
813
- "/api",
814
- (req, res, next) => handleApiRequest(
815
- req,
816
- res,
817
- next,
818
- apiDir,
819
- enableCors,
820
- () => honoCache,
821
- (v) => {
822
- honoCache = v;
731
+ if (import_fs.default.existsSync(API_DIR)) {
732
+ server.middlewares.use("/api", async (req, res, next) => {
733
+ try {
734
+ const { Hono } = await import("hono");
735
+ const app = new Hono();
736
+ const routes = scanApiRoutes();
737
+ for (const route of routes) {
738
+ const importUrl = (0, import_url.pathToFileURL)(route.filePath).href + "?t=" + Date.now();
739
+ const mod = await import(
740
+ /* @vite-ignore */
741
+ importUrl
742
+ );
743
+ const handler = mod.default;
744
+ if (typeof handler === "function") {
745
+ app.all(route.routePath, async (c) => {
746
+ try {
747
+ return await handler(c.req.raw);
748
+ } catch (e) {
749
+ return c.json({ error: e.message }, 500);
750
+ }
751
+ });
752
+ }
823
753
  }
824
- )
825
- );
754
+ const url = `http://${req.headers.host}${req.url}`;
755
+ const chunks = [];
756
+ for await (const chunk of req) chunks.push(chunk);
757
+ const body = chunks.length > 0 ? Buffer.concat(chunks) : void 0;
758
+ const webReq = new Request(url, {
759
+ method: req.method,
760
+ headers: req.headers,
761
+ body
762
+ });
763
+ const webRes = await app.fetch(webReq);
764
+ res.statusCode = webRes.status;
765
+ webRes.headers.forEach((value, key) => res.setHeader(key, value));
766
+ const buffer = await webRes.arrayBuffer();
767
+ res.end(Buffer.from(buffer));
768
+ } catch (e) {
769
+ console.error("API Error:", e);
770
+ next(e);
771
+ }
772
+ });
826
773
  }
827
774
  },
828
775
  async configurePreviewServer(server) {
829
776
  addSpaFallback(server);
830
- const apiDir = getApiDir();
831
- if (!import_fs.default.existsSync(apiDir)) return;
832
- server.middlewares.use(
833
- "/api",
834
- (req, res, next) => handleApiRequest(
835
- req,
836
- res,
837
- next,
838
- apiDir,
839
- enableCors,
840
- () => honoCache,
841
- (v) => {
842
- honoCache = v;
843
- }
844
- )
845
- );
846
777
  },
847
778
  transformIndexHtml: {
848
779
  order: "pre",
849
780
  handler(html) {
850
- const meta = parseAppMetadata(getAppDir());
781
+ const meta = parseAppMetadata();
851
782
  const title = meta.title ?? "Bini App";
852
783
  const vp = meta.viewport ?? "width=device-width, initial-scale=1.0";
853
784
  const lines = [];
@@ -875,18 +806,18 @@ function biniroute(options = {}) {
875
806
  lines.push(`<link rel="apple-touch-icon" href="${entry.url}"${sizes}${type} />`);
876
807
  }
877
808
  if (meta.openGraph?.title) {
878
- lines.push(`<meta property="og:type" content="${meta.openGraph.type ?? "website"}" />`);
879
- lines.push(`<meta property="og:title" content="${meta.openGraph.title}" />`);
809
+ lines.push(`<meta property="og:type" content="${meta.openGraph.type ?? "website"}" />`);
810
+ lines.push(`<meta property="og:title" content="${meta.openGraph.title}" />`);
880
811
  if (meta.openGraph.description) lines.push(`<meta property="og:description" content="${meta.openGraph.description}" />`);
881
- if (meta.openGraph.url) lines.push(`<meta property="og:url" content="${meta.openGraph.url}" />`);
882
- if (meta.openGraph.image) lines.push(`<meta property="og:image" content="${meta.openGraph.image}" />`);
812
+ if (meta.openGraph.url) lines.push(`<meta property="og:url" content="${meta.openGraph.url}" />`);
813
+ if (meta.openGraph.image) lines.push(`<meta property="og:image" content="${meta.openGraph.image}" />`);
883
814
  }
884
815
  if (meta.twitter?.title) {
885
- lines.push(`<meta name="twitter:card" content="${meta.twitter.card ?? "summary_large_image"}" />`);
886
- lines.push(`<meta name="twitter:title" content="${meta.twitter.title}" />`);
816
+ lines.push(`<meta name="twitter:card" content="${meta.twitter.card ?? "summary_large_image"}" />`);
817
+ lines.push(`<meta name="twitter:title" content="${meta.twitter.title}" />`);
887
818
  if (meta.twitter.description) lines.push(`<meta name="twitter:description" content="${meta.twitter.description}" />`);
888
- if (meta.twitter.creator) lines.push(`<meta name="twitter:creator" content="${meta.twitter.creator}" />`);
889
- if (meta.twitter.image) lines.push(`<meta name="twitter:image" content="${meta.twitter.image}" />`);
819
+ if (meta.twitter.creator) lines.push(`<meta name="twitter:creator" content="${meta.twitter.creator}" />`);
820
+ if (meta.twitter.image) lines.push(`<meta name="twitter:image" content="${meta.twitter.image}" />`);
890
821
  }
891
822
  const injected = lines.map((l) => ` ${l}`).join("\n");
892
823
  return html.replace(/<meta\s+charset[^>]*\/?>/gi, "").replace(/<meta\s+name="viewport"[^>]*\/?>/gi, "").replace(/<meta\s+name="description"[^>]*\/?>/gi, "").replace(/<meta\s+name="theme-color"[^>]*\/?>/gi, "").replace(/<meta\s+name="robots"[^>]*\/?>/gi, "").replace(/<meta\s+name="keywords"[^>]*\/?>/gi, "").replace(/<meta\s+name="author"[^>]*\/?>/gi, "").replace(/<meta\s+property="og:[^"]*"[^>]*\/?>/gi, "").replace(/<meta\s+name="twitter:[^"]*"[^>]*\/?>/gi, "").replace(/<link\s+rel="canonical"[^>]*\/?>/gi, "").replace(/<link\s+rel="manifest"[^>]*\/?>/gi, "").replace(/<link\s+rel="icon"[^>]*\/?>/gi, "").replace(/<link\s+rel="shortcut icon"[^>]*\/?>/gi, "").replace(/<link\s+rel="apple-touch-icon"[^>]*\/?>/gi, "").replace(/<title>.*?<\/title>/si, "").replace("</head>", `${injected}