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.
- package/dist/index.js +769 -752
- 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/
|
|
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
|
|
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
|
-
|
|
4545
|
+
logger3.info(`Written sonar-project.properties to ${filePath}`);
|
|
4516
4546
|
return filePath;
|
|
4517
4547
|
}
|
|
4518
|
-
var
|
|
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(
|
|
4628
|
-
this.baseUrl = normalizeUrl(
|
|
4629
|
-
this.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,
|
|
4712
|
-
return new SonarQubeClient({ url: url2, auth: { token } },
|
|
4752
|
+
function createClientWithToken(url2, token, logger4) {
|
|
4753
|
+
return new SonarQubeClient({ url: url2, auth: { token } }, logger4);
|
|
4713
4754
|
}
|
|
4714
|
-
function createClientWithCredentials(url2, user, password,
|
|
4715
|
-
return new SonarQubeClient({ url: url2, auth: { user, password } },
|
|
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
|
-
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
|
|
4732
|
-
|
|
4733
|
-
|
|
4734
|
-
|
|
4735
|
-
|
|
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
|
-
|
|
4755
|
-
|
|
4756
|
-
|
|
4757
|
-
|
|
4758
|
-
|
|
4759
|
-
|
|
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
|
-
|
|
4784
|
-
|
|
4785
|
-
|
|
4786
|
-
|
|
4787
|
-
|
|
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
|
-
|
|
4799
|
-
|
|
4800
|
-
|
|
4801
|
-
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
|
|
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
|
-
|
|
4811
|
-
|
|
4812
|
-
|
|
4813
|
-
|
|
4814
|
-
|
|
4815
|
-
|
|
4816
|
-
|
|
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
|
-
|
|
4822
|
-
|
|
4823
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4981
|
+
logger5.info("Added SonarQube exclusion to .gitignore");
|
|
4943
4982
|
}
|
|
4944
|
-
var
|
|
4983
|
+
var logger5, STATE_DIR = ".sonarqube", STATE_FILE = "project.json";
|
|
4945
4984
|
var init_state = __esm(() => {
|
|
4946
4985
|
init_types2();
|
|
4947
|
-
|
|
4948
|
-
|
|
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
|
-
|
|
5001
|
+
logger6.info(">>> needsBootstrap called", { directory });
|
|
4967
5002
|
const hasState = await hasProjectState(directory);
|
|
4968
|
-
|
|
5003
|
+
logger6.info("hasProjectState result", { hasState, directory });
|
|
4969
5004
|
if (!hasState) {
|
|
4970
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
5060
|
+
logger6.info("Enabled full plugin loading on server (sonar.plugins.downloadOnlyRequired=false)");
|
|
5026
5061
|
}
|
|
5027
5062
|
} catch {
|
|
5028
|
-
|
|
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
|
-
|
|
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:
|
|
5041
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
5114
|
+
logger6.info("Connected to SonarQube", { version: health.version });
|
|
5080
5115
|
await ensurePluginsEnabled(adminClient);
|
|
5081
5116
|
const detection = await detectProjectType(directory);
|
|
5082
|
-
const projectKey =
|
|
5083
|
-
const projectName =
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
5132
|
+
logger6.warn("Failed to set quality gate", { error: String(error45) });
|
|
5098
5133
|
}
|
|
5099
|
-
await configureProjectSettings(adminClient, projectKey, detection.languages,
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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 (
|
|
5139
|
-
settings.push({ key: "sonar.sources", value:
|
|
5173
|
+
if (config3.sources) {
|
|
5174
|
+
settings.push({ key: "sonar.sources", value: config3.sources });
|
|
5140
5175
|
}
|
|
5141
|
-
if (
|
|
5142
|
-
settings.push({ key: "sonar.tests", value:
|
|
5176
|
+
if (config3.tests) {
|
|
5177
|
+
settings.push({ key: "sonar.tests", value: config3.tests });
|
|
5143
5178
|
}
|
|
5144
|
-
if (
|
|
5145
|
-
settings.push({ key: "sonar.exclusions", value:
|
|
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
|
-
|
|
5202
|
+
logger6.info("Disconnected from SonarQube", { projectKey: state.projectKey });
|
|
5168
5203
|
}
|
|
5169
|
-
var
|
|
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
|
-
|
|
5209
|
+
init_config3();
|
|
5175
5210
|
init_state();
|
|
5176
5211
|
init_config();
|
|
5177
|
-
|
|
5178
|
-
|
|
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
|
-
|
|
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
|
-
|
|
17557
|
+
init_base_api();
|
|
17525
17558
|
|
|
17526
|
-
class IssuesAPI {
|
|
17527
|
-
client
|
|
17528
|
-
|
|
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
|
-
|
|
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
|
-
|
|
17706
|
-
|
|
17707
|
-
|
|
17708
|
-
|
|
17709
|
-
|
|
17710
|
-
|
|
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:
|
|
17771
|
-
securityRating:
|
|
17772
|
-
maintainabilityRating:
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
18019
|
-
|
|
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
|
-
|
|
18149
|
+
init_base_api();
|
|
18118
18150
|
|
|
18119
|
-
class SourcesAPI {
|
|
18120
|
-
client
|
|
18121
|
-
|
|
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
|
-
|
|
18286
|
+
init_base_api();
|
|
18258
18287
|
|
|
18259
|
-
class DuplicationsAPI {
|
|
18260
|
-
client
|
|
18261
|
-
|
|
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
|
-
|
|
18393
|
+
init_base_api();
|
|
18368
18394
|
var debugLog = (_msg) => {};
|
|
18369
18395
|
|
|
18370
|
-
class ComputeEngineAPI {
|
|
18371
|
-
client
|
|
18372
|
-
|
|
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
|
-
|
|
18542
|
+
init_base_api();
|
|
18520
18543
|
|
|
18521
|
-
class ProjectAnalysesAPI {
|
|
18522
|
-
client
|
|
18523
|
-
|
|
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
|
-
|
|
18609
|
+
init_base_api();
|
|
18590
18610
|
|
|
18591
|
-
class QualityProfilesAPI {
|
|
18592
|
-
client
|
|
18593
|
-
|
|
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
|
-
|
|
18700
|
+
init_base_api();
|
|
18684
18701
|
|
|
18685
|
-
class BranchesAPI {
|
|
18686
|
-
client
|
|
18687
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
18978
|
+
init_base_api();
|
|
18968
18979
|
|
|
18969
|
-
class ComponentsAPI {
|
|
18970
|
-
client
|
|
18971
|
-
|
|
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,
|
|
19103
|
-
this.logger =
|
|
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,
|
|
19140
|
-
const client = createClientWithToken(url2, token,
|
|
19141
|
-
return new SonarQubeAPI(client,
|
|
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(
|
|
19144
|
-
return createSonarQubeAPIWithToken(
|
|
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
|
-
|
|
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
|
|
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(
|
|
19274
|
+
async function runScanner(config3, state, options, directory) {
|
|
19267
19275
|
const dir = directory ?? process.cwd();
|
|
19268
|
-
|
|
19276
|
+
logger4.info(`Starting SonarQube analysis for ${options.projectKey}`);
|
|
19269
19277
|
const args = [
|
|
19270
|
-
`-Dsonar.host.url=${sanitizeArgValue(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
19349
|
+
async function runAnalysis(config3, state, options, directory) {
|
|
19342
19350
|
const dir = directory ?? process.cwd();
|
|
19343
|
-
const api2 = createSonarQubeAPI(
|
|
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
|
-
|
|
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 ??
|
|
19354
|
-
tests: options.tests ??
|
|
19355
|
-
exclusions: options.exclusions ??
|
|
19356
|
-
},
|
|
19357
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
19382
|
+
logger4.info(`Analysis task completed: ${task.status}`);
|
|
19375
19383
|
if (task.status === "FAILED") {
|
|
19376
|
-
|
|
19384
|
+
logger4.error(`Analysis failed: ${task.errorMessage ?? "Unknown error"}`);
|
|
19377
19385
|
}
|
|
19378
19386
|
} else {
|
|
19379
|
-
|
|
19387
|
+
logger4.warn("Task not found - continuing with available results");
|
|
19380
19388
|
}
|
|
19381
19389
|
} catch (waitError) {
|
|
19382
|
-
|
|
19390
|
+
logger4.debug("Could not wait for task via CE API", { waitError });
|
|
19383
19391
|
}
|
|
19384
19392
|
} else if (scannerResult.success) {
|
|
19385
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
19452
|
-
return
|
|
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(
|
|
19466
|
+
async function handleBootstrap(config3, directory) {
|
|
19459
19467
|
if (bootstrapInProgress) {
|
|
19460
|
-
|
|
19468
|
+
logger7.debug("Bootstrap already in progress");
|
|
19461
19469
|
return;
|
|
19462
19470
|
}
|
|
19463
|
-
|
|
19471
|
+
logger7.info("First run detected - initializing SonarQube integration");
|
|
19464
19472
|
bootstrapInProgress = true;
|
|
19465
19473
|
try {
|
|
19466
|
-
const result = await bootstrap({ config:
|
|
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
|
-
|
|
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(
|
|
19488
|
+
async function performAnalysis(config3, state, directory) {
|
|
19481
19489
|
const projectKey = state.projectKey;
|
|
19482
|
-
const projectName =
|
|
19483
|
-
const result = await runAnalysis(
|
|
19490
|
+
const projectName = config3.projectName ?? projectKey;
|
|
19491
|
+
const result = await runAnalysis(config3, state, {
|
|
19484
19492
|
projectKey,
|
|
19485
19493
|
projectName,
|
|
19486
|
-
sources:
|
|
19487
|
-
tests:
|
|
19494
|
+
sources: config3.sources,
|
|
19495
|
+
tests: config3.tests
|
|
19488
19496
|
}, directory);
|
|
19489
19497
|
editedFiles.clear();
|
|
19490
|
-
return formatAnalysisOutput(result,
|
|
19498
|
+
return formatAnalysisOutput(result, config3);
|
|
19491
19499
|
}
|
|
19492
|
-
function formatAnalysisOutput(result,
|
|
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,
|
|
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
|
|
19518
|
-
if (!isAnalysisEnabled(
|
|
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(
|
|
19531
|
+
return handleBootstrap(config3, directory);
|
|
19524
19532
|
}
|
|
19525
19533
|
if (isInCooldown()) {
|
|
19526
|
-
|
|
19534
|
+
logger7.debug("Skipping auto-analysis (cooldown)");
|
|
19527
19535
|
return;
|
|
19528
19536
|
}
|
|
19529
19537
|
if (editedFiles.size === 0) {
|
|
19530
|
-
|
|
19538
|
+
logger7.debug("Skipping auto-analysis (no edited files)");
|
|
19531
19539
|
return;
|
|
19532
19540
|
}
|
|
19533
|
-
|
|
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
|
-
|
|
19546
|
+
logger7.warn("No project state found, cannot run analysis");
|
|
19539
19547
|
return;
|
|
19540
19548
|
}
|
|
19541
|
-
return await performAnalysis(
|
|
19549
|
+
return await performAnalysis(config3, state, directory);
|
|
19542
19550
|
} catch (error45) {
|
|
19543
|
-
|
|
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
|
-
|
|
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
|
|
19619
|
-
async function handleSetup(
|
|
19620
|
-
const result = await bootstrap({ config:
|
|
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:
|
|
19683
|
+
level: config3.level || "enterprise",
|
|
19633
19684
|
autoAnalyze: true,
|
|
19634
|
-
sources:
|
|
19685
|
+
sources: config3.sources || "src",
|
|
19635
19686
|
newCodeDefinition: "previous_version"
|
|
19636
19687
|
};
|
|
19637
19688
|
await Bun.write(configPath, JSON.stringify(defaultConfig, null, 2));
|
|
19638
|
-
|
|
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(
|
|
19692
|
-
const
|
|
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(
|
|
19747
|
+
const result = await runAnalysis(config3, state, {
|
|
19696
19748
|
projectKey,
|
|
19697
19749
|
projectName,
|
|
19698
|
-
sources:
|
|
19699
|
-
tests:
|
|
19700
|
-
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
|
|
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(
|
|
19766
|
-
const api2 =
|
|
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
|
|
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
|
-
|
|
19857
|
+
logger9.debug("Could not fetch source context for issues");
|
|
19810
19858
|
}
|
|
19811
19859
|
}
|
|
19812
19860
|
return output;
|
|
19813
19861
|
}
|
|
19814
|
-
async function handleNewIssues(
|
|
19815
|
-
const api2 =
|
|
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
|
|
19874
|
+
return formatSuccess("New Code Issues", formatEmptyState("issues in new code", projectKey, `Your recent changes are clean.
|
|
19827
19875
|
|
|
19828
|
-
|
|
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(
|
|
19873
|
-
const api2 =
|
|
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
|
|
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(
|
|
19892
|
-
const api2 =
|
|
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(
|
|
19934
|
-
const api2 =
|
|
19935
|
-
const result = await api2.qualityGate.validateEnterpriseQuality(projectKey,
|
|
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 ${
|
|
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 ${
|
|
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(
|
|
19957
|
-
const api2 =
|
|
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
|
|
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(
|
|
20014
|
-
const api2 =
|
|
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
|
|
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(
|
|
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 =
|
|
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(
|
|
20084
|
-
const api2 =
|
|
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
|
|
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(
|
|
20101
|
-
const api2 =
|
|
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
|
|
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(
|
|
20122
|
-
const api2 =
|
|
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
|
|
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(
|
|
20140
|
-
const api2 =
|
|
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
|
|
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
|
|
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
|
|
20187
|
-
if (!
|
|
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
|
-
|
|
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(
|
|
20222
|
+
return await handleSetup(config3, directory, args.force);
|
|
20207
20223
|
}
|
|
20208
20224
|
if (await needsBootstrap(directory)) {
|
|
20209
|
-
|
|
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:
|
|
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
|
-
|
|
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(
|
|
20243
|
+
return await handleAnalyze(ctx, args);
|
|
20227
20244
|
case "issues":
|
|
20228
|
-
return await handleIssues(
|
|
20245
|
+
return await handleIssues(ctx, args);
|
|
20229
20246
|
case "newissues":
|
|
20230
|
-
return await handleNewIssues(
|
|
20247
|
+
return await handleNewIssues(ctx, args);
|
|
20231
20248
|
case "status":
|
|
20232
|
-
return await handleStatus(
|
|
20249
|
+
return await handleStatus(ctx);
|
|
20233
20250
|
case "validate":
|
|
20234
|
-
return await handleValidate(
|
|
20251
|
+
return await handleValidate(ctx);
|
|
20235
20252
|
case "hotspots":
|
|
20236
|
-
return await handleHotspots(
|
|
20253
|
+
return await handleHotspots(ctx);
|
|
20237
20254
|
case "duplications":
|
|
20238
|
-
return await handleDuplications(
|
|
20255
|
+
return await handleDuplications(ctx);
|
|
20239
20256
|
case "rule":
|
|
20240
|
-
return await handleRule(
|
|
20257
|
+
return await handleRule(ctx, args.ruleKey);
|
|
20241
20258
|
case "history":
|
|
20242
|
-
return await handleHistory(
|
|
20259
|
+
return await handleHistory(ctx, args.branch);
|
|
20243
20260
|
case "profile":
|
|
20244
|
-
return await handleProfile(
|
|
20261
|
+
return await handleProfile(ctx);
|
|
20245
20262
|
case "branches":
|
|
20246
|
-
return await handleBranches(
|
|
20263
|
+
return await handleBranches(ctx);
|
|
20247
20264
|
case "metrics":
|
|
20248
|
-
return await handleMetrics(
|
|
20265
|
+
return await handleMetrics(ctx, args.branch);
|
|
20249
20266
|
case "worstfiles":
|
|
20250
|
-
return await handleWorstFiles(
|
|
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
|
-
|
|
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 ${
|
|
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(
|
|
20324
|
-
const result = await bootstrap({ config:
|
|
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(
|
|
20333
|
-
const result = await runAnalysis(
|
|
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(
|
|
20342
|
-
const api2 = createSonarQubeAPI(
|
|
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(
|
|
20351
|
-
const api2 = createSonarQubeAPI(
|
|
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(
|
|
20365
|
-
const api2 = createSonarQubeAPI(
|
|
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
|
|
20406
|
-
if (!
|
|
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(
|
|
20432
|
+
return handleSetup2(config3, directory, args.includes("--force"));
|
|
20416
20433
|
}
|
|
20417
20434
|
if (await needsBootstrap(directory)) {
|
|
20418
|
-
const result = await bootstrap({ config:
|
|
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(
|
|
20462
|
+
return handleAnalyze2(config3, state, projectKey, directory);
|
|
20446
20463
|
}
|
|
20447
20464
|
if (args.includes("--status")) {
|
|
20448
|
-
return handleStatus2(
|
|
20465
|
+
return handleStatus2(config3, state, projectKey);
|
|
20449
20466
|
}
|
|
20450
20467
|
if (args.includes("--issues")) {
|
|
20451
|
-
return handleIssues2(
|
|
20468
|
+
return handleIssues2(config3, state, projectKey);
|
|
20452
20469
|
}
|
|
20453
20470
|
if (args.includes("--hotspots")) {
|
|
20454
|
-
return handleHotspots2(
|
|
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
|
|
20560
|
-
pluginConfig = { sonarqube:
|
|
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
|
|
20590
|
-
if (!
|
|
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:
|
|
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(
|
|
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
|
|
20697
|
-
if (!
|
|
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,
|
|
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
|
|
20754
|
-
if (!
|
|
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
|
|
20780
|
-
if (!
|
|
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
|
|
20873
|
-
if (!
|
|
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(
|
|
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
|
-
${
|
|
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)
|