opencode-sonarqube 0.2.9 → 0.3.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/README.md +74 -0
- package/dist/index.js +86 -29
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -381,6 +381,80 @@ The plugin provides 12 API modules for SonarQube interaction:
|
|
|
381
381
|
| `MetricsAPI` | Get detailed metrics with period comparison |
|
|
382
382
|
| `ComponentsAPI` | Get files/directories with issue counts |
|
|
383
383
|
|
|
384
|
+
## First Time Setup
|
|
385
|
+
|
|
386
|
+
When you use the plugin in a new project for the first time, you need to initialize it:
|
|
387
|
+
|
|
388
|
+
### Option 1: Let the AI do it
|
|
389
|
+
Simply tell the AI: "Set up SonarQube for this project" or "Initialize SonarQube"
|
|
390
|
+
|
|
391
|
+
### Option 2: Use the tool directly
|
|
392
|
+
```typescript
|
|
393
|
+
sonarqube({ action: "setup" })
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
This will:
|
|
397
|
+
1. Create a new project on your SonarQube server
|
|
398
|
+
2. Generate an authentication token
|
|
399
|
+
3. Create `.sonarqube/project.json` with the project configuration
|
|
400
|
+
4. Add `.sonarqube/` to your `.gitignore`
|
|
401
|
+
|
|
402
|
+
**Note:** The `.sonarqube/` directory contains sensitive tokens - never commit it!
|
|
403
|
+
|
|
404
|
+
## FAQ
|
|
405
|
+
|
|
406
|
+
### Where is the configuration stored?
|
|
407
|
+
|
|
408
|
+
| What | Location |
|
|
409
|
+
|------|----------|
|
|
410
|
+
| **Server credentials** | Environment variables (`SONAR_HOST_URL`, `SONAR_USER`, `SONAR_PASSWORD`) |
|
|
411
|
+
| **Plugin settings** | `.sonarqube/config.json` in your project (optional) |
|
|
412
|
+
| **Project state/tokens** | `.sonarqube/project.json` (auto-generated, don't commit!) |
|
|
413
|
+
| **OpenCode plugin list** | `opencode.json` |
|
|
414
|
+
|
|
415
|
+
### How do I enable debug logging?
|
|
416
|
+
|
|
417
|
+
Set the environment variable before starting OpenCode:
|
|
418
|
+
```bash
|
|
419
|
+
export SONARQUBE_DEBUG=true
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
Logs are written to `/tmp/sonarqube-plugin-debug.log`
|
|
423
|
+
|
|
424
|
+
### The plugin uses the wrong project directory
|
|
425
|
+
|
|
426
|
+
This can happen when multiple projects are open in OpenCode Desktop. The plugin uses `import.meta.url` to determine which project's `node_modules` it was loaded from. Make sure each project has its own installation:
|
|
427
|
+
|
|
428
|
+
```bash
|
|
429
|
+
cd /path/to/your/project
|
|
430
|
+
bun add opencode-sonarqube
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
### The quality gate shows issues but I just started
|
|
434
|
+
|
|
435
|
+
Run the setup first:
|
|
436
|
+
```typescript
|
|
437
|
+
sonarqube({ action: "setup" })
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
Then run an analysis:
|
|
441
|
+
```typescript
|
|
442
|
+
sonarqube({ action: "analyze" })
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
### How do I use this with multiple SonarQube servers?
|
|
446
|
+
|
|
447
|
+
Currently, the plugin uses global environment variables. For different servers per project, you'd need to set the environment variables differently per terminal session.
|
|
448
|
+
|
|
449
|
+
### Can I use this without OpenCode?
|
|
450
|
+
|
|
451
|
+
Yes! Use the CLI:
|
|
452
|
+
```bash
|
|
453
|
+
bun run src/index.ts --setup
|
|
454
|
+
bun run src/index.ts --analyze
|
|
455
|
+
bun run src/index.ts --status
|
|
456
|
+
```
|
|
457
|
+
|
|
384
458
|
## Requirements
|
|
385
459
|
|
|
386
460
|
- SonarQube server 9.9+ (tested with 26.1)
|
package/dist/index.js
CHANGED
|
@@ -4212,29 +4212,38 @@ ${entry}
|
|
|
4212
4212
|
await Bun.write(gitignorePath, newContent);
|
|
4213
4213
|
logger4.info("Added SonarQube exclusion to .gitignore");
|
|
4214
4214
|
}
|
|
4215
|
-
var LOG_FILE2 = "/tmp/sonarqube-plugin-debug.log", logger4, STATE_DIR = ".sonarqube", STATE_FILE = "project.json";
|
|
4215
|
+
var DEBUG2, LOG_FILE2 = "/tmp/sonarqube-plugin-debug.log", logger4, STATE_DIR = ".sonarqube", STATE_FILE = "project.json";
|
|
4216
4216
|
var init_state = __esm(() => {
|
|
4217
4217
|
init_types2();
|
|
4218
|
+
DEBUG2 = process.env["SONARQUBE_DEBUG"] === "true";
|
|
4218
4219
|
logger4 = {
|
|
4219
4220
|
info: (msg, extra) => {
|
|
4221
|
+
if (!DEBUG2)
|
|
4222
|
+
return;
|
|
4220
4223
|
try {
|
|
4221
4224
|
appendFileSync2(LOG_FILE2, `${new Date().toISOString()} [STATE] ${msg} ${extra ? JSON.stringify(extra) : ""}
|
|
4222
4225
|
`);
|
|
4223
4226
|
} catch {}
|
|
4224
4227
|
},
|
|
4225
4228
|
warn: (msg, extra) => {
|
|
4229
|
+
if (!DEBUG2)
|
|
4230
|
+
return;
|
|
4226
4231
|
try {
|
|
4227
4232
|
appendFileSync2(LOG_FILE2, `${new Date().toISOString()} [STATE-WARN] ${msg} ${extra ? JSON.stringify(extra) : ""}
|
|
4228
4233
|
`);
|
|
4229
4234
|
} catch {}
|
|
4230
4235
|
},
|
|
4231
4236
|
error: (msg, extra) => {
|
|
4237
|
+
if (!DEBUG2)
|
|
4238
|
+
return;
|
|
4232
4239
|
try {
|
|
4233
4240
|
appendFileSync2(LOG_FILE2, `${new Date().toISOString()} [STATE-ERROR] ${msg} ${extra ? JSON.stringify(extra) : ""}
|
|
4234
4241
|
`);
|
|
4235
4242
|
} catch {}
|
|
4236
4243
|
},
|
|
4237
4244
|
debug: (msg, extra) => {
|
|
4245
|
+
if (!DEBUG2)
|
|
4246
|
+
return;
|
|
4238
4247
|
try {
|
|
4239
4248
|
appendFileSync2(LOG_FILE2, `${new Date().toISOString()} [STATE-DEBUG] ${msg} ${extra ? JSON.stringify(extra) : ""}
|
|
4240
4249
|
`);
|
|
@@ -16566,21 +16575,28 @@ tool.schema = exports_external;
|
|
|
16566
16575
|
// src/utils/config.ts
|
|
16567
16576
|
init_types2();
|
|
16568
16577
|
import { appendFileSync } from "node:fs";
|
|
16578
|
+
var DEBUG = process.env["SONARQUBE_DEBUG"] === "true";
|
|
16569
16579
|
var LOG_FILE = "/tmp/sonarqube-plugin-debug.log";
|
|
16570
16580
|
var configLogger = {
|
|
16571
16581
|
info: (msg, extra) => {
|
|
16582
|
+
if (!DEBUG)
|
|
16583
|
+
return;
|
|
16572
16584
|
try {
|
|
16573
16585
|
appendFileSync(LOG_FILE, `${new Date().toISOString()} [CONFIG] ${msg} ${extra ? JSON.stringify(extra) : ""}
|
|
16574
16586
|
`);
|
|
16575
16587
|
} catch {}
|
|
16576
16588
|
},
|
|
16577
16589
|
warn: (msg, extra) => {
|
|
16590
|
+
if (!DEBUG)
|
|
16591
|
+
return;
|
|
16578
16592
|
try {
|
|
16579
16593
|
appendFileSync(LOG_FILE, `${new Date().toISOString()} [CONFIG-WARN] ${msg} ${extra ? JSON.stringify(extra) : ""}
|
|
16580
16594
|
`);
|
|
16581
16595
|
} catch {}
|
|
16582
16596
|
},
|
|
16583
16597
|
error: (msg, extra) => {
|
|
16598
|
+
if (!DEBUG)
|
|
16599
|
+
return;
|
|
16584
16600
|
try {
|
|
16585
16601
|
appendFileSync(LOG_FILE, `${new Date().toISOString()} [CONFIG-ERROR] ${msg} ${extra ? JSON.stringify(extra) : ""}
|
|
16586
16602
|
`);
|
|
@@ -17106,11 +17122,13 @@ class SonarQubeClient {
|
|
|
17106
17122
|
if (requestBody) {
|
|
17107
17123
|
headers["Content-Type"] = "application/x-www-form-urlencoded";
|
|
17108
17124
|
}
|
|
17109
|
-
|
|
17110
|
-
|
|
17111
|
-
|
|
17125
|
+
if (process.env["SONARQUBE_DEBUG"] === "true") {
|
|
17126
|
+
try {
|
|
17127
|
+
const { appendFileSync: appendFileSync2 } = await import("node:fs");
|
|
17128
|
+
appendFileSync2("/tmp/sonarqube-plugin-debug.log", `${new Date().toISOString()} [API] >>> ${method} ${endpoint} ${JSON.stringify({ url: url2, params, hasBody: !!body, bodyKeys: body ? Object.keys(body) : [] })}
|
|
17112
17129
|
`);
|
|
17113
|
-
|
|
17130
|
+
} catch {}
|
|
17131
|
+
}
|
|
17114
17132
|
try {
|
|
17115
17133
|
const response = await fetch(url2, {
|
|
17116
17134
|
method,
|
|
@@ -19403,12 +19421,14 @@ function formatActionPrompt(result, config2) {
|
|
|
19403
19421
|
}
|
|
19404
19422
|
function createIdleHook(getConfig, getDirectory) {
|
|
19405
19423
|
return async function handleSessionIdle() {
|
|
19406
|
-
|
|
19407
|
-
|
|
19408
|
-
|
|
19409
|
-
|
|
19424
|
+
if (process.env["SONARQUBE_DEBUG"] === "true") {
|
|
19425
|
+
try {
|
|
19426
|
+
const { appendFileSync: appendFileSync4 } = await import("node:fs");
|
|
19427
|
+
const dir = getDirectory();
|
|
19428
|
+
appendFileSync4("/tmp/sonarqube-plugin-debug.log", `${new Date().toISOString()} [IDLE-HOOK] getDirectory()=${dir}
|
|
19410
19429
|
`);
|
|
19411
|
-
|
|
19430
|
+
} catch {}
|
|
19431
|
+
}
|
|
19412
19432
|
const rawConfig = getConfig()?.["sonarqube"];
|
|
19413
19433
|
const config2 = loadConfig(rawConfig);
|
|
19414
19434
|
if (!isAnalysisEnabled(config2)) {
|
|
@@ -20094,20 +20114,58 @@ function getSeveritiesFromLevel(level) {
|
|
|
20094
20114
|
|
|
20095
20115
|
// src/index.ts
|
|
20096
20116
|
import { appendFileSync as appendFileSync4 } from "node:fs";
|
|
20097
|
-
|
|
20098
|
-
|
|
20099
|
-
|
|
20117
|
+
var DEBUG3 = process.env["SONARQUBE_DEBUG"] === "true";
|
|
20118
|
+
var LOG_FILE4 = "/tmp/sonarqube-plugin-debug.log";
|
|
20119
|
+
if (DEBUG3) {
|
|
20120
|
+
try {
|
|
20121
|
+
const moduleLoadId = Math.random().toString(36).substring(7);
|
|
20122
|
+
appendFileSync4(LOG_FILE4, `${new Date().toISOString()} [LOAD] Module loaded! id=${moduleLoadId} cwd=${process.cwd()} import.meta.url=${import.meta.url}
|
|
20100
20123
|
`);
|
|
20101
|
-
} catch {}
|
|
20102
|
-
|
|
20103
|
-
var
|
|
20104
|
-
var allRegisteredDirectories = new Set;
|
|
20124
|
+
} catch {}
|
|
20125
|
+
}
|
|
20126
|
+
var SHARED_STATE_FILE = "/tmp/sonarqube-plugin-shared-state.json";
|
|
20105
20127
|
var globalSafeLog = (msg) => {
|
|
20128
|
+
if (!DEBUG3)
|
|
20129
|
+
return;
|
|
20106
20130
|
try {
|
|
20107
|
-
appendFileSync4(
|
|
20131
|
+
appendFileSync4(LOG_FILE4, `${new Date().toISOString()} [GLOBAL] ${msg}
|
|
20108
20132
|
`);
|
|
20109
20133
|
} catch {}
|
|
20110
20134
|
};
|
|
20135
|
+
var readSharedState = () => {
|
|
20136
|
+
try {
|
|
20137
|
+
const content = __require("fs").readFileSync(SHARED_STATE_FILE, "utf-8");
|
|
20138
|
+
return JSON.parse(content);
|
|
20139
|
+
} catch {
|
|
20140
|
+
return { sessionToDirectory: {}, registeredDirectories: [], lastUpdated: "" };
|
|
20141
|
+
}
|
|
20142
|
+
};
|
|
20143
|
+
var writeSharedState = (state) => {
|
|
20144
|
+
try {
|
|
20145
|
+
state.lastUpdated = new Date().toISOString();
|
|
20146
|
+
__require("fs").writeFileSync(SHARED_STATE_FILE, JSON.stringify(state, null, 2));
|
|
20147
|
+
} catch (e) {
|
|
20148
|
+
globalSafeLog(`Failed to write shared state: ${e}`);
|
|
20149
|
+
}
|
|
20150
|
+
};
|
|
20151
|
+
var mapSessionToDirectory = (sessionId, directory) => {
|
|
20152
|
+
const state = readSharedState();
|
|
20153
|
+
state.sessionToDirectory[sessionId] = directory;
|
|
20154
|
+
writeSharedState(state);
|
|
20155
|
+
globalSafeLog(`Mapped session ${sessionId} to ${directory}`);
|
|
20156
|
+
};
|
|
20157
|
+
var getDirectoryForSession = (sessionId) => {
|
|
20158
|
+
const state = readSharedState();
|
|
20159
|
+
return state.sessionToDirectory[sessionId];
|
|
20160
|
+
};
|
|
20161
|
+
var registerDirectory = (directory) => {
|
|
20162
|
+
const state = readSharedState();
|
|
20163
|
+
if (!state.registeredDirectories.includes(directory)) {
|
|
20164
|
+
state.registeredDirectories.push(directory);
|
|
20165
|
+
writeSharedState(state);
|
|
20166
|
+
}
|
|
20167
|
+
globalSafeLog(`Registered directory: ${directory}, total: ${state.registeredDirectories.length}`);
|
|
20168
|
+
};
|
|
20111
20169
|
var IGNORED_FILE_PATTERNS2 = [
|
|
20112
20170
|
/node_modules/,
|
|
20113
20171
|
/\.git/,
|
|
@@ -20125,8 +20183,10 @@ function shouldIgnoreFile2(filePath) {
|
|
|
20125
20183
|
}
|
|
20126
20184
|
var SonarQubePlugin = async ({ client, directory, worktree }) => {
|
|
20127
20185
|
const safeLog = (msg) => {
|
|
20186
|
+
if (!DEBUG3)
|
|
20187
|
+
return;
|
|
20128
20188
|
try {
|
|
20129
|
-
appendFileSync4(
|
|
20189
|
+
appendFileSync4(LOG_FILE4, `${new Date().toISOString()} [PLUGIN] ${msg}
|
|
20130
20190
|
`);
|
|
20131
20191
|
} catch {}
|
|
20132
20192
|
};
|
|
@@ -20175,9 +20235,7 @@ var SonarQubePlugin = async ({ client, directory, worktree }) => {
|
|
|
20175
20235
|
};
|
|
20176
20236
|
const effectiveDirectory = resolveValidDirectory();
|
|
20177
20237
|
safeLog(`FINAL effectiveDirectory=${effectiveDirectory}`);
|
|
20178
|
-
|
|
20179
|
-
_lastInitializedDirectory = effectiveDirectory;
|
|
20180
|
-
globalSafeLog(`Registered directory: ${effectiveDirectory}, total registered: ${allRegisteredDirectories.size}`);
|
|
20238
|
+
registerDirectory(effectiveDirectory);
|
|
20181
20239
|
try {
|
|
20182
20240
|
await client.app.log({
|
|
20183
20241
|
body: {
|
|
@@ -20345,9 +20403,7 @@ After fixing, I will re-run the analysis to verify.`;
|
|
|
20345
20403
|
if (payload?.id) {
|
|
20346
20404
|
currentSessionId = payload.id;
|
|
20347
20405
|
if (event.type === "session.created") {
|
|
20348
|
-
|
|
20349
|
-
globalSafeLog(`Session ${payload.id} mapped to directory: ${effectiveDirectory}`);
|
|
20350
|
-
globalSafeLog(`Total sessions tracked: ${sessionToDirectory.size}`);
|
|
20406
|
+
mapSessionToDirectory(payload.id, effectiveDirectory);
|
|
20351
20407
|
}
|
|
20352
20408
|
}
|
|
20353
20409
|
};
|
|
@@ -20603,10 +20659,11 @@ Git operation completed with changes. Consider running:
|
|
|
20603
20659
|
const inputAny = _input;
|
|
20604
20660
|
const sessionID = inputAny?.sessionID;
|
|
20605
20661
|
safeLog(` sessionID: "${sessionID}"`);
|
|
20606
|
-
|
|
20607
|
-
safeLog(`
|
|
20608
|
-
|
|
20609
|
-
|
|
20662
|
+
const sharedState = readSharedState();
|
|
20663
|
+
safeLog(` sharedState sessions: ${JSON.stringify(sharedState.sessionToDirectory)}`);
|
|
20664
|
+
safeLog(` sharedState directories: ${JSON.stringify(sharedState.registeredDirectories)}`);
|
|
20665
|
+
const sessionDir = sessionID ? getDirectoryForSession(sessionID) : undefined;
|
|
20666
|
+
safeLog(` sessionDir from shared state: "${sessionDir}"`);
|
|
20610
20667
|
safeLog(` effectiveDirectory (fallback): "${effectiveDirectory}"`);
|
|
20611
20668
|
const dir = sessionDir || effectiveDirectory;
|
|
20612
20669
|
safeLog(` FINAL dir used: "${dir}"`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-sonarqube",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "OpenCode Plugin for SonarQube integration - Enterprise-level code quality from the start",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"homepage": "https://github.com/mguttmann/opencode-sonarqube#readme",
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"@opencode-ai/plugin": "^1.1.34",
|
|
41
|
-
"opencode-sonarqube": "0.2.
|
|
41
|
+
"opencode-sonarqube": "0.2.10",
|
|
42
42
|
"zod": "^3.24.0"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|