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 +148 -217
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -10
- package/dist/index.d.ts +2 -10
- package/dist/index.js +148 -217
- package/dist/index.js.map +1 -1
- package/package.json +13 -5
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
|
-
|
|
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
|
|
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(
|
|
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,
|
|
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
|
|
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
|
|
156
|
+
layouts: resolveLayoutChain(fullPath),
|
|
155
157
|
dynamic: isDynamic
|
|
156
158
|
});
|
|
157
159
|
}
|
|
158
|
-
routes.push(...scanRoutes(fullPath,
|
|
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(
|
|
216
|
+
function generateApp() {
|
|
215
217
|
const aliases = readTsconfigAliases();
|
|
216
|
-
const routes = scanRoutes(
|
|
217
|
-
const rootPage = findFile(
|
|
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(
|
|
222
|
-
layouts: resolveLayoutChain(
|
|
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(
|
|
238
|
-
const notFound = notFoundFile && hasDefaultExport(import_path.default.join(
|
|
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(
|
|
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(
|
|
352
|
-
const layout = findFile(
|
|
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(
|
|
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
|
-
|
|
548
|
-
const
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
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(
|
|
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(
|
|
692
|
-
if (isInDir(nf, norm(
|
|
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(
|
|
597
|
+
return isInDir(nf, norm(API_DIR)) && API_EXTS.includes(import_path.default.extname(f));
|
|
699
598
|
}
|
|
700
599
|
function applyApp() {
|
|
701
|
-
|
|
702
|
-
|
|
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.
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
780
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
804
|
-
server.
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
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(
|
|
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"
|
|
879
|
-
lines.push(`<meta property="og: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"
|
|
882
|
-
if (meta.openGraph.image) lines.push(`<meta property="og: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"
|
|
886
|
-
lines.push(`<meta name="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"
|
|
889
|
-
if (meta.twitter.image) lines.push(`<meta name="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}
|