opencode-sonarqube 1.2.54 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +769 -752
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -4103,81 +4103,6 @@ var init_types2 = __esm(() => {
4103
4103
  };
4104
4104
  });
4105
4105
 
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
4106
  // src/utils/logger.ts
4182
4107
  class Logger {
4183
4108
  service;
@@ -4252,6 +4177,10 @@ class Logger {
4252
4177
  });
4253
4178
  }
4254
4179
  }
4180
+ function createNoopLogger() {
4181
+ const noop = () => {};
4182
+ return { info: noop, warn: noop, error: noop, debug: noop };
4183
+ }
4255
4184
  var LOG_COLORS, RESET = "\x1B[0m", logger;
4256
4185
  var init_logger = __esm(() => {
4257
4186
  LOG_COLORS = {
@@ -4263,7 +4192,134 @@ var init_logger = __esm(() => {
4263
4192
  logger = new Logger("opencode-sonarqube");
4264
4193
  });
4265
4194
 
4266
- // src/scanner/config.ts
4195
+ // src/utils/config.ts
4196
+ function loadConfig(rawConfig) {
4197
+ configLogger.info(">>> loadConfig called", { hasRawConfig: !!rawConfig });
4198
+ const envUrl = process.env["SONAR_HOST_URL"];
4199
+ const envUser = process.env["SONAR_USER"];
4200
+ const envPassword = process.env["SONAR_PASSWORD"];
4201
+ configLogger.info("Environment variables", {
4202
+ hasEnvUrl: !!envUrl,
4203
+ hasEnvUser: !!envUser,
4204
+ hasEnvPassword: !!envPassword,
4205
+ envUrl: envUrl ? `${envUrl.substring(0, 20)}...` : undefined
4206
+ });
4207
+ const configToValidate = {
4208
+ ...DEFAULT_CONFIG,
4209
+ ...rawConfig,
4210
+ ...envUrl && { url: envUrl },
4211
+ ...envUser && { user: envUser },
4212
+ ...envPassword && { password: envPassword }
4213
+ };
4214
+ if (!configToValidate.url || !configToValidate.user || !configToValidate.password) {
4215
+ configLogger.warn("Missing required config", {
4216
+ hasUrl: !!configToValidate.url,
4217
+ hasUser: !!configToValidate.user,
4218
+ hasPassword: !!configToValidate.password
4219
+ });
4220
+ return null;
4221
+ }
4222
+ const result = SonarQubeConfigSchema.safeParse(configToValidate);
4223
+ if (!result.success) {
4224
+ configLogger.error("Config validation failed", { errors: result.error.format() });
4225
+ return null;
4226
+ }
4227
+ configLogger.info("<<< loadConfig success", { url: result.data.url, level: result.data.level });
4228
+ return result.data;
4229
+ }
4230
+ async function deriveProjectKey(directory) {
4231
+ try {
4232
+ const packageJsonPath = `${directory}/package.json`;
4233
+ const packageJson = await Bun.file(packageJsonPath).json();
4234
+ if (packageJson.name) {
4235
+ return packageJson.name;
4236
+ }
4237
+ } catch {}
4238
+ const dirName = directory.split("/").pop();
4239
+ if (dirName && dirName.length > 0) {
4240
+ return dirName;
4241
+ }
4242
+ const cwd = process.cwd();
4243
+ if (cwd && cwd !== "/") {
4244
+ const cwdName = cwd.split("/").pop();
4245
+ if (cwdName && cwdName.length > 0) {
4246
+ return cwdName;
4247
+ }
4248
+ }
4249
+ return `project-${Date.now()}`;
4250
+ }
4251
+ function sanitizeProjectKey(input) {
4252
+ return input.toLowerCase().replaceAll(/[^a-z0-9-_]/g, "-").replaceAll(/-+/g, "-").replaceAll(/(?:^-)|(?:-$)/g, "").slice(0, 400);
4253
+ }
4254
+ var configLogger, DEFAULT_CONFIG;
4255
+ var init_config = __esm(() => {
4256
+ init_types2();
4257
+ init_logger();
4258
+ configLogger = createNoopLogger();
4259
+ DEFAULT_CONFIG = {
4260
+ level: "enterprise",
4261
+ autoAnalyze: true,
4262
+ newCodeDefinition: "previous_version",
4263
+ sources: "src"
4264
+ };
4265
+ });
4266
+
4267
+ // src/scanner/config/patterns.ts
4268
+ function getDefaultExclusions(_detection) {
4269
+ const patterns = [
4270
+ "**/node_modules/**",
4271
+ "**/dist/**",
4272
+ "**/build/**",
4273
+ "**/.next/**",
4274
+ "**/coverage/**",
4275
+ "**/*.d.ts",
4276
+ "**/*.min.js",
4277
+ "**/*.min.css",
4278
+ "**/vendor/**",
4279
+ "**/__pycache__/**",
4280
+ "**/.venv/**",
4281
+ "**/venv/**",
4282
+ "**/target/**"
4283
+ ];
4284
+ return patterns.join(",");
4285
+ }
4286
+ function getTestPatterns(languages) {
4287
+ const patterns = [];
4288
+ if (languages.includes("typescript") || languages.includes("javascript")) {
4289
+ patterns.push("**/*.test.ts", "**/*.test.tsx", "**/*.test.js", "**/*.test.jsx", "**/*.spec.ts", "**/*.spec.tsx", "**/*.spec.js", "**/*.spec.jsx", "**/__tests__/**", "**/__mocks__/**");
4290
+ }
4291
+ if (languages.includes("python")) {
4292
+ patterns.push("**/test_*.py", "**/*_test.py", "**/tests/**");
4293
+ }
4294
+ if (languages.includes("java")) {
4295
+ patterns.push("**/*Test.java", "**/*Tests.java", "**/test/**");
4296
+ }
4297
+ if (languages.includes("go")) {
4298
+ patterns.push("**/*_test.go");
4299
+ }
4300
+ return patterns.join(",");
4301
+ }
4302
+ var SOURCE_DIRECTORY_PATTERNS;
4303
+ var init_patterns = __esm(() => {
4304
+ SOURCE_DIRECTORY_PATTERNS = [
4305
+ "apps",
4306
+ "packages",
4307
+ "modules",
4308
+ "services",
4309
+ "libs",
4310
+ "src",
4311
+ "source",
4312
+ "lib",
4313
+ "app",
4314
+ "cmd",
4315
+ "pkg",
4316
+ "internal",
4317
+ "main",
4318
+ "core"
4319
+ ];
4320
+ });
4321
+
4322
+ // src/scanner/config/detection.ts
4267
4323
  async function checkProjectFiles(directory) {
4268
4324
  const checks3 = await Promise.all([
4269
4325
  Bun.file(`${directory}/package.json`).exists(),
@@ -4380,23 +4436,6 @@ function detectLanguages(checks3) {
4380
4436
  }
4381
4437
  return languages.length > 0 ? languages : ["generic"];
4382
4438
  }
4383
- async function detectProjectType(directory) {
4384
- const checks3 = await checkProjectFiles(directory);
4385
- const languages = detectLanguages(checks3);
4386
- const result = { languages };
4387
- if (checks3.hasPackageJson) {
4388
- result.packageManager = detectPackageManager(checks3);
4389
- const packageInfo = await parsePackageJson(directory);
4390
- result.framework = packageInfo.framework;
4391
- result.testFramework = packageInfo.testFramework;
4392
- result.coverageReportPath = packageInfo.coverageReportPath;
4393
- }
4394
- logger2.info("Detected project type", {
4395
- languages: result.languages,
4396
- packageManager: result.packageManager
4397
- });
4398
- return result;
4399
- }
4400
4439
  function detectSourceDirectories(directory) {
4401
4440
  const existingDirs = [];
4402
4441
  for (const pattern of SOURCE_DIRECTORY_PATTERNS) {
@@ -4414,6 +4453,31 @@ function detectSourceDirectories(directory) {
4414
4453
  logger2.info("No common source directories found, scanning entire project");
4415
4454
  return ".";
4416
4455
  }
4456
+ async function detectProjectType(directory) {
4457
+ const checks3 = await checkProjectFiles(directory);
4458
+ const languages = detectLanguages(checks3);
4459
+ const result = { languages };
4460
+ if (checks3.hasPackageJson) {
4461
+ result.packageManager = detectPackageManager(checks3);
4462
+ const packageInfo = await parsePackageJson(directory);
4463
+ result.framework = packageInfo.framework;
4464
+ result.testFramework = packageInfo.testFramework;
4465
+ result.coverageReportPath = packageInfo.coverageReportPath;
4466
+ }
4467
+ logger2.info("Detected project type", {
4468
+ languages: result.languages,
4469
+ packageManager: result.packageManager
4470
+ });
4471
+ return result;
4472
+ }
4473
+ var logger2;
4474
+ var init_detection = __esm(() => {
4475
+ init_logger();
4476
+ init_patterns();
4477
+ logger2 = new Logger("scanner-config");
4478
+ });
4479
+
4480
+ // src/scanner/config/properties.ts
4417
4481
  async function generatePropertiesContent(options, config2, directory) {
4418
4482
  const dir = directory ?? process.cwd();
4419
4483
  const detection = await detectProjectType(dir);
@@ -4466,75 +4530,52 @@ async function generatePropertiesContent(options, config2, directory) {
4466
4530
  lines.push("", "# Analysis Settings", "sonar.sourceEncoding=UTF-8", "sonar.scm.provider=git", `sonar.cpd.exclusions=${getTestPatterns(detection.languages)}`, "sonar.coverage.exclusions=src/bootstrap/index.ts,src/index.ts,src/scanner/runner.ts,src/tools/handlers/setup.ts,src/cli.ts");
4467
4531
  if (options.additionalProperties) {
4468
4532
  lines.push("", "# Additional Properties");
4469
- for (const [key, value] of Object.entries(options.additionalProperties)) {
4470
- lines.push(`${key}=${value}`);
4471
- }
4472
- }
4473
- return lines.join(`
4474
- `);
4475
- }
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");
4533
+ for (const [key, value] of Object.entries(options.additionalProperties)) {
4534
+ lines.push(`${key}=${value}`);
4535
+ }
4507
4536
  }
4508
- return patterns.join(",");
4537
+ return lines.join(`
4538
+ `);
4509
4539
  }
4510
4540
  async function writePropertiesFile(options, config2, directory) {
4511
4541
  const dir = directory ?? process.cwd();
4512
4542
  const content = await generatePropertiesContent(options, config2, dir);
4513
4543
  const filePath = `${dir}/sonar-project.properties`;
4514
4544
  await Bun.write(filePath, content);
4515
- logger2.info(`Written sonar-project.properties to ${filePath}`);
4545
+ logger3.info(`Written sonar-project.properties to ${filePath}`);
4516
4546
  return filePath;
4517
4547
  }
4518
- var logger2, SOURCE_DIRECTORY_PATTERNS;
4548
+ var logger3;
4549
+ var init_properties = __esm(() => {
4550
+ init_logger();
4551
+ init_detection();
4552
+ init_patterns();
4553
+ logger3 = new Logger("scanner-config");
4554
+ });
4555
+
4556
+ // src/scanner/config/index.ts
4519
4557
  var init_config2 = __esm(() => {
4558
+ init_detection();
4559
+ init_properties();
4560
+ init_patterns();
4561
+ });
4562
+
4563
+ // src/scanner/config.ts
4564
+ var init_config3 = __esm(() => {
4565
+ init_config2();
4566
+ });
4567
+
4568
+ // src/api/base-api.ts
4569
+ class BaseAPI {
4570
+ client;
4571
+ logger;
4572
+ constructor(client, serviceName, logger4) {
4573
+ this.client = client;
4574
+ this.logger = logger4 ?? new Logger(serviceName);
4575
+ }
4576
+ }
4577
+ var init_base_api = __esm(() => {
4520
4578
  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
4579
  });
4539
4580
 
4540
4581
  // src/api/client.ts
@@ -4624,9 +4665,9 @@ function buildAuthHeader(auth) {
4624
4665
  class SonarQubeClient {
4625
4666
  baseUrl;
4626
4667
  auth;
4627
- constructor(config2, _logger) {
4628
- this.baseUrl = normalizeUrl(config2.url);
4629
- this.auth = config2.auth;
4668
+ constructor(config3, _logger) {
4669
+ this.baseUrl = normalizeUrl(config3.url);
4670
+ this.auth = config3.auth;
4630
4671
  }
4631
4672
  async request(endpoint, options = {}) {
4632
4673
  const { method = "GET", params, body } = options;
@@ -4708,119 +4749,117 @@ class SonarQubeClient {
4708
4749
  return this.request(endpoint, { method: "DELETE", params });
4709
4750
  }
4710
4751
  }
4711
- function createClientWithToken(url2, token, logger3) {
4712
- return new SonarQubeClient({ url: url2, auth: { token } }, logger3);
4752
+ function createClientWithToken(url2, token, logger4) {
4753
+ return new SonarQubeClient({ url: url2, auth: { token } }, logger4);
4713
4754
  }
4714
- function createClientWithCredentials(url2, user, password, logger3) {
4715
- return new SonarQubeClient({ url: url2, auth: { user, password } }, logger3);
4755
+ function createClientWithCredentials(url2, user, password, logger4) {
4756
+ return new SonarQubeClient({ url: url2, auth: { user, password } }, logger4);
4716
4757
  }
4717
4758
  var init_client = __esm(() => {
4718
4759
  init_types2();
4719
4760
  });
4720
4761
 
4721
4762
  // 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;
4763
+ var ProjectsAPI;
4764
+ var init_projects = __esm(() => {
4765
+ init_types2();
4766
+ init_base_api();
4767
+ ProjectsAPI = class ProjectsAPI extends BaseAPI {
4768
+ constructor(client, logger4) {
4769
+ super(client, "sonarqube-projects", logger4);
4770
+ }
4771
+ async create(options) {
4772
+ this.logger.info(`Creating project: ${options.projectKey}`);
4773
+ return this.client.post("/api/projects/create", {
4774
+ project: options.projectKey,
4775
+ name: options.name,
4776
+ mainBranch: options.mainBranch ?? "main",
4777
+ visibility: options.visibility ?? "private"
4778
+ });
4752
4779
  }
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;
4780
+ async search(options) {
4781
+ return this.client.get("/api/projects/search", {
4782
+ projects: options?.projects,
4783
+ q: options?.query,
4784
+ ps: options?.pageSize ?? 100,
4785
+ p: options?.page ?? 1
4786
+ });
4782
4787
  }
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);
4788
+ async exists(projectKey) {
4789
+ try {
4790
+ const response = await this.search({ projects: projectKey });
4791
+ return response.components.some((c) => c.key === projectKey);
4792
+ } catch {
4793
+ return false;
4795
4794
  }
4796
- return { created: false, project };
4797
4795
  }
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}`);
4796
+ async get(projectKey) {
4797
+ const response = await this.search({ projects: projectKey });
4798
+ return response.components.find((c) => c.key === projectKey) ?? null;
4799
+ }
4800
+ async delete(projectKey) {
4801
+ this.logger.warn(`Deleting project: ${projectKey}`);
4802
+ await this.client.post("/api/projects/delete", { project: projectKey });
4803
+ }
4804
+ async generateToken(options) {
4805
+ const tokenName = options.tokenName ?? `opencode-${options.projectKey}-${Date.now()}`;
4806
+ this.logger.info(`Generating token for project: ${options.projectKey}`);
4807
+ return this.client.post("/api/user_tokens/generate", {
4808
+ name: tokenName,
4809
+ type: "PROJECT_ANALYSIS_TOKEN",
4810
+ projectKey: options.projectKey,
4811
+ expirationDate: options.expirationDate
4812
+ });
4813
+ }
4814
+ async revokeToken(tokenName) {
4815
+ this.logger.info(`Revoking token: ${tokenName}`);
4816
+ await this.client.post("/api/user_tokens/revoke", { name: tokenName });
4817
+ }
4818
+ async setQualityGate(projectKey, gateName) {
4819
+ const gatesResponse = await this.client.get("/api/qualitygates/list");
4820
+ const gate = gatesResponse.qualitygates.find((g) => g.name === gateName);
4821
+ if (!gate) {
4822
+ this.logger.warn(`Quality gate '${gateName}' not found, using default`);
4823
+ return;
4808
4824
  }
4825
+ await this.client.post("/api/qualitygates/select", {
4826
+ projectKey,
4827
+ gateId: gate.id
4828
+ });
4829
+ this.logger.info(`Set quality gate '${gateName}' for project ${projectKey}`);
4809
4830
  }
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"
4831
+ async ensureExists(options) {
4832
+ const exists = await this.exists(options.projectKey);
4833
+ if (exists) {
4834
+ const project = await this.get(options.projectKey);
4835
+ if (!project) {
4836
+ throw ProjectNotFoundError(options.projectKey);
4837
+ }
4838
+ return { created: false, project };
4817
4839
  }
4818
- };
4819
- }
4820
- }
4821
- var init_projects = __esm(() => {
4822
- init_types2();
4823
- init_logger();
4840
+ const response = await this.create({
4841
+ projectKey: options.projectKey,
4842
+ name: options.name,
4843
+ mainBranch: options.mainBranch
4844
+ });
4845
+ if (options.qualityGate) {
4846
+ try {
4847
+ await this.setQualityGate(options.projectKey, options.qualityGate);
4848
+ } catch (error45) {
4849
+ this.logger.warn(`Failed to set quality gate: ${error45}`);
4850
+ }
4851
+ }
4852
+ return {
4853
+ created: true,
4854
+ project: {
4855
+ key: response.project.key,
4856
+ name: response.project.name,
4857
+ qualifier: response.project.qualifier,
4858
+ visibility: "private"
4859
+ }
4860
+ };
4861
+ }
4862
+ };
4824
4863
  });
4825
4864
 
4826
4865
  // src/utils/state.ts
@@ -4856,7 +4895,7 @@ async function loadProjectState(directory) {
4856
4895
  const state = ProjectStateSchema.parse(data);
4857
4896
  return state;
4858
4897
  } catch (error45) {
4859
- logger4.error("Failed to load project state", {
4898
+ logger5.error("Failed to load project state", {
4860
4899
  error: error45 instanceof Error ? error45.message : String(error45),
4861
4900
  statePath
4862
4901
  });
@@ -4872,12 +4911,12 @@ async function saveProjectState(directory, state) {
4872
4911
  }
4873
4912
  const content = JSON.stringify(state, null, 2);
4874
4913
  await Bun.write(statePath, content);
4875
- logger4.info("Saved project state", { projectKey: state.projectKey });
4914
+ logger5.info("Saved project state", { projectKey: state.projectKey });
4876
4915
  }
4877
4916
  async function updateProjectState(directory, updates) {
4878
4917
  const existing = await loadProjectState(directory);
4879
4918
  if (!existing) {
4880
- logger4.warn("Cannot update non-existent state");
4919
+ logger5.warn("Cannot update non-existent state");
4881
4920
  return null;
4882
4921
  }
4883
4922
  const updated = {
@@ -4897,7 +4936,7 @@ async function deleteProjectState(directory) {
4897
4936
  await Bun.write(statePath, "");
4898
4937
  const { unlink } = await import("node:fs/promises");
4899
4938
  await unlink(statePath);
4900
- logger4.info("Deleted project state", { directory });
4939
+ logger5.info("Deleted project state", { directory });
4901
4940
  return true;
4902
4941
  } catch {
4903
4942
  return false;
@@ -4922,7 +4961,7 @@ async function ensureGitignore(directory) {
4922
4961
  await Bun.write(gitignorePath, `# SonarQube local state
4923
4962
  ${entry}
4924
4963
  `);
4925
- logger4.info("Created .gitignore with SonarQube exclusion");
4964
+ logger5.info("Created .gitignore with SonarQube exclusion");
4926
4965
  return;
4927
4966
  }
4928
4967
  const content = await Bun.file(gitignorePath).text();
@@ -4939,17 +4978,13 @@ ${entry}
4939
4978
  ${entry}
4940
4979
  `;
4941
4980
  await Bun.write(gitignorePath, newContent);
4942
- logger4.info("Added SonarQube exclusion to .gitignore");
4981
+ logger5.info("Added SonarQube exclusion to .gitignore");
4943
4982
  }
4944
- var logger4, STATE_DIR = ".sonarqube", STATE_FILE = "project.json";
4983
+ var logger5, STATE_DIR = ".sonarqube", STATE_FILE = "project.json";
4945
4984
  var init_state = __esm(() => {
4946
4985
  init_types2();
4947
- logger4 = {
4948
- info: (_msg, _extra) => {},
4949
- warn: (_msg, _extra) => {},
4950
- error: (_msg, _extra) => {},
4951
- debug: (_msg, _extra) => {}
4952
- };
4986
+ init_logger();
4987
+ logger5 = createNoopLogger();
4953
4988
  });
4954
4989
 
4955
4990
  // src/bootstrap/index.ts
@@ -4963,16 +4998,16 @@ __export(exports_bootstrap, {
4963
4998
  bootstrap: () => bootstrap
4964
4999
  });
4965
5000
  async function needsBootstrap(directory) {
4966
- logger5.info(">>> needsBootstrap called", { directory });
5001
+ logger6.info(">>> needsBootstrap called", { directory });
4967
5002
  const hasState = await hasProjectState(directory);
4968
- logger5.info("hasProjectState result", { hasState, directory });
5003
+ logger6.info("hasProjectState result", { hasState, directory });
4969
5004
  if (!hasState) {
4970
- logger5.info("<<< needsBootstrap: true (no state file)");
5005
+ logger6.info("<<< needsBootstrap: true (no state file)");
4971
5006
  return true;
4972
5007
  }
4973
5008
  const state = await loadProjectState(directory);
4974
5009
  const needsBoot = !state?.setupComplete;
4975
- logger5.info("<<< needsBootstrap result", {
5010
+ logger6.info("<<< needsBootstrap result", {
4976
5011
  needsBoot,
4977
5012
  hasState: !!state,
4978
5013
  setupComplete: state?.setupComplete,
@@ -4983,12 +5018,12 @@ async function needsBootstrap(directory) {
4983
5018
  async function getProjectState(directory) {
4984
5019
  return loadProjectState(directory);
4985
5020
  }
4986
- async function createAuthenticatedClient(config2, directory) {
5021
+ async function createAuthenticatedClient(config3, directory) {
4987
5022
  const state = await loadProjectState(directory);
4988
5023
  if (!state?.projectToken) {
4989
5024
  return null;
4990
5025
  }
4991
- return createClientWithToken(config2.url, state.projectToken);
5026
+ return createClientWithToken(config3.url, state.projectToken);
4992
5027
  }
4993
5028
  function generateTokenName(projectKey) {
4994
5029
  const timestamp = Date.now();
@@ -5022,23 +5057,23 @@ async function ensurePluginsEnabled(client) {
5022
5057
  key: "sonar.plugins.downloadOnlyRequired",
5023
5058
  value: "false"
5024
5059
  });
5025
- logger5.info("Enabled full plugin loading on server (sonar.plugins.downloadOnlyRequired=false)");
5060
+ logger6.info("Enabled full plugin loading on server (sonar.plugins.downloadOnlyRequired=false)");
5026
5061
  }
5027
5062
  } catch {
5028
- logger5.warn("Could not check/set plugin loading setting");
5063
+ logger6.warn("Could not check/set plugin loading setting");
5029
5064
  }
5030
5065
  }
5031
5066
  async function generateAnalysisToken(client, tokenName, projectKey) {
5032
5067
  try {
5033
5068
  return await client.post("/api/user_tokens/generate", { name: tokenName, type: "PROJECT_ANALYSIS_TOKEN", projectKey });
5034
5069
  } catch {
5035
- logger5.warn("PROJECT_ANALYSIS_TOKEN not available, using GLOBAL_ANALYSIS_TOKEN");
5070
+ logger6.warn("PROJECT_ANALYSIS_TOKEN not available, using GLOBAL_ANALYSIS_TOKEN");
5036
5071
  return await client.post("/api/user_tokens/generate", { name: tokenName, type: "GLOBAL_ANALYSIS_TOKEN" });
5037
5072
  }
5038
5073
  }
5039
5074
  async function bootstrap(options) {
5040
- let { config: config2, directory, force = false } = options;
5041
- logger5.info("Starting bootstrap", { directory, projectKey: config2.projectKey || "(auto)" });
5075
+ let { config: config3, directory, force = false } = options;
5076
+ logger6.info("Starting bootstrap", { directory, projectKey: config3.projectKey || "(auto)" });
5042
5077
  if (!isValidDirectory(directory)) {
5043
5078
  const resolved = resolveDirectoryFromImportMeta();
5044
5079
  if (resolved) {
@@ -5059,7 +5094,7 @@ async function bootstrap(options) {
5059
5094
  if (!force) {
5060
5095
  const existingState = await loadProjectState(directory);
5061
5096
  if (existingState?.setupComplete) {
5062
- logger5.info("Project already bootstrapped", { projectKey: existingState.projectKey });
5097
+ logger6.info("Project already bootstrapped", { projectKey: existingState.projectKey });
5063
5098
  return {
5064
5099
  success: true,
5065
5100
  projectKey: existingState.projectKey,
@@ -5071,32 +5106,32 @@ async function bootstrap(options) {
5071
5106
  };
5072
5107
  }
5073
5108
  }
5074
- const adminClient = createClientWithCredentials(config2.url, config2.user, config2.password);
5109
+ const adminClient = createClientWithCredentials(config3.url, config3.user, config3.password);
5075
5110
  const health = await adminClient.healthCheck();
5076
5111
  if (!health.healthy) {
5077
5112
  throw SetupError(`Cannot connect to SonarQube: ${health.error}`);
5078
5113
  }
5079
- logger5.info("Connected to SonarQube", { version: health.version });
5114
+ logger6.info("Connected to SonarQube", { version: health.version });
5080
5115
  await ensurePluginsEnabled(adminClient);
5081
5116
  const detection = await detectProjectType(directory);
5082
- const projectKey = config2.projectKey || sanitizeProjectKey(await deriveProjectKey(directory));
5083
- const projectName = config2.projectName || projectKey;
5117
+ const projectKey = config3.projectKey || sanitizeProjectKey(await deriveProjectKey(directory));
5118
+ const projectName = config3.projectName || projectKey;
5084
5119
  const projectsApi = new ProjectsAPI(adminClient);
5085
5120
  const exists = await projectsApi.exists(projectKey);
5086
5121
  const isNewProject = !exists;
5087
5122
  if (isNewProject) {
5088
5123
  await projectsApi.create({ projectKey, name: projectName, visibility: "private" });
5089
- logger5.info("Created new project", { projectKey });
5124
+ logger6.info("Created new project", { projectKey });
5090
5125
  }
5091
5126
  const tokenName = generateTokenName(projectKey);
5092
5127
  const tokenResponse = await generateAnalysisToken(adminClient, tokenName, projectKey);
5093
- const qualityGateName = config2.qualityGate ?? QUALITY_GATE_MAPPING[config2.level];
5128
+ const qualityGateName = config3.qualityGate ?? QUALITY_GATE_MAPPING[config3.level];
5094
5129
  try {
5095
5130
  await setProjectQualityGate(adminClient, projectKey, qualityGateName);
5096
5131
  } catch (error45) {
5097
- logger5.warn("Failed to set quality gate", { error: String(error45) });
5132
+ logger6.warn("Failed to set quality gate", { error: String(error45) });
5098
5133
  }
5099
- await configureProjectSettings(adminClient, projectKey, detection.languages, config2);
5134
+ await configureProjectSettings(adminClient, projectKey, detection.languages, config3);
5100
5135
  const state = createInitialState({
5101
5136
  projectKey,
5102
5137
  projectToken: tokenResponse.token,
@@ -5106,7 +5141,7 @@ async function bootstrap(options) {
5106
5141
  });
5107
5142
  await saveProjectState(directory, state);
5108
5143
  await ensureGitignore(directory);
5109
- logger5.info("Bootstrap complete", { projectKey, isNewProject });
5144
+ logger6.info("Bootstrap complete", { projectKey, isNewProject });
5110
5145
  return {
5111
5146
  success: true,
5112
5147
  projectKey,
@@ -5121,7 +5156,7 @@ async function setProjectQualityGate(client, projectKey, qualityGateName) {
5121
5156
  const gatesResponse = await client.get("/api/qualitygates/list");
5122
5157
  const gate = gatesResponse.qualitygates.find((g) => g.name === qualityGateName);
5123
5158
  if (!gate) {
5124
- logger5.warn(`Quality gate '${qualityGateName}' not found, using default`);
5159
+ logger6.warn(`Quality gate '${qualityGateName}' not found, using default`);
5125
5160
  return;
5126
5161
  }
5127
5162
  await client.post("/api/qualitygates/select", {
@@ -5129,20 +5164,20 @@ async function setProjectQualityGate(client, projectKey, qualityGateName) {
5129
5164
  gateId: gate.id
5130
5165
  });
5131
5166
  }
5132
- async function configureProjectSettings(client, projectKey, languages, config2) {
5167
+ async function configureProjectSettings(client, projectKey, languages, config3) {
5133
5168
  const settings = [];
5134
5169
  settings.push({ key: "sonar.sourceEncoding", value: "UTF-8" });
5135
5170
  if (languages.includes("typescript") || languages.includes("javascript")) {
5136
5171
  settings.push({ key: "sonar.javascript.node.maxspace", value: "4096" });
5137
5172
  }
5138
- if (config2.sources) {
5139
- settings.push({ key: "sonar.sources", value: config2.sources });
5173
+ if (config3.sources) {
5174
+ settings.push({ key: "sonar.sources", value: config3.sources });
5140
5175
  }
5141
- if (config2.tests) {
5142
- settings.push({ key: "sonar.tests", value: config2.tests });
5176
+ if (config3.tests) {
5177
+ settings.push({ key: "sonar.tests", value: config3.tests });
5143
5178
  }
5144
- if (config2.exclusions) {
5145
- settings.push({ key: "sonar.exclusions", value: config2.exclusions });
5179
+ if (config3.exclusions) {
5180
+ settings.push({ key: "sonar.exclusions", value: config3.exclusions });
5146
5181
  }
5147
5182
  for (const setting of settings) {
5148
5183
  try {
@@ -5164,21 +5199,18 @@ async function disconnect(directory) {
5164
5199
  }
5165
5200
  const { deleteProjectState: deleteProjectState2 } = await Promise.resolve().then(() => (init_state(), exports_state));
5166
5201
  await deleteProjectState2(directory);
5167
- logger5.info("Disconnected from SonarQube", { projectKey: state.projectKey });
5202
+ logger6.info("Disconnected from SonarQube", { projectKey: state.projectKey });
5168
5203
  }
5169
- var logger5, QUALITY_GATE_MAPPING;
5204
+ var logger6, QUALITY_GATE_MAPPING;
5170
5205
  var init_bootstrap = __esm(() => {
5171
5206
  init_types2();
5172
5207
  init_client();
5173
5208
  init_projects();
5174
- init_config2();
5209
+ init_config3();
5175
5210
  init_state();
5176
5211
  init_config();
5177
- logger5 = {
5178
- info: (_msg, _extra) => {},
5179
- warn: (_msg, _extra) => {},
5180
- error: (_msg, _extra) => {}
5181
- };
5212
+ init_logger();
5213
+ logger6 = createNoopLogger();
5182
5214
  QUALITY_GATE_MAPPING = {
5183
5215
  enterprise: "Sonar way",
5184
5216
  standard: "Sonar way",
@@ -17514,21 +17546,19 @@ init_config();
17514
17546
  init_config();
17515
17547
 
17516
17548
  // src/scanner/index.ts
17517
- init_config2();
17549
+ init_config3();
17518
17550
 
17519
17551
  // src/api/index.ts
17552
+ init_base_api();
17520
17553
  init_client();
17521
17554
  init_projects();
17522
17555
 
17523
17556
  // src/api/issues.ts
17524
- init_logger();
17557
+ init_base_api();
17525
17558
 
17526
- class IssuesAPI {
17527
- client;
17528
- logger;
17529
- constructor(client, logger3) {
17530
- this.client = client;
17531
- this.logger = logger3 ?? new Logger("sonarqube-issues");
17559
+ class IssuesAPI extends BaseAPI {
17560
+ constructor(client, logger4) {
17561
+ super(client, "sonarqube-issues", logger4);
17532
17562
  }
17533
17563
  async search(options) {
17534
17564
  this.logger.debug(`Searching issues for project: ${options.projectKey}`);
@@ -17670,9 +17700,11 @@ class IssuesAPI {
17670
17700
  return hotspots.filter((h) => h.status === "TO_REVIEW");
17671
17701
  }
17672
17702
  }
17673
- // src/api/quality-gate.ts
17703
+ // src/api/quality-gate/api.ts
17674
17704
  init_types2();
17675
- init_logger();
17705
+ init_base_api();
17706
+
17707
+ // src/api/quality-gate/metrics.ts
17676
17708
  var METRIC_KEYS = {
17677
17709
  coverage: "coverage",
17678
17710
  newCoverage: "new_coverage",
@@ -17701,13 +17733,170 @@ var METRIC_KEYS = {
17701
17733
  complexity: "complexity",
17702
17734
  cognitiveComplexity: "cognitive_complexity"
17703
17735
  };
17736
+ var METRIC_NAMES = {
17737
+ coverage: "Coverage",
17738
+ new_coverage: "Coverage on New Code",
17739
+ duplicated_lines_density: "Duplicated Lines (%)",
17740
+ bugs: "Bugs",
17741
+ new_bugs: "New Bugs",
17742
+ vulnerabilities: "Vulnerabilities",
17743
+ new_vulnerabilities: "New Vulnerabilities",
17744
+ code_smells: "Code Smells",
17745
+ new_code_smells: "New Code Smells",
17746
+ security_hotspots: "Security Hotspots",
17747
+ reliability_rating: "Reliability Rating",
17748
+ security_rating: "Security Rating",
17749
+ sqale_rating: "Maintainability Rating",
17750
+ security_review_rating: "Security Review Rating"
17751
+ };
17752
+ var COMPARATOR_SYMBOLS = {
17753
+ GT: ">",
17754
+ LT: "<",
17755
+ EQ: "=",
17756
+ NE: "!="
17757
+ };
17758
+ function formatMetricName(metricKey) {
17759
+ return METRIC_NAMES[metricKey] ?? metricKey;
17760
+ }
17761
+ function formatComparator(comparator) {
17762
+ return COMPARATOR_SYMBOLS[comparator] ?? comparator;
17763
+ }
17764
+ function getRatingLetter(value) {
17765
+ if (!value)
17766
+ return "?";
17767
+ const ratings = ["A", "B", "C", "D", "E"];
17768
+ const index = Number.parseInt(value, 10) - 1;
17769
+ return ratings[index] ?? "?";
17770
+ }
17704
17771
 
17705
- class QualityGateAPI {
17706
- client;
17707
- logger;
17708
- constructor(client, logger3) {
17709
- this.client = client;
17710
- this.logger = logger3 ?? new Logger("sonarqube-quality-gate");
17772
+ // src/api/quality-gate/enterprise-validation.ts
17773
+ init_types2();
17774
+ function runEnterpriseChecks(metrics, thresholds) {
17775
+ const coverage = metrics.coverage ?? 0;
17776
+ const duplications = metrics.duplications ?? 0;
17777
+ const reliabilityNum = ratingToNumber(metrics.reliabilityRating);
17778
+ const securityNum = ratingToNumber(metrics.securityRating);
17779
+ const maintainabilityNum = ratingToNumber(metrics.maintainabilityRating);
17780
+ const expectedReliability = numberToRating(thresholds.minReliabilityRating);
17781
+ const expectedSecurity = numberToRating(thresholds.minSecurityRating);
17782
+ const expectedMaintainability = numberToRating(thresholds.minMaintainabilityRating);
17783
+ return [
17784
+ {
17785
+ name: "Coverage",
17786
+ passed: coverage >= thresholds.minCoverage,
17787
+ actual: `${coverage.toFixed(1)}%`,
17788
+ expected: `>= ${thresholds.minCoverage}%`,
17789
+ message: coverage >= thresholds.minCoverage ? `Coverage ${coverage.toFixed(1)}% meets threshold` : `Coverage ${coverage.toFixed(1)}% is below ${thresholds.minCoverage}% threshold`
17790
+ },
17791
+ {
17792
+ name: "Duplications",
17793
+ passed: duplications <= thresholds.maxDuplications,
17794
+ actual: `${duplications.toFixed(1)}%`,
17795
+ expected: `<= ${thresholds.maxDuplications}%`,
17796
+ message: duplications <= thresholds.maxDuplications ? `Duplications ${duplications.toFixed(1)}% meets threshold` : `Duplications ${duplications.toFixed(1)}% exceeds ${thresholds.maxDuplications}% threshold`
17797
+ },
17798
+ {
17799
+ name: "Bugs",
17800
+ passed: metrics.bugs <= thresholds.maxBugs,
17801
+ actual: metrics.bugs,
17802
+ expected: `<= ${thresholds.maxBugs}`,
17803
+ message: metrics.bugs <= thresholds.maxBugs ? `${metrics.bugs} bugs meets threshold` : `${metrics.bugs} bugs exceeds ${thresholds.maxBugs} threshold`
17804
+ },
17805
+ {
17806
+ name: "Vulnerabilities",
17807
+ passed: metrics.vulnerabilities <= thresholds.maxVulnerabilities,
17808
+ actual: metrics.vulnerabilities,
17809
+ expected: `<= ${thresholds.maxVulnerabilities}`,
17810
+ message: metrics.vulnerabilities <= thresholds.maxVulnerabilities ? `${metrics.vulnerabilities} vulnerabilities meets threshold` : `${metrics.vulnerabilities} vulnerabilities exceeds ${thresholds.maxVulnerabilities} threshold`
17811
+ },
17812
+ {
17813
+ name: "Code Smells",
17814
+ passed: metrics.codeSmells <= thresholds.maxCodeSmells,
17815
+ actual: metrics.codeSmells,
17816
+ expected: `<= ${thresholds.maxCodeSmells}`,
17817
+ message: metrics.codeSmells <= thresholds.maxCodeSmells ? `${metrics.codeSmells} code smells meets threshold` : `${metrics.codeSmells} code smells exceeds ${thresholds.maxCodeSmells} threshold`
17818
+ },
17819
+ {
17820
+ name: "Security Hotspots",
17821
+ passed: metrics.securityHotspots <= thresholds.maxSecurityHotspots,
17822
+ actual: metrics.securityHotspots,
17823
+ expected: `<= ${thresholds.maxSecurityHotspots}`,
17824
+ message: metrics.securityHotspots <= thresholds.maxSecurityHotspots ? `${metrics.securityHotspots} security hotspots meets threshold` : `${metrics.securityHotspots} security hotspots exceeds ${thresholds.maxSecurityHotspots} threshold`
17825
+ },
17826
+ {
17827
+ name: "Reliability Rating",
17828
+ passed: reliabilityNum <= thresholds.minReliabilityRating,
17829
+ actual: metrics.reliabilityRating,
17830
+ expected: `>= ${expectedReliability}`,
17831
+ message: reliabilityNum <= thresholds.minReliabilityRating ? `Reliability rating ${metrics.reliabilityRating} meets threshold` : `Reliability rating ${metrics.reliabilityRating} is below ${expectedReliability} threshold`
17832
+ },
17833
+ {
17834
+ name: "Security Rating",
17835
+ passed: securityNum <= thresholds.minSecurityRating,
17836
+ actual: metrics.securityRating,
17837
+ expected: `>= ${expectedSecurity}`,
17838
+ message: securityNum <= thresholds.minSecurityRating ? `Security rating ${metrics.securityRating} meets threshold` : `Security rating ${metrics.securityRating} is below ${expectedSecurity} threshold`
17839
+ },
17840
+ {
17841
+ name: "Maintainability Rating",
17842
+ passed: maintainabilityNum <= thresholds.minMaintainabilityRating,
17843
+ actual: metrics.maintainabilityRating,
17844
+ expected: `>= ${expectedMaintainability}`,
17845
+ message: maintainabilityNum <= thresholds.minMaintainabilityRating ? `Maintainability rating ${metrics.maintainabilityRating} meets threshold` : `Maintainability rating ${metrics.maintainabilityRating} is below ${expectedMaintainability} threshold`
17846
+ }
17847
+ ];
17848
+ }
17849
+ function formatEnterpriseValidation(result) {
17850
+ const statusIcon = result.passed ? "[PASS]" : "[FAIL]";
17851
+ const checkRows = result.checks.map((check2) => {
17852
+ const status = check2.passed ? "PASS" : "FAIL";
17853
+ return `| ${check2.name} | ${status} | ${check2.actual} | ${check2.expected} |`;
17854
+ });
17855
+ const metricsRows = [
17856
+ `| Coverage | ${result.metrics.coverage?.toFixed(1) ?? "N/A"}% |`,
17857
+ `| Duplications | ${result.metrics.duplications?.toFixed(1) ?? "N/A"}% |`,
17858
+ `| Bugs | ${result.metrics.bugs} |`,
17859
+ `| Vulnerabilities | ${result.metrics.vulnerabilities} |`,
17860
+ `| Code Smells | ${result.metrics.codeSmells} |`,
17861
+ `| Security Hotspots | ${result.metrics.securityHotspots} |`,
17862
+ `| Reliability Rating | ${result.metrics.reliabilityRating} |`,
17863
+ `| Security Rating | ${result.metrics.securityRating} |`,
17864
+ `| Maintainability Rating | ${result.metrics.maintainabilityRating} |`,
17865
+ `| Lines of Code | ${result.metrics.linesOfCode.toLocaleString()} |`
17866
+ ];
17867
+ const failedSection = result.passed ? [] : [
17868
+ "",
17869
+ "### Failed Checks",
17870
+ "",
17871
+ ...result.checks.filter((c) => !c.passed).map((check2) => `- **${check2.name}**: ${check2.message}`)
17872
+ ];
17873
+ return [
17874
+ `## Enterprise Quality Validation ${statusIcon}`,
17875
+ "",
17876
+ `**Project:** \`${result.projectKey}\``,
17877
+ `**Level:** ${result.level}`,
17878
+ `**Result:** ${result.summary}`,
17879
+ "",
17880
+ "### Quality Checks",
17881
+ "",
17882
+ "| Check | Status | Actual | Expected |",
17883
+ "|-------|--------|--------|----------|",
17884
+ ...checkRows,
17885
+ "",
17886
+ "### Current Metrics",
17887
+ "",
17888
+ "| Metric | Value |",
17889
+ "|--------|-------|",
17890
+ ...metricsRows,
17891
+ ...failedSection
17892
+ ].join(`
17893
+ `);
17894
+ }
17895
+
17896
+ // src/api/quality-gate/api.ts
17897
+ class QualityGateAPI extends BaseAPI {
17898
+ constructor(client, logger4) {
17899
+ super(client, "sonarqube-quality-gate", logger4);
17711
17900
  }
17712
17901
  async getStatus(projectKey, branch) {
17713
17902
  this.logger.debug(`Getting quality gate status for: ${projectKey}`);
@@ -17752,14 +17941,6 @@ class QualityGateAPI {
17752
17941
  const value = getValue(metric);
17753
17942
  return value ? Number.parseFloat(value) : 0;
17754
17943
  };
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
17944
  return {
17764
17945
  coverage: getValue(METRIC_KEYS.coverage) ? Number.parseFloat(getValue(METRIC_KEYS.coverage)) : undefined,
17765
17946
  duplications: getValue(METRIC_KEYS.duplicatedLinesDensity) ? Number.parseFloat(getValue(METRIC_KEYS.duplicatedLinesDensity)) : undefined,
@@ -17767,9 +17948,9 @@ class QualityGateAPI {
17767
17948
  vulnerabilities: getNumericValue(METRIC_KEYS.vulnerabilities),
17768
17949
  codeSmells: getNumericValue(METRIC_KEYS.codeSmells),
17769
17950
  securityHotspots: getNumericValue(METRIC_KEYS.securityHotspots),
17770
- reliabilityRating: getRating(METRIC_KEYS.reliabilityRating),
17771
- securityRating: getRating(METRIC_KEYS.securityRating),
17772
- maintainabilityRating: getRating(METRIC_KEYS.sqaleRating),
17951
+ reliabilityRating: getRatingLetter(getValue(METRIC_KEYS.reliabilityRating)),
17952
+ securityRating: getRatingLetter(getValue(METRIC_KEYS.securityRating)),
17953
+ maintainabilityRating: getRatingLetter(getValue(METRIC_KEYS.sqaleRating)),
17773
17954
  linesOfCode: getNumericValue(METRIC_KEYS.ncloc)
17774
17955
  };
17775
17956
  }
@@ -17800,40 +17981,12 @@ class QualityGateAPI {
17800
17981
  `);
17801
17982
  }
17802
17983
  formatCondition(condition) {
17803
- const metric = this.formatMetricName(condition.metricKey);
17984
+ const metric = formatMetricName(condition.metricKey);
17804
17985
  const actual = condition.actualValue ?? "N/A";
17805
17986
  const threshold = condition.errorThreshold ?? "N/A";
17806
- const comparator = this.formatComparator(condition.comparator);
17987
+ const comparator = formatComparator(condition.comparator);
17807
17988
  return `- **${metric}**: ${actual} (threshold: ${comparator} ${threshold})`;
17808
17989
  }
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
17990
  formatStatusEmoji(status) {
17838
17991
  switch (status) {
17839
17992
  case "OK":
@@ -17872,7 +18025,7 @@ class QualityGateAPI {
17872
18025
  };
17873
18026
  }
17874
18027
  const metrics = await this.getQualityMetrics(projectKey);
17875
- const checks3 = this.runEnterpriseChecks(metrics, thresholds);
18028
+ const checks3 = runEnterpriseChecks(metrics, thresholds);
17876
18029
  const passed = checks3.every((check2) => check2.passed);
17877
18030
  const failedCount = checks3.filter((c) => !c.passed).length;
17878
18031
  const summary = passed ? `All ${checks3.length} enterprise quality checks passed` : `${failedCount} of ${checks3.length} enterprise quality checks failed`;
@@ -17886,138 +18039,17 @@ class QualityGateAPI {
17886
18039
  timestamp: new Date().toISOString()
17887
18040
  };
17888
18041
  }
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
18042
  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
- `);
18043
+ return formatEnterpriseValidation(result);
18009
18044
  }
18010
18045
  }
18011
18046
  // src/api/rules.ts
18012
- init_logger();
18047
+ init_base_api();
18013
18048
 
18014
- class RulesAPI {
18015
- client;
18016
- logger;
18049
+ class RulesAPI extends BaseAPI {
18017
18050
  cache = new Map;
18018
- constructor(client, logger3) {
18019
- this.client = client;
18020
- this.logger = logger3 ?? new Logger("sonarqube-rules");
18051
+ constructor(client, logger4) {
18052
+ super(client, "sonarqube-rules", logger4);
18021
18053
  }
18022
18054
  parseRuleResponse(rule) {
18023
18055
  let rawDescription = rule.mdDesc ?? rule.htmlDesc ?? "";
@@ -18114,14 +18146,11 @@ class RulesAPI {
18114
18146
  }
18115
18147
  }
18116
18148
  // src/api/sources.ts
18117
- init_logger();
18149
+ init_base_api();
18118
18150
 
18119
- class SourcesAPI {
18120
- client;
18121
- logger;
18122
- constructor(client, logger3) {
18123
- this.client = client;
18124
- this.logger = logger3 ?? new Logger("sonarqube-sources");
18151
+ class SourcesAPI extends BaseAPI {
18152
+ constructor(client, logger4) {
18153
+ super(client, "sonarqube-sources", logger4);
18125
18154
  }
18126
18155
  async getSourceLines(componentKey, from, to) {
18127
18156
  this.logger.debug(`Fetching source lines: ${componentKey} [${from ?? 1}-${to ?? "end"}]`);
@@ -18254,14 +18283,11 @@ class SourcesAPI {
18254
18283
  }
18255
18284
  }
18256
18285
  // src/api/duplications.ts
18257
- init_logger();
18286
+ init_base_api();
18258
18287
 
18259
- class DuplicationsAPI {
18260
- client;
18261
- logger;
18262
- constructor(client, logger3) {
18263
- this.client = client;
18264
- this.logger = logger3 ?? new Logger("sonarqube-duplications");
18288
+ class DuplicationsAPI extends BaseAPI {
18289
+ constructor(client, logger4) {
18290
+ super(client, "sonarqube-duplications", logger4);
18265
18291
  }
18266
18292
  async getDuplications(componentKey) {
18267
18293
  this.logger.debug(`Fetching duplications for: ${componentKey}`);
@@ -18364,15 +18390,12 @@ class DuplicationsAPI {
18364
18390
  }
18365
18391
  }
18366
18392
  // src/api/ce.ts
18367
- init_logger();
18393
+ init_base_api();
18368
18394
  var debugLog = (_msg) => {};
18369
18395
 
18370
- class ComputeEngineAPI {
18371
- client;
18372
- logger;
18373
- constructor(client, logger3) {
18374
- this.client = client;
18375
- this.logger = logger3 ?? new Logger("sonarqube-ce");
18396
+ class ComputeEngineAPI extends BaseAPI {
18397
+ constructor(client, logger4) {
18398
+ super(client, "sonarqube-ce", logger4);
18376
18399
  }
18377
18400
  async getTask(taskId) {
18378
18401
  this.logger.debug(`Getting task status: ${taskId}`);
@@ -18516,14 +18539,11 @@ class ComputeEngineAPI {
18516
18539
  }
18517
18540
  }
18518
18541
  // src/api/analyses.ts
18519
- init_logger();
18542
+ init_base_api();
18520
18543
 
18521
- class ProjectAnalysesAPI {
18522
- client;
18523
- logger;
18524
- constructor(client, logger3) {
18525
- this.client = client;
18526
- this.logger = logger3 ?? new Logger("sonarqube-analyses");
18544
+ class ProjectAnalysesAPI extends BaseAPI {
18545
+ constructor(client, logger4) {
18546
+ super(client, "sonarqube-analyses", logger4);
18527
18547
  }
18528
18548
  async getAnalyses(options) {
18529
18549
  this.logger.info(`>>> getAnalyses called`, {
@@ -18586,14 +18606,11 @@ class ProjectAnalysesAPI {
18586
18606
  }
18587
18607
  }
18588
18608
  // src/api/profiles.ts
18589
- init_logger();
18609
+ init_base_api();
18590
18610
 
18591
- class QualityProfilesAPI {
18592
- client;
18593
- logger;
18594
- constructor(client, logger3) {
18595
- this.client = client;
18596
- this.logger = logger3 ?? new Logger("sonarqube-profiles");
18611
+ class QualityProfilesAPI extends BaseAPI {
18612
+ constructor(client, logger4) {
18613
+ super(client, "sonarqube-profiles", logger4);
18597
18614
  }
18598
18615
  async getProjectProfiles(projectKey) {
18599
18616
  this.logger.info(`>>> getProjectProfiles called`, { projectKey, projectKeyLength: projectKey?.length });
@@ -18680,14 +18697,11 @@ class QualityProfilesAPI {
18680
18697
  }
18681
18698
  }
18682
18699
  // src/api/branches.ts
18683
- init_logger();
18700
+ init_base_api();
18684
18701
 
18685
- class BranchesAPI {
18686
- client;
18687
- logger;
18688
- constructor(client, logger3) {
18689
- this.client = client;
18690
- this.logger = logger3 ?? new Logger("sonarqube-branches");
18702
+ class BranchesAPI extends BaseAPI {
18703
+ constructor(client, logger4) {
18704
+ super(client, "sonarqube-branches", logger4);
18691
18705
  }
18692
18706
  async getBranches(projectKey) {
18693
18707
  this.logger.info(`>>> getBranches called`, { projectKey, projectKeyLength: projectKey?.length });
@@ -18804,7 +18818,7 @@ class BranchesAPI {
18804
18818
  }
18805
18819
  }
18806
18820
  // src/api/metrics.ts
18807
- init_logger();
18821
+ init_base_api();
18808
18822
  var COMMON_METRICS = [
18809
18823
  "ncloc",
18810
18824
  "lines",
@@ -18834,12 +18848,9 @@ var COMMON_METRICS = [
18834
18848
  "sqale_debt_ratio"
18835
18849
  ];
18836
18850
 
18837
- class MetricsAPI {
18838
- client;
18839
- logger;
18840
- constructor(client, logger3) {
18841
- this.client = client;
18842
- this.logger = logger3 ?? new Logger("sonarqube-metrics");
18851
+ class MetricsAPI extends BaseAPI {
18852
+ constructor(client, logger4) {
18853
+ super(client, "sonarqube-metrics", logger4);
18843
18854
  }
18844
18855
  async getMetricDefinitions() {
18845
18856
  this.logger.debug("Getting metric definitions");
@@ -18964,14 +18975,11 @@ class MetricsAPI {
18964
18975
  }
18965
18976
  }
18966
18977
  // src/api/components.ts
18967
- init_logger();
18978
+ init_base_api();
18968
18979
 
18969
- class ComponentsAPI {
18970
- client;
18971
- logger;
18972
- constructor(client, logger3) {
18973
- this.client = client;
18974
- this.logger = logger3 ?? new Logger("sonarqube-components");
18980
+ class ComponentsAPI extends BaseAPI {
18981
+ constructor(client, logger4) {
18982
+ super(client, "sonarqube-components", logger4);
18975
18983
  }
18976
18984
  componentToSummary(comp, includeLanguage) {
18977
18985
  const measures = new Map(comp.measures?.map((m) => [m.metric, Number.parseInt(m.value, 10)]) ?? []);
@@ -19099,8 +19107,8 @@ class SonarQubeAPI {
19099
19107
  metrics;
19100
19108
  components;
19101
19109
  logger;
19102
- constructor(client, logger3) {
19103
- this.logger = logger3 ?? new Logger("sonarqube-api");
19110
+ constructor(client, logger4) {
19111
+ this.logger = logger4 ?? new Logger("sonarqube-api");
19104
19112
  this.client = client;
19105
19113
  this.projects = new ProjectsAPI(client, this.logger.child("projects"));
19106
19114
  this.issues = new IssuesAPI(client, this.logger.child("issues"));
@@ -19136,16 +19144,16 @@ class SonarQubeAPI {
19136
19144
  };
19137
19145
  }
19138
19146
  }
19139
- function createSonarQubeAPIWithToken(url2, token, logger3) {
19140
- const client = createClientWithToken(url2, token, logger3?.child("client"));
19141
- return new SonarQubeAPI(client, logger3);
19147
+ function createSonarQubeAPIWithToken(url2, token, logger4) {
19148
+ const client = createClientWithToken(url2, token, logger4?.child("client"));
19149
+ return new SonarQubeAPI(client, logger4);
19142
19150
  }
19143
- function createSonarQubeAPI(config2, state, logger3) {
19144
- return createSonarQubeAPIWithToken(config2.url, state.projectToken, logger3);
19151
+ function createSonarQubeAPI(config3, state, logger4) {
19152
+ return createSonarQubeAPIWithToken(config3.url, state.projectToken, logger4);
19145
19153
  }
19146
19154
 
19147
19155
  // src/scanner/runner.ts
19148
- init_config2();
19156
+ init_config3();
19149
19157
 
19150
19158
  // src/utils/severity.ts
19151
19159
  var SEVERITY_PRIORITY = {
@@ -19249,7 +19257,7 @@ function formatIssueBlock(issue2, _number2) {
19249
19257
 
19250
19258
  // src/scanner/runner.ts
19251
19259
  init_logger();
19252
- var logger3 = new Logger("scanner-runner");
19260
+ var logger4 = new Logger("scanner-runner");
19253
19261
  function extractTaskId(output) {
19254
19262
  const taskIdRegex = /api\/ce\/task\?id=([\w-]+)/;
19255
19263
  const taskIdMatch = taskIdRegex.exec(output);
@@ -19263,11 +19271,11 @@ function extractTaskId(output) {
19263
19271
  function sanitizeArgValue(value) {
19264
19272
  return value.replaceAll(/[;&|`$(){}[\]<>\\'"!\n\r]/g, "");
19265
19273
  }
19266
- async function runScanner(config2, state, options, directory) {
19274
+ async function runScanner(config3, state, options, directory) {
19267
19275
  const dir = directory ?? process.cwd();
19268
- logger3.info(`Starting SonarQube analysis for ${options.projectKey}`);
19276
+ logger4.info(`Starting SonarQube analysis for ${options.projectKey}`);
19269
19277
  const args = [
19270
- `-Dsonar.host.url=${sanitizeArgValue(config2.url)}`,
19278
+ `-Dsonar.host.url=${sanitizeArgValue(config3.url)}`,
19271
19279
  `-Dsonar.token=${sanitizeArgValue(state.projectToken)}`,
19272
19280
  `-Dsonar.projectKey=${sanitizeArgValue(options.projectKey)}`
19273
19281
  ];
@@ -19293,7 +19301,7 @@ async function runScanner(config2, state, options, directory) {
19293
19301
  args.push(`-D${sanitizeArgValue(key)}=${sanitizeArgValue(value)}`);
19294
19302
  }
19295
19303
  }
19296
- logger3.debug("Scanner arguments", { args: args.map((a) => a.includes("token") ? "-Dsonar.token=***" : a) });
19304
+ logger4.debug("Scanner arguments", { args: args.map((a) => a.includes("token") ? "-Dsonar.token=***" : a) });
19297
19305
  try {
19298
19306
  const proc = Bun.spawn(["npx", "sonarqube-scanner", ...args], {
19299
19307
  cwd: dir,
@@ -19311,7 +19319,7 @@ async function runScanner(config2, state, options, directory) {
19311
19319
  ${stderr}` : "");
19312
19320
  if (exitCode === 0) {
19313
19321
  const taskId = extractTaskId(output);
19314
- logger3.info("Scanner completed successfully", { taskId });
19322
+ logger4.info("Scanner completed successfully", { taskId });
19315
19323
  return {
19316
19324
  success: true,
19317
19325
  output,
@@ -19319,7 +19327,7 @@ ${stderr}` : "");
19319
19327
  taskId
19320
19328
  };
19321
19329
  } else {
19322
- logger3.error(`Scanner failed with exit code ${exitCode}`);
19330
+ logger4.error(`Scanner failed with exit code ${exitCode}`);
19323
19331
  return {
19324
19332
  success: false,
19325
19333
  output,
@@ -19329,7 +19337,7 @@ ${stderr}` : "");
19329
19337
  }
19330
19338
  } catch (error45) {
19331
19339
  const errorMessage = error45 instanceof Error ? error45.message : String(error45);
19332
- logger3.error(`Scanner execution failed: ${errorMessage}`);
19340
+ logger4.error(`Scanner execution failed: ${errorMessage}`);
19333
19341
  return {
19334
19342
  success: false,
19335
19343
  output: "",
@@ -19338,25 +19346,25 @@ ${stderr}` : "");
19338
19346
  };
19339
19347
  }
19340
19348
  }
19341
- async function runAnalysis(config2, state, options, directory) {
19349
+ async function runAnalysis(config3, state, options, directory) {
19342
19350
  const dir = directory ?? process.cwd();
19343
- const api2 = createSonarQubeAPI(config2, state);
19351
+ const api2 = createSonarQubeAPI(config3, state);
19344
19352
  const projectName = options.projectName ?? options.projectKey;
19345
19353
  const propertiesPath = `${dir}/sonar-project.properties`;
19346
19354
  const propertiesExist = await Bun.file(propertiesPath).exists();
19347
19355
  if (propertiesExist) {
19348
- logger3.info("Using existing sonar-project.properties (CI/CD compatible)");
19356
+ logger4.info("Using existing sonar-project.properties (CI/CD compatible)");
19349
19357
  } else {
19350
19358
  await writePropertiesFile({
19351
19359
  projectKey: options.projectKey,
19352
19360
  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");
19361
+ sources: options.sources ?? config3.sources,
19362
+ tests: options.tests ?? config3.tests,
19363
+ exclusions: options.exclusions ?? config3.exclusions
19364
+ }, config3, dir);
19365
+ logger4.info("Created sonar-project.properties");
19358
19366
  }
19359
- const scannerResult = await runScanner(config2, state, {
19367
+ const scannerResult = await runScanner(config3, state, {
19360
19368
  projectKey: options.projectKey,
19361
19369
  projectName,
19362
19370
  sources: options.sources,
@@ -19367,22 +19375,22 @@ async function runAnalysis(config2, state, options, directory) {
19367
19375
  branch: options.branch
19368
19376
  }, dir);
19369
19377
  if (scannerResult.success && scannerResult.taskId) {
19370
- logger3.info(`Waiting for analysis task ${scannerResult.taskId} to complete...`);
19378
+ logger4.info(`Waiting for analysis task ${scannerResult.taskId} to complete...`);
19371
19379
  try {
19372
19380
  const task = await api2.ce.waitForTask(scannerResult.taskId);
19373
19381
  if (task) {
19374
- logger3.info(`Analysis task completed: ${task.status}`);
19382
+ logger4.info(`Analysis task completed: ${task.status}`);
19375
19383
  if (task.status === "FAILED") {
19376
- logger3.error(`Analysis failed: ${task.errorMessage ?? "Unknown error"}`);
19384
+ logger4.error(`Analysis failed: ${task.errorMessage ?? "Unknown error"}`);
19377
19385
  }
19378
19386
  } else {
19379
- logger3.warn("Task not found - continuing with available results");
19387
+ logger4.warn("Task not found - continuing with available results");
19380
19388
  }
19381
19389
  } catch (waitError) {
19382
- logger3.debug("Could not wait for task via CE API", { waitError });
19390
+ logger4.debug("Could not wait for task via CE API", { waitError });
19383
19391
  }
19384
19392
  } else if (scannerResult.success) {
19385
- logger3.debug("No task ID in scanner output, waiting briefly...");
19393
+ logger4.debug("No task ID in scanner output, waiting briefly...");
19386
19394
  await Bun.sleep(3000);
19387
19395
  }
19388
19396
  try {
@@ -19408,7 +19416,7 @@ async function runAnalysis(config2, state, options, directory) {
19408
19416
  securityHotspots: fullMetrics.securityHotspots
19409
19417
  };
19410
19418
  } catch (metricsError) {
19411
- logger3.debug("Metrics not available yet, using issue counts only", { metricsError });
19419
+ logger4.debug("Metrics not available yet, using issue counts only", { metricsError });
19412
19420
  }
19413
19421
  return {
19414
19422
  success: scannerResult.success,
@@ -19419,7 +19427,7 @@ async function runAnalysis(config2, state, options, directory) {
19419
19427
  timestamp: new Date().toISOString()
19420
19428
  };
19421
19429
  } catch (error45) {
19422
- logger3.warn("Could not fetch results from API, returning partial result", { error: error45 });
19430
+ logger4.warn("Could not fetch results from API, returning partial result", { error: error45 });
19423
19431
  return {
19424
19432
  success: scannerResult.success,
19425
19433
  qualityGateStatus: "NONE",
@@ -19443,60 +19451,60 @@ function formatAnalysisResult(result) {
19443
19451
  // src/hooks/index.ts
19444
19452
  init_bootstrap();
19445
19453
  init_logger();
19446
- var logger6 = new Logger("sonarqube-hooks");
19454
+ var logger7 = new Logger("sonarqube-hooks");
19447
19455
  var editedFiles = new Set;
19448
19456
  var lastAnalysisTime = 0;
19449
19457
  var ANALYSIS_COOLDOWN_MS = 5 * 60 * 1000;
19450
19458
  var bootstrapInProgress = false;
19451
- function isAnalysisEnabled(config2) {
19452
- return config2 !== null && config2.level !== "off" && config2.autoAnalyze;
19459
+ function isAnalysisEnabled(config3) {
19460
+ return config3 !== null && config3.level !== "off" && config3.autoAnalyze;
19453
19461
  }
19454
19462
  function isInCooldown() {
19455
19463
  const now = Date.now();
19456
19464
  return now - lastAnalysisTime < ANALYSIS_COOLDOWN_MS;
19457
19465
  }
19458
- async function handleBootstrap(config2, directory) {
19466
+ async function handleBootstrap(config3, directory) {
19459
19467
  if (bootstrapInProgress) {
19460
- logger6.debug("Bootstrap already in progress");
19468
+ logger7.debug("Bootstrap already in progress");
19461
19469
  return;
19462
19470
  }
19463
- logger6.info("First run detected - initializing SonarQube integration");
19471
+ logger7.info("First run detected - initializing SonarQube integration");
19464
19472
  bootstrapInProgress = true;
19465
19473
  try {
19466
- const result = await bootstrap({ config: config2, directory });
19474
+ const result = await bootstrap({ config: config3, directory });
19467
19475
  bootstrapInProgress = false;
19468
19476
  return result.success ? formatBootstrapMessage(result) : `**SonarQube Setup Failed**
19469
19477
 
19470
19478
  ${result.message}`;
19471
19479
  } catch (error45) {
19472
19480
  bootstrapInProgress = false;
19473
- logger6.error("Bootstrap failed", { error: String(error45) });
19481
+ logger7.error("Bootstrap failed", { error: String(error45) });
19474
19482
  const errorMsg = error45 instanceof Error ? error45.message : String(error45);
19475
19483
  return `**SonarQube Setup Error**
19476
19484
 
19477
19485
  ${errorMsg}`;
19478
19486
  }
19479
19487
  }
19480
- async function performAnalysis(config2, state, directory) {
19488
+ async function performAnalysis(config3, state, directory) {
19481
19489
  const projectKey = state.projectKey;
19482
- const projectName = config2.projectName ?? projectKey;
19483
- const result = await runAnalysis(config2, state, {
19490
+ const projectName = config3.projectName ?? projectKey;
19491
+ const result = await runAnalysis(config3, state, {
19484
19492
  projectKey,
19485
19493
  projectName,
19486
- sources: config2.sources,
19487
- tests: config2.tests
19494
+ sources: config3.sources,
19495
+ tests: config3.tests
19488
19496
  }, directory);
19489
19497
  editedFiles.clear();
19490
- return formatAnalysisOutput(result, config2);
19498
+ return formatAnalysisOutput(result, config3);
19491
19499
  }
19492
- function formatAnalysisOutput(result, config2) {
19500
+ function formatAnalysisOutput(result, config3) {
19493
19501
  const hasIssues = result.formattedIssues.length > 0;
19494
19502
  const gateFailed = result.qualityGateStatus !== "OK";
19495
19503
  if (!hasIssues && !gateFailed) {
19496
19504
  return;
19497
19505
  }
19498
19506
  let message = formatAnalysisResult(result);
19499
- message += formatActionPrompt(result, config2);
19507
+ message += formatActionPrompt(result, config3);
19500
19508
  return message;
19501
19509
  }
19502
19510
  function formatActionPrompt(result, _config) {
@@ -19514,33 +19522,33 @@ function formatActionPrompt(result, _config) {
19514
19522
  function createIdleHook(getConfig, getDirectory) {
19515
19523
  return async function handleSessionIdle() {
19516
19524
  const rawConfig = getConfig()?.["sonarqube"];
19517
- const config2 = loadConfig(rawConfig);
19518
- if (!isAnalysisEnabled(config2)) {
19525
+ const config3 = loadConfig(rawConfig);
19526
+ if (!isAnalysisEnabled(config3)) {
19519
19527
  return;
19520
19528
  }
19521
19529
  const directory = getDirectory();
19522
19530
  if (await needsBootstrap(directory)) {
19523
- return handleBootstrap(config2, directory);
19531
+ return handleBootstrap(config3, directory);
19524
19532
  }
19525
19533
  if (isInCooldown()) {
19526
- logger6.debug("Skipping auto-analysis (cooldown)");
19534
+ logger7.debug("Skipping auto-analysis (cooldown)");
19527
19535
  return;
19528
19536
  }
19529
19537
  if (editedFiles.size === 0) {
19530
- logger6.debug("Skipping auto-analysis (no edited files)");
19538
+ logger7.debug("Skipping auto-analysis (no edited files)");
19531
19539
  return;
19532
19540
  }
19533
- logger6.info("Session idle - triggering auto-analysis");
19541
+ logger7.info("Session idle - triggering auto-analysis");
19534
19542
  lastAnalysisTime = Date.now();
19535
19543
  try {
19536
19544
  const state = await getProjectState(directory);
19537
19545
  if (!state) {
19538
- logger6.warn("No project state found, cannot run analysis");
19546
+ logger7.warn("No project state found, cannot run analysis");
19539
19547
  return;
19540
19548
  }
19541
- return await performAnalysis(config2, state, directory);
19549
+ return await performAnalysis(config3, state, directory);
19542
19550
  } catch (error45) {
19543
- logger6.error(`Auto-analysis failed: ${error45}`);
19551
+ logger7.error(`Auto-analysis failed: ${error45}`);
19544
19552
  return;
19545
19553
  }
19546
19554
  };
@@ -19581,7 +19589,7 @@ function createFileEditedHook() {
19581
19589
  const { filePath } = input;
19582
19590
  if (!shouldIgnoreFile(filePath)) {
19583
19591
  editedFiles.add(filePath);
19584
- logger6.debug(`Tracked edited file: ${filePath}`);
19592
+ logger7.debug(`Tracked edited file: ${filePath}`);
19585
19593
  }
19586
19594
  };
19587
19595
  }
@@ -19604,20 +19612,63 @@ init_config();
19604
19612
  init_bootstrap();
19605
19613
  init_logger();
19606
19614
 
19607
- // src/tools/formatters.ts
19615
+ // src/tools/formatters/error.ts
19608
19616
  function formatError2(message) {
19609
19617
  return `## SonarQube Error
19610
19618
 
19611
19619
  ${message}`;
19612
19620
  }
19621
+ // src/tools/formatters/success.ts
19622
+ function formatSuccess(title, content) {
19623
+ return `## SonarQube: ${title}
19613
19624
 
19625
+ ${content}`;
19626
+ }
19627
+ // src/tools/formatters/section.ts
19628
+ function formatKeyValue(key, value, bold = true) {
19629
+ const formattedKey = bold ? `**${key}:**` : `${key}:`;
19630
+ return `${formattedKey} ${value}`;
19631
+ }
19632
+ // src/tools/formatters/code.ts
19633
+ function formatInlineCode(code) {
19634
+ return `\`${code}\``;
19635
+ }
19636
+ // src/tools/formatters/project.ts
19637
+ function formatProjectHeader(projectKey, additionalInfo) {
19638
+ const lines = [`**Project:** ${formatInlineCode(projectKey)}`];
19639
+ if (additionalInfo) {
19640
+ lines.push(...Object.entries(additionalInfo).map(([key, value]) => formatKeyValue(key, value)));
19641
+ }
19642
+ return lines.join(`
19643
+ `);
19644
+ }
19645
+ function formatEmptyState(entity, projectKey, positiveMessage) {
19646
+ const lines = [
19647
+ formatProjectHeader(projectKey),
19648
+ "",
19649
+ `No ${entity} found.`,
19650
+ ...positiveMessage ? [positiveMessage] : []
19651
+ ];
19652
+ return lines.join(`
19653
+ `);
19654
+ }
19655
+ // src/tools/base-handler.ts
19656
+ function createHandlerContext(config3, state, projectKey, directory) {
19657
+ return {
19658
+ config: config3,
19659
+ state,
19660
+ projectKey,
19661
+ directory,
19662
+ api: createSonarQubeAPI(config3, state)
19663
+ };
19664
+ }
19614
19665
  // src/tools/handlers/setup.ts
19615
19666
  init_bootstrap();
19616
19667
  init_logger();
19617
19668
  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 });
19669
+ var logger8 = new Logger("sonarqube-handler-setup");
19670
+ async function handleSetup(config3, directory, force = false) {
19671
+ const result = await bootstrap({ config: config3, directory, force });
19621
19672
  if (!result.success) {
19622
19673
  return `## SonarQube Setup Failed
19623
19674
 
@@ -19629,13 +19680,13 @@ ${result.message}`;
19629
19680
  if (!configExists) {
19630
19681
  await mkdir(sonarqubeDir, { recursive: true });
19631
19682
  const defaultConfig = {
19632
- level: config2.level || "enterprise",
19683
+ level: config3.level || "enterprise",
19633
19684
  autoAnalyze: true,
19634
- sources: config2.sources || "src",
19685
+ sources: config3.sources || "src",
19635
19686
  newCodeDefinition: "previous_version"
19636
19687
  };
19637
19688
  await Bun.write(configPath, JSON.stringify(defaultConfig, null, 2));
19638
- logger7.info("Created default config.json", { path: configPath });
19689
+ logger8.info("Created default config.json", { path: configPath });
19639
19690
  }
19640
19691
  const lines = [
19641
19692
  "## SonarQube Project Initialized",
@@ -19688,16 +19739,17 @@ async function filterLcovForCoverage(directory) {
19688
19739
  `));
19689
19740
  } catch {}
19690
19741
  }
19691
- async function handleAnalyze(config2, state, projectKey, args, directory) {
19692
- const projectName = config2.projectName ?? projectKey;
19742
+ async function handleAnalyze(ctx, args) {
19743
+ const { config: config3, state, projectKey, directory } = ctx;
19744
+ const projectName = config3.projectName ?? projectKey;
19693
19745
  await filterLcovForCoverage(directory);
19694
19746
  const coverageExclusions = COVERAGE_EXCLUDED_FILES.map((f) => `**/${f.split("/").pop()}`).join(",");
19695
- const result = await runAnalysis(config2, state, {
19747
+ const result = await runAnalysis(config3, state, {
19696
19748
  projectKey,
19697
19749
  projectName,
19698
- sources: config2.sources,
19699
- tests: config2.tests,
19700
- exclusions: config2.exclusions,
19750
+ sources: config3.sources,
19751
+ tests: config3.tests,
19752
+ exclusions: config3.exclusions,
19701
19753
  additionalProperties: {
19702
19754
  "sonar.coverage.exclusions": coverageExclusions,
19703
19755
  "sonar.javascript.lcov.reportPaths": "coverage/lcov.info"
@@ -19744,7 +19796,7 @@ async function handleAnalyze(config2, state, projectKey, args, directory) {
19744
19796
  }
19745
19797
  // src/tools/handlers/issues.ts
19746
19798
  init_logger();
19747
- var logger8 = new Logger("sonarqube-handler-issues");
19799
+ var logger9 = new Logger("sonarqube-handler-issues");
19748
19800
  function getSeveritiesFromLevel(level) {
19749
19801
  switch (level.toLowerCase()) {
19750
19802
  case "blocker":
@@ -19762,19 +19814,15 @@ function getSeveritiesFromLevel(level) {
19762
19814
  return ["BLOCKER", "CRITICAL", "MAJOR", "MINOR", "INFO"];
19763
19815
  }
19764
19816
  }
19765
- async function handleIssues(config2, state, projectKey, args) {
19766
- const api2 = createSonarQubeAPI(config2, state);
19817
+ async function handleIssues(ctx, args) {
19818
+ const { api: api2, projectKey } = ctx;
19767
19819
  const severities = args.severity === "all" ? undefined : getSeveritiesFromLevel(args.severity ?? "all");
19768
19820
  const issues = await api2.issues.getFormattedIssues({
19769
19821
  projectKey,
19770
19822
  severities
19771
19823
  });
19772
19824
  if (issues.length === 0) {
19773
- return `## SonarQube Issues
19774
-
19775
- **Project:** \`${projectKey}\`
19776
-
19777
- No issues found matching your criteria. Code quality looks good!`;
19825
+ return formatSuccess("Issues", formatEmptyState("issues matching your criteria", projectKey, "Code quality looks good!"));
19778
19826
  }
19779
19827
  const qgStatus = await api2.qualityGate.getStatus(projectKey);
19780
19828
  const criticalIssues = issues.filter((i) => i.severity === "BLOCKER" || i.severity === "CRITICAL").slice(0, 10);
@@ -19806,13 +19854,13 @@ No issues found matching your criteria. Code quality looks good!`;
19806
19854
  `;
19807
19855
  }
19808
19856
  } catch {
19809
- logger8.debug("Could not fetch source context for issues");
19857
+ logger9.debug("Could not fetch source context for issues");
19810
19858
  }
19811
19859
  }
19812
19860
  return output;
19813
19861
  }
19814
- async function handleNewIssues(config2, state, projectKey, args) {
19815
- const api2 = createSonarQubeAPI(config2, state);
19862
+ async function handleNewIssues(ctx, args) {
19863
+ const { api: api2, projectKey } = ctx;
19816
19864
  const severities = args.severity === "all" ? undefined : getSeveritiesFromLevel(args.severity ?? "all");
19817
19865
  const response = await api2.issues.search({
19818
19866
  projectKey,
@@ -19823,13 +19871,9 @@ async function handleNewIssues(config2, state, projectKey, args) {
19823
19871
  });
19824
19872
  const issues = api2.issues.formatIssues(response.issues, response.components);
19825
19873
  if (issues.length === 0) {
19826
- return `## New Code Issues
19874
+ return formatSuccess("New Code Issues", formatEmptyState("issues in new code", projectKey, `Your recent changes are clean.
19827
19875
 
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.`;
19876
+ This means all code written during the current "new code period" passes quality standards.`));
19833
19877
  }
19834
19878
  let output = `## New Code Issues (Clean as You Code)
19835
19879
 
@@ -19869,15 +19913,11 @@ These issues were introduced in your recent changes and should be fixed before m
19869
19913
  }
19870
19914
  return output;
19871
19915
  }
19872
- async function handleWorstFiles(config2, state, projectKey) {
19873
- const api2 = createSonarQubeAPI(config2, state);
19916
+ async function handleWorstFiles(ctx) {
19917
+ const { api: api2, projectKey } = ctx;
19874
19918
  const worstFiles = await api2.components.getWorstFiles(projectKey, 20);
19875
19919
  if (worstFiles.length === 0) {
19876
- return `## Files with Issues
19877
-
19878
- **Project:** \`${projectKey}\`
19879
-
19880
- No files with issues found. Your codebase is clean!`;
19920
+ return formatSuccess("Files with Issues", formatEmptyState("files with issues", projectKey, "Your codebase is clean!"));
19881
19921
  }
19882
19922
  let output = api2.components.formatWorstFilesForAgent(worstFiles);
19883
19923
  output += `
@@ -19888,8 +19928,8 @@ Use \`sonarqube({ action: "issues" })\` to see detailed issues for specific file
19888
19928
  return output;
19889
19929
  }
19890
19930
  // src/tools/handlers/status.ts
19891
- async function handleStatus(config2, state, projectKey) {
19892
- const api2 = createSonarQubeAPI(config2, state);
19931
+ async function handleStatus(ctx) {
19932
+ const { api: api2, projectKey } = ctx;
19893
19933
  const [qgStatus, metrics] = await Promise.all([
19894
19934
  api2.qualityGate.getStatus(projectKey),
19895
19935
  api2.qualityGate.getQualityMetrics(projectKey)
@@ -19930,22 +19970,22 @@ async function handleStatus(config2, state, projectKey) {
19930
19970
  }
19931
19971
  return output;
19932
19972
  }
19933
- async function handleValidate(config2, state, projectKey) {
19934
- const api2 = createSonarQubeAPI(config2, state);
19935
- const result = await api2.qualityGate.validateEnterpriseQuality(projectKey, config2.level);
19973
+ async function handleValidate(ctx) {
19974
+ const { api: api2, projectKey, config: config3 } = ctx;
19975
+ const result = await api2.qualityGate.validateEnterpriseQuality(projectKey, config3.level);
19936
19976
  let output = api2.qualityGate.formatEnterpriseValidation(result);
19937
19977
  if (result.passed) {
19938
19978
  output += `
19939
19979
 
19940
19980
  ---
19941
19981
 
19942
- **Congratulations!** Project meets all ${config2.level} quality standards.`;
19982
+ **Congratulations!** Project meets all ${config3.level} quality standards.`;
19943
19983
  } else {
19944
19984
  output += `
19945
19985
 
19946
19986
  ---
19947
19987
 
19948
- **Action Required:** Project does not meet ${config2.level} quality standards.`;
19988
+ **Action Required:** Project does not meet ${config3.level} quality standards.`;
19949
19989
  output += `
19950
19990
 
19951
19991
  Please fix the failed checks before proceeding.`;
@@ -19953,15 +19993,11 @@ Please fix the failed checks before proceeding.`;
19953
19993
  return output;
19954
19994
  }
19955
19995
  // src/tools/handlers/security.ts
19956
- async function handleHotspots(config2, state, projectKey) {
19957
- const api2 = createSonarQubeAPI(config2, state);
19996
+ async function handleHotspots(ctx) {
19997
+ const { api: api2, projectKey } = ctx;
19958
19998
  const hotspots = await api2.issues.getSecurityHotspots(projectKey);
19959
19999
  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.`;
20000
+ return formatSuccess("Security Hotspots", formatEmptyState("security hotspots", projectKey, "Your code has no security concerns requiring manual review."));
19965
20001
  }
19966
20002
  const toReview = hotspots.filter((h) => h.status === "TO_REVIEW");
19967
20003
  const reviewed = hotspots.filter((h) => h.status !== "TO_REVIEW");
@@ -20010,15 +20046,11 @@ For each hotspot, determine if it's:
20010
20046
  return output;
20011
20047
  }
20012
20048
  // src/tools/handlers/duplications.ts
20013
- async function handleDuplications(config2, state, projectKey) {
20014
- const api2 = createSonarQubeAPI(config2, state);
20049
+ async function handleDuplications(ctx) {
20050
+ const { api: api2, projectKey } = ctx;
20015
20051
  const duplications = await api2.duplications.getProjectDuplications(projectKey);
20016
20052
  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!`;
20053
+ return formatSuccess("Code Duplications", formatEmptyState("code duplications", projectKey, "Your code follows the DRY (Don't Repeat Yourself) principle!"));
20022
20054
  }
20023
20055
  let output = `## Code Duplications
20024
20056
 
@@ -20030,7 +20062,7 @@ No code duplications found. Your code follows the DRY (Don't Repeat Yourself) pr
20030
20062
  return output;
20031
20063
  }
20032
20064
  // src/tools/handlers/rules.ts
20033
- async function handleRule(config2, state, ruleKey) {
20065
+ async function handleRule(ctx, ruleKey) {
20034
20066
  if (!ruleKey) {
20035
20067
  return `## SonarQube Error
20036
20068
 
@@ -20048,7 +20080,7 @@ Common rule prefixes:
20048
20080
  - \`python:\` - Python rules
20049
20081
  - \`common-\` - Language-agnostic rules`;
20050
20082
  }
20051
- const api2 = createSonarQubeAPI(config2, state);
20083
+ const { api: api2 } = ctx;
20052
20084
  const rule = await api2.rules.getRule(ruleKey);
20053
20085
  if (!rule) {
20054
20086
  return `## SonarQube Rule Not Found
@@ -20080,33 +20112,25 @@ Based on this rule, you should:
20080
20112
  3. Re-run analysis to verify the fix`;
20081
20113
  }
20082
20114
  // src/tools/handlers/history.ts
20083
- async function handleHistory(config2, state, projectKey, branch) {
20084
- const api2 = createSonarQubeAPI(config2, state);
20115
+ async function handleHistory(ctx, branch) {
20116
+ const { api: api2, projectKey } = ctx;
20085
20117
  const analyses = await api2.analyses.getAnalyses({ projectKey, branch, pageSize: 10 });
20086
20118
  if (analyses.length === 0) {
20087
- return `## Analysis History
20088
-
20089
- No analysis history found for project \`${projectKey}\`.
20090
-
20091
- This could mean:
20119
+ return formatSuccess("Analysis History", formatEmptyState("analysis history", projectKey, `This could mean:
20092
20120
  1. The project was recently created
20093
20121
  2. No analyses have been run yet
20094
20122
  3. The branch has no analysis history
20095
20123
 
20096
- Run \`sonarqube({ action: "analyze" })\` to create the first analysis.`;
20124
+ Run \`sonarqube({ action: "analyze" })\` to create the first analysis.`));
20097
20125
  }
20098
20126
  return api2.analyses.formatAnalysesForAgent(analyses);
20099
20127
  }
20100
- async function handleProfile(config2, state, projectKey) {
20101
- const api2 = createSonarQubeAPI(config2, state);
20128
+ async function handleProfile(ctx) {
20129
+ const { api: api2, projectKey } = ctx;
20102
20130
  const profiles = await api2.profiles.getProjectProfiles(projectKey);
20103
20131
  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.`;
20132
+ return formatSuccess("Quality Profiles", formatEmptyState("quality profiles", projectKey, `This is unusual - every project should have at least one profile per language.
20133
+ Please check the SonarQube server configuration.`));
20110
20134
  }
20111
20135
  let output = api2.profiles.formatProfilesForAgent(profiles, projectKey);
20112
20136
  output += `
@@ -20118,40 +20142,32 @@ The **Sonar way** profile is a recommended starting point maintained by SonarSou
20118
20142
  To view all available rules, visit your SonarQube server's Rules page.`;
20119
20143
  return output;
20120
20144
  }
20121
- async function handleBranches(config2, state, projectKey) {
20122
- const api2 = createSonarQubeAPI(config2, state);
20145
+ async function handleBranches(ctx) {
20146
+ const { api: api2, projectKey } = ctx;
20123
20147
  const branches = await api2.branches.getBranches(projectKey);
20124
20148
  if (branches.length === 0) {
20125
- return `## Project Branches
20126
-
20127
- No branch information available for project \`${projectKey}\`.
20128
-
20129
- This usually means:
20149
+ return formatSuccess("Project Branches", formatEmptyState("branch information", projectKey, `This usually means:
20130
20150
  1. Branch analysis is not enabled (Community Edition limitation)
20131
20151
  2. Only the main branch has been analyzed
20132
20152
 
20133
20153
  To analyze a specific branch, use:
20134
- \`sonarqube({ action: "analyze", branch: "feature-branch" })\``;
20154
+ \`sonarqube({ action: "analyze", branch: "feature-branch" })\``));
20135
20155
  }
20136
20156
  return api2.branches.formatBranchesForAgent(branches);
20137
20157
  }
20138
20158
  // src/tools/handlers/metrics.ts
20139
- async function handleMetrics(config2, state, projectKey, branch) {
20140
- const api2 = createSonarQubeAPI(config2, state);
20159
+ async function handleMetrics(ctx, branch) {
20160
+ const { api: api2, projectKey } = ctx;
20141
20161
  const measures = await api2.metrics.getMeasuresWithPeriod({
20142
20162
  componentKey: projectKey,
20143
20163
  branch
20144
20164
  });
20145
20165
  if (measures.length === 0) {
20146
- return `## Code Metrics
20147
-
20148
- No metrics available for project \`${projectKey}\`.
20149
-
20150
- This could mean:
20166
+ return formatSuccess("Code Metrics", formatEmptyState("metrics", projectKey, `This could mean:
20151
20167
  1. No analysis has been run yet
20152
20168
  2. The analysis is still processing
20153
20169
 
20154
- Run \`sonarqube({ action: "analyze" })\` to generate metrics.`;
20170
+ Run \`sonarqube({ action: "analyze" })\` to generate metrics.`));
20155
20171
  }
20156
20172
  let output = api2.metrics.formatMeasuresForAgent(measures);
20157
20173
  output += `
@@ -20170,7 +20186,7 @@ Run \`sonarqube({ action: "analyze" })\` to generate metrics.`;
20170
20186
  return output;
20171
20187
  }
20172
20188
  // src/tools/sonarqube.ts
20173
- var logger9 = new Logger("sonarqube-tool");
20189
+ var logger10 = new Logger("sonarqube-tool");
20174
20190
  var SonarQubeToolArgsSchema = exports_external2.object({
20175
20191
  action: exports_external2.enum(["analyze", "issues", "newissues", "status", "init", "setup", "validate", "hotspots", "duplications", "rule", "history", "profile", "branches", "metrics", "worstfiles"]).describe("Action to perform: analyze (run scanner), issues (all issues), newissues (only new code issues), status (quality gate), init/setup (initialize), validate (enterprise check), hotspots (security review), duplications (code duplicates), rule (explain rule), history (past analyses), profile (quality profile), branches (branch status), metrics (detailed metrics), worstfiles (files with most issues)"),
20176
20192
  scope: exports_external2.enum(["all", "new", "changed"]).optional().default("all").describe("Scope of analysis: all files, only new code, or changed files"),
@@ -20183,8 +20199,8 @@ var SonarQubeToolArgsSchema = exports_external2.object({
20183
20199
  });
20184
20200
  async function executeSonarQubeTool(args, context) {
20185
20201
  const directory = context.directory ?? process.cwd();
20186
- const config2 = loadConfig(context.config);
20187
- if (!config2) {
20202
+ const config3 = loadConfig(context.config);
20203
+ if (!config3) {
20188
20204
  return formatError2(`SonarQube configuration not found.
20189
20205
 
20190
20206
  Please ensure you have one of:
@@ -20200,64 +20216,65 @@ Optional:
20200
20216
  - SONAR_USER
20201
20217
  - SONAR_PASSWORD`);
20202
20218
  }
20203
- logger9.info(`Executing SonarQube tool: ${args.action}`, { directory });
20219
+ logger10.info(`Executing SonarQube tool: ${args.action}`, { directory });
20204
20220
  try {
20205
20221
  if (args.action === "init" || args.action === "setup") {
20206
- return await handleSetup(config2, directory, args.force);
20222
+ return await handleSetup(config3, directory, args.force);
20207
20223
  }
20208
20224
  if (await needsBootstrap(directory)) {
20209
- logger9.info("First run detected, running bootstrap");
20225
+ logger10.info("First run detected, running bootstrap");
20210
20226
  const { bootstrap: bootstrap2 } = await Promise.resolve().then(() => (init_bootstrap(), exports_bootstrap));
20211
- const setupResult = await bootstrap2({ config: config2, directory });
20227
+ const setupResult = await bootstrap2({ config: config3, directory });
20212
20228
  if (!setupResult.success) {
20213
20229
  return formatError2(`Setup Failed
20214
20230
 
20215
20231
  ${setupResult.message}`);
20216
20232
  }
20217
- logger9.info("Bootstrap completed", { projectKey: setupResult.projectKey });
20233
+ logger10.info("Bootstrap completed", { projectKey: setupResult.projectKey });
20218
20234
  }
20219
20235
  const state = await getProjectState(directory);
20220
20236
  if (!state) {
20221
20237
  return formatError2(`Project not initialized. Run with action: "setup" first.`);
20222
20238
  }
20223
20239
  const projectKey = args.projectKey ?? state.projectKey;
20240
+ const ctx = createHandlerContext(config3, state, projectKey, directory);
20224
20241
  switch (args.action) {
20225
20242
  case "analyze":
20226
- return await handleAnalyze(config2, state, projectKey, args, directory);
20243
+ return await handleAnalyze(ctx, args);
20227
20244
  case "issues":
20228
- return await handleIssues(config2, state, projectKey, args);
20245
+ return await handleIssues(ctx, args);
20229
20246
  case "newissues":
20230
- return await handleNewIssues(config2, state, projectKey, args);
20247
+ return await handleNewIssues(ctx, args);
20231
20248
  case "status":
20232
- return await handleStatus(config2, state, projectKey);
20249
+ return await handleStatus(ctx);
20233
20250
  case "validate":
20234
- return await handleValidate(config2, state, projectKey);
20251
+ return await handleValidate(ctx);
20235
20252
  case "hotspots":
20236
- return await handleHotspots(config2, state, projectKey);
20253
+ return await handleHotspots(ctx);
20237
20254
  case "duplications":
20238
- return await handleDuplications(config2, state, projectKey);
20255
+ return await handleDuplications(ctx);
20239
20256
  case "rule":
20240
- return await handleRule(config2, state, args.ruleKey);
20257
+ return await handleRule(ctx, args.ruleKey);
20241
20258
  case "history":
20242
- return await handleHistory(config2, state, projectKey, args.branch);
20259
+ return await handleHistory(ctx, args.branch);
20243
20260
  case "profile":
20244
- return await handleProfile(config2, state, projectKey);
20261
+ return await handleProfile(ctx);
20245
20262
  case "branches":
20246
- return await handleBranches(config2, state, projectKey);
20263
+ return await handleBranches(ctx);
20247
20264
  case "metrics":
20248
- return await handleMetrics(config2, state, projectKey, args.branch);
20265
+ return await handleMetrics(ctx, args.branch);
20249
20266
  case "worstfiles":
20250
- return await handleWorstFiles(config2, state, projectKey);
20267
+ return await handleWorstFiles(ctx);
20251
20268
  default:
20252
20269
  return formatError2(`Unknown action: ${args.action}`);
20253
20270
  }
20254
20271
  } catch (error45) {
20255
20272
  const errorMessage = error45 instanceof Error ? error45.message : String(error45);
20256
- logger9.error(`Tool execution failed: ${errorMessage}`);
20273
+ logger10.error(`Tool execution failed: ${errorMessage}`);
20257
20274
  return formatError2(`Action \`${args.action}\` failed: ${errorMessage}
20258
20275
 
20259
20276
  Please check:
20260
- 1. SonarQube server is reachable at ${config2.url}
20277
+ 1. SonarQube server is reachable at ${config3.url}
20261
20278
  2. Credentials are valid
20262
20279
  3. Run with action: "setup" to initialize`);
20263
20280
  }
@@ -20320,8 +20337,8 @@ Environment Variables:
20320
20337
  SONAR_USER SonarQube username
20321
20338
  SONAR_PASSWORD SonarQube password
20322
20339
  `;
20323
- async function handleSetup2(config2, directory, force) {
20324
- const result = await bootstrap({ config: config2, directory, force });
20340
+ async function handleSetup2(config3, directory, force) {
20341
+ const result = await bootstrap({ config: config3, directory, force });
20325
20342
  return {
20326
20343
  success: result.success,
20327
20344
  output: result.success ? `Initializing SonarQube project...
@@ -20329,8 +20346,8 @@ ${result.message}` : result.message,
20329
20346
  exitCode: result.success ? 0 : 1
20330
20347
  };
20331
20348
  }
20332
- async function handleAnalyze2(config2, state, projectKey, directory) {
20333
- const result = await runAnalysis(config2, state, { projectKey }, directory);
20349
+ async function handleAnalyze2(config3, state, projectKey, directory) {
20350
+ const result = await runAnalysis(config3, state, { projectKey }, directory);
20334
20351
  return {
20335
20352
  success: true,
20336
20353
  output: `Running analysis for ${projectKey}...
@@ -20338,8 +20355,8 @@ ${formatAnalysisResult(result)}`,
20338
20355
  exitCode: 0
20339
20356
  };
20340
20357
  }
20341
- async function handleStatus2(config2, state, projectKey) {
20342
- const api2 = createSonarQubeAPI(config2, state);
20358
+ async function handleStatus2(config3, state, projectKey) {
20359
+ const api2 = createSonarQubeAPI(config3, state);
20343
20360
  const status = await api2.qualityGate.getStatus(projectKey);
20344
20361
  return {
20345
20362
  success: true,
@@ -20347,8 +20364,8 @@ async function handleStatus2(config2, state, projectKey) {
20347
20364
  exitCode: 0
20348
20365
  };
20349
20366
  }
20350
- async function handleIssues2(config2, state, projectKey) {
20351
- const api2 = createSonarQubeAPI(config2, state);
20367
+ async function handleIssues2(config3, state, projectKey) {
20368
+ const api2 = createSonarQubeAPI(config3, state);
20352
20369
  const issues = await api2.issues.getFormattedIssues({ projectKey });
20353
20370
  const lines = [`Found ${issues.length} issues`];
20354
20371
  for (const issue2 of issues.slice(0, 20)) {
@@ -20361,8 +20378,8 @@ async function handleIssues2(config2, state, projectKey) {
20361
20378
  exitCode: 0
20362
20379
  };
20363
20380
  }
20364
- async function handleHotspots2(config2, state, projectKey) {
20365
- const api2 = createSonarQubeAPI(config2, state);
20381
+ async function handleHotspots2(config3, state, projectKey) {
20382
+ const api2 = createSonarQubeAPI(config3, state);
20366
20383
  try {
20367
20384
  const hotspots = await api2.issues.getSecurityHotspots(projectKey);
20368
20385
  if (hotspots.length === 0) {
@@ -20402,8 +20419,8 @@ async function runCLI(args, directory = process.cwd()) {
20402
20419
  if (args.includes("--help") || args.includes("-h")) {
20403
20420
  return { success: true, output: CLI_HELP, exitCode: 0 };
20404
20421
  }
20405
- const config2 = loadConfig();
20406
- if (!config2) {
20422
+ const config3 = loadConfig();
20423
+ if (!config3) {
20407
20424
  return {
20408
20425
  success: false,
20409
20426
  output: `Error: SonarQube not configured
@@ -20412,10 +20429,10 @@ Set SONAR_HOST_URL, SONAR_USER, and SONAR_PASSWORD environment variables`,
20412
20429
  };
20413
20430
  }
20414
20431
  if (args.includes("--setup")) {
20415
- return handleSetup2(config2, directory, args.includes("--force"));
20432
+ return handleSetup2(config3, directory, args.includes("--force"));
20416
20433
  }
20417
20434
  if (await needsBootstrap(directory)) {
20418
- const result = await bootstrap({ config: config2, directory });
20435
+ const result = await bootstrap({ config: config3, directory });
20419
20436
  if (!result.success) {
20420
20437
  return {
20421
20438
  success: false,
@@ -20442,16 +20459,16 @@ Setup failed: ${result.message}`,
20442
20459
  };
20443
20460
  }
20444
20461
  if (args.includes("--analyze")) {
20445
- return handleAnalyze2(config2, state, projectKey, directory);
20462
+ return handleAnalyze2(config3, state, projectKey, directory);
20446
20463
  }
20447
20464
  if (args.includes("--status")) {
20448
- return handleStatus2(config2, state, projectKey);
20465
+ return handleStatus2(config3, state, projectKey);
20449
20466
  }
20450
20467
  if (args.includes("--issues")) {
20451
- return handleIssues2(config2, state, projectKey);
20468
+ return handleIssues2(config3, state, projectKey);
20452
20469
  }
20453
20470
  if (args.includes("--hotspots")) {
20454
- return handleHotspots2(config2, state, projectKey);
20471
+ return handleHotspots2(config3, state, projectKey);
20455
20472
  }
20456
20473
  return {
20457
20474
  success: true,
@@ -20556,8 +20573,8 @@ var SonarQubePlugin = async ({ client, directory, worktree }) => {
20556
20573
  try {
20557
20574
  const configFile = Bun.file(sonarConfigPath);
20558
20575
  if (await configFile.exists()) {
20559
- const config2 = await configFile.json();
20560
- pluginConfig = { sonarqube: config2 };
20576
+ const config3 = await configFile.json();
20577
+ pluginConfig = { sonarqube: config3 };
20561
20578
  safeLog(`Config loaded from ${sonarConfigPath}`);
20562
20579
  return;
20563
20580
  }
@@ -20586,14 +20603,14 @@ ${blockerNote}Use \`sonarqube({ action: "issues" })\` to see details or \`sonarq
20586
20603
  try {
20587
20604
  await loadPluginConfig();
20588
20605
  const sonarConfig = pluginConfig?.["sonarqube"];
20589
- const config2 = loadConfig(sonarConfig);
20590
- if (!config2 || config2.level === "off")
20606
+ const config3 = loadConfig(sonarConfig);
20607
+ if (!config3 || config3.level === "off")
20591
20608
  return;
20592
20609
  const dir = getDirectory();
20593
20610
  safeLog(`performInitialQualityCheck: dir=${dir}`);
20594
20611
  if (await needsBootstrap(dir)) {
20595
20612
  safeLog(`performInitialQualityCheck: running bootstrap for ${dir}`);
20596
- const result = await bootstrap({ config: config2, directory: dir });
20613
+ const result = await bootstrap({ config: config3, directory: dir });
20597
20614
  if (!result.success) {
20598
20615
  safeLog(`performInitialQualityCheck: bootstrap failed: ${result.message}`);
20599
20616
  return;
@@ -20603,7 +20620,7 @@ ${blockerNote}Use \`sonarqube({ action: "issues" })\` to see details or \`sonarq
20603
20620
  const state = await getProjectState(dir);
20604
20621
  if (!state?.projectKey)
20605
20622
  return;
20606
- const api2 = createSonarQubeAPI(config2, state);
20623
+ const api2 = createSonarQubeAPI(config3, state);
20607
20624
  const [qgStatus, counts] = await Promise.all([
20608
20625
  api2.qualityGate.getStatus(state.projectKey),
20609
20626
  api2.issues.getCounts(state.projectKey)
@@ -20693,8 +20710,8 @@ ${blockerNote}Use \`sonarqube({ action: "issues" })\` to see details or \`sonarq
20693
20710
  const handleSessionIdleEvent = async () => {
20694
20711
  await loadPluginConfig();
20695
20712
  const sonarConfig = pluginConfig?.["sonarqube"];
20696
- const config2 = loadConfig(sonarConfig);
20697
- if (!config2 || config2.level === "off" || !config2.autoAnalyze) {
20713
+ const config3 = loadConfig(sonarConfig);
20714
+ if (!config3 || config3.level === "off" || !config3.autoAnalyze) {
20698
20715
  return;
20699
20716
  }
20700
20717
  const editedFiles2 = getEditedFiles();
@@ -20711,7 +20728,7 @@ ${blockerNote}Use \`sonarqube({ action: "issues" })\` to see details or \`sonarq
20711
20728
  }
20712
20729
  const passed = message.includes("[PASS]");
20713
20730
  await showToast(passed ? "SonarQube: Quality Gate Passed" : "SonarQube: Issues Found", passed ? "success" : "error");
20714
- await injectAnalysisResults(message, config2, currentSessionId);
20731
+ await injectAnalysisResults(message, config3, currentSessionId);
20715
20732
  };
20716
20733
  const buildCompactionContext = () => {
20717
20734
  if (!lastAnalysisResult) {
@@ -20750,8 +20767,8 @@ ${statusNote}`;
20750
20767
  return;
20751
20768
  await loadPluginConfig();
20752
20769
  const sonarConfig = pluginConfig?.["sonarqube"];
20753
- const config2 = loadConfig(sonarConfig);
20754
- if (!config2 || config2.level === "off" || !config2.autoAnalyze)
20770
+ const config3 = loadConfig(sonarConfig);
20771
+ if (!config3 || config3.level === "off" || !config3.autoAnalyze)
20755
20772
  return;
20756
20773
  const outputData = output.output;
20757
20774
  const filePathRegex = /(?:wrote|edited|modified)\s+(?:file\s+)?['"]?([^\s'"]+)/i;
@@ -20776,8 +20793,8 @@ ${statusNote}`;
20776
20793
  const command = output.metadata?.command ?? "";
20777
20794
  await loadPluginConfig();
20778
20795
  const sonarConfig = pluginConfig?.["sonarqube"];
20779
- const config2 = loadConfig(sonarConfig);
20780
- if (!config2 || config2.level === "off")
20796
+ const config3 = loadConfig(sonarConfig);
20797
+ if (!config3 || config3.level === "off")
20781
20798
  return;
20782
20799
  await handleGitPullMerge(command, outputData);
20783
20800
  await handleGitPush(command, outputData);
@@ -20869,15 +20886,15 @@ Git operation completed with changes. Consider running:
20869
20886
  safeLog(` FINAL dir used: "${dir}"`);
20870
20887
  await loadPluginConfig();
20871
20888
  const sonarConfig = pluginConfig?.["sonarqube"];
20872
- const config2 = loadConfig(sonarConfig);
20873
- if (!config2 || config2.level === "off") {
20889
+ const config3 = loadConfig(sonarConfig);
20890
+ if (!config3 || config3.level === "off") {
20874
20891
  safeLog(` config level is off or null, returning early`);
20875
20892
  return;
20876
20893
  }
20877
20894
  const state = await getProjectState(dir);
20878
20895
  if (!state?.projectKey)
20879
20896
  return;
20880
- const api2 = createSonarQubeAPI(config2, state);
20897
+ const api2 = createSonarQubeAPI(config3, state);
20881
20898
  const [qgStatus, counts, newCodeResponse] = await Promise.all([
20882
20899
  api2.qualityGate.getStatus(state.projectKey),
20883
20900
  api2.issues.getCounts(state.projectKey),
@@ -20902,7 +20919,7 @@ ${newCodeIssues > 0 ? `**New Code Issues:** ${newCodeIssues} issues in recent ch
20902
20919
  ` : ""}
20903
20920
  ${counts.blocker > 0 ? `**IMPORTANT:** There are BLOCKER issues that must be fixed before shipping code.
20904
20921
  ` : ""}
20905
- ${config2.level === "enterprise" ? `This project follows enterprise-level quality standards (zero tolerance for issues).
20922
+ ${config3.level === "enterprise" ? `This project follows enterprise-level quality standards (zero tolerance for issues).
20906
20923
  ` : ""}
20907
20924
  **Recommended Actions:**
20908
20925
  - \`sonarqube({ action: "newissues" })\` - See issues in your recent changes (Clean as You Code)