From 4424a0e0f772f7945e4ebedb35540bcc34747567 Mon Sep 17 00:00:00 2001 From: Brooklyn Nicholson Date: Mon, 27 Apr 2026 10:04:32 -0500 Subject: [PATCH 1/2] fix(docker): prebuild TUI assets in image --- Dockerfile | 8 ++++++-- tests/tools/test_dockerfile_pid1_reaping.py | 9 +++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 4ab1d380..bfe6402d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -30,18 +30,22 @@ WORKDIR /opt/hermes # unless the lockfiles themselves change. COPY package.json package-lock.json ./ COPY web/package.json web/package-lock.json web/ +COPY ui-tui/package.json ui-tui/package-lock.json ui-tui/ +COPY ui-tui/packages/hermes-ink/package.json ui-tui/packages/hermes-ink/ RUN npm install --prefer-offline --no-audit && \ npx playwright install --with-deps chromium --only-shell && \ (cd web && npm install --prefer-offline --no-audit) && \ + (cd ui-tui && npm install --prefer-offline --no-audit) && \ npm cache clean --force # ---------- Source code ---------- # .dockerignore excludes node_modules, so the installs above survive. COPY --chown=hermes:hermes . . -# Build web dashboard (Vite outputs to hermes_cli/web_dist/) -RUN cd web && npm run build +# Build browser dashboard and terminal UI assets. +RUN cd web && npm run build && \ + cd ../ui-tui && npm run build # ---------- Permissions ---------- # Make install dir world-readable so any HERMES_UID can read it at runtime. diff --git a/tests/tools/test_dockerfile_pid1_reaping.py b/tests/tools/test_dockerfile_pid1_reaping.py index 55bd5e06..657eba9d 100644 --- a/tests/tools/test_dockerfile_pid1_reaping.py +++ b/tests/tools/test_dockerfile_pid1_reaping.py @@ -76,3 +76,12 @@ def test_dockerfile_entrypoint_routes_through_the_init(dockerfile_text): "If tini is only installed but not wired into ENTRYPOINT, hermes " "still runs as PID 1 and zombies will accumulate (#15012)." ) + + +def test_dockerfile_installs_tui_dependencies(dockerfile_text): + assert "ui-tui/package.json" in dockerfile_text + assert "cd ui-tui && npm install" in dockerfile_text + + +def test_dockerfile_builds_tui_assets(dockerfile_text): + assert "cd ../ui-tui && npm run build" in dockerfile_text From b479205396f0405d9644a090044550c16c6faf32 Mon Sep 17 00:00:00 2001 From: Brooklyn Nicholson Date: Mon, 27 Apr 2026 10:15:00 -0500 Subject: [PATCH 2/2] fix(docker): tighten TUI build contract --- Dockerfile | 2 +- tests/tools/test_dockerfile_pid1_reaping.py | 40 +++++++++++++++++++-- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index bfe6402d..7f4ebc2d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -31,7 +31,7 @@ WORKDIR /opt/hermes COPY package.json package-lock.json ./ COPY web/package.json web/package-lock.json web/ COPY ui-tui/package.json ui-tui/package-lock.json ui-tui/ -COPY ui-tui/packages/hermes-ink/package.json ui-tui/packages/hermes-ink/ +COPY ui-tui/packages/hermes-ink/package.json ui-tui/packages/hermes-ink/package-lock.json ui-tui/packages/hermes-ink/ RUN npm install --prefer-offline --no-audit && \ npx playwright install --with-deps chromium --only-shell && \ diff --git a/tests/tools/test_dockerfile_pid1_reaping.py b/tests/tools/test_dockerfile_pid1_reaping.py index 657eba9d..1e47b64f 100644 --- a/tests/tools/test_dockerfile_pid1_reaping.py +++ b/tests/tools/test_dockerfile_pid1_reaping.py @@ -30,6 +30,31 @@ def dockerfile_text() -> str: return DOCKERFILE.read_text() +def _dockerfile_instructions(dockerfile_text: str) -> list[str]: + instructions: list[str] = [] + current = "" + + for raw_line in dockerfile_text.splitlines(): + line = raw_line.strip() + if not line or line.startswith("#"): + continue + + current = f"{current} {line.removesuffix('\\').strip()}".strip() + if not line.endswith("\\"): + instructions.append(current) + current = "" + + return instructions + + +def _run_steps(dockerfile_text: str) -> list[str]: + return [ + instruction + for instruction in _dockerfile_instructions(dockerfile_text) + if instruction.startswith("RUN ") + ] + + def test_dockerfile_installs_an_init_for_zombie_reaping(dockerfile_text): """Some init (tini, dumb-init, catatonit) must be installed. @@ -80,8 +105,19 @@ def test_dockerfile_entrypoint_routes_through_the_init(dockerfile_text): def test_dockerfile_installs_tui_dependencies(dockerfile_text): assert "ui-tui/package.json" in dockerfile_text - assert "cd ui-tui && npm install" in dockerfile_text + assert "ui-tui/packages/hermes-ink/package-lock.json" in dockerfile_text + assert any( + "ui-tui" in step + and "npm" in step + and (" install" in step or " ci" in step) + for step in _run_steps(dockerfile_text) + ) def test_dockerfile_builds_tui_assets(dockerfile_text): - assert "cd ../ui-tui && npm run build" in dockerfile_text + assert any( + "ui-tui" in step + and "npm" in step + and "run build" in step + for step in _run_steps(dockerfile_text) + )