dep-oracle 1.2.1 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -8
- package/dist/action/index.js +630 -16
- package/dist/badge-5Z3WAD2B.js +89 -0
- package/dist/badge-5Z3WAD2B.js.map +1 -0
- package/dist/chunk-32B3QIPY.js +1505 -0
- package/dist/chunk-32B3QIPY.js.map +1 -0
- package/dist/{chunk-TXSNFX3N.js → chunk-3SHHSWZU.js} +635 -17
- package/dist/chunk-3SHHSWZU.js.map +1 -0
- package/dist/chunk-7DST6SNA.js +258 -0
- package/dist/chunk-7DST6SNA.js.map +1 -0
- package/dist/{chunk-VHQCTVCZ.js → chunk-DBORVN6C.js} +361 -2338
- package/dist/chunk-DBORVN6C.js.map +1 -0
- package/dist/chunk-HX6MGNBD.js +271 -0
- package/dist/chunk-HX6MGNBD.js.map +1 -0
- package/dist/chunk-IVXGOPRU.js +145 -0
- package/dist/chunk-IVXGOPRU.js.map +1 -0
- package/dist/chunk-SP3VYPXX.js +218 -0
- package/dist/chunk-SP3VYPXX.js.map +1 -0
- package/dist/chunk-UMB5MJHL.js +239 -0
- package/dist/chunk-UMB5MJHL.js.map +1 -0
- package/dist/cli/index.js +414 -19
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +9 -84
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.js +33 -12
- package/dist/mcp/server.js.map +1 -1
- package/dist/npm-UB54H37N.js +9 -0
- package/dist/npm-UB54H37N.js.map +1 -0
- package/dist/orchestrator-VOOYKDPT.js +8 -0
- package/dist/orchestrator-VOOYKDPT.js.map +1 -0
- package/dist/python-U4G2GK4J.js +9 -0
- package/dist/python-U4G2GK4J.js.map +1 -0
- package/dist/{server-TKLM7YIF.js → server-MLFC2O2B.js} +50 -18
- package/dist/server-MLFC2O2B.js.map +1 -0
- package/dist/store-Z5UANEBB.js +8 -0
- package/dist/store-Z5UANEBB.js.map +1 -0
- package/dist/trust-score-YXYDFVPZ.js +8 -0
- package/dist/trust-score-YXYDFVPZ.js.map +1 -0
- package/package.json +1 -1
- package/server.json +2 -2
- package/dist/chunk-TXSNFX3N.js.map +0 -1
- package/dist/chunk-VHQCTVCZ.js.map +0 -1
- package/dist/server-TKLM7YIF.js.map +0 -1
package/dist/action/index.js
CHANGED
|
@@ -5608,6 +5608,87 @@ function createLogger(label) {
|
|
|
5608
5608
|
};
|
|
5609
5609
|
}
|
|
5610
5610
|
|
|
5611
|
+
// src/utils/rate-limiter.ts
|
|
5612
|
+
var RateLimiter = class {
|
|
5613
|
+
tokens;
|
|
5614
|
+
maxTokens;
|
|
5615
|
+
refillIntervalMs;
|
|
5616
|
+
lastRefill;
|
|
5617
|
+
waitQueue = [];
|
|
5618
|
+
/**
|
|
5619
|
+
* @param maxRequests Maximum number of requests allowed in the window
|
|
5620
|
+
* @param windowMs Window duration in milliseconds
|
|
5621
|
+
*/
|
|
5622
|
+
constructor(maxRequests, windowMs) {
|
|
5623
|
+
this.maxTokens = maxRequests;
|
|
5624
|
+
this.tokens = maxRequests;
|
|
5625
|
+
this.refillIntervalMs = windowMs;
|
|
5626
|
+
this.lastRefill = Date.now();
|
|
5627
|
+
}
|
|
5628
|
+
/**
|
|
5629
|
+
* Acquire a token. Resolves immediately when tokens are available,
|
|
5630
|
+
* otherwise waits until the bucket is refilled.
|
|
5631
|
+
*/
|
|
5632
|
+
async acquire() {
|
|
5633
|
+
this.refill();
|
|
5634
|
+
if (this.tokens > 0) {
|
|
5635
|
+
this.tokens--;
|
|
5636
|
+
return;
|
|
5637
|
+
}
|
|
5638
|
+
return new Promise((resolve2) => {
|
|
5639
|
+
this.waitQueue.push(resolve2);
|
|
5640
|
+
this.scheduleRefill();
|
|
5641
|
+
});
|
|
5642
|
+
}
|
|
5643
|
+
/**
|
|
5644
|
+
* Return the number of tokens currently available (without waiting).
|
|
5645
|
+
*/
|
|
5646
|
+
get remaining() {
|
|
5647
|
+
this.refill();
|
|
5648
|
+
return this.tokens;
|
|
5649
|
+
}
|
|
5650
|
+
/**
|
|
5651
|
+
* Return the number of milliseconds until the next refill.
|
|
5652
|
+
*/
|
|
5653
|
+
get msUntilRefill() {
|
|
5654
|
+
const elapsed = Date.now() - this.lastRefill;
|
|
5655
|
+
return Math.max(0, this.refillIntervalMs - elapsed);
|
|
5656
|
+
}
|
|
5657
|
+
// -----------------------------------------------------------------------
|
|
5658
|
+
// Internal
|
|
5659
|
+
// -----------------------------------------------------------------------
|
|
5660
|
+
refill() {
|
|
5661
|
+
const now = Date.now();
|
|
5662
|
+
const elapsed = now - this.lastRefill;
|
|
5663
|
+
if (elapsed >= this.refillIntervalMs) {
|
|
5664
|
+
const periods = Math.floor(elapsed / this.refillIntervalMs);
|
|
5665
|
+
this.tokens = Math.min(this.maxTokens, this.tokens + periods * this.maxTokens);
|
|
5666
|
+
this.lastRefill = now - elapsed % this.refillIntervalMs;
|
|
5667
|
+
this.drainWaitQueue();
|
|
5668
|
+
}
|
|
5669
|
+
}
|
|
5670
|
+
scheduleRefill() {
|
|
5671
|
+
const delay = this.msUntilRefill;
|
|
5672
|
+
if (delay <= 0) {
|
|
5673
|
+
this.refill();
|
|
5674
|
+
return;
|
|
5675
|
+
}
|
|
5676
|
+
setTimeout(() => {
|
|
5677
|
+
this.refill();
|
|
5678
|
+
}, delay);
|
|
5679
|
+
}
|
|
5680
|
+
drainWaitQueue() {
|
|
5681
|
+
while (this.waitQueue.length > 0 && this.tokens > 0) {
|
|
5682
|
+
this.tokens--;
|
|
5683
|
+
const resolve2 = this.waitQueue.shift();
|
|
5684
|
+
resolve2?.();
|
|
5685
|
+
}
|
|
5686
|
+
}
|
|
5687
|
+
};
|
|
5688
|
+
var githubRateLimiter = new RateLimiter(5e3, 36e5);
|
|
5689
|
+
var npmRateLimiter = new RateLimiter(300, 6e4);
|
|
5690
|
+
var pypiRateLimiter = new RateLimiter(100, 6e4);
|
|
5691
|
+
|
|
5611
5692
|
// src/collectors/base.ts
|
|
5612
5693
|
var BaseCollector = class {
|
|
5613
5694
|
cache;
|
|
@@ -5720,6 +5801,7 @@ var RegistryCollector = class extends BaseCollector {
|
|
|
5720
5801
|
async fetchPackument(packageName) {
|
|
5721
5802
|
const url = `https://registry.npmjs.org/${encodeURIComponent(packageName)}`;
|
|
5722
5803
|
logger.debug(`Fetching packument: ${url}`);
|
|
5804
|
+
await npmRateLimiter.acquire();
|
|
5723
5805
|
const res = await fetch(url, {
|
|
5724
5806
|
headers: { Accept: "application/json" }
|
|
5725
5807
|
});
|
|
@@ -5732,6 +5814,7 @@ var RegistryCollector = class extends BaseCollector {
|
|
|
5732
5814
|
const url = `https://api.npmjs.org/downloads/point/last-week/${encodeURIComponent(packageName)}`;
|
|
5733
5815
|
logger.debug(`Fetching weekly downloads: ${url}`);
|
|
5734
5816
|
try {
|
|
5817
|
+
await npmRateLimiter.acquire();
|
|
5735
5818
|
const res = await fetch(url, {
|
|
5736
5819
|
headers: { Accept: "application/json" }
|
|
5737
5820
|
});
|
|
@@ -5759,6 +5842,166 @@ var RegistryCollector = class extends BaseCollector {
|
|
|
5759
5842
|
}
|
|
5760
5843
|
};
|
|
5761
5844
|
|
|
5845
|
+
// src/collectors/pypi-registry.ts
|
|
5846
|
+
var PyPIRegistryCollector = class extends BaseCollector {
|
|
5847
|
+
name = "pypi-registry";
|
|
5848
|
+
constructor(cache2) {
|
|
5849
|
+
super(cache2);
|
|
5850
|
+
}
|
|
5851
|
+
async collect(packageName, version) {
|
|
5852
|
+
const cached = await this.getCached(packageName, version);
|
|
5853
|
+
if (cached) return cached;
|
|
5854
|
+
try {
|
|
5855
|
+
const [metadataResult, downloadsResult] = await Promise.allSettled([
|
|
5856
|
+
this.fetchMetadata(packageName),
|
|
5857
|
+
this.fetchWeeklyDownloads(packageName)
|
|
5858
|
+
]);
|
|
5859
|
+
const metadata = metadataResult.status === "fulfilled" ? metadataResult.value : null;
|
|
5860
|
+
const downloads = downloadsResult.status === "fulfilled" ? downloadsResult.value : 0;
|
|
5861
|
+
if (!metadata) {
|
|
5862
|
+
throw new Error(`PyPI registry returned no data for ${packageName}`);
|
|
5863
|
+
}
|
|
5864
|
+
const versionCount = metadata.releases ? Object.keys(metadata.releases).length : 0;
|
|
5865
|
+
const lastPublishDate = this.findLastPublishDate(metadata.releases);
|
|
5866
|
+
const deprecated = this.checkYanked(metadata.releases, version);
|
|
5867
|
+
const data = {
|
|
5868
|
+
packageName,
|
|
5869
|
+
version: version === "latest" ? metadata.info.version : version,
|
|
5870
|
+
description: metadata.info.summary ?? null,
|
|
5871
|
+
lastPublishDate,
|
|
5872
|
+
versionCount,
|
|
5873
|
+
deprecated,
|
|
5874
|
+
weeklyDownloads: downloads,
|
|
5875
|
+
license: metadata.info.license ?? null,
|
|
5876
|
+
repositoryUrl: this.extractRepoUrl(metadata.info)
|
|
5877
|
+
};
|
|
5878
|
+
await this.setCache(packageName, version, data);
|
|
5879
|
+
return {
|
|
5880
|
+
status: "success",
|
|
5881
|
+
data,
|
|
5882
|
+
collectedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
5883
|
+
};
|
|
5884
|
+
} catch (err) {
|
|
5885
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
5886
|
+
logger.error(
|
|
5887
|
+
`PyPIRegistryCollector failed for ${packageName}@${version}: ${message}`
|
|
5888
|
+
);
|
|
5889
|
+
return {
|
|
5890
|
+
status: "error",
|
|
5891
|
+
data: null,
|
|
5892
|
+
error: message,
|
|
5893
|
+
collectedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
5894
|
+
};
|
|
5895
|
+
}
|
|
5896
|
+
}
|
|
5897
|
+
// ---------------------------------------------------------------------------
|
|
5898
|
+
// Private helpers
|
|
5899
|
+
// ---------------------------------------------------------------------------
|
|
5900
|
+
async fetchMetadata(packageName) {
|
|
5901
|
+
await pypiRateLimiter.acquire();
|
|
5902
|
+
const url = `https://pypi.org/pypi/${encodeURIComponent(packageName)}/json`;
|
|
5903
|
+
logger.debug(`Fetching PyPI metadata: ${url}`);
|
|
5904
|
+
const res = await fetch(url, {
|
|
5905
|
+
headers: { Accept: "application/json" }
|
|
5906
|
+
});
|
|
5907
|
+
if (!res.ok) {
|
|
5908
|
+
if (res.status === 404) return null;
|
|
5909
|
+
throw new Error(`PyPI registry returned ${res.status} for ${packageName}`);
|
|
5910
|
+
}
|
|
5911
|
+
return await res.json();
|
|
5912
|
+
}
|
|
5913
|
+
async fetchWeeklyDownloads(packageName) {
|
|
5914
|
+
await pypiRateLimiter.acquire();
|
|
5915
|
+
const url = `https://pypistats.org/api/packages/${encodeURIComponent(packageName)}/recent`;
|
|
5916
|
+
logger.debug(`Fetching PyPI weekly downloads: ${url}`);
|
|
5917
|
+
try {
|
|
5918
|
+
const res = await fetch(url, {
|
|
5919
|
+
headers: { Accept: "application/json" }
|
|
5920
|
+
});
|
|
5921
|
+
if (!res.ok) {
|
|
5922
|
+
logger.warn(
|
|
5923
|
+
`PyPI stats API returned ${res.status} for ${packageName}`
|
|
5924
|
+
);
|
|
5925
|
+
return 0;
|
|
5926
|
+
}
|
|
5927
|
+
const body = await res.json();
|
|
5928
|
+
return body.data?.last_week ?? 0;
|
|
5929
|
+
} catch {
|
|
5930
|
+
logger.warn(`Could not fetch PyPI download stats for ${packageName}`);
|
|
5931
|
+
return 0;
|
|
5932
|
+
}
|
|
5933
|
+
}
|
|
5934
|
+
/**
|
|
5935
|
+
* Find the most recent upload date across all releases.
|
|
5936
|
+
*/
|
|
5937
|
+
findLastPublishDate(releases) {
|
|
5938
|
+
if (!releases) return null;
|
|
5939
|
+
const dates = [];
|
|
5940
|
+
for (const files of Object.values(releases)) {
|
|
5941
|
+
for (const file of files) {
|
|
5942
|
+
const dateStr = file.upload_time_iso_8601 ?? file.upload_time;
|
|
5943
|
+
if (dateStr) {
|
|
5944
|
+
const ts = new Date(dateStr).getTime();
|
|
5945
|
+
if (!isNaN(ts)) dates.push(ts);
|
|
5946
|
+
}
|
|
5947
|
+
}
|
|
5948
|
+
}
|
|
5949
|
+
if (dates.length === 0) return null;
|
|
5950
|
+
dates.sort((a, b) => b - a);
|
|
5951
|
+
return new Date(dates[0]).toISOString();
|
|
5952
|
+
}
|
|
5953
|
+
/**
|
|
5954
|
+
* Check if the requested version is yanked (PyPI's deprecation mechanism).
|
|
5955
|
+
* Returns the yank reason string, or null if not yanked.
|
|
5956
|
+
*/
|
|
5957
|
+
checkYanked(releases, version) {
|
|
5958
|
+
if (!releases || version === "latest") return null;
|
|
5959
|
+
const files = releases[version];
|
|
5960
|
+
if (!files || files.length === 0) return null;
|
|
5961
|
+
const yankedFile = files.find((f) => f.yanked);
|
|
5962
|
+
if (yankedFile) {
|
|
5963
|
+
return yankedFile.yanked_reason || "This version has been yanked";
|
|
5964
|
+
}
|
|
5965
|
+
return null;
|
|
5966
|
+
}
|
|
5967
|
+
/**
|
|
5968
|
+
* Extract a normalised repository URL from PyPI project_urls or home_page.
|
|
5969
|
+
*/
|
|
5970
|
+
extractRepoUrl(info) {
|
|
5971
|
+
const projectUrls = info.project_urls ?? {};
|
|
5972
|
+
const repoKeys = [
|
|
5973
|
+
"Source",
|
|
5974
|
+
"Source Code",
|
|
5975
|
+
"Repository",
|
|
5976
|
+
"GitHub",
|
|
5977
|
+
"Code",
|
|
5978
|
+
"Homepage",
|
|
5979
|
+
"source",
|
|
5980
|
+
"source_code",
|
|
5981
|
+
"repository",
|
|
5982
|
+
"github",
|
|
5983
|
+
"code",
|
|
5984
|
+
"homepage"
|
|
5985
|
+
];
|
|
5986
|
+
for (const key of repoKeys) {
|
|
5987
|
+
const url = projectUrls[key];
|
|
5988
|
+
if (url && (url.includes("github.com") || url.includes("gitlab.com") || url.includes("bitbucket.org"))) {
|
|
5989
|
+
return url.replace(/\.git$/, "");
|
|
5990
|
+
}
|
|
5991
|
+
}
|
|
5992
|
+
for (const url of Object.values(projectUrls)) {
|
|
5993
|
+
if (url && (url.includes("github.com") || url.includes("gitlab.com") || url.includes("bitbucket.org"))) {
|
|
5994
|
+
return url.replace(/\.git$/, "");
|
|
5995
|
+
}
|
|
5996
|
+
}
|
|
5997
|
+
const homePage = info.home_page;
|
|
5998
|
+
if (homePage && (homePage.includes("github.com") || homePage.includes("gitlab.com"))) {
|
|
5999
|
+
return homePage.replace(/\.git$/, "");
|
|
6000
|
+
}
|
|
6001
|
+
return null;
|
|
6002
|
+
}
|
|
6003
|
+
};
|
|
6004
|
+
|
|
5762
6005
|
// src/collectors/github.ts
|
|
5763
6006
|
var GitHubCollector = class extends BaseCollector {
|
|
5764
6007
|
name = "github";
|
|
@@ -5830,6 +6073,7 @@ var GitHubCollector = class extends BaseCollector {
|
|
|
5830
6073
|
async resolveRepoSlug(packageName) {
|
|
5831
6074
|
try {
|
|
5832
6075
|
const url = `https://registry.npmjs.org/${encodeURIComponent(packageName)}`;
|
|
6076
|
+
await npmRateLimiter.acquire();
|
|
5833
6077
|
const res = await fetch(url, {
|
|
5834
6078
|
headers: { Accept: "application/json" }
|
|
5835
6079
|
});
|
|
@@ -5872,6 +6116,7 @@ var GitHubCollector = class extends BaseCollector {
|
|
|
5872
6116
|
async fetchRepoInfo(owner, repo) {
|
|
5873
6117
|
const url = `https://api.github.com/repos/${owner}/${repo}`;
|
|
5874
6118
|
logger.debug(`GitHub: fetching repo info ${url}`);
|
|
6119
|
+
await githubRateLimiter.acquire();
|
|
5875
6120
|
const res = await fetch(url, { headers: this.headers() });
|
|
5876
6121
|
if (!res.ok) {
|
|
5877
6122
|
throw new Error(`GitHub API ${res.status} for ${url}`);
|
|
@@ -5886,6 +6131,7 @@ var GitHubCollector = class extends BaseCollector {
|
|
|
5886
6131
|
const url = `https://api.github.com/repos/${owner}/${repo}/contributors?per_page=1&anon=true`;
|
|
5887
6132
|
logger.debug(`GitHub: fetching contributor count ${url}`);
|
|
5888
6133
|
try {
|
|
6134
|
+
await githubRateLimiter.acquire();
|
|
5889
6135
|
const res = await fetch(url, { headers: this.headers() });
|
|
5890
6136
|
if (!res.ok) return 0;
|
|
5891
6137
|
const count = this.extractLastPage(res.headers.get("link"));
|
|
@@ -5906,6 +6152,7 @@ var GitHubCollector = class extends BaseCollector {
|
|
|
5906
6152
|
const url = `https://api.github.com/repos/${owner}/${repo}/commits?since=${since}&per_page=1`;
|
|
5907
6153
|
logger.debug(`GitHub: fetching recent commit count ${url}`);
|
|
5908
6154
|
try {
|
|
6155
|
+
await githubRateLimiter.acquire();
|
|
5909
6156
|
const res = await fetch(url, { headers: this.headers() });
|
|
5910
6157
|
if (!res.ok) return 0;
|
|
5911
6158
|
const count = this.extractLastPage(res.headers.get("link"));
|
|
@@ -5921,6 +6168,7 @@ var GitHubCollector = class extends BaseCollector {
|
|
|
5921
6168
|
const url = `https://api.github.com/repos/${owner}/${repo}/commits?per_page=1`;
|
|
5922
6169
|
logger.debug(`GitHub: fetching latest commit ${url}`);
|
|
5923
6170
|
try {
|
|
6171
|
+
await githubRateLimiter.acquire();
|
|
5924
6172
|
const res = await fetch(url, { headers: this.headers() });
|
|
5925
6173
|
if (!res.ok) return null;
|
|
5926
6174
|
const body = await res.json();
|
|
@@ -5937,6 +6185,7 @@ var GitHubCollector = class extends BaseCollector {
|
|
|
5937
6185
|
const url = `https://api.github.com/repos/${owner}/${repo}/contents/.github/FUNDING.yml`;
|
|
5938
6186
|
logger.debug(`GitHub: checking FUNDING.yml ${url}`);
|
|
5939
6187
|
try {
|
|
6188
|
+
await githubRateLimiter.acquire();
|
|
5940
6189
|
const res = await fetch(url, { headers: this.headers() });
|
|
5941
6190
|
return res.ok;
|
|
5942
6191
|
} catch {
|
|
@@ -5968,11 +6217,11 @@ var SecurityCollector = class extends BaseCollector {
|
|
|
5968
6217
|
constructor(cache2) {
|
|
5969
6218
|
super(cache2);
|
|
5970
6219
|
}
|
|
5971
|
-
async collect(packageName, version) {
|
|
6220
|
+
async collect(packageName, version, ecosystem) {
|
|
5972
6221
|
const cached = await this.getCached(packageName, version);
|
|
5973
6222
|
if (cached) return cached;
|
|
5974
6223
|
try {
|
|
5975
|
-
const vulns = await this.queryOsv(packageName);
|
|
6224
|
+
const vulns = await this.queryOsv(packageName, ecosystem);
|
|
5976
6225
|
const totalVulnerabilities = vulns.length;
|
|
5977
6226
|
const severityCounts = {
|
|
5978
6227
|
critical: 0,
|
|
@@ -6022,16 +6271,16 @@ var SecurityCollector = class extends BaseCollector {
|
|
|
6022
6271
|
// ---------------------------------------------------------------------------
|
|
6023
6272
|
// OSV API
|
|
6024
6273
|
// ---------------------------------------------------------------------------
|
|
6025
|
-
async queryOsv(packageName) {
|
|
6274
|
+
async queryOsv(packageName, ecosystem = "npm") {
|
|
6026
6275
|
const url = "https://api.osv.dev/v1/query";
|
|
6027
|
-
logger.debug(`OSV: querying vulnerabilities for ${packageName}`);
|
|
6276
|
+
logger.debug(`OSV: querying vulnerabilities for ${packageName} (ecosystem=${ecosystem})`);
|
|
6028
6277
|
const res = await fetch(url, {
|
|
6029
6278
|
method: "POST",
|
|
6030
6279
|
headers: { "Content-Type": "application/json" },
|
|
6031
6280
|
body: JSON.stringify({
|
|
6032
6281
|
package: {
|
|
6033
6282
|
name: packageName,
|
|
6034
|
-
ecosystem
|
|
6283
|
+
ecosystem
|
|
6035
6284
|
}
|
|
6036
6285
|
})
|
|
6037
6286
|
});
|
|
@@ -6195,6 +6444,7 @@ var FundingCollector = class extends BaseCollector {
|
|
|
6195
6444
|
async fetchNpmFunding(packageName) {
|
|
6196
6445
|
try {
|
|
6197
6446
|
const url = `https://registry.npmjs.org/${encodeURIComponent(packageName)}`;
|
|
6447
|
+
await npmRateLimiter.acquire();
|
|
6198
6448
|
const res = await fetch(url, {
|
|
6199
6449
|
headers: { Accept: "application/json" }
|
|
6200
6450
|
});
|
|
@@ -6229,6 +6479,7 @@ var FundingCollector = class extends BaseCollector {
|
|
|
6229
6479
|
if (this.githubToken) {
|
|
6230
6480
|
headers.Authorization = `Bearer ${this.githubToken}`;
|
|
6231
6481
|
}
|
|
6482
|
+
await githubRateLimiter.acquire();
|
|
6232
6483
|
const res = await fetch(url, { headers });
|
|
6233
6484
|
if (!res.ok) return null;
|
|
6234
6485
|
return await res.text();
|
|
@@ -6259,6 +6510,7 @@ var FundingCollector = class extends BaseCollector {
|
|
|
6259
6510
|
async resolveRepoSlug(packageName) {
|
|
6260
6511
|
try {
|
|
6261
6512
|
const url = `https://registry.npmjs.org/${encodeURIComponent(packageName)}`;
|
|
6513
|
+
await npmRateLimiter.acquire();
|
|
6262
6514
|
const res = await fetch(url, {
|
|
6263
6515
|
headers: { Accept: "application/json" }
|
|
6264
6516
|
});
|
|
@@ -6377,6 +6629,7 @@ var PopularityCollector = class extends BaseCollector {
|
|
|
6377
6629
|
const url = `https://api.npmjs.org/downloads/point/${period}/${encodeURIComponent(packageName)}`;
|
|
6378
6630
|
logger.debug(`Popularity: fetching ${period} downloads: ${url}`);
|
|
6379
6631
|
try {
|
|
6632
|
+
await npmRateLimiter.acquire();
|
|
6380
6633
|
const res = await fetch(url, {
|
|
6381
6634
|
headers: { Accept: "application/json" }
|
|
6382
6635
|
});
|
|
@@ -6404,6 +6657,7 @@ var PopularityCollector = class extends BaseCollector {
|
|
|
6404
6657
|
const url = `https://registry.npmjs.org/-/v1/search?text=${encodeURIComponent(packageName)}&size=1`;
|
|
6405
6658
|
logger.debug(`Popularity: fetching dependent count: ${url}`);
|
|
6406
6659
|
try {
|
|
6660
|
+
await npmRateLimiter.acquire();
|
|
6407
6661
|
const res = await fetch(url, {
|
|
6408
6662
|
headers: { Accept: "application/json" }
|
|
6409
6663
|
});
|
|
@@ -6429,6 +6683,7 @@ var PopularityCollector = class extends BaseCollector {
|
|
|
6429
6683
|
async fetchDependentCountFromRegistry(packageName) {
|
|
6430
6684
|
try {
|
|
6431
6685
|
const url = `https://www.npmjs.com/package/${encodeURIComponent(packageName)}`;
|
|
6686
|
+
await npmRateLimiter.acquire();
|
|
6432
6687
|
const res = await fetch(url, {
|
|
6433
6688
|
headers: {
|
|
6434
6689
|
Accept: "text/html",
|
|
@@ -6596,6 +6851,7 @@ var LicenseCollector = class extends BaseCollector {
|
|
|
6596
6851
|
async fetchLicense(packageName, version) {
|
|
6597
6852
|
const url = `https://registry.npmjs.org/${encodeURIComponent(packageName)}`;
|
|
6598
6853
|
logger.debug(`License: fetching packument ${url}`);
|
|
6854
|
+
await npmRateLimiter.acquire();
|
|
6599
6855
|
const res = await fetch(url, {
|
|
6600
6856
|
headers: { Accept: "application/json" }
|
|
6601
6857
|
});
|
|
@@ -6655,6 +6911,7 @@ var CollectorOrchestrator = class {
|
|
|
6655
6911
|
cache;
|
|
6656
6912
|
options;
|
|
6657
6913
|
registryCollector;
|
|
6914
|
+
pypiRegistryCollector;
|
|
6658
6915
|
githubCollector;
|
|
6659
6916
|
securityCollector;
|
|
6660
6917
|
fundingCollector;
|
|
@@ -6668,6 +6925,7 @@ var CollectorOrchestrator = class {
|
|
|
6668
6925
|
concurrency: options.concurrency ?? 10
|
|
6669
6926
|
};
|
|
6670
6927
|
this.registryCollector = new RegistryCollector(this.cache);
|
|
6928
|
+
this.pypiRegistryCollector = new PyPIRegistryCollector(this.cache);
|
|
6671
6929
|
this.githubCollector = new GitHubCollector(this.cache, this.options.githubToken || void 0);
|
|
6672
6930
|
this.securityCollector = new SecurityCollector(this.cache);
|
|
6673
6931
|
this.fundingCollector = new FundingCollector(this.cache, this.options.githubToken || void 0);
|
|
@@ -6681,13 +6939,15 @@ var CollectorOrchestrator = class {
|
|
|
6681
6939
|
* only the cache is consulted; if there is no cached entry the result gets
|
|
6682
6940
|
* `status: 'offline'` with `data: null`.
|
|
6683
6941
|
*/
|
|
6684
|
-
async collectAll(packageName, version) {
|
|
6942
|
+
async collectAll(packageName, version, ecosystem = "npm") {
|
|
6685
6943
|
logger.info(
|
|
6686
|
-
`Collecting data for ${packageName}@${version} (offline=${String(this.options.offline)})`
|
|
6944
|
+
`Collecting data for ${packageName}@${version} (ecosystem=${ecosystem}, offline=${String(this.options.offline)})`
|
|
6687
6945
|
);
|
|
6688
6946
|
const limit = pLimit(this.options.concurrency);
|
|
6947
|
+
const activeRegistryCollector = ecosystem === "pypi" ? this.pypiRegistryCollector : this.registryCollector;
|
|
6948
|
+
const osvEcosystem = ecosystem === "pypi" ? "PyPI" : "npm";
|
|
6689
6949
|
const entries = [
|
|
6690
|
-
{ key: "registry", collector:
|
|
6950
|
+
{ key: "registry", collector: activeRegistryCollector },
|
|
6691
6951
|
{ key: "github", collector: this.githubCollector },
|
|
6692
6952
|
{ key: "security", collector: this.securityCollector },
|
|
6693
6953
|
{ key: "funding", collector: this.fundingCollector },
|
|
@@ -6703,12 +6963,21 @@ var CollectorOrchestrator = class {
|
|
|
6703
6963
|
result = await this.offlineCollect(collector, packageName, version);
|
|
6704
6964
|
} else {
|
|
6705
6965
|
try {
|
|
6706
|
-
|
|
6707
|
-
|
|
6708
|
-
|
|
6709
|
-
|
|
6710
|
-
|
|
6711
|
-
|
|
6966
|
+
if (key === "security") {
|
|
6967
|
+
result = await Promise.race([
|
|
6968
|
+
this.securityCollector.collect(packageName, version, osvEcosystem),
|
|
6969
|
+
new Promise(
|
|
6970
|
+
(_, reject) => setTimeout(() => reject(new Error("Collector timeout")), COLLECTOR_TIMEOUT)
|
|
6971
|
+
)
|
|
6972
|
+
]);
|
|
6973
|
+
} else {
|
|
6974
|
+
result = await Promise.race([
|
|
6975
|
+
this.onlineCollect(collector, packageName, version),
|
|
6976
|
+
new Promise(
|
|
6977
|
+
(_, reject) => setTimeout(() => reject(new Error("Collector timeout")), COLLECTOR_TIMEOUT)
|
|
6978
|
+
)
|
|
6979
|
+
]);
|
|
6980
|
+
}
|
|
6712
6981
|
} catch {
|
|
6713
6982
|
logger.warn(`[${collector.name}] ${packageName}@${version} => timeout (${COLLECTOR_TIMEOUT}ms)`);
|
|
6714
6983
|
result = {
|
|
@@ -6833,7 +7102,7 @@ var TrustScoreEngine = class {
|
|
|
6833
7102
|
return {
|
|
6834
7103
|
trustScore: Math.round(trustScore),
|
|
6835
7104
|
metrics,
|
|
6836
|
-
insufficientData: unavailableMetrics.length >=
|
|
7105
|
+
insufficientData: unavailableMetrics.length >= 2,
|
|
6837
7106
|
unavailableMetrics
|
|
6838
7107
|
};
|
|
6839
7108
|
}
|
|
@@ -6975,6 +7244,11 @@ var TrustScoreEngine = class {
|
|
|
6975
7244
|
const effectiveWeight = m.weight / totalAvailableWeight;
|
|
6976
7245
|
score += m.score * effectiveWeight;
|
|
6977
7246
|
}
|
|
7247
|
+
const missingWeightFraction = 1 - totalAvailableWeight;
|
|
7248
|
+
if (missingWeightFraction > 0) {
|
|
7249
|
+
const penalty = (score - 50) * missingWeightFraction;
|
|
7250
|
+
score = score - penalty;
|
|
7251
|
+
}
|
|
6978
7252
|
return clamp(Math.round(score));
|
|
6979
7253
|
}
|
|
6980
7254
|
};
|
|
@@ -8496,6 +8770,238 @@ var MIGRATION_MAP = {
|
|
|
8496
8770
|
description: "Native trimEnd() is available since ES2019. Remove the polyfill.",
|
|
8497
8771
|
difficulty: "easy"
|
|
8498
8772
|
}
|
|
8773
|
+
],
|
|
8774
|
+
// ─── v1.4.0 additions ───────────────────────────────────────────────
|
|
8775
|
+
// Modern frameworks
|
|
8776
|
+
"express": [
|
|
8777
|
+
{
|
|
8778
|
+
alternative: "hono",
|
|
8779
|
+
description: "Ultra-fast web framework. Works on Node, Deno, Bun, Cloudflare Workers. Similar routing API.",
|
|
8780
|
+
difficulty: "moderate"
|
|
8781
|
+
},
|
|
8782
|
+
{
|
|
8783
|
+
alternative: "fastify",
|
|
8784
|
+
description: "Fast, low-overhead web framework with plugin architecture. Schema-based validation built-in.",
|
|
8785
|
+
difficulty: "moderate"
|
|
8786
|
+
},
|
|
8787
|
+
{
|
|
8788
|
+
alternative: "elysia",
|
|
8789
|
+
description: "Bun-native web framework with end-to-end type safety. Extremely fast.",
|
|
8790
|
+
difficulty: "hard"
|
|
8791
|
+
}
|
|
8792
|
+
],
|
|
8793
|
+
"create-react-app": [
|
|
8794
|
+
{
|
|
8795
|
+
alternative: "vite",
|
|
8796
|
+
description: 'Lightning-fast build tool with HMR. Use "npm create vite@latest" with React template.',
|
|
8797
|
+
difficulty: "moderate"
|
|
8798
|
+
},
|
|
8799
|
+
{
|
|
8800
|
+
alternative: "next",
|
|
8801
|
+
description: "Full-stack React framework with SSR, file-based routing, and API routes.",
|
|
8802
|
+
difficulty: "hard"
|
|
8803
|
+
}
|
|
8804
|
+
],
|
|
8805
|
+
"react-scripts": [
|
|
8806
|
+
{
|
|
8807
|
+
alternative: "vite",
|
|
8808
|
+
description: "Replace CRA build tooling with Vite. Much faster dev server and builds.",
|
|
8809
|
+
difficulty: "moderate"
|
|
8810
|
+
}
|
|
8811
|
+
],
|
|
8812
|
+
// ORM / Database
|
|
8813
|
+
"prisma": [
|
|
8814
|
+
{
|
|
8815
|
+
alternative: "drizzle-orm",
|
|
8816
|
+
description: "Lightweight TypeScript ORM with SQL-like syntax. Better performance, smaller bundle.",
|
|
8817
|
+
difficulty: "hard"
|
|
8818
|
+
}
|
|
8819
|
+
],
|
|
8820
|
+
// Testing
|
|
8821
|
+
"jest": [
|
|
8822
|
+
{
|
|
8823
|
+
alternative: "vitest",
|
|
8824
|
+
description: "Vite-native test runner. Jest-compatible API, much faster. Near drop-in replacement.",
|
|
8825
|
+
difficulty: "easy"
|
|
8826
|
+
}
|
|
8827
|
+
],
|
|
8828
|
+
// CSS-in-JS
|
|
8829
|
+
"styled-components": [
|
|
8830
|
+
{
|
|
8831
|
+
alternative: "tailwindcss",
|
|
8832
|
+
description: "Utility-first CSS framework. Zero runtime CSS. Requires rewriting component styles.",
|
|
8833
|
+
difficulty: "hard"
|
|
8834
|
+
},
|
|
8835
|
+
{
|
|
8836
|
+
alternative: "vanilla-extract",
|
|
8837
|
+
description: "Zero-runtime CSS-in-TypeScript. Type-safe styles with static extraction.",
|
|
8838
|
+
difficulty: "moderate"
|
|
8839
|
+
}
|
|
8840
|
+
],
|
|
8841
|
+
"@emotion/react": [
|
|
8842
|
+
{
|
|
8843
|
+
alternative: "tailwindcss",
|
|
8844
|
+
description: "Utility-first CSS framework. No runtime CSS overhead.",
|
|
8845
|
+
difficulty: "hard"
|
|
8846
|
+
},
|
|
8847
|
+
{
|
|
8848
|
+
alternative: "vanilla-extract",
|
|
8849
|
+
description: "Zero-runtime CSS-in-TypeScript. Static extraction at build time.",
|
|
8850
|
+
difficulty: "moderate"
|
|
8851
|
+
}
|
|
8852
|
+
],
|
|
8853
|
+
// State management
|
|
8854
|
+
"redux": [
|
|
8855
|
+
{
|
|
8856
|
+
alternative: "zustand",
|
|
8857
|
+
description: "Minimal state management with hooks. No boilerplate, no providers needed.",
|
|
8858
|
+
difficulty: "moderate"
|
|
8859
|
+
},
|
|
8860
|
+
{
|
|
8861
|
+
alternative: "jotai",
|
|
8862
|
+
description: "Primitive and flexible state management. Atomic approach, minimal API.",
|
|
8863
|
+
difficulty: "moderate"
|
|
8864
|
+
}
|
|
8865
|
+
],
|
|
8866
|
+
"mobx": [
|
|
8867
|
+
{
|
|
8868
|
+
alternative: "zustand",
|
|
8869
|
+
description: "Simpler state management with hooks API. Less magic, more predictable.",
|
|
8870
|
+
difficulty: "moderate"
|
|
8871
|
+
}
|
|
8872
|
+
],
|
|
8873
|
+
// Process managers
|
|
8874
|
+
"pm2": [
|
|
8875
|
+
{
|
|
8876
|
+
alternative: "node --watch",
|
|
8877
|
+
description: "Node.js 18+ native watch mode. For development, no extra dependency needed.",
|
|
8878
|
+
difficulty: "easy"
|
|
8879
|
+
}
|
|
8880
|
+
],
|
|
8881
|
+
"forever": [
|
|
8882
|
+
{
|
|
8883
|
+
alternative: "pm2",
|
|
8884
|
+
description: "Modern process manager with clustering, monitoring, and log management.",
|
|
8885
|
+
difficulty: "easy"
|
|
8886
|
+
}
|
|
8887
|
+
],
|
|
8888
|
+
// Validation
|
|
8889
|
+
"joi": [
|
|
8890
|
+
{
|
|
8891
|
+
alternative: "zod",
|
|
8892
|
+
description: "TypeScript-first schema validation. Better type inference, smaller bundle.",
|
|
8893
|
+
difficulty: "moderate"
|
|
8894
|
+
},
|
|
8895
|
+
{
|
|
8896
|
+
alternative: "valibot",
|
|
8897
|
+
description: "Ultra-small schema validation library. Modular, tree-shakeable.",
|
|
8898
|
+
difficulty: "moderate"
|
|
8899
|
+
}
|
|
8900
|
+
],
|
|
8901
|
+
"yup": [
|
|
8902
|
+
{
|
|
8903
|
+
alternative: "zod",
|
|
8904
|
+
description: "TypeScript-first schema validation. Similar API but better type inference.",
|
|
8905
|
+
difficulty: "easy"
|
|
8906
|
+
},
|
|
8907
|
+
{
|
|
8908
|
+
alternative: "valibot",
|
|
8909
|
+
description: "Modular validation library. Much smaller bundle size than yup.",
|
|
8910
|
+
difficulty: "moderate"
|
|
8911
|
+
}
|
|
8912
|
+
],
|
|
8913
|
+
"class-validator": [
|
|
8914
|
+
{
|
|
8915
|
+
alternative: "zod",
|
|
8916
|
+
description: "Functional schema validation without decorators. Works with any framework.",
|
|
8917
|
+
difficulty: "moderate"
|
|
8918
|
+
}
|
|
8919
|
+
],
|
|
8920
|
+
// HTTP / Fetch
|
|
8921
|
+
"supertest": [
|
|
8922
|
+
{
|
|
8923
|
+
alternative: "undici",
|
|
8924
|
+
description: "Node.js native HTTP/1.1 client. Fast, spec-compliant, built into Node 18+.",
|
|
8925
|
+
difficulty: "moderate"
|
|
8926
|
+
}
|
|
8927
|
+
],
|
|
8928
|
+
"needle": [
|
|
8929
|
+
{
|
|
8930
|
+
alternative: "undici",
|
|
8931
|
+
description: "Node.js native HTTP client. No external dependency needed in Node 18+.",
|
|
8932
|
+
difficulty: "moderate"
|
|
8933
|
+
}
|
|
8934
|
+
],
|
|
8935
|
+
// Logging (new additions)
|
|
8936
|
+
"console-log-level": [
|
|
8937
|
+
{
|
|
8938
|
+
alternative: "pino",
|
|
8939
|
+
description: "Ultra-fast JSON logger. Structured logging with minimal overhead.",
|
|
8940
|
+
difficulty: "easy"
|
|
8941
|
+
}
|
|
8942
|
+
],
|
|
8943
|
+
// File system
|
|
8944
|
+
"fs-extra": [
|
|
8945
|
+
{
|
|
8946
|
+
alternative: "node:fs/promises",
|
|
8947
|
+
description: "Native Node.js fs promises API. Most fs-extra methods are now in core (cp, mkdir recursive).",
|
|
8948
|
+
difficulty: "moderate"
|
|
8949
|
+
}
|
|
8950
|
+
],
|
|
8951
|
+
"graceful-fs": [
|
|
8952
|
+
{
|
|
8953
|
+
alternative: "node:fs",
|
|
8954
|
+
description: "Modern Node.js handles EMFILE gracefully. The polyfill is rarely needed now.",
|
|
8955
|
+
difficulty: "easy"
|
|
8956
|
+
}
|
|
8957
|
+
],
|
|
8958
|
+
// Environment
|
|
8959
|
+
"cross-env": [
|
|
8960
|
+
{
|
|
8961
|
+
alternative: "node --env-file",
|
|
8962
|
+
description: "Node.js 20+ supports --env-file flag natively. No extra package needed.",
|
|
8963
|
+
difficulty: "easy"
|
|
8964
|
+
}
|
|
8965
|
+
],
|
|
8966
|
+
"env-cmd": [
|
|
8967
|
+
{
|
|
8968
|
+
alternative: "node --env-file",
|
|
8969
|
+
description: "Node.js 20+ supports --env-file flag. Replace env-cmd with native feature.",
|
|
8970
|
+
difficulty: "easy"
|
|
8971
|
+
}
|
|
8972
|
+
],
|
|
8973
|
+
// Linting
|
|
8974
|
+
"eslint": [
|
|
8975
|
+
{
|
|
8976
|
+
alternative: "biome",
|
|
8977
|
+
description: "Rust-based linter and formatter. 10-100x faster than ESLint. Minimal config.",
|
|
8978
|
+
difficulty: "moderate"
|
|
8979
|
+
},
|
|
8980
|
+
{
|
|
8981
|
+
alternative: "oxlint",
|
|
8982
|
+
description: "Oxidation compiler linter. Extremely fast, compatible with many ESLint rules.",
|
|
8983
|
+
difficulty: "moderate"
|
|
8984
|
+
}
|
|
8985
|
+
],
|
|
8986
|
+
"prettier": [
|
|
8987
|
+
{
|
|
8988
|
+
alternative: "biome",
|
|
8989
|
+
description: "Biome includes a Prettier-compatible formatter. One tool for lint + format.",
|
|
8990
|
+
difficulty: "easy"
|
|
8991
|
+
}
|
|
8992
|
+
],
|
|
8993
|
+
// Monorepo
|
|
8994
|
+
"lerna": [
|
|
8995
|
+
{
|
|
8996
|
+
alternative: "turborepo",
|
|
8997
|
+
description: "High-performance monorepo build system. Caching, parallel execution.",
|
|
8998
|
+
difficulty: "moderate"
|
|
8999
|
+
},
|
|
9000
|
+
{
|
|
9001
|
+
alternative: "nx",
|
|
9002
|
+
description: "Smart monorepo tool with computation caching and affected commands.",
|
|
9003
|
+
difficulty: "moderate"
|
|
9004
|
+
}
|
|
8499
9005
|
]
|
|
8500
9006
|
};
|
|
8501
9007
|
var MigrationAdvisor = class {
|
|
@@ -10735,7 +11241,115 @@ var POPULAR_PACKAGES = [
|
|
|
10735
11241
|
"request",
|
|
10736
11242
|
"tslint",
|
|
10737
11243
|
"node-pre-gyp",
|
|
10738
|
-
"npm-lifecycle"
|
|
11244
|
+
"npm-lifecycle",
|
|
11245
|
+
// ---------------------------------------------------------------------------
|
|
11246
|
+
// Popular Python packages (PyPI)
|
|
11247
|
+
// ---------------------------------------------------------------------------
|
|
11248
|
+
"requests",
|
|
11249
|
+
"flask",
|
|
11250
|
+
"django",
|
|
11251
|
+
"fastapi",
|
|
11252
|
+
"numpy",
|
|
11253
|
+
"pandas",
|
|
11254
|
+
"scipy",
|
|
11255
|
+
"matplotlib",
|
|
11256
|
+
"tensorflow",
|
|
11257
|
+
"torch",
|
|
11258
|
+
"scikit-learn",
|
|
11259
|
+
"keras",
|
|
11260
|
+
"pytorch-lightning",
|
|
11261
|
+
"xgboost",
|
|
11262
|
+
"lightgbm",
|
|
11263
|
+
"pytest",
|
|
11264
|
+
"black",
|
|
11265
|
+
"mypy",
|
|
11266
|
+
"ruff",
|
|
11267
|
+
"pylint",
|
|
11268
|
+
"flake8",
|
|
11269
|
+
"isort",
|
|
11270
|
+
"celery",
|
|
11271
|
+
"redis",
|
|
11272
|
+
"sqlalchemy",
|
|
11273
|
+
"alembic",
|
|
11274
|
+
"pydantic",
|
|
11275
|
+
"httpx",
|
|
11276
|
+
"aiohttp",
|
|
11277
|
+
"uvicorn",
|
|
11278
|
+
"gunicorn",
|
|
11279
|
+
"starlette",
|
|
11280
|
+
"boto3",
|
|
11281
|
+
"botocore",
|
|
11282
|
+
"awscli",
|
|
11283
|
+
"google-cloud-storage",
|
|
11284
|
+
"azure-storage-blob",
|
|
11285
|
+
"pillow",
|
|
11286
|
+
"opencv-python",
|
|
11287
|
+
"beautifulsoup4",
|
|
11288
|
+
"lxml",
|
|
11289
|
+
"scrapy",
|
|
11290
|
+
"cryptography",
|
|
11291
|
+
"paramiko",
|
|
11292
|
+
"fabric",
|
|
11293
|
+
"ansible",
|
|
11294
|
+
"click",
|
|
11295
|
+
"typer",
|
|
11296
|
+
"rich",
|
|
11297
|
+
"tqdm",
|
|
11298
|
+
"colorama",
|
|
11299
|
+
"tabulate",
|
|
11300
|
+
"setuptools",
|
|
11301
|
+
"wheel",
|
|
11302
|
+
"pip",
|
|
11303
|
+
"twine",
|
|
11304
|
+
"poetry",
|
|
11305
|
+
"pdm",
|
|
11306
|
+
"hatch",
|
|
11307
|
+
"jinja2",
|
|
11308
|
+
"mako",
|
|
11309
|
+
"markupsafe",
|
|
11310
|
+
"werkzeug",
|
|
11311
|
+
"psycopg2",
|
|
11312
|
+
"pymongo",
|
|
11313
|
+
"motor",
|
|
11314
|
+
"peewee",
|
|
11315
|
+
"tortoise-orm",
|
|
11316
|
+
"marshmallow",
|
|
11317
|
+
"attrs",
|
|
11318
|
+
"dataclasses-json",
|
|
11319
|
+
"sentry-sdk",
|
|
11320
|
+
"prometheus-client",
|
|
11321
|
+
"opentelemetry-api",
|
|
11322
|
+
"transformers",
|
|
11323
|
+
"huggingface-hub",
|
|
11324
|
+
"tokenizers",
|
|
11325
|
+
"datasets",
|
|
11326
|
+
"langchain",
|
|
11327
|
+
"openai",
|
|
11328
|
+
"anthropic",
|
|
11329
|
+
"tiktoken",
|
|
11330
|
+
"pytest-cov",
|
|
11331
|
+
"pytest-asyncio",
|
|
11332
|
+
"pytest-mock",
|
|
11333
|
+
"coverage",
|
|
11334
|
+
"pyyaml",
|
|
11335
|
+
"toml",
|
|
11336
|
+
"python-dotenv",
|
|
11337
|
+
"decouple",
|
|
11338
|
+
"arrow",
|
|
11339
|
+
"pendulum",
|
|
11340
|
+
"python-dateutil",
|
|
11341
|
+
"stripe",
|
|
11342
|
+
"twilio",
|
|
11343
|
+
"sendgrid",
|
|
11344
|
+
"selenium",
|
|
11345
|
+
"playwright",
|
|
11346
|
+
"httptools",
|
|
11347
|
+
"orjson",
|
|
11348
|
+
"ujson",
|
|
11349
|
+
"msgpack",
|
|
11350
|
+
"networkx",
|
|
11351
|
+
"sympy",
|
|
11352
|
+
"statsmodels"
|
|
10739
11353
|
];
|
|
10740
11354
|
var TyposquatDetector = class _TyposquatDetector {
|
|
10741
11355
|
popularPackages;
|