fix(desktop): show 'hermes update' guidance for CLI installs instead of dead-end error
A user who installed via the CLI (irm|iex / install.sh) then ran
`hermes desktop` has no staged hermes-setup.exe, so clicking Update
in-app hit resolveUpdaterBinary()=null and showed a misleading error
('re-run the Hermes installer') with a Try-again button that could
never succeed — a dead loop for a perfectly valid install.
Treat the no-updater case as an intentional outcome, not a failure:
- main.cjs applyUpdates returns { ok:true, manual:true, command:'hermes update' }
(no throw, no 'error' stage) when no updater binary exists.
- New 'manual' update stage + apply-state.command thread the command to the UI.
- updates-overlay ManualView: a polished terminal-native card with the
exact command and a copy button, framed as the correct path for a CLI
user rather than an error.
GUI-installer users are unaffected — hermes-setup.exe present => seamless
auto-update runs as before. Zero new process orchestration; can't fail
the update demo.
This commit is contained in:
@@ -22,10 +22,21 @@ export interface UpdateApplyState {
|
||||
message: string
|
||||
percent: number | null
|
||||
error: string | null
|
||||
/** When the stage is 'manual': the exact command the user should run
|
||||
* (CLI install with no staged updater). */
|
||||
command: string | null
|
||||
log: readonly { stage: DesktopUpdateStage; message: string; at: number }[]
|
||||
}
|
||||
|
||||
const IDLE: UpdateApplyState = { applying: false, stage: 'idle', message: '', percent: null, error: null, log: [] }
|
||||
const IDLE: UpdateApplyState = {
|
||||
applying: false,
|
||||
stage: 'idle',
|
||||
message: '',
|
||||
percent: null,
|
||||
error: null,
|
||||
command: null,
|
||||
log: []
|
||||
}
|
||||
|
||||
export const $desktopVersion = atom<DesktopVersionInfo | null>(null)
|
||||
export const $updateApply = atom<UpdateApplyState>(IDLE)
|
||||
@@ -142,7 +153,20 @@ export async function applyUpdates(opts: DesktopUpdateApplyOptions = {}): Promis
|
||||
$updateApply.set({ ...IDLE, applying: true, stage: 'prepare', message: 'Starting update…' })
|
||||
|
||||
try {
|
||||
return await bridge.apply(opts)
|
||||
const result = await bridge.apply(opts)
|
||||
// CLI install with no staged updater: not an error — the user just runs
|
||||
// `hermes update` themselves. Land on a dedicated manual state so the
|
||||
// overlay shows the command + copy button instead of a dead retry loop.
|
||||
if (result?.manual) {
|
||||
$updateApply.set({
|
||||
...IDLE,
|
||||
applying: false,
|
||||
stage: 'manual',
|
||||
message: result.command ?? 'hermes update',
|
||||
command: result.command ?? 'hermes update'
|
||||
})
|
||||
}
|
||||
return result
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error)
|
||||
$updateApply.set({ ...$updateApply.get(), applying: false, stage: 'error', error: 'apply-failed', message })
|
||||
@@ -154,7 +178,7 @@ export async function applyUpdates(opts: DesktopUpdateApplyOptions = {}): Promis
|
||||
function ingestProgress(payload: DesktopUpdateProgress): void {
|
||||
const current = $updateApply.get()
|
||||
const log = [...current.log, { stage: payload.stage, message: payload.message, at: payload.at }].slice(-50)
|
||||
const terminal = payload.stage === 'error' || payload.stage === 'restart'
|
||||
const terminal = payload.stage === 'error' || payload.stage === 'restart' || payload.stage === 'manual'
|
||||
|
||||
$updateApply.set({
|
||||
applying: !terminal,
|
||||
@@ -162,6 +186,8 @@ function ingestProgress(payload: DesktopUpdateProgress): void {
|
||||
message: payload.message,
|
||||
percent: payload.percent,
|
||||
error: payload.error,
|
||||
// 'manual' carries the command to run in its message field.
|
||||
command: payload.stage === 'manual' ? payload.message : current.command,
|
||||
log
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user