@vibgrate/cli 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.
@@ -0,0 +1,3697 @@
1
+ // src/utils/fs.ts
2
+ import * as fs from "fs/promises";
3
+ import * as path from "path";
4
+ var SKIP_DIRS = /* @__PURE__ */ new Set([
5
+ "node_modules",
6
+ ".git",
7
+ ".next",
8
+ "dist",
9
+ "build",
10
+ "out",
11
+ ".turbo",
12
+ ".cache",
13
+ "coverage",
14
+ "bin",
15
+ "obj",
16
+ ".vs",
17
+ "packages",
18
+ "TestResults"
19
+ ]);
20
+ async function findFiles(rootDir, predicate) {
21
+ const results = [];
22
+ async function walk(dir) {
23
+ let entries;
24
+ try {
25
+ const dirents = await fs.readdir(dir, { withFileTypes: true });
26
+ entries = dirents.map((d) => ({
27
+ name: d.name,
28
+ isDirectory: d.isDirectory(),
29
+ isFile: d.isFile()
30
+ }));
31
+ } catch {
32
+ return;
33
+ }
34
+ for (const e of entries) {
35
+ if (e.isDirectory) {
36
+ if (SKIP_DIRS.has(e.name)) continue;
37
+ await walk(path.join(dir, e.name));
38
+ } else if (e.isFile && predicate(e.name)) {
39
+ results.push(path.join(dir, e.name));
40
+ }
41
+ }
42
+ }
43
+ await walk(rootDir);
44
+ return results;
45
+ }
46
+ async function findPackageJsonFiles(rootDir) {
47
+ return findFiles(rootDir, (name) => name === "package.json");
48
+ }
49
+ async function findSolutionFiles(rootDir) {
50
+ return findFiles(rootDir, (name) => name.endsWith(".sln"));
51
+ }
52
+ async function findCsprojFiles(rootDir) {
53
+ return findFiles(rootDir, (name) => name.endsWith(".csproj"));
54
+ }
55
+ async function readJsonFile(filePath) {
56
+ const txt = await fs.readFile(filePath, "utf8");
57
+ return JSON.parse(txt);
58
+ }
59
+ async function readTextFile(filePath) {
60
+ return fs.readFile(filePath, "utf8");
61
+ }
62
+ async function pathExists(p) {
63
+ try {
64
+ await fs.access(p);
65
+ return true;
66
+ } catch {
67
+ return false;
68
+ }
69
+ }
70
+ async function ensureDir(dir) {
71
+ await fs.mkdir(dir, { recursive: true });
72
+ }
73
+ async function writeJsonFile(filePath, data) {
74
+ await ensureDir(path.dirname(filePath));
75
+ await fs.writeFile(filePath, JSON.stringify(data, null, 2) + "\n", "utf8");
76
+ }
77
+ async function writeTextFile(filePath, content) {
78
+ await ensureDir(path.dirname(filePath));
79
+ await fs.writeFile(filePath, content, "utf8");
80
+ }
81
+
82
+ // src/scoring/drift-score.ts
83
+ var DEFAULT_THRESHOLDS = {
84
+ failOnError: {
85
+ eolDays: 180,
86
+ frameworkMajorLag: 3,
87
+ dependencyTwoPlusPercent: 50
88
+ },
89
+ warn: {
90
+ frameworkMajorLag: 2,
91
+ dependencyTwoPlusPercent: 30
92
+ }
93
+ };
94
+ function clamp(val, min, max) {
95
+ return Math.min(max, Math.max(min, val));
96
+ }
97
+ function runtimeScore(projects) {
98
+ if (projects.length === 0) return 100;
99
+ const lags = projects.map((p) => p.runtimeMajorsBehind).filter((v) => v !== void 0);
100
+ if (lags.length === 0) return 100;
101
+ const maxLag = Math.max(...lags);
102
+ if (maxLag === 0) return 100;
103
+ if (maxLag === 1) return 80;
104
+ if (maxLag === 2) return 50;
105
+ if (maxLag === 3) return 20;
106
+ return 0;
107
+ }
108
+ function frameworkScore(projects) {
109
+ const allFrameworks = projects.flatMap((p) => p.frameworks);
110
+ if (allFrameworks.length === 0) return 100;
111
+ const lags = allFrameworks.map((f) => f.majorsBehind).filter((v) => v !== null);
112
+ if (lags.length === 0) return 100;
113
+ const maxLag = Math.max(...lags);
114
+ const avgLag = lags.reduce((a, b) => a + b, 0) / lags.length;
115
+ const maxPenalty = Math.min(maxLag * 20, 100);
116
+ const avgPenalty = Math.min(avgLag * 15, 100);
117
+ return clamp(100 - (maxPenalty * 0.6 + avgPenalty * 0.4), 0, 100);
118
+ }
119
+ function dependencyScore(projects) {
120
+ let totalCurrent = 0;
121
+ let totalOne = 0;
122
+ let totalTwo = 0;
123
+ let totalUnknown = 0;
124
+ for (const p of projects) {
125
+ totalCurrent += p.dependencyAgeBuckets.current;
126
+ totalOne += p.dependencyAgeBuckets.oneBehind;
127
+ totalTwo += p.dependencyAgeBuckets.twoPlusBehind;
128
+ totalUnknown += p.dependencyAgeBuckets.unknown;
129
+ }
130
+ const total = totalCurrent + totalOne + totalTwo;
131
+ if (total === 0) return 100;
132
+ const currentPct = totalCurrent / total;
133
+ const onePct = totalOne / total;
134
+ const twoPct = totalTwo / total;
135
+ return clamp(Math.round(currentPct * 100 - onePct * 10 - twoPct * 40), 0, 100);
136
+ }
137
+ function eolScore(projects) {
138
+ let score = 100;
139
+ for (const p of projects) {
140
+ if (p.type === "node" && p.runtimeMajorsBehind !== void 0) {
141
+ if (p.runtimeMajorsBehind >= 3) score = Math.min(score, 0);
142
+ else if (p.runtimeMajorsBehind >= 2) score = Math.min(score, 30);
143
+ else if (p.runtimeMajorsBehind >= 1) score = Math.min(score, 70);
144
+ }
145
+ if (p.type === "dotnet" && p.runtimeMajorsBehind !== void 0) {
146
+ if (p.runtimeMajorsBehind >= 3) score = Math.min(score, 0);
147
+ else if (p.runtimeMajorsBehind >= 2) score = Math.min(score, 20);
148
+ else if (p.runtimeMajorsBehind >= 1) score = Math.min(score, 60);
149
+ }
150
+ }
151
+ return score;
152
+ }
153
+ function computeDriftScore(projects) {
154
+ const rs = runtimeScore(projects);
155
+ const fs5 = frameworkScore(projects);
156
+ const ds = dependencyScore(projects);
157
+ const es = eolScore(projects);
158
+ const score = Math.round(rs * 0.25 + fs5 * 0.25 + ds * 0.3 + es * 0.2);
159
+ let riskLevel;
160
+ if (score >= 70) riskLevel = "low";
161
+ else if (score >= 40) riskLevel = "moderate";
162
+ else riskLevel = "high";
163
+ return {
164
+ score,
165
+ riskLevel,
166
+ components: {
167
+ runtimeScore: rs,
168
+ frameworkScore: fs5,
169
+ dependencyScore: ds,
170
+ eolScore: es
171
+ }
172
+ };
173
+ }
174
+ function generateFindings(projects, config) {
175
+ const thresholds = {
176
+ failOnError: { ...DEFAULT_THRESHOLDS.failOnError, ...config?.thresholds?.failOnError },
177
+ warn: { ...DEFAULT_THRESHOLDS.warn, ...config?.thresholds?.warn }
178
+ };
179
+ const findings = [];
180
+ for (const project of projects) {
181
+ if (project.runtimeMajorsBehind !== void 0 && project.runtimeMajorsBehind >= 3) {
182
+ findings.push({
183
+ ruleId: "vibgrate/runtime-eol",
184
+ level: "error",
185
+ message: `${project.type === "node" ? "Node.js" : ".NET"} runtime "${project.runtime}" is ${project.runtimeMajorsBehind} major versions behind (latest: ${project.runtimeLatest}). Likely at or past EOL.`,
186
+ location: project.path
187
+ });
188
+ } else if (project.runtimeMajorsBehind !== void 0 && project.runtimeMajorsBehind >= 2) {
189
+ findings.push({
190
+ ruleId: "vibgrate/runtime-lag",
191
+ level: "warning",
192
+ message: `${project.type === "node" ? "Node.js" : ".NET"} runtime "${project.runtime}" is ${project.runtimeMajorsBehind} major versions behind (latest: ${project.runtimeLatest}).`,
193
+ location: project.path
194
+ });
195
+ }
196
+ for (const fw of project.frameworks) {
197
+ if (fw.majorsBehind !== null && thresholds.failOnError.frameworkMajorLag !== void 0 && fw.majorsBehind >= thresholds.failOnError.frameworkMajorLag) {
198
+ findings.push({
199
+ ruleId: "vibgrate/framework-major-lag",
200
+ level: "error",
201
+ message: `${fw.name} is ${fw.majorsBehind} major versions behind (current: ${fw.currentVersion}, latest: ${fw.latestVersion}).`,
202
+ location: project.path
203
+ });
204
+ } else if (fw.majorsBehind !== null && thresholds.warn.frameworkMajorLag !== void 0 && fw.majorsBehind >= thresholds.warn.frameworkMajorLag) {
205
+ findings.push({
206
+ ruleId: "vibgrate/framework-major-lag",
207
+ level: "warning",
208
+ message: `${fw.name} is ${fw.majorsBehind} major versions behind (current: ${fw.currentVersion}, latest: ${fw.latestVersion}).`,
209
+ location: project.path
210
+ });
211
+ }
212
+ }
213
+ const totalDeps = project.dependencyAgeBuckets.current + project.dependencyAgeBuckets.oneBehind + project.dependencyAgeBuckets.twoPlusBehind;
214
+ if (totalDeps > 0) {
215
+ const twoPlusPct = project.dependencyAgeBuckets.twoPlusBehind / totalDeps * 100;
216
+ if (thresholds.failOnError.dependencyTwoPlusPercent !== void 0 && twoPlusPct >= thresholds.failOnError.dependencyTwoPlusPercent) {
217
+ findings.push({
218
+ ruleId: "vibgrate/dependency-rot",
219
+ level: "error",
220
+ message: `${Math.round(twoPlusPct)}% of dependencies are 2+ major versions behind in ${project.name}.`,
221
+ location: project.path
222
+ });
223
+ } else if (thresholds.warn.dependencyTwoPlusPercent !== void 0 && twoPlusPct >= thresholds.warn.dependencyTwoPlusPercent) {
224
+ findings.push({
225
+ ruleId: "vibgrate/dependency-rot",
226
+ level: "warning",
227
+ message: `${Math.round(twoPlusPct)}% of dependencies are 2+ major versions behind in ${project.name}.`,
228
+ location: project.path
229
+ });
230
+ }
231
+ }
232
+ for (const dep of project.dependencies) {
233
+ if (dep.majorsBehind !== null && dep.majorsBehind >= 3) {
234
+ findings.push({
235
+ ruleId: "vibgrate/dependency-major-lag",
236
+ level: "error",
237
+ message: `${dep.package} is ${dep.majorsBehind} major versions behind (spec: ${dep.currentSpec}, latest: ${dep.latestStable}).`,
238
+ location: project.path
239
+ });
240
+ }
241
+ }
242
+ }
243
+ return findings;
244
+ }
245
+
246
+ // src/formatters/text.ts
247
+ import chalk from "chalk";
248
+ function formatText(artifact) {
249
+ const lines = [];
250
+ lines.push("");
251
+ lines.push(chalk.bold.cyan("\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
252
+ lines.push(chalk.bold.cyan("\u2551 Vibgrate Drift Report \u2551"));
253
+ lines.push(chalk.bold.cyan("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
254
+ lines.push("");
255
+ const scoreColor = artifact.drift.score >= 70 ? chalk.green : artifact.drift.score >= 40 ? chalk.yellow : chalk.red;
256
+ lines.push(chalk.bold(" Drift Score: ") + scoreColor.bold(`${artifact.drift.score}/100`));
257
+ lines.push(chalk.bold(" Risk Level: ") + riskBadge(artifact.drift.riskLevel));
258
+ lines.push(chalk.bold(" Projects: ") + `${artifact.projects.length}`);
259
+ if (artifact.vcs) {
260
+ const vcsParts = [artifact.vcs.type];
261
+ if (artifact.vcs.branch) vcsParts.push(artifact.vcs.branch);
262
+ if (artifact.vcs.shortSha) vcsParts.push(chalk.dim(artifact.vcs.shortSha));
263
+ lines.push(chalk.bold(" VCS: ") + vcsParts.join(" "));
264
+ }
265
+ lines.push("");
266
+ lines.push(chalk.bold.underline(" Score Breakdown"));
267
+ lines.push(` Runtime: ${scoreBar(artifact.drift.components.runtimeScore)}`);
268
+ lines.push(` Frameworks: ${scoreBar(artifact.drift.components.frameworkScore)}`);
269
+ lines.push(` Dependencies: ${scoreBar(artifact.drift.components.dependencyScore)}`);
270
+ lines.push(` EOL Risk: ${scoreBar(artifact.drift.components.eolScore)}`);
271
+ lines.push("");
272
+ for (const project of artifact.projects) {
273
+ lines.push(chalk.bold(` \u2500\u2500 ${project.name} `) + chalk.dim(`(${project.type}) ${project.path}`));
274
+ if (project.runtime) {
275
+ const behindStr = project.runtimeMajorsBehind !== void 0 && project.runtimeMajorsBehind > 0 ? chalk.yellow(` (${project.runtimeMajorsBehind} major${project.runtimeMajorsBehind > 1 ? "s" : ""} behind)`) : chalk.green(" (current)");
276
+ lines.push(` Runtime: ${project.runtime}${behindStr}`);
277
+ }
278
+ if (project.targetFramework) {
279
+ lines.push(` Target: ${project.targetFramework}`);
280
+ }
281
+ if (project.frameworks.length > 0) {
282
+ lines.push(" Frameworks:");
283
+ for (const fw of project.frameworks) {
284
+ const lag = fw.majorsBehind !== null ? fw.majorsBehind === 0 ? chalk.green("current") : chalk.yellow(`${fw.majorsBehind} behind`) : chalk.dim("unknown");
285
+ lines.push(` ${fw.name}: ${fw.currentVersion ?? "?"} \u2192 ${fw.latestVersion ?? "?"} (${lag})`);
286
+ }
287
+ }
288
+ const b = project.dependencyAgeBuckets;
289
+ const total = b.current + b.oneBehind + b.twoPlusBehind + b.unknown;
290
+ if (total > 0) {
291
+ lines.push(" Dependencies:");
292
+ lines.push(` ${chalk.green(`${b.current} current`)} ${chalk.yellow(`${b.oneBehind} 1-behind`)} ${chalk.red(`${b.twoPlusBehind} 2+ behind`)} ${chalk.dim(`${b.unknown} unknown`)}`);
293
+ }
294
+ lines.push("");
295
+ }
296
+ if (artifact.findings.length > 0) {
297
+ lines.push(chalk.bold.underline(" Findings"));
298
+ for (const f of artifact.findings) {
299
+ const icon = f.level === "error" ? chalk.red("\u2716") : f.level === "warning" ? chalk.yellow("\u26A0") : chalk.blue("\u2139");
300
+ lines.push(` ${icon} ${f.message}`);
301
+ lines.push(chalk.dim(` ${f.ruleId} in ${f.location}`));
302
+ }
303
+ lines.push("");
304
+ }
305
+ if (artifact.delta !== void 0) {
306
+ const deltaStr = artifact.delta > 0 ? chalk.green(`+${artifact.delta}`) : artifact.delta < 0 ? chalk.red(`${artifact.delta}`) : chalk.dim("0");
307
+ lines.push(chalk.bold(" Drift Delta: ") + deltaStr + " (vs baseline)");
308
+ lines.push("");
309
+ }
310
+ if (artifact.extended) {
311
+ lines.push(...formatExtended(artifact.extended));
312
+ }
313
+ lines.push(chalk.dim(` Scanned at ${artifact.timestamp}`));
314
+ lines.push("");
315
+ return lines.join("\n");
316
+ }
317
+ function riskBadge(level) {
318
+ switch (level) {
319
+ case "low":
320
+ return chalk.bgGreen.black(" LOW ");
321
+ case "moderate":
322
+ return chalk.bgYellow.black(" MODERATE ");
323
+ case "high":
324
+ return chalk.bgRed.white(" HIGH ");
325
+ default:
326
+ return level;
327
+ }
328
+ }
329
+ function scoreBar(score) {
330
+ const width = 20;
331
+ const filled = Math.round(score / 100 * width);
332
+ const empty = width - filled;
333
+ const color = score >= 70 ? chalk.green : score >= 40 ? chalk.yellow : chalk.red;
334
+ return color("\u2588".repeat(filled)) + chalk.dim("\u2591".repeat(empty)) + ` ${score}`;
335
+ }
336
+ var CATEGORY_LABELS = {
337
+ frontend: "Frontend",
338
+ metaFrameworks: "Meta-frameworks",
339
+ bundlers: "Bundlers",
340
+ css: "CSS / UI",
341
+ backend: "Backend",
342
+ orm: "ORM / Database",
343
+ testing: "Testing",
344
+ lintFormat: "Lint & Format",
345
+ apiMessaging: "API & Messaging",
346
+ observability: "Observability",
347
+ payment: "Payment",
348
+ auth: "Auth",
349
+ email: "Email",
350
+ cloud: "Cloud",
351
+ databases: "Databases",
352
+ messaging: "Messaging",
353
+ crm: "CRM & Comms",
354
+ storage: "Storage",
355
+ search: "Search & AI"
356
+ };
357
+ function formatExtended(ext) {
358
+ const lines = [];
359
+ if (ext.toolingInventory) {
360
+ const inv = ext.toolingInventory;
361
+ const categories = Object.entries(inv).filter(([, items]) => items.length > 0);
362
+ if (categories.length > 0) {
363
+ lines.push(chalk.bold.underline(" Tech Stack"));
364
+ for (const [cat, items] of categories) {
365
+ const label = CATEGORY_LABELS[cat] ?? cat;
366
+ const names = items.map((i) => chalk.white(i.name)).join(chalk.dim(", "));
367
+ lines.push(` ${chalk.cyan(label)}: ${names}`);
368
+ }
369
+ lines.push("");
370
+ }
371
+ }
372
+ if (ext.serviceDependencies) {
373
+ const svc = ext.serviceDependencies;
374
+ const categories = Object.entries(svc).filter(([, items]) => items.length > 0);
375
+ if (categories.length > 0) {
376
+ lines.push(chalk.bold.underline(" Services & Integrations"));
377
+ for (const [cat, items] of categories) {
378
+ const label = CATEGORY_LABELS[cat] ?? cat;
379
+ const names = items.map((i) => {
380
+ const ver = i.version ? chalk.dim(` ${i.version}`) : "";
381
+ return chalk.white(i.name) + ver;
382
+ }).join(chalk.dim(", "));
383
+ lines.push(` ${chalk.cyan(label)}: ${names}`);
384
+ }
385
+ lines.push("");
386
+ }
387
+ }
388
+ if (ext.breakingChangeExposure) {
389
+ const bc = ext.breakingChangeExposure;
390
+ if (bc.deprecatedPackages.length > 0 || bc.legacyPolyfills.length > 0) {
391
+ lines.push(chalk.bold.underline(" Breaking Change Exposure"));
392
+ const exposureColor = bc.exposureScore >= 40 ? chalk.red : bc.exposureScore >= 20 ? chalk.yellow : chalk.green;
393
+ lines.push(` Exposure Score: ${exposureColor.bold(`${bc.exposureScore}/100`)}`);
394
+ if (bc.deprecatedPackages.length > 0) {
395
+ lines.push(` ${chalk.red("Deprecated")}: ${bc.deprecatedPackages.map((p) => chalk.dim(p)).join(", ")}`);
396
+ }
397
+ if (bc.legacyPolyfills.length > 0) {
398
+ lines.push(` ${chalk.yellow("Polyfills")}: ${bc.legacyPolyfills.map((p) => chalk.dim(p)).join(", ")}`);
399
+ }
400
+ if (bc.peerConflictsDetected) {
401
+ lines.push(` ${chalk.red("\u26A0")} Peer dependency conflicts detected`);
402
+ }
403
+ lines.push("");
404
+ }
405
+ }
406
+ if (ext.tsModernity && ext.tsModernity.typescriptVersion) {
407
+ const ts = ext.tsModernity;
408
+ lines.push(chalk.bold.underline(" TypeScript"));
409
+ const parts = [];
410
+ parts.push(`v${ts.typescriptVersion}`);
411
+ if (ts.strict === true) parts.push(chalk.green("strict \u2714"));
412
+ else if (ts.strict === false) parts.push(chalk.yellow("strict \u2716"));
413
+ if (ts.moduleType) parts.push(ts.moduleType.toUpperCase());
414
+ if (ts.target) parts.push(`target: ${ts.target}`);
415
+ lines.push(` ${parts.join(chalk.dim(" \xB7 "))}`);
416
+ lines.push("");
417
+ }
418
+ if (ext.buildDeploy) {
419
+ const bd = ext.buildDeploy;
420
+ const hasSomething = bd.ci.length > 0 || bd.docker.dockerfileCount > 0 || bd.packageManagers.length > 0;
421
+ if (hasSomething) {
422
+ lines.push(chalk.bold.underline(" Build & Deploy"));
423
+ if (bd.ci.length > 0) lines.push(` CI: ${bd.ci.join(", ")}`);
424
+ if (bd.docker.dockerfileCount > 0) {
425
+ lines.push(` Docker: ${bd.docker.dockerfileCount} Dockerfile${bd.docker.dockerfileCount !== 1 ? "s" : ""} (${bd.docker.baseImages.join(", ")})`);
426
+ }
427
+ if (bd.packageManagers.length > 0) lines.push(` Package Managers: ${bd.packageManagers.join(", ")}`);
428
+ if (bd.monorepoTools.length > 0) lines.push(` Monorepo: ${bd.monorepoTools.join(", ")}`);
429
+ if (bd.iac.length > 0) lines.push(` IaC: ${bd.iac.join(", ")}`);
430
+ lines.push("");
431
+ }
432
+ }
433
+ if (ext.securityPosture) {
434
+ const sec = ext.securityPosture;
435
+ lines.push(chalk.bold.underline(" Security Posture"));
436
+ const checks = [];
437
+ checks.push(sec.lockfilePresent ? chalk.green("Lockfile \u2714") : chalk.red("Lockfile \u2716"));
438
+ checks.push(sec.gitignoreCoversEnv ? chalk.green(".env \u2714") : chalk.red(".env \u2716"));
439
+ checks.push(sec.gitignoreCoversNodeModules ? chalk.green("node_modules \u2714") : chalk.yellow("node_modules \u2716"));
440
+ if (sec.multipleLockfileTypes) checks.push(chalk.yellow("Multiple lockfiles \u26A0"));
441
+ if (sec.envFilesTracked) checks.push(chalk.red("Env files tracked \u2716"));
442
+ lines.push(` ${checks.join(chalk.dim(" \xB7 "))}`);
443
+ lines.push("");
444
+ }
445
+ if (ext.platformMatrix) {
446
+ const pm = ext.platformMatrix;
447
+ if (pm.nativeModules.length > 0 || pm.dockerBaseImages.length > 0) {
448
+ lines.push(chalk.bold.underline(" Platform"));
449
+ if (pm.nativeModules.length > 0) {
450
+ lines.push(` Native modules: ${pm.nativeModules.map((m) => chalk.dim(m)).join(", ")}`);
451
+ }
452
+ if (pm.osAssumptions.length > 0) {
453
+ lines.push(` OS assumptions: ${pm.osAssumptions.join(", ")}`);
454
+ }
455
+ lines.push("");
456
+ }
457
+ }
458
+ if (ext.dependencyGraph) {
459
+ const dg = ext.dependencyGraph;
460
+ if (dg.lockfileType) {
461
+ lines.push(chalk.bold.underline(" Dependency Graph"));
462
+ lines.push(` ${dg.lockfileType}: ${chalk.white(`${dg.totalUnique}`)} unique, ${chalk.white(`${dg.totalInstalled}`)} installed`);
463
+ if (dg.duplicatedPackages.length > 0) {
464
+ lines.push(` ${chalk.yellow(`${dg.duplicatedPackages.length} duplicated`)} packages`);
465
+ }
466
+ if (dg.phantomDependencies.length > 0) {
467
+ lines.push(` ${chalk.red(`${dg.phantomDependencies.length} phantom`)} dependencies`);
468
+ }
469
+ lines.push("");
470
+ }
471
+ }
472
+ return lines;
473
+ }
474
+
475
+ // src/formatters/sarif.ts
476
+ function formatSarif(artifact) {
477
+ const rules = buildRules(artifact.findings);
478
+ const results = artifact.findings.map((f) => toSarifResult(f));
479
+ return {
480
+ $schema: "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/main/sarif-2.1/schema/sarif-schema-2.1.0.json",
481
+ version: "2.1.0",
482
+ runs: [
483
+ {
484
+ tool: {
485
+ driver: {
486
+ name: "vibgrate",
487
+ version: artifact.vibgrateVersion,
488
+ informationUri: "https://vibgrate.com",
489
+ rules
490
+ }
491
+ },
492
+ results,
493
+ invocations: [
494
+ {
495
+ executionSuccessful: true,
496
+ startTimeUtc: artifact.timestamp
497
+ }
498
+ ]
499
+ }
500
+ ]
501
+ };
502
+ }
503
+ function buildRules(findings) {
504
+ const ruleIds = [...new Set(findings.map((f) => f.ruleId))];
505
+ return ruleIds.map((id) => {
506
+ const descriptions = {
507
+ "vibgrate/runtime-eol": {
508
+ id: "vibgrate/runtime-eol",
509
+ shortDescription: { text: "Runtime at or past end-of-life" },
510
+ helpUri: "https://vibgrate.com/rules/runtime-eol"
511
+ },
512
+ "vibgrate/runtime-lag": {
513
+ id: "vibgrate/runtime-lag",
514
+ shortDescription: { text: "Runtime major version lag" },
515
+ helpUri: "https://vibgrate.com/rules/runtime-lag"
516
+ },
517
+ "vibgrate/framework-major-lag": {
518
+ id: "vibgrate/framework-major-lag",
519
+ shortDescription: { text: "Framework major version behind latest" },
520
+ helpUri: "https://vibgrate.com/rules/framework-major-lag"
521
+ },
522
+ "vibgrate/dependency-rot": {
523
+ id: "vibgrate/dependency-rot",
524
+ shortDescription: { text: "High percentage of outdated dependencies" },
525
+ helpUri: "https://vibgrate.com/rules/dependency-rot"
526
+ },
527
+ "vibgrate/dependency-major-lag": {
528
+ id: "vibgrate/dependency-major-lag",
529
+ shortDescription: { text: "Individual dependency severely behind" },
530
+ helpUri: "https://vibgrate.com/rules/dependency-major-lag"
531
+ }
532
+ };
533
+ return descriptions[id] ?? {
534
+ id,
535
+ shortDescription: { text: id },
536
+ helpUri: "https://vibgrate.com"
537
+ };
538
+ });
539
+ }
540
+ function toSarifResult(finding) {
541
+ return {
542
+ ruleId: finding.ruleId,
543
+ level: finding.level === "error" ? "error" : finding.level === "warning" ? "warning" : "note",
544
+ message: { text: finding.message },
545
+ locations: [
546
+ {
547
+ physicalLocation: {
548
+ artifactLocation: {
549
+ uri: finding.location
550
+ }
551
+ }
552
+ }
553
+ ]
554
+ };
555
+ }
556
+
557
+ // src/version.ts
558
+ import { createRequire } from "module";
559
+ var require2 = createRequire(import.meta.url);
560
+ var pkg = require2("../package.json");
561
+ var VERSION = pkg.version;
562
+
563
+ // src/commands/scan.ts
564
+ import * as path12 from "path";
565
+ import { Command } from "commander";
566
+ import chalk3 from "chalk";
567
+
568
+ // src/scanners/node-scanner.ts
569
+ import * as path2 from "path";
570
+ import * as semver2 from "semver";
571
+
572
+ // src/scanners/npm-cache.ts
573
+ import { spawn } from "child_process";
574
+ import * as semver from "semver";
575
+ function stableOnly(versions) {
576
+ return versions.filter((v) => semver.valid(v) && semver.prerelease(v) === null);
577
+ }
578
+ function maxStable(versions) {
579
+ const stable = stableOnly(versions);
580
+ if (stable.length === 0) return null;
581
+ return stable.sort(semver.rcompare)[0] ?? null;
582
+ }
583
+ async function npmViewJson(args, cwd) {
584
+ return new Promise((resolve4, reject) => {
585
+ const child = spawn("npm", ["view", ...args, "--json"], {
586
+ cwd,
587
+ stdio: ["ignore", "pipe", "pipe"]
588
+ });
589
+ let out = "";
590
+ let err = "";
591
+ child.stdout.on("data", (d) => out += String(d));
592
+ child.stderr.on("data", (d) => err += String(d));
593
+ child.on("error", reject);
594
+ child.on("close", (code) => {
595
+ if (code !== 0) {
596
+ reject(new Error(`npm view ${args.join(" ")} failed (code=${code}): ${err.trim()}`));
597
+ return;
598
+ }
599
+ const trimmed = out.trim();
600
+ if (!trimmed) {
601
+ resolve4(null);
602
+ return;
603
+ }
604
+ try {
605
+ resolve4(JSON.parse(trimmed));
606
+ } catch {
607
+ resolve4(trimmed.replace(/^"|"$/g, ""));
608
+ }
609
+ });
610
+ });
611
+ }
612
+ var NpmCache = class {
613
+ constructor(cwd, sem) {
614
+ this.cwd = cwd;
615
+ this.sem = sem;
616
+ }
617
+ meta = /* @__PURE__ */ new Map();
618
+ get(pkg2) {
619
+ const existing = this.meta.get(pkg2);
620
+ if (existing) return existing;
621
+ const p = this.sem.run(async () => {
622
+ let latest = null;
623
+ try {
624
+ const dist = await npmViewJson([pkg2, "dist-tags"], this.cwd);
625
+ if (dist && typeof dist === "object" && typeof dist.latest === "string") {
626
+ latest = dist.latest;
627
+ }
628
+ } catch {
629
+ }
630
+ let versions = [];
631
+ try {
632
+ const v = await npmViewJson([pkg2, "versions"], this.cwd);
633
+ if (Array.isArray(v)) versions = v.map(String);
634
+ else if (typeof v === "string") versions = [v];
635
+ } catch {
636
+ }
637
+ const stable = stableOnly(versions);
638
+ const latestStableOverall = maxStable(stable);
639
+ if (!latest && latestStableOverall) latest = latestStableOverall;
640
+ return { latest, stableVersions: stable, latestStableOverall };
641
+ });
642
+ this.meta.set(pkg2, p);
643
+ return p;
644
+ }
645
+ };
646
+ function isSemverSpec(spec) {
647
+ const s = spec.trim();
648
+ if (!s) return false;
649
+ if (s.startsWith("workspace:")) return false;
650
+ if (s.startsWith("file:")) return false;
651
+ if (s.startsWith("link:")) return false;
652
+ if (s.startsWith("git+")) return false;
653
+ if (s.includes("://")) return false;
654
+ if (s.startsWith("github:")) return false;
655
+ if (s === "*" || s.toLowerCase() === "latest") return true;
656
+ return semver.validRange(s) !== null;
657
+ }
658
+
659
+ // src/scanners/node-scanner.ts
660
+ var KNOWN_FRAMEWORKS = {
661
+ // ── Frontend ──
662
+ "react": "React",
663
+ "react-dom": "React DOM",
664
+ "vue": "Vue",
665
+ "@angular/core": "Angular",
666
+ "svelte": "Svelte",
667
+ "solid-js": "Solid",
668
+ "preact": "Preact",
669
+ "lit": "Lit",
670
+ "qwik": "Qwik",
671
+ "htmx.org": "htmx",
672
+ "alpinejs": "Alpine.js",
673
+ "stimulus": "Stimulus",
674
+ // ── Meta-frameworks ──
675
+ "next": "Next.js",
676
+ "nuxt": "Nuxt",
677
+ "@remix-run/react": "Remix",
678
+ "@remix-run/node": "Remix (Node)",
679
+ "gatsby": "Gatsby",
680
+ "astro": "Astro",
681
+ "@sveltejs/kit": "SvelteKit",
682
+ "@analogjs/platform": "Analog",
683
+ "@tanstack/start": "TanStack Start",
684
+ // ── Backend ──
685
+ "express": "Express",
686
+ "fastify": "Fastify",
687
+ "@nestjs/core": "NestJS",
688
+ "hono": "Hono",
689
+ "koa": "Koa",
690
+ "@hapi/hapi": "Hapi",
691
+ "restify": "Restify",
692
+ "@elysiajs/eden": "Elysia",
693
+ "elysia": "Elysia",
694
+ "@adonisjs/core": "AdonisJS",
695
+ "moleculer": "Moleculer",
696
+ "@feathersjs/feathers": "Feathers",
697
+ "sails": "Sails",
698
+ // ── Language & Runtime ──
699
+ "typescript": "TypeScript",
700
+ // ── State Management ──
701
+ "redux": "Redux",
702
+ "@reduxjs/toolkit": "Redux Toolkit",
703
+ "zustand": "Zustand",
704
+ "mobx": "MobX",
705
+ "jotai": "Jotai",
706
+ "recoil": "Recoil",
707
+ "pinia": "Pinia",
708
+ "vuex": "Vuex",
709
+ "@tanstack/react-query": "TanStack Query",
710
+ "swr": "SWR",
711
+ "xstate": "XState",
712
+ "@ngrx/store": "NgRx",
713
+ // ── ORM & Database ──
714
+ "prisma": "Prisma",
715
+ "drizzle-orm": "Drizzle",
716
+ "typeorm": "TypeORM",
717
+ "sequelize": "Sequelize",
718
+ "@mikro-orm/core": "MikroORM",
719
+ "mongoose": "Mongoose",
720
+ "knex": "Knex",
721
+ "kysely": "Kysely",
722
+ "objection": "Objection.js",
723
+ // ── Bundlers ──
724
+ "vite": "Vite",
725
+ "webpack": "webpack",
726
+ "rollup": "Rollup",
727
+ "esbuild": "esbuild",
728
+ "parcel": "Parcel",
729
+ "turbo": "Turbo",
730
+ "tsup": "tsup",
731
+ "@swc/core": "SWC",
732
+ "bun": "Bun",
733
+ // ── Testing ──
734
+ "vitest": "Vitest",
735
+ "jest": "Jest",
736
+ "@playwright/test": "Playwright",
737
+ "cypress": "Cypress",
738
+ "mocha": "Mocha",
739
+ "ava": "AVA",
740
+ "storybook": "Storybook",
741
+ "@storybook/react": "Storybook"
742
+ };
743
+ async function scanNodeProjects(rootDir, npmCache) {
744
+ const packageJsonFiles = await findPackageJsonFiles(rootDir);
745
+ const results = [];
746
+ for (const pjPath of packageJsonFiles) {
747
+ try {
748
+ const scan = await scanOnePackageJson(pjPath, rootDir, npmCache);
749
+ results.push(scan);
750
+ } catch (e) {
751
+ const msg = e instanceof Error ? e.message : String(e);
752
+ console.error(`Error scanning ${pjPath}: ${msg}`);
753
+ }
754
+ }
755
+ return results;
756
+ }
757
+ async function scanOnePackageJson(packageJsonPath, rootDir, npmCache) {
758
+ const pj = await readJsonFile(packageJsonPath);
759
+ const absProjectPath = path2.dirname(packageJsonPath);
760
+ const projectPath = path2.relative(rootDir, absProjectPath) || ".";
761
+ const nodeEngine = pj.engines?.node ?? void 0;
762
+ let runtimeLatest;
763
+ let runtimeMajorsBehind;
764
+ if (nodeEngine) {
765
+ const latestLtsMajor = 22;
766
+ const parsed = semver2.minVersion(nodeEngine);
767
+ if (parsed) {
768
+ const currentMajor = semver2.major(parsed);
769
+ runtimeLatest = `${latestLtsMajor}.0.0`;
770
+ runtimeMajorsBehind = Math.max(0, latestLtsMajor - currentMajor);
771
+ }
772
+ }
773
+ const sections = [
774
+ { name: "dependencies", deps: pj.dependencies },
775
+ { name: "devDependencies", deps: pj.devDependencies },
776
+ { name: "peerDependencies", deps: pj.peerDependencies },
777
+ { name: "optionalDependencies", deps: pj.optionalDependencies }
778
+ ];
779
+ const dependencies = [];
780
+ const frameworks = [];
781
+ const buckets = { current: 0, oneBehind: 0, twoPlusBehind: 0, unknown: 0 };
782
+ const depEntries = [];
783
+ for (const s of sections) {
784
+ if (!s.deps) continue;
785
+ for (const [pkg2, spec] of Object.entries(s.deps)) {
786
+ if (!isSemverSpec(spec)) continue;
787
+ depEntries.push({ pkg: pkg2, section: s.name, spec });
788
+ }
789
+ }
790
+ const metaPromises = depEntries.map(async (entry) => {
791
+ const meta = await npmCache.get(entry.pkg);
792
+ return { ...entry, meta };
793
+ });
794
+ const resolved = await Promise.all(metaPromises);
795
+ for (const { pkg: pkg2, section, spec, meta } of resolved) {
796
+ const resolvedVersion = meta.stableVersions.length > 0 ? semver2.maxSatisfying(meta.stableVersions, spec) ?? null : null;
797
+ const latestStable = meta.latestStableOverall;
798
+ let majorsBehind = null;
799
+ let drift = "unknown";
800
+ if (resolvedVersion && latestStable) {
801
+ const currentMajor = semver2.major(resolvedVersion);
802
+ const latestMajor = semver2.major(latestStable);
803
+ majorsBehind = latestMajor - currentMajor;
804
+ if (majorsBehind === 0) {
805
+ drift = semver2.eq(resolvedVersion, latestStable) ? "current" : "minor-behind";
806
+ } else {
807
+ drift = "major-behind";
808
+ }
809
+ if (majorsBehind === 0) buckets.current++;
810
+ else if (majorsBehind === 1) buckets.oneBehind++;
811
+ else buckets.twoPlusBehind++;
812
+ } else {
813
+ buckets.unknown++;
814
+ }
815
+ dependencies.push({
816
+ package: pkg2,
817
+ section,
818
+ currentSpec: spec,
819
+ resolvedVersion,
820
+ latestStable,
821
+ majorsBehind,
822
+ drift
823
+ });
824
+ if (pkg2 in KNOWN_FRAMEWORKS) {
825
+ frameworks.push({
826
+ name: KNOWN_FRAMEWORKS[pkg2],
827
+ currentVersion: resolvedVersion,
828
+ latestVersion: latestStable,
829
+ majorsBehind
830
+ });
831
+ }
832
+ }
833
+ dependencies.sort((a, b) => {
834
+ const order = { "major-behind": 0, "minor-behind": 1, "current": 2, "unknown": 3 };
835
+ const diff = (order[a.drift] ?? 9) - (order[b.drift] ?? 9);
836
+ if (diff !== 0) return diff;
837
+ return a.package.localeCompare(b.package);
838
+ });
839
+ return {
840
+ type: "node",
841
+ path: projectPath,
842
+ name: pj.name ?? path2.basename(absProjectPath),
843
+ runtime: nodeEngine,
844
+ runtimeLatest,
845
+ runtimeMajorsBehind,
846
+ frameworks,
847
+ dependencies,
848
+ dependencyAgeBuckets: buckets
849
+ };
850
+ }
851
+
852
+ // src/scanners/dotnet-scanner.ts
853
+ import * as path3 from "path";
854
+ import { XMLParser } from "fast-xml-parser";
855
+ var parser = new XMLParser({
856
+ ignoreAttributes: false,
857
+ attributeNamePrefix: "@_"
858
+ });
859
+ var KNOWN_DOTNET_FRAMEWORKS = {
860
+ // ── ASP.NET Core & Web ──
861
+ "Microsoft.AspNetCore.App": "ASP.NET Core",
862
+ "Microsoft.AspNetCore.Mvc": "ASP.NET Core MVC",
863
+ "Microsoft.AspNetCore.Components": "Blazor",
864
+ "Microsoft.AspNetCore.Components.WebAssembly": "Blazor WASM",
865
+ "Microsoft.AspNetCore.SignalR": "SignalR",
866
+ "Microsoft.AspNetCore.OData": "OData",
867
+ "Microsoft.AspNetCore.Identity": "ASP.NET Identity",
868
+ "Microsoft.AspNetCore.Authentication.JwtBearer": "JWT Bearer Auth",
869
+ "Microsoft.AspNetCore.Diagnostics.HealthChecks": "Health Checks",
870
+ "Swashbuckle.AspNetCore": "Swashbuckle",
871
+ "NSwag.AspNetCore": "NSwag",
872
+ // ── Entity Framework & Data Access ──
873
+ "Microsoft.EntityFrameworkCore": "EF Core",
874
+ "Microsoft.EntityFrameworkCore.SqlServer": "EF Core SQL Server",
875
+ "Microsoft.EntityFrameworkCore.Sqlite": "EF Core SQLite",
876
+ "Microsoft.EntityFrameworkCore.Design": "EF Core Design",
877
+ "Microsoft.EntityFrameworkCore.Tools": "EF Core Tools",
878
+ "Npgsql.EntityFrameworkCore.PostgreSQL": "EF Core PostgreSQL",
879
+ "Pomelo.EntityFrameworkCore.MySql": "EF Core MySQL (Pomelo)",
880
+ "MongoDB.EntityFrameworkCore": "EF Core MongoDB",
881
+ "Dapper": "Dapper",
882
+ "Dapper.Contrib": "Dapper Contrib",
883
+ "NHibernate": "NHibernate",
884
+ "Npgsql": "Npgsql",
885
+ "MySqlConnector": "MySqlConnector",
886
+ "MongoDB.Driver": "MongoDB Driver",
887
+ "StackExchange.Redis": "StackExchange.Redis",
888
+ "Microsoft.Data.SqlClient": "SqlClient",
889
+ "Oracle.ManagedDataAccess.Core": "Oracle (Managed)",
890
+ "Cassandra": "Cassandra Driver",
891
+ "Neo4j.Driver": "Neo4j Driver",
892
+ "Marten": "Marten",
893
+ "LiteDB": "LiteDB",
894
+ "RavenDB.Client": "RavenDB",
895
+ // ── Hosting & Configuration ──
896
+ "Microsoft.Extensions.Hosting": ".NET Hosting",
897
+ "Microsoft.Extensions.DependencyInjection": ".NET DI",
898
+ "Microsoft.Extensions.Configuration": ".NET Configuration",
899
+ "Microsoft.Extensions.Logging": ".NET Logging",
900
+ "Microsoft.Extensions.Options": ".NET Options",
901
+ "Microsoft.Extensions.Caching.Memory": ".NET Memory Cache",
902
+ "Microsoft.Extensions.Caching.StackExchangeRedis": ".NET Redis Cache",
903
+ "Microsoft.Extensions.Http": ".NET HttpClientFactory",
904
+ "Microsoft.Extensions.Resilience": ".NET Resilience",
905
+ // ── CQRS & Mediator ──
906
+ "MediatR": "MediatR",
907
+ "Wolverine": "Wolverine",
908
+ "Brighter": "Brighter",
909
+ // ── Mapping ──
910
+ "AutoMapper": "AutoMapper",
911
+ "Mapster": "Mapster",
912
+ "Riok.Mapperly": "Mapperly",
913
+ // ── Validation ──
914
+ "FluentValidation": "FluentValidation",
915
+ "FluentValidation.AspNetCore": "FluentValidation ASP.NET",
916
+ // ── Serialization ──
917
+ "Newtonsoft.Json": "Newtonsoft.Json",
918
+ "System.Text.Json": "System.Text.Json",
919
+ "MessagePack": "MessagePack",
920
+ "protobuf-net": "protobuf-net",
921
+ "CsvHelper": "CsvHelper",
922
+ // ── Logging & Observability ──
923
+ "Serilog": "Serilog",
924
+ "Serilog.AspNetCore": "Serilog ASP.NET",
925
+ "Serilog.Sinks.Console": "Serilog Console",
926
+ "Serilog.Sinks.Seq": "Serilog Seq",
927
+ "Serilog.Sinks.File": "Serilog File",
928
+ "Serilog.Sinks.Elasticsearch": "Serilog Elasticsearch",
929
+ "NLog": "NLog",
930
+ "NLog.Web.AspNetCore": "NLog ASP.NET",
931
+ "log4net": "log4net",
932
+ "OpenTelemetry": "OpenTelemetry",
933
+ "OpenTelemetry.Extensions.Hosting": "OpenTelemetry Hosting",
934
+ "OpenTelemetry.Instrumentation.AspNetCore": "OpenTelemetry ASP.NET",
935
+ "OpenTelemetry.Exporter.Prometheus": "OpenTelemetry Prometheus",
936
+ "OpenTelemetry.Exporter.Jaeger": "OpenTelemetry Jaeger",
937
+ "OpenTelemetry.Exporter.OpenTelemetryProtocol": "OpenTelemetry OTLP",
938
+ "App.Metrics": "App.Metrics",
939
+ "prometheus-net": "Prometheus.NET",
940
+ "Elastic.Apm": "Elastic APM",
941
+ // ── Testing ──
942
+ "xunit": "xUnit",
943
+ "xunit.runner.visualstudio": "xUnit Runner",
944
+ "NUnit": "NUnit",
945
+ "NUnit3TestAdapter": "NUnit Adapter",
946
+ "MSTest.TestFramework": "MSTest",
947
+ "MSTest.TestAdapter": "MSTest Adapter",
948
+ "Moq": "Moq",
949
+ "NSubstitute": "NSubstitute",
950
+ "FakeItEasy": "FakeItEasy",
951
+ "FluentAssertions": "FluentAssertions",
952
+ "Shouldly": "Shouldly",
953
+ "Bogus": "Bogus",
954
+ "AutoFixture": "AutoFixture",
955
+ "WireMock.Net": "WireMock.Net",
956
+ "Testcontainers": "Testcontainers",
957
+ "Respawn": "Respawn",
958
+ "BenchmarkDotNet": "BenchmarkDotNet",
959
+ "coverlet.collector": "Coverlet",
960
+ "SpecFlow": "SpecFlow",
961
+ "TUnit": "TUnit",
962
+ "Verify.Xunit": "Verify",
963
+ "Snapshooter": "Snapshooter",
964
+ // ── Messaging & Event Bus ──
965
+ "MassTransit": "MassTransit",
966
+ "MassTransit.RabbitMQ": "MassTransit RabbitMQ",
967
+ "MassTransit.Azure.ServiceBus.Core": "MassTransit Azure SB",
968
+ "NServiceBus": "NServiceBus",
969
+ "RabbitMQ.Client": "RabbitMQ Client",
970
+ "Confluent.Kafka": "Confluent Kafka",
971
+ "Azure.Messaging.ServiceBus": "Azure Service Bus",
972
+ "Azure.Messaging.EventHubs": "Azure Event Hubs",
973
+ "Amazon.SQS": "AWS SQS",
974
+ "Amazon.SNS": "AWS SNS",
975
+ "Rebus": "Rebus",
976
+ "EasyNetQ": "EasyNetQ",
977
+ "SlimMessageBus": "SlimMessageBus",
978
+ "CAP": "DotNetCore.CAP",
979
+ // ── Cloud SDKs ──
980
+ "AWSSDK.Core": "AWS SDK Core",
981
+ "AWSSDK.S3": "AWS SDK S3",
982
+ "AWSSDK.SQS": "AWS SDK SQS",
983
+ "AWSSDK.DynamoDBv2": "AWS SDK DynamoDB",
984
+ "AWSSDK.Lambda": "AWS SDK Lambda",
985
+ "AWSSDK.SecretsManager": "AWS SDK Secrets Manager",
986
+ "AWSSDK.CloudWatch": "AWS SDK CloudWatch",
987
+ "Azure.Storage.Blobs": "Azure Blob Storage",
988
+ "Azure.Identity": "Azure Identity",
989
+ "Azure.Security.KeyVault.Secrets": "Azure Key Vault",
990
+ "Azure.Cosmos": "Azure Cosmos DB",
991
+ "Microsoft.Azure.Functions.Worker": "Azure Functions",
992
+ "Google.Cloud.Storage.V1": "GCP Storage",
993
+ "Google.Cloud.PubSub.V1": "GCP Pub/Sub",
994
+ "Google.Cloud.Firestore": "GCP Firestore",
995
+ // ── Auth & Identity ──
996
+ "Microsoft.Identity.Web": "Microsoft Identity Web",
997
+ "Microsoft.Identity.Client": "MSAL",
998
+ "IdentityServer4": "IdentityServer4",
999
+ "Duende.IdentityServer": "Duende IdentityServer",
1000
+ "Microsoft.AspNetCore.Authentication.OpenIdConnect": "OpenID Connect",
1001
+ "IdentityModel": "IdentityModel",
1002
+ // ── HTTP & API ──
1003
+ "Refit": "Refit",
1004
+ "RestSharp": "RestSharp",
1005
+ "Flurl.Http": "Flurl",
1006
+ "Polly": "Polly",
1007
+ "Polly.Extensions.Http": "Polly HTTP",
1008
+ "Microsoft.Extensions.Http.Polly": "HttpClient Polly",
1009
+ "Grpc.AspNetCore": "gRPC ASP.NET",
1010
+ "Grpc.Net.Client": "gRPC Client",
1011
+ "GraphQL.Server.All": "GraphQL Server",
1012
+ "HotChocolate.AspNetCore": "Hot Chocolate (GraphQL)",
1013
+ // ── Background Processing ──
1014
+ "Hangfire": "Hangfire",
1015
+ "Hangfire.Core": "Hangfire Core",
1016
+ "Hangfire.AspNetCore": "Hangfire ASP.NET",
1017
+ "Quartz": "Quartz.NET",
1018
+ "Quartz.Extensions.Hosting": "Quartz.NET Hosting",
1019
+ "Coravel": "Coravel",
1020
+ // ── File & Document ──
1021
+ "EPPlus": "EPPlus",
1022
+ "ClosedXML": "ClosedXML",
1023
+ "iTextSharp": "iTextSharp",
1024
+ "QuestPDF": "QuestPDF",
1025
+ "ImageSharp": "ImageSharp",
1026
+ "SixLabors.ImageSharp": "ImageSharp",
1027
+ // ── Feature Flags & Config ──
1028
+ "LaunchDarkly.ServerSdk": "LaunchDarkly",
1029
+ "Microsoft.FeatureManagement": "Feature Management",
1030
+ "Microsoft.FeatureManagement.AspNetCore": "Feature Management ASP.NET",
1031
+ // ── Microservices & Distributed ──
1032
+ "Dapr.Client": "Dapr",
1033
+ "Steeltoe.Discovery.ClientCore": "Steeltoe",
1034
+ "Ocelot": "Ocelot (API Gateway)",
1035
+ "Yarp.ReverseProxy": "YARP",
1036
+ // ── Real-time ──
1037
+ "Microsoft.AspNetCore.SignalR.Client": "SignalR Client"
1038
+ };
1039
+ var LATEST_DOTNET_MAJOR = 9;
1040
+ function parseTfmMajor(tfm) {
1041
+ const match = tfm.match(/^net(\d+)\.\d+$/);
1042
+ if (match?.[1]) return parseInt(match[1], 10);
1043
+ const coreMatch = tfm.match(/^netcoreapp(\d+)\.\d+$/);
1044
+ if (coreMatch?.[1]) return parseInt(coreMatch[1], 10);
1045
+ if (tfm.startsWith("netstandard")) return null;
1046
+ const fxMatch = tfm.match(/^net(\d)(\d+)?$/);
1047
+ if (fxMatch) return null;
1048
+ return null;
1049
+ }
1050
+ function parseCsproj(xml, filePath) {
1051
+ const parsed = parser.parse(xml);
1052
+ const project = parsed?.Project;
1053
+ if (!project) {
1054
+ return { targetFrameworks: [], packageReferences: [], projectName: path3.basename(filePath, ".csproj") };
1055
+ }
1056
+ const propertyGroups = Array.isArray(project.PropertyGroup) ? project.PropertyGroup : project.PropertyGroup ? [project.PropertyGroup] : [];
1057
+ const targetFrameworks = [];
1058
+ for (const pg of propertyGroups) {
1059
+ if (pg.TargetFramework) {
1060
+ targetFrameworks.push(String(pg.TargetFramework));
1061
+ }
1062
+ if (pg.TargetFrameworks) {
1063
+ const tfms = String(pg.TargetFrameworks).split(";").map((s) => s.trim()).filter(Boolean);
1064
+ targetFrameworks.push(...tfms);
1065
+ }
1066
+ }
1067
+ const itemGroups = Array.isArray(project.ItemGroup) ? project.ItemGroup : project.ItemGroup ? [project.ItemGroup] : [];
1068
+ const packageReferences = [];
1069
+ for (const ig of itemGroups) {
1070
+ const refs = Array.isArray(ig.PackageReference) ? ig.PackageReference : ig.PackageReference ? [ig.PackageReference] : [];
1071
+ for (const ref of refs) {
1072
+ const name = ref["@_Include"] ?? ref["@_include"] ?? "";
1073
+ const version = ref["@_Version"] ?? ref["@_version"] ?? ref.Version ?? "";
1074
+ if (name && version) {
1075
+ packageReferences.push({ name: String(name), version: String(version) });
1076
+ }
1077
+ }
1078
+ }
1079
+ return {
1080
+ targetFrameworks: [...new Set(targetFrameworks)],
1081
+ packageReferences,
1082
+ projectName: path3.basename(filePath, ".csproj")
1083
+ };
1084
+ }
1085
+ async function scanDotnetProjects(rootDir) {
1086
+ const csprojFiles = await findCsprojFiles(rootDir);
1087
+ const slnFiles = await findSolutionFiles(rootDir);
1088
+ const slnCsprojPaths = /* @__PURE__ */ new Set();
1089
+ for (const slnPath of slnFiles) {
1090
+ try {
1091
+ const slnContent = await readTextFile(slnPath);
1092
+ const slnDir = path3.dirname(slnPath);
1093
+ const projectRegex = /Project\("[^"]*"\)\s*=\s*"[^"]*",\s*"([^"]+\.csproj)"/g;
1094
+ let match;
1095
+ while ((match = projectRegex.exec(slnContent)) !== null) {
1096
+ if (match[1]) {
1097
+ const csprojPath = path3.resolve(slnDir, match[1].replace(/\\/g, "/"));
1098
+ slnCsprojPaths.add(csprojPath);
1099
+ }
1100
+ }
1101
+ } catch {
1102
+ }
1103
+ }
1104
+ const allCsprojFiles = /* @__PURE__ */ new Set([...csprojFiles, ...slnCsprojPaths]);
1105
+ const results = [];
1106
+ for (const csprojPath of allCsprojFiles) {
1107
+ try {
1108
+ const scan = await scanOneCsproj(csprojPath, rootDir);
1109
+ results.push(scan);
1110
+ } catch (e) {
1111
+ const msg = e instanceof Error ? e.message : String(e);
1112
+ console.error(`Error scanning ${csprojPath}: ${msg}`);
1113
+ }
1114
+ }
1115
+ return results;
1116
+ }
1117
+ async function scanOneCsproj(csprojPath, rootDir) {
1118
+ const xml = await readTextFile(csprojPath);
1119
+ const data = parseCsproj(xml, csprojPath);
1120
+ const primaryTfm = data.targetFrameworks[0];
1121
+ let runtimeMajorsBehind;
1122
+ let targetFramework = primaryTfm;
1123
+ if (primaryTfm) {
1124
+ const major2 = parseTfmMajor(primaryTfm);
1125
+ if (major2 !== null) {
1126
+ runtimeMajorsBehind = Math.max(0, LATEST_DOTNET_MAJOR - major2);
1127
+ }
1128
+ }
1129
+ const dependencies = data.packageReferences.map((ref) => ({
1130
+ package: ref.name,
1131
+ section: "dependencies",
1132
+ currentSpec: ref.version,
1133
+ resolvedVersion: ref.version,
1134
+ latestStable: null,
1135
+ // NuGet lookup not implemented in v1
1136
+ majorsBehind: null,
1137
+ drift: "unknown"
1138
+ }));
1139
+ const frameworks = [];
1140
+ for (const ref of data.packageReferences) {
1141
+ if (ref.name in KNOWN_DOTNET_FRAMEWORKS) {
1142
+ frameworks.push({
1143
+ name: KNOWN_DOTNET_FRAMEWORKS[ref.name],
1144
+ currentVersion: ref.version,
1145
+ latestVersion: null,
1146
+ majorsBehind: null
1147
+ });
1148
+ }
1149
+ }
1150
+ const buckets = { current: 0, oneBehind: 0, twoPlusBehind: 0, unknown: dependencies.length };
1151
+ return {
1152
+ type: "dotnet",
1153
+ path: path3.relative(rootDir, path3.dirname(csprojPath)) || ".",
1154
+ name: data.projectName,
1155
+ targetFramework,
1156
+ runtime: primaryTfm,
1157
+ runtimeLatest: `net${LATEST_DOTNET_MAJOR}.0`,
1158
+ runtimeMajorsBehind,
1159
+ frameworks,
1160
+ dependencies,
1161
+ dependencyAgeBuckets: buckets
1162
+ };
1163
+ }
1164
+
1165
+ // src/utils/semaphore.ts
1166
+ var Semaphore = class {
1167
+ available;
1168
+ queue = [];
1169
+ constructor(max) {
1170
+ this.available = max;
1171
+ }
1172
+ async run(fn) {
1173
+ await this.acquire();
1174
+ try {
1175
+ return await fn();
1176
+ } finally {
1177
+ this.release();
1178
+ }
1179
+ }
1180
+ acquire() {
1181
+ if (this.available > 0) {
1182
+ this.available--;
1183
+ return Promise.resolve();
1184
+ }
1185
+ return new Promise((resolve4) => this.queue.push(resolve4));
1186
+ }
1187
+ release() {
1188
+ const next = this.queue.shift();
1189
+ if (next) next();
1190
+ else this.available++;
1191
+ }
1192
+ };
1193
+
1194
+ // src/config.ts
1195
+ import * as path4 from "path";
1196
+ import * as fs2 from "fs/promises";
1197
+ var CONFIG_FILES = [
1198
+ "vibgrate.config.ts",
1199
+ "vibgrate.config.js",
1200
+ "vibgrate.config.json"
1201
+ ];
1202
+ var DEFAULT_CONFIG = {
1203
+ exclude: [],
1204
+ thresholds: {
1205
+ failOnError: {
1206
+ eolDays: 180,
1207
+ frameworkMajorLag: 3,
1208
+ dependencyTwoPlusPercent: 50
1209
+ },
1210
+ warn: {
1211
+ frameworkMajorLag: 2,
1212
+ dependencyTwoPlusPercent: 30
1213
+ }
1214
+ }
1215
+ };
1216
+ async function loadConfig(rootDir) {
1217
+ for (const file of CONFIG_FILES) {
1218
+ const configPath = path4.join(rootDir, file);
1219
+ if (await pathExists(configPath)) {
1220
+ if (file.endsWith(".json")) {
1221
+ const txt = await readTextFile(configPath);
1222
+ return { ...DEFAULT_CONFIG, ...JSON.parse(txt) };
1223
+ }
1224
+ try {
1225
+ const mod = await import(configPath);
1226
+ return { ...DEFAULT_CONFIG, ...mod.default ?? mod };
1227
+ } catch {
1228
+ }
1229
+ }
1230
+ }
1231
+ return DEFAULT_CONFIG;
1232
+ }
1233
+ async function writeDefaultConfig(rootDir) {
1234
+ const configPath = path4.join(rootDir, "vibgrate.config.ts");
1235
+ const content = `import type { VibgrateConfig } from '@vibgrate/cli';
1236
+
1237
+ const config: VibgrateConfig = {
1238
+ // exclude: ['legacy/**'],
1239
+ thresholds: {
1240
+ failOnError: {
1241
+ eolDays: 180,
1242
+ frameworkMajorLag: 3,
1243
+ dependencyTwoPlusPercent: 50,
1244
+ },
1245
+ warn: {
1246
+ frameworkMajorLag: 2,
1247
+ dependencyTwoPlusPercent: 30,
1248
+ },
1249
+ },
1250
+ };
1251
+
1252
+ export default config;
1253
+ `;
1254
+ await fs2.writeFile(configPath, content, "utf8");
1255
+ return configPath;
1256
+ }
1257
+
1258
+ // src/utils/vcs.ts
1259
+ import * as path5 from "path";
1260
+ import * as fs3 from "fs/promises";
1261
+ async function detectVcs(rootDir) {
1262
+ try {
1263
+ return await detectGit(rootDir);
1264
+ } catch {
1265
+ return { type: "unknown" };
1266
+ }
1267
+ }
1268
+ async function detectGit(rootDir) {
1269
+ const gitDir = await findGitDir(rootDir);
1270
+ if (!gitDir) {
1271
+ return { type: "unknown" };
1272
+ }
1273
+ const headPath = path5.join(gitDir, "HEAD");
1274
+ let headContent;
1275
+ try {
1276
+ headContent = (await fs3.readFile(headPath, "utf8")).trim();
1277
+ } catch {
1278
+ return { type: "unknown" };
1279
+ }
1280
+ let sha;
1281
+ let branch;
1282
+ if (headContent.startsWith("ref: ")) {
1283
+ const refPath = headContent.slice(5);
1284
+ branch = refPath.startsWith("refs/heads/") ? refPath.slice(11) : refPath;
1285
+ sha = await resolveRef(gitDir, refPath);
1286
+ } else if (/^[0-9a-f]{40}$/i.test(headContent)) {
1287
+ sha = headContent;
1288
+ }
1289
+ return {
1290
+ type: "git",
1291
+ sha: sha ?? void 0,
1292
+ shortSha: sha ? sha.slice(0, 7) : void 0,
1293
+ branch: branch ?? void 0
1294
+ };
1295
+ }
1296
+ async function findGitDir(startDir) {
1297
+ let dir = path5.resolve(startDir);
1298
+ const root = path5.parse(dir).root;
1299
+ while (dir !== root) {
1300
+ const gitPath = path5.join(dir, ".git");
1301
+ try {
1302
+ const stat3 = await fs3.stat(gitPath);
1303
+ if (stat3.isDirectory()) {
1304
+ return gitPath;
1305
+ }
1306
+ if (stat3.isFile()) {
1307
+ const content = (await fs3.readFile(gitPath, "utf8")).trim();
1308
+ if (content.startsWith("gitdir: ")) {
1309
+ const resolved = path5.resolve(dir, content.slice(8));
1310
+ return resolved;
1311
+ }
1312
+ }
1313
+ } catch {
1314
+ }
1315
+ dir = path5.dirname(dir);
1316
+ }
1317
+ return null;
1318
+ }
1319
+ async function resolveRef(gitDir, refPath) {
1320
+ const loosePath = path5.join(gitDir, refPath);
1321
+ try {
1322
+ const sha = (await fs3.readFile(loosePath, "utf8")).trim();
1323
+ if (/^[0-9a-f]{40}$/i.test(sha)) {
1324
+ return sha;
1325
+ }
1326
+ } catch {
1327
+ }
1328
+ const packedPath = path5.join(gitDir, "packed-refs");
1329
+ try {
1330
+ const packed = await fs3.readFile(packedPath, "utf8");
1331
+ for (const line of packed.split("\n")) {
1332
+ if (line.startsWith("#") || line.startsWith("^")) continue;
1333
+ const parts = line.trim().split(" ");
1334
+ if (parts.length >= 2 && parts[1] === refPath) {
1335
+ return parts[0];
1336
+ }
1337
+ }
1338
+ } catch {
1339
+ }
1340
+ return void 0;
1341
+ }
1342
+
1343
+ // src/ui/progress.ts
1344
+ import chalk2 from "chalk";
1345
+ var ROBOT = [
1346
+ chalk2.cyan(" \u256D\u2500\u2500\u2500\u256E") + chalk2.greenBright("\u279C"),
1347
+ chalk2.cyan(" \u256D\u2524") + chalk2.greenBright("\u25C9 \u25C9") + chalk2.cyan("\u251C\u256E"),
1348
+ chalk2.cyan(" \u2570\u2524") + chalk2.dim("\u2500\u2500\u2500") + chalk2.cyan("\u251C\u256F"),
1349
+ chalk2.cyan(" \u2570\u2500\u2500\u2500\u256F")
1350
+ ];
1351
+ var BRAND = [
1352
+ chalk2.bold.white(" V I B G R A T E"),
1353
+ chalk2.dim(" Drift Intelligence Engine")
1354
+ ];
1355
+ var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
1356
+ var ScanProgress = class {
1357
+ steps = [];
1358
+ stats = {
1359
+ projects: 0,
1360
+ dependencies: 0,
1361
+ frameworks: 0,
1362
+ findings: { warnings: 0, errors: 0, notes: 0 }
1363
+ };
1364
+ spinnerFrame = 0;
1365
+ timer = null;
1366
+ lastLineCount = 0;
1367
+ startTime = Date.now();
1368
+ isTTY;
1369
+ rootDir = "";
1370
+ constructor(rootDir) {
1371
+ this.isTTY = process.stderr.isTTY ?? false;
1372
+ this.rootDir = rootDir;
1373
+ }
1374
+ /** Register all steps up front */
1375
+ setSteps(steps) {
1376
+ this.steps = steps.map((s) => ({ ...s, status: "pending" }));
1377
+ if (this.isTTY) {
1378
+ this.startSpinner();
1379
+ }
1380
+ this.render();
1381
+ }
1382
+ /** Mark a step as active (currently running) */
1383
+ startStep(id) {
1384
+ const step = this.steps.find((s) => s.id === id);
1385
+ if (step) {
1386
+ step.status = "active";
1387
+ step.detail = void 0;
1388
+ step.count = void 0;
1389
+ }
1390
+ this.render();
1391
+ }
1392
+ /** Mark a step as completed */
1393
+ completeStep(id, detail, count) {
1394
+ const step = this.steps.find((s) => s.id === id);
1395
+ if (step) {
1396
+ step.status = "done";
1397
+ step.detail = detail;
1398
+ step.count = count;
1399
+ }
1400
+ this.render();
1401
+ }
1402
+ /** Mark a step as skipped */
1403
+ skipStep(id) {
1404
+ const step = this.steps.find((s) => s.id === id);
1405
+ if (step) {
1406
+ step.status = "skipped";
1407
+ step.detail = "disabled";
1408
+ }
1409
+ this.render();
1410
+ }
1411
+ /** Update live stats */
1412
+ updateStats(partial) {
1413
+ Object.assign(this.stats, partial);
1414
+ this.render();
1415
+ }
1416
+ /** Increment stats */
1417
+ addProjects(n) {
1418
+ this.stats.projects += n;
1419
+ this.render();
1420
+ }
1421
+ addDependencies(n) {
1422
+ this.stats.dependencies += n;
1423
+ this.render();
1424
+ }
1425
+ addFrameworks(n) {
1426
+ this.stats.frameworks += n;
1427
+ this.render();
1428
+ }
1429
+ addFindings(warnings, errors, notes) {
1430
+ this.stats.findings.warnings += warnings;
1431
+ this.stats.findings.errors += errors;
1432
+ this.stats.findings.notes += notes;
1433
+ this.render();
1434
+ }
1435
+ /** Stop the progress display and clear it */
1436
+ finish() {
1437
+ if (this.timer) {
1438
+ clearInterval(this.timer);
1439
+ this.timer = null;
1440
+ }
1441
+ if (this.isTTY) {
1442
+ this.clearLines();
1443
+ }
1444
+ const elapsed = ((Date.now() - this.startTime) / 1e3).toFixed(1);
1445
+ const doneCount = this.steps.filter((s) => s.status === "done").length;
1446
+ process.stderr.write(
1447
+ chalk2.dim(` \u2714 ${doneCount} scanners completed in ${elapsed}s
1448
+
1449
+ `)
1450
+ );
1451
+ }
1452
+ // ── Internal rendering ──
1453
+ startSpinner() {
1454
+ this.timer = setInterval(() => {
1455
+ this.spinnerFrame = (this.spinnerFrame + 1) % SPINNER_FRAMES.length;
1456
+ this.render();
1457
+ }, 80);
1458
+ }
1459
+ clearLines() {
1460
+ if (this.lastLineCount > 0) {
1461
+ process.stderr.write(`\x1B[${this.lastLineCount}A`);
1462
+ for (let i = 0; i < this.lastLineCount; i++) {
1463
+ process.stderr.write("\x1B[2K\n");
1464
+ }
1465
+ process.stderr.write(`\x1B[${this.lastLineCount}A`);
1466
+ }
1467
+ }
1468
+ render() {
1469
+ if (!this.isTTY) {
1470
+ this.renderCI();
1471
+ return;
1472
+ }
1473
+ this.clearLines();
1474
+ const lines = [];
1475
+ lines.push("");
1476
+ lines.push(` ${ROBOT[0]} ${BRAND[0]}`);
1477
+ lines.push(` ${ROBOT[1]} ${BRAND[1]}`);
1478
+ lines.push(` ${ROBOT[2]}`);
1479
+ lines.push(` ${ROBOT[3]} ${chalk2.dim(this.rootDir)}`);
1480
+ lines.push("");
1481
+ const totalSteps = this.steps.length;
1482
+ const doneSteps = this.steps.filter((s) => s.status === "done" || s.status === "skipped").length;
1483
+ const pct = totalSteps > 0 ? Math.round(doneSteps / totalSteps * 100) : 0;
1484
+ const barWidth = 30;
1485
+ const filled = Math.round(doneSteps / Math.max(totalSteps, 1) * barWidth);
1486
+ const bar = chalk2.greenBright("\u2501".repeat(filled)) + chalk2.dim("\u254C".repeat(barWidth - filled));
1487
+ const elapsed = ((Date.now() - this.startTime) / 1e3).toFixed(1);
1488
+ lines.push(` ${bar} ${chalk2.bold.white(`${pct}%`)} ${chalk2.dim(`${elapsed}s`)}`);
1489
+ lines.push("");
1490
+ for (const step of this.steps) {
1491
+ lines.push(this.renderStep(step));
1492
+ }
1493
+ lines.push("");
1494
+ lines.push(this.renderStats());
1495
+ lines.push("");
1496
+ const output = lines.join("\n") + "\n";
1497
+ process.stderr.write(output);
1498
+ this.lastLineCount = lines.length;
1499
+ }
1500
+ renderStep(step) {
1501
+ const spinner = SPINNER_FRAMES[this.spinnerFrame];
1502
+ let icon;
1503
+ let label;
1504
+ let detail = "";
1505
+ switch (step.status) {
1506
+ case "done":
1507
+ icon = chalk2.green("\u2714");
1508
+ label = chalk2.white(step.label);
1509
+ break;
1510
+ case "active":
1511
+ icon = chalk2.cyan(spinner);
1512
+ label = chalk2.bold.white(step.label);
1513
+ break;
1514
+ case "skipped":
1515
+ icon = chalk2.dim("\u25CC");
1516
+ label = chalk2.dim.strikethrough(step.label);
1517
+ break;
1518
+ default:
1519
+ icon = chalk2.dim("\u25CB");
1520
+ label = chalk2.dim(step.label);
1521
+ break;
1522
+ }
1523
+ if (step.detail) {
1524
+ detail = chalk2.dim(` \xB7 ${step.detail}`);
1525
+ }
1526
+ if (step.count !== void 0 && step.count > 0) {
1527
+ detail += chalk2.cyan(` (${step.count})`);
1528
+ }
1529
+ return ` ${icon} ${label}${detail}`;
1530
+ }
1531
+ renderStats() {
1532
+ const p = this.stats.projects;
1533
+ const d = this.stats.dependencies;
1534
+ const f = this.stats.frameworks;
1535
+ const w = this.stats.findings.warnings;
1536
+ const e = this.stats.findings.errors;
1537
+ const n = this.stats.findings.notes;
1538
+ const parts = [
1539
+ chalk2.bold.white(` ${p}`) + chalk2.dim(` project${p !== 1 ? "s" : ""}`),
1540
+ chalk2.white(`${d}`) + chalk2.dim(` dep${d !== 1 ? "s" : ""}`),
1541
+ chalk2.white(`${f}`) + chalk2.dim(` framework${f !== 1 ? "s" : ""}`)
1542
+ ];
1543
+ const findingParts = [];
1544
+ if (e > 0) findingParts.push(chalk2.red(`${e} \u2716`));
1545
+ if (w > 0) findingParts.push(chalk2.yellow(`${w} \u26A0`));
1546
+ if (n > 0) findingParts.push(chalk2.blue(`${n} \u2139`));
1547
+ if (findingParts.length > 0) {
1548
+ parts.push(findingParts.join(chalk2.dim(" \xB7 ")));
1549
+ }
1550
+ return ` ${chalk2.dim("\u2503")} ${parts.join(chalk2.dim(" \u2502 "))}`;
1551
+ }
1552
+ /** Simple CI-friendly output (no ANSI rewriting) */
1553
+ lastCIStep = null;
1554
+ renderCI() {
1555
+ const active = this.steps.find((s) => s.status === "active");
1556
+ if (active && active.id !== this.lastCIStep) {
1557
+ this.lastCIStep = active.id;
1558
+ process.stderr.write(` \u25C9 ${active.label}...
1559
+ `);
1560
+ }
1561
+ for (const step of this.steps) {
1562
+ if (step.status === "done" && step.id !== this.lastCIStep) {
1563
+ }
1564
+ }
1565
+ }
1566
+ };
1567
+
1568
+ // src/scanners/platform-matrix.ts
1569
+ import * as path6 from "path";
1570
+ var NATIVE_MODULE_PACKAGES = /* @__PURE__ */ new Set([
1571
+ // Image / media processing
1572
+ "sharp",
1573
+ // libvips native bindings
1574
+ "canvas",
1575
+ // node-canvas (Cairo native)
1576
+ "jimp",
1577
+ // pure JS but optionally uses native
1578
+ "@napi-rs/image",
1579
+ // NAPI-RS image processing
1580
+ "imagemagick",
1581
+ // ImageMagick bindings
1582
+ "gm",
1583
+ // GraphicsMagick
1584
+ "fluent-ffmpeg",
1585
+ // FFmpeg bindings
1586
+ "ffmpeg-static",
1587
+ // bundled FFmpeg binary
1588
+ "puppeteer",
1589
+ // ships Chromium binary
1590
+ "playwright",
1591
+ // ships browser binaries
1592
+ "playwright-core",
1593
+ // browser binaries
1594
+ // Cryptography / security
1595
+ "bcrypt",
1596
+ // native bcrypt
1597
+ "argon2",
1598
+ // native argon2 hashing
1599
+ "sodium-native",
1600
+ // libsodium bindings
1601
+ "libsodium-wrappers",
1602
+ // libsodium WASM/native
1603
+ "node-forge",
1604
+ // mostly JS but linked to native in some envs
1605
+ "ssh2",
1606
+ // native SSH bindings (optional)
1607
+ "keytar",
1608
+ // OS keychain native bindings
1609
+ // Database drivers
1610
+ "better-sqlite3",
1611
+ // native SQLite
1612
+ "sqlite3",
1613
+ // native SQLite
1614
+ "pg-native",
1615
+ // PostgreSQL native bindings
1616
+ "oracledb",
1617
+ // Oracle native client
1618
+ "odbc",
1619
+ // native ODBC
1620
+ "ibm_db",
1621
+ // IBM DB2 native
1622
+ "couchbase",
1623
+ // native Couchbase SDK
1624
+ "rocksdb",
1625
+ // native RocksDB store
1626
+ "leveldown",
1627
+ // native LevelDB
1628
+ "lmdb",
1629
+ // native LMDB bindings
1630
+ // Compilation & build tools
1631
+ "node-gyp",
1632
+ // native build tool itself
1633
+ "node-pre-gyp",
1634
+ // native binary distribution
1635
+ "@mapbox/node-pre-gyp",
1636
+ // native binary distribution
1637
+ "prebuild",
1638
+ // prebuilt native bindings
1639
+ "prebuild-install",
1640
+ // prebuilt native installer
1641
+ "esbuild",
1642
+ // platform-specific Go binary
1643
+ "@swc/core",
1644
+ // platform-specific Rust binary
1645
+ "@rspack/core",
1646
+ // platform-specific Rust binary
1647
+ "@biomejs/biome",
1648
+ // platform-specific Rust binary
1649
+ "node-sass",
1650
+ // deprecated, native libsass
1651
+ "sass-embedded",
1652
+ // Dart Sass embedded binary
1653
+ "turbo",
1654
+ // Turborepo Go/Rust binary
1655
+ "@vercel/nft",
1656
+ // native file tracing (optional)
1657
+ // System / hardware access
1658
+ "fsevents",
1659
+ // macOS-only file watching
1660
+ "cpu-features",
1661
+ // CPU instruction detection
1662
+ "deasync",
1663
+ // native event loop control
1664
+ "usb",
1665
+ // USB device access
1666
+ "serialport",
1667
+ // serial port access
1668
+ "node-hid",
1669
+ // HID device access
1670
+ "i2c-bus",
1671
+ // I2C bus access
1672
+ "spi-device",
1673
+ // SPI bus access
1674
+ "node-bluetooth",
1675
+ // Bluetooth
1676
+ "mdns",
1677
+ // mDNS/Bonjour
1678
+ // Compression
1679
+ "snappy",
1680
+ // native Snappy compression
1681
+ "zstd-napi",
1682
+ // native Zstandard
1683
+ "lz4",
1684
+ // native LZ4
1685
+ "brotli",
1686
+ // native Brotli (older, node has built-in)
1687
+ // Regex / text
1688
+ "re2",
1689
+ // native RE2 regex engine
1690
+ "oniguruma",
1691
+ // native Oniguruma regex
1692
+ "vscode-oniguruma",
1693
+ // native Oniguruma (VS Code)
1694
+ "tree-sitter",
1695
+ // native parser generator
1696
+ "node-tree-sitter",
1697
+ // native Tree-sitter
1698
+ // XML / HTML
1699
+ "libxmljs",
1700
+ // native libxml2
1701
+ "libxmljs2",
1702
+ // native libxml2
1703
+ "node-expat",
1704
+ // native Expat XML parser
1705
+ "htmlparser2",
1706
+ // mostly JS, optional native
1707
+ // Networking / IPC
1708
+ "@grpc/grpc-js",
1709
+ // gRPC (JS but has native dep for HTTP/2)
1710
+ "grpc",
1711
+ // deprecated native gRPC
1712
+ "zeromq",
1713
+ // native ZeroMQ
1714
+ "nanomsg",
1715
+ // native nanomsg
1716
+ "unix-dgram",
1717
+ // native Unix sockets
1718
+ // Observability
1719
+ "dtrace-provider",
1720
+ // native DTrace
1721
+ "v8-profiler-next",
1722
+ // native V8 profiler
1723
+ "heapdump",
1724
+ // native V8 heap dump
1725
+ // Misc
1726
+ "farmhash",
1727
+ // native FarmHash
1728
+ "xxhash",
1729
+ // native xxHash
1730
+ "xxhash-addon",
1731
+ // native xxHash
1732
+ "iconv",
1733
+ // native iconv
1734
+ "ref-napi",
1735
+ // native FFI
1736
+ "ffi-napi",
1737
+ // native FFI
1738
+ "node-pty",
1739
+ // native pseudo-terminal
1740
+ "robotjs",
1741
+ // native desktop automation
1742
+ "electron",
1743
+ // ships Chromium + Node binary
1744
+ "xdg-open",
1745
+ // OS-specific
1746
+ "windows-process-tree"
1747
+ // Windows-only
1748
+ ]);
1749
+ var OS_PATTERNS = [
1750
+ { pattern: /\bcmd\.exe\b|\.bat\b|\.cmd\b/i, label: "windows-scripts" },
1751
+ { pattern: /\bpowershell\b|\bpwsh\b/i, label: "powershell" },
1752
+ { pattern: /\bbash\b|#!\/bin\/bash/i, label: "bash-scripts" },
1753
+ { pattern: /\\\\/g, label: "backslash-paths" }
1754
+ ];
1755
+ async function scanPlatformMatrix(rootDir) {
1756
+ const result = {
1757
+ dotnetTargetFrameworks: [],
1758
+ nativeModules: [],
1759
+ osAssumptions: [],
1760
+ dockerBaseImages: [],
1761
+ nodeVersionFiles: []
1762
+ };
1763
+ const pkgFiles = await findPackageJsonFiles(rootDir);
1764
+ const allDeps = /* @__PURE__ */ new Set();
1765
+ const osAssumptions = /* @__PURE__ */ new Set();
1766
+ for (const pjPath of pkgFiles) {
1767
+ try {
1768
+ const pj = await readJsonFile(pjPath);
1769
+ if (pj.engines?.node && !result.nodeEngines) result.nodeEngines = pj.engines.node;
1770
+ if (pj.engines?.npm && !result.npmEngines) result.npmEngines = pj.engines.npm;
1771
+ if (pj.engines?.pnpm && !result.pnpmEngines) {
1772
+ result.pnpmEngines = pj.engines.pnpm;
1773
+ }
1774
+ for (const section of ["dependencies", "devDependencies", "optionalDependencies"]) {
1775
+ const deps = pj[section];
1776
+ if (deps) {
1777
+ for (const name of Object.keys(deps)) {
1778
+ allDeps.add(name);
1779
+ }
1780
+ }
1781
+ }
1782
+ const scripts = pj.scripts;
1783
+ if (scripts && typeof scripts === "object") {
1784
+ for (const val of Object.values(scripts)) {
1785
+ if (typeof val !== "string") continue;
1786
+ const firstToken = val.split(/\s/)[0] ?? "";
1787
+ for (const { pattern, label } of OS_PATTERNS) {
1788
+ if (pattern.test(firstToken)) {
1789
+ osAssumptions.add(label);
1790
+ }
1791
+ }
1792
+ }
1793
+ }
1794
+ } catch {
1795
+ }
1796
+ }
1797
+ for (const dep of allDeps) {
1798
+ if (NATIVE_MODULE_PACKAGES.has(dep)) {
1799
+ result.nativeModules.push(dep);
1800
+ }
1801
+ }
1802
+ result.nativeModules.sort();
1803
+ result.osAssumptions = [...osAssumptions].sort();
1804
+ const csprojFiles = await findFiles(rootDir, (name) => name.endsWith(".csproj"));
1805
+ const tfms = /* @__PURE__ */ new Set();
1806
+ for (const csprojPath of csprojFiles) {
1807
+ try {
1808
+ const xml = await readTextFile(csprojPath);
1809
+ const tfMatch = xml.match(/<TargetFramework>(.*?)<\/TargetFramework>/);
1810
+ if (tfMatch?.[1]) tfms.add(tfMatch[1]);
1811
+ const tfsMatch = xml.match(/<TargetFrameworks>(.*?)<\/TargetFrameworks>/);
1812
+ if (tfsMatch?.[1]) {
1813
+ for (const tfm of tfsMatch[1].split(";")) {
1814
+ if (tfm.trim()) tfms.add(tfm.trim());
1815
+ }
1816
+ }
1817
+ } catch {
1818
+ }
1819
+ }
1820
+ result.dotnetTargetFrameworks = [...tfms].sort();
1821
+ const dockerfiles = await findFiles(
1822
+ rootDir,
1823
+ (name) => name === "Dockerfile" || name.startsWith("Dockerfile.")
1824
+ );
1825
+ const baseImages = /* @__PURE__ */ new Set();
1826
+ for (const df of dockerfiles) {
1827
+ try {
1828
+ const content = await readTextFile(df);
1829
+ for (const line of content.split("\n")) {
1830
+ const trimmed = line.trim();
1831
+ if (/^FROM\s+/i.test(trimmed)) {
1832
+ const parts = trimmed.split(/\s+/);
1833
+ if (parts[1] && !parts[1].startsWith("--")) {
1834
+ baseImages.add(parts[1]);
1835
+ } else if (parts[1]?.startsWith("--")) {
1836
+ const imageIdx = parts[1].includes("=") ? 2 : 3;
1837
+ if (parts[imageIdx]) baseImages.add(parts[imageIdx]);
1838
+ }
1839
+ }
1840
+ }
1841
+ } catch {
1842
+ }
1843
+ }
1844
+ result.dockerBaseImages = [...baseImages].sort();
1845
+ for (const file of [".nvmrc", ".node-version", ".tool-versions"]) {
1846
+ if (await pathExists(path6.join(rootDir, file))) {
1847
+ result.nodeVersionFiles.push(file);
1848
+ }
1849
+ }
1850
+ return result;
1851
+ }
1852
+
1853
+ // src/scanners/dependency-risk.ts
1854
+ var DEPRECATED_PACKAGES = /* @__PURE__ */ new Set([
1855
+ "request",
1856
+ "node-sass",
1857
+ "tslint",
1858
+ "istanbul",
1859
+ "popper.js",
1860
+ "Left-pad",
1861
+ "left-pad",
1862
+ "bower",
1863
+ "grunt",
1864
+ "gulp",
1865
+ "coffee-script",
1866
+ "coffeescript",
1867
+ "merge",
1868
+ "nomnom",
1869
+ "optimist",
1870
+ "natives",
1871
+ "querystring",
1872
+ "domain-browser",
1873
+ "sys",
1874
+ "punycode"
1875
+ ]);
1876
+ var NATIVE_MODULE_PACKAGES2 = /* @__PURE__ */ new Set([
1877
+ "sharp",
1878
+ "canvas",
1879
+ "bcrypt",
1880
+ "node-gyp",
1881
+ "fsevents",
1882
+ "better-sqlite3",
1883
+ "sqlite3",
1884
+ "leveldown",
1885
+ "sodium-native",
1886
+ "node-sass",
1887
+ "argon2",
1888
+ "usb",
1889
+ "serialport",
1890
+ "re2",
1891
+ "libxmljs",
1892
+ "libxmljs2",
1893
+ "cpu-features",
1894
+ "deasync",
1895
+ "farmhash",
1896
+ "grpc",
1897
+ "@grpc/grpc-js"
1898
+ ]);
1899
+ function scanDependencyRisk(projects) {
1900
+ const deprecated = /* @__PURE__ */ new Set();
1901
+ const nativeModules = /* @__PURE__ */ new Set();
1902
+ let totalDeps = 0;
1903
+ for (const project of projects) {
1904
+ for (const dep of project.dependencies) {
1905
+ totalDeps++;
1906
+ if (DEPRECATED_PACKAGES.has(dep.package)) {
1907
+ deprecated.add(dep.package);
1908
+ }
1909
+ if (NATIVE_MODULE_PACKAGES2.has(dep.package)) {
1910
+ nativeModules.add(dep.package);
1911
+ }
1912
+ }
1913
+ }
1914
+ return {
1915
+ deprecatedPackages: [...deprecated].sort(),
1916
+ nativeModulePackages: [...nativeModules].sort(),
1917
+ totalDependencies: totalDeps
1918
+ };
1919
+ }
1920
+
1921
+ // src/scanners/dependency-graph.ts
1922
+ import * as path7 from "path";
1923
+ function parsePnpmLock(content) {
1924
+ const entries = [];
1925
+ const regex = /^\s+\/?(@?[^@\s][^@\s]*?)@(\d+\.\d+\.\d+[^:\s]*)\s*:/gm;
1926
+ let match;
1927
+ while ((match = regex.exec(content)) !== null) {
1928
+ if (match[1] && match[2]) {
1929
+ entries.push({ name: match[1], version: match[2] });
1930
+ }
1931
+ }
1932
+ return entries;
1933
+ }
1934
+ function parseNpmLock(content) {
1935
+ const entries = [];
1936
+ try {
1937
+ const lock = JSON.parse(content);
1938
+ if (lock.packages && typeof lock.packages === "object") {
1939
+ for (const [key, value] of Object.entries(lock.packages)) {
1940
+ if (key === "") continue;
1941
+ const v = value;
1942
+ if (v.version) {
1943
+ const name = key.replace(/^node_modules\//, "").replace(/.*node_modules\//, "");
1944
+ entries.push({ name, version: v.version });
1945
+ }
1946
+ }
1947
+ } else if (lock.dependencies && typeof lock.dependencies === "object") {
1948
+ let walkDeps2 = function(deps) {
1949
+ for (const [name, data] of Object.entries(deps)) {
1950
+ if (data.version) entries.push({ name, version: data.version });
1951
+ if (data.dependencies) walkDeps2(data.dependencies);
1952
+ }
1953
+ };
1954
+ var walkDeps = walkDeps2;
1955
+ walkDeps2(lock.dependencies);
1956
+ }
1957
+ } catch {
1958
+ }
1959
+ return entries;
1960
+ }
1961
+ function parseYarnLock(content) {
1962
+ const entries = [];
1963
+ const regex = /^"?(@?[^\s"@]+)@[^:]+:\s*\n\s+version\s+"([^"]+)"/gm;
1964
+ let match;
1965
+ while ((match = regex.exec(content)) !== null) {
1966
+ if (match[1] && match[2]) {
1967
+ entries.push({ name: match[1], version: match[2] });
1968
+ }
1969
+ }
1970
+ return entries;
1971
+ }
1972
+ async function scanDependencyGraph(rootDir) {
1973
+ const result = {
1974
+ lockfileType: null,
1975
+ totalUnique: 0,
1976
+ totalInstalled: 0,
1977
+ duplicatedPackages: [],
1978
+ phantomDependencies: []
1979
+ };
1980
+ let entries = [];
1981
+ const pnpmLock = path7.join(rootDir, "pnpm-lock.yaml");
1982
+ const npmLock = path7.join(rootDir, "package-lock.json");
1983
+ const yarnLock = path7.join(rootDir, "yarn.lock");
1984
+ if (await pathExists(pnpmLock)) {
1985
+ result.lockfileType = "pnpm";
1986
+ const content = await readTextFile(pnpmLock);
1987
+ entries = parsePnpmLock(content);
1988
+ } else if (await pathExists(npmLock)) {
1989
+ result.lockfileType = "npm";
1990
+ const content = await readTextFile(npmLock);
1991
+ entries = parseNpmLock(content);
1992
+ } else if (await pathExists(yarnLock)) {
1993
+ result.lockfileType = "yarn";
1994
+ const content = await readTextFile(yarnLock);
1995
+ entries = parseYarnLock(content);
1996
+ }
1997
+ if (entries.length === 0) return result;
1998
+ const versionMap = /* @__PURE__ */ new Map();
1999
+ for (const entry of entries) {
2000
+ const existing = versionMap.get(entry.name);
2001
+ if (existing) {
2002
+ existing.add(entry.version);
2003
+ } else {
2004
+ versionMap.set(entry.name, /* @__PURE__ */ new Set([entry.version]));
2005
+ }
2006
+ }
2007
+ result.totalInstalled = entries.length;
2008
+ result.totalUnique = versionMap.size;
2009
+ const duplicated = [];
2010
+ for (const [name, versions] of versionMap) {
2011
+ if (versions.size > 1) {
2012
+ duplicated.push({
2013
+ name,
2014
+ versions: [...versions].sort(),
2015
+ consumers: versions.size
2016
+ });
2017
+ }
2018
+ }
2019
+ duplicated.sort((a, b) => b.versions.length - a.versions.length || a.name.localeCompare(b.name));
2020
+ result.duplicatedPackages = duplicated;
2021
+ const lockedNames = new Set(versionMap.keys());
2022
+ const pkgFiles = await findPackageJsonFiles(rootDir);
2023
+ const phantoms = /* @__PURE__ */ new Set();
2024
+ for (const pjPath of pkgFiles) {
2025
+ try {
2026
+ const pj = await readJsonFile(pjPath);
2027
+ for (const section of ["dependencies", "devDependencies"]) {
2028
+ const deps = pj[section];
2029
+ if (!deps) continue;
2030
+ for (const [name, version] of Object.entries(deps)) {
2031
+ const ver = typeof version === "string" ? version : "";
2032
+ if (!lockedNames.has(name) && !ver.startsWith("workspace:")) {
2033
+ phantoms.add(name);
2034
+ }
2035
+ }
2036
+ }
2037
+ } catch {
2038
+ }
2039
+ }
2040
+ result.phantomDependencies = [...phantoms].sort();
2041
+ return result;
2042
+ }
2043
+
2044
+ // src/scanners/tooling-inventory.ts
2045
+ var CATEGORIES = {
2046
+ frontend: {
2047
+ "react": "React",
2048
+ "react-dom": "React DOM",
2049
+ "vue": "Vue",
2050
+ "@angular/core": "Angular",
2051
+ "svelte": "Svelte",
2052
+ "solid-js": "Solid",
2053
+ "qwik": "Qwik",
2054
+ "htmx.org": "htmx",
2055
+ "preact": "Preact",
2056
+ "lit": "Lit",
2057
+ "alpinejs": "Alpine.js",
2058
+ "stimulus": "Stimulus",
2059
+ "petite-vue": "petite-vue",
2060
+ "mithril": "Mithril",
2061
+ "inferno": "Inferno",
2062
+ "hyperapp": "Hyperapp",
2063
+ "marko": "Marko",
2064
+ "@builder.io/qwik": "Qwik (Builder)",
2065
+ "million": "Million",
2066
+ "ember-source": "Ember"
2067
+ },
2068
+ metaFrameworks: {
2069
+ "next": "Next.js",
2070
+ "nuxt": "Nuxt",
2071
+ "@analogjs/platform": "Analog",
2072
+ "astro": "Astro",
2073
+ "@remix-run/react": "Remix",
2074
+ "gatsby": "Gatsby",
2075
+ "@sveltejs/kit": "SvelteKit",
2076
+ "@tanstack/start": "TanStack Start",
2077
+ "@adonisjs/core": "AdonisJS",
2078
+ "@redwoodjs/core": "RedwoodJS",
2079
+ "blitz": "Blitz.js",
2080
+ "@solidjs/start": "SolidStart",
2081
+ "fresh": "Fresh (Deno)"
2082
+ },
2083
+ bundlers: {
2084
+ "vite": "Vite",
2085
+ "webpack": "webpack",
2086
+ "rollup": "Rollup",
2087
+ "esbuild": "esbuild",
2088
+ "parcel": "Parcel",
2089
+ "turbo": "Turbo",
2090
+ "tsup": "tsup",
2091
+ "unbuild": "unbuild",
2092
+ "@swc/core": "SWC",
2093
+ "bun": "Bun",
2094
+ "pkg": "pkg",
2095
+ "ncc": "ncc",
2096
+ "@vercel/ncc": "Vercel ncc",
2097
+ "microbundle": "microbundle",
2098
+ "tsc-watch": "tsc-watch",
2099
+ "ts-node": "ts-node",
2100
+ "tsx": "tsx",
2101
+ "jiti": "jiti",
2102
+ "@rspack/core": "Rspack",
2103
+ "farm": "Farm"
2104
+ },
2105
+ css: {
2106
+ "tailwindcss": "Tailwind CSS",
2107
+ "@mui/material": "MUI",
2108
+ "vuetify": "Vuetify",
2109
+ "bootstrap": "Bootstrap",
2110
+ "styled-components": "styled-components",
2111
+ "@emotion/react": "Emotion",
2112
+ "@chakra-ui/react": "Chakra UI",
2113
+ "sass": "Sass",
2114
+ "@mantine/core": "Mantine",
2115
+ "antd": "Ant Design",
2116
+ "@radix-ui/react-slot": "Radix UI",
2117
+ "@headlessui/react": "Headless UI",
2118
+ "daisyui": "DaisyUI",
2119
+ "@shadcn/ui": "shadcn/ui",
2120
+ "primereact": "PrimeReact",
2121
+ "primevue": "PrimeVue",
2122
+ "@nextui-org/react": "NextUI",
2123
+ "@ariakit/react": "Ariakit",
2124
+ "windstitch": "Windstitch",
2125
+ "vanilla-extract": "vanilla-extract",
2126
+ "@vanilla-extract/css": "vanilla-extract",
2127
+ "linaria": "Linaria",
2128
+ "stylex": "StyleX",
2129
+ "@stylexjs/stylex": "StyleX",
2130
+ "unocss": "UnoCSS",
2131
+ "postcss": "PostCSS",
2132
+ "autoprefixer": "Autoprefixer",
2133
+ "lightningcss": "Lightning CSS",
2134
+ "less": "Less",
2135
+ "stylus": "Stylus",
2136
+ "open-props": "Open Props",
2137
+ "@ark-ui/react": "Ark UI"
2138
+ },
2139
+ backend: {
2140
+ "express": "Express",
2141
+ "fastify": "Fastify",
2142
+ "@nestjs/core": "NestJS",
2143
+ "hono": "Hono",
2144
+ "koa": "Koa",
2145
+ "@hapi/hapi": "Hapi",
2146
+ "restify": "Restify",
2147
+ "elysia": "Elysia",
2148
+ "@elysiajs/eden": "Elysia Eden",
2149
+ "moleculer": "Moleculer",
2150
+ "@feathersjs/feathers": "Feathers",
2151
+ "sails": "Sails",
2152
+ "micro": "Micro",
2153
+ "polka": "Polka",
2154
+ "h3": "h3",
2155
+ "nitro": "Nitro",
2156
+ "@trpc/server": "tRPC Server",
2157
+ "@trpc/client": "tRPC Client",
2158
+ "middy": "Middy (Lambda)",
2159
+ "serverless": "Serverless Framework",
2160
+ "aws-lambda": "AWS Lambda",
2161
+ "@aws-sdk/client-lambda": "AWS Lambda SDK",
2162
+ "@cloudflare/workers-types": "Cloudflare Workers",
2163
+ "wrangler": "Wrangler"
2164
+ },
2165
+ orm: {
2166
+ "prisma": "Prisma",
2167
+ "@prisma/client": "Prisma Client",
2168
+ "drizzle-orm": "Drizzle",
2169
+ "typeorm": "TypeORM",
2170
+ "sequelize": "Sequelize",
2171
+ "knex": "Knex",
2172
+ "pg": "pg (PostgreSQL)",
2173
+ "mysql2": "mysql2",
2174
+ "mongodb": "MongoDB",
2175
+ "ioredis": "ioredis",
2176
+ "redis": "Redis",
2177
+ "better-sqlite3": "better-sqlite3",
2178
+ "@mikro-orm/core": "MikroORM",
2179
+ "mongoose": "Mongoose",
2180
+ "mssql": "mssql",
2181
+ "kysely": "Kysely",
2182
+ "objection": "Objection.js",
2183
+ "@planetscale/database": "PlanetScale",
2184
+ "@neondatabase/serverless": "Neon",
2185
+ "@libsql/client": "libSQL (Turso)",
2186
+ "@electric-sql/pglite": "PGlite",
2187
+ "sql.js": "sql.js",
2188
+ "oracledb": "Oracle DB",
2189
+ "cassandra-driver": "Cassandra",
2190
+ "neo4j-driver": "Neo4j",
2191
+ "@upstash/redis": "Upstash Redis",
2192
+ "@upstash/kafka": "Upstash Kafka",
2193
+ "dynamoose": "Dynamoose",
2194
+ "fauna": "Fauna",
2195
+ "faunadb": "FaunaDB",
2196
+ "@clickhouse/client": "ClickHouse",
2197
+ "influx": "InfluxDB",
2198
+ "slonik": "Slonik",
2199
+ "massive": "Massive.js"
2200
+ },
2201
+ testing: {
2202
+ "vitest": "Vitest",
2203
+ "jest": "Jest",
2204
+ "mocha": "Mocha",
2205
+ "@playwright/test": "Playwright",
2206
+ "cypress": "Cypress",
2207
+ "@testing-library/react": "Testing Library (React)",
2208
+ "@testing-library/vue": "Testing Library (Vue)",
2209
+ "@testing-library/svelte": "Testing Library (Svelte)",
2210
+ "@testing-library/jest-dom": "Testing Library DOM",
2211
+ "@testing-library/user-event": "Testing Library User Event",
2212
+ "chai": "Chai",
2213
+ "ava": "AVA",
2214
+ "tap": "node-tap",
2215
+ "supertest": "Supertest",
2216
+ "storybook": "Storybook",
2217
+ "@storybook/react": "Storybook (React)",
2218
+ "@storybook/vue3": "Storybook (Vue)",
2219
+ "msw": "Mock Service Worker",
2220
+ "nock": "Nock",
2221
+ "sinon": "Sinon",
2222
+ "faker": "Faker",
2223
+ "@faker-js/faker": "Faker.js",
2224
+ "testcontainers": "Testcontainers",
2225
+ "pact": "Pact",
2226
+ "@pact-foundation/pact": "Pact",
2227
+ "k6": "k6",
2228
+ "artillery": "Artillery",
2229
+ "autocannon": "Autocannon",
2230
+ "puppeteer": "Puppeteer",
2231
+ "webdriverio": "WebdriverIO",
2232
+ "nightwatch": "Nightwatch",
2233
+ "detox": "Detox (Mobile)",
2234
+ "jest-image-snapshot": "Image Snapshot",
2235
+ "happy-dom": "happy-dom",
2236
+ "jsdom": "jsdom",
2237
+ "c8": "c8 (coverage)",
2238
+ "nyc": "nyc (coverage)",
2239
+ "@vitest/coverage-v8": "Vitest Coverage"
2240
+ },
2241
+ lintFormat: {
2242
+ "eslint": "ESLint",
2243
+ "prettier": "Prettier",
2244
+ "stylelint": "Stylelint",
2245
+ "@biomejs/biome": "Biome",
2246
+ "oxlint": "oxlint",
2247
+ "tslint": "TSLint",
2248
+ "@typescript-eslint/parser": "typescript-eslint",
2249
+ "eslint-config-next": "ESLint Next.js",
2250
+ "eslint-config-prettier": "ESLint Prettier",
2251
+ "eslint-plugin-react": "ESLint React",
2252
+ "eslint-plugin-vue": "ESLint Vue",
2253
+ "husky": "Husky",
2254
+ "lint-staged": "lint-staged",
2255
+ "commitlint": "commitlint",
2256
+ "@commitlint/cli": "commitlint",
2257
+ "lefthook": "Lefthook",
2258
+ "cspell": "CSpell",
2259
+ "markdownlint": "markdownlint",
2260
+ "depcheck": "depcheck",
2261
+ "knip": "Knip",
2262
+ "madge": "Madge",
2263
+ "publint": "publint",
2264
+ "arethetypeswrong": "Are the Types Wrong",
2265
+ "sort-package-json": "sort-package-json"
2266
+ },
2267
+ apiMessaging: {
2268
+ "graphql": "GraphQL",
2269
+ "@grpc/grpc-js": "gRPC",
2270
+ "@trpc/server": "tRPC",
2271
+ "@connectrpc/connect": "Connect",
2272
+ "socket.io": "Socket.IO",
2273
+ "ws": "WebSocket (ws)",
2274
+ "@apollo/server": "Apollo Server",
2275
+ "@apollo/client": "Apollo Client",
2276
+ "urql": "URQL",
2277
+ "graphql-yoga": "GraphQL Yoga",
2278
+ "mercurius": "Mercurius",
2279
+ "type-graphql": "TypeGraphQL",
2280
+ "nexus": "Nexus",
2281
+ "pothos": "Pothos",
2282
+ "@graphql-codegen/cli": "GraphQL Codegen",
2283
+ "kafkajs": "Kafka.js",
2284
+ "amqplib": "AMQP (RabbitMQ)",
2285
+ "bullmq": "BullMQ",
2286
+ "bull": "Bull",
2287
+ "bee-queue": "Bee Queue",
2288
+ "agenda": "Agenda",
2289
+ "pg-boss": "pg-boss",
2290
+ "@temporalio/client": "Temporal",
2291
+ "inngest": "Inngest",
2292
+ "trigger.dev": "Trigger.dev",
2293
+ "nats": "NATS",
2294
+ "mqtt": "MQTT",
2295
+ "zeromq": "ZeroMQ",
2296
+ "sse-channel": "SSE Channel"
2297
+ },
2298
+ observability: {
2299
+ "@sentry/node": "Sentry (Node)",
2300
+ "@sentry/browser": "Sentry (Browser)",
2301
+ "@sentry/react": "Sentry (React)",
2302
+ "@sentry/nextjs": "Sentry (Next.js)",
2303
+ "@opentelemetry/api": "OpenTelemetry API",
2304
+ "@opentelemetry/sdk-node": "OpenTelemetry SDK",
2305
+ "@opentelemetry/auto-instrumentations-node": "OpenTelemetry Auto",
2306
+ "applicationinsights": "Application Insights",
2307
+ "pino": "Pino",
2308
+ "winston": "Winston",
2309
+ "dd-trace": "Datadog",
2310
+ "newrelic": "New Relic",
2311
+ "bunyan": "Bunyan",
2312
+ "@axiomhq/pino": "Axiom (Pino)",
2313
+ "loglevel": "loglevel",
2314
+ "consola": "consola",
2315
+ "@logtail/node": "Logtail",
2316
+ "elastic-apm-node": "Elastic APM",
2317
+ "prom-client": "Prometheus Client",
2318
+ "@google-cloud/trace-agent": "GCP Trace",
2319
+ "@google-cloud/logging": "GCP Logging",
2320
+ "lightstep-tracer": "Lightstep",
2321
+ "roarr": "Roarr",
2322
+ "cls-hooked": "CLS Hooked",
2323
+ "@baselime/node-opentelemetry": "Baselime",
2324
+ "highlight.run": "Highlight",
2325
+ "posthog-node": "PostHog",
2326
+ "@amplitude/node": "Amplitude",
2327
+ "mixpanel": "Mixpanel",
2328
+ "@segment/analytics-node": "Segment"
2329
+ }
2330
+ };
2331
+ function scanToolingInventory(projects) {
2332
+ const result = {
2333
+ frontend: [],
2334
+ metaFrameworks: [],
2335
+ bundlers: [],
2336
+ css: [],
2337
+ backend: [],
2338
+ orm: [],
2339
+ testing: [],
2340
+ lintFormat: [],
2341
+ apiMessaging: [],
2342
+ observability: []
2343
+ };
2344
+ const packageVersions = /* @__PURE__ */ new Map();
2345
+ for (const project of projects) {
2346
+ for (const dep of project.dependencies) {
2347
+ if (!packageVersions.has(dep.package)) {
2348
+ packageVersions.set(dep.package, dep.resolvedVersion);
2349
+ }
2350
+ }
2351
+ }
2352
+ for (const [category, packages] of Object.entries(CATEGORIES)) {
2353
+ const items = [];
2354
+ for (const [pkg2, displayName] of Object.entries(packages)) {
2355
+ if (packageVersions.has(pkg2)) {
2356
+ items.push({
2357
+ name: displayName,
2358
+ package: pkg2,
2359
+ version: packageVersions.get(pkg2) ?? null
2360
+ });
2361
+ }
2362
+ }
2363
+ items.sort((a, b) => a.name.localeCompare(b.name));
2364
+ result[category] = items;
2365
+ }
2366
+ return result;
2367
+ }
2368
+
2369
+ // src/scanners/build-deploy.ts
2370
+ import * as path8 from "path";
2371
+ var CI_FILES = {
2372
+ ".github/workflows": "github-actions",
2373
+ ".gitlab-ci.yml": "gitlab-ci",
2374
+ "azure-pipelines.yml": "azure-devops",
2375
+ "bitbucket-pipelines.yml": "bitbucket-pipelines",
2376
+ "Jenkinsfile": "jenkins",
2377
+ ".circleci/config.yml": "circleci",
2378
+ ".travis.yml": "travis-ci"
2379
+ };
2380
+ var RELEASE_PACKAGES = /* @__PURE__ */ new Set([
2381
+ "semantic-release",
2382
+ "@changesets/cli",
2383
+ "standard-version",
2384
+ "release-it",
2385
+ "auto",
2386
+ "lerna"
2387
+ ]);
2388
+ var RELEASE_FILES = {
2389
+ ".changeset": "changesets",
2390
+ ".releaserc": "semantic-release",
2391
+ ".releaserc.json": "semantic-release",
2392
+ ".releaserc.yml": "semantic-release",
2393
+ "release.config.js": "semantic-release",
2394
+ "release.config.cjs": "semantic-release",
2395
+ "GitVersion.yml": "gitversion"
2396
+ };
2397
+ var MONOREPO_FILES = {
2398
+ "pnpm-workspace.yaml": "pnpm-workspaces",
2399
+ "lerna.json": "lerna",
2400
+ "nx.json": "nx",
2401
+ "turbo.json": "turbo",
2402
+ "rush.json": "rush"
2403
+ };
2404
+ var IAC_EXTENSIONS = {
2405
+ ".tf": "terraform",
2406
+ ".bicep": "bicep"
2407
+ };
2408
+ async function scanBuildDeploy(rootDir) {
2409
+ const result = {
2410
+ ci: [],
2411
+ ciWorkflowCount: 0,
2412
+ docker: { dockerfileCount: 0, baseImages: [] },
2413
+ iac: [],
2414
+ releaseTooling: [],
2415
+ packageManagers: [],
2416
+ monorepoTools: []
2417
+ };
2418
+ const ciSystems = /* @__PURE__ */ new Set();
2419
+ for (const [file, system] of Object.entries(CI_FILES)) {
2420
+ const fullPath = path8.join(rootDir, file);
2421
+ if (await pathExists(fullPath)) {
2422
+ ciSystems.add(system);
2423
+ }
2424
+ }
2425
+ const ghWorkflowDir = path8.join(rootDir, ".github", "workflows");
2426
+ if (await pathExists(ghWorkflowDir)) {
2427
+ try {
2428
+ const files = await findFiles(
2429
+ ghWorkflowDir,
2430
+ (name) => name.endsWith(".yml") || name.endsWith(".yaml")
2431
+ );
2432
+ result.ciWorkflowCount = files.length;
2433
+ } catch {
2434
+ }
2435
+ }
2436
+ result.ci = [...ciSystems].sort();
2437
+ const dockerfiles = await findFiles(
2438
+ rootDir,
2439
+ (name) => name === "Dockerfile" || name.startsWith("Dockerfile.")
2440
+ );
2441
+ result.docker.dockerfileCount = dockerfiles.length;
2442
+ const baseImages = /* @__PURE__ */ new Set();
2443
+ for (const df of dockerfiles) {
2444
+ try {
2445
+ const content = await readTextFile(df);
2446
+ for (const line of content.split("\n")) {
2447
+ const trimmed = line.trim();
2448
+ if (/^FROM\s+/i.test(trimmed)) {
2449
+ const parts = trimmed.split(/\s+/);
2450
+ let imageIdx = 1;
2451
+ if (parts[1]?.startsWith("--")) {
2452
+ imageIdx = parts[1].includes("=") ? 2 : 3;
2453
+ }
2454
+ if (parts[imageIdx]) {
2455
+ baseImages.add(parts[imageIdx]);
2456
+ }
2457
+ }
2458
+ }
2459
+ } catch {
2460
+ }
2461
+ }
2462
+ result.docker.baseImages = [...baseImages].sort();
2463
+ const iacSystems = /* @__PURE__ */ new Set();
2464
+ for (const [ext, system] of Object.entries(IAC_EXTENSIONS)) {
2465
+ const files = await findFiles(rootDir, (name) => name.endsWith(ext));
2466
+ if (files.length > 0) iacSystems.add(system);
2467
+ }
2468
+ const cfnFiles = await findFiles(
2469
+ rootDir,
2470
+ (name) => name.endsWith(".cfn.json") || name.endsWith(".cfn.yaml")
2471
+ );
2472
+ if (cfnFiles.length > 0) iacSystems.add("cloudformation");
2473
+ if (await pathExists(path8.join(rootDir, "Pulumi.yaml"))) iacSystems.add("pulumi");
2474
+ result.iac = [...iacSystems].sort();
2475
+ const releaseTools = /* @__PURE__ */ new Set();
2476
+ for (const [file, tool] of Object.entries(RELEASE_FILES)) {
2477
+ if (await pathExists(path8.join(rootDir, file))) releaseTools.add(tool);
2478
+ }
2479
+ const pkgFiles = await findPackageJsonFiles(rootDir);
2480
+ for (const pjPath of pkgFiles) {
2481
+ try {
2482
+ const pj = await readJsonFile(pjPath);
2483
+ for (const section of ["dependencies", "devDependencies"]) {
2484
+ const deps = pj[section];
2485
+ if (!deps) continue;
2486
+ for (const name of Object.keys(deps)) {
2487
+ if (RELEASE_PACKAGES.has(name)) releaseTools.add(name);
2488
+ }
2489
+ }
2490
+ } catch {
2491
+ }
2492
+ }
2493
+ result.releaseTooling = [...releaseTools].sort();
2494
+ const lockfileMap = {
2495
+ "pnpm-lock.yaml": "pnpm",
2496
+ "package-lock.json": "npm",
2497
+ "yarn.lock": "yarn",
2498
+ "bun.lockb": "bun"
2499
+ };
2500
+ const managers = /* @__PURE__ */ new Set();
2501
+ for (const [file, manager] of Object.entries(lockfileMap)) {
2502
+ if (await pathExists(path8.join(rootDir, file))) managers.add(manager);
2503
+ }
2504
+ result.packageManagers = [...managers].sort();
2505
+ const monoTools = /* @__PURE__ */ new Set();
2506
+ for (const [file, tool] of Object.entries(MONOREPO_FILES)) {
2507
+ if (await pathExists(path8.join(rootDir, file))) monoTools.add(tool);
2508
+ }
2509
+ result.monorepoTools = [...monoTools].sort();
2510
+ return result;
2511
+ }
2512
+
2513
+ // src/scanners/ts-modernity.ts
2514
+ import * as path9 from "path";
2515
+ async function scanTsModernity(rootDir) {
2516
+ const result = {
2517
+ typescriptVersion: null,
2518
+ strict: null,
2519
+ noImplicitAny: null,
2520
+ strictNullChecks: null,
2521
+ module: null,
2522
+ moduleResolution: null,
2523
+ target: null,
2524
+ moduleType: null,
2525
+ exportsField: false
2526
+ };
2527
+ const pkgFiles = await findPackageJsonFiles(rootDir);
2528
+ let hasEsm = false;
2529
+ let hasCjs = false;
2530
+ for (const pjPath of pkgFiles) {
2531
+ try {
2532
+ const pj = await readJsonFile(pjPath);
2533
+ if (!result.typescriptVersion) {
2534
+ const tsVer = pj.devDependencies?.["typescript"] ?? pj.dependencies?.["typescript"];
2535
+ if (tsVer) {
2536
+ result.typescriptVersion = tsVer.replace(/^[\^~>=<\s]+/, "");
2537
+ }
2538
+ }
2539
+ const typeField = pj.type;
2540
+ if (typeField === "module") hasEsm = true;
2541
+ else if (typeField === "commonjs") hasCjs = true;
2542
+ else if (!typeField) hasCjs = true;
2543
+ if (pj.exports) {
2544
+ result.exportsField = true;
2545
+ }
2546
+ } catch {
2547
+ }
2548
+ }
2549
+ if (hasEsm && hasCjs) result.moduleType = "mixed";
2550
+ else if (hasEsm) result.moduleType = "esm";
2551
+ else if (hasCjs) result.moduleType = "cjs";
2552
+ let tsConfigPath = path9.join(rootDir, "tsconfig.json");
2553
+ if (!await pathExists(tsConfigPath)) {
2554
+ const tsConfigs = await findFiles(rootDir, (name) => name === "tsconfig.json");
2555
+ if (tsConfigs.length > 0) {
2556
+ tsConfigPath = tsConfigs[0];
2557
+ } else {
2558
+ return result;
2559
+ }
2560
+ }
2561
+ try {
2562
+ const raw = await readTextFile(tsConfigPath);
2563
+ const stripped = raw.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "").replace(/,(\s*[}\]])/g, "$1");
2564
+ const tsConfig = JSON.parse(stripped);
2565
+ const co = tsConfig?.compilerOptions;
2566
+ if (co) {
2567
+ if (typeof co.strict === "boolean") result.strict = co.strict;
2568
+ if (typeof co.noImplicitAny === "boolean") result.noImplicitAny = co.noImplicitAny;
2569
+ if (typeof co.strictNullChecks === "boolean") result.strictNullChecks = co.strictNullChecks;
2570
+ if (typeof co.module === "string") result.module = co.module;
2571
+ if (typeof co.moduleResolution === "string") result.moduleResolution = co.moduleResolution;
2572
+ if (typeof co.target === "string") result.target = co.target;
2573
+ }
2574
+ } catch {
2575
+ }
2576
+ return result;
2577
+ }
2578
+
2579
+ // src/scanners/breaking-change.ts
2580
+ var DEPRECATED_PACKAGES2 = /* @__PURE__ */ new Set([
2581
+ // Fully deprecated / archived
2582
+ "request",
2583
+ // deprecated 2020, use undici/node fetch
2584
+ "request-promise",
2585
+ // deprecated with request
2586
+ "request-promise-native",
2587
+ // deprecated with request
2588
+ "moment",
2589
+ // deprecated, use date-fns / luxon / Temporal
2590
+ "node-sass",
2591
+ // deprecated, use sass (Dart Sass)
2592
+ "tslint",
2593
+ // archived, use eslint + typescript-eslint
2594
+ "aws-sdk",
2595
+ // AWS SDK v2 EOL, use @aws-sdk/* v3
2596
+ "babel-core",
2597
+ // replaced by @babel/core
2598
+ "babel-preset-env",
2599
+ // replaced by @babel/preset-env
2600
+ "babel-preset-react",
2601
+ // replaced by @babel/preset-react
2602
+ "babel-loader",
2603
+ // 7.x deprecated, must pair with @babel/core
2604
+ "core-js",
2605
+ // v2 deprecated (v3 ok but signals old config)
2606
+ "istanbul",
2607
+ // replaced by nyc / c8
2608
+ "istanbul-instrumenter-loader",
2609
+ // deprecated with istanbul
2610
+ "left-pad",
2611
+ // meme package, use String.padStart
2612
+ "popper.js",
2613
+ // deprecated, use @popperjs/core
2614
+ "create-react-class",
2615
+ // deprecated React pattern
2616
+ "react-addons-css-transition-group",
2617
+ // deprecated React addon
2618
+ "react-addons-test-utils",
2619
+ // use react-dom/test-utils
2620
+ "@types/express-serve-static-core",
2621
+ // now shipped with express
2622
+ "enzyme",
2623
+ // unmaintained, use Testing Library or Vitest
2624
+ "enzyme-adapter-react-16",
2625
+ // unmaintained
2626
+ "enzyme-adapter-react-17",
2627
+ // unmaintained
2628
+ "react-hot-loader",
2629
+ // deprecated, use React Fast Refresh
2630
+ "react-loadable",
2631
+ // unmaintained, use React.lazy
2632
+ "react-router-dom-v5-compat",
2633
+ // migration shim
2634
+ "redux-thunk",
2635
+ // bundled into @reduxjs/toolkit since v1.5
2636
+ "redux-saga",
2637
+ // declining, consider RTK Query
2638
+ "recompose",
2639
+ // archived, use hooks
2640
+ "classnames",
2641
+ // works but clsx is faster & smaller
2642
+ "glamor",
2643
+ // deprecated CSS-in-JS
2644
+ "radium",
2645
+ // deprecated CSS-in-JS
2646
+ "material-ui",
2647
+ // replaced by @mui/material
2648
+ "@material-ui/core",
2649
+ // replaced by @mui/material
2650
+ // Unmaintained / abandoned
2651
+ "bower",
2652
+ // dead package manager
2653
+ "grunt",
2654
+ // superseded by npm scripts / modern bundlers
2655
+ "gulp",
2656
+ // declining, modern bundlers preferred
2657
+ "browserify",
2658
+ // superseded by modern bundlers
2659
+ "coffee-script",
2660
+ // CoffeeScript 1.x deprecated
2661
+ "coffeescript",
2662
+ // declining
2663
+ "jade",
2664
+ // renamed to pug
2665
+ "nomnom",
2666
+ // deprecated CLI parser
2667
+ "optimist",
2668
+ // deprecated CLI parser, use yargs/commander
2669
+ "minimist",
2670
+ // unmaintained, use mri or parseArgs
2671
+ "colors",
2672
+ // supply chain compromised, use chalk/picocolors
2673
+ "faker",
2674
+ // supply chain compromised, use @faker-js/faker
2675
+ "event-stream",
2676
+ // supply chain incident
2677
+ "ua-parser-js",
2678
+ // had supply chain incident
2679
+ "caniuse-db",
2680
+ // replaced by caniuse-lite
2681
+ "circular-json",
2682
+ // deprecated, use flatted
2683
+ "mkdirp",
2684
+ // Node has fs.mkdir recursive since v10
2685
+ "rimraf",
2686
+ // Node has fs.rm recursive since v14
2687
+ "glob",
2688
+ // consider fast-glob or Node fs.glob
2689
+ "swig",
2690
+ // abandoned template engine
2691
+ "dustjs-linkedin",
2692
+ // abandoned template engine
2693
+ "hogan.js",
2694
+ // abandoned template engine
2695
+ "passport-local-mongoose",
2696
+ // low maintenance
2697
+ // Known breaking-change magnets
2698
+ "@angular/http",
2699
+ // removed in Angular 15+
2700
+ "rxjs-compat",
2701
+ // migration shim for RxJS 5→6
2702
+ "protractor",
2703
+ // deprecated by Angular team
2704
+ "karma",
2705
+ // deprecated, Vitest/Jest preferred
2706
+ "karma-jasmine",
2707
+ // deprecated with Karma
2708
+ "jasmine"
2709
+ // declining in usage
2710
+ ]);
2711
+ var LEGACY_POLYFILLS = /* @__PURE__ */ new Set([
2712
+ // Built-in fetch & web APIs (Node 18+)
2713
+ "node-fetch",
2714
+ // native fetch since Node 18
2715
+ "cross-fetch",
2716
+ // native fetch since Node 18
2717
+ "isomorphic-fetch",
2718
+ // native fetch since Node 18
2719
+ "whatwg-fetch",
2720
+ // native fetch since Node 18
2721
+ "abort-controller",
2722
+ // native AbortController since Node 15
2723
+ "form-data",
2724
+ // native FormData since Node 18
2725
+ "formdata-polyfill",
2726
+ // native FormData since Node 18
2727
+ "web-streams-polyfill",
2728
+ // native ReadableStream since Node 18
2729
+ "whatwg-url",
2730
+ // native URL since Node 10
2731
+ "url-parse",
2732
+ // native URL since Node 10
2733
+ "domexception",
2734
+ // native DOMException since Node 17
2735
+ "abortcontroller-polyfill",
2736
+ // native since Node 15
2737
+ // Built-in Node modules shimmed for browser
2738
+ "querystring",
2739
+ // URLSearchParams preferred, native in Node
2740
+ "string_decoder",
2741
+ // native TextDecoder preferred
2742
+ "buffer",
2743
+ // native Buffer in Node, Uint8Array in browser
2744
+ "events",
2745
+ // native EventTarget preferred
2746
+ "path-browserify",
2747
+ // browser shim
2748
+ "stream-browserify",
2749
+ // browser shim
2750
+ "stream-http",
2751
+ // browser shim
2752
+ "https-browserify",
2753
+ // browser shim
2754
+ "os-browserify",
2755
+ // browser shim
2756
+ "crypto-browserify",
2757
+ // native Web Crypto API
2758
+ "assert",
2759
+ // native assert in Node, console.assert in browser
2760
+ "util",
2761
+ // Node native, deprecations in browser bundles
2762
+ "process",
2763
+ // shimmed, usually unnecessary
2764
+ "timers-browserify",
2765
+ // native timers
2766
+ "tty-browserify",
2767
+ // rarely needed
2768
+ "vm-browserify",
2769
+ // rarely needed
2770
+ "domain-browser",
2771
+ // domains deprecated in Node
2772
+ "punycode",
2773
+ // native in URL since Node 10
2774
+ "readable-stream",
2775
+ // Node streams polyfill, usually unnecessary
2776
+ // ES / language polyfills for ES2015+ (Node 18+ has all)
2777
+ "es6-promise",
2778
+ // native Promise since Node 4
2779
+ "promise-polyfill",
2780
+ // native Promise
2781
+ "es6-symbol",
2782
+ // native Symbol since Node 4
2783
+ "es6-map",
2784
+ // native Map since Node 4
2785
+ "es6-set",
2786
+ // native Set since Node 4
2787
+ "es6-weak-map",
2788
+ // native WeakMap since Node 4
2789
+ "es6-iterator",
2790
+ // native iterators
2791
+ "object-assign",
2792
+ // native Object.assign since Node 4
2793
+ "object.assign",
2794
+ // same
2795
+ "array.prototype.find",
2796
+ // native since ES2015
2797
+ "array.prototype.findindex",
2798
+ // native since ES2015
2799
+ "array.prototype.flat",
2800
+ // native since Node 11
2801
+ "array.prototype.flatmap",
2802
+ // native since Node 11
2803
+ "array-includes",
2804
+ // native Array.includes since Node 6
2805
+ "string.prototype.startswith",
2806
+ // native since ES2015
2807
+ "string.prototype.endswith",
2808
+ // native since ES2015
2809
+ "string.prototype.includes",
2810
+ // native since ES2015
2811
+ "string.prototype.padstart",
2812
+ // native since Node 8
2813
+ "string.prototype.padend",
2814
+ // native since Node 8
2815
+ "string.prototype.matchall",
2816
+ // native since Node 12
2817
+ "string.prototype.replaceall",
2818
+ // native since Node 15
2819
+ "string.prototype.trimstart",
2820
+ // native since Node 10
2821
+ "string.prototype.trimend",
2822
+ // native since Node 10
2823
+ "string.prototype.at",
2824
+ // native since Node 16.6
2825
+ "object.entries",
2826
+ // native since Node 7
2827
+ "object.values",
2828
+ // native since Node 7
2829
+ "object.fromentries",
2830
+ // native since Node 12
2831
+ "globalthis",
2832
+ // native globalThis since Node 12
2833
+ "symbol-observable",
2834
+ // TC39 withdrawn
2835
+ "setimmediate",
2836
+ // Node-only, setTimeout(fn, 0) preferred
2837
+ "regenerator-runtime",
2838
+ // async/await native since Node 8
2839
+ "@babel/polyfill",
2840
+ // deprecated, use core-js directly
2841
+ "whatwg-encoding",
2842
+ // native TextEncoder/TextDecoder
2843
+ "text-encoding",
2844
+ // native TextEncoder/TextDecoder since Node 11
2845
+ "encoding",
2846
+ // native TextEncoder/TextDecoder
2847
+ "unorm",
2848
+ // native String.normalize since Node 0.12
2849
+ "number.isnan",
2850
+ // native Number.isNaN since Node 0.12
2851
+ "is-nan",
2852
+ // native Number.isNaN
2853
+ "has-symbols",
2854
+ // native Symbol detection
2855
+ "has",
2856
+ // native Object.hasOwn since Node 16.9
2857
+ "hasown",
2858
+ // shim for Object.hasOwn
2859
+ "safe-buffer",
2860
+ // Buffer.from available since Node 5.10
2861
+ "safer-buffer"
2862
+ // Buffer.from available since Node 5.10
2863
+ ]);
2864
+ function scanBreakingChangeExposure(projects) {
2865
+ const deprecated = /* @__PURE__ */ new Set();
2866
+ const legacyPolyfills = /* @__PURE__ */ new Set();
2867
+ let peerConflictsDetected = false;
2868
+ const allDeps = /* @__PURE__ */ new Set();
2869
+ for (const project of projects) {
2870
+ for (const dep of project.dependencies) {
2871
+ allDeps.add(dep.package);
2872
+ if (DEPRECATED_PACKAGES2.has(dep.package)) {
2873
+ deprecated.add(dep.package);
2874
+ }
2875
+ if (LEGACY_POLYFILLS.has(dep.package)) {
2876
+ legacyPolyfills.add(dep.package);
2877
+ }
2878
+ if (dep.section === "peerDependencies" && dep.majorsBehind !== null && dep.majorsBehind >= 2) {
2879
+ peerConflictsDetected = true;
2880
+ }
2881
+ }
2882
+ }
2883
+ let score = 0;
2884
+ score += Math.min(deprecated.size * 10, 40);
2885
+ score += Math.min(legacyPolyfills.size * 5, 30);
2886
+ score += peerConflictsDetected ? 20 : 0;
2887
+ score = Math.min(score, 100);
2888
+ return {
2889
+ deprecatedPackages: [...deprecated].sort(),
2890
+ legacyPolyfills: [...legacyPolyfills].sort(),
2891
+ peerConflictsDetected,
2892
+ exposureScore: score
2893
+ };
2894
+ }
2895
+
2896
+ // src/scanners/file-hotspots.ts
2897
+ import * as fs4 from "fs/promises";
2898
+ import * as path10 from "path";
2899
+ var SKIP_DIRS2 = /* @__PURE__ */ new Set([
2900
+ "node_modules",
2901
+ ".git",
2902
+ ".next",
2903
+ "dist",
2904
+ "build",
2905
+ "out",
2906
+ ".turbo",
2907
+ ".cache",
2908
+ "coverage",
2909
+ "bin",
2910
+ "obj",
2911
+ ".vs",
2912
+ "TestResults",
2913
+ ".nuxt",
2914
+ ".output",
2915
+ ".svelte-kit"
2916
+ ]);
2917
+ var SKIP_EXTENSIONS = /* @__PURE__ */ new Set([
2918
+ ".map",
2919
+ ".lock",
2920
+ ".png",
2921
+ ".jpg",
2922
+ ".jpeg",
2923
+ ".gif",
2924
+ ".ico",
2925
+ ".svg",
2926
+ ".woff",
2927
+ ".woff2",
2928
+ ".ttf",
2929
+ ".eot",
2930
+ ".mp4",
2931
+ ".webm"
2932
+ ]);
2933
+ async function scanFileHotspots(rootDir) {
2934
+ const extensionCounts = {};
2935
+ const allFiles = [];
2936
+ let maxDepth = 0;
2937
+ async function walk(dir, depth) {
2938
+ if (depth > maxDepth) maxDepth = depth;
2939
+ let entries;
2940
+ try {
2941
+ const dirents = await fs4.readdir(dir, { withFileTypes: true });
2942
+ entries = dirents.map((d) => ({
2943
+ name: d.name,
2944
+ isDirectory: d.isDirectory(),
2945
+ isFile: d.isFile()
2946
+ }));
2947
+ } catch {
2948
+ return;
2949
+ }
2950
+ for (const e of entries) {
2951
+ if (e.isDirectory) {
2952
+ if (SKIP_DIRS2.has(e.name)) continue;
2953
+ await walk(path10.join(dir, e.name), depth + 1);
2954
+ } else if (e.isFile) {
2955
+ const ext = path10.extname(e.name).toLowerCase();
2956
+ if (SKIP_EXTENSIONS.has(ext)) continue;
2957
+ extensionCounts[ext] = (extensionCounts[ext] ?? 0) + 1;
2958
+ try {
2959
+ const stat3 = await fs4.stat(path10.join(dir, e.name));
2960
+ allFiles.push({
2961
+ path: path10.relative(rootDir, path10.join(dir, e.name)),
2962
+ bytes: stat3.size
2963
+ });
2964
+ } catch {
2965
+ }
2966
+ }
2967
+ }
2968
+ }
2969
+ await walk(rootDir, 0);
2970
+ allFiles.sort((a, b) => b.bytes - a.bytes);
2971
+ const largestFiles = allFiles.slice(0, 20);
2972
+ return {
2973
+ fileCountByExtension: extensionCounts,
2974
+ largestFiles,
2975
+ totalFiles: allFiles.length,
2976
+ maxDirectoryDepth: maxDepth,
2977
+ mostUsedPackages: []
2978
+ // Filled in by caller with project data
2979
+ };
2980
+ }
2981
+
2982
+ // src/scanners/security-posture.ts
2983
+ import * as path11 from "path";
2984
+ var LOCKFILES = {
2985
+ "pnpm-lock.yaml": "pnpm",
2986
+ "package-lock.json": "npm",
2987
+ "yarn.lock": "yarn",
2988
+ "bun.lockb": "bun",
2989
+ "packages.lock.json": "nuget"
2990
+ };
2991
+ async function scanSecurityPosture(rootDir) {
2992
+ const result = {
2993
+ lockfilePresent: false,
2994
+ multipleLockfileTypes: false,
2995
+ gitignoreCoversEnv: false,
2996
+ gitignoreCoversNodeModules: false,
2997
+ envFilesTracked: false,
2998
+ lockfileTypes: []
2999
+ };
3000
+ const foundLockfiles = [];
3001
+ for (const [file, type] of Object.entries(LOCKFILES)) {
3002
+ if (await pathExists(path11.join(rootDir, file))) {
3003
+ foundLockfiles.push(type);
3004
+ }
3005
+ }
3006
+ result.lockfilePresent = foundLockfiles.length > 0;
3007
+ result.multipleLockfileTypes = foundLockfiles.length > 1;
3008
+ result.lockfileTypes = foundLockfiles.sort();
3009
+ const gitignorePath = path11.join(rootDir, ".gitignore");
3010
+ if (await pathExists(gitignorePath)) {
3011
+ try {
3012
+ const content = await readTextFile(gitignorePath);
3013
+ const lines = content.split("\n").map((l) => l.trim());
3014
+ result.gitignoreCoversEnv = lines.some(
3015
+ (line) => line === ".env" || line === ".env*" || line === ".env.*" || line === ".env.local" || line === "*.env"
3016
+ );
3017
+ result.gitignoreCoversNodeModules = lines.some(
3018
+ (line) => line === "node_modules" || line === "node_modules/" || line === "/node_modules"
3019
+ );
3020
+ } catch {
3021
+ }
3022
+ }
3023
+ for (const envFile of [".env", ".env.local", ".env.development", ".env.production"]) {
3024
+ if (await pathExists(path11.join(rootDir, envFile))) {
3025
+ if (!result.gitignoreCoversEnv) {
3026
+ result.envFilesTracked = true;
3027
+ break;
3028
+ }
3029
+ }
3030
+ }
3031
+ return result;
3032
+ }
3033
+
3034
+ // src/scanners/service-dependencies.ts
3035
+ var SERVICE_CATEGORIES = {
3036
+ payment: {
3037
+ "stripe": "Stripe",
3038
+ "@stripe/stripe-js": "Stripe.js",
3039
+ "@stripe/react-stripe-js": "Stripe React",
3040
+ "braintree": "Braintree",
3041
+ "braintree-web": "Braintree Web",
3042
+ "@paypal/checkout-server-sdk": "PayPal",
3043
+ "@paypal/react-paypal-js": "PayPal React",
3044
+ "@paypal/paypal-js": "PayPal.js",
3045
+ "square": "Square",
3046
+ "razorpay": "Razorpay",
3047
+ "adyen-api-library": "Adyen",
3048
+ "@adyen/api-library": "Adyen",
3049
+ "@adyen/adyen-web": "Adyen Web",
3050
+ "mollie-api-node": "Mollie",
3051
+ "@lemonsqueezy/lemonsqueezy.js": "Lemon Squeezy",
3052
+ "paddle-sdk": "Paddle",
3053
+ "@paddle/paddle-node-sdk": "Paddle",
3054
+ "coinbase-commerce-node": "Coinbase Commerce",
3055
+ "gocardless-nodejs": "GoCardless",
3056
+ "klarna-checkout": "Klarna",
3057
+ "@recurly/recurly-js": "Recurly",
3058
+ "chargebee": "Chargebee"
3059
+ },
3060
+ auth: {
3061
+ "@auth0/auth0-spa-js": "Auth0 SPA",
3062
+ "@auth0/auth0-react": "Auth0 React",
3063
+ "@auth0/nextjs-auth0": "Auth0 Next.js",
3064
+ "auth0": "Auth0",
3065
+ "@clerk/clerk-sdk-node": "Clerk (Node)",
3066
+ "@clerk/nextjs": "Clerk (Next.js)",
3067
+ "@clerk/clerk-react": "Clerk (React)",
3068
+ "firebase-admin": "Firebase Admin",
3069
+ "firebase": "Firebase",
3070
+ "@firebase/auth": "Firebase Auth",
3071
+ "passport": "Passport.js",
3072
+ "passport-local": "Passport Local",
3073
+ "passport-google-oauth20": "Passport Google",
3074
+ "passport-github2": "Passport GitHub",
3075
+ "next-auth": "NextAuth",
3076
+ "@auth/core": "Auth.js",
3077
+ "@supabase/supabase-js": "Supabase",
3078
+ "@supabase/auth-helpers-nextjs": "Supabase Auth (Next.js)",
3079
+ "@supabase/ssr": "Supabase SSR",
3080
+ "jsonwebtoken": "JWT",
3081
+ "jose": "JOSE",
3082
+ "@okta/okta-auth-js": "Okta",
3083
+ "@okta/okta-react": "Okta React",
3084
+ "oidc-client-ts": "OIDC Client",
3085
+ "@casl/ability": "CASL",
3086
+ "casbin": "Casbin",
3087
+ "keycloak-js": "Keycloak",
3088
+ "@keycloak/keycloak-admin-client": "Keycloak Admin",
3089
+ "lucia": "Lucia Auth",
3090
+ "@lucia-auth/adapter-prisma": "Lucia (Prisma)",
3091
+ "arctic": "Arctic (OAuth)",
3092
+ "better-auth": "Better Auth",
3093
+ "grant": "Grant (OAuth)",
3094
+ "@workos-inc/node": "WorkOS",
3095
+ "stytch": "Stytch",
3096
+ "frontegg": "Frontegg",
3097
+ "@descope/node-sdk": "Descope",
3098
+ "kinde-auth-nextjs": "Kinde"
3099
+ },
3100
+ email: {
3101
+ "@sendgrid/mail": "SendGrid",
3102
+ "@sendgrid/client": "SendGrid Client",
3103
+ "nodemailer": "Nodemailer",
3104
+ "twilio": "Twilio",
3105
+ "@aws-sdk/client-ses": "AWS SES",
3106
+ "@aws-sdk/client-sesv2": "AWS SES v2",
3107
+ "postmark": "Postmark",
3108
+ "mailgun.js": "Mailgun",
3109
+ "resend": "Resend",
3110
+ "@react-email/components": "React Email",
3111
+ "react-email": "React Email",
3112
+ "mjml": "MJML",
3113
+ "email-templates": "Email Templates",
3114
+ "@mailchimp/mailchimp_marketing": "Mailchimp",
3115
+ "@mailchimp/mailchimp_transactional": "Mailchimp Transactional",
3116
+ "plunk-node": "Plunk",
3117
+ "brevo": "Brevo (Sendinblue)",
3118
+ "sib-api-v3-sdk": "Brevo v3",
3119
+ "@customer.io/node": "Customer.io",
3120
+ "sparkpost": "SparkPost",
3121
+ "mandrill-api": "Mandrill",
3122
+ "mail-listener5": "Mail Listener",
3123
+ "@azure/communication-email": "Azure Email"
3124
+ },
3125
+ cloud: {
3126
+ // AWS
3127
+ "aws-sdk": "AWS SDK v2",
3128
+ "@aws-sdk/client-s3": "AWS S3",
3129
+ "@aws-sdk/client-lambda": "AWS Lambda",
3130
+ "@aws-sdk/client-dynamodb": "AWS DynamoDB",
3131
+ "@aws-sdk/client-ec2": "AWS EC2",
3132
+ "@aws-sdk/client-ecs": "AWS ECS",
3133
+ "@aws-sdk/client-ecr": "AWS ECR",
3134
+ "@aws-sdk/client-cloudformation": "AWS CloudFormation",
3135
+ "@aws-sdk/client-cloudwatch": "AWS CloudWatch",
3136
+ "@aws-sdk/client-cloudfront": "AWS CloudFront",
3137
+ "@aws-sdk/client-iam": "AWS IAM",
3138
+ "@aws-sdk/client-secrets-manager": "AWS Secrets Manager",
3139
+ "@aws-sdk/client-ssm": "AWS SSM",
3140
+ "@aws-sdk/client-sts": "AWS STS",
3141
+ "@aws-sdk/client-cognito-identity-provider": "AWS Cognito",
3142
+ "@aws-sdk/client-route-53": "AWS Route 53",
3143
+ "@aws-sdk/client-step-functions": "AWS Step Functions",
3144
+ "@aws-sdk/client-eventbridge": "AWS EventBridge",
3145
+ "@aws-sdk/client-kinesis": "AWS Kinesis",
3146
+ "@aws-sdk/client-kms": "AWS KMS",
3147
+ "@aws-sdk/client-athena": "AWS Athena",
3148
+ "@aws-sdk/client-bedrock-runtime": "AWS Bedrock",
3149
+ "@aws-sdk/lib-dynamodb": "AWS DynamoDB Doc",
3150
+ // Azure
3151
+ "@azure/storage-blob": "Azure Blob",
3152
+ "@azure/identity": "Azure Identity",
3153
+ "@azure/cosmos": "Azure Cosmos DB",
3154
+ "@azure/keyvault-secrets": "Azure Key Vault",
3155
+ "@azure/service-bus": "Azure Service Bus",
3156
+ "@azure/event-hubs": "Azure Event Hubs",
3157
+ "@azure/functions": "Azure Functions",
3158
+ "@azure/ai-form-recognizer": "Azure Form Recognizer",
3159
+ "@azure/openai": "Azure OpenAI",
3160
+ "@azure/app-configuration": "Azure App Config",
3161
+ "@azure/monitor-opentelemetry": "Azure Monitor OTel",
3162
+ "@azure/container-registry": "Azure Container Registry",
3163
+ // GCP
3164
+ "@google-cloud/storage": "GCP Storage",
3165
+ "@google-cloud/functions-framework": "GCP Functions",
3166
+ "@google-cloud/bigquery": "GCP BigQuery",
3167
+ "@google-cloud/firestore": "GCP Firestore",
3168
+ "@google-cloud/pubsub": "GCP Pub/Sub",
3169
+ "@google-cloud/secret-manager": "GCP Secret Manager",
3170
+ "@google-cloud/tasks": "GCP Cloud Tasks",
3171
+ "@google-cloud/run": "GCP Cloud Run",
3172
+ "@google-cloud/logging": "GCP Logging",
3173
+ "@google-cloud/translate": "GCP Translate",
3174
+ "@google-cloud/vertexai": "GCP Vertex AI",
3175
+ // Multi-cloud / PaaS
3176
+ "@vercel/sdk": "Vercel",
3177
+ "@vercel/kv": "Vercel KV",
3178
+ "@vercel/blob": "Vercel Blob",
3179
+ "@vercel/postgres": "Vercel Postgres",
3180
+ "@vercel/edge-config": "Vercel Edge Config",
3181
+ "@netlify/functions": "Netlify Functions",
3182
+ "@cloudflare/workers-types": "Cloudflare Workers",
3183
+ "wrangler": "Wrangler",
3184
+ "@railway/cli": "Railway",
3185
+ "@fly/apps": "Fly.io",
3186
+ "heroku-client": "Heroku",
3187
+ "@pulumi/pulumi": "Pulumi",
3188
+ "@pulumi/aws": "Pulumi AWS",
3189
+ "cdktf": "CDK for Terraform",
3190
+ "aws-cdk-lib": "AWS CDK",
3191
+ "sst": "SST"
3192
+ },
3193
+ databases: {
3194
+ "pg": "PostgreSQL",
3195
+ "postgres": "Postgres.js",
3196
+ "mysql2": "MySQL",
3197
+ "mysql": "MySQL (legacy)",
3198
+ "mongodb": "MongoDB",
3199
+ "ioredis": "Redis (ioredis)",
3200
+ "redis": "Redis",
3201
+ "better-sqlite3": "SQLite",
3202
+ "sql.js": "sql.js",
3203
+ "@prisma/client": "Prisma",
3204
+ "drizzle-orm": "Drizzle",
3205
+ "typeorm": "TypeORM",
3206
+ "sequelize": "Sequelize",
3207
+ "knex": "Knex",
3208
+ "kysely": "Kysely",
3209
+ "objection": "Objection.js",
3210
+ "slonik": "Slonik",
3211
+ "massive": "Massive.js",
3212
+ "mongoose": "Mongoose",
3213
+ "@mikro-orm/core": "MikroORM",
3214
+ "mssql": "SQL Server",
3215
+ "oracledb": "Oracle",
3216
+ "cassandra-driver": "Cassandra",
3217
+ "neo4j-driver": "Neo4j",
3218
+ "arangojs": "ArangoDB",
3219
+ "@clickhouse/client": "ClickHouse",
3220
+ "influx": "InfluxDB",
3221
+ "@libsql/client": "libSQL (Turso)",
3222
+ "@neondatabase/serverless": "Neon",
3223
+ "@planetscale/database": "PlanetScale",
3224
+ "@electric-sql/pglite": "PGlite",
3225
+ "@upstash/redis": "Upstash Redis",
3226
+ "@upstash/kafka": "Upstash Kafka",
3227
+ "@upstash/qstash": "Upstash QStash",
3228
+ "@upstash/vector": "Upstash Vector",
3229
+ "dynamoose": "Dynamoose",
3230
+ "fauna": "Fauna",
3231
+ "faunadb": "FaunaDB",
3232
+ "@supabase/postgrest-js": "Supabase PostgREST",
3233
+ "couchbase": "Couchbase",
3234
+ "couchdb-nano": "CouchDB",
3235
+ "duckdb": "DuckDB",
3236
+ "@tidbcloud/serverless": "TiDB Serverless"
3237
+ },
3238
+ messaging: {
3239
+ "@aws-sdk/client-sqs": "AWS SQS",
3240
+ "@aws-sdk/client-sns": "AWS SNS",
3241
+ "amqplib": "RabbitMQ",
3242
+ "kafkajs": "Kafka",
3243
+ "bullmq": "BullMQ",
3244
+ "bull": "Bull",
3245
+ "@google-cloud/pubsub": "GCP Pub/Sub",
3246
+ "bee-queue": "Bee Queue",
3247
+ "pg-boss": "pg-boss",
3248
+ "agenda": "Agenda",
3249
+ "@azure/service-bus": "Azure Service Bus",
3250
+ "@azure/event-hubs": "Azure Event Hubs",
3251
+ "nats": "NATS",
3252
+ "mqtt": "MQTT",
3253
+ "zeromq": "ZeroMQ",
3254
+ "@temporalio/client": "Temporal Client",
3255
+ "@temporalio/worker": "Temporal Worker",
3256
+ "inngest": "Inngest",
3257
+ "@trigger.dev/sdk": "Trigger.dev",
3258
+ "@defer/client": "Defer",
3259
+ "graphile-worker": "Graphile Worker",
3260
+ "@quirrel/quirrel": "Quirrel",
3261
+ "rhea": "AMQP 1.0 (rhea)",
3262
+ "stompit": "STOMP",
3263
+ "@eventstore/db-client": "EventStoreDB",
3264
+ "@aws-sdk/client-eventbridge": "AWS EventBridge",
3265
+ "@aws-sdk/client-kinesis": "AWS Kinesis",
3266
+ "@aws-sdk/client-firehose": "AWS Firehose"
3267
+ },
3268
+ observability: {
3269
+ "@sentry/node": "Sentry (Node)",
3270
+ "@sentry/browser": "Sentry (Browser)",
3271
+ "@sentry/react": "Sentry (React)",
3272
+ "@sentry/nextjs": "Sentry (Next.js)",
3273
+ "@sentry/vue": "Sentry (Vue)",
3274
+ "@sentry/angular": "Sentry (Angular)",
3275
+ "@opentelemetry/api": "OpenTelemetry API",
3276
+ "@opentelemetry/sdk-node": "OpenTelemetry SDK",
3277
+ "@opentelemetry/auto-instrumentations-node": "OpenTelemetry Auto",
3278
+ "applicationinsights": "App Insights",
3279
+ "dd-trace": "Datadog",
3280
+ "@datadog/browser-rum": "Datadog RUM",
3281
+ "newrelic": "New Relic",
3282
+ "@newrelic/browser-agent": "New Relic Browser",
3283
+ "pino": "Pino",
3284
+ "winston": "Winston",
3285
+ "bunyan": "Bunyan",
3286
+ "consola": "Consola",
3287
+ "loglevel": "loglevel",
3288
+ "roarr": "Roarr",
3289
+ "prom-client": "Prometheus",
3290
+ "elastic-apm-node": "Elastic APM",
3291
+ "@logtail/node": "Logtail",
3292
+ "@axiomhq/pino": "Axiom (Pino)",
3293
+ "@axiomhq/winston": "Axiom (Winston)",
3294
+ "@baselime/node-opentelemetry": "Baselime",
3295
+ "highlight.run": "Highlight",
3296
+ "@google-cloud/trace-agent": "GCP Trace",
3297
+ "@google-cloud/logging": "GCP Logging",
3298
+ "@azure/monitor-opentelemetry": "Azure Monitor",
3299
+ "lightstep-tracer": "Lightstep",
3300
+ "honeycomb-beeline": "Honeycomb",
3301
+ "@honeycombio/opentelemetry-node": "Honeycomb OTel",
3302
+ "posthog-node": "PostHog",
3303
+ "@amplitude/node": "Amplitude",
3304
+ "mixpanel": "Mixpanel",
3305
+ "@segment/analytics-node": "Segment",
3306
+ "analytics-node": "Segment (legacy)",
3307
+ "@rudderstack/rudder-sdk-node": "RudderStack",
3308
+ "heap-api": "Heap",
3309
+ "@fullstory/browser": "FullStory",
3310
+ "@logrocket/react": "LogRocket",
3311
+ "logrocket": "LogRocket",
3312
+ "hotjar-js": "Hotjar",
3313
+ "@clarity-ai/clarity": "Microsoft Clarity"
3314
+ },
3315
+ crm: {
3316
+ "xero-node": "Xero",
3317
+ "hubspot-api-client": "HubSpot",
3318
+ "@hubspot/api-client": "HubSpot",
3319
+ "@slack/web-api": "Slack Web API",
3320
+ "@slack/bolt": "Slack Bolt",
3321
+ "@slack/events-api": "Slack Events",
3322
+ "discord.js": "Discord",
3323
+ "intercom-client": "Intercom",
3324
+ "salesforce-sdk": "Salesforce",
3325
+ "jsforce": "JSforce (Salesforce)",
3326
+ "@zendesk/sell-client": "Zendesk",
3327
+ "node-zendesk": "Zendesk",
3328
+ "freshdesk-api": "Freshdesk",
3329
+ "pipedrive": "Pipedrive",
3330
+ "airtable": "Airtable",
3331
+ "@notionhq/client": "Notion",
3332
+ "@linear/sdk": "Linear",
3333
+ "jira-client": "Jira",
3334
+ "jira.js": "Jira.js",
3335
+ "@octokit/rest": "GitHub (Octokit)",
3336
+ "@octokit/core": "GitHub (Octokit Core)",
3337
+ "gitlab": "GitLab",
3338
+ "@gitbeaker/rest": "GitLab (Gitbeaker)",
3339
+ "clickup.js": "ClickUp",
3340
+ "asana": "Asana",
3341
+ "monday-sdk-js": "Monday.com",
3342
+ "telegram-bot-api": "Telegram",
3343
+ "telegraf": "Telegraf (Telegram)",
3344
+ "@microsoft/microsoft-graph-client": "Microsoft Graph",
3345
+ "googleapis": "Google APIs",
3346
+ "@google-cloud/aiplatform": "Google AI Platform"
3347
+ },
3348
+ storage: {
3349
+ "@aws-sdk/client-s3": "AWS S3",
3350
+ "@aws-sdk/s3-request-presigner": "AWS S3 Presigner",
3351
+ "@azure/storage-blob": "Azure Blob",
3352
+ "@azure/storage-queue": "Azure Storage Queue",
3353
+ "@azure/storage-file-share": "Azure File Share",
3354
+ "@google-cloud/storage": "GCP Storage",
3355
+ "minio": "MinIO",
3356
+ "@supabase/storage-js": "Supabase Storage",
3357
+ "cloudinary": "Cloudinary",
3358
+ "uploadthing": "UploadThing",
3359
+ "@uploadthing/react": "UploadThing React",
3360
+ "multer": "Multer",
3361
+ "multer-s3": "Multer S3",
3362
+ "formidable": "Formidable",
3363
+ "busboy": "Busboy",
3364
+ "@vercel/blob": "Vercel Blob",
3365
+ "imagekit": "ImageKit",
3366
+ "sharp": "Sharp (image)",
3367
+ "jimp": "Jimp (image)",
3368
+ "fluent-ffmpeg": "FFmpeg",
3369
+ "@google-cloud/vision": "GCP Vision",
3370
+ "@aws-sdk/client-rekognition": "AWS Rekognition",
3371
+ "file-type": "file-type",
3372
+ "@backblaze-b2/sdk": "Backblaze B2",
3373
+ "wasabi-sdk": "Wasabi"
3374
+ },
3375
+ search: {
3376
+ "@elastic/elasticsearch": "Elasticsearch",
3377
+ "algoliasearch": "Algolia",
3378
+ "@algolia/client-search": "Algolia Client",
3379
+ "react-instantsearch": "Algolia InstantSearch",
3380
+ "meilisearch": "Meilisearch",
3381
+ "typesense": "Typesense",
3382
+ "@opensearch-project/opensearch": "OpenSearch",
3383
+ "solr-client": "Solr",
3384
+ "lunr": "Lunr",
3385
+ "flexsearch": "FlexSearch",
3386
+ "fuse.js": "Fuse.js",
3387
+ "minisearch": "MiniSearch",
3388
+ "orama": "Orama",
3389
+ "@orama/orama": "Orama",
3390
+ "@trieve/trieve-ts-sdk": "Trieve",
3391
+ "pagefind": "Pagefind",
3392
+ // Vector / AI search
3393
+ "@pinecone-database/pinecone": "Pinecone",
3394
+ "chromadb": "Chroma",
3395
+ "@qdrant/js-client-rest": "Qdrant",
3396
+ "weaviate-ts-client": "Weaviate",
3397
+ "@zilliz/milvus2-sdk-node": "Milvus",
3398
+ "openai": "OpenAI",
3399
+ "@anthropic-ai/sdk": "Anthropic",
3400
+ "@langchain/core": "LangChain",
3401
+ "langchain": "LangChain",
3402
+ "@google/generative-ai": "Google Gemini",
3403
+ "cohere-ai": "Cohere",
3404
+ "replicate": "Replicate",
3405
+ "@huggingface/inference": "Hugging Face",
3406
+ "ai": "Vercel AI SDK",
3407
+ "@ai-sdk/openai": "AI SDK (OpenAI)"
3408
+ }
3409
+ };
3410
+ function scanServiceDependencies(projects) {
3411
+ const packageVersions = /* @__PURE__ */ new Map();
3412
+ for (const project of projects) {
3413
+ for (const dep of project.dependencies) {
3414
+ if (!packageVersions.has(dep.package)) {
3415
+ packageVersions.set(dep.package, dep.resolvedVersion);
3416
+ }
3417
+ }
3418
+ }
3419
+ const result = {
3420
+ payment: [],
3421
+ auth: [],
3422
+ email: [],
3423
+ cloud: [],
3424
+ databases: [],
3425
+ messaging: [],
3426
+ observability: [],
3427
+ crm: [],
3428
+ storage: [],
3429
+ search: []
3430
+ };
3431
+ for (const [category, packages] of Object.entries(SERVICE_CATEGORIES)) {
3432
+ const items = [];
3433
+ for (const [pkg2, displayName] of Object.entries(packages)) {
3434
+ if (packageVersions.has(pkg2)) {
3435
+ items.push({
3436
+ name: displayName,
3437
+ package: pkg2,
3438
+ version: packageVersions.get(pkg2) ?? null
3439
+ });
3440
+ }
3441
+ }
3442
+ items.sort((a, b) => a.name.localeCompare(b.name));
3443
+ result[category] = items;
3444
+ }
3445
+ return result;
3446
+ }
3447
+
3448
+ // src/commands/scan.ts
3449
+ async function runScan(rootDir, opts) {
3450
+ const config = await loadConfig(rootDir);
3451
+ const sem = new Semaphore(opts.concurrency);
3452
+ const npmCache = new NpmCache(rootDir, sem);
3453
+ const scanners = config.scanners;
3454
+ const progress = new ScanProgress(rootDir);
3455
+ const steps = [
3456
+ { id: "config", label: "Loading configuration" },
3457
+ { id: "vcs", label: "Detecting version control" },
3458
+ { id: "node", label: "Scanning Node projects" },
3459
+ { id: "dotnet", label: "Scanning .NET projects" },
3460
+ ...scanners !== false ? [
3461
+ ...scanners?.platformMatrix?.enabled !== false ? [{ id: "platform", label: "Platform matrix" }] : [],
3462
+ ...scanners?.toolingInventory?.enabled !== false ? [{ id: "tooling", label: "Tooling inventory" }] : [],
3463
+ ...scanners?.serviceDependencies?.enabled !== false ? [{ id: "services", label: "Service dependencies" }] : [],
3464
+ ...scanners?.breakingChangeExposure?.enabled !== false ? [{ id: "breaking", label: "Breaking change exposure" }] : [],
3465
+ ...scanners?.securityPosture?.enabled !== false ? [{ id: "security", label: "Security posture" }] : [],
3466
+ ...scanners?.buildDeploy?.enabled !== false ? [{ id: "build", label: "Build & deploy analysis" }] : [],
3467
+ ...scanners?.tsModernity?.enabled !== false ? [{ id: "ts", label: "TypeScript modernity" }] : [],
3468
+ ...scanners?.fileHotspots?.enabled !== false ? [{ id: "hotspots", label: "File hotspots" }] : [],
3469
+ ...scanners?.dependencyGraph?.enabled !== false ? [{ id: "depgraph", label: "Dependency graph" }] : [],
3470
+ ...scanners?.dependencyRisk?.enabled !== false ? [{ id: "deprisk", label: "Dependency risk" }] : []
3471
+ ] : [],
3472
+ { id: "drift", label: "Computing drift score" },
3473
+ { id: "findings", label: "Generating findings" }
3474
+ ];
3475
+ progress.setSteps(steps);
3476
+ progress.completeStep("config", "loaded");
3477
+ progress.startStep("vcs");
3478
+ const vcs = await detectVcs(rootDir);
3479
+ const vcsDetail = vcs.type !== "unknown" ? `${vcs.type}${vcs.branch ? ` ${vcs.branch}` : ""}${vcs.shortSha ? ` @ ${vcs.shortSha}` : ""}` : "none detected";
3480
+ progress.completeStep("vcs", vcsDetail);
3481
+ progress.startStep("node");
3482
+ const nodeProjects = await scanNodeProjects(rootDir, npmCache);
3483
+ for (const p of nodeProjects) {
3484
+ progress.addDependencies(p.dependencies.length);
3485
+ progress.addFrameworks(p.frameworks.length);
3486
+ }
3487
+ progress.addProjects(nodeProjects.length);
3488
+ progress.completeStep("node", `${nodeProjects.length} project${nodeProjects.length !== 1 ? "s" : ""}`, nodeProjects.length);
3489
+ progress.startStep("dotnet");
3490
+ const dotnetProjects = await scanDotnetProjects(rootDir);
3491
+ for (const p of dotnetProjects) {
3492
+ progress.addDependencies(p.dependencies.length);
3493
+ progress.addFrameworks(p.frameworks.length);
3494
+ }
3495
+ progress.addProjects(dotnetProjects.length);
3496
+ progress.completeStep("dotnet", `${dotnetProjects.length} project${dotnetProjects.length !== 1 ? "s" : ""}`, dotnetProjects.length);
3497
+ const allProjects = [...nodeProjects, ...dotnetProjects];
3498
+ const extended = {};
3499
+ if (scanners !== false) {
3500
+ if (scanners?.platformMatrix?.enabled !== false) {
3501
+ progress.startStep("platform");
3502
+ extended.platformMatrix = await scanPlatformMatrix(rootDir);
3503
+ const nativeCount = extended.platformMatrix.nativeModules.length;
3504
+ const dockerCount = extended.platformMatrix.dockerBaseImages.length;
3505
+ const parts = [];
3506
+ if (nativeCount > 0) parts.push(`${nativeCount} native`);
3507
+ if (dockerCount > 0) parts.push(`${dockerCount} docker`);
3508
+ progress.completeStep("platform", parts.join(", ") || "clean", nativeCount + dockerCount);
3509
+ }
3510
+ if (scanners?.toolingInventory?.enabled !== false) {
3511
+ progress.startStep("tooling");
3512
+ extended.toolingInventory = scanToolingInventory(allProjects);
3513
+ const toolCount = Object.values(extended.toolingInventory).reduce((sum, arr) => sum + arr.length, 0);
3514
+ progress.completeStep("tooling", `${toolCount} tool${toolCount !== 1 ? "s" : ""} mapped`, toolCount);
3515
+ }
3516
+ if (scanners?.serviceDependencies?.enabled !== false) {
3517
+ progress.startStep("services");
3518
+ extended.serviceDependencies = scanServiceDependencies(allProjects);
3519
+ const svcCount = Object.values(extended.serviceDependencies).reduce((sum, arr) => sum + arr.length, 0);
3520
+ progress.completeStep("services", `${svcCount} service${svcCount !== 1 ? "s" : ""} detected`, svcCount);
3521
+ }
3522
+ if (scanners?.breakingChangeExposure?.enabled !== false) {
3523
+ progress.startStep("breaking");
3524
+ extended.breakingChangeExposure = scanBreakingChangeExposure(allProjects);
3525
+ const bc = extended.breakingChangeExposure;
3526
+ const bcTotal = bc.deprecatedPackages.length + bc.legacyPolyfills.length;
3527
+ progress.completeStep(
3528
+ "breaking",
3529
+ bcTotal > 0 ? `${bc.deprecatedPackages.length} deprecated, ${bc.legacyPolyfills.length} polyfills` : "none found",
3530
+ bcTotal
3531
+ );
3532
+ }
3533
+ if (scanners?.securityPosture?.enabled !== false) {
3534
+ progress.startStep("security");
3535
+ extended.securityPosture = await scanSecurityPosture(rootDir);
3536
+ const sec = extended.securityPosture;
3537
+ const secDetail = sec.lockfilePresent ? `lockfile \u2714${sec.gitignoreCoversEnv ? " \xB7 .env \u2714" : " \xB7 .env \u2716"}` : "no lockfile";
3538
+ progress.completeStep("security", secDetail);
3539
+ }
3540
+ if (scanners?.buildDeploy?.enabled !== false) {
3541
+ progress.startStep("build");
3542
+ extended.buildDeploy = await scanBuildDeploy(rootDir);
3543
+ const bd = extended.buildDeploy;
3544
+ const bdParts = [];
3545
+ if (bd.ci.length > 0) bdParts.push(bd.ci.join(", "));
3546
+ if (bd.docker.dockerfileCount > 0) bdParts.push(`${bd.docker.dockerfileCount} Dockerfile${bd.docker.dockerfileCount !== 1 ? "s" : ""}`);
3547
+ progress.completeStep("build", bdParts.join(" \xB7 ") || "none detected");
3548
+ }
3549
+ if (scanners?.tsModernity?.enabled !== false) {
3550
+ progress.startStep("ts");
3551
+ extended.tsModernity = await scanTsModernity(rootDir);
3552
+ const ts = extended.tsModernity;
3553
+ const tsParts = [];
3554
+ if (ts.typescriptVersion) tsParts.push(`v${ts.typescriptVersion}`);
3555
+ if (ts.strict === true) tsParts.push("strict");
3556
+ if (ts.moduleType) tsParts.push(ts.moduleType.toUpperCase());
3557
+ progress.completeStep("ts", tsParts.join(" \xB7 ") || "no tsconfig");
3558
+ }
3559
+ if (scanners?.fileHotspots?.enabled !== false) {
3560
+ progress.startStep("hotspots");
3561
+ extended.fileHotspots = await scanFileHotspots(rootDir);
3562
+ progress.completeStep("hotspots", `${extended.fileHotspots.totalFiles} files`, extended.fileHotspots.totalFiles);
3563
+ }
3564
+ if (scanners?.dependencyGraph?.enabled !== false) {
3565
+ progress.startStep("depgraph");
3566
+ extended.dependencyGraph = await scanDependencyGraph(rootDir);
3567
+ const dg = extended.dependencyGraph;
3568
+ const dgDetail = dg.lockfileType ? `${dg.lockfileType} \xB7 ${dg.totalUnique} unique` : "no lockfile";
3569
+ progress.completeStep("depgraph", dgDetail, dg.totalUnique);
3570
+ }
3571
+ if (scanners?.dependencyRisk?.enabled !== false) {
3572
+ progress.startStep("deprisk");
3573
+ extended.dependencyRisk = scanDependencyRisk(allProjects);
3574
+ const dr = extended.dependencyRisk;
3575
+ const drParts = [];
3576
+ if (dr.deprecatedPackages.length > 0) drParts.push(`${dr.deprecatedPackages.length} deprecated`);
3577
+ if (dr.nativeModulePackages.length > 0) drParts.push(`${dr.nativeModulePackages.length} native`);
3578
+ progress.completeStep("deprisk", drParts.join(", ") || "low risk");
3579
+ }
3580
+ }
3581
+ progress.startStep("drift");
3582
+ const drift = computeDriftScore(allProjects);
3583
+ progress.completeStep("drift", `${drift.score}/100 \u2014 ${drift.riskLevel} risk`);
3584
+ progress.startStep("findings");
3585
+ const findings = generateFindings(allProjects, config);
3586
+ const warnCount = findings.filter((f) => f.level === "warning").length;
3587
+ const errCount = findings.filter((f) => f.level === "error").length;
3588
+ const noteCount = findings.filter((f) => f.level === "note").length;
3589
+ progress.addFindings(warnCount, errCount, noteCount);
3590
+ const findingParts = [];
3591
+ if (errCount > 0) findingParts.push(`${errCount} error${errCount !== 1 ? "s" : ""}`);
3592
+ if (warnCount > 0) findingParts.push(`${warnCount} warning${warnCount !== 1 ? "s" : ""}`);
3593
+ if (noteCount > 0) findingParts.push(`${noteCount} note${noteCount !== 1 ? "s" : ""}`);
3594
+ progress.completeStep("findings", findingParts.join(", ") || "none");
3595
+ progress.finish();
3596
+ if (allProjects.length === 0) {
3597
+ console.log(chalk3.yellow("No projects found."));
3598
+ }
3599
+ const artifact = {
3600
+ schemaVersion: "1.0",
3601
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3602
+ vibgrateVersion: VERSION,
3603
+ rootPath: path12.basename(rootDir),
3604
+ ...vcs.type !== "unknown" ? { vcs } : {},
3605
+ projects: allProjects,
3606
+ drift,
3607
+ findings,
3608
+ ...Object.keys(extended).length > 0 ? { extended } : {}
3609
+ };
3610
+ if (opts.baseline) {
3611
+ const baselinePath = path12.resolve(opts.baseline);
3612
+ if (await pathExists(baselinePath)) {
3613
+ try {
3614
+ const baseline = await readJsonFile(baselinePath);
3615
+ artifact.baseline = baselinePath;
3616
+ artifact.delta = artifact.drift.score - baseline.drift.score;
3617
+ } catch {
3618
+ console.error(chalk3.yellow(`Warning: Could not read baseline file: ${baselinePath}`));
3619
+ }
3620
+ }
3621
+ }
3622
+ const vibgrateDir = path12.join(rootDir, ".vibgrate");
3623
+ await ensureDir(vibgrateDir);
3624
+ await writeJsonFile(path12.join(vibgrateDir, "scan_result.json"), artifact);
3625
+ if (opts.format === "json") {
3626
+ const jsonStr = JSON.stringify(artifact, null, 2);
3627
+ if (opts.out) {
3628
+ await writeTextFile(path12.resolve(opts.out), jsonStr);
3629
+ console.log(chalk3.green("\u2714") + ` JSON written to ${opts.out}`);
3630
+ } else {
3631
+ console.log(jsonStr);
3632
+ }
3633
+ } else if (opts.format === "sarif") {
3634
+ const sarif = formatSarif(artifact);
3635
+ const sarifStr = JSON.stringify(sarif, null, 2);
3636
+ if (opts.out) {
3637
+ await writeTextFile(path12.resolve(opts.out), sarifStr);
3638
+ console.log(chalk3.green("\u2714") + ` SARIF written to ${opts.out}`);
3639
+ } else {
3640
+ console.log(sarifStr);
3641
+ }
3642
+ } else {
3643
+ const text = formatText(artifact);
3644
+ console.log(text);
3645
+ if (opts.out) {
3646
+ await writeTextFile(path12.resolve(opts.out), text);
3647
+ }
3648
+ }
3649
+ return artifact;
3650
+ }
3651
+ var scanCommand = new Command("scan").description("Scan a project for upgrade drift").argument("[path]", "Path to scan", ".").option("--out <file>", "Output file path").option("--format <format>", "Output format (text|json|sarif)", "text").option("--fail-on <level>", "Fail on warn or error").option("--baseline <file>", "Compare against baseline").option("--changed-only", "Only scan changed files").option("--concurrency <n>", "Max concurrent npm calls", "8").action(async (targetPath, opts) => {
3652
+ const rootDir = path12.resolve(targetPath);
3653
+ if (!await pathExists(rootDir)) {
3654
+ console.error(chalk3.red(`Path does not exist: ${rootDir}`));
3655
+ process.exit(1);
3656
+ }
3657
+ const scanOpts = {
3658
+ out: opts.out,
3659
+ format: opts.format || "text",
3660
+ failOn: opts.failOn,
3661
+ baseline: opts.baseline,
3662
+ changedOnly: opts.changedOnly,
3663
+ concurrency: parseInt(opts.concurrency, 10) || 8
3664
+ };
3665
+ const artifact = await runScan(rootDir, scanOpts);
3666
+ if (opts.failOn) {
3667
+ const hasErrors = artifact.findings.some((f) => f.level === "error");
3668
+ const hasWarnings = artifact.findings.some((f) => f.level === "warning");
3669
+ if (opts.failOn === "error" && hasErrors) {
3670
+ console.error(chalk3.red(`
3671
+ Failing: ${artifact.findings.filter((f) => f.level === "error").length} error finding(s) detected.`));
3672
+ process.exit(2);
3673
+ }
3674
+ if (opts.failOn === "warn" && (hasErrors || hasWarnings)) {
3675
+ console.error(chalk3.red(`
3676
+ Failing: findings detected at warn level or above.`));
3677
+ process.exit(2);
3678
+ }
3679
+ }
3680
+ });
3681
+
3682
+ export {
3683
+ readJsonFile,
3684
+ readTextFile,
3685
+ pathExists,
3686
+ ensureDir,
3687
+ writeJsonFile,
3688
+ writeTextFile,
3689
+ writeDefaultConfig,
3690
+ computeDriftScore,
3691
+ generateFindings,
3692
+ formatText,
3693
+ formatSarif,
3694
+ VERSION,
3695
+ runScan,
3696
+ scanCommand
3697
+ };