#!/usr/bin/env node // Writes every text copy of the brand geometry from packages/ui/lib/multica-brand.ts: // favicons, the PWA icon source, the README wordmark SVGs, the mobile wordmark and // the inline SVG on the `multica login` callback page. // // node scripts/brand/sync.mjs rewrite the copies // node scripts/brand/sync.mjs --check exit 1 if any copy drifted (CI) // // Dependency-free on purpose: CI runs it without installing node_modules. import { readFileSync, realpathSync, writeFileSync } from "node:fs"; import { dirname, join, resolve } from "node:path"; import { fileURLToPath, pathToFileURL } from "node:url"; export const INK = "#0b0b0c"; export const PAPER = "#ffffff"; const CLI_WORDMARK_HEIGHT = 19.5; export async function loadBrand(root) { return import(pathToFileURL(join(root, "packages/ui/lib/multica-brand.ts")).href); } export function svgs(brand) { const { WORDMARK, WORDMARK_STROKE, ICON_TILE_RADIUS, starPath, iconStar, iconStarTransform } = brand; const star = (px, attrs) => { const { scale, stroke } = iconStar(px); return ``; }; const wordmark = (color, extra = "") => `${WORDMARK.letters.map((l) => `${l.star ? `` : ""}`).join("")}`; const tile = (px, { bleed = false, bg = INK, fg = PAPER } = {}) => `${star(px, `fill="${fg}"`)}`; const [, , vbW, vbH] = WORDMARK.viewBox.split(" ").map(Number); return { tile, wordmark, favicon: `${star(16, `class="m"`)}\n`, pwaIcon: ` ${star(512, `fill="${PAPER}"`)}\n`, cliWordmark: wordmark("currentColor", ` height="${CLI_WORDMARK_HEIGHT}" width="${Math.round((CLI_WORDMARK_HEIGHT * vbW) / vbH)}"`), }; } function mobileBlock({ WORDMARK, WORDMARK_STROKE }) { const [x, y, w, h] = WORDMARK.viewBox.split(" ").map(Number); const star = WORDMARK.letters.find((l) => l.star).star; return [ `const VIEW_BOX = { x: ${x}, y: ${y}, w: ${w}, h: ${h} };`, `const STROKE = ${WORDMARK_STROKE};`, "const LETTERS = [", ...WORDMARK.letters.map((l) => ` "${l.d}",`), "];", "/** The star on the i, filled. */", "const STAR =", ` "${star}";`, ].join("\n"); } const MOBILE_BEGIN = "// BEGIN GENERATED by scripts/brand/sync.mjs from WORDMARK in packages/ui/lib/multica-brand.ts\n"; const MOBILE_END = "\n// END GENERATED"; const CLI_SVG = /(
)(<\/div>)/; function splice(text, file, re, body) { if (!re.test(text)) throw new Error(`${file}: cannot find the generated wordmark`); return text.replace(re, (_, open, close) => open + body + close); } /** Every synced file as `[path relative to root, expected content]`. */ export async function targets(root) { const brand = await loadBrand(root); const s = svgs(brand); const read = (rel) => readFileSync(join(root, rel), "utf8"); const mobile = "apps/mobile/components/brand/multica-logo.tsx"; const cli = "server/cmd/multica/cmd_auth.go"; const between = new RegExp(`(${escapeRe(MOBILE_BEGIN)})[^]*?(${escapeRe(MOBILE_END)})`); return [ ["apps/web/public/favicon.svg", s.favicon], ["apps/docs/app/icon.svg", s.favicon], ["apps/web/public/icons/icon.svg", s.pwaIcon], ["docs/assets/logo-light.svg", s.wordmark(INK) + "\n"], ["docs/assets/logo-dark.svg", s.wordmark(PAPER) + "\n"], [mobile, splice(read(mobile), mobile, between, mobileBlock(brand))], [cli, splice(read(cli), cli, CLI_SVG, s.cliWordmark)], ]; } function escapeRe(s) { return s.replace(/[.*+?^${}()|[\]\\/]/g, "\\$&"); } export async function sync(root, { check = false } = {}) { const drifted = []; for (const [rel, want] of await targets(root)) { const file = join(root, rel); let have = null; try { have = readFileSync(file, "utf8"); } catch {} if (have === want) continue; drifted.push(rel); if (!check) writeFileSync(file, want); } return drifted; } if (process.argv[1] && realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url))) { const args = process.argv.slice(2); const check = args.includes("--check"); const rootAt = args.indexOf("--root"); const root = rootAt >= 0 ? resolve(args[rootAt + 1]) : resolve(dirname(fileURLToPath(import.meta.url)), "../.."); const drifted = await sync(root, { check }); if (check && drifted.length) { console.error(`Out of sync with packages/ui/lib/multica-brand.ts:\n${drifted.map((f) => ` ${f}`).join("\n")}\nRun: node scripts/brand/sync.mjs`); process.exit(1); } console.log(drifted.length ? `updated ${drifted.join(", ")}` : "brand copies in sync"); }