Skip to main content
Glama

DollhouseMCP

by DollhouseMCP
sonarcloud-manager.cjs12.8 kB
#!/usr/bin/env node /** * SonarCloud Issue Management Script * * This script provides comprehensive management of SonarCloud issues including: * - Fetching and categorizing all issues * - Bulk triaging false positives and won't fix items * - Managing code duplication exclusions * - Generating reports and statistics */ const https = require('https'); const { execSync } = require('child_process'); // Configuration const PROJECT_KEY = 'DollhouseMCP_mcp-server'; const SONARCLOUD_HOST = 'sonarcloud.io'; // Get token from macOS Keychain function getToken() { try { // Use full path to security command to avoid PATH manipulation const token = execSync('/usr/bin/security find-generic-password -s "sonar_token2" -w 2>/dev/null', { encoding: 'utf-8' }).trim(); return token; } catch (error) { console.error('Failed to get SonarCloud token from keychain'); process.exit(1); } } // Make API request function apiRequest(method, path, data = null) { return new Promise((resolve, reject) => { const token = getToken(); const options = { hostname: SONARCLOUD_HOST, path: `/api${path}`, method: method, headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/x-www-form-urlencoded' } }; const req = https.request(options, (res) => { let body = ''; res.on('data', (chunk) => body += chunk); res.on('end', () => { if (res.statusCode >= 200 && res.statusCode < 300) { try { resolve(JSON.parse(body)); } catch { resolve(body); } } else { reject(new Error(`API request failed: ${res.statusCode} - ${body}`)); } }); }); req.on('error', reject); if (data && method === 'POST') { const formData = new URLSearchParams(data).toString(); req.write(formData); } req.end(); }); } // Fetch all issues with pagination async function fetchAllIssues(filters = {}) { const allIssues = []; let page = 1; let hasMore = true; const params = new URLSearchParams({ projects: PROJECT_KEY, ps: 500, additionalFields: 'comments,actions,transitions', facets: 'severities,types,tags', ...filters }); while (hasMore) { params.set('p', page); try { const response = await apiRequest('GET', `/issues/search?${params.toString()}`); allIssues.push(...response.issues); console.log(`Fetched page ${page}: ${response.issues.length} issues (total: ${allIssues.length})`); hasMore = response.issues.length === 500; page++; // Safety check to prevent infinite loops if (allIssues.length >= 10000) { console.warn('Reached 10,000 issue limit'); break; } } catch (error) { console.error(`Failed to fetch page ${page}:`, error.message); break; } } return allIssues; } // Categorize issues function categorizeIssues(issues) { const categories = { vulnerabilities: [], bugs: [], codeSmells: [], securityHotspots: [], byFile: {}, bySeverity: { BLOCKER: [], CRITICAL: [], MAJOR: [], MINOR: [], INFO: [] } }; for (const issue of issues) { // By type switch (issue.type) { case 'VULNERABILITY': categories.vulnerabilities.push(issue); break; case 'BUG': categories.bugs.push(issue); break; case 'CODE_SMELL': categories.codeSmells.push(issue); break; case 'SECURITY_HOTSPOT': categories.securityHotspots.push(issue); break; } // By severity if (categories.bySeverity[issue.severity]) { categories.bySeverity[issue.severity].push(issue); } // By file const file = issue.component.replace(`${PROJECT_KEY}:`, ''); if (!categories.byFile[file]) { categories.byFile[file] = []; } categories.byFile[file].push(issue); } return categories; } // Identify potential false positives function identifyFalsePositives(issues) { const falsePositives = []; for (const issue of issues) { const file = issue.component.replace(`${PROJECT_KEY}:`, ''); // Common false positive patterns if ( // Test files with expected patterns (file.includes('test/') && issue.rule === 'typescript:S1854') || // Dead stores in tests (file.includes('suppressions.ts') && issue.type === 'CODE_SMELL') || // Suppression configs (file.includes('.spec.') && issue.rule === 'typescript:S2699') || // No assertions (may be integration tests) (file.includes('mock') && issue.severity === 'MINOR') || // Mock files (file.endsWith('.d.ts') && issue.type === 'CODE_SMELL') || // Type definitions // Known false positives from our codebase (issue.message && issue.message.includes('cognitive complexity') && issue.severity === 'MINOR') || (issue.rule === 'typescript:S6481' && file.includes('test/')) || // Prefer for...of in tests is fine // Example tokens and documentation (issue.type === 'VULNERABILITY' && issue.message && issue.message.includes('example')) ) { falsePositives.push(issue); } } return falsePositives; } // Bulk transition issues async function bulkTransition(issueKeys, transition, comment) { const chunks = []; for (let i = 0; i < issueKeys.length; i += 500) { chunks.push(issueKeys.slice(i, i + 500)); } for (const [index, chunk] of chunks.entries()) { console.log(`Processing chunk ${index + 1}/${chunks.length} (${chunk.length} issues)`); try { await apiRequest('POST', '/issues/bulk_change', { issues: chunk.join(','), do_transition: transition, comment: comment }); console.log(`✓ Transitioned ${chunk.length} issues to ${transition}`); } catch (error) { console.error(`✗ Failed to transition chunk ${index + 1}:`, error.message); } } } // Generate report function generateReport(issues, categories, falsePositives) { console.log('\n' + '='.repeat(80)); console.log('SONARCLOUD ISSUE REPORT'); console.log('='.repeat(80)); console.log('\n📊 SUMMARY'); console.log(`Total Issues: ${issues.length}`); console.log(`├─ Vulnerabilities: ${categories.vulnerabilities.length}`); console.log(`├─ Bugs: ${categories.bugs.length}`); console.log(`├─ Code Smells: ${categories.codeSmells.length}`); console.log(`└─ Security Hotspots: ${categories.securityHotspots.length}`); console.log('\n⚠️ SEVERITY DISTRIBUTION'); for (const [severity, severityIssues] of Object.entries(categories.bySeverity)) { if (severityIssues.length > 0) { console.log(`${severity}: ${severityIssues.length}`); } } console.log('\n📁 TOP FILES BY ISSUE COUNT'); const topFiles = Object.entries(categories.byFile) .sort((a, b) => b[1].length - a[1].length) .slice(0, 10); for (const [file, fileIssues] of topFiles) { console.log(`${fileIssues.length.toString().padStart(4)} issues: ${file}`); } console.log('\n🎯 IDENTIFIED FALSE POSITIVES'); console.log(`Found ${falsePositives.length} potential false positives`); if (falsePositives.length > 0 && falsePositives.length <= 10) { console.log('\nSample false positives:'); for (const fp of falsePositives.slice(0, 5)) { const file = fp.component.replace(`${PROJECT_KEY}:`, ''); console.log(` - [${fp.rule}] ${file}: ${fp.message?.substring(0, 60)}...`); } } console.log('\n' + '='.repeat(80)); } // Check quality gate status async function checkQualityGate(pullRequest = null) { const params = new URLSearchParams({ projectKey: PROJECT_KEY }); if (pullRequest) { params.set('pullRequest', pullRequest); } try { const response = await apiRequest('GET', `/qualitygates/project_status?${params.toString()}`); return response.projectStatus; } catch (error) { console.error('Failed to check quality gate:', error.message); return null; } } // Main command handler async function main() { const command = process.argv[2]; console.log('🔍 SonarCloud Issue Manager'); console.log(`Project: ${PROJECT_KEY}\n`); try { // Validate authentication const authResult = await apiRequest('GET', '/authentication/validate'); if (!authResult.valid) { console.error('Authentication failed'); process.exit(1); } console.log('✓ Authentication successful\n'); switch (command) { case 'fetch': case 'report': { console.log('Fetching all issues...'); const issues = await fetchAllIssues(); const categories = categorizeIssues(issues); const falsePositives = identifyFalsePositives(issues); generateReport(issues, categories, falsePositives); // Save to file for further processing const fs = require('fs'); const output = { timestamp: new Date().toISOString(), project: PROJECT_KEY, summary: { total: issues.length, vulnerabilities: categories.vulnerabilities.length, bugs: categories.bugs.length, codeSmells: categories.codeSmells.length, falsePositives: falsePositives.length }, issues: issues, categories: categories, falsePositives: falsePositives }; fs.writeFileSync('sonarcloud-issues.json', JSON.stringify(output, null, 2)); console.log('\n✓ Full report saved to sonarcloud-issues.json'); break; } case 'triage-fp': { console.log('Identifying and triaging false positives...'); const issues = await fetchAllIssues(); const falsePositives = identifyFalsePositives(issues); if (falsePositives.length === 0) { console.log('No false positives identified'); break; } console.log(`Found ${falsePositives.length} potential false positives`); console.log('\nWould mark as false positive:'); for (const fp of falsePositives.slice(0, 10)) { const file = fp.component.replace(`${PROJECT_KEY}:`, ''); console.log(` - [${fp.rule}] ${file}`); } if (process.argv[3] === '--apply') { const issueKeys = falsePositives.map(i => i.key); await bulkTransition( issueKeys, 'falsepositive', 'Automated triage: Common false positive pattern' ); } else { console.log('\nRun with --apply to mark these as false positives'); } break; } case 'quality-gate': { const pr = process.argv[3]; const status = await checkQualityGate(pr); if (status) { console.log(`Quality Gate: ${status.status}`); if (status.conditions) { console.log('\nConditions:'); for (const condition of status.conditions) { const symbol = condition.status === 'OK' ? '✓' : '✗'; console.log(`${symbol} ${condition.metricKey}: ${condition.actualValue} (threshold: ${condition.errorThreshold})`); } } } break; } case 'duplication': { console.log('Analyzing duplication issues...'); const issues = await fetchAllIssues({ types: 'CODE_SMELL' }); const duplicationIssues = issues.filter(i => i.rule === 'common-js:DuplicatedBlocks' || i.message?.includes('duplicat') ); console.log(`Found ${duplicationIssues.length} duplication issues`); const byFile = {}; for (const issue of duplicationIssues) { const file = issue.component.replace(`${PROJECT_KEY}:`, ''); if (!byFile[file]) byFile[file] = 0; byFile[file]++; } console.log('\nFiles with duplication:'); for (const [file, count] of Object.entries(byFile)) { console.log(` ${count} issues: ${file}`); } break; } default: console.log('Usage: node sonarcloud-manager.js <command> [options]'); console.log('\nCommands:'); console.log(' fetch/report - Fetch all issues and generate report'); console.log(' triage-fp - Identify false positives (add --apply to mark)'); console.log(' quality-gate [PR] - Check quality gate status'); console.log(' duplication - Analyze duplication issues'); } } catch (error) { console.error('Error:', error.message); process.exit(1); } } // Run if executed directly if (require.main === module) { main(); } module.exports = { getToken, apiRequest, fetchAllIssues, categorizeIssues, identifyFalsePositives, bulkTransition, checkQualityGate };

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/DollhouseMCP/DollhouseMCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server