feat(settings): manual Check for updates + Update button
Auto-update only checked at launch, so users with auto-updates off (or who just want it now) had no way to update from the app. Add an on-demand flow: checkForUpdatesNow/downloadUpdateNow/quitAndInstallUpdate in auto-updater (works regardless of the opt-in; not-packaged dev surfaces 'unsupported'), forwarded to the renderer as a live UpdateStatus over updates:status. The Updates section gains a Check for updates button and an Update button that downloads then installs (Restart & install), with checking/available/ downloading%/downloaded/error status. Refs #2207. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
7f5beed70e
commit
77d1cf9ef8
|
|
@ -11,8 +11,20 @@ import {
|
|||
WebContentsView,
|
||||
type OpenDialogOptions,
|
||||
} from "electron";
|
||||
import { startAutoUpdates, ensureUpdatePrefs } from "./main/auto-updater";
|
||||
import { readUpdateSettings, writeUpdateSettings, type UpdateSettings } from "./main/update-settings";
|
||||
import {
|
||||
startAutoUpdates,
|
||||
ensureUpdatePrefs,
|
||||
checkForUpdatesNow,
|
||||
downloadUpdateNow,
|
||||
quitAndInstallUpdate,
|
||||
getUpdateStatus,
|
||||
} from "./main/auto-updater";
|
||||
import {
|
||||
readUpdateSettings,
|
||||
writeUpdateSettings,
|
||||
type UpdateSettings,
|
||||
type UpdateStatus,
|
||||
} from "./main/update-settings";
|
||||
import { spawn, type ChildProcessWithoutNullStreams } from "node:child_process";
|
||||
import { existsSync } from "node:fs";
|
||||
import { readFile, rm } from "node:fs/promises";
|
||||
|
|
@ -818,6 +830,19 @@ ipcMain.handle("updateSettings:set", async (_event, settings: UpdateSettings) =>
|
|||
await writeUpdateSettings(path.dirname(runFile), settings);
|
||||
});
|
||||
|
||||
ipcMain.handle("updates:getStatus", (): UpdateStatus => getUpdateStatus());
|
||||
ipcMain.handle("updates:check", async () => {
|
||||
const runFile = runFilePath();
|
||||
if (!runFile) return;
|
||||
await checkForUpdatesNow(path.dirname(runFile));
|
||||
});
|
||||
ipcMain.handle("updates:download", async () => {
|
||||
await downloadUpdateNow();
|
||||
});
|
||||
ipcMain.handle("updates:install", () => {
|
||||
quitAndInstallUpdate();
|
||||
});
|
||||
|
||||
ipcMain.handle("notifications:show", (_event, notification: { id: string; title: string; body?: string }) => {
|
||||
if (!notification.id || !notification.title || !ElectronNotification.isSupported()) return;
|
||||
const toast = new ElectronNotification({
|
||||
|
|
|
|||
|
|
@ -1,8 +1,14 @@
|
|||
import { autoUpdater } from "electron-updater";
|
||||
import { dialog } from "electron";
|
||||
import { app, BrowserWindow, dialog } from "electron";
|
||||
import { existsSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { readUpdateSettings, writeUpdateSettings, UPDATE_SETTINGS_FILE_NAME } from "./update-settings";
|
||||
import {
|
||||
readUpdateSettings,
|
||||
writeUpdateSettings,
|
||||
UPDATE_SETTINGS_FILE_NAME,
|
||||
type UpdateChannel,
|
||||
type UpdateStatus,
|
||||
} from "./update-settings";
|
||||
|
||||
// Default release repo, mirroring backend cli.releaseRepo. Override via env for
|
||||
// fork test builds (AO_RELEASE_REPO=owner/repo).
|
||||
|
|
@ -15,6 +21,50 @@ function repo(): { owner: string; name: string } {
|
|||
return { owner: defOwner, name: defName };
|
||||
}
|
||||
|
||||
// configureFeed points electron-updater at the GitHub Releases feed for the
|
||||
// chosen channel. Shared by the launch auto-check and the manual Settings flow.
|
||||
function configureFeed(channel: UpdateChannel): void {
|
||||
const { owner, name } = repo();
|
||||
autoUpdater.setFeedURL({ provider: "github", owner, repo: name });
|
||||
autoUpdater.channel = channel; // "latest" | "nightly"
|
||||
autoUpdater.allowDowngrade = true; // permits a nightly -> stable channel switch
|
||||
}
|
||||
|
||||
let lastStatus: UpdateStatus = { state: "idle" };
|
||||
let eventsWired = false;
|
||||
|
||||
// broadcast pushes the latest update status to every renderer window so the
|
||||
// Global Settings Updates section can reflect check/download progress live.
|
||||
function broadcast(status: UpdateStatus): void {
|
||||
lastStatus = status;
|
||||
for (const win of BrowserWindow.getAllWindows()) {
|
||||
if (!win.isDestroyed()) win.webContents.send("updates:status", status);
|
||||
}
|
||||
}
|
||||
|
||||
// wireUpdaterEvents registers electron-updater listeners once and forwards each
|
||||
// to the renderer as an UpdateStatus. Idempotent: safe to call on every entry
|
||||
// point (launch auto-check and manual check).
|
||||
function wireUpdaterEvents(): void {
|
||||
if (eventsWired) return;
|
||||
eventsWired = true;
|
||||
autoUpdater.on("checking-for-update", () => broadcast({ state: "checking" }));
|
||||
autoUpdater.on("update-available", (info) => broadcast({ state: "available", version: info?.version }));
|
||||
autoUpdater.on("update-not-available", () => broadcast({ state: "not-available" }));
|
||||
autoUpdater.on("download-progress", (p) =>
|
||||
broadcast({ state: "downloading", percent: Math.max(0, Math.min(100, Math.round(p?.percent ?? 0))) }),
|
||||
);
|
||||
autoUpdater.on("update-downloaded", (info) => broadcast({ state: "downloaded", version: info?.version }));
|
||||
autoUpdater.on("error", (err) => {
|
||||
// Never crash on update failure (offline, unsigned macOS, etc.).
|
||||
broadcast({ state: "error", message: err?.message ?? String(err) });
|
||||
});
|
||||
}
|
||||
|
||||
export function getUpdateStatus(): UpdateStatus {
|
||||
return lastStatus;
|
||||
}
|
||||
|
||||
// startAutoUpdates configures electron-updater from the user's ~/.ao settings.
|
||||
// It is a thin shell: all policy (channel, opt-in) comes from update-settings.
|
||||
// Caller guards on app.isPackaged.
|
||||
|
|
@ -22,18 +72,11 @@ export async function startAutoUpdates(stateDir: string): Promise<void> {
|
|||
const settings = await readUpdateSettings(stateDir);
|
||||
if (!settings.enabled) return;
|
||||
|
||||
const { owner, name } = repo();
|
||||
autoUpdater.setFeedURL({ provider: "github", owner, repo: name });
|
||||
autoUpdater.channel = settings.channel; // "latest" | "nightly"
|
||||
autoUpdater.allowDowngrade = true; // permits a nightly -> stable channel switch
|
||||
wireUpdaterEvents();
|
||||
configureFeed(settings.channel);
|
||||
autoUpdater.autoDownload = true;
|
||||
autoUpdater.autoInstallOnAppQuit = true;
|
||||
|
||||
autoUpdater.on("error", (err) => {
|
||||
// Never crash on update failure (offline, unsigned macOS, etc.).
|
||||
console.error("auto-update error:", err?.message ?? err);
|
||||
});
|
||||
|
||||
try {
|
||||
await autoUpdater.checkForUpdates();
|
||||
} catch (err) {
|
||||
|
|
@ -41,6 +84,50 @@ export async function startAutoUpdates(stateDir: string): Promise<void> {
|
|||
}
|
||||
}
|
||||
|
||||
// checkForUpdatesNow runs a manual update check regardless of the auto-update
|
||||
// opt-in, so a user who never enabled auto-updates can still pull the latest
|
||||
// build from Settings. It does NOT auto-download — the user clicks Update — and
|
||||
// reports progress via the broadcast status. Updates only work in the packaged,
|
||||
// signed app; in dev electron-updater has no feed, so surface that plainly.
|
||||
export async function checkForUpdatesNow(stateDir: string): Promise<void> {
|
||||
wireUpdaterEvents();
|
||||
if (!app.isPackaged) {
|
||||
broadcast({ state: "unsupported", message: "Updates are only available in the installed app." });
|
||||
return;
|
||||
}
|
||||
const settings = await readUpdateSettings(stateDir);
|
||||
configureFeed(settings.channel);
|
||||
autoUpdater.autoDownload = false;
|
||||
autoUpdater.autoInstallOnAppQuit = true;
|
||||
broadcast({ state: "checking" });
|
||||
try {
|
||||
await autoUpdater.checkForUpdates();
|
||||
} catch (err) {
|
||||
broadcast({ state: "error", message: (err as Error)?.message ?? "Update check failed" });
|
||||
}
|
||||
}
|
||||
|
||||
// downloadUpdateNow starts downloading the update found by checkForUpdatesNow.
|
||||
export async function downloadUpdateNow(): Promise<void> {
|
||||
wireUpdaterEvents();
|
||||
if (!app.isPackaged) {
|
||||
broadcast({ state: "unsupported", message: "Updates are only available in the installed app." });
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await autoUpdater.downloadUpdate();
|
||||
} catch (err) {
|
||||
broadcast({ state: "error", message: (err as Error)?.message ?? "Download failed" });
|
||||
}
|
||||
}
|
||||
|
||||
// quitAndInstallUpdate installs a downloaded update and relaunches. isSilent
|
||||
// false keeps the installer UI on Windows; isForceRunAfter relaunches the app.
|
||||
export function quitAndInstallUpdate(): void {
|
||||
if (!app.isPackaged) return;
|
||||
autoUpdater.quitAndInstall(false, true);
|
||||
}
|
||||
|
||||
// ensureUpdatePrefs prompts once (first run, before any settings file exists)
|
||||
// for auto-update opt-in + channel, with a nightly instability disclaimer.
|
||||
export async function ensureUpdatePrefs(stateDir: string): Promise<void> {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,25 @@ export interface UpdateSettings {
|
|||
nightlyAck: boolean;
|
||||
}
|
||||
|
||||
// Live state of a manual update check/download, streamed to the renderer so the
|
||||
// Global Settings "Check for updates" / "Update" buttons can reflect progress.
|
||||
export type UpdateState =
|
||||
| "idle"
|
||||
| "checking"
|
||||
| "available"
|
||||
| "not-available"
|
||||
| "downloading"
|
||||
| "downloaded"
|
||||
| "error"
|
||||
| "unsupported";
|
||||
|
||||
export interface UpdateStatus {
|
||||
state: UpdateState;
|
||||
version?: string;
|
||||
percent?: number;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
/** File holding the user's auto-update preferences under the ~/.ao state dir. */
|
||||
export const UPDATE_SETTINGS_FILE_NAME = "update-settings.json";
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import type { BrowserNavState, BrowserRect } from "./main/browser-view-host";
|
|||
import type { DaemonStatus } from "./shared/daemon-status";
|
||||
import type { TelemetryBootstrap } from "./shared/telemetry";
|
||||
import type { MigrationState } from "./main/app-state";
|
||||
import type { UpdateSettings } from "./main/update-settings";
|
||||
import type { UpdateSettings, UpdateStatus } from "./main/update-settings";
|
||||
|
||||
export type BrowserBoundsInput = {
|
||||
viewId: string;
|
||||
|
|
@ -79,6 +79,19 @@ const api = {
|
|||
get: () => ipcRenderer.invoke("updateSettings:get") as Promise<UpdateSettings>,
|
||||
set: (settings: UpdateSettings) => ipcRenderer.invoke("updateSettings:set", settings) as Promise<void>,
|
||||
},
|
||||
updates: {
|
||||
getStatus: () => ipcRenderer.invoke("updates:getStatus") as Promise<UpdateStatus>,
|
||||
check: () => ipcRenderer.invoke("updates:check") as Promise<void>,
|
||||
download: () => ipcRenderer.invoke("updates:download") as Promise<void>,
|
||||
install: () => ipcRenderer.invoke("updates:install") as Promise<void>,
|
||||
onStatus: (listener: (status: UpdateStatus) => void) => {
|
||||
const wrapped = (_event: Electron.IpcRendererEvent, status: UpdateStatus) => listener(status);
|
||||
ipcRenderer.on("updates:status", wrapped);
|
||||
return () => {
|
||||
ipcRenderer.off("updates:status", wrapped);
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
contextBridge.exposeInMainWorld("ao", api);
|
||||
|
|
|
|||
|
|
@ -1,16 +1,33 @@
|
|||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { render, screen, waitFor } from "@testing-library/react";
|
||||
import { act, render, screen, waitFor } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { GlobalSettingsForm } from "./GlobalSettingsForm";
|
||||
|
||||
const { getMock, postMock, getMigration, setMigration, getUpdate, setUpdate } = vi.hoisted(() => ({
|
||||
const {
|
||||
getMock,
|
||||
postMock,
|
||||
getMigration,
|
||||
setMigration,
|
||||
getUpdate,
|
||||
setUpdate,
|
||||
updGetStatus,
|
||||
updCheck,
|
||||
updDownload,
|
||||
updInstall,
|
||||
updOnStatus,
|
||||
} = vi.hoisted(() => ({
|
||||
getMock: vi.fn(),
|
||||
postMock: vi.fn(),
|
||||
getMigration: vi.fn(),
|
||||
setMigration: vi.fn(),
|
||||
getUpdate: vi.fn(),
|
||||
setUpdate: vi.fn(),
|
||||
updGetStatus: vi.fn(),
|
||||
updCheck: vi.fn(),
|
||||
updDownload: vi.fn(),
|
||||
updInstall: vi.fn(),
|
||||
updOnStatus: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("../lib/api-client", () => ({
|
||||
|
|
@ -22,6 +39,13 @@ vi.mock("../lib/bridge", () => ({
|
|||
aoBridge: {
|
||||
appState: { getMigration, setMigration },
|
||||
updateSettings: { get: getUpdate, set: setUpdate },
|
||||
updates: {
|
||||
getStatus: updGetStatus,
|
||||
check: updCheck,
|
||||
download: updDownload,
|
||||
install: updInstall,
|
||||
onStatus: updOnStatus,
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
|
|
@ -43,6 +67,11 @@ beforeEach(() => {
|
|||
setMigration.mockResolvedValue(undefined);
|
||||
getUpdate.mockResolvedValue({ enabled: true, channel: "latest", nightlyAck: false });
|
||||
setUpdate.mockResolvedValue(undefined);
|
||||
updGetStatus.mockResolvedValue({ state: "idle" });
|
||||
updCheck.mockResolvedValue(undefined);
|
||||
updDownload.mockResolvedValue(undefined);
|
||||
updInstall.mockResolvedValue(undefined);
|
||||
updOnStatus.mockReturnValue(() => undefined);
|
||||
});
|
||||
|
||||
describe("GlobalSettingsForm", () => {
|
||||
|
|
@ -100,6 +129,41 @@ describe("GlobalSettingsForm", () => {
|
|||
expect(screen.getByRole("button", { name: "Run migration" })).toBeDisabled();
|
||||
});
|
||||
|
||||
it("Check for updates triggers a manual check", async () => {
|
||||
renderForm();
|
||||
const btn = await screen.findByRole("button", { name: "Check for updates" });
|
||||
await userEvent.click(btn);
|
||||
expect(updCheck).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("offers an Update button when an update is available and downloads it", async () => {
|
||||
let emit: (s: { state: string; version?: string }) => void = () => undefined;
|
||||
updOnStatus.mockImplementation((cb: (s: unknown) => void) => {
|
||||
emit = cb as typeof emit;
|
||||
return () => undefined;
|
||||
});
|
||||
renderForm();
|
||||
await screen.findByRole("button", { name: "Check for updates" });
|
||||
act(() => emit({ state: "available", version: "1.2.3" }));
|
||||
const updateBtn = await screen.findByRole("button", { name: "Update to v1.2.3" });
|
||||
await userEvent.click(updateBtn);
|
||||
expect(updDownload).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("offers Restart & install once downloaded and installs it", async () => {
|
||||
let emit: (s: { state: string; version?: string }) => void = () => undefined;
|
||||
updOnStatus.mockImplementation((cb: (s: unknown) => void) => {
|
||||
emit = cb as typeof emit;
|
||||
return () => undefined;
|
||||
});
|
||||
renderForm();
|
||||
await screen.findByRole("button", { name: "Check for updates" });
|
||||
act(() => emit({ state: "downloaded", version: "1.2.3" }));
|
||||
const installBtn = await screen.findByRole("button", { name: /Restart & install/ });
|
||||
await userEvent.click(installBtn);
|
||||
expect(updInstall).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("a failed import surfaces the error and marks failed", async () => {
|
||||
postMock.mockResolvedValue({ data: undefined, error: { message: "disk full" } });
|
||||
renderForm();
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Loader2 } from "lucide-react";
|
||||
import { aoBridge } from "../lib/bridge";
|
||||
import type { UpdateChannel, UpdateSettings } from "../../main/update-settings";
|
||||
import type { UpdateChannel, UpdateSettings, UpdateStatus } from "../../main/update-settings";
|
||||
import { Button } from "./ui/button";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "./ui/card";
|
||||
import { Label } from "./ui/label";
|
||||
|
|
@ -104,11 +105,85 @@ export function UpdatesSection() {
|
|||
)}
|
||||
{savedAt && !save.isPending && !save.isError && <span className="text-[12px] text-success">Saved.</span>}
|
||||
</div>
|
||||
|
||||
<UpdateActions />
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
// UpdateActions is the on-demand update control: a Check for updates button plus
|
||||
// an Update button that downloads then installs. It works even when automatic
|
||||
// updates are off, so users who never opted in can still pull the latest build.
|
||||
function UpdateActions() {
|
||||
const [status, setStatus] = useState<UpdateStatus>({ state: "idle" });
|
||||
|
||||
useEffect(() => {
|
||||
let live = true;
|
||||
void aoBridge.updates.getStatus().then((s) => {
|
||||
if (live) setStatus(s);
|
||||
});
|
||||
const off = aoBridge.updates.onStatus(setStatus);
|
||||
return () => {
|
||||
live = false;
|
||||
off?.();
|
||||
};
|
||||
}, []);
|
||||
|
||||
const checking = status.state === "checking";
|
||||
const downloading = status.state === "downloading";
|
||||
const busy = checking || downloading;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2 border-t border-border pt-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<Button type="button" variant="outline" onClick={() => void aoBridge.updates.check()} disabled={busy}>
|
||||
{checking && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
||||
Check for updates
|
||||
</Button>
|
||||
|
||||
{status.state === "available" && (
|
||||
<Button type="button" variant="primary" onClick={() => void aoBridge.updates.download()}>
|
||||
Update to {status.version ? `v${status.version}` : "latest"}
|
||||
</Button>
|
||||
)}
|
||||
{status.state === "downloaded" && (
|
||||
<Button type="button" variant="primary" onClick={() => void aoBridge.updates.install()}>
|
||||
Restart & install
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<UpdateStatusLine status={status} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function UpdateStatusLine({ status }: { status: UpdateStatus }) {
|
||||
switch (status.state) {
|
||||
case "checking":
|
||||
return <span className="text-[12px] text-muted-foreground">Checking for updates…</span>;
|
||||
case "available":
|
||||
return (
|
||||
<span className="text-[12px] text-muted-foreground">
|
||||
Update available{status.version ? ` (v${status.version})` : ""}.
|
||||
</span>
|
||||
);
|
||||
case "downloading":
|
||||
return <span className="text-[12px] text-muted-foreground">Downloading… {status.percent ?? 0}%</span>;
|
||||
case "downloaded":
|
||||
return <span className="text-[12px] text-success">Downloaded. Restart to finish updating.</span>;
|
||||
case "not-available":
|
||||
return <span className="text-[12px] text-muted-foreground">You're on the latest version.</span>;
|
||||
case "unsupported":
|
||||
return <span className="text-[12px] text-passive">{status.message ?? "Updates need the installed app."}</span>;
|
||||
case "error":
|
||||
return <span className="text-[12px] text-error">{status.message ?? "Update failed."}</span>;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function EnabledSelect({ id, value, onChange }: { id: string; value: boolean; onChange: (value: boolean) => void }) {
|
||||
return (
|
||||
<Select value={value ? "on" : "off"} onValueChange={(v) => onChange(v === "on")}>
|
||||
|
|
|
|||
|
|
@ -100,4 +100,11 @@ export const aoBridge: AoBridge =
|
|||
get: async () => ({ enabled: false, channel: "latest", nightlyAck: false }),
|
||||
set: async () => undefined,
|
||||
},
|
||||
updates: {
|
||||
getStatus: async () => ({ state: "idle" }),
|
||||
check: async () => undefined,
|
||||
download: async () => undefined,
|
||||
install: async () => undefined,
|
||||
onStatus: () => () => undefined,
|
||||
},
|
||||
} satisfies AoBridge);
|
||||
|
|
|
|||
|
|
@ -8,204 +8,209 @@
|
|||
// You should NOT make any changes in this file as it will be overwritten.
|
||||
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
|
||||
|
||||
import { Route as rootRouteImport } from "./routes/__root";
|
||||
import { Route as ShellRouteImport } from "./routes/_shell";
|
||||
import { Route as ShellIndexRouteImport } from "./routes/_shell.index";
|
||||
import { Route as ShellSettingsRouteImport } from "./routes/_shell.settings";
|
||||
import { Route as ShellPrsRouteImport } from "./routes/_shell.prs";
|
||||
import { Route as ShellSessionsSessionIdRouteImport } from "./routes/_shell.sessions.$sessionId";
|
||||
import { Route as ShellProjectsProjectIdRouteImport } from "./routes/_shell.projects.$projectId";
|
||||
import { Route as ShellProjectsProjectIdSettingsRouteImport } from "./routes/_shell.projects.$projectId_.settings";
|
||||
import { Route as ShellProjectsProjectIdSessionsSessionIdRouteImport } from "./routes/_shell.projects.$projectId_.sessions.$sessionId";
|
||||
import { Route as rootRouteImport } from './routes/__root'
|
||||
import { Route as ShellRouteImport } from './routes/_shell'
|
||||
import { Route as ShellIndexRouteImport } from './routes/_shell.index'
|
||||
import { Route as ShellSettingsRouteImport } from './routes/_shell.settings'
|
||||
import { Route as ShellPrsRouteImport } from './routes/_shell.prs'
|
||||
import { Route as ShellSessionsSessionIdRouteImport } from './routes/_shell.sessions.$sessionId'
|
||||
import { Route as ShellProjectsProjectIdRouteImport } from './routes/_shell.projects.$projectId'
|
||||
import { Route as ShellProjectsProjectIdSettingsRouteImport } from './routes/_shell.projects.$projectId_.settings'
|
||||
import { Route as ShellProjectsProjectIdSessionsSessionIdRouteImport } from './routes/_shell.projects.$projectId_.sessions.$sessionId'
|
||||
|
||||
const ShellRoute = ShellRouteImport.update({
|
||||
id: "/_shell",
|
||||
getParentRoute: () => rootRouteImport,
|
||||
} as any);
|
||||
id: '/_shell',
|
||||
getParentRoute: () => rootRouteImport,
|
||||
} as any)
|
||||
const ShellIndexRoute = ShellIndexRouteImport.update({
|
||||
id: "/",
|
||||
path: "/",
|
||||
getParentRoute: () => ShellRoute,
|
||||
} as any);
|
||||
id: '/',
|
||||
path: '/',
|
||||
getParentRoute: () => ShellRoute,
|
||||
} as any)
|
||||
const ShellSettingsRoute = ShellSettingsRouteImport.update({
|
||||
id: "/settings",
|
||||
path: "/settings",
|
||||
getParentRoute: () => ShellRoute,
|
||||
} as any);
|
||||
id: '/settings',
|
||||
path: '/settings',
|
||||
getParentRoute: () => ShellRoute,
|
||||
} as any)
|
||||
const ShellPrsRoute = ShellPrsRouteImport.update({
|
||||
id: "/prs",
|
||||
path: "/prs",
|
||||
getParentRoute: () => ShellRoute,
|
||||
} as any);
|
||||
id: '/prs',
|
||||
path: '/prs',
|
||||
getParentRoute: () => ShellRoute,
|
||||
} as any)
|
||||
const ShellSessionsSessionIdRoute = ShellSessionsSessionIdRouteImport.update({
|
||||
id: "/sessions/$sessionId",
|
||||
path: "/sessions/$sessionId",
|
||||
getParentRoute: () => ShellRoute,
|
||||
} as any);
|
||||
id: '/sessions/$sessionId',
|
||||
path: '/sessions/$sessionId',
|
||||
getParentRoute: () => ShellRoute,
|
||||
} as any)
|
||||
const ShellProjectsProjectIdRoute = ShellProjectsProjectIdRouteImport.update({
|
||||
id: "/projects/$projectId",
|
||||
path: "/projects/$projectId",
|
||||
getParentRoute: () => ShellRoute,
|
||||
} as any);
|
||||
const ShellProjectsProjectIdSettingsRoute = ShellProjectsProjectIdSettingsRouteImport.update({
|
||||
id: "/projects/$projectId_/settings",
|
||||
path: "/projects/$projectId/settings",
|
||||
getParentRoute: () => ShellRoute,
|
||||
} as any);
|
||||
const ShellProjectsProjectIdSessionsSessionIdRoute = ShellProjectsProjectIdSessionsSessionIdRouteImport.update({
|
||||
id: "/projects/$projectId_/sessions/$sessionId",
|
||||
path: "/projects/$projectId/sessions/$sessionId",
|
||||
getParentRoute: () => ShellRoute,
|
||||
} as any);
|
||||
id: '/projects/$projectId',
|
||||
path: '/projects/$projectId',
|
||||
getParentRoute: () => ShellRoute,
|
||||
} as any)
|
||||
const ShellProjectsProjectIdSettingsRoute =
|
||||
ShellProjectsProjectIdSettingsRouteImport.update({
|
||||
id: '/projects/$projectId_/settings',
|
||||
path: '/projects/$projectId/settings',
|
||||
getParentRoute: () => ShellRoute,
|
||||
} as any)
|
||||
const ShellProjectsProjectIdSessionsSessionIdRoute =
|
||||
ShellProjectsProjectIdSessionsSessionIdRouteImport.update({
|
||||
id: '/projects/$projectId_/sessions/$sessionId',
|
||||
path: '/projects/$projectId/sessions/$sessionId',
|
||||
getParentRoute: () => ShellRoute,
|
||||
} as any)
|
||||
|
||||
export interface FileRoutesByFullPath {
|
||||
"/": typeof ShellIndexRoute;
|
||||
"/prs": typeof ShellPrsRoute;
|
||||
"/settings": typeof ShellSettingsRoute;
|
||||
"/projects/$projectId": typeof ShellProjectsProjectIdRoute;
|
||||
"/sessions/$sessionId": typeof ShellSessionsSessionIdRoute;
|
||||
"/projects/$projectId/settings": typeof ShellProjectsProjectIdSettingsRoute;
|
||||
"/projects/$projectId/sessions/$sessionId": typeof ShellProjectsProjectIdSessionsSessionIdRoute;
|
||||
'/': typeof ShellIndexRoute
|
||||
'/prs': typeof ShellPrsRoute
|
||||
'/settings': typeof ShellSettingsRoute
|
||||
'/projects/$projectId': typeof ShellProjectsProjectIdRoute
|
||||
'/sessions/$sessionId': typeof ShellSessionsSessionIdRoute
|
||||
'/projects/$projectId/settings': typeof ShellProjectsProjectIdSettingsRoute
|
||||
'/projects/$projectId/sessions/$sessionId': typeof ShellProjectsProjectIdSessionsSessionIdRoute
|
||||
}
|
||||
export interface FileRoutesByTo {
|
||||
"/prs": typeof ShellPrsRoute;
|
||||
"/settings": typeof ShellSettingsRoute;
|
||||
"/": typeof ShellIndexRoute;
|
||||
"/projects/$projectId": typeof ShellProjectsProjectIdRoute;
|
||||
"/sessions/$sessionId": typeof ShellSessionsSessionIdRoute;
|
||||
"/projects/$projectId/settings": typeof ShellProjectsProjectIdSettingsRoute;
|
||||
"/projects/$projectId/sessions/$sessionId": typeof ShellProjectsProjectIdSessionsSessionIdRoute;
|
||||
'/prs': typeof ShellPrsRoute
|
||||
'/settings': typeof ShellSettingsRoute
|
||||
'/': typeof ShellIndexRoute
|
||||
'/projects/$projectId': typeof ShellProjectsProjectIdRoute
|
||||
'/sessions/$sessionId': typeof ShellSessionsSessionIdRoute
|
||||
'/projects/$projectId/settings': typeof ShellProjectsProjectIdSettingsRoute
|
||||
'/projects/$projectId/sessions/$sessionId': typeof ShellProjectsProjectIdSessionsSessionIdRoute
|
||||
}
|
||||
export interface FileRoutesById {
|
||||
__root__: typeof rootRouteImport;
|
||||
"/_shell": typeof ShellRouteWithChildren;
|
||||
"/_shell/prs": typeof ShellPrsRoute;
|
||||
"/_shell/settings": typeof ShellSettingsRoute;
|
||||
"/_shell/": typeof ShellIndexRoute;
|
||||
"/_shell/projects/$projectId": typeof ShellProjectsProjectIdRoute;
|
||||
"/_shell/sessions/$sessionId": typeof ShellSessionsSessionIdRoute;
|
||||
"/_shell/projects/$projectId_/settings": typeof ShellProjectsProjectIdSettingsRoute;
|
||||
"/_shell/projects/$projectId_/sessions/$sessionId": typeof ShellProjectsProjectIdSessionsSessionIdRoute;
|
||||
__root__: typeof rootRouteImport
|
||||
'/_shell': typeof ShellRouteWithChildren
|
||||
'/_shell/prs': typeof ShellPrsRoute
|
||||
'/_shell/settings': typeof ShellSettingsRoute
|
||||
'/_shell/': typeof ShellIndexRoute
|
||||
'/_shell/projects/$projectId': typeof ShellProjectsProjectIdRoute
|
||||
'/_shell/sessions/$sessionId': typeof ShellSessionsSessionIdRoute
|
||||
'/_shell/projects/$projectId_/settings': typeof ShellProjectsProjectIdSettingsRoute
|
||||
'/_shell/projects/$projectId_/sessions/$sessionId': typeof ShellProjectsProjectIdSessionsSessionIdRoute
|
||||
}
|
||||
export interface FileRouteTypes {
|
||||
fileRoutesByFullPath: FileRoutesByFullPath;
|
||||
fullPaths:
|
||||
| "/"
|
||||
| "/prs"
|
||||
| "/settings"
|
||||
| "/projects/$projectId"
|
||||
| "/sessions/$sessionId"
|
||||
| "/projects/$projectId/settings"
|
||||
| "/projects/$projectId/sessions/$sessionId";
|
||||
fileRoutesByTo: FileRoutesByTo;
|
||||
to:
|
||||
| "/prs"
|
||||
| "/settings"
|
||||
| "/"
|
||||
| "/projects/$projectId"
|
||||
| "/sessions/$sessionId"
|
||||
| "/projects/$projectId/settings"
|
||||
| "/projects/$projectId/sessions/$sessionId";
|
||||
id:
|
||||
| "__root__"
|
||||
| "/_shell"
|
||||
| "/_shell/prs"
|
||||
| "/_shell/settings"
|
||||
| "/_shell/"
|
||||
| "/_shell/projects/$projectId"
|
||||
| "/_shell/sessions/$sessionId"
|
||||
| "/_shell/projects/$projectId_/settings"
|
||||
| "/_shell/projects/$projectId_/sessions/$sessionId";
|
||||
fileRoutesById: FileRoutesById;
|
||||
fileRoutesByFullPath: FileRoutesByFullPath
|
||||
fullPaths:
|
||||
| '/'
|
||||
| '/prs'
|
||||
| '/settings'
|
||||
| '/projects/$projectId'
|
||||
| '/sessions/$sessionId'
|
||||
| '/projects/$projectId/settings'
|
||||
| '/projects/$projectId/sessions/$sessionId'
|
||||
fileRoutesByTo: FileRoutesByTo
|
||||
to:
|
||||
| '/prs'
|
||||
| '/settings'
|
||||
| '/'
|
||||
| '/projects/$projectId'
|
||||
| '/sessions/$sessionId'
|
||||
| '/projects/$projectId/settings'
|
||||
| '/projects/$projectId/sessions/$sessionId'
|
||||
id:
|
||||
| '__root__'
|
||||
| '/_shell'
|
||||
| '/_shell/prs'
|
||||
| '/_shell/settings'
|
||||
| '/_shell/'
|
||||
| '/_shell/projects/$projectId'
|
||||
| '/_shell/sessions/$sessionId'
|
||||
| '/_shell/projects/$projectId_/settings'
|
||||
| '/_shell/projects/$projectId_/sessions/$sessionId'
|
||||
fileRoutesById: FileRoutesById
|
||||
}
|
||||
export interface RootRouteChildren {
|
||||
ShellRoute: typeof ShellRouteWithChildren;
|
||||
ShellRoute: typeof ShellRouteWithChildren
|
||||
}
|
||||
|
||||
declare module "@tanstack/react-router" {
|
||||
interface FileRoutesByPath {
|
||||
"/_shell": {
|
||||
id: "/_shell";
|
||||
path: "";
|
||||
fullPath: "/";
|
||||
preLoaderRoute: typeof ShellRouteImport;
|
||||
parentRoute: typeof rootRouteImport;
|
||||
};
|
||||
"/_shell/": {
|
||||
id: "/_shell/";
|
||||
path: "/";
|
||||
fullPath: "/";
|
||||
preLoaderRoute: typeof ShellIndexRouteImport;
|
||||
parentRoute: typeof ShellRoute;
|
||||
};
|
||||
"/_shell/settings": {
|
||||
id: "/_shell/settings";
|
||||
path: "/settings";
|
||||
fullPath: "/settings";
|
||||
preLoaderRoute: typeof ShellSettingsRouteImport;
|
||||
parentRoute: typeof ShellRoute;
|
||||
};
|
||||
"/_shell/prs": {
|
||||
id: "/_shell/prs";
|
||||
path: "/prs";
|
||||
fullPath: "/prs";
|
||||
preLoaderRoute: typeof ShellPrsRouteImport;
|
||||
parentRoute: typeof ShellRoute;
|
||||
};
|
||||
"/_shell/sessions/$sessionId": {
|
||||
id: "/_shell/sessions/$sessionId";
|
||||
path: "/sessions/$sessionId";
|
||||
fullPath: "/sessions/$sessionId";
|
||||
preLoaderRoute: typeof ShellSessionsSessionIdRouteImport;
|
||||
parentRoute: typeof ShellRoute;
|
||||
};
|
||||
"/_shell/projects/$projectId": {
|
||||
id: "/_shell/projects/$projectId";
|
||||
path: "/projects/$projectId";
|
||||
fullPath: "/projects/$projectId";
|
||||
preLoaderRoute: typeof ShellProjectsProjectIdRouteImport;
|
||||
parentRoute: typeof ShellRoute;
|
||||
};
|
||||
"/_shell/projects/$projectId_/settings": {
|
||||
id: "/_shell/projects/$projectId_/settings";
|
||||
path: "/projects/$projectId/settings";
|
||||
fullPath: "/projects/$projectId/settings";
|
||||
preLoaderRoute: typeof ShellProjectsProjectIdSettingsRouteImport;
|
||||
parentRoute: typeof ShellRoute;
|
||||
};
|
||||
"/_shell/projects/$projectId_/sessions/$sessionId": {
|
||||
id: "/_shell/projects/$projectId_/sessions/$sessionId";
|
||||
path: "/projects/$projectId/sessions/$sessionId";
|
||||
fullPath: "/projects/$projectId/sessions/$sessionId";
|
||||
preLoaderRoute: typeof ShellProjectsProjectIdSessionsSessionIdRouteImport;
|
||||
parentRoute: typeof ShellRoute;
|
||||
};
|
||||
}
|
||||
declare module '@tanstack/react-router' {
|
||||
interface FileRoutesByPath {
|
||||
'/_shell': {
|
||||
id: '/_shell'
|
||||
path: ''
|
||||
fullPath: '/'
|
||||
preLoaderRoute: typeof ShellRouteImport
|
||||
parentRoute: typeof rootRouteImport
|
||||
}
|
||||
'/_shell/': {
|
||||
id: '/_shell/'
|
||||
path: '/'
|
||||
fullPath: '/'
|
||||
preLoaderRoute: typeof ShellIndexRouteImport
|
||||
parentRoute: typeof ShellRoute
|
||||
}
|
||||
'/_shell/settings': {
|
||||
id: '/_shell/settings'
|
||||
path: '/settings'
|
||||
fullPath: '/settings'
|
||||
preLoaderRoute: typeof ShellSettingsRouteImport
|
||||
parentRoute: typeof ShellRoute
|
||||
}
|
||||
'/_shell/prs': {
|
||||
id: '/_shell/prs'
|
||||
path: '/prs'
|
||||
fullPath: '/prs'
|
||||
preLoaderRoute: typeof ShellPrsRouteImport
|
||||
parentRoute: typeof ShellRoute
|
||||
}
|
||||
'/_shell/sessions/$sessionId': {
|
||||
id: '/_shell/sessions/$sessionId'
|
||||
path: '/sessions/$sessionId'
|
||||
fullPath: '/sessions/$sessionId'
|
||||
preLoaderRoute: typeof ShellSessionsSessionIdRouteImport
|
||||
parentRoute: typeof ShellRoute
|
||||
}
|
||||
'/_shell/projects/$projectId': {
|
||||
id: '/_shell/projects/$projectId'
|
||||
path: '/projects/$projectId'
|
||||
fullPath: '/projects/$projectId'
|
||||
preLoaderRoute: typeof ShellProjectsProjectIdRouteImport
|
||||
parentRoute: typeof ShellRoute
|
||||
}
|
||||
'/_shell/projects/$projectId_/settings': {
|
||||
id: '/_shell/projects/$projectId_/settings'
|
||||
path: '/projects/$projectId/settings'
|
||||
fullPath: '/projects/$projectId/settings'
|
||||
preLoaderRoute: typeof ShellProjectsProjectIdSettingsRouteImport
|
||||
parentRoute: typeof ShellRoute
|
||||
}
|
||||
'/_shell/projects/$projectId_/sessions/$sessionId': {
|
||||
id: '/_shell/projects/$projectId_/sessions/$sessionId'
|
||||
path: '/projects/$projectId/sessions/$sessionId'
|
||||
fullPath: '/projects/$projectId/sessions/$sessionId'
|
||||
preLoaderRoute: typeof ShellProjectsProjectIdSessionsSessionIdRouteImport
|
||||
parentRoute: typeof ShellRoute
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface ShellRouteChildren {
|
||||
ShellPrsRoute: typeof ShellPrsRoute;
|
||||
ShellSettingsRoute: typeof ShellSettingsRoute;
|
||||
ShellIndexRoute: typeof ShellIndexRoute;
|
||||
ShellProjectsProjectIdRoute: typeof ShellProjectsProjectIdRoute;
|
||||
ShellSessionsSessionIdRoute: typeof ShellSessionsSessionIdRoute;
|
||||
ShellProjectsProjectIdSettingsRoute: typeof ShellProjectsProjectIdSettingsRoute;
|
||||
ShellProjectsProjectIdSessionsSessionIdRoute: typeof ShellProjectsProjectIdSessionsSessionIdRoute;
|
||||
ShellPrsRoute: typeof ShellPrsRoute
|
||||
ShellSettingsRoute: typeof ShellSettingsRoute
|
||||
ShellIndexRoute: typeof ShellIndexRoute
|
||||
ShellProjectsProjectIdRoute: typeof ShellProjectsProjectIdRoute
|
||||
ShellSessionsSessionIdRoute: typeof ShellSessionsSessionIdRoute
|
||||
ShellProjectsProjectIdSettingsRoute: typeof ShellProjectsProjectIdSettingsRoute
|
||||
ShellProjectsProjectIdSessionsSessionIdRoute: typeof ShellProjectsProjectIdSessionsSessionIdRoute
|
||||
}
|
||||
|
||||
const ShellRouteChildren: ShellRouteChildren = {
|
||||
ShellPrsRoute: ShellPrsRoute,
|
||||
ShellSettingsRoute: ShellSettingsRoute,
|
||||
ShellIndexRoute: ShellIndexRoute,
|
||||
ShellProjectsProjectIdRoute: ShellProjectsProjectIdRoute,
|
||||
ShellSessionsSessionIdRoute: ShellSessionsSessionIdRoute,
|
||||
ShellProjectsProjectIdSettingsRoute: ShellProjectsProjectIdSettingsRoute,
|
||||
ShellProjectsProjectIdSessionsSessionIdRoute: ShellProjectsProjectIdSessionsSessionIdRoute,
|
||||
};
|
||||
ShellPrsRoute: ShellPrsRoute,
|
||||
ShellSettingsRoute: ShellSettingsRoute,
|
||||
ShellIndexRoute: ShellIndexRoute,
|
||||
ShellProjectsProjectIdRoute: ShellProjectsProjectIdRoute,
|
||||
ShellSessionsSessionIdRoute: ShellSessionsSessionIdRoute,
|
||||
ShellProjectsProjectIdSettingsRoute: ShellProjectsProjectIdSettingsRoute,
|
||||
ShellProjectsProjectIdSessionsSessionIdRoute:
|
||||
ShellProjectsProjectIdSessionsSessionIdRoute,
|
||||
}
|
||||
|
||||
const ShellRouteWithChildren = ShellRoute._addFileChildren(ShellRouteChildren);
|
||||
const ShellRouteWithChildren = ShellRoute._addFileChildren(ShellRouteChildren)
|
||||
|
||||
const rootRouteChildren: RootRouteChildren = {
|
||||
ShellRoute: ShellRouteWithChildren,
|
||||
};
|
||||
export const routeTree = rootRouteImport._addFileChildren(rootRouteChildren)._addFileTypes<FileRouteTypes>();
|
||||
ShellRoute: ShellRouteWithChildren,
|
||||
}
|
||||
export const routeTree = rootRouteImport
|
||||
._addFileChildren(rootRouteChildren)
|
||||
._addFileTypes<FileRouteTypes>()
|
||||
|
|
|
|||
|
|
@ -145,5 +145,12 @@ if (typeof window !== "undefined") {
|
|||
get: async () => ({ enabled: false, channel: "latest", nightlyAck: false }),
|
||||
set: async () => undefined,
|
||||
},
|
||||
updates: {
|
||||
getStatus: async () => ({ state: "idle" }),
|
||||
check: async () => undefined,
|
||||
download: async () => undefined,
|
||||
install: async () => undefined,
|
||||
onStatus: () => () => undefined,
|
||||
},
|
||||
};
|
||||
} // end if (typeof window !== "undefined")
|
||||
|
|
|
|||
Loading…
Reference in New Issue