From 0096b9282e063cb527b465e6ae134124413dd849 Mon Sep 17 00:00:00 2001 From: gabemeola <14303404+gabemeola@users.noreply.github.com> Date: Mon, 6 Jul 2026 22:53:36 +0000 Subject: [PATCH 1/9] Add npm/npx lazy-init shims via mise; restore openssh-client --- Dockerfile | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 38ad14b..32218b8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -23,7 +23,7 @@ ENV DEBIAN_FRONTEND=noninteractive # CLI utilities for day-to-day dev work (git, curl, etc.). RUN apt-get update && apt-get install -y --no-install-recommends \ - ca-certificates curl git unzip \ + ca-certificates curl git openssh-client unzip \ less libatomic1 sudo tini tzdata \ && rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*.deb \ && rm -rf /usr/share/doc /usr/share/man /usr/share/locale \ @@ -162,6 +162,10 @@ RUN opencode --version \ && printf '#!/usr/bin/env bash\nexec /usr/local/bin/mise exec "%s" -- %s "$@"\n' "$key" "$shim" > "/opt/auto-install-shims/$shim" \ && chmod 0755 "/opt/auto-install-shims/$shim"; \ done \ + && printf '#!/usr/bin/env bash\nexec /usr/local/bin/mise exec "node" -- npm "$@"\n' > /opt/auto-install-shims/npm \ + && chmod 0755 /opt/auto-install-shims/npm \ + && printf '#!/usr/bin/env bash\nexec /usr/local/bin/mise exec "node" -- npx "$@"\n' > /opt/auto-install-shims/npx \ + && chmod 0755 /opt/auto-install-shims/npx \ && chown -R opencode:opencode /opt/auto-install-shims \ && mkdir -p /home/opencode/workspace \ && chown -R opencode:opencode /home/opencode From 6de830b1a1bbe24083b8fe2948288b75780f5731 Mon Sep 17 00:00:00 2001 From: gabemeola <14303404+gabemeola@users.noreply.github.com> Date: Mon, 6 Jul 2026 22:57:03 +0000 Subject: [PATCH 2/9] Add npm/npx lazy-init shims via mise-config multi-shim support --- Dockerfile | 18 ++++++++---------- mise-config.toml | 2 +- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/Dockerfile b/Dockerfile index 32218b8..434ac11 100644 --- a/Dockerfile +++ b/Dockerfile @@ -152,20 +152,18 @@ RUN opencode --version \ && mkdir -p /opt/auto-install-shims \ && grep -E '^\s*"' /etc/mise/config.toml | while IFS='=' read -r key value; do \ key="$(echo "$key" | tr -d ' "')" \ - && shim="$(echo "$value" | sed -n 's/.*# shim:\([^ ]*\).*/\1/p')" \ - && if [ -z "$shim" ]; then \ + && shim_list="$(echo "$value" | sed -n 's/.*# shim:\([^ ]*\).*/\1/p')" \ + && if [ -z "$shim_list" ]; then \ case "$key" in \ - github:*) shim="${key##*/}" ;; \ - *) shim="${key#*:}" ;; \ + github:*) shim_list="${key##*/}" ;; \ + *) shim_list="${key#*:}" ;; \ esac; \ fi \ - && printf '#!/usr/bin/env bash\nexec /usr/local/bin/mise exec "%s" -- %s "$@"\n' "$key" "$shim" > "/opt/auto-install-shims/$shim" \ - && chmod 0755 "/opt/auto-install-shims/$shim"; \ + && for shim in $(echo "$shim_list" | tr ',' ' '); do \ + printf '#!/usr/bin/env bash\nexec /usr/local/bin/mise exec "%s" -- %s "$@"\n' "$key" "$shim" > "/opt/auto-install-shims/$shim" \ + && chmod 0755 "/opt/auto-install-shims/$shim"; \ + done; \ done \ - && printf '#!/usr/bin/env bash\nexec /usr/local/bin/mise exec "node" -- npm "$@"\n' > /opt/auto-install-shims/npm \ - && chmod 0755 /opt/auto-install-shims/npm \ - && printf '#!/usr/bin/env bash\nexec /usr/local/bin/mise exec "node" -- npx "$@"\n' > /opt/auto-install-shims/npx \ - && chmod 0755 /opt/auto-install-shims/npx \ && chown -R opencode:opencode /opt/auto-install-shims \ && mkdir -p /home/opencode/workspace \ && chown -R opencode:opencode /home/opencode diff --git a/mise-config.toml b/mise-config.toml index 21c3c45..46d3d9f 100644 --- a/mise-config.toml +++ b/mise-config.toml @@ -10,5 +10,5 @@ "github:zyedidia/micro" = "latest" "zerobrew:nano" = "latest" "zerobrew:python" = "latest" -"node" = "latest" +"node" = "latest" # shim:node,npm,npx "github:facebook/sapling" = "latest" # shim:sl From dc711257ad3e6458d22bd358c046ec0bc16d5bd5 Mon Sep 17 00:00:00 2001 From: gabemeola <14303404+gabemeola@users.noreply.github.com> Date: Mon, 6 Jul 2026 23:03:28 +0000 Subject: [PATCH 3/9] Add command_not_found_handle fallback for mise-managed tools --- Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 434ac11..bf684b1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -144,11 +144,12 @@ COPY --from=builder /opt/opencode /usr/local/bin/opencode RUN opencode --version \ && printf 'for d in "$HOME/.local/bin" "/home/linuxbrew/.linuxbrew/bin" "/home/linuxbrew/.linuxbrew/sbin" "$HOME/.local/share/zerobrew/prefix/bin"; do case ":$PATH:" in *":$d:"*) ;; *) PATH="$d:$PATH";; esac; done\nexport PATH\n' > /etc/profile.d/brew-path.sh \ && chmod 0644 /etc/profile.d/brew-path.sh \ - && printf '\neval "$(mise activate bash)"\n' >> /home/opencode/.bashrc \ + && printf '\n# Mise activation for interactive shells\neval "$(mise activate bash)"\n' >> /home/opencode/.bashrc \ && printf '\neval "$(mise activate zsh)"\n' >> /home/opencode/.zshrc \ && mkdir -p /home/opencode/.config/fish \ && printf '\nmise activate fish | source\n' >> /home/opencode/.config/fish/config.fish \ && printf '\neval "$(mise activate sh)"\n' >> /home/opencode/.profile \ + && printf '\n# Fallback: route unknown commands through mise\ncommand_not_found_handle() {\n if /usr/local/bin/mise which "$1" &>/dev/null; then\n /usr/local/bin/mise exec "$1" -- "$@"\n return $?\n fi\n return 127\n}\n' >> /home/opencode/.bashrc \ && mkdir -p /opt/auto-install-shims \ && grep -E '^\s*"' /etc/mise/config.toml | while IFS='=' read -r key value; do \ key="$(echo "$key" | tr -d ' "')" \ From 59b1cb7aa39291d5dc87a195af0c7ddd2a4a9221 Mon Sep 17 00:00:00 2001 From: gabemeola <14303404+gabemeola@users.noreply.github.com> Date: Mon, 6 Jul 2026 23:08:05 +0000 Subject: [PATCH 4/9] Move mise fallback to /etc/opencode-mise.bash sourced via BASH_ENV for non-interactive shells --- Dockerfile | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index bf684b1..2c2e9fc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -109,6 +109,7 @@ ENV HOMEBREW_NO_AUTO_UPDATE=1 ENV HOMEBREW_INSTALL_FROM_API=1 ENV MISE_DATA_DIR=/opt/mise ENV MISE_ALWAYS_INSTALL=1 +ENV BASH_ENV=/etc/opencode-mise.bash LABEL io.artifacthub.package.readme-url="https://raw.githubusercontent.com/sprisa/opencode-server/refs/heads/main/README.md" \ org.opencontainers.image.created="${IMAGE_CREATED}" \ @@ -144,12 +145,13 @@ COPY --from=builder /opt/opencode /usr/local/bin/opencode RUN opencode --version \ && printf 'for d in "$HOME/.local/bin" "/home/linuxbrew/.linuxbrew/bin" "/home/linuxbrew/.linuxbrew/sbin" "$HOME/.local/share/zerobrew/prefix/bin"; do case ":$PATH:" in *":$d:"*) ;; *) PATH="$d:$PATH";; esac; done\nexport PATH\n' > /etc/profile.d/brew-path.sh \ && chmod 0644 /etc/profile.d/brew-path.sh \ - && printf '\n# Mise activation for interactive shells\neval "$(mise activate bash)"\n' >> /home/opencode/.bashrc \ + && printf '\n# Mise activation for interactive shells\nsource /etc/opencode-mise.bash\neval "$(mise activate bash)"\n' >> /home/opencode/.bashrc \ && printf '\neval "$(mise activate zsh)"\n' >> /home/opencode/.zshrc \ && mkdir -p /home/opencode/.config/fish \ && printf '\nmise activate fish | source\n' >> /home/opencode/.config/fish/config.fish \ && printf '\neval "$(mise activate sh)"\n' >> /home/opencode/.profile \ - && printf '\n# Fallback: route unknown commands through mise\ncommand_not_found_handle() {\n if /usr/local/bin/mise which "$1" &>/dev/null; then\n /usr/local/bin/mise exec "$1" -- "$@"\n return $?\n fi\n return 127\n}\n' >> /home/opencode/.bashrc \ + && printf '#!/usr/bin/env bash\n# Route unknown commands through mise (fallback for non-interactive shells)\nif [ -n "${BASH_VERSION-}" ]; then\n command_not_found_handle() {\n if /usr/local/bin/mise which "$1" &>/dev/null; then\n /usr/local/bin/mise exec "$1" -- "$@"\n return $?\n fi\n return 127\n }\nfi\n' > /etc/opencode-mise.bash \ + && chmod 0644 /etc/opencode-mise.bash \ && mkdir -p /opt/auto-install-shims \ && grep -E '^\s*"' /etc/mise/config.toml | while IFS='=' read -r key value; do \ key="$(echo "$key" | tr -d ' "')" \ From 96ab98e5a0c51c71ecb7293618b159fbfb1893a5 Mon Sep 17 00:00:00 2001 From: gabemeola <14303404+gabemeola@users.noreply.github.com> Date: Mon, 6 Jul 2026 23:11:53 +0000 Subject: [PATCH 5/9] Add npm/npx to mise-config.toml directly --- mise-config.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mise-config.toml b/mise-config.toml index 46d3d9f..1772f5c 100644 --- a/mise-config.toml +++ b/mise-config.toml @@ -10,5 +10,6 @@ "github:zyedidia/micro" = "latest" "zerobrew:nano" = "latest" "zerobrew:python" = "latest" -"node" = "latest" # shim:node,npm,npx +"node" = "latest" +"npm" = "latest" # shim:npm,npx "github:facebook/sapling" = "latest" # shim:sl From 49521a579cf8aa8a58fff67599bdae2c6fb71395 Mon Sep 17 00:00:00 2001 From: gabemeola <14303404+gabemeola@users.noreply.github.com> Date: Mon, 6 Jul 2026 23:15:18 +0000 Subject: [PATCH 6/9] Add runtime behavior test workflow for PRs --- .github/workflows/pr-test.yml | 136 ++++++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 .github/workflows/pr-test.yml diff --git a/.github/workflows/pr-test.yml b/.github/workflows/pr-test.yml new file mode 100644 index 0000000..d5037ea --- /dev/null +++ b/.github/workflows/pr-test.yml @@ -0,0 +1,136 @@ +name: PR test + +on: + workflow_dispatch: + pull_request: + +permissions: + contents: read + pull-requests: write + +jobs: + test: + name: Runtime behavior tests + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build image + uses: docker/build-push-action@v6 + with: + context: . + load: true + build-args: OPENCODE_VERSION=0.0.0-test + tags: opencode-test:latest + + - name: Run runtime tests + id: tests + run: | + set -e + PASS=0 + FAIL=0 + REPORT="" + RESULTS="" + + test() { + local desc="$1" cmd="$2" + if eval "$cmd" 2>&1; then + PASS=$((PASS+1)) + REPORT="${REPORT}- [x] ${desc}\n" + echo "PASS: ${desc}" + else + FAIL=$((FAIL+1)) + REPORT="${REPORT}- [ ] ${desc}\n" + echo "FAIL: ${desc}" + fi + } + + IN="docker run --rm --entrypoint bash opencode-test:latest" + + echo "=== Pre-installed tools (interactive) ===" + + test "node works in interactive shell" \ + "$IN -i -c 'source ~/.bashrc 2>/dev/null; node --version'" + + test "npm works in interactive shell" \ + "$IN -i -c 'source ~/.bashrc 2>/dev/null; npm --version'" + + test "npx works in interactive shell" \ + "$IN -i -c 'source ~/.bashrc 2>/dev/null; npx --version'" + + test "gh works in interactive shell" \ + "$IN -i -c 'source ~/.bashrc 2>/dev/null; gh --version'" + + echo "=== mise use (interactive) ===" + + test "mise use go works in interactive shell" \ + "$IN -i -c 'source ~/.bashrc 2>/dev/null; mise use go@latest && go version'" + + echo "=== Pre-installed tools (non-interactive, via BASH_ENV) ===" + + test "node works in non-interactive shell" \ + "$IN -c 'which node && node --version'" + + test "npm works in non-interactive shell" \ + "$IN -c 'which npm && npm --version'" + + test "npx works in non-interactive shell" \ + "$IN -c 'which npx && npx --version'" + + echo "=== mise use (non-interactive, via BASH_ENV fallback) ===" + + test "mise use go works in non-interactive shell" \ + "$IN -c 'mise use go@latest && go version'" + + echo "=== Shim path verification (non-interactive) ===" + + test "node resolves from auto-install-shims" \ + "$IN -c 'which node | grep -q /opt/auto-install-shims/node'" + + test "npm resolves from auto-install-shims" \ + "$IN -c 'which npm | grep -q /opt/auto-install-shims/npm'" + + test "npx resolves from auto-install-shims" \ + "$IN -c 'which npx | grep -q /opt/auto-install-shims/npx'" + + echo "" + echo "=== Results: $PASS passed, $FAIL failed ===" + + RESULTS="**${PASS} passed, ${FAIL} failed**\n\n${REPORT}" + RESULTS="${RESULTS}\n### Shorthand\n\n| Scope | Interactive | Non-interactive |\n|---|---|---|" + RESULTS="${RESULTS}\n| Pre-installed tools | \`node --version\`, \`npm --version\`, \`npx --version\`, \`gh --version\` | \`which node\`, \`which npm\`, \`which npx\` |" + RESULTS="${RESULTS}\n| mise use | \`mise use go && go version\` | \`mise use go && go version\` |" + + # Write multi-line output properly + EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64) + echo "results<<$EOF" >> "$GITHUB_OUTPUT" + printf "%b" "$RESULTS" >> "$GITHUB_OUTPUT" + echo "$EOF" >> "$GITHUB_OUTPUT" + + # Fail the step if any test failed + [ "$FAIL" -eq 0 ] + + - name: Comment results on PR + if: github.event_name == 'pull_request' && always() + env: + GH_TOKEN: ${{ github.token }} + run: | + BODY=$(cat </dev/null \ + || gh pr comment "${{ github.event.number }}" \ + --repo "${{ github.repository }}" \ + --body "$BODY" From c304aad428d548e4744cdf4127a76a15382f4c54 Mon Sep 17 00:00:00 2001 From: gabemeola <14303404+gabemeola@users.noreply.github.com> Date: Mon, 6 Jul 2026 23:18:28 +0000 Subject: [PATCH 7/9] Fix test workflow: use real opencode version from version.txt --- .github/workflows/pr-test.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pr-test.yml b/.github/workflows/pr-test.yml index d5037ea..5175744 100644 --- a/.github/workflows/pr-test.yml +++ b/.github/workflows/pr-test.yml @@ -19,12 +19,18 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 + - name: Read version + id: version + run: | + VERSION=$(cat version.txt | tr -d '[:space:]') + echo "version=${VERSION}" >> "$GITHUB_OUTPUT" + - name: Build image uses: docker/build-push-action@v6 with: context: . load: true - build-args: OPENCODE_VERSION=0.0.0-test + build-args: OPENCODE_VERSION=${{ steps.version.outputs.version }} tags: opencode-test:latest - name: Run runtime tests From f9b2de916a7ef8328f94a487f69cf78e351e9ae8 Mon Sep 17 00:00:00 2001 From: gabemeola <14303404+gabemeola@users.noreply.github.com> Date: Mon, 6 Jul 2026 23:25:17 +0000 Subject: [PATCH 8/9] Pass GITHUB_TOKEN to test containers to avoid GitHub API rate limiting --- .github/workflows/pr-test.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pr-test.yml b/.github/workflows/pr-test.yml index 5175744..b989295 100644 --- a/.github/workflows/pr-test.yml +++ b/.github/workflows/pr-test.yml @@ -35,6 +35,8 @@ jobs: - name: Run runtime tests id: tests + env: + GITHUB_TOKEN: ${{ github.token }} run: | set -e PASS=0 @@ -55,7 +57,7 @@ jobs: fi } - IN="docker run --rm --entrypoint bash opencode-test:latest" + IN="docker run --rm -e GITHUB_TOKEN --entrypoint bash opencode-test:latest" echo "=== Pre-installed tools (interactive) ===" From b2d0b107f547440ac6ad1840bbf31f8a0a505f18 Mon Sep 17 00:00:00 2001 From: gabemeola <14303404+gabemeola@users.noreply.github.com> Date: Mon, 6 Jul 2026 23:29:51 +0000 Subject: [PATCH 9/9] Simplify test workflow: drop PR comment, use step summary only --- .github/workflows/pr-test.yml | 50 ++++++++++++----------------------- 1 file changed, 17 insertions(+), 33 deletions(-) diff --git a/.github/workflows/pr-test.yml b/.github/workflows/pr-test.yml index b989295..7669719 100644 --- a/.github/workflows/pr-test.yml +++ b/.github/workflows/pr-test.yml @@ -6,7 +6,6 @@ on: permissions: contents: read - pull-requests: write jobs: test: @@ -42,10 +41,10 @@ jobs: PASS=0 FAIL=0 REPORT="" - RESULTS="" test() { local desc="$1" cmd="$2" + echo "::group::${desc}" if eval "$cmd" 2>&1; then PASS=$((PASS+1)) REPORT="${REPORT}- [x] ${desc}\n" @@ -55,6 +54,7 @@ jobs: REPORT="${REPORT}- [ ] ${desc}\n" echo "FAIL: ${desc}" fi + echo "::endgroup::" } IN="docker run --rm -e GITHUB_TOKEN --entrypoint bash opencode-test:latest" @@ -108,37 +108,21 @@ jobs: echo "" echo "=== Results: $PASS passed, $FAIL failed ===" - RESULTS="**${PASS} passed, ${FAIL} failed**\n\n${REPORT}" - RESULTS="${RESULTS}\n### Shorthand\n\n| Scope | Interactive | Non-interactive |\n|---|---|---|" - RESULTS="${RESULTS}\n| Pre-installed tools | \`node --version\`, \`npm --version\`, \`npx --version\`, \`gh --version\` | \`which node\`, \`which npm\`, \`which npx\` |" - RESULTS="${RESULTS}\n| mise use | \`mise use go && go version\` | \`mise use go && go version\` |" - - # Write multi-line output properly - EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64) - echo "results<<$EOF" >> "$GITHUB_OUTPUT" - printf "%b" "$RESULTS" >> "$GITHUB_OUTPUT" - echo "$EOF" >> "$GITHUB_OUTPUT" + # Write to step summary + { + echo "## Runtime test results" + echo "" + echo "**${PASS} passed, ${FAIL} failed**" + echo "" + printf "%b" "$REPORT" + echo "" + echo "### Shorthand" + echo "" + echo "| Scope | Interactive | Non-interactive |" + echo "|---|---|---|" + echo "| Pre-installed tools | \`node --version\`, \`npm --version\`, \`npx --version\`, \`gh --version\` | \`which node\`, \`which npm\`, \`which npx\` |" + echo "| mise use | \`mise use go && go version\` | \`mise use go && go version\` |" + } >> "$GITHUB_STEP_SUMMARY" # Fail the step if any test failed [ "$FAIL" -eq 0 ] - - - name: Comment results on PR - if: github.event_name == 'pull_request' && always() - env: - GH_TOKEN: ${{ github.token }} - run: | - BODY=$(cat </dev/null \ - || gh pr comment "${{ github.event.number }}" \ - --repo "${{ github.repository }}" \ - --body "$BODY"