dep-oracle 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +33 -8
  2. package/dist/action/index.js +398 -16
  3. package/dist/badge-5Z3WAD2B.js +89 -0
  4. package/dist/badge-5Z3WAD2B.js.map +1 -0
  5. package/dist/chunk-32B3QIPY.js +1505 -0
  6. package/dist/chunk-32B3QIPY.js.map +1 -0
  7. package/dist/chunk-7DST6SNA.js +258 -0
  8. package/dist/chunk-7DST6SNA.js.map +1 -0
  9. package/dist/{chunk-TXSNFX3N.js → chunk-DLWG22RC.js} +403 -17
  10. package/dist/chunk-DLWG22RC.js.map +1 -0
  11. package/dist/chunk-HX6MGNBD.js +271 -0
  12. package/dist/chunk-HX6MGNBD.js.map +1 -0
  13. package/dist/chunk-IVXGOPRU.js +145 -0
  14. package/dist/chunk-IVXGOPRU.js.map +1 -0
  15. package/dist/chunk-SP3VYPXX.js +218 -0
  16. package/dist/chunk-SP3VYPXX.js.map +1 -0
  17. package/dist/chunk-T5EVLWZM.js +4234 -0
  18. package/dist/chunk-T5EVLWZM.js.map +1 -0
  19. package/dist/chunk-UMB5MJHL.js +239 -0
  20. package/dist/chunk-UMB5MJHL.js.map +1 -0
  21. package/dist/cli/index.js +163 -6499
  22. package/dist/cli/index.js.map +1 -1
  23. package/dist/index.js +9 -84
  24. package/dist/index.js.map +1 -1
  25. package/dist/mcp/server.js +33 -12
  26. package/dist/mcp/server.js.map +1 -1
  27. package/dist/npm-UB54H37N.js +9 -0
  28. package/dist/npm-UB54H37N.js.map +1 -0
  29. package/dist/orchestrator-VOOYKDPT.js +8 -0
  30. package/dist/orchestrator-VOOYKDPT.js.map +1 -0
  31. package/dist/python-U4G2GK4J.js +9 -0
  32. package/dist/python-U4G2GK4J.js.map +1 -0
  33. package/dist/server-WONIBSG4.js +640 -0
  34. package/dist/server-WONIBSG4.js.map +1 -0
  35. package/dist/store-Z5UANEBB.js +8 -0
  36. package/dist/store-Z5UANEBB.js.map +1 -0
  37. package/dist/trust-score-YXYDFVPZ.js +8 -0
  38. package/dist/trust-score-YXYDFVPZ.js.map +1 -0
  39. package/package.json +1 -1
  40. package/server.json +2 -2
  41. package/dist/chunk-TXSNFX3N.js.map +0 -1
@@ -940,6 +940,87 @@ function createLogger(label) {
940
940
  };
941
941
  }
942
942
 
