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: '<p>Dry run only</p>',
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<string, unknown> = {},
) {
const input = requireRecord(params, 'smoke-test')
return smokeTest({
...parseAuthInput(input),
setupOnly: input.setupOnly === true,
})
}
export { rejectBroadcastSend }