← back to Ga4 Fleet

build-repo-map.mjs

62 lines

#!/usr/bin/env node
// Best-effort domain -> local repo path map, for inject-gtag.mjs --all.
// Sources: domain-app-map.txt (domain => proxy:port:pm2name | static:root) + the set of
// dirs under ~/Projects. Heuristic: a pm2 name usually corresponds to ~/Projects/<name>,
// with common suffix variants stripped. Emits repo-map.json + a coverage report.
// Pure local filesystem read — no credentials, no network.
import { readFileSync, writeFileSync, existsSync, readdirSync, statSync } from 'node:fs';
import { join } from 'node:path';

const DIR = '/Users/macstudio3/Projects/ga4-fleet';
const PROJ = `${process.env.HOME}/Projects`;

const projDirs = new Set(readdirSync(PROJ).filter((f) => { try { return statSync(join(PROJ, f)).isDirectory(); } catch { return false; } }));
const publicDomains = new Set(readFileSync(`${DIR}/domains-public.txt`, 'utf8').split('\n').map((s) => s.trim()).filter(Boolean));

// candidate repo names for an app identifier (pm2 name or static-root basename)
function candidates(name) {
  const n = name.toLowerCase();
  const out = new Set([name, n]);
  for (const suf of ['-viewer', '-site', '-website', '-landing', '-internal', '-app', '-8080', '-9080', '-web']) {
    if (n.endsWith(suf)) out.add(n.slice(0, -suf.length));
  }
  out.add(n.replace(/-viewer$|-site$|-internal$/, ''));
  return [...out];
}

function resolveRepo(appId) {
  for (const c of candidates(appId)) {
    if (projDirs.has(c)) return join(PROJ, c);
    // case-insensitive match
    for (const d of projDirs) if (d.toLowerCase() === c.toLowerCase()) return join(PROJ, d);
  }
  return null;
}

const map = {};
const unmapped = [];
for (const line of readFileSync(`${DIR}/domain-app-map.txt`, 'utf8').split('\n')) {
  const m = line.match(/^(.+?)\s+=>\s+(.+)$/);
  if (!m) continue;
  const domains = m[1].split(',').map((d) => d.trim().replace(/^www\./, '')).filter(Boolean);
  const kind = m[2].trim();
  let appId = null;
  const pm = kind.match(/^proxy:\d+:(.+)$/); const st = kind.match(/^static:.*\/([^/]+)$/);
  if (pm && pm[1] !== '?') appId = pm[1];
  else if (st) appId = st[1];
  const repo = appId ? resolveRepo(appId) : null;
  for (const d of domains) {
    if (!publicDomains.has(d)) continue;      // only GA4-worthy domains
    if (repo) map[d] = repo; else unmapped.push(`${d} (app=${appId || 'unknown'})`);
  }
}

writeFileSync(`${DIR}/repo-map.json`, JSON.stringify(map, null, 2));
const mapped = Object.keys(map).length;
console.log(`repo-map.json written: ${mapped} domains mapped / ${publicDomains.size} public`);
console.log(`unmapped: ${unmapped.length}`);
console.log('sample mapped:');
Object.entries(map).slice(0, 10).forEach(([d, r]) => console.log(`  ${d} -> ${r.replace(process.env.HOME, '~')}`));
console.log('sample unmapped (need manual repo, or served elsewhere):');
unmapped.slice(0, 10).forEach((u) => console.log(`  ${u}`));