UNPKG

rsshub

Version:
117 lines (116 loc) 4.43 kB
import { i as registerRssRoutes, r as registerApiRoutes, t as applyModulesToNamespaces } from "./registry-helpers-DAzZYtjh.mjs"; import path from "node:path"; import { pathToFileURL } from "node:url"; import { Hono } from "hono"; import fs from "node:fs"; //#region lib/utils/directory-import.ts const VALID_IMPORT_EXTENSIONS = /* @__PURE__ */ new Set([ ".js", ".mjs", ".cjs", ".ts", ".tsx", ".json" ]); const readDirectory = (targetDirectoryPath, includeSubdirectories) => { const entries = fs.readdirSync(targetDirectoryPath, { withFileTypes: true }); const files = []; for (const entry of entries) { const fullPath = path.join(targetDirectoryPath, entry.name); if (entry.isDirectory()) { if (includeSubdirectories) files.push(...readDirectory(fullPath, includeSubdirectories)); continue; } if (entry.isFile()) files.push(fullPath); } return files; }; const directoryImport = async ({ targetDirectoryPath, importPattern = /.*/, includeSubdirectories = true }) => { const modules = {}; const filesPaths = readDirectory(targetDirectoryPath, includeSubdirectories); await Promise.all(filesPaths.map(async (filePath) => { const { ext: fileExtension } = path.parse(filePath); const isValidModuleExtension = VALID_IMPORT_EXTENSIONS.has(fileExtension); const isDeclarationFile = filePath.endsWith(".d.ts") || filePath.endsWith(".d.tsx"); const isValidFilePath = importPattern.test(filePath); if (!isValidModuleExtension || isDeclarationFile || !isValidFilePath) return; const relativeModulePath = filePath.slice(targetDirectoryPath.length); modules[relativeModulePath] = await import(pathToFileURL(filePath).href); })); return modules; }; //#endregion //#region lib/registry-dev.ts /** * Dev-only lazy route loading. Hono routers reject new routes once an app has matched a request * ("matcher is already built"), so each top-level routes directory gets its own self-contained * sub-app, built on first request and dispatched from the middleware. */ function createDevRegistry({ routesDirectory, namespaces }) { const subApps = /* @__PURE__ */ new Map(); const outerContexts = /* @__PURE__ */ new WeakMap(); const topDirectories = new Set(fs.readdirSync(routesDirectory, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name)); const bridge = async (ctx, next) => { const outer = outerContexts.get(ctx.req.raw); if (outer) for (const [key, value] of Object.entries(outer.var)) ctx.set(key, value); await next(); if (outer) for (const [key, value] of Object.entries(ctx.var)) outer.set(key, value); const vars = ctx.var; if (!ctx.finalized && (vars.data || vars.apiData)) ctx.res = new Response(null, { status: 204 }); }; const loadTopDirectory = async (name) => { const modules = await directoryImport({ targetDirectoryPath: path.join(routesDirectory, name), importPattern: /\.tsx?$/ }); const prefixed = {}; for (const key in modules) prefixed[`/${name}${key}`] = modules[key]; applyModulesToNamespaces(prefixed, namespaces); const subApp = new Hono(); subApp.onError((error) => { throw error; }); subApp.use("*", bridge); const subset = Object.fromEntries(Object.entries(namespaces).filter(([key]) => key === name || key.startsWith(`${name}/`))); registerRssRoutes(subApp, subset); registerApiRoutes(subApp, subset); return subApp; }; const ensureTopDirectory = async (name) => { let promise = subApps.get(name); if (!promise) { promise = loadTopDirectory(name); subApps.set(name, promise); } try { return await promise; } catch (error) { subApps.delete(name); throw error; } }; const middleware = async (ctx, next) => { const segments = ctx.req.path.split("/").filter(Boolean); const candidate = segments[0] === "api" ? segments[1] : segments[0]; if (!candidate || !topDirectories.has(candidate)) return next(); const subApp = await ensureTopDirectory(candidate); outerContexts.set(ctx.req.raw, ctx); try { const response = await subApp.fetch(ctx.req.raw); if (ctx.get("data") || ctx.get("apiData")) return; if (response.status === 404) return next(); return response; } finally { outerContexts.delete(ctx.req.raw); } }; const ensureAllLoaded = async () => { await Promise.all([...topDirectories].map((name) => ensureTopDirectory(name))); }; return { middleware, ensureAllLoaded }; } //#endregion export { createDevRegistry };