@agntn/registries 0.3.0 → 0.3.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.
Files changed (110) hide show
  1. package/README.md +26 -3
  2. package/dist/THIRD-PARTY-LICENSES.md +36 -0
  3. package/dist/_chunks/cache.mjs +15 -0
  4. package/dist/{cache/index.mjs.map → _chunks/cache.mjs.map} +1 -1
  5. package/dist/{commands/cache.mjs → _chunks/cache2.mjs} +6 -8
  6. package/dist/_chunks/cache2.mjs.map +1 -0
  7. package/dist/{cache → _chunks}/cached-registry.mjs +12 -8
  8. package/dist/_chunks/cached-registry.mjs.map +1 -0
  9. package/dist/{commands → _chunks}/deps.mjs +19 -13
  10. package/dist/_chunks/deps.mjs.map +1 -0
  11. package/dist/_chunks/helpers.mjs +53 -19
  12. package/dist/_chunks/helpers.mjs.map +1 -1
  13. package/dist/_chunks/index.d.mts +9 -23
  14. package/dist/_chunks/index.d.mts.map +1 -1
  15. package/dist/_chunks/index2.d.mts +181 -0
  16. package/dist/_chunks/index2.d.mts.map +1 -0
  17. package/dist/_chunks/index3.d.mts +188 -0
  18. package/dist/_chunks/index3.d.mts.map +1 -0
  19. package/dist/_chunks/info.mjs +105 -0
  20. package/dist/_chunks/info.mjs.map +1 -0
  21. package/dist/_chunks/libs/typebox.mjs +278 -0
  22. package/dist/_chunks/libs/typebox.mjs.map +1 -0
  23. package/dist/_chunks/lockfile.mjs +183 -0
  24. package/dist/_chunks/lockfile.mjs.map +1 -0
  25. package/dist/_chunks/maintainers.mjs +53 -0
  26. package/dist/_chunks/maintainers.mjs.map +1 -0
  27. package/dist/_chunks/mcp.mjs +15 -0
  28. package/dist/_chunks/mcp.mjs.map +1 -0
  29. package/dist/_chunks/registries.mjs +514 -245
  30. package/dist/_chunks/registries.mjs.map +1 -1
  31. package/dist/_chunks/registry.d.mts +121 -10
  32. package/dist/_chunks/registry.d.mts.map +1 -1
  33. package/dist/_chunks/registry.mjs +121 -31
  34. package/dist/_chunks/registry.mjs.map +1 -1
  35. package/dist/{commands → _chunks}/shared.mjs +15 -8
  36. package/dist/_chunks/shared.mjs.map +1 -0
  37. package/dist/_chunks/tool-operations.mjs +63 -0
  38. package/dist/_chunks/tool-operations.mjs.map +1 -0
  39. package/dist/_chunks/version.mjs +4 -0
  40. package/dist/_chunks/version.mjs.map +1 -0
  41. package/dist/{commands → _chunks}/versions.mjs +10 -9
  42. package/dist/_chunks/versions.mjs.map +1 -0
  43. package/dist/ai.d.mts +4 -4
  44. package/dist/ai.mjs +0 -1
  45. package/dist/ai.mjs.map +1 -1
  46. package/dist/cache/index.d.mts +3 -23
  47. package/dist/cache/index.mjs +4 -11
  48. package/dist/cli.mjs +9 -7
  49. package/dist/cli.mjs.map +1 -1
  50. package/dist/index.d.mts +4 -56
  51. package/dist/index.mjs +4 -7
  52. package/dist/mcp.d.mts +7 -0
  53. package/dist/mcp.d.mts.map +1 -0
  54. package/dist/mcp.mjs +155 -0
  55. package/dist/mcp.mjs.map +1 -0
  56. package/dist/registries/index.d.mts +0 -1
  57. package/dist/registries/index.mjs +0 -1
  58. package/dist/tool-operations.d.mts +36 -0
  59. package/dist/tool-operations.d.mts.map +1 -0
  60. package/dist/tool-operations.mjs +5 -0
  61. package/package.json +55 -9
  62. package/packages/omp/extensions/registries.ts +112 -0
  63. package/packages/pi/extensions/registries.ts +125 -0
  64. package/dist/_chunks/errors.d.mts +0 -90
  65. package/dist/_chunks/errors.d.mts.map +0 -1
  66. package/dist/_chunks/purl.mjs +0 -200
  67. package/dist/_chunks/purl.mjs.map +0 -1
  68. package/dist/_chunks/types.d.mts +0 -73
  69. package/dist/_chunks/types.d.mts.map +0 -1
  70. package/dist/cache/cached-registry.d.mts +0 -35
  71. package/dist/cache/cached-registry.d.mts.map +0 -1
  72. package/dist/cache/cached-registry.mjs.map +0 -1
  73. package/dist/cache/index.d.mts.map +0 -1
  74. package/dist/cache/lockfile.d.mts +0 -50
  75. package/dist/cache/lockfile.d.mts.map +0 -1
  76. package/dist/cache/lockfile.mjs +0 -68
  77. package/dist/cache/lockfile.mjs.map +0 -1
  78. package/dist/cache/paths.d.mts +0 -11
  79. package/dist/cache/paths.d.mts.map +0 -1
  80. package/dist/cache/paths.mjs +0 -26
  81. package/dist/cache/paths.mjs.map +0 -1
  82. package/dist/cache/storage.d.mts +0 -22
  83. package/dist/cache/storage.d.mts.map +0 -1
  84. package/dist/cache/storage.mjs +0 -40
  85. package/dist/cache/storage.mjs.map +0 -1
  86. package/dist/commands/cache.d.mts +0 -7
  87. package/dist/commands/cache.d.mts.map +0 -1
  88. package/dist/commands/cache.mjs.map +0 -1
  89. package/dist/commands/deps.d.mts +0 -23
  90. package/dist/commands/deps.d.mts.map +0 -1
  91. package/dist/commands/deps.mjs.map +0 -1
  92. package/dist/commands/info.d.mts +0 -26
  93. package/dist/commands/info.d.mts.map +0 -1
  94. package/dist/commands/info.mjs +0 -54
  95. package/dist/commands/info.mjs.map +0 -1
  96. package/dist/commands/maintainers.d.mts +0 -23
  97. package/dist/commands/maintainers.d.mts.map +0 -1
  98. package/dist/commands/maintainers.mjs +0 -46
  99. package/dist/commands/maintainers.mjs.map +0 -1
  100. package/dist/commands/shared.d.mts +0 -27
  101. package/dist/commands/shared.d.mts.map +0 -1
  102. package/dist/commands/shared.mjs.map +0 -1
  103. package/dist/commands/versions.d.mts +0 -31
  104. package/dist/commands/versions.d.mts.map +0 -1
  105. package/dist/commands/versions.mjs.map +0 -1
  106. package/dist/core/index.d.mts +0 -4
  107. package/dist/core/index.mjs +0 -3
  108. package/dist/index.d.mts.map +0 -1
  109. package/dist/types.d.mts +0 -3
  110. package/dist/types.mjs +0 -1
