install-e2e.yml only ran on the clock, so nothing in front of a merge installed a release and updated it on a real OS. A pull_request trigger, path-filtered to the install/update surface (derived from 60 days of update/install/pm commits), runs the new `pr` route of generate-e2e-matrix.mjs with only the newest release tag sampled: linux installer-script -> hermes-update (newest release -> PR) linux installer-script -> hermes-update (PR -> NEXT) windows installer-script -> hermes-update (PR -> NEXT) macos installer-script -> hermes-update (newest release -> PR) The bundle-manifest validation job is skipped on PRs (bundled legs need dispatch-only manifests). The full matrix stays on schedule and release.
483 lines
22 KiB
YAML
483 lines
22 KiB
YAML
name: Install & Update E2E
|
|
|
|
# Can a user on a released version get to this commit?
|
|
#
|
|
# The support matrix -- every {os, install-method, update-method} combination
|
|
# a user could be on -- lives in scripts/sandbox/generate-e2e-matrix.mjs.
|
|
# generate-matrix expands it against the picked release tags into one leg
|
|
# per {combination, tag}, split into one matrix job per OS:
|
|
#
|
|
# Matrix: linux the real curl|bash install one-liner, isolated by a
|
|
# git URL redirect to a local bare clone
|
|
# (install-e2e-run.yml)
|
|
# Matrix: windows the real desktop user flow: website Hermes-Setup.exe
|
|
# clicked by AutoHotkey, update via the app, Playwright
|
|
# clicking "Update now" (install-e2e-windows-run.yml)
|
|
# Matrix: macos script installs on the shared OS-agnostic driver,
|
|
# plus the real desktop user flow: website
|
|
# Hermes-Setup.dmg mounted and run, updates via the
|
|
# app under Playwright (install-e2e-macos-run.yml)
|
|
#
|
|
# Every combination is dispatched to its OS's run workflow; the run
|
|
# workflow natively skips (grey) what its driver cannot run yet -- an
|
|
# unimplemented method pair, or a starting tag that predates the surface
|
|
# under test (pick-releases annotates each tag with what its tree ships,
|
|
# e.g. whether the desktop app exists yet). Capability knowledge lives
|
|
# next to each driver, never here and never in the generator: declaring a
|
|
# method is a spec edit, implementing one is flipping the run workflow's
|
|
# gate.
|
|
#
|
|
# 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;
|
|
# * manually, where you can pick the route (a preset or specific legs) and how many
|
|
# releases to sample;
|
|
# * pull_request, path-filtered to the install/update surface, runs ONLY the
|
|
# `pr` route (generate-e2e-matrix.mjs PR_SUBSET): installer-script ->
|
|
# hermes-update on linux (newest release -> PR, and PR -> NEXT), windows
|
|
# (PR -> NEXT) and macos (newest release -> PR), sampling just the newest tag.
|
|
# Four legs in parallel keep a PR's wall clock near one leg (~15 min);
|
|
# the full matrix multiplies that, so it stays on the clock and releases.
|
|
# A red scheduled run is reported by install-e2e-red.yml.
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
inputs:
|
|
route:
|
|
description: 'Which legs to run: all = every OS; both/update/installer = the linux legs; windows-desktop / macos-desktop = that OS; bundled / windows-bundled / macos-bundled = the signed-bundle legs. Anything else selects legs by name (a leg or job name, or a fragment of one).'
|
|
required: false
|
|
type: string
|
|
default: all
|
|
windows-bundle-manifest:
|
|
description: 'HTTPS manifest pinning signed Windows OLD/NEW msixbundles; NEW must match this ref.'
|
|
type: string
|
|
default: ''
|
|
macos-bundle-manifest:
|
|
description: 'HTTPS manifest pinning signed macOS OLD/NEW app ZIPs; NEW must match this ref.'
|
|
type: string
|
|
default: ''
|
|
tag-count:
|
|
description: 'How many release tags to sample (newest, oldest, and a spread between).'
|
|
required: false
|
|
type: string
|
|
default: '3'
|
|
install-ref:
|
|
description: 'Optional exact release tag for a focused reproduction; overrides tag-count.'
|
|
required: false
|
|
type: string
|
|
default: ''
|
|
workflow_call:
|
|
# Reusable entry for the stable-release orchestrator. Same input names as
|
|
# workflow_dispatch; on a release run the caller dispatches on the exact
|
|
# candidate tag, so github.sha is the candidate commit throughout.
|
|
#
|
|
# route: which combinations to run (same choices as dispatch). The
|
|
# bundled routes require the matching bundle manifests, exactly like a
|
|
# manual dispatch — a source (non-bundle) release run passes route=all
|
|
# with empty manifests and gets the source install/update matrix only.
|
|
inputs:
|
|
release:
|
|
description: 'Stable-release candidate run (informational; jobs behave as route=all with empty manifests).'
|
|
required: false
|
|
type: boolean
|
|
default: false
|
|
route:
|
|
description: 'Which legs to run: all = every OS; both/update/installer = the linux legs; windows-desktop / macos-desktop = that OS; bundled / windows-bundled / macos-bundled = the signed-bundle legs. Anything else selects legs by name (a leg or job name, or a fragment of one).'
|
|
required: false
|
|
type: string
|
|
default: all
|
|
windows-bundle-manifest:
|
|
description: 'HTTPS manifest pinning signed Windows OLD/NEW msixbundles; NEW must match this ref.'
|
|
required: false
|
|
type: string
|
|
default: ''
|
|
macos-bundle-manifest:
|
|
description: 'HTTPS manifest pinning signed macOS OLD/NEW app ZIPs; NEW must match this ref.'
|
|
required: false
|
|
type: string
|
|
default: ''
|
|
tag-count:
|
|
description: 'How many release tags to sample (newest, oldest, and a spread between).'
|
|
required: false
|
|
type: string
|
|
default: '3'
|
|
install-ref:
|
|
description: 'Optional exact release tag for a focused reproduction; overrides tag-count.'
|
|
required: false
|
|
type: string
|
|
default: ''
|
|
exclude-ref:
|
|
description: 'Exact release tag to EXCLUDE from the sampled OLD baselines (e.g. the candidate itself — testing an update from it would be a no-change test).'
|
|
required: false
|
|
type: string
|
|
default: ''
|
|
schedule:
|
|
# Every 12 hours, off the hour to avoid the top-of-hour runner crunch.
|
|
- cron: '20 7,19 * * *'
|
|
pull_request:
|
|
# The install/update surface: derived from 60 days of commits whose subject
|
|
# names update/install/pm/bootstrap/relaunch. Keep it to code a real
|
|
# install or `hermes update` executes; hermes_cli/main.py and friends are
|
|
# deliberately absent (they change daily and are covered by e2e-upgrade).
|
|
paths:
|
|
- 'scripts/install*'
|
|
- 'scripts/desktop-update/**'
|
|
- 'setup-hermes.*'
|
|
- 'hermes_bootstrap.py'
|
|
- 'pm/**'
|
|
- 'hermes_cli/update*'
|
|
- 'hermes_cli/*update*'
|
|
- 'hermes_cli/pm*'
|
|
- 'hermes_cli/*install*'
|
|
- 'hermes_cli/_early_recovery.py'
|
|
- 'hermes_cli/_launchers.py'
|
|
- 'hermes_cli/managed_uv.py'
|
|
- 'hermes_cli/venv_sync.py'
|
|
- 'hermes_cli/relaunch.py'
|
|
- 'hermes_cli/gateway.py'
|
|
- 'gateway/status.py'
|
|
- 'apps/bootstrap-installer/**'
|
|
- 'apps/desktop/electron/*update*'
|
|
- 'apps/desktop/electron/updater/**'
|
|
- 'pyproject.toml'
|
|
- 'uv.lock'
|
|
- 'package.json'
|
|
- 'package-lock.json'
|
|
- 'tests/install/**'
|
|
- 'scripts/sandbox/generate-e2e-matrix.mjs'
|
|
- 'scripts/sandbox/pick-release-tags.sh'
|
|
- 'scripts/ci/setup_toolchain.py'
|
|
- '.github/actions/setup-pm/**'
|
|
- '.github/workflows/install-e2e*.yml'
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
# A workflow_call run (stable release) is never cancelled: its group uses
|
|
# github.run_id so a parent rerun cannot kill this child mid-flight. The
|
|
# scheduled/manual runs keep ref-scoped collapse.
|
|
concurrency:
|
|
group: install-e2e-${{ inputs.release == true && github.run_id || github.ref }}
|
|
cancel-in-progress: ${{ inputs.release != true }}
|
|
|
|
jobs:
|
|
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 trees, 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
|
|
scripts/releases/pick_tags.py
|
|
sparse-checkout-cone-mode: false
|
|
- id: pick
|
|
env:
|
|
# Inputs never touch shell syntax directly: TAG_COUNT arrives via
|
|
# the environment and is validated decimal-only (bash arithmetic
|
|
# reads a leading zero as octal). GitHub's 256-job cap applies to
|
|
# each per-OS matrix separately; at 10 tags the largest is windows
|
|
# at 180 (first over the cap at 15 tags = 270).
|
|
#
|
|
# EXCLUDE_REF removes one tag from the sampled OLD baselines: on a
|
|
# stable release the candidate tag itself is the newest release
|
|
# tag, and sampling it as an OLD version would produce a no-change
|
|
# update leg — the candidate must never update from itself.
|
|
TAG_COUNT: ${{ github.event_name == 'pull_request' && '1' || inputs.tag-count || 2 }}
|
|
INSTALL_REF: ${{ inputs.install-ref }}
|
|
EXCLUDE_REF: ${{ inputs.exclude-ref }}
|
|
RELEASE_MODE: ${{ inputs.release }}
|
|
run: |
|
|
set -euo pipefail
|
|
[[ "$TAG_COUNT" =~ ^(10|[1-9])$ ]] || { echo "tag-count must be 1-10, got: $TAG_COUNT" >&2; exit 1; }
|
|
if [ -n "$INSTALL_REF" ]; then
|
|
[[ "$INSTALL_REF" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(\.[0-9]+)?$ ]] || { echo 'install-ref must be an exact release tag' >&2; exit 1; }
|
|
[ "$INSTALL_REF" != "$EXCLUDE_REF" ] || { echo "Candidate cannot be its own baseline" >&2; exit 1; }
|
|
git rev-parse --verify "refs/tags/$INSTALL_REF^{commit}" >/dev/null
|
|
tags="$(jq -cn --arg ref "$INSTALL_REF" '[$ref]')"
|
|
elif [ "$RELEASE_MODE" = true ]; then
|
|
tags="$(python3 -m scripts.releases.pick_tags --count "$TAG_COUNT" --exclude-ref "$EXCLUDE_REF")"
|
|
else
|
|
tags="$(scripts/sandbox/pick-release-tags.sh --count "$TAG_COUNT")"
|
|
fi
|
|
echo "Testing updates from: $tags"
|
|
# Annotate each tag with what its own tree supports, so run
|
|
# workflows can natively skip surfaces the starting version does
|
|
# not have. Today: does the release ship the desktop app
|
|
# (apps/desktop, #20059)? Cheaper here -- the tags are already
|
|
# fetched -- than a probe job per leg.
|
|
enriched="$(for t in $(echo "$tags" | jq -r '.[]'); do
|
|
if git ls-tree -d "$t" apps/desktop | grep -q .; then d=true; else d=false; fi
|
|
echo "{\"ref\":\"$t\",\"desktop\":$d}"
|
|
done | jq -sc .)"
|
|
echo "Annotated: $enriched"
|
|
echo "tags=$enriched" >> "$GITHUB_OUTPUT"
|
|
|
|
# Expand the support matrix against the picked tags: one leg per
|
|
# {os, install-method, update-method, tag}, split into a matrix per OS.
|
|
generate-matrix:
|
|
name: Expand combinations
|
|
needs: pick-releases
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 5
|
|
outputs:
|
|
linux: ${{ steps.gen.outputs.linux }}
|
|
windows: ${{ steps.gen.outputs.windows }}
|
|
macos: ${{ steps.gen.outputs.macos }}
|
|
linux-legs: ${{ steps.gen.outputs.linux-legs }}
|
|
windows-legs: ${{ steps.gen.outputs.windows-legs }}
|
|
macos-legs: ${{ steps.gen.outputs.macos-legs }}
|
|
steps:
|
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
with:
|
|
sparse-checkout: scripts/sandbox/generate-e2e-matrix.mjs
|
|
sparse-checkout-cone-mode: false
|
|
- id: gen
|
|
env:
|
|
# Free text on dispatch, so it reaches the script as data, never as script.
|
|
# Scheduled runs have no inputs: the default route. PRs run the pr subset.
|
|
ROUTE: ${{ github.event_name == 'pull_request' && 'pr' || inputs.route || 'all' }}
|
|
run: |
|
|
set -euo pipefail
|
|
matrices="$(node scripts/sandbox/generate-e2e-matrix.mjs \
|
|
--tags '${{ needs.pick-releases.outputs.tags }}' --route "$ROUTE")"
|
|
echo "$matrices"
|
|
for key in linux windows macos; do
|
|
echo "$key=$(echo "$matrices" | node -e 'let d="";process.stdin.on("data",c=>d+=c).on("end",()=>console.log(JSON.stringify(JSON.parse(d)[process.argv[1]])))' "$key")" >> "$GITHUB_OUTPUT"
|
|
echo "$key-legs=$(echo "$matrices" | node -e 'let d="";process.stdin.on("data",c=>d+=c).on("end",()=>console.log(JSON.parse(d)[process.argv[1]].include.length))' "$key")" >> "$GITHUB_OUTPUT"
|
|
done
|
|
# The plan, human-readable: a combination x starting-tag chart on
|
|
# the run's summary page.
|
|
node scripts/sandbox/generate-e2e-matrix.mjs \
|
|
--tags '${{ needs.pick-releases.outputs.tags }}' \
|
|
--format markdown >> "$GITHUB_STEP_SUMMARY"
|
|
|
|
bundle-inputs:
|
|
name: Validate pinned bundle transitions
|
|
# Bundled legs need dispatch-only manifests; a PR never runs them.
|
|
if: github.event_name != 'pull_request'
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 5
|
|
outputs:
|
|
windows: ${{ steps.inputs.outputs.windows }}
|
|
macos: ${{ steps.inputs.outputs.macos }}
|
|
windows-arch: ${{ steps.inputs.outputs.windows-arch }}
|
|
macos-arch: ${{ steps.inputs.outputs.macos-arch }}
|
|
steps:
|
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
- id: inputs
|
|
env:
|
|
BUNDLE_WINDOWS_MANIFEST: ${{ inputs.windows-bundle-manifest }}
|
|
BUNDLE_MACOS_MANIFEST: ${{ inputs.macos-bundle-manifest }}
|
|
BUNDLE_ROUTE: ${{ inputs.route }}
|
|
run: |
|
|
npm ci --ignore-scripts --no-audit --no-fund
|
|
node tests/install/e2e-assets/bundle-plan.mjs
|
|
|
|
windows-bundled:
|
|
name: ${{ matrix.name }}
|
|
if: inputs.windows-bundle-manifest != '' && contains(fromJSON('["all", "bundled", "windows-bundled"]'), inputs.route)
|
|
needs: bundle-inputs
|
|
strategy:
|
|
fail-fast: false
|
|
matrix: ${{ fromJSON(needs.bundle-inputs.outputs.windows) }}
|
|
uses: ./.github/workflows/install-e2e-windows-run.yml
|
|
with:
|
|
install-method: packaged-app
|
|
update-method: open-app-update
|
|
install-ref: ${{ matrix.install_ref }}
|
|
leg-id: ${{ matrix.leg_id }}
|
|
bundle-manifest-url: ${{ inputs.windows-bundle-manifest }}
|
|
bundle-arch: ${{ needs.bundle-inputs.outputs.windows-arch }}
|
|
|
|
macos-bundled:
|
|
name: ${{ matrix.name }}
|
|
if: inputs.macos-bundle-manifest != '' && contains(fromJSON('["all", "bundled", "macos-bundled"]'), inputs.route)
|
|
needs: bundle-inputs
|
|
strategy:
|
|
fail-fast: false
|
|
matrix: ${{ fromJSON(needs.bundle-inputs.outputs.macos) }}
|
|
uses: ./.github/workflows/install-e2e-macos-run.yml
|
|
with:
|
|
install-method: packaged-app
|
|
update-method: open-app-update
|
|
install-ref: ${{ matrix.install_ref }}
|
|
leg-id: ${{ matrix.leg_id }}
|
|
bundle-manifest-url: ${{ inputs.macos-bundle-manifest }}
|
|
bundle-arch: ${{ needs.bundle-inputs.outputs.macos-arch }}
|
|
|
|
linux:
|
|
name: ${{ matrix.name }}
|
|
# The generator applies the route; an OS it selected no legs for is skipped
|
|
# (GitHub rejects an empty matrix).
|
|
if: needs.generate-matrix.outputs.linux-legs != '0'
|
|
needs: generate-matrix
|
|
strategy:
|
|
# One leg breaking is worth knowing about even if another already
|
|
# failed, so let every leg report.
|
|
fail-fast: false
|
|
matrix: ${{ fromJSON(needs.generate-matrix.outputs.linux) }}
|
|
uses: ./.github/workflows/install-e2e-run.yml
|
|
with:
|
|
install-method: ${{ matrix.install_method }}
|
|
update-method: ${{ matrix.update_method }}
|
|
install-ref: ${{ matrix.install_ref }}
|
|
update-ref: ${{ matrix.update_ref }}
|
|
tag-has-desktop: ${{ matrix.tag_has_desktop }}
|
|
leg-id: ${{ matrix.leg_id }}
|
|
|
|
windows:
|
|
name: ${{ matrix.name }}
|
|
if: needs.generate-matrix.outputs.windows-legs != '0'
|
|
needs: generate-matrix
|
|
strategy:
|
|
fail-fast: false
|
|
matrix: ${{ fromJSON(needs.generate-matrix.outputs.windows) }}
|
|
uses: ./.github/workflows/install-e2e-windows-run.yml
|
|
with:
|
|
install-method: ${{ matrix.install_method }}
|
|
update-method: ${{ matrix.update_method }}
|
|
install-ref: ${{ matrix.install_ref }}
|
|
update-ref: ${{ matrix.update_ref }}
|
|
tag-has-desktop: ${{ matrix.tag_has_desktop }}
|
|
leg-id: ${{ matrix.leg_id }}
|
|
|
|
macos:
|
|
name: ${{ matrix.name }}
|
|
if: needs.generate-matrix.outputs.macos-legs != '0'
|
|
needs: generate-matrix
|
|
strategy:
|
|
fail-fast: false
|
|
matrix: ${{ fromJSON(needs.generate-matrix.outputs.macos) }}
|
|
# Two driver arms: the OS-agnostic script driver (shared with linux)
|
|
# and the published-dmg GUI driver; the run workflow routes.
|
|
uses: ./.github/workflows/install-e2e-macos-run.yml
|
|
with:
|
|
install-method: ${{ matrix.install_method }}
|
|
update-method: ${{ matrix.update_method }}
|
|
install-ref: ${{ matrix.install_ref }}
|
|
update-ref: ${{ matrix.update_ref }}
|
|
tag-has-desktop: ${{ matrix.tag_has_desktop }}
|
|
leg-id: ${{ matrix.leg_id }}
|
|
|
|
# The leg player: one static HTML for the whole run. Uploaded BEFORE the
|
|
# matrix legs so it exists even when every leg dies; the report job links
|
|
# every ran leg to it with that leg's logs zip as a #zip= hash param
|
|
# (hash survives the artifact URL's server-side redirect, the query does
|
|
# not). archive: false makes GitHub name the artifact after the FILE
|
|
# (playback.html), ignoring the name: input -- harmless, the renderer
|
|
# looks it up by that name.
|
|
leg-player:
|
|
name: Upload leg player
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 5
|
|
steps:
|
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
with:
|
|
sparse-checkout: tests/install/e2e-assets/playback.html
|
|
sparse-checkout-cone-mode: false
|
|
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
with:
|
|
name: install-e2e-player
|
|
path: tests/install/e2e-assets/playback.html
|
|
archive: false
|
|
retention-days: 14
|
|
if-no-files-found: error
|
|
|
|
# Strict source-matrix gate (release runs). needs collapses each matrix to
|
|
# one aggregate, so the three source parents are the required check here.
|
|
# Every source leg must succeed — a skip is a failure EXCEPT when it is the
|
|
# native, declared capability skip (a starting tag predating the desktop
|
|
# app reports tag-has-desktop=false and its GUI leg legitimately cannot
|
|
# run); those are honest skips, counted and listed. A no-legs-at-all
|
|
# generation (empty matrix) also fails: a release run must actually test
|
|
# the source install/update paths.
|
|
source-gate:
|
|
name: Source matrix gate
|
|
if: always() && inputs.release == true
|
|
needs: [generate-matrix, linux, windows, macos]
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 5
|
|
steps:
|
|
- name: Evaluate source legs
|
|
env:
|
|
LINUX: ${{ needs.linux.result }}
|
|
WINDOWS: ${{ needs.windows.result }}
|
|
MACOS: ${{ needs.macos.result }}
|
|
LINUX_MATRIX: ${{ needs.generate-matrix.outputs.linux }}
|
|
WINDOWS_MATRIX: ${{ needs.generate-matrix.outputs.windows }}
|
|
MACOS_MATRIX: ${{ needs.generate-matrix.outputs.macos }}
|
|
run: |
|
|
set -euo pipefail
|
|
fail=0
|
|
for os_name in LINUX WINDOWS MACOS; do
|
|
result="${!os_name}"
|
|
matrix_var="${os_name}_MATRIX"
|
|
matrix="${!matrix_var}"
|
|
legs=$(printf '%s' "$matrix" | jq 'if type == "object" then ([.include // []] | add // []) else . end | length')
|
|
echo "$os_name: result=$result legs=$legs"
|
|
if [ "$result" != "success" ] || [ "$legs" -eq 0 ]; then
|
|
echo "$os_name requires executed source coverage"
|
|
echo "::error::$os_name source matrix result=$result (legs=$legs)"
|
|
fail=1
|
|
fi
|
|
done
|
|
if [ "$fail" -ne 0 ]; then
|
|
echo "::error::source install/update matrix did not fully succeed; a release cannot proceed on a partial or skipped matrix"
|
|
exit 1
|
|
fi
|
|
echo "Source install/update matrix: all OS aggregates succeeded"
|
|
|
|
# The outcome, human-readable: the plan chart again, with each cell
|
|
# replaced by how that leg actually concluded. Per-leg conclusions are
|
|
# NOT reachable through `needs` (a matrix job's result collapses to one
|
|
# aggregate), so the table body comes from the run's own job list; the
|
|
# `needs` results only sequence this job after every leg and provide
|
|
# the per-OS aggregates.
|
|
report:
|
|
name: Result chart
|
|
if: always()
|
|
needs: [leg-player, pick-releases, linux, windows, macos, bundle-inputs, windows-bundled, macos-bundled]
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 5
|
|
steps:
|
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
with:
|
|
sparse-checkout: |
|
|
scripts/sandbox/generate-e2e-matrix.mjs
|
|
tests/install/e2e-assets/known-failures.json
|
|
sparse-checkout-cone-mode: false
|
|
- env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
run: |
|
|
set -euo pipefail
|
|
{
|
|
echo "OS jobs: linux ${{ needs.linux.result }}, windows ${{ needs.windows.result }}, macos ${{ needs.macos.result }}"
|
|
echo
|
|
# The tag annotations let the chart say WHY a cell skipped
|
|
# (pre-desktop vs declared TODO) instead of a flat "skip".
|
|
gh api "repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/jobs?per_page=100" \
|
|
--paginate --jq '.jobs[] | {name, conclusion}' > /tmp/e2e-jobs.ndjson
|
|
gh api "repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts?per_page=100" \
|
|
--paginate --jq '.artifacts[] | {name, id}' > /tmp/e2e-artifacts.ndjson
|
|
echo 'Legend: ✅ upgrade passed · known [n] = exact historical failure, see footnote · ❌ unexpected failure · pre-desktop / TODO = why a leg skipped · 📼 opens the leg player (recording + synced logs)'
|
|
echo
|
|
node scripts/sandbox/generate-e2e-matrix.mjs --format results \
|
|
--tags '${{ needs.pick-releases.outputs.tags }}' \
|
|
--artifacts /tmp/e2e-artifacts.ndjson < /tmp/e2e-jobs.ndjson
|
|
} >> "$GITHUB_STEP_SUMMARY"
|