ConusAI vendor v0.1.0-dev.20261027+9b1131fb
ci / gate (push) Waiting to run

This commit is contained in:
ConusAI vendor v0.1.0-dev.20261027+9b1131fb
2026-10-27 18:05:17 +00:00
commit 3102273ec7
538 changed files with 80203 additions and 0 deletions
+151
View File
@@ -0,0 +1,151 @@
// The brand as a share image and an email show it (spec §10.2, §10.3): the OG image of an app page or an
// organization's page, and a transactional email's header. Both draw the same thing, a name and a logo, so
// both start from `shareBrand`, which decides them once.
//
// The name is the organization's display name, else its own name, else the app brand's. The logo is the one
// `resolveLogo` picks for the dark page both of them sit on (the organization's own, the organization's
// other one when it has only one, or the app brand's), and it goes through the kit's logo checks wherever it
// came from: an organization's logo as stored, the app brand's as a file of apps/web/public.
//
// A logo that cannot be drawn does not stop a card or a mail: the app brand's logo stands in, and the reason
// is the kit's, naming a rule and not the file.
import { tokens } from "@conusai-kit/brand"
import { emailHeader, emailHeaderLogo } from "@conusai-kit/brand/email"
import type { LogoSource } from "@conusai-kit/brand/logo/check"
import { renderOgImage } from "@conusai-kit/brand/og"
import { type BrandView, type LogoVariant, resolveLogo } from "../contract/index.js"
/** What the app brand (config/src/brand.ts) contributes: its name, and the paths of its two logos. */
export interface AppIdentity {
readonly name: string
readonly logo: Readonly<Record<LogoVariant, string>>
}
/** Where the logos are read from: the organization's stored logo, and a file of the app's public folder. */
export interface ShareReaders {
tenantLogo(variant: LogoVariant): Promise<LogoSource>
appLogo(path: string): Promise<LogoSource>
}
export interface ShareBrand {
/** The name the card or the mail carries. */
readonly name: string
/** The logo to draw; an organization's when it has one. */
readonly logo: LogoSource
/** The app brand's own logo, which stands in when `logo` cannot be drawn. */
readonly fallback: LogoSource
}
export async function shareBrand(input: {
readonly app: AppIdentity
/** The organization the card or mail is for, or null for the app's own. */
readonly tenant: { readonly name: string; readonly brand: BrandView } | null
readonly readers: ShareReaders
/** The page the logo sits on; both a share card and a mail header are dark. */
readonly theme?: LogoVariant
}): Promise<ShareBrand> {
const theme = input.theme ?? "dark"
const choice = resolveLogo(input.tenant?.brand ?? null, input.app.logo, theme)
const name = input.tenant?.brand.displayName ?? input.tenant?.name ?? input.app.name
if (choice.source === "app") {
const logo = await input.readers.appLogo(input.app.logo[theme])
return { name, logo, fallback: logo }
}
const logo = await input.readers.tenantLogo(choice.variant)
// The app brand's logo only stands in; an organization with a good logo is not stopped by a missing app file.
const fallback = await input.readers.appLogo(input.app.logo[theme]).catch(() => logo)
return { name, logo, fallback }
}
/** True for the error the kit's renderers throw when the logo they were given is refused. */
const isLogoRefusal = (error: unknown): boolean =>
error instanceof Error && /the logo cannot be used/.test(error.message)
/** Tries the chosen logo, then the app brand's; any other failure is not about the logo and is thrown. */
async function withLogo<T>(share: ShareBrand, draw: (logo: LogoSource) => Promise<T>): Promise<T> {
try {
return await draw(share.logo)
} catch (error) {
if (share.logo === share.fallback || !isLogoRefusal(error)) throw error
return draw(share.fallback)
}
}
export interface ShareText {
readonly title: string
readonly subtitle?: string
readonly footer?: string
}
/** The share image of a page: 1200x630 PNG in the brand's treatment, the organization's logo if it has one. */
export function renderShareImage(share: ShareBrand, text: ShareText): Promise<Uint8Array> {
return withLogo(share, (logo) => renderOgImage({ ...text, logo }))
}
export interface EmailContent {
readonly subject: string
/** The body, one string per paragraph, as plain text: it is escaped, never taken as markup. */
readonly paragraphs: readonly string[]
readonly locale?: string
}
export interface EmailAttachment {
readonly cid: string
readonly contentType: "image/png"
readonly content: Uint8Array
}
export interface ComposedEmail {
readonly subject: string
readonly html: string
readonly text: string
/** The logo, inline: the HTML refers to it as `cid:`, so the mail names no host. */
readonly attachments: readonly EmailAttachment[]
}
/** The port an app implements to send mail (SMTP, a provider's API): this module composes and does not deliver. */
export interface MailTransport {
send(message: ComposedEmail & { readonly to: string }): Promise<void>
}
const escapeHtml = (text: string): string => text.replace(/[&<>"']/g, (char) => `&#${char.charCodeAt(0)};`)
const LINE_BREAK = /[\r\n]/
/** A transactional email in the brand: the header with the logo, the paragraphs, and the name at the foot. */
export async function composeEmail(share: ShareBrand, content: EmailContent): Promise<ComposedEmail> {
if (LINE_BREAK.test(content.subject)) throw new Error("composeEmail: the subject is one line")
if (content.paragraphs.length === 0) throw new Error("composeEmail: a mail has at least one paragraph")
const logo = await withLogo(share, (source) => emailHeaderLogo(source))
const dark = tokens.color.dark
const fonts = `'${tokens.typography.families.sans}', ${tokens.typography.fallbacks.sans}`
const cell = `padding:24px 32px;font-family:${fonts};color:${dark.foreground};line-height:${tokens.typography.roles.body.lineHeight};`
const body = content.paragraphs
.map((paragraph) => `<p style="margin:0 0 16px;">${escapeHtml(paragraph)}</p>`)
.join("")
const html =
`<!doctype html><html lang="${escapeHtml(content.locale ?? "en")}"><head><meta charset="utf-8"><meta name="color-scheme" content="dark"></head>` +
`<body style="margin:0;background-color:${dark.background};">` +
emailHeader({ alt: share.name, aspect: logo.aspect }) +
`<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="background-color:${dark.background};">` +
`<tr><td style="${cell}">${body}</td></tr>` +
`<tr><td style="${cell}color:${dark.text70};font-size:14px;">${escapeHtml(share.name)}</td></tr></table></body></html>`
return {
subject: `${content.subject} - ${share.name}`,
html,
text: `${content.paragraphs.join("\n\n")}\n\n${share.name}\n`,
attachments: [{ cid: logo.cid, contentType: logo.contentType, content: logo.content }],
}
}
/** Composes the mail and hands it to the app's transport. `to` is one address and cannot carry a header. */
export async function sendEmail(
transport: MailTransport,
share: ShareBrand,
to: string,
content: EmailContent,
): Promise<void> {
if (LINE_BREAK.test(to) || !/^[^\s@,;<>]+@[^\s@,;<>]+$/.test(to))
throw new Error("sendEmail: to is one email address")
await transport.send({ ...(await composeEmail(share, content)), to })
}