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.
Files changed (3) hide show
  1. package/README.md +74 -0
  2. package/dist/index.js +86 -29
  3. 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
- try {
17110
- const { appendFileSync: appendFileSync2 } = await import("node:fs");
17111
- appendFileSync2("/tmp/sonarqube-plugin-debug.log", `${new Date().toISOString()} [API] >>> ${method} ${endpoint} ${JSON.stringify({ url: url2, params, hasBody: !!body, bodyKeys: body ? Object.keys(body) : [] })}
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
- } catch {}
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
- try {
19407
- const { appendFileSync: appendFileSync4 } = await import("node:fs");
19408
- const dir = getDirectory();
19409
- appendFileSync4("/tmp/sonarqube-plugin-debug.log", `${new Date().toISOString()} [IDLE-HOOK] getDirectory()=${dir}
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
- } catch {}
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
- try {
20098
- const moduleLoadId = Math.random().toString(36).substring(7);
20099
- appendFileSync4("/tmp/sonarqube-plugin-debug.log", `${new Date().toISOString()} [LOAD] Module loaded! id=${moduleLoadId} cwd=${process.cwd()} import.meta.url=${import.meta.url}
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
- var sessionToDirectory = new Map;
20103
- var _lastInitializedDirectory = null;
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("/tmp/sonarqube-plugin-debug.log", `${new Date().toISOString()} [GLOBAL] ${msg}
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("/tmp/sonarqube-plugin-debug.log", `${new Date().toISOString()} [PLUGIN] ${msg}
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
- allRegisteredDirectories.add(effectiveDirectory);
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
- sessionToDirectory.set(payload.id, effectiveDirectory);
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
- safeLog(` sessionToDirectory size: ${sessionToDirectory.size}`);
20607
- safeLog(` sessionToDirectory entries: ${JSON.stringify([...sessionToDirectory.entries()])}`);
20608
- const sessionDir = sessionID ? sessionToDirectory.get(sessionID) : undefined;
20609
- safeLog(` sessionDir from map: "${sessionDir}"`);
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.2.9",
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.8",
41
+ "opencode-sonarqube": "0.2.10",
42
42
  "zod": "^3.24.0"
43
43
  },
44
44
  "devDependencies": {