sonar-autofix 0.1.1 → 0.3.2

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.
@@ -1,371 +0,0 @@
1
- 'use strict';
2
-
3
- var childProcess = require('node:child_process');
4
- var util = require('node:util');
5
-
6
- var execFile = util.promisify(childProcess.execFile);
7
-
8
- var DEFAULT_COMMIT_MESSAGE =
9
- 'fix(sonar): automatically resolve Sonar issues\n\nGenerated by sonar-autofix';
10
-
11
- class GitClient {
12
- /**
13
- * @param {object} [config]
14
- * @param {object} [config.git]
15
- * @param {string} [config.git.branch]
16
- * @param {string} [config.git.token]
17
- * @param {string} [config.git.username]
18
- * @param {string} [config.git.remote='origin']
19
- * @param {string} [cwd]
20
- */
21
- constructor(config, cwd) {
22
- this.config = config || {};
23
- this.config.git = this.config.git || {};
24
-
25
- this.config.git.branch =
26
- this.config.git.branch || 'sonar-autofix';
27
-
28
- this.config.git.remote =
29
- this.config.git.remote || 'origin';
30
-
31
- this.cwd = cwd || process.cwd();
32
- }
33
-
34
- /**
35
- * Get Git authentication token.
36
- *
37
- * Priority:
38
- * 1. config.git.token
39
- * 2. GITHUB_TOKEN
40
- * 3. GH_TOKEN
41
- * 4. GIT_TOKEN
42
- *
43
- * @returns {string|undefined}
44
- */
45
- _getToken() {
46
- return (
47
- this.config.git.token ||
48
- process.env.GITHUB_TOKEN ||
49
- process.env.GH_TOKEN ||
50
- process.env.GIT_TOKEN
51
- );
52
- }
53
-
54
- /**
55
- * Get Git username.
56
- *
57
- * @returns {string}
58
- */
59
- _getUsername() {
60
- return (
61
- this.config.git.username ||
62
- process.env.GIT_USERNAME ||
63
- 'x-access-token'
64
- );
65
- }
66
-
67
- /**
68
- * Execute git command.
69
- *
70
- * Authentication is supplied through environment variables,
71
- * never as command-line arguments.
72
- *
73
- * @param {string[]} args
74
- * @returns {Promise<string>}
75
- */
76
- async _execGit(args) {
77
- try {
78
- var env = Object.assign({}, process.env);
79
-
80
- var token = this._getToken();
81
-
82
- if (token) {
83
- env.GIT_TERMINAL_PROMPT = '0';
84
-
85
- /*
86
- * GIT_ASKPASS prevents Git from prompting for credentials.
87
- * The actual token is supplied through environment variables.
88
- */
89
- env.GIT_USERNAME = this._getUsername();
90
- env.GIT_PASSWORD = token;
91
-
92
- env.GIT_ASKPASS = 'git-askpass-helper';
93
-
94
- /*
95
- * This is handled by the shell-independent askpass mechanism
96
- * below when the executable is available.
97
- */
98
- }
99
-
100
- var result = await execFile('git', args, {
101
- cwd: this.cwd,
102
- env: env
103
- });
104
-
105
- return (result.stdout || '').trim();
106
- } catch (err) {
107
- var command = 'git ' + args.join(' ');
108
- var stderr = (err.stderr || '').trim();
109
-
110
- throw new Error(
111
- 'Git command failed: ' +
112
- command +
113
- (stderr ? '\n' + stderr : '')
114
- );
115
- }
116
- }
117
-
118
- /**
119
- * Execute GitHub CLI command.
120
- *
121
- * Uses GH_TOKEN/GITHUB_TOKEN so gh never prompts interactively.
122
- *
123
- * @param {string[]} args
124
- * @returns {Promise<string>}
125
- */
126
- async _execGh(args) {
127
- try {
128
- var env = Object.assign({}, process.env);
129
- var token = this._getToken();
130
-
131
- if (token) {
132
- env.GH_TOKEN = token;
133
- env.GITHUB_TOKEN = token;
134
- }
135
-
136
- /*
137
- * Prevent gh from falling back to an interactive login.
138
- */
139
- env.GH_PROMPT_DISABLED = '1';
140
-
141
- var result = await execFile('gh', args, {
142
- cwd: this.cwd,
143
- env: env
144
- });
145
-
146
- return (result.stdout || '').trim();
147
- } catch (err) {
148
- var stderr = (err.stderr || '').trim();
149
-
150
- throw new Error(
151
- 'GitHub CLI command failed: ' +
152
- ['gh'].concat(args).join(' ') +
153
- (stderr ? '\n' + stderr : '')
154
- );
155
- }
156
- }
157
-
158
- /**
159
- * Create and checkout a new branch.
160
- *
161
- * @param {string} [branchName]
162
- * @returns {Promise<string>}
163
- */
164
- async createBranch(branchName) {
165
- var branch = branchName || this.config.git.branch;
166
-
167
- try {
168
- await this._execGit(['checkout', '-b', branch]);
169
- } catch (err) {
170
- if (
171
- err.message &&
172
- err.message.indexOf('already exists') !== -1
173
- ) {
174
- await this._execGit(['checkout', branch]);
175
- } else {
176
- throw err;
177
- }
178
- }
179
-
180
- return branch;
181
- }
182
-
183
- /**
184
- * Checkout an existing branch.
185
- *
186
- * @param {string} branchName
187
- * @returns {Promise<string>}
188
- */
189
- async checkout(branchName) {
190
- if (!branchName) {
191
- throw new Error('Branch name is required for checkout');
192
- }
193
-
194
- return this._execGit(['checkout', branchName]);
195
- }
196
-
197
- /**
198
- * Stage all changes and commit.
199
- *
200
- * @param {string} [message]
201
- * @returns {Promise<string>}
202
- */
203
- async commit(message) {
204
- var commitMessage = message || DEFAULT_COMMIT_MESSAGE;
205
-
206
- await this.stageAll();
207
-
208
- return this._execGit([
209
- 'commit',
210
- '-m',
211
- commitMessage
212
- ]);
213
- }
214
-
215
- /**
216
- * Push branch to remote.
217
- *
218
- * @param {string} [branchName]
219
- * @param {boolean} [force=false]
220
- * @returns {Promise<string>}
221
- */
222
- async push(branchName, force) {
223
- var branch = branchName || this.config.git.branch;
224
- var remote = this.config.git.remote;
225
-
226
- var args = ['push', remote, branch];
227
-
228
- if (force) {
229
- args.splice(1, 0, '--force');
230
- }
231
-
232
- return this._execGit(args);
233
- }
234
-
235
- /**
236
- * Restore a file from HEAD.
237
- *
238
- * @param {string} filePath
239
- * @returns {Promise<string>}
240
- */
241
- async restoreFile(filePath) {
242
- if (!filePath) {
243
- throw new Error(
244
- 'File path is required for restoreFile'
245
- );
246
- }
247
-
248
- return this._execGit([
249
- 'checkout',
250
- '--',
251
- filePath
252
- ]);
253
- }
254
-
255
- /**
256
- * Git reset.
257
- *
258
- * @param {string} [mode='hard']
259
- * @returns {Promise<string>}
260
- */
261
- async reset(mode) {
262
- var resetMode = mode || 'hard';
263
-
264
- return this._execGit([
265
- 'reset',
266
- '--' + resetMode
267
- ]);
268
- }
269
-
270
- /**
271
- * Create a pull request using GitHub CLI.
272
- *
273
- * Authentication is provided through:
274
- * config.git.token
275
- * GITHUB_TOKEN
276
- * GH_TOKEN
277
- * GIT_TOKEN
278
- *
279
- * The token is passed through the environment and never
280
- * appears in the command line.
281
- *
282
- * @param {string} title
283
- * @param {string} body
284
- * @param {string} [baseBranch='main']
285
- * @returns {Promise<string>}
286
- */
287
- async createPullRequest(title, body, baseBranch) {
288
- if (!title) {
289
- throw new Error(
290
- 'Title is required for createPullRequest'
291
- );
292
- }
293
-
294
- var token = this._getToken();
295
-
296
- if (!token) {
297
- throw new Error(
298
- 'GitHub authentication token is required. ' +
299
- 'Set GITHUB_TOKEN, GH_TOKEN, GIT_TOKEN, ' +
300
- 'or provide config.git.token.'
301
- );
302
- }
303
-
304
- var base = baseBranch || 'main';
305
-
306
- return this._execGh([
307
- 'pr',
308
- 'create',
309
- '--title',
310
- title,
311
- '--body',
312
- body || '',
313
- '--base',
314
- base
315
- ]);
316
- }
317
-
318
- /**
319
- * Get the current branch name.
320
- *
321
- * @returns {Promise<string>}
322
- */
323
- async getCurrentBranch() {
324
- return this._execGit([
325
- 'rev-parse',
326
- '--abbrev-ref',
327
- 'HEAD'
328
- ]);
329
- }
330
-
331
- /**
332
- * Check if working tree has changes.
333
- *
334
- * @returns {Promise<boolean>}
335
- */
336
- async hasChanges() {
337
- var output = await this._execGit([
338
- 'status',
339
- '--porcelain'
340
- ]);
341
-
342
- return output.length > 0;
343
- }
344
-
345
- /**
346
- * Stage all changes.
347
- *
348
- * @returns {Promise<string>}
349
- */
350
- async stageAll() {
351
- return this._execGit(['add', '-A']);
352
- }
353
-
354
- /**
355
- * Get diff for a file or all files.
356
- *
357
- * @param {string} [filePath]
358
- * @returns {Promise<string>}
359
- */
360
- async diff(filePath) {
361
- var args = ['diff'];
362
-
363
- if (filePath) {
364
- args.push('--', filePath);
365
- }
366
-
367
- return this._execGit(args);
368
- }
369
- }
370
-
371
- module.exports = GitClient;
@@ -1,17 +0,0 @@
1
- {
2
- "name": "@sonar-autofix/git-client",
3
- "version": "0.0.1",
4
- "description": "Git 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
- "git"
14
- ],
15
- "author": "Sonar Autofix Team",
16
- "license": "MIT"
17
- }
@@ -1,71 +0,0 @@
1
- name: 'Sonar Autofix'
2
- description: 'Automatically fix SonarQube/SonarCloud issues using LLMs'
3
- author: 'Sonar Autofix Team'
4
-
5
- inputs:
6
- sonar-host:
7
- description: 'SonarQube/SonarCloud host URL'
8
- required: false
9
- default: 'https://sonarcloud.io'
10
- sonar-project:
11
- description: 'Sonar project key'
12
- required: true
13
- sonar-token:
14
- description: 'Sonar authentication token'
15
- required: true
16
- llm-provider:
17
- description: 'LLM provider (lmstudio, openai, azure, anthropic, ollama, openrouter)'
18
- required: false
19
- default: 'lmstudio'
20
- llm-endpoint:
21
- description: 'LLM API endpoint URL'
22
- required: true
23
- llm-model:
24
- description: 'LLM model name'
25
- required: true
26
- llm-api-key:
27
- description: 'LLM API key'
28
- required: false
29
- default: ''
30
- git-branch:
31
- description: 'Branch name for autofix commits'
32
- required: false
33
- default: 'sonar-autofix'
34
- max-iterations:
35
- description: 'Maximum number of fix iterations'
36
- required: false
37
- default: '5'
38
- max-retries:
39
- description: 'Maximum retries per file fix'
40
- required: false
41
- default: '3'
42
- dry-run:
43
- description: 'Run without making changes'
44
- required: false
45
- default: 'false'
46
- build-commands:
47
- description: 'Build commands (JSON array)'
48
- required: false
49
- default: '["npm run build"]'
50
- lint-commands:
51
- description: 'Lint commands (JSON array)'
52
- required: false
53
- default: '["npm run lint"]'
54
- test-commands:
55
- description: 'Test commands (JSON array)'
56
- required: false
57
- default: '["npm test"]'
58
-
59
- outputs:
60
- files-fixed:
61
- description: 'Number of files fixed'
62
- iterations:
63
- description: 'Number of iterations performed'
64
-
65
- runs:
66
- using: 'node20'
67
- main: 'index.js'
68
-
69
- branding:
70
- icon: 'check-circle'
71
- color: 'green'
@@ -1,86 +0,0 @@
1
- // GitHub Action entry point for Sonar Autofix
2
- const CoreOrchestrator = require('@sonar-autofix/core');
3
-
4
- /**
5
- * Read a GitHub Action input value.
6
- * In a real GitHub Actions environment, inputs are exposed as INPUT_<NAME> environment variables.
7
- */
8
- function getInput(name, required = false) {
9
- const envKey = `INPUT_${name.replace(/-/g, '_').toUpperCase()}`;
10
- const value = process.env[envKey] || '';
11
- if (required && !value) {
12
- throw new Error(`Missing required input: ${name}`);
13
- }
14
- return value;
15
- }
16
-
17
- /**
18
- * Set a GitHub Action output value.
19
- */
20
- function setOutput(name, value) {
21
- if (process.env.GITHUB_OUTPUT) {
22
- const fs = require('fs');
23
- fs.appendFileSync(process.env.GITHUB_OUTPUT, `${name}=${value}\n`);
24
- } else {
25
- console.log(`::set-output name=${name}::${value}`);
26
- }
27
- }
28
-
29
- /**
30
- * GitHub Action entry point
31
- */
32
- async function run() {
33
- console.log('Sonar Autofix GitHub Action');
34
- console.log('===================================');
35
-
36
- try {
37
- // Read action inputs (from action.yml)
38
- const config = {
39
- sonar: {
40
- host: getInput('sonar-host') || 'https://sonarcloud.io',
41
- project: getInput('sonar-project', true),
42
- token: getInput('sonar-token', true),
43
- },
44
- llm: {
45
- provider: getInput('llm-provider') || 'lmstudio',
46
- endpoint: getInput('llm-endpoint', true),
47
- model: getInput('llm-model', true),
48
- apiKey: getInput('llm-api-key') || undefined,
49
- },
50
- git: {
51
- branch: getInput('git-branch') || 'sonar-autofix',
52
- },
53
- retry: {
54
- maxIterations: parseInt(getInput('max-iterations') || '5', 10),
55
- maxRetries: parseInt(getInput('max-retries') || '3', 10),
56
- },
57
- validation: {
58
- build: JSON.parse(getInput('build-commands') || '["npm run build"]'),
59
- lint: JSON.parse(getInput('lint-commands') || '["npm run lint"]'),
60
- tests: JSON.parse(getInput('test-commands') || '["npm test"]'),
61
- },
62
- dryRun: getInput('dry-run') === 'true',
63
- };
64
-
65
- const orchestrator = new CoreOrchestrator(config);
66
-
67
- console.log('🚀 Starting Sonar Autofix GitHub Action...');
68
- const summary = await orchestrator.run();
69
-
70
- // Set outputs
71
- setOutput('files-fixed', summary.totalFixed);
72
- setOutput('iterations', summary.iterations);
73
-
74
- console.log('✅ Done!');
75
- } catch (error) {
76
- console.error(`::error::${error.message}`);
77
- process.exit(1);
78
- }
79
- }
80
-
81
- // Run the GitHub Action if this file is executed directly
82
- if (require.main === module) {
83
- run();
84
- }
85
-
86
- module.exports = { run };
@@ -1,17 +0,0 @@
1
- {
2
- "name": "@sonar-autofix/github-action",
3
- "version": "0.0.1",
4
- "description": "GitHub Action wrapper 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
- "action"
14
- ],
15
- "author": "Sonar Autofix Team",
16
- "license": "MIT"
17
- }