@jonathan0823/pi-sonarqube 0.4.0 → 0.6.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/src/config.ts CHANGED
@@ -1,7 +1,12 @@
1
1
  import { CONFIG_DIR_NAME } from "@earendil-works/pi-coding-agent";
2
2
  import { mkdir, readFile, stat, writeFile } from "node:fs/promises";
3
3
  import { basename, dirname, relative, resolve } from "node:path";
4
- import type { SonarInitConfig, SonarProjectConfig, SonarWorkspaceRegistry, ResolvedTarget } from "./types.js";
4
+ import type {
5
+ SonarInitConfig,
6
+ SonarProjectConfig,
7
+ SonarWorkspaceRegistry,
8
+ ResolvedTarget,
9
+ } from "./types.js";
5
10
 
6
11
  // ── Generic helpers ─────────────────────────────────────────────────────────
7
12
 
@@ -40,7 +45,10 @@ export function normalizeServerUrl(url: string | undefined): string {
40
45
  return url.trim().replace(/\/+$/, "");
41
46
  }
42
47
 
43
- export function resolveProjectKey(baseDir: string, props: Record<string, string>): string {
48
+ export function resolveProjectKey(
49
+ baseDir: string,
50
+ props: Record<string, string>,
51
+ ): string {
44
52
  const fromProps = props["sonar.projectKey"]?.trim();
45
53
  if (fromProps) return fromProps;
46
54
  return slugify(basename(baseDir));
@@ -48,7 +56,9 @@ export function resolveProjectKey(baseDir: string, props: Record<string, string>
48
56
 
49
57
  // ── File helpers ────────────────────────────────────────────────────────────
50
58
 
51
- export async function readOptionalText(path: string): Promise<string | undefined> {
59
+ export async function readOptionalText(
60
+ path: string,
61
+ ): Promise<string | undefined> {
52
62
  try {
53
63
  return await readFile(path, "utf8");
54
64
  } catch {
@@ -56,7 +66,9 @@ export async function readOptionalText(path: string): Promise<string | undefined
56
66
  }
57
67
  }
58
68
 
59
- export async function readOptionalJson<T>(path: string): Promise<T | undefined> {
69
+ export async function readOptionalJson<T>(
70
+ path: string,
71
+ ): Promise<T | undefined> {
60
72
  try {
61
73
  const text = await readFile(path, "utf8");
62
74
  return JSON.parse(text) as T;
@@ -87,14 +99,24 @@ export const DEFAULT_SONAR_EXCLUSIONS = [
87
99
  "coverage/**",
88
100
  ".scannerwork/**",
89
101
  "**/.env*",
102
+ ".next/**",
103
+ ".pi/**",
104
+ "build/**",
105
+ "out/**",
106
+ ".turbo/**",
107
+ ".vercel/**",
90
108
  ];
91
109
 
92
- const DEFAULT_SONAR_PROJECT_PROPERTIES = [
93
- "sonar.sources=.",
94
- `sonar.exclusions=${DEFAULT_SONAR_EXCLUSIONS.join(",")}`,
95
- ].join("\n") + "\n";
110
+ const DEFAULT_SONAR_PROJECT_PROPERTIES =
111
+ [
112
+ "sonar.sources=.",
113
+ `sonar.exclusions=${DEFAULT_SONAR_EXCLUSIONS.join(",")}`,
114
+ ].join("\n") + "\n";
96
115
 
97
- export function mergeCommaSeparatedValues(existingValue: string | undefined, additions: string[]): string {
116
+ export function mergeCommaSeparatedValues(
117
+ existingValue: string | undefined,
118
+ additions: string[],
119
+ ): string {
98
120
  const merged = new Set(
99
121
  (existingValue ?? "")
100
122
  .split(",")
@@ -110,7 +132,9 @@ export function mergeCommaSeparatedValues(existingValue: string | undefined, add
110
132
 
111
133
  const EXCLUSION_RE = /^(\s*)(sonar\.(?:sources|exclusions))\s*=\s*(.*)$/;
112
134
 
113
- export async function ensureDefaultSonarProjectProperties(baseDir: string): Promise<"created" | "updated" | "unchanged"> {
135
+ export async function ensureDefaultSonarProjectProperties(
136
+ baseDir: string,
137
+ ): Promise<"created" | "updated" | "unchanged"> {
114
138
  const path = sonarProjectPropertiesPath(baseDir);
115
139
  const existing = await readOptionalText(path);
116
140
  if (!existing?.trim()) {
@@ -165,19 +189,31 @@ export async function ensureDefaultSonarProjectProperties(baseDir: string): Prom
165
189
 
166
190
  const normalized = updatedLines.join("\n").replace(/\n*$/, "\n");
167
191
  await writeFile(path, normalized, "utf8");
168
- return existing.includes("sonar.sources") || existing.includes("sonar.exclusions") ? "updated" : "created";
192
+ return existing.includes("sonar.sources") ||
193
+ existing.includes("sonar.exclusions")
194
+ ? "updated"
195
+ : "created";
169
196
  }
170
197
 
171
198
  // ── Config load/save ────────────────────────────────────────────────────────
172
199
 
173
- export async function loadProjectConfig(baseDir: string): Promise<SonarInitConfig | undefined> {
200
+ export async function loadProjectConfig(
201
+ baseDir: string,
202
+ ): Promise<SonarInitConfig | undefined> {
174
203
  return readOptionalJson<SonarInitConfig>(projectConfigPath(baseDir));
175
204
  }
176
205
 
177
- export async function saveProjectConfig(baseDir: string, config: SonarInitConfig): Promise<void> {
206
+ export async function saveProjectConfig(
207
+ baseDir: string,
208
+ config: SonarInitConfig,
209
+ ): Promise<void> {
178
210
  const dir = sonarqubeConfigDir(baseDir);
179
211
  await mkdir(dir, { recursive: true });
180
- await writeFile(projectConfigPath(baseDir), JSON.stringify(config, null, 2) + "\n", "utf8");
212
+ await writeFile(
213
+ projectConfigPath(baseDir),
214
+ JSON.stringify(config, null, 2) + "\n",
215
+ "utf8",
216
+ );
181
217
  }
182
218
 
183
219
  // ── Workspace registry ──────────────────────────────────────────────────────
@@ -211,16 +247,29 @@ export async function loadWorkspaceRegistry(
211
247
  startDir: string,
212
248
  ): Promise<{ repoRoot: string; registry: SonarWorkspaceRegistry }> {
213
249
  const repoRoot = await findRepoRoot(startDir);
214
- const raw = await readOptionalJson<Partial<SonarWorkspaceRegistry>>(workspaceRegistryPath(repoRoot));
215
- const workspaces = raw?.workspaces && typeof raw.workspaces === "object" ? { ...raw.workspaces } : {};
250
+ const raw = await readOptionalJson<Partial<SonarWorkspaceRegistry>>(
251
+ workspaceRegistryPath(repoRoot),
252
+ );
253
+ const workspaces =
254
+ raw?.workspaces && typeof raw.workspaces === "object"
255
+ ? { ...raw.workspaces }
256
+ : {};
216
257
  return { repoRoot, registry: { version: 1, workspaces } };
217
258
  }
218
259
 
219
- export async function saveWorkspaceRegistry(startDir: string, alias: string, targetDir: string): Promise<void> {
260
+ export async function saveWorkspaceRegistry(
261
+ startDir: string,
262
+ alias: string,
263
+ targetDir: string,
264
+ ): Promise<void> {
220
265
  const { repoRoot, registry } = await loadWorkspaceRegistry(startDir);
221
266
  registry.workspaces[alias] = relative(repoRoot, targetDir) || ".";
222
267
  await mkdir(sonarqubeConfigDir(repoRoot), { recursive: true });
223
- await writeFile(workspaceRegistryPath(repoRoot), JSON.stringify(registry, null, 2) + "\n", "utf8");
268
+ await writeFile(
269
+ workspaceRegistryPath(repoRoot),
270
+ JSON.stringify(registry, null, 2) + "\n",
271
+ "utf8",
272
+ );
224
273
  }
225
274
 
226
275
  export function looksLikePath(token: string): boolean {
@@ -237,10 +286,15 @@ export function looksLikePath(token: string): boolean {
237
286
  }
238
287
 
239
288
  export function knownTargets(registry: SonarWorkspaceRegistry): string[] {
240
- return Object.keys(registry.workspaces).sort((left, right) => left.localeCompare(right));
289
+ return Object.keys(registry.workspaces).sort((left, right) =>
290
+ left.localeCompare(right),
291
+ );
241
292
  }
242
293
 
243
- export async function resolveTarget(ctx: { cwd: string }, targetInput?: string): Promise<ResolvedTarget> {
294
+ export async function resolveTarget(
295
+ ctx: { cwd: string },
296
+ targetInput?: string,
297
+ ): Promise<ResolvedTarget> {
244
298
  const { repoRoot, registry } = await loadWorkspaceRegistry(ctx.cwd);
245
299
  if (!targetInput) {
246
300
  return { baseDir: ctx.cwd, repoRoot };
@@ -251,7 +305,9 @@ export async function resolveTarget(ctx: { cwd: string }, targetInput?: string):
251
305
  const baseDir = resolve(repoRoot, aliasTarget);
252
306
  const baseDirStat = await stat(baseDir).catch(() => undefined);
253
307
  if (!baseDirStat?.isDirectory()) {
254
- throw new Error(`SonarQube target "${targetInput}" points to a missing directory: ${baseDir}`);
308
+ throw new Error(
309
+ `SonarQube target "${targetInput}" points to a missing directory: ${baseDir}`,
310
+ );
255
311
  }
256
312
  return { baseDir, repoRoot, alias: targetInput };
257
313
  }
@@ -271,7 +327,9 @@ export async function resolveTarget(ctx: { cwd: string }, targetInput?: string):
271
327
  `Unknown SonarQube target "${targetInput}". Known targets: ${known.join(", ")}. Use /sonarqube init <alias> <path> to add one.`,
272
328
  );
273
329
  }
274
- throw new Error(`Unknown SonarQube target "${targetInput}". Use /sonarqube init <alias> <path> to add one.`);
330
+ throw new Error(
331
+ `Unknown SonarQube target "${targetInput}". Use /sonarqube init <alias> <path> to add one.`,
332
+ );
275
333
  }
276
334
 
277
335
  export async function resolveInitTarget(
@@ -302,7 +360,10 @@ interface ResolveCtx {
302
360
  signal?: AbortSignal;
303
361
  }
304
362
 
305
- export async function resolveConfig(ctx: ResolveCtx, inputPath?: string): Promise<SonarProjectConfig> {
363
+ export async function resolveConfig(
364
+ ctx: ResolveCtx,
365
+ inputPath?: string,
366
+ ): Promise<SonarProjectConfig> {
306
367
  const { baseDir } = await resolveTarget(ctx, inputPath);
307
368
  const propertiesPath = resolve(baseDir, "sonar-project.properties");
308
369
  const propertiesText = await readOptionalText(propertiesPath);