943
+ // src/utils/rate-limiter.ts
944
+ var RateLimiter = class {
945
+ tokens;
946
+ maxTokens;
947
+ refillIntervalMs;
948
+ lastRefill;
949
+ waitQueue = [];
950
+ /**
951
+ * @param maxRequests Maximum number of requests allowed in the window
952
+ * @param windowMs Window duration in milliseconds
953
+ */
954
+ constructor(maxRequests, windowMs) {
955
+ this.maxTokens = maxRequests;
956
+ this.tokens = maxRequests;
957
+ this.refillIntervalMs = windowMs;
958
+ this.lastRefill = Date.now();
959
+ }
960
+ /**
961
+ * Acquire a token. Resolves immediately when tokens are available,
962
+ * otherwise waits until the bucket is refilled.
963
+ */
964
+ async acquire() {
965
+ this.refill();
966
+ if (this.tokens > 0) {
967
+ this.tokens--;
968
+ return;
969
+ }
970
+ return new Promise((resolve) => {
971
+ this.waitQueue.push(resolve);
972
+ this.scheduleRefill();
973
+ });
974
+ }
975
+ /**
976
+ * Return the number of tokens currently available (without waiting).
977
+ */
978
+ get remaining() {
979
+ this.refill();
980
+ return this.tokens;
981
+ }
982
+ /**
983
+ * Return the number of milliseconds until the next refill.
984
+ */
985
+ get msUntilRefill() {
986
+ const elapsed = Date.now() - this.lastRefill;
987
+ return Math.max(0, this.refillIntervalMs - elapsed);
988
+ }
989
+ // -----------------------------------------------------------------------
990
+ // Internal
991
+ // -----------------------------------------------------------------------
992
+ refill() {
993
+ const now = Date.now();
994
+ const elapsed = now - this.lastRefill;
995
+ if (elapsed >= this.refillIntervalMs) {
996
+ const periods = Math.floor(elapsed / this.refillIntervalMs);
997
+ this.tokens = Math.min(this.maxTokens, this.tokens + periods * this.maxTokens);
998
+ this.lastRefill = now - elapsed % this.refillIntervalMs;
999
+ this.drainWaitQueue();
1000
+ }
1001
+ }
1002
+ scheduleRefill() {
1003
+ const delay = this.msUntilRefill;
1004
+ if (delay <= 0) {
1005
+ this.refill();
1006
+ return;
1007
+ }
1008
+ setTimeout(() => {
1009
+ this.refill();
1010
+ }, delay);
1011
+ }
1012
+ drainWaitQueue() {
1013
+ while (this.waitQueue.length > 0 && this.tokens > 0) {
1014
+ this.tokens--;
1015
+ const resolve = this.waitQueue.shift();
1016
+ resolve?.();
1017
+ }
1018
+ }
1019
+ };
1020
+ var githubRateLimiter = new RateLimiter(5e3, 36e5);
1021
+ var npmRateLimiter = new RateLimiter(300, 6e4);
1022
+ var pypiRateLimiter = new RateLimiter(100, 6e4);
1023
+
943
1024
  // src/collectors/orchestrator.ts
944
1025
  import pLimit from "p-limit";
945
1026
 
