041a19f060
* feat: add service-managed self-update * chore: add temporary self-update test scripts * chore: simplify temporary script markdown fences * chore: run frontend in temporary self-update scripts * fix: show self-update job progress after apply * chore: add real npm self-update test script * fix: isolate npm self-update prefix * fix: detect npm service installs from bin shim * chore: reset service before real npm update test * chore: expand real npm status diagnostics * fix: preserve service home during self-update * fix(cli): tee self-update helper output to a log file * fix(cli): avoid launchd bootstrap race when reloading the macOS service * fix(backend): run macOS self-update helper once via transient launchd plist * fix(web): show self-update progress and lock the apply button until the update lands * fix(cli): reinstall via the kandev wrapper on Homebrew self-update * chore: remove temp self-update test scripts * fix(web): drop unused apply path from useUpdates hook * fix(backend): snapshot install state once in apply preflight * docs: align self-update docs with transient LaunchAgent flow * chore(release): run cli bundle from apps workspace root * fix(backend): guard against concurrent self-update applies * fix(backend): check scheme and helper paths in self-update apply * fix(backend): drop duplicate binary name from npx manual command * fix(cli): allow --help for service self-update without --intent * fix(cli): harden self-update dir perms and darwin uid resolution * fix(web): set required POST fields last in applyUpdate * docs: use JSON body notation for the apply endpoint * fix(backend): expire self-update guard so a stuck helper cannot wedge apply * refactor(cli): collapse redundant writeSync branch in self-update log
107 lines
3.2 KiB
TypeScript
107 lines
3.2 KiB
TypeScript
"use client";
|
|
|
|
import { Button } from "@kandev/ui/button";
|
|
import { Spinner } from "@kandev/ui/spinner";
|
|
import { IconCheck, IconAlertTriangle } from "@tabler/icons-react";
|
|
import type { SelfUpdatePhase } from "@/hooks/domains/system/use-self-update";
|
|
|
|
type SelfUpdateProgressProps = {
|
|
phase: SelfUpdatePhase;
|
|
targetVersion: string | null;
|
|
errorMessage: string | null;
|
|
onDismiss: () => void;
|
|
};
|
|
|
|
function activeText(phase: SelfUpdatePhase, target: string | null): string {
|
|
const version = target ?? "the new version";
|
|
switch (phase) {
|
|
case "starting":
|
|
return `Starting update to ${version}…`;
|
|
case "installing":
|
|
return `Downloading and installing ${version}…`;
|
|
case "restarting":
|
|
return "Restarting Kandev — this can take up to a minute.";
|
|
default:
|
|
return "";
|
|
}
|
|
}
|
|
|
|
function ActiveRow({
|
|
phase,
|
|
targetVersion,
|
|
}: {
|
|
phase: SelfUpdatePhase;
|
|
targetVersion: string | null;
|
|
}) {
|
|
return (
|
|
<div className="space-y-1">
|
|
<div className="flex items-center gap-2 text-sm">
|
|
<Spinner className="size-4" />
|
|
<span>{activeText(phase, targetVersion)}</span>
|
|
</div>
|
|
<p className="text-xs text-muted-foreground">
|
|
Keep this page open. It will refresh automatically when the update finishes.
|
|
</p>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function DoneRow({ targetVersion }: { targetVersion: string | null }) {
|
|
return (
|
|
<div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
|
|
<div className="flex items-center gap-2 text-sm">
|
|
<IconCheck className="size-4 text-emerald-500" />
|
|
<span>Updated to {targetVersion ?? "the latest version"}.</span>
|
|
</div>
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
className="cursor-pointer self-start sm:self-auto"
|
|
onClick={() => window.location.reload()}
|
|
data-testid="system-updates-progress-reload"
|
|
>
|
|
Reload page
|
|
</Button>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function ErrorRow({ message, onDismiss }: { message: string | null; onDismiss: () => void }) {
|
|
return (
|
|
<div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
|
|
<div className="flex items-start gap-2 text-sm text-destructive">
|
|
<IconAlertTriangle className="size-4 shrink-0" />
|
|
<span>{message ?? "The update failed."}</span>
|
|
</div>
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
className="cursor-pointer self-start sm:self-auto"
|
|
onClick={onDismiss}
|
|
data-testid="system-updates-progress-dismiss"
|
|
>
|
|
Dismiss
|
|
</Button>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function ProgressBody({ phase, targetVersion, errorMessage, onDismiss }: SelfUpdateProgressProps) {
|
|
if (phase === "done") return <DoneRow targetVersion={targetVersion} />;
|
|
if (phase === "error") return <ErrorRow message={errorMessage} onDismiss={onDismiss} />;
|
|
return <ActiveRow phase={phase} targetVersion={targetVersion} />;
|
|
}
|
|
|
|
export function SelfUpdateProgress(props: SelfUpdateProgressProps) {
|
|
if (props.phase === "idle") return null;
|
|
return (
|
|
<div
|
|
className="rounded-md border bg-muted/30 px-3 py-2"
|
|
data-testid="system-updates-progress"
|
|
data-phase={props.phase}
|
|
>
|
|
<ProgressBody {...props} />
|
|
</div>
|
|
);
|
|
}
|