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