@sonar-autofix/sonar-client 0.0.1
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/index.js +327 -0
- package/package.json +20 -0
package/index.js
ADDED
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
// sonarclient.js
|
|
2
|
+
const axios = require('axios');
|
|
3
|
+
const { exec } = require('node:child_process');
|
|
4
|
+
const { promisify } = require('node:util');
|
|
5
|
+
const execAsync = promisify(exec);
|
|
6
|
+
|
|
7
|
+
class SonarClient {
|
|
8
|
+
constructor(config) {
|
|
9
|
+
this.baseUrl = config.sonar?.host || config.baseUrl || 'http://localhost:9000';
|
|
10
|
+
this.token = config.sonar?.token || config.token;
|
|
11
|
+
this.axiosInstance = axios.create({
|
|
12
|
+
baseURL: this.baseUrl,
|
|
13
|
+
headers: {
|
|
14
|
+
'Authorization': `Bearer ${this.token}`,
|
|
15
|
+
'Content-Type': 'application/json'
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Get project analysis
|
|
21
|
+
async getAnalysis(projectKey) {
|
|
22
|
+
try {
|
|
23
|
+
const response = await this.axiosInstance.get(`/api/qualitygates/project_status?projectKey=${projectKey}`);
|
|
24
|
+
return response.data;
|
|
25
|
+
} catch (error) {
|
|
26
|
+
throw new Error(`Failed to get analysis: ${error.message}`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Get issues for a project - Updated to use proper endpoint
|
|
31
|
+
async getIssues(projectKey, severity) {
|
|
32
|
+
if (!projectKey) {
|
|
33
|
+
throw new Error('projectKey is required to fetch issues');
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
var params = {
|
|
37
|
+
componentKeys: projectKey,
|
|
38
|
+
statuses: 'OPEN,CONFIRMED,REOPENED'
|
|
39
|
+
};
|
|
40
|
+
if (severity) {
|
|
41
|
+
params.severities = severity;
|
|
42
|
+
}
|
|
43
|
+
const response = await this.axiosInstance.get(`/api/issues/search`, {
|
|
44
|
+
params: params
|
|
45
|
+
});
|
|
46
|
+
return response.data;
|
|
47
|
+
} catch (error) {
|
|
48
|
+
// Provide more detailed error information for debugging
|
|
49
|
+
if (error.response) {
|
|
50
|
+
throw new Error(`Failed to get issues: ${error.message} - Status: ${error.response.status}, Data: ${JSON.stringify(error.response.data)}`);
|
|
51
|
+
} else {
|
|
52
|
+
throw new Error(`Failed to get issues: ${error.message}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Get quality gate status - Updated endpoint
|
|
58
|
+
async getQualityGate(projectKey) {
|
|
59
|
+
try {
|
|
60
|
+
const response = await this.axiosInstance.get(`/api/qualitygates/project_status?projectKey=${projectKey}`);
|
|
61
|
+
return response.data;
|
|
62
|
+
} catch (error) {
|
|
63
|
+
// If the quality gate endpoint fails, try a different one
|
|
64
|
+
if (error.response && error.response.status === 404) {
|
|
65
|
+
console.log('Quality gate endpoint not available, trying alternative API');
|
|
66
|
+
// Try to get project info as fallback
|
|
67
|
+
const response = await this.axiosInstance.get(`/api/projects/search?projects=${projectKey}`);
|
|
68
|
+
return response.data;
|
|
69
|
+
}
|
|
70
|
+
throw new Error(`Failed to get quality gate: ${error.message}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Get project information (fallback for quality gate)
|
|
75
|
+
async getProjectInfo(projectKey) {
|
|
76
|
+
try {
|
|
77
|
+
const response = await this.axiosInstance.get(`/api/projects/search?projects=${projectKey}`);
|
|
78
|
+
return response.data;
|
|
79
|
+
} catch (error) {
|
|
80
|
+
throw new Error(`Failed to get project info: ${error.message}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Get project branches
|
|
85
|
+
async getProjectBranches(projectKey) {
|
|
86
|
+
try {
|
|
87
|
+
const response = await this.axiosInstance.get(`/api/project_branches/list?project=${projectKey}`);
|
|
88
|
+
return response.data;
|
|
89
|
+
} catch (error) {
|
|
90
|
+
throw new Error(`Failed to get project branches: ${error.message}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Get project pull requests
|
|
95
|
+
async getProjectPullRequests(projectKey) {
|
|
96
|
+
try {
|
|
97
|
+
const response = await this.axiosInstance.get(`/api/pull_requests/list?project=${projectKey}`);
|
|
98
|
+
return response.data;
|
|
99
|
+
} catch (error) {
|
|
100
|
+
throw new Error(`Failed to get project pull requests: ${error.message}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Get project issues by file
|
|
105
|
+
async getIssuesByFile(projectKey, fileKey) {
|
|
106
|
+
try {
|
|
107
|
+
const response = await this.axiosInstance.get(`/api/issues/search?projectKeys=${projectKey}&fileUuids=${fileKey}`);
|
|
108
|
+
return response.data;
|
|
109
|
+
} catch (error) {
|
|
110
|
+
throw new Error(`Failed to get issues by file: ${error.message}`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Get project issues by component
|
|
115
|
+
async getIssuesByComponent(projectKey, componentKey) {
|
|
116
|
+
try {
|
|
117
|
+
const response = await this.axiosInstance.get(`/api/issues/search?projectKeys=${projectKey}&componentKeys=${componentKey}`);
|
|
118
|
+
return response.data;
|
|
119
|
+
} catch (error) {
|
|
120
|
+
throw new Error(`Failed to get issues by component: ${error.message}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Get project status (more reliable endpoint)
|
|
125
|
+
async getProjectStatus(projectKey) {
|
|
126
|
+
try {
|
|
127
|
+
const response = await this.axiosInstance.get(`/api/projects/search?projects=${projectKey}`);
|
|
128
|
+
return response.data;
|
|
129
|
+
} catch (error) {
|
|
130
|
+
throw new Error(`Failed to get project status: ${error.message}`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Get project metrics
|
|
135
|
+
async getProjectMetrics(projectKey, metricKeys) {
|
|
136
|
+
try {
|
|
137
|
+
const response = await this.axiosInstance.get(`/api/measures/component?component=${projectKey}&metricKeys=${metricKeys.join(',')}`);
|
|
138
|
+
return response.data;
|
|
139
|
+
} catch (error) {
|
|
140
|
+
throw new Error(`Failed to get project metrics: ${error.message}`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Get project issues by component (with proper error handling)
|
|
145
|
+
async getIssuesByComponentKey(projectKey, componentKey) {
|
|
146
|
+
try {
|
|
147
|
+
const response = await this.axiosInstance.get(`/api/issues/search?projectKeys=${projectKey}&componentKeys=${componentKey}`);
|
|
148
|
+
return response.data;
|
|
149
|
+
} catch (error) {
|
|
150
|
+
// If component key search fails, try with project key only
|
|
151
|
+
if (error.response && error.response.status === 400) {
|
|
152
|
+
console.log('Component search failed, falling back to project search');
|
|
153
|
+
const response = await this.axiosInstance.get(`/api/issues/search?projectKeys=${projectKey}`);
|
|
154
|
+
return response.data;
|
|
155
|
+
}
|
|
156
|
+
throw new Error(`Failed to get issues by component key: ${error.message}`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Get project issues by file (with proper error handling)
|
|
161
|
+
async getIssuesByFileKey(projectKey, fileKey) {
|
|
162
|
+
try {
|
|
163
|
+
const response = await this.axiosInstance.get(`/api/issues/search?projectKeys=${projectKey}&fileUuids=${fileKey}`);
|
|
164
|
+
return response.data;
|
|
165
|
+
} catch (error) {
|
|
166
|
+
// If file search fails, try with project key only
|
|
167
|
+
if (error.response && error.response.status === 400) {
|
|
168
|
+
console.log('File search failed, falling back to project search');
|
|
169
|
+
const response = await this.axiosInstance.get(`/api/issues/search?projectKeys=${projectKey}`);
|
|
170
|
+
return response.data;
|
|
171
|
+
}
|
|
172
|
+
throw new Error(`Failed to get issues by file key: ${error.message}`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Enhanced project information with MCP integration capability
|
|
177
|
+
async getEnhancedProjectInfo(projectKey) {
|
|
178
|
+
try {
|
|
179
|
+
// First, try to get project info
|
|
180
|
+
const projectInfo = await this.getProjectInfo(projectKey);
|
|
181
|
+
|
|
182
|
+
// Then try to get metrics if available
|
|
183
|
+
let metrics = null;
|
|
184
|
+
try {
|
|
185
|
+
metrics = await this.getProjectMetrics(projectKey, ['ncloc', 'bugs', 'vulnerabilities']);
|
|
186
|
+
} catch (metricsError) {
|
|
187
|
+
console.log('Metrics not available:', metricsError.message);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Then try to get branches if available
|
|
191
|
+
let branches = null;
|
|
192
|
+
try {
|
|
193
|
+
branches = await this.getProjectBranches(projectKey);
|
|
194
|
+
} catch (branchesError) {
|
|
195
|
+
console.log('Branches not available:', branchesError.message);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Then try to get pull requests if available
|
|
199
|
+
let pullRequests = null;
|
|
200
|
+
try {
|
|
201
|
+
pullRequests = await this.getProjectPullRequests(projectKey);
|
|
202
|
+
} catch (prError) {
|
|
203
|
+
console.log('Pull requests not available:', prError.message);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return {
|
|
207
|
+
projectInfo,
|
|
208
|
+
metrics,
|
|
209
|
+
branches,
|
|
210
|
+
pullRequests
|
|
211
|
+
};
|
|
212
|
+
} catch (error) {
|
|
213
|
+
throw new Error(`Failed to get enhanced project info: ${error.message}`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// MCP Server integration methods
|
|
218
|
+
// async getMCPProjectInfo(projectKey) {
|
|
219
|
+
// try {
|
|
220
|
+
// // This method is specifically designed to work with the SonarQube MCP server
|
|
221
|
+
// const projectInfo = await this.getProjectInfo(projectKey);
|
|
222
|
+
//
|
|
223
|
+
// // Try to use the MCP server's enhanced capabilities
|
|
224
|
+
// try {
|
|
225
|
+
// const mcpInfo = await this.getEnhancedProjectInfo(projectKey);
|
|
226
|
+
// return {
|
|
227
|
+
// ...mcpInfo,
|
|
228
|
+
// source: 'sonarqube_mcp'
|
|
229
|
+
// };
|
|
230
|
+
// } catch (mcpError) {
|
|
231
|
+
// console.log('MCP integration not available, returning basic project info');
|
|
232
|
+
// return {
|
|
233
|
+
// projectInfo,
|
|
234
|
+
// source: 'sonarqube_basic'
|
|
235
|
+
// };
|
|
236
|
+
// }
|
|
237
|
+
// } catch (error) {
|
|
238
|
+
// throw new Error(`Failed to get MCP project info: ${error.message}`);
|
|
239
|
+
// }
|
|
240
|
+
// }
|
|
241
|
+
|
|
242
|
+
// Method to check if MCP server is available and working
|
|
243
|
+
// async testMCPConnection() {
|
|
244
|
+
// try {
|
|
245
|
+
// // Test basic project search to verify connection
|
|
246
|
+
// const response = await this.axiosInstance.get(`/api/projects/search?projects=test`);
|
|
247
|
+
// return {
|
|
248
|
+
// connected: true,
|
|
249
|
+
// status: 'MCP server is accessible',
|
|
250
|
+
// response: response.data
|
|
251
|
+
// };
|
|
252
|
+
// } catch (error) {
|
|
253
|
+
// return {
|
|
254
|
+
// connected: false,
|
|
255
|
+
// status: `MCP server connection failed: ${error.message}`,
|
|
256
|
+
// error: error.message
|
|
257
|
+
// };
|
|
258
|
+
// }
|
|
259
|
+
// }
|
|
260
|
+
|
|
261
|
+
// Method to list projects using MCP server if available
|
|
262
|
+
async listProjects(query = '') {
|
|
263
|
+
try {
|
|
264
|
+
// First attempt to use MCP server's search function if available
|
|
265
|
+
const response = await this.axiosInstance.get(`/api/projects/search?q=${query}`);
|
|
266
|
+
return response.data;
|
|
267
|
+
} catch (error) {
|
|
268
|
+
// Fallback to basic project search
|
|
269
|
+
console.log('MCP search not available, using fallback');
|
|
270
|
+
const response = await this.axiosInstance.get(`/api/projects/search?projects=${query}`);
|
|
271
|
+
return response.data;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Run sonar-scanner analysis
|
|
276
|
+
async runAnalysis(projectKey) {
|
|
277
|
+
try {
|
|
278
|
+
const { stdout } = await execAsync(
|
|
279
|
+
`npx sonar-scanner -Dsonar.projectKey=${projectKey} -Dsonar.host.url=${this.baseUrl} -Dsonar.token=${this.token}`
|
|
280
|
+
);
|
|
281
|
+
return { success: true, output: stdout };
|
|
282
|
+
} catch (error) {
|
|
283
|
+
return { success: false, output: error.message };
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Wait for quality gate result by polling task status
|
|
288
|
+
async waitForQualityGate(projectKey, taskId, maxWaitMs = 120000) {
|
|
289
|
+
const pollInterval = 5000;
|
|
290
|
+
const startTime = Date.now();
|
|
291
|
+
|
|
292
|
+
while (Date.now() - startTime < maxWaitMs) {
|
|
293
|
+
const response = await this.axiosInstance.get(`/api/ce/task?id=${taskId}`);
|
|
294
|
+
const status = response.data.task.status;
|
|
295
|
+
|
|
296
|
+
if (['SUCCESS', 'FAILED', 'CANCELED'].includes(status)) {
|
|
297
|
+
return this.getQualityGate(projectKey);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
await new Promise(resolve => setTimeout(resolve, pollInterval));
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
throw new Error(`Quality gate timeout after ${maxWaitMs}ms`);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Get security hotspots for a project
|
|
307
|
+
async getHotspots(projectKey) {
|
|
308
|
+
try {
|
|
309
|
+
const response = await this.axiosInstance.get(`/api/hotspots/search?projectKey=${projectKey}`);
|
|
310
|
+
return response.data;
|
|
311
|
+
} catch (error) {
|
|
312
|
+
throw new Error(`Failed to get hotspots: ${error.message}`);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Get raw source code for a component
|
|
317
|
+
async getSourceCode(componentKey) {
|
|
318
|
+
try {
|
|
319
|
+
const response = await this.axiosInstance.get(`/api/sources/raw?key=${componentKey}`);
|
|
320
|
+
return response.data;
|
|
321
|
+
} catch (error) {
|
|
322
|
+
throw new Error(`Failed to get source code: ${error.message}`);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
module.exports = SonarClient;
|
package/package.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sonar-autofix/sonar-client",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Sonar client for the Sonar Autofix tool",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
8
|
+
},
|
|
9
|
+
"keywords": [
|
|
10
|
+
"sonar",
|
|
11
|
+
"autofix",
|
|
12
|
+
"github",
|
|
13
|
+
"client"
|
|
14
|
+
],
|
|
15
|
+
"author": "Sonar Autofix Team",
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"axios": "^1.19.0"
|
|
19
|
+
}
|
|
20
|
+
}
|