import { optionalString, requireString, type SupabaseAuthInput, } from './types.ts' const ORIGIN = 'https://kody.codes' const PAT_GENERATE_URL = 'https://supabase.com/dashboard/account/tokens' const API_KEYS_URL = 'https://supabase.com/dashboard/project/_/settings/api-keys/' const MANAGEMENT_HOST = 'api.supabase.com' export const DEFAULT_PAT_SECRET = 'supabasePat' export const DEFAULT_SERVICE_ROLE_SECRET = 'supabaseServiceRoleKey' /** * Return prefilled Supabase secret setup URLs for PAT and service-role secrets. * Use before other exports when auth is not connected yet. * * @param input.account - Optional account purpose suffix. * @param input.projectRef - Optional project ref for service-role setup URL. * @returns Setup URL helpers for Supabase secrets. * * @example * import setup from 'kody:@kody/supabase/setup' * const urls = setup({ account: 'work', projectRef: 'abcdefghijklmnop' }) */ export function projectHost(projectRef: string): string { return `${projectRef}.supabase.co` } export function projectBaseUrl(projectRef: string): string { return `https://${projectHost(projectRef)}` } export function encodeQuery(params: Record): string { return Object.entries(params) .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`) .join('&') } export function patSetupUrl(secretName = DEFAULT_PAT_SECRET): string { return `${ORIGIN}/account/secrets/new?${encodeQuery({ name: secretName, description: 'Supabase personal access token for the Management API (projects, orgs, generic /v1 paths)', allowedHosts: MANAGEMENT_HOST, scope: 'user', })}` } export function serviceRoleSetupUrl(input: { secretName?: string projectRef?: string projectHost?: string } = {}): string { const secretName = input.secretName ?? DEFAULT_SERVICE_ROLE_SECRET const host = input.projectHost ?? (input.projectRef ? projectHost(input.projectRef) : undefined) const params: Record = { name: secretName, description: 'Supabase project service role or secret key for Auth admin, PostgREST, and Storage', scope: 'user', } if (host) params.allowedHosts = host return `${ORIGIN}/account/secrets/new?${encodeQuery(params)}` } export function setupUrls(input: SupabaseAuthInput = {}) { const names = resolveSecretNames(input) const host = input.projectUrl ? new URL(input.projectUrl).host : input.projectRef ? projectHost(input.projectRef) : undefined return { patGenerateUrl: PAT_GENERATE_URL, apiKeysUrl: API_KEYS_URL, patSetupUrl: patSetupUrl(names.patSecretName), serviceRoleSetupUrl: serviceRoleSetupUrl({ secretName: names.serviceRoleSecretName, projectHost: host, projectRef: input.projectRef, }), managementHost: MANAGEMENT_HOST, projectHost: host, } } export function resolveAccountPurpose(account?: string): string | undefined { const trimmed = account?.trim() if (!trimmed || trimmed === 'default' || trimmed === 'supabase') return undefined if (trimmed.startsWith('supabase-')) return trimmed.slice('supabase-'.length) return trimmed } export function resolveSecretNames(input: SupabaseAuthInput): { patSecretName: string serviceRoleSecretName: string purpose?: string } { const purpose = resolveAccountPurpose(input.account) const suffix = purpose ? `-${purpose}` : '' return { purpose, patSecretName: optionalString(input.patSecretName, 'patSecretName') ?? optionalString(input.secretName, 'secretName') ?? `${DEFAULT_PAT_SECRET}${suffix}`, serviceRoleSecretName: optionalString(input.serviceRoleSecretName, 'serviceRoleSecretName') ?? `${DEFAULT_SERVICE_ROLE_SECRET}${suffix}`, } } export function projectAuth(input: SupabaseAuthInput): SupabaseAuthInput { return { account: input.account, projectRef: input.projectRef, projectUrl: input.projectUrl, patSecretName: input.patSecretName, serviceRoleSecretName: input.serviceRoleSecretName ?? input.secretName, } } export function managementAuth(input: SupabaseAuthInput): SupabaseAuthInput { return { account: input.account, projectRef: input.projectRef, projectUrl: input.projectUrl, secretName: input.secretName, patSecretName: input.patSecretName, serviceRoleSecretName: input.serviceRoleSecretName, } } export function assertSecretName(name: string, label: string): string { const trimmed = requireString(name, label) if (!/^[A-Za-z][A-Za-z0-9_-]{0,127}$/.test(trimmed)) { throw new Error( `${label} must be a secret identifier (letters, digits, underscore, hyphen).`, ) } return trimmed } export function secretPlaceholder(name: string): string { return `{{secret:${assertSecretName(name, 'secretName')}}}` } /** * Return prefilled Supabase secret setup URLs. * @example * import setup from 'kody:@kody/supabase/setup' * const urls = setup({ account: 'work', projectRef: 'abcdefghijklmnop' }) */ export default function setupEntrypoint( input: SupabaseAuthInput & Record = {}, ) { return setupUrls({ account: optionalString(input.account, 'account'), secretName: optionalString(input.secretName, 'secretName'), patSecretName: optionalString(input.patSecretName, 'patSecretName'), serviceRoleSecretName: optionalString( input.serviceRoleSecretName, 'serviceRoleSecretName', ), projectRef: optionalString(input.projectRef, 'projectRef'), projectUrl: optionalString(input.projectUrl, 'projectUrl'), }) }