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:
ethernet
2026-08-11 16:01:36 -04:00
parent 5a31a14f95
commit 83d009ae82
6 changed files with 281 additions and 181 deletions
+12 -9
View File
@@ -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:
+100
View File
@@ -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
+35 -20
View File
@@ -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
+44 -92
View File
@@ -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' }}
+71 -47
View File
@@ -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 },
+19 -13
View File
@@ -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"
}
}
}