← 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}`));