UNPKG

alinea

Version:
224 lines (222 loc) 7.01 kB
import "../../chunks/chunk-NZLE2WMY.js"; // src/cli/serve/CreateLocalServer.ts import fs from "node:fs"; import path from "node:path"; import { Readable } from "node:stream"; import { ReadableStream, Response } from "@alinea/iso"; import { router } from "alinea/backend/router/Router"; import { trigger } from "alinea/core/Trigger"; import { buildEmitter } from "../build/BuildEmitter.js"; import { ignorePlugin } from "../util/IgnorePlugin.js"; import { publicDefines } from "../util/PublicDefines.js"; import { reportFatal } from "../util/Report.js"; import { viewsPlugin } from "../util/ViewsPlugin.js"; var mimeTypes = new Map( Object.entries({ // Text ".css": "text/css; charset=utf-8", ".htm": "text/html; charset=utf-8", ".html": "text/html; charset=utf-8", ".js": "text/javascript; charset=utf-8", ".json": "application/json", ".mjs": "text/javascript; charset=utf-8", ".xml": "text/xml; charset=utf-8", // Images ".gif": "image/gif", ".jpeg": "image/jpeg", ".jpg": "image/jpeg", ".png": "image/png", ".svg": "image/svg+xml", ".webp": "image/webp", // Fonts ".eot": "application/vnd.ms-fontobject", ".otf": "font/otf", ".sfnt": "font/sfnt", ".ttf": "font/ttf", ".woff": "font/woff", ".woff2": "font/woff2", // Other ".pdf": "application/pdf", ".wasm": "application/wasm" }) ); function buildFiles(outdir, result) { return new Map( result.outputFiles.map((file) => { return [ file.path.slice(outdir.length).replace(/\\/g, "/").toLowerCase(), file ]; }) ); } function createLocalServer({ cmd, configLocation, rootDir, staticDir, alineaDev, buildOptions, production, liveReload, buildId }, cms, handleApi, user) { function devHandler(request) { return handleApi(request, { isDev: true, handlerUrl: new URL(request.url.split("?")[0]), apiKey: process.env.ALINEA_API_KEY || "dev" }); } if (cmd === "build") return { close() { }, handle: devHandler }; const devDir = path.join(staticDir, "dev"); const matcher = router.matcher(); const entryPoints = { entry: "alinea/cli/static/dashboard/dev", config: "#alinea/entry" }; const tsconfigLocation = path.join(rootDir, "tsconfig.json"); const tsconfig = fs.existsSync(tsconfigLocation) ? tsconfigLocation : void 0; let currentBuild = trigger(); let initial = true; const plugins = buildOptions?.plugins || []; plugins.push(viewsPlugin(rootDir, cms), ignorePlugin); const config = { format: "esm", target: "esnext", treeShaking: true, minify: true, splitting: true, sourcemap: true, outdir: devDir, bundle: true, absWorkingDir: rootDir, entryPoints, platform: "browser", ...buildOptions, plugins, alias: { "alinea/next": "alinea/core", "#alinea/config": configLocation, "#alinea/entry": `data:text/javascript, export * from '#alinea/config' export * from '${viewsPlugin.entry}' ` }, external: ["@alinea/generated"], inject: ["alinea/cli/util/WarnPublicEnv"], define: { "process.env.NODE_ENV": production ? '"production"' : '"development"', "process.env.ALINEA_DEV": alineaDev ? "true" : "false", "process.env.ALINEA_USER": JSON.stringify(JSON.stringify(user)), "process.env.ALINEA_FORCE_AUTH": process.env.ALINEA_CLOUD_URL ? "true" : "false", "process.env.ALINEA_BUILD_ID": JSON.stringify(buildId), ...publicDefines(process.env) }, logOverride: { "ignored-bare-import": "silent" }, tsconfig, write: false }; const builder = buildEmitter(config); (async () => { for await (const { type, result } of builder) { if (type === "start") { if (initial) initial = false; else currentBuild = trigger(); } else { if (result.errors.length) { reportFatal("Building Alinea dashboard failed"); } else { currentBuild.resolve(buildFiles(devDir, result)); liveReload.reload(alineaDev ? "reload" : "refresh"); } } } })(); async function serveBrowserBuild(request) { const result = await currentBuild; if (!result) return new Response("Build failed", { status: 500 }); const url = new URL(request.url); const fileName = url.pathname.toLowerCase(); const file = result.get(fileName); if (!file) return void 0; const ifNoneMatch = request.headers.get("if-none-match"); const etag = `"${file.hash}"`; if (ifNoneMatch && ifNoneMatch === etag) return new Response(void 0, { status: 304 }); const extension = path.extname(fileName); return new Response(file.contents, { headers: { "content-type": mimeTypes.get(extension) || "application/octet-stream", etag } }); } const httpRouter = router( matcher.get("/~dev").map(() => { const stream = new ReadableStream({ start(controller) { liveReload.register({ write: (v) => controller.enqueue(v), close: () => controller.close() }); } }); return new Response(stream, { headers: { "content-type": "text/event-stream", "cache-control": "no-cache", "access-control-allow-origin": "*", connection: "keep-alive" } }); }), router.queryMatcher.post("/upload").map(async ({ request, url }) => { if (!request.body) return new Response("No body", { status: 400 }); const file = url.searchParams.get("file"); const dir = path.join(rootDir, path.dirname(file)); await fs.promises.mkdir(dir, { recursive: true }); await fs.promises.writeFile( path.join(rootDir, file), Readable.fromWeb(request.body) ); return new Response("Upload ok"); }), router.compress( matcher.all("/api").map(async ({ url, request }) => { return devHandler(request); }), matcher.get("/").map(({ url }) => { const handlerUrl = `${url.protocol}//${url.host}`; return new Response( `<!DOCTYPE html> <meta charset="utf-8" /> <link rel="icon" href="data:," /> <link href="/config.css" rel="stylesheet" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="handshake_url" value="${handlerUrl}/api?auth=handshake" /> <meta name="redirect_url" value="${handlerUrl}/api?auth=login" /> <body> <script type="module" src="./entry.js?${buildId}"></script> </body>`, { headers: { "content-type": "text/html" } } ); }), serveBrowserBuild ) ).notFound(() => new Response("Not found", { status: 404 })); return { close() { builder.return(); }, async handle(request) { return await httpRouter.handle(request); } }; } export { createLocalServer };