@@ -1,5 +1,239 @@
1
- import { a as register, c as HTTPError, l as InvalidPURLError, t as Registry, u as NotFoundError } from "./registry.mjs";
2
- import { a as normalizeRepositoryURL, o as combineLicenses, s as normalizeLicense, t as buildPURL } from "./purl.mjs";
1
+ import { a as register, c as HTTPError, l as InvalidPURLError, n as create, t as Registry, u as NotFoundError } from "./registry.mjs";
2
+ /**
3
+ * Normalize common license names to SPDX expressions.
4
+ *
5
+ * @param raw - License name or SPDX expression.
6
+ * @returns {string} The normalized SPDX expression, or an empty string.
7
+ */
8
+ function normalizeLicense(raw) {
9
+ if (!raw) return "";
10
+ const trimmed = raw.trim();
11
+ if (!trimmed) return "";
12
+ const lower = trimmed.toLowerCase();
13
+ const known = LICENSE_MAP.get(lower);
14
+ if (known) return known;
15
+ return trimmed;
16
+ }
17
+ const LICENSE_MAP = new Map([
18
+ ["mit license", "MIT"],
19
+ ["mit", "MIT"],
20
+ ["apache license 2.0", "Apache-2.0"],
21
+ ["apache 2.0", "Apache-2.0"],
22
+ ["apache-2.0", "Apache-2.0"],
23
+ ["apache license, version 2.0", "Apache-2.0"],
24
+ ["apache2", "Apache-2.0"],
25
+ ["bsd-2-clause", "BSD-2-Clause"],
26
+ ["bsd-3-clause", "BSD-3-Clause"],
27
+ ["bsd 2-clause", "BSD-2-Clause"],
28
+ ["bsd 3-clause", "BSD-3-Clause"],
29
+ ["simplified bsd license", "BSD-2-Clause"],
30
+ ["new bsd license", "BSD-3-Clause"],
31
+ ["isc license", "ISC"],
32
+ ["isc", "ISC"],
33
+ ["gpl-2.0", "GPL-2.0-only"],
34
+ ["gpl-3.0", "GPL-3.0-only"],
35
+ ["gpl-2.0-only", "GPL-2.0-only"],
36
+ ["gpl-3.0-only", "GPL-3.0-only"],
37
+ ["lgpl-2.1", "LGPL-2.1-only"],
38
+ ["lgpl-3.0", "LGPL-3.0-only"],
39
+ ["mpl-2.0", "MPL-2.0"],
40
+ ["mozilla public license 2.0", "MPL-2.0"],
41
+ ["unlicense", "Unlicense"],
42
+ ["the unlicense", "Unlicense"],
43
+ ["cc0-1.0", "CC0-1.0"],
44
+ ["cc0", "CC0-1.0"],
45
+ ["public domain", "Unlicense"],
46
+ ["zlib", "Zlib"],
47
+ ["wtfpl", "WTFPL"],
48
+ ["artistic-2.0", "Artistic-2.0"],
49
+ ["boost software license 1.0", "BSL-1.0"],
50
+ ["bsl-1.0", "BSL-1.0"]
51
+ ]);
52
+ /**
53
+ * Combine license strings into one SPDX expression.
54
+ *
55
+ * @param licenses - License names or expressions to normalize.
56
+ * @param operator - SPDX operator joining multiple licenses.
57
+ * @returns {string} The combined SPDX expression, or an empty string.
58
+ */
59
+ function combineLicenses(licenses, operator = "OR") {
60
+ const normalized = licenses.map((l) => normalizeLicense(l)).filter(Boolean);
61
+ if (normalized.length === 0) return "";
62
+ if (normalized.length === 1) return normalized[0];
63
+ return normalized.join(` ${operator} `);
64
+ }
65
+ const REPOSITORY_SHORTHANDS = [
66
+ ["github:", "https://github.com/"],
67
+ ["gitlab:", "https://gitlab.com/"],
68
+ ["bitbucket:", "https://bitbucket.org/"]
69
+ ];
70
+ const GIT_TRANSPORTS = [["git://", "https://"], ["ssh://git@", "https://"]];
71
+ function extractRepositoryURL(raw) {
72
+ if (typeof raw === "string") return raw;
73
+ if (typeof raw !== "object" || raw === null) return "";
74
+ const url = raw["url"];
75
+ return typeof url === "string" ? url : "";
76
+ }
77
+ function replacePrefix(value, replacements) {
78
+ for (const [prefix, replacement] of replacements) if (value.startsWith(prefix)) return replacement + value.slice(prefix.length);
79
+ return value;
80
+ }
81
+ /**
82
+ * Normalize repository shorthands and Git transports to HTTPS.
83
+ *
84
+ * @param raw - Repository URL string or object with a URL field.
85
+ * @returns {string} The normalized URL, or an empty string.
86
+ */
87
+ function normalizeRepositoryURL(raw) {
88
+ let url = extractRepositoryURL(raw).trim();
89
+ if (!url) return "";
90
+ url = replacePrefix(url, REPOSITORY_SHORTHANDS);
91
+ if (url.startsWith("git+")) url = url.slice(4);
92
+ url = replacePrefix(url, GIT_TRANSPORTS);
93
+ const sshMatch = url.match(/^git@([^:]+):(.+)$/);
94
+ if (sshMatch) url = `https://${sshMatch[1]}/${sshMatch[2]}`;
95
+ return url.replace(/\/$/, "").replace(/\.git$/, "");
96
+ }
97
+ const QUALIFIER_KEY_PATTERN = /^[a-z][a-z0-9._-]*$/;
98
+ function decodePURLComponent(purlStr, value) {
99
+ try {
100
+ return decodeURIComponent(value);
101
+ } catch {
102
+ throw new InvalidPURLError(purlStr, "malformed percent-encoding");
103
+ }
104
+ }
105
+ function extractSubpath(purlStr, value) {
106
+ const hashIdx = value.indexOf("#");
107
+ if (hashIdx === -1) return {
108
+ remainder: value,
109
+ subpath: ""
110
+ };
111
+ const subpath = value.slice(hashIdx + 1).split("/").map((segment) => decodePURLComponent(purlStr, segment)).join("/");
112
+ return {
113
+ remainder: value.slice(0, hashIdx),
114
+ subpath
115
+ };
116
+ }
117
+ function extractQualifiers(purlStr, value) {
118
+ const queryIdx = value.indexOf("?");
119
+ if (queryIdx === -1) return {
120
+ qualifiers: {},
121
+ remainder: value
122
+ };
123
+ const qualifiers = {};
124
+ for (const pair of value.slice(queryIdx + 1).split("&")) {
125
+ const eqIdx = pair.indexOf("=");
126
+ if (eqIdx !== -1) qualifiers[decodePURLComponent(purlStr, pair.slice(0, eqIdx))] = decodePURLComponent(purlStr, pair.slice(eqIdx + 1));
127
+ }
128
+ return {
129
+ qualifiers,
130
+ remainder: value.slice(0, queryIdx)
131
+ };
132
+ }
133
+ function extractVersion(purlStr, value) {
134
+ const lastSlashIdx = value.lastIndexOf("/");
135
+ const lastAtIdx = value.lastIndexOf("@");
136
+ if (lastAtIdx === -1 || lastAtIdx <= lastSlashIdx) return {
137
+ remainder: value,
138
+ version: ""
139
+ };
140
+ return {
141
+ remainder: value.slice(0, lastAtIdx),
142
+ version: decodePURLComponent(purlStr, value.slice(lastAtIdx + 1))
143
+ };
144
+ }
145
+ /**
146
+ * Parse a package URL into normalized components.
147
+ *
148
+ * @param purlStr - Package URL to parse.
149
+ * @returns {ParsedPURL} The normalized components.
150
+ * @see https://github.com/package-url/purl-spec (ECMA-427)
151
+ */
152
+ function parsePURL(purlStr) {
153
+ if (!purlStr.startsWith("pkg:")) throw new InvalidPURLError(purlStr, "must start with \"pkg:\"");
154
+ const subpathParts = extractSubpath(purlStr, purlStr.slice(4));
155
+ const qualifierParts = extractQualifiers(purlStr, subpathParts.remainder);
156
+ const versionParts = extractVersion(purlStr, qualifierParts.remainder);
157
+ const remainder = versionParts.remainder;
158
+ const slashIdx = remainder.indexOf("/");
159
+ if (slashIdx === -1) throw new InvalidPURLError(purlStr, "missing type/name separator");
160
+ const type = remainder.slice(0, slashIdx).toLowerCase();
161
+ if (!type) throw new InvalidPURLError(purlStr, "empty type");
162
+ const rest = remainder.slice(slashIdx + 1);
163
+ if (!rest) throw new InvalidPURLError(purlStr, "empty name");
164
+ const lastSlashIdx = rest.lastIndexOf("/");
165
+ let namespace = "";
166
+ let name;
167
+ if (lastSlashIdx !== -1) {
168
+ namespace = rest.slice(0, lastSlashIdx).split("/").map((s) => decodePURLComponent(purlStr, s)).join("/");
169
+ name = decodePURLComponent(purlStr, rest.slice(lastSlashIdx + 1));
170
+ } else name = decodePURLComponent(purlStr, rest);
171
+ if (type === "pypi") name = name.toLowerCase().replaceAll(/[-_.]+/g, "-");
172
+ if (type === "alpm") name = name.toLowerCase();
173
+ return {
174
+ type,
175
+ namespace,
176
+ name,
177
+ version: versionParts.version,
178
+ qualifiers: qualifierParts.qualifiers,
179
+ subpath: subpathParts.subpath
180
+ };
181
+ }
182
+ /**
183
+ * Build a registry package name from PURL components.
184
+ *
185
+ * @param parsed - Parsed package URL components.
186
+ * @returns {string} The namespace-qualified package name.
187
+ */
188
+ function fullName(parsed) {
189
+ if (parsed.namespace) return `${parsed.namespace}/${parsed.name}`;
190
+ return parsed.name;
191
+ }
192
+ /**
193
+ * Create a registry from a package URL.
194
+ *
195
+ * @param purlStr - Package URL to resolve.
196
+ * @param client - Optional HTTP client.
197
+ * @returns {[Registry, string, string]} The registry, package name, and version.
198
+ */
199
+ function createFromPURL(purlStr, client) {
200
+ const parsed = parsePURL(purlStr);
201
+ const baseURL = parsed.qualifiers["repository_url"] ?? "";
202
+ return [
203
+ create(parsed.type, baseURL || void 0, client),
204
+ fullName(parsed),
205
+ parsed.version
206
+ ];
207
+ }
208
+ /**
209
+ * Build a package URL from normalized components.
210
+ *
211
+ * @param parts - Package URL components.
212
+ * @returns {string} The canonical package URL.
213
+ */
214
+ function buildPURL(parts) {
215
+ let purl = `pkg:${parts.type}/`;
216
+ if (parts.namespace) purl += `${parts.namespace.split("/").map((s) => encodeURIComponent(s)).join("/")}/`;
217
+ purl += encodeURIComponent(parts.name);
218
+ if (parts.version) purl += `@${encodeURIComponent(parts.version)}`;
219
+ if (parts.qualifiers && Object.keys(parts.qualifiers).length > 0) {
220
+ const qualifiers = /* @__PURE__ */ new Map();
221
+ for (const [key, value] of Object.entries(parts.qualifiers)) {
222
+ const canonicalKey = key.toLowerCase();
223
+ if (!QUALIFIER_KEY_PATTERN.test(canonicalKey)) throw new InvalidPURLError(purl, `invalid qualifier key "${key}"`);
224
+ if (qualifiers.has(canonicalKey)) throw new InvalidPURLError(purl, `duplicate qualifier key "${canonicalKey}"`);
225
+ qualifiers.set(canonicalKey, value);
226
+ }
227
+ const qs = [...qualifiers].sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0).map(([key, value]) => `${key}=${encodeURIComponent(value)}`).join("&");
228
+ purl += `?${qs}`;
229
+ }
230
+ if (parts.subpath) purl += `#${parts.subpath.split("/").map((s) => encodeURIComponent(s)).join("/")}`;
231
+ return purl;
232
+ }
233
+ function rethrowFetchError(error, ecosystem, packageName, version = "") {
234
+ if (error instanceof HTTPError && error.isNotFound()) throw new NotFoundError(ecosystem, packageName, version);
235
+ throw error;
236
+ }
3
237
  /** npm registry client. */
