Fix Vercel API breaking change (#1883)

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.

<!-- This is an auto-generated description by cubic. -->
---
## 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.

<sup>Written for commit 306af5c3f235f0ab9d87c809bb8cf54016a5d59f.
Summary will update automatically on new commits.</sup>

<!-- End of auto-generated description by cubic. -->

<!-- CURSOR_SUMMARY -->
---

> [!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`.
> 
> <sup>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).</sup>
<!-- /CURSOR_SUMMARY -->
This commit is contained in:
Will Chen
2025-12-03 14:32:01 -08:00
committed by GitHub
parent 2ca14345b6
commit f806414ec6
3 changed files with 60 additions and 26 deletions

23
package-lock.json generated
View File

@@ -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": {

View File

@@ -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",

View File

@@ -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<GetVercelProjectsResponse> {
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<boolean> {
try {
const vercel = createVercelClient(token);
@@ -184,8 +232,7 @@ async function handleListVercelProjects(): Promise<VercelProject[]> {
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,
);