diff --git a/apps/dashboard/package.json b/apps/dashboard/package.json
index 7c87c0fae..3f95db21c 100644
--- a/apps/dashboard/package.json
+++ b/apps/dashboard/package.json
@@ -45,6 +45,7 @@
"@types/node": "^22.13.0",
"@types/react": "^19.1.0",
"@types/react-dom": "^19.1.0",
+ "happy-dom": "^20.11.2",
"postcss": "^8.5.8",
"tailwindcss": "^4.2.1",
"typescript": "^5.9.3",
diff --git a/apps/dashboard/src/app/(dashboard)/settings/_components/GitHubConnection.tsx b/apps/dashboard/src/app/(dashboard)/settings/_components/GitHubConnection.tsx
index 341e80a78..b541859c7 100644
--- a/apps/dashboard/src/app/(dashboard)/settings/_components/GitHubConnection.tsx
+++ b/apps/dashboard/src/app/(dashboard)/settings/_components/GitHubConnection.tsx
@@ -130,6 +130,14 @@ export function GitHubConnection() {
return () => window.removeEventListener(GITHUB_SOURCES_CHANGED_EVENT, refreshForSourceChange);
}, [loadStatus]);
+ // The provider finishes device/token sign-in asynchronously. This card owns
+ // a separate status snapshot, so refresh it when the pending action finishes.
+ const previousActionRef = useRef(cliAction);
+ useEffect(() => {
+ if (previousActionRef.current && !cliAction) void loadStatus(true);
+ previousActionRef.current = cliAction;
+ }, [cliAction, loadStatus]);
+
// Connect/install opens a separate window (OAuth popup or the GitHub App
// install tab). The connect call returns as soon as that window opens, so
// the immediate loadStatus below is stale. Arm this flag on click and
@@ -285,12 +293,7 @@ export function GitHubConnection() {
iconBg="bg-foreground/5"
iconColor="text-foreground"
>
- {loading ? (
-
void loadStatus(true)}
isDesktop={isDesktop}
/>
+ ) : loading ? (
+
+
+ {t.settings.github.checkingConnection}
+
) : anyConnected ? (
{/* The identity that is actually authorizing clones, first. */}
diff --git a/apps/dashboard/src/app/(dashboard)/settings/_components/github-device-signin.test.tsx b/apps/dashboard/src/app/(dashboard)/settings/_components/github-device-signin.test.tsx
new file mode 100644
index 000000000..35f824a4e
--- /dev/null
+++ b/apps/dashboard/src/app/(dashboard)/settings/_components/github-device-signin.test.tsx
@@ -0,0 +1,174 @@
+// @vitest-environment happy-dom
+
+import { act } from "react";
+import { createRoot, type Root } from "react-dom/client";
+import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
+import { GitHubProvider, type GitHubConnectionState } from "@/context/GitHubContext";
+import { GitHubConnection } from "./GitHubConnection";
+
+const api = vi.hoisted(() => ({
+ connect: vi.fn(),
+ pollConnect: vi.fn(),
+ getUserHome: vi.fn(),
+ getStatusDeduped: vi.fn(),
+ invalidateStatus: vi.fn(),
+ showToast: vi.fn(),
+}));
+
+vi.mock("@/lib/api", () => ({
+ githubApi: api,
+ settingsApi: { get: async () => ({}) },
+ getApiErrorMessage: (error: Error) => error.message,
+ GITHUB_SOURCES_CHANGED_EVENT: "github-sources-changed",
+}));
+vi.mock("@/context/ToastContext", () => ({ useToast: () => ({ showToast: api.showToast }) }));
+vi.mock("@/context/CloudContext", () => ({ useCloud: () => ({ connected: false }) }));
+vi.mock("@/context/ModalContext", () => ({ useModal: () => ({}) }));
+vi.mock("@/context/PlatformContext", () => ({
+ usePlatform: () => ({ selfHosted: true, deployMode: "docker" }),
+}));
+vi.mock("next/navigation", () => ({ useRouter: () => ({ push: vi.fn() }) }));
+
+const disconnected: GitHubConnectionState = {
+ primary: null,
+ sources: {
+ openshipApp: { connected: false },
+ ghCli: { available: false, problem: "rejected", method: "device" },
+ },
+};
+const connected: GitHubConnectionState = {
+ primary: "gh-cli",
+ sources: {
+ openshipApp: { connected: false },
+ ghCli: { available: true, login: "new-account", method: "device" },
+ },
+};
+const deviceResponse = {
+ connected: false,
+ flow: "device_code",
+ userCode: "ABCD-1234",
+ verificationUri: "https://github.com/login/device",
+ expiresIn: 899,
+ interval: 5,
+};
+
+let container: HTMLDivElement;
+let root: Root;
+
+beforeEach(() => {
+ vi.useFakeTimers();
+ vi.clearAllMocks();
+ vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
+ api.connect.mockResolvedValue(deviceResponse);
+ api.pollConnect.mockResolvedValue({ status: "pending" });
+ api.getStatusDeduped.mockResolvedValue({ state: disconnected });
+ api.getUserHome.mockResolvedValue({ state: disconnected });
+ container = document.createElement("div");
+ document.body.append(container);
+ root = createRoot(container);
+});
+
+afterEach(async () => {
+ await act(async () => root.unmount());
+ container.remove();
+ vi.useRealTimers();
+ vi.unstubAllGlobals();
+});
+
+async function render(initialState = disconnected) {
+ await act(async () => {
+ root.render(
+
+
+ ,
+ );
+ });
+}
+
+async function click(label: string) {
+ const button = [...container.querySelectorAll("button")].find((el) =>
+ el.textContent?.includes(label),
+ );
+ expect(button, `button: ${label}`).toBeDefined();
+ await act(async () => button!.click());
+}
+
+function expectDeviceInstructions() {
+ expect(container.textContent).toContain(deviceResponse.userCode);
+ expect(container.querySelector('a[href="https://github.com/login/device"]')).not.toBeNull();
+}
+
+describe("Settings GitHub device sign-in (#851)", () => {
+ it("shows the returned code, copies it, and updates the card after polling completes", async () => {
+ const copy = vi.spyOn(navigator.clipboard, "writeText").mockResolvedValue();
+ await render();
+ await click("Sign in with GitHub");
+ expectDeviceInstructions();
+ await click(deviceResponse.userCode);
+ expect(copy).toHaveBeenCalledWith(deviceResponse.userCode);
+
+ api.pollConnect.mockResolvedValue({ status: "complete" });
+ api.getStatusDeduped.mockResolvedValue({ state: connected });
+ api.getUserHome.mockResolvedValue({ state: connected });
+ await act(async () => vi.advanceTimersByTimeAsync(5000));
+
+ expect(container.textContent).not.toContain(deviceResponse.userCode);
+ expect(container.textContent).toContain("@new-account");
+ expect(container.textContent).not.toContain("Sign in with GitHub");
+ api.pollConnect.mockClear();
+ await act(async () => vi.advanceTimersByTimeAsync(15000));
+ expect(api.pollConnect).not.toHaveBeenCalled();
+ copy.mockRestore();
+ });
+
+ it("keeps device instructions when switching from an already connected App", async () => {
+ const appState: GitHubConnectionState = {
+ ...disconnected,
+ primary: "openship-app",
+ sources: { ...disconnected.sources, openshipApp: { connected: true, login: "app-user" } },
+ };
+ api.getStatusDeduped.mockResolvedValue({ state: appState });
+ api.getUserHome.mockResolvedValue({ state: appState });
+ await render(appState);
+ await click("Change method");
+ await click("Sign in with GitHub");
+ expectDeviceInstructions();
+ await act(async () => vi.advanceTimersByTimeAsync(5000));
+ expectDeviceInstructions();
+ });
+
+ it("does not let stale provider connectivity dismiss a new device grant", async () => {
+ // The library provider can still have its initial verified identity while
+ // the Settings card's fresh probe reports that credential as rejected.
+ await render(connected);
+ await click("Sign in with GitHub");
+ expectDeviceInstructions();
+ await act(async () => vi.advanceTimersByTimeAsync(5000));
+ expectDeviceInstructions();
+ });
+
+ it("keeps the code visible while the card refreshes its status", async () => {
+ await render();
+ let resolveStatus!: (value: { state: GitHubConnectionState }) => void;
+ api.getStatusDeduped.mockImplementationOnce(
+ () =>
+ new Promise((resolve) => {
+ resolveStatus = resolve;
+ }),
+ );
+ await click("Sign in with GitHub");
+ expectDeviceInstructions();
+ await act(async () => resolveStatus({ state: disconnected }));
+ expectDeviceInstructions();
+ });
+
+ it("returns to sign-in and surfaces the error when the device grant expires", async () => {
+ await render();
+ await click("Sign in with GitHub");
+ api.pollConnect.mockResolvedValue({ status: "error", message: "The device code expired" });
+ await act(async () => vi.advanceTimersByTimeAsync(5000));
+ expect(container.textContent).not.toContain(deviceResponse.userCode);
+ expect(container.textContent).toContain("Sign in with GitHub");
+ expect(api.showToast).toHaveBeenCalledWith("The device code expired", "error", "GitHub");
+ });
+});
diff --git a/apps/dashboard/src/context/GitHubContext.tsx b/apps/dashboard/src/context/GitHubContext.tsx
index 88a77d713..3b4f608fb 100644
--- a/apps/dashboard/src/context/GitHubContext.tsx
+++ b/apps/dashboard/src/context/GitHubContext.tsx
@@ -257,7 +257,6 @@ export function GitHubProvider({ children, initialData }: GitHubProviderProps) {
if (res?.capabilities) setCapabilities(res.capabilities as GitHubCapabilities);
if (nextState.primary !== null) {
- setCliAction(null);
setAccounts(res.accounts ?? []);
const primaryLogin =
nextState.sources.openshipApp.login ?? nextState.sources.ghCli.login ?? "";
@@ -494,12 +493,12 @@ export function GitHubProvider({ children, initialData }: GitHubProviderProps) {
}, [cliAction, refresh, showToast]);
/* ── Auto-detect a completed login ──────────────────────────── */
- // Any pending CLI action (the device flow OR a `gh auth login` the operator ran
- // on the instance) clears the moment the connection lands, so the UI never gets
- // stuck showing a code/command after success.
+ // Only a terminal login completes through a status probe. A device grant has
+ // its own authoritative poll above; a previously connected App or stale CLI
+ // identity must not dismiss the new code before the operator authorizes it.
useEffect(() => {
- if (connected && cliAction) setCliAction(null);
- }, [connected, cliAction]);
+ if (cliAction?.type === "terminal" && state.sources.ghCli.available) setCliAction(null);
+ }, [state.sources.ghCli.available, cliAction]);
// Terminal (`gh auth login`) has no device code to poll — refresh the status
// periodically so the UI flips to connected as soon as the operator finishes,
diff --git a/bun.lock b/bun.lock
index 1099b491b..68f604e1b 100644
--- a/bun.lock
+++ b/bun.lock
@@ -108,6 +108,7 @@
"@types/node": "^22.13.0",
"@types/react": "^19.1.0",
"@types/react-dom": "^19.1.0",
+ "happy-dom": "^20.11.2",
"postcss": "^8.5.8",
"tailwindcss": "^4.2.1",
"typescript": "^5.9.3",
@@ -1073,10 +1074,14 @@
"@types/webidl-conversions": ["@types/webidl-conversions@7.0.3", "", {}, "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA=="],
+ "@types/whatwg-mimetype": ["@types/whatwg-mimetype@3.0.2", "", {}, "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA=="],
+
"@types/whatwg-url": ["@types/whatwg-url@13.0.0", "", { "dependencies": { "@types/webidl-conversions": "*" } }, "sha512-N8WXpbE6Wgri7KUSvrmQcqrMllKZ9uxkYWMt+mCSGwNc0Hsw9VQTW7ApqI4XNrx6/SaM2QQJCzMPDEXE058s+Q=="],
"@types/wrap-ansi": ["@types/wrap-ansi@3.0.0", "", {}, "sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g=="],
+ "@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="],
+
"@types/yauzl": ["@types/yauzl@2.10.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q=="],
"@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="],
@@ -1243,6 +1248,8 @@
"buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="],
+ "buffer-image-size": ["buffer-image-size@0.6.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-nEh+kZOPY1w+gcCMobZ6ETUp9WfibndnosbpwB1iJk/8Gt5ZF2bhS6+B6bPYz424KtwsR6Rflc3tCz1/ghX2dQ=="],
+
"buildcheck": ["buildcheck@0.0.7", "", {}, "sha512-lHblz4ahamxpTmnsk+MNTRWsjYKv965MwOrSJyeD588rR3Jcu7swE+0wN5F+PbL5cjgu/9ObkhfzEPuofEMwLA=="],
"bullmq": ["bullmq@5.70.4", "", { "dependencies": { "cron-parser": "4.9.0", "ioredis": "5.9.3", "msgpackr": "1.11.5", "node-abort-controller": "3.1.1", "semver": "7.7.4", "tslib": "2.8.1", "uuid": "11.1.0" } }, "sha512-S58YT/tGdhc4pEPcIahtZRBR1TcTLpss1UKiXimF+Vy4yZwF38pW2IvhHqs4j4dEbZqDt8oi0jGGN/WYQHbPDg=="],
@@ -1501,7 +1508,7 @@
"enhanced-resolve": ["enhanced-resolve@5.20.0", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.0" } }, "sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ=="],
- "entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="],
+ "entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="],
"env-paths": ["env-paths@2.2.1", "", {}, "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="],
@@ -1741,6 +1748,8 @@
"gsap": ["gsap@3.14.2", "", {}, "sha512-P8/mMxVLU7o4+55+1TCnQrPmgjPKnwkzkXOK1asnR9Jg2lna4tEY5qBJjMmAaOBDDZWtlRjBXjLa0w53G/uBLA=="],
+ "happy-dom": ["happy-dom@20.14.0", "", { "dependencies": { "@types/node": ">=20.0.0", "@types/whatwg-mimetype": "^3.0.2", "@types/ws": "^8.18.1", "buffer-image-size": "^0.6.4", "entities": "^7.0.1", "whatwg-mimetype": "^3.0.0", "ws": "^8.21.0" } }, "sha512-4bRh1KzRvKDnFNTlLhzT1RZTpkKhQbQDl9j+7GXszWsvuspYdo29k6OHRf4PwiM6oLb8r/pMWeYiJjkfod5AvQ=="],
+
"has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
"has-property-descriptors": ["has-property-descriptors@1.0.2", "", { "dependencies": { "es-define-property": "^1.0.0" } }, "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg=="],
@@ -2937,6 +2946,8 @@
"whatwg-fetch": ["whatwg-fetch@3.6.20", "", {}, "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg=="],
+ "whatwg-mimetype": ["whatwg-mimetype@3.0.0", "", {}, "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q=="],
+
"whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="],
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
@@ -3119,6 +3130,8 @@
"@types/ssh2/@types/node": ["@types/node@18.19.130", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg=="],
+ "@types/ws/@types/node": ["@types/node@25.3.5", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-oX8xrhvpiyRCQkG1MFchB09f+cXftgIXb3a7UUa4Y3wpmZPw5tyZGTLWhlESOLq1Rq6oDlc8npVU2/9xiCuXMA=="],
+
"ansi-escapes/type-fest": ["type-fest@0.21.3", "", {}, "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w=="],
"api/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
@@ -3135,6 +3148,8 @@
"bl/buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="],
+ "buffer-image-size/@types/node": ["@types/node@25.3.5", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-oX8xrhvpiyRCQkG1MFchB09f+cXftgIXb3a7UUa4Y3wpmZPw5tyZGTLWhlESOLq1Rq6oDlc8npVU2/9xiCuXMA=="],
+
"bullmq/ioredis": ["ioredis@5.9.3", "", { "dependencies": { "@ioredis/commands": "1.5.0", "cluster-key-slot": "^1.1.0", "debug": "^4.3.4", "denque": "^2.1.0", "lodash.defaults": "^4.2.0", "lodash.isarguments": "^3.1.0", "redis-errors": "^1.2.0", "redis-parser": "^3.0.0", "standard-as-callback": "^2.1.0" } }, "sha512-VI5tMCdeoxZWU5vjHWsiE/Su76JGhBvWF1MJnV9ZtGltHk9BmD48oDq8Tj8haZ85aceXZMxLNDQZRVo5QKNgXA=="],
"bullmq/uuid": ["uuid@11.1.0", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A=="],
@@ -3227,6 +3242,10 @@
"glob/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="],
+ "happy-dom/@types/node": ["@types/node@25.3.5", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-oX8xrhvpiyRCQkG1MFchB09f+cXftgIXb3a7UUa4Y3wpmZPw5tyZGTLWhlESOLq1Rq6oDlc8npVU2/9xiCuXMA=="],
+
+ "happy-dom/ws": ["ws@8.21.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw=="],
+
"jest-worker/supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="],
"load-json-file/pify": ["pify@2.3.0", "", {}, "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog=="],
@@ -3289,6 +3308,8 @@
"parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="],
+ "parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="],
+
"path-type/pify": ["pify@2.3.0", "", {}, "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog=="],
"pkg-dir/find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="],
@@ -3429,6 +3450,8 @@
"@types/ssh2/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
+ "@types/ws/@types/node/undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
+
"api/ora/cli-cursor": ["cli-cursor@3.1.0", "", { "dependencies": { "restore-cursor": "^3.1.0" } }, "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw=="],
"api/ora/cli-spinners": ["cli-spinners@2.9.2", "", {}, "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg=="],
@@ -3445,6 +3468,8 @@
"appdmg/execa/npm-run-path": ["npm-run-path@2.0.2", "", { "dependencies": { "path-key": "^2.0.0" } }, "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw=="],
+ "buffer-image-size/@types/node/undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
+
"bullmq/ioredis/@ioredis/commands": ["@ioredis/commands@1.5.0", "", {}, "sha512-eUgLqrMf8nJkZxT24JvVRrQya1vZkQh8BBeYNwGDqa5I0VUi8ACx7uFvAaLxintokpTenkK6DASvo/bvNbBGow=="],
"cacache/rimraf/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],
@@ -3539,6 +3564,8 @@
"glob/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
+ "happy-dom/@types/node/undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
+
"log-update/ansi-escapes/type-fest": ["type-fest@1.4.0", "", {}, "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA=="],
"log-update/cli-cursor/restore-cursor": ["restore-cursor@4.0.0", "", { "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" } }, "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg=="],