← back to Site Factory

critic/checks/architect.js

81 lines

// architect.js — site structure check.
// For Next.js site dirs (paths containing /app/ at depth 1), verify the
// Steve-standard scaffolding is present.
const fs = require('fs');
const path = require('path');

const SOURCE = 'architect';

function isNextSiteRoot(p) {
  // a "site root" has an `app/` subdirectory directly under it
  try {
    const st = fs.statSync(path.join(p, 'app'));
    return st.isDirectory();
  } catch {
    return false;
  }
}

// expand paths to a list of candidate site roots
function findSiteRoots(paths) {
  const roots = new Set();
  for (const p of paths || []) {
    if (!p) continue;
    let stat;
    try { stat = fs.statSync(p); } catch { continue; }
    if (!stat.isDirectory()) continue;
    if (isNextSiteRoot(p)) {
      roots.add(p);
      continue;
    }
    // also scan one level deep — the orchestrator may pass the parent
    let entries = [];
    try { entries = fs.readdirSync(p, { withFileTypes: true }); } catch {}
    for (const e of entries) {
      if (!e.isDirectory()) continue;
      const sub = path.join(p, e.name);
      if (isNextSiteRoot(sub)) roots.add(sub);
    }
  }
  return [...roots];
}

function fileExists(p) {
  try { return fs.statSync(p).isFile(); } catch { return false; }
}

const REQUIRED = [
  { rel: 'app/layout.tsx',                 severity: 'high',   title: 'Missing app/layout.tsx',
    fix: 'Create a Next.js app-router root layout at app/layout.tsx (html/body, font, providers).' },
  { rel: 'components/HamburgerPanel.tsx',  severity: 'medium', title: 'Missing components/HamburgerPanel.tsx',
    fix: 'Add the standard top-right hamburger → right-slide panel component (About / Contact / What it does / Who it’s for / Pricing).' },
  { rel: '.env.example',                   severity: 'medium', title: 'Missing .env.example',
    fix: 'Add .env.example documenting required env vars (no real secrets). Helps new envs / CI / Vercel deploys.' },
  { rel: 'tailwind.config.ts',             severity: 'low',    title: 'Missing tailwind.config.ts',
    fix: 'Add tailwind.config.ts wired to the site palette tokens (primary/secondary/accent/surface/ink).' },
];

async function run({ paths /* , screenshots, domain */ }) {
  const findings = [];
  const roots = findSiteRoots(paths);

  for (const root of roots) {
    for (const req of REQUIRED) {
      const target = path.join(root, req.rel);
      if (!fileExists(target)) {
        findings.push({
          source: SOURCE,
          severity: req.severity,
          title: `${req.title} at ${root}`,
          detail: `Expected ${target} not found.`,
          suggested_fix: req.fix,
        });
      }
    }
  }

  return findings;
}

module.exports = { run };