From 3793f89d04558c499e3dd700b77634c829aa518b Mon Sep 17 00:00:00 2001 From: Jeffrey Alan Scudder Date: Mon, 30 Mar 2026 04:40:03 +0000 Subject: [PATCH] Fix lith news route resolution --- lith/route-resolution.mjs | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ lith/server.mjs | 44 ++------------------------------------------ tests/lith-route-resolution.test.mjs | 21 +++++++++++++++++++++ 3 file(s) changed, 82 insertion(s)(+), 42 deletion(s)(-) diff --git a/lith/route-resolution.mjs b/lith/route-resolution.mjs new file mode 100644 --- /dev/null +++ b/lith/route-resolution.mjs @@ -0,0 +1,59 @@ +const ROUTE_ALIASES = { + "verify-password": "verify-builds-password", + "pack-telemetry": "bundle-telemetry", + "pack-telemetry-query": "bundle-telemetry-query", + "track-tape": "track-media", + "logo.png": "logo", +}; + +const NESTED_ROUTES = { + "chat/messages": "chat-messages", + "chat/heart": "chat-heart", + "auth/cli-callback": "auth-cli-callback", + "news/toll": "news-toll", +}; + +const NEWS_API_ROUTES = new Set([ + "posts", + "updates", + "submit", + "comment", + "vote", + "delete", + "unfurl", +]); + +function normalizeRest(rest) { + if (Array.isArray(rest)) return rest.join("/"); + if (typeof rest === "string") return rest; + return ""; +} + +function resolveFunctionName(fn, rest, functions = {}) { + const normalizedRest = normalizeRest(rest); + + if (normalizedRest) { + const nested = `${fn}/${normalizedRest}`; + for (const [pattern, target] of Object.entries(NESTED_ROUTES)) { + if (nested === pattern || nested.startsWith(`${pattern}/`)) { + return target; + } + } + } + + if (ROUTE_ALIASES[fn]) return ROUTE_ALIASES[fn]; + + if (fn === "news" && normalizedRest) { + const firstSegment = normalizedRest.split("/")[0]; + return NEWS_API_ROUTES.has(firstSegment) ? "news-api" : "news"; + } + + if (fn === "ff1" && normalizedRest) { + const subFn = `ff1-${normalizedRest.split("/")[0]}`; + if (functions[subFn]) return subFn; + } + + return fn; +} + +export { resolveFunctionName }; diff --git a/lith/server.mjs b/lith/server.mjs --- a/lith/server.mjs +++ b/lith/server.mjs @@ -20,6 +20,7 @@ import { join, dirname } from "path"; import { fileURLToPath } from "url"; import { createServer as createHttpsServer } from "https"; import { createServer as createHttpServer } from "http"; +import { resolveFunctionName } from "./route-resolution.mjs"; const __dirname = dirname(fileURLToPath(import.meta.url)); const SYSTEM = join(__dirname, "..", "system"); @@ -282,50 +283,9 @@ res.status(500).send("Internal Server Error"); } } -// --- Route aliases (from netlify.toml where URL path ≠ function name) --- -const ROUTE_ALIASES = { - "verify-password": "verify-builds-password", - "pack-telemetry": "bundle-telemetry", - "pack-telemetry-query": "bundle-telemetry-query", - "track-tape": "track-media", - "logo.png": "logo", -}; - -// --- Nested API routes (e.g. /api/chat/messages → chat-messages) --- -const NESTED_ROUTES = { - "chat/messages": "chat-messages", - "chat/heart": "chat-heart", - "auth/cli-callback": "auth-cli-callback", - "news/toll": "news-toll", - // /api/news/* (posts, updates, submit, etc.) → news-api function - "news/": "news-api", -}; - // Resolve function name from URL params function resolveFunction(req) { - const fn = req.params.fn; - const rest = req.params.rest; - - // Check nested routes first (e.g., /api/chat/messages) - if (rest) { - const nested = `${fn}/${rest}`; - for (const [pattern, target] of Object.entries(NESTED_ROUTES)) { - if (nested === pattern || nested.startsWith(pattern)) { - return target; - } - } - } - - // Check aliases - if (ROUTE_ALIASES[fn]) return ROUTE_ALIASES[fn]; - - // ff1 dynamic routing: /api/ff1-proxy, /api/ff1-pair, /api/ff1-devices - if (fn === "ff1" && rest) { - const subFn = `ff1-${rest.split("/")[0]}`; - if (functions[subFn]) return subFn; - } - - return fn; + return resolveFunctionName(req.params.fn, req.params.rest, functions); } // --- Function handler (updated to use resolveFunction) --- diff --git a/tests/lith-route-resolution.test.mjs b/tests/lith-route-resolution.test.mjs new file mode 100644 --- /dev/null +++ b/tests/lith-route-resolution.test.mjs @@ -0,0 +1,21 @@ +import assert from "node:assert/strict"; +import { resolveFunctionName } from "../lith/route-resolution.mjs"; + +function run() { + assert.equal(resolveFunctionName("news", undefined), "news"); + assert.equal(resolveFunctionName("news", "posts"), "news-api"); + assert.equal(resolveFunctionName("news", "submit"), "news-api"); + assert.equal(resolveFunctionName("news", "new"), "news"); + assert.equal(resolveFunctionName("news", "report"), "news"); + assert.equal(resolveFunctionName("news", "nwhh"), "news"); + assert.equal(resolveFunctionName("news", ["nwhh"]), "news"); + assert.equal(resolveFunctionName("news", "item/abcd"), "news"); + assert.equal(resolveFunctionName("news", "toll"), "news-toll"); + assert.equal(resolveFunctionName("chat", "messages"), "chat-messages"); + assert.equal(resolveFunctionName("verify-password"), "verify-builds-password"); + assert.equal(resolveFunctionName("ff1", "proxy/status", { "ff1-proxy": true }), "ff1-proxy"); + + console.log("✅ lith route resolution tests passed"); +} + +run(); -- tangled.sh