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/check-cad-assets.mjs
import assert from "node:assert/strict";
import { createHash } from "node:crypto";
import { readFile, mkdir, writeFile } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { previewImages } from "./prepare-preview-archives.mjs";
const root = fileURLToPath(new URL("../", import.meta.url));
const circuitFile = path.resolve(process.argv[2] ?? path.join(root, "dist/index/circuit.json"));
const base = process.argv[3];
const circuit = JSON.parse(await readFile(circuitFile, "utf8"));
const paths = new Set();
for (const cad of circuit.filter((item) => item.type === "cad_component")) {
for (const key of ["model_obj_url", "model_step_url", "model_glb_url"]) {
if (!cad[key]) continue;
assert(cad[key].startsWith("./"), `Unbundled model: ${cad[key]}`);
paths.add(cad[key].slice(2));
}
}
const digest = (bytes) => createHash("sha256").update(bytes).digest("hex");
const manifest = JSON.parse(await readFile(path.join(root, "assets/components/sources.json"), "utf8"));
for (const item of manifest) {
assert.equal(digest(await readFile(path.join(root, item.path))), item.sha256, `Changed supplier CAD: ${item.path}`);
}
// These files must be directly downloadable from the hosted release as well.
for (const filename of ["reader-print-plate.3mf", "esp32-reader-printable-stls.zip", "PRINTING.md", "fdm-reference.ini"])
paths.add("assets/enclosure/" + filename);
for (const filename of ["display-connection.json", "stl/display-interface-check.json", "stl/display-connection-check.json", "stl/toolpath-check.json", "stl/printability-check.json", "stl/clearance-check.json", "stl/mesh-check.json"])
paths.add("assets/enclosure/" + filename);
for (const filename of ["battery-connection.json", "battery-model.json", "stl/battery-connection-check.json"])
paths.add("assets/enclosure/" + filename);
for (const filename of ["front-chassis", "rear-cover"])
paths.add(`assets/enclosure/print/${filename}.stl`);
const archives = JSON.parse(await readFile(path.join(root, "assets/components/supplier-step-archives.json"), "utf8"));
paths.add("assets/components/supplier-step-archives.json");
for (const archive of archives) {
assert.equal(digest(await readFile(path.join(root, archive.path))), archive.sha256);
paths.add(archive.path);
}
for (const filename of previewImages) {
const relative = "assets/enclosure/" + filename;
const encoded = JSON.parse(await readFile(path.join(root, "assets/enclosure/preview-sources", filename + ".json"), "utf8"));
assert.equal(digest(Buffer.from(encoded.base64, "base64")), encoded.sha256);
assert.equal(digest(await readFile(path.join(root, relative))), encoded.sha256, `Stale preview restore source: ${filename}`);
paths.add(relative);
}
const results = [];
const todo = [...paths];
async function worker() {
while (todo.length) {
const relative = todo.shift();
const source = await readFile(path.join(root, relative));
assert(source.length > 0, `Empty asset: ${relative}`);
if (relative.endsWith(".glb")) {
assert.equal(source.subarray(0, 4).toString(), "glTF");
assert.equal(source.readUInt32LE(4), 2);
assert.equal(source.readUInt32LE(8), source.length);
const json = JSON.parse(source.subarray(20, 20 + source.readUInt32LE(12)).toString());
assert(json.meshes?.length, `Missing GLB mesh: ${relative}`);
assert((json.buffers ?? []).every((buffer) => !buffer.uri), `External GLB buffer: ${relative}`);
}
if (relative.endsWith(".obj")) assert(/^v /m.test(source.toString()) && /^f /m.test(source.toString()), `Invalid OBJ: ${relative}`);
const target = base ? new URL(relative, base).href : path.join(root, "dist", relative);
const deployed = base
? await fetch(target, { signal: AbortSignal.timeout(60000) }).then(async (response) => {
assert.equal(response.status, 200, `Unavailable model/file: ${target}`);
return Buffer.from(await response.arrayBuffer());
})
: await readFile(target);
assert.equal(digest(deployed), digest(source), `Asset does not match source: ${target}`);
results.push({ path: relative, bytes: source.length, sha256: digest(source), matches_source: true });
}
}
await Promise.all([worker(), worker(), worker()]);
await mkdir(path.join(root, "checks/model-assets"), { recursive: true });
await writeFile(path.join(root, "checks/model-assets", base ? "published.json" : "local.json"), JSON.stringify({ circuit: circuitFile, base: base ?? "dist", assets: results.sort((a, b) => a.path.localeCompare(b.path)) }, null, 2) + "\n");
console.log(`Verified ${results.length} bundled models and print files; all match source.`);