abse/museair-usb

USB-C–powered ESP32-C3 controller board with AP63203 buck conversion, AP2112K 3.3V regulation, I²C SEN62 sensor interface, status LED, buttons, UART/I²C headers, and extensive decoupling.

Version
0.5.7
License
unset
Stars
0

scripts/package-release.py

"""Package the checked prototype project without dependencies or build caches."""
from pathlib import Path
import datetime, hashlib, json, zipfile

root = Path(__file__).resolve().parent.parent
audit = json.loads((root / 'validation/production-audit.json').read_text())
assert audit['status'] == 'pass'
for rel, digest in audit['sha256'].items():
    assert hashlib.sha256((root / rel).read_bytes()).hexdigest() == digest, rel

excluded_dirs = {'node_modules', '.tscircuit', '.claude', '__pycache__'}
excluded_files = {'RELEASE-MANIFEST.json', 'dist/index/raw.kicad_pcb'}
files = sorted(f for f in root.rglob('*')
               if f.is_file() and not f.is_symlink()
               and not excluded_dirs.intersection(f.relative_to(root).parts)
               and str(f.relative_to(root)) not in excluded_files
               and f.name != '.DS_Store' and f.suffix != '.kicad_prl')
manifest = {
    'project': 'MuseAir USB v0.5 — SEN62-SIN-T',
    'generated_at_utc': datetime.datetime.now(datetime.timezone.utc).isoformat(),
    'pcb_audit': 'pass; zero DRC violations and zero unconnected items',
    'sensor_and_cable': 'Separate owner purchase and installation; not in JLC SMT BOM/CPL',
    'carrier_status': 'Printed prototype; CAD clearances checked, physical fit pending',
    'stock_reserved': False,
    'hardware_tested': False,
    'sha256': {str(f.relative_to(root)): hashlib.sha256(f.read_bytes()).hexdigest()
               for f in files},
}
(root / 'RELEASE-MANIFEST.json').write_text(json.dumps(manifest, indent=2) + '\n')
files.append(root / 'RELEASE-MANIFEST.json')
out = root.parent / 'museair-usb-prototype-package.zip'
with zipfile.ZipFile(out, 'w', zipfile.ZIP_DEFLATED) as z:
    for f in files:
        z.write(f, 'museair-usb/' + str(f.relative_to(root)))
with zipfile.ZipFile(out) as z:
    assert z.testzip() is None
    assert len(z.namelist()) == len(files)
digest = hashlib.sha256(out.read_bytes()).hexdigest()
(out.parent / (out.name + '.sha256')).write_text(digest + '  ' + out.name + '\n')
print(f'Saved {out.name}: {len(files)} files, {out.stat().st_size:,} bytes')