capscan 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +727 -0
- package/dist/index.js.map +1 -0
- package/package.json +39 -0
- package/src/benchmark.ts +114 -0
- package/src/commands/check.ts +134 -0
- package/src/commands/compare.ts +95 -0
- package/src/commands/diff.ts +61 -0
- package/src/commands/init.ts +132 -0
- package/src/commands/scan.ts +44 -0
- package/src/commands/snapshot.ts +28 -0
- package/src/commands/why.ts +112 -0
- package/src/determinism-test.ts +76 -0
- package/src/generate-expected.ts +30 -0
- package/src/golden-test.ts +79 -0
- package/src/index.ts +27 -0
- package/src/performance-budget.ts +89 -0
- package/src/reporters/json.ts +12 -0
- package/src/reporters/markdown.ts +55 -0
- package/src/reporters/terminal.ts +128 -0
- package/tsconfig.json +9 -0
- package/tsup.config.ts +10 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/commands/scan.ts","../src/reporters/terminal.ts","../src/reporters/json.ts","../src/reporters/markdown.ts","../src/commands/diff.ts","../src/commands/snapshot.ts","../src/commands/why.ts","../src/commands/compare.ts","../src/commands/check.ts","../src/commands/init.ts"],"sourcesContent":["import { defineCommand, runMain } from 'citty';\nimport { scanCommand } from './commands/scan.js';\nimport { diffCommand } from './commands/diff.js';\nimport { snapshotCommand } from './commands/snapshot.js';\nimport { whyCommand } from './commands/why.js';\nimport { compareCommand } from './commands/compare.js';\nimport { checkCommand } from './commands/check.js';\nimport { initCommand } from './commands/init.js';\n\nconst main = defineCommand({\n meta: {\n name: 'capscan',\n version: '0.1.0',\n description: 'Deterministic capability analysis engine for software dependencies',\n },\n subCommands: {\n scan: scanCommand,\n why: whyCommand,\n diff: diffCommand,\n snapshot: snapshotCommand,\n compare: compareCommand,\n check: checkCommand,\n init: initCommand,\n },\n});\n\nrunMain(main);\n","import { defineCommand } from 'citty';\nimport { scan } from '@capscan/engine';\nimport { reportTerminal } from '../reporters/terminal.js';\nimport { reportJson } from '../reporters/json.js';\nimport { reportMarkdown } from '../reporters/markdown.js';\nimport ora from 'ora';\n\nexport const scanCommand = defineCommand({\n meta: { name: 'scan', description: 'Scan a project for dependency capabilities' },\n args: {\n path: { type: 'positional', description: 'Path to project directory', default: '.' },\n format: { type: 'string', description: 'Output format (json, terminal, markdown)', default: 'terminal', alias: 'f' },\n output: { type: 'string', description: 'Output file path', alias: 'o' },\n },\n async run({ args }) {\n const spinner = ora('Scanning project...').start();\n try {\n const result = await scan({ path: args.path });\n spinner.succeed(`Found ${result.summary.packagesWithCapabilities} packages with capabilities`);\n\n if (args.format === 'json') {\n const out = await reportJson(result, args.output);\n if (args.output) console.log(`Report saved to ${args.output}`);\n else console.log(out);\n } else if (args.format === 'markdown') {\n const md = reportMarkdown(result);\n if (args.output) {\n await writeFile(args.output, md, 'utf-8');\n console.log(`Report saved to ${args.output}`);\n } else {\n console.log(md);\n }\n } else {\n console.log(reportTerminal(result));\n }\n } catch (error) {\n spinner.fail('Scan failed');\n console.error(error);\n process.exit(1);\n }\n },\n});\n\nimport { writeFile } from 'node:fs/promises';\n","import type { ScanResult, CapabilityCategory, Permission, CapabilityFinding, PackageResult } from '@capscan/engine';\n\nconst CATEGORY_ICONS: Record<CapabilityCategory, string> = {\n filesystem: '📁',\n network: '🌐',\n process: '⚙️',\n environment: '🔑',\n crypto: '🔐',\n dynamic_code: '📦',\n native: '🧩',\n installation: '🔧',\n obfuscation: '🎭',\n};\n\nconst CATEGORY_LABELS: Record<CapabilityCategory, string> = {\n filesystem: 'Filesystem',\n network: 'Network',\n process: 'Process',\n environment: 'Environment',\n crypto: 'Crypto',\n dynamic_code: 'Dynamic Code',\n native: 'Native',\n installation: 'Installation',\n obfuscation: 'Obfuscation',\n};\n\nconst CONFIDENCE_COLORS: Record<string, (s: string) => string> = {\n high: (s: string) => `\\x1b[32m${s}\\x1b[0m`,\n medium: (s: string) => `\\x1b[33m${s}\\x1b[0m`,\n low: (s: string) => `\\x1b[31m${s}\\x1b[0m`,\n};\n\nfunction padRight(str: string, len: number): string {\n return str.length >= len ? str : str + ' '.repeat(len - str.length);\n}\n\nfunction groupByCategory(findings: CapabilityFinding[]): Map<CapabilityCategory, CapabilityFinding[]> {\n const grouped = new Map<CapabilityCategory, CapabilityFinding[]>();\n for (const f of findings) {\n const arr = grouped.get(f.capability.category) || [];\n arr.push(f);\n grouped.set(f.capability.category, arr);\n }\n return grouped;\n}\n\nfunction formatEvidenceLine(e: { file: string; line: number; symbol: string; confidence: string }, indent: string): string {\n const confColor = CONFIDENCE_COLORS[e.confidence] || CONFIDENCE_COLORS.medium;\n const location = e.line > 0 ? `${e.file}:${e.line}` : e.file;\n return `${indent}${confColor('●')} ${dim(location)} ${bold(e.symbol)}`;\n}\n\nfunction dim(s: string): string { return `\\x1b[2m${s}\\x1b[0m`; }\nfunction bold(s: string): string { return `\\x1b[1m${s}\\x1b[0m`; }\n\nfunction formatPackage(pkg: PackageResult): string[] {\n const lines: string[] = [];\n const grouped = groupByCategory(pkg.capabilities);\n lines.push(` \\x1b[33m●\\x1b[0m ${bold(pkg.name)}@${dim(pkg.version)}`);\n\n for (const [category, findings] of grouped) {\n const icon = CATEGORY_ICONS[category];\n const label = CATEGORY_LABELS[category];\n lines.push(` ${icon} ${bold(label)}`);\n\n const byPermission = new Map<string, CapabilityFinding[]>();\n for (const f of findings) {\n const perm = f.capability.permission;\n const arr = byPermission.get(perm) || [];\n arr.push(f);\n byPermission.set(perm, arr);\n }\n\n for (const [perm, permFindings] of byPermission) {\n lines.push(` ${dim(perm)}`);\n for (const f of permFindings.slice(0, 3)) {\n for (const e of f.evidence.slice(0, 2)) {\n lines.push(formatEvidenceLine(e, ' '));\n }\n }\n if (permFindings.length > 3) lines.push(dim(` ... and ${permFindings.length - 3} more`));\n }\n }\n\n return lines;\n}\n\nexport function reportTerminal(result: ScanResult): string {\n const lines: string[] = [];\n lines.push('');\n lines.push(`${bold('\\x1b[36mCapScan\\x1b[0m')} ${dim('v0.1.0 — deterministic capability analysis engine')}`);\n lines.push('');\n lines.push(`${bold('Project:')} ${result.meta.scanPath}`);\n lines.push(`${bold('Package Manager:')} ${result.meta.packageManager}`);\n lines.push(`${bold('Dependencies:')} ${result.summary.totalPackages.toString()}`);\n lines.push('');\n lines.push(bold('\\x1b[4mCapabilities\\x1b[0m'));\n lines.push(' ' + '─'.repeat(35));\n\n for (const [cat, perms] of Object.entries(result.summary.capabilities)) {\n const catLabel = CATEGORY_LABELS[cat as CapabilityCategory];\n const icon = CATEGORY_ICONS[cat as CapabilityCategory];\n const entries = Object.entries(perms).filter(([, c]) => c > 0);\n if (entries.length === 0) continue;\n lines.push(` ${icon} ${bold(catLabel)}`);\n for (const [perm, count] of entries) {\n const dots = '.'.repeat(Math.max(0, 25 - perm.length));\n const pkgText = count === 1 ? 'package' : 'packages';\n lines.push(` ${dim(perm)} ${dots} ${count.toString()} ${pkgText}`);\n }\n }\n\n const packagesWithCaps = result.packages.filter(p => p.capabilities.length > 0);\n if (packagesWithCaps.length > 0) {\n lines.push('');\n lines.push(bold('\\x1b[4mPackages with Capabilities\\x1b[0m'));\n lines.push(' ' + '─'.repeat(35));\n lines.push('');\n for (const pkg of packagesWithCaps) {\n lines.push(...formatPackage(pkg));\n lines.push('');\n }\n }\n\n lines.push(dim(` Scan completed at ${result.meta.timestamp}`));\n lines.push('');\n return lines.join('\\n');\n}\n","import { writeFile } from 'node:fs/promises';\nimport { resolve } from 'node:path';\nimport type { ScanResult } from '@capscan/engine';\n\nexport async function reportJson(result: ScanResult, outputPath?: string): Promise<string> {\n const json = JSON.stringify(result, null, 2);\n if (outputPath) {\n await writeFile(resolve(outputPath), json, 'utf-8');\n return outputPath;\n }\n return json;\n}\n","import type { ScanResult, CapabilityCategory } from '@capscan/engine';\n\nconst CAPABILITY_LABELS: Record<CapabilityCategory, string> = {\n filesystem: 'Filesystem', network: 'Network', process: 'Process',\n environment: 'Environment', crypto: 'Crypto', dynamic_code: 'Dynamic Code',\n native: 'Native', installation: 'Installation', obfuscation: 'Obfuscation',\n};\n\nfunction generateCapabilityTable(summary: ScanResult['summary']): string {\n const lines: string[] = [];\n lines.push('| Category | Permission | Count |');\n lines.push('|----------|------------|-------|');\n for (const [cat, perms] of Object.entries(summary.capabilities)) {\n for (const [perm, count] of Object.entries(perms)) {\n if (count > 0) lines.push(`| ${CAPABILITY_LABELS[cat as CapabilityCategory]} | \\`${perm}\\` | ${count} |`);\n }\n }\n return lines.join('\\n');\n}\n\nexport function reportMarkdown(result: ScanResult): string {\n const lines: string[] = [];\n lines.push('# CapScan Report');\n lines.push('');\n lines.push(`**Generated**: ${result.meta.timestamp}`);\n lines.push(`**Engine**: ${result.meta.engine}`);\n lines.push(`**Package Manager**: ${result.meta.packageManager}`);\n lines.push(`**Total Dependencies**: ${result.summary.totalPackages}`);\n lines.push(`**Packages with Capabilities**: ${result.summary.packagesWithCapabilities}`);\n lines.push('');\n lines.push('## Summary');\n lines.push('');\n lines.push(generateCapabilityTable(result.summary));\n lines.push('');\n\n const packagesWithCaps = result.packages.filter(p => p.capabilities.length > 0);\n if (packagesWithCaps.length > 0) {\n lines.push('## Packages');\n lines.push('');\n for (const pkg of packagesWithCaps) {\n lines.push(`### ${pkg.name}@${pkg.version}`);\n lines.push('');\n lines.push('| Permission | Symbol | File | Line | Confidence |');\n lines.push('|------------|--------|------|------|------------|');\n for (const f of pkg.capabilities) {\n for (const e of f.evidence) {\n lines.push(`| \\`${f.capability.permission}\\` | \\`${e.symbol}\\` | \\`${e.file}\\` | ${e.line} | ${e.confidence} |`);\n }\n }\n lines.push('');\n }\n }\n\n return lines.join('\\n');\n}\n","import { defineCommand } from 'citty';\nimport { scan, createSnapshot, loadSnapshot, diffSnapshots } from '@capscan/engine';\nimport { join } from 'node:path';\nimport ora from 'ora';\n\nexport const diffCommand = defineCommand({\n meta: { name: 'diff', description: 'Compare current scan with last snapshot' },\n args: {\n path: { type: 'positional', description: 'Path to project directory', default: '.' },\n snapshot: { type: 'string', description: 'Snapshot file path', default: '.capscan/snapshot.json' },\n },\n async run({ args }) {\n const spinner = ora('Scanning project...').start();\n try {\n const snapshotPath = join(args.path, args.snapshot);\n const oldSnap = await loadSnapshot(snapshotPath);\n\n if (!oldSnap) {\n spinner.fail(`No snapshot found at ${args.snapshot}`);\n console.log('\\nRun `capscan snapshot` first to create a baseline.');\n return;\n }\n\n const result = await scan({ path: args.path });\n const newSnap = await createSnapshot(result, snapshotPath);\n const diff = diffSnapshots(oldSnap, newSnap);\n\n spinner.succeed('Diff complete');\n\n if (diff.added.length === 0 && diff.removed.length === 0) {\n console.log('\\n No capability changes detected.\\n');\n return;\n }\n\n const lines: string[] = [];\n lines.push('');\n\n if (diff.added.length > 0) {\n lines.push(' \\x1b[32m+ Added\\x1b[0m');\n for (const item of diff.added) {\n lines.push(` + ${item.permission} in ${item.package}`);\n }\n lines.push('');\n }\n\n if (diff.removed.length > 0) {\n lines.push(' \\x1b[31m- Removed\\x1b[0m');\n for (const item of diff.removed) {\n lines.push(` - ${item.permission} in ${item.package}`);\n }\n lines.push('');\n }\n\n console.log(lines.join('\\n'));\n } catch (error) {\n spinner.fail('Diff failed');\n console.error(error);\n process.exit(1);\n }\n },\n});\n","import { defineCommand } from 'citty';\nimport { scan, createSnapshot } from '@capscan/engine';\nimport { join } from 'node:path';\nimport ora from 'ora';\n\nexport const snapshotCommand = defineCommand({\n meta: { name: 'snapshot', description: 'Create a capability snapshot for diffing' },\n args: {\n path: { type: 'positional', description: 'Path to project directory', default: '.' },\n output: { type: 'string', description: 'Snapshot file path', default: '.capscan/snapshot.json' },\n },\n async run({ args }) {\n const spinner = ora('Creating snapshot...').start();\n try {\n const result = await scan({ path: args.path });\n const snapshotPath = join(args.path, args.output);\n const snapshot = await createSnapshot(result, snapshotPath);\n\n const pkgCount = Object.keys(snapshot.packages).length;\n spinner.succeed(`Snapshot saved to ${args.output}`);\n console.log(` ${pkgCount} packages with capabilities recorded.`);\n } catch (error) {\n spinner.fail('Snapshot failed');\n console.error(error);\n process.exit(1);\n }\n },\n});\n","import { defineCommand } from 'citty';\nimport { scan } from '@capscan/engine';\nimport type { CapabilityCategory, Permission, ScanResult, PackageResult, CapabilityFinding } from '@capscan/engine';\n\nfunction dim(s: string): string { return `\\x1b[2m${s}\\x1b[0m`; }\nfunction bold(s: string): string { return `\\x1b[1m${s}\\x1b[0m`; }\n\nconst CATEGORY_LABELS: Record<CapabilityCategory, string> = {\n filesystem: 'Filesystem', network: 'Network', process: 'Process',\n environment: 'Environment', crypto: 'Crypto', dynamic_code: 'Dynamic Code',\n native: 'Native', installation: 'Installation', obfuscation: 'Obfuscation',\n};\n\nconst CATEGORY_ICONS: Record<CapabilityCategory, string> = {\n filesystem: '📁', network: '🌐', process: '⚙️', environment: '🔑',\n crypto: '🔐', dynamic_code: '📦', native: '🧩', installation: '🔧', obfuscation: '🎭',\n};\n\nfunction isCategory(input: string): input is CapabilityCategory {\n return Object.keys(CATEGORY_LABELS).includes(input);\n}\n\nfunction extractPermissions(finding: CapabilityFinding): Permission[] {\n return [finding.capability.permission];\n}\n\nfunction printFindings(label: string, findings: Array<{ pkg: PackageResult; finding: CapabilityFinding }>) {\n const lines: string[] = [];\n lines.push('');\n lines.push(` ${bold(label)}`);\n lines.push(` ${dim('─'.repeat(50))}`);\n lines.push('');\n\n // Group by package\n const byPkg = new Map<string, Array<{ finding: CapabilityFinding }>>();\n for (const { pkg, finding } of findings) {\n const arr = byPkg.get(pkg.name) || [];\n arr.push({ finding });\n byPkg.set(pkg.name, arr);\n }\n\n let totalEvidence = 0;\n for (const [pkgName, items] of byPkg) {\n const pkgVersion = items[0] ? findings.find(f => f.pkg.name === pkgName)?.pkg.version : '';\n lines.push(` ${bold(pkgName)}@${dim(pkgVersion || '')}`);\n for (const { finding } of items) {\n for (const e of finding.evidence) {\n totalEvidence++;\n const loc = e.line > 0 ? `${e.file}:${e.line}` : e.file;\n lines.push(` └── ${dim(loc)} ${bold(e.symbol)}`);\n }\n }\n lines.push('');\n }\n\n lines.push(` ${dim(`${totalEvidence} evidence points across ${byPkg.size} packages`)}`);\n lines.push('');\n console.log(lines.join('\\n'));\n}\n\nexport const whyCommand = defineCommand({\n meta: { name: 'why', description: 'Explain why a capability, permission, or package is detected' },\n args: {\n target: { type: 'positional', description: 'Category, permission, or package name', required: true },\n extra: { type: 'positional', description: 'Second filter (permission or package)', required: false },\n path: { type: 'string', description: 'Path to project directory', default: '.', alias: 'p' },\n },\n async run({ args }) {\n const result = await scan({ path: args.path });\n const target = args.target.toLowerCase();\n const extra = args.extra?.toLowerCase();\n\n // Find matching findings\n const matches: Array<{ pkg: PackageResult; finding: CapabilityFinding }> = [];\n\n for (const pkg of result.packages) {\n for (const finding of pkg.capabilities) {\n const matchesTarget =\n isCategory(target) && finding.capability.category === target ||\n finding.capability.permission === target ||\n pkg.name.toLowerCase() === target ||\n pkg.name.toLowerCase().includes(target);\n\n const matchesExtra = !extra ||\n finding.capability.permission === extra ||\n pkg.name.toLowerCase() === extra ||\n pkg.name.toLowerCase().includes(extra);\n\n if (matchesTarget && matchesExtra) {\n matches.push({ pkg, finding });\n }\n }\n }\n\n if (matches.length === 0) {\n console.log(`\\n No findings for \"${args.target}${extra ? ' ' + extra : ''}\".\\n`);\n return;\n }\n\n // Determine display label\n let label: string;\n if (isCategory(target)) {\n label = `${CATEGORY_ICONS[target]} ${CATEGORY_LABELS[target]}`;\n } else if (matches[0]?.finding.capability.permission === target) {\n label = target;\n } else {\n label = `Package: ${target}`;\n }\n\n printFindings(label, matches);\n },\n});\n","import { defineCommand } from 'citty';\nimport { scan } from '@capscan/engine';\nimport type { ScanResult, PackageResult, CapabilityFinding, Permission } from '@capscan/engine';\n\nfunction dim(s: string): string { return `\\x1b[2m${s}\\x1b[0m`; }\nfunction bold(s: string): string { return `\\x1b[1m${s}\\x1b[0m`; }\n\nfunction getPermissions(pkg: PackageResult): Map<string, Permission[]> {\n const perms = new Map<string, Permission[]>();\n for (const f of pkg.capabilities) {\n const key = f.capability.category;\n const arr = perms.get(key) || [];\n arr.push(f.capability.permission);\n perms.set(key, [...new Set(arr)]);\n }\n return perms;\n}\n\nexport const compareCommand = defineCommand({\n meta: { name: 'compare', description: 'Compare capabilities of two dependency versions' },\n args: {\n pkgA: { type: 'positional', description: 'First package (name or name@version)', required: true },\n pkgB: { type: 'positional', description: 'Second package (name or name@version)', required: true },\n path: { type: 'string', description: 'Path to project directory', default: '.', alias: 'p' },\n },\n async run({ args }) {\n const [nameA, verA] = args.pkgA.includes('@') ? args.pkgA.split('@') : [args.pkgA, ''];\n const [nameB, verB] = args.pkgB.includes('@') ? args.pkgB.split('@') : [args.pkgB, ''];\n\n console.log(`\\n Scanning packages...`);\n\n // We need to scan a temp project that has both versions\n // For now, scan the project and find the packages\n const result = await scan({ path: args.path });\n\n const pkgA = result.packages.find(p => p.name === nameA);\n const pkgB = result.packages.find(p => p.name === nameB);\n\n if (!pkgA && !pkgB) {\n console.log(`\\n Neither \"${nameA}\" nor \"${nameB}\" found in dependencies.\\n`);\n return;\n }\n if (!pkgA) {\n console.log(`\\n \"${nameA}\" not found in dependencies.\\n`);\n return;\n }\n if (!pkgB) {\n console.log(`\\n \"${nameB}\" not found in dependencies.\\n`);\n return;\n }\n\n const permsA = getPermissions(pkgA);\n const permsB = getPermissions(pkgB);\n\n // Flatten to permission strings\n const flatA = new Set<string>();\n const flatB = new Set<string>();\n for (const perms of permsA.values()) for (const p of perms) flatA.add(p);\n for (const perms of permsB.values()) for (const p of perms) flatB.add(p);\n\n const added = [...flatB].filter(p => !flatA.has(p));\n const removed = [...flatA].filter(p => !flatB.has(p));\n const unchanged = [...flatA].filter(p => flatB.has(p));\n\n const lines: string[] = [];\n lines.push('');\n lines.push(` ${bold(`Capability changes: ${pkgA.name}@${pkgA.version} → ${pkgB.name}@${pkgB.version}`)}`);\n lines.push('');\n\n if (added.length > 0) {\n lines.push(' \\x1b[32m+ Added\\x1b[0m');\n for (const p of added) lines.push(` + ${p}`);\n lines.push('');\n }\n\n if (removed.length > 0) {\n lines.push(' \\x1b[31m- Removed\\x1b[0m');\n for (const p of removed) lines.push(` - ${p}`);\n lines.push('');\n }\n\n if (unchanged.length > 0) {\n lines.push(' \\x1b[36m✓ Unchanged\\x1b[0m');\n for (const p of unchanged) lines.push(` ✓ ${p}`);\n lines.push('');\n }\n\n if (added.length === 0 && removed.length === 0) {\n lines.push(' No capability differences found.');\n lines.push('');\n }\n\n console.log(lines.join('\\n'));\n },\n});\n","import { defineCommand } from 'citty';\nimport { scan } from '@capscan/engine';\nimport type { Permission, ScanResult } from '@capscan/engine';\n\ninterface CheckResult {\n allowed: boolean;\n blocked: Array<{\n package: string;\n permission: Permission;\n evidence: { file: string; line: number; symbol: string }[];\n }>;\n summary: {\n totalPackages: number;\n packagesWithCapabilities: number;\n blockedCount: number;\n };\n}\n\nfunction parseAllowList(allow?: string): Permission[] {\n if (!allow) return [];\n return allow.split(',').map(s => s.trim()) as Permission[];\n}\n\nfunction checkCapabilities(\n result: ScanResult,\n allowedPermissions: Permission[]\n): CheckResult {\n const blocked: CheckResult['blocked'] = [];\n const allowedSet = new Set(allowedPermissions);\n\n for (const pkg of result.packages) {\n for (const finding of pkg.capabilities) {\n if (!allowedSet.has(finding.capability.permission)) {\n blocked.push({\n package: pkg.name,\n permission: finding.capability.permission,\n evidence: finding.evidence.map(e => ({\n file: e.file,\n line: e.line,\n symbol: e.symbol,\n })),\n });\n }\n }\n }\n\n return {\n allowed: blocked.length === 0,\n blocked,\n summary: {\n totalPackages: result.summary.totalPackages,\n packagesWithCapabilities: result.summary.packagesWithCapabilities,\n blockedCount: blocked.length,\n },\n };\n}\n\nfunction formatCheckOutput(result: CheckResult, quiet: boolean): string {\n if (quiet && result.allowed) {\n return '';\n }\n\n const lines: string[] = [];\n\n if (result.allowed) {\n lines.push('\\x1b[32m✓\\x1b[0m All capabilities allowed');\n lines.push(` ${result.summary.packagesWithCapabilities} packages with capabilities`);\n return lines.join('\\n');\n }\n\n lines.push('');\n lines.push('\\x1b[31m✗\\x1b[0m \\x1b[1mBlocked capabilities detected\\x1b[0m');\n lines.push('');\n\n const byPackage = new Map<string, typeof result.blocked>();\n for (const item of result.blocked) {\n const arr = byPackage.get(item.package) || [];\n arr.push(item);\n byPackage.set(item.package, arr);\n }\n\n for (const [pkg, items] of byPackage) {\n lines.push(` \\x1b[33m●\\x1b[0m ${pkg}`);\n for (const item of items) {\n const evidence = item.evidence[0];\n const location = evidence ? `${evidence.file}:${evidence.line}` : '';\n lines.push(` \\x1b[31m✗\\x1b[0m ${item.permission} ${location ? `\\x1b[2m${location}\\x1b[0m` : ''}`);\n }\n lines.push('');\n }\n\n lines.push(` \\x1b[2m${result.summary.blockedCount} blocked capabilities\\x1b[0m`);\n lines.push('');\n\n return lines.join('\\n');\n}\n\nexport const checkCommand = defineCommand({\n meta: {\n name: 'check',\n description: 'Check if project capabilities are within allowed permissions',\n },\n args: {\n path: { type: 'positional', description: 'Path to project directory', default: '.' },\n allow: {\n type: 'string',\n description: 'Comma-separated list of allowed permissions (e.g., fs:read,env:read)',\n alias: 'a',\n },\n quiet: {\n type: 'boolean',\n description: 'Only output if blocked',\n default: false,\n alias: 'q',\n },\n },\n async run({ args }) {\n try {\n const result = await scan({ path: args.path });\n const allowedPermissions = parseAllowList(args.allow);\n const checkResult = checkCapabilities(result, allowedPermissions);\n const output = formatCheckOutput(checkResult, args.quiet);\n\n if (output) {\n console.log(output);\n }\n\n process.exit(checkResult.allowed ? 0 : 1);\n } catch (error) {\n console.error('\\x1b[31m✗\\x1b[0m Check failed:', error);\n process.exit(1);\n }\n },\n});\n","import { defineCommand } from 'citty';\nimport { readFile, writeFile, access } from 'node:fs/promises';\nimport { join } from 'node:path';\n\nconst NPMRC_CONTENT = `\n# CapScan pre-install hook\n# Checks dependency capabilities before installing\npreinstall = npx capscan check --quiet\n`.trim();\n\nconst GITIGNORE_LINE = 'node_modules/';\n\nasync function fileExists(path: string): Promise<boolean> {\n try {\n await access(path);\n return true;\n } catch {\n return false;\n }\n}\n\nasync function readOrCreateNpmrc(projectPath: string): Promise<string> {\n const npmrcPath = join(projectPath, '.npmrc');\n\n if (await fileExists(npmrcPath)) {\n return await readFile(npmrcPath, 'utf-8');\n }\n\n return '';\n}\n\nasync function readOrCreateGitignore(projectPath: string): Promise<string> {\n const gitignorePath = join(projectPath, '.gitignore');\n\n if (await fileExists(gitignorePath)) {\n return await readFile(gitignorePath, 'utf-8');\n }\n\n return '';\n}\n\nfunction hasCapscanHook(content: string): boolean {\n return content.includes('capscan check');\n}\n\nfunction addCapscanHook(content: string): string {\n if (hasCapscanHook(content)) {\n return content;\n }\n\n const lines = content.split('\\n');\n const lastEmptyIndex = lines.length - 1;\n\n // Find a good place to insert\n let insertIndex = lastEmptyIndex;\n for (let i = lines.length - 1; i >= 0; i--) {\n if (lines[i].trim() === '') {\n insertIndex = i;\n break;\n }\n }\n\n // If no empty line found, append at end\n if (insertIndex === lastEmptyIndex && lines[lastEmptyIndex]?.trim() !== '') {\n lines.push('');\n insertIndex = lines.length;\n }\n\n lines.splice(insertIndex, 0, ...NPMRC_CONTENT.split('\\n'));\n\n return lines.join('\\n');\n}\n\nfunction addNodeModulesToGitignore(content: string): string {\n if (content.includes('node_modules/')) {\n return content;\n }\n\n return content.trim() + '\\n\\n' + GITIGNORE_LINE + '\\n';\n}\n\nexport const initCommand = defineCommand({\n meta: {\n name: 'init',\n description: 'Initialize CapScan hooks in your project',\n },\n args: {\n path: { type: 'positional', description: 'Path to project directory', default: '.' },\n force: {\n type: 'boolean',\n description: 'Overwrite existing configuration',\n default: false,\n alias: 'f',\n },\n },\n async run({ args }) {\n const projectPath = args.path;\n\n try {\n console.log('\\n \\x1b[36mCapScan\\x1b[0m \\x1b[2mInit\\x1b[0m\\n');\n\n // Update .npmrc\n const npmrcContent = await readOrCreateNpmrc(projectPath);\n if (hasCapscanHook(npmrcContent) && !args.force) {\n console.log(' \\x1b[33m●\\x1b[0m .npmrc already configured');\n } else {\n const newNpmrc = addCapscanHook(npmrcContent);\n const npmrcPath = join(projectPath, '.npmrc');\n await writeFile(npmrcPath, newNpmrc, 'utf-8');\n console.log(' \\x1b[32m✓\\x1b[0m .npmrc updated');\n }\n\n // Update .gitignore\n const gitignoreContent = await readOrCreateGitignore(projectPath);\n if (gitignoreContent.includes('node_modules/')) {\n console.log(' \\x1b[33m●\\x1b[0m .gitignore already has node_modules/');\n } else {\n const newGitignore = addNodeModulesToGitignore(gitignoreContent);\n const gitignorePath = join(projectPath, '.gitignore');\n await writeFile(gitignorePath, newGitignore, 'utf-8');\n console.log(' \\x1b[32m✓\\x1b[0m .gitignore updated');\n }\n\n console.log('\\n \\x1b[2mCapScan will now check capabilities before each install.\\x1b[0m');\n console.log(' \\x1b[2mTo disable: remove the \"preinstall\" line from .npmrc\\x1b[0m\\n');\n\n } catch (error) {\n console.error('\\x1b[31m✗\\x1b[0m Init failed:', error);\n process.exit(1);\n }\n },\n});\n"],"mappings":"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","names":["defineCommand","writeFile","defineCommand","scan","ora","defineCommand","scan","createSnapshot","join","ora","defineCommand","scan","dim","bold","CATEGORY_LABELS","CATEGORY_ICONS","bold","dim","defineCommand","scan","CATEGORY_ICONS","CATEGORY_LABELS","defineCommand","scan","bold","defineCommand","scan","defineCommand","scan","defineCommand","writeFile","join","defineCommand"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "capscan",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI for CapScan capability analysis engine",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"capscan": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsup",
|
|
11
|
+
"dev": "tsup --watch",
|
|
12
|
+
"typecheck": "tsc --noEmit",
|
|
13
|
+
"golden-test": "tsx src/golden-test.ts",
|
|
14
|
+
"generate-expected": "tsx src/generate-expected.ts",
|
|
15
|
+
"benchmark": "tsx src/benchmark.ts",
|
|
16
|
+
"determinism-test": "tsx src/determinism-test.ts",
|
|
17
|
+
"performance-budget": "tsx src/performance-budget.ts"
|
|
18
|
+
},
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@capscan/engine": "workspace:*",
|
|
21
|
+
"chalk": "^5.3.0",
|
|
22
|
+
"citty": "^0.2.2",
|
|
23
|
+
"consola": "^3.2.0",
|
|
24
|
+
"ora": "^8.0.0"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@types/node": "^22.0.0",
|
|
28
|
+
"tsup": "^8.0.0",
|
|
29
|
+
"tsx": "^4.22.4",
|
|
30
|
+
"typescript": "^5.5.0"
|
|
31
|
+
},
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "public"
|
|
35
|
+
},
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=20.19.0"
|
|
38
|
+
}
|
|
39
|
+
}
|
package/src/benchmark.ts
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { scan } from '@capscan/engine';
|
|
2
|
+
import { readFileSync, readdirSync, statSync } from 'node:fs';
|
|
3
|
+
import { join, resolve } from 'node:path';
|
|
4
|
+
|
|
5
|
+
const FIXTURES_DIR = resolve(import.meta.dirname, '../../../fixtures');
|
|
6
|
+
|
|
7
|
+
function formatBytes(bytes: number): string {
|
|
8
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
9
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
10
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function countFiles(dir: string): number {
|
|
14
|
+
let count = 0;
|
|
15
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
16
|
+
if (entry.name === 'node_modules' || entry.name === '.git') continue;
|
|
17
|
+
const path = join(dir, entry.name);
|
|
18
|
+
if (entry.isDirectory()) count += countFiles(path);
|
|
19
|
+
else count++;
|
|
20
|
+
}
|
|
21
|
+
return count;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function countSourceLines(dir: string): number {
|
|
25
|
+
let lines = 0;
|
|
26
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
27
|
+
if (entry.name === 'node_modules' || entry.name === '.git') continue;
|
|
28
|
+
const path = join(dir, entry.name);
|
|
29
|
+
if (entry.isDirectory()) lines += countSourceLines(path);
|
|
30
|
+
else if (entry.name.endsWith('.js') || entry.name.endsWith('.ts') || entry.name.endsWith('.mjs')) {
|
|
31
|
+
lines += readFileSync(path, 'utf-8').split('\n').length;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return lines;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function benchmark() {
|
|
38
|
+
const fixtures = readdirSync(FIXTURES_DIR).filter(f => {
|
|
39
|
+
try {
|
|
40
|
+
readFileSync(join(FIXTURES_DIR, f, 'package.json'), 'utf-8');
|
|
41
|
+
return true;
|
|
42
|
+
} catch {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
console.log(`\n CapScan Benchmark`);
|
|
48
|
+
console.log(` ${'─'.repeat(60)}\n`);
|
|
49
|
+
|
|
50
|
+
const results: Array<{
|
|
51
|
+
fixture: string;
|
|
52
|
+
time: number;
|
|
53
|
+
memBefore: number;
|
|
54
|
+
memAfter: number;
|
|
55
|
+
packages: number;
|
|
56
|
+
capabilities: number;
|
|
57
|
+
evidence: number;
|
|
58
|
+
}> = [];
|
|
59
|
+
|
|
60
|
+
for (const fixture of fixtures) {
|
|
61
|
+
const fixtureDir = join(FIXTURES_DIR, fixture);
|
|
62
|
+
const fileCount = countFiles(fixtureDir);
|
|
63
|
+
const sourceLines = countSourceLines(fixtureDir);
|
|
64
|
+
|
|
65
|
+
// Warm up
|
|
66
|
+
await scan({ path: fixtureDir });
|
|
67
|
+
|
|
68
|
+
// Benchmark
|
|
69
|
+
const memBefore = process.memoryUsage().heapUsed;
|
|
70
|
+
const start = performance.now();
|
|
71
|
+
const result = await scan({ path: fixtureDir });
|
|
72
|
+
const end = performance.now();
|
|
73
|
+
const memAfter = process.memoryUsage().heapUsed;
|
|
74
|
+
|
|
75
|
+
const time = end - start;
|
|
76
|
+
const evidence = result.packages.reduce((sum, p) => sum + p.capabilities.reduce((s, c) => s + c.evidence.length, 0), 0);
|
|
77
|
+
|
|
78
|
+
results.push({
|
|
79
|
+
fixture,
|
|
80
|
+
time,
|
|
81
|
+
memBefore,
|
|
82
|
+
memAfter,
|
|
83
|
+
packages: result.summary.totalPackages,
|
|
84
|
+
capabilities: result.summary.packagesWithCapabilities,
|
|
85
|
+
evidence,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
console.log(` ${fixture}`);
|
|
89
|
+
console.log(` Time: ${time.toFixed(0)}ms`);
|
|
90
|
+
console.log(` Memory: ${formatBytes(memAfter - memBefore)}`);
|
|
91
|
+
console.log(` Dependencies: ${result.summary.totalPackages}`);
|
|
92
|
+
console.log(` With caps: ${result.summary.packagesWithCapabilities}`);
|
|
93
|
+
console.log(` Evidence: ${evidence}`);
|
|
94
|
+
console.log(` Files: ${fileCount}`);
|
|
95
|
+
console.log(` Source lines: ${sourceLines}`);
|
|
96
|
+
console.log('');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Summary
|
|
100
|
+
const totalTime = results.reduce((sum, r) => sum + r.time, 0);
|
|
101
|
+
const totalPkgs = results.reduce((sum, r) => sum + r.packages, 0);
|
|
102
|
+
const avgTimePerPkg = totalTime / totalPkgs;
|
|
103
|
+
|
|
104
|
+
console.log(` ${'─'.repeat(60)}`);
|
|
105
|
+
console.log(` Summary`);
|
|
106
|
+
console.log(` Total fixtures: ${results.length}`);
|
|
107
|
+
console.log(` Total time: ${totalTime.toFixed(0)}ms`);
|
|
108
|
+
console.log(` Total packages: ${totalPkgs}`);
|
|
109
|
+
console.log(` Avg ms/package: ${avgTimePerPkg.toFixed(1)}ms`);
|
|
110
|
+
console.log(` Throughput: ${(totalPkgs / (totalTime / 1000)).toFixed(0)} packages/sec`);
|
|
111
|
+
console.log('');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
benchmark();
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { defineCommand } from 'citty';
|
|
2
|
+
import { scan } from '@capscan/engine';
|
|
3
|
+
import type { Permission, ScanResult } from '@capscan/engine';
|
|
4
|
+
|
|
5
|
+
interface CheckResult {
|
|
6
|
+
allowed: boolean;
|
|
7
|
+
blocked: Array<{
|
|
8
|
+
package: string;
|
|
9
|
+
permission: Permission;
|
|
10
|
+
evidence: { file: string; line: number; symbol: string }[];
|
|
11
|
+
}>;
|
|
12
|
+
summary: {
|
|
13
|
+
totalPackages: number;
|
|
14
|
+
packagesWithCapabilities: number;
|
|
15
|
+
blockedCount: number;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function parseAllowList(allow?: string): Permission[] {
|
|
20
|
+
if (!allow) return [];
|
|
21
|
+
return allow.split(',').map(s => s.trim()) as Permission[];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function checkCapabilities(
|
|
25
|
+
result: ScanResult,
|
|
26
|
+
allowedPermissions: Permission[]
|
|
27
|
+
): CheckResult {
|
|
28
|
+
const blocked: CheckResult['blocked'] = [];
|
|
29
|
+
const allowedSet = new Set(allowedPermissions);
|
|
30
|
+
|
|
31
|
+
for (const pkg of result.packages) {
|
|
32
|
+
for (const finding of pkg.capabilities) {
|
|
33
|
+
if (!allowedSet.has(finding.capability.permission)) {
|
|
34
|
+
blocked.push({
|
|
35
|
+
package: pkg.name,
|
|
36
|
+
permission: finding.capability.permission,
|
|
37
|
+
evidence: finding.evidence.map(e => ({
|
|
38
|
+
file: e.file,
|
|
39
|
+
line: e.line,
|
|
40
|
+
symbol: e.symbol,
|
|
41
|
+
})),
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
allowed: blocked.length === 0,
|
|
49
|
+
blocked,
|
|
50
|
+
summary: {
|
|
51
|
+
totalPackages: result.summary.totalPackages,
|
|
52
|
+
packagesWithCapabilities: result.summary.packagesWithCapabilities,
|
|
53
|
+
blockedCount: blocked.length,
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function formatCheckOutput(result: CheckResult, quiet: boolean): string {
|
|
59
|
+
if (quiet && result.allowed) {
|
|
60
|
+
return '';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const lines: string[] = [];
|
|
64
|
+
|
|
65
|
+
if (result.allowed) {
|
|
66
|
+
lines.push('\x1b[32m✓\x1b[0m All capabilities allowed');
|
|
67
|
+
lines.push(` ${result.summary.packagesWithCapabilities} packages with capabilities`);
|
|
68
|
+
return lines.join('\n');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
lines.push('');
|
|
72
|
+
lines.push('\x1b[31m✗\x1b[0m \x1b[1mBlocked capabilities detected\x1b[0m');
|
|
73
|
+
lines.push('');
|
|
74
|
+
|
|
75
|
+
const byPackage = new Map<string, typeof result.blocked>();
|
|
76
|
+
for (const item of result.blocked) {
|
|
77
|
+
const arr = byPackage.get(item.package) || [];
|
|
78
|
+
arr.push(item);
|
|
79
|
+
byPackage.set(item.package, arr);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
for (const [pkg, items] of byPackage) {
|
|
83
|
+
lines.push(` \x1b[33m●\x1b[0m ${pkg}`);
|
|
84
|
+
for (const item of items) {
|
|
85
|
+
const evidence = item.evidence[0];
|
|
86
|
+
const location = evidence ? `${evidence.file}:${evidence.line}` : '';
|
|
87
|
+
lines.push(` \x1b[31m✗\x1b[0m ${item.permission} ${location ? `\x1b[2m${location}\x1b[0m` : ''}`);
|
|
88
|
+
}
|
|
89
|
+
lines.push('');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
lines.push(` \x1b[2m${result.summary.blockedCount} blocked capabilities\x1b[0m`);
|
|
93
|
+
lines.push('');
|
|
94
|
+
|
|
95
|
+
return lines.join('\n');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export const checkCommand = defineCommand({
|
|
99
|
+
meta: {
|
|
100
|
+
name: 'check',
|
|
101
|
+
description: 'Check if project capabilities are within allowed permissions',
|
|
102
|
+
},
|
|
103
|
+
args: {
|
|
104
|
+
path: { type: 'positional', description: 'Path to project directory', default: '.' },
|
|
105
|
+
allow: {
|
|
106
|
+
type: 'string',
|
|
107
|
+
description: 'Comma-separated list of allowed permissions (e.g., fs:read,env:read)',
|
|
108
|
+
alias: 'a',
|
|
109
|
+
},
|
|
110
|
+
quiet: {
|
|
111
|
+
type: 'boolean',
|
|
112
|
+
description: 'Only output if blocked',
|
|
113
|
+
default: false,
|
|
114
|
+
alias: 'q',
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
async run({ args }) {
|
|
118
|
+
try {
|
|
119
|
+
const result = await scan({ path: args.path });
|
|
120
|
+
const allowedPermissions = parseAllowList(args.allow);
|
|
121
|
+
const checkResult = checkCapabilities(result, allowedPermissions);
|
|
122
|
+
const output = formatCheckOutput(checkResult, args.quiet);
|
|
123
|
+
|
|
124
|
+
if (output) {
|
|
125
|
+
console.log(output);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
process.exit(checkResult.allowed ? 0 : 1);
|
|
129
|
+
} catch (error) {
|
|
130
|
+
console.error('\x1b[31m✗\x1b[0m Check failed:', error);
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
});
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { defineCommand } from 'citty';
|
|
2
|
+
import { scan } from '@capscan/engine';
|
|
3
|
+
import type { ScanResult, PackageResult, CapabilityFinding, Permission } from '@capscan/engine';
|
|
4
|
+
|
|
5
|
+
function dim(s: string): string { return `\x1b[2m${s}\x1b[0m`; }
|
|
6
|
+
function bold(s: string): string { return `\x1b[1m${s}\x1b[0m`; }
|
|
7
|
+
|
|
8
|
+
function getPermissions(pkg: PackageResult): Map<string, Permission[]> {
|
|
9
|
+
const perms = new Map<string, Permission[]>();
|
|
10
|
+
for (const f of pkg.capabilities) {
|
|
11
|
+
const key = f.capability.category;
|
|
12
|
+
const arr = perms.get(key) || [];
|
|
13
|
+
arr.push(f.capability.permission);
|
|
14
|
+
perms.set(key, [...new Set(arr)]);
|
|
15
|
+
}
|
|
16
|
+
return perms;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const compareCommand = defineCommand({
|
|
20
|
+
meta: { name: 'compare', description: 'Compare capabilities of two dependency versions' },
|
|
21
|
+
args: {
|
|
22
|
+
pkgA: { type: 'positional', description: 'First package (name or name@version)', required: true },
|
|
23
|
+
pkgB: { type: 'positional', description: 'Second package (name or name@version)', required: true },
|
|
24
|
+
path: { type: 'string', description: 'Path to project directory', default: '.', alias: 'p' },
|
|
25
|
+
},
|
|
26
|
+
async run({ args }) {
|
|
27
|
+
const [nameA, verA] = args.pkgA.includes('@') ? args.pkgA.split('@') : [args.pkgA, ''];
|
|
28
|
+
const [nameB, verB] = args.pkgB.includes('@') ? args.pkgB.split('@') : [args.pkgB, ''];
|
|
29
|
+
|
|
30
|
+
console.log(`\n Scanning packages...`);
|
|
31
|
+
|
|
32
|
+
// We need to scan a temp project that has both versions
|
|
33
|
+
// For now, scan the project and find the packages
|
|
34
|
+
const result = await scan({ path: args.path });
|
|
35
|
+
|
|
36
|
+
const pkgA = result.packages.find(p => p.name === nameA);
|
|
37
|
+
const pkgB = result.packages.find(p => p.name === nameB);
|
|
38
|
+
|
|
39
|
+
if (!pkgA && !pkgB) {
|
|
40
|
+
console.log(`\n Neither "${nameA}" nor "${nameB}" found in dependencies.\n`);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (!pkgA) {
|
|
44
|
+
console.log(`\n "${nameA}" not found in dependencies.\n`);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (!pkgB) {
|
|
48
|
+
console.log(`\n "${nameB}" not found in dependencies.\n`);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const permsA = getPermissions(pkgA);
|
|
53
|
+
const permsB = getPermissions(pkgB);
|
|
54
|
+
|
|
55
|
+
// Flatten to permission strings
|
|
56
|
+
const flatA = new Set<string>();
|
|
57
|
+
const flatB = new Set<string>();
|
|
58
|
+
for (const perms of permsA.values()) for (const p of perms) flatA.add(p);
|
|
59
|
+
for (const perms of permsB.values()) for (const p of perms) flatB.add(p);
|
|
60
|
+
|
|
61
|
+
const added = [...flatB].filter(p => !flatA.has(p));
|
|
62
|
+
const removed = [...flatA].filter(p => !flatB.has(p));
|
|
63
|
+
const unchanged = [...flatA].filter(p => flatB.has(p));
|
|
64
|
+
|
|
65
|
+
const lines: string[] = [];
|
|
66
|
+
lines.push('');
|
|
67
|
+
lines.push(` ${bold(`Capability changes: ${pkgA.name}@${pkgA.version} → ${pkgB.name}@${pkgB.version}`)}`);
|
|
68
|
+
lines.push('');
|
|
69
|
+
|
|
70
|
+
if (added.length > 0) {
|
|
71
|
+
lines.push(' \x1b[32m+ Added\x1b[0m');
|
|
72
|
+
for (const p of added) lines.push(` + ${p}`);
|
|
73
|
+
lines.push('');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (removed.length > 0) {
|
|
77
|
+
lines.push(' \x1b[31m- Removed\x1b[0m');
|
|
78
|
+
for (const p of removed) lines.push(` - ${p}`);
|
|
79
|
+
lines.push('');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (unchanged.length > 0) {
|
|
83
|
+
lines.push(' \x1b[36m✓ Unchanged\x1b[0m');
|
|
84
|
+
for (const p of unchanged) lines.push(` ✓ ${p}`);
|
|
85
|
+
lines.push('');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (added.length === 0 && removed.length === 0) {
|
|
89
|
+
lines.push(' No capability differences found.');
|
|
90
|
+
lines.push('');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
console.log(lines.join('\n'));
|
|
94
|
+
},
|
|
95
|
+
});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { defineCommand } from 'citty';
|
|
2
|
+
import { scan, createSnapshot, loadSnapshot, diffSnapshots } from '@capscan/engine';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import ora from 'ora';
|
|
5
|
+
|
|
6
|
+
export const diffCommand = defineCommand({
|
|
7
|
+
meta: { name: 'diff', description: 'Compare current scan with last snapshot' },
|
|
8
|
+
args: {
|
|
9
|
+
path: { type: 'positional', description: 'Path to project directory', default: '.' },
|
|
10
|
+
snapshot: { type: 'string', description: 'Snapshot file path', default: '.capscan/snapshot.json' },
|
|
11
|
+
},
|
|
12
|
+
async run({ args }) {
|
|
13
|
+
const spinner = ora('Scanning project...').start();
|
|
14
|
+
try {
|
|
15
|
+
const snapshotPath = join(args.path, args.snapshot);
|
|
16
|
+
const oldSnap = await loadSnapshot(snapshotPath);
|
|
17
|
+
|
|
18
|
+
if (!oldSnap) {
|
|
19
|
+
spinner.fail(`No snapshot found at ${args.snapshot}`);
|
|
20
|
+
console.log('\nRun `capscan snapshot` first to create a baseline.');
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const result = await scan({ path: args.path });
|
|
25
|
+
const newSnap = await createSnapshot(result, snapshotPath);
|
|
26
|
+
const diff = diffSnapshots(oldSnap, newSnap);
|
|
27
|
+
|
|
28
|
+
spinner.succeed('Diff complete');
|
|
29
|
+
|
|
30
|
+
if (diff.added.length === 0 && diff.removed.length === 0) {
|
|
31
|
+
console.log('\n No capability changes detected.\n');
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const lines: string[] = [];
|
|
36
|
+
lines.push('');
|
|
37
|
+
|
|
38
|
+
if (diff.added.length > 0) {
|
|
39
|
+
lines.push(' \x1b[32m+ Added\x1b[0m');
|
|
40
|
+
for (const item of diff.added) {
|
|
41
|
+
lines.push(` + ${item.permission} in ${item.package}`);
|
|
42
|
+
}
|
|
43
|
+
lines.push('');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (diff.removed.length > 0) {
|
|
47
|
+
lines.push(' \x1b[31m- Removed\x1b[0m');
|
|
48
|
+
for (const item of diff.removed) {
|
|
49
|
+
lines.push(` - ${item.permission} in ${item.package}`);
|
|
50
|
+
}
|
|
51
|
+
lines.push('');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
console.log(lines.join('\n'));
|
|
55
|
+
} catch (error) {
|
|
56
|
+
spinner.fail('Diff failed');
|
|
57
|
+
console.error(error);
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
});
|