From 1a4e5b5162bcae12b5716a49220cce0bb4cb8654 Mon Sep 17 00:00:00 2001 From: Waishnav <86405648+Waishnav@users.noreply.github.com> Date: Fri, 30 Oct 2026 11:10:21 +0530 Subject: [PATCH 1/4] ci: run the installers on Linux, macOS, and Windows Each job packs this commit and runs install.sh or install.ps1 against the tarball through DEVSPACE_PACKAGE_SPEC, once reusing a compatible Node on PATH and once with a broken node shadowing it so the installer must download its own. It then checks the launcher pins the expected Node, doctor reports node:sqlite, the agent daemon starts without the experimental warning, PATH was updated for new shells, and (on Unix) that rerunning updates in place. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/install.yml | 154 ++++++++++++++++++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100644 .github/workflows/install.yml diff --git a/.github/workflows/install.yml b/.github/workflows/install.yml new file mode 100644 index 000000000..8e6bcb163 --- /dev/null +++ b/.github/workflows/install.yml @@ -0,0 +1,154 @@ +name: Installers + +# Runs install/install.sh and install/install.ps1 against this commit's packed tarball, the way a user's +# `curl | sh` or `irm | iex` would, on each OS. The published npm package is not used: the installers take +# DEVSPACE_PACKAGE_SPEC so the job installs exactly what this change ships. + +on: + pull_request: + paths: + - install/** + - bin/** + - package.json + - pnpm-lock.yaml + - src/db/** + - src/cli.ts + - src/local-agent-client.ts + - .github/workflows/install.yml + push: + branches: + - main + paths: + - install/** + - bin/** + - package.json + - pnpm-lock.yaml + - .github/workflows/install.yml + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + install: + name: ${{ matrix.os }} (${{ matrix.node }} Node) + runs-on: ${{ matrix.os }} + timeout-minutes: 25 + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + # existing: reuse the compatible Node on PATH. managed: only a broken `node` is on PATH, so the installer + # must download its own Node. + node: [existing, managed] + + env: + DEVSPACE_NO_INIT: "1" + DEVSPACE_OAUTH_OWNER_TOKEN: ci-owner-token-that-is-long-enough + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup pnpm and Node + uses: pnpm/setup@84cb39b217b10273981911c288cd62326dc7c6d2 # v2 + with: + runtime: node@22 + cache: true + install: false + + - name: Pack this commit + shell: bash + run: | + pnpm install --frozen-lockfile + mkdir -p "$RUNNER_TEMP/pack" + npm pack --silent --pack-destination "$RUNNER_TEMP/pack" + tarball=$(find "$RUNNER_TEMP/pack" -name '*.tgz' | head -n 1) + echo "DEVSPACE_PACKAGE_SPEC=$tarball" >> "$GITHUB_ENV" + + # A standard Node install with npm beside it, as users have from nodejs.org, Homebrew, nvm, or mise. + - name: Setup Node for the installer + uses: actions/setup-node@v4 + with: + node-version: 22 + + - name: Shadow node with an incompatible one + if: matrix.node == 'managed' + shell: bash + run: | + shim="$RUNNER_TEMP/broken-node" + mkdir -p "$shim" + printf '#!/bin/sh\nexit 1\n' > "$shim/node" + printf '@exit /b 1\r\n' > "$shim/node.cmd" + chmod +x "$shim/node" + echo "$shim" >> "$GITHUB_PATH" + + - name: Run install.sh + if: runner.os != 'Windows' + shell: bash + run: sh install/install.sh + + - name: Run install.ps1 + if: runner.os == 'Windows' + shell: pwsh + run: ./install/install.ps1 + + - name: Verify the installed CLI (Unix) + if: runner.os != 'Windows' + shell: bash + run: | + devspace="$HOME/.local/bin/devspace" + cat "$devspace" + if [ "${{ matrix.node }}" = managed ]; then + grep -q "$HOME/.local/share/devspace/install/node/bin/node" "$devspace" + else + grep -q "$(command -v node)" "$devspace" + fi + + "$devspace" --version + "$devspace" doctor | tee doctor.txt + grep -q '^SQLite: ok (node:sqlite' doctor.txt + + # Starts the agent daemon, which opens the database through node:sqlite. + "$devspace" agents ls 2>&1 | tee agents.txt + if grep -q ExperimentalWarning agents.txt doctor.txt; then + echo "node:sqlite warning leaked" >&2 + exit 1 + fi + + # The installer must have put the launcher directory on PATH for new shells. + grep -rqs "$HOME/.local/bin" "$HOME/.bashrc" "$HOME/.bash_profile" "$HOME/.zshrc" "$HOME/.profile" + + - name: Verify the installed CLI (Windows) + if: runner.os == 'Windows' + shell: pwsh + run: | + $devspace = Join-Path $env:LOCALAPPDATA "devspace\bin\devspace.cmd" + Get-Content $devspace + if ("${{ matrix.node }}" -eq "managed") { + if (-not (Select-String -Quiet -SimpleMatch (Join-Path $env:LOCALAPPDATA "devspace\install\node\node.exe") $devspace)) { throw "launcher does not pin the managed Node" } + } + + & $devspace --version + if ($LASTEXITCODE -ne 0) { throw "devspace --version failed" } + $doctor = & $devspace doctor 2>&1 | Out-String + $doctor + if ($doctor -notmatch "SQLite: ok \(node:sqlite") { throw "doctor did not report node:sqlite" } + + # Starts the agent daemon, which opens the database through node:sqlite. + $agents = & $devspace agents ls 2>&1 | Out-String + $agents + if (($doctor + $agents) -match "ExperimentalWarning") { throw "node:sqlite warning leaked" } + + # The installer must have put the launcher directory on the user PATH for new terminals. + $userPath = [Environment]::GetEnvironmentVariable("Path", "User") + if (($userPath -split ";") -notcontains (Join-Path $env:LOCALAPPDATA "devspace\bin")) { throw "bin directory missing from user PATH" } + + - name: Rerun installs in place + if: runner.os != 'Windows' + shell: bash + run: sh install/install.sh && "$HOME/.local/bin/devspace" --version From 5e83c6b1100f1bf6724dc72bfe5472752e0835cd Mon Sep 17 00:00:00 2001 From: Waishnav <86405648+Waishnav@users.noreply.github.com> Date: Fri, 30 Oct 2026 11:16:41 +0530 Subject: [PATCH 2/4] ci: accept a launcher directory that is already on PATH Ubuntu runners ship ~/.local/bin on PATH, so the installer correctly leaves shell profiles alone there; only require the profile edit when the directory was missing. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/install.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/install.yml b/.github/workflows/install.yml index 8e6bcb163..84b2332fd 100644 --- a/.github/workflows/install.yml +++ b/.github/workflows/install.yml @@ -120,8 +120,12 @@ jobs: exit 1 fi - # The installer must have put the launcher directory on PATH for new shells. - grep -rqs "$HOME/.local/bin" "$HOME/.bashrc" "$HOME/.bash_profile" "$HOME/.zshrc" "$HOME/.profile" + # New shells must find the launcher: either PATH already had it (Ubuntu runners do), or the installer + # added it to a shell profile. + case ":$PATH:" in + *":$HOME/.local/bin:"*) echo "$HOME/.local/bin was already on PATH" ;; + *) grep -rqs "$HOME/.local/bin" "$HOME/.bashrc" "$HOME/.bash_profile" "$HOME/.zshrc" "$HOME/.profile" ;; + esac - name: Verify the installed CLI (Windows) if: runner.os == 'Windows' From e4cb12930f01af271a0c4726824b30ae20beb3d4 Mon Sep 17 00:00:00 2001 From: Waishnav <86405648+Waishnav@users.noreply.github.com> Date: Fri, 30 Oct 2026 13:15:54 +0530 Subject: [PATCH 3/4] ci: match the %LOCALAPPDATA% launcher path on Windows Co-Authored-By: Claude Opus 5.5 --- .github/workflows/install.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/install.yml b/.github/workflows/install.yml index 84b2332fd..9d0d33460 100644 --- a/.github/workflows/install.yml +++ b/.github/workflows/install.yml @@ -134,7 +134,8 @@ jobs: $devspace = Join-Path $env:LOCALAPPDATA "devspace\bin\devspace.cmd" Get-Content $devspace if ("${{ matrix.node }}" -eq "managed") { - if (-not (Select-String -Quiet -SimpleMatch (Join-Path $env:LOCALAPPDATA "devspace\install\node\node.exe") $devspace)) { throw "launcher does not pin the managed Node" } + # Launchers spell profile paths through %LOCALAPPDATA% so non-ASCII usernames survive cmd.exe. + if (-not (Select-String -Quiet -SimpleMatch "%LOCALAPPDATA%\devspace\install\node\node.exe" $devspace)) { throw "launcher does not pin the managed Node" } } & $devspace --version From 97bc8f9494388c82064a1d89205579ecc4b660d5 Mon Sep 17 00:00:00 2001 From: Waishnav <86405648+Waishnav@users.noreply.github.com> Date: Fri, 30 Oct 2026 13:25:54 +0530 Subject: [PATCH 4/4] ci: scope installer runs per PR and check the Windows existing-Node pin Key concurrency on the PR number so same-named fork branches cannot cancel each other, run the installer jobs on main pushes that touch the CLI or SQLite layer, and verify the Windows launcher pins the Node on PATH in the existing-Node job. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/install.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/install.yml b/.github/workflows/install.yml index 9d0d33460..4d130868d 100644 --- a/.github/workflows/install.yml +++ b/.github/workflows/install.yml @@ -23,6 +23,9 @@ on: - bin/** - package.json - pnpm-lock.yaml + - src/db/** + - src/cli.ts + - src/local-agent-client.ts - .github/workflows/install.yml workflow_dispatch: @@ -30,7 +33,8 @@ permissions: contents: read concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + # Key PR runs on the PR number so same-named branches from different forks never cancel each other. + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }} cancel-in-progress: true jobs: @@ -136,6 +140,9 @@ jobs: if ("${{ matrix.node }}" -eq "managed") { # Launchers spell profile paths through %LOCALAPPDATA% so non-ASCII usernames survive cmd.exe. if (-not (Select-String -Quiet -SimpleMatch "%LOCALAPPDATA%\devspace\install\node\node.exe" $devspace)) { throw "launcher does not pin the managed Node" } + } else { + $node = & (Get-Command node).Source -p "process.execPath" + if (-not (Select-String -Quiet -SimpleMatch $node $devspace)) { throw "launcher does not pin the Node on PATH ($node)" } } & $devspace --version