alinea
Version:
[](https://npmjs.org/package/alinea) [](https://packagephobia.com/result?p=alinea)
211 lines (209 loc) • 6.41 kB
JavaScript
import "../../chunks/chunk-U5RRZUYZ.js";
// src/cli/serve/CreateLocalServer.ts
import { ReadableStream, Response, TextEncoderStream } from "@alinea/iso";
import { router } from "alinea/backend/router/Router";
import { cloudUrl } from "alinea/cloud/server/CloudConfig";
import { trigger } from "alinea/core";
import esbuild from "esbuild";
import fs from "node:fs";
import path from "node:path";
import { Readable } from "node:stream";
import { publicDefines } from "../util/PublicDefines.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({
rootDir: cwd,
staticDir,
alineaDev,
buildOptions,
production,
liveReload
}, handler) {
const devDir = path.join(staticDir, "dev");
const matcher = router.matcher();
const entry = `alinea/cli/static/dashboard/dev`;
const altConfig = path.join(cwd, "tsconfig.alinea.json");
const tsconfig = fs.existsSync(altConfig) ? altConfig : void 0;
let currentBuild = trigger(), initial = true;
const config = {
external: [
"next/navigation",
"next/headers",
"better-sqlite3",
"@alinea/generated/store.js"
],
format: "esm",
target: "esnext",
treeShaking: true,
minify: true,
splitting: true,
sourcemap: true,
outdir: devDir,
bundle: true,
absWorkingDir: cwd,
entryPoints: {
config: "@alinea/generated/config.js",
entry
},
platform: "browser",
...buildOptions,
plugins: buildOptions?.plugins || [],
inject: ["alinea/cli/util/WarnPublicEnv"],
define: {
"process.env.NODE_ENV": production ? "'production'" : "'development'",
"process.env.ALINEA_CLOUD_URL": cloudUrl ? JSON.stringify(cloudUrl) : "undefined",
...publicDefines(process.env)
},
logOverride: {
"ignored-bare-import": "silent"
},
tsconfig,
write: false
};
config.plugins.push({
name: "files",
setup(build) {
build.onStart(() => {
if (initial)
initial = false;
else
currentBuild = trigger();
});
build.onEnd((result) => {
if (result.errors.length) {
console.log("> building alinea dashboard failed");
}
currentBuild.resolve(buildFiles(devDir, result));
if (alineaDev)
liveReload.reload("reload");
});
}
});
esbuild.context(config).then((ctx) => ctx.watch());
async function serveBrowserBuild(request) {
let 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({
// Todo: check types here
write: (v) => controller.enqueue(v),
close: () => controller.close()
});
}
}).pipeThrough(new TextEncoderStream());
return new Response(stream, {
headers: {
"content-type": "text/event-stream",
"cache-control": "no-cache",
"access-control-allow-origin": "*",
Connection: "keep-alive"
}
});
}),
matcher.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(cwd, path.dirname(file));
await fs.promises.mkdir(dir, { recursive: true });
await fs.promises.writeFile(
path.join(cwd, file),
Readable.fromWeb(request.body)
);
return new Response("Upload ok");
}),
matcher.get("/preview").map(async ({ request, url }) => {
return new Response();
}),
router.compress(
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" />
<link href="/entry.css" rel="stylesheet" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="handshake_url" value="${handlerUrl}/hub/auth/handshake" />
<meta name="redirect_url" value="${handlerUrl}/hub/auth" />
<body>
<script type="module" src="./entry.js"></script>
</body>`,
{
headers: { "content-type": "text/html" }
}
);
}),
handler.router,
serveBrowserBuild,
matcher.get("/config.css").map(() => {
return new Response("", { headers: { "content-type": "text/css" } });
})
)
).notFound(() => new Response("Not found", { status: 404 }));
return async (request) => {
return await httpRouter.handle(request);
};
}
export {
createLocalServer
};