asset-release.py (2090B)
1 #!/usr/bin/env python3 2 """Place a complete static dependency tree under a reproducible release URL.""" 3 import argparse 4 import hashlib 5 import json 6 from pathlib import Path 7 8 9 def freeze(root): 10 root = Path(root) 11 names = ['dist', 'runtime', 'images', 'reference', 'style.css', 'catalogue.json', 'compiler.json', 12 'packages.json', 'environments.json'] 13 files = sorted(p for name in names for p in 14 ((root / name).rglob('*') if (root / name).is_dir() else [root / name]) 15 if p.is_file()) 16 rows = [{'path': p.relative_to(root).as_posix(), 'size': p.stat().st_size, 17 'sha256': hashlib.sha256(p.read_bytes()).hexdigest()} for p in files] 18 rows.sort(key=lambda row: row['path']) 19 encoded = json.dumps(rows, sort_keys=True, separators=(',', ':')).encode() 20 version = hashlib.sha256(b'arboricx.site-assets.v1\0' + encoded).hexdigest() 21 prefix = 'assets/' + version 22 html = (root / 'index.html').read_text() 23 for name in ['dist/app.js', 'style.css', 'images/favicon.png', 'images/icon.svg', 'images/apple-touch-icon.png']: 24 old = './' + name 25 if html.count('"' + old + '"') != 1: 26 raise ValueError('Expected one HTML entrypoint: ' + old) 27 html = html.replace('"' + old + '"', '"./' + prefix + '/' + name + '"') 28 html = html.replace('https://arbori.cx/images/preview.png', 'https://arbori.cx/' + prefix + '/images/preview.png') 29 llms = root / 'llms.txt' 30 if llms.exists(): 31 llms.write_text(llms.read_text().replace('./reference/', './' + prefix + '/reference/')) 32 destination = root / prefix 33 destination.mkdir(parents=True, exist_ok=False) 34 for name in names: 35 (root / name).rename(destination / name) 36 (root / 'index.html').write_text(html) 37 (root / 'assets.json').write_text(json.dumps( 38 {'version': version, 'base': prefix + '/', 'files': rows}, indent=2) + '\n') 39 40 41 if __name__ == '__main__': 42 parser = argparse.ArgumentParser(description=__doc__) 43 parser.add_argument('root', type=Path) 44 freeze(parser.parse_args().root)