← back to Coming Soon Template

scripts/build.js

128 lines

#!/usr/bin/env node
/**
 * Instantiates template.html per-domain from manifest.json → out/<domain>/index.html
 *
 * Usage:
 *   node scripts/build.js                    # build all
 *   node scripts/build.js abramsos.com       # build one
 *   node scripts/build.js --deploy           # also rsync to Kamatera /var/www/<domain>/
 *
 * Swap points (case-sensitive, double-curly):
 *   {{WORDMARK}} {{WORDMARK_INITIAL}} {{TAGLINE}} {{HERO_URL}} {{HERO_ALT}}
 *   {{DOMAIN}} {{PAGE_TITLE}} {{ACCENT_HEX}} {{YEAR}}
 */

'use strict';

const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');

const ROOT = path.resolve(__dirname, '..');
const TEMPLATE = path.join(ROOT, 'template.html');
const MANIFEST = path.join(ROOT, 'manifest.json');
const OUT = path.join(ROOT, 'out');

const args = process.argv.slice(2);
const deploy = args.includes('--deploy');
const onlyDomain = args.find(a => !a.startsWith('--'));

if (!fs.existsSync(TEMPLATE)) { console.error('template.html missing'); process.exit(1); }
if (!fs.existsSync(MANIFEST)) { console.error('manifest.json missing'); process.exit(1); }

const tpl = fs.readFileSync(TEMPLATE, 'utf8');
const manifest = JSON.parse(fs.readFileSync(MANIFEST, 'utf8'));
const year = manifest.year || new Date().getFullYear();

// Per-domain /admin/ is just a redirect to the central admin portal at
// admin.agentabrams.com. One auth surface, one allowlist, one login.
const ADMIN_TPL = `<!doctype html>
<html lang="en"><head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="robots" content="noindex,nofollow">
<meta http-equiv="refresh" content="0; url=https://admin.agentabrams.com/">
<title>redirecting to admin portal…</title>
<style>body{font:14px/1.6 system-ui;color:#57544c;background:#faf8f3;max-width:32em;margin:10em auto;padding:0 1.5em;text-align:center}a{color:#0f0e0c}</style>
</head><body>
<p>Redirecting to the admin portal…</p>
<p>If you are not redirected automatically, <a href="https://admin.agentabrams.com/">click here</a>.</p>
</body></html>
`;

function subsFor(entry) {
  return {
    WORDMARK: entry.wordmark,
    WORDMARK_INITIAL: entry.wordmark_initial || entry.wordmark.slice(0, 1),
    TAGLINE: entry.tagline,
    HERO_URL: entry.hero_url,
    HERO_ALT: entry.hero_alt || (entry.wordmark + ' coming soon'),
    DOMAIN: entry.domain,
    PAGE_TITLE: entry.page_title || (entry.wordmark + ' — coming soon'),
    ACCENT_HEX: entry.accent_hex || '#3a3a3a',
    YEAR: String(year),
  };
}

function render(source, subs) {
  let out = source;
  for (const [k, v] of Object.entries(subs)) {
    out = out.replaceAll('{{' + k + '}}', v);
  }
  return out;
}

function instantiate(entry) {
  return render(tpl, subsFor(entry));
}

const targets = onlyDomain
  ? manifest.domains.filter(d => d.domain === onlyDomain)
  : manifest.domains;

if (!targets.length) {
  console.error(onlyDomain ? `no manifest entry for ${onlyDomain}` : 'no domains in manifest');
  process.exit(1);
}

let built = 0;
for (const entry of targets) {
  const subs = subsFor(entry);
  const html = render(tpl, subs);
  const adminHtml = render(ADMIN_TPL, subs);
  const dir = path.join(OUT, entry.domain);
  fs.mkdirSync(path.join(dir, 'admin'), { recursive: true });
  fs.writeFileSync(path.join(dir, 'index.html'), html);
  fs.writeFileSync(path.join(dir, 'admin', 'index.html'), adminHtml);
  built++;
  process.stdout.write(`  built  ${entry.domain.padEnd(28)}  ${entry.tagline}\n`);
}
console.log(`\n✓ ${built} page${built === 1 ? '' : 's'} built into ${OUT}/`);

if (deploy) {
  const REMOTE = 'root@45.61.58.125';
  console.log(`\n→ rsync to ${REMOTE} (/var/www/<domain>/)…`);
  // Pre-create remote dirs in one ssh call (BSD rsync on macOS lacks --mkpath).
  const dirs = targets.map(t => '/var/www/' + t.domain).join(' ');
  try {
    execSync(`ssh -o StrictHostKeyChecking=no ${REMOTE} 'mkdir -p ${dirs}'`, { stdio: 'inherit' });
  } catch (e) {
    console.error('  ✗ remote mkdir failed:', e.message);
    process.exit(1);
  }
  let ok = 0, fail = 0;
  for (const entry of targets) {
    const src = path.join(OUT, entry.domain, 'index.html');
    const dest = REMOTE + ':/var/www/' + entry.domain + '/index.html';
    try {
      execSync(`rsync -az "${src}" "${dest}"`, { stdio: 'pipe' });
      process.stdout.write(`  deploy ${entry.domain}\n`);
      ok++;
    } catch (e) {
      console.error(`  ✗ ${entry.domain} — ${String(e.stderr || e.message).slice(0, 200)}`);
      fail++;
    }
  }
  console.log(`\n✓ deploy: ${ok} ok, ${fail} failed (vhosts must point at /var/www/<domain>/index.html)`);
}