opencode-sonarqube 0.2.0 → 0.2.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.
Files changed (3) hide show
  1. package/README.md +77 -67
  2. package/dist/index.js +86 -152
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -85,36 +85,46 @@ Add these to your `~/.zshrc` or `~/.bashrc` to make them permanent.
85
85
 
86
86
  ## Configuration
87
87
 
88
- ### Using the Configuration Script
88
+ ### Environment Variables (Required)
89
+
90
+ Add these to your `~/.zshrc` or `~/.bashrc`:
89
91
 
90
92
  ```bash
91
- ./scripts/configure.sh
93
+ export SONAR_HOST_URL="https://your-sonarqube-server.com"
94
+ export SONAR_USER="admin"
95
+ export SONAR_PASSWORD="your-password"
92
96
  ```
93
97
 
94
- This interactive script allows you to:
95
- - Update SonarQube URL
96
- - Change credentials
97
- - Test connection
98
- - Reset project state
99
-
100
- ### Environment Variables (Required)
101
-
102
- | Variable | Description |
103
- |----------|-------------|
104
- | `SONAR_HOST_URL` | SonarQube server URL (e.g., `https://sonarqube.example.com`) |
105
- | `SONAR_USER` | Username for authentication |
106
- | `SONAR_PASSWORD` | Password for authentication |
98
+ ### Plugin Configuration (Optional)
107
99
 
108
- ### Default Behavior
100
+ Create `.sonarqube/config.json` in your project root:
109
101
 
110
- The plugin uses these defaults (configurable in future versions):
102
+ ```json
103
+ {
104
+ "level": "enterprise",
105
+ "autoAnalyze": true,
106
+ "autoFix": false,
107
+ "sources": "src",
108
+ "tests": "tests",
109
+ "exclusions": "**/node_modules/**,**/dist/**",
110
+ "newCodeDefinition": "previous_version"
111
+ }
112
+ ```
111
113
 
112
- | Setting | Default | Description |
113
- |---------|---------|-------------|
114
- | Level | `enterprise` | Strictest quality requirements |
115
- | Auto-Analyze | `true` | Analyze when AI becomes idle |
116
- | Auto-Fix | `false` | Don't auto-fix issues |
117
- | Sources | `src` | Source directory |
114
+ ### All Configuration Options
115
+
116
+ | Option | Type | Default | Description |
117
+ |--------|------|---------|-------------|
118
+ | `level` | `"enterprise"` \| `"standard"` \| `"relaxed"` \| `"off"` | `"enterprise"` | Analysis strictness level |
119
+ | `autoAnalyze` | `boolean` | `true` | Auto-analyze when AI becomes idle |
120
+ | `autoFix` | `boolean` | `false` | Automatically attempt to fix issues |
121
+ | `projectKey` | `string` | auto | SonarQube project key (auto-generated from package.json or directory) |
122
+ | `projectName` | `string` | auto | Display name on SonarQube |
123
+ | `qualityGate` | `string` | `"Sonar way"` | Quality gate to use |
124
+ | `newCodeDefinition` | `"previous_version"` \| `"number_of_days"` \| `"reference_branch"` \| `"specific_analysis"` | `"previous_version"` | How to define 'new code' |
125
+ | `sources` | `string` | `"src"` | Source directories (comma-separated) |
126
+ | `tests` | `string` | - | Test directories (comma-separated) |
127
+ | `exclusions` | `string` | - | File exclusion patterns (glob) |
118
128
 
119
129
  ### Strictness Levels
120
130
 
@@ -125,6 +135,34 @@ The plugin uses these defaults (configurable in future versions):
125
135
  | `relaxed` | Only blocker/critical, blocks on blocker |
126
136
  | `off` | Plugin disabled |
127
137
 
