bini-router 1.0.4 → 1.0.5
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 +142 -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 +142 -217
- package/dist/index.js.map +1 -1
- package/package.json +13 -5
package/dist/index.cjs
CHANGED
|
@@ -36,6 +36,7 @@ __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");
|
|
39
40
|
var PAGE_FILES = ["page.tsx", "page.jsx", "page.ts", "page.js"];
|
|
40
41
|
var LAYOUT_FILES = ["layout.tsx", "layout.jsx", "layout.ts", "layout.js"];
|
|
41
42
|
var SUPPORTED_EXTS = [".tsx", ".jsx", ".ts", ".js"];
|
|
@@ -45,13 +46,13 @@ var API_EXTS = [".ts", ".js"];
|
|
|
45
46
|
var DEBOUNCE_MS = 60;
|
|
46
47
|
var EVENT_DEDUP_MS = 500;
|
|
47
48
|
var EVENT_TTL_MS = 2e3;
|
|
49
|
+
var APP_DIR = import_path.default.join(process.cwd(), "src/app");
|
|
50
|
+
var API_DIR = import_path.default.join(process.cwd(), "src/app/api");
|
|
48
51
|
function norm(p) {
|
|
49
52
|
return p.replace(/\\/g, "/");
|
|
50
53
|
}
|
|
51
54
|
function isInDir(file, dir) {
|
|
52
|
-
|
|
53
|
-
const nDir = norm(dir).replace(/\/$/, "");
|
|
54
|
-
return nFile.startsWith(nDir + "/") || nFile === nDir;
|
|
55
|
+
return norm(file).startsWith(norm(dir));
|
|
55
56
|
}
|
|
56
57
|
function readTsconfigAliases() {
|
|
57
58
|
const aliases = {};
|
|
@@ -104,20 +105,20 @@ function getAppFile() {
|
|
|
104
105
|
const ts = import_path.default.join(process.cwd(), "src/App.tsx");
|
|
105
106
|
return import_fs.default.existsSync(ts) ? ts : import_path.default.join(process.cwd(), "src/App.jsx");
|
|
106
107
|
}
|
|
107
|
-
function resolveLayoutChain(pageDir
|
|
108
|
+
function resolveLayoutChain(pageDir) {
|
|
108
109
|
const chain = [];
|
|
109
110
|
let current = pageDir;
|
|
110
111
|
while (true) {
|
|
111
112
|
const layout = findFile(current, LAYOUT_FILES);
|
|
112
113
|
if (layout) chain.unshift(import_path.default.join(current, layout));
|
|
113
|
-
if (import_path.default.resolve(current) === import_path.default.resolve(
|
|
114
|
+
if (import_path.default.resolve(current) === import_path.default.resolve(APP_DIR)) break;
|
|
114
115
|
const parent = import_path.default.dirname(current);
|
|
115
116
|
if (parent === current) break;
|
|
116
117
|
current = parent;
|
|
117
118
|
}
|
|
118
119
|
return chain;
|
|
119
120
|
}
|
|
120
|
-
function scanRoutes(dir,
|
|
121
|
+
function scanRoutes(dir, baseRoute = "") {
|
|
121
122
|
const routes = [];
|
|
122
123
|
if (!import_fs.default.existsSync(dir)) return routes;
|
|
123
124
|
let entries;
|
|
@@ -135,7 +136,7 @@ function scanRoutes(dir, appDir, baseRoute = "") {
|
|
|
135
136
|
routes.push({
|
|
136
137
|
routePath: `${baseRoute}/${base}`,
|
|
137
138
|
filePath: import_path.default.join(dir, entry.name),
|
|
138
|
-
layouts: resolveLayoutChain(dir
|
|
139
|
+
layouts: resolveLayoutChain(dir),
|
|
139
140
|
dynamic: false
|
|
140
141
|
});
|
|
141
142
|
}
|
|
@@ -151,11 +152,11 @@ function scanRoutes(dir, appDir, baseRoute = "") {
|
|
|
151
152
|
routes.push({
|
|
152
153
|
routePath,
|
|
153
154
|
filePath: import_path.default.join(fullPath, pageFile),
|
|
154
|
-
layouts: resolveLayoutChain(fullPath
|
|
155
|
+
layouts: resolveLayoutChain(fullPath),
|
|
155
156
|
dynamic: isDynamic
|
|
156
157
|
});
|
|
157
158
|
}
|
|
158
|
-
routes.push(...scanRoutes(fullPath,
|
|
159
|
+
routes.push(...scanRoutes(fullPath, routePath));
|
|
159
160
|
}
|
|
160
161
|
return routes;
|
|
161
162
|
}
|
|
@@ -211,15 +212,15 @@ function renderChain(layouts, routesInChain, layoutNames, pageNames, layoutTitle
|
|
|
211
212
|
`${pad}</Route>`
|
|
212
213
|
].join("\n");
|
|
213
214
|
}
|
|
214
|
-
function generateApp(
|
|
215
|
+
function generateApp() {
|
|
215
216
|
const aliases = readTsconfigAliases();
|
|
216
|
-
const routes = scanRoutes(
|
|
217
|
-
const rootPage = findFile(
|
|
217
|
+
const routes = scanRoutes(APP_DIR);
|
|
218
|
+
const rootPage = findFile(APP_DIR, PAGE_FILES);
|
|
218
219
|
if (rootPage) {
|
|
219
220
|
routes.unshift({
|
|
220
221
|
routePath: "/",
|
|
221
|
-
filePath: import_path.default.join(
|
|
222
|
-
layouts: resolveLayoutChain(
|
|
222
|
+
filePath: import_path.default.join(APP_DIR, rootPage),
|
|
223
|
+
layouts: resolveLayoutChain(APP_DIR),
|
|
223
224
|
dynamic: false
|
|
224
225
|
});
|
|
225
226
|
}
|
|
@@ -234,8 +235,8 @@ function generateApp(appDir) {
|
|
|
234
235
|
if (a.dynamic !== b.dynamic) return a.dynamic ? 1 : -1;
|
|
235
236
|
return a.routePath.length - b.routePath.length;
|
|
236
237
|
});
|
|
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(
|
|
238
|
+
const notFoundFile = NOT_FOUND_FILES.find((f) => import_fs.default.existsSync(import_path.default.join(APP_DIR, f)));
|
|
239
|
+
const notFound = notFoundFile && hasDefaultExport(import_path.default.join(APP_DIR, notFoundFile)) ? notFoundFile : void 0;
|
|
239
240
|
const allLayouts = /* @__PURE__ */ new Set();
|
|
240
241
|
for (const r of validRoutes) r.layouts.forEach((l) => {
|
|
241
242
|
if (isUsableLayout(l)) allLayouts.add(l);
|
|
@@ -256,7 +257,7 @@ function generateApp(appDir) {
|
|
|
256
257
|
for (const [fp, name] of layoutNames)
|
|
257
258
|
lazyImports.push(`const ${name} = React.lazy(() => import('${toImportPath(fp, aliases)}'));`);
|
|
258
259
|
if (notFound)
|
|
259
|
-
lazyImports.push(`const NotFound = React.lazy(() => import('${toImportPath(import_path.default.join(
|
|
260
|
+
lazyImports.push(`const NotFound = React.lazy(() => import('${toImportPath(import_path.default.join(APP_DIR, notFound), aliases)}'));`);
|
|
260
261
|
const emittedPages = /* @__PURE__ */ new Set();
|
|
261
262
|
for (const r of validRoutes) {
|
|
262
263
|
if (emittedPages.has(r.filePath)) continue;
|
|
@@ -318,7 +319,6 @@ function Spinner() {
|
|
|
318
319
|
}
|
|
319
320
|
|
|
320
321
|
// 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
322
|
function TitleSetter({ title }: { title: string }) {
|
|
323
323
|
React.useEffect(() => { document.title = title; }, [title]);
|
|
324
324
|
return null;
|
|
@@ -348,12 +348,12 @@ ${catchAll}
|
|
|
348
348
|
}
|
|
349
349
|
`;
|
|
350
350
|
}
|
|
351
|
-
function parseAppMetadata(
|
|
352
|
-
const layout = findFile(
|
|
351
|
+
function parseAppMetadata() {
|
|
352
|
+
const layout = findFile(APP_DIR, LAYOUT_FILES);
|
|
353
353
|
if (!layout) return {};
|
|
354
354
|
let src = "";
|
|
355
355
|
try {
|
|
356
|
-
src = import_fs.default.readFileSync(import_path.default.join(
|
|
356
|
+
src = import_fs.default.readFileSync(import_path.default.join(APP_DIR, layout), "utf8");
|
|
357
357
|
} catch {
|
|
358
358
|
return {};
|
|
359
359
|
}
|
|
@@ -515,7 +515,7 @@ function extractArrayRaw(source, key) {
|
|
|
515
515
|
}
|
|
516
516
|
return void 0;
|
|
517
517
|
}
|
|
518
|
-
function scanApiRoutes(dir, baseRoute = "") {
|
|
518
|
+
function scanApiRoutes(dir = API_DIR, baseRoute = "") {
|
|
519
519
|
const routes = [];
|
|
520
520
|
if (!import_fs.default.existsSync(dir)) return routes;
|
|
521
521
|
let entries;
|
|
@@ -544,136 +544,34 @@ function scanApiRoutes(dir, baseRoute = "") {
|
|
|
544
544
|
}
|
|
545
545
|
return routes;
|
|
546
546
|
}
|
|
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
|
-
`);
|
|
547
|
+
function generateNitroApiEntry() {
|
|
548
|
+
const routes = scanApiRoutes();
|
|
549
|
+
if (!routes.length) return "";
|
|
550
|
+
const imports = [];
|
|
551
|
+
const registrations = [];
|
|
552
|
+
routes.forEach((route, i) => {
|
|
553
|
+
const relPath = import_path.default.relative(
|
|
554
|
+
import_path.default.join(process.cwd(), "server"),
|
|
555
|
+
route.filePath
|
|
556
|
+
).replace(/\\/g, "/").replace(/\.(ts|js)$/, "");
|
|
557
|
+
imports.push(`import handler${i} from './${relPath}';`);
|
|
558
|
+
registrations.push(
|
|
559
|
+
` app.all('${route.routePath}', async (c) => { try { return await handler${i}(c.req.raw); } catch (e) { return c.json({ error: e.message }, 500); } });`
|
|
560
|
+
);
|
|
561
|
+
});
|
|
562
|
+
return `// Auto-generated by bini-router
|
|
563
|
+
import { Hono } from 'hono';
|
|
564
|
+
${imports.join("\n")}
|
|
565
|
+
|
|
566
|
+
const app = new Hono();
|
|
567
|
+
${registrations.join("\n")}
|
|
568
|
+
|
|
569
|
+
export default app;
|
|
570
|
+
`;
|
|
669
571
|
}
|
|
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");
|
|
572
|
+
function biniroute() {
|
|
674
573
|
let debounceTimer = null;
|
|
675
574
|
let lastGeneratedCode = "";
|
|
676
|
-
let honoCache = null;
|
|
677
575
|
const eventLog = /* @__PURE__ */ new Map();
|
|
678
576
|
function shouldProcess(file, event) {
|
|
679
577
|
const key = `${file}:${event}`;
|
|
@@ -688,19 +586,18 @@ function biniroute(options = {}) {
|
|
|
688
586
|
const base = import_path.default.basename(f, import_path.default.extname(f));
|
|
689
587
|
const ext = import_path.default.extname(f);
|
|
690
588
|
if (!SUPPORTED_EXTS.includes(ext)) return false;
|
|
691
|
-
if (!isInDir(nf, norm(
|
|
692
|
-
if (isInDir(nf, norm(
|
|
589
|
+
if (!isInDir(nf, norm(APP_DIR))) return false;
|
|
590
|
+
if (isInDir(nf, norm(API_DIR))) return false;
|
|
693
591
|
if (base.startsWith("_")) return false;
|
|
694
592
|
return true;
|
|
695
593
|
}
|
|
696
594
|
function isApiFile(f) {
|
|
697
595
|
const nf = norm(f);
|
|
698
|
-
return isInDir(nf, norm(
|
|
596
|
+
return isInDir(nf, norm(API_DIR)) && API_EXTS.includes(import_path.default.extname(f));
|
|
699
597
|
}
|
|
700
598
|
function applyApp() {
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
const code = generateApp(dir);
|
|
599
|
+
if (!import_fs.default.existsSync(APP_DIR)) return null;
|
|
600
|
+
const code = generateApp();
|
|
704
601
|
if (code === lastGeneratedCode) return null;
|
|
705
602
|
import_fs.default.writeFileSync(getAppFile(), code, "utf8");
|
|
706
603
|
lastGeneratedCode = code;
|
|
@@ -718,19 +615,53 @@ function biniroute(options = {}) {
|
|
|
718
615
|
function addSpaFallback(server) {
|
|
719
616
|
server.middlewares.use((req, res, next) => {
|
|
720
617
|
const url = req.url;
|
|
721
|
-
if (url.
|
|
618
|
+
if (url.includes(".")) return next();
|
|
722
619
|
req.url = "/index.html";
|
|
723
620
|
next();
|
|
724
621
|
});
|
|
725
622
|
}
|
|
623
|
+
function buildApiWithNitro() {
|
|
624
|
+
const routes = scanApiRoutes();
|
|
625
|
+
if (routes.length === 0) return;
|
|
626
|
+
console.log("\n\u{1F4E6} Building API routes with Nitro...");
|
|
627
|
+
const serverDir = import_path.default.join(process.cwd(), "server");
|
|
628
|
+
const apiDir = import_path.default.join(serverDir, "api");
|
|
629
|
+
import_fs.default.mkdirSync(apiDir, { recursive: true });
|
|
630
|
+
const entry = generateNitroApiEntry();
|
|
631
|
+
import_fs.default.writeFileSync(import_path.default.join(serverDir, "index.ts"), entry, "utf8");
|
|
632
|
+
routes.forEach((route) => {
|
|
633
|
+
const destPath = import_path.default.join(apiDir, import_path.default.basename(route.filePath));
|
|
634
|
+
import_fs.default.copyFileSync(route.filePath, destPath);
|
|
635
|
+
});
|
|
636
|
+
const nitroConfig = import_path.default.join(process.cwd(), "nitro.config.ts");
|
|
637
|
+
if (!import_fs.default.existsSync(nitroConfig)) {
|
|
638
|
+
const config = `import { defineNitroConfig } from 'nitropack/config';
|
|
639
|
+
|
|
640
|
+
export default defineNitroConfig({
|
|
641
|
+
handlers: [
|
|
642
|
+
{ route: '/api/**', handler: '~/server/index' }
|
|
643
|
+
]
|
|
644
|
+
});
|
|
645
|
+
`;
|
|
646
|
+
import_fs.default.writeFileSync(nitroConfig, config, "utf8");
|
|
647
|
+
}
|
|
648
|
+
try {
|
|
649
|
+
(0, import_child_process.execSync)("npx nitropack build", {
|
|
650
|
+
stdio: "inherit",
|
|
651
|
+
cwd: process.cwd()
|
|
652
|
+
});
|
|
653
|
+
console.log("\u2705 API routes built successfully");
|
|
654
|
+
} catch (error) {
|
|
655
|
+
console.error("\u274C API build failed:", error);
|
|
656
|
+
process.exit(1);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
726
659
|
return {
|
|
727
660
|
name: "bini-router",
|
|
728
661
|
enforce: "pre",
|
|
729
|
-
// Strip `export const metadata = {...}` from all src/app files before
|
|
730
|
-
// @vitejs/plugin-react's Fast Refresh transform sees them.
|
|
731
662
|
transform(code, id) {
|
|
732
663
|
const nid = norm(id);
|
|
733
|
-
if (!isInDir(nid, norm(
|
|
664
|
+
if (!isInDir(nid, norm(APP_DIR))) return;
|
|
734
665
|
const ext = import_path.default.extname(id);
|
|
735
666
|
if (!SUPPORTED_EXTS.includes(ext)) return;
|
|
736
667
|
if (!code.includes("export const metadata")) return;
|
|
@@ -765,89 +696,83 @@ function biniroute(options = {}) {
|
|
|
765
696
|
buildStart() {
|
|
766
697
|
applyApp();
|
|
767
698
|
},
|
|
699
|
+
// This runs after Vite build is complete
|
|
768
700
|
closeBundle() {
|
|
769
|
-
|
|
701
|
+
buildApiWithNitro();
|
|
770
702
|
},
|
|
771
703
|
buildEnd() {
|
|
772
|
-
honoCache = null;
|
|
773
704
|
if (debounceTimer) {
|
|
774
705
|
clearTimeout(debounceTimer);
|
|
775
706
|
debounceTimer = null;
|
|
776
707
|
}
|
|
777
708
|
},
|
|
778
709
|
async configureServer(server) {
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
if (!import_fs.default.existsSync(appDir)) return;
|
|
782
|
-
server.watcher.add(appDir);
|
|
710
|
+
if (!import_fs.default.existsSync(APP_DIR)) return;
|
|
711
|
+
server.watcher.add(APP_DIR);
|
|
783
712
|
server.watcher.on("add", (f) => isPageFile(f) && shouldProcess(f, "add") && scheduleRegen(server, 300));
|
|
784
713
|
server.watcher.on("unlink", (f) => isPageFile(f) && shouldProcess(f, "unlink") && scheduleRegen(server));
|
|
785
714
|
server.watcher.on("change", (f) => isPageFile(f) && shouldProcess(f, "change") && scheduleRegen(server));
|
|
786
715
|
server.watcher.on("change", (f) => {
|
|
787
716
|
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(
|
|
717
|
+
const inAppRoot = import_path.default.resolve(import_path.default.dirname(f)) === import_path.default.resolve(APP_DIR);
|
|
789
718
|
if (!inAppRoot || base !== "layout") return;
|
|
790
719
|
server.moduleGraph.invalidateAll();
|
|
791
720
|
server.ws.send({ type: "full-reload", path: "*" });
|
|
792
721
|
});
|
|
793
722
|
server.watcher.on("addDir", (d) => {
|
|
794
|
-
|
|
795
|
-
if (!isInDir(nd, norm(appDir)) || d.includes("node_modules") || isInDir(nd, norm(apiDir))) return;
|
|
723
|
+
if (!isInDir(norm(d), norm(APP_DIR)) || d.includes("node_modules") || d.includes("api")) return;
|
|
796
724
|
setTimeout(() => PAGE_FILES.some((f) => import_fs.default.existsSync(import_path.default.join(d, f))) && scheduleRegen(server), 300);
|
|
797
725
|
});
|
|
798
726
|
server.watcher.on("unlinkDir", (d) => {
|
|
799
|
-
|
|
800
|
-
if (isInDir(nd, norm(appDir)) && !d.includes("node_modules") && !isInDir(nd, norm(apiDir)))
|
|
727
|
+
if (isInDir(norm(d), norm(APP_DIR)) && !d.includes("node_modules") && !d.includes("api"))
|
|
801
728
|
scheduleRegen(server);
|
|
802
729
|
});
|
|
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
|
-
(v) => {
|
|
822
|
-
honoCache = v;
|
|
730
|
+
if (import_fs.default.existsSync(API_DIR)) {
|
|
731
|
+
server.middlewares.use("/api", async (req, res, next) => {
|
|
732
|
+
try {
|
|
733
|
+
const { Hono } = await import("hono");
|
|
734
|
+
const app = new Hono();
|
|
735
|
+
const routes = scanApiRoutes();
|
|
736
|
+
for (const route of routes) {
|
|
737
|
+
const mod = await import(route.filePath + "?t=" + Date.now());
|
|
738
|
+
const handler = mod.default;
|
|
739
|
+
if (typeof handler === "function") {
|
|
740
|
+
app.all(route.routePath, async (c) => {
|
|
741
|
+
try {
|
|
742
|
+
return await handler(c.req.raw);
|
|
743
|
+
} catch (e) {
|
|
744
|
+
return c.json({ error: e.message }, 500);
|
|
745
|
+
}
|
|
746
|
+
});
|
|
747
|
+
}
|
|
823
748
|
}
|
|
824
|
-
|
|
825
|
-
|
|
749
|
+
const url = `http://${req.headers.host}${req.url}`;
|
|
750
|
+
const chunks = [];
|
|
751
|
+
for await (const chunk of req) chunks.push(chunk);
|
|
752
|
+
const body = chunks.length > 0 ? Buffer.concat(chunks) : void 0;
|
|
753
|
+
const webReq = new Request(url, {
|
|
754
|
+
method: req.method,
|
|
755
|
+
headers: req.headers,
|
|
756
|
+
body
|
|
757
|
+
});
|
|
758
|
+
const webRes = await app.fetch(webReq);
|
|
759
|
+
res.statusCode = webRes.status;
|
|
760
|
+
webRes.headers.forEach((value, key) => res.setHeader(key, value));
|
|
761
|
+
const buffer = await webRes.arrayBuffer();
|
|
762
|
+
res.end(Buffer.from(buffer));
|
|
763
|
+
} catch (e) {
|
|
764
|
+
next(e);
|
|
765
|
+
}
|
|
766
|
+
});
|
|
826
767
|
}
|
|
827
768
|
},
|
|
828
769
|
async configurePreviewServer(server) {
|
|
829
770
|
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
771
|
},
|
|
847
772
|
transformIndexHtml: {
|
|
848
773
|
order: "pre",
|
|
849
774
|
handler(html) {
|
|
850
|
-
const meta = parseAppMetadata(
|
|
775
|
+
const meta = parseAppMetadata();
|
|
851
776
|
const title = meta.title ?? "Bini App";
|
|
852
777
|
const vp = meta.viewport ?? "width=device-width, initial-scale=1.0";
|
|
853
778
|
const lines = [];
|
|
@@ -875,18 +800,18 @@ function biniroute(options = {}) {
|
|
|
875
800
|
lines.push(`<link rel="apple-touch-icon" href="${entry.url}"${sizes}${type} />`);
|
|
876
801
|
}
|
|
877
802
|
if (meta.openGraph?.title) {
|
|
878
|
-
lines.push(`<meta property="og:type"
|
|
879
|
-
lines.push(`<meta property="og:title"
|
|
803
|
+
lines.push(`<meta property="og:type" content="${meta.openGraph.type ?? "website"}" />`);
|
|
804
|
+
lines.push(`<meta property="og:title" content="${meta.openGraph.title}" />`);
|
|
880
805
|
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"
|
|
806
|
+
if (meta.openGraph.url) lines.push(`<meta property="og:url" content="${meta.openGraph.url}" />`);
|
|
807
|
+
if (meta.openGraph.image) lines.push(`<meta property="og:image" content="${meta.openGraph.image}" />`);
|
|
883
808
|
}
|
|
884
809
|
if (meta.twitter?.title) {
|
|
885
|
-
lines.push(`<meta name="twitter:card"
|
|
886
|
-
lines.push(`<meta name="twitter:title"
|
|
810
|
+
lines.push(`<meta name="twitter:card" content="${meta.twitter.card ?? "summary_large_image"}" />`);
|
|
811
|
+
lines.push(`<meta name="twitter:title" content="${meta.twitter.title}" />`);
|
|
887
812
|
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"
|
|
813
|
+
if (meta.twitter.creator) lines.push(`<meta name="twitter:creator" content="${meta.twitter.creator}" />`);
|
|
814
|
+
if (meta.twitter.image) lines.push(`<meta name="twitter:image" content="${meta.twitter.image}" />`);
|
|
890
815
|
}
|
|
891
816
|
const injected = lines.map((l) => ` ${l}`).join("\n");
|
|
892
817
|
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}
|