99 lines
2.9 KiB
JavaScript
99 lines
2.9 KiB
JavaScript
import { createReadStream, existsSync, readFileSync, statSync } from "node:fs";
|
|
import { createServer } from "node:http";
|
|
import { extname, join, relative, resolve } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const projectRoot = resolve(fileURLToPath(new URL(".", import.meta.url)));
|
|
const websiteRoot = join(projectRoot, "website");
|
|
const distRoot = join(websiteRoot, "dist");
|
|
const root = existsSync(distRoot) ? distRoot : websiteRoot;
|
|
const publicRoot = existsSync(distRoot) ? distRoot : join(websiteRoot, "public");
|
|
|
|
function loadEnvFile(file = ".env") {
|
|
if (!existsSync(file)) return;
|
|
|
|
const lines = readFileSync(file, "utf8").split(/\r?\n/);
|
|
lines.forEach((line) => {
|
|
const trimmed = line.trim();
|
|
if (!trimmed || trimmed.startsWith("#")) return;
|
|
|
|
const separatorIndex = trimmed.indexOf("=");
|
|
if (separatorIndex < 1) return;
|
|
|
|
const key = trimmed.slice(0, separatorIndex).trim();
|
|
const value = trimmed.slice(separatorIndex + 1).trim().replace(/^["']|["']$/g, "");
|
|
if (!process.env[key]) process.env[key] = value;
|
|
});
|
|
}
|
|
|
|
loadEnvFile();
|
|
|
|
const port = Number(process.env.PORT || 5173);
|
|
|
|
const types = {
|
|
".html": "text/html; charset=utf-8",
|
|
".css": "text/css; charset=utf-8",
|
|
".js": "text/javascript; charset=utf-8",
|
|
".json": "application/json; charset=utf-8",
|
|
".svg": "image/svg+xml",
|
|
".ico": "image/x-icon",
|
|
".png": "image/png",
|
|
".jpg": "image/jpeg",
|
|
".jpeg": "image/jpeg",
|
|
".webp": "image/webp"
|
|
};
|
|
|
|
function isInsideRoot(parent, file) {
|
|
const relativePath = relative(parent, file);
|
|
return Boolean(relativePath) && !relativePath.startsWith("..") && !relativePath.startsWith("/");
|
|
}
|
|
|
|
function fileIfReadable(file) {
|
|
return isInsideRoot(root, file) && existsSync(file) && statSync(file).isFile() ? file : null;
|
|
}
|
|
|
|
function resolvePath(url) {
|
|
let requested;
|
|
try {
|
|
requested = decodeURIComponent(new URL(url, "http://localhost").pathname);
|
|
} catch {
|
|
return null;
|
|
}
|
|
|
|
const candidate = fileIfReadable(resolve(root, `.${requested}`));
|
|
if (candidate) return candidate;
|
|
|
|
const publicCandidate = fileIfReadable(resolve(publicRoot, `.${requested}`));
|
|
if (publicCandidate && isInsideRoot(publicRoot, publicCandidate)) return publicCandidate;
|
|
|
|
return join(root, "index.html");
|
|
}
|
|
|
|
const server = createServer((req, res) => {
|
|
const file = resolvePath(req.url || "/");
|
|
if (!file || !existsSync(file)) {
|
|
res.writeHead(404);
|
|
res.end("Not found");
|
|
return;
|
|
}
|
|
|
|
res.writeHead(200, {
|
|
"Content-Type": types[extname(file)] || "application/octet-stream",
|
|
"Cache-Control": "no-store"
|
|
});
|
|
createReadStream(file).pipe(res);
|
|
});
|
|
|
|
server.on("error", (error) => {
|
|
if (error.code === "EADDRINUSE") {
|
|
console.error(`Port ${port} is already in use. Set PORT to another value.`);
|
|
process.exit(1);
|
|
}
|
|
|
|
console.error(error);
|
|
process.exit(1);
|
|
});
|
|
|
|
server.listen(port, () => {
|
|
console.log(`Sokko G dev server running at http://localhost:${port}`);
|
|
});
|