opencode-sonarqube 0.1.1 → 0.1.3
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 +160 -25
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4205,20 +4205,30 @@ async function hasProjectState(directory) {
|
|
|
4205
4205
|
}
|
|
4206
4206
|
async function loadProjectState(directory) {
|
|
4207
4207
|
const statePath = getStatePath(directory);
|
|
4208
|
+
logger4.info(">>> loadProjectState called", { directory, statePath });
|
|
4208
4209
|
const exists = await Bun.file(statePath).exists();
|
|
4210
|
+
logger4.info("State file exists check", { exists, statePath });
|
|
4209
4211
|
if (!exists) {
|
|
4210
|
-
logger4.
|
|
4212
|
+
logger4.info("No project state file found", { directory, statePath });
|
|
4211
4213
|
return null;
|
|
4212
4214
|
}
|
|
4213
4215
|
try {
|
|
4214
4216
|
const content = await Bun.file(statePath).text();
|
|
4217
|
+
logger4.info("State file content loaded", { contentLength: content.length });
|
|
4215
4218
|
const data = JSON.parse(content);
|
|
4219
|
+
logger4.info("State file parsed", { keys: Object.keys(data) });
|
|
4216
4220
|
const state = ProjectStateSchema.parse(data);
|
|
4217
|
-
logger4.
|
|
4221
|
+
logger4.info("<<< loadProjectState success", {
|
|
4222
|
+
projectKey: state.projectKey,
|
|
4223
|
+
projectKeyLength: state.projectKey?.length,
|
|
4224
|
+
hasToken: !!state.projectToken,
|
|
4225
|
+
tokenLength: state.projectToken?.length
|
|
4226
|
+
});
|
|
4218
4227
|
return state;
|
|
4219
4228
|
} catch (error45) {
|
|
4220
|
-
logger4.
|
|
4221
|
-
error: error45 instanceof Error ? error45.message : String(error45)
|
|
4229
|
+
logger4.error("Failed to load project state", {
|
|
4230
|
+
error: error45 instanceof Error ? error45.message : String(error45),
|
|
4231
|
+
statePath
|
|
4222
4232
|
});
|
|
4223
4233
|
return null;
|
|
4224
4234
|
}
|
|
@@ -16601,6 +16611,8 @@ function tool(input) {
|
|
|
16601
16611
|
tool.schema = exports_external;
|
|
16602
16612
|
// src/utils/config.ts
|
|
16603
16613
|
init_types2();
|
|
16614
|
+
init_logger();
|
|
16615
|
+
var configLogger = new Logger("sonarqube-config");
|
|
16604
16616
|
var DEFAULT_CONFIG = {
|
|
16605
16617
|
level: "enterprise",
|
|
16606
16618
|
autoAnalyze: true,
|
|
@@ -16609,9 +16621,16 @@ var DEFAULT_CONFIG = {
|
|
|
16609
16621
|
sources: "src"
|
|
16610
16622
|
};
|
|
16611
16623
|
function loadConfig(rawConfig) {
|
|
16624
|
+
configLogger.info(">>> loadConfig called", { hasRawConfig: !!rawConfig });
|
|
16612
16625
|
const envUrl = process.env["SONAR_HOST_URL"];
|
|
16613
16626
|
const envUser = process.env["SONAR_USER"];
|
|
16614
16627
|
const envPassword = process.env["SONAR_PASSWORD"];
|
|
16628
|
+
configLogger.info("Environment variables", {
|
|
16629
|
+
hasEnvUrl: !!envUrl,
|
|
16630
|
+
hasEnvUser: !!envUser,
|
|
16631
|
+
hasEnvPassword: !!envPassword,
|
|
16632
|
+
envUrl: envUrl ? `${envUrl.substring(0, 20)}...` : undefined
|
|
16633
|
+
});
|
|
16615
16634
|
const configToValidate = {
|
|
16616
16635
|
...DEFAULT_CONFIG,
|
|
16617
16636
|
...rawConfig,
|
|
@@ -16620,13 +16639,19 @@ function loadConfig(rawConfig) {
|
|
|
16620
16639
|
...envPassword && { password: envPassword }
|
|
16621
16640
|
};
|
|
16622
16641
|
if (!configToValidate.url || !configToValidate.user || !configToValidate.password) {
|
|
16642
|
+
configLogger.warn("Missing required config", {
|
|
16643
|
+
hasUrl: !!configToValidate.url,
|
|
16644
|
+
hasUser: !!configToValidate.user,
|
|
16645
|
+
hasPassword: !!configToValidate.password
|
|
16646
|
+
});
|
|
16623
16647
|
return null;
|
|
16624
16648
|
}
|
|
16625
16649
|
const result = SonarQubeConfigSchema.safeParse(configToValidate);
|
|
16626
16650
|
if (!result.success) {
|
|
16627
|
-
|
|
16651
|
+
configLogger.error("Config validation failed", { errors: result.error.format() });
|
|
16628
16652
|
return null;
|
|
16629
16653
|
}
|
|
16654
|
+
configLogger.info("<<< loadConfig success", { url: result.data.url, level: result.data.level });
|
|
16630
16655
|
return result.data;
|
|
16631
16656
|
}
|
|
16632
16657
|
async function deriveProjectKey(directory) {
|
|
@@ -17049,19 +17074,36 @@ class SonarQubeClient {
|
|
|
17049
17074
|
if (requestBody) {
|
|
17050
17075
|
headers["Content-Type"] = "application/x-www-form-urlencoded";
|
|
17051
17076
|
}
|
|
17052
|
-
this.logger.
|
|
17077
|
+
this.logger.info(`>>> API Request: ${method} ${endpoint}`, {
|
|
17078
|
+
url: url2,
|
|
17079
|
+
params: JSON.stringify(params),
|
|
17080
|
+
hasBody: !!body,
|
|
17081
|
+
bodyKeys: body ? Object.keys(body) : []
|
|
17082
|
+
});
|
|
17053
17083
|
try {
|
|
17054
17084
|
const response = await fetch(url2, {
|
|
17055
17085
|
method,
|
|
17056
17086
|
headers,
|
|
17057
17087
|
body: requestBody
|
|
17058
17088
|
});
|
|
17089
|
+
this.logger.info(`<<< API Response: ${method} ${endpoint}`, {
|
|
17090
|
+
status: response.status,
|
|
17091
|
+
ok: response.ok
|
|
17092
|
+
});
|
|
17059
17093
|
if (!response.ok) {
|
|
17094
|
+
this.logger.error(`API Error: ${method} ${endpoint}`, {
|
|
17095
|
+
status: response.status,
|
|
17096
|
+
url: url2
|
|
17097
|
+
});
|
|
17060
17098
|
await handleResponseError(response);
|
|
17061
17099
|
}
|
|
17062
17100
|
const text = await response.text();
|
|
17063
17101
|
return parseResponseBody(text);
|
|
17064
17102
|
} catch (error45) {
|
|
17103
|
+
this.logger.error(`API Exception: ${method} ${endpoint}`, {
|
|
17104
|
+
error: String(error45),
|
|
17105
|
+
url: url2
|
|
17106
|
+
});
|
|
17065
17107
|
handleFetchError(error45, this.baseUrl);
|
|
17066
17108
|
}
|
|
17067
17109
|
}
|
|
@@ -18199,7 +18241,14 @@ class ProjectAnalysesAPI {
|
|
|
18199
18241
|
this.logger = logger3 ?? new Logger("sonarqube-analyses");
|
|
18200
18242
|
}
|
|
18201
18243
|
async getAnalyses(options) {
|
|
18202
|
-
this.logger.
|
|
18244
|
+
this.logger.info(`>>> getAnalyses called`, {
|
|
18245
|
+
projectKey: options.projectKey,
|
|
18246
|
+
projectKeyLength: options.projectKey?.length
|
|
18247
|
+
});
|
|
18248
|
+
if (!options.projectKey) {
|
|
18249
|
+
this.logger.error(`getAnalyses: projectKey is empty/undefined!`);
|
|
18250
|
+
return [];
|
|
18251
|
+
}
|
|
18203
18252
|
try {
|
|
18204
18253
|
const response = await this.client.get("/api/project_analyses/search", {
|
|
18205
18254
|
project: options.projectKey,
|
|
@@ -18209,9 +18258,10 @@ class ProjectAnalysesAPI {
|
|
|
18209
18258
|
to: options.to,
|
|
18210
18259
|
ps: options.pageSize ?? 10
|
|
18211
18260
|
});
|
|
18261
|
+
this.logger.info(`<<< getAnalyses success`, { analysesCount: response.analyses.length });
|
|
18212
18262
|
return response.analyses;
|
|
18213
18263
|
} catch (error45) {
|
|
18214
|
-
this.logger.
|
|
18264
|
+
this.logger.error(`getAnalyses failed`, { error: String(error45), projectKey: options.projectKey });
|
|
18215
18265
|
return [];
|
|
18216
18266
|
}
|
|
18217
18267
|
}
|
|
@@ -18261,12 +18311,17 @@ class QualityProfilesAPI {
|
|
|
18261
18311
|
this.logger = logger3 ?? new Logger("sonarqube-profiles");
|
|
18262
18312
|
}
|
|
18263
18313
|
async getProjectProfiles(projectKey) {
|
|
18264
|
-
this.logger.
|
|
18314
|
+
this.logger.info(`>>> getProjectProfiles called`, { projectKey, projectKeyLength: projectKey?.length });
|
|
18315
|
+
if (!projectKey) {
|
|
18316
|
+
this.logger.error(`getProjectProfiles: projectKey is empty/undefined!`);
|
|
18317
|
+
return [];
|
|
18318
|
+
}
|
|
18265
18319
|
try {
|
|
18266
18320
|
const response = await this.client.get("/api/qualityprofiles/search", { project: projectKey });
|
|
18321
|
+
this.logger.info(`<<< getProjectProfiles success`, { profileCount: response.profiles.length });
|
|
18267
18322
|
return response.profiles;
|
|
18268
18323
|
} catch (error45) {
|
|
18269
|
-
this.logger.
|
|
18324
|
+
this.logger.error(`getProjectProfiles failed`, { error: String(error45), projectKey });
|
|
18270
18325
|
return [];
|
|
18271
18326
|
}
|
|
18272
18327
|
}
|
|
@@ -18350,12 +18405,17 @@ class BranchesAPI {
|
|
|
18350
18405
|
this.logger = logger3 ?? new Logger("sonarqube-branches");
|
|
18351
18406
|
}
|
|
18352
18407
|
async getBranches(projectKey) {
|
|
18353
|
-
this.logger.
|
|
18408
|
+
this.logger.info(`>>> getBranches called`, { projectKey, projectKeyLength: projectKey?.length });
|
|
18409
|
+
if (!projectKey) {
|
|
18410
|
+
this.logger.error(`getBranches: projectKey is empty/undefined!`);
|
|
18411
|
+
return [];
|
|
18412
|
+
}
|
|
18354
18413
|
try {
|
|
18355
18414
|
const response = await this.client.get("/api/project_branches/list", { project: projectKey });
|
|
18415
|
+
this.logger.info(`<<< getBranches success`, { branchCount: response.branches.length });
|
|
18356
18416
|
return response.branches;
|
|
18357
18417
|
} catch (error45) {
|
|
18358
|
-
this.logger.
|
|
18418
|
+
this.logger.error(`getBranches failed`, { error: String(error45), projectKey });
|
|
18359
18419
|
return [];
|
|
18360
18420
|
}
|
|
18361
18421
|
}
|
|
@@ -18797,7 +18857,18 @@ function createSonarQubeAPIWithToken(url2, token, logger3) {
|
|
|
18797
18857
|
return new SonarQubeAPI(client, logger3);
|
|
18798
18858
|
}
|
|
18799
18859
|
function createSonarQubeAPI(config2, state, logger3) {
|
|
18800
|
-
|
|
18860
|
+
const apiLogger = logger3 ?? new Logger("sonarqube-api");
|
|
18861
|
+
apiLogger.info(">>> createSonarQubeAPI called", {
|
|
18862
|
+
url: config2.url,
|
|
18863
|
+
projectKey: state.projectKey,
|
|
18864
|
+
projectKeyLength: state.projectKey?.length,
|
|
18865
|
+
hasToken: !!state.projectToken,
|
|
18866
|
+
tokenLength: state.projectToken?.length
|
|
18867
|
+
});
|
|
18868
|
+
if (!state.projectToken) {
|
|
18869
|
+
apiLogger.error("createSonarQubeAPI: projectToken is missing!");
|
|
18870
|
+
}
|
|
18871
|
+
return createSonarQubeAPIWithToken(config2.url, state.projectToken, apiLogger);
|
|
18801
18872
|
}
|
|
18802
18873
|
|
|
18803
18874
|
// src/utils/severity.ts
|
|
@@ -19085,12 +19156,22 @@ var QUALITY_GATE_MAPPING = {
|
|
|
19085
19156
|
off: "Sonar way"
|
|
19086
19157
|
};
|
|
19087
19158
|
async function needsBootstrap(directory) {
|
|
19159
|
+
logger5.info(">>> needsBootstrap called", { directory });
|
|
19088
19160
|
const hasState = await hasProjectState(directory);
|
|
19161
|
+
logger5.info("hasProjectState result", { hasState, directory });
|
|
19089
19162
|
if (!hasState) {
|
|
19163
|
+
logger5.info("<<< needsBootstrap: true (no state file)");
|
|
19090
19164
|
return true;
|
|
19091
19165
|
}
|
|
19092
19166
|
const state = await loadProjectState(directory);
|
|
19093
|
-
|
|
19167
|
+
const needsBoot = !state?.setupComplete;
|
|
19168
|
+
logger5.info("<<< needsBootstrap result", {
|
|
19169
|
+
needsBoot,
|
|
19170
|
+
hasState: !!state,
|
|
19171
|
+
setupComplete: state?.setupComplete,
|
|
19172
|
+
projectKey: state?.projectKey
|
|
19173
|
+
});
|
|
19174
|
+
return needsBoot;
|
|
19094
19175
|
}
|
|
19095
19176
|
async function getProjectState(directory) {
|
|
19096
19177
|
return loadProjectState(directory);
|
|
@@ -19990,7 +20071,9 @@ function getSeveritiesFromLevel(level) {
|
|
|
19990
20071
|
|
|
19991
20072
|
// src/index.ts
|
|
19992
20073
|
init_logger();
|
|
20074
|
+
init_logger();
|
|
19993
20075
|
init_types2();
|
|
20076
|
+
var debugLog = new Logger("sonarqube-debug");
|
|
19994
20077
|
var IGNORED_FILE_PATTERNS2 = [
|
|
19995
20078
|
/node_modules/,
|
|
19996
20079
|
/\.git/,
|
|
@@ -20007,6 +20090,7 @@ function shouldIgnoreFile2(filePath) {
|
|
|
20007
20090
|
return IGNORED_FILE_PATTERNS2.some((pattern) => pattern.test(filePath));
|
|
20008
20091
|
}
|
|
20009
20092
|
var SonarQubePlugin = async ({ client, directory, worktree }) => {
|
|
20093
|
+
debugLog.info("=== PLUGIN START ===", { directory, worktree });
|
|
20010
20094
|
await client.app.log({
|
|
20011
20095
|
body: {
|
|
20012
20096
|
service: "opencode-sonarqube",
|
|
@@ -20020,38 +20104,71 @@ var SonarQubePlugin = async ({ client, directory, worktree }) => {
|
|
|
20020
20104
|
const getConfig = () => pluginConfig;
|
|
20021
20105
|
const getDirectory = () => worktree ?? directory;
|
|
20022
20106
|
const loadPluginConfig = async () => {
|
|
20023
|
-
|
|
20107
|
+
debugLog.info("loadPluginConfig called", { hasExistingConfig: !!pluginConfig });
|
|
20108
|
+
if (pluginConfig) {
|
|
20109
|
+
debugLog.info("Config already loaded, skipping");
|
|
20024
20110
|
return;
|
|
20111
|
+
}
|
|
20025
20112
|
try {
|
|
20026
|
-
const
|
|
20113
|
+
const configPath = `${getDirectory()}/opencode.json`;
|
|
20114
|
+
debugLog.info("Loading config from", { configPath });
|
|
20115
|
+
const configFile = Bun.file(configPath);
|
|
20027
20116
|
if (await configFile.exists()) {
|
|
20028
20117
|
pluginConfig = await configFile.json();
|
|
20118
|
+
debugLog.info("Config loaded", { keys: Object.keys(pluginConfig ?? {}) });
|
|
20119
|
+
} else {
|
|
20120
|
+
debugLog.info("No opencode.json found");
|
|
20029
20121
|
}
|
|
20030
|
-
} catch {
|
|
20122
|
+
} catch (error45) {
|
|
20123
|
+
debugLog.warn("Config load error", { error: String(error45) });
|
|
20124
|
+
}
|
|
20031
20125
|
};
|
|
20032
20126
|
const hooks = createHooks(getConfig, getDirectory);
|
|
20033
20127
|
let currentSessionId;
|
|
20034
20128
|
let initialCheckDone = false;
|
|
20035
20129
|
const performInitialQualityCheck = async (sessionId) => {
|
|
20036
|
-
|
|
20130
|
+
debugLog.info("=== performInitialQualityCheck START ===", { sessionId, initialCheckDone });
|
|
20131
|
+
if (initialCheckDone) {
|
|
20132
|
+
debugLog.info("Initial check already done, skipping");
|
|
20037
20133
|
return;
|
|
20134
|
+
}
|
|
20038
20135
|
initialCheckDone = true;
|
|
20039
20136
|
try {
|
|
20040
20137
|
await loadPluginConfig();
|
|
20041
20138
|
const sonarConfig = pluginConfig?.["sonarqube"];
|
|
20139
|
+
debugLog.info("Loading SonarQube config", { hasSonarConfig: !!sonarConfig });
|
|
20042
20140
|
const config2 = loadConfig(sonarConfig);
|
|
20043
|
-
|
|
20141
|
+
debugLog.info("Config loaded", { hasConfig: !!config2, level: config2?.level });
|
|
20142
|
+
if (!config2 || config2.level === "off") {
|
|
20143
|
+
debugLog.info("Config missing or level=off, skipping");
|
|
20044
20144
|
return;
|
|
20045
|
-
|
|
20145
|
+
}
|
|
20146
|
+
const dir = getDirectory();
|
|
20147
|
+
debugLog.info("Checking needsBootstrap", { directory: dir });
|
|
20148
|
+
const needsBoot = await needsBootstrap(dir);
|
|
20149
|
+
debugLog.info("needsBootstrap result", { needsBoot });
|
|
20150
|
+
if (needsBoot) {
|
|
20151
|
+
debugLog.info("Bootstrap needed, skipping initial check");
|
|
20046
20152
|
return;
|
|
20047
|
-
|
|
20048
|
-
|
|
20153
|
+
}
|
|
20154
|
+
debugLog.info("Loading project state");
|
|
20155
|
+
const state = await getProjectState(dir);
|
|
20156
|
+
debugLog.info("Project state loaded", {
|
|
20157
|
+
hasState: !!state,
|
|
20158
|
+
projectKey: state?.projectKey,
|
|
20159
|
+
hasToken: !!state?.projectToken
|
|
20160
|
+
});
|
|
20161
|
+
if (!state || !state.projectKey) {
|
|
20162
|
+
debugLog.info("No state or projectKey, skipping");
|
|
20049
20163
|
return;
|
|
20164
|
+
}
|
|
20165
|
+
debugLog.info("Creating API and fetching quality status", { projectKey: state.projectKey });
|
|
20050
20166
|
const api2 = createSonarQubeAPI(config2, state);
|
|
20051
20167
|
const [qgStatus, counts] = await Promise.all([
|
|
20052
20168
|
api2.qualityGate.getStatus(state.projectKey),
|
|
20053
20169
|
api2.issues.getCounts(state.projectKey)
|
|
20054
20170
|
]);
|
|
20171
|
+
debugLog.info("Quality status fetched", { qgStatus: qgStatus.projectStatus.status, counts });
|
|
20055
20172
|
const hasIssues = counts.blocker > 0 || counts.critical > 0 || counts.major > 0;
|
|
20056
20173
|
const qgFailed = qgStatus.projectStatus.status !== "OK";
|
|
20057
20174
|
if (hasIssues || qgFailed) {
|
|
@@ -20167,7 +20284,7 @@ After fixing, I will re-run the analysis to verify.`;
|
|
|
20167
20284
|
});
|
|
20168
20285
|
if (config2.autoFix && lastAnalysisResult) {
|
|
20169
20286
|
const state = await getProjectState(getDirectory());
|
|
20170
|
-
if (state) {
|
|
20287
|
+
if (state && state.projectKey) {
|
|
20171
20288
|
const api2 = createSonarQubeAPI(config2, state);
|
|
20172
20289
|
const issues = await api2.issues.getFormattedIssues({
|
|
20173
20290
|
projectKey: state.projectKey,
|
|
@@ -20238,7 +20355,7 @@ ${statusNote}`;
|
|
|
20238
20355
|
}
|
|
20239
20356
|
try {
|
|
20240
20357
|
const state = await getProjectState(getDirectory());
|
|
20241
|
-
if (!state)
|
|
20358
|
+
if (!state || !state.projectKey)
|
|
20242
20359
|
return;
|
|
20243
20360
|
const api2 = createSonarQubeAPI(config2, state);
|
|
20244
20361
|
const counts = await api2.issues.getCounts(state.projectKey);
|
|
@@ -20383,18 +20500,36 @@ Git operation completed with changes. Consider running:
|
|
|
20383
20500
|
}
|
|
20384
20501
|
},
|
|
20385
20502
|
"experimental.chat.system.transform": async (_input, output) => {
|
|
20503
|
+
debugLog.info("=== experimental.chat.system.transform START ===");
|
|
20386
20504
|
await loadPluginConfig();
|
|
20387
20505
|
const sonarConfig = pluginConfig?.["sonarqube"];
|
|
20506
|
+
debugLog.info("system.transform: Loading config", { hasSonarConfig: !!sonarConfig });
|
|
20388
20507
|
const config2 = loadConfig(sonarConfig);
|
|
20508
|
+
debugLog.info("system.transform: Config result", { hasConfig: !!config2, level: config2?.level });
|
|
20389
20509
|
if (!config2 || config2.level === "off") {
|
|
20510
|
+
debugLog.info("system.transform: No config or level=off, skipping");
|
|
20390
20511
|
return;
|
|
20391
20512
|
}
|
|
20392
20513
|
try {
|
|
20393
|
-
const
|
|
20514
|
+
const dir = getDirectory();
|
|
20515
|
+
debugLog.info("system.transform: Loading project state", { directory: dir });
|
|
20516
|
+
const state = await getProjectState(dir);
|
|
20517
|
+
debugLog.info("system.transform: State loaded", {
|
|
20518
|
+
hasState: !!state,
|
|
20519
|
+
projectKey: state?.projectKey,
|
|
20520
|
+
hasToken: !!state?.projectToken
|
|
20521
|
+
});
|
|
20394
20522
|
if (!state || !state.projectKey) {
|
|
20523
|
+
debugLog.info("system.transform: No state or projectKey, skipping");
|
|
20395
20524
|
return;
|
|
20396
20525
|
}
|
|
20526
|
+
debugLog.info("system.transform: Creating API", {
|
|
20527
|
+
url: config2.url,
|
|
20528
|
+
projectKey: state.projectKey,
|
|
20529
|
+
tokenLength: state.projectToken?.length
|
|
20530
|
+
});
|
|
20397
20531
|
const api2 = createSonarQubeAPI(config2, state);
|
|
20532
|
+
debugLog.info("system.transform: Fetching quality data", { projectKey: state.projectKey });
|
|
20398
20533
|
const [qgStatus, counts, newCodeResponse] = await Promise.all([
|
|
20399
20534
|
api2.qualityGate.getStatus(state.projectKey),
|
|
20400
20535
|
api2.issues.getCounts(state.projectKey),
|