rushabhcodes/ESP32-E-Reader

A compact ESP32-C3 e-reader PCB integrating LiPo charging/protection, USB-C, 3.3 V regulation, e-paper display bias generation, microSD storage, and tactile controls.

Version
1.0.85
License
unset
Stars
3

scripts/export-assembly-preview.mjs

import { createServer } from "node:http";
import { readFile, writeFile, mkdtemp, rm, mkdir } from "node:fs/promises";
import { spawn } from "node:child_process";
import { tmpdir } from "node:os";
import path from "node:path";
import { fileURLToPath } from "node:url";

const root = fileURLToPath(new URL("../", import.meta.url));
const output = path.resolve(process.argv[2] ?? path.join(root, "dist/index/assembly.glb"));
const circuit = JSON.parse(await readFile(path.join(root, "dist/index/circuit.json"), "utf8"));
if (circuit.some((item) => item.type.endsWith("_error")))
	throw new Error("Build the circuit without errors before exporting the assembly");

// The CLI resolves remote CAD but cannot fetch ./ paths from a JSON export.
// Serve only the referenced local CAD files, on loopback, for this export.
const localModels = new Map();
for (const item of circuit.filter((item) => item.type === "cad_component")) {
	for (const key of ["model_glb_url", "model_obj_url", "model_step_url"]) {
		if (!item[key]?.startsWith("./")) continue;
		const relative = item[key].slice(2);
		const absolute = path.resolve(root, relative);
		if (!absolute.startsWith(root) || ![".glb", ".obj", ".step"].includes(path.extname(absolute)))
			throw new Error(`Unsupported local CAD path: ${relative}`);
		localModels.set("/" + relative, await readFile(absolute));
	}
}
const server = createServer((request, response) => {
	const key = decodeURIComponent(new URL(request.url, "http://localhost").pathname);
	const bytes = localModels.get(key);
	if (!bytes) { response.writeHead(404); response.end(); return; }
	response.writeHead(200, { "Content-Type": key.endsWith(".glb") ? "model/gltf-binary" : "text/plain" });
	response.end(bytes);
});
await new Promise((resolve, reject) => {
	server.once("error", reject);
	server.listen(0, "127.0.0.1", resolve);
});
const temporary = await mkdtemp(path.join(tmpdir(), "reader-assembly-"));
try {
	const base = `http://127.0.0.1:${server.address().port}`;
	for (const item of circuit.filter((item) => item.type === "cad_component")) {
		for (const key of ["model_glb_url", "model_obj_url", "model_step_url"])
			if (item[key]?.startsWith("./")) item[key] = base + "/" + item[key].slice(2);
	}
	const input = path.join(temporary, "assembly.circuit.json");
	await writeFile(input, JSON.stringify(circuit));
	await mkdir(path.dirname(output), { recursive: true });
	const child = spawn(process.execPath, [path.join(root, "node_modules/.bin/tsci"), "export", input, "--format", "glb", "--output", output], { cwd: root });
	let diagnostics = "";
	for (const stream of [child.stdout, child.stderr]) stream.on("data", (chunk) => { diagnostics += chunk; process.stdout.write(chunk); });
	const code = await new Promise((resolve, reject) => { child.once("error", reject); child.once("exit", resolve); });
	if (code !== 0 || /Failed to fetch (GLB|OBJ)/i.test(diagnostics))
		throw new Error("Assembly export failed or substituted a missing CAD model");
} finally {
	await new Promise((resolve) => server.close(resolve));
	await rm(temporary, { recursive: true, force: true });
}