import type { Account } from './account.ts' export type MutationMode = { execute: boolean; reason: string } /** Writes preview by default: only `confirm: true` without `dryRun: true` executes. */ export function mutationMode(input: { dryRun?: boolean; confirm?: boolean }): MutationMode { if (input.dryRun === true) return { execute: false, reason: input.confirm ? 'dryRun: true wins over confirm: true' : 'dryRun: true' } if (input.confirm === true) return { execute: true, reason: 'confirm: true' } return { execute: false, reason: 'no confirm: true (preview by default)' } } export function echo(a: Account, action: string, target: string, mode: MutationMode) { return { mode: mode.execute ? 'write' : 'dry-run', action, account: a.account, secretName: a.secretName, target } } export function preview(a: Account, action: string, target: string, mode: MutationMode, request: Record, extra: Record = {}) { return { dryRun: true, executed: false, echo: echo(a, action, target, mode), reason: mode.reason, request, ...extra, next: 'Nothing was sent to Granola. Show this preview to the user; re-run the same call with confirm: true (and without dryRun) only if they asked for this exact change.', } } const PRIVATE_HOST = /^(localhost|.*\.localhost|.*\.local|.*\.internal|127\.\d+\.\d+\.\d+|10\.\d+\.\d+\.\d+|192\.168\.\d+\.\d+|172\.(1[6-9]|2\d|3[01])\.\d+\.\d+|169\.254\.\d+\.\d+|0\.0\.0\.0|\[?::1\]?|\[?f[cd][0-9a-f]{2}:.*)$/i /** Granola only accepts public HTTPS webhook URLs; refuse obvious mistakes before any request. */ export function checkWebhookUrl(raw: string): string { let u: URL try { u = new URL(raw) } catch { throw new Error(`Webhook url "${raw}" is not a valid URL.`) } if (u.protocol !== 'https:') throw new Error('Webhook url must use https:// (Granola rejects other schemes).') if (PRIVATE_HOST.test(u.hostname)) throw new Error(`Webhook url host "${u.hostname}" is private/local; Granola only delivers to publicly reachable HTTPS URLs.`) if (u.username || u.password) throw new Error('Webhook url must not embed credentials.') return u.toString() }