4
238
  var NpmRegistry = class extends Registry {
5
239
  constructor(baseURL, client) {
@@ -19,7 +253,7 @@ var NpmRegistry = class extends Registry {
19
253
  const data = await this.client.getJSON(url, signal);
20
254
  const latestVersion = data["dist-tags"].latest;
21
255
  const latestVersionData = data.versions[latestVersion];
22
- const licenses = this.extractLicense(data.license || latestVersionData?.license);
256
+ const licenses = data.license ? this.extractLicense(data.license) : this.extractLicense(latestVersionData?.license);
23
257
  const namespace = this.extractNamespace(name);
24
258
  return {
25
259
  name: data.name,
@@ -28,14 +262,13 @@ var NpmRegistry = class extends Registry {
28
262
  documentation: "",
29
263
  repository: normalizeRepositoryURL(data.repository || ""),
30
264
  licenses,
31
- keywords: data.keywords || [],
265
+ keywords: data.keywords ?? [],
32
266
  namespace,
33
267
  latestVersion,
34
268
  metadata: {}
35
269
  };
36
270
  } catch (error) {
37
- if (error instanceof HTTPError && error.isNotFound()) throw new NotFoundError("npm", name);
38
- throw error;
271
+ rethrowFetchError(error, this.ecosystem(), name);
39
272
  }
40
273
  }
41
274
  async fetchVersions(name, signal) {
@@ -43,25 +276,21 @@ var NpmRegistry = class extends Registry {
43
276
  const url = `${this.baseURL}/${encodedName}`;
44
277
  try {
45
278
  const data = await this.client.getJSON(url, signal);
46
- const versions = [];
47
- for (const [versionStr, versionData] of Object.entries(data.versions)) {
279
+ return Object.entries(data.versions).map(([versionStr, versionData]) => {
48
280
  const licenses = this.extractLicense(versionData.license);
49
281
  const publishedAt = data.time?.[versionStr] ? new Date(data.time[versionStr]) : null;
50
282
  const status = versionData.deprecated ? "deprecated" : "";
51
- const integrity = versionData.dist?.integrity ? versionData.dist.integrity : versionData.dist?.shasum ? `sha1-${versionData.dist.shasum}` : "";
52
- versions.push({
283
+ return {
53
284
  number: versionStr,
54
285
  publishedAt,
55
286
  licenses,
56
- integrity,
287
+ integrity: versionData.dist?.integrity ? versionData.dist.integrity : versionData.dist?.shasum ? `sha1-${versionData.dist.shasum}` : "",
57
288
  status,
58
289
  metadata: {}
59
- });
60
- }
61
- return versions;
290
+ };
291
+ });
62
292
  } catch (error) {
63
- if (error instanceof HTTPError && error.isNotFound()) throw new NotFoundError("npm", name);
64
- throw error;
293
+ rethrowFetchError(error, this.ecosystem(), name);
65
294
  }
66
295
  }
67
296
  async fetchDependencies(name, version, signal) {
@@ -69,38 +298,15 @@ var NpmRegistry = class extends Registry {
69
298
  const url = `${this.baseURL}/${encodedName}/${version}`;
70
299
  try {
71
300
  const versionData = await this.client.getJSON(url, signal);
72
- const dependencies = [];
73
- if (versionData.dependencies) for (const [depName, requirements] of Object.entries(versionData.dependencies)) {
74
- if (versionData.optionalDependencies && Object.hasOwn(versionData.optionalDependencies, depName)) continue;
75
- dependencies.push({
76
- name: depName,
77
- requirements,
78
- scope: "runtime",
79
- optional: false
80
- });
81
- }
82
- if (versionData.devDependencies) for (const [depName, requirements] of Object.entries(versionData.devDependencies)) dependencies.push({
83
- name: depName,
84
- requirements,
85
- scope: "development",
86
- optional: false
87
- });
88
- if (versionData.optionalDependencies) for (const [depName, requirements] of Object.entries(versionData.optionalDependencies)) dependencies.push({
89
- name: depName,
90
- requirements,
91
- scope: "runtime",
92
- optional: true
93
- });
94
- if (versionData.peerDependencies) for (const [depName, requirements] of Object.entries(versionData.peerDependencies)) dependencies.push({
95
- name: depName,
96
- requirements,
97
- scope: "runtime",
98
- optional: false
99
- });
100
- return dependencies;
301
+ const optionalNames = Object.keys(versionData.optionalDependencies ?? {});
302
+ return [
303
+ ...this.mapDependencies(versionData.dependencies, "runtime", false, optionalNames),
304
+ ...this.mapDependencies(versionData.devDependencies, "development", false),
305
+ ...this.mapDependencies(versionData.optionalDependencies, "runtime", true),
306
+ ...this.mapDependencies(versionData.peerDependencies, "runtime", false)
307
+ ];
101
308
  } catch (error) {
102
- if (error instanceof HTTPError && error.isNotFound()) throw new NotFoundError("npm", name, version);
103
- throw error;
309
+ rethrowFetchError(error, this.ecosystem(), name, version);
104
310
  }
105
311
  }
106
312
  async fetchMaintainers(name, signal) {
@@ -108,55 +314,10 @@ var NpmRegistry = class extends Registry {
108
314
  const url = `${this.baseURL}/${encodedName}`;
109
315
  try {
110
316
  const data = await this.client.getJSON(url, signal);
111
- const maintainers = [];
112
- const seen = /* @__PURE__ */ new Set();
113
- if (data.maintainers) for (const maintainer of data.maintainers) {
114
- const key = this.maintainerKey(maintainer.name, maintainer.email);
115
- if (!key || seen.has(key)) continue;
116
- seen.add(key);
117
- maintainers.push({
118
- uuid: "",
119
- login: maintainer.email ? maintainer.email.split("@")[0] : "",
120
- name: maintainer.name || "",
121
- email: maintainer.email || "",
122
- url: maintainer.url || "",
123
- role: ""
124
- });
125
- }
126
- const latestTag = data["dist-tags"].latest;
127
- const latestVersion = data.versions[latestTag];
128
- if (latestVersion?.author) {
129
- const key = this.maintainerKey(latestVersion.author.name, latestVersion.author.email);
130
- if (key && !seen.has(key)) {
131
- seen.add(key);
132
- maintainers.push({
133
- uuid: "",
134
- login: latestVersion.author.email ? latestVersion.author.email.split("@")[0] : "",
135
- name: latestVersion.author.name || "",
136
- email: latestVersion.author.email || "",
137
- url: latestVersion.author.url || "",
138
- role: "author"
139
- });
140
- }
141
- }
142
- if (latestVersion?.contributors) for (const contributor of latestVersion.contributors) {
143
- const key = this.maintainerKey(contributor.name, contributor.email);
144
- if (key && !seen.has(key)) {
145
- seen.add(key);
146
- maintainers.push({
147
- uuid: "",
148
- login: contributor.email ? contributor.email.split("@")[0] : "",
149
- name: contributor.name || "",
150
- email: contributor.email || "",
151
- url: contributor.url || "",
152
- role: "contributor"
153
- });
154
- }
155
- }
156
- return maintainers;
317
+ const latestVersion = data.versions[data["dist-tags"].latest];
318
+ return this.collectMaintainers(data.maintainers, latestVersion?.author, latestVersion?.contributors);
157
319
  } catch (error) {
158
- if (error instanceof HTTPError && error.isNotFound()) throw new NotFoundError("npm", name);
159
- throw error;
320
+ rethrowFetchError(error, this.ecosystem(), name);
160
321
  }
161
322
  }
162
323
  urls() {
@@ -185,23 +346,58 @@ var NpmRegistry = class extends Registry {
185
346
  }
186
347
  };
187
348
  }
188
- /** Build a stable dedup key. Prefers email (unique per account), falls back to name. */
349
+ mapDependencies(entries, scope, optional, excluded = []) {
350
+ const dependencies = [];
351
+ for (const [name, requirements] of Object.entries(entries ?? {})) {
352
+ if (excluded.includes(name)) continue;
353
+ dependencies.push({
354
+ name,
355
+ requirements,
356
+ scope,
357
+ optional
358
+ });
359
+ }
360
+ return dependencies;
361
+ }
362
+ collectMaintainers(maintainers, author, contributors) {
363
+ const candidates = [
364
+ ...(maintainers ?? []).map((maintainer) => [maintainer, ""]),
365
+ ...author ? [[author, "author"]] : [],
366
+ ...(contributors ?? []).map((contributor) => [contributor, "contributor"])
367
+ ];
368
+ const seen = /* @__PURE__ */ new Set();
369
+ const result = [];
370
+ for (const [maintainer, role] of candidates) {
371
+ const key = this.maintainerKey(maintainer.name, maintainer.email);
372
+ if (!key || seen.has(key)) continue;
373
+ seen.add(key);
374
+ result.push(this.toMaintainer(maintainer, role));
375
+ }
376
+ return result;
377
+ }
378
+ toMaintainer(maintainer, role) {
379
+ return {
380
+ uuid: "",
381
+ login: maintainer.email ? maintainer.email.split("@")[0] : "",
382
+ name: maintainer.name || "",
383
+ email: maintainer.email || "",
384
+ url: maintainer.url || "",
385
+ role
386
+ };
387
+ }
189
388
  maintainerKey(name, email) {
190
389
  if (email) return email;
191
390
  if (name) return name;
192
391
  return "";
193
392
  }
194
- /** Encode package name for URL (handle scoped packages). */
195
393
  encodeName(name) {
196
394
  if (name.startsWith("@")) return name.replace("/", "%2F");
197
395
  return name;
198
396
  }
199
- /** Extract namespace from scoped package name. */
200
397
  extractNamespace(name) {
201
398
  if (name.startsWith("@")) return name.split("/")[0] || "";
202
399
  return "";
203
400
  }
204
- /** Extract and normalize license. */
205
401
  extractLicense(raw) {
206
402
  if (!raw) return "";
207
403
  if (typeof raw === "string") return normalizeLicense(raw);
@@ -213,6 +409,9 @@ var NpmRegistry = class extends Registry {
213
409
  }
214
410
  };
215
411
  register("npm", "https://registry.npmjs.org", NpmRegistry);
412
+ function preferNonEmpty(value, fallback) {
413
+ return value || fallback;
414
+ }
216
415
  /** Crates.io registry client. */
217
416
  var CargoRegistry = class extends Registry {
218
417
  constructor(baseURL, client) {
@@ -230,7 +429,7 @@ var CargoRegistry = class extends Registry {
230
429
  try {
231
430
  const data = await this.client.getJSON(url, signal);
232
431
  const crateData = data.crate;
233
- const latestVersion = crateData.max_stable_version || crateData.max_version;
432
+ const latestVersion = preferNonEmpty(crateData.max_stable_version, crateData.max_version);
234
433
  const latestVersionData = data.versions.find((v) => v.num === latestVersion);
235
434
  return {
236
435
  name: crateData.name,
@@ -253,8 +452,7 @@ var CargoRegistry = class extends Registry {
253
452
  }
254
453
  };
255
454
  } catch (error) {
256
- if (error instanceof HTTPError && error.isNotFound()) throw new NotFoundError("cargo", name);
257
- throw error;
455
+ rethrowFetchError(error, this.ecosystem(), name);
258
456
  }
259
457
  }
260
458
  async fetchVersions(name, signal) {
@@ -280,8 +478,7 @@ var CargoRegistry = class extends Registry {
280
478
  }
281
479
  return versions;
282
480
  } catch (error) {
283
- if (error instanceof HTTPError && error.isNotFound()) throw new NotFoundError("cargo", name);
284
- throw error;
481
+ rethrowFetchError(error, this.ecosystem(), name);
285
482
  }
286
483
  }
287
484
  async fetchDependencies(name, version, signal) {
@@ -303,8 +500,7 @@ var CargoRegistry = class extends Registry {
303
500
  }
304
501
  return dependencies;
305
502
  } catch (error) {
306
- if (error instanceof HTTPError && error.isNotFound()) throw new NotFoundError("cargo", name, version);
307
- throw error;
503
+ rethrowFetchError(error, this.ecosystem(), name, version);
308
504
  }
309
505
  }
310
506
  async fetchMaintainers(name, signal) {
@@ -322,8 +518,7 @@ var CargoRegistry = class extends Registry {
322
518
  });
323
519
  return maintainers;
324
520
  } catch (error) {
325
- if (error instanceof HTTPError && error.isNotFound()) throw new NotFoundError("cargo", name);
326
- throw error;
521
+ rethrowFetchError(error, this.ecosystem(), name);
327
522
  }
328
523
  }
329
524
  urls() {
@@ -357,6 +552,80 @@ const PYPI_FILENAME_SEPARATORS = [
357
552
  "-",
358
553
  "."
359
554
  ];
555
+ /**
556
+ * PEP 639 projects put the SPDX expression in `license_expression` and leave `license` null.
557
+ *
558
+ * @param info - License fields of the JSON API `info` object.
559
+ * @returns {string} The normalized SPDX expression, or an empty string.
560
+ */
561
+ function pypiLicense(info) {
562
+ return normalizeLicense(info.license_expression || info.license);
563
+ }
564
+ const QUOTED_NAME_PATTERN = /^"((?:[^"\\]|\\.)*)"\s*<([^>]*)>$/;
565
+ const ANGLE_ADDRESS_PATTERN = /^([^<]*?)\s*<([^>]*)>$/;
566
+ const COMMENT_PATTERN = /\(([^)]*)\)/g;
567
+ const ADDRESS_ITEM_PATTERN = /(?:"(?:[^"\\]|\\.)*"|<[^>]*>|\([^)]*\)|[^,])+/g;
568
+ /**
569
+ * Split an address list on the commas outside quotes, angle brackets and one level of comments.
570
+ *
571
+ * @param value - Comma separated mailboxes.
572
+ * @returns {string[]} The trimmed, non-empty items.
573
+ */
574
+ function splitAddressList(value) {
575
+ return [...value.matchAll(ADDRESS_ITEM_PATTERN)].map(([item]) => item.trim()).filter(Boolean);
576
+ }
577
+ /**
578
+ * Read one mailbox; a comment names a bare address, and a bare name has no address.
579
+ *
580
+ * @param item - One item of an address list.
581
+ * @returns {PyPIContact} The name and email found in it.
582
+ */
583
+ function parseMailbox(item) {
584
+ const quoted = item.match(QUOTED_NAME_PATTERN);
585
+ if (quoted) return {
586
+ name: quoted[1].replaceAll(/\\(.)/g, "$1").trim(),
587
+ email: quoted[2].trim()
588
+ };
589
+ const comments = [];
590
+ const bare = item.replaceAll(COMMENT_PATTERN, (_match, comment) => {
591
+ comments.push(comment.trim());
592
+ return " ";
593
+ }).trim();
594
+ const comment = comments.filter(Boolean).join(" ");
595
+ const angled = bare.match(ANGLE_ADDRESS_PATTERN);
596
+ if (angled) return {
597
+ name: angled[1].trim() || comment,
598
+ email: angled[2].trim()
599
+ };
600
+ return bare.includes("@") ? {
601
+ name: comment,
602
+ email: bare
603
+ } : {
604
+ name: bare || comment,
605
+ email: ""
606
+ };
607
+ }
608
+ /**
609
+ * PEP 621 keeps people without an email in the name field, so the label stays its own entry.
610
+ *
611
+ * @param name - The `author` or `maintainer` field.
612
+ * @param emails - The matching `author_email` or `maintainer_email` field.
613
+ * @returns {PyPIContact[]} One contact per person.
614
+ */
615
+ function pypiContacts(name, emails) {
616
+ const label = name?.trim() ?? "";
617
+ const contacts = splitAddressList(emails ?? "").map(parseMailbox);
618
+ if (!label) return contacts;
619
+ if (contacts.length === 1 && !contacts[0].name) return [{
620
+ name: label,
621
+ email: contacts[0].email
622
+ }];
623
+ if (contacts.some((contact) => contact.name.toLowerCase() === label.toLowerCase())) return contacts;
624
+ return [{
625
+ name: label,
626
+ email: ""
627
+ }, ...contacts];
628
+ }
360
629
  /** PyPI registry client. */
