shibosoftwaredev/f1c100s-linux-nema17-stepper-controller
A four-layer NEMA 17 motor-controller PCB combining an Allwinner F1C100S Linux computer, USB‑PD/buck power system, W5500 Ethernet, STM32 real-time motion control, DRV8825 stepper driver, protection/temperature/current telemetry, USB interfaces, and local LED/buzzer diagnostics.
- Version
- 0.4.56
- License
- unset
- Stars
- 0
scripts/prepare-stencil-publication.mjs
import fs from "node:fs"
import path from "node:path"
import crypto from "node:crypto"
import assert from "node:assert/strict"
const read=p=>JSON.parse(fs.readFileSync(p,"utf8"))
const sha=bytes=>crypto.createHash("sha256").update(bytes).digest("hex")
const hash=p=>sha(fs.readFileSync(p))
const version="0.4.52"
const status=read("reports/fab-stencil-regression.json")
const fetched=read("reports/round9-live-latest/fetch-manifest.json")
const published=read("reports/round9-live-latest/latest-dist_index_circuit.json")
const local=read("dist/index/circuit.json")
assert.equal(hash("dist/index/circuit.json"),status.circuitSha256)
assert.equal(status.noRegressionsInReviewedScope,true)
assert.equal(status.fabricationReady,false)
assert.equal(fetched.latestVersion,"0.4.51")
assert.equal(fetched.latestReleaseId,"0f69dae4-43af-422d-94d0-c14f4453ddbd")
assert.ok(fetched.buildCompletedAt && !fetched.buildError)
const stable=value=>JSON.stringify(value,(_,v)=>typeof v==="number"?Math.round(v*1e8)/1e8:
v&&typeof v==="object"&&!Array.isArray(v)?Object.fromEntries(Object.entries(v).sort(([a],[b])=>a.localeCompare(b))):v)
const records=(c,includePaste=false)=>c.filter(e=>e.type!=="source_project_metadata"&&(includePaste||e.type!=="pcb_solder_paste")).map(stable).sort()
assert.deepEqual(records(local),records(published),"live electrical/physical/mechanical/CAD/schematic/DRC records match; only paste changes")
const old=read("../f1c100s-linux-nema17-working-v046/reports/round14-publish-manifest.json")
assert.deepEqual(records(published,true),records(read(path.join(old.destination,"dist/index/circuit.json")),true),"live latest matches preserved baseline including original paste")
for(const f of fetched.downloaded.filter(f=>f.file.startsWith("firmware/")))assert.equal(hash(f.file),f.sha256)
for(const name of ["tests","typecheck","shorts","verify"]){
const log=fs.readFileSync(`reports/stencil-prepush-${name}.log`,"utf8")
if(name==="tests")assert.match(log,/43 pass[\s\S]*0 fail/)
if(name==="typecheck")assert.doesNotMatch(log,/error TS\d+|Error:/)
if(name==="shorts")assert.match(log,/No shorts detected/)
if(name==="verify")assert.match(log,/"fabricationReady": false/)
}
async function request(endpoint,body){
const r=await fetch(`https://registry-api.tscircuit.com/${endpoint}`,{method:body?"POST":"GET",
headers:body?{"content-type":"application/json"}:undefined,body:body?JSON.stringify(body):undefined,signal:AbortSignal.timeout(45000)})
if(r.status===404)return null
assert.ok(r.ok,`${endpoint}: HTTP ${r.status}`)
return r.json()
}
const pkg=(await request("packages/get",{name:fetched.packageName})).package
assert.equal(pkg.latest_package_release_id,fetched.latestReleaseId,"stop for concurrent latest change")
assert.equal(await request("package_releases/get",{package_name_with_version:`${fetched.packageName}@${version}`}),null,"never replace an existing numbered release")
const comparison={checkedAt:new Date().toISOString(),latestVersion:fetched.latestVersion,latestReleaseId:fetched.latestReleaseId,
latestCloudCircuitSha256:hash("reports/round9-live-latest/latest-dist_index_circuit.json"),localCircuitSha256:status.circuitSha256,
noRegressionsInCheckedScope:true,allNonPasteDesignRecordsUnchanged:true,nativeErrors:0,nativeWarnings:151,
improvements:{missingLeadAperturesRestored:96,bareTestPadAperturesRemoved:4,filledCappedViaOrderProcessNowSpecified:true},
qualificationNotExpanded:true,fabricationReady:false,remaining:status.remaining}
fs.writeFileSync("reports/stencil-prepush-comparison.json",JSON.stringify(comparison,null,2)+"\n")
const selected=new Set(old.files.map(f=>f.file).filter(f=>!f.startsWith("dist/")))
for(const f of ["assembly-stencil.ts","manufacturing-process.json","tests/assembly-stencil.test.ts","tests/fixtures/rounded-pad-baseline.json",
"scripts/audit-stencil-artwork.py","scripts/check-assembly-motor-reference.mjs","scripts/check-fab-stencil-regression.mjs",
"scripts/prepare-stencil-publication.mjs","scripts/verify-stencil-publication.mjs",
"reports/fab-stencil-regression.json","reports/assembly-motor-reference.json","reports/stencil-artwork-audit.json",
"reports/stencil-baseline-negative.json","reports/stencil-prepush-comparison.json",
...["build","shorts","style","typecheck","continuity","copper","drill","dfm","solder","tests","bom","verify"].map(n=>`reports/fab-stencil-${n}.log`),
...["tests","shorts","typecheck","verify","artwork"].map(n=>`reports/stencil-prepush-${n}.log`)])selected.add(f)
const destination=fs.mkdtempSync(path.resolve("../../publish-staging/nema17-stencil-v052-"))
for(const f of selected){
assert.ok(!f.includes("..")&&!/(?:^|\/)(?:node_modules|\.env|\.npmrc|\.netrc)(?:\/|$)/.test(f))
const to=path.join(destination,f)
fs.mkdirSync(path.dirname(to),{recursive:true});fs.copyFileSync(f,to)
}
const write=(p,v)=>fs.writeFileSync(path.join(destination,p),JSON.stringify(v,null,2)+"\n")
const metadata=read("package.json");metadata.version=version;write("package.json",metadata)
const lock=read("package-lock.json");lock.version=version;if(lock.packages?.[""])lock.packages[""].version=version;write("package-lock.json",lock)
let readme=fs.readFileSync("README.md","utf8")
readme=readme.replace(/Release `0\.4\.51`[\s\S]*?## Features/,
`Release \`${version}\` improves on \`0.4.51\` by restoring 96 missing rounded-lead solder-paste apertures and removing paste from four bare SWD test pads. All electrical, copper, placement, mounting, schematic and CAD records are unchanged.\n\n`+
`**Not fabrication-approved.** The included manufacturing process specifies paid four-layer epoxy-filled/copper-capped vias; it is not fabricator acceptance. Thermal-pad stencil/reflow, buck feedback layout, USB/Ethernet stackup/return paths, actual motor rear fastening/harness and first-article testing remain open.\n\n`+
`The fresh stock check covers five boards; inventory is not reserved. Current evidence is in \`reports/fab-stencil-regression.json\`, \`reports/stencil-artwork-audit.json\` and \`reports/stencil-prepush-comparison.json\`. Earlier reports retain their original circuit hashes and local/unpushed labels as history; publication metadata establishes the uploaded version.\n\n## Features`)
fs.writeFileSync(path.join(destination,"README.md"),readme)
const verification=read("verification.json");verification.releaseVersion=version;verification.localChecksPassed=true;verification.fabricationReady=false;write("verification.json",verification)
fs.writeFileSync(path.join(destination,"CHECKPOINT_SAVED.md"),`# Validated local checkpoint\n\nRelease ${version}: stencil artwork improvements only; no fabrication approval.\nAudited local circuit: ${status.circuitSha256}.\nRestore-verified backup: nema17-fabrication-stencil-20261021-b5aNNW.\nSee README.md and reports/fab-stencil-regression.json for remaining qualification.\n`)
const files=[...selected].sort().map(file=>({file,sha256:hash(path.join(destination,file)),bytes:fs.statSync(path.join(destination,file)).size}))
const manifest={createdAt:new Date().toISOString(),source:process.cwd(),destination,packageName:fetched.packageName,version,
previous:{version:pkg.latest_version,releaseId:pkg.latest_package_release_id},circuitSha256:status.circuitSha256,
fabricationReady:false,files,cloudBuildConfigUnchanged:true,staged:false}
assert.equal(hash(path.join(destination,"tscircuit.config.json")),hash("tscircuit.config.json"))
fs.writeFileSync("reports/stencil-publish-manifest.json",JSON.stringify(manifest,null,2)+"\n")
console.log(JSON.stringify({version,destination,files:files.length,totalBytes:files.reduce((s,f)=>s+f.bytes,0),noRegressionsInCheckedScope:true,fabricationReady:false}))