@vibgrate/cli 2026.611.2 → 2026.613.1

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.
@@ -1717,6 +1717,195 @@ var require_fxp = __commonJS({
1717
1717
  }
1718
1718
  });
1719
1719
 
1720
+ // ../vibgrate-core/dist/chunk-KML6OTXK.js
1721
+ function productForType(type) {
1722
+ switch (type) {
1723
+ case "node":
1724
+ return "nodejs";
1725
+ case "python":
1726
+ return "python";
1727
+ case "dotnet":
1728
+ return "dotnet";
1729
+ case "java":
1730
+ return "java";
1731
+ case "php":
1732
+ return "php";
1733
+ case "ruby":
1734
+ return "ruby";
1735
+ case "go":
1736
+ return "go";
1737
+ default:
1738
+ return null;
1739
+ }
1740
+ }
1741
+ function extractCycle(type, runtime) {
1742
+ if (!runtime) return null;
1743
+ switch (type) {
1744
+ case "python":
1745
+ case "php":
1746
+ case "ruby":
1747
+ case "go": {
1748
+ const m = runtime.match(/(\d+)\.(\d+)/);
1749
+ return m ? `${m[1]}.${m[2]}` : null;
1750
+ }
1751
+ case "dotnet": {
1752
+ const m = runtime.match(/(\d+)\.(\d+)/) ?? runtime.match(/(\d+)/);
1753
+ if (!m) return null;
1754
+ return m[2] !== void 0 ? `${m[1]}.${m[2]}` : `${m[1]}.0`;
1755
+ }
1756
+ case "node": {
1757
+ const m = runtime.match(/(\d+)/);
1758
+ return m ? m[1] : null;
1759
+ }
1760
+ case "java": {
1761
+ const legacy = runtime.match(/1\.(\d+)/);
1762
+ if (legacy) return legacy[1];
1763
+ const m = runtime.match(/(\d+)/);
1764
+ return m ? m[1] : null;
1765
+ }
1766
+ default:
1767
+ return null;
1768
+ }
1769
+ }
1770
+ function cycleEolStatus(cycle, cycles, now = Date.now()) {
1771
+ const entry = cycles.find((c) => String(c.cycle) === cycle);
1772
+ if (!entry) return null;
1773
+ if (typeof entry.eol === "boolean") return entry.eol;
1774
+ const eolMs = Date.parse(entry.eol);
1775
+ if (Number.isNaN(eolMs)) return null;
1776
+ return eolMs <= now;
1777
+ }
1778
+ function parseCycle(cycle) {
1779
+ const m = String(cycle).match(/^(\d+)(?:\.(\d+))?/);
1780
+ if (!m) return null;
1781
+ return { major: parseInt(m[1], 10), minor: m[2] ? parseInt(m[2], 10) : 0 };
1782
+ }
1783
+ function rank(v) {
1784
+ return v.major * 1e3 + v.minor;
1785
+ }
1786
+ function isReleased(cycle, now) {
1787
+ if (typeof cycle.releaseDate !== "string") return true;
1788
+ const ts3 = Date.parse(cycle.releaseDate);
1789
+ return Number.isNaN(ts3) ? true : ts3 <= now;
1790
+ }
1791
+ function isLts(cycle, now) {
1792
+ if (cycle.lts === true) return true;
1793
+ if (typeof cycle.lts === "string") {
1794
+ const ts3 = Date.parse(cycle.lts);
1795
+ return !Number.isNaN(ts3) && ts3 <= now;
1796
+ }
1797
+ return false;
1798
+ }
1799
+ function cyclesFor(catalog, product) {
1800
+ return catalog.products[product]?.cycles ?? [];
1801
+ }
1802
+ function latestStable(catalog, product, now = Date.now()) {
1803
+ let best = null;
1804
+ for (const cycle of cyclesFor(catalog, product)) {
1805
+ if (!isReleased(cycle, now)) continue;
1806
+ const v = parseCycle(cycle.cycle);
1807
+ if (v && (!best || rank(v) > rank(best))) best = v;
1808
+ }
1809
+ return best;
1810
+ }
1811
+ function latestLts(catalog, product, now = Date.now()) {
1812
+ let best = null;
1813
+ for (const cycle of cyclesFor(catalog, product)) {
1814
+ if (!isReleased(cycle, now) || !isLts(cycle, now)) continue;
1815
+ const v = parseCycle(cycle.cycle);
1816
+ if (v && (!best || rank(v) > rank(best))) best = v;
1817
+ }
1818
+ return best;
1819
+ }
1820
+ function eolDate(catalog, product, cycle) {
1821
+ const entry = cyclesFor(catalog, product).find((c) => String(c.cycle) === cycle);
1822
+ return entry && typeof entry.eol === "string" ? entry.eol : void 0;
1823
+ }
1824
+ function runtimeEolStatus(catalog, type, runtime, now = Date.now()) {
1825
+ const product = productForType(type);
1826
+ if (!product) return null;
1827
+ const cycle = extractCycle(type, runtime);
1828
+ if (!cycle) return null;
1829
+ return cycleEolStatus(cycle, cyclesFor(catalog, product), now);
1830
+ }
1831
+ var BUNDLED_RUNTIME_CATALOG = {
1832
+ generatedAt: "2026-06-01",
1833
+ source: "endoflife.date",
1834
+ products: {
1835
+ nodejs: {
1836
+ product: "nodejs",
1837
+ cycles: [
1838
+ { cycle: "24", releaseDate: "2025-05-06", lts: "2025-10-28", eol: "2028-04-30" },
1839
+ { cycle: "23", releaseDate: "2024-10-16", lts: false, eol: "2025-06-01" },
1840
+ { cycle: "22", releaseDate: "2024-04-24", lts: "2024-10-29", eol: "2027-04-30" },
1841
+ { cycle: "20", releaseDate: "2023-04-18", lts: "2023-10-24", eol: "2026-04-30" },
1842
+ { cycle: "18", releaseDate: "2022-04-19", lts: "2022-10-25", eol: "2025-04-30" }
1843
+ ]
1844
+ },
1845
+ python: {
1846
+ product: "python",
1847
+ cycles: [
1848
+ { cycle: "3.14", releaseDate: "2025-10-07", eol: "2030-10-01" },
1849
+ { cycle: "3.13", releaseDate: "2024-10-07", eol: "2029-10-01" },
1850
+ { cycle: "3.12", releaseDate: "2023-10-02", eol: "2028-10-01" },
1851
+ { cycle: "3.11", releaseDate: "2022-10-24", eol: "2027-10-01" },
1852
+ { cycle: "3.10", releaseDate: "2021-10-04", eol: "2026-10-01" },
1853
+ { cycle: "3.9", releaseDate: "2020-10-05", eol: "2025-10-01" },
1854
+ { cycle: "3.8", releaseDate: "2019-10-14", eol: "2024-10-07" }
1855
+ ]
1856
+ },
1857
+ dotnet: {
1858
+ product: "dotnet",
1859
+ cycles: [
1860
+ { cycle: "10.0", releaseDate: "2025-11-11", lts: true, eol: "2028-11-10" },
1861
+ { cycle: "9.0", releaseDate: "2024-11-12", lts: false, eol: "2026-05-12" },
1862
+ { cycle: "8.0", releaseDate: "2023-11-14", lts: true, eol: "2026-11-10" },
1863
+ { cycle: "7.0", releaseDate: "2022-11-08", lts: false, eol: "2024-05-14" },
1864
+ { cycle: "6.0", releaseDate: "2021-11-08", lts: true, eol: "2024-11-12" }
1865
+ ]
1866
+ },
1867
+ java: {
1868
+ product: "java",
1869
+ cycles: [
1870
+ { cycle: "25", releaseDate: "2025-09-16", lts: true, eol: "2033-09-01" },
1871
+ { cycle: "21", releaseDate: "2023-09-19", lts: true, eol: "2031-09-01" },
1872
+ { cycle: "17", releaseDate: "2021-09-14", lts: true, eol: "2029-09-01" },
1873
+ { cycle: "11", releaseDate: "2018-09-25", lts: true, eol: "2026-09-01" },
1874
+ { cycle: "8", releaseDate: "2014-03-18", lts: true, eol: "2030-12-01" }
1875
+ ]
1876
+ },
1877
+ php: {
1878
+ product: "php",
1879
+ cycles: [
1880
+ { cycle: "8.4", releaseDate: "2024-11-21", eol: "2028-12-31" },
1881
+ { cycle: "8.3", releaseDate: "2023-11-23", eol: "2027-12-31" },
1882
+ { cycle: "8.2", releaseDate: "2022-12-08", eol: "2026-12-31" },
1883
+ { cycle: "8.1", releaseDate: "2021-11-25", eol: "2025-12-31" },
1884
+ { cycle: "8.0", releaseDate: "2020-11-26", eol: "2023-11-26" }
1885
+ ]
1886
+ },
1887
+ ruby: {
1888
+ product: "ruby",
1889
+ cycles: [
1890
+ { cycle: "3.5", releaseDate: "2025-12-25", eol: "2029-03-31" },
1891
+ { cycle: "3.4", releaseDate: "2024-12-25", eol: "2028-03-31" },
1892
+ { cycle: "3.3", releaseDate: "2023-12-25", eol: "2027-03-31" },
1893
+ { cycle: "3.2", releaseDate: "2022-12-25", eol: "2026-03-31" },
1894
+ { cycle: "3.1", releaseDate: "2021-12-25", eol: "2025-03-31" }
1895
+ ]
1896
+ },
1897
+ go: {
1898
+ product: "go",
1899
+ cycles: [
1900
+ { cycle: "1.25", releaseDate: "2025-08-13", eol: false },
1901
+ { cycle: "1.24", releaseDate: "2025-02-11", eol: false },
1902
+ { cycle: "1.23", releaseDate: "2024-08-13", eol: true },
1903
+ { cycle: "1.22", releaseDate: "2024-02-06", eol: true }
1904
+ ]
1905
+ }
1906
+ }
1907
+ };
1908
+
1720
1909
  // ../vibgrate-core/dist/index.js
1721
1910
  var semver2 = __toESM(require_semver(), 1);
1722
1911
  var semver = __toESM(require_semver(), 1);
@@ -1799,13 +1988,16 @@ import * as path30 from "path";
1799
1988
  import * as crypto2 from "crypto";
1800
1989
  import * as fs6 from "fs/promises";
1801
1990
  import * as path31 from "path";
1802
- import * as path33 from "path";
1991
+ import * as path34 from "path";
1803
1992
  import chalk3 from "chalk";
