import { accounts } from './accounts.ts' import { getAccount } from './account.ts' import { createBroadcastDraft, listBroadcasts } from './broadcasts.ts' import { subscribeAndTag } from './subscribers.ts' import { parseAuthInput } from './auth.ts' import { API_KEY_SETUP_URL, KIT_CALLBACK_URL, KIT_REQUIRED_HOSTS, OAUTH_CONNECT_URL } from './setup.ts' import { rejectBroadcastSend } from './safety.ts' import type { KitAuthInput } from './types.ts' import { requireRecord } from './types.ts' export type SmokeTestInput = KitAuthInput & { /** When true, skip the live account query even if credentials exist. */ setupOnly?: boolean } /** * Local dry-run self-check plus an optional live account + broadcast list. * Never creates subscribers, drafts, or sends email. * @example * import kit from 'kody:@kody/kit' * const result = await kit({ action: 'smoke-test' }) */ export async function smokeTest(input: SmokeTestInput = {}) { const draftPreview = await createBroadcastDraft({ subject: 'Kody Kit smoke', content: '
Dry run only
', dryRun: true, }) if (!('dryRun' in draftPreview) || draftPreview.dryRun !== true) { throw new Error('createBroadcastDraft dryRun self-check failed.') } const subscribePreview = await subscribeAndTag({ email_address: 'ada@example.com', tagName: 'kody-kit-smoke', dryRun: true, }) if (!('dryRun' in subscribePreview) || subscribePreview.dryRun !== true) { throw new Error('subscribeAndTag dryRun self-check failed.') } let sendAtBlocked = false try { rejectBroadcastSend({ send_at: '2026-01-01T00:00:00Z' }, 'broadcast draft') } catch { sendAtBlocked = true } if (!sendAtBlocked) throw new Error('send_at rejection self-check failed.') const info = await accounts(input) const canLive = Boolean(info.preferredAuth) && input.setupOnly !== true const selfCheck = { dryRunCreateDraft: true, dryRunSubscribeAndTag: true, sendAtRejected: sendAtBlocked, connectUrlUsesKitHost: info.connectUrl.includes('api.kit.com'), apiKeyUrlUsesKitHost: info.apiKeyUrl.includes('api.kit.com'), callbackUrl: info.callbackUrl === KIT_CALLBACK_URL, } if (!canLive) { return { ok: true, live: false, selfCheck, authMode: info.preferredAuth, integrationName: info.integrationName, secretName: info.secretName, oauthConnected: info.oauthConnected, apiKeySaved: info.apiKeySaved, setup: { connectUrl: info.connectUrl, apiKeyUrl: info.apiKeyUrl, callbackUrl: info.callbackUrl, requiredHosts: info.requiredHosts, nextSteps: [ `Save a v4 API key (do not paste it in chat): ${info.apiKeyUrl}`, `Or connect OAuth: ${info.connectUrl}`, `Approve host ${KIT_REQUIRED_HOSTS.join(', ')}.`, ], }, } } const [account, broadcasts] = await Promise.all([ getAccount(input), listBroadcasts({ ...input, maxItems: 5 }), ]) return { ok: true, live: true, selfCheck, authMode: info.preferredAuth, integrationName: info.integrationName, secretName: info.secretName, oauthConnected: info.oauthConnected, apiKeySaved: info.apiKeySaved, account: { id: account.id, name: account.name, planType: account.planType, }, broadcastCount: broadcasts.length, sampleBroadcast: broadcasts[0] ? { id: broadcasts[0].id, subject: broadcasts[0].subject, status: broadcasts[0].status } : null, setup: { connectUrl: OAUTH_CONNECT_URL, apiKeyUrl: API_KEY_SETUP_URL, requiredHosts: [...KIT_REQUIRED_HOSTS], }, } } /** * Local dry-run self-check plus an optional live account read. * @example * import smokeTest from 'kody:@kody/kit/smoke-test' * const result = await smokeTest() */ export default async function smokeTestEntrypoint( params: SmokeTestInput & Record