export const API_BASE_URL = 'https://api.netlify.com/api/v1' export const API_KEY_SECRET = 'netlifyToken' export const API_HOSTS = ['api.netlify.com', 'app.netlify.com'] export const SECRET_URL = 'https://kody.codes/account/secrets/new?name=netlifyToken&description=Netlify%20personal%20access%20token%20(Bearer%20for%20api.netlify.com)&allowedHosts=api.netlify.com&scope=user' export const TOKEN_DASHBOARD_URL = 'https://app.netlify.com/user/applications#personal-access-tokens' export const API_DOCS_URL = 'https://docs.netlify.com/api-and-cli-guides/api-guides/get-started-with-api/' const RETIRED_ALIASES = new Set([ 'kent', 'kentcdodds', 'epicweb', 'epic-web', 'kcd', 'kentcdodds.com', ]) export function setupUrls() { return { tokenUrl: SECRET_URL, tokenDashboardUrl: TOKEN_DASHBOARD_URL, apiDocsUrl: API_DOCS_URL, hosts: [...API_HOSTS], tokenSecret: API_KEY_SECRET, } } 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 Netlify account alias "${value}" (${fieldName}) is not supported. Pass secretName for a personal access token. Save a token at ${SECRET_URL}.`, ) } export function authSetupMessage(prefix = 'Netlify credentials are missing or were rejected.') { return `${prefix} Save a personal access token at ${SECRET_URL} (approve host api.netlify.com). Create the token at ${TOKEN_DASHBOARD_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', 'account', 'path', 'method', ] export function queryFromInput(input, ignoredKeys = []) { if (isRecord(input?.query)) return { ...input.query } return 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, account: input?.account, } } export function requireConfirm(input, action, method, path, body) { if (input?.dryRun) { return { dryRun: true, method, path, body: body ?? null, query: input?.query ?? null } } if (input?.confirm !== true) { throw new Error( `${action} writes live Netlify state. Pass dryRun: true to preview, or confirm: true to apply.`, ) } return null } export function redactEnvValue(body) { if (Array.isArray(body)) return body.map(redactEnvValue) if (!isRecord(body)) return body const next = { ...body } if (Object.prototype.hasOwnProperty.call(next, 'value')) next.value = '[redacted]' if (Array.isArray(next.values)) { next.values = next.values.map((entry) => { if (!isRecord(entry)) return entry if (!Object.prototype.hasOwnProperty.call(entry, 'value')) return { ...entry } return { ...entry, value: '[redacted]' } }) } if (Array.isArray(next.environment)) { next.environment = next.environment.map(redactEnvValue) } return next } export function slimUser(user) { if (!isRecord(user)) return user return { id: user.id, email: user.email ?? null, full_name: user.full_name ?? user.fullName ?? null, avatar_url: user.avatar ?? user.avatar_url ?? null, slug: user.slug ?? null, } } export function slimAccount(account) { if (!isRecord(account)) return account return { id: account.id, name: account.name ?? null, slug: account.slug ?? null, type: account.type_name ?? account.type ?? null, role: account.role ?? null, } } export function slimDeploy(deploy) { if (!isRecord(deploy)) return deploy return { id: deploy.id, site_id: deploy.site_id ?? null, state: deploy.state ?? null, name: deploy.name ?? null, url: deploy.url ?? null, ssl_url: deploy.ssl_url ?? null, deploy_url: deploy.deploy_ssl_url ?? deploy.deploy_url ?? null, admin_url: deploy.admin_url ?? null, branch: deploy.branch ?? null, context: deploy.context ?? null, draft: deploy.draft === true, error_message: deploy.error_message ?? null, commit_ref: deploy.commit_ref ?? null, title: deploy.title ?? null, created_at: deploy.created_at ?? null, published_at: deploy.published_at ?? null, framework: deploy.framework ?? null, } } export function slimSite(site) { if (!isRecord(site)) return site const published = isRecord(site.published_deploy) ? slimDeploy(site.published_deploy) : null return { id: site.id, name: site.name ?? null, url: site.url ?? null, ssl_url: site.ssl_url ?? null, admin_url: site.admin_url ?? null, state: site.state ?? null, custom_domain: site.custom_domain ?? null, account_id: site.account_id ?? null, account_name: site.account_name ?? null, account_slug: site.account_slug ?? null, created_at: site.created_at ?? null, updated_at: site.updated_at ?? null, published_deploy: published, } } export function slimEnvVar(env) { if (!isRecord(env)) return env const values = Array.isArray(env.values) ? env.values.map((entry) => { if (!isRecord(entry)) return { context: null } return { id: entry.id ?? null, context: entry.context ?? null, context_parameter: entry.context_parameter ?? null, } }) : [] return { key: env.key, scopes: env.scopes ?? null, is_secret: env.is_secret === true, updated_at: env.updated_at ?? null, values, } } export function slimForm(form) { if (!isRecord(form)) return form return { id: form.id, site_id: form.site_id ?? null, name: form.name ?? null, paths: form.paths ?? null, submission_count: form.submission_count ?? null, created_at: form.created_at ?? null, } } export function slimSubmission(submission) { if (!isRecord(submission)) return submission return { id: submission.id, number: submission.number ?? null, email: submission.email ?? null, name: submission.name ?? null, summary: submission.summary ?? null, created_at: submission.created_at ?? null, site_url: submission.site_url ?? null, data: isRecord(submission.data) ? submission.data : null, } } export function paginationFromInput(input = {}) { const page = input.page ?? input.query?.page const perPage = input.per_page ?? input.perPage ?? input.query?.per_page return { page: page == null ? null : Number(page), per_page: perPage == null ? null : Number(perPage), } }