#!/usr/bin/env node import { readFile } from 'node:fs/promises'; import path from 'node:path'; const cwd = process.cwd(); const args = process.argv.slice(2); function parseArgs(argv) { const out = {}; for (let i = 0; i < argv.length; i += 1) { const token = argv[i]; if (!token.startsWith('--')) continue; const key = token.slice(2); const next = argv[i + 1]; if (!next || next.startsWith('--')) { out[key] = 'true'; continue; } out[key] = next; i += 1; } return out; } const parsedArgs = parseArgs(args); const configPath = path.resolve( cwd, parsedArgs['config'] || process.env.SIN_GITHUB_APP_ROUTING_PATH || './config/github-app-routing.local.json', ); function firstString(...values) { for (const value of values) { if (typeof value === 'string' && value.trim()) return value.trim(); } return null; } function readEnv(name) { if (!name) return null; const value = process.env[name]; return typeof value === 'string' && value.trim() ? value.trim() : null; } function parseList(value) { if (!value || typeof value !== 'string') return []; return value .split(',') .map((item) => item.trim()) .filter(Boolean); } function toRoute(entry, defaultWebhookPath) { const appId = firstString(String(entry.appId || '').trim(), readEnv(entry.appIdEnv)); const clientId = firstString(entry.clientId, readEnv(entry.clientIdEnv)); const routePath = firstString(entry.routePath, entry.webhookPath, defaultWebhookPath || '/github/webhook'); const agentSlug = firstString(entry.agentSlug, entry.preferredAgentSlug, entry.appSlug); const privateKey = firstString(entry.privateKeyPem, readEnv(entry.privateKeyEnv)); const webhookSecret = firstString(entry.webhookSecret, readEnv(entry.webhookSecretEnv)); return { agentSlug, appSlug: firstString(entry.appSlug), botName: firstString(entry.botName), routePath, appId, clientId, privateKeyRef: firstString(entry.privateKeyEnv, entry.privateKeyPath, entry.privateKeyPem ? 'inline' : null), webhookSecretRef: firstString(entry.webhookSecretEnv, entry.webhookSecretPath, entry.webhookSecret ? 'inline' : null), privateKeyResolved: Boolean(privateKey), webhookSecretResolved: Boolean(webhookSecret), }; } async function main() { const raw = await readFile(configPath, 'utf8'); const config = JSON.parse(raw); const defaultWebhookPath = firstString(config.defaultWebhookPath, '/github/webhook'); const agentsFilter = new Set(parseList(parsedArgs.agents || parsedArgs.agent)); const configuredOnly = parsedArgs['configured-only'] === 'true'; const apps = (Array.isArray(config.apps) ? config.apps.map((entry) => toRoute(entry, defaultWebhookPath)) : []).filter((app) => { const matchesAgent = agentsFilter.size === 0 || agentsFilter.has(app.agentSlug || '') || agentsFilter.has(app.appSlug || ''); const isConfigured = !configuredOnly || Boolean(app.appId || app.clientId || app.privateKeyResolved || app.webhookSecretResolved); return matchesAgent && isConfigured; }); const missing = apps.flatMap((app) => { const failures = []; if (!app.agentSlug) failures.push('agentSlug'); if (!app.appId) failures.push('appId'); if (!app.clientId) failures.push('clientId'); if (!app.privateKeyResolved) failures.push('privateKey'); if (!app.webhookSecretResolved) failures.push('webhookSecret'); return failures.length ? [{ agentSlug: app.agentSlug || app.appSlug || 'unknown', missing: failures }] : []; }); console.log(JSON.stringify({ ok: missing.length === 0, configPath, defaultWebhookPath, filters: { agents: [...agentsFilter], configuredOnly, }, apps, missing, }, null, 2)); if (missing.length > 0) process.exitCode = 1; } main().catch((error) => { console.error(error instanceof Error ? error.message : String(error)); process.exit(1); });