1993
+ import * as os4 from "os";
1994
+ import * as path32 from "path";
1995
+ import { readFile as readFile4, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
1804
1996
  import chalk2 from "chalk";
1805
1997
  import { writeFileSync, mkdirSync } from "fs";
1806
1998
  import { dirname as dirname182, basename as basename192 } from "path";
1807
1999
  import * as fs7 from "fs/promises";
1808
- import * as path32 from "path";
2000
+ import * as path33 from "path";
1809
2001
  var CONFIG_FILES = [
1810
2002
  "vibgrate.config.ts",
1811
2003
  "vibgrate.config.js",
@@ -1999,10 +2191,10 @@ async function appendExcludePatterns(rootDir, newPatterns) {
1999
2191
  }
2000
2192
  var MS_PER_DAY = 864e5;
2001
2193
  var DAYS_PER_YEAR = 365.25;
2002
- function ageDaysBetween(resolvedVersion, latestStable, releaseDates) {
2003
- if (!resolvedVersion || !latestStable || !releaseDates) return null;
2194
+ function ageDaysBetween(resolvedVersion, latestStable2, releaseDates) {
2195
+ if (!resolvedVersion || !latestStable2 || !releaseDates) return null;
2004
2196
  const resolvedIso = releaseDates[resolvedVersion];
2005
- const latestIso = releaseDates[latestStable];
2197
+ const latestIso = releaseDates[latestStable2];
2006
2198
  if (!resolvedIso || !latestIso) return null;
2007
2199
  const resolvedMs = Date.parse(resolvedIso);
2008
2200
  const latestMs = Date.parse(latestIso);
@@ -2089,11 +2281,29 @@ function dependencyScore(projects) {
2089
2281
  const twoPct = totalTwo / total;
2090
2282
  return clamp(Math.round(currentPct * 100 - onePct * 10 - twoPct * 40), 0, 100);
2091
2283
  }
2284
+ var MONTH_MS = 1e3 * 60 * 60 * 24 * 30;
2092
2285
  function eolScore(projects) {
2093
- const hasRuntimeData = projects.some((p) => p.runtimeMajorsBehind !== void 0);
2286
+ const hasRuntimeData = projects.some(
2287
+ (p) => p.runtimeMajorsBehind !== void 0 || p.runtimeEol !== void 0 && p.runtimeEol !== null
2288
+ );
2094
2289
  if (!hasRuntimeData) return null;
2095
2290
  let score = 100;
2291
+ const now = Date.now();
2096
2292
  for (const p of projects) {
2293
+ if (p.runtimeEol === true) {
2294
+ score = Math.min(score, 0);
2295
+ continue;
2296
+ }
2297
+ if (p.runtimeEol === false) {
2298
+ if (p.runtimeEolDate) {
2299
+ const remaining = Date.parse(p.runtimeEolDate) - now;
2300
+ if (!Number.isNaN(remaining)) {
2301
+ if (remaining <= 6 * MONTH_MS) score = Math.min(score, 40);
2302
+ else if (remaining <= 12 * MONTH_MS) score = Math.min(score, 75);
2303
+ }
2304
+ }
2305
+ continue;
2306
+ }
2097
2307
  if (p.type === "node" && p.runtimeMajorsBehind !== void 0) {
2098
2308
  if (p.runtimeMajorsBehind >= 3) score = Math.min(score, 0);
2099
2309
  else if (p.runtimeMajorsBehind >= 2) score = Math.min(score, 30);
@@ -2201,8 +2411,16 @@ function generateFindings(projects, config) {
2201
2411
  };
2202
2412
  const findings = [];
2203
2413
  for (const project of projects) {
2204
- if (project.runtimeMajorsBehind !== void 0 && project.runtimeMajorsBehind >= 3) {
2205
- const runtimeLabel = project.type === "node" ? "Node.js" : project.type === "dotnet" ? ".NET" : project.type === "python" ? "Python" : project.type === "java" ? "Java" : project.type;
2414
+ const runtimeLabel = project.type === "node" ? "Node.js" : project.type === "dotnet" ? ".NET" : project.type === "python" ? "Python" : project.type === "java" ? "Java" : project.type;
2415
+ if (project.runtimeEol === true) {
2416
+ const when = project.runtimeEolDate ? ` on ${project.runtimeEolDate}` : "";
2417
+ findings.push({
2418
+ ruleId: "vibgrate/runtime-eol",
2419
+ level: "error",
2420
+ message: `${runtimeLabel} runtime "${project.runtime}" reached end-of-life${when} (latest: ${project.runtimeLatest}).`,
2421
+ location: project.path
2422
+ });
2423
+ } else if (project.runtimeMajorsBehind !== void 0 && project.runtimeMajorsBehind >= 3) {
2206
2424
  findings.push({
2207
2425
  ruleId: "vibgrate/runtime-eol",
2208
2426
  level: "error",
@@ -2210,7 +2428,6 @@ function generateFindings(projects, config) {
2210
2428
  location: project.path
2211
2429
  });
2212
2430
  } else if (project.runtimeMajorsBehind !== void 0 && project.runtimeMajorsBehind >= 2) {
2213
- const runtimeLabel = project.type === "node" ? "Node.js" : project.type === "dotnet" ? ".NET" : project.type === "python" ? "Python" : project.type === "java" ? "Java" : project.type;
2214
2431
  findings.push({
2215
2432
  ruleId: "vibgrate/runtime-lag",
2216
2433
  level: "warning",
@@ -2342,11 +2559,6 @@ function summarizeBilling(projects) {
2342
2559
  function formatText(artifact) {
2343
2560
  const lines = [];
2344
2561
  lines.push("");
2345
- lines.push(chalk.cyan(" \u256D\u2500\u2500\u2500\u256E") + chalk.greenBright("\u279C"));
2346
- lines.push(chalk.cyan(" \u256D\u2524") + chalk.greenBright("\u25C9 \u25C9") + chalk.cyan("\u251C\u256E") + " " + chalk.bold.white("V I B G R A T E"));
2347
- lines.push(chalk.cyan(" \u2570\u2524") + chalk.dim("\u2500\u2500\u2500") + chalk.cyan("\u251C\u256F") + " " + chalk.dim(`Drift Intelligence Engine v${artifact.vibgrateVersion ?? "unknown"}`));
2348
- lines.push(chalk.cyan(" \u2570\u2500\u2500\u2500\u256F"));
2349
- lines.push("");
2350
2562
  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"));
2351
2563
  lines.push(chalk.bold.cyan("\u2551 Vibgrate Drift Report \u2551"));
2352
2564
  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"));
@@ -3996,7 +4208,7 @@ var KNOWN_FRAMEWORKS = {
3996
4208
  "storybook": "Storybook",
3997
4209
  "@storybook/react": "Storybook"
3998
4210
  };
3999
- async function scanNodeProjects(rootDir, npmCache, cache, projectScanTimeout) {
4211
+ async function scanNodeProjects(rootDir, npmCache, cache, projectScanTimeout, catalog = BUNDLED_RUNTIME_CATALOG) {
4000
4212
  const packageJsonFiles = cache ? await cache.findPackageJsonFiles(rootDir) : await findPackageJsonFiles(rootDir);
4001
4213
  const results = [];
4002
4214
  const packageNameToPath = /* @__PURE__ */ new Map();
@@ -4060,7 +4272,7 @@ async function scanNodeProjects(rootDir, npmCache, cache, projectScanTimeout) {
4060
4272
  }
4061
4273
  const scannedProjects = await Promise.all(packageJsonFiles.map(async (pjPath) => projectSem.run(async () => {
4062
4274
  try {
4063
- const scanPromise = scanOnePackageJson(pjPath, rootDir, npmCache, cache);
4275
+ const scanPromise = scanOnePackageJson(pjPath, rootDir, npmCache, cache, catalog);
4064
4276
  const result = await withTimeout(scanPromise, STUCK_TIMEOUT_MS);
4065
4277
  if (result.ok) {
4066
4278
  return result.value;
@@ -4106,21 +4318,26 @@ async function scanNodeProjects(rootDir, npmCache, cache, projectScanTimeout) {
4106
4318
  }
4107
4319
  return results;
4108
4320
  }
4109
- async function scanOnePackageJson(packageJsonPath, rootDir, npmCache, cache) {
4321
+ async function scanOnePackageJson(packageJsonPath, rootDir, npmCache, cache, catalog) {
4110
4322
  const pj = cache ? await cache.readJsonFile(packageJsonPath) : await readJsonFile(packageJsonPath);
4111
4323
  const absProjectPath = path3.dirname(packageJsonPath);
4112
4324
  const projectPath = path3.relative(rootDir, absProjectPath) || ".";
4113
4325
  const nodeEngine = pj.engines?.node ?? void 0;
4114
4326
  let runtimeLatest;
4115
4327
  let runtimeMajorsBehind;
4328
+ let runtimeEol;
4329
+ let runtimeEolDate;
4116
4330
  if (nodeEngine) {
4117
- const latestLtsMajor = 22;
4331
+ const latest = latestLts(catalog, "nodejs");
4118
4332
  const parsed = semver2.minVersion(nodeEngine);
4119
- if (parsed) {
4333
+ if (latest && parsed) {
4120
4334
  const currentMajor = semver2.major(parsed);
4121
- runtimeLatest = `${latestLtsMajor}.0.0`;
4122
- runtimeMajorsBehind = Math.max(0, latestLtsMajor - currentMajor);
4335
+ runtimeLatest = `${latest.major}.0.0`;
4336
+ runtimeMajorsBehind = Math.max(0, latest.major - currentMajor);
4123
4337
  }
4338
+ runtimeEol = runtimeEolStatus(catalog, "node", nodeEngine);
4339
+ const cycle = extractCycle("node", nodeEngine);
4340
+ if (cycle) runtimeEolDate = eolDate(catalog, "nodejs", cycle);
4124
4341
  }
4125
4342
  const sections = [
4126
4343
  { name: "dependencies", deps: pj.dependencies },
@@ -4147,15 +4364,15 @@ async function scanOnePackageJson(packageJsonPath, rootDir, npmCache, cache) {
4147
4364
  const resolved = await Promise.all(metaPromises);
4148
4365
  for (const { pkg: pkg2, section, spec, meta } of resolved) {
4149
4366
  const resolvedVersion = meta.stableVersions.length > 0 ? semver2.maxSatisfying(meta.stableVersions, spec) ?? null : null;
4150
- const latestStable = meta.latestStableOverall;
4367
+ const latestStable2 = meta.latestStableOverall;
4151
4368
  let majorsBehind = null;
4152
4369
  let drift = "unknown";
4153
- if (resolvedVersion && latestStable) {
4370
+ if (resolvedVersion && latestStable2) {
4154
4371
  const currentMajor = semver2.major(resolvedVersion);
4155
- const latestMajor = semver2.major(latestStable);
4372
+ const latestMajor = semver2.major(latestStable2);
4156
4373
  majorsBehind = latestMajor - currentMajor;
4157
4374
  if (majorsBehind === 0) {
4158
- drift = semver2.eq(resolvedVersion, latestStable) ? "current" : "minor-behind";
4375
+ drift = semver2.eq(resolvedVersion, latestStable2) ? "current" : "minor-behind";
4159
4376
  } else {
4160
4377
  drift = "major-behind";
4161
4378
  }
@@ -4165,14 +4382,14 @@ async function scanOnePackageJson(packageJsonPath, rootDir, npmCache, cache) {
4165
4382
  } else {
4166
4383
  buckets.unknown++;
4167
4384
  }
4168
- const ageDays = ageDaysBetween(resolvedVersion, latestStable, meta.releaseDates);
4385
+ const ageDays = ageDaysBetween(resolvedVersion, latestStable2, meta.releaseDates);
4169
4386
  const libyears = daysToLibyears(ageDays);
4170
4387
  dependencies.push({
4171
4388
  package: pkg2,
4172
4389
  section,
4173
4390
  currentSpec: spec,
4174
4391
  resolvedVersion,
4175
- latestStable,
4392
+ latestStable: latestStable2,
4176
4393
  majorsBehind,
4177
4394
  drift,
4178
4395
  license: buildDependencyLicense(meta.license, "registry"),
@@ -4183,7 +4400,7 @@ async function scanOnePackageJson(packageJsonPath, rootDir, npmCache, cache) {
4183
4400
  frameworks.push({
4184
4401
  name: KNOWN_FRAMEWORKS[pkg2],
4185
4402
  currentVersion: resolvedVersion,
4186
- latestVersion: latestStable,
4403
+ latestVersion: latestStable2,
4187
4404
  majorsBehind
4188
4405
  });
4189
4406
  }
@@ -4223,6 +4440,8 @@ async function scanOnePackageJson(packageJsonPath, rootDir, npmCache, cache) {
4223
4440
  runtime: nodeEngine,
4224
4441
  runtimeLatest,
4225
4442
  runtimeMajorsBehind,
4443
+ runtimeEol,
4444
+ runtimeEolDate,
4226
4445
  frameworks,
4227
4446
  dependencies,
4228
4447
  dependencyAgeBuckets: buckets,
@@ -4447,7 +4666,6 @@ var KNOWN_DOTNET_FRAMEWORKS = {
4447
4666
  // ── Real-time ──
4448
4667
  "Microsoft.AspNetCore.SignalR.Client": "SignalR Client"
4449
4668
  };
4450
- var LATEST_DOTNET_MAJOR = 9;
4451
4669
  function normalizePath(p) {
4452
4670
  return p.replace(/\\/g, "/");
4453
4671
  }
@@ -4511,7 +4729,7 @@ function parseDotnetProjectFile(xml, filePath) {
4511
4729
  projectName: stripDotnetProjectExtension(filePath)
4512
4730
  };
4513
4731
  }
4514
- async function scanDotnetProjects(rootDir, nugetCache, cache, projectScanTimeout) {
4732
+ async function scanDotnetProjects(rootDir, nugetCache, cache, projectScanTimeout, catalog = BUNDLED_RUNTIME_CATALOG) {
4515
4733
  const projectFiles = cache ? await cache.findFiles(rootDir, isDotnetProjectFile) : await findFiles(rootDir, isDotnetProjectFile);
4516
4734
  const slnFiles = cache ? await cache.findSolutionFiles(rootDir) : await findSolutionFiles(rootDir);
4517
4735
  const slnProjectPaths = /* @__PURE__ */ new Set();
@@ -4535,7 +4753,7 @@ async function scanDotnetProjects(rootDir, nugetCache, cache, projectScanTimeout
4535
4753
  const STUCK_TIMEOUT_MS = projectScanTimeout ?? cache?.projectScanTimeout ?? 18e4;
4536
4754
  for (const csprojPath of allCsprojFiles) {
4537
4755
  try {
4538
- const scanPromise = scanOneDotnetProjectFile(csprojPath, rootDir, nugetCache, cache);
4756
+ const scanPromise = scanOneDotnetProjectFile(csprojPath, rootDir, nugetCache, cache, catalog);
4539
4757
  const result = await withTimeout(scanPromise, STUCK_TIMEOUT_MS);
4540
4758
  if (result.ok) {
4541
4759
  results.push(result.value);
@@ -4556,18 +4774,24 @@ async function scanDotnetProjects(rootDir, nugetCache, cache, projectScanTimeout
4556
4774
  }
4557
4775
  return results;
4558
4776
  }
4559
- async function scanOneDotnetProjectFile(csprojPath, rootDir, nugetCache, cache) {
4777
+ async function scanOneDotnetProjectFile(csprojPath, rootDir, nugetCache, cache, catalog) {
4560
4778
  const xml = cache ? await cache.readTextFile(csprojPath) : await readTextFile(csprojPath);
4561
4779
  const data = parseDotnetProjectFile(xml, csprojPath);
4562
4780
  const csprojDir = path4.dirname(csprojPath);
4563
4781
  const primaryTfm = data.targetFrameworks[0];
4782
+ const dotnetLatest = latestStable(catalog, "dotnet")?.major;
4564
4783
  let runtimeMajorsBehind;
4784
+ let runtimeEol;
4785
+ let runtimeEolDate;
4565
4786
  let targetFramework = primaryTfm;
4566
4787
  if (primaryTfm) {
4567
4788
  const major11 = parseTfmMajor(primaryTfm);
4568
- if (major11 !== null) {
4569
- runtimeMajorsBehind = Math.max(0, LATEST_DOTNET_MAJOR - major11);
4789
+ if (major11 !== null && dotnetLatest !== void 0) {
4790
+ runtimeMajorsBehind = Math.max(0, dotnetLatest - major11);
4570
4791
  }
4792
+ runtimeEol = runtimeEolStatus(catalog, "dotnet", primaryTfm);
4793
+ const cycle = extractCycle("dotnet", primaryTfm);
4794
+ if (cycle) runtimeEolDate = eolDate(catalog, "dotnet", cycle);
4571
4795
  }
4572
4796
  const dependencies = [];
4573
4797
  const bucketsMut = { current: 0, oneBehind: 0, twoPlusBehind: 0, unknown: 0 };
@@ -4579,15 +4803,15 @@ async function scanOneDotnetProjectFile(csprojPath, rootDir, nugetCache, cache)
4579
4803
  const resolved = await Promise.all(metaPromises);
4580
4804
  for (const { ref, meta } of resolved) {
4581
4805
  const resolvedVersion = semver3.valid(ref.version) ? ref.version : null;
4582
- const latestStable = meta.latestStableOverall;
4806
+ const latestStable2 = meta.latestStableOverall;
4583
4807
  let majorsBehind = null;
4584
4808
  let drift = "unknown";
4585
- if (resolvedVersion && latestStable) {
4809
+ if (resolvedVersion && latestStable2) {
4586
4810
  const currentMajor = semver3.major(resolvedVersion);
4587
- const latestMajor = semver3.major(latestStable);
4811
+ const latestMajor = semver3.major(latestStable2);
4588
4812
  majorsBehind = latestMajor - currentMajor;
4589
4813
  if (majorsBehind === 0) {
4590
- drift = semver3.eq(resolvedVersion, latestStable) ? "current" : "minor-behind";
4814
+ drift = semver3.eq(resolvedVersion, latestStable2) ? "current" : "minor-behind";
4591
4815
  } else if (majorsBehind > 0) {
4592
4816
  drift = "major-behind";
4593
4817
  } else {
@@ -4604,7 +4828,7 @@ async function scanOneDotnetProjectFile(csprojPath, rootDir, nugetCache, cache)
4604
4828
  section: "dependencies",
4605
4829
  currentSpec: ref.version,
4606
4830
  resolvedVersion,
4607
- latestStable,
4831
+ latestStable: latestStable2,
4608
4832
  majorsBehind,
4609
4833
  drift
4610
4834
  });
@@ -4664,8 +4888,10 @@ async function scanOneDotnetProjectFile(csprojPath, rootDir, nugetCache, cache)
4664
4888
  name: data.projectName,
4665
4889
  targetFramework,
4666
4890
  runtime: primaryTfm,
4667
- runtimeLatest: `net${LATEST_DOTNET_MAJOR}.0`,
4891
+ runtimeLatest: dotnetLatest !== void 0 ? `net${dotnetLatest}.0` : void 0,
4668
4892
  runtimeMajorsBehind,
4893
+ runtimeEol,
4894
+ runtimeEolDate,
4669
4895
  frameworks,
4670
4896
  dependencies,
4671
4897
  dependencyAgeBuckets: buckets,
@@ -4778,7 +5004,6 @@ var KNOWN_PYTHON_FRAMEWORKS = {
4778
5004
  "opentelemetry-api": "OpenTelemetry",
4779
5005
  "prometheus-client": "prometheus-client"
4780
5006
  };
4781
- var LATEST_PYTHON_MINOR = { major: 3, minor: 13 };
4782
5007
  function parseRequirementLine(line) {
4783
5008
  const trimmed = line.trim();
4784
5009
  if (!trimmed || trimmed.startsWith("#") || trimmed.startsWith("-")) return null;
@@ -4908,7 +5133,7 @@ var PYTHON_MANIFEST_FILES = /* @__PURE__ */ new Set([
4908
5133
  "setup.cfg",
4909
5134
  "Pipfile"
4910
5135
  ]);
4911
- async function scanPythonProjects(rootDir, pypiCache, cache, projectScanTimeout) {
5136
+ async function scanPythonProjects(rootDir, pypiCache, cache, projectScanTimeout, catalog = BUNDLED_RUNTIME_CATALOG) {
4912
5137
  const manifestFiles = cache ? await cache.findFiles(rootDir, (name) => PYTHON_MANIFEST_FILES.has(name) || /^requirements.*\.txt$/.test(name)) : await findPythonManifests(rootDir);
4913
5138
  const projectDirs = /* @__PURE__ */ new Map();
4914
5139
  for (const f of manifestFiles) {
@@ -4920,7 +5145,7 @@ async function scanPythonProjects(rootDir, pypiCache, cache, projectScanTimeout)
4920
5145
  const STUCK_TIMEOUT_MS = projectScanTimeout ?? cache?.projectScanTimeout ?? 18e4;
4921
5146
  for (const [dir, files] of projectDirs) {
4922
5147
  try {
4923
- const scanPromise = scanOnePythonProject(dir, files, rootDir, pypiCache, cache);
5148
+ const scanPromise = scanOnePythonProject(dir, files, rootDir, pypiCache, cache, catalog);
4924
5149
  const result = await withTimeout(scanPromise, STUCK_TIMEOUT_MS);
4925
5150
  if (result.ok) {
4926
5151
  results.push(result.value);
@@ -4943,7 +5168,7 @@ async function findPythonManifests(rootDir) {
4943
5168
  const { findFiles: findFiles2 } = await import("./fs-PXXYZATK-EW5LCUA7.js");
4944
5169
  return findFiles2(rootDir, (name) => PYTHON_MANIFEST_FILES.has(name) || /^requirements.*\.txt$/.test(name));
4945
5170
  }
4946
- async function scanOnePythonProject(dir, manifestFiles, rootDir, pypiCache, cache) {
5171
+ async function scanOnePythonProject(dir, manifestFiles, rootDir, pypiCache, cache, catalog) {
4947
5172
  const relDir = path5.relative(rootDir, dir) || ".";
4948
5173
  let projectName = path5.basename(dir === rootDir ? rootDir : dir);
4949
5174
  let pythonVersion;
@@ -4977,18 +5202,26 @@ async function scanOnePythonProject(dir, manifestFiles, rootDir, pypiCache, cach
4977
5202
  }
4978
5203
  let runtimeMajorsBehind;
4979
5204
  let runtimeLatest;
5205
+ let runtimeEol;
5206
+ let runtimeEolDate;
4980
5207
  if (pythonVersion) {
4981
5208
  const verMatch = pythonVersion.match(/(\d+)\.(\d+)/);
4982
5209
  if (verMatch) {
4983
5210
  const reqMajor = parseInt(verMatch[1], 10);
4984
5211
  const reqMinor = parseInt(verMatch[2], 10);
4985
- if (reqMajor === LATEST_PYTHON_MINOR.major) {
4986
- runtimeMajorsBehind = Math.max(0, LATEST_PYTHON_MINOR.minor - reqMinor);
4987
- } else if (reqMajor < LATEST_PYTHON_MINOR.major) {
4988
- runtimeMajorsBehind = LATEST_PYTHON_MINOR.minor + (LATEST_PYTHON_MINOR.major - reqMajor) * 10;
5212
+ const LATEST_PYTHON_MINOR = latestStable(catalog, "python");
5213
+ if (LATEST_PYTHON_MINOR) {
5214
+ if (reqMajor === LATEST_PYTHON_MINOR.major) {
5215
+ runtimeMajorsBehind = Math.max(0, LATEST_PYTHON_MINOR.minor - reqMinor);
5216
+ } else if (reqMajor < LATEST_PYTHON_MINOR.major) {
5217
+ runtimeMajorsBehind = LATEST_PYTHON_MINOR.minor + (LATEST_PYTHON_MINOR.major - reqMajor) * 10;
5218
+ }
5219
+ runtimeLatest = `${LATEST_PYTHON_MINOR.major}.${LATEST_PYTHON_MINOR.minor}`;
4989
5220
  }
4990
- runtimeLatest = `${LATEST_PYTHON_MINOR.major}.${LATEST_PYTHON_MINOR.minor}`;
4991
5221
  }
5222
+ runtimeEol = runtimeEolStatus(catalog, "python", pythonVersion);
5223
+ const cycle = extractCycle("python", pythonVersion);
5224
+ if (cycle) runtimeEolDate = eolDate(catalog, "python", cycle);
4992
5225
  }
4993
5226
  const dependencies = [];
4994
5227
  const frameworks = [];
@@ -5002,15 +5235,15 @@ async function scanOnePythonProject(dir, manifestFiles, rootDir, pypiCache, cach
5002
5235
  for (const { dep, meta } of resolved) {
5003
5236
  const pinnedVersion = extractPinnedVersion(dep.spec);
5004
5237
  const resolvedVersion = pinnedVersion ? pep440ToSemver(pinnedVersion) : null;
5005
- const latestStable = meta.latestStableOverall;
5238
+ const latestStable2 = meta.latestStableOverall;
5006
5239
  let majorsBehind = null;
5007
5240
  let drift = "unknown";
5008
- if (resolvedVersion && latestStable) {
5241
+ if (resolvedVersion && latestStable2) {
5009
5242
  const currentMajor = semver4.major(resolvedVersion);
5010
- const latestMajor = semver4.major(latestStable);
5243
+ const latestMajor = semver4.major(latestStable2);
5011
5244
  majorsBehind = latestMajor - currentMajor;
5012
5245
  if (majorsBehind === 0) {
5013
- drift = semver4.eq(resolvedVersion, latestStable) ? "current" : "minor-behind";
5246
+ drift = semver4.eq(resolvedVersion, latestStable2) ? "current" : "minor-behind";
5014
5247
  } else if (majorsBehind > 0) {
5015
5248
  drift = "major-behind";
5016
5249
  } else {
@@ -5027,7 +5260,7 @@ async function scanOnePythonProject(dir, manifestFiles, rootDir, pypiCache, cach
5027
5260
  section: "dependencies",
5028
5261
  currentSpec: dep.spec || "*",
5029
5262
  resolvedVersion,
5030
- latestStable,
5263
+ latestStable: latestStable2,
5031
5264
  majorsBehind,
5032
5265
  drift
5033
5266
  });
@@ -5035,7 +5268,7 @@ async function scanOnePythonProject(dir, manifestFiles, rootDir, pypiCache, cach
5035
5268
  frameworks.push({
5036
5269
  name: KNOWN_PYTHON_FRAMEWORKS[dep.normalisedName],
5037
5270
  currentVersion: resolvedVersion,
5038
- latestVersion: latestStable,
5271
+ latestVersion: latestStable2,
5039
5272
  majorsBehind
5040
5273
  });
5041
5274
  }
@@ -5058,6 +5291,8 @@ async function scanOnePythonProject(dir, manifestFiles, rootDir, pypiCache, cach
5058
5291
  runtime: pythonVersion,
5059
5292
  runtimeLatest,
5060
5293
  runtimeMajorsBehind,
5294
+ runtimeEol,
5295
+ runtimeEolDate,
5061
5296
  frameworks,
5062
5297
  dependencies,
5063
5298
  dependencyAgeBuckets: buckets,
@@ -5161,7 +5396,6 @@ var KNOWN_JAVA_FRAMEWORKS = {
5161
5396
  "io.projectreactor:reactor-core": "Project Reactor",
5162
5397
  "io.reactivex.rxjava3:rxjava": "RxJava 3"
5163
5398
  };
5164
- var LATEST_JAVA_LTS = 21;
5165
5399
  function parsePom(xml, filePath) {
5166
5400
  const parsed = parser2.parse(xml);
5167
5401
  const project = parsed?.project;
@@ -5301,7 +5535,7 @@ function mavenToSemver(ver) {
5301
5535
  return semver5.valid(v);
5302
5536
  }
5303
5537
  var JAVA_MANIFEST_FILES = /* @__PURE__ */ new Set(["pom.xml", "build.gradle", "build.gradle.kts"]);
5304
- async function scanJavaProjects(rootDir, mavenCache, cache, projectScanTimeout) {
5538
+ async function scanJavaProjects(rootDir, mavenCache, cache, projectScanTimeout, catalog = BUNDLED_RUNTIME_CATALOG) {
5305
5539
  const manifestFiles = cache ? await cache.findFiles(rootDir, (name) => JAVA_MANIFEST_FILES.has(name)) : await findJavaManifests(rootDir);
5306
5540
  const projectDirs = /* @__PURE__ */ new Map();
5307
5541
  for (const f of manifestFiles) {
@@ -5313,7 +5547,7 @@ async function scanJavaProjects(rootDir, mavenCache, cache, projectScanTimeout)
5313
5547
  const STUCK_TIMEOUT_MS = projectScanTimeout ?? cache?.projectScanTimeout ?? 18e4;
5314
5548
  for (const [dir, files] of projectDirs) {
5315
5549
  try {
5316
- const scanPromise = scanOneJavaProject(dir, files, rootDir, mavenCache, cache);
5550
+ const scanPromise = scanOneJavaProject(dir, files, rootDir, mavenCache, cache, catalog);
5317
5551
  const result = await withTimeout(scanPromise, STUCK_TIMEOUT_MS);
5318
5552
  if (result.ok) {
5319
5553
  results.push(result.value);
@@ -5340,7 +5574,7 @@ async function findJavaManifests(rootDir) {
5340
5574
  const { findFiles: findFiles2 } = await import("./fs-PXXYZATK-EW5LCUA7.js");
5341
5575
  return findFiles2(rootDir, (name) => JAVA_MANIFEST_FILES.has(name));
5342
5576
  }
5343
- async function scanOneJavaProject(dir, manifestFiles, rootDir, mavenCache, cache) {
5577
+ async function scanOneJavaProject(dir, manifestFiles, rootDir, mavenCache, cache, catalog) {
5344
5578
  const relDir = path6.relative(rootDir, dir) || ".";
5345
5579
  let projectName = path6.basename(dir === rootDir ? rootDir : dir);
5346
5580
  let javaVersion;
@@ -5379,14 +5613,19 @@ async function scanOneJavaProject(dir, manifestFiles, rootDir, mavenCache, cache
5379
5613
  }
5380
5614
  }
5381
5615
  let runtimeMajorsBehind;
5382
- let runtimeLatest;
5616
+ let runtimeEol;
5617
+ let runtimeEolDate;
5618
+ const javaLts = latestLts(catalog, "java")?.major;
5619
+ const runtimeLatest = javaLts !== void 0 ? String(javaLts) : void 0;
5383
5620
  if (javaVersion) {
5384
5621
  const jvMatch = javaVersion.match(/^(1\.)?(\d+)/);
5385
5622
  if (jvMatch) {
5386
5623
  const major11 = jvMatch[1] ? parseInt(jvMatch[2], 10) : parseInt(jvMatch[2], 10);
5387
- runtimeMajorsBehind = Math.max(0, LATEST_JAVA_LTS - major11);
5388
- runtimeLatest = String(LATEST_JAVA_LTS);
5624
+ if (javaLts !== void 0) runtimeMajorsBehind = Math.max(0, javaLts - major11);
5389
5625
  }
5626
+ runtimeEol = runtimeEolStatus(catalog, "java", javaVersion);
5627
+ const cycle = extractCycle("java", javaVersion);
5628
+ if (cycle) runtimeEolDate = eolDate(catalog, "java", cycle);
5390
5629
  }
5391
5630
  const dependencies = [];
5392
5631
  const frameworks = [];
@@ -5399,15 +5638,15 @@ async function scanOneJavaProject(dir, manifestFiles, rootDir, mavenCache, cache
5399
5638
  const resolved = await Promise.all(metaPromises);
5400
5639
  for (const { key, dep, meta } of resolved) {
5401
5640
  const resolvedVersion = mavenToSemver(dep.version);
5402
- const latestStable = meta.latestStableOverall;
5641
+ const latestStable2 = meta.latestStableOverall;
5403
5642
  let majorsBehind = null;
5404
5643
  let drift = "unknown";
5405
- if (resolvedVersion && latestStable) {
5644
+ if (resolvedVersion && latestStable2) {
5406
5645
  const currentMajor = semver5.major(resolvedVersion);
5407
- const latestMajor = semver5.major(latestStable);
5646
+ const latestMajor = semver5.major(latestStable2);
5408
5647
  majorsBehind = latestMajor - currentMajor;
5409
5648
  if (majorsBehind === 0) {
5410
- drift = semver5.eq(resolvedVersion, latestStable) ? "current" : "minor-behind";
5649
+ drift = semver5.eq(resolvedVersion, latestStable2) ? "current" : "minor-behind";
5411
5650
  } else if (majorsBehind > 0) {
5412
5651
  drift = "major-behind";
5413
5652
  } else {
@@ -5424,7 +5663,7 @@ async function scanOneJavaProject(dir, manifestFiles, rootDir, mavenCache, cache
5424
5663
  section: "dependencies",
5425
5664
  currentSpec: dep.version,
5426
5665
  resolvedVersion,
5427
- latestStable,
5666
+ latestStable: latestStable2,
5428
5667
  majorsBehind,
5429
5668
  drift
5430
5669
  });
@@ -5432,7 +5671,7 @@ async function scanOneJavaProject(dir, manifestFiles, rootDir, mavenCache, cache
5432
5671
  frameworks.push({
5433
5672
  name: KNOWN_JAVA_FRAMEWORKS[key],
5434
5673
  currentVersion: resolvedVersion,
5435
- latestVersion: latestStable,
5674
+ latestVersion: latestStable2,
5436
5675
  majorsBehind
5437
5676
  });
5438
5677
  }
@@ -5453,8 +5692,10 @@ async function scanOneJavaProject(dir, manifestFiles, rootDir, mavenCache, cache
5453
5692
  path: relDir,
5454
5693
  name: projectName,
5455
5694
  runtime: javaVersion ? `Java ${javaVersion}` : void 0,
5456
- runtimeLatest: String(LATEST_JAVA_LTS),
5695
+ runtimeLatest,
5457
5696
  runtimeMajorsBehind,
5697
+ runtimeEol,
5698
+ runtimeEolDate,
5458
5699
  targetFramework: javaVersion ? `Java ${javaVersion}` : void 0,
5459
5700
  frameworks,
5460
5701
  dependencies,
@@ -5580,7 +5821,6 @@ var KNOWN_RUBY_FRAMEWORKS = {
5580
5821
  "dotenv": "dotenv",
5581
5822
  "figaro": "Figaro"
5582
5823
  };
5583
- var LATEST_RUBY_MINOR = { major: 3, minor: 4 };
5584
5824
  function parseGemfileLine(line) {
5585
5825
  const trimmed = line.trim();
5586
5826
  if (!trimmed || trimmed.startsWith("#")) return null;
@@ -5670,7 +5910,7 @@ var RUBY_MANIFEST_FILES = /* @__PURE__ */ new Set([
5670
5910
  function isGemspec(name) {
5671
5911
  return name.endsWith(".gemspec");
5672
5912
  }
5673
- async function scanRubyProjects(rootDir, rubygemsCache, cache, projectScanTimeout) {
5913
+ async function scanRubyProjects(rootDir, rubygemsCache, cache, projectScanTimeout, catalog = BUNDLED_RUNTIME_CATALOG) {
5674
5914
  const manifestFiles = cache ? await cache.findFiles(rootDir, (name) => RUBY_MANIFEST_FILES.has(name) || isGemspec(name)) : await findRubyManifests(rootDir);
5675
5915
  const projectDirs = /* @__PURE__ */ new Map();
5676
5916
  for (const f of manifestFiles) {
@@ -5682,7 +5922,7 @@ async function scanRubyProjects(rootDir, rubygemsCache, cache, projectScanTimeou
5682
5922
  const STUCK_TIMEOUT_MS = projectScanTimeout ?? cache?.projectScanTimeout ?? 18e4;
5683
5923
  for (const [dir, files] of projectDirs) {
5684
5924
  try {
5685
- const scanPromise = scanOneRubyProject(dir, files, rootDir, rubygemsCache, cache);
5925
+ const scanPromise = scanOneRubyProject(dir, files, rootDir, rubygemsCache, cache, catalog);
5686
5926
  const result = await withTimeout(scanPromise, STUCK_TIMEOUT_MS);
5687
5927
  if (result.ok) {
5688
5928
  results.push(result.value);
@@ -5705,7 +5945,7 @@ async function findRubyManifests(rootDir) {
5705
5945
  const { findFiles: findFiles2 } = await import("./fs-PXXYZATK-EW5LCUA7.js");
5706
5946
  return findFiles2(rootDir, (name) => RUBY_MANIFEST_FILES.has(name) || isGemspec(name));
5707
5947
  }
5708
- async function scanOneRubyProject(dir, manifestFiles, rootDir, rubygemsCache, cache) {
5948
+ async function scanOneRubyProject(dir, manifestFiles, rootDir, rubygemsCache, cache, catalog) {
5709
5949
  const relDir = path7.relative(rootDir, dir) || ".";
5710
5950
  let projectName = path7.basename(dir === rootDir ? rootDir : dir);
5711
5951
  let rubyVersion;
@@ -5729,18 +5969,26 @@ async function scanOneRubyProject(dir, manifestFiles, rootDir, rubygemsCache, ca
5729
5969
  }
5730
5970
  let runtimeMajorsBehind;
5731
5971
  let runtimeLatest;
5972
+ let runtimeEol;
5973
+ let runtimeEolDate;
5732
5974
  if (rubyVersion) {
5733
5975
  const verMatch = rubyVersion.match(/(\d+)\.(\d+)/);
5734
5976
  if (verMatch) {
5735
5977
  const reqMajor = parseInt(verMatch[1], 10);
5736
5978
  const reqMinor = parseInt(verMatch[2], 10);
5737
- if (reqMajor === LATEST_RUBY_MINOR.major) {
5738
- runtimeMajorsBehind = Math.max(0, LATEST_RUBY_MINOR.minor - reqMinor);
5739
- } else if (reqMajor < LATEST_RUBY_MINOR.major) {
5740
- runtimeMajorsBehind = LATEST_RUBY_MINOR.minor + (LATEST_RUBY_MINOR.major - reqMajor) * 10;
5979
+ const LATEST_RUBY_MINOR = latestStable(catalog, "ruby");
5980
+ if (LATEST_RUBY_MINOR) {
5981
+ if (reqMajor === LATEST_RUBY_MINOR.major) {
5982
+ runtimeMajorsBehind = Math.max(0, LATEST_RUBY_MINOR.minor - reqMinor);
5983
+ } else if (reqMajor < LATEST_RUBY_MINOR.major) {
5984
+ runtimeMajorsBehind = LATEST_RUBY_MINOR.minor + (LATEST_RUBY_MINOR.major - reqMajor) * 10;
5985
+ }
5986
+ runtimeLatest = `${LATEST_RUBY_MINOR.major}.${LATEST_RUBY_MINOR.minor}`;
5741
5987
  }
5742
- runtimeLatest = `${LATEST_RUBY_MINOR.major}.${LATEST_RUBY_MINOR.minor}`;
5743
5988
  }
5989
+ runtimeEol = runtimeEolStatus(catalog, "ruby", rubyVersion);
5990
+ const cycle = extractCycle("ruby", rubyVersion);
5991
+ if (cycle) runtimeEolDate = eolDate(catalog, "ruby", cycle);
5744
5992
  }
5745
5993
  const dependencies = [];
5746
5994
  const frameworks = [];
@@ -5754,15 +6002,15 @@ async function scanOneRubyProject(dir, manifestFiles, rootDir, rubygemsCache, ca
5754
6002
  for (const { dep, meta } of resolved) {
5755
6003
  const rawVersion = extractGemVersion(dep.spec);
5756
6004
  const resolvedVersion = rawVersion ? rubyVersionToSemver(rawVersion) : null;
5757
- const latestStable = meta.latestStableOverall;
6005
+ const latestStable2 = meta.latestStableOverall;
5758
6006
  let majorsBehind = null;
5759
6007
  let drift = "unknown";
5760
- if (resolvedVersion && latestStable) {
6008
+ if (resolvedVersion && latestStable2) {
5761
6009
  const currentMajor = semver6.major(resolvedVersion);
5762
- const latestMajor = semver6.major(latestStable);
6010
+ const latestMajor = semver6.major(latestStable2);
5763
6011
  majorsBehind = latestMajor - currentMajor;
5764
6012
  if (majorsBehind === 0) {
5765
- drift = semver6.eq(resolvedVersion, latestStable) ? "current" : "minor-behind";
6013
+ drift = semver6.eq(resolvedVersion, latestStable2) ? "current" : "minor-behind";
5766
6014
  } else if (majorsBehind > 0) {
5767
6015
  drift = "major-behind";
5768
6016
  } else {
@@ -5780,7 +6028,7 @@ async function scanOneRubyProject(dir, manifestFiles, rootDir, rubygemsCache, ca
5780
6028
  section,
5781
6029
  currentSpec: dep.spec,
5782
6030
  resolvedVersion,
5783
- latestStable,
6031
+ latestStable: latestStable2,
5784
6032
  majorsBehind,
5785
6033
  drift
5786
6034
  });
@@ -5788,7 +6036,7 @@ async function scanOneRubyProject(dir, manifestFiles, rootDir, rubygemsCache, ca
5788
6036
  frameworks.push({
5789
6037
  name: KNOWN_RUBY_FRAMEWORKS[dep.name],
5790
6038
  currentVersion: resolvedVersion,
5791
- latestVersion: latestStable,
6039
+ latestVersion: latestStable2,
5792
6040
  majorsBehind
5793
6041
  });
5794
6042
  }
@@ -5811,6 +6059,8 @@ async function scanOneRubyProject(dir, manifestFiles, rootDir, rubygemsCache, ca
5811
6059
  runtime: rubyVersion,
5812
6060
  runtimeLatest,
5813
6061
  runtimeMajorsBehind,
6062
+ runtimeEol,
6063
+ runtimeEolDate,
5814
6064
  frameworks,
5815
6065
  dependencies,
5816
6066
  dependencyAgeBuckets: buckets,
@@ -5968,15 +6218,15 @@ async function scanOneSwiftProject(dir, manifestFile, rootDir, swiftCache, cache
5968
6218
  for (const { dep, meta } of resolved) {
5969
6219
  const resolvedVersionStr = resolvedVersions.get(dep.name.toLowerCase()) ?? (dep.type === "exact" ? dep.version : null);
5970
6220
  const resolvedVersion = resolvedVersionStr ? semver7.valid(semver7.clean(resolvedVersionStr)) : null;
5971
- const latestStable = meta.latestStableOverall;
6221
+ const latestStable2 = meta.latestStableOverall;
5972
6222
  let majorsBehind = null;
5973
6223
  let drift = "unknown";
5974
- if (resolvedVersion && latestStable) {
6224
+ if (resolvedVersion && latestStable2) {
5975
6225
  const currentMajor = semver7.major(resolvedVersion);
5976
- const latestMajor = semver7.major(latestStable);
6226
+ const latestMajor = semver7.major(latestStable2);
5977
6227
  majorsBehind = latestMajor - currentMajor;
5978
6228
  if (majorsBehind === 0) {
5979
- drift = semver7.eq(resolvedVersion, latestStable) ? "current" : "minor-behind";
6229
+ drift = semver7.eq(resolvedVersion, latestStable2) ? "current" : "minor-behind";
5980
6230
  } else if (majorsBehind > 0) {
5981
6231
  drift = "major-behind";
5982
6232
  } else {
@@ -5993,7 +6243,7 @@ async function scanOneSwiftProject(dir, manifestFile, rootDir, swiftCache, cache
5993
6243
  section: "dependencies",
5994
6244
  currentSpec: dep.version,
5995
6245
  resolvedVersion,
5996
- latestStable,
6246
+ latestStable: latestStable2,
5997
6247
  majorsBehind,
5998
6248
  drift
5999
6249
  });
@@ -6002,7 +6252,7 @@ async function scanOneSwiftProject(dir, manifestFile, rootDir, swiftCache, cache
6002
6252
  frameworks.push({
6003
6253
  name: KNOWN_SWIFT_FRAMEWORKS[lowerName],
6004
6254
  currentVersion: resolvedVersion,
6005
- latestVersion: latestStable,
6255
+ latestVersion: latestStable2,
6006
6256
  majorsBehind
6007
6257
  });
6008
6258
  }
@@ -6089,7 +6339,6 @@ var KNOWN_GO_FRAMEWORKS = {
6089
6339
  "github.com/spf13/cast": "Cast",
6090
6340
  "github.com/pkg/errors": "pkg/errors"
6091
6341
  };
6092
- var LATEST_GO_MINOR = { major: 1, minor: 23 };
6093
6342
  function parseGoMod(content) {
6094
6343
  const deps = [];
6095
6344
  let goVersion;
@@ -6136,14 +6385,14 @@ function parseGoMod(content) {
6136
6385
  var GO_MANIFEST_FILES = /* @__PURE__ */ new Set([
6137
6386
  "go.mod"
6138
6387
  ]);
6139
- async function scanGoProjects(rootDir, goCache, cache, projectScanTimeout) {
6388
+ async function scanGoProjects(rootDir, goCache, cache, projectScanTimeout, catalog = BUNDLED_RUNTIME_CATALOG) {
6140
6389
  const manifestFiles = cache ? await cache.findFiles(rootDir, (name) => GO_MANIFEST_FILES.has(name)) : await findGoManifests(rootDir);
6141
6390
  const results = [];
6142
6391
  const STUCK_TIMEOUT_MS = projectScanTimeout ?? cache?.projectScanTimeout ?? 18e4;
6143
6392
  for (const manifestFile of manifestFiles) {
6144
6393
  const dir = path9.dirname(manifestFile);
6145
6394
  try {
6146
- const scanPromise = scanOneGoProject(dir, manifestFile, rootDir, goCache, cache);
6395
+ const scanPromise = scanOneGoProject(dir, manifestFile, rootDir, goCache, cache, catalog);
6147
6396
  const result = await withTimeout(scanPromise, STUCK_TIMEOUT_MS);
6148
6397
  if (result.ok) {
6149
6398
  results.push(result.value);
@@ -6166,7 +6415,7 @@ async function findGoManifests(rootDir) {
6166
6415
  const { findFiles: findFiles2 } = await import("./fs-PXXYZATK-EW5LCUA7.js");
6167
6416
  return findFiles2(rootDir, (name) => GO_MANIFEST_FILES.has(name));
6168
6417
  }
6169
- async function scanOneGoProject(dir, manifestFile, rootDir, goCache, cache) {
6418
+ async function scanOneGoProject(dir, manifestFile, rootDir, goCache, cache, catalog) {
6170
6419
  const relDir = path9.relative(rootDir, dir) || ".";
6171
6420
  const projectName = path9.basename(dir === rootDir ? rootDir : dir);
6172
6421
  const content = cache ? await cache.readTextFile(manifestFile) : await readTextFile(manifestFile);
@@ -6174,18 +6423,26 @@ async function scanOneGoProject(dir, manifestFile, rootDir, goCache, cache) {
6174
6423
  const directDeps = allDeps.filter((d) => !d.indirect);
6175
6424
  let runtimeMajorsBehind;
6176
6425
  let runtimeLatest;
6426
+ let runtimeEol;
6427
+ let runtimeEolDate;
6177
6428
  if (goVersion) {
6178
6429
  const verMatch = goVersion.match(/(\d+)\.(\d+)/);
6179
6430
  if (verMatch) {
6180
6431
  const reqMajor = parseInt(verMatch[1], 10);
6181
6432
  const reqMinor = parseInt(verMatch[2], 10);
6182
- if (reqMajor === LATEST_GO_MINOR.major) {
6183
- runtimeMajorsBehind = Math.max(0, LATEST_GO_MINOR.minor - reqMinor);
6184
- } else if (reqMajor < LATEST_GO_MINOR.major) {
6185
- runtimeMajorsBehind = LATEST_GO_MINOR.minor + (LATEST_GO_MINOR.major - reqMajor) * 100;
6433
+ const LATEST_GO_MINOR = latestStable(catalog, "go");
6434
+ if (LATEST_GO_MINOR) {
6435
+ if (reqMajor === LATEST_GO_MINOR.major) {
6436
+ runtimeMajorsBehind = Math.max(0, LATEST_GO_MINOR.minor - reqMinor);
6437
+ } else if (reqMajor < LATEST_GO_MINOR.major) {
6438
+ runtimeMajorsBehind = LATEST_GO_MINOR.minor + (LATEST_GO_MINOR.major - reqMajor) * 100;
6439
+ }
6440
+ runtimeLatest = `${LATEST_GO_MINOR.major}.${LATEST_GO_MINOR.minor}`;
6186
6441
  }
6187
- runtimeLatest = `${LATEST_GO_MINOR.major}.${LATEST_GO_MINOR.minor}`;
6188
6442
  }
6443
+ runtimeEol = runtimeEolStatus(catalog, "go", goVersion);
6444
+ const cycle = extractCycle("go", goVersion);
6445
+ if (cycle) runtimeEolDate = eolDate(catalog, "go", cycle);
6189
6446
  }
6190
6447
  const dependencies = [];
6191
6448
  const frameworks = [];
@@ -6197,15 +6454,15 @@ async function scanOneGoProject(dir, manifestFile, rootDir, goCache, cache) {
6197
6454
  const resolved = await Promise.all(metaPromises);
6198
6455
  for (const { dep, meta } of resolved) {
6199
6456
  const resolvedVersion = semver8.valid(semver8.clean(dep.version));
6200
- const latestStable = meta.latestStableOverall;
6457
+ const latestStable2 = meta.latestStableOverall;
6201
6458
  let majorsBehind = null;
6202
6459
  let drift = "unknown";
6203
- if (resolvedVersion && latestStable) {
6460
+ if (resolvedVersion && latestStable2) {
6204
6461
  const currentMajor = semver8.major(resolvedVersion);
6205
- const latestMajor = semver8.major(latestStable);
6462
+ const latestMajor = semver8.major(latestStable2);
6206
6463
  majorsBehind = latestMajor - currentMajor;
6207
6464
  if (majorsBehind === 0) {
6208
- drift = semver8.eq(resolvedVersion, latestStable) ? "current" : "minor-behind";
6465
+ drift = semver8.eq(resolvedVersion, latestStable2) ? "current" : "minor-behind";
6209
6466
  } else if (majorsBehind > 0) {
6210
6467
  drift = "major-behind";
6211
6468
  } else {
@@ -6222,7 +6479,7 @@ async function scanOneGoProject(dir, manifestFile, rootDir, goCache, cache) {
6222
6479
  section: "dependencies",
6223
6480
  currentSpec: dep.version,
6224
6481
  resolvedVersion,
6225
- latestStable,
6482
+ latestStable: latestStable2,
6226
6483
  majorsBehind,
6227
6484
  drift
6228
6485
  });
@@ -6230,7 +6487,7 @@ async function scanOneGoProject(dir, manifestFile, rootDir, goCache, cache) {
6230
6487
  frameworks.push({
6231
6488
  name: KNOWN_GO_FRAMEWORKS[dep.path],
6232
6489
  currentVersion: resolvedVersion,
6233
- latestVersion: latestStable,
6490
+ latestVersion: latestStable2,
6234
6491
  majorsBehind
6235
6492
  });
6236
6493
  }
@@ -6253,6 +6510,8 @@ async function scanOneGoProject(dir, manifestFile, rootDir, goCache, cache) {
6253
6510
  runtime: goVersion,
6254
6511
  runtimeLatest,
6255
6512
  runtimeMajorsBehind,
6513
+ runtimeEol,
6514
+ runtimeEolDate,
6256
6515
  frameworks,
6257
6516
  dependencies,
6258
6517
  dependencyAgeBuckets: buckets,
@@ -6425,15 +6684,15 @@ async function scanOneRustProject(dir, manifestFile, rootDir, cargoCache, cache)
6425
6684
  const resolved = await Promise.all(metaPromises);
6426
6685
  for (const { dep, meta } of resolved) {
6427
6686
  const resolvedVersion = semver9.valid(semver9.coerce(dep.version));
6428
- const latestStable = meta.latestStableOverall;
6687
+ const latestStable2 = meta.latestStableOverall;
6429
6688
  let majorsBehind = null;
6430
6689
  let drift = "unknown";
6431
- if (resolvedVersion && latestStable) {
6690
+ if (resolvedVersion && latestStable2) {
6432
6691
  const currentMajor = semver9.major(resolvedVersion);
6433
- const latestMajor = semver9.major(latestStable);
6692
+ const latestMajor = semver9.major(latestStable2);
6434
6693
  majorsBehind = latestMajor - currentMajor;
6435
6694
  if (majorsBehind === 0) {
6436
- drift = semver9.eq(resolvedVersion, latestStable) ? "current" : "minor-behind";
6695
+ drift = semver9.eq(resolvedVersion, latestStable2) ? "current" : "minor-behind";
6437
6696
  } else if (majorsBehind > 0) {
6438
6697
  drift = "major-behind";
6439
6698
  } else {
@@ -6451,7 +6710,7 @@ async function scanOneRustProject(dir, manifestFile, rootDir, cargoCache, cache)
6451
6710
  section,
6452
6711
  currentSpec: dep.version,
6453
6712
  resolvedVersion,
6454
- latestStable,
6713
+ latestStable: latestStable2,
6455
6714
  majorsBehind,
6456
6715
  drift
6457
6716
  });
@@ -6459,7 +6718,7 @@ async function scanOneRustProject(dir, manifestFile, rootDir, cargoCache, cache)
6459
6718
  frameworks.push({
6460
6719
  name: KNOWN_RUST_FRAMEWORKS[dep.name],
6461
6720
  currentVersion: resolvedVersion,
6462
- latestVersion: latestStable,
6721
+ latestVersion: latestStable2,
6463
6722
  majorsBehind
6464
6723
  });
6465
6724
  }
@@ -6665,15 +6924,15 @@ async function scanOnePhpProject(dir, manifestFile, rootDir, composerCache, cach
6665
6924
  for (const { dep, meta } of resolved) {
6666
6925
  const resolvedVersionStr = resolvedVersions.get(dep.name);
6667
6926
  const resolvedVersion = resolvedVersionStr ? semver10.valid(semver10.clean(resolvedVersionStr)) : null;
6668
- const latestStable = meta.latestStableOverall;
6927
+ const latestStable2 = meta.latestStableOverall;
6669
6928
  let majorsBehind = null;
6670
6929
  let drift = "unknown";
6671
- if (resolvedVersion && latestStable) {
6930
+ if (resolvedVersion && latestStable2) {
6672
6931
  const currentMajor = semver10.major(resolvedVersion);
6673
- const latestMajor = semver10.major(latestStable);
6932
+ const latestMajor = semver10.major(latestStable2);
6674
6933
  majorsBehind = latestMajor - currentMajor;
6675
6934
  if (majorsBehind === 0) {
6676
- drift = semver10.eq(resolvedVersion, latestStable) ? "current" : "minor-behind";
6935
+ drift = semver10.eq(resolvedVersion, latestStable2) ? "current" : "minor-behind";
6677
6936
  } else if (majorsBehind > 0) {
6678
6937
  drift = "major-behind";
6679
6938
  } else {
@@ -6691,7 +6950,7 @@ async function scanOnePhpProject(dir, manifestFile, rootDir, composerCache, cach
6691
6950
  section,
6692
6951
  currentSpec: dep.version,
6693
6952
  resolvedVersion,
6694
- latestStable,
6953
+ latestStable: latestStable2,
6695
6954
  majorsBehind,
6696
6955
  drift
6697
6956
  });
@@ -6699,7 +6958,7 @@ async function scanOnePhpProject(dir, manifestFile, rootDir, composerCache, cach
6699
6958
  frameworks.push({
6700
6959
  name: KNOWN_PHP_FRAMEWORKS[dep.name],
6701
6960
  currentVersion: resolvedVersion,
6702
- latestVersion: latestStable,
6961
+ latestVersion: latestStable2,
6703
6962
  majorsBehind
6704
6963
  });
6705
6964
  }
@@ -6931,15 +7190,15 @@ async function scanOneDartProject(dir, manifestFile, rootDir, pubCache, cache) {
6931
7190
  for (const { dep, meta } of resolved) {
6932
7191
  const resolvedVersionStr = resolvedVersions.get(dep.name);
6933
7192
  const resolvedVersion = resolvedVersionStr ? semver11.valid(semver11.clean(resolvedVersionStr)) : null;
6934
- const latestStable = meta.latestStableOverall;
7193
+ const latestStable2 = meta.latestStableOverall;
6935
7194
  let majorsBehind = null;
6936
7195
  let drift = "unknown";
6937
- if (resolvedVersion && latestStable) {
7196
+ if (resolvedVersion && latestStable2) {
6938
7197
  const currentMajor = semver11.major(resolvedVersion);
6939
- const latestMajor = semver11.major(latestStable);
7198
+ const latestMajor = semver11.major(latestStable2);
6940
7199
  majorsBehind = latestMajor - currentMajor;
6941
7200
  if (majorsBehind === 0) {
6942
- drift = semver11.eq(resolvedVersion, latestStable) ? "current" : "minor-behind";
7201
+ drift = semver11.eq(resolvedVersion, latestStable2) ? "current" : "minor-behind";
6943
7202
  } else if (majorsBehind > 0) {
6944
7203
  drift = "major-behind";
6945
7204
  } else {
@@ -6957,7 +7216,7 @@ async function scanOneDartProject(dir, manifestFile, rootDir, pubCache, cache) {
6957
7216
  section,
6958
7217
  currentSpec: dep.version,
6959
7218
  resolvedVersion,
6960
- latestStable,
7219
+ latestStable: latestStable2,
6961
7220
  majorsBehind,
6962
7221
  drift
6963
7222
  });
@@ -6965,7 +7224,7 @@ async function scanOneDartProject(dir, manifestFile, rootDir, pubCache, cache) {
6965
7224
  frameworks.push({
6966
7225
  name: KNOWN_DART_FRAMEWORKS[dep.name],
6967
7226
  currentVersion: resolvedVersion,
6968
- latestVersion: latestStable,
7227
+ latestVersion: latestStable2,
6969
7228
  majorsBehind
6970
7229
  });
6971
7230
  }
@@ -13419,6 +13678,96 @@ async function computeTreeMetadataHash(rootDir, options) {
13419
13678
  }
13420
13679
  return digest.digest("hex");
13421
13680
  }
13681
+ var DEFAULT_API_BASE = "https://api.vibgrate.com";
13682
+ var CACHE_TTL_MS = 1e3 * 60 * 60 * 24;
13683
+ var FETCH_TIMEOUT_MS = 5e3;
13684
+ function isRuntimeCatalog(value) {
13685
+ return !!value && typeof value === "object" && typeof value.generatedAt === "string" && typeof value.products === "object" && value.products !== null;
13686
+ }
13687
+ var RuntimeCatalogClient = class {
13688
+ apiBase;
13689
+ offline;
13690
+ manifest;
13691
+ cacheDir;
13692
+ fetchImpl;
13693
+ resolution = null;
13694
+ constructor(opts = {}) {
13695
+ this.apiBase = (opts.apiBase ?? process.env.VIBGRATE_API_BASE ?? DEFAULT_API_BASE).replace(/\/+$/, "");
13696
+ this.offline = opts.offline ?? false;
13697
+ this.manifest = opts.manifest;
13698
+ this.cacheDir = opts.cacheDir ?? path32.join(os4.homedir(), ".vibgrate", "cache");
13699
+ this.fetchImpl = opts.fetchImpl ?? globalThis.fetch;
13700
+ }
13701
+ /** Resolve the catalog once; concurrent/repeat calls share the result. */
13702
+ resolve() {
13703
+ if (!this.resolution) this.resolution = this.doResolve();
13704
+ return this.resolution;
13705
+ }
13706
+ get cacheFile() {
13707
+ return path32.join(this.cacheDir, "runtimes.json");
13708
+ }
13709
+ async doResolve() {
13710
+ let staleCache;
13711
+ if (!this.offline) {
13712
+ const envelope = await this.readCache();
13713
+ if (envelope) {
13714
+ const fresh = Date.now() - Date.parse(envelope.fetchedAt) < CACHE_TTL_MS;
13715
+ if (fresh) return { catalog: envelope.catalog, source: "cache" };
13716
+ staleCache = envelope.catalog;
13717
+ }
13718
+ const fetched = await this.fetchFromApi();
13719
+ if (fetched) {
13720
+ await this.writeCache(fetched);
13721
+ return { catalog: fetched, source: "api" };
13722
+ }
13723
+ }
13724
+ const manifestCatalog = this.manifest?.runtimes;
13725
+ if (isRuntimeCatalog(manifestCatalog)) {
13726
+ return { catalog: manifestCatalog, source: "manifest" };
13727
+ }
13728
+ if (staleCache && Date.parse(staleCache.generatedAt) > Date.parse(BUNDLED_RUNTIME_CATALOG.generatedAt)) {
13729
+ return { catalog: staleCache, source: "cache" };
13730
+ }
13731
+ return { catalog: BUNDLED_RUNTIME_CATALOG, source: "bundled" };
13732
+ }
13733
+ async fetchFromApi() {
13734
+ if (!this.fetchImpl) return null;
13735
+ const controller = new AbortController();
13736
+ const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
13737
+ try {
13738
+ const res = await this.fetchImpl(`${this.apiBase}/v1/reference/runtimes`, {
13739
+ signal: controller.signal,
13740
+ headers: { Accept: "application/json" }
13741
+ });
13742
+ if (!res.ok) return null;
13743
+ const data = await res.json();
13744
+ return isRuntimeCatalog(data) ? data : null;
13745
+ } catch {
13746
+ return null;
13747
+ } finally {
13748
+ clearTimeout(timer);
13749
+ }
13750
+ }
13751
+ async readCache() {
13752
+ try {
13753
+ const text = await readFile4(this.cacheFile, "utf8");
13754
+ const parsed = JSON.parse(text);
13755
+ if (parsed && typeof parsed.fetchedAt === "string" && isRuntimeCatalog(parsed.catalog)) {
13756
+ return parsed;
13757
+ }
13758
+ } catch {
13759
+ }
13760
+ return null;
13761
+ }
13762
+ async writeCache(catalog) {
13763
+ try {
13764
+ await mkdir3(this.cacheDir, { recursive: true });
13765
+ const envelope = { fetchedAt: (/* @__PURE__ */ new Date()).toISOString(), catalog };
13766
+ await writeFile3(this.cacheFile, JSON.stringify(envelope), "utf8");
13767
+ } catch {
13768
+ }
13769
+ }
13770
+ };
13422
13771
  var ProgressTrace = class _ProgressTrace {
13423
13772
  constructor(outPath) {
13424
13773
  this.outPath = outPath;
@@ -13429,8 +13778,8 @@ var ProgressTrace = class _ProgressTrace {
13429
13778
  flushed = false;
13430
13779
  /** Returns a trace when VIBGRATE_TRACE_EVENTS is set, else null. */
13431
13780
  static fromEnv() {
13432
- const path34 = process.env.VIBGRATE_TRACE_EVENTS;
13433
- return path34 ? new _ProgressTrace(path34) : null;
13781
+ const path35 = process.env.VIBGRATE_TRACE_EVENTS;
13782
+ return path35 ? new _ProgressTrace(path35) : null;
13434
13783
  }
13435
13784
  record(op, data = {}) {
13436
13785
  if (this.flushed) return;
@@ -13947,7 +14296,7 @@ var ScanProgress = class {
13947
14296
  var HISTORY_FILENAME = "scan_history.json";
13948
14297
  var MAX_RECORDS = 10;
13949
14298
  async function loadScanHistory(rootDir) {
13950
- const filePath = path32.join(rootDir, ".vibgrate", HISTORY_FILENAME);
14299
+ const filePath = path33.join(rootDir, ".vibgrate", HISTORY_FILENAME);
13951
14300
  try {
13952
14301
  const txt = await fs7.readFile(filePath, "utf8");
13953
14302
  const data = JSON.parse(txt);
@@ -13960,8 +14309,8 @@ async function loadScanHistory(rootDir) {
13960
14309
  }
13961
14310
  }
13962
14311
  async function saveScanHistory(rootDir, record2) {
13963
- const dir = path32.join(rootDir, ".vibgrate");
13964
- const filePath = path32.join(dir, HISTORY_FILENAME);
14312
+ const dir = path33.join(rootDir, ".vibgrate");
14313
+ const filePath = path33.join(dir, HISTORY_FILENAME);
13965
14314
  let history;
13966
14315
  const existing = await loadScanHistory(rootDir);
13967
14316
  if (existing) {
@@ -14029,18 +14378,18 @@ async function discoverSolutions(rootDir, fileCache) {
14029
14378
  for (const solutionFile of solutionFiles) {
14030
14379
  try {
14031
14380
  const content = await fileCache.readTextFile(solutionFile);
14032
- const dir = path33.dirname(solutionFile);
14033
- const rootBasename = path33.basename(rootDir);
14034
- const relSolutionPath = [rootBasename, path33.relative(rootDir, solutionFile).replace(/\\/g, "/")].join("/");
14381
+ const dir = path34.dirname(solutionFile);
14382
+ const rootBasename = path34.basename(rootDir);
14383
+ const relSolutionPath = [rootBasename, path34.relative(rootDir, solutionFile).replace(/\\/g, "/")].join("/");
14035
14384
  const projectPaths = /* @__PURE__ */ new Set();
14036
14385
  const projectRegex = /Project\("[^"]*"\)\s*=\s*"([^"]*)",\s*"([^"]+\.(?:cs|vb)proj)"/g;
14037
14386
  let match;
14038
14387
  while ((match = projectRegex.exec(content)) !== null) {
14039
14388
  const projectRelative = match[2];
14040
- const absProjectPath = path33.resolve(dir, projectRelative.replace(/\\/g, "/"));
14041
- projectPaths.add(path33.relative(rootDir, absProjectPath).replace(/\\/g, "/"));
14389
+ const absProjectPath = path34.resolve(dir, projectRelative.replace(/\\/g, "/"));
14390
+ projectPaths.add(path34.relative(rootDir, absProjectPath).replace(/\\/g, "/"));
14042
14391
  }
14043
- const solutionName = path33.basename(solutionFile, path33.extname(solutionFile));
14392
+ const solutionName = path34.basename(solutionFile, path34.extname(solutionFile));
14044
14393
  parsed.push({
14045
14394
  path: relSolutionPath,
14046
14395
  name: solutionName,
@@ -14148,6 +14497,9 @@ async function runScan(rootDir, opts) {
14148
14497
  ].join("\n");
14149
14498
  throw new Error(msg);
14150
14499
  }
14500
+ const runtimeClient = new RuntimeCatalogClient({ offline: offlineMode, manifest: packageManifest });
14501
+ const resolvedRuntimeCatalog = await runtimeClient.resolve();
14502
+ const runtimeCatalog = resolvedRuntimeCatalog.catalog;
14151
14503
  const treeCountPromise = quickTreeCount(rootDir, excludePatterns);
14152
14504
  progress.startStep("discovery");
14153
14505
  const treeCount = await treeCountPromise;
@@ -14173,7 +14525,7 @@ async function runScan(rootDir, opts) {
14173
14525
  progress.updateStats({ treeSummary: indexedTreeCount });
14174
14526
  }
14175
14527
  progress.completeStep("walk", `${treeCount.totalFiles.toLocaleString()} files indexed`);
14176
- const nodeProjects = await scanNodeProjects(rootDir, npmCache, fileCache, projectScanTimeoutMs);
14528
+ const nodeProjects = await scanNodeProjects(rootDir, npmCache, fileCache, projectScanTimeoutMs, runtimeCatalog);
14177
14529
  if (nodeProjects.length > 0) {
14178
14530
  progress.insertStepBefore("drift", { id: "node", label: "Found Node projects", weight: 4 });
14179
14531
  progress.startStep("node");
@@ -14185,7 +14537,7 @@ async function runScan(rootDir, opts) {
14185
14537
  progress.addProjects(nodeProjects.length);
14186
14538
  progress.completeStep("node", `${nodeProjects.length} project${nodeProjects.length !== 1 ? "s" : ""}`, nodeProjects.length);
14187
14539
  }
14188
- const dotnetProjects = await scanDotnetProjects(rootDir, nugetCache, fileCache, projectScanTimeoutMs);
14540
+ const dotnetProjects = await scanDotnetProjects(rootDir, nugetCache, fileCache, projectScanTimeoutMs, runtimeCatalog);
14189
14541
  if (dotnetProjects.length > 0) {
14190
14542
  progress.insertStepBefore("drift", { id: "dotnet", label: "Found .NET projects", weight: 2 });
14191
14543
  progress.startStep("dotnet");
@@ -14197,7 +14549,7 @@ async function runScan(rootDir, opts) {
14197
14549
  progress.addProjects(dotnetProjects.length);
14198
14550
  progress.completeStep("dotnet", `${dotnetProjects.length} project${dotnetProjects.length !== 1 ? "s" : ""}`, dotnetProjects.length);
14199
14551
  }
14200
- const pythonProjects = await scanPythonProjects(rootDir, pypiCache, fileCache, projectScanTimeoutMs);
14552
+ const pythonProjects = await scanPythonProjects(rootDir, pypiCache, fileCache, projectScanTimeoutMs, runtimeCatalog);
14201
14553
  if (pythonProjects.length > 0) {
14202
14554
  progress.insertStepBefore("drift", { id: "python", label: "Found Python projects", weight: 3 });
14203
14555
  progress.startStep("python");
@@ -14209,7 +14561,7 @@ async function runScan(rootDir, opts) {
14209
14561
  progress.addProjects(pythonProjects.length);
14210
14562
  progress.completeStep("python", `${pythonProjects.length} project${pythonProjects.length !== 1 ? "s" : ""}`, pythonProjects.length);
14211
14563
  }
14212
- const javaProjects = await scanJavaProjects(rootDir, mavenCache, fileCache, projectScanTimeoutMs);
14564
+ const javaProjects = await scanJavaProjects(rootDir, mavenCache, fileCache, projectScanTimeoutMs, runtimeCatalog);
14213
14565
  if (javaProjects.length > 0) {
14214
14566
  progress.insertStepBefore("drift", { id: "java", label: "Found Java projects", weight: 3 });
14215
14567
  progress.startStep("java");
@@ -14221,7 +14573,7 @@ async function runScan(rootDir, opts) {
14221
14573
  progress.addProjects(javaProjects.length);
14222
14574
  progress.completeStep("java", `${javaProjects.length} project${javaProjects.length !== 1 ? "s" : ""}`, javaProjects.length);
14223
14575
  }
14224
- const rubyProjects = await scanRubyProjects(rootDir, rubygemsCache, fileCache, projectScanTimeoutMs);
14576
+ const rubyProjects = await scanRubyProjects(rootDir, rubygemsCache, fileCache, projectScanTimeoutMs, runtimeCatalog);
14225
14577
  if (rubyProjects.length > 0) {
14226
14578
  progress.insertStepBefore("drift", { id: "ruby", label: "Found Ruby projects", weight: 2 });
14227
14579
  progress.startStep("ruby");
@@ -14245,7 +14597,7 @@ async function runScan(rootDir, opts) {
14245
14597
  progress.addProjects(swiftProjects.length);
14246
14598
  progress.completeStep("swift", `${swiftProjects.length} project${swiftProjects.length !== 1 ? "s" : ""}`, swiftProjects.length);
14247
14599
  }
14248
- const goProjects = await scanGoProjects(rootDir, goCache, fileCache, projectScanTimeoutMs);
14600
+ const goProjects = await scanGoProjects(rootDir, goCache, fileCache, projectScanTimeoutMs, runtimeCatalog);
14249
14601
  if (goProjects.length > 0) {
14250
14602
  progress.insertStepBefore("drift", { id: "go", label: "Found Go projects", weight: 2 });
14251
14603
  progress.startStep("go");
@@ -14384,7 +14736,7 @@ async function runScan(rootDir, opts) {
14384
14736
  for (const project of allProjects) {
14385
14737
  project.drift = computeDriftScore([project]);
14386
14738
  project.projectId = computeProjectId(project.path, project.name, workspaceId);
14387
- const absProjectDir = path33.resolve(rootDir, project.path);
14739
+ const absProjectDir = path34.resolve(rootDir, project.path);
14388
14740
  try {
14389
14741
  const source = await fileCache.sourceMetricsUnder(rootDir, absProjectDir);
14390
14742
  if (project.fileCount === void 0) project.fileCount = source.fileCount;
@@ -14398,7 +14750,7 @@ async function runScan(rootDir, opts) {
14398
14750
  dependencyCount: project.dependencyCount
14399
14751
  });
14400
14752
  }
14401
- const solutionsManifestPath = path33.join(rootDir, ".vibgrate", "solutions.json");
14753
+ const solutionsManifestPath = path34.join(rootDir, ".vibgrate", "solutions.json");
14402
14754
  const persistedSolutionIds = /* @__PURE__ */ new Map();
14403
14755
  if (await pathExists(solutionsManifestPath)) {
14404
14756
  try {
@@ -14420,7 +14772,7 @@ async function runScan(rootDir, opts) {
14420
14772
  const projectsByPath = new Map(allProjects.map((project) => [project.path, project]));
14421
14773
  for (const solution of solutions) {
14422
14774
  const includedProjects = solution.projectPaths.map((projectPath) => {
14423
- return projectsByPath.get(projectPath) ?? projectsByPath.get(path33.dirname(projectPath).replace(/\\/g, "/"));
14775
+ return projectsByPath.get(projectPath) ?? projectsByPath.get(path34.dirname(projectPath).replace(/\\/g, "/"));
14424
14776
  }).filter((project) => Boolean(project));
14425
14777
  solution.drift = includedProjects.length > 0 ? computeDriftScore(includedProjects) : void 0;
14426
14778
  for (const project of includedProjects) {
@@ -14635,7 +14987,7 @@ async function runScan(rootDir, opts) {
14635
14987
  const summary = [`${up.topEvidence.length} evidence`, ...up.capped ? ["capped"] : []].join(" \xB7 ");
14636
14988
  progress.completeStep("uipurpose", summary, up.topEvidence.length);
14637
14989
  await Promise.all(allProjects.map(async (project) => {
14638
- const projectDir = path33.join(rootDir, project.path);
14990
+ const projectDir = path34.join(rootDir, project.path);
14639
14991
  const projectResult = await scanUiPurpose(projectDir, fileCache, 150);
14640
14992
  if (projectResult.topEvidence.length > 0) {
14641
14993
  project.uiPurpose = compactUiPurpose(projectResult);
@@ -14643,7 +14995,7 @@ async function runScan(rootDir, opts) {
14643
14995
  }));
14644
14996
  }
14645
14997
  await Promise.all(allProjects.map(async (project) => {
14646
- const projectAbsPath = path33.join(rootDir, project.path);
14998
+ const projectAbsPath = path34.join(rootDir, project.path);
14647
14999
  const favicon = await findProjectFavicon(projectAbsPath);
14648
15000
  if (favicon) project.faviconBase64 = favicon;
14649
15001
  }));
@@ -14668,7 +15020,7 @@ async function runScan(rootDir, opts) {
14668
15020
  project.architecture = await scanProjectArchitecture(rootDir, project, fileCache);
14669
15021
  }));
14670
15022
  for (const solution of solutions) {
14671
- const memberProjects = solution.projectPaths.map((pp) => projectsByPath.get(pp) ?? projectsByPath.get(path33.dirname(pp).replace(/\\/g, "/"))).filter((p) => Boolean(p));
15023
+ const memberProjects = solution.projectPaths.map((pp) => projectsByPath.get(pp) ?? projectsByPath.get(path34.dirname(pp).replace(/\\/g, "/"))).filter((p) => Boolean(p));
14672
15024
  const memberArchResults = memberProjects.map((p) => p.architecture).filter((a) => Boolean(a));
14673
15025
  if (memberArchResults.length > 0) {
14674
15026
  solution.architecture = aggregateSolutionArchitecture(memberArchResults);
@@ -14681,6 +15033,13 @@ async function runScan(rootDir, opts) {
14681
15033
  );
14682
15034
  }
14683
15035
  }
15036
+ const catalogAgeDays = (Date.now() - Date.parse(runtimeCatalog.generatedAt)) / (1e3 * 60 * 60 * 24);
15037
+ const runtimeCatalogStale = Number.isFinite(catalogAgeDays) && catalogAgeDays > 30;
15038
+ extended.runtimeCatalogInfo = {
15039
+ generatedAt: runtimeCatalog.generatedAt,
15040
+ source: resolvedRuntimeCatalog.source,
15041
+ stale: runtimeCatalogStale
15042
+ };
14684
15043
  progress.startStep("drift");
14685
15044
  const drift = computeDriftScore(allProjects);
14686
15045
  progress.completeStep("drift", `${drift.score}/100 \u2014 ${drift.riskLevel} risk`);
@@ -14752,7 +15111,7 @@ async function runScan(rootDir, opts) {
14752
15111
  schemaVersion: "1.0",
14753
15112
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
14754
15113
  vibgrateVersion,
14755
- rootPath: path33.basename(rootDir),
15114
+ rootPath: path34.basename(rootDir),
14756
15115
  ...vcs.type !== "unknown" ? { vcs } : {},
14757
15116
  repository,
14758
15117
  projects: allProjects,
@@ -14767,7 +15126,7 @@ async function runScan(rootDir, opts) {
14767
15126
  billing: summarizeBilling(allProjects)
14768
15127
  };
14769
15128
  if (opts.baseline) {
14770
- const baselinePath = path33.resolve(opts.baseline);
15129
+ const baselinePath = path34.resolve(opts.baseline);
14771
15130
  if (await pathExists(baselinePath)) {
14772
15131
  try {
14773
15132
  const baseline = await readJsonFile(baselinePath);
@@ -14779,10 +15138,10 @@ async function runScan(rootDir, opts) {
14779
15138
  }
14780
15139
  }
14781
15140
  if (!opts.noLocalArtifacts && !maxPrivacyMode) {
14782
- const vibgrateDir = path33.join(rootDir, ".vibgrate");
15141
+ const vibgrateDir = path34.join(rootDir, ".vibgrate");
14783
15142
  await ensureDir(vibgrateDir);
14784
- await writeJsonFile(path33.join(vibgrateDir, "scan_result.json"), artifact);
14785
- await writeJsonFile(path33.join(vibgrateDir, "solutions.json"), {
15143
+ await writeJsonFile(path34.join(vibgrateDir, "scan_result.json"), artifact);
15144
+ await writeJsonFile(path34.join(vibgrateDir, "solutions.json"), {
14786
15145
  scannedAt: artifact.timestamp,
14787
15146
  solutions: solutions.map((solution) => ({
14788
15147
  solutionId: solution.solutionId,
@@ -14821,15 +15180,15 @@ async function runScan(rootDir, opts) {
14821
15180
  }
14822
15181
  }
14823
15182
  if (Object.keys(projectScores).length > 0) {
14824
- const vibgrateDir = path33.join(rootDir, ".vibgrate");
15183
+ const vibgrateDir = path34.join(rootDir, ".vibgrate");
14825
15184
  await ensureDir(vibgrateDir);
14826
- await writeJsonFile(path33.join(vibgrateDir, "project_scores.json"), projectScores);
15185
+ await writeJsonFile(path34.join(vibgrateDir, "project_scores.json"), projectScores);
14827
15186
  }
14828
15187
  }
14829
15188
  if (opts.format === "json") {
14830
15189
  const jsonStr = JSON.stringify(artifact, null, 2);
14831
15190
  if (opts.out) {
14832
- await writeTextFile(path33.resolve(opts.out), jsonStr);
15191
+ await writeTextFile(path34.resolve(opts.out), jsonStr);
14833
15192
  console.log(chalk3.green("\u2714") + ` JSON written to ${opts.out}`);
14834
15193
  } else {
14835
15194
  console.log(jsonStr);
@@ -14838,7 +15197,7 @@ async function runScan(rootDir, opts) {
14838
15197
  const sarif = formatSarif(artifact);
14839
15198
  const sarifStr = JSON.stringify(sarif, null, 2);
14840
15199
  if (opts.out) {
14841
- await writeTextFile(path33.resolve(opts.out), sarifStr);
15200
+ await writeTextFile(path34.resolve(opts.out), sarifStr);
14842
15201
  console.log(chalk3.green("\u2714") + ` SARIF written to ${opts.out}`);
14843
15202
  } else {
14844
15203
  console.log(sarifStr);
@@ -14847,13 +15206,13 @@ async function runScan(rootDir, opts) {
14847
15206
  const markdown = formatMarkdown(artifact);
14848
15207
  console.log(markdown);
14849
15208
  if (opts.out) {
14850
- await writeTextFile(path33.resolve(opts.out), markdown);
15209
+ await writeTextFile(path34.resolve(opts.out), markdown);
14851
15210
  }
14852
15211
  } else {
14853
15212
  const text = formatText(artifact);
14854
15213
  console.log(text);
14855
15214
  if (opts.out) {
14856
- await writeTextFile(path33.resolve(opts.out), text);
15215
+ await writeTextFile(path34.resolve(opts.out), text);
14857
15216
  }
14858
15217
  }
14859
15218
  return artifact;
@@ -14861,7 +15220,7 @@ async function runScan(rootDir, opts) {
14861
15220
  async function buildRepositoryInfo(rootDir, remoteUrl, ciSystems) {
14862
15221
  const name = await resolveRepositoryName(rootDir);
14863
15222
  let version;
14864
- const packageJsonPath = path33.join(rootDir, "package.json");
15223
+ const packageJsonPath = path34.join(rootDir, "package.json");
14865
15224
  if (await pathExists(packageJsonPath)) {
14866
15225
  try {
14867
15226
  const packageJson = await readJsonFile(packageJsonPath);