opencode-sonarqube 1.2.54 → 1.4.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 (2) hide show
  1. package/dist/index.js +1115 -989
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -3999,47 +3999,32 @@ function numberToRating(num) {
3999
3999
  const ratings = ["A", "B", "C", "D", "E"];
4000
4000
  return ratings[num - 1] ?? "?";
4001
4001
  }
4002
- function SonarQubeError(message, code, statusCode) {
4002
+ function createSonarQubeError(name, message, code, statusCode) {
4003
4003
  const error45 = new Error(message);
4004
- error45.name = "SonarQubeError";
4004
+ error45.name = name;
4005
4005
  error45.code = code;
4006
4006
  error45.statusCode = statusCode;
4007
4007
  return error45;
4008
4008
  }
4009
+ function SonarQubeError(message, code, statusCode) {
4010
+ return createSonarQubeError("SonarQubeError", message, code, statusCode);
4011
+ }
4009
4012
  function ConnectionError(message) {
4010
- const error45 = new Error(message);
4011
- error45.name = "ConnectionError";
4012
- error45.code = "CONNECTION_ERROR";
4013
- return error45;
4013
+ return createSonarQubeError("ConnectionError", message, "CONNECTION_ERROR");
4014
4014
  }
4015
4015
  function AuthenticationError(message) {
4016
- const error45 = new Error(message);
4017
- error45.name = "AuthenticationError";
4018
- error45.code = "AUTH_ERROR";
4019
- error45.statusCode = 401;
4020
- return error45;
4016
+ return createSonarQubeError("AuthenticationError", message, "AUTH_ERROR", 401);
4021
4017
  }
4022
4018
  function ProjectNotFoundError(projectKey) {
4023
- const error45 = new Error(`Project '${projectKey}' not found`);
4024
- error45.name = "ProjectNotFoundError";
4025
- error45.code = "PROJECT_NOT_FOUND";
4026
- error45.statusCode = 404;
4027
- return error45;
4019
+ return createSonarQubeError("ProjectNotFoundError", `Project '${projectKey}' not found`, "PROJECT_NOT_FOUND", 404);
4028
4020
  }
4029
4021
  function RateLimitError(retryAfter) {
4030
4022
  const hasRetryAfter = retryAfter !== undefined && retryAfter > 0;
4031
4023
  const retryMessage = hasRetryAfter ? `. Retry after ${retryAfter}s` : "";
4032
- const error45 = new Error(`Rate limit exceeded${retryMessage}`);
4033
- error45.name = "RateLimitError";
4034
- error45.code = "RATE_LIMIT";
4035
- error45.statusCode = 429;
4036
- return error45;
4024
+ return createSonarQubeError("RateLimitError", `Rate limit exceeded${retryMessage}`, "RATE_LIMIT", 429);
4037
4025
  }
4038
4026
  function SetupError(message) {
4039
- const error45 = new Error(message);
4040
- error45.name = "SetupError";
4041
- error45.code = "SETUP_ERROR";
4042
- return error45;
4027
+ return createSonarQubeError("SetupError", message, "SETUP_ERROR");
4043
4028
  }
4044
4029
  var SonarQubeConfigSchema, ProjectStateSchema, ENTERPRISE_THRESHOLDS, STANDARD_THRESHOLDS, RELAXED_THRESHOLDS;
4045
4030
  var init_types2 = __esm(() => {
@@ -4103,81 +4088,6 @@ var init_types2 = __esm(() => {
4103
4088
  };
4104
4089
  });
4105
4090
 
4106
- // src/utils/config.ts
4107
- function loadConfig(rawConfig) {
4108
- configLogger.info(">>> loadConfig called", { hasRawConfig: !!rawConfig });
4109
- const envUrl = process.env["SONAR_HOST_URL"];
4110
- const envUser = process.env["SONAR_USER"];
4111
- const envPassword = process.env["SONAR_PASSWORD"];
4112
- configLogger.info("Environment variables", {
4113
- hasEnvUrl: !!envUrl,
4114
- hasEnvUser: !!envUser,
4115
- hasEnvPassword: !!envPassword,
4116
- envUrl: envUrl ? `${envUrl.substring(0, 20)}...` : undefined
4117
- });
4118
- const configToValidate = {
4119
- ...DEFAULT_CONFIG,
4120
- ...rawConfig,
4121
- ...envUrl && { url: envUrl },
4122
- ...envUser && { user: envUser },
4123
- ...envPassword && { password: envPassword }
4124
- };
4125
- if (!configToValidate.url || !configToValidate.user || !configToValidate.password) {
4126
- configLogger.warn("Missing required config", {
4127
- hasUrl: !!configToValidate.url,
4128
- hasUser: !!configToValidate.user,
4129
- hasPassword: !!configToValidate.password
4130
- });
4131
- return null;
4132
- }
4133
- const result = SonarQubeConfigSchema.safeParse(configToValidate);
4134
- if (!result.success) {
4135
- configLogger.error("Config validation failed", { errors: result.error.format() });
4136
- return null;
4137
- }
4138
- configLogger.info("<<< loadConfig success", { url: result.data.url, level: result.data.level });
4139
- return result.data;
4140
- }
4141
- async function deriveProjectKey(directory) {
4142
- try {
4143
- const packageJsonPath = `${directory}/package.json`;
4144
- const packageJson = await Bun.file(packageJsonPath).json();
4145
- if (packageJson.name) {
4146
- return packageJson.name;
4147
- }
4148
- } catch {}
4149
- const dirName = directory.split("/").pop();
4150
- if (dirName && dirName.length > 0) {
4151
- return dirName;
4152
- }
4153
- const cwd = process.cwd();
4154
- if (cwd && cwd !== "/") {
4155
- const cwdName = cwd.split("/").pop();
4156
- if (cwdName && cwdName.length > 0) {
4157
- return cwdName;
4158
- }
4159
- }
4160
- return `project-${Date.now()}`;
4161
- }
4162
- function sanitizeProjectKey(input) {
4163
- return input.toLowerCase().replaceAll(/[^a-z0-9-_]/g, "-").replaceAll(/-+/g, "-").replaceAll(/(?:^-)|(?:-$)/g, "").slice(0, 400);
4164
- }
4165
- var configLogger, DEFAULT_CONFIG;
4166
- var init_config = __esm(() => {
4167
- init_types2();
4168
- configLogger = {
4169
- info: (_msg, _extra) => {},
4170
- warn: (_msg, _extra) => {},
4171
- error: (_msg, _extra) => {}
4172
- };
4173
- DEFAULT_CONFIG = {
4174
- level: "enterprise",
4175
- autoAnalyze: true,
4176
- newCodeDefinition: "previous_version",
4177
- sources: "src"
4178
- };
4179
- });
4180
-
4181
4091
  // src/utils/logger.ts
4182
4092
  class Logger {
4183
4093
  service;
@@ -4252,6 +4162,10 @@ class Logger {
4252
4162
  });
4253
4163
  }
4254
4164
  }
4165
+ function createNoopLogger() {
4166
+ const noop = () => {};
4167
+ return { info: noop, warn: noop, error: noop, debug: noop };
4168
+ }
4255
4169
  var LOG_COLORS, RESET = "\x1B[0m", logger;
