tools/serve.py: build, serve and rebuild on change (t9)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -63,7 +63,7 @@ them. In short:
|
|||||||
|
|
||||||
```sh
|
```sh
|
||||||
python3 build.py # build dist/index.html from src/ (takes a second)
|
python3 build.py # build dist/index.html from src/ (takes a second)
|
||||||
python3 -m http.server -d dist # play at http://localhost:8000 (or open dist/index.html)
|
python3 tools/serve.py # build, serve at http://localhost:8000, rebuild on src/ changes
|
||||||
python3 test/run_all.py # all headless test suites, in parallel (about a minute)
|
python3 test/run_all.py # all headless test suites, in parallel (about a minute)
|
||||||
python3 test/run_all.py pipes # only suites whose name contains "pipes"
|
python3 test/run_all.py pipes # only suites whose name contains "pipes"
|
||||||
python3 test/run_all.py --browser # also the browser tests (rebuilds first; about 3 more minutes)
|
python3 test/run_all.py --browser # also the browser tests (rebuilds first; about 3 more minutes)
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ machines, until the land claim is paid off and beyond.
|
|||||||
|
|
||||||
```sh
|
```sh
|
||||||
python3 build.py
|
python3 build.py
|
||||||
python3 -m http.server -d dist # then open http://localhost:8000
|
python3 tools/serve.py # builds, serves at http://localhost:8000 and rebuilds when src/ changes
|
||||||
```
|
```
|
||||||
|
|
||||||
Opening `dist/index.html` directly works too. The game is one self-contained page with no
|
Opening `dist/index.html` directly works too. The game is one self-contained page with no
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ code) into one classic script, so they share one global scope and 'use strict' a
|
|||||||
src/sim/base.js applies to all of it. No bundler, no dependencies.
|
src/sim/base.js applies to all of it. No bundler, no dependencies.
|
||||||
|
|
||||||
Writes:
|
Writes:
|
||||||
dist/index.html the full page: open it, or serve dist/ with python -m http.server
|
dist/index.html the full page: open it, or serve with python3 tools/serve.py
|
||||||
dist/deepgrain-foundry.html the same without <!doctype>/<head>/<body>, for hosts (like a
|
dist/deepgrain-foundry.html the same without <!doctype>/<head>/<body>, for hosts (like a
|
||||||
claude.ai artifact) that wrap the page themselves
|
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
|
dist/bundle-lines.json where each source file starts in dist/index.html, so a line number
|
||||||
|
|||||||
+5
-3
@@ -20,9 +20,10 @@ src/ui/ the browser shell: input, HUD, build menu, notebook, setti
|
|||||||
audio, the frame loop
|
audio, the frame loop
|
||||||
test/ headless suites (t_*.js) and their harness; browser tests (play.py,
|
test/ headless suites (t_*.js) and their harness; browser tests (play.py,
|
||||||
t_smooth.py); run_all.py
|
t_smooth.py); run_all.py
|
||||||
tools/ shot.py (screenshots), where.py, map.py, perf.py, playthrough.py,
|
tools/ serve.py (build, serve, rebuild on change), shot.py (screenshots),
|
||||||
use_venv.py (switches browser scripts to .venv), scenes/ (screenshot
|
where.py, map.py, perf.py, playthrough.py, use_venv.py (switches browser
|
||||||
setups), experiments/ (measurements and reports)
|
scripts to .venv), scenes/ (screenshot setups), experiments/
|
||||||
|
(measurements and reports)
|
||||||
docs/ this map, DESIGN.md, TESTING.md, systems/ (notes per system),
|
docs/ this map, DESIGN.md, TESTING.md, systems/ (notes per system),
|
||||||
history/ (the owner's requests), agent-log.md
|
history/ (the owner's requests), agent-log.md
|
||||||
dist/, shots/ build output and screenshots (not committed)
|
dist/, shots/ build output and screenshots (not committed)
|
||||||
@@ -456,6 +457,7 @@ The frame loop and boot.
|
|||||||
- `tools/map.py`: Regenerate the file and symbol index at the end of docs/PROJECT_MAP.md.
|
- `tools/map.py`: Regenerate the file and symbol index at the end of docs/PROJECT_MAP.md.
|
||||||
- `tools/perf.py`: Times the renderer and the simulation in a headless browser: light, drawWorld (with and without light and glow), the pixel upload and blit, and one simulation tick, in ms. Rebuild first.
|
- `tools/perf.py`: Times the renderer and the simulation in a headless browser: light, drawWorld (with and without light and glow), the pixel upload and blit, and one simulation tick, in ms. Rebuild first.
|
||||||
- `tools/playthrough.py`: Headless browser run: load the page, start a world, play a little, take screenshots.
|
- `tools/playthrough.py`: Headless browser run: load the page, start a world, play a little, take screenshots.
|
||||||
|
- `tools/serve.py`: Build, serve and rebuild when sources change: python3 tools/serve.py [--port 8000].
|
||||||
- `tools/shot.py`: Screenshot the game in a headless browser, after setting up a scene with JavaScript.
|
- `tools/shot.py`: Screenshot the game in a headless browser, after setting up a scene with JavaScript.
|
||||||
- `tools/use_venv.py`: Let browser scripts run with plain python3 by switching to the repo's .venv.
|
- `tools/use_venv.py`: Let browser scripts run with plain python3 by switching to the repo's .venv.
|
||||||
- `tools/where.py`: Turn a line number in dist/index.html into the source file and line it came from.
|
- `tools/where.py`: Turn a line number in dist/index.html into the source file and line it came from.
|
||||||
|
|||||||
@@ -4,6 +4,15 @@ What agents have done, newest first. Add an entry for every piece of work: the d
|
|||||||
note or request it was for, what changed, the files touched, the tests run, and anything left
|
note or request it was for, what changed, the files touched, the tests run, and anything left
|
||||||
open or not verified. Keep each entry short; the git history has the details.
|
open or not verified. Keep each entry short; the git history has the details.
|
||||||
|
|
||||||
|
## 2026-10-24: t9 Script to run the local server for testing (Claude Code, Codex)
|
||||||
|
|
||||||
|
- New `tools/serve.py`: builds, serves `dist/` on 127.0.0.1 (port 8000, or the next free one up
|
||||||
|
to 8010), sends `Cache-Control: no-store`, and rebuilds when `src/` or `build.py` changes; a
|
||||||
|
failed build is printed and the old page keeps being served.
|
||||||
|
- `README.md`, `AGENTS.md`, `build.py` docstring, `docs/PROJECT_MAP.md`: point at it.
|
||||||
|
- Checked by hand: page served with no-store; touching a source file rebuilt it; with the owner's
|
||||||
|
old server on 8000 it moved to 8001; Ctrl+C stops it. Headless suites and map check pass.
|
||||||
|
|
||||||
## 2026-10-24: t11 Welcome and first milestone shown again on an older world (Claude Code, Codex)
|
## 2026-10-24: t11 Welcome and first milestone shown again on an older world (Claude Code, Codex)
|
||||||
|
|
||||||
- Cause: nothing cleared the discovery cards (`#cards`) when a world was entered, so a new
|
- Cause: nothing cleared the discovery cards (`#cards`) when a world was entered, so a new
|
||||||
|
|||||||
@@ -1,11 +1,23 @@
|
|||||||
---
|
---
|
||||||
id: t9
|
id: t9
|
||||||
title: Script to run the local server for testing
|
title: Script to run the local server for testing
|
||||||
status: Todo
|
status: Review
|
||||||
created: 2026-10-24
|
created: 2026-10-24
|
||||||
updated: 2026-10-24T04:41:46Z
|
updated: 2026-10-24T05:06:19Z
|
||||||
|
order: 3
|
||||||
|
report: |
|
||||||
|
(agent) New: python3 tools/serve.py builds the game, serves it at http://localhost:8000 and
|
||||||
|
rebuilds by itself when anything in src/ changes. Pages aren't cached, so a normal refresh
|
||||||
|
shows the new build. If 8000 is taken (an old server still running), it uses 8001 and says so.
|
||||||
|
To check: stop your old server (Ctrl+C), run python3 tools/serve.py, open the address it
|
||||||
|
prints, change something in src/, refresh. Ctrl+C stops it.
|
||||||
---
|
---
|
||||||
|
|
||||||
## Context
|
## Context
|
||||||
|
|
||||||
- i need a script to run the local server for testing.
|
- i need a script to run the local server for testing.
|
||||||
|
|
||||||
|
## Subtasks
|
||||||
|
|
||||||
|
- [x] Add the server and update the command references and map.
|
||||||
|
- [x] Check serving, rebuilds, port fallback and shutdown; run the headless suites.
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Build, serve and rebuild when sources change: python3 tools/serve.py [--port 8000]."""
|
||||||
|
import argparse, errno, http.server, pathlib, subprocess, sys, threading, time
|
||||||
|
|
||||||
|
ROOT = pathlib.Path(__file__).resolve().parent.parent
|
||||||
|
DIST = ROOT / 'dist'
|
||||||
|
|
||||||
|
|
||||||
|
class Handler(http.server.SimpleHTTPRequestHandler):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, directory=str(DIST), **kwargs)
|
||||||
|
|
||||||
|
def end_headers(self):
|
||||||
|
self.send_header('Cache-Control', 'no-store')
|
||||||
|
super().end_headers()
|
||||||
|
|
||||||
|
def log_message(self, format, *args):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def build(first=False):
|
||||||
|
result = subprocess.run([sys.executable, str(ROOT / 'build.py')], cwd=ROOT,
|
||||||
|
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
|
||||||
|
if result.returncode:
|
||||||
|
print('build failed:\n' + result.stdout, flush=True)
|
||||||
|
elif first:
|
||||||
|
print(result.stdout, end='', flush=True)
|
||||||
|
else:
|
||||||
|
print(time.strftime('rebuilt %H:%M:%S'), flush=True)
|
||||||
|
|
||||||
|
|
||||||
|
def mtimes():
|
||||||
|
files = [ROOT / 'build.py', *(ROOT / 'src').rglob('*')]
|
||||||
|
stamps = {}
|
||||||
|
for path in files:
|
||||||
|
try:
|
||||||
|
if path.is_file(): stamps[path] = path.stat().st_mtime_ns
|
||||||
|
except FileNotFoundError:
|
||||||
|
pass
|
||||||
|
return stamps
|
||||||
|
|
||||||
|
|
||||||
|
def watch(stopped, previous):
|
||||||
|
while not stopped.wait(0.5):
|
||||||
|
current = mtimes()
|
||||||
|
if current != previous:
|
||||||
|
previous = current
|
||||||
|
build()
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description=__doc__)
|
||||||
|
parser.add_argument('--port', type=int, default=8000)
|
||||||
|
args = parser.parse_args()
|
||||||
|
if not 1 <= args.port <= 65525:
|
||||||
|
parser.error('--port must be between 1 and 65525 (leaves room for ten retries)')
|
||||||
|
previous = mtimes()
|
||||||
|
build(first=True)
|
||||||
|
if not (DIST / 'index.html').is_file():
|
||||||
|
sys.exit('Cannot serve: dist/index.html does not exist. Fix the build and try again.')
|
||||||
|
for port in range(args.port, args.port + 11):
|
||||||
|
try:
|
||||||
|
server = http.server.ThreadingHTTPServer(('127.0.0.1', port), Handler)
|
||||||
|
break
|
||||||
|
except OSError as error:
|
||||||
|
if error.errno != errno.EADDRINUSE:
|
||||||
|
sys.exit(f'Cannot serve on port {port}: {error}')
|
||||||
|
else:
|
||||||
|
sys.exit(f'Cannot serve: ports {args.port}-{args.port + 10} are all busy.')
|
||||||
|
if port != args.port:
|
||||||
|
print(f'Port {args.port} was busy; using {port}.', flush=True)
|
||||||
|
print(f'Serving dist/ at http://localhost:{port} (Ctrl+C to stop)', flush=True)
|
||||||
|
stopped = threading.Event()
|
||||||
|
threading.Thread(target=watch, args=(stopped, previous), daemon=True).start()
|
||||||
|
try:
|
||||||
|
with server:
|
||||||
|
server.serve_forever()
|
||||||
|
finally:
|
||||||
|
stopped.set()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
try:
|
||||||
|
main()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print('\nServer stopped.', flush=True)
|
||||||
Reference in New Issue
Block a user