Something went wrong. Try again.
A warm, tech-dystopian color scheme inspired by corrupted hardware and failing systems. digitalrust.katsuricata.com
theme color-scheme color-theme warm color
Something went wrong. Try again.
5.3 kB · 136 lines
Python
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137#!/usr/bin/env python3# SPDX-FileCopyrightText: 2025-2026 Kat Suricata <https://KatSuricata.com/># SPDX-License-Identifier: (CC-BY-4.0+ OR Apache-2.0+)
"""Builds the release files into `.release/dist/`.
The release pipeline runs this, and so can you, before a tag exists:
python3 scripts/make-archives.py --version 1.3.1 --check-only python3 scripts/make-archives.py --version 1.3.1
Two rules come from the Tangled pipeline and are enforced here:
* Every published file carries an extension. Pages maps a path without one to a directory and then answers 404, so `SHA256SUMS` is `SHA256SUMS.txt`.* The version in the specification, the version in `package.json`, and the version on the tag must agree. The site's version badge reads the specification, so a mismatch ships a release that reports the wrong number.
The archives carry no timestamps from the machine that built them, so the samecommit produces the same file contents in the same order. The compressed bytescan still differ between machines, because a deflate implementation is free tovary between zlib versions: the release published from v1.3.0 matches a localrebuild entry for entry and CRC for CRC, but its two archives are notbyte-identical. Compare the entries, or compare a download against the checksumspublished beside it."""
from __future__ import annotations
import argparseimport hashlibimport jsonimport pathlibimport reimport sysimport zipfile
ROOT = pathlib.Path(__file__).resolve().parent.parentSPEC = ROOT / 'Digital Rust Syntax Highlighting Specification.md'UI_SPEC = ROOT / 'Digital Rust UI Specification.md'PORTS = ROOT / 'static' / 'ports'SKILL_ZIP = PORTS / 'agentskill.zip'DIST = ROOT / '.release' / 'dist'
# A zip entry needs a date. A fixed one keeps the archive reproducible, and the# release date is on the pages index where a reader looks for it.FIXED_DATE = (1980, 1, 1, 0, 0, 0)
def spec_version() -> str: """The first version history entry in the syntax specification.""" match = re.search(r'^- \*\*v(\d+\.\d+\.\d+)\*\*', SPEC.read_text(), re.MULTILINE) if not match: sys.exit(f'no version line in {SPEC.name}; the release cannot be named') return match.group(1)
def package_version() -> str: return json.loads((ROOT / 'package.json').read_text())['version']
def check_versions(version: str) -> None: problems = [] if spec_version() != version: problems.append(f'the specification says v{spec_version()}') if package_version() != version: problems.append(f'package.json says {package_version()}') if problems: sys.exit(f'version {version} does not match: ' + '; '.join(problems))
def write_zip(path: pathlib.Path, entries: list[pathlib.Path]) -> None: """Writes a zip whose entries are sorted, so the bytes do not move.""" with zipfile.ZipFile(path, 'w', zipfile.ZIP_DEFLATED) as archive: for source in sorted(entries): relative = source.relative_to(ROOT / 'static') info = zipfile.ZipInfo(str(relative), date_time=FIXED_DATE) info.compress_type = zipfile.ZIP_DEFLATED info.create_system = 3 # unix, so the container fields do not follow the platform info.external_attr = 0o644 << 16 archive.writestr(info, source.read_bytes())
def build(version: str) -> list[pathlib.Path]: DIST.mkdir(parents=True, exist_ok=True) for stale in DIST.iterdir(): stale.unlink()
# The ports, one file per tool. The agent skill ships as its own archive # because it is installed as a directory, not read in place. ports = [p for p in PORTS.rglob('*') if p.is_file() and p != SKILL_ZIP] ports_zip = DIST / f'digital-rust-ports-v{version}.zip' write_zip(ports_zip, ports)
skill_zip = DIST / f'digital-rust-agentskill-v{version}.zip' skill_zip.write_bytes(SKILL_ZIP.read_bytes())
# The specifications are the canonical documents, and the release is where a # reader goes to get them without a git client. specs = [ (SPEC, DIST / 'digital-rust-syntax-highlighting-specification.md'), (UI_SPEC, DIST / 'digital-rust-ui-specification.md'), ] for source, target in specs: target.write_bytes(source.read_bytes())
published = sorted([ports_zip, skill_zip, *(target for _, target in specs)]) lines = [ f'{hashlib.sha256(path.read_bytes()).hexdigest()} {path.name}' for path in published ] (DIST / 'SHA256SUMS.txt').write_text('\n'.join(lines) + '\n') return [*published, DIST / 'SHA256SUMS.txt']
def main() -> None: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('--version', required=True, help='release version, without the v') parser.add_argument( '--check-only', action='store_true', help='check the versions and stop, for a run before the tag exists' ) args = parser.parse_args()
check_versions(args.version) if args.check_only: print(f'version {args.version} agrees in the specification and package.json') return
for path in build(args.version): print(f'{path.relative_to(ROOT)} {path.stat().st_size} bytes')
if __name__ == '__main__': main()