361
630
  var PyPIRegistry = class extends Registry {
362
631
  constructor(baseURL, client) {
@@ -375,7 +644,7 @@ var PyPIRegistry = class extends Registry {
375
644
  const url = `${this.baseURL}/pypi/${normalized}/json`;
376
645
  try {
377
646
  const info = (await this.client.getJSON(url, signal)).info;
378
- const licenses = normalizeLicense(info.license);
647
+ const licenses = pypiLicense(info);
379
648
  const repository = this.extractRepository(info.project_urls);
380
649
  const keywords = this.parseKeywords(info.keywords);
381
650
  return {
@@ -391,8 +660,7 @@ var PyPIRegistry = class extends Registry {
391
660
  metadata: {}
392
661
  };
393
662
  } catch (error) {
394
- if (error instanceof HTTPError && error.isNotFound()) throw new NotFoundError("pypi", name);
395
- throw error;
663
+ rethrowFetchError(error, this.ecosystem(), name);
396
664
  }
397
665
  }
398
666
  async fetchVersions(name, signal) {
@@ -400,43 +668,9 @@ var PyPIRegistry = class extends Registry {
400
668
  const url = `${this.baseURL}/simple/${normalized}/`;
401
669
  try {
402
670
  const data = await this.client.getJSON(url, signal, { Accept: "application/vnd.pypi.simple.v1+json" });
403
- const filesByVersion = /* @__PURE__ */ new Map();
404
- for (const version of data.versions) filesByVersion.set(version, []);
405
- for (const file of data.files) {
406
- const version = this.matchFileVersion(file.filename, normalized, data.versions);
407
- if (version) filesByVersion.get(version).push(file);
408
- }
409
- const versions = [];
410
- for (const [versionStr, files] of filesByVersion) {
411
- if (files.length === 0) {
412
- versions.push({
413
- number: versionStr,
414
- publishedAt: null,
415
- licenses: "",
416
- integrity: "",
417
- status: "",
418
- metadata: {}
419
- });
420
- continue;
421
- }
422
- const file = files.find((f) => f.filename.endsWith(".tar.gz")) ?? files[0];
423
- const publishedAt = file["upload-time"] ? new Date(file["upload-time"]) : null;
424
- const integrity = file.hashes?.sha256 ? `sha256-${file.hashes.sha256}` : "";
425
- const status = file.yanked ? "yanked" : "";
426
- this.downloadUrls.set(`${normalized}@${versionStr}`, file.url);
427
- versions.push({
428
- number: versionStr,
429
- publishedAt,
430
- licenses: "",
431
- integrity,
432
- status,
433
- metadata: {}
434
- });
435
- }
436
- return versions;
671
+ return [...this.indexFilesByVersion(normalized, data.versions, data.files)].map(([version, files]) => this.toVersion(normalized, version, files));
437
672
  } catch (error) {
438
- if (error instanceof HTTPError && error.isNotFound()) throw new NotFoundError("pypi", name);
439
- throw error;
673
+ rethrowFetchError(error, this.ecosystem(), name);
440
674
  }
441
675
  }
442
676
  async fetchDependencies(name, version, signal) {
@@ -451,28 +685,33 @@ var PyPIRegistry = class extends Registry {
451
685
  }
452
686
  return dependencies;
453
687
  } catch (error) {
454
- if (error instanceof HTTPError && error.isNotFound()) throw new NotFoundError("pypi", name, version);
455
- throw error;
688
+ rethrowFetchError(error, this.ecosystem(), name, version);
456
689
  }
457
690
  }
458
691
  async fetchMaintainers(name, signal) {
459
692
  const normalized = this.normalizeName(name);
460
693
  const url = `${this.baseURL}/pypi/${normalized}/json`;
461
694
  try {
462
- const data = await this.client.getJSON(url, signal);
695
+ const { info } = await this.client.getJSON(url, signal);
696
+ const groups = [["author", pypiContacts(info.author, info.author_email)], ["maintainer", pypiContacts(info.maintainer, info.maintainer_email)]];
697
+ const seen = /* @__PURE__ */ new Set();
463
698
  const maintainers = [];
464
- if (data.info.author || data.info.author_email) maintainers.push({
465
- uuid: "",
466
- login: data.info.author_email ? data.info.author_email.split("@")[0] : "",
467
- name: data.info.author || "",
468
- email: data.info.author_email || "",
469
- url: "",
470
- role: "author"
471
- });
699
+ for (const [role, contacts] of groups) for (const contact of contacts) {
700
+ const key = (contact.email || contact.name).toLowerCase();
701
+ if (seen.has(key)) continue;
702
+ seen.add(key);
703
+ maintainers.push({
704
+ uuid: "",
705
+ login: contact.email ? contact.email.split("@")[0] : "",
706
+ name: contact.name,
707
+ email: contact.email,
708
+ url: "",
709
+ role
710
+ });
711
+ }
472
712
  return maintainers;
473
713
  } catch (error) {
474
- if (error instanceof HTTPError && error.isNotFound()) throw new NotFoundError("pypi", name);
475
- throw error;
714
+ rethrowFetchError(error, this.ecosystem(), name);
476
715
  }
477
716
  }
478
717
  urls() {
@@ -501,7 +740,38 @@ var PyPIRegistry = class extends Registry {
501
740
  }
502
741
  };
503
742
  }
504
- /** Match a filename to a version from the known versions list. */
743
+ indexFilesByVersion(normalizedName, versions, files) {
744
+ const filesByVersion = /* @__PURE__ */ new Map();
745
+ for (const version of versions) filesByVersion.set(version, []);
746
+ for (const file of files) {
747
+ const version = this.matchFileVersion(file.filename, normalizedName, versions);
748
+ if (version) filesByVersion.get(version).push(file);
749
+ }
750
+ return filesByVersion;
751
+ }
752
+ toVersion(normalizedName, version, files) {
753
+ if (files.length === 0) return {
754
+ number: version,
755
+ publishedAt: null,
756
+ licenses: "",
757
+ integrity: "",
758
+ status: "",
759
+ metadata: {}
760
+ };
761
+ const file = files.find((file) => file.filename.endsWith(".tar.gz")) ?? files[0];
762
+ const publishedAt = file["upload-time"] ? new Date(file["upload-time"]) : null;
763
+ const integrity = file.hashes?.sha256 ? `sha256-${file.hashes.sha256}` : "";
764
+ const status = file.yanked ? "yanked" : "";
765
+ this.downloadUrls.set(`${normalizedName}@${version}`, file.url);
766
+ return {
767
+ number: version,
768
+ publishedAt,
769
+ licenses: "",
770
+ integrity,
771
+ status,
772
+ metadata: {}
773
+ };
774
+ }
505
775
  matchFileVersion(filename, normalizedName, versions) {
506
776
  const lower = filename.toLowerCase();
507
777
  let prefix;
@@ -522,11 +792,9 @@ var PyPIRegistry = class extends Registry {
522
792
  const stripped = afterPrefix.replace(/\.(tar\.(gz|bz2|xz)|zip)$/i, "");
523
793
  if (versions.includes(stripped)) return stripped;
524
794
  }
525
- /** Normalize package name per PEP 503. */
526
795
  normalizeName(name) {
527
- return name.toLowerCase().replace(/[-_.]+/g, "-");
796
+ return name.toLowerCase().replaceAll(/[-_.]+/g, "-");
528
797
  }
529
- /** Extract repository URL from project_urls. */
530
798
  extractRepository(projectUrls) {
531
799
  return normalizeRepositoryURL(this.findProjectUrl(projectUrls, [
532
800
  "Repository",
@@ -536,7 +804,6 @@ var PyPIRegistry = class extends Registry {
536
804
  "Homepage"
537
805
  ]));
538
806
  }
539
- /** Find a project URL by key, case-insensitive. */
540
807
  findProjectUrl(projectUrls, keys) {
541
808
  if (!projectUrls) return "";
542
809
  const lowered = /* @__PURE__ */ new Map();
@@ -547,12 +814,30 @@ var PyPIRegistry = class extends Registry {
547
814
  }
548
815
  return "";
549
816
  }
550
- /** Parse comma-separated keywords. */
551
817
  parseKeywords(keywords) {
552
818
  if (!keywords) return [];
553
819
  return keywords.split(",").map((k) => k.trim()).filter(Boolean);
554
820
  }
555
- /** Parse PEP 508 dependency string into a normalized Dependency. */
821
+ parseExtraMarker(marker) {
822
+ const extraMatch = marker.match(/extra\s*==\s*["']([^"']+)["']/);
823
+ if (!extraMatch) return {
824
+ scope: "runtime",
825
+ optional: false
826
+ };
827
+ const extraName = extraMatch[1].toLowerCase();
828
+ if (/^dev(elop(ment)?)?$/.test(extraName)) return {
829
+ scope: "development",
830
+ optional: true
831
+ };
832
+ if (/^test(s|ing)?$/.test(extraName)) return {
833
+ scope: "test",
834
+ optional: true
835
+ };
836
+ return {
837
+ scope: "runtime",
838
+ optional: true
839
+ };
840
+ }
556
841
  parsePEP508(depStr) {
557
842
  const semiIdx = depStr.indexOf(";");
558
843
  const mainPart = semiIdx === -1 ? depStr.trim() : depStr.slice(0, semiIdx).trim();
@@ -562,26 +847,28 @@ var PyPIRegistry = class extends Registry {
562
847
  const depName = match[1];
563
848
  let versionSpec = match[2].trim();
564
849
  if (versionSpec.startsWith("(") && versionSpec.endsWith(")")) versionSpec = versionSpec.slice(1, -1).trim();
565
- let scope = "runtime";
566
- let optional = false;
567
- if (markerStr) {
568
- const extraMatch = markerStr.match(/extra\s*==\s*["']([^"']+)["']/);
569
- if (extraMatch) {
570
- optional = true;
571
- const extraName = extraMatch[1].toLowerCase();
572
- if (/^dev(elop(ment)?)?$/.test(extraName)) scope = "development";
573
- else if (/^test(s|ing)?$/.test(extraName)) scope = "test";
574
- }
575
- }
850
+ const marker = this.parseExtraMarker(markerStr);
576
851
  return {
577
852
  name: this.normalizeName(depName),
578
853
  requirements: versionSpec,
579
- scope,
580
- optional
854
+ scope: marker.scope,
855
+ optional: marker.optional
581
856
  };
582
857
  }
583
858
  };
584
859
  register("pypi", "https://pypi.org", PyPIRegistry);
860
+ function orEmpty(value) {
861
+ return value || "";
862
+ }
863
+ function gemLicenses(licenses) {
864
+ return licenses ? combineLicenses(licenses.map((license) => normalizeLicense(license))) : "";
865
+ }
866
+ function gemRepositoryURL(sourceCodeURI, metadata, homepageURI) {
867
+ const metadataURI = metadata?.["source_code_uri"];
868
+ if (sourceCodeURI) return normalizeRepositoryURL(sourceCodeURI);
869
+ if (metadataURI) return normalizeRepositoryURL(metadataURI);
870
+ return normalizeRepositoryURL(homepageURI ?? "");
871
+ }
585
872
  /** RubyGems registry client. */
586
873
  var RubyGemsRegistry = class extends Registry {
587
874
  constructor(baseURL, client) {
@@ -598,23 +885,22 @@ var RubyGemsRegistry = class extends Registry {
598
885
  const url = `${this.baseURL}/api/v1/gems/${name}.json`;
599
886
  try {
600
887
  const data = await this.client.getJSON(url, signal);
601
- const licenses = data.licenses ? combineLicenses(data.licenses.map((l) => normalizeLicense(l))) : "";
602
- const repository = normalizeRepositoryURL(data.source_code_uri || data.metadata?.source_code_uri || data.homepage_uri || "");
888
+ const licenses = gemLicenses(data.licenses);
889
+ const repository = gemRepositoryURL(data.source_code_uri, data.metadata, data.homepage_uri);
603
890
  return {
604
891
  name: data.name,
605
- description: data.description || "",
606
- homepage: data.homepage_uri || "",
607
- documentation: data.documentation_uri || "",
892
+ description: orEmpty(data.description),
893
+ homepage: orEmpty(data.homepage_uri),
894
+ documentation: orEmpty(data.documentation_uri),
608
895
  repository,
609
896
  licenses,
610
897
  keywords: [],
611
898
  namespace: "",
612
- latestVersion: data.version || "",
613
- metadata: data.metadata || {}
899
+ latestVersion: orEmpty(data.version),
900
+ metadata: data.metadata ?? {}
614
901
  };
615
902
  } catch (error) {
616
- if (error instanceof HTTPError && error.isNotFound()) throw new NotFoundError("gem", name);
617
- throw error;
903
+ rethrowFetchError(error, this.ecosystem(), name);
618
904
  }
619
905
  }
620
906
  async fetchVersions(name, signal) {
@@ -636,8 +922,7 @@ var RubyGemsRegistry = class extends Registry {
636
922
  }
637
923
  return versions;
638
924
  } catch (error) {
639
- if (error instanceof HTTPError && error.isNotFound()) throw new NotFoundError("gem", name);
640
- throw error;
925
+ rethrowFetchError(error, this.ecosystem(), name);
641
926
  }
642
927
  }
643
928
  async fetchDependencies(name, version, signal) {
@@ -659,8 +944,7 @@ var RubyGemsRegistry = class extends Registry {
659
944
  });
660
945
  return dependencies;
661
946
  } catch (error) {
662
- if (error instanceof HTTPError && error.isNotFound()) throw new NotFoundError("gem", name, version);
663
- throw error;
947
+ rethrowFetchError(error, this.ecosystem(), name, version);
664
948
  }
665
949
  }
666
950
  async fetchMaintainers(name, signal) {
@@ -678,8 +962,7 @@ var RubyGemsRegistry = class extends Registry {
678
962
  });
679
963
  return maintainers;
680
964
  } catch (error) {
681
- if (error instanceof HTTPError && error.isNotFound()) throw new NotFoundError("gem", name);
682
- throw error;
965
+ rethrowFetchError(error, this.ecosystem(), name);
683
966
  }
684
967
  }
685
968
  urls() {
@@ -735,16 +1018,15 @@ var PackagistRegistry = class extends Registry {
735
1018
  description: packageData.description || "",
736
1019
  homepage: "",
737
1020
  documentation: "",
738
- repository: normalizeRepositoryURL(packageData.repository || latestVersionData.source?.url || ""),
1021
+ repository: normalizeRepositoryURL(packageData.repository ? packageData.repository : latestVersionData.source?.url ?? ""),
739
1022
  licenses,
740
- keywords: packageData.keywords || [],
1023
+ keywords: packageData.keywords ?? [],
741
1024
  namespace: vendor,
742
1025
  latestVersion,
743
1026
  metadata: {}
744
1027
  };
745
1028
  } catch (error) {
746
- if (error instanceof HTTPError && error.isNotFound()) throw new NotFoundError("composer", name);
747
- throw error;
1029
+ rethrowFetchError(error, this.ecosystem(), name);
748
1030
  }
749
1031
  }
