From be4d74df254c83330b0c43321cc03169df81199b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Beteg=C3=B3n?= Date: Mon, 13 Apr 2026 16:14:28 +0200 Subject: [PATCH 1/2] feat(init): cache project and DSN after create/init MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit createProjectWithDsn now seeds project_cache (by orgId:projectId and DSN public key) so the next command resolves from cache instead of hitting the API. Covers both `sentry init` and `sentry project create`. sentry init additionally seeds dsn_cache (keyed by directory) at both success paths (existing project + new project) so detectDsn() gets a cache hit instead of re-scanning source files. Follows the existing inline caching pattern used by resolveFromDsn and detectDsn — no new abstractions. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/lib/api/projects.ts | 33 ++++++++++++++++++++++- src/lib/init/local-ops.ts | 55 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 1 deletion(-) diff --git a/src/lib/api/projects.ts b/src/lib/api/projects.ts index af3017702..973a3cb9f 100644 --- a/src/lib/api/projects.ts +++ b/src/lib/api/projects.ts @@ -21,8 +21,13 @@ import type { SentryProject, } from "../../types/index.js"; -import { cacheProjectsForOrg } from "../db/project-cache.js"; +import { + cacheProjectsForOrg, + setCachedProject, + setCachedProjectByDsnKey, +} from "../db/project-cache.js"; import { getCachedOrganizations } from "../db/regions.js"; +import { parseDsn } from "../dsn/parser.js"; import { type AuthGuardSuccess, withAuthGuard } from "../errors.js"; import { logger } from "../logger.js"; import { getApiBaseUrl } from "../sentry-client.js"; @@ -190,6 +195,32 @@ export async function createProjectWithDsn( const project = await createProject(orgSlug, teamSlug, body); const dsn = await tryGetPrimaryDsn(orgSlug, project.slug); const url = buildProjectUrl(orgSlug, project.slug); + + // Seed project_cache so the next command doesn't re-resolve via API + try { + const parsed = dsn ? parseDsn(dsn) : null; + if (parsed?.orgId) { + setCachedProject(parsed.orgId, parsed.projectId, { + orgSlug, + orgName: orgSlug, + projectSlug: project.slug, + projectName: project.name, + projectId: project.id, + }); + } + if (parsed?.publicKey) { + setCachedProjectByDsnKey(parsed.publicKey, { + orgSlug, + orgName: orgSlug, + projectSlug: project.slug, + projectName: project.name, + projectId: project.id, + }); + } + } catch { + // Best-effort — cache failure should not break project creation + } + return { project, dsn, url }; } diff --git a/src/lib/init/local-ops.ts b/src/lib/init/local-ops.ts index a201bca15..46db5c332 100644 --- a/src/lib/init/local-ops.ts +++ b/src/lib/init/local-ops.ts @@ -15,6 +15,8 @@ import { listOrganizations, tryGetPrimaryDsn, } from "../api-client.js"; +import { setCachedDsn } from "../db/dsn-cache.js"; +import { parseDsn } from "../dsn/parser.js"; import { ApiError } from "../errors.js"; import { resolveOrCreateTeam } from "../resolve-team.js"; import { buildProjectUrl } from "../sentry-urls.js"; @@ -1489,6 +1491,7 @@ async function glob(payload: GlobPayload): Promise { // ── Sentry project + DSN ──────────────────────────────────────────── +// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: branching for dry-run, existing project, new project, and best-effort cache seeding async function createSentryProject( payload: CreateSentryProjectPayload, options: WizardOptions @@ -1535,6 +1538,34 @@ async function createSentryProject( if (options.org && options.project) { const existing = await tryGetExistingProject(orgSlug, slug); if (existing) { + // Seed dsn_cache so detectDsn() gets a cache hit next time + const d = existing.data as { + orgSlug: string; + projectSlug: string; + projectId: string; + dsn: string; + }; + if (d.dsn) { + try { + const parsed = parseDsn(d.dsn); + if (parsed) { + setCachedDsn(payload.cwd, { + dsn: d.dsn, + projectId: parsed.projectId, + orgId: parsed.orgId, + source: "code", + resolved: { + orgSlug: d.orgSlug, + orgName: d.orgSlug, + projectSlug: d.projectSlug, + projectName: d.projectSlug, + }, + }); + } + } catch { + // Best-effort + } + } return { ...existing, message: `Using existing project "${slug}" in ${orgSlug}`, @@ -1556,6 +1587,30 @@ async function createSentryProject( { name, platform } ); + // Seed dsn_cache so detectDsn() gets a cache hit next time + // (project_cache already seeded by createProjectWithDsn) + if (dsn) { + try { + const parsed = parseDsn(dsn); + if (parsed) { + setCachedDsn(payload.cwd, { + dsn, + projectId: parsed.projectId, + orgId: parsed.orgId, + source: "code", + resolved: { + orgSlug, + orgName: orgSlug, + projectSlug: project.slug, + projectName: project.name, + }, + }); + } + } catch { + // Best-effort + } + } + return { ok: true, data: { From 94dc729f76178100510867f5b153cc154c4d40fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Beteg=C3=B3n?= Date: Tue, 14 Apr 2026 10:58:13 +0200 Subject: [PATCH 2/2] fix(init): move dsn_cache seeding to after wizard completes The previous dsn_cache seeding in createSentryProject was broken: - No sourcePath (DSN not yet written to files at that point) - payload.cwd might not match projectRoot (detectDsn looks up by root) Move the cache seeding to after handleFinalResult in runWizard. At that point apply-patchset has already written the DSN to source files, so detectDsn finds it and caches with the correct projectRoot and sourcePath. The project_cache seeding in createProjectWithDsn (from the prior commit) still handles the API-avoidance side. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/lib/init/local-ops.ts | 55 ----------------------------------- src/lib/init/wizard-runner.ts | 13 +++++++++ 2 files changed, 13 insertions(+), 55 deletions(-) diff --git a/src/lib/init/local-ops.ts b/src/lib/init/local-ops.ts index 46db5c332..a201bca15 100644 --- a/src/lib/init/local-ops.ts +++ b/src/lib/init/local-ops.ts @@ -15,8 +15,6 @@ import { listOrganizations, tryGetPrimaryDsn, } from "../api-client.js"; -import { setCachedDsn } from "../db/dsn-cache.js"; -import { parseDsn } from "../dsn/parser.js"; import { ApiError } from "../errors.js"; import { resolveOrCreateTeam } from "../resolve-team.js"; import { buildProjectUrl } from "../sentry-urls.js"; @@ -1491,7 +1489,6 @@ async function glob(payload: GlobPayload): Promise { // ── Sentry project + DSN ──────────────────────────────────────────── -// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: branching for dry-run, existing project, new project, and best-effort cache seeding async function createSentryProject( payload: CreateSentryProjectPayload, options: WizardOptions @@ -1538,34 +1535,6 @@ async function createSentryProject( if (options.org && options.project) { const existing = await tryGetExistingProject(orgSlug, slug); if (existing) { - // Seed dsn_cache so detectDsn() gets a cache hit next time - const d = existing.data as { - orgSlug: string; - projectSlug: string; - projectId: string; - dsn: string; - }; - if (d.dsn) { - try { - const parsed = parseDsn(d.dsn); - if (parsed) { - setCachedDsn(payload.cwd, { - dsn: d.dsn, - projectId: parsed.projectId, - orgId: parsed.orgId, - source: "code", - resolved: { - orgSlug: d.orgSlug, - orgName: d.orgSlug, - projectSlug: d.projectSlug, - projectName: d.projectSlug, - }, - }); - } - } catch { - // Best-effort - } - } return { ...existing, message: `Using existing project "${slug}" in ${orgSlug}`, @@ -1587,30 +1556,6 @@ async function createSentryProject( { name, platform } ); - // Seed dsn_cache so detectDsn() gets a cache hit next time - // (project_cache already seeded by createProjectWithDsn) - if (dsn) { - try { - const parsed = parseDsn(dsn); - if (parsed) { - setCachedDsn(payload.cwd, { - dsn, - projectId: parsed.projectId, - orgId: parsed.orgId, - source: "code", - resolved: { - orgSlug, - orgName: orgSlug, - projectSlug: project.slug, - projectName: project.name, - }, - }); - } - } catch { - // Best-effort - } - } - return { ok: true, data: { diff --git a/src/lib/init/wizard-runner.ts b/src/lib/init/wizard-runner.ts index 604ede635..9cab102d4 100644 --- a/src/lib/init/wizard-runner.ts +++ b/src/lib/init/wizard-runner.ts @@ -728,6 +728,19 @@ export async function runWizard(initialOptions: WizardOptions): Promise { } handleFinalResult(result, spin, spinState); + + // Eagerly populate dsn_cache so the next command doesn't re-scan files. + // The DSN is now in source code (written by apply-patchset), so detectDsn + // will find it and cache with the correct projectRoot and sourcePath. + // project_cache was already seeded by createProjectWithDsn. + if (!dryRun) { + try { + const { detectDsn } = await import("../dsn/index.js"); + await detectDsn(directory); + } catch { + // Best-effort — cache seeding failure doesn't affect init result + } + } } function handleFinalResult(