import { createHash } from "node:crypto"; import { copyFileSync, existsSync, readFileSync, realpathSync, writeFileSync } from "node:fs"; import { createRequire } from "node:module"; import { basename, dirname, join } from "node:path"; const [appDir, themePath, fontPath] = process.argv.slice(2); function packageDir(from, name) { for (const dir of createRequire(join(from, "/")).resolve.paths(name)) { const candidate = join(dir, name); if (existsSync(candidate)) return realpathSync(candidate); } throw new Error(`${name} not resolvable from ${from}`); } const pdsDir = packageDir(appDir, "@atproto/pds"); const providerDir = join(packageDir(pdsDir, "@atproto/oauth-provider"), "dist"); const uiManifestPath = createRequire(join(providerDir, "router/assets/assets.js")).resolve( "@atproto/oauth-provider-ui/bundle-manifest.json", ); const sha256 = (data) => createHash("sha256").update(data).digest("base64"); function patch(path, replacements, { all = false, append = "" } = {}) { let text = readFileSync(path, "utf8"); for (const [from, to] of replacements) { const count = text.split(from).length - 1; if (all ? count === 0 : count !== 1) throw new Error(`${path}: expected ${all ? "some" : "one"} ${from}, found ${count}`); text = text.replaceAll(from, () => to); } text += append; writeFileSync(path, text); return text; } patch(join(pdsDir, "dist/mailer/index.js"), [ ["logoUrl: branding.logo ?? DEFAULT_LOGO_URL,", "logoUrl: process.env.PDS_EMAIL_LOGO_URL ?? branding.logo ?? DEFAULT_LOGO_URL,"], ["markUrl: branding.logo ?? DEFAULT_MARK_URL,", "markUrl: process.env.PDS_EMAIL_LOGO_URL ?? branding.logo ?? DEFAULT_MARK_URL,"], ]); patch(join(providerDir, "oauth-provider.js"), [["ttl: 600e3", "ttl: 1e3"]], { all: true }); patch(join(providerDir, "lib/csp/index.js"), [[" 'default-src',\n", " 'default-src',\n 'font-src',\n"]]); patch(join(providerDir, "router/assets/assets.js"), [ [`'connect-src': ["'self'"],`, `'connect-src': ["'self'"],\n 'font-src': ["'self'"],`], ]); patch(join(providerDir, "router/assets/assets.js"), [["'img-src': ['https:'],", "'img-src': ['https:', 'data:'],"]]); patch(join(providerDir, "router/assets/assets-manifest.js"), [ ["[null, 'document', 'style', 'script']", "[null, 'document', 'style', 'script', 'font']"], ["'public, max-age=31536000, immutable'", "'no-cache'"], ]); const uiDir = dirname(uiManifestPath); const manifest = JSON.parse(readFileSync(uiManifestPath, "utf8")); const styles = Object.entries(manifest).filter(([, asset]) => asset.mime === "text/css"); if (styles.length === 0) throw new Error(`${uiManifestPath}: no stylesheet in manifest`); const theme = readFileSync(themePath, "utf8"); for (const [filename, asset] of styles) { if (asset.data) throw new Error(`${filename}: inlined in manifest`); const css = patch( join(uiDir, filename), [ ["--radius:.625rem", "--radius:.5rem"], ["calc(var(--radius) + 4px)", "var(--radius)"], ["calc(var(--radius) - 2px)", "var(--radius)"], ["calc(var(--radius) - 4px)", "var(--radius)"], ], { all: true, append: theme }, ); asset.sha256 = sha256(css); } const scripts = [ [/flag:`[\u{1F1E6}-\u{1F1FF}]{2}`/gu, "flag:``"], [ /(side:\w+=`bottom`,sideOffset:\w+=)4(,align:\w+=)`center`(,alignOffset:\w+=0,alignItemWithTrigger:\w+=)!0/g, "$18$2`start`$3!1", ], ]; const hits = scripts.map(() => 0); for (const [filename, asset] of Object.entries(manifest)) { if (asset.type !== "chunk") continue; const path = join(uiDir, filename); let js = readFileSync(path, "utf8"); scripts.forEach(([pattern, replacement], i) => { js = js.replace(pattern, (...match) => { hits[i]++; return replacement.replace(/\$(\d)/g, (_, n) => match[n]); }); }); writeFileSync(path, js); asset.sha256 = sha256(js); } scripts.forEach(([pattern], i) => { if (hits[i] === 0) throw new Error(`${uiDir}: ${pattern} not found`); }); const font = basename(fontPath); copyFileSync(fontPath, join(uiDir, font)); manifest[font] = { type: "asset", mime: "font/woff2", sha256: sha256(readFileSync(fontPath)) }; writeFileSync(uiManifestPath, JSON.stringify(manifest));