fix(desktop): repair missing Windows runtime
This commit is contained in:
@@ -3707,16 +3707,20 @@ async function handOffWindowsBootstrapRecovery(reason) {
|
||||
const venvHermes = path.join(venvBin, IS_WINDOWS ? 'hermes.exe' : 'hermes')
|
||||
const venvPython = path.join(venvBin, IS_WINDOWS ? 'python.exe' : 'python')
|
||||
|
||||
// Choose the gentle in-place --update when ANY real-install signal is present,
|
||||
// not just the `hermes.exe` console-script shim. That shim is generated at the
|
||||
// END of venv setup and is absent in exactly the interrupted/quarantined states
|
||||
// this recovery exists to heal — gating on it alone forced the destructive
|
||||
// --repair (full venv recreate) and drove reinstall loops. The venv interpreter
|
||||
// and the bootstrap-complete marker are present earlier and are better signals.
|
||||
const haveRealInstall =
|
||||
fileExists(venvPython) || fileExists(venvHermes) || fileExists(path.join(updateRoot, '.hermes-bootstrap-complete'))
|
||||
|
||||
const updaterArgs = chooseUpdaterArgs(haveRealInstall, branch)
|
||||
// The updater invokes the venv's Hermes launcher, which in turn requires the
|
||||
// venv interpreter. A bootstrap-complete marker proves only that setup once
|
||||
// finished; it can outlive a manually removed or quarantined venv. Sending a
|
||||
// marker-only install through --update dead-ends at "Could not find the hermes
|
||||
// CLI" instead of rebuilding the runtime, so only a runnable pair gets the
|
||||
// gentle update path. Partial or missing runtimes go through full repair.
|
||||
const updaterArgs = chooseUpdaterArgs(
|
||||
{
|
||||
hasBootstrapMarker: fileExists(path.join(updateRoot, '.hermes-bootstrap-complete')),
|
||||
hasVenvHermes: fileExists(venvHermes),
|
||||
hasVenvPython: fileExists(venvPython)
|
||||
},
|
||||
branch
|
||||
)
|
||||
|
||||
await releaseBackendLockForUpdate(updateRoot)
|
||||
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
// 1. buildPathExtCandidates() — PATHEXT extensions must be tried BEFORE the
|
||||
// empty extension, or an extensionless Git-Bash `hermes` shim shadows
|
||||
// the real hermes.cmd/hermes.exe.
|
||||
// 2. chooseUpdaterArgs() — must gate on haveRealInstall (any real-install
|
||||
// signal), not just the hermes.exe console-script shim, or healthy
|
||||
// installs get forced into a destructive --repair.
|
||||
// 2. chooseUpdaterArgs() — must distinguish a runnable updater from stale
|
||||
// install provenance. The bootstrap marker can outlive the venv, and a
|
||||
// partial venv cannot run the updater; those states require --repair.
|
||||
// 3. resolveVenvHermesCommand() — must probe the venv python via
|
||||
// canImportHermesCli() before trusting it, or a broken venv gets
|
||||
// re-selected forever instead of falling through to bootstrap.
|
||||
@@ -45,17 +45,40 @@ test('buildPathExtCandidates: non-Windows only tries the bare name', () => {
|
||||
assert.deepEqual(buildPathExtCandidates(undefined, false), [''])
|
||||
})
|
||||
|
||||
test('chooseUpdaterArgs: gentle --update when a real-install signal is present', () => {
|
||||
assert.deepEqual(chooseUpdaterArgs(true, 'main'), ['--update', '--branch', 'main'])
|
||||
test('chooseUpdaterArgs: gentle --update when both updater runtime files exist', () => {
|
||||
assert.deepEqual(
|
||||
chooseUpdaterArgs({ hasBootstrapMarker: true, hasVenvHermes: true, hasVenvPython: true }, 'main'),
|
||||
['--update', '--branch', 'main']
|
||||
)
|
||||
})
|
||||
|
||||
test('chooseUpdaterArgs: destructive --repair only when NO real-install signal is present', () => {
|
||||
assert.deepEqual(chooseUpdaterArgs(false, 'main'), ['--repair', '--branch', 'main'])
|
||||
test('chooseUpdaterArgs: marker-only install uses --repair when the venv is gone', () => {
|
||||
assert.deepEqual(
|
||||
chooseUpdaterArgs({ hasBootstrapMarker: true, hasVenvHermes: false, hasVenvPython: false }, 'main'),
|
||||
['--repair', '--branch', 'main']
|
||||
)
|
||||
})
|
||||
|
||||
test('chooseUpdaterArgs: passes the branch through unchanged in both cases', () => {
|
||||
assert.deepEqual(chooseUpdaterArgs(true, 'release/1.2'), ['--update', '--branch', 'release/1.2'])
|
||||
assert.deepEqual(chooseUpdaterArgs(false, 'release/1.2'), ['--repair', '--branch', 'release/1.2'])
|
||||
test('chooseUpdaterArgs: partial updater runtimes use --repair', () => {
|
||||
assert.deepEqual(
|
||||
chooseUpdaterArgs({ hasBootstrapMarker: true, hasVenvHermes: false, hasVenvPython: true }, 'main'),
|
||||
['--repair', '--branch', 'main']
|
||||
)
|
||||
assert.deepEqual(
|
||||
chooseUpdaterArgs({ hasBootstrapMarker: true, hasVenvHermes: true, hasVenvPython: false }, 'main'),
|
||||
['--repair', '--branch', 'main']
|
||||
)
|
||||
})
|
||||
|
||||
test('chooseUpdaterArgs: passes the branch through unchanged in both modes', () => {
|
||||
assert.deepEqual(
|
||||
chooseUpdaterArgs({ hasBootstrapMarker: false, hasVenvHermes: true, hasVenvPython: true }, 'release/1.2'),
|
||||
['--update', '--branch', 'release/1.2']
|
||||
)
|
||||
assert.deepEqual(
|
||||
chooseUpdaterArgs({ hasBootstrapMarker: false, hasVenvHermes: false, hasVenvPython: false }, 'release/1.2'),
|
||||
['--repair', '--branch', 'release/1.2']
|
||||
)
|
||||
})
|
||||
|
||||
function makeDeps(overrides: Partial<Parameters<typeof resolveVenvHermesCommand>[2]> = {}) {
|
||||
|
||||
@@ -11,12 +11,11 @@
|
||||
* hermes.cmd/hermes.exe; the shim then failed the --version probe and
|
||||
* the desktop fell through to a spurious bootstrap/repair. The fix:
|
||||
* PATHEXT extensions first, empty extension LAST.
|
||||
* 2. chooseUpdaterArgs() — handOffWindowsBootstrapRecovery() chose
|
||||
* --update vs the destructive --repair by checking ONLY
|
||||
* venv\Scripts\hermes.exe (the console-script shim, written at the END
|
||||
* of venv setup and absent in interrupted states), so it escalated to a
|
||||
* full venv recreate even on healthy installs. The fix: gate on ANY
|
||||
* real-install signal, not just the shim.
|
||||
* 2. chooseUpdaterArgs() — handOffWindowsBootstrapRecovery() must separate
|
||||
* install provenance from updater viability. A bootstrap-complete marker
|
||||
* can outlive a deleted venv, while the updater needs BOTH the venv Python
|
||||
* and Hermes launcher. Marker-only or partial runtimes must use --repair;
|
||||
* only a runnable pair can use --update.
|
||||
* 3. resolveVenvHermesCommand() — unwrapWindowsVenvHermesCommand() returned
|
||||
* the venv python with NO runtime probe (bypassing the caller's
|
||||
* --version check too), so a venv broken mid-update (e.g. missing
|
||||
@@ -61,23 +60,26 @@ export function buildPathExtCandidates(pathext: string | undefined, isWindows: b
|
||||
}
|
||||
|
||||
/**
|
||||
* Choose the Windows bootstrap-recovery updater invocation: the gentle
|
||||
* in-place --update when ANY real-install signal is present, the
|
||||
* destructive --repair (full venv recreate) otherwise.
|
||||
* Choose the Windows bootstrap-recovery invocation. The gentle in-place
|
||||
* updater can only start when both pieces of its runtime contract exist: the
|
||||
* venv Python interpreter and the Hermes launcher that drives `hermes update`.
|
||||
* A bootstrap-complete marker proves install provenance, not current runtime
|
||||
* usability, and may remain after the venv is removed or quarantined.
|
||||
*
|
||||
* haveRealInstall must be computed by the caller from ALL real-install
|
||||
* signals (venv python interpreter, venv hermes shim, bootstrap-complete
|
||||
* marker) — gating on just the hermes.exe console-script shim alone is the
|
||||
* regression this function's callers must avoid: that shim is written at
|
||||
* the END of venv setup and is absent in exactly the interrupted/quarantined
|
||||
* states this recovery exists to heal.
|
||||
*
|
||||
* @param {boolean} haveRealInstall
|
||||
* @param {BootstrapRecoverySignals} signals
|
||||
* @param {string} branch
|
||||
* @returns {string[]} updater argv, e.g. ['--update', '--branch', 'main'].
|
||||
*/
|
||||
export function chooseUpdaterArgs(haveRealInstall: boolean, branch: string): string[] {
|
||||
return haveRealInstall ? ['--update', '--branch', branch] : ['--repair', '--branch', branch]
|
||||
export interface BootstrapRecoverySignals {
|
||||
hasBootstrapMarker: boolean
|
||||
hasVenvHermes: boolean
|
||||
hasVenvPython: boolean
|
||||
}
|
||||
|
||||
export function chooseUpdaterArgs(signals: BootstrapRecoverySignals, branch: string): string[] {
|
||||
const canRunUpdater = signals.hasVenvHermes && signals.hasVenvPython
|
||||
|
||||
return canRunUpdater ? ['--update', '--branch', branch] : ['--repair', '--branch', branch]
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user