feat: interactive update flow for non-patch releases (#18662)
This commit is contained in:
@@ -5,8 +5,8 @@ import { MouseButton, TextAttributes } from "@opentui/core"
|
||||
import { RouteProvider, useRoute } from "@tui/context/route"
|
||||
import { Switch, Match, createEffect, untrack, ErrorBoundary, createSignal, onMount, batch, Show, on } from "solid-js"
|
||||
import { win32DisableProcessedInput, win32FlushInputBuffer, win32InstallCtrlCGuard } from "./win32"
|
||||
import { Installation } from "@/installation"
|
||||
import { Flag } from "@/flag/flag"
|
||||
import semver from "semver"
|
||||
import { DialogProvider, useDialog } from "@tui/ui/dialog"
|
||||
import { DialogProvider as DialogProviderList } from "@tui/component/dialog-provider"
|
||||
import { SDKProvider, useSDK } from "@tui/context/sdk"
|
||||
@@ -29,6 +29,7 @@ import { PromptHistoryProvider } from "./component/prompt/history"
|
||||
import { FrecencyProvider } from "./component/prompt/frecency"
|
||||
import { PromptStashProvider } from "./component/prompt/stash"
|
||||
import { DialogAlert } from "./ui/dialog-alert"
|
||||
import { DialogConfirm } from "./ui/dialog-confirm"
|
||||
import { ToastProvider, useToast } from "./ui/toast"
|
||||
import { ExitProvider, useExit } from "./context/exit"
|
||||
import { Session as SessionApi } from "@/session"
|
||||
@@ -103,6 +104,7 @@ async function getTerminalBackgroundColor(): Promise<"dark" | "light"> {
|
||||
}
|
||||
|
||||
import type { EventSource } from "./context/sdk"
|
||||
import { Installation } from "@/installation"
|
||||
|
||||
export function tui(input: {
|
||||
url: string
|
||||
@@ -729,13 +731,51 @@ function App() {
|
||||
})
|
||||
})
|
||||
|
||||
sdk.event.on(Installation.Event.UpdateAvailable.type, (evt) => {
|
||||
sdk.event.on("installation.update-available", async (evt) => {
|
||||
const version = evt.properties.version
|
||||
|
||||
const skipped = kv.get("skipped_version")
|
||||
if (skipped && !semver.gt(version, skipped)) return
|
||||
|
||||
const choice = await DialogConfirm.show(
|
||||
dialog,
|
||||
`Update Available`,
|
||||
`A new release v${version} is available. Would you like to update now?`,
|
||||
"skip",
|
||||
)
|
||||
|
||||
if (choice === false) {
|
||||
kv.set("skipped_version", version)
|
||||
return
|
||||
}
|
||||
|
||||
if (choice !== true) return
|
||||
|
||||
toast.show({
|
||||
variant: "info",
|
||||
title: "Update Available",
|
||||
message: `OpenCode v${evt.properties.version} is available. Run 'opencode upgrade' to update manually.`,
|
||||
duration: 10000,
|
||||
message: `Updating to v${version}...`,
|
||||
duration: 30000,
|
||||
})
|
||||
|
||||
const result = await sdk.client.global.upgrade({ target: version })
|
||||
|
||||
if (result.error || !result.data?.success) {
|
||||
toast.show({
|
||||
variant: "error",
|
||||
title: "Update Failed",
|
||||
message: "Update failed",
|
||||
duration: 10000,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
await DialogAlert.show(
|
||||
dialog,
|
||||
"Update Complete",
|
||||
`Successfully updated to OpenCode v${result.data.version}. Please restart the application.`,
|
||||
)
|
||||
|
||||
exit()
|
||||
})
|
||||
|
||||
return (
|
||||
|
||||
@@ -11,8 +11,11 @@ export type DialogConfirmProps = {
|
||||
message: string
|
||||
onConfirm?: () => void
|
||||
onCancel?: () => void
|
||||
label?: string
|
||||
}
|
||||
|
||||
export type DialogConfirmResult = boolean | undefined
|
||||
|
||||
export function DialogConfirm(props: DialogConfirmProps) {
|
||||
const dialog = useDialog()
|
||||
const { theme } = useTheme()
|
||||
@@ -45,7 +48,7 @@ export function DialogConfirm(props: DialogConfirmProps) {
|
||||
<text fg={theme.textMuted}>{props.message}</text>
|
||||
</box>
|
||||
<box flexDirection="row" justifyContent="flex-end" paddingBottom={1}>
|
||||
<For each={["cancel", "confirm"]}>
|
||||
<For each={["cancel", "confirm"] as const}>
|
||||
{(key) => (
|
||||
<box
|
||||
paddingLeft={1}
|
||||
@@ -58,7 +61,7 @@ export function DialogConfirm(props: DialogConfirmProps) {
|
||||
}}
|
||||
>
|
||||
<text fg={key === store.active ? theme.selectedListItemText : theme.textMuted}>
|
||||
{Locale.titlecase(key)}
|
||||
{Locale.titlecase(key === "cancel" ? (props.label ?? key) : key)}
|
||||
</text>
|
||||
</box>
|
||||
)}
|
||||
@@ -68,8 +71,8 @@ export function DialogConfirm(props: DialogConfirmProps) {
|
||||
)
|
||||
}
|
||||
|
||||
DialogConfirm.show = (dialog: DialogContext, title: string, message: string) => {
|
||||
return new Promise<boolean>((resolve) => {
|
||||
DialogConfirm.show = (dialog: DialogContext, title: string, message: string, label?: string) => {
|
||||
return new Promise<DialogConfirmResult>((resolve) => {
|
||||
dialog.replace(
|
||||
() => (
|
||||
<DialogConfirm
|
||||
@@ -77,9 +80,10 @@ DialogConfirm.show = (dialog: DialogContext, title: string, message: string) =>
|
||||
message={message}
|
||||
onConfirm={() => resolve(true)}
|
||||
onCancel={() => resolve(false)}
|
||||
label={label}
|
||||
/>
|
||||
),
|
||||
() => resolve(false),
|
||||
() => resolve(undefined),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user