@@ -1055,6 +1136,7 @@ var RegistryCollector = class extends BaseCollector {
1055
1136
  async fetchPackument(packageName) {
1056
1137
  const url = `https://registry.npmjs.org/${encodeURIComponent(packageName)}`;
1057
1138
  logger.debug(`Fetching packument: ${url}`);
1139
+ await npmRateLimiter.acquire();
1058
1140
  const res = await fetch(url, {
1059
1141
  headers: { Accept: "application/json" }
1060
1142
  });
@@ -1067,6 +1149,7 @@ var RegistryCollector = class extends BaseCollector {
1067
1149
  const url = `https://api.npmjs.org/downloads/point/last-week/${encodeURIComponent(packageName)}`;
1068
1150
  logger.debug(`Fetching weekly downloads: ${url}`);
1069
1151
  try {
1152
+ await npmRateLimiter.acquire();
1070
1153
  const res = await fetch(url, {
1071
1154
  headers: { Accept: "application/json" }
1072
1155
  });
@@ -1094,6 +1177,166 @@ var RegistryCollector = class extends BaseCollector {
1094
1177
  }
1095
1178
  };
1096
1179
 
1180
+ // src/collectors/pypi-registry.ts
1181
+ var PyPIRegistryCollector = class extends BaseCollector {
1182
+ name = "pypi-registry";
1183
+ constructor(cache) {
1184
+ super(cache);
1185
+ }
1186
+ async collect(packageName, version) {
1187
+ const cached = await this.getCached(packageName, version);
1188
+ if (cached) return cached;
1189
+ try {
1190
+ const [metadataResult, downloadsResult] = await Promise.allSettled([
1191
+ this.fetchMetadata(packageName),
1192
+ this.fetchWeeklyDownloads(packageName)
1193
+ ]);
1194
+ const metadata = metadataResult.status === "fulfilled" ? metadataResult.value : null;
1195
+ const downloads = downloadsResult.status === "fulfilled" ? downloadsResult.value : 0;
1196
+ if (!metadata) {
1197
+ throw new Error(`PyPI registry returned no data for ${packageName}`);
1198
+ }
1199
+ const versionCount = metadata.releases ? Object.keys(metadata.releases).length : 0;
1200
+ const lastPublishDate = this.findLastPublishDate(metadata.releases);
1201
+ const deprecated = this.checkYanked(metadata.releases, version);
1202
+ const data = {
1203
+ packageName,
1204
+ version: version === "latest" ? metadata.info.version : version,
1205
+ description: metadata.info.summary ?? null,
1206
+ lastPublishDate,
1207
+ versionCount,
1208
+ deprecated,
1209
+ weeklyDownloads: downloads,
1210
+ license: metadata.info.license ?? null,
1211
+ repositoryUrl: this.extractRepoUrl(metadata.info)
1212
+ };
1213
+ await this.setCache(packageName, version, data);
1214
+ return {
1215
+ status: "success",
1216
+ data,
1217
+ collectedAt: (/* @__PURE__ */ new Date()).toISOString()
1218
+ };
1219
+ } catch (err) {
1220
+ const message = err instanceof Error ? err.message : String(err);
1221
+ logger.error(
1222
+ `PyPIRegistryCollector failed for ${packageName}@${version}: ${message}`
1223
+ );
1224
+ return {
1225
+ status: "error",
1226
+ data: null,
1227
+ error: message,
1228
+ collectedAt: (/* @__PURE__ */ new Date()).toISOString()
1229
+ };
1230
+ }
1231
+ }
1232
+ // ---------------------------------------------------------------------------
1233
+ // Private helpers
1234
+ // ---------------------------------------------------------------------------
1235
+ async fetchMetadata(packageName) {
1236
+ await pypiRateLimiter.acquire();
1237
+ const url = `https://pypi.org/pypi/${encodeURIComponent(packageName)}/json`;
1238
+ logger.debug(`Fetching PyPI metadata: ${url}`);
1239
+ const res = await fetch(url, {
1240
+ headers: { Accept: "application/json" }
1241
+ });
1242
+ if (!res.ok) {
1243
+ if (res.status === 404) return null;
1244
+ throw new Error(`PyPI registry returned ${res.status} for ${packageName}`);
1245
+ }
1246
+ return await res.json();
1247
+ }
1248
+ async fetchWeeklyDownloads(packageName) {
1249
+ await pypiRateLimiter.acquire();
1250
+ const url = `https://pypistats.org/api/packages/${encodeURIComponent(packageName)}/recent`;
1251
+ logger.debug(`Fetching PyPI weekly downloads: ${url}`);
1252
+ try {
1253
+ const res = await fetch(url, {
1254
+ headers: { Accept: "application/json" }
1255
+ });
1256
+ if (!res.ok) {
1257
+ logger.warn(
1258
+ `PyPI stats API returned ${res.status} for ${packageName}`
1259
+ );
1260
+ return 0;
1261
+ }
1262
+ const body = await res.json();
1263
+ return body.data?.last_week ?? 0;
1264
+ } catch {
1265
+ logger.warn(`Could not fetch PyPI download stats for ${packageName}`);
1266
+ return 0;
1267
+ }
1268
+ }
1269
+ /**
1270
+ * Find the most recent upload date across all releases.
1271
+ */
1272
+ findLastPublishDate(releases) {
1273
+ if (!releases) return null;
1274
+ const dates = [];
1275
+ for (const files of Object.values(releases)) {
1276
+ for (const file of files) {
1277
+ const dateStr = file.upload_time_iso_8601 ?? file.upload_time;
1278
+ if (dateStr) {
1279
+ const ts = new Date(dateStr).getTime();
1280
+ if (!isNaN(ts)) dates.push(ts);
1281
+ }
1282
+ }
1283
+ }
1284
+ if (dates.length === 0) return null;
1285
+ dates.sort((a, b) => b - a);
1286
+ return new Date(dates[0]).toISOString();
1287
+ }
1288
+ /**
1289
+ * Check if the requested version is yanked (PyPI's deprecation mechanism).
1290
+ * Returns the yank reason string, or null if not yanked.
1291
+ */
1292
+ checkYanked(releases, version) {
1293
+ if (!releases || version === "latest") return null;
1294
+ const files = releases[version];
1295
+ if (!files || files.length === 0) return null;
1296
+ const yankedFile = files.find((f) => f.yanked);
1297
+ if (yankedFile) {
1298
+ return yankedFile.yanked_reason || "This version has been yanked";
1299
+ }
1300
+ return null;
1301
+ }
1302
+ /**
1303
+ * Extract a normalised repository URL from PyPI project_urls or home_page.
1304
+ */
1305
+ extractRepoUrl(info) {
1306
+ const projectUrls = info.project_urls ?? {};
1307
+ const repoKeys = [
1308
+ "Source",
1309
+ "Source Code",
1310
+ "Repository",
1311
+ "GitHub",
1312
+ "Code",
1313
+ "Homepage",
1314
+ "source",
1315
+ "source_code",
1316
+ "repository",
1317
+ "github",
1318
+ "code",
1319
+ "homepage"
1320
+ ];
1321
+ for (const key of repoKeys) {
1322
+ const url = projectUrls[key];
1323
+ if (url && (url.includes("github.com") || url.includes("gitlab.com") || url.includes("bitbucket.org"))) {
1324
+ return url.replace(/\.git$/, "");
1325
+ }
1326
+ }
1327
+ for (const url of Object.values(projectUrls)) {
1328
+ if (url && (url.includes("github.com") || url.includes("gitlab.com") || url.includes("bitbucket.org"))) {
1329
+ return url.replace(/\.git$/, "");
1330
+ }
1331
+ }
1332
+ const homePage = info.home_page;
1333
+ if (homePage && (homePage.includes("github.com") || homePage.includes("gitlab.com"))) {
1334
+ return homePage.replace(/\.git$/, "");
1335
+ }
1336
+ return null;
1337
+ }
1338
+ };
1339
+
1097
1340
  // src/collectors/github.ts
1098
1341
  var GitHubCollector = class extends BaseCollector {
1099
1342
  name = "github";
@@ -1165,6 +1408,7 @@ var GitHubCollector = class extends BaseCollector {
1165
1408
  async resolveRepoSlug(packageName) {
1166
1409
  try {
1167
1410
  const url = `https://registry.npmjs.org/${encodeURIComponent(packageName)}`;
1411
+ await npmRateLimiter.acquire();
1168
1412
  const res = await fetch(url, {
1169
1413
  headers: { Accept: "application/json" }
1170
1414
  });
@@ -1207,6 +1451,7 @@ var GitHubCollector = class extends BaseCollector {
1207
1451
  async fetchRepoInfo(owner, repo) {
1208
1452
  const url = `https://api.github.com/repos/${owner}/${repo}`;
1209
1453
  logger.debug(`GitHub: fetching repo info ${url}`);
1454
+ await githubRateLimiter.acquire();
1210
1455
  const res = await fetch(url, { headers: this.headers() });
1211
1456
  if (!res.ok) {
1212
1457
  throw new Error(`GitHub API ${res.status} for ${url}`);
@@ -1221,6 +1466,7 @@ var GitHubCollector = class extends BaseCollector {
1221
1466
  const url = `https://api.github.com/repos/${owner}/${repo}/contributors?per_page=1&anon=true`;
1222
1467
  logger.debug(`GitHub: fetching contributor count ${url}`);
1223
1468
  try {
1469
+ await githubRateLimiter.acquire();
1224
1470
  const res = await fetch(url, { headers: this.headers() });
1225
1471
  if (!res.ok) return 0;
1226
1472
  const count = this.extractLastPage(res.headers.get("link"));
@@ -1241,6 +1487,7 @@ var GitHubCollector = class extends BaseCollector {
1241
1487
  const url = `https://api.github.com/repos/${owner}/${repo}/commits?since=${since}&per_page=1`;
1242
1488
  logger.debug(`GitHub: fetching recent commit count ${url}`);
1243
1489
  try {
1490
+ await githubRateLimiter.acquire();
1244
1491
  const res = await fetch(url, { headers: this.headers() });
1245
1492
  if (!res.ok) return 0;
1246
1493
  const count = this.extractLastPage(res.headers.get("link"));
@@ -1256,6 +1503,7 @@ var GitHubCollector = class extends BaseCollector {
1256
1503
  const url = `https://api.github.com/repos/${owner}/${repo}/commits?per_page=1`;
1257
1504
  logger.debug(`GitHub: fetching latest commit ${url}`);
1258
1505
  try {
1506
+ await githubRateLimiter.acquire();
1259
1507
  const res = await fetch(url, { headers: this.headers() });
1260
1508
  if (!res.ok) return null;
1261
1509
  const body = await res.json();
@@ -1272,6 +1520,7 @@ var GitHubCollector = class extends BaseCollector {
1272
1520
  const url = `https://api.github.com/repos/${owner}/${repo}/contents/.github/FUNDING.yml`;
1273
1521
  logger.debug(`GitHub: checking FUNDING.yml ${url}`);
1274
1522
  try {
1523
+ await githubRateLimiter.acquire();
1275
1524
  const res = await fetch(url, { headers: this.headers() });
1276
1525
  return res.ok;
1277
1526
  } catch {
@@ -1303,11 +1552,11 @@ var SecurityCollector = class extends BaseCollector {
1303
1552
  constructor(cache) {
1304
1553
  super(cache);
1305
1554
  }
1306
- async collect(packageName, version) {
1555
+ async collect(packageName, version, ecosystem) {
1307
1556
  const cached = await this.getCached(packageName, version);
1308
1557
  if (cached) return cached;
1309
1558
  try {
1310
- const vulns = await this.queryOsv(packageName);
1559
+ const vulns = await this.queryOsv(packageName, ecosystem);
1311
1560
  const totalVulnerabilities = vulns.length;
1312
1561
  const severityCounts = {
1313
1562
  critical: 0,
@@ -1357,16 +1606,16 @@ var SecurityCollector = class extends BaseCollector {
1357
1606
  // ---------------------------------------------------------------------------
1358
1607
  // OSV API
1359
1608
  // ---------------------------------------------------------------------------
1360
- async queryOsv(packageName) {
1609
+ async queryOsv(packageName, ecosystem = "npm") {
1361
1610
  const url = "https://api.osv.dev/v1/query";
1362
- logger.debug(`OSV: querying vulnerabilities for ${packageName}`);
1611
+ logger.debug(`OSV: querying vulnerabilities for ${packageName} (ecosystem=${ecosystem})`);
1363
1612
  const res = await fetch(url, {
1364
1613
  method: "POST",
1365
1614
  headers: { "Content-Type": "application/json" },
1366
1615
  body: JSON.stringify({
1367
1616
  package: {
1368
1617
  name: packageName,
1369
- ecosystem: "npm"
1618
+ ecosystem
1370
1619
  }
1371
1620
  })
1372
1621
  });
@@ -1530,6 +1779,7 @@ var FundingCollector = class extends BaseCollector {
1530
1779
  async fetchNpmFunding(packageName) {
1531
1780
  try {
1532
1781
  const url = `https://registry.npmjs.org/${encodeURIComponent(packageName)}`;
1782
+ await npmRateLimiter.acquire();
1533
1783
  const res = await fetch(url, {
1534
1784
  headers: { Accept: "application/json" }
1535
1785
  });
@@ -1564,6 +1814,7 @@ var FundingCollector = class extends BaseCollector {
1564
1814
  if (this.githubToken) {
1565
1815
  headers.Authorization = `Bearer ${this.githubToken}`;
1566
1816
  }
1817
+ await githubRateLimiter.acquire();
1567
1818
  const res = await fetch(url, { headers });
1568
1819
  if (!res.ok) return null;
1569
1820
  return await res.text();
@@ -1594,6 +1845,7 @@ var FundingCollector = class extends BaseCollector {
1594
1845
  async resolveRepoSlug(packageName) {
1595
1846
  try {
1596
1847
  const url = `https://registry.npmjs.org/${encodeURIComponent(packageName)}`;
1848
+ await npmRateLimiter.acquire();
1597
1849
  const res = await fetch(url, {
1598
1850
  headers: { Accept: "application/json" }
1599
1851
  });
@@ -1712,6 +1964,7 @@ var PopularityCollector = class extends BaseCollector {
1712
1964
  const url = `https://api.npmjs.org/downloads/point/${period}/${encodeURIComponent(packageName)}`;
1713
1965
  logger.debug(`Popularity: fetching ${period} downloads: ${url}`);
1714
1966
  try {
1967
+ await npmRateLimiter.acquire();
1715
1968
  const res = await fetch(url, {
1716
1969
  headers: { Accept: "application/json" }
1717
1970
  });
@@ -1739,6 +1992,7 @@ var PopularityCollector = class extends BaseCollector {
1739
1992
  const url = `https://registry.npmjs.org/-/v1/search?text=${encodeURIComponent(packageName)}&size=1`;
1740
1993
  logger.debug(`Popularity: fetching dependent count: ${url}`);
1741
1994
  try {
1995
+ await npmRateLimiter.acquire();
1742
1996
  const res = await fetch(url, {
1743
1997
  headers: { Accept: "application/json" }
1744
1998
  });
@@ -1764,6 +2018,7 @@ var PopularityCollector = class extends BaseCollector {
1764
2018
  async fetchDependentCountFromRegistry(packageName) {
1765
2019
  try {
1766
2020
  const url = `https://www.npmjs.com/package/${encodeURIComponent(packageName)}`;
2021
+ await npmRateLimiter.acquire();
1767
2022
  const res = await fetch(url, {
1768
2023
  headers: {
1769
2024
  Accept: "text/html",
@@ -1931,6 +2186,7 @@ var LicenseCollector = class extends BaseCollector {
1931
2186
  async fetchLicense(packageName, version) {
1932
2187
  const url = `https://registry.npmjs.org/${encodeURIComponent(packageName)}`;
1933
2188
  logger.debug(`License: fetching packument ${url}`);
2189
+ await npmRateLimiter.acquire();
1934
2190
  const res = await fetch(url, {
1935
2191
  headers: { Accept: "application/json" }
1936
2192
  });
@@ -1990,6 +2246,7 @@ var CollectorOrchestrator = class {
1990
2246
  cache;
1991
2247
  options;
1992
2248
  registryCollector;
2249
+ pypiRegistryCollector;
1993
2250
  githubCollector;
1994
2251
  securityCollector;
1995
2252
  fundingCollector;
@@ -2003,6 +2260,7 @@ var CollectorOrchestrator = class {
2003
2260
  concurrency: options.concurrency ?? 10
2004
2261
  };
2005
2262
  this.registryCollector = new RegistryCollector(this.cache);
2263
+ this.pypiRegistryCollector = new PyPIRegistryCollector(this.cache);
2006
2264
  this.githubCollector = new GitHubCollector(this.cache, this.options.githubToken || void 0);
2007
2265
  this.securityCollector = new SecurityCollector(this.cache);
2008
2266
  this.fundingCollector = new FundingCollector(this.cache, this.options.githubToken || void 0);
@@ -2016,13 +2274,15 @@ var CollectorOrchestrator = class {
2016
2274
  * only the cache is consulted; if there is no cached entry the result gets
2017
2275
  * `status: 'offline'` with `data: null`.
2018
2276
  */
2019
- async collectAll(packageName, version) {
2277
+ async collectAll(packageName, version, ecosystem = "npm") {
2020
2278
  logger.info(
2021
- `Collecting data for ${packageName}@${version} (offline=${String(this.options.offline)})`
2279
+ `Collecting data for ${packageName}@${version} (ecosystem=${ecosystem}, offline=${String(this.options.offline)})`
2022
2280
  );
2023
2281
  const limit = pLimit(this.options.concurrency);
2282
+ const activeRegistryCollector = ecosystem === "pypi" ? this.pypiRegistryCollector : this.registryCollector;
2283
+ const osvEcosystem = ecosystem === "pypi" ? "PyPI" : "npm";
2024
2284
  const entries = [
2025
- { key: "registry", collector: this.registryCollector },
2285
+ { key: "registry", collector: activeRegistryCollector },
2026
2286
  { key: "github", collector: this.githubCollector },
2027
2287
  { key: "security", collector: this.securityCollector },
2028
2288
  { key: "funding", collector: this.fundingCollector },
@@ -2038,12 +2298,21 @@ var CollectorOrchestrator = class {
2038
2298
  result = await this.offlineCollect(collector, packageName, version);
2039
2299
  } else {
2040
2300
  try {
2041
- result = await Promise.race([
2042
- this.onlineCollect(collector, packageName, version),
2043
- new Promise(
2044
- (_, reject) => setTimeout(() => reject(new Error("Collector timeout")), COLLECTOR_TIMEOUT)
2045
- )
2046
- ]);
2301
+ if (key === "security") {
2302
+ result = await Promise.race([
2303
+ this.securityCollector.collect(packageName, version, osvEcosystem),
2304
+ new Promise(
2305
+ (_, reject) => setTimeout(() => reject(new Error("Collector timeout")), COLLECTOR_TIMEOUT)
2306
+ )
2307
+ ]);
2308
+ } else {
2309
+ result = await Promise.race([
2310
+ this.onlineCollect(collector, packageName, version),
2311
+ new Promise(
2312
+ (_, reject) => setTimeout(() => reject(new Error("Collector timeout")), COLLECTOR_TIMEOUT)
2313
+ )
2314
+ ]);
2315
+ }
2047
2316
  } catch {
2048
2317
  logger.warn(`[${collector.name}] ${packageName}@${version} => timeout (${COLLECTOR_TIMEOUT}ms)`);
2049
2318
  result = {
@@ -2168,7 +2437,7 @@ var TrustScoreEngine = class {
2168
2437
  return {
2169
2438
  trustScore: Math.round(trustScore),
2170
2439
  metrics,
2171
- insufficientData: unavailableMetrics.length >= 3,
2440
+ insufficientData: unavailableMetrics.length >= 2,
2172
2441
  unavailableMetrics
2173
2442
  };
2174
2443
  }
@@ -2310,6 +2579,11 @@ var TrustScoreEngine = class {
2310
2579
  const effectiveWeight = m.weight / totalAvailableWeight;
2311
2580
  score += m.score * effectiveWeight;
2312
2581
  }
2582
+ const missingWeightFraction = 1 - totalAvailableWeight;
2583
+ if (missingWeightFraction > 0) {
2584
+ const penalty = (score - 50) * missingWeightFraction;
2585
+ score = score - penalty;
2586
+ }
2313
2587
  return clamp(Math.round(score));
2314
2588
  }
2315
2589
  };
@@ -4764,7 +5038,115 @@ var POPULAR_PACKAGES = [
4764
5038
  "request",
4765
5039
  "tslint",
4766
5040
  "node-pre-gyp",
4767
- "npm-lifecycle"
5041
+ "npm-lifecycle",
5042
+ // ---------------------------------------------------------------------------
5043
+ // Popular Python packages (PyPI)
5044
+ // ---------------------------------------------------------------------------
5045
+ "requests",
5046
+ "flask",
5047
+ "django",
5048
+ "fastapi",
5049
+ "numpy",
5050
+ "pandas",
5051
+ "scipy",
5052
+ "matplotlib",
5053
+ "tensorflow",
5054
+ "torch",
5055
+ "scikit-learn",
5056
+ "keras",
5057
+ "pytorch-lightning",
5058
+ "xgboost",
5059
+ "lightgbm",
5060
+ "pytest",
5061
+ "black",
5062
+ "mypy",
5063
+ "ruff",
5064
+ "pylint",
5065
+ "flake8",
5066
+ "isort",
5067
+ "celery",
5068
+ "redis",
5069
+ "sqlalchemy",
5070
+ "alembic",
5071
+ "pydantic",
5072
+ "httpx",
5073
+ "aiohttp",
5074
+ "uvicorn",
5075
+ "gunicorn",
5076
+ "starlette",
5077
+ "boto3",
5078
+ "botocore",
5079
+ "awscli",
5080
+ "google-cloud-storage",
5081
+ "azure-storage-blob",
5082
+ "pillow",
5083
+ "opencv-python",
5084
+ "beautifulsoup4",
5085
+ "lxml",
5086
+ "scrapy",
5087
+ "cryptography",
5088
+ "paramiko",
5089
+ "fabric",
5090
+ "ansible",
5091
+ "click",
5092
+ "typer",
5093
+ "rich",
5094
+ "tqdm",
5095
+ "colorama",
5096
+ "tabulate",
5097
+ "setuptools",
5098
+ "wheel",
5099
+ "pip",
5100
+ "twine",
5101
+ "poetry",
5102
+ "pdm",
5103
+ "hatch",
5104
+ "jinja2",
5105
+ "mako",
5106
+ "markupsafe",
5107
+ "werkzeug",
5108
+ "psycopg2",
5109
+ "pymongo",
5110
+ "motor",
5111
+ "peewee",
5112
+ "tortoise-orm",
5113
+ "marshmallow",
5114
+ "attrs",
5115
+ "dataclasses-json",
5116
+ "sentry-sdk",
5117
+ "prometheus-client",
5118
+ "opentelemetry-api",
5119
+ "transformers",
5120
+ "huggingface-hub",
5121
+ "tokenizers",
5122
+ "datasets",
5123
+ "langchain",
5124
+ "openai",
5125
+ "anthropic",
5126
+ "tiktoken",
5127
+ "pytest-cov",
5128
+ "pytest-asyncio",
5129
+ "pytest-mock",
5130
+ "coverage",
5131
+ "pyyaml",
5132
+ "toml",
5133
+ "python-dotenv",
5134
+ "decouple",
5135
+ "arrow",
5136
+ "pendulum",
5137
+ "python-dateutil",
5138
+ "stripe",
5139
+ "twilio",
5140
+ "sendgrid",
5141
+ "selenium",
5142
+ "playwright",
5143
+ "httptools",
5144
+ "orjson",
5145
+ "ujson",
5146
+ "msgpack",
5147
+ "networkx",
5148
+ "sympy",
5149
+ "statsmodels"
4768
5150
  ];
4769
5151
  var TyposquatDetector = class _TyposquatDetector {
4770
5152
  popularPackages;
@@ -6576,6 +6958,10 @@ export {
6576
6958
  isDebug,
6577
6959
  logger,
6578
6960
  createLogger,
6961
+ RateLimiter,
6962
+ githubRateLimiter,
6963
+ npmRateLimiter,
6964
+ pypiRateLimiter,
6579
6965
  CollectorOrchestrator,
6580
6966
  TrustScoreEngine,
6581
6967
  ZombieDetector,
@@ -6586,4 +6972,4 @@ export {
6586
6972
  getBlastRadius,
6587
6973
  getImportingFiles
6588
6974
  };
6589
- //# sourceMappingURL=chunk-TXSNFX3N.js.map
6975
+ //# sourceMappingURL=chunk-DLWG22RC.js.map