astra/am3352-sbc
A tscircuit package created by astra.
- Version
- 0.1.28
- License
- unset
- Stars
- 0
check-routing.ts
import * as checks from "@tscircuit/checks"
import { createHash } from "node:crypto"
import { readFileSync } from "node:fs"
import { resolve } from "node:path"
import { fileURLToPath } from "node:url"
import type { AnyCircuitElement } from "circuit-json"
import { getFullConnectivityMapFromCircuitJson } from "circuit-json-to-connectivity-map"
/** All default runAllRoutingChecks checks in @tscircuit/checks 0.0.252.
* Run sequentially in fresh processes so geometry allocations are released
* between checks. This is validation only: no generated files are modified.
*
* Usage: bun check-routing.ts [native Circuit JSON path]
*/
const checkNames = [
"checkEachPcbPortConnectedToPcbTraces",
"checkSourceTracesHavePcbTraces",
"checkSourceTracesMatchPcbTraceThickness",
"checkPcbBendZoneTraces",
"checkPcbTraceLengths",
"checkPcbTraceStyle",
"checkPcbBusLengthSkew",
"checkPcbRoutingConstraints",
"checkPcbTraceViaCounts",
"checkEachPcbTraceNonOverlapping",
"checkCopperPourShorts",
"checkPadTraceClearance",
"checkHoleTraceClearance",
"checkViaTraceClearance",
"checkViaPadClearance",
"checkSameNetViaSpacing",
"checkDifferentNetViaSpacing",
"checkTracesAreContiguous",
"checkDanglingTraces",
"checkPcbTracesOutOfBoard",
] as const
type CheckName = typeof checkNames[number]
type Finding = AnyCircuitElement & { error_type?: string; message?: string }
type WorkerResult = {
check: CheckName
nativeSha256: string
findings: Finding[]
storedErrors?: Finding[]
officialCheckInput?: string
}
const protocolPrefix = "PCB_ROUTING_CHECK_RESULT:"
const sha256 = (bytes: Buffer) => createHash("sha256").update(bytes).digest("hex")
const isError = (finding: Finding) => finding.type.endsWith("_error") || Boolean(finding.error_type)
async function runWorker() {
const [, name, nativePath, expectedSha256] = process.argv.slice(2)
if (!checkNames.includes(name as CheckName)) throw new Error(`Unknown routing check: ${name}`)
const bytes = readFileSync(nativePath!)
const nativeSha256 = sha256(bytes)
if (nativeSha256 !== expectedSha256) throw new Error("Generated native changed between checks")
const first = name === checkNames[0]
const rows: AnyCircuitElement[] = JSON.parse(first ? bytes.toString("utf8") : await Bun.stdin.text())
if (!Array.isArray(rows)) throw new Error("Native Circuit JSON must be an array")
const fn = checks[name as CheckName] as (
circuit: AnyCircuitElement[],
options?: { connMap: ReturnType<typeof getFullConnectivityMapFromCircuitJson> },
) => AnyCircuitElement[] | Promise<AnyCircuitElement[]>
// The public first check performs upstream's endpoint annotation helper and
// constructs its complete indexed PCB connectivity map internally. That
// builder is private to @tscircuit/checks; its public default is authoritative.
// Retain only this private in-memory check input for subsequent workers,
// matching the aggregator without repeating its heavy first check 20 times.
const findings = await (first ? fn(rows) : fn(rows, { connMap: getFullConnectivityMapFromCircuitJson(rows) }))
if (!Array.isArray(findings)) throw new Error(`${name} did not return a finding array`)
if (sha256(readFileSync(nativePath!)) !== nativeSha256) throw new Error("Generated native changed during a check")
const result: WorkerResult = {
check: name as CheckName, nativeSha256, findings: findings as Finding[],
...(first ? { storedErrors: rows.filter(row => isError(row as Finding)) as Finding[],
officialCheckInput: JSON.stringify(rows) } : {}),
}
console.log(protocolPrefix + JSON.stringify(result))
}
async function runChecks() {
const nativePath = resolve(process.argv[2] ?? ".tscircuit/generated/index.circuit.json")
const nativeSha256 = sha256(readFileSync(nativePath))
const scriptPath = fileURLToPath(import.meta.url)
let officialCheckInput: string | undefined
let errorCount = 0
let warningCount = 0
for (const name of checkNames) {
const child = Bun.spawn([process.execPath, "--smol", scriptPath, "--worker", name, nativePath, nativeSha256], {
stdin: "pipe", stdout: "pipe", stderr: "inherit",
})
if (officialCheckInput) child.stdin.write(officialCheckInput)
child.stdin.end()
const [output, exitCode] = await Promise.all([new Response(child.stdout).text(), child.exited])
if (exitCode !== 0) throw new Error(`${name} worker exited with code ${exitCode}`)
const position = output.lastIndexOf(protocolPrefix)
if (position < 0) throw new Error(`${name} worker returned no result`)
const result = JSON.parse(output.slice(position + protocolPrefix.length).trim()) as WorkerResult
if (result.check !== name || result.nativeSha256 !== nativeSha256 || !Array.isArray(result.findings)) {
throw new Error(`${name} worker returned inconsistent evidence`)
}
if (name === checkNames[0]) {
if (!result.officialCheckInput) throw new Error("First check returned no official private check input")
officialCheckInput = result.officialCheckInput
const storedErrors = result.storedErrors ?? []
errorCount += storedErrors.length
for (const finding of storedErrors) console.error(`Stored error: ${JSON.stringify(finding)}`)
}
const errors = result.findings.filter(isError)
errorCount += errors.length
warningCount += result.findings.length - errors.length
console.log(`${name}: ${errors.length} errors, ${result.findings.length - errors.length} warnings`)
for (const finding of result.findings) {
const display = `${name}: ${JSON.stringify(finding)}`
if (isError(finding)) console.error(display)
else console.log(display)
}
}
if (sha256(readFileSync(nativePath)) !== nativeSha256) throw new Error("Generated native changed during routing validation")
console.log(`${checkNames.length} routing checks completed: ${errorCount} errors, ${warningCount} warnings.`)
process.exitCode = errorCount ? 1 : 0
}
try {
if (process.argv[2] === "--worker") await runWorker()
else await runChecks()
} catch (error) {
console.error(error instanceof Error ? error.message : String(error))
process.exitCode = 1
}