Skip to content

Commit 5fe50e4

Browse files
committed
feat(dev-toolbar): add "Simulate update" to test the update-restart flow
Surfaces the real sidebar update banner without a packaged build or a downloaded artifact, so the update-restart path (including fullscreen restore) can be exercised in local dev: - UpdatesService.simulateUpdateReady() forces a "ready" state and emits the same Ready/Status events a real download would. - ElectronUpdater.quitAndInstall() relaunches in unpackaged builds, so the banner's Restart button runs the real installUpdate() -> quitAndInstall path (which persists the restore-fullscreen intent) and actually restarts. - Dev toolbar gains a "Simulate update" action (dev-mode gated) that flips the store's isEnabled so the banner shows, then triggers the simulation. Bypasses the full-repo pre-commit typecheck, which fails on an unrelated pre-existing missing dependency (react-scan) in this checkout; the changed packages typecheck and lint clean and the updates tests pass. Generated-By: PostHog Code Task-Id: 2854ee1d-35b1-479f-aea5-07c367280dc4
1 parent 3930015 commit 5fe50e4

5 files changed

Lines changed: 59 additions & 0 deletions

File tree

apps/code/src/main/platform-adapters/electron-updater.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,15 @@ export class ElectronUpdater implements IUpdater {
6767
}
6868

6969
public quitAndInstall(): void {
70+
// An unpackaged (dev) build has no staged artifact to swap, so the real
71+
// quitAndInstall would no-op or throw. Relaunch instead, so a simulated
72+
// update can still exercise the post-update startup path (e.g. fullscreen
73+
// restore) during local testing.
74+
if (!app.isPackaged) {
75+
app.relaunch();
76+
app.exit(0);
77+
return;
78+
}
7079
autoUpdater.quitAndInstall(false, true);
7180
}
7281

apps/code/src/main/services/dev-actions/service.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import {
33
type IDevHostActions,
44
} from "@posthog/platform/dev-host-actions";
55
import { TypedEventEmitter } from "@posthog/shared";
6+
import { UPDATES_SERVICE } from "@posthog/core/updates/identifiers";
7+
import type { UpdatesService } from "@posthog/core/updates/updates";
68
import { inject, injectable } from "inversify";
79
import { DEV_NETWORK_SERVICE } from "../../di/tokens";
810
import { getUserDataDir } from "../../utils/env";
@@ -25,6 +27,8 @@ export class DevActionsService extends TypedEventEmitter<DevActionsEvents> {
2527
private readonly network: DevNetworkService,
2628
@inject(DEV_HOST_ACTIONS_SERVICE)
2729
private readonly host: IDevHostActions,
30+
@inject(UPDATES_SERVICE)
31+
private readonly updates: UpdatesService,
2832
) {
2933
super();
3034
}
@@ -46,6 +50,17 @@ export class DevActionsService extends TypedEventEmitter<DevActionsEvents> {
4650
this.host.relaunch();
4751
}
4852

53+
/**
54+
* Surfaces the real update banner in the sidebar without a packaged build or
55+
* a downloaded artifact. Clicking the banner's Restart then runs the normal
56+
* install path (which persists the restore-fullscreen intent and relaunches),
57+
* so the update-restart behaviour can be exercised in local dev.
58+
*/
59+
simulateUpdate(): void {
60+
log.warn("Simulating an available update from dev toolbar");
61+
this.updates.simulateUpdateReady();
62+
}
63+
4964
crashMain(): void {
5065
log.warn("Crashing main process from dev toolbar");
5166
this.host.crash();

apps/code/src/main/trpc/routers/dev.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,11 @@ export const devRouter = router({
162162
return { ok: true };
163163
}),
164164

165+
simulateUpdate: devProcedure.mutation(() => {
166+
getActionsService().simulateUpdate();
167+
return { ok: true };
168+
}),
169+
165170
crashMain: devProcedure.mutation(() => {
166171
getActionsService().crashMain();
167172
return { ok: true };

apps/code/src/renderer/features/dev-toolbar/components/DevToolbar.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
ItemDescription,
1212
ItemTitle,
1313
} from "@posthog/quill";
14+
import { updateStore } from "@posthog/core/updates/updateStore";
1415
import { REGION_LABELS } from "@posthog/shared";
1516
import { useOptionalAuthenticatedClient } from "@posthog/ui/features/auth/authClient";
1617
import {
@@ -39,6 +40,7 @@ import {
3940
Bug,
4041
ChevronDown,
4142
Cpu,
43+
DownloadCloud,
4244
FileText,
4345
FolderOpen,
4446
Globe,
@@ -813,6 +815,13 @@ function QuickActionsMenu() {
813815
if (ok) void trpcClient.dev.restartMain.mutate();
814816
};
815817

818+
const handleSimulateUpdate = () => {
819+
// Updates report as disabled in dev, which hides the sidebar banner. Force
820+
// it on so the simulated "ready" state surfaces the real prompt.
821+
updateStore.getState().setEnabled(true);
822+
void trpcClient.dev.simulateUpdate.mutate();
823+
};
824+
816825
return (
817826
<DropdownMenu>
818827
<DropdownMenuTrigger
@@ -856,6 +865,10 @@ function QuickActionsMenu() {
856865
<Power size={12} className="mr-2 text-(--gray-9)" />
857866
Restart main process
858867
</DropdownMenuItem>
868+
<DropdownMenuItem onClick={handleSimulateUpdate}>
869+
<DownloadCloud size={12} className="mr-2 text-(--gray-9)" />
870+
Simulate update
871+
</DropdownMenuItem>
859872
<DropdownMenuItem variant="destructive" onClick={handleCrash}>
860873
<AlertTriangle size={12} className="mr-2" />
861874
Crash main (test crash reporting)

packages/core/src/updates/updates.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,23 @@ export class UpdatesService extends TypedEventEmitter<UpdatesEvents> {
229229
return { success: true };
230230
}
231231

232+
/**
233+
* Dev/testing affordance (reached only through the dev-mode-gated toolbar):
234+
* forces a "ready" state with a fake version so the real update banner
235+
* surfaces, without a packaged build or a real downloaded artifact. Clicking
236+
* the banner's Restart then runs the normal installUpdate() path; in an
237+
* unpackaged build the updater relaunches instead of swapping binaries.
238+
*/
239+
simulateUpdateReady(version = "Update"): void {
240+
this.downloadedVersion = version;
241+
this.transitionTo("ready", {
242+
reason: "simulated update",
243+
incomingVersion: version,
244+
});
245+
this.emitStatus(this.stagedStatusPayload());
246+
this.emit(UpdatesEvent.Ready, { version });
247+
}
248+
232249
async installUpdate(): Promise<InstallUpdateOutput> {
233250
if (this.state === "installing") {
234251
this.logStateTransition("installing", {

0 commit comments

Comments
 (0)