From be92f4276ed80f9e1acf50e966da16cf6f4c3763 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Beteg=C3=B3n?= Date: Fri, 8 May 2026 20:16:19 +0200 Subject: [PATCH] fix(init): retry project creation without platform when API rejects slug MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some SDK keys (e.g. sentry.javascript.hono) produce platform IDs via the naive transform that the Sentry project creation API doesn't recognize. When the API returns 400 {"platform":["Invalid platform"]}, retry once without the platform field. The platform label is cosmetic — the wizard installs the correct SDK and generates the right code from the detected SDK key regardless of the project's platform tag. Fixes CLI-SERVER-E. Co-Authored-By: Claude Sonnet 4.6 (1M context) --- src/lib/init/tools/create-sentry-project.ts | 26 ++++++++++--- .../init/tools/create-sentry-project.test.ts | 39 +++++++++++++++++++ 2 files changed, 60 insertions(+), 5 deletions(-) diff --git a/src/lib/init/tools/create-sentry-project.ts b/src/lib/init/tools/create-sentry-project.ts index 88f118abe..a0735e927 100644 --- a/src/lib/init/tools/create-sentry-project.ts +++ b/src/lib/init/tools/create-sentry-project.ts @@ -1,4 +1,5 @@ import { createProjectWithDsn } from "../../api-client.js"; +import { ApiError } from "../../errors.js"; import { resolveOrCreateTeam } from "../../resolve-team.js"; import { slugify } from "../../utils.js"; import { tryGetExistingProjectData } from "../existing-project.js"; @@ -72,15 +73,30 @@ export async function createSentryProject( }; } - const { project, dsn, url } = await createProjectWithDsn( - context.org, - teamSlug, - { + let createdProject: Awaited>; + try { + createdProject = await createProjectWithDsn(context.org, teamSlug, { name, platform: payload.params.platform, + }); + } catch (error) { + // Platform slug rejected by the API — retry without it. The platform + // label is cosmetic: the wizard installs the correct SDK and generates + // the right code regardless of the project's platform tag. + if ( + error instanceof ApiError && + error.status === 400 && + error.detail?.includes('"platform"') + ) { + createdProject = await createProjectWithDsn(context.org, teamSlug, { + name, + }); + } else { + throw error; } - ); + } + const { project, dsn, url } = createdProject; return { ok: true, data: { diff --git a/test/lib/init/tools/create-sentry-project.test.ts b/test/lib/init/tools/create-sentry-project.test.ts index 7916d7de0..600470696 100644 --- a/test/lib/init/tools/create-sentry-project.test.ts +++ b/test/lib/init/tools/create-sentry-project.test.ts @@ -193,6 +193,45 @@ describe("createSentryProject", () => { expect(createProjectWithDsnSpy).not.toHaveBeenCalled(); }); + test("retries without platform when API rejects the platform slug", async () => { + getProjectSpy.mockRejectedValueOnce(new ApiError("Not found", 404)); + createProjectWithDsnSpy + .mockRejectedValueOnce( + new ApiError( + "Failed to create project: 400 Bad Request", + 400, + '{"platform":["Invalid platform"]}' + ) + ) + .mockResolvedValueOnce({ + project: { id: "42", slug: "my-app", name: "my-app" } as any, + dsn: "https://abc@o1.ingest.sentry.io/42", + url: "https://sentry.io/settings/acme/projects/my-app/", + }); + + const result = await createSentryProject( + makePayload({ platform: "javascript-hono" }), + { dryRun: false, org: "acme", team: "platform", project: undefined } + ); + + expect(result.ok).toBe(true); + expect(createProjectWithDsnSpy).toHaveBeenCalledTimes(2); + // First call with the original (rejected) platform + expect(createProjectWithDsnSpy).toHaveBeenNthCalledWith( + 1, + "acme", + "platform", + expect.objectContaining({ platform: "javascript-hono" }) + ); + // Retry without platform + expect(createProjectWithDsnSpy).toHaveBeenNthCalledWith( + 2, + "acme", + "platform", + expect.not.objectContaining({ platform: expect.anything() }) + ); + }); + test("resolves the team at project creation time when preflight deferred it", async () => { getProjectSpy.mockRejectedValueOnce(new ApiError("Not found", 404));