4256
4170
  var init_logger = __esm(() => {
4257
4171
  LOG_COLORS = {
@@ -4263,7 +4177,134 @@ var init_logger = __esm(() => {
4263
4177
  logger = new Logger("opencode-sonarqube");
4264
4178
  });
4265
4179
 
4266
- // src/scanner/config.ts
4180
+ // src/utils/config.ts
4181
+ function loadConfig(rawConfig) {
4182
+ configLogger.info(">>> loadConfig called", { hasRawConfig: !!rawConfig });
4183
+ const envUrl = process.env["SONAR_HOST_URL"];
4184
+ const envUser = process.env["SONAR_USER"];
4185
+ const envPassword = process.env["SONAR_PASSWORD"];
4186
+ configLogger.info("Environment variables", {
4187
+ hasEnvUrl: !!envUrl,
4188
+ hasEnvUser: !!envUser,
4189
+ hasEnvPassword: !!envPassword,
4190
+ envUrl: envUrl ? `${envUrl.substring(0, 20)}...` : undefined
4191
+ });
4192
+ const configToValidate = {
4193
+ ...DEFAULT_CONFIG,
4194
+ ...rawConfig,
4195
+ ...envUrl && { url: envUrl },
4196
+ ...envUser && { user: envUser },
4197
+ ...envPassword && { password: envPassword }
4198
+ };
4199
+ if (!configToValidate.url || !configToValidate.user || !configToValidate.password) {
4200
+ configLogger.warn("Missing required config", {
4201
+ hasUrl: !!configToValidate.url,
4202
+ hasUser: !!configToValidate.user,
4203
+ hasPassword: !!configToValidate.password
4204
+ });
4205
+ return null;
4206
+ }
4207
+ const result = SonarQubeConfigSchema.safeParse(configToValidate);
4208
+ if (!result.success) {
4209
+ configLogger.error("Config validation failed", { errors: result.error.format() });
4210
+ return null;
4211
+ }
4212
+ configLogger.info("<<< loadConfig success", { url: result.data.url, level: result.data.level });
4213
+ return result.data;
4214
+ }
4215
+ async function deriveProjectKey(directory) {
4216
+ try {
4217
+ const packageJsonPath = `${directory}/package.json`;
4218
+ const packageJson = await Bun.file(packageJsonPath).json();
4219
+ if (packageJson.name) {
4220
+ return packageJson.name;
4221
+ }
4222
+ } catch {}
4223
+ const dirName = directory.split("/").pop();
4224
+ if (dirName && dirName.length > 0) {
4225
+ return dirName;
4226
+ }
4227
+ const cwd = process.cwd();
4228
+ if (cwd && cwd !== "/") {
4229
+ const cwdName = cwd.split("/").pop();
4230
+ if (cwdName && cwdName.length > 0) {
4231
+ return cwdName;
4232
+ }
4233
+ }
4234
+ return `project-${Date.now()}`;
4235
+ }
4236
+ function sanitizeProjectKey(input) {
4237
+ return input.toLowerCase().replaceAll(/[^a-z0-9-_]/g, "-").replaceAll(/-+/g, "-").replaceAll(/(?:^-)|(?:-$)/g, "").slice(0, 400);
4238
+ }
4239
+ var configLogger, DEFAULT_CONFIG;
4240
+ var init_config = __esm(() => {
4241
+ init_types2();
4242
+ init_logger();
4243
+ configLogger = createNoopLogger();
4244
+ DEFAULT_CONFIG = {
4245
+ level: "enterprise",
4246
+ autoAnalyze: true,
4247
+ newCodeDefinition: "previous_version",
4248
+ sources: "src"
4249
+ };
4250
+ });
4251
+
4252
+ // src/scanner/config/patterns.ts
4253
+ function getDefaultExclusions(_detection) {
4254
+ const patterns = [
4255
+ "**/node_modules/**",
4256
+ "**/dist/**",
4257
+ "**/build/**",
4258
+ "**/.next/**",
4259
+ "**/coverage/**",
4260
+ "**/*.d.ts",
4261
+ "**/*.min.js",
4262
+ "**/*.min.css",
4263
+ "**/vendor/**",
4264
+ "**/__pycache__/**",
4265
+ "**/.venv/**",
4266
+ "**/venv/**",
4267
+ "**/target/**"
4268
+ ];
4269
+ return patterns.join(",");
4270
+ }
4271
+ function getTestPatterns(languages) {
4272
+ const patterns = [];
4273
+ if (languages.includes("typescript") || languages.includes("javascript")) {
4274
+ patterns.push("**/*.test.ts", "**/*.test.tsx", "**/*.test.js", "**/*.test.jsx", "**/*.spec.ts", "**/*.spec.tsx", "**/*.spec.js", "**/*.spec.jsx", "**/__tests__/**", "**/__mocks__/**");
4275
+ }
4276
+ if (languages.includes("python")) {
4277
+ patterns.push("**/test_*.py", "**/*_test.py", "**/tests/**");
4278
+ }
4279
+ if (languages.includes("java")) {
4280
+ patterns.push("**/*Test.java", "**/*Tests.java", "**/test/**");
4281
+ }
4282
+ if (languages.includes("go")) {
4283
+ patterns.push("**/*_test.go");
4284
+ }
4285
+ return patterns.join(",");
4286
+ }
4287
+ var SOURCE_DIRECTORY_PATTERNS;
4288
+ var init_patterns = __esm(() => {
4289
+ SOURCE_DIRECTORY_PATTERNS = [
4290
+ "apps",
4291
+ "packages",
4292
+ "modules",
4293
+ "services",
4294
+ "libs",
4295
+ "src",
4296
+ "source",
4297
+ "lib",
4298
+ "app",
4299
+ "cmd",
4300
+ "pkg",
4301
+ "internal",
4302
+ "main",
4303
+ "core"
4304
+ ];
4305
+ });
4306
+
4307
+ // src/scanner/config/detection.ts
4267
4308
  async function checkProjectFiles(directory) {
4268
4309
  const checks3 = await Promise.all([
4269
4310
  Bun.file(`${directory}/package.json`).exists(),
@@ -4353,32 +4394,32 @@ async function parsePackageJson(directory) {
4353
4394
  }
4354
4395
  function detectLanguages(checks3) {
4355
4396
  const languages = [];
4356
- if (checks3.hasPackageJson) {
4357
- if (checks3.hasTsConfig) {
4358
- languages.push("typescript", "javascript");
4359
- } else {
4360
- languages.push("javascript");
4397
+ for (const detector of LANGUAGE_DETECTORS) {
4398
+ if (detector.check(checks3)) {
4399
+ languages.push(detector.name);
4400
+ if (detector.includes) {
4401
+ languages.push(...detector.includes);
4402
+ }
4361
4403
  }
4362
4404
  }
4363
- if (checks3.hasRequirements || checks3.hasPyproject) {
4364
- languages.push("python");
4365
- }
4366
- if (checks3.hasPom || checks3.hasGradle) {
4367
- languages.push("java");
4368
- }
4369
- if (checks3.hasGoMod) {
4370
- languages.push("go");
4371
- }
4372
- if (checks3.hasCargo) {
4373
- languages.push("rust");
4374
- }
4375
- if (checks3.hasComposer) {
4376
- languages.push("php");
4405
+ return languages.length > 0 ? languages : ["generic"];
4406
+ }
4407
+ function detectSourceDirectories(directory) {
4408
+ const existingDirs = [];
4409
+ for (const pattern of SOURCE_DIRECTORY_PATTERNS) {
4410
+ try {
4411
+ const dirPath = `${directory}/${pattern}`;
4412
+ if (Bun.spawnSync(["test", "-d", dirPath]).exitCode === 0) {
4413
+ existingDirs.push(pattern);
4414
+ }
4415
+ } catch {}
4377
4416
  }
4378
- if (checks3.hasGemfile) {
4379
- languages.push("ruby");
4417
+ if (existingDirs.length > 0) {
4418
+ logger2.info("Detected source directories", { directories: existingDirs });
4419
+ return existingDirs.join(",");
4380
4420
  }
4381
- return languages.length > 0 ? languages : ["generic"];
4421
+ logger2.info("No common source directories found, scanning entire project");
4422
+ return ".";
4382
4423
  }
4383
4424
  async function detectProjectType(directory) {
4384
4425
  const checks3 = await checkProjectFiles(directory);
@@ -4397,23 +4438,28 @@ async function detectProjectType(directory) {
4397
4438
  });
4398
4439
  return result;
4399
4440
  }
4400
- function detectSourceDirectories(directory) {
4401
- const existingDirs = [];
4402
- for (const pattern of SOURCE_DIRECTORY_PATTERNS) {
4403
- try {
4404
- const dirPath = `${directory}/${pattern}`;
4405
- if (Bun.spawnSync(["test", "-d", dirPath]).exitCode === 0) {
4406
- existingDirs.push(pattern);
4407
- }
4408
- } catch {}
4409
- }
4410
- if (existingDirs.length > 0) {
4411
- logger2.info("Detected source directories", { directories: existingDirs });
4412
- return existingDirs.join(",");
4413
- }
4414
- logger2.info("No common source directories found, scanning entire project");
4415
- return ".";
4416
- }
4441
+ var logger2, LANGUAGE_DETECTORS;
4442
+ var init_detection = __esm(() => {
4443
+ init_logger();
4444
+ init_patterns();
4445
+ logger2 = new Logger("scanner-config");
4446
+ LANGUAGE_DETECTORS = [
4447
+ {
4448
+ name: "typescript",
4449
+ check: (c) => c.hasPackageJson && c.hasTsConfig,
4450
+ includes: ["javascript"]
4451
+ },
4452
+ { name: "javascript", check: (c) => c.hasPackageJson && !c.hasTsConfig },
4453
+ { name: "python", check: (c) => c.hasRequirements || c.hasPyproject },
4454
+ { name: "java", check: (c) => c.hasPom || c.hasGradle },
4455
+ { name: "go", check: (c) => c.hasGoMod },
4456
+ { name: "rust", check: (c) => c.hasCargo },
4457
+ { name: "php", check: (c) => c.hasComposer },
4458
+ { name: "ruby", check: (c) => c.hasGemfile }
4459
+ ];
4460
+ });
4461
+
4462
+ // src/scanner/config/properties.ts
4417
4463
  async function generatePropertiesContent(options, config2, directory) {
4418
4464
  const dir = directory ?? process.cwd();
4419
4465
  const detection = await detectProjectType(dir);
@@ -4473,68 +4519,60 @@ async function generatePropertiesContent(options, config2, directory) {
4473
4519
  return lines.join(`
4474
4520
  `);
4475
4521
  }
4476
- function getDefaultExclusions(_detection) {
4477
- const patterns = [
4478
- "**/node_modules/**",
4479
- "**/dist/**",
4480
- "**/build/**",
4481
- "**/.next/**",
4482
- "**/coverage/**",
4483
- "**/*.d.ts",
4484
- "**/*.min.js",
4485
- "**/*.min.css",
4486
- "**/vendor/**",
4487
- "**/__pycache__/**",
4488
- "**/.venv/**",
4489
- "**/venv/**",
4490
- "**/target/**"
4491
- ];
4492
- return patterns.join(",");
4493
- }
4494
- function getTestPatterns(languages) {
4495
- const patterns = [];
4496
- if (languages.includes("typescript") || languages.includes("javascript")) {
4497
- patterns.push("**/*.test.ts", "**/*.test.tsx", "**/*.test.js", "**/*.test.jsx", "**/*.spec.ts", "**/*.spec.tsx", "**/*.spec.js", "**/*.spec.jsx", "**/__tests__/**", "**/__mocks__/**");
4498
- }
4499
- if (languages.includes("python")) {
4500
- patterns.push("**/test_*.py", "**/*_test.py", "**/tests/**");
4501
- }
4502
- if (languages.includes("java")) {
4503
- patterns.push("**/*Test.java", "**/*Tests.java", "**/test/**");
4504
- }
4505
- if (languages.includes("go")) {
4506
- patterns.push("**/*_test.go");
4507
- }
4508
- return patterns.join(",");
4509
- }
4510
4522
  async function writePropertiesFile(options, config2, directory) {
4511
4523
  const dir = directory ?? process.cwd();
4512
4524
  const content = await generatePropertiesContent(options, config2, dir);
4513
4525
  const filePath = `${dir}/sonar-project.properties`;
4514
4526
  await Bun.write(filePath, content);
4515
- logger2.info(`Written sonar-project.properties to ${filePath}`);
4527
+ logger3.info(`Written sonar-project.properties to ${filePath}`);
4516
4528
  return filePath;
4517
4529
  }
4518
- var logger2, SOURCE_DIRECTORY_PATTERNS;
4530
+ var logger3;
4531
+ var init_properties = __esm(() => {
4532
+ init_logger();
4533
+ init_detection();
4534
+ init_patterns();
4535
+ logger3 = new Logger("scanner-config");
4536
+ });
4537
+
4538
+ // src/scanner/config/index.ts
4519
4539
  var init_config2 = __esm(() => {
4540
+ init_detection();
4541
+ init_properties();
4542
+ init_patterns();
4543
+ });
4544
+
4545
+ // src/scanner/config.ts
4546
+ var init_config3 = __esm(() => {
4547
+ init_config2();
4548
+ });
4549
+
4550
+ // src/api/base-api.ts
4551
+ class BaseAPI {
4552
+ client;
4553
+ logger;
4554
+ constructor(client, serviceName, logger4) {
4555
+ this.client = client;
4556
+ this.logger = logger4 ?? new Logger(serviceName);
4557
+ }
4558
+ validateProjectKey(projectKey, methodName) {
4559
+ if (!projectKey) {
4560
+ this.logger.error(`${methodName}: projectKey is empty/undefined!`);
4561
+ return false;
4562
+ }
4563
+ return true;
4564
+ }
4565
+ async safeRequest(request, fallback, context) {
4566
+ try {
4567
+ return await request();
4568
+ } catch (error45) {
4569
+ this.logger.warn(`${context}: ${error45 instanceof Error ? error45.message : String(error45)}`);
4570
+ return fallback;
4571
+ }
4572
+ }
4573
+ }
4574
+ var init_base_api = __esm(() => {
4520
4575
  init_logger();
4521
- logger2 = new Logger("scanner-config");
4522
- SOURCE_DIRECTORY_PATTERNS = [
4523
- "apps",
4524
- "packages",
4525
- "modules",
4526
- "services",
4527
- "libs",
4528
- "src",
4529
- "source",
4530
- "lib",
4531
- "app",
4532
- "cmd",
4533
- "pkg",
4534
- "internal",
4535
- "main",
4536
- "core"
4537
- ];
4538
4576
  });
4539
4577
 
4540
4578
  // src/api/client.ts
@@ -4624,9 +4662,9 @@ function buildAuthHeader(auth) {
4624
4662
  class SonarQubeClient {
4625
4663
  baseUrl;
4626
4664
  auth;
4627
- constructor(config2, _logger) {
4628
- this.baseUrl = normalizeUrl(config2.url);
4629
- this.auth = config2.auth;
4665
+ constructor(config3, _logger) {
4666
+ this.baseUrl = normalizeUrl(config3.url);
4667
+ this.auth = config3.auth;
4630
4668
  }
4631
4669
  async request(endpoint, options = {}) {
4632
4670
  const { method = "GET", params, body } = options;
@@ -4708,119 +4746,117 @@ class SonarQubeClient {
4708
4746
  return this.request(endpoint, { method: "DELETE", params });
4709
4747
  }
4710
4748
  }
4711
- function createClientWithToken(url2, token, logger3) {
4712
- return new SonarQubeClient({ url: url2, auth: { token } }, logger3);
4749
+ function createClientWithToken(url2, token, logger4) {
4750
+ return new SonarQubeClient({ url: url2, auth: { token } }, logger4);
4713
4751
  }
4714
- function createClientWithCredentials(url2, user, password, logger3) {
4715
- return new SonarQubeClient({ url: url2, auth: { user, password } }, logger3);
4752
+ function createClientWithCredentials(url2, user, password, logger4) {
4753
+ return new SonarQubeClient({ url: url2, auth: { user, password } }, logger4);
4716
4754
  }
4717
4755
  var init_client = __esm(() => {
4718
4756
  init_types2();
4719
4757
  });
4720
4758
 
4721
4759
  // src/api/projects.ts
4722
- class ProjectsAPI {
4723
- client;
4724
- logger;
4725
- constructor(client, logger3) {
4726
- this.client = client;
4727
- this.logger = logger3 ?? new Logger("sonarqube-projects");
4728
- }
4729
- async create(options) {
4730
- this.logger.info(`Creating project: ${options.projectKey}`);
4731
- return this.client.post("/api/projects/create", {
4732
- project: options.projectKey,
4733
- name: options.name,
4734
- mainBranch: options.mainBranch ?? "main",
4735
- visibility: options.visibility ?? "private"
4736
- });
4737
- }
4738
- async search(options) {
4739
- return this.client.get("/api/projects/search", {
4740
- projects: options?.projects,
4741
- q: options?.query,
4742
- ps: options?.pageSize ?? 100,
4743
- p: options?.page ?? 1
4744
- });
4745
- }
4746
- async exists(projectKey) {
4747
- try {
4748
- const response = await this.search({ projects: projectKey });
4749
- return response.components.some((c) => c.key === projectKey);
4750
- } catch {
4751
- return false;
4760
+ var ProjectsAPI;
4761
+ var init_projects = __esm(() => {
4762
+ init_types2();
4763
+ init_base_api();
4764
+ ProjectsAPI = class ProjectsAPI extends BaseAPI {
4765
+ constructor(client, logger4) {
4766
+ super(client, "sonarqube-projects", logger4);
4767
+ }
4768
+ async create(options) {
4769
+ this.logger.info(`Creating project: ${options.projectKey}`);
4770
+ return this.client.post("/api/projects/create", {
4771
+ project: options.projectKey,
4772
+ name: options.name,
4773
+ mainBranch: options.mainBranch ?? "main",
4774
+ visibility: options.visibility ?? "private"
4775
+ });
4752
4776
  }
4753
- }
4754
- async get(projectKey) {
4755
- const response = await this.search({ projects: projectKey });
4756
- return response.components.find((c) => c.key === projectKey) ?? null;
4757
- }
4758
- async delete(projectKey) {
4759
- this.logger.warn(`Deleting project: ${projectKey}`);
4760
- await this.client.post("/api/projects/delete", { project: projectKey });
4761
- }
4762
- async generateToken(options) {
4763
- const tokenName = options.tokenName ?? `opencode-${options.projectKey}-${Date.now()}`;
4764
- this.logger.info(`Generating token for project: ${options.projectKey}`);
4765
- return this.client.post("/api/user_tokens/generate", {
4766
- name: tokenName,
4767
- type: "PROJECT_ANALYSIS_TOKEN",
4768
- projectKey: options.projectKey,
4769
- expirationDate: options.expirationDate
4770
- });
4771
- }
4772
- async revokeToken(tokenName) {
4773
- this.logger.info(`Revoking token: ${tokenName}`);
4774
- await this.client.post("/api/user_tokens/revoke", { name: tokenName });
4775
- }
4776
- async setQualityGate(projectKey, gateName) {
4777
- const gatesResponse = await this.client.get("/api/qualitygates/list");
4778
- const gate = gatesResponse.qualitygates.find((g) => g.name === gateName);
4779
- if (!gate) {
4780
- this.logger.warn(`Quality gate '${gateName}' not found, using default`);
4781
- return;
4777
+ async search(options) {
4778
+ return this.client.get("/api/projects/search", {
4779
+ projects: options?.projects,
4780
+ q: options?.query,
4781
+ ps: options?.pageSize ?? 100,
4782
+ p: options?.page ?? 1
4783
+ });
4782
4784
  }
4783
- await this.client.post("/api/qualitygates/select", {
4784
- projectKey,
4785
- gateId: gate.id
4786
- });
4787
- this.logger.info(`Set quality gate '${gateName}' for project ${projectKey}`);
4788
- }
4789
- async ensureExists(options) {
4790
- const exists = await this.exists(options.projectKey);
4791
- if (exists) {
4792
- const project = await this.get(options.projectKey);
4793
- if (!project) {
4794
- throw ProjectNotFoundError(options.projectKey);
4785
+ async exists(projectKey) {
4786
+ try {
4787
+ const response = await this.search({ projects: projectKey });
4788
+ return response.components.some((c) => c.key === projectKey);
4789
+ } catch {
4790
+ return false;
4795
4791
  }
4796
- return { created: false, project };
4797
4792
  }
4798
- const response = await this.create({
4799
- projectKey: options.projectKey,
4800
- name: options.name,
4801
- mainBranch: options.mainBranch
4802
- });
4803
- if (options.qualityGate) {
4804
- try {
4805
- await this.setQualityGate(options.projectKey, options.qualityGate);
4806
- } catch (error45) {
4807
- this.logger.warn(`Failed to set quality gate: ${error45}`);
4793
+ async get(projectKey) {
4794
+ const response = await this.search({ projects: projectKey });
4795
+ return response.components.find((c) => c.key === projectKey) ?? null;
4796
+ }
4797
+ async delete(projectKey) {
4798
+ this.logger.warn(`Deleting project: ${projectKey}`);
4799
+ await this.client.post("/api/projects/delete", { project: projectKey });
4800
+ }
4801
+ async generateToken(options) {
4802
+ const tokenName = options.tokenName ?? `opencode-${options.projectKey}-${Date.now()}`;
4803
+ this.logger.info(`Generating token for project: ${options.projectKey}`);
4804
+ return this.client.post("/api/user_tokens/generate", {
4805
+ name: tokenName,
4806
+ type: "PROJECT_ANALYSIS_TOKEN",
4807
+ projectKey: options.projectKey,
4808
+ expirationDate: options.expirationDate
4809
+ });
4810
+ }
4811
+ async revokeToken(tokenName) {
4812
+ this.logger.info(`Revoking token: ${tokenName}`);
4813
+ await this.client.post("/api/user_tokens/revoke", { name: tokenName });
4814
+ }
4815
+ async setQualityGate(projectKey, gateName) {
4816
+ const gatesResponse = await this.client.get("/api/qualitygates/list");
4817
+ const gate = gatesResponse.qualitygates.find((g) => g.name === gateName);
4818
+ if (!gate) {
4819
+ this.logger.warn(`Quality gate '${gateName}' not found, using default`);
4820
+ return;
4808
4821
  }
4822
+ await this.client.post("/api/qualitygates/select", {
4823
+ projectKey,
4824
+ gateId: gate.id
4825
+ });
4826
+ this.logger.info(`Set quality gate '${gateName}' for project ${projectKey}`);
4809
4827
  }
4810
- return {
4811
- created: true,
4812
- project: {
4813
- key: response.project.key,
4814
- name: response.project.name,
4815
- qualifier: response.project.qualifier,
4816
- visibility: "private"
4828
+ async ensureExists(options) {
4829
+ const exists = await this.exists(options.projectKey);
4830
+ if (exists) {
4831
+ const project = await this.get(options.projectKey);
4832
+ if (!project) {
4833
+ throw ProjectNotFoundError(options.projectKey);
4834
+ }
4835
+ return { created: false, project };
4817
4836
  }
4818
- };
4819
- }
4820
- }
4821
- var init_projects = __esm(() => {
4822
- init_types2();
4823
- init_logger();
4837
+ const response = await this.create({
4838
+ projectKey: options.projectKey,
4839
+ name: options.name,
4840
+ mainBranch: options.mainBranch
4841
+ });
4842
+ if (options.qualityGate) {
4843
+ try {
4844
+ await this.setQualityGate(options.projectKey, options.qualityGate);
4845
+ } catch (error45) {
4846
+ this.logger.warn(`Failed to set quality gate: ${error45}`);
4847
+ }
4848
+ }
4849
+ return {
4850
+ created: true,
4851
+ project: {
4852
+ key: response.project.key,
4853
+ name: response.project.name,
4854
+ qualifier: response.project.qualifier,
4855
+ visibility: "private"
4856
+ }
4857
+ };
4858
+ }
4859
+ };
4824
4860
  });
4825
4861
 
4826
4862
  // src/utils/state.ts
@@ -4856,7 +4892,7 @@ async function loadProjectState(directory) {
4856
4892
  const state = ProjectStateSchema.parse(data);
4857
4893
  return state;
4858
4894
  } catch (error45) {
4859
- logger4.error("Failed to load project state", {
4895
+ logger5.error("Failed to load project state", {
4860
4896
  error: error45 instanceof Error ? error45.message : String(error45),
4861
4897
  statePath
4862
4898
  });
@@ -4872,12 +4908,12 @@ async function saveProjectState(directory, state) {
4872
4908
  }
4873
4909
  const content = JSON.stringify(state, null, 2);
4874
4910
  await Bun.write(statePath, content);
4875
- logger4.info("Saved project state", { projectKey: state.projectKey });
4911
+ logger5.info("Saved project state", { projectKey: state.projectKey });
4876
4912
  }
4877
4913
  async function updateProjectState(directory, updates) {
4878
4914
  const existing = await loadProjectState(directory);
4879
4915
  if (!existing) {
4880
- logger4.warn("Cannot update non-existent state");
4916
+ logger5.warn("Cannot update non-existent state");
4881
4917
  return null;
4882
4918
  }
4883
4919
  const updated = {
@@ -4897,7 +4933,7 @@ async function deleteProjectState(directory) {
4897
4933
  await Bun.write(statePath, "");
4898
4934
  const { unlink } = await import("node:fs/promises");
4899
4935
  await unlink(statePath);
4900
- logger4.info("Deleted project state", { directory });
4936
+ logger5.info("Deleted project state", { directory });
4901
4937
  return true;
4902
4938
  } catch {
4903
4939
  return false;
@@ -4922,7 +4958,7 @@ async function ensureGitignore(directory) {
4922
4958
  await Bun.write(gitignorePath, `# SonarQube local state
4923
4959
  ${entry}
4924
4960
  `);
4925
- logger4.info("Created .gitignore with SonarQube exclusion");
4961
+ logger5.info("Created .gitignore with SonarQube exclusion");
4926
4962
  return;
4927
4963
  }
4928
4964
  const content = await Bun.file(gitignorePath).text();
@@ -4939,17 +4975,13 @@ ${entry}
4939
4975
  ${entry}
4940
4976
  `;
4941
4977
  await Bun.write(gitignorePath, newContent);
4942
- logger4.info("Added SonarQube exclusion to .gitignore");
4978
+ logger5.info("Added SonarQube exclusion to .gitignore");
4943
4979
  }
4944
- var logger4, STATE_DIR = ".sonarqube", STATE_FILE = "project.json";
4980
+ var logger5, STATE_DIR = ".sonarqube", STATE_FILE = "project.json";
4945
4981
  var init_state = __esm(() => {
4946
4982
  init_types2();
4947
- logger4 = {
4948
- info: (_msg, _extra) => {},
4949
- warn: (_msg, _extra) => {},
4950
- error: (_msg, _extra) => {},
4951
- debug: (_msg, _extra) => {}
4952
- };
4983
+ init_logger();
4984
+ logger5 = createNoopLogger();
4953
4985
  });
4954
4986
 
4955
4987
  // src/bootstrap/index.ts
@@ -4963,16 +4995,16 @@ __export(exports_bootstrap, {
4963
4995
  bootstrap: () => bootstrap
4964
4996
  });
4965
4997
  async function needsBootstrap(directory) {
4966
- logger5.info(">>> needsBootstrap called", { directory });
4998
+ logger6.info(">>> needsBootstrap called", { directory });
4967
4999
  const hasState = await hasProjectState(directory);
4968
- logger5.info("hasProjectState result", { hasState, directory });
5000
+ logger6.info("hasProjectState result", { hasState, directory });
4969
5001
  if (!hasState) {
4970
- logger5.info("<<< needsBootstrap: true (no state file)");
5002
+ logger6.info("<<< needsBootstrap: true (no state file)");
4971
5003
  return true;
4972
5004
  }
4973
5005
  const state = await loadProjectState(directory);
4974
5006
  const needsBoot = !state?.setupComplete;
4975
- logger5.info("<<< needsBootstrap result", {
5007
+ logger6.info("<<< needsBootstrap result", {
4976
5008
  needsBoot,
4977
5009
  hasState: !!state,
4978
5010
  setupComplete: state?.setupComplete,
@@ -4983,12 +5015,12 @@ async function needsBootstrap(directory) {
4983
5015
  async function getProjectState(directory) {
4984
5016
  return loadProjectState(directory);
4985
5017
  }
4986
- async function createAuthenticatedClient(config2, directory) {
5018
+ async function createAuthenticatedClient(config3, directory) {
4987
5019
  const state = await loadProjectState(directory);
4988
5020
  if (!state?.projectToken) {
4989
5021
  return null;
4990
5022
  }
4991
- return createClientWithToken(config2.url, state.projectToken);
5023
+ return createClientWithToken(config3.url, state.projectToken);
4992
5024
  }
4993
5025
  function generateTokenName(projectKey) {
4994
5026
  const timestamp = Date.now();
@@ -5022,23 +5054,23 @@ async function ensurePluginsEnabled(client) {
5022
5054
  key: "sonar.plugins.downloadOnlyRequired",
5023
5055
  value: "false"
5024
5056
  });
5025
- logger5.info("Enabled full plugin loading on server (sonar.plugins.downloadOnlyRequired=false)");
5057
+ logger6.info("Enabled full plugin loading on server (sonar.plugins.downloadOnlyRequired=false)");
5026
5058
  }
5027
5059
  } catch {
5028
- logger5.warn("Could not check/set plugin loading setting");
5060
+ logger6.warn("Could not check/set plugin loading setting");
5029
5061
  }
5030
5062
  }
5031
5063
  async function generateAnalysisToken(client, tokenName, projectKey) {
5032
5064
  try {
5033
5065
  return await client.post("/api/user_tokens/generate", { name: tokenName, type: "PROJECT_ANALYSIS_TOKEN", projectKey });
5034
5066
  } catch {
5035
- logger5.warn("PROJECT_ANALYSIS_TOKEN not available, using GLOBAL_ANALYSIS_TOKEN");
5067
+ logger6.warn("PROJECT_ANALYSIS_TOKEN not available, using GLOBAL_ANALYSIS_TOKEN");
5036
5068
  return await client.post("/api/user_tokens/generate", { name: tokenName, type: "GLOBAL_ANALYSIS_TOKEN" });
5037
5069
  }
5038
5070
  }
5039
5071
  async function bootstrap(options) {
5040
- let { config: config2, directory, force = false } = options;
5041
- logger5.info("Starting bootstrap", { directory, projectKey: config2.projectKey || "(auto)" });
5072
+ let { config: config3, directory, force = false } = options;
5073
+ logger6.info("Starting bootstrap", { directory, projectKey: config3.projectKey || "(auto)" });
5042
5074
  if (!isValidDirectory(directory)) {
5043
5075
  const resolved = resolveDirectoryFromImportMeta();
5044
5076
  if (resolved) {
@@ -5059,7 +5091,7 @@ async function bootstrap(options) {
5059
5091
  if (!force) {
5060
5092
  const existingState = await loadProjectState(directory);
5061
5093
  if (existingState?.setupComplete) {
5062
- logger5.info("Project already bootstrapped", { projectKey: existingState.projectKey });
5094
+ logger6.info("Project already bootstrapped", { projectKey: existingState.projectKey });
5063
5095
  return {
5064
5096
  success: true,
5065
5097
  projectKey: existingState.projectKey,
@@ -5071,32 +5103,32 @@ async function bootstrap(options) {
5071
5103
  };
5072
5104
  }
5073
5105
  }
5074
- const adminClient = createClientWithCredentials(config2.url, config2.user, config2.password);
5106
+ const adminClient = createClientWithCredentials(config3.url, config3.user, config3.password);
5075
5107
  const health = await adminClient.healthCheck();
5076
5108
  if (!health.healthy) {
5077
5109
  throw SetupError(`Cannot connect to SonarQube: ${health.error}`);
5078
5110
  }
5079
- logger5.info("Connected to SonarQube", { version: health.version });
5111
+ logger6.info("Connected to SonarQube", { version: health.version });
5080
5112
  await ensurePluginsEnabled(adminClient);
5081
5113
  const detection = await detectProjectType(directory);
5082
- const projectKey = config2.projectKey || sanitizeProjectKey(await deriveProjectKey(directory));
5083
- const projectName = config2.projectName || projectKey;
5114
+ const projectKey = config3.projectKey || sanitizeProjectKey(await deriveProjectKey(directory));
5115
+ const projectName = config3.projectName || projectKey;
5084
5116
  const projectsApi = new ProjectsAPI(adminClient);
5085
5117
  const exists = await projectsApi.exists(projectKey);
5086
5118
  const isNewProject = !exists;
5087
5119
  if (isNewProject) {
5088
5120
  await projectsApi.create({ projectKey, name: projectName, visibility: "private" });
5089
- logger5.info("Created new project", { projectKey });
5121
+ logger6.info("Created new project", { projectKey });
5090
5122
  }
5091
5123
  const tokenName = generateTokenName(projectKey);
5092
5124
  const tokenResponse = await generateAnalysisToken(adminClient, tokenName, projectKey);
5093
- const qualityGateName = config2.qualityGate ?? QUALITY_GATE_MAPPING[config2.level];
5125
+ const qualityGateName = config3.qualityGate ?? QUALITY_GATE_MAPPING[config3.level];
5094
5126
  try {
5095
5127
  await setProjectQualityGate(adminClient, projectKey, qualityGateName);
5096
5128
  } catch (error45) {
5097
- logger5.warn("Failed to set quality gate", { error: String(error45) });
5129
+ logger6.warn("Failed to set quality gate", { error: String(error45) });
5098
5130
  }
5099
- await configureProjectSettings(adminClient, projectKey, detection.languages, config2);
5131
+ await configureProjectSettings(adminClient, projectKey, detection.languages, config3);
5100
5132
  const state = createInitialState({
5101
5133
  projectKey,
5102
5134
  projectToken: tokenResponse.token,
@@ -5106,7 +5138,7 @@ async function bootstrap(options) {
5106
5138
  });
5107
5139
  await saveProjectState(directory, state);
5108
5140
  await ensureGitignore(directory);
5109
- logger5.info("Bootstrap complete", { projectKey, isNewProject });
5141
+ logger6.info("Bootstrap complete", { projectKey, isNewProject });
5110
5142
  return {
5111
5143
  success: true,
5112
5144
  projectKey,
@@ -5121,7 +5153,7 @@ async function setProjectQualityGate(client, projectKey, qualityGateName) {
5121
5153
  const gatesResponse = await client.get("/api/qualitygates/list");
5122
5154
  const gate = gatesResponse.qualitygates.find((g) => g.name === qualityGateName);
5123
5155
  if (!gate) {
5124
- logger5.warn(`Quality gate '${qualityGateName}' not found, using default`);
5156
+ logger6.warn(`Quality gate '${qualityGateName}' not found, using default`);
5125
5157
  return;
5126
5158
  }
5127
5159
  await client.post("/api/qualitygates/select", {
@@ -5129,20 +5161,20 @@ async function setProjectQualityGate(client, projectKey, qualityGateName) {
5129
5161
  gateId: gate.id
5130
5162
  });
5131
5163
  }
5132
- async function configureProjectSettings(client, projectKey, languages, config2) {
5164
+ async function configureProjectSettings(client, projectKey, languages, config3) {
5133
5165
  const settings = [];
5134
5166
  settings.push({ key: "sonar.sourceEncoding", value: "UTF-8" });
5135
5167
  if (languages.includes("typescript") || languages.includes("javascript")) {
5136
5168
  settings.push({ key: "sonar.javascript.node.maxspace", value: "4096" });
5137
5169
  }
5138
- if (config2.sources) {
5139
- settings.push({ key: "sonar.sources", value: config2.sources });
5170
+ if (config3.sources) {
5171
+ settings.push({ key: "sonar.sources", value: config3.sources });
5140
5172
  }
5141
- if (config2.tests) {
5142
- settings.push({ key: "sonar.tests", value: config2.tests });
5173
+ if (config3.tests) {
5174
+ settings.push({ key: "sonar.tests", value: config3.tests });
5143
5175
  }
5144
- if (config2.exclusions) {
5145
- settings.push({ key: "sonar.exclusions", value: config2.exclusions });
5176
+ if (config3.exclusions) {
5177
+ settings.push({ key: "sonar.exclusions", value: config3.exclusions });
5146
5178
  }
5147
5179
  for (const setting of settings) {
5148
5180
  try {
@@ -5164,21 +5196,18 @@ async function disconnect(directory) {
5164
5196
  }
5165
5197
  const { deleteProjectState: deleteProjectState2 } = await Promise.resolve().then(() => (init_state(), exports_state));
5166
5198
  await deleteProjectState2(directory);
5167
- logger5.info("Disconnected from SonarQube", { projectKey: state.projectKey });
5199
+ logger6.info("Disconnected from SonarQube", { projectKey: state.projectKey });
5168
5200
  }
5169
- var logger5, QUALITY_GATE_MAPPING;
5201
+ var logger6, QUALITY_GATE_MAPPING;
5170
5202
  var init_bootstrap = __esm(() => {
5171
5203
  init_types2();
5172
5204
  init_client();
5173
5205
  init_projects();
5174
- init_config2();
5206
+ init_config3();
5175
5207
  init_state();
5176
5208
  init_config();
5177
- logger5 = {
5178
- info: (_msg, _extra) => {},
5179
- warn: (_msg, _extra) => {},
5180
- error: (_msg, _extra) => {}
5181
- };
5209
+ init_logger();
5210
+ logger6 = createNoopLogger();
5182
5211
  QUALITY_GATE_MAPPING = {
5183
5212
  enterprise: "Sonar way",
5184
5213
  standard: "Sonar way",
@@ -17514,21 +17543,49 @@ init_config();
17514
17543
  init_config();
17515
17544
 
17516
17545
  // src/scanner/index.ts
17517
- init_config2();
17546
+ init_config3();
17518
17547
 
17519
17548
  // src/api/index.ts
17549
+ init_base_api();
17520
17550
  init_client();
17521
17551
  init_projects();
17522
17552
 
17553
+ // src/utils/paths.ts
17554
+ function extractFilePathFromComponentKey(componentKey) {
17555
+ const colonIndex = componentKey.indexOf(":");
17556
+ if (colonIndex >= 0) {
17557
+ return componentKey.substring(colonIndex + 1);
17558
+ }
17559
+ return componentKey;
17560
+ }
17561
+ function shortenPath(path, maxLength = 50) {
17562
+ if (path.length <= maxLength)
17563
+ return path;
17564
+ return "..." + path.slice(-(maxLength - 3));
17565
+ }
17566
+
17523
17567
  // src/api/issues.ts
17524
- init_logger();
17568
+ init_base_api();
17525
17569
 
17526
- class IssuesAPI {
17527
- client;
17528
- logger;
17529
- constructor(client, logger3) {
17530
- this.client = client;
17531
- this.logger = logger3 ?? new Logger("sonarqube-issues");
17570
+ // src/constants.ts
17571
+ var API = {
17572
+ DEFAULT_PAGE_SIZE: 500,
17573
+ DEFAULT_LIMIT: 20,
17574
+ MAX_PAGES: 10,
17575
+ POLL_INTERVAL_MS: 2000,
17576
+ ANALYSIS_TIMEOUT_MS: 300000,
17577
+ SOURCE_CONTEXT_LINES: 5
17578
+ };
17579
+ var RATINGS = ["A", "B", "C", "D", "E"];
17580
+ function numericToRating(value) {
17581
+ const index = Math.min(Math.max(Math.round(value) - 1, 0), 4);
17582
+ return RATINGS[index];
17583
+ }
17584
+
17585
+ // src/api/issues.ts
17586
+ class IssuesAPI extends BaseAPI {
17587
+ constructor(client, logger4) {
17588
+ super(client, "sonarqube-issues", logger4);
17532
17589
  }
17533
17590
  async search(options) {
17534
17591
  this.logger.debug(`Searching issues for project: ${options.projectKey}`);
@@ -17540,7 +17597,7 @@ class IssuesAPI {
17540
17597
  resolved: options.resolved === undefined ? undefined : String(options.resolved),
17541
17598
  branch: options.onBranch,
17542
17599
  inNewCodePeriod: options.inNewCode === undefined ? undefined : String(options.inNewCode),
17543
- ps: options.pageSize ?? 500,
17600
+ ps: options.pageSize ?? API.DEFAULT_PAGE_SIZE,
17544
17601
  p: options.page ?? 1
17545
17602
  });
17546
17603
  }
@@ -17555,7 +17612,7 @@ class IssuesAPI {
17555
17612
  types: options.types,
17556
17613
  resolved: false,
17557
17614
  page,
17558
- pageSize: 500
17615
+ pageSize: API.DEFAULT_PAGE_SIZE
17559
17616
  });
17560
17617
  allIssues.push(...response.issues);
17561
17618
  const totalPages = Math.ceil(response.paging.total / response.paging.pageSize);
@@ -17591,11 +17648,7 @@ class IssuesAPI {
17591
17648
  }
17592
17649
  return issues.map((issue2) => {
17593
17650
  const componentPath = componentPaths.get(issue2.component);
17594
- let filePath = componentPath ?? issue2.component;
17595
- const colonIndex = filePath.indexOf(":");
17596
- if (colonIndex !== -1) {
17597
- filePath = filePath.substring(colonIndex + 1);
17598
- }
17651
+ const filePath = extractFilePathFromComponentKey(componentPath ?? issue2.component);
17599
17652
  return {
17600
17653
  key: issue2.key,
17601
17654
  severity: issue2.severity,
@@ -17655,7 +17708,7 @@ class IssuesAPI {
17655
17708
  try {
17656
17709
  const response = await this.client.get("/api/hotspots/search", {
17657
17710
  projectKey,
17658
- ps: 500,
17711
+ ps: API.DEFAULT_PAGE_SIZE,
17659
17712
  p: 1
17660
17713
  });
17661
17714
  this.logger.info(`Found ${response.hotspots.length} security hotspots for project ${projectKey}`);
@@ -17670,9 +17723,11 @@ class IssuesAPI {
17670
17723
  return hotspots.filter((h) => h.status === "TO_REVIEW");
17671
17724
  }
17672
17725
  }
17673
- // src/api/quality-gate.ts
17726
+ // src/api/quality-gate/api.ts
17674
17727
  init_types2();
17675
- init_logger();
17728
+ init_base_api();
17729
+
17730
+ // src/api/quality-gate/metrics.ts
17676
17731
  var METRIC_KEYS = {
17677
17732
  coverage: "coverage",
17678
17733
  newCoverage: "new_coverage",
@@ -17701,13 +17756,170 @@ var METRIC_KEYS = {
17701
17756
  complexity: "complexity",
17702
17757
  cognitiveComplexity: "cognitive_complexity"
17703
17758
  };
17759
+ var METRIC_NAMES = {
17760
+ coverage: "Coverage",
17761
+ new_coverage: "Coverage on New Code",
17762
+ duplicated_lines_density: "Duplicated Lines (%)",
17763
+ bugs: "Bugs",
17764
+ new_bugs: "New Bugs",
17765
+ vulnerabilities: "Vulnerabilities",
17766
+ new_vulnerabilities: "New Vulnerabilities",
17767
+ code_smells: "Code Smells",
17768
+ new_code_smells: "New Code Smells",
17769
+ security_hotspots: "Security Hotspots",
17770
+ reliability_rating: "Reliability Rating",
17771
+ security_rating: "Security Rating",
17772
+ sqale_rating: "Maintainability Rating",
17773
+ security_review_rating: "Security Review Rating"
17774
+ };
17775
+ var COMPARATOR_SYMBOLS = {
17776
+ GT: ">",
17777
+ LT: "<",
17778
+ EQ: "=",
17779
+ NE: "!="
17780
+ };
17781
+ function formatMetricName(metricKey) {
17782
+ return METRIC_NAMES[metricKey] ?? metricKey;
17783
+ }
17784
+ function formatComparator(comparator) {
17785
+ return COMPARATOR_SYMBOLS[comparator] ?? comparator;
17786
+ }
17787
+ function getRatingLetter(value) {
17788
+ if (!value)
17789
+ return "?";
17790
+ const ratings = ["A", "B", "C", "D", "E"];
17791
+ const index = Number.parseInt(value, 10) - 1;
17792
+ return ratings[index] ?? "?";
17793
+ }
17704
17794
 
17705
- class QualityGateAPI {
17706
- client;
17707
- logger;
17708
- constructor(client, logger3) {
17709
- this.client = client;
17710
- this.logger = logger3 ?? new Logger("sonarqube-quality-gate");
17795
+ // src/api/quality-gate/enterprise-validation.ts
17796
+ init_types2();
17797
+ function runEnterpriseChecks(metrics, thresholds) {
17798
+ const coverage = metrics.coverage ?? 0;
17799
+ const duplications = metrics.duplications ?? 0;
17800
+ const reliabilityNum = ratingToNumber(metrics.reliabilityRating);
17801
+ const securityNum = ratingToNumber(metrics.securityRating);
17802
+ const maintainabilityNum = ratingToNumber(metrics.maintainabilityRating);
17803
+ const expectedReliability = numberToRating(thresholds.minReliabilityRating);
17804
+ const expectedSecurity = numberToRating(thresholds.minSecurityRating);
17805
+ const expectedMaintainability = numberToRating(thresholds.minMaintainabilityRating);
17806
+ return [
17807
+ {
17808
+ name: "Coverage",
17809
+ passed: coverage >= thresholds.minCoverage,
17810
+ actual: `${coverage.toFixed(1)}%`,
17811
+ expected: `>= ${thresholds.minCoverage}%`,
17812
+ message: coverage >= thresholds.minCoverage ? `Coverage ${coverage.toFixed(1)}% meets threshold` : `Coverage ${coverage.toFixed(1)}% is below ${thresholds.minCoverage}% threshold`
17813
+ },
17814
+ {
17815
+ name: "Duplications",
17816
+ passed: duplications <= thresholds.maxDuplications,
17817
+ actual: `${duplications.toFixed(1)}%`,
17818
+ expected: `<= ${thresholds.maxDuplications}%`,
17819
+ message: duplications <= thresholds.maxDuplications ? `Duplications ${duplications.toFixed(1)}% meets threshold` : `Duplications ${duplications.toFixed(1)}% exceeds ${thresholds.maxDuplications}% threshold`
17820
+ },
17821
+ {
17822
+ name: "Bugs",
17823
+ passed: metrics.bugs <= thresholds.maxBugs,
17824
+ actual: metrics.bugs,
17825
+ expected: `<= ${thresholds.maxBugs}`,
17826
+ message: metrics.bugs <= thresholds.maxBugs ? `${metrics.bugs} bugs meets threshold` : `${metrics.bugs} bugs exceeds ${thresholds.maxBugs} threshold`
17827
+ },
17828
+ {
17829
+ name: "Vulnerabilities",
17830
+ passed: metrics.vulnerabilities <= thresholds.maxVulnerabilities,
17831
+ actual: metrics.vulnerabilities,
17832
+ expected: `<= ${thresholds.maxVulnerabilities}`,
17833
+ message: metrics.vulnerabilities <= thresholds.maxVulnerabilities ? `${metrics.vulnerabilities} vulnerabilities meets threshold` : `${metrics.vulnerabilities} vulnerabilities exceeds ${thresholds.maxVulnerabilities} threshold`
17834
+ },
17835
+ {
17836
+ name: "Code Smells",
17837
+ passed: metrics.codeSmells <= thresholds.maxCodeSmells,
17838
+ actual: metrics.codeSmells,
17839
+ expected: `<= ${thresholds.maxCodeSmells}`,
17840
+ message: metrics.codeSmells <= thresholds.maxCodeSmells ? `${metrics.codeSmells} code smells meets threshold` : `${metrics.codeSmells} code smells exceeds ${thresholds.maxCodeSmells} threshold`
17841
+ },
17842
+ {
17843
+ name: "Security Hotspots",
17844
+ passed: metrics.securityHotspots <= thresholds.maxSecurityHotspots,
17845
+ actual: metrics.securityHotspots,
17846
+ expected: `<= ${thresholds.maxSecurityHotspots}`,
17847
+ message: metrics.securityHotspots <= thresholds.maxSecurityHotspots ? `${metrics.securityHotspots} security hotspots meets threshold` : `${metrics.securityHotspots} security hotspots exceeds ${thresholds.maxSecurityHotspots} threshold`
17848
+ },
17849
+ {
17850
+ name: "Reliability Rating",
17851
+ passed: reliabilityNum <= thresholds.minReliabilityRating,
17852
+ actual: metrics.reliabilityRating,
17853
+ expected: `>= ${expectedReliability}`,
17854
+ message: reliabilityNum <= thresholds.minReliabilityRating ? `Reliability rating ${metrics.reliabilityRating} meets threshold` : `Reliability rating ${metrics.reliabilityRating} is below ${expectedReliability} threshold`
17855
+ },
17856
+ {
17857
+ name: "Security Rating",
17858
+ passed: securityNum <= thresholds.minSecurityRating,
17859
+ actual: metrics.securityRating,
17860
+ expected: `>= ${expectedSecurity}`,
17861
+ message: securityNum <= thresholds.minSecurityRating ? `Security rating ${metrics.securityRating} meets threshold` : `Security rating ${metrics.securityRating} is below ${expectedSecurity} threshold`
17862
+ },
17863
+ {
17864
+ name: "Maintainability Rating",
17865
+ passed: maintainabilityNum <= thresholds.minMaintainabilityRating,
17866
+ actual: metrics.maintainabilityRating,
17867
+ expected: `>= ${expectedMaintainability}`,
17868
+ message: maintainabilityNum <= thresholds.minMaintainabilityRating ? `Maintainability rating ${metrics.maintainabilityRating} meets threshold` : `Maintainability rating ${metrics.maintainabilityRating} is below ${expectedMaintainability} threshold`
17869
+ }
17870
+ ];
17871
+ }
17872
+ function formatEnterpriseValidation(result) {
17873
+ const statusIcon = result.passed ? "[PASS]" : "[FAIL]";
17874
+ const checkRows = result.checks.map((check2) => {
17875
+ const status = check2.passed ? "PASS" : "FAIL";
17876
+ return `| ${check2.name} | ${status} | ${check2.actual} | ${check2.expected} |`;
17877
+ });
17878
+ const metricsRows = [
17879
+ `| Coverage | ${result.metrics.coverage?.toFixed(1) ?? "N/A"}% |`,
17880
+ `| Duplications | ${result.metrics.duplications?.toFixed(1) ?? "N/A"}% |`,
17881
+ `| Bugs | ${result.metrics.bugs} |`,
17882
+ `| Vulnerabilities | ${result.metrics.vulnerabilities} |`,
17883
+ `| Code Smells | ${result.metrics.codeSmells} |`,
17884
+ `| Security Hotspots | ${result.metrics.securityHotspots} |`,
17885
+ `| Reliability Rating | ${result.metrics.reliabilityRating} |`,
17886
+ `| Security Rating | ${result.metrics.securityRating} |`,
17887
+ `| Maintainability Rating | ${result.metrics.maintainabilityRating} |`,
17888
+ `| Lines of Code | ${result.metrics.linesOfCode.toLocaleString()} |`
17889
+ ];
17890
+ const failedSection = result.passed ? [] : [
17891
+ "",
17892
+ "### Failed Checks",
17893
+ "",
17894
+ ...result.checks.filter((c) => !c.passed).map((check2) => `- **${check2.name}**: ${check2.message}`)
17895
+ ];
17896
+ return [
17897
+ `## Enterprise Quality Validation ${statusIcon}`,
17898
+ "",
17899
+ `**Project:** \`${result.projectKey}\``,
17900
+ `**Level:** ${result.level}`,
17901
+ `**Result:** ${result.summary}`,
17902
+ "",
17903
+ "### Quality Checks",
17904
+ "",
17905
+ "| Check | Status | Actual | Expected |",
17906
+ "|-------|--------|--------|----------|",
17907
+ ...checkRows,
17908
+ "",
17909
+ "### Current Metrics",
17910
+ "",
17911
+ "| Metric | Value |",
17912
+ "|--------|-------|",
17913
+ ...metricsRows,
17914
+ ...failedSection
17915
+ ].join(`
17916
+ `);
17917
+ }
17918
+
17919
+ // src/api/quality-gate/api.ts
17920
+ class QualityGateAPI extends BaseAPI {
17921
+ constructor(client, logger4) {
17922
+ super(client, "sonarqube-quality-gate", logger4);
17711
17923
  }
17712
17924
  async getStatus(projectKey, branch) {
17713
17925
  this.logger.debug(`Getting quality gate status for: ${projectKey}`);
@@ -17752,14 +17964,6 @@ class QualityGateAPI {
17752
17964
  const value = getValue(metric);
17753
17965
  return value ? Number.parseFloat(value) : 0;
17754
17966
  };
17755
- const getRating = (metric) => {
17756
- const value = getValue(metric);
17757
- if (!value)
17758
- return "?";
17759
- const ratings = ["A", "B", "C", "D", "E"];
17760
- const index = Number.parseInt(value, 10) - 1;
17761
- return ratings[index] ?? "?";
17762
- };
17763
17967
  return {
17764
17968
  coverage: getValue(METRIC_KEYS.coverage) ? Number.parseFloat(getValue(METRIC_KEYS.coverage)) : undefined,
17765
17969
  duplications: getValue(METRIC_KEYS.duplicatedLinesDensity) ? Number.parseFloat(getValue(METRIC_KEYS.duplicatedLinesDensity)) : undefined,
@@ -17767,9 +17971,9 @@ class QualityGateAPI {
17767
17971
  vulnerabilities: getNumericValue(METRIC_KEYS.vulnerabilities),
17768
17972
  codeSmells: getNumericValue(METRIC_KEYS.codeSmells),
17769
17973
  securityHotspots: getNumericValue(METRIC_KEYS.securityHotspots),
17770
- reliabilityRating: getRating(METRIC_KEYS.reliabilityRating),
17771
- securityRating: getRating(METRIC_KEYS.securityRating),
17772
- maintainabilityRating: getRating(METRIC_KEYS.sqaleRating),
17974
+ reliabilityRating: getRatingLetter(getValue(METRIC_KEYS.reliabilityRating)),
17975
+ securityRating: getRatingLetter(getValue(METRIC_KEYS.securityRating)),
17976
+ maintainabilityRating: getRatingLetter(getValue(METRIC_KEYS.sqaleRating)),
17773
17977
  linesOfCode: getNumericValue(METRIC_KEYS.ncloc)
17774
17978
  };
17775
17979
  }
@@ -17800,40 +18004,12 @@ class QualityGateAPI {
17800
18004
  `);
17801
18005
  }
17802
18006
  formatCondition(condition) {
17803
- const metric = this.formatMetricName(condition.metricKey);
18007
+ const metric = formatMetricName(condition.metricKey);
17804
18008
  const actual = condition.actualValue ?? "N/A";
17805
18009
  const threshold = condition.errorThreshold ?? "N/A";
17806
- const comparator = this.formatComparator(condition.comparator);
18010
+ const comparator = formatComparator(condition.comparator);
17807
18011
  return `- **${metric}**: ${actual} (threshold: ${comparator} ${threshold})`;
17808
18012
  }
17809
- formatMetricName(metricKey) {
17810
- const names = {
17811
- coverage: "Coverage",
17812
- new_coverage: "Coverage on New Code",
17813
- duplicated_lines_density: "Duplicated Lines (%)",
17814
- bugs: "Bugs",
17815
- new_bugs: "New Bugs",
17816
- vulnerabilities: "Vulnerabilities",
17817
- new_vulnerabilities: "New Vulnerabilities",
17818
- code_smells: "Code Smells",
17819
- new_code_smells: "New Code Smells",
17820
- security_hotspots: "Security Hotspots",
17821
- reliability_rating: "Reliability Rating",
17822
- security_rating: "Security Rating",
17823
- sqale_rating: "Maintainability Rating",
17824
- security_review_rating: "Security Review Rating"
17825
- };
17826
- return names[metricKey] ?? metricKey;
17827
- }
17828
- formatComparator(comparator) {
17829
- const symbols = {
17830
- GT: ">",
17831
- LT: "<",
17832
- EQ: "=",
17833
- NE: "!="
17834
- };
17835
- return symbols[comparator] ?? comparator;
17836
- }
17837
18013
  formatStatusEmoji(status) {
17838
18014
  switch (status) {
17839
18015
  case "OK":
@@ -17872,152 +18048,30 @@ class QualityGateAPI {
17872
18048
  };
17873
18049
  }
17874
18050
  const metrics = await this.getQualityMetrics(projectKey);
17875
- const checks3 = this.runEnterpriseChecks(metrics, thresholds);
18051
+ const checks3 = runEnterpriseChecks(metrics, thresholds);
17876
18052
  const passed = checks3.every((check2) => check2.passed);
17877
18053
  const failedCount = checks3.filter((c) => !c.passed).length;
17878
18054
  const summary = passed ? `All ${checks3.length} enterprise quality checks passed` : `${failedCount} of ${checks3.length} enterprise quality checks failed`;
17879
18055
  return {
17880
- passed,
17881
- projectKey,
17882
- level,
17883
- summary,
17884
- checks: checks3,
17885
- metrics,
17886
- timestamp: new Date().toISOString()
17887
- };
17888
- }
17889
- runEnterpriseChecks(metrics, thresholds) {
17890
- const coverage = metrics.coverage ?? 0;
17891
- const duplications = metrics.duplications ?? 0;
17892
- const reliabilityNum = ratingToNumber(metrics.reliabilityRating);
17893
- const securityNum = ratingToNumber(metrics.securityRating);
17894
- const maintainabilityNum = ratingToNumber(metrics.maintainabilityRating);
17895
- const expectedReliability = numberToRating(thresholds.minReliabilityRating);
17896
- const expectedSecurity = numberToRating(thresholds.minSecurityRating);
17897
- const expectedMaintainability = numberToRating(thresholds.minMaintainabilityRating);
17898
- return [
17899
- {
17900
- name: "Coverage",
17901
- passed: coverage >= thresholds.minCoverage,
17902
- actual: `${coverage.toFixed(1)}%`,
17903
- expected: `>= ${thresholds.minCoverage}%`,
17904
- message: coverage >= thresholds.minCoverage ? `Coverage ${coverage.toFixed(1)}% meets threshold` : `Coverage ${coverage.toFixed(1)}% is below ${thresholds.minCoverage}% threshold`
17905
- },
17906
- {
17907
- name: "Duplications",
17908
- passed: duplications <= thresholds.maxDuplications,
17909
- actual: `${duplications.toFixed(1)}%`,
17910
- expected: `<= ${thresholds.maxDuplications}%`,
17911
- message: duplications <= thresholds.maxDuplications ? `Duplications ${duplications.toFixed(1)}% meets threshold` : `Duplications ${duplications.toFixed(1)}% exceeds ${thresholds.maxDuplications}% threshold`
17912
- },
17913
- {
17914
- name: "Bugs",
17915
- passed: metrics.bugs <= thresholds.maxBugs,
17916
- actual: metrics.bugs,
17917
- expected: `<= ${thresholds.maxBugs}`,
17918
- message: metrics.bugs <= thresholds.maxBugs ? `${metrics.bugs} bugs meets threshold` : `${metrics.bugs} bugs exceeds ${thresholds.maxBugs} threshold`
17919
- },
17920
- {
17921
- name: "Vulnerabilities",
17922
- passed: metrics.vulnerabilities <= thresholds.maxVulnerabilities,
17923
- actual: metrics.vulnerabilities,
17924
- expected: `<= ${thresholds.maxVulnerabilities}`,
17925
- message: metrics.vulnerabilities <= thresholds.maxVulnerabilities ? `${metrics.vulnerabilities} vulnerabilities meets threshold` : `${metrics.vulnerabilities} vulnerabilities exceeds ${thresholds.maxVulnerabilities} threshold`
17926
- },
17927
- {
17928
- name: "Code Smells",
17929
- passed: metrics.codeSmells <= thresholds.maxCodeSmells,
17930
- actual: metrics.codeSmells,
17931
- expected: `<= ${thresholds.maxCodeSmells}`,
17932
- message: metrics.codeSmells <= thresholds.maxCodeSmells ? `${metrics.codeSmells} code smells meets threshold` : `${metrics.codeSmells} code smells exceeds ${thresholds.maxCodeSmells} threshold`
17933
- },
17934
- {
17935
- name: "Security Hotspots",
17936
- passed: metrics.securityHotspots <= thresholds.maxSecurityHotspots,
17937
- actual: metrics.securityHotspots,
17938
- expected: `<= ${thresholds.maxSecurityHotspots}`,
17939
- message: metrics.securityHotspots <= thresholds.maxSecurityHotspots ? `${metrics.securityHotspots} security hotspots meets threshold` : `${metrics.securityHotspots} security hotspots exceeds ${thresholds.maxSecurityHotspots} threshold`
17940
- },
17941
- {
17942
- name: "Reliability Rating",
17943
- passed: reliabilityNum <= thresholds.minReliabilityRating,
17944
- actual: metrics.reliabilityRating,
17945
- expected: `>= ${expectedReliability}`,
17946
- message: reliabilityNum <= thresholds.minReliabilityRating ? `Reliability rating ${metrics.reliabilityRating} meets threshold` : `Reliability rating ${metrics.reliabilityRating} is below ${expectedReliability} threshold`
17947
- },
17948
- {
17949
- name: "Security Rating",
17950
- passed: securityNum <= thresholds.minSecurityRating,
17951
- actual: metrics.securityRating,
17952
- expected: `>= ${expectedSecurity}`,
17953
- message: securityNum <= thresholds.minSecurityRating ? `Security rating ${metrics.securityRating} meets threshold` : `Security rating ${metrics.securityRating} is below ${expectedSecurity} threshold`
17954
- },
17955
- {
17956
- name: "Maintainability Rating",
17957
- passed: maintainabilityNum <= thresholds.minMaintainabilityRating,
17958
- actual: metrics.maintainabilityRating,
17959
- expected: `>= ${expectedMaintainability}`,
17960
- message: maintainabilityNum <= thresholds.minMaintainabilityRating ? `Maintainability rating ${metrics.maintainabilityRating} meets threshold` : `Maintainability rating ${metrics.maintainabilityRating} is below ${expectedMaintainability} threshold`
17961
- }
17962
- ];
17963
- }
17964
- formatEnterpriseValidation(result) {
17965
- const statusIcon = result.passed ? "[PASS]" : "[FAIL]";
17966
- const checkRows = result.checks.map((check2) => {
17967
- const status = check2.passed ? "PASS" : "FAIL";
17968
- return `| ${check2.name} | ${status} | ${check2.actual} | ${check2.expected} |`;
17969
- });
17970
- const metricsRows = [
17971
- `| Coverage | ${result.metrics.coverage?.toFixed(1) ?? "N/A"}% |`,
17972
- `| Duplications | ${result.metrics.duplications?.toFixed(1) ?? "N/A"}% |`,
17973
- `| Bugs | ${result.metrics.bugs} |`,
17974
- `| Vulnerabilities | ${result.metrics.vulnerabilities} |`,
17975
- `| Code Smells | ${result.metrics.codeSmells} |`,
17976
- `| Security Hotspots | ${result.metrics.securityHotspots} |`,
17977
- `| Reliability Rating | ${result.metrics.reliabilityRating} |`,
17978
- `| Security Rating | ${result.metrics.securityRating} |`,
17979
- `| Maintainability Rating | ${result.metrics.maintainabilityRating} |`,
17980
- `| Lines of Code | ${result.metrics.linesOfCode.toLocaleString()} |`
17981
- ];
17982
- const failedSection = result.passed ? [] : [
17983
- "",
17984
- "### Failed Checks",
17985
- "",
17986
- ...result.checks.filter((c) => !c.passed).map((check2) => `- **${check2.name}**: ${check2.message}`)
17987
- ];
17988
- return [
17989
- `## Enterprise Quality Validation ${statusIcon}`,
17990
- "",
17991
- `**Project:** \`${result.projectKey}\``,
17992
- `**Level:** ${result.level}`,
17993
- `**Result:** ${result.summary}`,
17994
- "",
17995
- "### Quality Checks",
17996
- "",
17997
- "| Check | Status | Actual | Expected |",
17998
- "|-------|--------|--------|----------|",
17999
- ...checkRows,
18000
- "",
18001
- "### Current Metrics",
18002
- "",
18003
- "| Metric | Value |",
18004
- "|--------|-------|",
18005
- ...metricsRows,
18006
- ...failedSection
18007
- ].join(`
18008
- `);
18056
+ passed,
18057
+ projectKey,
18058
+ level,
18059
+ summary,
18060
+ checks: checks3,
18061
+ metrics,
18062
+ timestamp: new Date().toISOString()
18063
+ };
18064
+ }
18065
+ formatEnterpriseValidation(result) {
18066
+ return formatEnterpriseValidation(result);
18009
18067
  }
18010
18068
  }
18011
18069
  // src/api/rules.ts
18012
- init_logger();
18013
-
18014
- class RulesAPI {
18015
- client;
18016
- logger;
18070
+ init_base_api();
18071
+ class RulesAPI extends BaseAPI {
18017
18072
  cache = new Map;
18018
- constructor(client, logger3) {
18019
- this.client = client;
18020
- this.logger = logger3 ?? new Logger("sonarqube-rules");
18073
+ constructor(client, logger4) {
18074
+ super(client, "sonarqube-rules", logger4);
18021
18075
  }
18022
18076
  parseRuleResponse(rule) {
18023
18077
  let rawDescription = rule.mdDesc ?? rule.htmlDesc ?? "";
@@ -18075,7 +18129,7 @@ class RulesAPI {
18075
18129
  try {
18076
18130
  const response = await this.client.get("/api/rules/search", {
18077
18131
  rule_key: uncached.join(","),
18078
- ps: 500
18132
+ ps: API.DEFAULT_PAGE_SIZE
18079
18133
  });
18080
18134
  for (const rule of response.rules) {
18081
18135
  const details = this.parseRuleResponse(rule);
@@ -18114,14 +18168,37 @@ class RulesAPI {
18114
18168
  }
18115
18169
  }
18116
18170
  // src/api/sources.ts
18117
- init_logger();
18171
+ init_base_api();
18118
18172
 
18119
- class SourcesAPI {
18120
- client;
18121
- logger;
18122
- constructor(client, logger3) {
18123
- this.client = client;
18124
- this.logger = logger3 ?? new Logger("sonarqube-sources");
18173
+ // src/utils/group-by.ts
18174
+ function groupBy(items, keyFn) {
18175
+ const groups = {};
18176
+ for (const item of items) {
18177
+ const key = keyFn(item);
18178
+ if (!groups[key]) {
18179
+ groups[key] = [];
18180
+ }
18181
+ groups[key].push(item);
18182
+ }
18183
+ return groups;
18184
+ }
18185
+
18186
+ // src/api/sources.ts
18187
+ function createIssueWithContext(issue2, sourceContext) {
18188
+ return {
18189
+ key: issue2.key,
18190
+ rule: issue2.rule,
18191
+ severity: issue2.severity,
18192
+ message: issue2.message,
18193
+ component: issue2.component,
18194
+ line: issue2.line,
18195
+ sourceContext
18196
+ };
18197
+ }
18198
+
18199
+ class SourcesAPI extends BaseAPI {
18200
+ constructor(client, logger4) {
18201
+ super(client, "sonarqube-sources", logger4);
18125
18202
  }
18126
18203
  async getSourceLines(componentKey, from, to) {
18127
18204
  this.logger.debug(`Fetching source lines: ${componentKey} [${from ?? 1}-${to ?? "end"}]`);
@@ -18152,20 +18229,12 @@ class SourcesAPI {
18152
18229
  };
18153
18230
  }
18154
18231
  groupIssuesByComponent(issues) {
18155
- const grouped = new Map;
18156
- for (const issue2 of issues) {
18157
- const existing = grouped.get(issue2.component) ?? [];
18158
- existing.push(issue2);
18159
- grouped.set(issue2.component, existing);
18160
- }
18161
- return grouped;
18232
+ const grouped = groupBy(issues, (issue2) => issue2.component);
18233
+ return new Map(Object.entries(grouped));
18162
18234
  }
18163
- createIssueWithContext(issue2, component, lineMap, contextLines) {
18164
- if (issue2.line === undefined) {
18165
- return { ...issue2, sourceContext: undefined };
18166
- }
18167
- const contextStart = Math.max(1, issue2.line - contextLines);
18168
- const contextEnd = issue2.line + contextLines;
18235
+ buildSourceContext(component, issueLine, lineMap, contextLines) {
18236
+ const contextStart = Math.max(1, issueLine - contextLines);
18237
+ const contextEnd = issueLine + contextLines;
18169
18238
  const contextSourceLines = [];
18170
18239
  for (let l = contextStart;l <= contextEnd; l++) {
18171
18240
  const sourceLine = lineMap.get(l);
@@ -18174,20 +18243,21 @@ class SourcesAPI {
18174
18243
  }
18175
18244
  }
18176
18245
  return {
18177
- ...issue2,
18178
- sourceContext: {
18179
- component,
18180
- lines: contextSourceLines,
18181
- startLine: contextStart,
18182
- endLine: contextEnd
18183
- }
18246
+ component,
18247
+ lines: contextSourceLines,
18248
+ startLine: contextStart,
18249
+ endLine: contextEnd
18184
18250
  };
18185
18251
  }
18252
+ createIssueWithSourceContext(issue2, lineMap, contextLines) {
18253
+ if (issue2.line === undefined) {
18254
+ return createIssueWithContext(issue2);
18255
+ }
18256
+ const sourceContext = this.buildSourceContext(issue2.component, issue2.line, lineMap, contextLines);
18257
+ return createIssueWithContext(issue2, sourceContext);
18258
+ }
18186
18259
  addIssuesWithoutContext(issues) {
18187
- return issues.map((issue2) => ({
18188
- ...issue2,
18189
- sourceContext: undefined
18190
- }));
18260
+ return issues.map((issue2) => createIssueWithContext(issue2));
18191
18261
  }
18192
18262
  async getIssuesWithContext(issues, contextLines = 3) {
18193
18263
  const results = [];
@@ -18204,7 +18274,7 @@ class SourcesAPI {
18204
18274
  const sourceLines = await this.getSourceLines(component, minLine, maxLine);
18205
18275
  const lineMap = new Map(sourceLines.map((l) => [l.line, l]));
18206
18276
  for (const issue2 of componentIssues) {
18207
- results.push(this.createIssueWithContext(issue2, component, lineMap, contextLines));
18277
+ results.push(this.createIssueWithSourceContext(issue2, lineMap, contextLines));
18208
18278
  }
18209
18279
  } catch {
18210
18280
  results.push(...this.addIssuesWithoutContext(componentIssues));
@@ -18225,7 +18295,7 @@ class SourcesAPI {
18225
18295
  return ["", "```", ...codeLines, "```"];
18226
18296
  }
18227
18297
  formatIssueWithContext(issue2) {
18228
- const filePath = issue2.component.includes(":") ? issue2.component.split(":").slice(1).join(":") : issue2.component;
18298
+ const filePath = extractFilePathFromComponentKey(issue2.component);
18229
18299
  const lines = [
18230
18300
  `### ${issue2.severity}: ${issue2.message}`,
18231
18301
  this.formatFileLocation(filePath, issue2.line),
@@ -18254,14 +18324,11 @@ class SourcesAPI {
18254
18324
  }
18255
18325
  }
18256
18326
  // src/api/duplications.ts
18257
- init_logger();
18327
+ init_base_api();
18258
18328
 
18259
- class DuplicationsAPI {
18260
- client;
18261
- logger;
18262
- constructor(client, logger3) {
18263
- this.client = client;
18264
- this.logger = logger3 ?? new Logger("sonarqube-duplications");
18329
+ class DuplicationsAPI extends BaseAPI {
18330
+ constructor(client, logger4) {
18331
+ super(client, "sonarqube-duplications", logger4);
18265
18332
  }
18266
18333
  async getDuplications(componentKey) {
18267
18334
  this.logger.debug(`Fetching duplications for: ${componentKey}`);
@@ -18313,12 +18380,12 @@ class DuplicationsAPI {
18313
18380
  if (!originalFile || !duplicateFile)
18314
18381
  continue;
18315
18382
  result.push({
18316
- originalFile: this.extractFilePath(originalFile.key),
18383
+ originalFile: extractFilePathFromComponentKey(originalFile.key),
18317
18384
  originalLines: {
18318
18385
  from: originalBlock.from,
18319
18386
  to: originalBlock.from + originalBlock.size - 1
18320
18387
  },
18321
- duplicateFile: this.extractFilePath(duplicateFile.key),
18388
+ duplicateFile: extractFilePathFromComponentKey(duplicateFile.key),
18322
18389
  duplicateLines: {
18323
18390
  from: dupBlock.from,
18324
18391
  to: dupBlock.from + dupBlock.size - 1
@@ -18359,20 +18426,14 @@ class DuplicationsAPI {
18359
18426
  return lines.join(`
18360
18427
  `);
18361
18428
  }
18362
- extractFilePath(componentKey) {
18363
- return componentKey.includes(":") ? componentKey.split(":").slice(1).join(":") : componentKey;
18364
- }
18365
18429
  }
18366
18430
  // src/api/ce.ts
18367
- init_logger();
18431
+ init_base_api();
18368
18432
  var debugLog = (_msg) => {};
18369
18433
 
18370
- class ComputeEngineAPI {
18371
- client;
18372
- logger;
18373
- constructor(client, logger3) {
18374
- this.client = client;
18375
- this.logger = logger3 ?? new Logger("sonarqube-ce");
18434
+ class ComputeEngineAPI extends BaseAPI {
18435
+ constructor(client, logger4) {
18436
+ super(client, "sonarqube-ce", logger4);
18376
18437
  }
18377
18438
  async getTask(taskId) {
18378
18439
  this.logger.debug(`Getting task status: ${taskId}`);
@@ -18410,7 +18471,7 @@ class ComputeEngineAPI {
18410
18471
  onlyCurrents: options.onlyCurrents?.toString(),
18411
18472
  maxExecutedAt: options.maxExecutedAt,
18412
18473
  minSubmittedAt: options.minSubmittedAt,
18413
- ps: options.pageSize ?? 20
18474
+ ps: options.pageSize ?? API.DEFAULT_LIMIT
18414
18475
  });
18415
18476
  return response.tasks;
18416
18477
  } catch (error45) {
@@ -18429,29 +18490,42 @@ class ComputeEngineAPI {
18429
18490
  const { current, queue } = await this.getComponentTasks(componentKey);
18430
18491
  return current !== undefined || queue.length > 0;
18431
18492
  }
18432
- async waitForTask(taskId, pollIntervalMs = 2000, maxWaitMs = 300000) {
18493
+ shouldContinuePolling(startTime, maxWaitMs) {
18494
+ return Date.now() - startTime < maxWaitMs;
18495
+ }
18496
+ isTaskComplete(status) {
18497
+ return ["SUCCESS", "FAILED", "CANCELED"].includes(status);
18498
+ }
18499
+ handleTaskNotFound(taskId, retries, maxRetries) {
18500
+ if (retries >= maxRetries) {
18501
+ this.logger.warn(`Failed to get task ${taskId}: Not found after ${maxRetries} retries`);
18502
+ return { shouldContinue: false, newRetries: retries };
18503
+ }
18504
+ return { shouldContinue: true, newRetries: retries + 1 };
18505
+ }
18506
+ async waitForTask(taskId, pollIntervalMs = API.POLL_INTERVAL_MS, maxWaitMs = API.ANALYSIS_TIMEOUT_MS) {
18433
18507
  this.logger.info(`Waiting for task to complete: ${taskId}`);
18434
18508
  const startTime = Date.now();
18435
18509
  let taskNotFoundRetries = 0;
18436
18510
  const maxTaskNotFoundRetries = 10;
18437
18511
  debugLog(`waitForTask: starting for ${taskId}`);
18438
- while (Date.now() - startTime < maxWaitMs) {
18512
+ while (this.shouldContinuePolling(startTime, maxWaitMs)) {
18439
18513
  const task = await this.getTask(taskId);
18440
18514
  if (!task) {
18441
- taskNotFoundRetries++;
18442
- debugLog(`waitForTask: task not found, retry ${taskNotFoundRetries}/${maxTaskNotFoundRetries}`);
18443
- if (taskNotFoundRetries >= maxTaskNotFoundRetries) {
18444
- this.logger.warn(`Task ${taskId} not found after ${maxTaskNotFoundRetries} retries`);
18515
+ const result = this.handleTaskNotFound(taskId, taskNotFoundRetries, maxTaskNotFoundRetries);
18516
+ taskNotFoundRetries = result.newRetries;
18517
+ if (!result.shouldContinue) {
18445
18518
  debugLog(`waitForTask: giving up after ${maxTaskNotFoundRetries} retries`);
18446
18519
  return;
18447
18520
  }
18521
+ debugLog(`waitForTask: task not found, retry ${taskNotFoundRetries}/${maxTaskNotFoundRetries}`);
18448
18522
  this.logger.debug(`Task ${taskId} not found yet, retrying (${taskNotFoundRetries}/${maxTaskNotFoundRetries})...`);
18449
18523
  await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
18450
18524
  continue;
18451
18525
  }
18452
18526
  taskNotFoundRetries = 0;
18453
18527
  debugLog(`waitForTask: task found, status=${task.status}`);
18454
- if (task.status === "SUCCESS" || task.status === "FAILED" || task.status === "CANCELED") {
18528
+ if (this.isTaskComplete(task.status)) {
18455
18529
  this.logger.info(`Task completed with status: ${task.status}`);
18456
18530
  debugLog(`waitForTask: task completed with ${task.status}`);
18457
18531
  return task;
@@ -18463,7 +18537,7 @@ class ComputeEngineAPI {
18463
18537
  debugLog(`waitForTask: timeout after ${maxWaitMs}ms`);
18464
18538
  return;
18465
18539
  }
18466
- async waitForAnalysis(componentKey, timeoutMs = 300000, pollIntervalMs = 2000) {
18540
+ async waitForAnalysis(componentKey, timeoutMs = API.ANALYSIS_TIMEOUT_MS, pollIntervalMs = API.POLL_INTERVAL_MS) {
18467
18541
  this.logger.info(`Waiting for analysis to complete: ${componentKey}`);
18468
18542
  const startTime = Date.now();
18469
18543
  let lastTaskId;
@@ -18516,22 +18590,18 @@ class ComputeEngineAPI {
18516
18590
  }
18517
18591
  }
18518
18592
  // src/api/analyses.ts
18519
- init_logger();
18593
+ init_base_api();
18520
18594
 
18521
- class ProjectAnalysesAPI {
18522
- client;
18523
- logger;
18524
- constructor(client, logger3) {
18525
- this.client = client;
18526
- this.logger = logger3 ?? new Logger("sonarqube-analyses");
18595
+ class ProjectAnalysesAPI extends BaseAPI {
18596
+ constructor(client, logger4) {
18597
+ super(client, "sonarqube-analyses", logger4);
18527
18598
  }
18528
18599
  async getAnalyses(options) {
18529
18600
  this.logger.info(`>>> getAnalyses called`, {
18530
18601
  projectKey: options.projectKey,
18531
18602
  projectKeyLength: options.projectKey?.length
18532
18603
  });
18533
- if (!options.projectKey) {
18534
- this.logger.error(`getAnalyses: projectKey is empty/undefined!`);
18604
+ if (!this.validateProjectKey(options.projectKey, "getAnalyses")) {
18535
18605
  return [];
18536
18606
  }
18537
18607
  try {
@@ -18586,29 +18656,22 @@ class ProjectAnalysesAPI {
18586
18656
  }
18587
18657
  }
18588
18658
  // src/api/profiles.ts
18589
- init_logger();
18659
+ init_base_api();
18590
18660
 
18591
- class QualityProfilesAPI {
18592
- client;
18593
- logger;
18594
- constructor(client, logger3) {
18595
- this.client = client;
18596
- this.logger = logger3 ?? new Logger("sonarqube-profiles");
18661
+ class QualityProfilesAPI extends BaseAPI {
18662
+ constructor(client, logger4) {
18663
+ super(client, "sonarqube-profiles", logger4);
18597
18664
  }
18598
18665
  async getProjectProfiles(projectKey) {
18599
18666
  this.logger.info(`>>> getProjectProfiles called`, { projectKey, projectKeyLength: projectKey?.length });
18600
- if (!projectKey) {
18601
- this.logger.error(`getProjectProfiles: projectKey is empty/undefined!`);
18667
+ if (!this.validateProjectKey(projectKey, "getProjectProfiles")) {
18602
18668
  return [];
18603
18669
  }
18604
- try {
18670
+ return this.safeRequest(async () => {
18605
18671
  const response = await this.client.get("/api/qualityprofiles/search", { project: projectKey });
18606
18672
  this.logger.info(`<<< getProjectProfiles success`, { profileCount: response.profiles.length });
18607
18673
  return response.profiles;
18608
- } catch (error45) {
18609
- this.logger.error(`getProjectProfiles failed`, { error: String(error45), projectKey });
18610
- return [];
18611
- }
18674
+ }, [], `getProjectProfiles failed for ${projectKey}`);
18612
18675
  }
18613
18676
  async getAllProfiles(language) {
18614
18677
  const languageInfo = language ? ` for ${language}` : "";
@@ -18623,12 +18686,9 @@ class QualityProfilesAPI {
18623
18686
  }
18624
18687
  async getInheritance(profileKey) {
18625
18688
  this.logger.debug(`Getting inheritance for profile ${profileKey}`);
18626
- try {
18689
+ return this.safeRequest(async () => {
18627
18690
  return await this.client.get("/api/qualityprofiles/inheritance", { qualityProfile: profileKey });
18628
- } catch (error45) {
18629
- this.logger.warn(`Failed to get inheritance: ${error45}`);
18630
- return;
18631
- }
18691
+ }, undefined, `Failed to get inheritance for ${profileKey}`);
18632
18692
  }
18633
18693
  formatProfilesForAgent(profiles, projectKey) {
18634
18694
  if (profiles.length === 0) {
@@ -18680,29 +18740,22 @@ class QualityProfilesAPI {
18680
18740
  }
18681
18741
  }
18682
18742
  // src/api/branches.ts
18683
- init_logger();
18743
+ init_base_api();
18684
18744
 
18685
- class BranchesAPI {
18686
- client;
18687
- logger;
18688
- constructor(client, logger3) {
18689
- this.client = client;
18690
- this.logger = logger3 ?? new Logger("sonarqube-branches");
18745
+ class BranchesAPI extends BaseAPI {
18746
+ constructor(client, logger4) {
18747
+ super(client, "sonarqube-branches", logger4);
18691
18748
  }
18692
18749
  async getBranches(projectKey) {
18693
18750
  this.logger.info(`>>> getBranches called`, { projectKey, projectKeyLength: projectKey?.length });
18694
- if (!projectKey) {
18695
- this.logger.error(`getBranches: projectKey is empty/undefined!`);
18751
+ if (!this.validateProjectKey(projectKey, "getBranches")) {
18696
18752
  return [];
18697
18753
  }
18698
- try {
18754
+ return this.safeRequest(async () => {
18699
18755
  const response = await this.client.get("/api/project_branches/list", { project: projectKey });
18700
18756
  this.logger.info(`<<< getBranches success`, { branchCount: response.branches.length });
18701
18757
  return response.branches;
18702
- } catch (error45) {
18703
- this.logger.error(`getBranches failed`, { error: String(error45), projectKey });
18704
- return [];
18705
- }
18758
+ }, [], `getBranches failed for ${projectKey}`);
18706
18759
  }
18707
18760
  async getMainBranch(projectKey) {
18708
18761
  const branches = await this.getBranches(projectKey);
@@ -18714,16 +18767,13 @@ class BranchesAPI {
18714
18767
  }
18715
18768
  async deleteBranch(projectKey, branchName) {
18716
18769
  this.logger.debug(`Deleting branch ${branchName} from ${projectKey}`);
18717
- try {
18770
+ return this.safeRequest(async () => {
18718
18771
  await this.client.post("/api/project_branches/delete", {
18719
18772
  project: projectKey,
18720
18773
  branch: branchName
18721
18774
  });
18722
18775
  return true;
18723
- } catch (error45) {
18724
- this.logger.warn(`Failed to delete branch: ${error45}`);
18725
- return false;
18726
- }
18776
+ }, false, `Failed to delete branch ${branchName}`);
18727
18777
  }
18728
18778
  async renameMainBranch(projectKey, newName) {
18729
18779
  this.logger.debug(`Renaming main branch to ${newName} in ${projectKey}`);
@@ -18804,7 +18854,37 @@ class BranchesAPI {
18804
18854
  }
18805
18855
  }
18806
18856
  // src/api/metrics.ts
18807
- init_logger();
18857
+ init_base_api();
18858
+
18859
+ // src/utils/metric-formatter.ts
18860
+ var MetricFormatter = {
18861
+ rating(value) {
18862
+ const num = typeof value === "string" ? Number.parseFloat(value) : value;
18863
+ return numericToRating(num);
18864
+ },
18865
+ percentage(value, decimals = 1) {
18866
+ const num = typeof value === "string" ? Number.parseFloat(value) : value;
18867
+ return `${num.toFixed(decimals)}%`;
18868
+ },
18869
+ duration(minutes) {
18870
+ if (minutes < 60)
18871
+ return `${minutes}min`;
18872
+ const hours = Math.floor(minutes / 60);
18873
+ const mins = minutes % 60;
18874
+ return `${hours}h ${mins}min`;
18875
+ },
18876
+ formatByType(metricKey, value) {
18877
+ if (metricKey.includes("rating")) {
18878
+ return this.rating(value);
18879
+ }
18880
+ if (metricKey.includes("coverage") || metricKey.includes("duplicat")) {
18881
+ return this.percentage(value);
18882
+ }
18883
+ return value;
18884
+ }
18885
+ };
18886
+
18887
+ // src/api/metrics.ts
18808
18888
  var COMMON_METRICS = [
18809
18889
  "ncloc",
18810
18890
  "lines",
@@ -18834,37 +18914,28 @@ var COMMON_METRICS = [
18834
18914
  "sqale_debt_ratio"
18835
18915
  ];
18836
18916
 
18837
- class MetricsAPI {
18838
- client;
18839
- logger;
18840
- constructor(client, logger3) {
18841
- this.client = client;
18842
- this.logger = logger3 ?? new Logger("sonarqube-metrics");
18917
+ class MetricsAPI extends BaseAPI {
18918
+ constructor(client, logger4) {
18919
+ super(client, "sonarqube-metrics", logger4);
18843
18920
  }
18844
18921
  async getMetricDefinitions() {
18845
18922
  this.logger.debug("Getting metric definitions");
18846
- try {
18847
- const response = await this.client.get("/api/metrics/search", { ps: 500 });
18923
+ return this.safeRequest(async () => {
18924
+ const response = await this.client.get("/api/metrics/search", { ps: API.DEFAULT_PAGE_SIZE });
18848
18925
  return response.metrics;
18849
- } catch (error45) {
18850
- this.logger.warn(`Failed to get metrics: ${error45}`);
18851
- return [];
18852
- }
18926
+ }, [], "Failed to get metric definitions");
18853
18927
  }
18854
18928
  async getMeasures(options) {
18855
18929
  const metrics = options.metricKeys ?? [...COMMON_METRICS];
18856
18930
  this.logger.debug(`Getting measures for ${options.componentKey}`);
18857
- try {
18931
+ return this.safeRequest(async () => {
18858
18932
  const response = await this.client.get("/api/measures/component", {
18859
18933
  component: options.componentKey,
18860
18934
  metricKeys: metrics.join(","),
18861
18935
  branch: options.branch
18862
18936
  });
18863
18937
  return response.component.measures;
18864
- } catch (error45) {
18865
- this.logger.warn(`Failed to get measures: ${error45}`);
18866
- return [];
18867
- }
18938
+ }, [], `Failed to get measures for ${options.componentKey}`);
18868
18939
  }
18869
18940
  async getMeasuresWithPeriod(options) {
18870
18941
  const metrics = options.metricKeys ?? [...COMMON_METRICS];
@@ -18945,33 +19016,32 @@ class MetricsAPI {
18945
19016
  }
18946
19017
  formatMetricValue(metricKey, value) {
18947
19018
  if (metricKey.includes("rating")) {
18948
- const ratings = ["A", "B", "C", "D", "E"];
18949
- const index = Number.parseInt(value, 10) - 1;
18950
- return ratings[index] ?? value;
19019
+ const numericValue = Number.parseInt(value, 10);
19020
+ if (numericValue >= 1 && numericValue <= 5) {
19021
+ return MetricFormatter.rating(numericValue);
19022
+ }
19023
+ return value;
18951
19024
  }
18952
19025
  if (metricKey.includes("coverage") || metricKey.includes("density") || metricKey.includes("ratio")) {
18953
- return `${value}%`;
19026
+ return MetricFormatter.percentage(value, 1);
18954
19027
  }
18955
19028
  if (metricKey === "sqale_index") {
18956
19029
  const minutes = Number.parseInt(value, 10);
18957
19030
  if (minutes < 60)
18958
19031
  return `${minutes}min`;
18959
19032
  if (minutes < 1440)
18960
- return `${Math.floor(minutes / 60)}h ${minutes % 60}min`;
19033
+ return MetricFormatter.duration(minutes);
18961
19034
  return `${Math.floor(minutes / 1440)}d`;
18962
19035
  }
18963
19036
  return value;
18964
19037
  }
18965
19038
  }
18966
19039
  // src/api/components.ts
18967
- init_logger();
19040
+ init_base_api();
18968
19041
 
18969
- class ComponentsAPI {
18970
- client;
18971
- logger;
18972
- constructor(client, logger3) {
18973
- this.client = client;
18974
- this.logger = logger3 ?? new Logger("sonarqube-components");
19042
+ class ComponentsAPI extends BaseAPI {
19043
+ constructor(client, logger4) {
19044
+ super(client, "sonarqube-components", logger4);
18975
19045
  }
18976
19046
  componentToSummary(comp, includeLanguage) {
18977
19047
  const measures = new Map(comp.measures?.map((m) => [m.metric, Number.parseInt(m.value, 10)]) ?? []);
@@ -19050,8 +19120,7 @@ class ComponentsAPI {
19050
19120
  "|------|------|-------|--------|-------|"
19051
19121
  ];
19052
19122
  for (const file2 of files.slice(0, 20)) {
19053
- const shortPath = this.shortenPath(file2.path);
19054
- lines.push(`| ${shortPath} | ${file2.bugs} | ${file2.vulnerabilities} | ${file2.codeSmells} | **${file2.total}** |`);
19123
+ lines.push(`| ${shortenPath(file2.path)} | ${file2.bugs} | ${file2.vulnerabilities} | ${file2.codeSmells} | **${file2.total}** |`);
19055
19124
  }
19056
19125
  if (files.length > 20) {
19057
19126
  lines.push(`
@@ -19069,14 +19138,6 @@ class ComponentsAPI {
19069
19138
  return lines.join(`
19070
19139
  `);
19071
19140
  }
19072
- shortenPath(path) {
19073
- if (path.length <= 50)
19074
- return path;
19075
- const parts = path.split("/");
19076
- if (parts.length <= 2)
19077
- return path;
19078
- return `.../${parts.slice(-2).join("/")}`;
19079
- }
19080
19141
  }
19081
19142
 
19082
19143
  // src/api/index.ts
@@ -19099,8 +19160,8 @@ class SonarQubeAPI {
19099
19160
  metrics;
19100
19161
  components;
19101
19162
  logger;
19102
- constructor(client, logger3) {
19103
- this.logger = logger3 ?? new Logger("sonarqube-api");
19163
+ constructor(client, logger4) {
19164
+ this.logger = logger4 ?? new Logger("sonarqube-api");
19104
19165
  this.client = client;
19105
19166
  this.projects = new ProjectsAPI(client, this.logger.child("projects"));
19106
19167
  this.issues = new IssuesAPI(client, this.logger.child("issues"));
@@ -19136,18 +19197,30 @@ class SonarQubeAPI {
19136
19197
  };
19137
19198
  }
19138
19199
  }
19139
- function createSonarQubeAPIWithToken(url2, token, logger3) {
19140
- const client = createClientWithToken(url2, token, logger3?.child("client"));
19141
- return new SonarQubeAPI(client, logger3);
19200
+ function createSonarQubeAPIWithToken(url2, token, logger4) {
19201
+ const client = createClientWithToken(url2, token, logger4?.child("client"));
19202
+ return new SonarQubeAPI(client, logger4);
19142
19203
  }
19143
- function createSonarQubeAPI(config2, state, logger3) {
19144
- return createSonarQubeAPIWithToken(config2.url, state.projectToken, logger3);
19204
+ function createSonarQubeAPI(config3, state, logger4) {
19205
+ return createSonarQubeAPIWithToken(config3.url, state.projectToken, logger4);
19145
19206
  }
19146
19207
 
19147
19208
  // src/scanner/runner.ts
19148
- init_config2();
19209
+ init_config3();
19149
19210
 
19150
19211
  // src/utils/severity.ts
19212
+ var SEVERITIES = ["BLOCKER", "CRITICAL", "MAJOR", "MINOR", "INFO"];
19213
+ var SEVERITY_LEVELS = {
19214
+ blocker: ["BLOCKER"],
19215
+ critical: ["BLOCKER", "CRITICAL"],
19216
+ major: ["BLOCKER", "CRITICAL", "MAJOR"],
19217
+ minor: ["BLOCKER", "CRITICAL", "MAJOR", "MINOR"],
19218
+ info: ["BLOCKER", "CRITICAL", "MAJOR", "MINOR", "INFO"],
19219
+ all: ["BLOCKER", "CRITICAL", "MAJOR", "MINOR", "INFO"]
19220
+ };
19221
+ function getSeveritiesFromLevel(level) {
19222
+ return SEVERITY_LEVELS[level.toLowerCase()] ?? [...SEVERITIES];
19223
+ }
19151
19224
  var SEVERITY_PRIORITY = {
19152
19225
  BLOCKER: 5,
19153
19226
  CRITICAL: 4,
@@ -19249,7 +19322,7 @@ function formatIssueBlock(issue2, _number2) {
19249
19322
 
19250
19323
  // src/scanner/runner.ts
19251
19324
  init_logger();
19252
- var logger3 = new Logger("scanner-runner");
19325
+ var logger4 = new Logger("scanner-runner");
19253
19326
  function extractTaskId(output) {
19254
19327
  const taskIdRegex = /api\/ce\/task\?id=([\w-]+)/;
19255
19328
  const taskIdMatch = taskIdRegex.exec(output);
@@ -19263,11 +19336,11 @@ function extractTaskId(output) {
19263
19336
  function sanitizeArgValue(value) {
19264
19337
  return value.replaceAll(/[;&|`$(){}[\]<>\\'"!\n\r]/g, "");
19265
19338
  }
19266
- async function runScanner(config2, state, options, directory) {
19339
+ async function runScanner(config3, state, options, directory) {
19267
19340
  const dir = directory ?? process.cwd();
19268
- logger3.info(`Starting SonarQube analysis for ${options.projectKey}`);
19341
+ logger4.info(`Starting SonarQube analysis for ${options.projectKey}`);
19269
19342
  const args = [
19270
- `-Dsonar.host.url=${sanitizeArgValue(config2.url)}`,
19343
+ `-Dsonar.host.url=${sanitizeArgValue(config3.url)}`,
19271
19344
  `-Dsonar.token=${sanitizeArgValue(state.projectToken)}`,
19272
19345
  `-Dsonar.projectKey=${sanitizeArgValue(options.projectKey)}`
19273
19346
  ];
@@ -19293,7 +19366,7 @@ async function runScanner(config2, state, options, directory) {
19293
19366
  args.push(`-D${sanitizeArgValue(key)}=${sanitizeArgValue(value)}`);
19294
19367
  }
19295
19368
  }
19296
- logger3.debug("Scanner arguments", { args: args.map((a) => a.includes("token") ? "-Dsonar.token=***" : a) });
19369
+ logger4.debug("Scanner arguments", { args: args.map((a) => a.includes("token") ? "-Dsonar.token=***" : a) });
19297
19370
  try {
19298
19371
  const proc = Bun.spawn(["npx", "sonarqube-scanner", ...args], {
19299
19372
  cwd: dir,
@@ -19311,7 +19384,7 @@ async function runScanner(config2, state, options, directory) {
19311
19384
  ${stderr}` : "");
19312
19385
  if (exitCode === 0) {
19313
19386
  const taskId = extractTaskId(output);
19314
- logger3.info("Scanner completed successfully", { taskId });
19387
+ logger4.info("Scanner completed successfully", { taskId });
19315
19388
  return {
19316
19389
  success: true,
19317
19390
  output,
@@ -19319,7 +19392,7 @@ ${stderr}` : "");
19319
19392
  taskId
19320
19393
  };
19321
19394
  } else {
19322
- logger3.error(`Scanner failed with exit code ${exitCode}`);
19395
+ logger4.error(`Scanner failed with exit code ${exitCode}`);
19323
19396
  return {
19324
19397
  success: false,
19325
19398
  output,
@@ -19329,7 +19402,7 @@ ${stderr}` : "");
19329
19402
  }
19330
19403
  } catch (error45) {
19331
19404
  const errorMessage = error45 instanceof Error ? error45.message : String(error45);
19332
- logger3.error(`Scanner execution failed: ${errorMessage}`);
19405
+ logger4.error(`Scanner execution failed: ${errorMessage}`);
19333
19406
  return {
19334
19407
  success: false,
19335
19408
  output: "",
@@ -19338,25 +19411,25 @@ ${stderr}` : "");
19338
19411
  };
19339
19412
  }
19340
19413
  }
19341
- async function runAnalysis(config2, state, options, directory) {
19414
+ async function runAnalysis(config3, state, options, directory) {
19342
19415
  const dir = directory ?? process.cwd();
19343
- const api2 = createSonarQubeAPI(config2, state);
19416
+ const api2 = createSonarQubeAPI(config3, state);
19344
19417
  const projectName = options.projectName ?? options.projectKey;
19345
19418
  const propertiesPath = `${dir}/sonar-project.properties`;
19346
19419
  const propertiesExist = await Bun.file(propertiesPath).exists();
19347
19420
  if (propertiesExist) {
19348
- logger3.info("Using existing sonar-project.properties (CI/CD compatible)");
19421
+ logger4.info("Using existing sonar-project.properties (CI/CD compatible)");
19349
19422
  } else {
19350
19423
  await writePropertiesFile({
19351
19424
  projectKey: options.projectKey,
19352
19425
  projectName,
19353
- sources: options.sources ?? config2.sources,
19354
- tests: options.tests ?? config2.tests,
19355
- exclusions: options.exclusions ?? config2.exclusions
19356
- }, config2, dir);
19357
- logger3.info("Created sonar-project.properties");
19426
+ sources: options.sources ?? config3.sources,
19427
+ tests: options.tests ?? config3.tests,
19428
+ exclusions: options.exclusions ?? config3.exclusions
19429
+ }, config3, dir);
19430
+ logger4.info("Created sonar-project.properties");
19358
19431
  }
19359
- const scannerResult = await runScanner(config2, state, {
19432
+ const scannerResult = await runScanner(config3, state, {
19360
19433
  projectKey: options.projectKey,
19361
19434
  projectName,
19362
19435
  sources: options.sources,
@@ -19367,22 +19440,22 @@ async function runAnalysis(config2, state, options, directory) {
19367
19440
  branch: options.branch
19368
19441
  }, dir);
19369
19442
  if (scannerResult.success && scannerResult.taskId) {
19370
- logger3.info(`Waiting for analysis task ${scannerResult.taskId} to complete...`);
19443
+ logger4.info(`Waiting for analysis task ${scannerResult.taskId} to complete...`);
19371
19444
  try {
19372
19445
  const task = await api2.ce.waitForTask(scannerResult.taskId);
19373
19446
  if (task) {
19374
- logger3.info(`Analysis task completed: ${task.status}`);
19447
+ logger4.info(`Analysis task completed: ${task.status}`);
19375
19448
  if (task.status === "FAILED") {
19376
- logger3.error(`Analysis failed: ${task.errorMessage ?? "Unknown error"}`);
19449
+ logger4.error(`Analysis failed: ${task.errorMessage ?? "Unknown error"}`);
19377
19450
  }
19378
19451
  } else {
19379
- logger3.warn("Task not found - continuing with available results");
19452
+ logger4.warn("Task not found - continuing with available results");
19380
19453
  }
19381
19454
  } catch (waitError) {
19382
- logger3.debug("Could not wait for task via CE API", { waitError });
19455
+ logger4.debug("Could not wait for task via CE API", { waitError });
19383
19456
  }
19384
19457
  } else if (scannerResult.success) {
19385
- logger3.debug("No task ID in scanner output, waiting briefly...");
19458
+ logger4.debug("No task ID in scanner output, waiting briefly...");
19386
19459
  await Bun.sleep(3000);
19387
19460
  }
19388
19461
  try {
@@ -19408,7 +19481,7 @@ async function runAnalysis(config2, state, options, directory) {
19408
19481
  securityHotspots: fullMetrics.securityHotspots
19409
19482
  };
19410
19483
  } catch (metricsError) {
19411
- logger3.debug("Metrics not available yet, using issue counts only", { metricsError });
19484
+ logger4.debug("Metrics not available yet, using issue counts only", { metricsError });
19412
19485
  }
19413
19486
  return {
19414
19487
  success: scannerResult.success,
@@ -19419,7 +19492,7 @@ async function runAnalysis(config2, state, options, directory) {
19419
19492
  timestamp: new Date().toISOString()
19420
19493
  };
19421
19494
  } catch (error45) {
19422
- logger3.warn("Could not fetch results from API, returning partial result", { error: error45 });
19495
+ logger4.warn("Could not fetch results from API, returning partial result", { error: error45 });
19423
19496
  return {
19424
19497
  success: scannerResult.success,
19425
19498
  qualityGateStatus: "NONE",
@@ -19443,60 +19516,60 @@ function formatAnalysisResult(result) {
19443
19516
  // src/hooks/index.ts
19444
19517
  init_bootstrap();
19445
19518
  init_logger();
19446
- var logger6 = new Logger("sonarqube-hooks");
19519
+ var logger7 = new Logger("sonarqube-hooks");
19447
19520
  var editedFiles = new Set;
19448
19521
  var lastAnalysisTime = 0;
19449
19522
  var ANALYSIS_COOLDOWN_MS = 5 * 60 * 1000;
19450
19523
  var bootstrapInProgress = false;
19451
- function isAnalysisEnabled(config2) {
19452
- return config2 !== null && config2.level !== "off" && config2.autoAnalyze;
19524
+ function isAnalysisEnabled(config3) {
19525
+ return config3 !== null && config3.level !== "off" && config3.autoAnalyze;
19453
19526
  }
19454
19527
  function isInCooldown() {
19455
19528
  const now = Date.now();
19456
19529
  return now - lastAnalysisTime < ANALYSIS_COOLDOWN_MS;
19457
19530
  }
19458
- async function handleBootstrap(config2, directory) {
19531
+ async function handleBootstrap(config3, directory) {
19459
19532
  if (bootstrapInProgress) {
19460
- logger6.debug("Bootstrap already in progress");
19533
+ logger7.debug("Bootstrap already in progress");
19461
19534
  return;
19462
19535
  }
19463
- logger6.info("First run detected - initializing SonarQube integration");
19536
+ logger7.info("First run detected - initializing SonarQube integration");
19464
19537
  bootstrapInProgress = true;
19465
19538
  try {
19466
- const result = await bootstrap({ config: config2, directory });
19539
+ const result = await bootstrap({ config: config3, directory });
19467
19540
  bootstrapInProgress = false;
19468
19541
  return result.success ? formatBootstrapMessage(result) : `**SonarQube Setup Failed**
19469
19542
 
19470
19543
  ${result.message}`;
19471
19544
  } catch (error45) {
19472
19545
  bootstrapInProgress = false;
19473
- logger6.error("Bootstrap failed", { error: String(error45) });
19546
+ logger7.error("Bootstrap failed", { error: String(error45) });
19474
19547
  const errorMsg = error45 instanceof Error ? error45.message : String(error45);
19475
19548
  return `**SonarQube Setup Error**
19476
19549
 
19477
19550
  ${errorMsg}`;
19478
19551
  }
19479
19552
  }
19480
- async function performAnalysis(config2, state, directory) {
19553
+ async function performAnalysis(config3, state, directory) {
19481
19554
  const projectKey = state.projectKey;
19482
- const projectName = config2.projectName ?? projectKey;
19483
- const result = await runAnalysis(config2, state, {
19555
+ const projectName = config3.projectName ?? projectKey;
19556
+ const result = await runAnalysis(config3, state, {
19484
19557
  projectKey,
19485
19558
  projectName,
19486
- sources: config2.sources,
19487
- tests: config2.tests
19559
+ sources: config3.sources,
19560
+ tests: config3.tests
19488
19561
  }, directory);
19489
19562
  editedFiles.clear();
19490
- return formatAnalysisOutput(result, config2);
19563
+ return formatAnalysisOutput(result, config3);
19491
19564
  }
19492
- function formatAnalysisOutput(result, config2) {
19565
+ function formatAnalysisOutput(result, config3) {
19493
19566
  const hasIssues = result.formattedIssues.length > 0;
19494
19567
  const gateFailed = result.qualityGateStatus !== "OK";
19495
19568
  if (!hasIssues && !gateFailed) {
19496
19569
  return;
19497
19570
  }
19498
19571
  let message = formatAnalysisResult(result);
19499
- message += formatActionPrompt(result, config2);
19572
+ message += formatActionPrompt(result, config3);
19500
19573
  return message;
19501
19574
  }
19502
19575
  function formatActionPrompt(result, _config) {
@@ -19514,33 +19587,33 @@ function formatActionPrompt(result, _config) {
19514
19587
  function createIdleHook(getConfig, getDirectory) {
19515
19588
  return async function handleSessionIdle() {
19516
19589
  const rawConfig = getConfig()?.["sonarqube"];
19517
- const config2 = loadConfig(rawConfig);
19518
- if (!isAnalysisEnabled(config2)) {
19590
+ const config3 = loadConfig(rawConfig);
19591
+ if (!isAnalysisEnabled(config3)) {
19519
19592
  return;
19520
19593
  }
19521
19594
  const directory = getDirectory();
19522
19595
  if (await needsBootstrap(directory)) {
19523
- return handleBootstrap(config2, directory);
19596
+ return handleBootstrap(config3, directory);
19524
19597
  }
19525
19598
  if (isInCooldown()) {
19526
- logger6.debug("Skipping auto-analysis (cooldown)");
19599
+ logger7.debug("Skipping auto-analysis (cooldown)");
19527
19600
  return;
19528
19601
  }
19529
19602
  if (editedFiles.size === 0) {
19530
- logger6.debug("Skipping auto-analysis (no edited files)");
19603
+ logger7.debug("Skipping auto-analysis (no edited files)");
19531
19604
  return;
19532
19605
  }
19533
- logger6.info("Session idle - triggering auto-analysis");
19606
+ logger7.info("Session idle - triggering auto-analysis");
19534
19607
  lastAnalysisTime = Date.now();
19535
19608
  try {
19536
19609
  const state = await getProjectState(directory);
19537
19610
  if (!state) {
19538
- logger6.warn("No project state found, cannot run analysis");
19611
+ logger7.warn("No project state found, cannot run analysis");
19539
19612
  return;
19540
19613
  }
19541
- return await performAnalysis(config2, state, directory);
19614
+ return await performAnalysis(config3, state, directory);
19542
19615
  } catch (error45) {
19543
- logger6.error(`Auto-analysis failed: ${error45}`);
19616
+ logger7.error(`Auto-analysis failed: ${error45}`);
19544
19617
  return;
19545
19618
  }
19546
19619
  };
@@ -19581,7 +19654,7 @@ function createFileEditedHook() {
19581
19654
  const { filePath } = input;
19582
19655
  if (!shouldIgnoreFile(filePath)) {
19583
19656
  editedFiles.add(filePath);
19584
- logger6.debug(`Tracked edited file: ${filePath}`);
19657
+ logger7.debug(`Tracked edited file: ${filePath}`);
19585
19658
  }
19586
19659
  };
19587
19660
  }
@@ -19604,20 +19677,185 @@ init_config();
19604
19677
  init_bootstrap();
19605
19678
  init_logger();
19606
19679
 
19607
- // src/tools/formatters.ts
19680
+ // src/tools/formatters/error.ts
19608
19681
  function formatError2(message) {
19609
19682
  return `## SonarQube Error
19610
19683
 
19611
19684
  ${message}`;
19612
19685
  }
19686
+ // src/tools/formatters/success.ts
19687
+ function formatSuccess(title, content) {
19688
+ return `## SonarQube: ${title}
19689
+
19690
+ ${content}`;
19691
+ }
19692
+ // src/tools/formatters/section.ts
19693
+ function formatKeyValue(key, value, bold = true) {
19694
+ const formattedKey = bold ? `**${key}:**` : `${key}:`;
19695
+ return `${formattedKey} ${value}`;
19696
+ }
19697
+ // src/tools/formatters/code.ts
19698
+ function formatInlineCode(code) {
19699
+ return `\`${code}\``;
19700
+ }
19701
+ // src/tools/formatters/project.ts
19702
+ function formatProjectHeader(projectKey, additionalInfo) {
19703
+ const lines = [`**Project:** ${formatInlineCode(projectKey)}`];
19704
+ if (additionalInfo) {
19705
+ lines.push(...Object.entries(additionalInfo).map(([key, value]) => formatKeyValue(key, value)));
19706
+ }
19707
+ return lines.join(`
19708
+ `);
19709
+ }
19710
+ function formatEmptyState(entity, projectKey, positiveMessage) {
19711
+ const lines = [
19712
+ formatProjectHeader(projectKey),
19713
+ "",
19714
+ `No ${entity} found.`,
19715
+ ...positiveMessage ? [positiveMessage] : []
19716
+ ];
19717
+ return lines.join(`
19718
+ `);
19719
+ }
19720
+ // src/tools/formatters/suggestions.ts
19721
+ function formatFixSuggestions(issues) {
19722
+ const blockers = issues.filter((i) => i.severity === "BLOCKER");
19723
+ const critical = issues.filter((i) => i.severity === "CRITICAL");
19724
+ if (blockers.length === 0 && critical.length === 0) {
19725
+ return "";
19726
+ }
19727
+ let output = `
19728
+
19729
+ ---
19730
+
19731
+ ## Fix Suggestions
19732
+
19733
+ `;
19734
+ output += `Based on the issues found, here are recommended actions:
19735
+
19736
+ `;
19737
+ if (blockers.length > 0) {
19738
+ output += `### Immediate Fixes Required (Blockers)
19739
+ `;
19740
+ for (const issue2 of blockers.slice(0, 5)) {
19741
+ output += `1. **${issue2.file}:${issue2.line ?? "?"}** - ${issue2.message}
19742
+ `;
19743
+ output += ` - Rule: \`${issue2.rule}\` - Review and fix this security/reliability issue
19744
+
19745
+ `;
19746
+ }
19747
+ }
19748
+ if (critical.length > 0) {
19749
+ output += `### High Priority Fixes (Critical)
19750
+ `;
19751
+ for (const issue2 of critical.slice(0, 5)) {
19752
+ output += `1. **${issue2.file}:${issue2.line ?? "?"}** - ${issue2.message}
19753
+ `;
19754
+ output += ` - Rule: \`${issue2.rule}\`
19755
+
19756
+ `;
19757
+ }
19758
+ }
19759
+ return output;
19760
+ }
19761
+ // src/utils/error-messages.ts
19762
+ var ErrorMessages = {
19763
+ missingParameter(param, example) {
19764
+ let msg = `Missing required parameter: ${param}`;
19765
+ if (example) {
19766
+ msg += `
19767
+
19768
+ Example: ${example}`;
19769
+ }
19770
+ return msg;
19771
+ },
19772
+ notFound(entity, key, suggestions) {
19773
+ let msg = `${entity} not found: ${key}`;
19774
+ if (suggestions?.length) {
19775
+ const suggestionList = suggestions.map((s) => "- " + s).join(`
19776
+ `);
19777
+ msg += `
19778
+
19779
+ Did you mean:
19780
+ ` + suggestionList;
19781
+ }
19782
+ return msg;
19783
+ },
19784
+ configurationMissing(message, requiredSteps) {
19785
+ let msg = message;
19786
+ if (requiredSteps?.length) {
19787
+ const stepList = requiredSteps.map((s, i) => i + 1 + ". " + s).join(`
19788
+ `);
19789
+ msg += `
19790
+
19791
+ Required steps:
19792
+ ` + stepList;
19793
+ }
19794
+ return msg;
19795
+ },
19796
+ apiError(action, message, serverUrl) {
19797
+ let msg = `Action \`${action}\` failed: ${message}`;
19798
+ const hints = [
19799
+ serverUrl ? `SonarQube server is reachable at ${serverUrl}` : "SonarQube server is reachable",
19800
+ "Credentials are valid",
19801
+ 'Run with action: "setup" to initialize'
19802
+ ];
19803
+ const hintList = hints.map((h, i) => i + 1 + ". " + h).join(`
19804
+ `);
19805
+ msg += `
19806
+
19807
+ Please check:
19808
+ ` + hintList;
19809
+ return msg;
19810
+ },
19811
+ connectionError(serverUrl) {
19812
+ return `Cannot connect to SonarQube server at ${serverUrl}`;
19813
+ },
19814
+ authenticationError(reason) {
19815
+ return `Authentication failed: ${reason}`;
19816
+ },
19817
+ ruleNotFound(ruleKey) {
19818
+ return `Rule \`${ruleKey}\` was not found.
19819
+
19820
+ Please check:
19821
+ 1. The rule key is correct (e.g., "typescript:S1234")
19822
+ 2. The rule is available on your SonarQube server
19823
+ 3. The rule's language plugin is installed`;
19824
+ },
19825
+ missingRuleKey() {
19826
+ return `Missing \`ruleKey\` parameter. Please provide a rule key to explain.
19827
+
19828
+ Example usage:
19829
+ \`\`\`
19830
+ sonarqube({ action: "rule", ruleKey: "typescript:S1234" })
19831
+ \`\`\`
19832
+
19833
+ Common rule prefixes:
19834
+ - \`typescript:\` - TypeScript rules
19835
+ - \`javascript:\` - JavaScript rules
19836
+ - \`java:\` - Java rules
19837
+ - \`python:\` - Python rules
19838
+ - \`common-\` - Language-agnostic rules`;
19839
+ }
19840
+ };
19613
19841
 
19842
+ // src/tools/base-handler.ts
19843
+ function createHandlerContext(config3, state, projectKey, directory) {
19844
+ return {
19845
+ config: config3,
19846
+ state,
19847
+ projectKey,
19848
+ directory,
19849
+ api: createSonarQubeAPI(config3, state)
19850
+ };
19851
+ }
19614
19852
  // src/tools/handlers/setup.ts
19615
19853
  init_bootstrap();
19616
19854
  init_logger();
19617
19855
  import { mkdir } from "node:fs/promises";
19618
- var logger7 = new Logger("sonarqube-handler-setup");
19619
- async function handleSetup(config2, directory, force = false) {
19620
- const result = await bootstrap({ config: config2, directory, force });
19856
+ var logger8 = new Logger("sonarqube-handler-setup");
19857
+ async function handleSetup(config3, directory, force = false) {
19858
+ const result = await bootstrap({ config: config3, directory, force });
19621
19859
  if (!result.success) {
19622
19860
  return `## SonarQube Setup Failed
19623
19861
 
@@ -19629,13 +19867,13 @@ ${result.message}`;
19629
19867
  if (!configExists) {
19630
19868
  await mkdir(sonarqubeDir, { recursive: true });
19631
19869
  const defaultConfig = {
19632
- level: config2.level || "enterprise",
19870
+ level: config3.level || "enterprise",
19633
19871
  autoAnalyze: true,
19634
- sources: config2.sources || "src",
19872
+ sources: config3.sources || "src",
19635
19873
  newCodeDefinition: "previous_version"
19636
19874
  };
19637
19875
  await Bun.write(configPath, JSON.stringify(defaultConfig, null, 2));
19638
- logger7.info("Created default config.json", { path: configPath });
19876
+ logger8.info("Created default config.json", { path: configPath });
19639
19877
  }
19640
19878
  const lines = [
19641
19879
  "## SonarQube Project Initialized",
@@ -19688,16 +19926,17 @@ async function filterLcovForCoverage(directory) {
19688
19926
  `));
19689
19927
  } catch {}
19690
19928
  }
19691
- async function handleAnalyze(config2, state, projectKey, args, directory) {
19692
- const projectName = config2.projectName ?? projectKey;
19929
+ async function handleAnalyze(ctx, args) {
19930
+ const { config: config3, state, projectKey, directory } = ctx;
19931
+ const projectName = config3.projectName ?? projectKey;
19693
19932
  await filterLcovForCoverage(directory);
19694
19933
  const coverageExclusions = COVERAGE_EXCLUDED_FILES.map((f) => `**/${f.split("/").pop()}`).join(",");
19695
- const result = await runAnalysis(config2, state, {
19934
+ const result = await runAnalysis(config3, state, {
19696
19935
  projectKey,
19697
19936
  projectName,
19698
- sources: config2.sources,
19699
- tests: config2.tests,
19700
- exclusions: config2.exclusions,
19937
+ sources: config3.sources,
19938
+ tests: config3.tests,
19939
+ exclusions: config3.exclusions,
19701
19940
  additionalProperties: {
19702
19941
  "sonar.coverage.exclusions": coverageExclusions,
19703
19942
  "sonar.javascript.lcov.reportPaths": "coverage/lcov.info"
@@ -19705,76 +19944,20 @@ async function handleAnalyze(config2, state, projectKey, args, directory) {
19705
19944
  }, directory);
19706
19945
  let output = formatAnalysisResult(result);
19707
19946
  if (args.fix && result.formattedIssues.length > 0) {
19708
- output += `
19709
-
19710
- ---
19711
-
19712
- ## Fix Suggestions
19713
-
19714
- `;
19715
- output += `Based on the issues found, here are recommended actions:
19716
-
19717
- `;
19718
- const blockers = result.formattedIssues.filter((i) => i.severity === "BLOCKER");
19719
- const critical = result.formattedIssues.filter((i) => i.severity === "CRITICAL");
19720
- if (blockers.length > 0) {
19721
- output += `### Immediate Fixes Required (Blockers)
19722
- `;
19723
- for (const issue2 of blockers.slice(0, 5)) {
19724
- output += `1. **${issue2.file}:${issue2.line ?? "?"}** - ${issue2.message}
19725
- `;
19726
- output += ` - Rule: \`${issue2.rule}\` - Review and fix this security/reliability issue
19727
-
19728
- `;
19729
- }
19730
- }
19731
- if (critical.length > 0) {
19732
- output += `### High Priority Fixes (Critical)
19733
- `;
19734
- for (const issue2 of critical.slice(0, 5)) {
19735
- output += `1. **${issue2.file}:${issue2.line ?? "?"}** - ${issue2.message}
19736
- `;
19737
- output += ` - Rule: \`${issue2.rule}\`
19738
-
19739
- `;
19740
- }
19741
- }
19947
+ output += formatFixSuggestions(result.formattedIssues);
19742
19948
  }
19743
19949
  return output;
19744
19950
  }
19745
19951
  // src/tools/handlers/issues.ts
19746
- init_logger();
19747
- var logger8 = new Logger("sonarqube-handler-issues");
19748
- function getSeveritiesFromLevel(level) {
19749
- switch (level.toLowerCase()) {
19750
- case "blocker":
19751
- return ["BLOCKER"];
19752
- case "critical":
19753
- return ["BLOCKER", "CRITICAL"];
19754
- case "major":
19755
- return ["BLOCKER", "CRITICAL", "MAJOR"];
19756
- case "minor":
19757
- return ["BLOCKER", "CRITICAL", "MAJOR", "MINOR"];
19758
- case "info":
19759
- case "all":
19760
- return ["BLOCKER", "CRITICAL", "MAJOR", "MINOR", "INFO"];
19761
- default:
19762
- return ["BLOCKER", "CRITICAL", "MAJOR", "MINOR", "INFO"];
19763
- }
19764
- }
19765
- async function handleIssues(config2, state, projectKey, args) {
19766
- const api2 = createSonarQubeAPI(config2, state);
19952
+ async function handleIssues(ctx, args) {
19953
+ const { api: api2, projectKey } = ctx;
19767
19954
  const severities = args.severity === "all" ? undefined : getSeveritiesFromLevel(args.severity ?? "all");
19768
19955
  const issues = await api2.issues.getFormattedIssues({
19769
19956
  projectKey,
19770
19957
  severities
19771
19958
  });
19772
19959
  if (issues.length === 0) {
19773
- return `## SonarQube Issues
19774
-
19775
- **Project:** \`${projectKey}\`
19776
-
19777
- No issues found matching your criteria. Code quality looks good!`;
19960
+ return formatSuccess("Issues", formatEmptyState("issues matching your criteria", projectKey, "Code quality looks good!"));
19778
19961
  }
19779
19962
  const qgStatus = await api2.qualityGate.getStatus(projectKey);
19780
19963
  const criticalIssues = issues.filter((i) => i.severity === "BLOCKER" || i.severity === "CRITICAL").slice(0, 10);
@@ -19788,7 +19971,7 @@ No issues found matching your criteria. Code quality looks good!`;
19788
19971
  message: i.message,
19789
19972
  component: `${projectKey}:${i.file}`,
19790
19973
  line: i.line
19791
- })), 5);
19974
+ })), API.SOURCE_CONTEXT_LINES);
19792
19975
  output += `
19793
19976
 
19794
19977
  ---
@@ -19805,31 +19988,25 @@ No issues found matching your criteria. Code quality looks good!`;
19805
19988
 
19806
19989
  `;
19807
19990
  }
19808
- } catch {
19809
- logger8.debug("Could not fetch source context for issues");
19810
- }
19991
+ } catch {}
19811
19992
  }
19812
19993
  return output;
19813
19994
  }
19814
- async function handleNewIssues(config2, state, projectKey, args) {
19815
- const api2 = createSonarQubeAPI(config2, state);
19995
+ async function handleNewIssues(ctx, args) {
19996
+ const { api: api2, projectKey } = ctx;
19816
19997
  const severities = args.severity === "all" ? undefined : getSeveritiesFromLevel(args.severity ?? "all");
19817
19998
  const response = await api2.issues.search({
19818
19999
  projectKey,
19819
20000
  severities,
19820
20001
  inNewCode: true,
19821
20002
  resolved: false,
19822
- pageSize: 500
20003
+ pageSize: API.DEFAULT_PAGE_SIZE
19823
20004
  });
19824
20005
  const issues = api2.issues.formatIssues(response.issues, response.components);
19825
20006
  if (issues.length === 0) {
19826
- return `## New Code Issues
20007
+ return formatSuccess("New Code Issues", formatEmptyState("issues in new code", projectKey, `Your recent changes are clean.
19827
20008
 
19828
- **Project:** \`${projectKey}\`
19829
-
19830
- No issues in new code! Your recent changes are clean.
19831
-
19832
- This means all code written during the current "new code period" passes quality standards.`;
20009
+ This means all code written during the current "new code period" passes quality standards.`));
19833
20010
  }
19834
20011
  let output = `## New Code Issues (Clean as You Code)
19835
20012
 
@@ -19845,8 +20022,7 @@ These issues were introduced in your recent changes and should be fixed before m
19845
20022
  bySeverity[issue2.severity] = [];
19846
20023
  bySeverity[issue2.severity].push(issue2);
19847
20024
  }
19848
- const severityOrder = ["BLOCKER", "CRITICAL", "MAJOR", "MINOR", "INFO"];
19849
- for (const sev of severityOrder) {
20025
+ for (const sev of SEVERITIES) {
19850
20026
  const sevIssues = bySeverity[sev];
19851
20027
  if (!sevIssues || sevIssues.length === 0)
19852
20028
  continue;
@@ -19869,15 +20045,11 @@ These issues were introduced in your recent changes and should be fixed before m
19869
20045
  }
19870
20046
  return output;
19871
20047
  }
19872
- async function handleWorstFiles(config2, state, projectKey) {
19873
- const api2 = createSonarQubeAPI(config2, state);
20048
+ async function handleWorstFiles(ctx) {
20049
+ const { api: api2, projectKey } = ctx;
19874
20050
  const worstFiles = await api2.components.getWorstFiles(projectKey, 20);
19875
20051
  if (worstFiles.length === 0) {
19876
- return `## Files with Issues
19877
-
19878
- **Project:** \`${projectKey}\`
19879
-
19880
- No files with issues found. Your codebase is clean!`;
20052
+ return formatSuccess("Files with Issues", formatEmptyState("files with issues", projectKey, "Your codebase is clean!"));
19881
20053
  }
19882
20054
  let output = api2.components.formatWorstFilesForAgent(worstFiles);
19883
20055
  output += `
@@ -19888,8 +20060,8 @@ Use \`sonarqube({ action: "issues" })\` to see detailed issues for specific file
19888
20060
  return output;
19889
20061
  }
19890
20062
  // src/tools/handlers/status.ts
19891
- async function handleStatus(config2, state, projectKey) {
19892
- const api2 = createSonarQubeAPI(config2, state);
20063
+ async function handleStatus(ctx) {
20064
+ const { api: api2, projectKey } = ctx;
19893
20065
  const [qgStatus, metrics] = await Promise.all([
19894
20066
  api2.qualityGate.getStatus(projectKey),
19895
20067
  api2.qualityGate.getQualityMetrics(projectKey)
@@ -19930,22 +20102,22 @@ async function handleStatus(config2, state, projectKey) {
19930
20102
  }
19931
20103
  return output;
19932
20104
  }
19933
- async function handleValidate(config2, state, projectKey) {
19934
- const api2 = createSonarQubeAPI(config2, state);
19935
- const result = await api2.qualityGate.validateEnterpriseQuality(projectKey, config2.level);
20105
+ async function handleValidate(ctx) {
20106
+ const { api: api2, projectKey, config: config3 } = ctx;
20107
+ const result = await api2.qualityGate.validateEnterpriseQuality(projectKey, config3.level);
19936
20108
  let output = api2.qualityGate.formatEnterpriseValidation(result);
19937
20109
  if (result.passed) {
19938
20110
  output += `
19939
20111
 
19940
20112
  ---
19941
20113
 
19942
- **Congratulations!** Project meets all ${config2.level} quality standards.`;
20114
+ **Congratulations!** Project meets all ${config3.level} quality standards.`;
19943
20115
  } else {
19944
20116
  output += `
19945
20117
 
19946
20118
  ---
19947
20119
 
19948
- **Action Required:** Project does not meet ${config2.level} quality standards.`;
20120
+ **Action Required:** Project does not meet ${config3.level} quality standards.`;
19949
20121
  output += `
19950
20122
 
19951
20123
  Please fix the failed checks before proceeding.`;
@@ -19953,15 +20125,11 @@ Please fix the failed checks before proceeding.`;
19953
20125
  return output;
19954
20126
  }
19955
20127
  // src/tools/handlers/security.ts
19956
- async function handleHotspots(config2, state, projectKey) {
19957
- const api2 = createSonarQubeAPI(config2, state);
20128
+ async function handleHotspots(ctx) {
20129
+ const { api: api2, projectKey } = ctx;
19958
20130
  const hotspots = await api2.issues.getSecurityHotspots(projectKey);
19959
20131
  if (hotspots.length === 0) {
19960
- return `## Security Hotspots
19961
-
19962
- **Project:** \`${projectKey}\`
19963
-
19964
- No security hotspots found. Your code has no security concerns requiring manual review.`;
20132
+ return formatSuccess("Security Hotspots", formatEmptyState("security hotspots", projectKey, "Your code has no security concerns requiring manual review."));
19965
20133
  }
19966
20134
  const toReview = hotspots.filter((h) => h.status === "TO_REVIEW");
19967
20135
  const reviewed = hotspots.filter((h) => h.status !== "TO_REVIEW");
@@ -20010,15 +20178,11 @@ For each hotspot, determine if it's:
20010
20178
  return output;
20011
20179
  }
20012
20180
  // src/tools/handlers/duplications.ts
20013
- async function handleDuplications(config2, state, projectKey) {
20014
- const api2 = createSonarQubeAPI(config2, state);
20181
+ async function handleDuplications(ctx) {
20182
+ const { api: api2, projectKey } = ctx;
20015
20183
  const duplications = await api2.duplications.getProjectDuplications(projectKey);
20016
20184
  if (duplications.length === 0) {
20017
- return `## Code Duplications
20018
-
20019
- **Project:** \`${projectKey}\`
20020
-
20021
- No code duplications found. Your code follows the DRY (Don't Repeat Yourself) principle!`;
20185
+ return formatSuccess("Code Duplications", formatEmptyState("code duplications", projectKey, "Your code follows the DRY (Don't Repeat Yourself) principle!"));
20022
20186
  }
20023
20187
  let output = `## Code Duplications
20024
20188
 
@@ -20030,35 +20194,18 @@ No code duplications found. Your code follows the DRY (Don't Repeat Yourself) pr
20030
20194
  return output;
20031
20195
  }
20032
20196
  // src/tools/handlers/rules.ts
20033
- async function handleRule(config2, state, ruleKey) {
20197
+ async function handleRule(ctx, ruleKey) {
20034
20198
  if (!ruleKey) {
20035
20199
  return `## SonarQube Error
20036
20200
 
20037
- Missing \`ruleKey\` parameter. Please provide a rule key to explain.
20038
-
20039
- Example usage:
20040
- \`\`\`
20041
- sonarqube({ action: "rule", ruleKey: "typescript:S1234" })
20042
- \`\`\`
20043
-
20044
- Common rule prefixes:
20045
- - \`typescript:\` - TypeScript rules
20046
- - \`javascript:\` - JavaScript rules
20047
- - \`java:\` - Java rules
20048
- - \`python:\` - Python rules
20049
- - \`common-\` - Language-agnostic rules`;
20201
+ ${ErrorMessages.missingRuleKey()}`;
20050
20202
  }
20051
- const api2 = createSonarQubeAPI(config2, state);
20203
+ const { api: api2 } = ctx;
20052
20204
  const rule = await api2.rules.getRule(ruleKey);
20053
20205
  if (!rule) {
20054
20206
  return `## SonarQube Rule Not Found
20055
20207
 
20056
- Rule \`${ruleKey}\` was not found.
20057
-
20058
- Please check:
20059
- 1. The rule key is correct (e.g., "typescript:S1234")
20060
- 2. The rule is available on your SonarQube server
20061
- 3. The rule's language plugin is installed`;
20208
+ ${ErrorMessages.ruleNotFound(ruleKey)}`;
20062
20209
  }
20063
20210
  return `## SonarQube Rule: ${rule.name}
20064
20211
 
@@ -20080,33 +20227,25 @@ Based on this rule, you should:
20080
20227
  3. Re-run analysis to verify the fix`;
20081
20228
  }
20082
20229
  // src/tools/handlers/history.ts
20083
- async function handleHistory(config2, state, projectKey, branch) {
20084
- const api2 = createSonarQubeAPI(config2, state);
20230
+ async function handleHistory(ctx, branch) {
20231
+ const { api: api2, projectKey } = ctx;
20085
20232
  const analyses = await api2.analyses.getAnalyses({ projectKey, branch, pageSize: 10 });
20086
20233
  if (analyses.length === 0) {
20087
- return `## Analysis History
20088
-
20089
- No analysis history found for project \`${projectKey}\`.
20090
-
20091
- This could mean:
20234
+ return formatSuccess("Analysis History", formatEmptyState("analysis history", projectKey, `This could mean:
20092
20235
  1. The project was recently created
20093
20236
  2. No analyses have been run yet
20094
20237
  3. The branch has no analysis history
20095
20238
 
20096
- Run \`sonarqube({ action: "analyze" })\` to create the first analysis.`;
20239
+ Run \`sonarqube({ action: "analyze" })\` to create the first analysis.`));
20097
20240
  }
20098
20241
  return api2.analyses.formatAnalysesForAgent(analyses);
20099
20242
  }
20100
- async function handleProfile(config2, state, projectKey) {
20101
- const api2 = createSonarQubeAPI(config2, state);
20243
+ async function handleProfile(ctx) {
20244
+ const { api: api2, projectKey } = ctx;
20102
20245
  const profiles = await api2.profiles.getProjectProfiles(projectKey);
20103
20246
  if (profiles.length === 0) {
20104
- return `## Quality Profiles
20105
-
20106
- No quality profiles found for project \`${projectKey}\`.
20107
-
20108
- This is unusual - every project should have at least one profile per language.
20109
- Please check the SonarQube server configuration.`;
20247
+ return formatSuccess("Quality Profiles", formatEmptyState("quality profiles", projectKey, `This is unusual - every project should have at least one profile per language.
20248
+ Please check the SonarQube server configuration.`));
20110
20249
  }
20111
20250
  let output = api2.profiles.formatProfilesForAgent(profiles, projectKey);
20112
20251
  output += `
@@ -20118,40 +20257,32 @@ The **Sonar way** profile is a recommended starting point maintained by SonarSou
20118
20257
  To view all available rules, visit your SonarQube server's Rules page.`;
20119
20258
  return output;
20120
20259
  }
20121
- async function handleBranches(config2, state, projectKey) {
20122
- const api2 = createSonarQubeAPI(config2, state);
20260
+ async function handleBranches(ctx) {
20261
+ const { api: api2, projectKey } = ctx;
20123
20262
  const branches = await api2.branches.getBranches(projectKey);
20124
20263
  if (branches.length === 0) {
20125
- return `## Project Branches
20126
-
20127
- No branch information available for project \`${projectKey}\`.
20128
-
20129
- This usually means:
20264
+ return formatSuccess("Project Branches", formatEmptyState("branch information", projectKey, `This usually means:
20130
20265
  1. Branch analysis is not enabled (Community Edition limitation)
20131
20266
  2. Only the main branch has been analyzed
20132
20267
 
20133
20268
  To analyze a specific branch, use:
20134
- \`sonarqube({ action: "analyze", branch: "feature-branch" })\``;
20269
+ \`sonarqube({ action: "analyze", branch: "feature-branch" })\``));
20135
20270
  }
20136
20271
  return api2.branches.formatBranchesForAgent(branches);
20137
20272
  }
20138
20273
  // src/tools/handlers/metrics.ts
20139
- async function handleMetrics(config2, state, projectKey, branch) {
20140
- const api2 = createSonarQubeAPI(config2, state);
20274
+ async function handleMetrics(ctx, branch) {
20275
+ const { api: api2, projectKey } = ctx;
20141
20276
  const measures = await api2.metrics.getMeasuresWithPeriod({
20142
20277
  componentKey: projectKey,
20143
20278
  branch
20144
20279
  });
20145
20280
  if (measures.length === 0) {
20146
- return `## Code Metrics
20147
-
20148
- No metrics available for project \`${projectKey}\`.
20149
-
20150
- This could mean:
20281
+ return formatSuccess("Code Metrics", formatEmptyState("metrics", projectKey, `This could mean:
20151
20282
  1. No analysis has been run yet
20152
20283
  2. The analysis is still processing
20153
20284
 
20154
- Run \`sonarqube({ action: "analyze" })\` to generate metrics.`;
20285
+ Run \`sonarqube({ action: "analyze" })\` to generate metrics.`));
20155
20286
  }
20156
20287
  let output = api2.metrics.formatMeasuresForAgent(measures);
20157
20288
  output += `
@@ -20183,14 +20314,13 @@ var SonarQubeToolArgsSchema = exports_external2.object({
20183
20314
  });
20184
20315
  async function executeSonarQubeTool(args, context) {
20185
20316
  const directory = context.directory ?? process.cwd();
20186
- const config2 = loadConfig(context.config);
20187
- if (!config2) {
20188
- return formatError2(`SonarQube configuration not found.
20189
-
20190
- Please ensure you have one of:
20191
- 1. Environment variables: SONAR_HOST_URL and SONAR_TOKEN
20192
- 2. A .sonarqube/config.json file in your project
20193
- 3. Plugin configuration in opencode.json
20317
+ const config3 = loadConfig(context.config);
20318
+ if (!config3) {
20319
+ return formatError2(ErrorMessages.configurationMissing("SonarQube configuration not found.", [
20320
+ "Set environment variables: SONAR_HOST_URL and SONAR_TOKEN",
20321
+ "Or create a .sonarqube/config.json file in your project",
20322
+ "Or add plugin configuration in opencode.json"
20323
+ ]) + `
20194
20324
 
20195
20325
  Required environment variables:
20196
20326
  - SONAR_HOST_URL (e.g., https://sonarqube.company.com)
@@ -20203,12 +20333,12 @@ Optional:
20203
20333
  logger9.info(`Executing SonarQube tool: ${args.action}`, { directory });
20204
20334
  try {
20205
20335
  if (args.action === "init" || args.action === "setup") {
20206
- return await handleSetup(config2, directory, args.force);
20336
+ return await handleSetup(config3, directory, args.force);
20207
20337
  }
20208
20338
  if (await needsBootstrap(directory)) {
20209
20339
  logger9.info("First run detected, running bootstrap");
20210
20340
  const { bootstrap: bootstrap2 } = await Promise.resolve().then(() => (init_bootstrap(), exports_bootstrap));
20211
- const setupResult = await bootstrap2({ config: config2, directory });
20341
+ const setupResult = await bootstrap2({ config: config3, directory });
20212
20342
  if (!setupResult.success) {
20213
20343
  return formatError2(`Setup Failed
20214
20344
 
@@ -20221,45 +20351,41 @@ ${setupResult.message}`);
20221
20351
  return formatError2(`Project not initialized. Run with action: "setup" first.`);
20222
20352
  }
20223
20353
  const projectKey = args.projectKey ?? state.projectKey;
20354
+ const ctx = createHandlerContext(config3, state, projectKey, directory);
20224
20355
  switch (args.action) {
20225
20356
  case "analyze":
20226
- return await handleAnalyze(config2, state, projectKey, args, directory);
20357
+ return await handleAnalyze(ctx, args);
20227
20358
  case "issues":
20228
- return await handleIssues(config2, state, projectKey, args);
20359
+ return await handleIssues(ctx, args);
20229
20360
  case "newissues":
20230
- return await handleNewIssues(config2, state, projectKey, args);
20361
+ return await handleNewIssues(ctx, args);
20231
20362
  case "status":
20232
- return await handleStatus(config2, state, projectKey);
20363
+ return await handleStatus(ctx);
20233
20364
  case "validate":
20234
- return await handleValidate(config2, state, projectKey);
20365
+ return await handleValidate(ctx);
20235
20366
  case "hotspots":
20236
- return await handleHotspots(config2, state, projectKey);
20367
+ return await handleHotspots(ctx);
20237
20368
  case "duplications":
20238
- return await handleDuplications(config2, state, projectKey);
20369
+ return await handleDuplications(ctx);
20239
20370
  case "rule":
20240
- return await handleRule(config2, state, args.ruleKey);
20371
+ return await handleRule(ctx, args.ruleKey);
20241
20372
  case "history":
20242
- return await handleHistory(config2, state, projectKey, args.branch);
20373
+ return await handleHistory(ctx, args.branch);
20243
20374
  case "profile":
20244
- return await handleProfile(config2, state, projectKey);
20375
+ return await handleProfile(ctx);
20245
20376
  case "branches":
20246
- return await handleBranches(config2, state, projectKey);
20377
+ return await handleBranches(ctx);
20247
20378
  case "metrics":
20248
- return await handleMetrics(config2, state, projectKey, args.branch);
20379
+ return await handleMetrics(ctx, args.branch);
20249
20380
  case "worstfiles":
20250
- return await handleWorstFiles(config2, state, projectKey);
20381
+ return await handleWorstFiles(ctx);
20251
20382
  default:
20252
20383
  return formatError2(`Unknown action: ${args.action}`);
20253
20384
  }
20254
20385
  } catch (error45) {
20255
20386
  const errorMessage = error45 instanceof Error ? error45.message : String(error45);
20256
20387
  logger9.error(`Tool execution failed: ${errorMessage}`);
20257
- return formatError2(`Action \`${args.action}\` failed: ${errorMessage}
20258
-
20259
- Please check:
20260
- 1. SonarQube server is reachable at ${config2.url}
20261
- 2. Credentials are valid
20262
- 3. Run with action: "setup" to initialize`);
20388
+ return formatError2(ErrorMessages.apiError(args.action, errorMessage, config3.url));
20263
20389
  }
20264
20390
  }
20265
20391
 
@@ -20320,8 +20446,8 @@ Environment Variables:
20320
20446
  SONAR_USER SonarQube username
20321
20447
  SONAR_PASSWORD SonarQube password
20322
20448
  `;
20323
- async function handleSetup2(config2, directory, force) {
20324
- const result = await bootstrap({ config: config2, directory, force });
20449
+ async function handleSetup2(config3, directory, force) {
20450
+ const result = await bootstrap({ config: config3, directory, force });
20325
20451
  return {
20326
20452
  success: result.success,
20327
20453
  output: result.success ? `Initializing SonarQube project...
@@ -20329,8 +20455,8 @@ ${result.message}` : result.message,
20329
20455
  exitCode: result.success ? 0 : 1
20330
20456
  };
20331
20457
  }
20332
- async function handleAnalyze2(config2, state, projectKey, directory) {
20333
- const result = await runAnalysis(config2, state, { projectKey }, directory);
20458
+ async function handleAnalyze2(config3, state, projectKey, directory) {
20459
+ const result = await runAnalysis(config3, state, { projectKey }, directory);
20334
20460
  return {
20335
20461
  success: true,
20336
20462
  output: `Running analysis for ${projectKey}...
@@ -20338,8 +20464,8 @@ ${formatAnalysisResult(result)}`,
20338
20464
  exitCode: 0
20339
20465
  };
20340
20466
  }
20341
- async function handleStatus2(config2, state, projectKey) {
20342
- const api2 = createSonarQubeAPI(config2, state);
20467
+ async function handleStatus2(config3, state, projectKey) {
20468
+ const api2 = createSonarQubeAPI(config3, state);
20343
20469
  const status = await api2.qualityGate.getStatus(projectKey);
20344
20470
  return {
20345
20471
  success: true,
@@ -20347,8 +20473,8 @@ async function handleStatus2(config2, state, projectKey) {
20347
20473
  exitCode: 0
20348
20474
  };
20349
20475
  }
20350
- async function handleIssues2(config2, state, projectKey) {
20351
- const api2 = createSonarQubeAPI(config2, state);
20476
+ async function handleIssues2(config3, state, projectKey) {
20477
+ const api2 = createSonarQubeAPI(config3, state);
20352
20478
  const issues = await api2.issues.getFormattedIssues({ projectKey });
20353
20479
  const lines = [`Found ${issues.length} issues`];
20354
20480
  for (const issue2 of issues.slice(0, 20)) {
@@ -20361,8 +20487,8 @@ async function handleIssues2(config2, state, projectKey) {
20361
20487
  exitCode: 0
20362
20488
  };
20363
20489
  }
20364
- async function handleHotspots2(config2, state, projectKey) {
20365
- const api2 = createSonarQubeAPI(config2, state);
20490
+ async function handleHotspots2(config3, state, projectKey) {
20491
+ const api2 = createSonarQubeAPI(config3, state);
20366
20492
  try {
20367
20493
  const hotspots = await api2.issues.getSecurityHotspots(projectKey);
20368
20494
  if (hotspots.length === 0) {
@@ -20402,8 +20528,8 @@ async function runCLI(args, directory = process.cwd()) {
20402
20528
  if (args.includes("--help") || args.includes("-h")) {
20403
20529
  return { success: true, output: CLI_HELP, exitCode: 0 };
20404
20530
  }
20405
- const config2 = loadConfig();
20406
- if (!config2) {
20531
+ const config3 = loadConfig();
20532
+ if (!config3) {
20407
20533
  return {
20408
20534
  success: false,
20409
20535
  output: `Error: SonarQube not configured
@@ -20412,10 +20538,10 @@ Set SONAR_HOST_URL, SONAR_USER, and SONAR_PASSWORD environment variables`,
20412
20538
  };
20413
20539
  }
20414
20540
  if (args.includes("--setup")) {
20415
- return handleSetup2(config2, directory, args.includes("--force"));
20541
+ return handleSetup2(config3, directory, args.includes("--force"));
20416
20542
  }
20417
20543
  if (await needsBootstrap(directory)) {
20418
- const result = await bootstrap({ config: config2, directory });
20544
+ const result = await bootstrap({ config: config3, directory });
20419
20545
  if (!result.success) {
20420
20546
  return {
20421
20547
  success: false,
@@ -20442,16 +20568,16 @@ Setup failed: ${result.message}`,
20442
20568
  };
20443
20569
  }
20444
20570
  if (args.includes("--analyze")) {
20445
- return handleAnalyze2(config2, state, projectKey, directory);
20571
+ return handleAnalyze2(config3, state, projectKey, directory);
20446
20572
  }
20447
20573
  if (args.includes("--status")) {
20448
- return handleStatus2(config2, state, projectKey);
20574
+ return handleStatus2(config3, state, projectKey);
20449
20575
  }
20450
20576
  if (args.includes("--issues")) {
20451
- return handleIssues2(config2, state, projectKey);
20577
+ return handleIssues2(config3, state, projectKey);
20452
20578
  }
20453
20579
  if (args.includes("--hotspots")) {
20454
- return handleHotspots2(config2, state, projectKey);
20580
+ return handleHotspots2(config3, state, projectKey);
20455
20581
  }
20456
20582
  return {
20457
20583
  success: true,
@@ -20556,8 +20682,8 @@ var SonarQubePlugin = async ({ client, directory, worktree }) => {
20556
20682
  try {
20557
20683
  const configFile = Bun.file(sonarConfigPath);
20558
20684
  if (await configFile.exists()) {
20559
- const config2 = await configFile.json();
20560
- pluginConfig = { sonarqube: config2 };
20685
+ const config3 = await configFile.json();
20686
+ pluginConfig = { sonarqube: config3 };
20561
20687
  safeLog(`Config loaded from ${sonarConfigPath}`);
20562
20688
  return;
20563
20689
  }
@@ -20586,14 +20712,14 @@ ${blockerNote}Use \`sonarqube({ action: "issues" })\` to see details or \`sonarq
20586
20712
  try {
20587
20713
  await loadPluginConfig();
20588
20714
  const sonarConfig = pluginConfig?.["sonarqube"];
20589
- const config2 = loadConfig(sonarConfig);
20590
- if (!config2 || config2.level === "off")
20715
+ const config3 = loadConfig(sonarConfig);
20716
+ if (!config3 || config3.level === "off")
20591
20717
  return;
20592
20718
  const dir = getDirectory();
20593
20719
  safeLog(`performInitialQualityCheck: dir=${dir}`);
20594
20720
  if (await needsBootstrap(dir)) {
20595
20721
  safeLog(`performInitialQualityCheck: running bootstrap for ${dir}`);
20596
- const result = await bootstrap({ config: config2, directory: dir });
20722
+ const result = await bootstrap({ config: config3, directory: dir });
20597
20723
  if (!result.success) {
20598
20724
  safeLog(`performInitialQualityCheck: bootstrap failed: ${result.message}`);
20599
20725
  return;
@@ -20603,7 +20729,7 @@ ${blockerNote}Use \`sonarqube({ action: "issues" })\` to see details or \`sonarq
20603
20729
  const state = await getProjectState(dir);
20604
20730
  if (!state?.projectKey)
20605
20731
  return;
20606
- const api2 = createSonarQubeAPI(config2, state);
20732
+ const api2 = createSonarQubeAPI(config3, state);
20607
20733
  const [qgStatus, counts] = await Promise.all([
20608
20734
  api2.qualityGate.getStatus(state.projectKey),
20609
20735
  api2.issues.getCounts(state.projectKey)
@@ -20693,8 +20819,8 @@ ${blockerNote}Use \`sonarqube({ action: "issues" })\` to see details or \`sonarq
20693
20819
  const handleSessionIdleEvent = async () => {
20694
20820
  await loadPluginConfig();
20695
20821
  const sonarConfig = pluginConfig?.["sonarqube"];
20696
- const config2 = loadConfig(sonarConfig);
20697
- if (!config2 || config2.level === "off" || !config2.autoAnalyze) {
20822
+ const config3 = loadConfig(sonarConfig);
20823
+ if (!config3 || config3.level === "off" || !config3.autoAnalyze) {
20698
20824
  return;
20699
20825
  }
20700
20826
  const editedFiles2 = getEditedFiles();
@@ -20711,7 +20837,7 @@ ${blockerNote}Use \`sonarqube({ action: "issues" })\` to see details or \`sonarq
20711
20837
  }
20712
20838
  const passed = message.includes("[PASS]");
20713
20839
  await showToast(passed ? "SonarQube: Quality Gate Passed" : "SonarQube: Issues Found", passed ? "success" : "error");
20714
- await injectAnalysisResults(message, config2, currentSessionId);
20840
+ await injectAnalysisResults(message, config3, currentSessionId);
20715
20841
  };
20716
20842
  const buildCompactionContext = () => {
20717
20843
  if (!lastAnalysisResult) {
@@ -20750,8 +20876,8 @@ ${statusNote}`;
20750
20876
  return;
20751
20877
  await loadPluginConfig();
20752
20878
  const sonarConfig = pluginConfig?.["sonarqube"];
20753
- const config2 = loadConfig(sonarConfig);
20754
- if (!config2 || config2.level === "off" || !config2.autoAnalyze)
20879
+ const config3 = loadConfig(sonarConfig);
20880
+ if (!config3 || config3.level === "off" || !config3.autoAnalyze)
20755
20881
  return;
20756
20882
  const outputData = output.output;
20757
20883
  const filePathRegex = /(?:wrote|edited|modified)\s+(?:file\s+)?['"]?([^\s'"]+)/i;
@@ -20776,8 +20902,8 @@ ${statusNote}`;
20776
20902
  const command = output.metadata?.command ?? "";
20777
20903
  await loadPluginConfig();
20778
20904
  const sonarConfig = pluginConfig?.["sonarqube"];
20779
- const config2 = loadConfig(sonarConfig);
20780
- if (!config2 || config2.level === "off")
20905
+ const config3 = loadConfig(sonarConfig);
20906
+ if (!config3 || config3.level === "off")
20781
20907
  return;
20782
20908
  await handleGitPullMerge(command, outputData);
20783
20909
  await handleGitPush(command, outputData);
@@ -20869,15 +20995,15 @@ Git operation completed with changes. Consider running:
20869
20995
  safeLog(` FINAL dir used: "${dir}"`);
20870
20996
  await loadPluginConfig();
20871
20997
  const sonarConfig = pluginConfig?.["sonarqube"];
20872
- const config2 = loadConfig(sonarConfig);
20873
- if (!config2 || config2.level === "off") {
20998
+ const config3 = loadConfig(sonarConfig);
20999
+ if (!config3 || config3.level === "off") {
20874
21000
  safeLog(` config level is off or null, returning early`);
20875
21001
  return;
20876
21002
  }
20877
21003
  const state = await getProjectState(dir);
20878
21004
  if (!state?.projectKey)
20879
21005
  return;
20880
- const api2 = createSonarQubeAPI(config2, state);
21006
+ const api2 = createSonarQubeAPI(config3, state);
20881
21007
  const [qgStatus, counts, newCodeResponse] = await Promise.all([
20882
21008
  api2.qualityGate.getStatus(state.projectKey),
20883
21009
  api2.issues.getCounts(state.projectKey),
@@ -20902,7 +21028,7 @@ ${newCodeIssues > 0 ? `**New Code Issues:** ${newCodeIssues} issues in recent ch
20902
21028
  ` : ""}
20903
21029
  ${counts.blocker > 0 ? `**IMPORTANT:** There are BLOCKER issues that must be fixed before shipping code.
20904
21030
  ` : ""}
20905
- ${config2.level === "enterprise" ? `This project follows enterprise-level quality standards (zero tolerance for issues).
21031
+ ${config3.level === "enterprise" ? `This project follows enterprise-level quality standards (zero tolerance for issues).
20906
21032
  ` : ""}
20907
21033
  **Recommended Actions:**
20908
21034
  - \`sonarqube({ action: "newissues" })\` - See issues in your recent changes (Clean as You Code)