750
1032
  async fetchVersions(name, signal) {
@@ -767,8 +1049,7 @@ var PackagistRegistry = class extends Registry {
767
1049
  }
768
1050
  return versions;
769
1051
  } catch (error) {
770
- if (error instanceof HTTPError && error.isNotFound()) throw new NotFoundError("composer", name);
771
- throw error;
1052
+ rethrowFetchError(error, this.ecosystem(), name);
772
1053
  }
773
1054
  }
774
1055
  async fetchDependencies(name, version, signal) {
@@ -796,8 +1077,7 @@ var PackagistRegistry = class extends Registry {
796
1077
  }
797
1078
  return dependencies;
798
1079
  } catch (error) {
799
- if (error instanceof HTTPError && error.isNotFound()) throw new NotFoundError("composer", name, version);
800
- throw error;
1080
+ rethrowFetchError(error, this.ecosystem(), name, version);
801
1081
  }
802
1082
  }
803
1083
  async fetchMaintainers(name, signal) {
@@ -805,26 +1085,9 @@ var PackagistRegistry = class extends Registry {
805
1085
  const url = `${this.baseURL}/packages/${vendor}/${pkg}.json`;
806
1086
  try {
807
1087
  const data = await this.client.getJSON(url, signal);
808
- const maintainers = [];
809
- const seen = /* @__PURE__ */ new Set();
810
- for (const versionData of Object.values(data.package.versions)) if (versionData.authors) for (const author of versionData.authors) {
811
- const key = `${author.name}:${author.email}`;
812
- if (!seen.has(key)) {
813
- seen.add(key);
814
- maintainers.push({
815
- uuid: "",
816
- login: author.email ? author.email.split("@")[0] : "",
817
- name: author.name || "",
818
- email: author.email || "",
819
- url: author.homepage || "",
820
- role: author.role || ""
821
- });
822
- }
823
- }
824
- return maintainers;
1088
+ return this.collectMaintainers(Object.values(data.package.versions));
825
1089
  } catch (error) {
826
- if (error instanceof HTTPError && error.isNotFound()) throw new NotFoundError("composer", name);
827
- throw error;
1090
+ rethrowFetchError(error, this.ecosystem(), name);
828
1091
  }
829
1092
  }
830
1093
  urls() {
@@ -857,13 +1120,32 @@ var PackagistRegistry = class extends Registry {
857
1120
  }
858
1121
  };
859
1122
  }
