fix(install): remove upstream stage-system splice that shadowed pm-era stages
The upstream merge concatenated upstream's owner-era stage system
(InstallStages array + Stage-Uv/Git/Python/SystemPackages wrappers +
Get-InstallStage/Step-OutOfInstallDir/Invoke-Stage) onto the pm-era
installer's stage functions. PowerShell last-def-wins meant the orphaned
wrappers (e.g. 'function Stage-Repository { Install-Repository }' where
Install-Repository exists NOWHERE) shadowed the real pm-era bodies —
every -Stage invocation crashed with 'not recognized'. Deleted the
entire upstream splice (lines 986-1221), restored Stage-Desktop wrapper
(Install-DesktopVoiceDeps + Install-Desktop); file parses clean and
each stage fn has exactly one def.
This commit is contained in:
@@ -304,6 +304,11 @@ function Stage-Gateway {
|
||||
& (Join-Path $InstallDir "venv\Scripts\python.exe") (Join-Path $InstallDir "hermes") gateway install
|
||||
}
|
||||
|
||||
function Stage-Desktop {
|
||||
Install-DesktopVoiceDeps
|
||||
Install-Desktop
|
||||
}
|
||||
|
||||
function Stage-Complete {
|
||||
$commit = $Commit
|
||||
if (-not $commit) { $commit = git -C $InstallDir rev-parse HEAD 2>$null }
|
||||
@@ -983,242 +988,6 @@ function Write-Completion {
|
||||
}
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Stage protocol
|
||||
# ============================================================================
|
||||
#
|
||||
# install.ps1 supports a small, stable "stage protocol" that lets programmatic
|
||||
# callers (the desktop GUI's onboarding wizard, CI, future install.sh, etc.)
|
||||
# drive the install one step at a time and surface progress/errors with their
|
||||
# own UI. CLI users running the canonical `irm | iex` one-liner never
|
||||
# encounter this -- default invocation behaves exactly as before.
|
||||
#
|
||||
# Entry points:
|
||||
#
|
||||
# install.ps1 Interactive install (today's behavior).
|
||||
# install.ps1 -ProtocolVersion Emit the protocol version integer.
|
||||
# install.ps1 -Manifest Emit the stage manifest as JSON.
|
||||
# install.ps1 -Stage <name> Run one stage and emit its result.
|
||||
# install.ps1 -NonInteractive Disable all Read-Host prompts (also
|
||||
# skips the setup wizard and the gateway
|
||||
# autostart prompt). Can be combined
|
||||
# with default invocation to do a full
|
||||
# non-interactive install.
|
||||
# install.ps1 -Json Emit machine-readable JSON instead of
|
||||
# the human-readable success banner at
|
||||
# the end of a full install.
|
||||
#
|
||||
# Manifest schema (the JSON returned by -Manifest):
|
||||
#
|
||||
# {
|
||||
# "protocol_version": 1,
|
||||
# "stages": [
|
||||
# {
|
||||
# "name": "uv",
|
||||
# "title": "Installing uv package manager",
|
||||
# "category": "prereqs",
|
||||
# "needs_user_input": false
|
||||
# },
|
||||
# ...
|
||||
# ]
|
||||
# }
|
||||
#
|
||||
# Stage result (the JSON written by -Stage <name>):
|
||||
#
|
||||
# {
|
||||
# "stage": "uv",
|
||||
# "ok": true,
|
||||
# "skipped": false,
|
||||
# "reason": null,
|
||||
# "duration_ms": 1234
|
||||
# }
|
||||
#
|
||||
# Exit codes:
|
||||
#
|
||||
# 0 -- success (stage ran, or stage was deliberately skipped).
|
||||
# 1 -- generic failure; the stage threw.
|
||||
# 2 -- unknown stage name passed to -Stage.
|
||||
#
|
||||
# Adding a stage:
|
||||
#
|
||||
# 1. Append an entry to $InstallStages below.
|
||||
# 2. Make sure the worker function it points at is idempotent and respects
|
||||
# $NonInteractive when it has prompts. Add it before "configure"
|
||||
# (the wizard) or "gateway" (autostart) if it should run unconditionally;
|
||||
# after those if it's optional post-install glue.
|
||||
# 3. Do NOT bump $InstallStageProtocolVersion -- adding stages is additive.
|
||||
# Drivers iterate the manifest dynamically.
|
||||
#
|
||||
# ============================================================================
|
||||
|
||||
# Stage definitions -- the single source of truth. Each entry maps a stable
|
||||
# stage name (the API contract drivers depend on) to the worker function that
|
||||
# implements it. ``Title`` is what UIs show; ``Category`` lets UIs group
|
||||
# stages; ``NeedsUserInput`` tells UIs "this stage prompts -- either skip it
|
||||
# or arrange to provide answers another way."
|
||||
$InstallStages = @(
|
||||
@{ Name = "uv"; Title = "Installing uv package manager"; Category = "prereqs"; NeedsUserInput = $false; Worker = "Stage-Uv" }
|
||||
@{ Name = "git"; Title = "Installing Git"; Category = "prereqs"; NeedsUserInput = $false; Worker = "Stage-Git" }
|
||||
@{ Name = "node"; Title = "Detecting Node.js"; Category = "prereqs"; NeedsUserInput = $false; Worker = "Stage-Node" }
|
||||
@{ Name = "system-packages"; Title = "Installing ripgrep and ffmpeg"; Category = "prereqs"; NeedsUserInput = $false; Worker = "Stage-SystemPackages" }
|
||||
@{ Name = "repository"; Title = "Cloning Hermes repository"; Category = "install"; NeedsUserInput = $false; Worker = "Stage-Repository" }
|
||||
# Managed Python lives under $InstallDir\.hermes-runtime, so the checkout
|
||||
# must exist before this stage creates that directory. Otherwise the later
|
||||
# repository stage treats the runtime-only directory as a broken checkout,
|
||||
# parks it, and leaves Stage-Venv with no managed interpreter.
|
||||
@{ Name = "python"; Title = "Verifying Python $PythonVersion"; Category = "prereqs"; NeedsUserInput = $false; Worker = "Stage-Python" }
|
||||
@{ Name = "venv"; Title = "Creating Python virtual environment"; Category = "install"; NeedsUserInput = $false; Worker = "Stage-Venv" }
|
||||
@{ Name = "dependencies"; Title = "Installing Python dependencies"; Category = "install"; NeedsUserInput = $false; Worker = "Stage-Dependencies" }
|
||||
@{ Name = "node-deps"; Title = "Installing Node.js dependencies"; Category = "install"; NeedsUserInput = $false; Worker = "Stage-NodeDeps" }
|
||||
)
|
||||
if ($IncludeDesktop) {
|
||||
# Insert AFTER node-deps so workspace npm is already installed when
|
||||
# the desktop build runs. Inserted only when explicitly requested
|
||||
# (Hermes-Setup.exe), never via the irm|iex CLI one-liner.
|
||||
$InstallStages += @{ Name = "desktop"; Title = "Building desktop app"; Category = "install"; NeedsUserInput = $false; Worker = "Stage-Desktop" }
|
||||
}
|
||||
$InstallStages += @(
|
||||
@{ Name = "path"; Title = "Adding Hermes to PATH"; Category = "finalize"; NeedsUserInput = $false; Worker = "Stage-Path" }
|
||||
@{ Name = "config-templates"; Title = "Writing configuration templates"; Category = "finalize"; NeedsUserInput = $false; Worker = "Stage-ConfigTemplates" }
|
||||
@{ Name = "platform-sdks"; Title = "Installing messaging platform SDKs"; Category = "finalize"; NeedsUserInput = $false; Worker = "Stage-PlatformSdks" }
|
||||
@{ Name = "bootstrap-marker"; Title = "Marking install complete"; Category = "finalize"; NeedsUserInput = $false; Worker = "Stage-BootstrapMarker" }
|
||||
# Interactive stages. In non-interactive mode these become no-ops; the
|
||||
# caller (GUI / CI) handles the equivalent UX themselves.
|
||||
@{ Name = "configure"; Title = "Configuring API keys and models"; Category = "post-install"; NeedsUserInput = $true; Worker = "Stage-Configure" }
|
||||
@{ Name = "gateway"; Title = "Starting messaging gateway"; Category = "post-install"; NeedsUserInput = $true; Worker = "Stage-Gateway" }
|
||||
)
|
||||
|
||||
# Stage workers -- thin wrappers that delegate to the existing Install-* /
|
||||
# Test-* / Invoke-* functions while preserving their error semantics. Kept
|
||||
# as a separate layer so the existing functions remain callable directly
|
||||
# (helpful for one-off recovery: ``. install.ps1; Install-Venv``).
|
||||
#
|
||||
# Stages that depend on uv (anything after Stage-Uv) call Resolve-UvCmd
|
||||
# first so they work in cross-process driver mode where $script:UvCmd
|
||||
# set by Stage-Uv in a sibling powershell process is not visible here.
|
||||
# Resolve-UvCmd is a fast no-op when $script:UvCmd is already populated
|
||||
# (the default-invocation case where Main runs everything in one
|
||||
# process), and throws cleanly if uv truly isn't installed yet.
|
||||
function Stage-Uv { if (-not (Install-Uv)) { throw "uv installation failed" } }
|
||||
function Stage-Python { Resolve-UvCmd; if (-not (Test-Python)) { throw "Python $PythonVersion not available" } }
|
||||
function Stage-Git {
|
||||
if (-not (Install-Git)) {
|
||||
if ($script:GitInstallFailureReason) { throw $script:GitInstallFailureReason }
|
||||
throw "Git not available and auto-install failed -- install from https://git-scm.com/download/win then re-run"
|
||||
}
|
||||
}
|
||||
# Node is optional (browser tools degrade gracefully without it). Surface
|
||||
# failure to the JSON contract as skipped=true / reason rather than ok=true,
|
||||
# so a GUI driver consuming the manifest can distinguish "node ready" from
|
||||
# "node missing". Install flow continues either way -- matches the
|
||||
# existing Write-Completion behavior that prints a "Note: Node.js could
|
||||
# not be installed" hint instead of aborting.
|
||||
function Stage-Node {
|
||||
if (-not (Test-Node)) {
|
||||
$script:_StageSkippedReason = "Node.js not available; browser tools will be unavailable until node is installed manually from https://nodejs.org/en/download/"
|
||||
}
|
||||
}
|
||||
function Stage-SystemPackages { Install-SystemPackages }
|
||||
function Stage-Repository { Install-Repository }
|
||||
function Stage-Venv { Resolve-UvCmd; Install-Venv }
|
||||
function Stage-Dependencies { Resolve-UvCmd; Install-Dependencies }
|
||||
function Stage-NodeDeps { Install-NodeDeps }
|
||||
function Stage-Desktop { Install-DesktopVoiceDeps; Install-Desktop }
|
||||
function Stage-Path { Set-PathVariable }
|
||||
function Stage-ConfigTemplates { Copy-ConfigTemplates }
|
||||
function Stage-PlatformSdks { Resolve-UvCmd; Install-PlatformSdks }
|
||||
function Stage-BootstrapMarker { Write-BootstrapMarker }
|
||||
function Stage-Configure { Invoke-SetupWizard }
|
||||
function Stage-Gateway { Start-GatewayIfConfigured }
|
||||
|
||||
function Get-InstallStage {
|
||||
param([string]$Name)
|
||||
foreach ($s in $InstallStages) {
|
||||
if ($s.Name -eq $Name) { return $s }
|
||||
}
|
||||
return $null
|
||||
}
|
||||
|
||||
function Step-OutOfInstallDir {
|
||||
# Windows refuses to delete a directory any shell is currently cd'd
|
||||
# inside -- and silently leaves orphan files behind, which then wedge
|
||||
# "is this a valid git repo" probes on re-install. Harmless when the
|
||||
# caller ran the installer from somewhere else.
|
||||
try {
|
||||
$currentResolved = (Get-Location).ProviderPath
|
||||
$installResolved = $null
|
||||
if (Test-Path $InstallDir) {
|
||||
$installResolved = (Resolve-Path $InstallDir -ErrorAction SilentlyContinue).ProviderPath
|
||||
}
|
||||
if ($installResolved -and $currentResolved.ToLower().StartsWith($installResolved.ToLower())) {
|
||||
Write-Info "Stepping out of $InstallDir so Windows can replace files there if needed..."
|
||||
Set-Location $env:USERPROFILE
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
||||
function Invoke-Stage {
|
||||
param(
|
||||
[Parameter(Mandatory=$true)] [hashtable]$StageDef
|
||||
)
|
||||
|
||||
# Refresh PATH from registry so this stage sees binaries installed by
|
||||
# prior stages, even when each stage runs in its own powershell process.
|
||||
# No-op in cost-relevant cases (default invocation path syncs once per
|
||||
# foreach pass; cross-process drivers get the necessary freshening).
|
||||
Sync-EnvPath
|
||||
|
||||
# Per-stage soft-skip channel. A worker can populate
|
||||
# $script:_StageSkippedReason to surface "ran, but the thing it was
|
||||
# supposed to set up is not available" as skipped=true in the JSON
|
||||
# frame, without throwing. Used by Stage-Node so the install flow
|
||||
# doesn't abort when an optional capability is missing while still
|
||||
# being honest in the protocol contract. Reset before each stage so
|
||||
# a prior stage's reason can never leak into a later stage's frame.
|
||||
$script:_StageSkippedReason = $null
|
||||
|
||||
$start = [DateTime]::UtcNow
|
||||
$result = @{
|
||||
stage = $StageDef.Name
|
||||
ok = $false
|
||||
skipped = $false
|
||||
reason = $null
|
||||
duration_ms = 0
|
||||
}
|
||||
|
||||
try {
|
||||
& $StageDef.Worker
|
||||
$result.ok = $true
|
||||
if ($script:_StageSkippedReason) {
|
||||
$result.skipped = $true
|
||||
$result.reason = $script:_StageSkippedReason
|
||||
}
|
||||
} catch {
|
||||
$result.ok = $false
|
||||
$result.reason = "$_"
|
||||
throw
|
||||
} finally {
|
||||
$result.duration_ms = [int]([DateTime]::UtcNow - $start).TotalMilliseconds
|
||||
if ($Json -or $Stage) {
|
||||
# In stage-driver mode every stage emits a JSON line so the
|
||||
# caller can stream progress. In default interactive mode we
|
||||
# stay silent here (the worker already wrote human output).
|
||||
$result | ConvertTo-Json -Compress | Write-Output
|
||||
# Tell the entry-point catch that we've already emitted a
|
||||
# frame for this failure (when $result.ok = $false), so it
|
||||
# doesn't double-emit a second JSON object and break the
|
||||
# one-line-per-stage contract the driver protocol promises.
|
||||
if (-not $result.ok) {
|
||||
$script:_StageEmittedErrorFrame = $true
|
||||
}
|
||||
}
|
||||
$markerPath = Join-Path $InstallDir ".hermes-bootstrap-complete"
|
||||
$tmp = "$markerPath.tmp"
|
||||
$marker | ConvertTo-Json | Set-Content -Path $tmp -Encoding utf8
|
||||
Move-Item -Force $tmp $markerPath
|
||||
}
|
||||
Log "install complete. Run: hermes"
|
||||
}
|
||||
|
||||
function Invoke-StageByName([string]$name) {
|
||||
switch ($name) {
|
||||
|
||||
Reference in New Issue
Block a user