UNPKG

aiwg

Version:

Deployment tool and support utility for AI context. Copies agents, skills, commands, rules, and behaviors into the paths each AI platform reads (Claude Code, Codex, Copilot, Cursor, Warp, OpenClaw, and 6 more) so one source of truth works across 10 platfo

113 lines (99 loc) 4.18 kB
#!/usr/bin/env node /** * Runtime-dependency budget gate. * * Counts direct runtime dependencies declared in package.json and fails * if the count exceeds the budget. Optional flag `--all` includes the * full transitive dependency graph via `npm ls --prod --all --json`. * * Rationale: a CLI should be careful about what it ships. Every new * runtime dep slows cold start, bloats install size, and extends the * supply-chain attack surface. This gate forces an explicit decision * when the number grows. * * Budgets (overridable via env): * AIWG_DIRECT_DEPS_BUDGET — default 15 (direct runtime deps) * AIWG_TRANSITIVE_DEPS_BUDGET — default 250 (transitive, when --all set) * * Usage: * node tools/cli/check-dep-budget.mjs # direct count only (fast) * node tools/cli/check-dep-budget.mjs --all # full tree (slow) * node tools/cli/check-dep-budget.mjs --verbose */ import { readFileSync } from 'fs'; import { execSync } from 'child_process'; import { fileURLToPath } from 'url'; import path from 'path'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const REPO_ROOT = path.resolve(__dirname, '..', '..'); const DIRECT_BUDGET = parseIntEnv('AIWG_DIRECT_DEPS_BUDGET', 15); const TRANSITIVE_BUDGET = parseIntEnv('AIWG_TRANSITIVE_DEPS_BUDGET', 250); const includeTransitive = process.argv.includes('--all'); const verbose = process.argv.includes('--verbose'); function parseIntEnv(name, def) { const raw = process.env[name]; const n = raw ? parseInt(raw, 10) : NaN; return Number.isFinite(n) && n > 0 ? n : def; } function countTransitive(tree) { // Walk an npm ls tree, counting unique package names. The root itself // doesn't count as a dep of the user project. const seen = new Set(); function walk(node) { for (const [name, child] of Object.entries(node.dependencies ?? {})) { if (!child || seen.has(name)) continue; seen.add(name); if (typeof child === 'object') walk(child); } } walk(tree); return seen.size; } const pkg = JSON.parse(readFileSync(path.join(REPO_ROOT, 'package.json'), 'utf-8')); const directDeps = Object.keys(pkg.dependencies ?? {}); const directCount = directDeps.length; if (verbose) { console.log(`Direct runtime deps (${directCount}, budget ${DIRECT_BUDGET}):`); for (const d of directDeps) console.log(` - ${d}`); console.log(''); } const failures = []; if (directCount > DIRECT_BUDGET) { failures.push(`Direct runtime deps ${directCount} exceeds budget of ${DIRECT_BUDGET}`); } let transitiveCount; if (includeTransitive) { try { const raw = execSync('npm ls --prod --all --json', { cwd: REPO_ROOT, stdio: ['ignore', 'pipe', 'pipe'], encoding: 'utf-8', maxBuffer: 32 * 1024 * 1024, }); const tree = JSON.parse(raw); transitiveCount = countTransitive(tree); if (verbose) console.log(`Transitive deps: ${transitiveCount} (budget ${TRANSITIVE_BUDGET})`); if (transitiveCount > TRANSITIVE_BUDGET) { failures.push(`Transitive deps ${transitiveCount} exceeds budget of ${TRANSITIVE_BUDGET}`); } } catch (err) { // npm ls exits non-zero on optional/peer warnings; recover and parse // stdout if available. If parsing still fails, warn but don't fail // the gate — the direct-deps check is the primary signal. const message = err instanceof Error ? err.message : String(err); console.warn(`check-dep-budget: transitive check skipped (${message.slice(0, 80)}...)`); } } if (failures.length > 0) { console.error('check-dep-budget: FAILED'); for (const f of failures) console.error(` - ${f}`); console.error(''); console.error('Every new runtime dep slows cold start, bloats install size, and'); console.error('extends the supply-chain attack surface. If a new dep is genuinely'); console.error('needed, bump AIWG_DIRECT_DEPS_BUDGET and document the tradeoff in the'); console.error('commit message.'); process.exit(1); } const transitiveMsg = transitiveCount !== undefined ? ` / ${transitiveCount} transitive` : ''; console.log(`check-dep-budget: OK (${directCount} direct${transitiveMsg})`);