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));