138
+ ### Example Configurations
139
+
140
+ **Enterprise (strictest)**:
141
+ ```json
142
+ {
143
+ "level": "enterprise",
144
+ "autoAnalyze": true,
145
+ "autoFix": false
146
+ }
147
+ ```
148
+
149
+ **Standard (balanced)**:
150
+ ```json
151
+ {
152
+ "level": "standard",
153
+ "autoAnalyze": true,
154
+ "sources": "src,lib"
155
+ }
156
+ ```
157
+
158
+ **Relaxed (lenient)**:
159
+ ```json
160
+ {
161
+ "level": "relaxed",
162
+ "autoAnalyze": false
163
+ }
164
+ ```
165
+
128
166
  ## Tool Actions (15 total)
129
167
 
130
168
  The plugin adds a `sonarqube` tool with these actions:
@@ -296,51 +334,23 @@ bun run src/index.ts --status --project-key=my-project
296
334
  bun run src/index.ts --setup --force
297
335
  ```
298
336
 
299
- ## Programmatic API
337
+ ## Project State
300
338
 
301
- ```typescript
302
- import {
303
- createSonarQubeAPI,
304
- loadConfig,
305
- getProjectState,
306
- runAnalysis,
307
- bootstrap
308
- } from "opencode-sonarqube";
309
-
310
- // Create API client
311
- const config = loadConfig();
312
- const state = await getProjectState("./");
313
- const api = createSonarQubeAPI(config, state);
314
-
315
- // Health check
316
- const health = await api.healthCheck();
317
- console.log("Healthy:", health.healthy);
318
-
319
- // Get issues
320
- const issues = await api.issues.getFormattedIssues({
321
- projectKey: "my-project",
322
- severities: ["BLOCKER", "CRITICAL"],
323
- });
324
-
325
- // Get quality gate status
326
- const status = await api.qualityGate.getStatus("my-project");
327
- console.log("Status:", status.projectStatus.status);
328
-
329
- // Get new code issues only
330
- const newIssues = await api.issues.search({
331
- projectKey: "my-project",
332
- inNewCode: true,
333
- });
334
-
335
- // Get worst files for refactoring
336
- const worstFiles = await api.components.getWorstFiles("my-project", 10);
337
-
338
- // Run full analysis
339
- const result = await runAnalysis(config, state, { projectKey: "my-project" }, "./");
340
- console.log("Quality Gate:", result.qualityGateStatus);
339
+ The plugin stores project state in `.sonarqube/project.json`:
340
+
341
+ ```json
342
+ {
343
+ "projectKey": "my-project",
344
+ "projectToken": "sqp_xxx...",
345
+ "tokenName": "opencode-my-project-...",
346
+ "initializedAt": "2024-01-01T00:00:00.000Z",
347
+ "languages": ["typescript", "javascript"],
348
+ "qualityGate": "Sonar way",
349
+ "setupComplete": true
350
+ }
341
351
  ```
342
352
 
343
- See [API Documentation](./docs/API.md) for complete reference.
353
+ **Important:** Add `.sonarqube/` to your `.gitignore` - it contains authentication tokens!
344
354
 
345
355
  ## Documentation
346
356
 
@@ -383,16 +393,16 @@ This project maintains enterprise-level quality:
383
393
 
384
394
  | Metric | Value |
385
395
  |--------|-------|
386
- | Test Coverage | 100% |
396
+ | Test Coverage | 87.9% |
387
397
  | Tests | 626 |
388
398
  | Bugs | 0 |
389
399
  | Vulnerabilities | 0 |
390
400
  | Code Smells | 0 |
391
401
  | Duplications | 0% |
392
- | Quality Gate | Passed |
393
402
  | Reliability Rating | A |
394
403
  | Security Rating | A |
395
404
  | Maintainability Rating | A |
405
+ | Lines of Code | ~6,000 |
396
406
 
397
407
  ## License
398
408
 
package/dist/index.js CHANGED
@@ -19178,33 +19178,43 @@ function generateTokenName(projectKey) {
19178
19178
  const uuid3 = crypto.randomUUID().split("-")[0];
19179
19179
  return `opencode-${projectKey}-${timestamp}-${uuid3}`;
19180
19180
  }
