@vibgrate/cli 1.0.56 → 1.0.58

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/DOCS.md CHANGED
@@ -770,4 +770,4 @@ import type {
770
770
 
771
771
  ---
772
772
 
773
- Copyright © 2026 Vibgrate. All rights reserved. See [LICENSE](./LICENSE) for terms.
773
+ Copyright © 2026 Vibgrate. All rights reserved. See [LICENSE](https://vibgrate.com/license) for terms.
package/README.md CHANGED
@@ -350,8 +350,8 @@ The CLI writes per-project score files to `.vibgrate/` inside each detected proj
350
350
 
351
351
  ## Full docs
352
352
 
353
- For full command reference, configuration, scanner details, and advanced examples, see [DOCS.md](./DOCS.md).
353
+ For full command reference, configuration, scanner details, and advanced examples, see [CLI reference](https://vibgrate.com/cli).
354
354
 
355
355
  ---
356
356
 
357
- Copyright © 2026 Vibgrate. All rights reserved. See [LICENSE.md](./LICENSE.md) for terms.
357
+ Copyright © 2026 Vibgrate. All rights reserved. See [Vibgrate CLI License](https://vibgrate.com/license) for terms.
@@ -0,0 +1,10 @@
1
+ import {
2
+ baselineCommand,
3
+ runBaseline
4
+ } from "./chunk-KLJ5NIGX.js";
5
+ import "./chunk-H473HICY.js";
6
+ import "./chunk-43ACU2WV.js";
7
+ export {
8
+ baselineCommand,
9
+ runBaseline
10
+ };
@@ -1,7 +1,9 @@
1
1
  // src/utils/fs.ts
2
+ import { execFile } from "child_process";
2
3
  import * as fs from "fs/promises";
3
4
  import * as os from "os";
4
5
  import * as path2 from "path";
6
+ import { promisify } from "util";
5
7
 
6
8
  // src/utils/semaphore.ts
7
9
  var Semaphore = class {
@@ -113,6 +115,7 @@ function escapeRegex(s) {
113
115
  }
114
116
 
115
117
  // src/utils/fs.ts
118
+ var execFileAsync = promisify(execFile);
116
119
  var SKIP_DIRS = /* @__PURE__ */ new Set([
117
120
  "node_modules",
118
121
  ".git",
@@ -205,6 +208,7 @@ var SKIP_EXTENSIONS = /* @__PURE__ */ new Set([
205
208
  // Source maps & lockfiles (large, not useful for drift analysis)
206
209
  ".map"
207
210
  ]);
211
+ var EXTRA_SKIP_DIRS = /* @__PURE__ */ new Set([".nuxt", ".output", ".svelte-kit"]);
208
212
  var TEXT_CACHE_MAX_BYTES = 1048576;
209
213
  var FileCache = class _FileCache {
210
214
  /** Directory walk results keyed by rootDir */
@@ -225,6 +229,10 @@ var FileCache = class _FileCache {
225
229
  _maxFileSize = 0;
226
230
  /** Root dir for relative-path computation (set by the first walkDir call) */
227
231
  _rootDir = null;
232
+ /** Cached tree summary captured during the shared walk */
233
+ walkSummary = /* @__PURE__ */ new Map();
234
+ /** Fast lookup for exact filename (e.g. package.json) */
235
+ fileNameIndex = /* @__PURE__ */ new Map();
228
236
  /** Set exclude patterns from config (call once before the walk) */
229
237
  setExcludePatterns(patterns) {
230
238
  this.excludePredicate = compileGlobs(patterns);
@@ -266,8 +274,12 @@ var FileCache = class _FileCache {
266
274
  this.walkCache.set(rootDir, promise);
267
275
  return promise;
268
276
  }
277
+ /** Return tree summary from the cached walk, if available. */
278
+ getWalkSummary(rootDir) {
279
+ return this.walkSummary.get(rootDir);
280
+ }
269
281
  /** Additional dirs skipped only by the cached walk (framework outputs) */
270
- static EXTRA_SKIP = /* @__PURE__ */ new Set([".nuxt", ".output", ".svelte-kit"]);
282
+ static EXTRA_SKIP = EXTRA_SKIP_DIRS;
271
283
  async _doWalk(rootDir, onProgress) {
272
284
  const results = [];
273
285
  const cores = typeof os.availableParallelism === "function" ? os.availableParallelism() : os.cpus().length || 4;
@@ -328,6 +340,20 @@ var FileCache = class _FileCache {
328
340
  await Promise.all(subWalks);
329
341
  }
330
342
  await walk(rootDir);
343
+ let totalDirs = 0;
344
+ const rootNameIndex = /* @__PURE__ */ new Map();
345
+ for (const entry of results) {
346
+ if (entry.isDirectory) totalDirs++;
347
+ if (!entry.isFile) continue;
348
+ const bucket = rootNameIndex.get(entry.name);
349
+ if (bucket) {
350
+ bucket.push(entry.absPath);
351
+ } else {
352
+ rootNameIndex.set(entry.name, [entry.absPath]);
353
+ }
354
+ }
355
+ this.walkSummary.set(rootDir, { totalFiles: foundCount, totalDirs });
356
+ this.fileNameIndex.set(rootDir, rootNameIndex);
331
357
  if (onProgress && foundCount !== lastReported) {
332
358
  onProgress(foundCount, "");
333
359
  }
@@ -342,13 +368,16 @@ var FileCache = class _FileCache {
342
368
  return entries.filter((e) => e.isFile && predicate(e.name)).map((e) => e.absPath);
343
369
  }
344
370
  async findPackageJsonFiles(rootDir) {
345
- return this.findFiles(rootDir, (name) => name === "package.json");
371
+ await this.walkDir(rootDir);
372
+ return this.fileNameIndex.get(rootDir)?.get("package.json") ?? [];
346
373
  }
347
374
  async findCsprojFiles(rootDir) {
348
- return this.findFiles(rootDir, (name) => name.endsWith(".csproj"));
375
+ const entries = await this.walkDir(rootDir);
376
+ return entries.filter((e) => e.isFile && e.name.endsWith(".csproj")).map((e) => e.absPath);
349
377
  }
350
378
  async findSolutionFiles(rootDir) {
351
- return this.findFiles(rootDir, (name) => name.endsWith(".sln"));
379
+ const entries = await this.walkDir(rootDir);
380
+ return entries.filter((e) => e.isFile && e.name.endsWith(".sln")).map((e) => e.absPath);
352
381
  }
353
382
  // ── File content reading ──
354
383
  /**
@@ -416,6 +445,8 @@ var FileCache = class _FileCache {
416
445
  /** Release all cached data. Call after the scan completes. */
417
446
  clear() {
418
447
  this.walkCache.clear();
448
+ this.walkSummary.clear();
449
+ this.fileNameIndex.clear();
419
450
  this.textCache.clear();
420
451
  this.jsonCache.clear();
421
452
  this.existsCache.clear();
@@ -430,12 +461,14 @@ var FileCache = class _FileCache {
430
461
  }
431
462
  };
432
463
  async function quickTreeCount(rootDir, excludePatterns) {
464
+ const native = await quickTreeCountWithRipgrep(rootDir, excludePatterns);
465
+ if (native) return native;
433
466
  let totalFiles = 0;
434
467
  let totalDirs = 0;
435
468
  const cores = typeof os.availableParallelism === "function" ? os.availableParallelism() : os.cpus().length || 4;
436
469
  const maxConcurrent = Math.max(8, Math.min(128, cores * 8));
437
470
  const sem = new Semaphore(maxConcurrent);
438
- const extraSkip = /* @__PURE__ */ new Set([".nuxt", ".output", ".svelte-kit"]);
471
+ const extraSkip = EXTRA_SKIP_DIRS;
439
472
  const isExcluded = excludePatterns ? compileGlobs(excludePatterns) : null;
440
473
  async function count(dir) {
441
474
  let entries;
@@ -462,6 +495,39 @@ async function quickTreeCount(rootDir, excludePatterns) {
462
495
  await count(rootDir);
463
496
  return { totalFiles, totalDirs };
464
497
  }
498
+ function normalizeGlobForRipgrep(pattern) {
499
+ return pattern.replace(/\\/g, "/");
500
+ }
501
+ async function quickTreeCountWithRipgrep(rootDir, excludePatterns) {
502
+ const args = ["--files", "--hidden", "--no-ignore", "--null"];
503
+ for (const dir of SKIP_DIRS) {
504
+ args.push("-g", `!**/${dir}/**`);
505
+ }
506
+ for (const dir of EXTRA_SKIP_DIRS) {
507
+ args.push("-g", `!**/${dir}/**`);
508
+ }
509
+ for (const ext of SKIP_EXTENSIONS) {
510
+ args.push("-g", `!**/*${ext}`);
511
+ }
512
+ for (const pattern of excludePatterns ?? []) {
513
+ const trimmed = pattern.trim();
514
+ if (!trimmed) continue;
515
+ args.push("-g", `!${normalizeGlobForRipgrep(trimmed)}`);
516
+ }
517
+ try {
518
+ const { stdout } = await execFileAsync("rg", args, { cwd: rootDir, maxBuffer: 64 * 1024 * 1024, encoding: "utf8" });
519
+ if (!stdout) return { totalFiles: 0, totalDirs: 0 };
520
+ const files = stdout.split("\0").filter(Boolean);
521
+ const dirs = /* @__PURE__ */ new Set();
522
+ for (const file of files) {
523
+ const dir = path2.dirname(file);
524
+ if (dir && dir !== ".") dirs.add(dir);
525
+ }
526
+ return { totalFiles: files.length, totalDirs: dirs.size };
527
+ } catch {
528
+ return null;
529
+ }
530
+ }
465
531
  async function countFilesInDir(dir, recursive = true) {
466
532
  let count = 0;
467
533
  const extraSkip = /* @__PURE__ */ new Set(["obj", "bin", "Debug", "Release", "TestResults"]);
@@ -555,6 +621,7 @@ export {
555
621
  Semaphore,
556
622
  FileCache,
557
623
  quickTreeCount,
624
+ normalizeGlobForRipgrep,
558
625
  countFilesInDir,
559
626
  findFiles,
560
627
  findPackageJsonFiles,
@@ -13,7 +13,7 @@ import {
13
13
  readTextFile,
14
14
  writeJsonFile,
15
15
  writeTextFile
16
- } from "./chunk-RNVZIZNL.js";
16
+ } from "./chunk-43ACU2WV.js";
17
17
 
18
18
  // src/scoring/drift-score.ts
19
19
  import * as crypto from "crypto";
@@ -484,14 +484,14 @@ function formatExtended(ext) {
484
484
  }
485
485
  }
486
486
  if (ext.tsModernity && ext.tsModernity.typescriptVersion) {
487
- const ts2 = ext.tsModernity;
487
+ const ts3 = ext.tsModernity;
488
488
  lines.push(chalk.bold.underline(" TypeScript"));
489
489
  const parts = [];
490
- parts.push(`v${ts2.typescriptVersion}`);
491
- if (ts2.strict === true) parts.push(chalk.green("strict \u2714"));
492
- else if (ts2.strict === false) parts.push(chalk.yellow("strict \u2716"));
493
- if (ts2.moduleType) parts.push(ts2.moduleType.toUpperCase());
494
- if (ts2.target) parts.push(`target: ${ts2.target}`);
490
+ parts.push(`v${ts3.typescriptVersion}`);
491
+ if (ts3.strict === true) parts.push(chalk.green("strict \u2714"));
492
+ else if (ts3.strict === false) parts.push(chalk.yellow("strict \u2716"));
493
+ if (ts3.moduleType) parts.push(ts3.moduleType.toUpperCase());
494
+ if (ts3.target) parts.push(`target: ${ts3.target}`);
495
495
  lines.push(` ${parts.join(chalk.dim(" \xB7 "))}`);
496
496
  lines.push("");
497
497
  }
@@ -1213,6 +1213,7 @@ import { Command as Command3 } from "commander";
1213
1213
  import chalk6 from "chalk";
1214
1214
 
1215
1215
  // src/scanners/node-scanner.ts
1216
+ import * as os2 from "os";
1216
1217
  import * as path4 from "path";
1217
1218
  import * as semver2 from "semver";
1218
1219
 
@@ -1314,11 +1315,52 @@ function maxStable(versions) {
1314
1315
  if (stable.length === 0) return null;
1315
1316
  return stable.sort(semver.rcompare)[0] ?? null;
1316
1317
  }
1318
+ function parseNpmMetaPayload(data) {
1319
+ let latest = null;
1320
+ let versions = [];
1321
+ if (data && typeof data === "object") {
1322
+ const record = data;
1323
+ const dtLatest = record["dist-tags.latest"];
1324
+ if (typeof dtLatest === "string") latest = dtLatest;
1325
+ const distTags = record["dist-tags"];
1326
+ if (!latest && distTags && typeof distTags === "object" && typeof distTags.latest === "string") {
1327
+ latest = distTags.latest;
1328
+ }
1329
+ const v = record.versions;
1330
+ if (Array.isArray(v)) versions = v.map(String);
1331
+ else if (typeof v === "string") versions = [v];
1332
+ }
1333
+ const stable = stableOnly(versions);
1334
+ const latestStableOverall = maxStable(stable);
1335
+ if (!latest && latestStableOverall) latest = latestStableOverall;
1336
+ return { latest, stableVersions: stable, latestStableOverall };
1337
+ }
1338
+ var BATCH_SIZE = 24;
1339
+ var WINDOWS_MAX_COMMAND_CHARS = 7e3;
1340
+ var POSIX_MAX_COMMAND_CHARS = 3e4;
1341
+ function buildNpmViewChunks(pkgs, platform = process.platform) {
1342
+ const maxCommandChars = platform === "win32" ? WINDOWS_MAX_COMMAND_CHARS : POSIX_MAX_COMMAND_CHARS;
1343
+ const chunks = [];
1344
+ let current = [];
1345
+ let currentChars = 0;
1346
+ for (const pkg2 of pkgs) {
1347
+ const pkgChars = pkg2.length + 1;
1348
+ if (current.length >= BATCH_SIZE || current.length > 0 && currentChars + pkgChars > maxCommandChars) {
1349
+ chunks.push(current);
1350
+ current = [];
1351
+ currentChars = 0;
1352
+ }
1353
+ current.push(pkg2);
1354
+ currentChars += pkgChars;
1355
+ }
1356
+ if (current.length > 0) chunks.push(current);
1357
+ return chunks;
1358
+ }
1317
1359
  async function npmViewJson(args, cwd) {
1318
1360
  return new Promise((resolve10, reject) => {
1319
- const child = spawn2("npm", ["view", ...args, "--json"], {
1361
+ const npmCommand = process.platform === "win32" ? "npm.cmd" : "npm";
1362
+ const child = spawn2(npmCommand, ["view", ...args, "--json"], {
1320
1363
  cwd,
1321
- shell: true,
1322
1364
  stdio: ["ignore", "pipe", "pipe"]
1323
1365
  });
1324
1366
  let out = "";
@@ -1352,56 +1394,127 @@ var NpmCache = class {
1352
1394
  this.offline = offline;
1353
1395
  }
1354
1396
  meta = /* @__PURE__ */ new Map();
1397
+ async prefetch(pkgs) {
1398
+ const unique = [...new Set(pkgs.filter(Boolean))];
1399
+ const pending = unique.filter((pkg2) => !this.meta.has(pkg2));
1400
+ if (pending.length === 0) return;
1401
+ const batchPromise = this.sem.run(() => this.fetchBatch(pending));
1402
+ for (const pkg2 of pending) {
1403
+ const promise = batchPromise.then((batch) => batch.get(pkg2) ?? { latest: null, stableVersions: [], latestStableOverall: null });
1404
+ this.meta.set(pkg2, promise);
1405
+ }
1406
+ await batchPromise;
1407
+ }
1355
1408
  get(pkg2) {
1356
1409
  const existing = this.meta.get(pkg2);
1357
1410
  if (existing) return existing;
1358
- const p = this.sem.run(async () => {
1411
+ const p = this.sem.run(() => this.fetchOne(pkg2));
1412
+ this.meta.set(pkg2, p);
1413
+ return p;
1414
+ }
1415
+ async fetchBatch(pkgs) {
1416
+ const out = /* @__PURE__ */ new Map();
1417
+ const remote = [];
1418
+ for (const pkg2 of pkgs) {
1359
1419
  const manifestEntry = getManifestEntry(this.manifest, "npm", pkg2);
1360
1420
  if (manifestEntry) {
1361
- const stable2 = stableOnly(manifestEntry.versions ?? []);
1362
- const latestStableOverall2 = maxStable(stable2);
1363
- return {
1364
- latest: manifestEntry.latest ?? latestStableOverall2,
1365
- stableVersions: stable2,
1366
- latestStableOverall: latestStableOverall2
1367
- };
1421
+ const stable = stableOnly(manifestEntry.versions ?? []);
1422
+ const latestStableOverall = maxStable(stable);
1423
+ out.set(pkg2, {
1424
+ latest: manifestEntry.latest ?? latestStableOverall,
1425
+ stableVersions: stable,
1426
+ latestStableOverall
1427
+ });
1428
+ } else if (this.offline) {
1429
+ out.set(pkg2, { latest: null, stableVersions: [], latestStableOverall: null });
1430
+ } else {
1431
+ remote.push(pkg2);
1368
1432
  }
1369
- if (this.offline) {
1370
- return { latest: null, stableVersions: [], latestStableOverall: null };
1433
+ }
1434
+ if (remote.length === 0) return out;
1435
+ for (const chunk of buildNpmViewChunks(remote)) {
1436
+ const chunkResults = await this.fetchRemoteChunk(chunk);
1437
+ for (const [pkg2, meta] of chunkResults) {
1438
+ out.set(pkg2, meta);
1371
1439
  }
1440
+ }
1441
+ return out;
1442
+ }
1443
+ async fetchRemoteChunk(pkgs) {
1444
+ const out = /* @__PURE__ */ new Map();
1445
+ if (pkgs.length === 1) {
1446
+ out.set(pkgs[0], await this.fetchOneRemote(pkgs[0]));
1447
+ return out;
1448
+ }
1449
+ try {
1450
+ const data = await npmViewJson([...pkgs, "dist-tags.latest", "versions"], this.cwd);
1451
+ if (Array.isArray(data)) {
1452
+ for (let i = 0; i < data.length && i < pkgs.length; i++) {
1453
+ out.set(pkgs[i], parseNpmMetaPayload(data[i]));
1454
+ }
1455
+ } else if (data && typeof data === "object") {
1456
+ const record = data;
1457
+ let keyedByPkg = 0;
1458
+ for (const pkg2 of pkgs) {
1459
+ if (pkg2 in record) {
1460
+ out.set(pkg2, parseNpmMetaPayload(record[pkg2]));
1461
+ keyedByPkg++;
1462
+ }
1463
+ }
1464
+ if (keyedByPkg === 0 && pkgs.length === 1) {
1465
+ out.set(pkgs[0], parseNpmMetaPayload(record));
1466
+ }
1467
+ }
1468
+ } catch {
1469
+ }
1470
+ for (const pkg2 of pkgs) {
1471
+ if (!out.has(pkg2)) {
1472
+ out.set(pkg2, await this.fetchOneRemote(pkg2));
1473
+ }
1474
+ }
1475
+ return out;
1476
+ }
1477
+ async fetchOne(pkg2) {
1478
+ const manifestEntry = getManifestEntry(this.manifest, "npm", pkg2);
1479
+ if (manifestEntry) {
1480
+ const stable = stableOnly(manifestEntry.versions ?? []);
1481
+ const latestStableOverall = maxStable(stable);
1482
+ return {
1483
+ latest: manifestEntry.latest ?? latestStableOverall,
1484
+ stableVersions: stable,
1485
+ latestStableOverall
1486
+ };
1487
+ }
1488
+ if (this.offline) {
1489
+ return { latest: null, stableVersions: [], latestStableOverall: null };
1490
+ }
1491
+ return this.fetchOneRemote(pkg2);
1492
+ }
1493
+ async fetchOneRemote(pkg2) {
1494
+ try {
1495
+ const data = await npmViewJson([pkg2, "dist-tags.latest", "versions"], this.cwd);
1496
+ return parseNpmMetaPayload(data);
1497
+ } catch {
1372
1498
  let latest = null;
1373
1499
  let versions = [];
1374
1500
  try {
1375
- const data = await npmViewJson([pkg2, "dist-tags.latest", "versions"], this.cwd);
1376
- if (data && typeof data === "object") {
1377
- const dt = data["dist-tags.latest"];
1378
- if (typeof dt === "string") latest = dt;
1379
- const v = data.versions;
1380
- if (Array.isArray(v)) versions = v.map(String);
1381
- else if (typeof v === "string") versions = [v];
1501
+ const dist = await npmViewJson([pkg2, "dist-tags"], this.cwd);
1502
+ if (dist && typeof dist === "object" && typeof dist.latest === "string") {
1503
+ latest = dist.latest;
1382
1504
  }
1383
1505
  } catch {
1384
- try {
1385
- const dist = await npmViewJson([pkg2, "dist-tags"], this.cwd);
1386
- if (dist && typeof dist === "object" && typeof dist.latest === "string") {
1387
- latest = dist.latest;
1388
- }
1389
- } catch {
1390
- }
1391
- try {
1392
- const v = await npmViewJson([pkg2, "versions"], this.cwd);
1393
- if (Array.isArray(v)) versions = v.map(String);
1394
- else if (typeof v === "string") versions = [v];
1395
- } catch {
1396
- }
1506
+ }
1507
+ try {
1508
+ const v = await npmViewJson([pkg2, "versions"], this.cwd);
1509
+ if (Array.isArray(v)) versions = v.map(String);
1510
+ else if (typeof v === "string") versions = [v];
1511
+ } catch {
1397
1512
  }
1398
1513
  const stable = stableOnly(versions);
1399
1514
  const latestStableOverall = maxStable(stable);
1400
1515
  if (!latest && latestStableOverall) latest = latestStableOverall;
1401
1516
  return { latest, stableVersions: stable, latestStableOverall };
1402
- });
1403
- this.meta.set(pkg2, p);
1404
- return p;
1517
+ }
1405
1518
  }
1406
1519
  };
1407
1520
  async function checkRegistryAccess(cwd) {
@@ -1514,24 +1627,32 @@ async function scanNodeProjects(rootDir, npmCache, cache) {
1514
1627
  const results = [];
1515
1628
  const packageNameToPath = /* @__PURE__ */ new Map();
1516
1629
  const STUCK_TIMEOUT_MS = 6e4;
1517
- for (const pjPath of packageJsonFiles) {
1630
+ const cores = typeof os2.availableParallelism === "function" ? os2.availableParallelism() : os2.cpus().length || 4;
1631
+ const projectConcurrency = Math.max(2, Math.min(16, cores * 2));
1632
+ const projectSem = new Semaphore(projectConcurrency);
1633
+ const scannedProjects = await Promise.all(packageJsonFiles.map(async (pjPath) => projectSem.run(async () => {
1518
1634
  try {
1519
1635
  const scanPromise = scanOnePackageJson(pjPath, rootDir, npmCache, cache);
1520
1636
  const result = await withTimeout(scanPromise, STUCK_TIMEOUT_MS);
1521
1637
  if (result.ok) {
1522
- results.push(result.value);
1523
- packageNameToPath.set(result.value.name, result.value.path);
1524
- } else {
1525
- const relPath = path4.relative(rootDir, path4.dirname(pjPath));
1526
- if (cache) {
1527
- cache.addStuckPath(relPath || ".");
1528
- }
1529
- console.error(`Timeout scanning ${pjPath} (>${STUCK_TIMEOUT_MS / 1e3}s) \u2014 skipped`);
1638
+ return result.value;
1530
1639
  }
1640
+ const relPath = path4.relative(rootDir, path4.dirname(pjPath));
1641
+ if (cache) {
1642
+ cache.addStuckPath(relPath || ".");
1643
+ }
1644
+ console.error(`Timeout scanning ${pjPath} (>${STUCK_TIMEOUT_MS / 1e3}s) \u2014 skipped`);
1645
+ return null;
1531
1646
  } catch (e) {
1532
1647
  const msg = e instanceof Error ? e.message : String(e);
1533
1648
  console.error(`Error scanning ${pjPath}: ${msg}`);
1649
+ return null;
1534
1650
  }
1651
+ })));
1652
+ for (const project of scannedProjects) {
1653
+ if (!project) continue;
1654
+ results.push(project);
1655
+ packageNameToPath.set(project.name, project.path);
1535
1656
  }
1536
1657
  for (const project of results) {
1537
1658
  const workspaceRefs = [];
@@ -1584,6 +1705,7 @@ async function scanOnePackageJson(packageJsonPath, rootDir, npmCache, cache) {
1584
1705
  depEntries.push({ pkg: pkg2, section: s.name, spec });
1585
1706
  }
1586
1707
  }
1708
+ await npmCache.prefetch(depEntries.map((entry) => entry.pkg));
1587
1709
  const metaPromises = depEntries.map(async (entry) => {
1588
1710
  const meta = await npmCache.get(entry.pkg);
1589
1711
  return { ...entry, meta };
@@ -2338,7 +2460,7 @@ async function scanPythonProjects(rootDir, pypiCache, cache) {
2338
2460
  return results;
2339
2461
  }
2340
2462
  async function findPythonManifests(rootDir) {
2341
- const { findFiles: findFiles2 } = await import("./fs-Q63DRR7L.js");
2463
+ const { findFiles: findFiles2 } = await import("./fs-TWKR3VSL.js");
2342
2464
  return findFiles2(rootDir, (name) => PYTHON_MANIFEST_FILES.has(name) || /^requirements.*\.txt$/.test(name));
2343
2465
  }
2344
2466
  async function scanOnePythonProject(dir, manifestFiles, rootDir, pypiCache, cache) {
@@ -2737,7 +2859,7 @@ async function scanJavaProjects(rootDir, mavenCache, cache) {
2737
2859
  return results;
2738
2860
  }
2739
2861
  async function findJavaManifests(rootDir) {
2740
- const { findFiles: findFiles2 } = await import("./fs-Q63DRR7L.js");
2862
+ const { findFiles: findFiles2 } = await import("./fs-TWKR3VSL.js");
2741
2863
  return findFiles2(rootDir, (name) => JAVA_MANIFEST_FILES.has(name));
2742
2864
  }
2743
2865
  async function scanOneJavaProject(dir, manifestFiles, rootDir, mavenCache, cache) {
@@ -3225,11 +3347,74 @@ var MavenCache = class {
3225
3347
  // src/config.ts
3226
3348
  import * as path9 from "path";
3227
3349
  import * as fs from "fs/promises";
3350
+ import ts from "typescript";
3228
3351
  var CONFIG_FILES = [
3229
3352
  "vibgrate.config.ts",
3230
3353
  "vibgrate.config.js",
3231
3354
  "vibgrate.config.json"
3232
3355
  ];
3356
+ var TRUSTED_CONFIG_ENV = "VIBGRATE_TRUST_CONFIG";
3357
+ function isRecord(value) {
3358
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3359
+ }
3360
+ function toStaticValue(expr, constBindings) {
3361
+ if (ts.isParenthesizedExpression(expr)) return toStaticValue(expr.expression, constBindings);
3362
+ if (ts.isStringLiteralLike(expr)) return expr.text;
3363
+ if (ts.isNumericLiteral(expr)) return Number(expr.text);
3364
+ if (expr.kind === ts.SyntaxKind.TrueKeyword) return true;
3365
+ if (expr.kind === ts.SyntaxKind.FalseKeyword) return false;
3366
+ if (expr.kind === ts.SyntaxKind.NullKeyword) return null;
3367
+ if (ts.isArrayLiteralExpression(expr)) {
3368
+ return expr.elements.map((el) => {
3369
+ if (ts.isSpreadElement(el)) throw new Error("Spread not supported in static config arrays");
3370
+ return toStaticValue(el, constBindings);
3371
+ });
3372
+ }
3373
+ if (ts.isObjectLiteralExpression(expr)) {
3374
+ const out = {};
3375
+ for (const prop of expr.properties) {
3376
+ if (!ts.isPropertyAssignment(prop) || prop.initializer === void 0) {
3377
+ throw new Error("Only plain object properties are supported in static config");
3378
+ }
3379
+ if (ts.isComputedPropertyName(prop.name)) {
3380
+ throw new Error("Computed property names are not supported in static config");
3381
+ }
3382
+ const key = ts.isIdentifier(prop.name) ? prop.name.text : ts.isStringLiteral(prop.name) ? prop.name.text : ts.isNumericLiteral(prop.name) ? prop.name.text : null;
3383
+ if (key === null) {
3384
+ throw new Error("Unsupported object key in static config");
3385
+ }
3386
+ out[key] = toStaticValue(prop.initializer, constBindings);
3387
+ }
3388
+ return out;
3389
+ }
3390
+ if (ts.isIdentifier(expr)) {
3391
+ const bound = constBindings.get(expr.text);
3392
+ if (!bound) throw new Error(`Unknown identifier in static config: ${expr.text}`);
3393
+ return toStaticValue(bound, constBindings);
3394
+ }
3395
+ throw new Error("Non-static expression in config");
3396
+ }
3397
+ function tryParseStaticConfig(text, configPath) {
3398
+ const scriptKind = configPath.endsWith(".ts") ? ts.ScriptKind.TS : ts.ScriptKind.JS;
3399
+ const source = ts.createSourceFile(configPath, text, ts.ScriptTarget.ESNext, true, scriptKind);
3400
+ const constBindings = /* @__PURE__ */ new Map();
3401
+ for (const stmt of source.statements) {
3402
+ if (!ts.isVariableStatement(stmt)) continue;
3403
+ if (!(stmt.declarationList.flags & ts.NodeFlags.Const)) continue;
3404
+ for (const decl of stmt.declarationList.declarations) {
3405
+ if (ts.isIdentifier(decl.name) && decl.initializer) {
3406
+ constBindings.set(decl.name.text, decl.initializer);
3407
+ }
3408
+ }
3409
+ }
3410
+ for (const stmt of source.statements) {
3411
+ if (!ts.isExportAssignment(stmt)) continue;
3412
+ const parsed = toStaticValue(stmt.expression, constBindings);
3413
+ if (!isRecord(parsed)) return null;
3414
+ return { ...DEFAULT_CONFIG, ...parsed };
3415
+ }
3416
+ return null;
3417
+ }
3233
3418
  var DEFAULT_MAX_FILE_SIZE = 5242880;
3234
3419
  var DEFAULT_CONFIG = {
3235
3420
  exclude: [],
@@ -3252,15 +3437,28 @@ async function loadConfig(rootDir) {
3252
3437
  const configPath = path9.join(rootDir, file);
3253
3438
  if (await pathExists(configPath)) {
3254
3439
  if (file.endsWith(".json")) {
3255
- const txt = await readTextFile(configPath);
3256
- config = { ...DEFAULT_CONFIG, ...JSON.parse(txt) };
3440
+ const txt2 = await readTextFile(configPath);
3441
+ config = { ...DEFAULT_CONFIG, ...JSON.parse(txt2) };
3257
3442
  break;
3258
3443
  }
3444
+ const txt = await readTextFile(configPath);
3445
+ let staticConfig = null;
3259
3446
  try {
3260
- const mod = await import(configPath);
3261
- config = { ...DEFAULT_CONFIG, ...mod.default ?? mod };
3262
- break;
3447
+ staticConfig = tryParseStaticConfig(txt, configPath);
3263
3448
  } catch {
3449
+ staticConfig = null;
3450
+ }
3451
+ if (staticConfig) {
3452
+ config = staticConfig;
3453
+ break;
3454
+ }
3455
+ if (process.env[TRUSTED_CONFIG_ENV] === "1") {
3456
+ try {
3457
+ const mod = await import(configPath);
3458
+ config = { ...DEFAULT_CONFIG, ...mod.default ?? mod };
3459
+ break;
3460
+ } catch {
3461
+ }
3264
3462
  }
3265
3463
  }
3266
3464
  }
@@ -6587,7 +6785,7 @@ async function scanArchitecture(rootDir, projects, tooling, services, cache) {
6587
6785
 
6588
6786
  // src/scanners/code-quality.ts
6589
6787
  import * as path21 from "path";
6590
- import * as ts from "typescript";
6788
+ import * as ts2 from "typescript";
6591
6789
  var SOURCE_EXTENSIONS2 = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
6592
6790
  var DEFAULT_RESULT = {
6593
6791
  filesAnalyzed: 0,
@@ -6618,7 +6816,7 @@ async function scanCodeQuality(rootDir, cache) {
6618
6816
  }
6619
6817
  if (!raw.trim()) continue;
6620
6818
  const rel = normalizeModuleId(path21.relative(rootDir, filePath));
6621
- const source = ts.createSourceFile(filePath, raw, ts.ScriptTarget.Latest, true);
6819
+ const source = ts2.createSourceFile(filePath, raw, ts2.ScriptTarget.Latest, true);
6622
6820
  const imports = collectLocalImports(source, path21.dirname(filePath), rootDir);
6623
6821
  depGraph.set(rel, imports);
6624
6822
  const fileMetrics = computeFileMetrics(source, raw);
@@ -6661,11 +6859,11 @@ async function findSourceFiles(rootDir, cache) {
6661
6859
  function collectLocalImports(source, fileDir, rootDir) {
6662
6860
  const deps = /* @__PURE__ */ new Set();
6663
6861
  const visit = (node) => {
6664
- if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) {
6862
+ if (ts2.isImportDeclaration(node) && ts2.isStringLiteral(node.moduleSpecifier)) {
6665
6863
  const target = resolveLocalImport(node.moduleSpecifier.text, fileDir, rootDir);
6666
6864
  if (target) deps.add(target);
6667
6865
  }
6668
- if (ts.isCallExpression(node) && node.expression.kind === ts.SyntaxKind.ImportKeyword && node.arguments[0] && ts.isStringLiteral(node.arguments[0])) {
6866
+ if (ts2.isCallExpression(node) && node.expression.kind === ts2.SyntaxKind.ImportKeyword && node.arguments[0] && ts2.isStringLiteral(node.arguments[0])) {
6669
6867
  const target = resolveLocalImport(node.arguments[0].text, fileDir, rootDir);
6670
6868
  if (target) deps.add(target);
6671
6869
  }
@@ -6701,7 +6899,7 @@ function computeFileMetrics(source, raw) {
6701
6899
  totalFunctionLength += lineLength;
6702
6900
  maxNestingDepth = Math.max(maxNestingDepth, nestingDepth);
6703
6901
  }
6704
- if (ts.isFunctionDeclaration(node) && node.name) {
6902
+ if (ts2.isFunctionDeclaration(node) && node.name) {
6705
6903
  functionDecls.push(node);
6706
6904
  }
6707
6905
  visitEach(node, visit);
@@ -6728,20 +6926,20 @@ function computeCyclomatic(fn) {
6728
6926
  let complexity = 1;
6729
6927
  const visit = (node) => {
6730
6928
  switch (node.kind) {
6731
- case ts.SyntaxKind.IfStatement:
6732
- case ts.SyntaxKind.ForStatement:
6733
- case ts.SyntaxKind.ForOfStatement:
6734
- case ts.SyntaxKind.ForInStatement:
6735
- case ts.SyntaxKind.WhileStatement:
6736
- case ts.SyntaxKind.DoStatement:
6737
- case ts.SyntaxKind.CaseClause:
6738
- case ts.SyntaxKind.CatchClause:
6739
- case ts.SyntaxKind.ConditionalExpression:
6929
+ case ts2.SyntaxKind.IfStatement:
6930
+ case ts2.SyntaxKind.ForStatement:
6931
+ case ts2.SyntaxKind.ForOfStatement:
6932
+ case ts2.SyntaxKind.ForInStatement:
6933
+ case ts2.SyntaxKind.WhileStatement:
6934
+ case ts2.SyntaxKind.DoStatement:
6935
+ case ts2.SyntaxKind.CaseClause:
6936
+ case ts2.SyntaxKind.CatchClause:
6937
+ case ts2.SyntaxKind.ConditionalExpression:
6740
6938
  complexity++;
6741
6939
  break;
6742
- case ts.SyntaxKind.BinaryExpression: {
6940
+ case ts2.SyntaxKind.BinaryExpression: {
6743
6941
  const be = node;
6744
- if (be.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken || be.operatorToken.kind === ts.SyntaxKind.BarBarToken || be.operatorToken.kind === ts.SyntaxKind.QuestionQuestionToken) {
6942
+ if (be.operatorToken.kind === ts2.SyntaxKind.AmpersandAmpersandToken || be.operatorToken.kind === ts2.SyntaxKind.BarBarToken || be.operatorToken.kind === ts2.SyntaxKind.QuestionQuestionToken) {
6745
6943
  complexity++;
6746
6944
  }
6747
6945
  break;
@@ -6793,15 +6991,15 @@ function computeNodeLineLength(source, node) {
6793
6991
  return end - start + 1;
6794
6992
  }
6795
6993
  function isFunctionLike(node) {
6796
- return ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node) || ts.isFunctionExpression(node) || ts.isArrowFunction(node) || ts.isConstructorDeclaration(node);
6994
+ return ts2.isFunctionDeclaration(node) || ts2.isMethodDeclaration(node) || ts2.isFunctionExpression(node) || ts2.isArrowFunction(node) || ts2.isConstructorDeclaration(node);
6797
6995
  }
6798
6996
  function isNestingNode(node) {
6799
- return node.kind === ts.SyntaxKind.IfStatement || node.kind === ts.SyntaxKind.ForStatement || node.kind === ts.SyntaxKind.ForOfStatement || node.kind === ts.SyntaxKind.ForInStatement || node.kind === ts.SyntaxKind.WhileStatement || node.kind === ts.SyntaxKind.DoStatement || node.kind === ts.SyntaxKind.SwitchStatement || node.kind === ts.SyntaxKind.TryStatement || node.kind === ts.SyntaxKind.CatchClause;
6997
+ return node.kind === ts2.SyntaxKind.IfStatement || node.kind === ts2.SyntaxKind.ForStatement || node.kind === ts2.SyntaxKind.ForOfStatement || node.kind === ts2.SyntaxKind.ForInStatement || node.kind === ts2.SyntaxKind.WhileStatement || node.kind === ts2.SyntaxKind.DoStatement || node.kind === ts2.SyntaxKind.SwitchStatement || node.kind === ts2.SyntaxKind.TryStatement || node.kind === ts2.SyntaxKind.CatchClause;
6800
6998
  }
6801
6999
  function isExported(node) {
6802
- if (!ts.canHaveModifiers(node)) return false;
6803
- const modifiers = ts.getModifiers(node);
6804
- return !!modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword);
7000
+ if (!ts2.canHaveModifiers(node)) return false;
7001
+ const modifiers = ts2.getModifiers(node);
7002
+ return !!modifiers?.some((m) => m.kind === ts2.SyntaxKind.ExportKeyword);
6805
7003
  }
6806
7004
  function countWholeWord(input, word) {
6807
7005
  const re = new RegExp(`\\b${escapeRegExp(word)}\\b`, "g");
@@ -7513,8 +7711,9 @@ async function runScan(rootDir, opts) {
7513
7711
  console.error(msg);
7514
7712
  process.exit(1);
7515
7713
  }
7714
+ const treeCountPromise = quickTreeCount(rootDir, excludePatterns);
7516
7715
  progress.startStep("discovery");
7517
- const treeCount = await quickTreeCount(rootDir, excludePatterns);
7716
+ const treeCount = await treeCountPromise;
7518
7717
  progress.updateStats({ treeSummary: treeCount });
7519
7718
  progress.completeStep(
7520
7719
  "discovery",
@@ -7532,6 +7731,10 @@ async function runScan(rootDir, opts) {
7532
7731
  await fileCache.walkDir(rootDir, (found, currentPath) => {
7533
7732
  progress.updateStepProgress("walk", found, treeCount.totalFiles, currentPath);
7534
7733
  });
7734
+ const indexedTreeCount = fileCache.getWalkSummary(rootDir);
7735
+ if (indexedTreeCount && (indexedTreeCount.totalFiles !== treeCount.totalFiles || indexedTreeCount.totalDirs !== treeCount.totalDirs)) {
7736
+ progress.updateStats({ treeSummary: indexedTreeCount });
7737
+ }
7535
7738
  progress.completeStep("walk", `${treeCount.totalFiles.toLocaleString()} files indexed`);
7536
7739
  progress.startStep("node");
7537
7740
  const nodeProjects = await scanNodeProjects(rootDir, npmCache, fileCache);
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  runScan
3
- } from "./chunk-PQEUNAVK.js";
3
+ } from "./chunk-H473HICY.js";
4
4
  import {
5
5
  writeJsonFile
6
- } from "./chunk-RNVZIZNL.js";
6
+ } from "./chunk-43ACU2WV.js";
7
7
 
8
8
  // src/commands/baseline.ts
9
9
  import * as path from "path";
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  baselineCommand
4
- } from "./chunk-CQMW6PVB.js";
4
+ } from "./chunk-KLJ5NIGX.js";
5
5
  import {
6
6
  VERSION,
7
7
  dsnCommand,
@@ -10,13 +10,13 @@ import {
10
10
  pushCommand,
11
11
  scanCommand,
12
12
  writeDefaultConfig
13
- } from "./chunk-PQEUNAVK.js";
13
+ } from "./chunk-H473HICY.js";
14
14
  import {
15
15
  ensureDir,
16
16
  pathExists,
17
17
  readJsonFile,
18
18
  writeTextFile
19
- } from "./chunk-RNVZIZNL.js";
19
+ } from "./chunk-43ACU2WV.js";
20
20
 
21
21
  // src/cli.ts
22
22
  import { Command as Command6 } from "commander";
@@ -39,7 +39,7 @@ var initCommand = new Command("init").description("Initialize vibgrate in a proj
39
39
  console.log(chalk.green("\u2714") + ` Created ${chalk.bold("vibgrate.config.ts")}`);
40
40
  }
41
41
  if (opts.baseline) {
42
- const { runBaseline } = await import("./baseline-5ZOILNXB.js");
42
+ const { runBaseline } = await import("./baseline-NZZNS4UI.js");
43
43
  await runBaseline(rootDir);
44
44
  }
45
45
  console.log("");
@@ -6,13 +6,14 @@ import {
6
6
  findFiles,
7
7
  findPackageJsonFiles,
8
8
  findSolutionFiles,
9
+ normalizeGlobForRipgrep,
9
10
  pathExists,
10
11
  quickTreeCount,
11
12
  readJsonFile,
12
13
  readTextFile,
13
14
  writeJsonFile,
14
15
  writeTextFile
15
- } from "./chunk-RNVZIZNL.js";
16
+ } from "./chunk-43ACU2WV.js";
16
17
  export {
17
18
  FileCache,
18
19
  countFilesInDir,
@@ -21,6 +22,7 @@ export {
21
22
  findFiles,
22
23
  findPackageJsonFiles,
23
24
  findSolutionFiles,
25
+ normalizeGlobForRipgrep,
24
26
  pathExists,
25
27
  quickTreeCount,
26
28
  readJsonFile,
package/dist/index.js CHANGED
@@ -5,8 +5,8 @@ import {
5
5
  formatText,
6
6
  generateFindings,
7
7
  runScan
8
- } from "./chunk-PQEUNAVK.js";
9
- import "./chunk-RNVZIZNL.js";
8
+ } from "./chunk-H473HICY.js";
9
+ import "./chunk-43ACU2WV.js";
10
10
  export {
11
11
  computeDriftScore,
12
12
  formatMarkdown,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibgrate/cli",
3
- "version": "1.0.56",
3
+ "version": "1.0.58",
4
4
  "description": "CLI for measuring upgrade drift across Node, .NET, Python & Java projects",
5
5
  "type": "module",
6
6
  "bin": {
@@ -19,11 +19,14 @@
19
19
  "DOCS.md"
20
20
  ],
21
21
  "scripts": {
22
- "build": "tsup src/cli.ts src/index.ts --format esm --dts --clean",
22
+ "build": "tsup src/cli.ts src/index.ts --format esm --dts --clean && pnpm test:solutions",
23
+ "build:only": "tsup src/cli.ts src/index.ts --format esm --dts --clean",
23
24
  "dev": "tsx src/cli.ts",
24
25
  "lint": "eslint src",
25
26
  "typecheck": "tsc --noEmit",
26
- "test": "vitest run"
27
+ "test": "vitest run",
28
+ "test:solutions": "tsx test-solutions/test-runner.ts",
29
+ "test:all": "vitest run && pnpm test:solutions"
27
30
  },
28
31
  "keywords": [
29
32
  "upgrade",
@@ -1,10 +0,0 @@
1
- import {
2
- baselineCommand,
3
- runBaseline
4
- } from "./chunk-CQMW6PVB.js";
5
- import "./chunk-PQEUNAVK.js";
6
- import "./chunk-RNVZIZNL.js";
7
- export {
8
- baselineCommand,
9
- runBaseline
10
- };