UNPKG

ubon

Version:

Security scanner for AI-generated apps (Cursor, Lovable, Windsurf, v0). Catches hardcoded secrets, prompt injection, hallucinated imports, Server Actions / Edge runtime mistakes, and the vibe-coded vulnerabilities traditional linters miss.

137 lines 6.66 kB
"use strict"; /** * `ubon doctor` — environment diagnostic. Mirrors `eslint --print-config` * style: tells the user exactly what Ubon thinks about their environment so * support requests can be debugged without back-and-forth. * * Outputs are intentionally cheap (no scan run): we only check that the * binaries / optional deps Ubon depends on are reachable. */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.runDoctor = runDoctor; const fs_1 = require("fs"); const path_1 = require("path"); const child_process_1 = require("child_process"); const package_json_1 = __importDefault(require("../../package.json")); function check(name, fn) { return Promise.resolve() .then(fn) .catch((error) => ({ name, status: 'fail', detail: error?.message || String(error) })); } function tryExec(cmd, args = []) { try { // ubon-disable-next-line SEC026 argv-safe execFileSync wrapper for fixed doctor probes return (0, child_process_1.execFileSync)(cmd, args, { stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim(); } catch { return null; } } async function runDoctor(directory = process.cwd(), options = {}) { const checks = []; checks.push(check('Ubon version', async () => ({ name: 'Ubon version', status: 'ok', detail: `${package_json_1.default.version}` }))); checks.push(check('Node.js', async () => { const major = parseInt(process.versions.node.split('.')[0] ?? '0', 10); const status = major >= 20 ? 'ok' : 'fail'; return { name: 'Node.js', status, detail: `${process.versions.node}${major < 20 ? ' (Ubon v3 requires Node 20+)' : ''}` }; })); checks.push(check('Project package.json', async () => { const pkgPath = (0, path_1.join)(directory, 'package.json'); if (!(0, fs_1.existsSync)(pkgPath)) { return { name: 'Project package.json', status: 'warn', detail: 'no package.json detected — profile auto-detection limited' }; } return { name: 'Project package.json', status: 'ok', detail: pkgPath }; })); checks.push(check('git', async () => { const v = tryExec('git', ['--version']); return v ? { name: 'git', status: 'ok', detail: v } : { name: 'git', status: 'warn', detail: 'git not on PATH — git-history scanner and --git-changed-since disabled' }; })); checks.push(check('@modelcontextprotocol/sdk', async () => { try { require.resolve('@modelcontextprotocol/sdk/package.json'); return { name: '@modelcontextprotocol/sdk', status: 'ok', detail: 'installed — `ubon mcp` available' }; } catch { return { name: '@modelcontextprotocol/sdk', status: 'warn', detail: 'not installed — install with `npm i -g @modelcontextprotocol/sdk` to enable `ubon mcp`' }; } })); checks.push(check('puppeteer (deprecated)', async () => { try { require.resolve('puppeteer/package.json'); return { name: 'puppeteer', status: 'warn', detail: 'installed — `--crawl-internal` works but is deprecated and slated for removal in v3.1' }; } catch { return { name: 'puppeteer', status: 'ok', detail: 'not installed (recommended)' }; } })); if (options.agentHarness) { checks.push(check('Cursor hooks', async () => { const hooks = (0, path_1.join)(directory, '.cursor', 'hooks.json'); return (0, fs_1.existsSync)(hooks) ? { name: 'Cursor hooks', status: 'ok', detail: hooks } : { name: 'Cursor hooks', status: 'warn', detail: 'not installed — run `ubon agent install --cursor --write`' }; })); checks.push(check('Agent guidance', async () => { const hasGuidance = (0, fs_1.existsSync)((0, path_1.join)(directory, 'AGENTS.md')) || (0, fs_1.existsSync)((0, path_1.join)(directory, 'CLAUDE.md')) || (0, fs_1.existsSync)((0, path_1.join)(directory, '.cursor', 'rules', 'ubon.mdc')); return hasGuidance ? { name: 'Agent guidance', status: 'ok', detail: 'found repo-level agent instructions' } : { name: 'Agent guidance', status: 'warn', detail: 'no AGENTS.md, CLAUDE.md, or .cursor/rules/ubon.mdc found' }; })); checks.push(check('Pre-commit hook', async () => { const preCommit = (0, path_1.join)(directory, '.pre-commit-config.yaml'); return (0, fs_1.existsSync)(preCommit) ? { name: 'Pre-commit hook', status: 'ok', detail: preCommit } : { name: 'Pre-commit hook', status: 'warn', detail: 'not configured — run `ubon agent install --pre-commit --write`' }; })); checks.push(check('Ubon cache ignore', async () => { const gitignore = (0, path_1.join)(directory, '.gitignore'); const ignored = (0, fs_1.existsSync)(gitignore) && (0, fs_1.readFileSync)(gitignore, 'utf-8').split(/\r?\n/).includes('.ubon/'); return ignored ? { name: 'Ubon cache ignore', status: 'ok', detail: '.ubon/ is ignored' } : { name: 'Ubon cache ignore', status: 'warn', detail: 'add .ubon/ to .gitignore for repo-local result caches' }; })); checks.push(check('GitHub Ubon workflow', async () => { const workflow = (0, path_1.join)(directory, '.github', 'workflows', 'ubon.yml'); return (0, fs_1.existsSync)(workflow) ? { name: 'GitHub Ubon workflow', status: 'ok', detail: workflow } : { name: 'GitHub Ubon workflow', status: 'warn', detail: 'not configured — run `ubon agent install --github --write`' }; })); } const results = await Promise.all(checks); const icon = { ok: '🪷', warn: '⚠️', fail: '❌' }; console.log('🪷 Ubon doctor'); console.log('─'.repeat(50)); for (const r of results) { console.log(`${icon[r.status]} ${r.name.padEnd(28)} ${r.detail}`); } console.log(''); const fails = results.filter((r) => r.status === 'fail').length; if (fails > 0) { console.log(`${fails} blocker(s) — please address before running ubon.`); process.exit(1); } } //# sourceMappingURL=doctor.js.map