name: Install & Update E2E # Can a user on a released version get to this commit? # # For each release we sample, a leg installs that release through the real # `curl | install.sh` one-liner (uv, a managed Python, Node, the venv) inside # scripts/dev-sandbox.sh, then applies one update route and requires the # checkout to land on this commit with a working `hermes`. # # The starting versions are chosen at runtime from the repo's release tags # (scripts/sandbox/pick-release-tags.sh): newest, oldest, and a spread between. # A hardcoded list would stop covering the newest release the day after it # ships, and would pin an "oldest" that nobody still runs. # # Triggers: # * every 12 hours, so upstream drift (a new uv, a Node bump, a PyPI change) # surfaces on a schedule rather than in someone's review cycle; # * when a release tag is created -- the moment the set of versions users can # update FROM changes, and the moment a broken updater would strand them; # * manually, where you can pick the route and how many releases to sample. # # Deliberately NOT on pull_request: a leg takes ~11 minutes of real toolchain # installation, and the matrix multiplies that. Updating is release-shaped work, # so it is gated on releases and the clock instead. on: workflow_dispatch: inputs: route: description: 'Which update route to exercise.' required: false type: choice default: both options: [both, update, installer] tag-count: description: 'How many release tags to sample (newest, oldest, and a spread between).' required: false type: string default: '5' schedule: # Every 12 hours, off the hour to avoid the top-of-hour runner crunch. - cron: '20 7,19 * * *' push: tags: # Release tags only: the repo also carries backup/* and one-off tags. - 'v[0-9]+.[0-9]+.[0-9]+' - 'v[0-9]+.[0-9]+.[0-9]+.[0-9]+' permissions: contents: read concurrency: group: install-e2e-${{ github.ref }} cancel-in-progress: true jobs: # Which released versions do we test updating FROM? Resolved once and shared # by both route matrices, so the two routes cover the same set. pick-releases: name: Pick release tags runs-on: ubuntu-latest timeout-minutes: 5 outputs: tags: ${{ steps.pick.outputs.tags }} steps: # This job only reads tag names and runs one script, so take the cheap # checkout: no blobs (filter), no other files (sparse), but DO fetch tags # -- they are the whole input, and the default shallow checkout has none. - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: filter: blob:none fetch-tags: true sparse-checkout: scripts/sandbox/pick-release-tags.sh sparse-checkout-cone-mode: false - id: pick run: | set -euo pipefail tags="$(scripts/sandbox/pick-release-tags.sh --count '${{ inputs.tag-count || 5 }}')" echo "Testing updates from: $tags" echo "tags=$tags" >> "$GITHUB_OUTPUT" # `hermes update` -- the route most users take. update: if: github.event_name != 'workflow_dispatch' || inputs.route != 'installer' needs: pick-releases strategy: # One release breaking is worth knowing about even if another already # failed, so let every leg report. fail-fast: false matrix: install-ref: ${{ fromJSON(needs.pick-releases.outputs.tags) }} uses: ./.github/workflows/install-e2e-run.yml with: route: update install-ref: ${{ matrix.install-ref }} # Re-running the curl one-liner over an existing checkout: autostash + pull # rather than the updater's own git handling. installer: if: github.event_name != 'workflow_dispatch' || inputs.route != 'update' needs: pick-releases strategy: fail-fast: false max-parallel: 3 matrix: install-ref: ${{ fromJSON(needs.pick-releases.outputs.tags) }} uses: ./.github/workflows/install-e2e-run.yml with: route: installer install-ref: ${{ matrix.install-ref }}