Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fix-gridland-react-singleton.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@prover-coder-ai/docker-git": patch
---

Pin React and React DOM to the Gridland renderer-compatible 19.2.4 release so the CLI menu keeps a single valid React hook dispatcher.
19 changes: 10 additions & 9 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@
"unrs-resolver"
],
"overrides": {
"react": "19.2.7"
"react": "19.2.4",
"react-dom": "19.2.4"
},
"repository": {
"type": "git",
Expand Down
31 changes: 26 additions & 5 deletions packages/api/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,32 @@ RUN set -eu; \
&& nvidia-ctk runtime configure --runtime=docker \
&& rm -rf /var/lib/apt/lists/*

RUN curl -fsSL https://deb.nodesource.com/setup_24.x | bash - \
&& apt-get install -y --no-install-recommends nodejs \
&& curl -fsSL https://bun.sh/install | bash \
&& npm i -g node-gyp \
&& rm -rf /var/lib/apt/lists/*
RUN set -eu; \
installed=0; \
for attempt in 1 2 3 4 5; do \
rm -rf /var/lib/apt/lists/* /opt/bun; \
if curl -fsSL --retry 5 --retry-all-errors --retry-delay 2 https://deb.nodesource.com/setup_24.x -o /tmp/nodesource-setup.sh \
&& bash /tmp/nodesource-setup.sh \
&& apt-get -o Acquire::Retries=3 install -y --no-install-recommends nodejs \
&& npm install -g --prefix /opt/bun --no-audit --no-fund bun@1.3.11 node-gyp \

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the cited Dockerfile lines
echo "=== packages/api/Dockerfile (lines 35-80) ==="
nl -ba packages/api/Dockerfile | sed -n '35,80p'

# Find all node-gyp mentions
echo
echo "=== rg 'node-gyp' ==="
rg -n "node-gyp" -S .

# Find any hadolint/static-analysis mentions of DL3016/DL3008
echo
echo "=== rg 'DL3016|DL3008' ==="
rg -n "DL3016|DL3008" -S .

Repository: ProverCoderAI/docker-git

Length of output: 175


🏁 Script executed:

#!/bin/bash
set -euo pipefail

command -v rg >/dev/null 2>&1 && echo "rg: OK" || echo "rg: MISSING"
command -v awk >/dev/null 2>&1 && echo "awk: OK" || echo "awk: MISSING"
command -v sed >/dev/null 2>&1 && echo "sed: OK" || echo "sed: MISSING"

# Inspect cited Dockerfile lines (use awk for line numbers)
echo "=== packages/api/Dockerfile (lines 35-80) ==="
awk 'NR>=35 && NR<=80 {printf "%d\t%s\n", NR, $0}' packages/api/Dockerfile

echo
echo "=== rg 'node-gyp' ==="
rg -n "node-gyp" -S .

echo
echo "=== rg 'DL3016|DL3008' ==="
rg -n "DL3016|DL3008" -S .

Repository: ProverCoderAI/docker-git

Length of output: 4653


Зафиксируйте точную версию node-gyp при глобальной установке controller tooling.

В packages/api/Dockerfile строка 56 npm install -g --prefix /opt/bun --no-audit --no-fund bun@1.3.11 node-gyp выполняется без версии, из-за чего сборка недетерминирована и растёт supply-chain риск (также в корневом Dockerfile npm i -g node-gyp без версии).

Предлагаемый фикс
-        && npm install -g --prefix /opt/bun --no-audit --no-fund bun@1.3.11 node-gyp \
+        && npm install -g --prefix /opt/bun --no-audit --no-fund bun@1.3.11 node-gyp@<exact-version> \
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/api/Dockerfile` at line 56, Pin the node-gyp package to an explicit
version in the global installs to make builds deterministic: change the command
that currently reads "npm install -g --prefix /opt/bun --no-audit --no-fund
bun@1.3.11 node-gyp" to include a fixed node-gyp version (e.g., node-gyp@X.Y.Z)
and likewise update the root Dockerfile's "npm i -g node-gyp" to the same pinned
version; ensure both locations use the identical explicit version string so the
controller tooling install is reproducible.

Sources: Coding guidelines, Linters/SAST tools

&& node -v \
&& npm -v \
&& bun --version \
&& test "$(bun --version)" = "1.3.11" \
&& node-gyp --version; then \
installed=1; \
break; \
fi; \
echo "controller tooling install attempt ${attempt} failed; retrying..." >&2; \
rm -f /tmp/nodesource-setup.sh; \
sleep $((attempt * 2)); \
done; \
if [ "$installed" != "1" ]; then \
echo "controller tooling install failed after retries" >&2; \
exit 1; \
fi; \
rm -f /tmp/nodesource-setup.sh; \
rm -rf /var/lib/apt/lists/*

FROM controller-base AS workspace-deps

Expand Down
4 changes: 2 additions & 2 deletions packages/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,8 @@
"@gridland/bun": "0.4.3",
"@gridland/web": "0.4.3",
"effect": "^3.21.2",
"react": "^19.2.7",
"react-dom": "^19.2.7",
"react": "19.2.4",
"react-dom": "19.2.4",
"react-reconciler": "^0.33.0",
"ts-morph": "^28.0.0"
},
Expand Down
28 changes: 28 additions & 0 deletions packages/app/tests/docker-git/controller-resource-limits.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,34 @@ describe("API Dockerfile Electron materialization", () => {
}))
})

describe("API Dockerfile controller tooling install", () => {
it.effect("retries network-bound controller tooling downloads", () =>
Effect.gen(function*(_) {
const contents = yield* _(readComposeFile("packages/api/Dockerfile"))
expect(contents).toContain("https://deb.nodesource.com/setup_24.x -o /tmp/nodesource-setup.sh")
expect(contents).toContain("npm install -g --prefix /opt/bun --no-audit --no-fund bun@1.3.11 node-gyp")
expect(contents).toContain("curl -fsSL --retry 5 --retry-all-errors --retry-delay 2")
expect(contents).toContain("controller tooling install failed after retries")
expect(contents).toContain("test \"$(bun --version)\" = \"1.3.11\"")
expect(contents).toContain("node-gyp --version")
}))
Comment on lines +112 to +121

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Добавьте проверку верхней границы retry-цикла (1..5).

Сейчас тест не закрепляет ключевой инвариант из этого изменения — максимальное число попыток. На практике это позволит регрессии пройти незаметно.

Предлагаемое усиление теста
       expect(contents).toContain("curl -fsSL --retry 5 --retry-all-errors --retry-delay 2")
+      expect(contents).toContain("for attempt in 1 2 3 4 5; do")
       expect(contents).toContain("controller tooling install failed after retries")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/app/tests/docker-git/controller-resource-limits.test.ts` around
lines 112 - 121, The test "retries network-bound controller tooling downloads"
doesn't assert the explicit retry-range invariant; update the test (inside the
it.effect block using readComposeFile) to also assert that the retry loop upper
bound is fixed to 5 by checking for the exact loop/range token used in the
Dockerfile (e.g., assert contents contains "1..5" or the concrete loop string
your script emits such as "for i in 1..5" or "seq 1 5"), so the max-attempts
invariant is anchored alongside the existing checks for "--retry 5" and the
failure message.

})

describe("OpenCode E2E auth bootstrap", () => {
it.effect("retries controller auth commands before the clone scenario", () =>
Effect.gen(function*(_) {
const contents = yield* _(readComposeFile("scripts/e2e/opencode-autoconnect.sh"))
expect(contents).toContain("auth_attempts=3")
expect(contents).toContain(": > \"$AUTH_LOG\"")
expect(contents).toContain("if (")
expect(contents).toContain("dg_run_docker_git \"$REPO_ROOT\" auth codex import")
expect(contents).toContain("dg_run_docker_git \"$REPO_ROOT\" auth codex status")
expect(contents).toContain(") >>\"$AUTH_LOG\" 2>&1")
expect(contents).toContain("auth bootstrap attempt $auth_attempt/$auth_attempts failed")
expect(contents).toContain("docker-git auth bootstrap failed after $auth_attempts attempts")
}))
})

describe("controller resource limit resolution", () => {
it.effect("resolves CPU and RAM defaults to 90% of host resources", () =>
Effect.sync(() => {
Expand Down
22 changes: 21 additions & 1 deletion packages/app/tests/docker-git/gridland-react-singleton.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,31 @@ import { describe, expect, it } from "@effect/vitest"
import { Effect } from "effect"

import rootPackage from "../../../../package.json" with { type: "json" }
import terminalPackage from "../../../terminal/package.json" with { type: "json" }
import appPackage from "../../package.json" with { type: "json" }

// CHANGE: encode the React version resolved for the Gridland Bun renderer.
// WHY: @gridland/bun embeds react-reconciler@0.33.0; Bun resolves that renderer contract to React 19.2.4.
// QUOTE(ISSUE): "TypeError: null is not an object (evaluating 'resolveDispatcher().useCallback')"
// REF: issue-385
// SOURCE: n/a
// FORMAT THEOREM: workspaceReactVersion = rendererReactVersion -> dispatcher(hookCall) != null
// PURITY: CORE
// EFFECT: n/a
// INVARIANT: every workspace React entry resolves to the renderer-compatible singleton version.
// COMPLEXITY: O(1)
const gridlandRendererReactVersion = "19.2.4"

const stripCaret = (value: string): string => value.replace(/^\^/u, "")

describe("Gridland React singleton contract", () => {
it.effect("pins React across workspace dependencies for the Gridland renderer", () =>
Effect.sync(() => {
expect(rootPackage.overrides.react).toBe(appPackage.dependencies.react.replace(/^\^/u, ""))
expect(rootPackage.overrides.react).toBe(gridlandRendererReactVersion)
expect(rootPackage.overrides["react-dom"]).toBe(gridlandRendererReactVersion)
expect(stripCaret(appPackage.dependencies.react)).toBe(gridlandRendererReactVersion)
expect(stripCaret(appPackage.dependencies["react-dom"])).toBe(gridlandRendererReactVersion)
expect(stripCaret(terminalPackage.dependencies.react)).toBe(gridlandRendererReactVersion)
expect(stripCaret(terminalPackage.devDependencies["react-dom"])).toBe(gridlandRendererReactVersion)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Проверка, что тест валидирует нужное поле и манифест соответствует ему"
rg -n --type=ts 'terminalPackage\.(devDependencies|dependencies)\["react-dom"\]' packages/app/tests/docker-git/gridland-react-singleton.test.ts
python - <<'PY'
import json, pathlib
pkg = json.loads(pathlib.Path("packages/terminal/package.json").read_text())
print("dependencies.react-dom:", pkg.get("dependencies", {}).get("react-dom"))
print("devDependencies.react-dom:", pkg.get("devDependencies", {}).get("react-dom"))
PY

Repository: ProverCoderAI/docker-git

Length of output: 311


Исправить тест singleton-контракта: проверять react-dom в dependencies, а не в devDependencies.

Сейчас тест на Line ~30 фиксирует terminalPackage.devDependencies["react-dom"], но в packages/terminal/package.json react-dom отсутствует в dependencies (там None), из-за чего регресс runtime-зависимости может пройти незамеченной.

Предлагаемый фикс
-      expect(stripCaret(terminalPackage.devDependencies["react-dom"])).toBe(gridlandRendererReactVersion)
+      expect(stripCaret(terminalPackage.dependencies["react-dom"])).toBe(gridlandRendererReactVersion)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/app/tests/docker-git/gridland-react-singleton.test.ts` at line 30,
Тест проверяет неверное поле: вместо проверять
terminalPackage.devDependencies["react-dom"] надо проверять
terminalPackage.dependencies["react-dom"]; исправьте ожидание в тесте (используя
уже применённую stripCaret и gridlandRendererReactVersion) так, чтобы оно
проверяло наличие и версию react-dom в dependencies, а не в devDependencies,
сохранив остальную логику и переменные (terminalPackage, stripCaret,
gridlandRendererReactVersion, "react-dom").

Source: Coding guidelines

}))
})
4 changes: 2 additions & 2 deletions packages/terminal/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"@effect/platform-node": "^0.107.0",
"@effect/schema": "^0.75.5",
"effect": "^3.21.2",
"react": "^19.2.7",
"react": "19.2.4",
"xterm": "^5.3.0",
"xterm-addon-fit": "^0.8.0"
},
Expand Down Expand Up @@ -55,7 +55,7 @@
"fast-check": "^4.8.0",
"globals": "^17.6.0",
"jscpd": "^4.2.4",
"react-dom": "^19.2.7",
"react-dom": "19.2.4",
"typescript": "^6.0.3",
"typescript-eslint": "^8.60.1",
"vite": "^8.0.16",
Expand Down
25 changes: 20 additions & 5 deletions scripts/e2e/opencode-autoconnect.sh
Original file line number Diff line number Diff line change
Expand Up @@ -124,11 +124,26 @@ OPENCODE_AUTO_CONNECT=1
EOF_ENV

AUTH_LOG="$ROOT/codex-auth.log"
(
cd "$REPO_ROOT"
dg_run_docker_git "$REPO_ROOT" auth codex import --codex-auth "$ROOT/.orch/auth/codex"
dg_run_docker_git "$REPO_ROOT" auth codex status --codex-auth "$ROOT/.orch/auth/codex"
) >"$AUTH_LOG" 2>&1
auth_attempts=3
auth_attempt=1
auth_exit=0
: > "$AUTH_LOG"
while [[ "$auth_attempt" -le "$auth_attempts" ]]; do
if (
cd "$REPO_ROOT"
dg_run_docker_git "$REPO_ROOT" auth codex import --codex-auth "$ROOT/.orch/auth/codex"
dg_run_docker_git "$REPO_ROOT" auth codex status --codex-auth "$ROOT/.orch/auth/codex"
) >>"$AUTH_LOG" 2>&1; then
auth_exit=0
break
else
auth_exit=$?
fi
echo "e2e/opencode-autoconnect: auth bootstrap attempt $auth_attempt/$auth_attempts failed (exit: $auth_exit); retrying..." >&2
auth_attempt="$((auth_attempt + 1))"
sleep 2
done
[[ "$auth_exit" -eq 0 ]] || fail "docker-git auth bootstrap failed after $auth_attempts attempts (last exit: $auth_exit)"

auth_confirmation_count="$(grep -Fc -- "Codex auth imported into controller state (account: ci@example.com)." "$AUTH_LOG" || true)"
[[ "$auth_confirmation_count" -ge 2 ]] \
Expand Down
Loading