Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 18 additions & 4 deletions build.mjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { rm, mkdir, readdir } from "node:fs/promises";
import { rm, mkdir, readdir, stat, cp } from "node:fs/promises";
import { join } from "node:path";

const DIST = "dist";
Expand All @@ -7,12 +7,21 @@ const ASSETS = join(DIST, "assets");
async function build() {
console.log("🚀 Starting ZeroCMS Production Build (via Bun)...");

// 1. Clean and Create Directories
// 1. Compile WASM First
console.log("🦀 Compiling Rust WASM module...");
const proc = Bun.spawn(["bash", "scripts/build_tagger.sh"]);
const exitCode = await proc.exited;
if (exitCode !== 0) {
console.error("❌ Failed to compile WASM module");
process.exit(1);
}

// 2. Clean and Create Directories
await rm(DIST, { recursive: true, force: true });
await mkdir(ASSETS, { recursive: true });
await mkdir(join(DIST, "lib"), { recursive: true });

// 2. Bundle Dashboard App
// 3. Bundle Dashboard App
console.log("📦 Bundling app... ");
const result = await Bun.build({
entrypoints: ["app.js"],
Expand All @@ -35,7 +44,12 @@ async function build() {
for (const lib of libs) {
const src = join("lib", lib);
const dest = join(DIST, "lib", lib);
await Bun.write(dest, Bun.file(src));
const stats = await stat(src);
if (stats.isDirectory()) {
await cp(src, dest, { recursive: true });
} else {
await Bun.write(dest, Bun.file(src));
}
}

// Extract the generated hashed file name from Bun's output
Expand Down
26 changes: 21 additions & 5 deletions serve.js
Original file line number Diff line number Diff line change
Expand Up @@ -292,16 +292,32 @@ const server = http.createServer((req, res) => {
return;
}

let filePath = '.' + req.url.split('?')[0];
const basePath = process.cwd();
let requestPath = req.url.split('?')[0];
try {
requestPath = decodeURIComponent(requestPath);
} catch (e) {
res.writeHead(400, { 'Content-Type': 'text/plain' });
res.end('Bad Request');
return;
}

let filePath = path.join(basePath, requestPath);

// Security: Prevent path traversal
if (!filePath.startsWith(basePath) || (filePath !== basePath && !filePath.startsWith(basePath + path.sep))) {
res.writeHead(403, { 'Content-Type': 'text/plain' });
res.end('Forbidden');
return;
}

// Map root to the main index.html (One-Pager)
if (filePath === './') {
filePath = './index.html';
if (filePath === basePath || filePath === basePath + path.sep) {
filePath = path.join(basePath, 'index.html');
}

// High-Performance Library Serving
if (req.url.startsWith('/lib/')) {
filePath = '.' + req.url;
res.setHeader('Cache-Control', 'public, max-age=31536000, immutable');
}

Expand Down