diff --git a/.github/workflows/pr-test.yml b/.github/workflows/pr-test.yml new file mode 100644 index 0000000..7669719 --- /dev/null +++ b/.github/workflows/pr-test.yml @@ -0,0 +1,128 @@ +name: PR test + +on: + workflow_dispatch: + pull_request: + +permissions: + contents: read + +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: 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=${{ steps.version.outputs.version }} + tags: opencode-test:latest + + - name: Run runtime tests + id: tests + env: + GITHUB_TOKEN: ${{ github.token }} + run: | + set -e + PASS=0 + FAIL=0 + REPORT="" + + test() { + local desc="$1" cmd="$2" + echo "::group::${desc}" + 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 + echo "::endgroup::" + } + + IN="docker run --rm -e GITHUB_TOKEN --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 ===" + + # 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 ] diff --git a/Dockerfile b/Dockerfile index 38ad14b..2c2e9fc 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 \ @@ -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,23 +145,27 @@ 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\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 '#!/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 ' "')" \ - && 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 \ && chown -R opencode:opencode /opt/auto-install-shims \ && mkdir -p /home/opencode/workspace \ diff --git a/mise-config.toml b/mise-config.toml index 21c3c45..1772f5c 100644 --- a/mise-config.toml +++ b/mise-config.toml @@ -11,4 +11,5 @@ "zerobrew:nano" = "latest" "zerobrew:python" = "latest" "node" = "latest" +"npm" = "latest" # shim:npm,npx "github:facebook/sapling" = "latest" # shim:sl