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:
23
package-lock.json
generated
23
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "dyad",
|
"name": "dyad",
|
||||||
"version": "0.27.1",
|
"version": "0.28.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "dyad",
|
"name": "dyad",
|
||||||
"version": "0.27.1",
|
"version": "0.28.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ai-sdk/amazon-bedrock": "^3.0.15",
|
"@ai-sdk/amazon-bedrock": "^3.0.15",
|
||||||
@@ -48,7 +48,7 @@
|
|||||||
"@tanstack/react-query": "^5.75.5",
|
"@tanstack/react-query": "^5.75.5",
|
||||||
"@tanstack/react-router": "^1.114.34",
|
"@tanstack/react-router": "^1.114.34",
|
||||||
"@types/uuid": "^10.0.0",
|
"@types/uuid": "^10.0.0",
|
||||||
"@vercel/sdk": "^1.10.0",
|
"@vercel/sdk": "^1.18.0",
|
||||||
"@vitejs/plugin-react": "^4.3.4",
|
"@vitejs/plugin-react": "^4.3.4",
|
||||||
"ai": "^5.0.15",
|
"ai": "^5.0.15",
|
||||||
"better-sqlite3": "^12.4.1",
|
"better-sqlite3": "^12.4.1",
|
||||||
@@ -7435,22 +7435,15 @@
|
|||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/@vercel/sdk": {
|
"node_modules/@vercel/sdk": {
|
||||||
"version": "1.11.0",
|
"version": "1.18.0",
|
||||||
"resolved": "https://registry.npmjs.org/@vercel/sdk/-/sdk-1.11.0.tgz",
|
"resolved": "https://registry.npmjs.org/@vercel/sdk/-/sdk-1.18.0.tgz",
|
||||||
"integrity": "sha512-epfIZMS3/NtBFSrkF470dvlrMEq05g3si8IGD2v18bvK9MmgtoDN3JwRoUaYU/mLER5AeO3TzFtrCbccPfDh9A==",
|
"integrity": "sha512-61FFMZ6WSUdOmInLcrY/+vfJgzu8ua46SkeLXXXUWuifKzJvnWPsfRw31uXvlmWJDLRYtHpna6Q4pljZccW5XQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"zod": "^3.20.0"
|
"@modelcontextprotocol/sdk": ">=1.5.0 <1.10.0",
|
||||||
|
"zod": "^3.25.0 || ^4.0.0"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"mcp": "bin/mcp-server.js"
|
"mcp": "bin/mcp-server.js"
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@modelcontextprotocol/sdk": ">=1.5.0 <1.10.0"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"@modelcontextprotocol/sdk": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vitejs/plugin-react": {
|
"node_modules/@vitejs/plugin-react": {
|
||||||
|
|||||||
@@ -124,7 +124,7 @@
|
|||||||
"@tanstack/react-query": "^5.75.5",
|
"@tanstack/react-query": "^5.75.5",
|
||||||
"@tanstack/react-router": "^1.114.34",
|
"@tanstack/react-router": "^1.114.34",
|
||||||
"@types/uuid": "^10.0.0",
|
"@types/uuid": "^10.0.0",
|
||||||
"@vercel/sdk": "^1.10.0",
|
"@vercel/sdk": "^1.18.0",
|
||||||
"@vitejs/plugin-react": "^4.3.4",
|
"@vitejs/plugin-react": "^4.3.4",
|
||||||
"ai": "^5.0.15",
|
"ai": "^5.0.15",
|
||||||
"better-sqlite3": "^12.4.1",
|
"better-sqlite3": "^12.4.1",
|
||||||
|
|||||||
@@ -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> {
|
async function validateVercelToken(token: string): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
const vercel = createVercelClient(token);
|
const vercel = createVercelClient(token);
|
||||||
@@ -184,8 +232,7 @@ async function handleListVercelProjects(): Promise<VercelProject[]> {
|
|||||||
throw new Error("Not authenticated with Vercel.");
|
throw new Error("Not authenticated with Vercel.");
|
||||||
}
|
}
|
||||||
|
|
||||||
const vercel = createVercelClient(accessToken);
|
const response = await getVercelProjects(accessToken);
|
||||||
const response = await vercel.projects.getProjects({});
|
|
||||||
|
|
||||||
if (!response.projects) {
|
if (!response.projects) {
|
||||||
throw new Error("Failed to retrieve projects from Vercel.");
|
throw new Error("Failed to retrieve projects from Vercel.");
|
||||||
@@ -214,12 +261,8 @@ async function handleIsProjectAvailable(
|
|||||||
return { available: false, error: "Not authenticated with Vercel." };
|
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
|
// Check if project name is available by searching for projects with that name
|
||||||
const response = await vercel.projects.getProjects({
|
const response = await getVercelProjects(accessToken, { search: name });
|
||||||
search: name,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.projects) {
|
if (!response.projects) {
|
||||||
return {
|
return {
|
||||||
@@ -361,10 +404,8 @@ async function handleConnectToExistingProject(
|
|||||||
`Connecting to existing Vercel project: ${projectId} for app ${appId}`,
|
`Connecting to existing Vercel project: ${projectId} for app ${appId}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
const vercel = createVercelClient(accessToken);
|
|
||||||
|
|
||||||
// Verify the project exists and get its details
|
// Verify the project exists and get its details
|
||||||
const response = await vercel.projects.getProjects({});
|
const response = await getVercelProjects(accessToken);
|
||||||
const projectData = response.projects?.find(
|
const projectData = response.projects?.find(
|
||||||
(p) => p.id === projectId || p.name === projectId,
|
(p) => p.id === projectId || p.name === projectId,
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user