/** * This file was generated by @pikku/cli@0.12.54 */ type EmailPrimitive = string | number | boolean | null | undefined type EmailTemplateValue = EmailPrimitive | Record | Array const EMAIL_THEME = { "appName": "Pikku Starter", "fonts": { "body": "Inter, Arial, sans-serif" }, "colors": { "canvas": "#0b1020", "surface": "#11182d", "border": "#263252", "text": "#f7f8fb", "muted": "#a8b0c5", "accent": "#7dd3fc", "button": "#f59e0b", "buttonText": "#111827" } } as const const EMAIL_LOCALES = { "en": { "common": { "footer": "If you did not create this account, you can safely ignore this email." }, "confirmEmail": { "subject": "Confirm your email for {{appName}}", "preview": "Confirm your email address to finish setting up your account.", "heading": "Confirm your email", "intro": "Thanks for signing up for {{appName}}! Confirm {{email}} to finish setting up your account.", "cta": "Confirm email", "fallback": "If the button does not work, copy and paste this URL into your browser:", "expiry": "This link expires in 24 hours." } } } as const const EMAIL_PARTIALS = { "footer": "

\n {{t.common.footer}}\n

\n", "layout": "\n\n \n \n \n {{subject}}\n \n \n
\n {{content}}\n
\n \n\n" } as const const EMAIL_TEMPLATES = { "confirm-email": { "html": "

\n {{appName}}\n

\n

\n {{t.confirmEmail.heading}}\n

\n

\n {{t.confirmEmail.intro}}\n

\n

\n \n {{t.confirmEmail.cta}}\n \n

\n

\n {{t.confirmEmail.fallback}}\n

\n

\n {{confirmUrl}}\n

\n

\n {{t.confirmEmail.expiry}}\n

\n{{> footer}}\n", "subject": "{{t.confirmEmail.subject}}\n", "text": "{{t.confirmEmail.heading}}\n\n{{t.confirmEmail.intro}}\n\n{{confirmUrl}}\n\n{{t.confirmEmail.expiry}}\n", "variables": [ "appName", "confirmUrl", "email" ], "hashes": { "en": { "contentHash": "fa3ef0d0ff652703fc0586c5c78b4c4820206a09bd47651c56a9f1c26a5d2cfd", "htmlHash": "c4b9403e096b1f6c439e120f701bf8cbc6bdba9fdb8e4f21fbad524961343a39", "subjectHash": "209d4ac15c8f7e70e14de04c81724beb00dac7f4ea450a6e8ce527faf4a0ad75", "textHash": "4b494839407f2817290b9b08103cd84103e53a6339ff4e4f24e71dd89ebb7d13" } } } } as const export type EmailTemplateName = keyof typeof EMAIL_TEMPLATES export type EmailLocale = keyof typeof EMAIL_LOCALES type TemplateVariableMap = { "confirm-email": { "appName"?: EmailTemplateValue "confirmUrl"?: EmailTemplateValue "email"?: EmailTemplateValue } } export type EmailTemplateVariables = TemplateVariableMap[TName] export type RenderEmailInput = { name: TName locale?: EmailLocale data: EmailTemplateVariables } export type RenderedEmail = { name: TName locale: EmailLocale subject: string html: string text?: string variables: ReadonlyArray hash: (typeof EMAIL_TEMPLATES)[TName]['hashes'][EmailLocale]['contentHash'] } function getNestedValue(source: Record, path: string): string { const segments = path.split('.') let current: unknown = source for (const segment of segments) { if (!current || typeof current !== 'object' || !(segment in current)) { return '' } current = (current as Record)[segment] } return typeof current === 'string' || typeof current === 'number' ? String(current) : '' } function applyTemplate(source: string, context: Record): string { return source.replace(/\{\{\s*([^}]+?)\s*\}\}/g, (_match, rawKey) => { const key = String(rawKey).trim() if (key === 'content') { return typeof context.content === 'string' ? context.content : '' } if (key.startsWith('>')) { return '' } return getNestedValue(context, key) }) } function renderTemplate(source: string, context: Record): string { let rendered = source for (let i = 0; i < 5; i += 1) { const next = applyTemplate(rendered, context) if (next === rendered) break rendered = next } return rendered } function renderPartial(name: string, context: Record): string { const partial = EMAIL_PARTIALS[name as keyof typeof EMAIL_PARTIALS] return partial ? renderTemplate(partial, context) : '' } export const EMAILS = EMAIL_TEMPLATES export function renderEmailTemplate( input: RenderEmailInput ): RenderedEmail { const locale = (input.locale ?? 'en') as EmailLocale const template = EMAIL_TEMPLATES[input.name] if (!template) { throw new Error(`Unknown email template: ${String(input.name)}`) } const strings = EMAIL_LOCALES[locale] if (!strings) { throw new Error(`Unknown email locale: ${String(locale)}`) } const data = (input.data ?? {}) as Record const appName = (typeof data.appName === 'string' && data.appName) || getNestedValue(EMAIL_THEME as Record, 'appName') const baseContext = { ...data, locale, theme: EMAIL_THEME, t: strings, appName, } const subject = renderTemplate(template.subject, baseContext).trim() const htmlWithPartials = template.html.replace( /\{\{\s*>\s*([a-zA-Z0-9-_/.]+)\s*\}\}/g, (_match, partialName) => `@@PARTIAL:${String(partialName).trim()}@@` ) let htmlBody = renderTemplate(htmlWithPartials, { ...baseContext, subject, }) const partialMatches = [...htmlBody.matchAll(/@@PARTIAL:([^@]+)@@/g)] for (const match of partialMatches) { const rendered = renderPartial(match[1], { ...baseContext, subject, }) htmlBody = htmlBody.replace(match[0], rendered) } const html = EMAIL_PARTIALS.layout ? renderTemplate(EMAIL_PARTIALS.layout, { ...baseContext, subject, content: htmlBody, }) : htmlBody const text = template.text ? renderTemplate(template.text, { ...baseContext, subject, }).trim() : undefined const hash = (template.hashes[locale]?.contentHash ?? '') as RenderedEmail['hash'] return { name: input.name, locale, subject, html, ...(text ? { text } : {}), variables: template.variables, hash, } }