ShiboSoftwareDev/linux-gameboy-advance
A four-layer Linux handheld game-console carrier PCB built around an F1C100S SoC, USB-C and 2×AA power, LCD/microSD interfaces, flash, audio amplifier/speaker, controls, service headers, and RGB status LED.
- Version
- 1.0.124
- License
- unset
- Stars
- 0
scripts/audit-jlc.mjs
import assert from "node:assert/strict"
import fs from "node:fs"
const delay = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds))
const fetchWithRetry = async (url, options, attempts = 3) => {
let lastError
for (let attempt = 1; attempt <= attempts; attempt++) {
try {
const response = await fetch(url, {
...options,
signal: AbortSignal.timeout(20_000),
})
if (response.ok || response.status < 500) return response
lastError = new Error(`${url}: HTTP ${response.status}`)
} catch (error) {
lastError = error
}
if (attempt < attempts) await delay(250 * attempt)
}
throw lastError
}
const circuit = JSON.parse(fs.readFileSync("dist/index/circuit.json", "utf8"))
const sourceComponents = circuit.filter((record) => record.type === "source_component")
const sourceById = new Map(sourceComponents.map((component) => [component.source_component_id, component]))
const fitted = circuit
.filter((record) => record.type === "pcb_component" && !record.do_not_place)
.map((pcb) => sourceById.get(pcb.source_component_id))
.filter((source) => source && source.ftype !== "simple_test_point")
const referencesByPart = new Map()
for (const component of fitted) {
const parts = component.supplier_part_numbers?.jlcpcb ?? []
assert.ok(parts.length > 0, `${component.name} has no JLCPCB part number`)
for (const part of parts) {
assert.match(part, /^C\d+$/, `${component.name}: invalid JLCPCB part number ${part}`)
if (!referencesByPart.has(part)) referencesByPart.set(part, new Set())
referencesByPart.get(part).add(component.name)
}
}
const partNumbers = [...referencesByPart.keys()].sort((a, b) => Number(a.slice(1)) - Number(b.slice(1)))
const results = []
const batchSize = 8
for (let start = 0; start < partNumbers.length; start += batchSize) {
const batch = partNumbers.slice(start, start + batchSize)
results.push(...await Promise.all(batch.map(async (partNumber) => {
const url = `https://jlcsearch.tscircuit.com/api/search?q=${partNumber}`
let payload = { components: [] }
try {
const response = await fetchWithRetry(url, { headers: { accept: "application/json" } })
if (response.ok) payload = await response.json()
} catch {
// Continue to the exact-SKU official inventory fallback below. The
// community mirror is useful but must not be a single point of failure.
}
const numericId = Number(partNumber.slice(1))
const match = payload.components?.find((component) => component.lcsc === numericId)
const detailUrl = `https://jlcpcb.com/partdetail/${partNumber}`
let officialInStock = false
let jlcpcbAssemblyListed = false
let officialStock = null
let officialManufacturerPartNumber = null
let officialPackage = null
if (!match) {
// The community search mirror can temporarily omit valid parts. In
// that case use LCSC's exact-SKU inventory. JLCPCB and LCSC share the
// C-number. The separate JLCPCB page is recorded when available, but
// is not required for manual/PTH parts such as the volume control.
const lcscUrl = `https://www.lcsc.com/product-detail/${partNumber}.html`
const [detailResult, lcscResult] = await Promise.allSettled([
fetchWithRetry(detailUrl, { headers: { accept: "text/html" } }),
fetchWithRetry(lcscUrl, { headers: { accept: "text/html" } }),
])
if (detailResult.status === "fulfilled" && detailResult.value.ok) {
const detailHtml = await detailResult.value.text()
jlcpcbAssemblyListed = detailHtml.includes(partNumber) &&
(detailHtml.includes("SMT Assembly") || detailHtml.includes("PCB assembly"))
}
if (lcscResult.status === "fulfilled" && lcscResult.value.ok) {
const lcscHtml = await lcscResult.value.text()
const productJsonLd = [...lcscHtml.matchAll(/<script type="application\/ld\+json">([\s\S]*?)<\/script>/g)]
.map((script) => {
try { return JSON.parse(script[1]) } catch { return null }
})
.find((record) => record?.["@type"] === "Product" && record.sku === partNumber)
officialStock = Number(productJsonLd?.offers?.inventoryLevel ?? 0)
officialManufacturerPartNumber = productJsonLd?.mpn ?? null
officialPackage = productJsonLd?.additionalProperty
?.find((property) => property.name === "Package")?.value ?? null
officialInStock = productJsonLd?.offers?.availability === "http://schema.org/InStock" &&
officialStock >= referencesByPart.get(partNumber).size
}
}
const references = [...referencesByPart.get(partNumber)].sort()
const requiredQuantity = references.length
return {
partNumber,
references,
requiredQuantity,
found: Boolean(match) || officialInStock,
manufacturerPartNumber: match?.mfr ?? officialManufacturerPartNumber,
package: match?.package ?? officialPackage,
stock: match?.stock ?? officialStock,
sufficientStock: Boolean(match) ? (match?.stock ?? 0) >= requiredQuantity : officialInStock,
jlcpcbAssemblyListed: Boolean(match) || jlcpcbAssemblyListed,
availabilitySource: match ? "jlcsearch.tscircuit.com live index" :
officialInStock ? "official LCSC live exact-SKU Product inventory" : "not found",
basic: match?.is_basic ?? false,
preferred: match?.is_preferred ?? false,
queryUrl: url,
detailUrl,
}
})))
}
const unavailable = results.filter((part) => !part.sufficientStock)
const report = {
auditedAt: new Date().toISOString(),
source: "jlcsearch.tscircuit.com live index with official JLCPCB/LCSC exact-SKU fallback",
note: "Indexed stock is checked against this board's fitted quantity. If the mirror omits a SKU, the audit requires the exact LCSC Product JSON-LD to report InStock inventory sufficient for the board; a JLCPCB assembly-page match is recorded separately because manual/PTH parts need not have one. This is only a point-in-time availability check and not an inventory reservation. Recheck immediately before ordering.",
fittedComponents: fitted.length,
uniqueJlcpcbPartNumbers: results.length,
availablePartNumbers: results.length - unavailable.length,
unavailablePartNumbers: unavailable.map((part) => part.partNumber),
parts: results,
}
fs.writeFileSync("jlc-availability.json", `${JSON.stringify(report, null, 2)}\n`)
console.log(JSON.stringify({
auditedAt: report.auditedAt,
fittedComponents: report.fittedComponents,
uniqueJlcpcbPartNumbers: report.uniqueJlcpcbPartNumbers,
availablePartNumbers: report.availablePartNumbers,
unavailablePartNumbers: report.unavailablePartNumbers,
}, null, 2))
assert.deepEqual(unavailable, [], `unavailable JLCPCB parts: ${unavailable.map((part) => part.partNumber).join(", ")}`)