860
- /** Parse "vendor/package" format. */
1123
+ collectMaintainers(versions) {
1124
+ const maintainers = [];
1125
+ const seen = /* @__PURE__ */ new Set();
1126
+ for (const version of versions) for (const author of version.authors ?? []) {
1127
+ const key = `${author.name}:${author.email}`;
1128
+ if (seen.has(key)) continue;
1129
+ seen.add(key);
1130
+ maintainers.push(this.toMaintainer(author));
1131
+ }
1132
+ return maintainers;
1133
+ }
1134
+ toMaintainer(author) {
1135
+ return {
1136
+ uuid: "",
1137
+ login: author.email ? author.email.split("@")[0] : "",
1138
+ name: author.name || "",
1139
+ email: author.email || "",
1140
+ url: author.homepage || "",
1141
+ role: author.role || ""
1142
+ };
1143
+ }
861
1144
  parseName(name) {
862
1145
  const parts = name.split("/");
863
1146
  if (parts.length !== 2 || !parts[0] || !parts[1]) throw new InvalidPURLError(`pkg:composer/${name}`, "invalid Composer package name, expected \"vendor/package\" format");
864
1147
  return [parts[0], parts[1]];
865
1148
  }
866
- /** Find the highest non-dev version. */
867
1149
  findLatestVersion(versions) {
868
1150
  const stable = versions.filter((v) => !v.startsWith("dev-") && !v.endsWith("-dev"));
869
1151
  if (stable.length === 0) return versions[0] || "";
@@ -880,12 +1162,10 @@ var PackagistRegistry = class extends Registry {
880
1162
  return 0;
881
1163
  })[0] || versions[0] || "";
