name: Environments # Execution environments on every OS: Durable's NodeExecutionEnv and pi-env's daemon and RemoteExecutionEnv, which # must give the same results. The main CI runs on Linux only. The release builds of the daemon are tested as shipped, # and so is the npm package's file list. on: push: branches: [main, "durable-env-*"] paths: ["packages/durable/src/env/**", "packages/durable/src/tools/**", "packages/durable/test/env-*", "packages/env/**", ".github/workflows/env.yml", ".github/workflows/env-daemons.yml"] pull_request: branches: [main] paths: ["packages/durable/src/env/**", "packages/durable/src/tools/**", "packages/durable/test/env-*", "packages/env/**", ".github/workflows/env.yml", ".github/workflows/env-daemons.yml"] workflow_dispatch: concurrency: group: env-${{ github.ref }} cancel-in-progress: true jobs: daemons: uses: ./.github/workflows/env-daemons.yml permissions: contents: read check: needs: daemons runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Setup Node.js uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: 22 cache: npm - name: Install system dependencies run: | sudo apt-get update sudo apt-get install -y libcairo2-dev libpango1.0-dev libjpeg-dev libgif-dev librsvg2-dev fd-find ripgrep sudo ln -s $(which fdfind) /usr/local/bin/fd - name: Install dependencies run: npm ci --ignore-scripts - name: Build run: npm run build - name: Check run: npm run check - name: Download the pi-env daemons uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: pattern: pi-env-* path: packages/env/bin - name: Check the pi-env package contents working-directory: packages/env run: | npm pack --dry-run --json > "$RUNNER_TEMP/pack.json" node -e ' const files = new Set(JSON.parse(require("node:fs").readFileSync(process.argv[1], "utf8"))[0].files.map((file) => file.path)); const expected = ["dist/index.js", "dist/index.d.ts", "dist/ssh.js", "README.md", "CHANGELOG.md", "docs/protocol.md"]; for (const name of ["linux-x64", "linux-arm64", "android-arm64", "android-x64", "darwin-arm64", "darwin-x64"]) expected.push(`bin/pi-env-${name}/pi-env`); for (const name of ["windows-x64", "windows-arm64"]) expected.push(`bin/pi-env-${name}/pi-env.exe`); const missing = expected.filter((file) => !files.has(file)); const unexpected = [...files].filter((file) => file.startsWith("src/") || file.startsWith("daemon/") || file.startsWith("test/")); if (missing.length || unexpected.length) { console.error({ missing, unexpected }); process.exit(1); } console.log(`${files.size} files`); ' "$RUNNER_TEMP/pack.json" # The pi-env tests against each release build of the daemon, as the npm package ships it. release-daemons: needs: daemons strategy: fail-fast: false matrix: include: - { os: ubuntu-latest, name: linux-x64 } - { os: ubuntu-24.04-arm, name: linux-arm64 } - { os: macos-latest, name: darwin-arm64 } # Under Rosetta on the Apple silicon runner. - { os: macos-latest, name: darwin-x64 } - { os: windows-latest, name: windows-x64 } - { os: windows-11-arm, name: windows-arm64 } runs-on: ${{ matrix.os }} defaults: run: shell: bash steps: - name: Checkout uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Setup Node.js uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: 22 cache: npm - name: Install dependencies run: npm ci --ignore-scripts - name: Install an SSH server for the bootstrap tests if: runner.os == 'Linux' run: command -v sshd || test -x /usr/sbin/sshd || (sudo apt-get update && sudo apt-get install -y openssh-server) - name: Install Rosetta if: matrix.name == 'darwin-x64' run: softwareupdate --install-rosetta --agree-to-license - name: Download the daemon uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: name: pi-env-${{ matrix.name }} path: ${{ runner.temp }}/daemon - name: pi-env tests working-directory: packages/env run: | daemon="$RUNNER_TEMP/daemon/pi-env" case "${{ matrix.name }}" in windows-*) daemon="$daemon.exe" ;; esac chmod +x "$daemon" file "$daemon" || true PI_ENV_DAEMON="$daemon" npx vitest --run tests: strategy: fail-fast: false matrix: os: [ubuntu-latest, ubuntu-24.04-arm, macos-latest, windows-latest, windows-11-arm] runs-on: ${{ matrix.os }} defaults: run: shell: bash steps: - name: Checkout uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Setup Node.js uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: 22 cache: npm - name: Install dependencies run: npm ci --ignore-scripts - name: Install an SSH server for the bootstrap tests if: runner.os == 'Linux' run: command -v sshd || test -x /usr/sbin/sshd || (sudo apt-get update && sudo apt-get install -y openssh-server) - name: Daemon format, lint and unit tests working-directory: packages/env/daemon run: | cargo fmt --check cargo clippy --locked --all-targets -- -D warnings cargo test --locked - name: Build the daemon working-directory: packages/env run: npm run build:daemon - name: Durable env tests working-directory: packages/durable run: npx vitest --run test/env-node.test.ts test/env-node-spill.test.ts test/env-node-conformance.test.ts test/env-line-scan.test.ts test/tools.test.ts test/tools-read-differential.test.ts - name: pi-env tests working-directory: packages/env run: npx vitest --run - name: Start Windows' OpenSSH server if: matrix.os == 'windows-latest' shell: pwsh run: | if (-not (Get-Service sshd -ErrorAction SilentlyContinue)) { Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0 | Out-Null } Start-Service sshd $key = Join-Path $env:RUNNER_TEMP 'ssh_client_key' & "$env:WINDIR\System32\OpenSSH\ssh-keygen.exe" -q -t ed25519 -N '' -f $key icacls $key /inheritance:r /grant "${env:USERNAME}:F" | Out-Null # The runner's user is an administrator; sshd reads administrators' keys from ProgramData. $authorized = "$env:ProgramData\ssh\administrators_authorized_keys" Get-Content "$key.pub" | Set-Content -Path $authorized -Encoding ascii icacls $authorized /inheritance:r /grant "Administrators:F" /grant "SYSTEM:F" | Out-Null - name: pi-env tests over Windows' OpenSSH, with cmd.exe and then PowerShell as its default shell if: matrix.os == 'windows-latest' working-directory: packages/env env: PI_ENV_SSH_HOST: 127.0.0.1 PI_ENV_SSH_KEY: ${{ runner.temp }}\ssh_client_key run: | export PI_ENV_SSH_USER="$USERNAME" export PI_ENV_SSH_PROGRAM="$WINDIR\System32\OpenSSH\ssh.exe" npx vitest --run test/ssh-external.test.ts powershell -NoProfile -Command "New-ItemProperty -Path 'HKLM:\SOFTWARE\OpenSSH' -Name DefaultShell -Value 'C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe' -PropertyType String -Force | Out-Null" PI_ENV_SSH_SHELL=powershell npx vitest --run test/ssh-external.test.ts