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/push-release.mjs
import { execFileSync, spawn } from "node:child_process";
import { createHash } from "node:crypto";
import { mkdir, mkdtemp, readFile, rm } from "node:fs/promises";
import { homedir, tmpdir } from "node:os";
import path from "node:path";
import { fileURLToPath } from "node:url";
const root = fileURLToPath(new URL("../", import.meta.url));
if (execFileSync("git", ["status", "--porcelain"], { cwd: root, encoding: "utf8" }).trim())
throw new Error("Commit all release changes before publishing.");
const api = "https://registry-api.tscircuit.com/";
const digest = (bytes) => createHash("sha256").update(bytes).digest("hex");
async function post(route, body, token) {
const response = await fetch(new URL(route, api), {
method: "POST", headers: { "Content-Type": "application/json", ...(token ? { Authorization: `Bearer ${token}` } : {}) },
body: JSON.stringify(body), signal: AbortSignal.timeout(120000),
});
if (!response.ok) throw new Error(`Registry ${route}: HTTP ${response.status}`);
return response.json();
}
// tsci ignores dotfiles, but does not honor .gitignore. Start with committed
// files. Preserve original STEP models in Git and the downloadable ZIPs while
// omitting individual STEP copies and reconstructed preview binaries from
// the browser's build source.
const temporary = await mkdtemp(path.join(tmpdir(), "reader-release-"));
try {
const archive = path.join(temporary, "source.tar"), project = path.join(temporary, "project");
await mkdir(project);
execFileSync("git", ["archive", "--format=tar", "--output", archive, "HEAD"], { cwd: root });
execFileSync("tar", ["-xf", archive, "-C", project]);
const committed = execFileSync("git", ["ls-files", "-z"], { cwd: root, encoding: "utf8" }).split("\0").filter(Boolean);
const reconstructed = (file) => /\.(step|png|glb|stl)$/i.test(file);
for (const file of committed.filter(reconstructed))
await rm(path.join(project, file));
const files = committed.filter((file) => !file.split("/").some((part) => part.startsWith(".")) && !reconstructed(file) && !file.startsWith("dist/") && !file.startsWith("node_modules/"));
for (const file of files) {
if ((await readFile(path.join(project, file))).length > 3 * 1024 * 1024)
throw new Error(`Source exceeds the safe registry request size: ${file}. Split downloadable archives before publishing.`);
}
const pkg = JSON.parse(await readFile(path.join(project, "package.json"), "utf8"));
const expected = pkg.version;
const child = spawn(process.execPath, [path.join(root, "node_modules/.bin/tsci"), "push", "index.circuit.tsx", "--compress"], { cwd: project, stdio: "inherit" });
const code = await new Promise((resolve, reject) => { child.once("error", reject); child.once("exit", resolve); });
const published = JSON.parse(await readFile(path.join(project, "package.json"), "utf8")).version;
if (published !== expected) throw new Error(`Published ${published}; commit package.json to match the registry version.`);
const packageName = pkg.name.replace(/^@tsci\//, "").replace(".", "/");
const identity = { package_name_with_version: `${packageName}@${expected}` };
const { filesystem_map: remote } = await post("package_releases/get_filesystem_map", identity);
if (Object.keys(remote).length !== files.length) throw new Error(`Release source has ${Object.keys(remote).length} files; expected ${files.length}.`);
for (const file of files) {
const source = await readFile(path.join(project, file)), uploaded = remote[file];
if (typeof uploaded !== "string" || ![Buffer.from(uploaded, "utf8"), Buffer.from(uploaded, "base64")].some((bytes) => digest(bytes) === digest(source)))
throw new Error(`Missing or changed release source: ${file}`);
}
// The CLI uses short request timeouts. Only recover a timeout after proving
// that every intended source file exists and matches the committed bytes.
if (code !== 0) {
const config = JSON.parse(await readFile(path.join(homedir(), ".config/tscircuit-nodejs/config.json"), "utf8"));
if (!config.sessionToken) throw new Error(`tsci push exited ${code}; no session available to finalize verified upload.`);
await post("package_releases/update", { ...identity, ready_to_build: true }, config.sessionToken);
console.log("Recovered completed upload after verifying every source file.");
}
console.log(`Verified ${files.length} committed runtime files for ${identity.package_name_with_version}.`);
} finally {
await rm(temporary, { recursive: true, force: true });
}