AnasSarkiz/muse-voice-remote

Muse Voice Remote — bare ESP32-S3R8, 155 TOP JLCPCB parts with real supplier CAD, native 3D assembly and GLB exports. Revised 17 mm case allocation; 0.30 mm via drill /0.45 mm pad. Checked prototype PCB; mechanical, RF and MUSE qualification pending.

Version
1.3.2
License
unset
Stars
0

scripts/prepare-registry.ts

import { cp, mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises"
import { createHash } from "node:crypto"
import { resolve, join, relative } from "node:path"

const root = process.cwd()
const target = resolve(root, ".tscircuit/publish")
const json = JSON.parse(await readFile("dist/circuit.json", "utf8")) as { type: string }[]
const copper = JSON.parse(await readFile("design/routed-copper.json", "utf8")) as { tracks: unknown[]; vias: unknown[] }
const catalog = JSON.parse(await readFile("docs/component-catalog.json", "utf8")) as unknown[]
const fabrication = JSON.parse(await readFile("manufacturing/verification.json", "utf8")) as { version:string; populated_top_components:number }
const pkg = JSON.parse(await readFile("package.json", "utf8")) as {version:string}
if (fabrication.version !== pkg.version || fabrication.populated_top_components !== catalog.length) {
  throw new Error("Publish requires the checked manufacturing handoff for this version.")
}
if (json.filter(e => e.type === "pcb_component").length !== catalog.length ||
    json.filter(e => e.type === "cad_component").length !== catalog.length ||
    json.filter(e => e.type === "pcb_copper_pour").length < 3 ||
    json.filter(e => e.type === "pcb_trace").length !== copper.tracks.length ||
    json.filter(e => e.type === "pcb_via").length !== copper.vias.length ||
    json.some(e => e.type.endsWith("_error"))) {
  throw new Error("Publish requires the complete final routed board without Circuit JSON errors.")
}
await rm(target, { recursive: true, force: true })
await mkdir(join(target, "dist/index"), { recursive: true })
for (const name of ["MuseVoiceRemote.circuit.tsx", "MuseVoiceRemoteAssembly.circuit.tsx", "index.circuit.tsx", "circuit.json", "README.md",
  "package.json", "package-lock.json", "tsconfig.json", "tscircuit.config.json",
  "design", "docs", "hardware", "imports", "scripts", "tests", "vendor", "models", "manufacturing"]) {
  await cp(join(root, name), join(target, name), {
    recursive: true,
    filter: path => !path.endsWith("/docs/registry-publication.json") && !(path.includes("/manufacturing/") && path.endsWith(".zip") && !path.endsWith(`v${pkg.version}.zip`)) && !path.split("/").includes("__pycache__") &&
      !/\.(?:log|pyc|kicad_prl|lck)$/.test(path),
  })
}
// Deliberately select final outputs: dist also retains historical failed router attempts.
// The registry deliberately deletes dist/circuit.json when uploading source.
// Persistent verified native outputs are root circuit.json and dist/index/circuit.json.
for (const name of ["index/circuit.json", "diagnostics.json", "pcb-top.svg", "pcb.svg", "pcb-top.png",
  "schematic.svg", "assembly-top.svg", "assembly-top.png", "review.html", "bom.csv", "placement.json",
  "MuseVoiceRemote.kicad_pcb", "MuseVoiceRemote.kicad_pro", "native-drc.txt"]) {
  await cp(join(root, "dist", name), join(target, "dist", name))
}
await cp(join(root,"dist/assembly"),join(target,"dist/assembly"),{recursive:true,filter:path=>!path.endsWith(".glb")})
await cp(join(root,"dist/asset-parts"),join(target,"dist/asset-parts"),{recursive:true})
await cp(join(root,"dist/assembly-bundle-assets.json"),join(target,"dist/assembly-bundle-assets.json"))
// The original GLB/ZIP bytes remain locally. Public byte parts are verified
// against their complete hashes and transparently reassembled by the viewer.
const files: { path: string; bytes: number; sha256: string }[] = []
async function scan(dir: string): Promise<void> {
  for (const entry of await readdir(dir, { withFileTypes: true })) {
    const path = join(dir, entry.name)
    if (entry.isDirectory()) await scan(path)
    else {
      const data = await readFile(path)
      files.push({ path: relative(target, path), bytes: data.length,
        sha256: createHash("sha256").update(data).digest("hex") })
    }
  }
}
await scan(target)
files.sort((a, b) => a.path.localeCompare(b.path))
await writeFile(join(target, "registry-manifest.json"), JSON.stringify({
  package: "muse-voice-remote", status: "prepared; not proof of registry publication",
  components: catalog.length, copperSegments: copper.tracks.length, vias: copper.vias.length, files,
}, null, 2) + "\n")
console.log(`Prepared ${files.length + 1} files at ${target}, including root circuit.json and final dist outputs.`)