ShiboSoftwareDev/linux-gameboy-advance

Multilayer PCB carrier board integrating USB‑C, battery charging/power management, buck‑boost regulation, MCU/SoC, LCD/SD interfaces, audio controls, status LEDs, backlight, and UART/I²C/SPI service connectors with reviewed multilayer copper and vias.

Version
1.0.134
License
unset
Stars
0

scripts/cloud-artifact-sources.mjs

import { createHash } from "node:crypto"
import {
  readFileSync,
  readdirSync,
  statSync,
} from "node:fs"
import { join, relative, resolve, sep } from "node:path"

const sourceInputs = [
  "index.circuit.tsx",
  "carrier-router.ts",
  "carrier-routes-v2.json",
  "backlight-routes.json",
  "assembly-parts.json",
  "mechanical",
  "scripts/render-manual-enclosure.mjs",
  "tscircuit.config.json",
  "assets/er-tft035ips-6-oem-installed-v5.glb",
  "assets/sod-123fl.glb",
  "footprints",
  "handheld-imports",
  "imports",
  "modules/f1c100s/imports",
  "modules/f1c100s/src",
  "schematic",
  "subcircuits",
]

const collectFiles = (path) => {
  const stats = statSync(path)
  if (stats.isFile()) return [path]

  return readdirSync(path, { withFileTypes: true })
    // Generated Circuit JSON/metrics/route caches are reproducible outputs,
    // not build inputs. Excluding them keeps the freshness manifest identical
    // in the lean registry package, where those multi-megabyte caches are
    // intentionally omitted.
    .filter(
      (entry) =>
        !entry.name.startsWith(".") &&
        entry.name !== "generated" &&
        !entry.name.endsWith(".step"),
    )
    .sort((a, b) => a.name.localeCompare(b.name))
    .flatMap((entry) => collectFiles(join(path, entry.name)))
}

export const sha256File = (path) =>
  createHash("sha256").update(readFileSync(path)).digest("hex")

const sha256Text = (text) =>
  createHash("sha256").update(text).digest("hex")

const sortObject = (value) => {
  if (Array.isArray(value)) return value.map(sortObject)
  if (!value || typeof value !== "object") return value

  return Object.fromEntries(
    Object.entries(value)
      .sort(([left], [right]) => left.localeCompare(right))
      .map(([key, child]) => [key, sortObject(child)]),
  )
}

export const getCloudArtifactSourceState = (projectDirectory) => {
  const root = resolve(projectDirectory)
  const packageJsonPath = resolve(root, "package.json")
  const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"))
  const files = sourceInputs
    .flatMap((input) => collectFiles(resolve(root, input)))
    .sort()
  const dependencyDeclarations = sortObject({
    dependencies: packageJson.dependencies ?? {},
    devDependencies: packageJson.devDependencies ?? {},
    optionalDependencies: packageJson.optionalDependencies ?? {},
    peerDependencies: packageJson.peerDependencies ?? {},
  })
  const sourceFiles = files.map((path) => ({
    path: relative(root, path).split(sep).join("/"),
    sha256: sha256File(path),
    mtimeMs: statSync(path).mtimeMs,
  })).concat({
    // The registry increments package.json.version during `tsci push`, and Bun
    // may rewrite bun.lock during its cloud install. Hash only the immutable
    // dependency declarations so those expected mutations cannot make a fresh
    // board artifact appear stale.
    path: "package.json#dependencies",
    sha256: sha256Text(JSON.stringify(dependencyDeclarations)),
    // package.json's mtime also changes when `tsci push` increments only its
    // version, so it cannot be used as a reliable build-staleness timestamp.
    mtimeMs: 0,
  }).sort((left, right) => left.path.localeCompare(right.path))
  const combinedHash = createHash("sha256")

  for (const file of sourceFiles) {
    combinedHash.update(file.path)
    combinedHash.update("\0")
    combinedHash.update(file.sha256)
    combinedHash.update("\n")
  }

  return {
    sourceSha256: combinedHash.digest("hex"),
    newestSourceMtimeMs: Math.max(...sourceFiles.map((file) => file.mtimeMs)),
    sourceFiles: sourceFiles.map(({ path, sha256 }) => ({ path, sha256 })),
  }
}