Run the entire CI workflow before Docker build and tests. Require Nix, native payload smoke tests, install/update E2E and signed-package upgrade acceptance before publishing. Keep Desktop Playwright E2E deferred. Archive tested Docker images and signed bundle candidates with provenance and hashes. Publishers consume those exact artifacts without rebuilding. Advance stable channels only after all required publications succeed. Keep canaries on their separate path and reject direct stable-builder publication that bypasses the gate. Move shared release transport, manifests and gates to Python. Keep native Electron adapters in JS and share feed/MIME facts as JSON. Replace the R2/feed JS implementation and move its protocol tests to Python. Verified targeted Python and JS tests, real loopback transport and CLI execution, temporary Git admission, workflow graph lint, and typechecks. No live stable release was run. Native signing, package upgrades and real registry/Store promotion still need their release-run receipts. Separate services cannot promote atomically. A promotion failure keeps the run red.
161 lines
7.0 KiB
YAML
161 lines
7.0 KiB
YAML
name: Install & Update E2E (reusable)
|
|
|
|
# Runs ONE {install-method, update-method} combination against ONE starting
|
|
# commit, with a real install (uv, a managed Python, Node, the venv) behind
|
|
# it.
|
|
#
|
|
# Reusable so callers can fan out over the combinations that matter --
|
|
# update from the tip vs. from an older release, `hermes update` vs.
|
|
# re-running the installer -- without duplicating the runner setup. Each leg
|
|
# is independent: its own isolated HOME, its own install, nothing rewound
|
|
# or shared.
|
|
#
|
|
# No sandbox: tests/install/installer-script-e2e.sh points every git
|
|
# process at a local bare clone (url.<file://serve.git>.insteadOf in a
|
|
# driver-owned GIT_CONFIG_GLOBAL) and isolates HOME, so the installer and
|
|
# updater run byte-for-byte against their real URLs on the bare runner --
|
|
# which is disposable, and therefore IS the sandbox. That also makes this
|
|
# workflow OS-agnostic: the same driver runs on ubuntu and macos runners.
|
|
#
|
|
# Method ids come from scripts/sandbox/generate-e2e-matrix.mjs. Supported
|
|
# today: install via installer-script, update via hermes-update or
|
|
# installer-script (re-run the one-liner).
|
|
# Anything else NATIVELY SKIPS (grey check, no runner): capability
|
|
# knowledge lives here, next to the driver, so the caller can dispatch
|
|
# every declared combination without knowing which ones work.
|
|
#
|
|
# Call it:
|
|
#
|
|
# jobs:
|
|
# tip:
|
|
# uses: ./.github/workflows/install-e2e-run.yml
|
|
# with:
|
|
# install-method: installer-script
|
|
# update-method: hermes-update
|
|
# install-ref: refs/heads/main
|
|
|
|
on:
|
|
workflow_call:
|
|
inputs:
|
|
install-method:
|
|
description: 'How the starting version gets installed. Supported: installer-script (the real curl | install.sh one-liner) and installer-script+desktop (the same one-liner with --include-desktop).'
|
|
required: true
|
|
type: string
|
|
update-method:
|
|
description: 'How the install updates to HEAD. Supported: hermes-update (the updater), installer-script (re-run the one-liner), installer-script+desktop (re-run with --include-desktop), hermes-desktop-app-update (launch via hermes desktop under Playwright, click Update now). open-app-update runs only where an OS entry point exists (see the per-OS run workflows); pairs without one skip.'
|
|
required: true
|
|
type: string
|
|
install-ref:
|
|
description: 'What to install before updating: a branch, a tag (v2026.7.7), or a SHA reachable from main.'
|
|
required: false
|
|
type: string
|
|
default: refs/heads/main
|
|
leg-id:
|
|
description: 'Artifact-safe matrix leg id (from generate-e2e-matrix.mjs legId). Names this leg''s logs + player artifacts so the report job can link a row to its zip.'
|
|
required: true
|
|
type: string
|
|
tag-has-desktop:
|
|
description: "Whether install-ref ships the desktop app (apps/desktop). The caller annotates this from the tag's own tree; desktop-method legs from pre-desktop releases natively skip."
|
|
required: false
|
|
type: boolean
|
|
default: true
|
|
runner:
|
|
description: 'Runner label.'
|
|
required: false
|
|
type: string
|
|
default: ubuntu-latest
|
|
timeout-minutes:
|
|
description: 'Job timeout. A cold run installs real toolchains twice, and app-update legs add a full Electron build + launch.'
|
|
required: false
|
|
type: number
|
|
default: 60
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
jobs:
|
|
e2e:
|
|
name: install & update
|
|
# The pairs the driver can run today; anything else natively skips.
|
|
# Desktop-surface methods (+desktop installs,
|
|
# hermes-desktop-app-update) also need the starting tag to ship
|
|
# apps/desktop (their flags shipped with it).
|
|
if: >-
|
|
(inputs.install-method == 'installer-script'
|
|
|| (inputs.install-method == 'installer-script+desktop' && inputs.tag-has-desktop))
|
|
&& (contains(fromJSON('["hermes-update", "installer-script"]'), inputs.update-method)
|
|
|| (contains(fromJSON('["installer-script+desktop", "hermes-desktop-app-update"]'), inputs.update-method) && inputs.tag-has-desktop))
|
|
runs-on: ${{ inputs.runner }}
|
|
timeout-minutes: ${{ inputs.timeout-minutes }}
|
|
|
|
steps:
|
|
# Full history: the driver bare-clones this checkout as the repo the
|
|
# installer/updater talk to, and both OLD and HEAD must be reachable
|
|
# in that clone. A shallow clone cannot serve either need.
|
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
with:
|
|
ref: ${{ github.sha }}
|
|
fetch-depth: 0
|
|
|
|
# One recording mechanism on every OS (Xvfb gives headless linux a
|
|
# display; the same display serves any app the driver launches).
|
|
- name: Start screen recording
|
|
uses: ./.github/actions/e2e-screen-record
|
|
with:
|
|
mode: start
|
|
output: ${{ runner.temp }}/e2e-logs/recording.mkv
|
|
|
|
- name: Run install + update E2E
|
|
run: |
|
|
set -euo pipefail
|
|
tests/install/installer-script-e2e.sh \
|
|
--install-method '${{ inputs.install-method }}' \
|
|
--update-method '${{ inputs.update-method }}' \
|
|
--install-ref '${{ inputs.install-ref }}'
|
|
env:
|
|
# Outside the workspace on purpose: logs written into the repo
|
|
# would trip the driver's own dirty-tree guard.
|
|
HERMES_E2E_LOG_DIR: ${{ runner.temp }}/e2e-logs
|
|
|
|
- name: Stop screen recording
|
|
if: always()
|
|
uses: ./.github/actions/e2e-screen-record
|
|
with:
|
|
mode: stop
|
|
output: ${{ runner.temp }}/e2e-logs/recording.mkv
|
|
|
|
# Browsers cannot play Matroska: remux (copy codec, no re-encode) so
|
|
# the artifact zip feeds the static playback.html leg player directly.
|
|
- name: Remux recording for browser playback
|
|
if: always()
|
|
run: |
|
|
set -euo pipefail
|
|
if [ -f "${{ runner.temp }}/e2e-logs/recording.mkv" ]; then
|
|
ffmpeg -y -hide_banner -loglevel error -i "${{ runner.temp }}/e2e-logs/recording.mkv" \
|
|
-c copy "${{ runner.temp }}/e2e-logs/recording.mp4"
|
|
fi
|
|
|
|
# The leg player: ONE static HTML per run, uploaded up front by the
|
|
# leg-player job in install-e2e.yml (archive: false, so GitHub names
|
|
# the artifact after the file: playback.html). The report job links
|
|
# every ran leg to it with the leg's zip as a #zip= hash param.
|
|
- name: Build artifact name
|
|
if: always()
|
|
id: artifact
|
|
run: |
|
|
set -euo pipefail
|
|
echo "name=install-e2e-logs-${{ inputs.leg-id }}" >> "$GITHUB_OUTPUT"
|
|
|
|
# The installer's own transcripts say far more than the assertion that
|
|
# tripped when a real install breaks.
|
|
- name: Upload installer logs
|
|
if: always()
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
with:
|
|
# Unique per leg: a matrix over releases runs this workflow several
|
|
# times per route, and same-named artifacts collide.
|
|
name: ${{ steps.artifact.outputs.name }}-${{ github.sha }}
|
|
path: ${{ runner.temp }}/e2e-logs
|
|
retention-days: 14
|
|
if-no-files-found: ignore
|