#!/usr/bin/env python3 """Build Deepgrain Foundry into one self-contained HTML file. The source files listed in src/manifest.json are joined in order (sim first, then the browser code) into one classic script, so they share one global scope and 'use strict' at the top of src/sim/base.js applies to all of it. No bundler, no dependencies. Writes: dist/index.html the full page: open it, or serve dist/ with python -m http.server dist/deepgrain-foundry.html the same without /
/, for hosts (like a claude.ai artifact) that wrap the page themselves dist/bundle-lines.json where each source file starts in dist/index.html, so a line number from a browser error can be traced back: python3 tools/where.py LINE """ import json, pathlib root = pathlib.Path(__file__).resolve().parent src = root / 'src' dist = root / 'dist' man = json.loads((src / 'manifest.json').read_text()) def join(files): """Join files with one newline between them; return the text and [first line, file] pairs.""" parts, starts, line = [], [], 1 for f in files: text = (src / f).read_text().rstrip('\n') starts.append([line, f]) parts.append(text) line += text.count('\n') + 1 return '\n'.join(parts), starts def main(): shell = (src / man['shell']).read_text() css, _ = join(man['css']) js, starts = join(man['sim'] + man['client']) head, tail = shell.split('//__JS__') body = head.replace('/*__CSS__*/', css) page = body + js + tail pre = ('' '' '') full = pre + page + '\n' dist.mkdir(exist_ok=True) (dist / 'deepgrain-foundry.html').write_text(page) (dist / 'index.html').write_text(full) # the script starts on this line of index.html (the doctype prefix has no newlines) js_line = body.count('\n') + 1 (dist / 'bundle-lines.json').write_text(json.dumps( {'page': 'dist/index.html', 'files': [[js_line + s - 1, f] for s, f in starts]}, indent=1) + '\n') print(f'built dist/index.html ({len(full):,} bytes, {len(starts)} source files)') if __name__ == '__main__': main()