From f806414ec6b85ecb01e7e887870882464c651bde Mon Sep 17 00:00:00 2001 From: Will Chen Date: Wed, 3 Dec 2025 14:32:01 -0800 Subject: [PATCH] Fix Vercel API breaking change (#1883) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes https://github.com/dyad-sh/dyad/issues/1652 This is kind of a hack because the Vercel SDK has a bug since their API has subtly made a breaking change in the last month or so and the Vercel SDK still hasn't been updated https://github.com/vercel/sdk/issues/175#issuecomment-3608968116 Note: the Vercel SDK upgrade in this PR doesn't actually fix the issue, but is probably good to do anyways. --- ## Summary by cubic Works around a breaking change in Vercel’s API by bypassing the SDK for project queries. Restores listing, availability checks, and project linking. - **Bug Fixes** - Added a direct HTTP call to GET /v9/projects using the Vercel token. - Replaced SDK calls in list, name availability, and connect-to-project flows. - Added minimal types and clearer error handling for project responses. - **Dependencies** - Bumped @vercel/sdk to 1.18.0. The upgrade doesn’t fix the bug but is safe to adopt. Written for commit 306af5c3f235f0ab9d87c809bb8cf54016a5d59f. Summary will update automatically on new commits. --- > [!NOTE] > Replaces Vercel project retrieval with a direct HTTP helper used across handlers to handle API changes, and updates @vercel/sdk to ^1.18.0. > > - **IPC/Backend (Vercel)**: > - Add `getVercelProjects` helper to fetch projects via `GET /v9/projects`, mimicking `vercel.projects.getProjects`. > - Update handlers to use the new helper: > - `vercel:list-projects` (`handleListVercelProjects`) > - `vercel:is-project-available` (`handleIsProjectAvailable`) > - `vercel:connect-existing-project` (`handleConnectToExistingProject`) > - Add lightweight types: `VercelProjectResponse`, `GetVercelProjectsResponse`. > - **Dependencies**: > - Bump `@vercel/sdk` to `^1.18.0`. > > Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 306af5c3f235f0ab9d87c809bb8cf54016a5d59f. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot). --- package-lock.json | 23 ++++------- package.json | 2 +- src/ipc/handlers/vercel_handlers.ts | 61 ++++++++++++++++++++++++----- 3 files changed, 60 insertions(+), 26 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0cd5f41..3802c25 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "dyad", - "version": "0.27.1", + "version": "0.28.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "dyad", - "version": "0.27.1", + "version": "0.28.0", "license": "MIT", "dependencies": { "@ai-sdk/amazon-bedrock": "^3.0.15", @@ -48,7 +48,7 @@ "@tanstack/react-query": "^5.75.5", "@tanstack/react-router": "^1.114.34", "@types/uuid": "^10.0.0", - "@vercel/sdk": "^1.10.0", + "@vercel/sdk": "^1.18.0", "@vitejs/plugin-react": "^4.3.4", "ai": "^5.0.15", "better-sqlite3": "^12.4.1", @@ -7435,22 +7435,15 @@ "license": "ISC" }, "node_modules/@vercel/sdk": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@vercel/sdk/-/sdk-1.11.0.tgz", - "integrity": "sha512-epfIZMS3/NtBFSrkF470dvlrMEq05g3si8IGD2v18bvK9MmgtoDN3JwRoUaYU/mLER5AeO3TzFtrCbccPfDh9A==", + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/@vercel/sdk/-/sdk-1.18.0.tgz", + "integrity": "sha512-61FFMZ6WSUdOmInLcrY/+vfJgzu8ua46SkeLXXXUWuifKzJvnWPsfRw31uXvlmWJDLRYtHpna6Q4pljZccW5XQ==", "dependencies": { - "zod": "^3.20.0" + "@modelcontextprotocol/sdk": ">=1.5.0 <1.10.0", + "zod": "^3.25.0 || ^4.0.0" }, "bin": { "mcp": "bin/mcp-server.js" - }, - "peerDependencies": { - "@modelcontextprotocol/sdk": ">=1.5.0 <1.10.0" - }, - "peerDependenciesMeta": { - "@modelcontextprotocol/sdk": { - "optional": true - } } }, "node_modules/@vitejs/plugin-react": { diff --git a/package.json b/package.json index 7cd62ba..a26021d 100644 --- a/package.json +++ b/package.json @@ -124,7 +124,7 @@ "@tanstack/react-query": "^5.75.5", "@tanstack/react-router": "^1.114.34", "@types/uuid": "^10.0.0", - "@vercel/sdk": "^1.10.0", + "@vercel/sdk": "^1.18.0", "@vitejs/plugin-react": "^4.3.4", "ai": "^5.0.15", "better-sqlite3": "^12.4.1", diff --git a/src/ipc/handlers/vercel_handlers.ts b/src/ipc/handlers/vercel_handlers.ts index 8643291..ee58678 100644 --- a/src/ipc/handlers/vercel_handlers.ts +++ b/src/ipc/handlers/vercel_handlers.ts @@ -42,6 +42,54 @@ function createVercelClient(token: string): Vercel { }); } +interface VercelProjectResponse { + id: string; + name: string; + framework?: string | null; + targets?: { + production?: { + url?: string; + }; + }; +} + +interface GetVercelProjectsResponse { + projects: VercelProjectResponse[]; +} + +/** + * Fetch Vercel projects via HTTP request (bypasses the broken SDK). + * Mimics the SDK's `vercel.projects.getProjects` API. + */ +async function getVercelProjects( + token: string, + options?: { search?: string }, +): Promise { + const url = new URL(`${VERCEL_API_BASE}/v9/projects`); + if (options?.search) { + url.searchParams.set("search", options.search); + } + + const response = await fetch(url.toString(), { + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, + }); + + if (!response.ok) { + const errorText = await response.text(); + throw new Error( + `Failed to fetch Vercel projects: ${response.status} ${response.statusText} - ${errorText}`, + ); + } + + const data = await response.json(); + return { + projects: data.projects || [], + }; +} + async function validateVercelToken(token: string): Promise { try { const vercel = createVercelClient(token); @@ -184,8 +232,7 @@ async function handleListVercelProjects(): Promise { throw new Error("Not authenticated with Vercel."); } - const vercel = createVercelClient(accessToken); - const response = await vercel.projects.getProjects({}); + const response = await getVercelProjects(accessToken); if (!response.projects) { throw new Error("Failed to retrieve projects from Vercel."); @@ -214,12 +261,8 @@ async function handleIsProjectAvailable( return { available: false, error: "Not authenticated with Vercel." }; } - const vercel = createVercelClient(accessToken); - // Check if project name is available by searching for projects with that name - const response = await vercel.projects.getProjects({ - search: name, - }); + const response = await getVercelProjects(accessToken, { search: name }); if (!response.projects) { return { @@ -361,10 +404,8 @@ async function handleConnectToExistingProject( `Connecting to existing Vercel project: ${projectId} for app ${appId}`, ); - const vercel = createVercelClient(accessToken); - // Verify the project exists and get its details - const response = await vercel.projects.getProjects({}); + const response = await getVercelProjects(accessToken); const projectData = response.projects?.find( (p) => p.id === projectId || p.name === projectId, );