fix(install.ps1): Stage-Node honest reporting + reject empty -Stage
Two protocol-correctness gaps from review:
1. Stage-Node used [void](Test-Node) which discarded Test-Node's return
value, so the JSON frame always reported ok=true even when Node
install fully failed. A GUI driver consuming the manifest couldn't
tell 'node ready' from 'node missing'. Wire a soft-skip channel
($script:_StageSkippedReason) that workers can populate to surface
'ran, but the thing it was supposed to set up is not available' as
skipped=true with a reason in the JSON, without aborting the install
(Node is optional -- browser tools degrade gracefully, matches
Write-Completion's existing 'Note: Node.js could not be installed'
behavior). Reset before each stage so a prior reason can't leak.
2. The -Stage dispatch used 'if ($Stage)' which is falsy for empty
string, so 'install.ps1 -Stage ""' fell through to Main and silently
kicked off a full destructive install. Switch to
PSBoundParameters.ContainsKey('Stage') so an explicit empty value
surfaces as unknown-stage exit 2 with a structured JSON frame, the
way every other bad stage name does.
This commit is contained in:
@@ -1926,7 +1926,17 @@ $InstallStages = @(
|
|||||||
function Stage-Uv { if (-not (Install-Uv)) { throw "uv installation failed" } }
|
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-Python { Resolve-UvCmd; if (-not (Test-Python)) { throw "Python $PythonVersion not available" } }
|
||||||
function Stage-Git { if (-not (Install-Git)) { throw "Git not available and auto-install failed -- install from https://git-scm.com/download/win then re-run" } }
|
function Stage-Git { if (-not (Install-Git)) { throw "Git not available and auto-install failed -- install from https://git-scm.com/download/win then re-run" } }
|
||||||
function Stage-Node { [void](Test-Node) }
|
# 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-SystemPackages { Install-SystemPackages }
|
||||||
function Stage-Repository { Install-Repository }
|
function Stage-Repository { Install-Repository }
|
||||||
function Stage-Venv { Resolve-UvCmd; Install-Venv }
|
function Stage-Venv { Resolve-UvCmd; Install-Venv }
|
||||||
@@ -1975,6 +1985,15 @@ function Invoke-Stage {
|
|||||||
# foreach pass; cross-process drivers get the necessary freshening).
|
# foreach pass; cross-process drivers get the necessary freshening).
|
||||||
Sync-EnvPath
|
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
|
$start = [DateTime]::UtcNow
|
||||||
$result = @{
|
$result = @{
|
||||||
stage = $StageDef.Name
|
stage = $StageDef.Name
|
||||||
@@ -1987,6 +2006,10 @@ function Invoke-Stage {
|
|||||||
try {
|
try {
|
||||||
& $StageDef.Worker
|
& $StageDef.Worker
|
||||||
$result.ok = $true
|
$result.ok = $true
|
||||||
|
if ($script:_StageSkippedReason) {
|
||||||
|
$result.skipped = $true
|
||||||
|
$result.reason = $script:_StageSkippedReason
|
||||||
|
}
|
||||||
} catch {
|
} catch {
|
||||||
$result.ok = $false
|
$result.ok = $false
|
||||||
$result.reason = "$_"
|
$result.reason = "$_"
|
||||||
@@ -2060,7 +2083,12 @@ try {
|
|||||||
exit 0
|
exit 0
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($Stage) {
|
# Use PSBoundParameters rather than $Stage truthiness so that an
|
||||||
|
# explicit `-Stage ""` from a misbehaving driver doesn't fall through
|
||||||
|
# to the full-install Main path and silently kick off a destructive
|
||||||
|
# operation. Empty string is a contract violation; surface it as
|
||||||
|
# unknown-stage exit 2 with a structured JSON frame.
|
||||||
|
if ($PSBoundParameters.ContainsKey("Stage")) {
|
||||||
$def = Get-InstallStage -Name $Stage
|
$def = Get-InstallStage -Name $Stage
|
||||||
if (-not $def) {
|
if (-not $def) {
|
||||||
$err = @{
|
$err = @{
|
||||||
|
|||||||
Reference in New Issue
Block a user