export const API_BASE_URL = 'https://api.vercel.com' export const API_KEY_SECRET = 'vercelToken' export const DEFAULT_INTEGRATION_NAME = 'vercel' export const API_HOSTS = ['api.vercel.com', 'vercel.com'] export const SECRET_URL = 'https://kody.codes/account/secrets/new?name=vercelToken&description=Vercel%20access%20token%20(Bearer%20for%20api.vercel.com)&allowedHosts=api.vercel.com&scope=user' export const OAUTH_URL = 'https://kody.codes/connect/oauth?provider=vercel&authorizeUrl=https%3A%2F%2Fvercel.com%2Foauth%2Fauthorize&tokenUrl=https%3A%2F%2Fapi.vercel.com%2Flogin%2Foauth%2Ftoken&flow=pkce&pkce=true&scopes=openid%20email%20profile%20offline_access&allowedHosts=api.vercel.com%2Cvercel.com&apiBaseUrl=https%3A%2F%2Fapi.vercel.com&dashboardUrl=https%3A%2F%2Fvercel.com%2Fdocs%2Fsign-in-with-vercel&providerSetupInstructions=Create%20a%20Sign%20in%20with%20Vercel%20app%20and%20register%20redirect%20URI%20exactly%20https%3A%2F%2Fkody.codes%2Fconnect%2Foauth.%20REST%20API%20permissions%20for%20this%20OAuth%20lane%20are%20in%20private%20beta%20%E2%80%94%20prefer%20an%20access%20token%20for%20teams%2C%20projects%2C%20and%20deployments.%20Paste%20the%20client%20id%20into%20this%20form.' export const TOKEN_DASHBOARD_URL = 'https://vercel.com/account/tokens' export const OAUTH_DASHBOARD_URL = 'https://vercel.com/docs/sign-in-with-vercel' export const REDIRECT_URI = 'https://kody.codes/connect/oauth' const RETIRED_ALIASES = new Set(['kent', 'kentcdodds', 'epicweb', 'epic-web', 'kcd']) export function setupUrls() { return { tokenUrl: SECRET_URL, oauthUrl: OAUTH_URL, tokenDashboardUrl: TOKEN_DASHBOARD_URL, oauthDashboardUrl: OAUTH_DASHBOARD_URL, redirectUri: REDIRECT_URI, hosts: [...API_HOSTS], tokenSecret: API_KEY_SECRET, integrationName: DEFAULT_INTEGRATION_NAME, } } export function assertNoRetiredAlias(value, fieldName) { if (typeof value !== 'string') return const normalized = value.trim().toLowerCase() if (!RETIRED_ALIASES.has(normalized)) return throw new Error( `Hard-coded Vercel account alias "${value}" (${fieldName}) is not supported. Pass secretName for an access token or integrationName for OAuth. Save a token at ${SECRET_URL} or connect OAuth at ${OAUTH_URL}.`, ) } export function authSetupMessage(prefix = 'Vercel credentials are missing or were rejected.') { return `${prefix} Save an access token at ${SECRET_URL} (approve host api.vercel.com) or connect OAuth at ${OAUTH_URL}.` } export function isRecord(value) { return value !== null && typeof value === 'object' && !Array.isArray(value) } export function withoutKeys(record, keys) { const blocked = new Set(keys) return Object.fromEntries( Object.entries(record ?? {}) .filter(([, value]) => value !== undefined) .filter(([key]) => !blocked.has(key)), ) } export const AUTH_AND_CONTROL_KEYS = [ 'action', 'body', 'headers', 'params', 'dryRun', 'confirm', 'secretName', 'integrationName', 'account', 'teamId', 'teamSlug', 'slug', ] export function queryFromInput(input, ignoredKeys = []) { if (isRecord(input?.query)) return { ...teamQuery(input), ...input.query } return { ...teamQuery(input), ...withoutKeys(input, [...AUTH_AND_CONTROL_KEYS, ...ignoredKeys]) } } export function bodyFromInput(input, ignoredKeys = []) { if (isRecord(input?.body)) return input.body return withoutKeys(input, [...AUTH_AND_CONTROL_KEYS, 'query', ...ignoredKeys]) } export function authFromInput(input) { return { secretName: input?.secretName, integrationName: input?.integrationName, account: input?.account, } } export function teamQuery(input = {}) { const query = {} if (typeof input.teamId === 'string' && input.teamId.trim()) query.teamId = input.teamId.trim() const slug = typeof input.teamSlug === 'string' ? input.teamSlug : input.slug if (typeof slug === 'string' && slug.trim() && !query.teamId) query.slug = slug.trim() return query } export function requireConfirm(input, action, method, path, body) { if (input?.dryRun) { return { dryRun: true, method, path, body: body ?? null, query: teamQuery(input) } } if (input?.confirm !== true) { throw new Error( `${action} writes live Vercel state. Pass dryRun: true to preview, or confirm: true to apply.`, ) } return null } export function redactEnvValue(body) { if (!isRecord(body)) return body if (!Object.prototype.hasOwnProperty.call(body, 'value')) return body return { ...body, value: '[redacted]' } } export function slimUser(user) { if (!isRecord(user)) return user return { id: user.id, username: user.username, name: user.name ?? null, email: user.email ?? null, defaultTeamId: user.defaultTeamId ?? null, } } export function slimTeam(team) { if (!isRecord(team)) return team return { id: team.id, name: team.name, slug: team.slug, createdAt: team.createdAt ?? null, limited: team.limited === true, } } export function slimProject(project) { if (!isRecord(project)) return project const link = isRecord(project.link) ? { type: project.link.type ?? null, repo: project.link.repo ?? project.link.repoId ?? null, org: project.link.org ?? null, } : null return { id: project.id, name: project.name, framework: project.framework ?? null, updatedAt: project.updatedAt ?? null, link, } } export function slimDeployment(deployment) { if (!isRecord(deployment)) return deployment return { id: deployment.uid ?? deployment.id, name: deployment.name ?? null, url: deployment.url ?? null, state: deployment.readyState ?? deployment.state ?? null, target: deployment.target ?? null, createdAt: deployment.createdAt ?? null, inspectorUrl: deployment.inspectorUrl ?? null, projectId: deployment.projectId ?? null, } } export function slimDomain(domain) { if (!isRecord(domain)) return domain return { name: domain.name ?? domain.apexName ?? null, verified: domain.verified ?? null, gitBranch: domain.gitBranch ?? null, redirect: domain.redirect ?? null, projectId: domain.projectId ?? null, } } export function slimEnvVar(env) { if (!isRecord(env)) return env return { id: env.id ?? null, key: env.key, type: env.type ?? null, target: env.target ?? null, gitBranch: env.gitBranch ?? null, createdAt: env.createdAt ?? null, updatedAt: env.updatedAt ?? null, comment: env.comment ?? null, } } export function paginationFrom(body) { if (isRecord(body?.pagination)) { return { count: body.pagination.count ?? null, next: body.pagination.next ?? null, prev: body.pagination.prev ?? null, } } return null }