mirror of
https://github.com/hanzoai/trust.git
synced 2026-10-30 14:54:58 +00:00
122 lines
5.2 KiB
JavaScript
122 lines
5.2 KiB
JavaScript
#!/usr/bin/env node
|
|
// Generates components/marks.ts — the monochrome mark worn by each party the
|
|
// subprocessor list names.
|
|
//
|
|
// Run: node scripts/marks.mjs
|
|
//
|
|
// ONE TABLE below is the whole provenance record: every mark id resolves to
|
|
// exactly one source, and the only source is simple-icons (CC0-1.0), the
|
|
// canonical monochrome brand set. A party with no entry here carries no mark
|
|
// and renders the monogram plate instead. That is deliberate and not a gap:
|
|
// simple-icons does not carry every party's mark, and a mark it does not carry
|
|
// is one we hold no redistribution licence for. Pin the CURRENT release and
|
|
// take what it ships — reaching back to an older one to recover a mark its
|
|
// owner had removed is not a licence, and drawing something that looks like it
|
|
// is worse. A uniform monogram is the honest fallback; a look-alike is not.
|
|
//
|
|
// This is hanzo.ai/scripts/gen-marks.mjs's policy, applied to a different
|
|
// consumer. The OUTPUT differs and the reason is the deployment: that page
|
|
// serves files out of public/, and this one is a static export whose enforced
|
|
// policy is `img-src 'self' data:` with no origin to fetch from and no wish to
|
|
// add one. So the geometry is emitted as a TypeScript module of path bodies
|
|
// and inlined — the same bytes, carried a way the page can actually render.
|
|
|
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
import { fileURLToPath } from "node:url";
|
|
import { dirname, join } from "node:path";
|
|
|
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
const root = join(here, "..");
|
|
const ICONS = join(root, "node_modules", "simple-icons", "icons");
|
|
|
|
// mark id -> simple-icons slug. The id is what a party names in
|
|
// subprocessors.json; the slug is what the set files it under, and the two are
|
|
// not always the same word, which is the reason this is a table and not a
|
|
// derivation.
|
|
const SOURCES = {
|
|
digitalocean: "digitalocean",
|
|
cloudflare: "cloudflare",
|
|
openrouter: "openrouter",
|
|
anthropic: "anthropic",
|
|
deepseek: "deepseek",
|
|
google: "google",
|
|
github: "github",
|
|
square: "square",
|
|
stripe: "stripe",
|
|
googleads: "googleads",
|
|
meta: "meta",
|
|
};
|
|
|
|
// Parties we deliberately leave to the monogram, and the reason. Recorded so
|
|
// the absence reads as a decision — the next person to wonder why Slack has no
|
|
// mark finds the answer here rather than adding one from somewhere else.
|
|
const MONOGRAM = {
|
|
slack: "simple-icons no longer carries it",
|
|
openai: "simple-icons does not carry it",
|
|
twilio: "simple-icons does not carry it",
|
|
fireworks: "simple-icons does not carry it",
|
|
amazon: "the entry is about a party nothing reaches; a mark would dress it up",
|
|
};
|
|
|
|
if (!existsSync(ICONS)) {
|
|
console.error(
|
|
`marks: ${ICONS} is not there.\n` +
|
|
" simple-icons is the one source and it is a devDependency; install first.",
|
|
);
|
|
process.exit(1);
|
|
}
|
|
|
|
// The outline data, and only that. The set draws every mark as filled paths on
|
|
// a 24-unit box, so the `d` attributes ARE the mark — the element around them
|
|
// carries a title and a fill this page supplies itself. Emitting the geometry
|
|
// rather than the markup means nothing parses XML in a browser to draw a logo.
|
|
const outline = (slug) => {
|
|
const from = join(ICONS, `${slug}.svg`);
|
|
if (!existsSync(from)) throw new Error(`mark "${slug}": source not found — ${from}`);
|
|
const svg = readFileSync(from, "utf8");
|
|
const d = [...svg.matchAll(/<path\b[^>]*\bd="([^"]+)"/g)].map((m) => m[1]);
|
|
if (!d.length) throw new Error(`mark "${slug}": no path geometry in ${from}`);
|
|
// Anything but a path would be dropped silently and the mark would render
|
|
// wrong rather than not at all, which is the worse failure.
|
|
const other = svg.replace(/<title>[\s\S]*?<\/title>/, "").match(/<(circle|rect|polygon|ellipse|line|polyline)\b/);
|
|
if (other) throw new Error(`mark "${slug}": carries a ${other[1]}, which this emitter drops`);
|
|
return d;
|
|
};
|
|
|
|
const version = JSON.parse(
|
|
readFileSync(join(root, "node_modules", "simple-icons", "package.json"), "utf8"),
|
|
).version;
|
|
|
|
const rows = Object.keys(SOURCES)
|
|
.sort()
|
|
.map((id) => ` ${id}: ${JSON.stringify(outline(SOURCES[id]))},`)
|
|
.join("\n");
|
|
|
|
const out = `// GENERATED by scripts/marks.mjs — do not edit. Run that script to change it.
|
|
//
|
|
// Canonical geometry, taken verbatim from simple-icons ${version} (CC0-1.0),
|
|
// which is the ONE source. Each entry is the mark's path data on a 24-unit
|
|
// box, so every mark shares one coordinate space and one optical weight.
|
|
//
|
|
// A party absent from this table carries no mark and renders the monogram
|
|
// plate. That is the licence boundary, not an oversight: the set does not
|
|
// carry every party's mark, and one it does not carry is one we may not
|
|
// redistribute. Never hand-draw a substitute — a plate that says nothing is
|
|
// honest, and a look-alike is a claim about somebody else's brand.
|
|
${Object.keys(MONOGRAM)
|
|
.sort()
|
|
.map((id) => `// ${id.padEnd(13)} monogram — ${MONOGRAM[id]}`)
|
|
.join("\n")}
|
|
|
|
export const MARKS: Record<string, string[]> = {
|
|
${rows}
|
|
};
|
|
`;
|
|
|
|
mkdirSync(join(root, "components"), { recursive: true });
|
|
writeFileSync(join(root, "components", "marks.ts"), out);
|
|
console.log(
|
|
`wrote components/marks.ts — ${Object.keys(SOURCES).length} marks from simple-icons ${version}, ` +
|
|
`${Object.keys(MONOGRAM).length} deliberate monograms`,
|
|
);
|