ci(install-e2e): nest the graph per starting tag; skips live where the knowledge lives
Two structural changes to the combination fanout:
1. Tags become the OUTER axis, as a sub-graph per starting version:
install-e2e.yml fans a plain matrix over the picked tags into a new
per-tag reusable workflow (install-e2e-tag.yml), which runs the
combination generator for that one tag and fans out one job per
{os, install-method, update-method}. The Actions graph now reads
'from vX -> windows: install -> update' per leg. Nothing is
hardcoded in the workflows: the tag workflow calls the generator
itself.
2. Native skips move to the point that owns the capability knowledge:
macOS combos (no driving workflow exists) grey out in the tag
workflow via install-e2e-skip.yml, untouched by the tag axis; ALL
windows combos dispatch to install-e2e-windows-run.yml, which takes
install-method/update-method inputs and natively skips the pairs
its driver cannot run yet - so implementing a windows method is a
change in the run workflow + driver only. The driver's -Route ids
now match the generator's method ids verbatim.
Generator output shape, route filters, and all error paths re-verified
locally; all four workflows pass actionlint; driver re-parses clean
pure-ASCII.
This commit is contained in:
@@ -1,15 +1,18 @@
|
||||
name: Install & Update E2E — unsupported combo (reusable)
|
||||
|
||||
# A declared-but-unimplemented {os, install-method, update-method}
|
||||
# combination from scripts/sandbox/generate-e2e-matrix.mjs. The caller fans
|
||||
# out one of these per unsupported combo so every coverage gap is its own
|
||||
# named, NATIVELY SKIPPED check (grey, conclusion=skipped, no runner) in the
|
||||
# Checks tab.
|
||||
# A declared {os, install-method, update-method} combination whose OS has
|
||||
# no driving E2E workflow at all (today: every macOS combo). The per-tag
|
||||
# caller (install-e2e-tag.yml) fans out one of these per such combo so the
|
||||
# coverage gap is its own named, NATIVELY SKIPPED check (grey,
|
||||
# conclusion=skipped, no runner) in the Checks tab.
|
||||
#
|
||||
# `implemented` stays at its false default until the combo has a real
|
||||
# driver; flipping a combo to implemented happens in the generator (move it
|
||||
# into IMPLEMENTED and point it at a real reusable workflow), never by
|
||||
# setting this input.
|
||||
# OSes WITH a driving workflow don't come here: their unimplemented method
|
||||
# pairs natively skip inside that workflow, next to the driver that will
|
||||
# implement them (see install-e2e-windows-run.yml).
|
||||
#
|
||||
# `implemented` stays at its false default until the OS has a real driving
|
||||
# workflow; flipping happens by routing the combo to that workflow in
|
||||
# generate-e2e-matrix.mjs, never by setting this input.
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
name: Install & Update E2E — one starting tag (reusable)
|
||||
|
||||
# Everything a user starting on ONE released version could do: for each
|
||||
# {os, install-method, update-method} combination the support matrix
|
||||
# (scripts/sandbox/generate-e2e-matrix.mjs) declares, one job that installs
|
||||
# this tag and updates to HEAD.
|
||||
#
|
||||
# Skips happen where the knowledge lives:
|
||||
# * macOS combos have no workflow at all -- they natively skip right
|
||||
# here via install-e2e-skip.yml;
|
||||
# * windows combos ALL dispatch to install-e2e-windows-run.yml, which
|
||||
# natively skips the method pairs its driver cannot run yet;
|
||||
# * linux combos are all implemented.
|
||||
#
|
||||
# Call it:
|
||||
#
|
||||
# jobs:
|
||||
# tag:
|
||||
# strategy:
|
||||
# matrix: { install-ref: [v2026.8.3, v2026.3.12] }
|
||||
# uses: ./.github/workflows/install-e2e-tag.yml
|
||||
# with:
|
||||
# install-ref: ${{ matrix.install-ref }}
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
install-ref:
|
||||
description: 'The released version to start from: installed first, then updated to HEAD.'
|
||||
required: true
|
||||
type: string
|
||||
route:
|
||||
description: 'Combination filter, mirroring the dispatch route choice: all, both, update, installer, windows-desktop.'
|
||||
required: false
|
||||
type: string
|
||||
default: all
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
generate-matrix:
|
||||
name: expand combinations
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
outputs:
|
||||
linux: ${{ steps.gen.outputs.linux }}
|
||||
windows: ${{ steps.gen.outputs.windows }}
|
||||
skipped: ${{ steps.gen.outputs.skipped }}
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
sparse-checkout: scripts/sandbox/generate-e2e-matrix.mjs
|
||||
sparse-checkout-cone-mode: false
|
||||
- id: gen
|
||||
run: |
|
||||
set -euo pipefail
|
||||
matrices="$(node scripts/sandbox/generate-e2e-matrix.mjs \
|
||||
--tags '["${{ inputs.install-ref }}"]' \
|
||||
--route '${{ inputs.route }}')"
|
||||
echo "$matrices"
|
||||
for key in linux windows skipped; 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"
|
||||
done
|
||||
|
||||
linux:
|
||||
name: "linux: ${{ matrix.name }}"
|
||||
if: fromJSON(needs.generate-matrix.outputs.linux).include[0] != null
|
||||
needs: generate-matrix
|
||||
strategy:
|
||||
# One combo 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:
|
||||
route: ${{ matrix.route }}
|
||||
install-ref: ${{ matrix.install_ref }}
|
||||
|
||||
windows:
|
||||
name: "windows: ${{ matrix.name }}"
|
||||
if: fromJSON(needs.generate-matrix.outputs.windows).include[0] != null
|
||||
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 }}
|
||||
|
||||
skipped:
|
||||
name: "${{ matrix.os }}: ${{ matrix.install }} -> ${{ matrix.update }}"
|
||||
if: fromJSON(needs.generate-matrix.outputs.skipped).include[0] != null
|
||||
needs: generate-matrix
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJSON(needs.generate-matrix.outputs.skipped) }}
|
||||
uses: ./.github/workflows/install-e2e-skip.yml
|
||||
@@ -15,31 +15,43 @@ name: Install & Update E2E — Windows desktop (reusable)
|
||||
# update leg — an update becomes available exactly the way it does for a
|
||||
# real user.
|
||||
#
|
||||
# Routes (the update mechanism under test):
|
||||
# desktop the app's own Update button: the installed Hermes.exe runs
|
||||
# under Playwright's Electron driver, which clicks Settings ->
|
||||
# About -> "Update now"; the production hand-off chain runs
|
||||
# untouched (marker, app quit, detached updater, hermes
|
||||
# update, desktop rebuild, relaunch).
|
||||
# update TODO: `hermes update` from the installed venv.
|
||||
# installer TODO: re-run the bootstrap exe over the existing install.
|
||||
# Routes (from the update-method input):
|
||||
# desktop-app the app's own Update button: the installed
|
||||
# Hermes.exe runs under Playwright's Electron
|
||||
# driver, which clicks Settings -> About ->
|
||||
# "Update now"; the production hand-off chain runs
|
||||
# untouched (marker, app quit, detached updater,
|
||||
# hermes update, desktop rebuild, relaunch).
|
||||
# hermes-update TODO: `hermes update` from the installed venv.
|
||||
# desktop-installer-rerun@latest
|
||||
# TODO: re-run the bootstrap exe over the install.
|
||||
# irm-iex TODO: re-run the irm | iex one-liner.
|
||||
#
|
||||
# Method pairs without a driver yet NATIVELY SKIP (grey check, no runner):
|
||||
# the capability knowledge lives here, next to the driver, so the caller
|
||||
# can dispatch every declared combination without knowing which ones work.
|
||||
#
|
||||
# Call it:
|
||||
#
|
||||
# jobs:
|
||||
# windows-desktop:
|
||||
# windows:
|
||||
# uses: ./.github/workflows/install-e2e-windows-run.yml
|
||||
# with:
|
||||
# route: desktop
|
||||
# install-method: desktop-installer@latest
|
||||
# update-method: desktop-app
|
||||
# install-ref: v2026.8.3
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
route:
|
||||
description: 'Update mechanism to exercise: desktop (Update button; implemented), update (hermes update; TODO), installer (re-run bootstrap exe; TODO).'
|
||||
required: false
|
||||
install-method:
|
||||
description: 'How OLD gets installed. Supported: desktop-installer@latest (website exe, AHK-clicked). Declared-but-TODO methods skip.'
|
||||
required: true
|
||||
type: string
|
||||
update-method:
|
||||
description: 'How the install updates to HEAD. Supported: desktop-app (Update button under Playwright). Declared-but-TODO methods skip.'
|
||||
required: true
|
||||
type: string
|
||||
default: desktop
|
||||
install-ref:
|
||||
description: 'Ref to install as OLD (served as main while the installer runs). auto = the newest release tag in the checkout.'
|
||||
required: false
|
||||
@@ -61,7 +73,10 @@ permissions:
|
||||
|
||||
jobs:
|
||||
e2e:
|
||||
name: "${{ inputs.route }} route from website installer"
|
||||
name: "${{ inputs.install-method }} -> ${{ inputs.update-method }}"
|
||||
# The one pair the driver can run today. Anything else is a declared
|
||||
# TODO: native skip, so the coverage gap is a grey check on every run.
|
||||
if: inputs.install-method == 'desktop-installer@latest' && inputs.update-method == 'desktop-app'
|
||||
runs-on: windows-latest
|
||||
timeout-minutes: ${{ inputs.timeout-minutes }}
|
||||
|
||||
@@ -110,15 +125,15 @@ jobs:
|
||||
|
||||
- name: Stage serve repo (main -> OLD)
|
||||
shell: powershell
|
||||
run: powershell -NoProfile -ExecutionPolicy Bypass -File tests\install\windows-desktop-gui-e2e.ps1 -Phase stage -Route ${{ inputs.route }} -InstallRef "${{ inputs.install-ref }}" -SetupExeUrl ${{ inputs.setup-exe-url }}
|
||||
run: powershell -NoProfile -ExecutionPolicy Bypass -File tests\install\windows-desktop-gui-e2e.ps1 -Phase stage -Route "${{ inputs.update-method }}" -InstallRef "${{ inputs.install-ref }}" -SetupExeUrl ${{ inputs.setup-exe-url }}
|
||||
|
||||
- name: Install OLD via website Hermes-Setup.exe (headed, AHK-clicked)
|
||||
shell: powershell
|
||||
run: powershell -NoProfile -ExecutionPolicy Bypass -File tests\install\windows-desktop-gui-e2e.ps1 -Phase install-gui -Route ${{ inputs.route }} -InstallRef "${{ inputs.install-ref }}" -SetupExeUrl ${{ inputs.setup-exe-url }}
|
||||
run: powershell -NoProfile -ExecutionPolicy Bypass -File tests\install\windows-desktop-gui-e2e.ps1 -Phase install-gui -Route "${{ inputs.update-method }}" -InstallRef "${{ inputs.install-ref }}" -SetupExeUrl ${{ inputs.setup-exe-url }}
|
||||
|
||||
- name: Update OLD -> HEAD (${{ inputs.route }} route)
|
||||
- name: Update OLD -> HEAD (${{ inputs.update-method }})
|
||||
shell: powershell
|
||||
run: powershell -NoProfile -ExecutionPolicy Bypass -File tests\install\windows-desktop-gui-e2e.ps1 -Phase update-gui -Route ${{ inputs.route }} -InstallRef "${{ inputs.install-ref }}" -SetupExeUrl ${{ inputs.setup-exe-url }}
|
||||
run: powershell -NoProfile -ExecutionPolicy Bypass -File tests\install\windows-desktop-gui-e2e.ps1 -Phase update-gui -Route "${{ inputs.update-method }}" -InstallRef "${{ inputs.install-ref }}" -SetupExeUrl ${{ inputs.setup-exe-url }}
|
||||
|
||||
- name: Collect proof + logs
|
||||
if: always()
|
||||
@@ -141,7 +156,7 @@ jobs:
|
||||
if: always()
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: install-e2e-windows-${{ inputs.route }}-${{ github.sha }}
|
||||
name: install-e2e-windows-${{ inputs.update-method }}-${{ inputs.install-ref }}-${{ github.sha }}
|
||||
path: gui-e2e-proof
|
||||
retention-days: 14
|
||||
if-no-files-found: ignore
|
||||
|
||||
@@ -4,27 +4,34 @@ name: Install & Update E2E
|
||||
#
|
||||
# The support matrix -- every {os, install-method, update-method} combination
|
||||
# a user could be on -- lives in scripts/sandbox/generate-e2e-matrix.mjs.
|
||||
# The generate-matrix job expands it into ONE JOB PER COMBINATION:
|
||||
# The job graph nests one sub-graph per starting release tag:
|
||||
#
|
||||
# * linux legs install a sampled release through the real
|
||||
# `curl | install.sh` one-liner (uv, a managed Python, Node, the venv)
|
||||
# inside scripts/dev-sandbox.sh, then apply the combo's update route and
|
||||
# require the checkout to land on this commit with a working `hermes`;
|
||||
# * windows legs run the real desktop user flow (install-e2e-windows-run
|
||||
# .yml): the website's Hermes-Setup.exe clicked by AutoHotkey, then the
|
||||
# update driven through the app itself, Playwright clicking "Update now";
|
||||
# * combinations the spec declares but CI can't drive yet (macOS, the
|
||||
# remaining windows install/update methods) fan out too, one NATIVELY
|
||||
# SKIPPED check per combo, so every coverage gap is enumerable from the
|
||||
# Checks tab.
|
||||
# from <tag> (install-e2e-tag.yml)
|
||||
# -> linux: <install> -> <update> one job per combination, a real
|
||||
# curl|bash install in bubblewrap
|
||||
# (install-e2e-run.yml)
|
||||
# -> windows: <install> -> <update> one job per combination, the real
|
||||
# desktop user flow: website exe
|
||||
# clicked by AutoHotkey, update via
|
||||
# the app, Playwright clicking
|
||||
# "Update now"
|
||||
# (install-e2e-windows-run.yml)
|
||||
# -> macos: <install> -> <update> natively skipped until a macos
|
||||
# workflow exists
|
||||
# (install-e2e-skip.yml)
|
||||
#
|
||||
# The linux starting versions are chosen at runtime from the repo's release
|
||||
# tags (scripts/sandbox/pick-release-tags.sh): newest, oldest, and a spread
|
||||
# Every combination is dispatched; unimplemented ones natively skip (grey)
|
||||
# at the point that owns the knowledge -- macOS combos in the tag workflow
|
||||
# (no driver exists), windows method pairs inside the windows run workflow
|
||||
# (next to the driver that will implement them).
|
||||
#
|
||||
# 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. The
|
||||
# windows starting version is the newest release tag (the published
|
||||
# installer has no commit pin -- it installs what `main` serves, which the
|
||||
# harness stages to that tag).
|
||||
# after it ships, and would pin an "oldest" that nobody still runs. The tag
|
||||
# axis is applied to EVERY os: linux legs install the tag in the sandbox;
|
||||
# windows legs stage serve.git's `main` at it, which is what the published
|
||||
# installer (no commit pin) then installs.
|
||||
#
|
||||
# Triggers:
|
||||
# * every 12 hours, so upstream drift (a new uv, a Node bump, a PyPI change)
|
||||
@@ -68,8 +75,8 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
# Which released versions do we test updating FROM? Resolved once and shared
|
||||
# by every linux leg, so all routes cover the same set.
|
||||
# Which released versions do we test updating FROM? Resolved once; the
|
||||
# generator applies the tag axis to every OS's legs.
|
||||
pick-releases:
|
||||
name: Pick release tags
|
||||
runs-on: ubuntu-latest
|
||||
@@ -93,79 +100,24 @@ jobs:
|
||||
echo "Testing updates from: $tags"
|
||||
echo "tags=$tags" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# Expand the {os, install-method, update-method} support matrix
|
||||
# (scripts/sandbox/generate-e2e-matrix.mjs) into one job per combination.
|
||||
# Implemented combos land in the linux/windows matrices below; everything
|
||||
# the spec declares but CI can't drive yet lands in `skipped`, so the TODO
|
||||
# surface is visible in every run instead of buried in comments.
|
||||
generate-matrix:
|
||||
name: Generate combination matrix
|
||||
# One sub-graph per starting tag: install-e2e-tag.yml expands the
|
||||
# {os, install-method, update-method} support matrix for that tag and
|
||||
# fans out one job per combination, so the graph reads
|
||||
# "from vX -> windows: install -> update" per leg. Skips happen where
|
||||
# the knowledge lives: macOS combos natively skip inside the tag
|
||||
# workflow (no workflow exists for them at all); unimplemented windows
|
||||
# method pairs natively skip inside install-e2e-windows-run.yml, next
|
||||
# to the driver that will implement them.
|
||||
from-tag:
|
||||
name: "from ${{ matrix.install-ref }}"
|
||||
needs: pick-releases
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
outputs:
|
||||
linux: ${{ steps.gen.outputs.linux }}
|
||||
windows: ${{ steps.gen.outputs.windows }}
|
||||
skipped: ${{ steps.gen.outputs.skipped }}
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
sparse-checkout: scripts/sandbox/generate-e2e-matrix.mjs
|
||||
sparse-checkout-cone-mode: false
|
||||
- id: gen
|
||||
run: |
|
||||
set -euo pipefail
|
||||
matrices="$(node scripts/sandbox/generate-e2e-matrix.mjs \
|
||||
--tags '${{ needs.pick-releases.outputs.tags }}' \
|
||||
--route '${{ inputs.route || 'all' }}')"
|
||||
echo "$matrices"
|
||||
for key in linux windows skipped; 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"
|
||||
done
|
||||
|
||||
# Linux sandbox legs: one job per {install-method -> update-method, release
|
||||
# tag} combination, each a real curl|bash install in bubblewrap followed by
|
||||
# the combo's update route.
|
||||
linux:
|
||||
name: "linux: ${{ matrix.name }}"
|
||||
if: fromJSON(needs.generate-matrix.outputs.linux).include[0] != null
|
||||
needs: generate-matrix
|
||||
strategy:
|
||||
# One combo breaking is worth knowing about even if another already
|
||||
# failed, so let every leg report.
|
||||
# One tag breaking is worth knowing about even if another already
|
||||
# failed, so let every sub-graph report.
|
||||
fail-fast: false
|
||||
max-parallel: 4
|
||||
matrix: ${{ fromJSON(needs.generate-matrix.outputs.linux) }}
|
||||
uses: ./.github/workflows/install-e2e-run.yml
|
||||
matrix:
|
||||
install-ref: ${{ fromJSON(needs.pick-releases.outputs.tags) }}
|
||||
uses: ./.github/workflows/install-e2e-tag.yml
|
||||
with:
|
||||
route: ${{ matrix.route }}
|
||||
install-ref: ${{ matrix.install_ref }}
|
||||
|
||||
# Windows GUI legs: one job per combination of the real desktop user flow
|
||||
# (website installer clicked by AutoHotkey, update driven through the app).
|
||||
windows:
|
||||
name: "windows: ${{ matrix.name }}"
|
||||
if: fromJSON(needs.generate-matrix.outputs.windows).include[0] != null
|
||||
needs: generate-matrix
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJSON(needs.generate-matrix.outputs.windows) }}
|
||||
uses: ./.github/workflows/install-e2e-windows-run.yml
|
||||
with:
|
||||
route: ${{ matrix.route }}
|
||||
install-ref: ${{ matrix.install_ref }}
|
||||
|
||||
# Declared-but-unimplemented combinations: one job per combo, each a
|
||||
# NATIVE skip (grey, conclusion=skipped, no runner). Matrix context is not
|
||||
# available in job-level `if`, so the native skip lives in the called
|
||||
# workflow instead: its inner job is gated on an input that defaults to
|
||||
# false and is never passed. Implementing a combo moves it into
|
||||
# IMPLEMENTED in the generator.
|
||||
skipped:
|
||||
name: "todo: ${{ matrix.os }}: ${{ matrix.install }} -> ${{ matrix.update }}"
|
||||
if: fromJSON(needs.generate-matrix.outputs.skipped).include[0] != null
|
||||
needs: generate-matrix
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJSON(needs.generate-matrix.outputs.skipped) }}
|
||||
uses: ./.github/workflows/install-e2e-skip.yml
|
||||
install-ref: ${{ matrix.install-ref }}
|
||||
route: ${{ inputs.route || 'all' }}
|
||||
|
||||
@@ -7,15 +7,19 @@
|
||||
* per combination -- and everything else lands in a "skipped" matrix so the
|
||||
* TODO surface stays visible in every run instead of buried in comments.
|
||||
*
|
||||
* Used by .github/workflows/install-e2e.yml:
|
||||
* Used by .github/workflows/install-e2e-tag.yml, which runs it once per
|
||||
* starting release tag:
|
||||
*
|
||||
* node scripts/sandbox/generate-e2e-matrix.mjs \
|
||||
* --tags '["v2026.8.3","v2026.3.12"]' --route all
|
||||
* --tags '["v2026.8.3"]' --route all
|
||||
*
|
||||
* Prints JSON: { linux: {include:[...]}, windows: {include:[...]},
|
||||
* skipped: {include:[...]} }. The linux include entries carry
|
||||
* {name, route, install_ref} for install-e2e-run.yml; windows entries carry
|
||||
* {name, route, install_ref} for install-e2e-windows-run.yml.
|
||||
* skipped: {include:[...]} }. linux entries carry {name, route,
|
||||
* install_ref} for install-e2e-run.yml; windows entries carry {name,
|
||||
* install_method, update_method, install_ref} for
|
||||
* install-e2e-windows-run.yml (which itself natively skips method pairs it
|
||||
* cannot drive yet); skipped entries are the macOS combos (no workflow
|
||||
* exists at all).
|
||||
*/
|
||||
|
||||
import path from 'node:path';
|
||||
@@ -84,18 +88,20 @@ const KNOWN_METHODS = new Set([
|
||||
const ALLOWED_VERSIONS = new Set(['latest']);
|
||||
|
||||
/**
|
||||
* The combinations CI actually runs today, and which reusable workflow +
|
||||
* route input drives each. Everything in SPEC but not here is emitted as a
|
||||
* skipped combo. Install ids include the expanded @version suffix.
|
||||
* How each implemented combination maps onto a reusable workflow.
|
||||
*
|
||||
* linux: every combo is implemented; `route` is install-e2e-run.yml's input.
|
||||
* windows: ALL combos dispatch to install-e2e-windows-run.yml -- the run
|
||||
* workflow itself natively skips (grey) any {install, update} method pair
|
||||
* it cannot drive yet, so "which windows methods work" lives THERE, next
|
||||
* to the driver, not here.
|
||||
* macos: nothing is implemented; combos surface as native skips in the
|
||||
* caller via install-e2e-skip.yml without burning a tag fanout.
|
||||
*/
|
||||
export const IMPLEMENTED = [
|
||||
// scripts/dev-sandbox.sh legs (install-e2e-run.yml, bubblewrap sandbox).
|
||||
{ os: 'linux', install: 'curl-bash', update: 'hermes-update', workflow: 'linux', route: 'update' },
|
||||
{ os: 'linux', install: 'curl-bash', update: 'curl-bash', workflow: 'linux', route: 'installer' },
|
||||
// Real Windows GUI flow (install-e2e-windows-run.yml): website exe clicked
|
||||
// by AutoHotkey, Update now clicked by Playwright.
|
||||
{ os: 'windows', install: 'desktop-installer@latest', update: 'desktop-app', workflow: 'windows', route: 'desktop' },
|
||||
];
|
||||
export const LINUX_ROUTES = {
|
||||
'hermes-update': 'update',
|
||||
'curl-bash': 'installer',
|
||||
};
|
||||
|
||||
function validateEntry(os, kind, entry) {
|
||||
if (typeof entry.method !== 'string' || !KNOWN_METHODS.has(entry.method)) {
|
||||
@@ -150,25 +156,19 @@ export function generateEnvironments(spec) {
|
||||
return envs;
|
||||
}
|
||||
|
||||
function findImplementation(env) {
|
||||
return IMPLEMENTED.find(
|
||||
(m) => m.os === env.os && m.install === env.install && m.update === env.update,
|
||||
);
|
||||
}
|
||||
|
||||
/** Mirror of install-e2e.yml's dispatch `route` choice. */
|
||||
function routeWants(route, env, impl) {
|
||||
function routeWants(route, env) {
|
||||
switch (route) {
|
||||
case 'all':
|
||||
return true;
|
||||
case 'both':
|
||||
return env.os === 'linux' && Boolean(impl);
|
||||
return env.os === 'linux';
|
||||
case 'update':
|
||||
return impl?.route === 'update';
|
||||
return env.os === 'linux' && env.update === 'hermes-update';
|
||||
case 'installer':
|
||||
return impl?.route === 'installer';
|
||||
return env.os === 'linux' && env.update === 'curl-bash';
|
||||
case 'windows-desktop':
|
||||
return env.os === 'windows' && Boolean(impl);
|
||||
return env.os === 'windows';
|
||||
default:
|
||||
throw new Error(`unknown route filter: ${JSON.stringify(route)}`);
|
||||
}
|
||||
@@ -176,39 +176,63 @@ function routeWants(route, env, impl) {
|
||||
|
||||
/**
|
||||
* Split the combinations into per-workflow matrices.
|
||||
* Linux combos fan out further over `tags` (which released version the leg
|
||||
* installs first); Windows install versions come from the method id instead
|
||||
* (desktop-installer@latest = whatever the website serves).
|
||||
*
|
||||
* `tags` (the released versions we test updating FROM) is the OUTER axis,
|
||||
* applied to every OS with a driving workflow: for each tag, for each
|
||||
* combo, one job that installs the tag and updates to HEAD. Linux legs
|
||||
* pass the tag as install-ref for the sandbox to install; Windows legs
|
||||
* pass it as the ref the staged serve.git parks `main` at -- the published
|
||||
* installer has no commit pin, so that IS the installed version.
|
||||
*
|
||||
* Skip placement follows where the knowledge lives: macOS combos are known
|
||||
* unimplementable HERE (no workflow exists), so they go to the `skipped`
|
||||
* matrix -- one native grey check per combo, not multiplied by tags, since
|
||||
* no tag would change the outcome. Windows combos ALL dispatch to the run
|
||||
* workflow, which natively skips the method pairs it cannot drive.
|
||||
*/
|
||||
export function buildMatrices(envs, { tags = [], route = 'all' } = {}) {
|
||||
const linux = [];
|
||||
const windows = [];
|
||||
const skipped = [];
|
||||
const needTags = () => {
|
||||
if (tags.length === 0) {
|
||||
throw new Error('a combo with a driving workflow was selected but no --tags given');
|
||||
}
|
||||
};
|
||||
for (const env of envs) {
|
||||
const impl = findImplementation(env);
|
||||
if (!routeWants(route, env, impl)) continue;
|
||||
if (!impl) {
|
||||
skipped.push({ ...env, reason: 'not implemented yet' });
|
||||
} else if (impl.workflow === 'linux') {
|
||||
if (tags.length === 0) {
|
||||
throw new Error(`linux combo ${env.install} -> ${env.update} selected but no --tags given`);
|
||||
if (!routeWants(route, env)) continue;
|
||||
if (env.os === 'macos') {
|
||||
skipped.push({ ...env, reason: 'no macos workflow yet' });
|
||||
continue;
|
||||
}
|
||||
if (env.os === 'linux') {
|
||||
const linuxRoute = LINUX_ROUTES[env.update];
|
||||
if (!linuxRoute) {
|
||||
throw new Error(`linux update method ${JSON.stringify(env.update)} has no route mapping`);
|
||||
}
|
||||
needTags();
|
||||
for (const tag of tags) {
|
||||
linux.push({
|
||||
name: `${env.install} @ ${tag} -> ${env.update}`,
|
||||
route: impl.route,
|
||||
name: `${env.install} -> ${env.update}`,
|
||||
route: linuxRoute,
|
||||
install_ref: tag,
|
||||
});
|
||||
}
|
||||
} else if (impl.workflow === 'windows') {
|
||||
windows.push({
|
||||
name: `${env.install} -> ${env.update}`,
|
||||
route: impl.route,
|
||||
install_ref: 'auto',
|
||||
});
|
||||
} else {
|
||||
throw new Error(`unknown workflow ${JSON.stringify(impl.workflow)} for ${env.os}`);
|
||||
continue;
|
||||
}
|
||||
if (env.os === 'windows') {
|
||||
needTags();
|
||||
for (const tag of tags) {
|
||||
windows.push({
|
||||
name: `${env.install} -> ${env.update}`,
|
||||
install_method: env.install,
|
||||
update_method: env.update,
|
||||
install_ref: tag,
|
||||
});
|
||||
}
|
||||
continue;
|
||||
}
|
||||
throw new Error(`no workflow routing for os ${JSON.stringify(env.os)}`);
|
||||
}
|
||||
return {
|
||||
linux: { include: linux },
|
||||
|
||||
@@ -71,12 +71,13 @@ param(
|
||||
[ValidateSet("stage", "install-gui", "update-gui", "all")]
|
||||
[string]$Phase = "all",
|
||||
|
||||
# Update route to exercise in the update-gui phase. Only "desktop" (the
|
||||
# app's own Update button) is implemented; "update" (CLI `hermes
|
||||
# update`) and "installer" (re-run the bootstrap exe) are declared arms
|
||||
# so the workflow surface is stable when they land.
|
||||
[ValidateSet("desktop", "update", "installer")]
|
||||
[string]$Route = "desktop",
|
||||
# Update method to exercise in the update-gui phase, named by the same
|
||||
# ids the combination generator (scripts/sandbox/generate-e2e-matrix
|
||||
# .mjs) uses. Only "desktop-app" (the app's own Update button) is
|
||||
# implemented; the others are declared arms so the surface is stable
|
||||
# when they land.
|
||||
[ValidateSet("desktop-app", "hermes-update", "desktop-installer-rerun@latest", "irm-iex")]
|
||||
[string]$Route = "desktop-app",
|
||||
|
||||
# The OLD version: the ref served as `main` while the installer runs,
|
||||
# i.e. what the user starts on. The published Hermes-Setup.exe carries
|
||||
@@ -657,20 +658,25 @@ function Invoke-GuiUpdateDesktopRoute([string]$TargetSha) {
|
||||
function Invoke-PhaseUpdateGui {
|
||||
$state = Read-State
|
||||
switch ($Route) {
|
||||
"desktop" {
|
||||
"desktop-app" {
|
||||
Invoke-GuiUpdateDesktopRoute $state.current
|
||||
}
|
||||
"update" {
|
||||
"hermes-update" {
|
||||
# TODO: run `hermes update` from the installed venv -- the CLI
|
||||
# route. Needs the same completion/sha/relaunch asserts minus
|
||||
# the app-quit dance.
|
||||
throw "route 'update' (CLI hermes update) is not implemented yet"
|
||||
# route. Needs the same completion/sha asserts minus the
|
||||
# app-quit dance.
|
||||
throw "update method 'hermes-update' is not implemented yet"
|
||||
}
|
||||
"installer" {
|
||||
"desktop-installer-rerun@latest" {
|
||||
# TODO: re-run the bootstrap Hermes-Setup.exe over the existing
|
||||
# install (its --update flow jumps straight to progress and
|
||||
# runs unattended).
|
||||
throw "route 'installer' (re-run bootstrap exe) is not implemented yet"
|
||||
throw "update method 'desktop-installer-rerun@latest' is not implemented yet"
|
||||
}
|
||||
"irm-iex" {
|
||||
# TODO: re-run the irm | iex one-liner over the existing
|
||||
# install.
|
||||
throw "update method 'irm-iex' is not implemented yet"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user