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));