Skip to content

Built for people who want to own their automations. Join the waitlist for an invite.

Package listing

@kody/netlify

src/setup.js

235 lines · 6.6 KB · JavaScript
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),
	}
}