mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-12 06:06:37 +03:00
Top nav was at six items and growing. The anti-patterns catalog and the visual-mode overlay demo were always two views of the same subject (the rule set and seeing it caught in place), so collapsing them into one page reduces nav weight and puts the catalog + live demo next to each other. Four sections in one scroll: See it (iframe demo), Try it live (specimen gallery), The catalog (full rule list), Run it yourself (invocation methods). Sidebar TOC nests the catalog sections under "The catalog" for deep linking. 301s from the old URLs preserve external links. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
216 lines
8.1 KiB
JavaScript
216 lines
8.1 KiB
JavaScript
import { serve, file } from "bun";
|
|
import path from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import homepage from "../public/index.html";
|
|
import privacy from "../public/privacy.html";
|
|
import {
|
|
getSkills,
|
|
getCommands,
|
|
getCommandSource,
|
|
getPatterns,
|
|
handleFileDownload,
|
|
handleBundleDownload
|
|
} from "./lib/api-handlers.js";
|
|
import { generateSubPages } from "../scripts/build-sub-pages.js";
|
|
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
const __dirname = path.dirname(__filename);
|
|
const ROOT_DIR = path.resolve(__dirname, "..");
|
|
|
|
// Pre-generate sub-pages so dev + prod share the same output shape.
|
|
console.log("📝 Generating sub-pages for dev server...");
|
|
const { files: subPageFiles } = await generateSubPages(ROOT_DIR);
|
|
console.log(`✓ Generated ${subPageFiles.length} sub-page(s)`);
|
|
|
|
// Helper: serve a generated HTML file by absolute path, 404 if missing.
|
|
async function serveGenerated(pagePath) {
|
|
const f = file(pagePath);
|
|
if (!(await f.exists())) return new Response("Not Found", { status: 404 });
|
|
return new Response(f, {
|
|
headers: {
|
|
"Content-Type": "text/html;charset=utf-8",
|
|
"X-Content-Type-Options": "nosniff",
|
|
"X-Frame-Options": "DENY",
|
|
},
|
|
});
|
|
}
|
|
|
|
const server = serve({
|
|
port: process.env.PORT || 3000,
|
|
|
|
routes: {
|
|
"/": homepage,
|
|
"/privacy": privacy,
|
|
|
|
// Legacy URL redirects (kept stable for external links and existing users).
|
|
"/cheatsheet": Response.redirect("/docs", 301),
|
|
"/gallery": Response.redirect("/slop#try-it-live", 301),
|
|
"/skills": Response.redirect("/docs", 301),
|
|
"/skills/:id": (req) => Response.redirect(`/docs/${req.params.id}`, 301),
|
|
"/anti-patterns": Response.redirect("/slop#catalog", 301),
|
|
"/visual-mode": Response.redirect("/slop#see-it", 301),
|
|
|
|
// Generated sub-pages — served directly from the pre-generated files
|
|
"/docs": () => serveGenerated(path.join(ROOT_DIR, "public/docs/index.html")),
|
|
"/docs/:id": (req) => {
|
|
const id = req.params.id.replace(/[^a-z0-9-]/gi, "");
|
|
return serveGenerated(path.join(ROOT_DIR, `public/docs/${id}.html`));
|
|
},
|
|
"/slop": () => serveGenerated(path.join(ROOT_DIR, "public/slop/index.html")),
|
|
"/live-mode": () => serveGenerated(path.join(ROOT_DIR, "public/live-mode/index.html")),
|
|
"/designing": () => serveGenerated(path.join(ROOT_DIR, "public/designing/index.html")),
|
|
"/tutorials": () => serveGenerated(path.join(ROOT_DIR, "public/tutorials/index.html")),
|
|
"/tutorials/:slug": (req) => {
|
|
const slug = req.params.slug.replace(/[^a-z0-9-]/gi, "");
|
|
return serveGenerated(path.join(ROOT_DIR, `public/tutorials/${slug}.html`));
|
|
},
|
|
|
|
// Static assets - all public subdirectories
|
|
"/assets/*": async (req) => {
|
|
const url = new URL(req.url);
|
|
if (url.pathname.includes('..')) return new Response("Bad Request", { status: 400 });
|
|
const filePath = `./public${url.pathname}`;
|
|
const assetFile = file(filePath);
|
|
if (await assetFile.exists()) {
|
|
return new Response(assetFile, {
|
|
headers: { "X-Content-Type-Options": "nosniff", "X-Frame-Options": "DENY" }
|
|
});
|
|
}
|
|
return new Response("Not Found", { status: 404 });
|
|
},
|
|
"/css/*": async (req) => {
|
|
const url = new URL(req.url);
|
|
if (url.pathname.includes('..')) return new Response("Bad Request", { status: 400 });
|
|
const filePath = `./public${url.pathname}`;
|
|
const assetFile = file(filePath);
|
|
if (await assetFile.exists()) {
|
|
return new Response(assetFile, {
|
|
headers: { "Content-Type": "text/css", "X-Content-Type-Options": "nosniff", "X-Frame-Options": "DENY" }
|
|
});
|
|
}
|
|
return new Response("Not Found", { status: 404 });
|
|
},
|
|
"/js/*": async (req) => {
|
|
const url = new URL(req.url);
|
|
if (url.pathname.includes('..')) return new Response("Bad Request", { status: 400 });
|
|
// Check public/js/ first, then fall back to built artifacts
|
|
const headers = { "Content-Type": "application/javascript", "X-Content-Type-Options": "nosniff", "X-Frame-Options": "DENY" };
|
|
const publicFile = file(`./public${url.pathname}`);
|
|
if (await publicFile.exists()) return new Response(publicFile, { headers });
|
|
// Browser detector served from impeccable package
|
|
if (url.pathname === '/js/detect-antipatterns-browser.js') {
|
|
const pkgFile = file('./src/detect-antipatterns-browser.js');
|
|
if (await pkgFile.exists()) return new Response(pkgFile, { headers });
|
|
}
|
|
return new Response("Not Found", { status: 404 });
|
|
},
|
|
// Test fixtures (for browser visual testing)
|
|
"/fixtures/*": async (req) => {
|
|
const url = new URL(req.url);
|
|
if (url.pathname.includes('..')) return new Response("Bad Request", { status: 400 });
|
|
const filePath = `./tests${url.pathname}`;
|
|
const assetFile = file(filePath);
|
|
if (await assetFile.exists()) {
|
|
const ext = url.pathname.split('.').pop();
|
|
const types = { html: 'text/html', css: 'text/css', js: 'application/javascript' };
|
|
return new Response(assetFile, {
|
|
headers: { "Content-Type": types[ext] || "application/octet-stream", "X-Content-Type-Options": "nosniff" }
|
|
});
|
|
}
|
|
return new Response("Not Found", { status: 404 });
|
|
},
|
|
"/antipattern-images/*": async (req) => {
|
|
const url = new URL(req.url);
|
|
if (url.pathname.includes('..')) return new Response("Bad Request", { status: 400 });
|
|
const filePath = `./public${url.pathname}`;
|
|
const assetFile = file(filePath);
|
|
if (await assetFile.exists()) {
|
|
return new Response(assetFile, {
|
|
headers: { "X-Content-Type-Options": "nosniff" }
|
|
});
|
|
}
|
|
return new Response("Not Found", { status: 404 });
|
|
},
|
|
"/antipattern-examples/*": async (req) => {
|
|
const url = new URL(req.url);
|
|
if (url.pathname.includes('..')) return new Response("Bad Request", { status: 400 });
|
|
const filePath = `./public${url.pathname}`;
|
|
const assetFile = file(filePath);
|
|
if (await assetFile.exists()) {
|
|
return new Response(assetFile, {
|
|
headers: { "Content-Type": "text/html", "X-Content-Type-Options": "nosniff", "X-Frame-Options": "SAMEORIGIN" }
|
|
});
|
|
}
|
|
return new Response("Not Found", { status: 404 });
|
|
},
|
|
|
|
// API: Get all skills
|
|
"/api/skills": {
|
|
async GET() {
|
|
const skills = await getSkills();
|
|
return Response.json(skills);
|
|
},
|
|
},
|
|
|
|
// API: Get all commands
|
|
"/api/commands": {
|
|
async GET() {
|
|
const commands = await getCommands();
|
|
return Response.json(commands);
|
|
},
|
|
},
|
|
|
|
// API: Get patterns and antipatterns
|
|
"/api/patterns": {
|
|
async GET() {
|
|
const patterns = await getPatterns();
|
|
return Response.json(patterns);
|
|
},
|
|
},
|
|
|
|
// API: Get command source content
|
|
"/api/command-source/:id": async (req) => {
|
|
const { id } = req.params;
|
|
const result = await getCommandSource(id);
|
|
if (result && result.error) {
|
|
return Response.json({ error: result.error }, { status: result.status });
|
|
}
|
|
if (!result) {
|
|
return Response.json({ error: "Command not found" }, { status: 404 });
|
|
}
|
|
return Response.json({ content: result });
|
|
},
|
|
|
|
// API: Download individual file
|
|
"/api/download/:type/:provider/:id": async (req) => {
|
|
const { type, provider, id } = req.params;
|
|
return handleFileDownload(type, provider, id);
|
|
},
|
|
|
|
// API: Download provider bundle ZIP
|
|
"/api/download/bundle/:provider": async (req) => {
|
|
const { provider } = req.params;
|
|
return handleBundleDownload(provider);
|
|
},
|
|
},
|
|
|
|
// Serve root-level static files (og-image.png, favicon, robots.txt, etc.)
|
|
fetch(req) {
|
|
const url = new URL(req.url);
|
|
if (url.pathname.includes('..')) {
|
|
return new Response("Bad Request", { status: 400 });
|
|
}
|
|
const filePath = `./public${url.pathname}`;
|
|
const staticFile = file(filePath);
|
|
if (staticFile.size > 0) {
|
|
return new Response(staticFile);
|
|
}
|
|
return new Response("Not Found", { status: 404 });
|
|
},
|
|
|
|
development: process.env.NODE_ENV !== "production",
|
|
});
|
|
|
|
console.log(`🎨 impeccable.style running at ${server.url}`);
|
|
|