882
1164
  }
883
- /** Extract and normalize licenses. */
884
1165
  extractLicenses(raw) {
885
1166
  if (!raw) return "";
886
1167
  return combineLicenses(Array.isArray(raw) ? raw : [raw]);
887
1168
  }
888
- /** Skip PHP and extension dependencies. */
889
1169
  shouldSkipDependency(name) {
890
1170
  return name === "php" || name.startsWith("ext-");
891
1171
  }
@@ -1017,17 +1297,14 @@ var AlpmRegistry = class extends Registry {
1017
1297
  role: "maintainer"
1018
1298
  }));
1019
1299
  }
1020
- /** Search official repos by exact package name. Prefers x86_64 results. */
1021
1300
  async searchOfficial(name, signal) {
1022
1301
  const url = `${this.baseURL}/packages/search/json/?name=${encodeURIComponent(name)}`;
1023
1302
  try {
1024
1303
  const data = await this.client.getJSON(url, signal);
1025
1304
  if (!data.results || data.results.length === 0) throw new NotFoundError("alpm", name);
1026
- return data.results.find((r) => r.arch === "x86_64") || data.results[0];
1305
+ return data.results.find((r) => r.arch === "x86_64") ?? data.results[0];
1027
1306
  } catch (error) {
1028
- if (error instanceof NotFoundError) throw error;
1029
- if (error instanceof HTTPError && error.isNotFound()) throw new NotFoundError("alpm", name);
1030
- throw error;
1307
+ rethrowFetchError(error, this.ecosystem(), name);
1031
1308
  }
1032
1309
  }