19181
- async function bootstrap(options) {
19182
- let { config: config2, directory, force = false } = options;
19181
+ function isValidDirectory(dir) {
19182
+ return Boolean(dir && dir !== "/" && dir !== "." && dir.length >= 2);
19183
+ }
19184
+ function resolveDirectoryFromImportMeta() {
19183
19185
  try {
19184
- const { appendFileSync: appendFileSync4 } = await import("node:fs");
19185
- appendFileSync4("/tmp/sonarqube-plugin-debug.log", `${new Date().toISOString()} [BOOTSTRAP] Starting bootstrap directory=${directory} config.projectKey="${config2.projectKey || "(empty)"}"
19186
- `);
19187
- } catch {}
19188
- if (!directory || directory === "/" || directory === "." || directory.length < 2) {
19189
- try {
19190
- const pluginUrl = import.meta.url;
19191
- const pluginPath = decodeURIComponent(pluginUrl.replace("file://", ""));
19192
- const pathParts = pluginPath.split("/");
19193
- const nodeModulesIndex = pathParts.findIndex((p) => p === "node_modules");
19194
- if (nodeModulesIndex > 0) {
19195
- const projectPath = pathParts.slice(0, nodeModulesIndex).join("/");
19196
- if (projectPath && projectPath !== "/" && projectPath.length > 1) {
19197
- directory = projectPath;
19198
- try {
19199
- const { appendFileSync: appendFileSync4 } = await import("node:fs");
19200
- appendFileSync4("/tmp/sonarqube-plugin-debug.log", `${new Date().toISOString()} [BOOTSTRAP] Fixed directory from import.meta.url: ${directory}
19201
- `);
19202
- } catch {}
19203
- }
19186
+ const pluginUrl = import.meta.url;
19187
+ const pluginPath = decodeURIComponent(pluginUrl.replace("file://", ""));
19188
+ const pathParts = pluginPath.split("/");
19189
+ const nodeModulesIndex = pathParts.indexOf("node_modules");
19190
+ if (nodeModulesIndex > 0) {
19191
+ const projectPath = pathParts.slice(0, nodeModulesIndex).join("/");
19192
+ if (isValidDirectory(projectPath)) {
19193
+ return projectPath;
19204
19194
  }
19205
- } catch {}
19195
+ }
19196
+ } catch {}
19197
+ return null;
19198
+ }
19199
+ async function generateAnalysisToken(client, tokenName, projectKey) {
19200
+ try {
19201
+ return await client.post("/api/user_tokens/generate", { name: tokenName, type: "PROJECT_ANALYSIS_TOKEN", projectKey });
19202
+ } catch {
19203
+ logger5.warn("PROJECT_ANALYSIS_TOKEN not available, using GLOBAL_ANALYSIS_TOKEN");
19204
+ return await client.post("/api/user_tokens/generate", { name: tokenName, type: "GLOBAL_ANALYSIS_TOKEN" });
19206
19205
  }
19207
- if (!directory || directory === "/" || directory === "." || directory.length < 2) {
19206
+ }
19207
+ async function bootstrap(options) {
19208
+ let { config: config2, directory, force = false } = options;
19209
+ logger5.info("Starting bootstrap", { directory, projectKey: config2.projectKey || "(auto)" });
19210
+ if (!isValidDirectory(directory)) {
19211
+ const resolved = resolveDirectoryFromImportMeta();
19212
+ if (resolved) {
19213
+ directory = resolved;
19214
+ logger5.info("Resolved directory from import.meta.url", { directory });
19215
+ }
19216
+ }
19217
+ if (!isValidDirectory(directory)) {
19208
19218
  logger5.error("Invalid directory for bootstrap", { directory });
19209
19219
  return {
19210
19220
  success: false,
@@ -19212,21 +19222,20 @@ async function bootstrap(options) {
19212
19222
  projectToken: "",
19213
19223
  qualityGate: "",
19214
19224
  languages: [],
19215
- message: `Invalid directory: ${directory}. OpenCode may have passed the wrong working directory.`,
19225
+ message: `Invalid directory: ${directory}`,
19216
19226
  isNewProject: false
19217
19227
  };
19218
19228
  }
19219
- logger5.info("Starting SonarQube bootstrap", { directory });
19220
19229
  if (!force) {
19221
- const state2 = await loadProjectState(directory);
19222
- if (state2?.setupComplete) {
19223
- logger5.info("Project already bootstrapped", { projectKey: state2.projectKey });
19230
+ const existingState = await loadProjectState(directory);
19231
+ if (existingState?.setupComplete) {
19232
+ logger5.info("Project already bootstrapped", { projectKey: existingState.projectKey });
19224
19233
  return {
19225
19234
  success: true,
19226
- projectKey: state2.projectKey,
19227
- projectToken: state2.projectToken,
19228
- qualityGate: state2.qualityGate,
19229
- languages: state2.languages,
19235
+ projectKey: existingState.projectKey,
19236
+ projectToken: existingState.projectToken,
19237
+ qualityGate: existingState.qualityGate,
19238
+ languages: existingState.languages,
19230
19239
  message: "Project already configured",
19231
19240
  isNewProject: false
19232
19241
  };
@@ -19239,49 +19248,24 @@ async function bootstrap(options) {
19239
19248
  }
19240
19249
  logger5.info("Connected to SonarQube", { version: health.version });
19241
19250
  const detection = await detectProjectType(directory);
19242
- logger5.info("Detected project type", { languages: detection.languages });
19243
19251
  const projectKey = config2.projectKey || sanitizeProjectKey(await deriveProjectKey(directory));
19244
19252
  const projectName = config2.projectName || projectKey;
19245
- logger5.info("Project identification", { projectKey, projectName });
19246
19253
  const projectsApi = new ProjectsAPI(adminClient);
19247
- let isNewProject = false;
19248
19254
  const exists = await projectsApi.exists(projectKey);
19249
- if (exists) {
19250
- logger5.info("Project already exists on SonarQube", { projectKey });
19251
- } else {
19252
- logger5.info("Creating new project on SonarQube", { projectKey });
19253
- await projectsApi.create({
19254
- projectKey,
19255
- name: projectName,
19256
- visibility: "private"
19257
- });
19258
- isNewProject = true;
19255
+ const isNewProject = !exists;
19256
+ if (isNewProject) {
19257
+ await projectsApi.create({ projectKey, name: projectName, visibility: "private" });
19258
+ logger5.info("Created new project", { projectKey });
19259
19259
  }
19260
19260
  const tokenName = generateTokenName(projectKey);
19261
- logger5.info("Generating project token", { tokenName });
19262
- let tokenResponse;
19263
- try {
19264
- tokenResponse = await adminClient.post("/api/user_tokens/generate", {
19265
- name: tokenName,
19266
- type: "PROJECT_ANALYSIS_TOKEN",
19267
- projectKey
19268
- });
19269
- } catch {
19270
- logger5.warn("PROJECT_ANALYSIS_TOKEN not available, using GLOBAL_ANALYSIS_TOKEN");
19271
- tokenResponse = await adminClient.post("/api/user_tokens/generate", {
19272
- name: tokenName,
19273
- type: "GLOBAL_ANALYSIS_TOKEN"
19274
- });
19275
- }
19261
+ const tokenResponse = await generateAnalysisToken(adminClient, tokenName, projectKey);
19276
19262
  const qualityGateName = config2.qualityGate ?? QUALITY_GATE_MAPPING[config2.level];
19277
19263
  try {
19278
19264
  await setProjectQualityGate(adminClient, projectKey, qualityGateName);
19279
- logger5.info("Quality gate configured", { qualityGate: qualityGateName });
19280
19265
  } catch (error45) {
19281
19266
  logger5.warn("Failed to set quality gate", { error: String(error45) });
19282
19267
  }
19283
19268
  await configureProjectSettings(adminClient, projectKey, detection.languages, config2);
19284
- logger5.info("Project settings configured");
19285
19269
  const state = createInitialState({
19286
19270
  projectKey,
19287
19271
  projectToken: tokenResponse.token,
@@ -19298,7 +19282,7 @@ async function bootstrap(options) {
19298
19282
  projectToken: tokenResponse.token,
19299
19283
  qualityGate: qualityGateName,
19300
19284
  languages: detection.languages,
19301
- message: isNewProject ? `Created new project '${projectKey}' on SonarQube` : `Configured existing project '${projectKey}'`,
19285
+ message: isNewProject ? `Created new project '${projectKey}'` : `Configured existing project '${projectKey}'`,
19302
19286
  isNewProject
19303
19287
  };
19304
19288
  }
@@ -20112,26 +20096,6 @@ try {
20112
20096
  appendFileSync4("/tmp/sonarqube-plugin-debug.log", `${new Date().toISOString()} [LOAD] Plugin module loaded! CWD=${process.cwd()}
20113
20097
  `);
20114
20098
  } catch {}
20115
- var LOG_FILE4 = "/tmp/sonarqube-plugin-debug.log";
20116
- var debugLog = {
20117
- _write: (level, msg, extra) => {
20118
- const timestamp = new Date().toISOString();
20119
- const logLine = `${timestamp} [${level}] ${msg} ${extra ? JSON.stringify(extra) : ""}
20120
- `;
20121
- try {
20122
- appendFileSync4(LOG_FILE4, logLine);
20123
- } catch {}
20124
- },
20125
- info: (msg, extra) => {
20126
- debugLog._write("INFO", msg, extra);
20127
- },
20128
- warn: (msg, extra) => {
20129
- debugLog._write("WARN", msg, extra);
20130
- },
20131
- error: (msg, extra) => {
20132
- debugLog._write("ERROR", msg, extra);
20133
- }
20134
- };
20135
20099
  var IGNORED_FILE_PATTERNS2 = [
20136
20100
  /node_modules/,
20137
20101
  /\.git/,
@@ -20173,7 +20137,7 @@ var SonarQubePlugin = async ({ client, directory, worktree }) => {
20173
20137
  const pluginUrl = import.meta.url;
20174
20138
  const pluginPath = decodeURIComponent(pluginUrl.replace("file://", ""));
20175
20139
  const pathParts = pluginPath.split("/");
20176
- const nodeModulesIndex = pathParts.findIndex((p) => p === "node_modules");
20140
+ const nodeModulesIndex = pathParts.indexOf("node_modules");
20177
20141
  if (nodeModulesIndex > 0) {
20178
20142
  const projectPath = pathParts.slice(0, nodeModulesIndex).join("/");
20179
20143
  if (projectPath && projectPath !== "/" && projectPath.length > 1) {
@@ -20205,98 +20169,69 @@ var SonarQubePlugin = async ({ client, directory, worktree }) => {
20205
20169
  const getConfig = () => pluginConfig;
20206
20170
  const getDirectory = () => effectiveDirectory;
20207
20171
  const loadPluginConfig = async () => {
20208
- debugLog.info("loadPluginConfig called", { hasExistingConfig: !!pluginConfig });
20209
- if (pluginConfig) {
20210
- debugLog.info("Config already loaded, skipping");
20172
+ if (pluginConfig)
20211
20173
  return;
20212
- }
20174
+ const dir = getDirectory();
20175
+ const sonarConfigPath = `${dir}/.sonarqube/config.json`;
20213
20176
  try {
20214
- const configPath = `${getDirectory()}/opencode.json`;
20215
- debugLog.info("Loading config from", { configPath });
20216
- const configFile = Bun.file(configPath);
20177
+ const configFile = Bun.file(sonarConfigPath);
20217
20178
  if (await configFile.exists()) {
20218
- pluginConfig = await configFile.json();
20219
- debugLog.info("Config loaded", { keys: Object.keys(pluginConfig ?? {}) });
20220
- } else {
20221
- debugLog.info("No opencode.json found");
20179
+ const config2 = await configFile.json();
20180
+ pluginConfig = { sonarqube: config2 };
20181
+ safeLog(`Config loaded from ${sonarConfigPath}`);
20182
+ return;
20222
20183
  }
20223
- } catch (error45) {
20224
- debugLog.warn("Config load error", { error: String(error45) });
20225
- }
20184
+ } catch {}
20185
+ pluginConfig = {};
20186
+ safeLog("Using environment variables for config");
20226
20187
  };
20227
20188
  const hooks = createHooks(getConfig, getDirectory);
20228
20189
  let currentSessionId;
20229
20190
  let initialCheckDone = false;
20191
+ const buildQualityNotification = (qgStatus, counts, qgFailed) => {
20192
+ const statusEmoji = qgFailed ? "[FAIL]" : "[WARN]";
20193
+ const blockerNote = counts.blocker > 0 ? `**Action Required:** There are BLOCKER issues that should be fixed.
20194
+ ` : "";
20195
+ return `## SonarQube Status: ${statusEmoji}
20196
+
20197
+ **Quality Gate:** ${qgStatus}
20198
+ **Outstanding Issues:** ${counts.blocker} blockers, ${counts.critical} critical, ${counts.major} major
20199
+
20200
+ ${blockerNote}Use \`sonarqube({ action: "issues" })\` to see details or \`sonarqube({ action: "analyze" })\` to re-analyze.`;
20201
+ };
20230
20202
  const performInitialQualityCheck = async (sessionId) => {
20231
- debugLog.info("=== performInitialQualityCheck START ===", { sessionId, initialCheckDone });
20232
- if (initialCheckDone) {
20233
- debugLog.info("Initial check already done, skipping");
20203
+ if (initialCheckDone)
20234
20204
  return;
20235
- }
20236
20205
  initialCheckDone = true;
20237
20206
  try {
20238
20207
  await loadPluginConfig();
20239
20208
  const sonarConfig = pluginConfig?.["sonarqube"];
20240
- debugLog.info("Loading SonarQube config", { hasSonarConfig: !!sonarConfig });
20241
20209
  const config2 = loadConfig(sonarConfig);
20242
- debugLog.info("Config loaded", { hasConfig: !!config2, level: config2?.level });
20243
- if (!config2 || config2.level === "off") {
20244
- debugLog.info("Config missing or level=off, skipping");
20210
+ if (!config2 || config2.level === "off")
20245
20211
  return;
20246
- }
20247
20212
  const dir = getDirectory();
20248
- debugLog.info("Checking needsBootstrap", { directory: dir });
20249
- const needsBoot = await needsBootstrap(dir);
20250
- debugLog.info("needsBootstrap result", { needsBoot });
20251
- if (needsBoot) {
20252
- debugLog.info("Bootstrap needed, skipping initial check");
20213
+ if (await needsBootstrap(dir))
20253
20214
  return;
20254
- }
20255
- debugLog.info("Loading project state");
20256
20215
  const state = await getProjectState(dir);
20257
- debugLog.info("Project state loaded", {
20258
- hasState: !!state,
20259
- projectKey: state?.projectKey,
20260
- hasToken: !!state?.projectToken
20261
- });
20262
- if (!state || !state.projectKey) {
20263
- debugLog.info("No state or projectKey, skipping");
20216
+ if (!state?.projectKey)
20264
20217
  return;
20265
- }
20266
- debugLog.info("Creating API and fetching quality status", { projectKey: state.projectKey });
20267
20218
  const api2 = createSonarQubeAPI(config2, state);
20268
20219
  const [qgStatus, counts] = await Promise.all([
20269
20220
  api2.qualityGate.getStatus(state.projectKey),
20270
20221
  api2.issues.getCounts(state.projectKey)
20271
20222
  ]);
20272
- debugLog.info("Quality status fetched", { qgStatus: qgStatus.projectStatus.status, counts });
20273
20223
  const hasIssues = counts.blocker > 0 || counts.critical > 0 || counts.major > 0;
20274
20224
  const qgFailed = qgStatus.projectStatus.status !== "OK";
20275
- if (hasIssues || qgFailed) {
20276
- const statusEmoji = qgFailed ? "[FAIL]" : "[WARN]";
20277
- const notification = `## SonarQube Status: ${statusEmoji}
20278
-
20279
- **Quality Gate:** ${qgStatus.projectStatus.status}
20280
- **Outstanding Issues:** ${counts.blocker} blockers, ${counts.critical} critical, ${counts.major} major
20281
-
20282
- ${counts.blocker > 0 ? `**Action Required:** There are BLOCKER issues that should be fixed.
20283
- ` : ""}
20284
- Use \`sonarqube({ action: "issues" })\` to see details or \`sonarqube({ action: "analyze" })\` to re-analyze.`;
20285
- await client.session.prompt({
20286
- path: { id: sessionId },
20287
- body: {
20288
- noReply: true,
20289
- parts: [{ type: "text", text: notification }]
20290
- }
20291
- });
20292
- await showToast(qgFailed ? "SonarQube: Quality Gate Failed" : "SonarQube: Issues Found", qgFailed ? "error" : "info");
20293
- }
20225
+ if (!hasIssues && !qgFailed)
20226
+ return;
20227
+ const notification = buildQualityNotification(qgStatus.projectStatus.status, counts, qgFailed);
20228
+ await client.session.prompt({
20229
+ path: { id: sessionId },
20230
+ body: { noReply: true, parts: [{ type: "text", text: notification }] }
20231
+ });
20232
+ await showToast(qgFailed ? "SonarQube: Quality Gate Failed" : "SonarQube: Issues Found", qgFailed ? "error" : "info");
20294
20233
  } catch (error45) {
20295
- try {
20296
- const { appendFileSync: appendFileSync5 } = await import("node:fs");
20297
- appendFileSync5("/tmp/sonarqube-plugin-debug.log", `${new Date().toISOString()} [ERROR] performInitialQualityCheck FAILED: ${error45}
20298
- `);
20299
- } catch {}
20234
+ safeLog(`performInitialQualityCheck error: ${error45}`);
20300
20235
  }
20301
20236
  };
20302
20237
  const showToast = async (message, variant = "info") => {
@@ -20462,7 +20397,7 @@ ${statusNote}`;
20462
20397
  }
20463
20398
  try {
20464
20399
  const state = await getProjectState(getDirectory());
20465
- if (!state || !state.projectKey)
20400
+ if (!state?.projectKey)
20466
20401
  return;
20467
20402
  const api2 = createSonarQubeAPI(config2, state);
20468
20403
  const counts = await api2.issues.getCounts(state.projectKey);
@@ -20625,9 +20560,8 @@ Git operation completed with changes. Consider running:
20625
20560
  }
20626
20561
  const dir = getDirectory();
20627
20562
  const state = await getProjectState(dir);
20628
- if (!state || !state.projectKey) {
20563
+ if (!state?.projectKey)
20629
20564
  return;
20630
- }
20631
20565
  const api2 = createSonarQubeAPI(config2, state);
20632
20566
  const [qgStatus, counts, newCodeResponse] = await Promise.all([
20633
20567
  api2.qualityGate.getStatus(state.projectKey),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-sonarqube",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
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.0",
41
+ "opencode-sonarqube": "0.2.2",
42
42
  "zod": "^3.24.0"
43
43
  },
44
44
  "devDependencies": {