@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/README.md +2 -2
- package/dist/api.d.ts +12 -2
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +116 -26
- package/dist/api.js.map +1 -1
- package/dist/commands.d.ts +2 -1
- package/dist/commands.d.ts.map +1 -1
- package/dist/commands.js +285 -30
- package/dist/commands.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +13 -2
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +182 -52
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +3 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +26 -4
- package/dist/types.js.map +1 -1
- package/dist/ui.d.ts +29 -13
- package/dist/ui.d.ts.map +1 -1
- package/dist/ui.js +160 -70
- package/dist/ui.js.map +1 -1
- package/package.json +1 -1
- package/src/api.ts +310 -59
- package/src/commands.ts +454 -66
- package/src/config.ts +84 -23
- package/src/index.ts +462 -94
- package/src/types.ts +29 -4
- package/src/ui.ts +271 -85
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 {
|
|
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(
|
|
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(
|
|
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>(
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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(
|
|
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(
|
|
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") ||
|
|
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(
|
|
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(
|
|
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(
|
|
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>>(
|
|
215
|
-
|
|
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(
|
|
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(
|
|
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) =>
|
|
289
|
+
return Object.keys(registry.workspaces).sort((left, right) =>
|
|
290
|
+
left.localeCompare(right),
|
|
291
|
+
);
|
|
241
292
|
}
|
|
242
293
|
|
|
243
|
-
export async function resolveTarget(
|
|
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(
|
|
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(
|
|
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(
|
|
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);
|