import { kody } from 'kody:runtime' import { DEFAULT_SECRET_NAME, oauthConnectUrl, resolveIntegrationName, resolveSecretName, secretSetupUrl, } from './auth.ts' import { listOrganizations } from './client.ts' import { DEFAULT_SENTRY_HOST, runHostHelperSelfCheck } from './host.ts' import type { SentryClientOptions } from './types.ts' export type SentrySmokeTestResult = { ok: boolean selfCheck: { ok: true; checks: number } live: boolean host: string secretPresent: boolean integration?: string secretName: string organizations: Array<{ slug: string; name?: string }> setup: { nextSteps: Array } } function secretEntries(result: unknown): Array<{ name?: string; scope?: string }> { if (result && typeof result === 'object' && Array.isArray((result as { secrets?: unknown }).secrets)) { return (result as { secrets: Array<{ name?: string; scope?: string }> }).secrets } return Array.isArray(result) ? result : [] } async function hasUserSecret(name: string): Promise { const listed = await kody.secretList({ scope: 'user' }) for (const entry of secretEntries(listed)) { if (entry?.name === name && (entry.scope === 'user' || !entry.scope)) { return true } } return false } /** * Local host-normalization checks, plus an optional live organization list. * * Without `sentryAuthToken` (or the selected `secretName` / `integration`) this * still returns `{ ok: true, live: false }` and the prefilled secret URL so the * package is forkable before anyone connects. * * @example * import smokeTest from 'kody:@kody/sentry/smoke-test' * const result = await smokeTest() */ export async function smokeTest(params: SentryClientOptions = {}): Promise { const selfCheck = runHostHelperSelfCheck() const host = params.host?.trim() || DEFAULT_SENTRY_HOST const integration = resolveIntegrationName(params) const secretName = resolveSecretName(params) const nextSteps: Array = [] if (integration) { const organizations = await listOrganizations(params) return { ok: true, selfCheck, live: true, host, secretPresent: true, integration, secretName, organizations: organizations.map((org) => ({ slug: org.slug, name: typeof org.name === 'string' ? org.name : undefined, })), setup: { nextSteps: [] }, } } const secretPresent = await hasUserSecret(secretName) if (!secretPresent) { nextSteps.push( 'Save a Sentry user or internal-integration auth token as ' + secretName + '.', ) nextSteps.push(secretSetupUrl(secretName)) nextSteps.push( 'Approve hosts sentry.io, us.sentry.io, and de.sentry.io (plus any self-hosted host).', ) nextSteps.push( 'Optional BYO OAuth (only if you already have a Sentry OAuth app): ' + oauthConnectUrl(), ) return { ok: true, selfCheck, live: false, host, secretPresent, secretName, organizations: [], setup: { nextSteps }, } } const organizations = await listOrganizations(params) return { ok: true, selfCheck, live: true, host, secretPresent, secretName, organizations: organizations.map((org) => ({ slug: org.slug, name: typeof org.name === 'string' ? org.name : undefined, })), setup: { nextSteps: [] }, } } /** Default export: smoke test. */ export default smokeTest