rsshub
Version:
Make RSS Great Again!
117 lines (116 loc) • 4.43 kB
JavaScript
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 };