@mcptoolshop/registry-stats 0.3.1 → 0.4.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.
- package/README.es.md +110 -60
- package/README.fr.md +151 -63
- package/README.hi.md +113 -63
- package/README.it.md +111 -61
- package/README.ja.md +109 -59
- package/README.md +12 -3
- package/README.pt-BR.md +107 -57
- package/README.zh.md +109 -59
- package/assets/logo.png +0 -0
- package/dist/cli.js +216 -15
- package/dist/index.cjs +154 -15
- package/dist/index.d.cts +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +154 -15
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -48,22 +48,64 @@ var RegistryError = class extends Error {
|
|
|
48
48
|
var RETRYABLE = /* @__PURE__ */ new Set([429, 500, 502, 503, 504]);
|
|
49
49
|
var MAX_RETRIES = 3;
|
|
50
50
|
var BASE_DELAY = 1e3;
|
|
51
|
+
var registryLocks = /* @__PURE__ */ new Map();
|
|
52
|
+
var REGISTRY_DELAYS = {
|
|
53
|
+
npm: 400,
|
|
54
|
+
// ~2.5 req/s — safe for 54+ scoped packages
|
|
55
|
+
pypi: 2200,
|
|
56
|
+
// 30 req/60s = 1 per 2s, with headroom
|
|
57
|
+
docker: 4e3
|
|
58
|
+
// 10 req/3600s — very tight
|
|
59
|
+
};
|
|
60
|
+
var DEFAULT_DELAY = 100;
|
|
61
|
+
function acquireSlot(registry) {
|
|
62
|
+
const minDelay = REGISTRY_DELAYS[registry] ?? DEFAULT_DELAY;
|
|
63
|
+
const prev = registryLocks.get(registry) ?? Promise.resolve();
|
|
64
|
+
const slot = prev.then(() => new Promise((r) => setTimeout(r, minDelay)));
|
|
65
|
+
registryLocks.set(registry, slot);
|
|
66
|
+
return prev;
|
|
67
|
+
}
|
|
51
68
|
async function fetchWithRetry(url, registry, init) {
|
|
69
|
+
let lastError;
|
|
70
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
71
|
+
await acquireSlot(registry);
|
|
72
|
+
const res = await fetch(url, init);
|
|
73
|
+
if (res.status === 404) return null;
|
|
74
|
+
if (res.ok) return res.json();
|
|
75
|
+
const retryAfter = res.headers.get("retry-after");
|
|
76
|
+
const retryAfterSeconds = retryAfter ? parseInt(retryAfter, 10) : void 0;
|
|
77
|
+
lastError = new RegistryError(
|
|
78
|
+
registry,
|
|
79
|
+
res.status,
|
|
80
|
+
`${res.statusText}: ${url}`,
|
|
81
|
+
retryAfterSeconds
|
|
82
|
+
);
|
|
83
|
+
if (!RETRYABLE.has(res.status) || attempt === MAX_RETRIES) break;
|
|
84
|
+
const backoff = BASE_DELAY * Math.pow(2, attempt);
|
|
85
|
+
const retryAfterMs = retryAfterSeconds ? retryAfterSeconds * 1e3 : 0;
|
|
86
|
+
const delay = Math.max(backoff, retryAfterMs);
|
|
87
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
88
|
+
}
|
|
89
|
+
throw lastError;
|
|
90
|
+
}
|
|
91
|
+
async function fetchDirect(url, registry, init) {
|
|
52
92
|
let lastError;
|
|
53
93
|
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
54
94
|
const res = await fetch(url, init);
|
|
55
95
|
if (res.status === 404) return null;
|
|
56
96
|
if (res.ok) return res.json();
|
|
57
97
|
const retryAfter = res.headers.get("retry-after");
|
|
58
|
-
const
|
|
98
|
+
const retryAfterSeconds = retryAfter ? parseInt(retryAfter, 10) : void 0;
|
|
59
99
|
lastError = new RegistryError(
|
|
60
100
|
registry,
|
|
61
101
|
res.status,
|
|
62
102
|
`${res.statusText}: ${url}`,
|
|
63
|
-
|
|
103
|
+
retryAfterSeconds
|
|
64
104
|
);
|
|
65
105
|
if (!RETRYABLE.has(res.status) || attempt === MAX_RETRIES) break;
|
|
66
|
-
const
|
|
106
|
+
const backoff = BASE_DELAY * Math.pow(2, attempt);
|
|
107
|
+
const retryAfterMs = retryAfterSeconds ? retryAfterSeconds * 1e3 : 0;
|
|
108
|
+
const delay = Math.max(backoff, retryAfterMs);
|
|
67
109
|
await new Promise((r) => setTimeout(r, delay));
|
|
68
110
|
}
|
|
69
111
|
throw lastError;
|
|
@@ -74,20 +116,22 @@ var API = "https://api.npmjs.org/downloads";
|
|
|
74
116
|
var npm = {
|
|
75
117
|
name: "npm",
|
|
76
118
|
async getStats(pkg) {
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
119
|
+
const end = /* @__PURE__ */ new Date();
|
|
120
|
+
const start = new Date(end);
|
|
121
|
+
start.setDate(start.getDate() - 30);
|
|
122
|
+
const data = await fetchWithRetry(
|
|
123
|
+
`${API}/range/${fmt(start)}:${fmt(end)}/${pkg}`,
|
|
124
|
+
"npm"
|
|
125
|
+
);
|
|
126
|
+
if (!data || !data.downloads || data.downloads.length === 0) return null;
|
|
127
|
+
const days = data.downloads;
|
|
128
|
+
const lastDay = days[days.length - 1]?.downloads ?? 0;
|
|
129
|
+
const lastWeek = days.slice(-7).reduce((s, d) => s + d.downloads, 0);
|
|
130
|
+
const lastMonth = days.reduce((s, d) => s + d.downloads, 0);
|
|
83
131
|
return {
|
|
84
132
|
registry: "npm",
|
|
85
133
|
package: pkg,
|
|
86
|
-
downloads: {
|
|
87
|
-
lastDay: day?.downloads,
|
|
88
|
-
lastWeek: week?.downloads,
|
|
89
|
-
lastMonth: month?.downloads
|
|
90
|
-
},
|
|
134
|
+
downloads: { lastDay, lastWeek, lastMonth },
|
|
91
135
|
fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
92
136
|
};
|
|
93
137
|
},
|
|
@@ -115,6 +159,27 @@ var npm = {
|
|
|
115
159
|
return chunks;
|
|
116
160
|
}
|
|
117
161
|
};
|
|
162
|
+
async function npmBulkPoint(packages, period = "last-month") {
|
|
163
|
+
const result = /* @__PURE__ */ new Map();
|
|
164
|
+
if (packages.length === 0) return result;
|
|
165
|
+
const BATCH_SIZE = 128;
|
|
166
|
+
for (let i = 0; i < packages.length; i += BATCH_SIZE) {
|
|
167
|
+
const batch = packages.slice(i, i + BATCH_SIZE);
|
|
168
|
+
const joined = batch.join(",");
|
|
169
|
+
const data = await fetchDirect(
|
|
170
|
+
`${API}/point/${period}/${joined}`,
|
|
171
|
+
"npm"
|
|
172
|
+
);
|
|
173
|
+
if (data) {
|
|
174
|
+
for (const [name, entry] of Object.entries(data)) {
|
|
175
|
+
if (entry && typeof entry.downloads === "number") {
|
|
176
|
+
result.set(name, entry.downloads);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return result;
|
|
182
|
+
}
|
|
118
183
|
function fmt(d) {
|
|
119
184
|
return d.toISOString().slice(0, 10);
|
|
120
185
|
}
|
|
@@ -508,7 +573,12 @@ function createHandler(opts) {
|
|
|
508
573
|
}
|
|
509
574
|
error(res, "Not found", 404);
|
|
510
575
|
} catch (e) {
|
|
511
|
-
|
|
576
|
+
if (e instanceof RegistryError) {
|
|
577
|
+
const status = e.statusCode === 429 ? 429 : e.statusCode === 404 ? 404 : e.statusCode >= 500 ? 502 : 500;
|
|
578
|
+
error(res, e.message, status);
|
|
579
|
+
} else {
|
|
580
|
+
error(res, e.message, 500);
|
|
581
|
+
}
|
|
512
582
|
}
|
|
513
583
|
};
|
|
514
584
|
}
|
|
@@ -607,10 +677,54 @@ stats.bulk = async function bulk(registry, packages, options) {
|
|
|
607
677
|
if (!provider) {
|
|
608
678
|
throw new RegistryError(registry, 0, `Unknown registry "${registry}".`);
|
|
609
679
|
}
|
|
680
|
+
if (registry === "npm" && packages.length > 1) {
|
|
681
|
+
return npmBulkStats(packages, options);
|
|
682
|
+
}
|
|
610
683
|
const concurrency = options?.concurrency ?? 5;
|
|
611
684
|
const limit = pLimit(concurrency);
|
|
612
685
|
return Promise.all(packages.map((pkg) => limit(() => stats(registry, pkg, options))));
|
|
613
686
|
};
|
|
687
|
+
async function npmBulkStats(packages, options) {
|
|
688
|
+
const scoped = [];
|
|
689
|
+
const unscoped = [];
|
|
690
|
+
for (const pkg of packages) {
|
|
691
|
+
if (pkg.startsWith("@")) {
|
|
692
|
+
scoped.push(pkg);
|
|
693
|
+
} else {
|
|
694
|
+
unscoped.push(pkg);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
const bulkMonth = unscoped.length > 0 ? await npmBulkPoint(unscoped, "last-month") : /* @__PURE__ */ new Map();
|
|
698
|
+
const bulkWeek = unscoped.length > 0 ? await npmBulkPoint(unscoped, "last-week") : /* @__PURE__ */ new Map();
|
|
699
|
+
const bulkDay = unscoped.length > 0 ? await npmBulkPoint(unscoped, "last-day") : /* @__PURE__ */ new Map();
|
|
700
|
+
const unscopedResults = /* @__PURE__ */ new Map();
|
|
701
|
+
for (const pkg of unscoped) {
|
|
702
|
+
const month = bulkMonth.get(pkg);
|
|
703
|
+
const week = bulkWeek.get(pkg);
|
|
704
|
+
const day = bulkDay.get(pkg);
|
|
705
|
+
if (month === void 0 && week === void 0 && day === void 0) {
|
|
706
|
+
unscopedResults.set(pkg, null);
|
|
707
|
+
} else {
|
|
708
|
+
unscopedResults.set(pkg, {
|
|
709
|
+
registry: "npm",
|
|
710
|
+
package: pkg,
|
|
711
|
+
downloads: {
|
|
712
|
+
lastDay: day,
|
|
713
|
+
lastWeek: week,
|
|
714
|
+
lastMonth: month
|
|
715
|
+
},
|
|
716
|
+
fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
717
|
+
});
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
const limit = pLimit(1);
|
|
721
|
+
const scopedResults = await Promise.all(
|
|
722
|
+
scoped.map((pkg) => limit(() => stats("npm", pkg, options)))
|
|
723
|
+
);
|
|
724
|
+
const scopedMap = /* @__PURE__ */ new Map();
|
|
725
|
+
scoped.forEach((pkg, i) => scopedMap.set(pkg, scopedResults[i]));
|
|
726
|
+
return packages.map((pkg) => unscopedResults.get(pkg) ?? scopedMap.get(pkg) ?? null);
|
|
727
|
+
}
|
|
614
728
|
stats.range = async function range(registry, pkg, start, end, options) {
|
|
615
729
|
const provider = providers[registry];
|
|
616
730
|
if (!provider) {
|
|
@@ -654,6 +768,31 @@ stats.compare = async function compare(pkg, registries, options) {
|
|
|
654
768
|
fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
655
769
|
};
|
|
656
770
|
};
|
|
771
|
+
stats.mine = async function mine(maintainer, options) {
|
|
772
|
+
const packages = [];
|
|
773
|
+
const PAGE_SIZE = 250;
|
|
774
|
+
let offset = 0;
|
|
775
|
+
while (true) {
|
|
776
|
+
const url = `https://registry.npmjs.org/-/v1/search?text=maintainer:${encodeURIComponent(maintainer)}&size=${PAGE_SIZE}&from=${offset}`;
|
|
777
|
+
const data = await fetchDirect(url, "npm");
|
|
778
|
+
if (!data || data.objects.length === 0) break;
|
|
779
|
+
for (const obj of data.objects) {
|
|
780
|
+
packages.push(obj.package.name);
|
|
781
|
+
}
|
|
782
|
+
offset += data.objects.length;
|
|
783
|
+
if (offset >= data.total) break;
|
|
784
|
+
}
|
|
785
|
+
if (packages.length === 0) return [];
|
|
786
|
+
const results = [];
|
|
787
|
+
const bulkResults = await stats.bulk("npm", packages, options);
|
|
788
|
+
for (let i = 0; i < packages.length; i++) {
|
|
789
|
+
const r = bulkResults[i];
|
|
790
|
+
if (r) results.push(r);
|
|
791
|
+
options?.onProgress?.(i + 1, packages.length, packages[i]);
|
|
792
|
+
}
|
|
793
|
+
results.sort((a, b) => (b.downloads.lastMonth ?? 0) - (a.downloads.lastMonth ?? 0));
|
|
794
|
+
return results;
|
|
795
|
+
};
|
|
657
796
|
// Annotate the CommonJS export names for ESM import in node:
|
|
658
797
|
0 && (module.exports = {
|
|
659
798
|
RegistryError,
|
package/dist/index.d.cts
CHANGED
|
@@ -125,6 +125,9 @@ declare namespace stats {
|
|
|
125
125
|
var bulk: (registry: string, packages: string[], options?: StatsOptions) => Promise<(PackageStats | null)[]>;
|
|
126
126
|
var range: (registry: string, pkg: string, start: string, end: string, options?: StatsOptions) => Promise<DailyDownloads[]>;
|
|
127
127
|
var compare: (pkg: string, registries?: string[], options?: StatsOptions) => Promise<ComparisonResult>;
|
|
128
|
+
var mine: (maintainer: string, options?: StatsOptions & {
|
|
129
|
+
onProgress?: (done: number, total: number, pkg: string) => void;
|
|
130
|
+
}) => Promise<PackageStats[]>;
|
|
128
131
|
}
|
|
129
132
|
|
|
130
133
|
export { type ChartData, type ComparisonResult, type Config, type DailyDownloads, type PackageConfig, type PackageStats, type RateLimitConfig, RegistryError, type RegistryName, type RegistryProvider, type ServerOptions, type StatsCache, type StatsOptions, calc, createCache, createHandler, defaultConfig, loadConfig, registerProvider, serve, starterConfig, stats };
|
package/dist/index.d.ts
CHANGED
|
@@ -125,6 +125,9 @@ declare namespace stats {
|
|
|
125
125
|
var bulk: (registry: string, packages: string[], options?: StatsOptions) => Promise<(PackageStats | null)[]>;
|
|
126
126
|
var range: (registry: string, pkg: string, start: string, end: string, options?: StatsOptions) => Promise<DailyDownloads[]>;
|
|
127
127
|
var compare: (pkg: string, registries?: string[], options?: StatsOptions) => Promise<ComparisonResult>;
|
|
128
|
+
var mine: (maintainer: string, options?: StatsOptions & {
|
|
129
|
+
onProgress?: (done: number, total: number, pkg: string) => void;
|
|
130
|
+
}) => Promise<PackageStats[]>;
|
|
128
131
|
}
|
|
129
132
|
|
|
130
133
|
export { type ChartData, type ComparisonResult, type Config, type DailyDownloads, type PackageConfig, type PackageStats, type RateLimitConfig, RegistryError, type RegistryName, type RegistryProvider, type ServerOptions, type StatsCache, type StatsOptions, calc, createCache, createHandler, defaultConfig, loadConfig, registerProvider, serve, starterConfig, stats };
|
package/dist/index.js
CHANGED
|
@@ -13,22 +13,64 @@ var RegistryError = class extends Error {
|
|
|
13
13
|
var RETRYABLE = /* @__PURE__ */ new Set([429, 500, 502, 503, 504]);
|
|
14
14
|
var MAX_RETRIES = 3;
|
|
15
15
|
var BASE_DELAY = 1e3;
|
|
16
|
+
var registryLocks = /* @__PURE__ */ new Map();
|
|
17
|
+
var REGISTRY_DELAYS = {
|
|
18
|
+
npm: 400,
|
|
19
|
+
// ~2.5 req/s — safe for 54+ scoped packages
|
|
20
|
+
pypi: 2200,
|
|
21
|
+
// 30 req/60s = 1 per 2s, with headroom
|
|
22
|
+
docker: 4e3
|
|
23
|
+
// 10 req/3600s — very tight
|
|
24
|
+
};
|
|
25
|
+
var DEFAULT_DELAY = 100;
|
|
26
|
+
function acquireSlot(registry) {
|
|
27
|
+
const minDelay = REGISTRY_DELAYS[registry] ?? DEFAULT_DELAY;
|
|
28
|
+
const prev = registryLocks.get(registry) ?? Promise.resolve();
|
|
29
|
+
const slot = prev.then(() => new Promise((r) => setTimeout(r, minDelay)));
|
|
30
|
+
registryLocks.set(registry, slot);
|
|
31
|
+
return prev;
|
|
32
|
+
}
|
|
16
33
|
async function fetchWithRetry(url, registry, init) {
|
|
34
|
+
let lastError;
|
|
35
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
36
|
+
await acquireSlot(registry);
|
|
37
|
+
const res = await fetch(url, init);
|
|
38
|
+
if (res.status === 404) return null;
|
|
39
|
+
if (res.ok) return res.json();
|
|
40
|
+
const retryAfter = res.headers.get("retry-after");
|
|
41
|
+
const retryAfterSeconds = retryAfter ? parseInt(retryAfter, 10) : void 0;
|
|
42
|
+
lastError = new RegistryError(
|
|
43
|
+
registry,
|
|
44
|
+
res.status,
|
|
45
|
+
`${res.statusText}: ${url}`,
|
|
46
|
+
retryAfterSeconds
|
|
47
|
+
);
|
|
48
|
+
if (!RETRYABLE.has(res.status) || attempt === MAX_RETRIES) break;
|
|
49
|
+
const backoff = BASE_DELAY * Math.pow(2, attempt);
|
|
50
|
+
const retryAfterMs = retryAfterSeconds ? retryAfterSeconds * 1e3 : 0;
|
|
51
|
+
const delay = Math.max(backoff, retryAfterMs);
|
|
52
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
53
|
+
}
|
|
54
|
+
throw lastError;
|
|
55
|
+
}
|
|
56
|
+
async function fetchDirect(url, registry, init) {
|
|
17
57
|
let lastError;
|
|
18
58
|
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
19
59
|
const res = await fetch(url, init);
|
|
20
60
|
if (res.status === 404) return null;
|
|
21
61
|
if (res.ok) return res.json();
|
|
22
62
|
const retryAfter = res.headers.get("retry-after");
|
|
23
|
-
const
|
|
63
|
+
const retryAfterSeconds = retryAfter ? parseInt(retryAfter, 10) : void 0;
|
|
24
64
|
lastError = new RegistryError(
|
|
25
65
|
registry,
|
|
26
66
|
res.status,
|
|
27
67
|
`${res.statusText}: ${url}`,
|
|
28
|
-
|
|
68
|
+
retryAfterSeconds
|
|
29
69
|
);
|
|
30
70
|
if (!RETRYABLE.has(res.status) || attempt === MAX_RETRIES) break;
|
|
31
|
-
const
|
|
71
|
+
const backoff = BASE_DELAY * Math.pow(2, attempt);
|
|
72
|
+
const retryAfterMs = retryAfterSeconds ? retryAfterSeconds * 1e3 : 0;
|
|
73
|
+
const delay = Math.max(backoff, retryAfterMs);
|
|
32
74
|
await new Promise((r) => setTimeout(r, delay));
|
|
33
75
|
}
|
|
34
76
|
throw lastError;
|
|
@@ -39,20 +81,22 @@ var API = "https://api.npmjs.org/downloads";
|
|
|
39
81
|
var npm = {
|
|
40
82
|
name: "npm",
|
|
41
83
|
async getStats(pkg) {
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
84
|
+
const end = /* @__PURE__ */ new Date();
|
|
85
|
+
const start = new Date(end);
|
|
86
|
+
start.setDate(start.getDate() - 30);
|
|
87
|
+
const data = await fetchWithRetry(
|
|
88
|
+
`${API}/range/${fmt(start)}:${fmt(end)}/${pkg}`,
|
|
89
|
+
"npm"
|
|
90
|
+
);
|
|
91
|
+
if (!data || !data.downloads || data.downloads.length === 0) return null;
|
|
92
|
+
const days = data.downloads;
|
|
93
|
+
const lastDay = days[days.length - 1]?.downloads ?? 0;
|
|
94
|
+
const lastWeek = days.slice(-7).reduce((s, d) => s + d.downloads, 0);
|
|
95
|
+
const lastMonth = days.reduce((s, d) => s + d.downloads, 0);
|
|
48
96
|
return {
|
|
49
97
|
registry: "npm",
|
|
50
98
|
package: pkg,
|
|
51
|
-
downloads: {
|
|
52
|
-
lastDay: day?.downloads,
|
|
53
|
-
lastWeek: week?.downloads,
|
|
54
|
-
lastMonth: month?.downloads
|
|
55
|
-
},
|
|
99
|
+
downloads: { lastDay, lastWeek, lastMonth },
|
|
56
100
|
fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
57
101
|
};
|
|
58
102
|
},
|
|
@@ -80,6 +124,27 @@ var npm = {
|
|
|
80
124
|
return chunks;
|
|
81
125
|
}
|
|
82
126
|
};
|
|
127
|
+
async function npmBulkPoint(packages, period = "last-month") {
|
|
128
|
+
const result = /* @__PURE__ */ new Map();
|
|
129
|
+
if (packages.length === 0) return result;
|
|
130
|
+
const BATCH_SIZE = 128;
|
|
131
|
+
for (let i = 0; i < packages.length; i += BATCH_SIZE) {
|
|
132
|
+
const batch = packages.slice(i, i + BATCH_SIZE);
|
|
133
|
+
const joined = batch.join(",");
|
|
134
|
+
const data = await fetchDirect(
|
|
135
|
+
`${API}/point/${period}/${joined}`,
|
|
136
|
+
"npm"
|
|
137
|
+
);
|
|
138
|
+
if (data) {
|
|
139
|
+
for (const [name, entry] of Object.entries(data)) {
|
|
140
|
+
if (entry && typeof entry.downloads === "number") {
|
|
141
|
+
result.set(name, entry.downloads);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return result;
|
|
147
|
+
}
|
|
83
148
|
function fmt(d) {
|
|
84
149
|
return d.toISOString().slice(0, 10);
|
|
85
150
|
}
|
|
@@ -473,7 +538,12 @@ function createHandler(opts) {
|
|
|
473
538
|
}
|
|
474
539
|
error(res, "Not found", 404);
|
|
475
540
|
} catch (e) {
|
|
476
|
-
|
|
541
|
+
if (e instanceof RegistryError) {
|
|
542
|
+
const status = e.statusCode === 429 ? 429 : e.statusCode === 404 ? 404 : e.statusCode >= 500 ? 502 : 500;
|
|
543
|
+
error(res, e.message, status);
|
|
544
|
+
} else {
|
|
545
|
+
error(res, e.message, 500);
|
|
546
|
+
}
|
|
477
547
|
}
|
|
478
548
|
};
|
|
479
549
|
}
|
|
@@ -572,10 +642,54 @@ stats.bulk = async function bulk(registry, packages, options) {
|
|
|
572
642
|
if (!provider) {
|
|
573
643
|
throw new RegistryError(registry, 0, `Unknown registry "${registry}".`);
|
|
574
644
|
}
|
|
645
|
+
if (registry === "npm" && packages.length > 1) {
|
|
646
|
+
return npmBulkStats(packages, options);
|
|
647
|
+
}
|
|
575
648
|
const concurrency = options?.concurrency ?? 5;
|
|
576
649
|
const limit = pLimit(concurrency);
|
|
577
650
|
return Promise.all(packages.map((pkg) => limit(() => stats(registry, pkg, options))));
|
|
578
651
|
};
|
|
652
|
+
async function npmBulkStats(packages, options) {
|
|
653
|
+
const scoped = [];
|
|
654
|
+
const unscoped = [];
|
|
655
|
+
for (const pkg of packages) {
|
|
656
|
+
if (pkg.startsWith("@")) {
|
|
657
|
+
scoped.push(pkg);
|
|
658
|
+
} else {
|
|
659
|
+
unscoped.push(pkg);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
const bulkMonth = unscoped.length > 0 ? await npmBulkPoint(unscoped, "last-month") : /* @__PURE__ */ new Map();
|
|
663
|
+
const bulkWeek = unscoped.length > 0 ? await npmBulkPoint(unscoped, "last-week") : /* @__PURE__ */ new Map();
|
|
664
|
+
const bulkDay = unscoped.length > 0 ? await npmBulkPoint(unscoped, "last-day") : /* @__PURE__ */ new Map();
|
|
665
|
+
const unscopedResults = /* @__PURE__ */ new Map();
|
|
666
|
+
for (const pkg of unscoped) {
|
|
667
|
+
const month = bulkMonth.get(pkg);
|
|
668
|
+
const week = bulkWeek.get(pkg);
|
|
669
|
+
const day = bulkDay.get(pkg);
|
|
670
|
+
if (month === void 0 && week === void 0 && day === void 0) {
|
|
671
|
+
unscopedResults.set(pkg, null);
|
|
672
|
+
} else {
|
|
673
|
+
unscopedResults.set(pkg, {
|
|
674
|
+
registry: "npm",
|
|
675
|
+
package: pkg,
|
|
676
|
+
downloads: {
|
|
677
|
+
lastDay: day,
|
|
678
|
+
lastWeek: week,
|
|
679
|
+
lastMonth: month
|
|
680
|
+
},
|
|
681
|
+
fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
const limit = pLimit(1);
|
|
686
|
+
const scopedResults = await Promise.all(
|
|
687
|
+
scoped.map((pkg) => limit(() => stats("npm", pkg, options)))
|
|
688
|
+
);
|
|
689
|
+
const scopedMap = /* @__PURE__ */ new Map();
|
|
690
|
+
scoped.forEach((pkg, i) => scopedMap.set(pkg, scopedResults[i]));
|
|
691
|
+
return packages.map((pkg) => unscopedResults.get(pkg) ?? scopedMap.get(pkg) ?? null);
|
|
692
|
+
}
|
|
579
693
|
stats.range = async function range(registry, pkg, start, end, options) {
|
|
580
694
|
const provider = providers[registry];
|
|
581
695
|
if (!provider) {
|
|
@@ -619,6 +733,31 @@ stats.compare = async function compare(pkg, registries, options) {
|
|
|
619
733
|
fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
620
734
|
};
|
|
621
735
|
};
|
|
736
|
+
stats.mine = async function mine(maintainer, options) {
|
|
737
|
+
const packages = [];
|
|
738
|
+
const PAGE_SIZE = 250;
|
|
739
|
+
let offset = 0;
|
|
740
|
+
while (true) {
|
|
741
|
+
const url = `https://registry.npmjs.org/-/v1/search?text=maintainer:${encodeURIComponent(maintainer)}&size=${PAGE_SIZE}&from=${offset}`;
|
|
742
|
+
const data = await fetchDirect(url, "npm");
|
|
743
|
+
if (!data || data.objects.length === 0) break;
|
|
744
|
+
for (const obj of data.objects) {
|
|
745
|
+
packages.push(obj.package.name);
|
|
746
|
+
}
|
|
747
|
+
offset += data.objects.length;
|
|
748
|
+
if (offset >= data.total) break;
|
|
749
|
+
}
|
|
750
|
+
if (packages.length === 0) return [];
|
|
751
|
+
const results = [];
|
|
752
|
+
const bulkResults = await stats.bulk("npm", packages, options);
|
|
753
|
+
for (let i = 0; i < packages.length; i++) {
|
|
754
|
+
const r = bulkResults[i];
|
|
755
|
+
if (r) results.push(r);
|
|
756
|
+
options?.onProgress?.(i + 1, packages.length, packages[i]);
|
|
757
|
+
}
|
|
758
|
+
results.sort((a, b) => (b.downloads.lastMonth ?? 0) - (a.downloads.lastMonth ?? 0));
|
|
759
|
+
return results;
|
|
760
|
+
};
|
|
622
761
|
export {
|
|
623
762
|
RegistryError,
|
|
624
763
|
calc,
|
package/package.json
CHANGED