Ctrl K
ComunidadGuías…

scripts/brand/sync.mjs

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
#!/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 `<path ${attrs} transform="${iconStarTransform(scale)}" d="${starPath(stroke)}"/>`;
  };
  const wordmark = (color, extra = "") =>
    `<svg xmlns="http://www.w3.org/2000/svg" viewBox="${WORDMARK.viewBox}" role="img" aria-label="Multica"${extra}><g fill="none" stroke="${color}" stroke-width="${WORDMARK_STROKE}">${WORDMARK.letters.map((l) => `<path d="${l.d}"/>${l.star ? `<path d="${l.star}" fill="${color}" stroke="none"/>` : ""}`).join("")}</g></svg>`;
  const tile = (px, { bleed = false, bg = INK, fg = PAPER } = {}) =>
    `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="${px}" height="${px}"><rect width="100" height="100"${bleed ? "" : ` rx="${ICON_TILE_RADIUS}"`} fill="${bg}"/>${star(px, `fill="${fg}"`)}</svg>`;

  const [, , vbW, vbH] = WORDMARK.viewBox.split(" ").map(Number);
  return {
    tile,
    wordmark,
    favicon: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" role="img" aria-label="Multica"><style>.t{fill:${INK}}.m{fill:${PAPER}}@media (prefers-color-scheme:dark){.t{fill:${PAPER}}.m{fill:${INK}}}</style><rect class="t" width="100" height="100" rx="${ICON_TILE_RADIUS}"/>${star(16, `class="m"`)}</svg>\n`,
    pwaIcon: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" role="img" aria-label="Multica">
  <!--
    Full-bleed source for the generated home-screen icons (apple-touch-icon.png,
    icon-maskable-512.png). Full-bleed because both surfaces mask the artwork
    themselves: iOS rounds the corners, Android crops to the launcher shape.
    Geometry is packages/ui/lib/multica-brand.ts (starPath, iconStar).
  --><rect width="100" height="100" fill="${INK}"/>${star(512, `fill="${PAPER}"`)}</svg>\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 class="brand">)<svg[^]*?<\/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");
}