1033
1310
  async fetchAurPackage(name, signal) {
@@ -1038,8 +1315,8 @@ var AlpmRegistry = class extends Registry {
1038
1315
  homepage: result.URL || "",
1039
1316
  documentation: "",
1040
1317
  repository: "",
1041
- licenses: combineLicenses(result.License || []),
1042
- keywords: result.Keywords || [],
1318
+ licenses: combineLicenses(result.License ?? []),
1319
+ keywords: result.Keywords ?? [],
1043
1320
  namespace: "aur",
1044
1321
  latestVersion: result.Version,
1045
1322
  metadata: {
@@ -1057,7 +1334,7 @@ var AlpmRegistry = class extends Registry {
1057
1334
  return [{
1058
1335
  number: result.Version,
1059
1336
  publishedAt: result.LastModified ? /* @__PURE__ */ new Date(result.LastModified * 1e3) : null,
1060
- licenses: combineLicenses(result.License || []),
1337
+ licenses: combineLicenses(result.License ?? []),
1061
1338
  integrity: "",
1062
1339
  status: result.OutOfDate ? "deprecated" : "",
1063
1340
  metadata: {
@@ -1069,7 +1346,7 @@ var AlpmRegistry = class extends Registry {
1069
1346
  async fetchAurDependencies(name, version, signal) {
1070
1347
  const result = await this.fetchAurInfo(name, signal);
1071
1348
  this.assertVersionMatches(`aur/${name}`, version, result.Version);
1072
- return this.buildDependencies(result.Depends || [], result.MakeDepends || [], result.OptDepends || [], result.CheckDepends || []);
1349
+ return this.buildDependencies(result.Depends ?? [], result.MakeDepends ?? [], result.OptDepends ?? [], result.CheckDepends ?? []);
1073
1350
  }
1074
1351
  async fetchAurMaintainers(name, signal) {
1075
1352
  const result = await this.fetchAurInfo(name, signal);
@@ -1083,7 +1360,6 @@ var AlpmRegistry = class extends Registry {
1083
1360
  role: "maintainer"
1084
1361
  }];
1085
1362
  }
1086
- /** Fetch package info from AUR RPC v5. */
1087
1363
  async fetchAurInfo(name, signal) {
1088
1364
  const url = `${AUR_BASE_URL}/rpc/v5/info/${encodeURIComponent(name)}`;
1089
1365
  try {
@@ -1091,12 +1367,9 @@ var AlpmRegistry = class extends Registry {
1091
1367
  if (!data.results || data.results.length === 0) throw new NotFoundError("alpm", `aur/${name}`);
1092
1368
  return data.results[0];
1093
1369
  } catch (error) {
1094
- if (error instanceof NotFoundError) throw error;
1095
- if (error instanceof HTTPError && error.isNotFound()) throw new NotFoundError("alpm", `aur/${name}`);
1096
- throw error;
1370
+ rethrowFetchError(error, this.ecosystem(), `aur/${name}`);
1097
1371
  }
1098
1372
  }
1099
- /** Parse "namespace/name" into components. Defaults to "arch" namespace. */
1100
1373
  parseName(fullName) {
1101
1374
  const normalized = fullName.trim().toLowerCase();
1102
1375
  const slashIdx = normalized.indexOf("/");
@@ -1115,11 +1388,9 @@ var AlpmRegistry = class extends Registry {
1115
1388
  assertVersionMatches(name, requested, current) {
1116
1389
  if (requested && requested !== current) throw new NotFoundError("alpm", name, requested);
1117
1390
  }
1118
- /** Format ALPM version: epoch:pkgver-pkgrel (epoch omitted when 0). */
1119
1391
  formatVersion(result) {
1120
1392
  return `${result.epoch && result.epoch > 0 ? `${result.epoch}:` : ""}${result.pkgver}-${result.pkgrel}`;
1121
1393
  }
1122
- /** Build normalized dependency list from ALPM dependency arrays. */
1123
1394
  buildDependencies(depends, makedepends, optdepends, checkdepends) {
1124
1395
  const dependencies = [];
1125
1396
  for (const dep of depends) {
@@ -1160,7 +1431,6 @@ var AlpmRegistry = class extends Registry {
1160
1431
  }
1161
1432
  return dependencies;
1162
1433
  }
1163
- /** Parse a dependency string like "glibc>=2.33" or "bash". */
1164
1434
  parseDep(dep) {
1165
1435
  const match = dep.match(/^([a-zA-Z0-9@._+-]+)(.*)$/);
1166
1436
  if (!match) return {
@@ -1172,7 +1442,6 @@ var AlpmRegistry = class extends Registry {
1172
1442
  requirements: match[2].trim()
1173
1443
  };
1174
1444
  }
1175
- /** Parse optional dependency like "perl-locale-gettext: translation support". */
1176
1445
  parseOptDep(dep) {
1177
1446
  const colonIdx = dep.indexOf(":");
1178
1447
  if (colonIdx === -1) {
@@ -1192,6 +1461,6 @@ var AlpmRegistry = class extends Registry {
1192
1461
  }
1193
1462
  };
1194
1463
  register("alpm", "https://archlinux.org", AlpmRegistry);
1195
- export { CargoRegistry as a, PyPIRegistry as i, PackagistRegistry as n, NpmRegistry as o, RubyGemsRegistry as r, AlpmRegistry as t };
1464
+ export { CargoRegistry as a, createFromPURL as c, normalizeRepositoryURL as d, combineLicenses as f, PyPIRegistry as i, fullName as l, PackagistRegistry as n, NpmRegistry as o, normalizeLicense as p, RubyGemsRegistry as r, buildPURL as s, AlpmRegistry as t, parsePURL as u };
1196
1465
 
1197
1466
  //# sourceMappingURL=registries.mjs.map