@agntn/registries 0.2.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.
- package/README.md +26 -3
- package/dist/THIRD-PARTY-LICENSES.md +36 -0
- package/dist/_chunks/cache.mjs +15 -0
- package/dist/{cache/index.mjs.map → _chunks/cache.mjs.map} +1 -1
- package/dist/{commands/cache.mjs → _chunks/cache2.mjs} +6 -8
- package/dist/_chunks/cache2.mjs.map +1 -0
- package/dist/{cache → _chunks}/cached-registry.mjs +12 -8
- package/dist/_chunks/cached-registry.mjs.map +1 -0
- package/dist/{commands → _chunks}/deps.mjs +20 -14
- package/dist/_chunks/deps.mjs.map +1 -0
- package/dist/_chunks/helpers.mjs +53 -19
- package/dist/_chunks/helpers.mjs.map +1 -1
- package/dist/_chunks/index.d.mts +9 -23
- package/dist/_chunks/index.d.mts.map +1 -1
- package/dist/_chunks/index2.d.mts +156 -25
- package/dist/_chunks/index2.d.mts.map +1 -1
- package/dist/_chunks/index3.d.mts +173 -3
- package/dist/_chunks/index3.d.mts.map +1 -1
- package/dist/_chunks/info.mjs +105 -0
- package/dist/_chunks/info.mjs.map +1 -0
- package/dist/_chunks/libs/typebox.mjs +278 -0
- package/dist/_chunks/libs/typebox.mjs.map +1 -0
- package/dist/_chunks/lockfile.mjs +183 -0
- package/dist/_chunks/lockfile.mjs.map +1 -0
- package/dist/_chunks/maintainers.mjs +53 -0
- package/dist/_chunks/maintainers.mjs.map +1 -0
- package/dist/_chunks/mcp.mjs +15 -0
- package/dist/_chunks/mcp.mjs.map +1 -0
- package/dist/_chunks/registries.mjs +514 -245
- package/dist/_chunks/registries.mjs.map +1 -1
- package/dist/_chunks/registry.d.mts +121 -10
- package/dist/_chunks/registry.d.mts.map +1 -1
- package/dist/_chunks/registry.mjs +121 -31
- package/dist/_chunks/registry.mjs.map +1 -1
- package/dist/{commands → _chunks}/shared.mjs +15 -8
- package/dist/_chunks/shared.mjs.map +1 -0
- package/dist/_chunks/tool-operations.mjs +63 -0
- package/dist/_chunks/tool-operations.mjs.map +1 -0
- package/dist/_chunks/version.mjs +4 -0
- package/dist/_chunks/version.mjs.map +1 -0
- package/dist/{commands → _chunks}/versions.mjs +10 -9
- package/dist/_chunks/versions.mjs.map +1 -0
- package/dist/ai.d.mts +2 -8
- package/dist/ai.d.mts.map +1 -1
- package/dist/ai.mjs +0 -1
- package/dist/ai.mjs.map +1 -1
- package/dist/cache/index.d.mts +1 -6
- package/dist/cache/index.mjs +4 -11
- package/dist/cli.mjs +9 -7
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +3 -9
- package/dist/index.mjs +4 -7
- package/dist/mcp.d.mts +7 -0
- package/dist/mcp.d.mts.map +1 -0
- package/dist/mcp.mjs +155 -0
- package/dist/mcp.mjs.map +1 -0
- package/dist/registries/index.d.mts +0 -1
- package/dist/registries/index.mjs +0 -1
- package/dist/tool-operations.d.mts +36 -0
- package/dist/tool-operations.d.mts.map +1 -0
- package/dist/tool-operations.mjs +5 -0
- package/package.json +55 -9
- package/packages/omp/extensions/registries.ts +112 -0
- package/packages/pi/extensions/registries.ts +125 -0
- package/dist/_chunks/cached-registry.d.mts +0 -35
- package/dist/_chunks/cached-registry.d.mts.map +0 -1
- package/dist/_chunks/errors.d.mts +0 -90
- package/dist/_chunks/errors.d.mts.map +0 -1
- package/dist/_chunks/lockfile.d.mts +0 -50
- package/dist/_chunks/lockfile.d.mts.map +0 -1
- package/dist/_chunks/paths.d.mts +0 -11
- package/dist/_chunks/paths.d.mts.map +0 -1
- package/dist/_chunks/purl.mjs +0 -192
- package/dist/_chunks/purl.mjs.map +0 -1
- package/dist/_chunks/storage.d.mts +0 -22
- package/dist/_chunks/storage.d.mts.map +0 -1
- package/dist/_chunks/types.d.mts +0 -73
- package/dist/_chunks/types.d.mts.map +0 -1
- package/dist/cache/cached-registry.d.mts +0 -4
- package/dist/cache/cached-registry.mjs.map +0 -1
- package/dist/cache/lockfile.d.mts +0 -2
- package/dist/cache/lockfile.mjs +0 -68
- package/dist/cache/lockfile.mjs.map +0 -1
- package/dist/cache/paths.d.mts +0 -2
- package/dist/cache/paths.mjs +0 -26
- package/dist/cache/paths.mjs.map +0 -1
- package/dist/cache/storage.d.mts +0 -2
- package/dist/cache/storage.mjs +0 -40
- package/dist/cache/storage.mjs.map +0 -1
- package/dist/commands/cache.d.mts +0 -7
- package/dist/commands/cache.d.mts.map +0 -1
- package/dist/commands/cache.mjs.map +0 -1
- package/dist/commands/deps.d.mts +0 -23
- package/dist/commands/deps.d.mts.map +0 -1
- package/dist/commands/deps.mjs.map +0 -1
- package/dist/commands/info.d.mts +0 -26
- package/dist/commands/info.d.mts.map +0 -1
- package/dist/commands/info.mjs +0 -54
- package/dist/commands/info.mjs.map +0 -1
- package/dist/commands/maintainers.d.mts +0 -23
- package/dist/commands/maintainers.d.mts.map +0 -1
- package/dist/commands/maintainers.mjs +0 -46
- package/dist/commands/maintainers.mjs.map +0 -1
- package/dist/commands/shared.d.mts +0 -27
- package/dist/commands/shared.d.mts.map +0 -1
- package/dist/commands/shared.mjs.map +0 -1
- package/dist/commands/versions.d.mts +0 -31
- package/dist/commands/versions.d.mts.map +0 -1
- package/dist/commands/versions.mjs.map +0 -1
- package/dist/core/index.d.mts +0 -4
- package/dist/core/index.mjs +0 -3
- package/dist/types.d.mts +0 -3
- package/dist/types.mjs +0 -1
|
@@ -1,5 +1,74 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
//#region src/core/types.d.ts
|
|
2
|
+
/** Normalized package metadata returned by any registry. */
|
|
3
|
+
interface Package {
|
|
4
|
+
name: string;
|
|
5
|
+
description: string;
|
|
6
|
+
homepage: string;
|
|
7
|
+
documentation: string;
|
|
8
|
+
repository: string;
|
|
9
|
+
licenses: string;
|
|
10
|
+
keywords: string[];
|
|
11
|
+
namespace: string;
|
|
12
|
+
latestVersion: string;
|
|
13
|
+
metadata: Record<string, unknown>;
|
|
14
|
+
}
|
|
15
|
+
/** A single version entry of a package. */
|
|
16
|
+
interface Version {
|
|
17
|
+
number: string;
|
|
18
|
+
publishedAt: Date | null;
|
|
19
|
+
licenses: string;
|
|
20
|
+
integrity: string;
|
|
21
|
+
status: VersionStatus;
|
|
22
|
+
metadata: Record<string, unknown>;
|
|
23
|
+
}
|
|
24
|
+
/** Dependency of a package version. */
|
|
25
|
+
interface Dependency {
|
|
26
|
+
name: string;
|
|
27
|
+
requirements: string;
|
|
28
|
+
scope: Scope;
|
|
29
|
+
optional: boolean;
|
|
30
|
+
}
|
|
31
|
+
/** Maintainer / author of a package. */
|
|
32
|
+
interface Maintainer {
|
|
33
|
+
uuid: string;
|
|
34
|
+
login: string;
|
|
35
|
+
name: string;
|
|
36
|
+
email: string;
|
|
37
|
+
url: string;
|
|
38
|
+
role: string;
|
|
39
|
+
}
|
|
40
|
+
type VersionStatus = "" | "yanked" | "deprecated" | "retracted";
|
|
41
|
+
type Scope = "runtime" | "development" | "test" | "build" | "optional";
|
|
42
|
+
/** URL builder for a specific registry. */
|
|
43
|
+
interface URLBuilder {
|
|
44
|
+
registry(name: string, version?: string): string;
|
|
45
|
+
download(name: string, version: string): string;
|
|
46
|
+
documentation(name: string, version?: string): string;
|
|
47
|
+
readme(name: string, version?: string): string;
|
|
48
|
+
purl(name: string, version?: string): string;
|
|
49
|
+
}
|
|
50
|
+
/** Options for the HTTP client. */
|
|
51
|
+
interface ClientOptions {
|
|
52
|
+
maxRetries?: number;
|
|
53
|
+
baseDelay?: number;
|
|
54
|
+
timeout?: number;
|
|
55
|
+
rateLimiter?: RateLimiter;
|
|
56
|
+
userAgent?: string;
|
|
57
|
+
}
|
|
58
|
+
/** Rate limiter interface — bring your own implementation. */
|
|
59
|
+
interface RateLimiter {
|
|
60
|
+
wait(signal?: AbortSignal): Promise<void>;
|
|
61
|
+
}
|
|
62
|
+
/** Parsed PURL components. */
|
|
63
|
+
interface ParsedPURL {
|
|
64
|
+
type: string;
|
|
65
|
+
namespace: string;
|
|
66
|
+
name: string;
|
|
67
|
+
version: string;
|
|
68
|
+
qualifiers: Record<string, string>;
|
|
69
|
+
subpath: string;
|
|
70
|
+
}
|
|
71
|
+
//#endregion
|
|
3
72
|
//#region src/core/client.d.ts
|
|
4
73
|
/** HTTP client with retry, backoff, rate limiting, and timeout. */
|
|
5
74
|
declare class Client {
|
|
@@ -10,10 +79,30 @@ declare class Client {
|
|
|
10
79
|
private readonly rateLimiter;
|
|
11
80
|
private readonly fetch;
|
|
12
81
|
constructor(options?: ClientOptions);
|
|
13
|
-
/**
|
|
14
|
-
|
|
82
|
+
/**
|
|
83
|
+
* Fetch JSON with retry and rate limiting.
|
|
84
|
+
*
|
|
85
|
+
* @param url - Request URL.
|
|
86
|
+
* @param signal - Optional cancellation signal.
|
|
87
|
+
* @param headers - Optional request headers.
|
|
88
|
+
* @returns {Promise<T>} The decoded response body.
|
|
89
|
+
*/
|
|
90
|
+
getJSON<T>(url: string, signal?: AbortSignal, headers?: Readonly<Record<string, string>>): Promise<T>;
|
|
91
|
+
/**
|
|
92
|
+
* ofetch drops its timeout once a signal is supplied, sleeps through backoff with a plain
|
|
93
|
+
* timer and retries after an abort, so the request owns all three here.
|
|
94
|
+
*
|
|
95
|
+
* @param signal - Optional cancellation signal.
|
|
96
|
+
* @returns {FetchOptions} The request hooks that own timeout, backoff and cancellation.
|
|
97
|
+
*/
|
|
98
|
+
private retryControl;
|
|
99
|
+
private attemptSignal;
|
|
15
100
|
}
|
|
16
|
-
/**
|
|
101
|
+
/**
|
|
102
|
+
* Get or create the shared HTTP client.
|
|
103
|
+
*
|
|
104
|
+
* @returns {Client} The shared client.
|
|
105
|
+
*/
|
|
17
106
|
declare function defaultClient(): Client;
|
|
18
107
|
//#endregion
|
|
19
108
|
//#region src/core/registry.d.ts
|
|
@@ -26,14 +115,36 @@ declare abstract class Registry {
|
|
|
26
115
|
abstract fetchMaintainers(name: string, signal?: AbortSignal): Promise<Maintainer[]>;
|
|
27
116
|
abstract urls(): URLBuilder;
|
|
28
117
|
}
|
|
29
|
-
/**
|
|
118
|
+
/**
|
|
119
|
+
* Register an ecosystem adapter.
|
|
120
|
+
*
|
|
121
|
+
* @param ecosystem - Ecosystem key.
|
|
122
|
+
* @param defaultURL - Default registry API URL.
|
|
123
|
+
* @param RegistryClass - Adapter constructor.
|
|
124
|
+
*/
|
|
30
125
|
declare function register(ecosystem: string, defaultURL: string, RegistryClass: new (baseURL: string, client: Client) => Registry): void;
|
|
31
|
-
/**
|
|
126
|
+
/**
|
|
127
|
+
* Create an adapter for a registered ecosystem.
|
|
128
|
+
*
|
|
129
|
+
* @param ecosystem - Ecosystem key.
|
|
130
|
+
* @param baseURL - Optional registry API URL override.
|
|
131
|
+
* @param client - Optional HTTP client.
|
|
132
|
+
* @returns {Registry} The registry adapter.
|
|
133
|
+
*/
|
|
32
134
|
declare function create(ecosystem: string, baseURL?: string, client?: Client): Registry;
|
|
33
|
-
/**
|
|
135
|
+
/**
|
|
136
|
+
* List registered ecosystems.
|
|
137
|
+
*
|
|
138
|
+
* @returns {string[]} The ecosystem keys.
|
|
139
|
+
*/
|
|
34
140
|
declare function ecosystems(): string[];
|
|
35
|
-
/**
|
|
141
|
+
/**
|
|
142
|
+
* Check whether an ecosystem is registered.
|
|
143
|
+
*
|
|
144
|
+
* @param ecosystem - Ecosystem key.
|
|
145
|
+
* @returns {boolean} Whether an adapter is registered.
|
|
146
|
+
*/
|
|
36
147
|
declare function has(ecosystem: string): boolean;
|
|
37
148
|
//#endregion
|
|
38
|
-
export { register as a, has as i, create as n, Client as o, ecosystems as r, defaultClient as s, Registry as t };
|
|
149
|
+
export { VersionStatus as _, register as a, ClientOptions as c, Package as d, ParsedPURL as f, Version as g, URLBuilder as h, has as i, Dependency as l, Scope as m, create as n, Client as o, RateLimiter as p, ecosystems as r, defaultClient as s, Registry as t, Maintainer as u };
|
|
39
150
|
//# sourceMappingURL=registry.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.d.mts","names":[],"sources":["../../src/core/client.ts","../../src/core/registry.ts"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"registry.d.mts","names":[],"sources":["../../src/core/types.ts","../../src/core/client.ts","../../src/core/registry.ts"],"mappings":";;UACiB,OAAA;EACf,IAAA;EACA,WAAA;EACA,QAAA;EACA,aAAA;EACA,UAAA;EACA,QAAA;EACA,QAAA;EACA,SAAA;EACA,aAAA;EACA,QAAA,EAAU,MAAA;AAAA;;UAIK,OAAA;EACf,MAAA;EACA,WAAA,EAAa,IAAA;EACb,QAAA;EACA,SAAA;EACA,MAAA,EAAQ,aAAA;EACR,QAAA,EAAU,MAAA;AAAA;;UAIK,UAAA;EACf,IAAA;EACA,YAAA;EACA,KAAA,EAAO,KAAA;EACP,QAAA;AAAA;;UAIe,UAAA;EACf,IAAA;EACA,KAAA;EACA,IAAA;EACA,KAAA;EACA,GAAA;EACA,IAAA;AAAA;AAAA,KAGU,aAAA;AAAA,KACA,KAAA;;UAGK,UAAA;EACf,QAAA,CAAS,IAAA,UAAc,OAAA;EACvB,QAAA,CAAS,IAAA,UAAc,OAAA;EACvB,aAAA,CAAc,IAAA,UAAc,OAAA;EAC5B,MAAA,CAAO,IAAA,UAAc,OAAA;EACrB,IAAA,CAAK,IAAA,UAAc,OAAA;AAAA;AAlBrB;AAAA,UAsBiB,aAAA;EACf,UAAA;EACA,SAAA;EACA,OAAA;EACA,WAAA,GAAc,WAAA;EACd,SAAA;AAAA;;UAIe,WAAA;EACf,IAAA,CAAK,MAAA,GAAS,WAAA,GAAc,OAAA;AAAA;AAvB9B;AAAA,UA2BiB,UAAA;EACf,IAAA;EACA,SAAA;EACA,IAAA;EACA,OAAA;EACA,UAAA,EAAY,MAAA;EACZ,OAAA;AAAA;;;;cCdW,MAAA;EAAA,SACF,UAAA;EAAA,SACA,SAAA;EAAA,SACA,OAAA;EAAA,SACA,SAAA;EAAA,iBACQ,WAAA;EAAA,iBACA,KAAA;cAEL,OAAA,GAAS,aAAA;EDhDL;;;;;;;;ECyEV,OAAA,GAAA,CACJ,GAAA,UACA,MAAA,GAAS,WAAA,EACT,OAAA,GAAU,QAAA,CAAS,MAAA,oBAClB,OAAA,CAAQ,CAAA;ED7ED;;;AAIZ;;;;EAJY,QCyGF,YAAA;EAAA,QAqCA,aAAA;AAAA;;;;;ADlIV;iBCuKgB,aAAA,CAAA,GAAiB,MAAA;;;;uBClMX,QAAA;EAAA,SACX,SAAA,CAAA;EAAA,SACA,YAAA,CAAa,IAAA,UAAc,MAAA,GAAS,WAAA,GAAc,OAAA,CAAQ,OAAA;EAAA,SAC1D,aAAA,CAAc,IAAA,UAAc,MAAA,GAAS,WAAA,GAAc,OAAA,CAAQ,OAAA;EAAA,SAC3D,iBAAA,CACP,IAAA,UACA,OAAA,UACA,MAAA,GAAS,WAAA,GACR,OAAA,CAAQ,UAAA;EAAA,SACF,gBAAA,CAAiB,IAAA,UAAc,MAAA,GAAS,WAAA,GAAc,OAAA,CAAQ,UAAA;EAAA,SAC9D,IAAA,CAAA,GAAQ,UAAA;AAAA;;;;;;;;iBAaH,QAAA,CACd,SAAA,UACA,UAAA,UACA,aAAA,OAAoB,OAAA,UAAiB,MAAA,EAAQ,MAAA,KAAW,QAAA;;;;;;;;;iBAc1C,MAAA,CAAO,SAAA,UAAmB,OAAA,WAAkB,MAAA,GAAS,MAAA,GAAS,QAAA;;;;;;iBAa9D,UAAA,CAAA;;;;AFlChB;;;iBE4CgB,GAAA,CAAI,SAAA"}
|
|
@@ -69,6 +69,9 @@ const DEFAULT_BASE_DELAY = 50;
|
|
|
69
69
|
const DEFAULT_TIMEOUT = 3e4;
|
|
70
70
|
const DEFAULT_USER_AGENT = "registries/0.1.0";
|
|
71
71
|
const MAX_TIMER_DELAY = 2147483647;
|
|
72
|
+
function retryCount(value) {
|
|
73
|
+
return typeof value === "number" ? value : 0;
|
|
74
|
+
}
|
|
72
75
|
function parseRetryAfterValue(header) {
|
|
73
76
|
if (!header) return void 0;
|
|
74
77
|
const trimmed = header.trim();
|
|
@@ -83,18 +86,21 @@ function parseRetryAfterValue(header) {
|
|
|
83
86
|
return Math.max(seconds, 0);
|
|
84
87
|
}
|
|
85
88
|
/**
|
|
86
|
-
* Parse a
|
|
87
|
-
*
|
|
88
|
-
* Handles two formats (RFC 7231 §7.1.3):
|
|
89
|
-
* - **Numeric**: `"120"` → 120
|
|
90
|
-
* - **HTTP-date**: `"Wed, 21 Oct 2025 07:28:00 GMT"` → seconds until that time
|
|
89
|
+
* Parse a Retry-After header into seconds, defaulting to 60.
|
|
91
90
|
*
|
|
92
|
-
*
|
|
91
|
+
* @param header - Numeric seconds or an HTTP date.
|
|
92
|
+
* @returns {number} The retry delay in seconds.
|
|
93
93
|
*/
|
|
94
94
|
function parseRetryAfter(header) {
|
|
95
95
|
return parseRetryAfterValue(header) ?? 60;
|
|
96
96
|
}
|
|
97
|
-
/**
|
|
97
|
+
/**
|
|
98
|
+
* Apply a valid Retry-After value or reject an unschedulable delay.
|
|
99
|
+
*
|
|
100
|
+
* @param header - Numeric seconds or an HTTP date.
|
|
101
|
+
* @param fallbackDelay - Delay in milliseconds used without a valid header.
|
|
102
|
+
* @returns {number} A timer-safe delay in milliseconds.
|
|
103
|
+
*/
|
|
98
104
|
function retryDelayFor(header, fallbackDelay) {
|
|
99
105
|
const retryAfter = parseRetryAfterValue(header);
|
|
100
106
|
if (retryAfter === void 0) return fallbackDelay;
|
|
@@ -116,22 +122,8 @@ var Client = class {
|
|
|
116
122
|
this.timeout = options.timeout ?? DEFAULT_TIMEOUT;
|
|
117
123
|
this.userAgent = options.userAgent ?? DEFAULT_USER_AGENT;
|
|
118
124
|
this.rateLimiter = options.rateLimiter ?? null;
|
|
119
|
-
const maxRetries = this.maxRetries;
|
|
120
|
-
const baseDelay = this.baseDelay;
|
|
121
125
|
this.fetch = ofetch.create({
|
|
122
126
|
retry: this.maxRetries,
|
|
123
|
-
retryDelay(context) {
|
|
124
|
-
const attempt = maxRetries - (typeof context.options.retry === "number" ? context.options.retry : 0);
|
|
125
|
-
const delay = baseDelay * Math.pow(2, attempt - 1);
|
|
126
|
-
const jitteredDelay = delay + delay * Math.random() * .1;
|
|
127
|
-
const retryAfter = context.response?.headers.get("Retry-After");
|
|
128
|
-
try {
|
|
129
|
-
return retryDelayFor(retryAfter, jitteredDelay);
|
|
130
|
-
} catch (error) {
|
|
131
|
-
if (error instanceof RateLimitError && context.response?.status !== 429) throw new HTTPError(context.response?.status ?? 0, String(context.request), "");
|
|
132
|
-
throw error;
|
|
133
|
-
}
|
|
134
|
-
},
|
|
135
127
|
retryStatusCodes: [
|
|
136
128
|
408,
|
|
137
129
|
409,
|
|
@@ -142,20 +134,26 @@ var Client = class {
|
|
|
142
134
|
503,
|
|
143
135
|
504
|
|
144
136
|
],
|
|
145
|
-
timeout: this.timeout,
|
|
146
137
|
headers: {
|
|
147
138
|
Accept: "application/json",
|
|
148
139
|
"User-Agent": this.userAgent
|
|
149
140
|
}
|
|
150
141
|
});
|
|
151
142
|
}
|
|
152
|
-
/**
|
|
143
|
+
/**
|
|
144
|
+
* Fetch JSON with retry and rate limiting.
|
|
145
|
+
*
|
|
146
|
+
* @param url - Request URL.
|
|
147
|
+
* @param signal - Optional cancellation signal.
|
|
148
|
+
* @param headers - Optional request headers.
|
|
149
|
+
* @returns {Promise<T>} The decoded response body.
|
|
150
|
+
*/
|
|
153
151
|
async getJSON(url, signal, headers) {
|
|
154
152
|
if (this.rateLimiter) await this.rateLimiter.wait(signal);
|
|
155
153
|
try {
|
|
156
154
|
return await this.fetch(url, {
|
|
157
|
-
|
|
158
|
-
|
|
155
|
+
headers,
|
|
156
|
+
...this.retryControl(signal)
|
|
159
157
|
});
|
|
160
158
|
} catch (error) {
|
|
161
159
|
if (error instanceof FetchError) {
|
|
@@ -166,33 +164,125 @@ var Client = class {
|
|
|
166
164
|
throw error;
|
|
167
165
|
}
|
|
168
166
|
}
|
|
167
|
+
/**
|
|
168
|
+
* ofetch drops its timeout once a signal is supplied, sleeps through backoff with a plain
|
|
169
|
+
* timer and retries after an abort, so the request owns all three here.
|
|
170
|
+
*
|
|
171
|
+
* @param signal - Optional cancellation signal.
|
|
172
|
+
* @returns {FetchOptions} The request hooks that own timeout, backoff and cancellation.
|
|
173
|
+
*/
|
|
174
|
+
retryControl(signal) {
|
|
175
|
+
let pendingDelay = 0;
|
|
176
|
+
return {
|
|
177
|
+
signal,
|
|
178
|
+
retryDelay: (context) => {
|
|
179
|
+
const remaining = retryCount(context.options.retry);
|
|
180
|
+
const attempt = this.maxRetries - remaining;
|
|
181
|
+
const delay = this.baseDelay * Math.pow(2, attempt - 1);
|
|
182
|
+
const jitteredDelay = delay + delay * Math.random() * .1;
|
|
183
|
+
const retryAfter = context.response?.headers.get("Retry-After");
|
|
184
|
+
try {
|
|
185
|
+
pendingDelay = retryDelayFor(retryAfter, jitteredDelay);
|
|
186
|
+
} catch (error) {
|
|
187
|
+
if (error instanceof RateLimitError && context.response?.status !== 429) {
|
|
188
|
+
const requestURL = typeof context.request === "string" ? context.request : context.request.url;
|
|
189
|
+
throw new HTTPError(context.response?.status ?? 0, requestURL, "");
|
|
190
|
+
}
|
|
191
|
+
throw error;
|
|
192
|
+
}
|
|
193
|
+
return 0;
|
|
194
|
+
},
|
|
195
|
+
onRequest: async ({ options }) => {
|
|
196
|
+
if (pendingDelay > 0) {
|
|
197
|
+
await abortableDelay(pendingDelay, signal);
|
|
198
|
+
pendingDelay = 0;
|
|
199
|
+
}
|
|
200
|
+
options.signal = this.attemptSignal(signal);
|
|
201
|
+
},
|
|
202
|
+
onRequestError: ({ options }) => {
|
|
203
|
+
if (signal?.aborted) options.retry = false;
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
attemptSignal(signal) {
|
|
208
|
+
const active = [signal, this.timeout > 0 ? AbortSignal.timeout(this.timeout) : void 0].filter((candidate) => candidate !== void 0);
|
|
209
|
+
return active.length > 0 ? AbortSignal.any(active) : void 0;
|
|
210
|
+
}
|
|
169
211
|
};
|
|
212
|
+
/**
|
|
213
|
+
* Sleep that returns as soon as the signal fires, so an abort never waits out a Retry-After.
|
|
214
|
+
*
|
|
215
|
+
* @param milliseconds - Delay before resolving.
|
|
216
|
+
* @param signal - Optional cancellation signal that ends the wait early.
|
|
217
|
+
* @returns {Promise<void>} Resolution after the delay or the abort.
|
|
218
|
+
*/
|
|
219
|
+
function abortableDelay(milliseconds, signal) {
|
|
220
|
+
if (!signal) return new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
221
|
+
if (signal.aborted) return Promise.resolve();
|
|
222
|
+
return new Promise((resolve) => {
|
|
223
|
+
const onAbort = () => {
|
|
224
|
+
clearTimeout(timer);
|
|
225
|
+
resolve();
|
|
226
|
+
};
|
|
227
|
+
const timer = setTimeout(() => {
|
|
228
|
+
signal.removeEventListener("abort", onAbort);
|
|
229
|
+
resolve();
|
|
230
|
+
}, milliseconds);
|
|
231
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
232
|
+
});
|
|
233
|
+
}
|
|
170
234
|
let _defaultClient;
|
|
171
|
-
/**
|
|
235
|
+
/**
|
|
236
|
+
* Get or create the shared HTTP client.
|
|
237
|
+
*
|
|
238
|
+
* @returns {Client} The shared client.
|
|
239
|
+
*/
|
|
172
240
|
function defaultClient() {
|
|
173
|
-
|
|
241
|
+
_defaultClient ??= new Client();
|
|
174
242
|
return _defaultClient;
|
|
175
243
|
}
|
|
176
244
|
/** Common base for package registries and registry decorators. */
|
|
177
245
|
var Registry = class {};
|
|
178
246
|
const constructors = /* @__PURE__ */ new Map();
|
|
179
247
|
const defaults = /* @__PURE__ */ new Map();
|
|
180
|
-
/**
|
|
248
|
+
/**
|
|
249
|
+
* Register an ecosystem adapter.
|
|
250
|
+
*
|
|
251
|
+
* @param ecosystem - Ecosystem key.
|
|
252
|
+
* @param defaultURL - Default registry API URL.
|
|
253
|
+
* @param RegistryClass - Adapter constructor.
|
|
254
|
+
*/
|
|
181
255
|
function register(ecosystem, defaultURL, RegistryClass) {
|
|
182
256
|
constructors.set(ecosystem, RegistryClass);
|
|
183
257
|
defaults.set(ecosystem, defaultURL);
|
|
184
258
|
}
|
|
185
|
-
/**
|
|
259
|
+
/**
|
|
260
|
+
* Create an adapter for a registered ecosystem.
|
|
261
|
+
*
|
|
262
|
+
* @param ecosystem - Ecosystem key.
|
|
263
|
+
* @param baseURL - Optional registry API URL override.
|
|
264
|
+
* @param client - Optional HTTP client.
|
|
265
|
+
* @returns {Registry} The registry adapter.
|
|
266
|
+
*/
|
|
186
267
|
function create(ecosystem, baseURL, client) {
|
|
187
268
|
const RegistryClass = constructors.get(ecosystem);
|
|
188
269
|
if (!RegistryClass) throw new UnknownEcosystemError(ecosystem);
|
|
189
270
|
return new RegistryClass(baseURL || defaults.get(ecosystem), client ?? defaultClient());
|
|
190
271
|
}
|
|
191
|
-
/**
|
|
272
|
+
/**
|
|
273
|
+
* List registered ecosystems.
|
|
274
|
+
*
|
|
275
|
+
* @returns {string[]} The ecosystem keys.
|
|
276
|
+
*/
|
|
192
277
|
function ecosystems() {
|
|
193
278
|
return [...constructors.keys()];
|
|
194
279
|
}
|
|
195
|
-
/**
|
|
280
|
+
/**
|
|
281
|
+
* Check whether an ecosystem is registered.
|
|
282
|
+
*
|
|
283
|
+
* @param ecosystem - Ecosystem key.
|
|
284
|
+
* @returns {boolean} Whether an adapter is registered.
|
|
285
|
+
*/
|
|
196
286
|
function has(ecosystem) {
|
|
197
287
|
return constructors.has(ecosystem);
|
|
198
288
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.mjs","names":[],"sources":["../../src/core/errors.ts","../../src/core/client.ts","../../src/core/registry.ts"],"sourcesContent":["/** Custom error types for registry operations. */\n\nexport class PkioError extends Error {\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = \"PkioError\";\n }\n}\n\nexport class HTTPError extends PkioError {\n readonly statusCode: number;\n readonly url: string;\n readonly body: string;\n\n constructor(statusCode: number, url: string, body: string) {\n super(`HTTP ${statusCode}: ${url}`);\n this.name = \"HTTPError\";\n this.statusCode = statusCode;\n this.url = url;\n this.body = body;\n }\n\n isNotFound(): boolean {\n return this.statusCode === 404;\n }\n\n isRateLimit(): boolean {\n return this.statusCode === 429;\n }\n\n isServerError(): boolean {\n return this.statusCode >= 500;\n }\n}\n\nexport class NotFoundError extends PkioError {\n readonly ecosystem: string;\n readonly packageName: string;\n readonly version: string;\n\n constructor(ecosystem: string, packageName: string, version = \"\") {\n const versionSuffix = version ? `@${version}` : \"\";\n super(`Package not found: ${ecosystem}/${packageName}${versionSuffix}`);\n this.name = \"NotFoundError\";\n this.ecosystem = ecosystem;\n this.packageName = packageName;\n this.version = version;\n }\n}\n\nexport class RateLimitError extends PkioError {\n readonly retryAfter: number;\n\n constructor(retryAfter: number) {\n super(`Rate limited. Retry after ${retryAfter}s`);\n this.name = \"RateLimitError\";\n this.retryAfter = retryAfter;\n }\n}\n\nexport class UnknownEcosystemError extends PkioError {\n readonly ecosystem: string;\n\n constructor(ecosystem: string) {\n super(`Unknown ecosystem: ${ecosystem}`);\n this.name = \"UnknownEcosystemError\";\n this.ecosystem = ecosystem;\n }\n}\n\nexport class InvalidPURLError extends PkioError {\n readonly purl: string;\n\n constructor(purl: string, reason: string) {\n super(`Invalid PURL \"${purl}\": ${reason}`);\n this.name = \"InvalidPURLError\";\n this.purl = purl;\n }\n}\n","import { ofetch, FetchError } from \"ofetch\";\nimport type { $Fetch } from \"ofetch\";\nimport type { ClientOptions, RateLimiter } from \"./types.ts\";\nimport { HTTPError, RateLimitError } from \"./errors.ts\";\n\nconst DEFAULT_MAX_RETRIES = 5;\nconst DEFAULT_BASE_DELAY = 50;\nconst DEFAULT_TIMEOUT = 30_000;\nconst DEFAULT_USER_AGENT = \"registries/0.1.0\";\nconst MAX_TIMER_DELAY = 2_147_483_647;\n\nfunction parseRetryAfterValue(header: string | null | undefined): number | undefined {\n if (!header) return undefined;\n const trimmed = header.trim();\n if (!trimmed) return undefined;\n\n if (/^\\d+$/.test(trimmed)) {\n const seconds = Number(trimmed);\n return Number.isNaN(seconds) ? undefined : seconds;\n }\n\n const timestamp = /[a-z]/i.test(trimmed) ? Date.parse(trimmed) : NaN;\n if (Number.isNaN(timestamp)) return undefined;\n\n const seconds = Math.ceil((timestamp - Date.now()) / 1000);\n return Math.max(seconds, 0);\n}\n\n/**\n * Parse a `Retry-After` header into seconds.\n *\n * Handles two formats (RFC 7231 §7.1.3):\n * - **Numeric**: `\"120\"` → 120\n * - **HTTP-date**: `\"Wed, 21 Oct 2025 07:28:00 GMT\"` → seconds until that time\n *\n * Returns 60 when the header is absent, empty, or unparseable.\n */\nexport function parseRetryAfter(header: string | null | undefined): number {\n return parseRetryAfterValue(header) ?? 60;\n}\n\n/** Apply a valid, timer-safe `Retry-After` value or reject an unschedulable delay. */\nexport function retryDelayFor(header: string | null | undefined, fallbackDelay: number): number {\n const retryAfter = parseRetryAfterValue(header);\n if (retryAfter === undefined) return fallbackDelay;\n\n const retryAfterDelay = retryAfter * 1000;\n if (!Number.isFinite(retryAfterDelay) || retryAfterDelay > MAX_TIMER_DELAY) {\n throw new RateLimitError(retryAfter);\n }\n return Math.max(fallbackDelay, retryAfterDelay);\n}\n\n/** HTTP client with retry, backoff, rate limiting, and timeout. */\nexport class Client {\n readonly maxRetries: number;\n readonly baseDelay: number;\n readonly timeout: number;\n readonly userAgent: string;\n private readonly rateLimiter: RateLimiter | null;\n private readonly fetch: $Fetch;\n\n constructor(options: ClientOptions = {}) {\n this.maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;\n this.baseDelay = options.baseDelay ?? DEFAULT_BASE_DELAY;\n this.timeout = options.timeout ?? DEFAULT_TIMEOUT;\n this.userAgent = options.userAgent ?? DEFAULT_USER_AGENT;\n this.rateLimiter = options.rateLimiter ?? null;\n\n const maxRetries = this.maxRetries;\n const baseDelay = this.baseDelay;\n\n this.fetch = ofetch.create({\n retry: this.maxRetries,\n retryDelay(context) {\n const remaining = typeof context.options.retry === \"number\" ? context.options.retry : 0;\n const attempt = maxRetries - remaining;\n const delay = baseDelay * Math.pow(2, attempt - 1);\n const jitteredDelay = delay + delay * Math.random() * 0.1;\n const retryAfter = context.response?.headers.get(\"Retry-After\");\n try {\n return retryDelayFor(retryAfter, jitteredDelay);\n } catch (error) {\n if (error instanceof RateLimitError && context.response?.status !== 429) {\n throw new HTTPError(context.response?.status ?? 0, String(context.request), \"\");\n }\n throw error;\n }\n },\n retryStatusCodes: [408, 409, 425, 429, 500, 502, 503, 504],\n timeout: this.timeout,\n headers: {\n Accept: \"application/json\",\n \"User-Agent\": this.userAgent,\n },\n });\n }\n\n /** Fetch JSON from a URL with retry and rate limiting. */\n async getJSON<T>(\n url: string,\n signal?: AbortSignal,\n headers?: Record<string, string>,\n ): Promise<T> {\n if (this.rateLimiter) {\n await this.rateLimiter.wait(signal);\n }\n\n try {\n return await this.fetch<T>(url, { signal, headers });\n } catch (error) {\n if (error instanceof FetchError) {\n if (error.statusCode === 429) {\n throw new RateLimitError(parseRetryAfter(error.response?.headers.get(\"Retry-After\")));\n }\n\n const body = typeof error.data === \"string\" ? error.data : JSON.stringify(error.data ?? \"\");\n\n throw new HTTPError(error.statusCode ?? 0, url, body);\n }\n throw error;\n }\n }\n}\n\nlet _defaultClient: Client | undefined;\n\n/** Get or create the shared default client. */\nexport function defaultClient(): Client {\n if (!_defaultClient) {\n _defaultClient = new Client();\n }\n return _defaultClient;\n}\n","import type { Client } from \"./client.ts\";\nimport { defaultClient } from \"./client.ts\";\nimport { UnknownEcosystemError } from \"./errors.ts\";\nimport type { Dependency, Maintainer, Package, URLBuilder, Version } from \"./types.ts\";\n\n/** Common base for package registries and registry decorators. */\nexport abstract class Registry {\n abstract ecosystem(): string;\n abstract fetchPackage(name: string, signal?: AbortSignal): Promise<Package>;\n abstract fetchVersions(name: string, signal?: AbortSignal): Promise<Version[]>;\n abstract fetchDependencies(\n name: string,\n version: string,\n signal?: AbortSignal,\n ): Promise<Dependency[]>;\n abstract fetchMaintainers(name: string, signal?: AbortSignal): Promise<Maintainer[]>;\n abstract urls(): URLBuilder;\n}\n\nconst constructors = new Map<string, new (baseURL: string, client: Client) => Registry>();\nconst defaults = new Map<string, string>();\n\n/** Register an ecosystem class. Called by each registry module on import. */\nexport function register(\n ecosystem: string,\n defaultURL: string,\n RegistryClass: new (baseURL: string, client: Client) => Registry,\n): void {\n constructors.set(ecosystem, RegistryClass);\n defaults.set(ecosystem, defaultURL);\n}\n\n/** Create a registry instance for the given ecosystem. */\nexport function create(ecosystem: string, baseURL?: string, client?: Client): Registry {\n const RegistryClass = constructors.get(ecosystem);\n if (!RegistryClass) {\n throw new UnknownEcosystemError(ecosystem);\n }\n return new RegistryClass(baseURL || defaults.get(ecosystem)!, client ?? defaultClient());\n}\n\n/** List all registered ecosystem names. */\nexport function ecosystems(): string[] {\n return [...constructors.keys()];\n}\n\n/** Check if an ecosystem is registered. */\nexport function has(ecosystem: string): boolean {\n return constructors.has(ecosystem);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"registry.mjs","names":[],"sources":["../../src/core/errors.ts","../../src/core/client.ts","../../src/core/registry.ts"],"sourcesContent":["/** Custom error types for registry operations. */\n\nexport class PkioError extends Error {\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = \"PkioError\";\n }\n}\n\nexport class HTTPError extends PkioError {\n readonly statusCode: number;\n readonly url: string;\n readonly body: string;\n\n constructor(statusCode: number, url: string, body: string) {\n super(`HTTP ${statusCode}: ${url}`);\n this.name = \"HTTPError\";\n this.statusCode = statusCode;\n this.url = url;\n this.body = body;\n }\n\n isNotFound(): boolean {\n return this.statusCode === 404;\n }\n\n isRateLimit(): boolean {\n return this.statusCode === 429;\n }\n\n isServerError(): boolean {\n return this.statusCode >= 500;\n }\n}\n\nexport class NotFoundError extends PkioError {\n readonly ecosystem: string;\n readonly packageName: string;\n readonly version: string;\n\n constructor(ecosystem: string, packageName: string, version = \"\") {\n const versionSuffix = version ? `@${version}` : \"\";\n super(`Package not found: ${ecosystem}/${packageName}${versionSuffix}`);\n this.name = \"NotFoundError\";\n this.ecosystem = ecosystem;\n this.packageName = packageName;\n this.version = version;\n }\n}\n\nexport class RateLimitError extends PkioError {\n readonly retryAfter: number;\n\n constructor(retryAfter: number) {\n super(`Rate limited. Retry after ${retryAfter}s`);\n this.name = \"RateLimitError\";\n this.retryAfter = retryAfter;\n }\n}\n\nexport class UnknownEcosystemError extends PkioError {\n readonly ecosystem: string;\n\n constructor(ecosystem: string) {\n super(`Unknown ecosystem: ${ecosystem}`);\n this.name = \"UnknownEcosystemError\";\n this.ecosystem = ecosystem;\n }\n}\n\nexport class InvalidPURLError extends PkioError {\n readonly purl: string;\n\n constructor(purl: string, reason: string) {\n super(`Invalid PURL \"${purl}\": ${reason}`);\n this.name = \"InvalidPURLError\";\n this.purl = purl;\n }\n}\n","import { ofetch, FetchError } from \"ofetch\";\nimport type { $Fetch, FetchOptions } from \"ofetch\";\nimport type { ClientOptions, RateLimiter } from \"./types.ts\";\nimport { HTTPError, RateLimitError } from \"./errors.ts\";\n\nconst DEFAULT_MAX_RETRIES = 5;\nconst DEFAULT_BASE_DELAY = 50;\nconst DEFAULT_TIMEOUT = 30_000;\nconst DEFAULT_USER_AGENT = \"registries/0.1.0\";\nconst MAX_TIMER_DELAY = 2_147_483_647;\n\nfunction retryCount(value: unknown): number {\n return typeof value === \"number\" ? value : 0;\n}\n\nfunction parseRetryAfterValue(header: string | null | undefined): number | undefined {\n if (!header) return undefined;\n const trimmed = header.trim();\n if (!trimmed) return undefined;\n\n if (/^\\d+$/.test(trimmed)) {\n const seconds = Number(trimmed);\n return Number.isNaN(seconds) ? undefined : seconds;\n }\n\n const timestamp = /[a-z]/i.test(trimmed) ? Date.parse(trimmed) : NaN;\n if (Number.isNaN(timestamp)) return undefined;\n\n const seconds = Math.ceil((timestamp - Date.now()) / 1000);\n return Math.max(seconds, 0);\n}\n\n/**\n * Parse a Retry-After header into seconds, defaulting to 60.\n *\n * @param header - Numeric seconds or an HTTP date.\n * @returns {number} The retry delay in seconds.\n */\nexport function parseRetryAfter(header: string | null | undefined): number {\n return parseRetryAfterValue(header) ?? 60;\n}\n\n/**\n * Apply a valid Retry-After value or reject an unschedulable delay.\n *\n * @param header - Numeric seconds or an HTTP date.\n * @param fallbackDelay - Delay in milliseconds used without a valid header.\n * @returns {number} A timer-safe delay in milliseconds.\n */\nexport function retryDelayFor(header: string | null | undefined, fallbackDelay: number): number {\n const retryAfter = parseRetryAfterValue(header);\n if (retryAfter === undefined) return fallbackDelay;\n\n const retryAfterDelay = retryAfter * 1000;\n if (!Number.isFinite(retryAfterDelay) || retryAfterDelay > MAX_TIMER_DELAY) {\n throw new RateLimitError(retryAfter);\n }\n return Math.max(fallbackDelay, retryAfterDelay);\n}\n\n/** HTTP client with retry, backoff, rate limiting, and timeout. */\nexport class Client {\n readonly maxRetries: number;\n readonly baseDelay: number;\n readonly timeout: number;\n readonly userAgent: string;\n private readonly rateLimiter: RateLimiter | null;\n private readonly fetch: $Fetch;\n\n constructor(options: ClientOptions = {}) {\n this.maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;\n this.baseDelay = options.baseDelay ?? DEFAULT_BASE_DELAY;\n this.timeout = options.timeout ?? DEFAULT_TIMEOUT;\n this.userAgent = options.userAgent ?? DEFAULT_USER_AGENT;\n this.rateLimiter = options.rateLimiter ?? null;\n\n this.fetch = ofetch.create({\n retry: this.maxRetries,\n retryStatusCodes: [408, 409, 425, 429, 500, 502, 503, 504],\n headers: {\n Accept: \"application/json\",\n \"User-Agent\": this.userAgent,\n },\n });\n }\n\n /**\n * Fetch JSON with retry and rate limiting.\n *\n * @param url - Request URL.\n * @param signal - Optional cancellation signal.\n * @param headers - Optional request headers.\n * @returns {Promise<T>} The decoded response body.\n */\n async getJSON<T>(\n url: string,\n signal?: AbortSignal,\n headers?: Readonly<Record<string, string>>,\n ): Promise<T> {\n if (this.rateLimiter) {\n await this.rateLimiter.wait(signal);\n }\n\n try {\n return await this.fetch<T>(url, { headers, ...this.retryControl(signal) });\n } catch (error) {\n if (error instanceof FetchError) {\n if (error.statusCode === 429) {\n throw new RateLimitError(parseRetryAfter(error.response?.headers.get(\"Retry-After\")));\n }\n\n const body = typeof error.data === \"string\" ? error.data : JSON.stringify(error.data ?? \"\");\n\n throw new HTTPError(error.statusCode ?? 0, url, body);\n }\n throw error;\n }\n }\n\n /**\n * ofetch drops its timeout once a signal is supplied, sleeps through backoff with a plain\n * timer and retries after an abort, so the request owns all three here.\n *\n * @param signal - Optional cancellation signal.\n * @returns {FetchOptions} The request hooks that own timeout, backoff and cancellation.\n */\n private retryControl(\n signal?: AbortSignal,\n ): Pick<FetchOptions, \"signal\" | \"retryDelay\" | \"onRequest\" | \"onRequestError\"> {\n let pendingDelay = 0;\n return {\n signal,\n retryDelay: (context) => {\n const remaining = retryCount(context.options.retry);\n const attempt = this.maxRetries - remaining;\n const delay = this.baseDelay * Math.pow(2, attempt - 1);\n const jitteredDelay = delay + delay * Math.random() * 0.1;\n const retryAfter = context.response?.headers.get(\"Retry-After\");\n try {\n pendingDelay = retryDelayFor(retryAfter, jitteredDelay);\n } catch (error) {\n if (error instanceof RateLimitError && context.response?.status !== 429) {\n const requestURL =\n typeof context.request === \"string\" ? context.request : context.request.url;\n throw new HTTPError(context.response?.status ?? 0, requestURL, \"\");\n }\n throw error;\n }\n return 0;\n },\n onRequest: async ({ options }) => {\n if (pendingDelay > 0) {\n await abortableDelay(pendingDelay, signal);\n pendingDelay = 0;\n }\n options.signal = this.attemptSignal(signal);\n },\n onRequestError: ({ options }) => {\n if (signal?.aborted) options.retry = false;\n },\n };\n }\n\n private attemptSignal(signal?: AbortSignal): AbortSignal | undefined {\n const signals = [signal, this.timeout > 0 ? AbortSignal.timeout(this.timeout) : undefined];\n const active = signals.filter((candidate) => candidate !== undefined);\n return active.length > 0 ? AbortSignal.any(active) : undefined;\n }\n}\n\n/**\n * Sleep that returns as soon as the signal fires, so an abort never waits out a Retry-After.\n *\n * @param milliseconds - Delay before resolving.\n * @param signal - Optional cancellation signal that ends the wait early.\n * @returns {Promise<void>} Resolution after the delay or the abort.\n */\nfunction abortableDelay(milliseconds: number, signal?: AbortSignal): Promise<void> {\n if (!signal) return new Promise((resolve) => setTimeout(resolve, milliseconds));\n if (signal.aborted) return Promise.resolve();\n return new Promise((resolve) => {\n const onAbort = (): void => {\n clearTimeout(timer);\n resolve();\n };\n const timer = setTimeout(() => {\n signal.removeEventListener(\"abort\", onAbort);\n resolve();\n }, milliseconds);\n signal.addEventListener(\"abort\", onAbort, { once: true });\n });\n}\n\nlet _defaultClient: Client | undefined;\n\n/**\n * Get or create the shared HTTP client.\n *\n * @returns {Client} The shared client.\n */\nexport function defaultClient(): Client {\n _defaultClient ??= new Client();\n return _defaultClient;\n}\n","import type { Client } from \"./client.ts\";\nimport { defaultClient } from \"./client.ts\";\nimport { UnknownEcosystemError } from \"./errors.ts\";\nimport type { Dependency, Maintainer, Package, URLBuilder, Version } from \"./types.ts\";\n\n/** Common base for package registries and registry decorators. */\nexport abstract class Registry {\n abstract ecosystem(): string;\n abstract fetchPackage(name: string, signal?: AbortSignal): Promise<Package>;\n abstract fetchVersions(name: string, signal?: AbortSignal): Promise<Version[]>;\n abstract fetchDependencies(\n name: string,\n version: string,\n signal?: AbortSignal,\n ): Promise<Dependency[]>;\n abstract fetchMaintainers(name: string, signal?: AbortSignal): Promise<Maintainer[]>;\n abstract urls(): URLBuilder;\n}\n\nconst constructors = new Map<string, new (baseURL: string, client: Client) => Registry>();\nconst defaults = new Map<string, string>();\n\n/**\n * Register an ecosystem adapter.\n *\n * @param ecosystem - Ecosystem key.\n * @param defaultURL - Default registry API URL.\n * @param RegistryClass - Adapter constructor.\n */\nexport function register(\n ecosystem: string,\n defaultURL: string,\n RegistryClass: new (baseURL: string, client: Client) => Registry,\n): void {\n constructors.set(ecosystem, RegistryClass);\n defaults.set(ecosystem, defaultURL);\n}\n\n/**\n * Create an adapter for a registered ecosystem.\n *\n * @param ecosystem - Ecosystem key.\n * @param baseURL - Optional registry API URL override.\n * @param client - Optional HTTP client.\n * @returns {Registry} The registry adapter.\n */\nexport function create(ecosystem: string, baseURL?: string, client?: Client): Registry {\n const RegistryClass = constructors.get(ecosystem);\n if (!RegistryClass) {\n throw new UnknownEcosystemError(ecosystem);\n }\n return new RegistryClass(baseURL || defaults.get(ecosystem)!, client ?? defaultClient());\n}\n\n/**\n * List registered ecosystems.\n *\n * @returns {string[]} The ecosystem keys.\n */\nexport function ecosystems(): string[] {\n return [...constructors.keys()];\n}\n\n/**\n * Check whether an ecosystem is registered.\n *\n * @param ecosystem - Ecosystem key.\n * @returns {boolean} Whether an adapter is registered.\n */\nexport function has(ecosystem: string): boolean {\n return constructors.has(ecosystem);\n}\n"],"mappings":"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"}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { c as HTTPError, f as RateLimitError, l as InvalidPURLError, n as create, p as UnknownEcosystemError, r as ecosystems, u as NotFoundError } from "
|
|
2
|
-
import {
|
|
3
|
-
import "
|
|
4
|
-
import { CachedRegistry } from "../cache/cached-registry.mjs";
|
|
1
|
+
import { c as HTTPError, f as RateLimitError, l as InvalidPURLError, n as create, p as UnknownEcosystemError, r as ecosystems, u as NotFoundError } from "./registry.mjs";
|
|
2
|
+
import { c as createFromPURL, l as fullName, u as parsePURL } from "./registries.mjs";
|
|
3
|
+
import { t as CachedRegistry } from "./cached-registry.mjs";
|
|
5
4
|
import consola from "consola";
|
|
6
5
|
const sharedArgs = {
|
|
7
6
|
json: {
|
|
@@ -16,8 +15,11 @@ const sharedArgs = {
|
|
|
16
15
|
}
|
|
17
16
|
};
|
|
18
17
|
/**
|
|
19
|
-
* Resolve a PURL
|
|
20
|
-
*
|
|
18
|
+
* Resolve a PURL or shorthand, using cache unless disabled.
|
|
19
|
+
*
|
|
20
|
+
* @param input - PURL or shorthand package identifier.
|
|
21
|
+
* @param useCache - Whether to wrap the registry with caching.
|
|
22
|
+
* @returns {[Registry, string, string]} The registry, package name, and version.
|
|
21
23
|
*/
|
|
22
24
|
function resolvePURL(input, useCache = true) {
|
|
23
25
|
let purl = input;
|
|
@@ -33,7 +35,12 @@ function resolvePURL(input, useCache = true) {
|
|
|
33
35
|
}
|
|
34
36
|
return createFromPURL(purl);
|
|
35
37
|
}
|
|
36
|
-
/**
|
|
38
|
+
/**
|
|
39
|
+
* Run a command with standard CLI error handling.
|
|
40
|
+
*
|
|
41
|
+
* @param fn - Command operation to run.
|
|
42
|
+
* @returns {Promise<void>} Completion after the command or handled exit.
|
|
43
|
+
*/
|
|
37
44
|
async function withErrorHandling(fn) {
|
|
38
45
|
try {
|
|
39
46
|
await fn();
|
|
@@ -64,6 +71,6 @@ async function withErrorHandling(fn) {
|
|
|
64
71
|
throw error;
|
|
65
72
|
}
|
|
66
73
|
}
|
|
67
|
-
export {
|
|
74
|
+
export { sharedArgs as n, withErrorHandling as r, resolvePURL as t };
|
|
68
75
|
|
|
69
76
|
//# sourceMappingURL=shared.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shared.mjs","names":[],"sources":["../../src/commands/shared.ts"],"sourcesContent":["import consola from \"consola\";\nimport { createFromPURL, parsePURL, fullName } from \"../core/purl.ts\";\nimport { create, ecosystems, type Registry } from \"../core/registry.ts\";\nimport {\n HTTPError,\n NotFoundError,\n RateLimitError,\n UnknownEcosystemError,\n InvalidPURLError,\n} from \"../core/errors.ts\";\nimport { CachedRegistry } from \"../cache/cached-registry.ts\";\nimport \"../registries/index.ts\";\n\nexport const sharedArgs = {\n json: {\n type: \"boolean\" as const,\n description: \"Output as JSON\",\n default: false,\n },\n \"no-cache\": {\n type: \"boolean\" as const,\n description: \"Bypass cache, fetch fresh data\",\n default: false,\n },\n} as const;\n\n/**\n * Resolve a PURL or shorthand, using cache unless disabled.\n *\n * @param input - PURL or shorthand package identifier.\n * @param useCache - Whether to wrap the registry with caching.\n * @returns {[Registry, string, string]} The registry, package name, and version.\n */\nexport function resolvePURL(input: string, useCache = true): [Registry, string, string] {\n let purl = input;\n if (!purl.startsWith(\"pkg:\")) {\n purl = `pkg:${purl}`;\n }\n\n if (useCache) {\n const parsed = parsePURL(purl);\n const baseURL = parsed.qualifiers[\"repository_url\"] ?? \"\";\n const inner = create(parsed.type, baseURL || undefined);\n const reg = new CachedRegistry(inner);\n return [reg, fullName(parsed), parsed.version];\n }\n\n return createFromPURL(purl);\n}\n\n/**\n * Run a command with standard CLI error handling.\n *\n * @param fn - Command operation to run.\n * @returns {Promise<void>} Completion after the command or handled exit.\n */\nexport async function withErrorHandling(fn: () => Promise<void>): Promise<void> {\n try {\n await fn();\n } catch (error) {\n if (error instanceof NotFoundError) {\n consola.error(\n `Package not found: ${error.ecosystem}/${error.packageName}${error.version ? `@${error.version}` : \"\"}`,\n );\n return process.exit(1);\n }\n if (error instanceof UnknownEcosystemError) {\n consola.error(`Unknown ecosystem: ${error.ecosystem}`);\n consola.info(`Supported: ${ecosystems().join(\", \")}`);\n return process.exit(1);\n }\n if (error instanceof InvalidPURLError) {\n consola.error(`Invalid PURL: ${error.purl}`);\n consola.info(\"Examples: pkg:npm/lodash, npm/lodash@4.17.21, pkg:cargo/serde\");\n return process.exit(1);\n }\n if (error instanceof RateLimitError) {\n consola.error(`Rate limited by registry. Retry after ${error.retryAfter}s`);\n return process.exit(1);\n }\n if (error instanceof HTTPError) {\n consola.error(`Registry request failed: HTTP ${error.statusCode} (${error.url})`);\n if (error.isServerError()) {\n consola.info(\"The registry may be temporarily unavailable. Try again later.\");\n }\n return process.exit(1);\n }\n throw error;\n }\n}\n"],"mappings":";;;;AAaA,MAAa,aAAa;CACxB,MAAM;EACJ,MAAM;EACN,aAAa;EACb,SAAS;EACV;CACD,YAAY;EACV,MAAM;EACN,aAAa;EACb,SAAS;;CAEZ;;;;;;;;AASD,SAAgB,YAAY,OAAe,WAAW,MAAkC;CACtF,IAAI,OAAO;AACX,KAAI,CAAC,KAAK,WAAW,OAAO,CAC1B,QAAO,OAAO;AAGhB,KAAI,UAAU;EACZ,MAAM,SAAS,UAAU,KAAK;EAC9B,MAAM,UAAU,OAAO,WAAW,qBAAqB;AAGvD,SAAO;GADK,IAAI,eADF,OAAO,OAAO,MAAM,WAAW,KAAA,EAAU,CAClB;GACxB,SAAS,OAAO;GAAE,OAAO;GAAQ;;AAGhD,QAAO,eAAe,KAAK;;;;;;;;AAS7B,eAAsB,kBAAkB,IAAwC;AAC9E,KAAI;AACF,QAAM,IAAI;UACH,OAAO;AACd,MAAI,iBAAiB,eAAe;AAClC,WAAQ,MACN,sBAAsB,MAAM,UAAU,GAAG,MAAM,cAAc,MAAM,UAAU,IAAI,MAAM,YAAY,KACpG;AACD,UAAO,QAAQ,KAAK,EAAE;;AAExB,MAAI,iBAAiB,uBAAuB;AAC1C,WAAQ,MAAM,sBAAsB,MAAM,YAAY;AACtD,WAAQ,KAAK,cAAc,YAAY,CAAC,KAAK,KAAK,GAAG;AACrD,UAAO,QAAQ,KAAK,EAAE;;AAExB,MAAI,iBAAiB,kBAAkB;AACrC,WAAQ,MAAM,iBAAiB,MAAM,OAAO;AAC5C,WAAQ,KAAK,gEAAgE;AAC7E,UAAO,QAAQ,KAAK,EAAE;;AAExB,MAAI,iBAAiB,gBAAgB;AACnC,WAAQ,MAAM,yCAAyC,MAAM,WAAW,GAAG;AAC3E,UAAO,QAAQ,KAAK,EAAE;;AAExB,MAAI,iBAAiB,WAAW;AAC9B,WAAQ,MAAM,iCAAiC,MAAM,WAAW,IAAI,MAAM,IAAI,GAAG;AACjF,OAAI,MAAM,eAAe,CACvB,SAAQ,KAAK,gEAAgE;AAE/E,UAAO,QAAQ,KAAK,EAAE;;AAExB,QAAM"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { d as PkioError, l as InvalidPURLError, r as ecosystems } from "./registry.mjs";
|
|
2
|
+
import { u as parsePURL } from "./registries.mjs";
|
|
3
|
+
import { a as fetchVersionsFromPURL, i as fetchPackageFromPURL, n as fetchDependenciesFromPURL, r as fetchMaintainersFromPURL, t as bulkFetchPackages } from "./helpers.mjs";
|
|
4
|
+
const MAX_PURL_LENGTH = 2048;
|
|
5
|
+
const MAX_BULK_PACKAGES = 50;
|
|
6
|
+
const DEFAULT_BULK_CONCURRENCY = 15;
|
|
7
|
+
const MAX_BULK_CONCURRENCY = 50;
|
|
8
|
+
function jsonResult(details) {
|
|
9
|
+
return {
|
|
10
|
+
content: [{
|
|
11
|
+
type: "text",
|
|
12
|
+
text: JSON.stringify(details, null, 2)
|
|
13
|
+
}],
|
|
14
|
+
details
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function assertPURL(purl) {
|
|
18
|
+
if (typeof purl !== "string" || purl.length === 0) throw new InvalidPURLError(String(purl), "must be a non-empty string");
|
|
19
|
+
if (purl.length > MAX_PURL_LENGTH) throw new InvalidPURLError(purl.slice(0, MAX_PURL_LENGTH), `must not exceed ${MAX_PURL_LENGTH} characters`);
|
|
20
|
+
const parsed = parsePURL(purl);
|
|
21
|
+
if (Object.hasOwn(parsed.qualifiers, "repository_url")) throw new InvalidPURLError(purl, "repository_url is not allowed in agent tools");
|
|
22
|
+
}
|
|
23
|
+
function isPURLArray(value) {
|
|
24
|
+
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
25
|
+
}
|
|
26
|
+
function assertBulkParams(params) {
|
|
27
|
+
if (!isPURLArray(params.purls) || params.purls.length === 0) throw new PkioError("Bulk package lookup needs at least one PURL");
|
|
28
|
+
if (params.purls.length > MAX_BULK_PACKAGES) throw new PkioError(`Bulk package lookup accepts at most ${MAX_BULK_PACKAGES} PURLs`);
|
|
29
|
+
for (const purl of params.purls) assertPURL(purl);
|
|
30
|
+
const concurrency = params.concurrency ?? DEFAULT_BULK_CONCURRENCY;
|
|
31
|
+
if (!Number.isInteger(concurrency) || concurrency < 1 || concurrency > MAX_BULK_CONCURRENCY) throw new PkioError(`Concurrency must be an integer from 1 through ${MAX_BULK_CONCURRENCY}`);
|
|
32
|
+
}
|
|
33
|
+
async function packageOperation(params, signal, client) {
|
|
34
|
+
assertPURL(params.purl);
|
|
35
|
+
return jsonResult(await fetchPackageFromPURL(params.purl, signal, client));
|
|
36
|
+
}
|
|
37
|
+
async function versionsOperation(params, signal, client) {
|
|
38
|
+
assertPURL(params.purl);
|
|
39
|
+
return jsonResult(await fetchVersionsFromPURL(params.purl, signal, client));
|
|
40
|
+
}
|
|
41
|
+
async function dependenciesOperation(params, signal, client) {
|
|
42
|
+
assertPURL(params.purl);
|
|
43
|
+
return jsonResult(await fetchDependenciesFromPURL(params.purl, signal, client));
|
|
44
|
+
}
|
|
45
|
+
async function maintainersOperation(params, signal, client) {
|
|
46
|
+
assertPURL(params.purl);
|
|
47
|
+
return jsonResult(await fetchMaintainersFromPURL(params.purl, signal, client));
|
|
48
|
+
}
|
|
49
|
+
async function bulkPackagesOperation(params, signal, client) {
|
|
50
|
+
assertBulkParams(params);
|
|
51
|
+
const packages = await bulkFetchPackages(params.purls, {
|
|
52
|
+
concurrency: params.concurrency ?? DEFAULT_BULK_CONCURRENCY,
|
|
53
|
+
signal,
|
|
54
|
+
client
|
|
55
|
+
});
|
|
56
|
+
return jsonResult(Object.fromEntries(packages));
|
|
57
|
+
}
|
|
58
|
+
async function ecosystemsOperation() {
|
|
59
|
+
return jsonResult({ ecosystems: ecosystems().sort() });
|
|
60
|
+
}
|
|
61
|
+
export { bulkPackagesOperation as a, maintainersOperation as c, MAX_PURL_LENGTH as i, packageOperation as l, MAX_BULK_CONCURRENCY as n, dependenciesOperation as o, MAX_BULK_PACKAGES as r, ecosystemsOperation as s, DEFAULT_BULK_CONCURRENCY as t, versionsOperation as u };
|
|
62
|
+
|
|
63
|
+
//# sourceMappingURL=tool-operations.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-operations.mjs","names":[],"sources":["../../src/tool-operations.ts"],"sourcesContent":["import \"./registries/index.ts\";\nimport type { Client } from \"./core/client.ts\";\nimport { InvalidPURLError, PkioError } from \"./core/errors.ts\";\nimport { parsePURL } from \"./core/purl.ts\";\nimport { ecosystems } from \"./core/registry.ts\";\nimport type { Package } from \"./core/types.ts\";\nimport {\n bulkFetchPackages,\n fetchDependenciesFromPURL,\n fetchMaintainersFromPURL,\n fetchPackageFromPURL,\n fetchVersionsFromPURL,\n} from \"./helpers.ts\";\n\nexport const MAX_PURL_LENGTH = 2_048;\nexport const MAX_BULK_PACKAGES = 50;\nexport const DEFAULT_BULK_CONCURRENCY = 15;\nexport const MAX_BULK_CONCURRENCY = 50;\n\nexport interface ToolResult<T> {\n content: Array<{ type: \"text\"; text: string }>;\n details: T;\n isError?: boolean;\n}\n\nexport interface PURLParams {\n readonly purl: string;\n}\n\nexport interface BulkPackagesParams {\n readonly purls: readonly string[];\n readonly concurrency?: number;\n}\n\nfunction jsonResult<T>(details: T): ToolResult<T> {\n return {\n content: [{ type: \"text\", text: JSON.stringify(details, null, 2) }],\n details,\n };\n}\n\nfunction assertPURL(purl: string): void {\n if (typeof purl !== \"string\" || purl.length === 0) {\n throw new InvalidPURLError(String(purl), \"must be a non-empty string\");\n }\n if (purl.length > MAX_PURL_LENGTH) {\n throw new InvalidPURLError(\n purl.slice(0, MAX_PURL_LENGTH),\n `must not exceed ${MAX_PURL_LENGTH} characters`,\n );\n }\n\n const parsed = parsePURL(purl);\n if (Object.hasOwn(parsed.qualifiers, \"repository_url\")) {\n throw new InvalidPURLError(purl, \"repository_url is not allowed in agent tools\");\n }\n}\n\nfunction isPURLArray(value: unknown): value is readonly string[] {\n return Array.isArray(value) && value.every((item: unknown) => typeof item === \"string\");\n}\n\nfunction assertBulkParams(params: Readonly<BulkPackagesParams>): void {\n if (!isPURLArray(params.purls) || params.purls.length === 0) {\n throw new PkioError(\"Bulk package lookup needs at least one PURL\");\n }\n if (params.purls.length > MAX_BULK_PACKAGES) {\n throw new PkioError(`Bulk package lookup accepts at most ${MAX_BULK_PACKAGES} PURLs`);\n }\n for (const purl of params.purls) assertPURL(purl);\n\n const concurrency = params.concurrency ?? DEFAULT_BULK_CONCURRENCY;\n if (!Number.isInteger(concurrency) || concurrency < 1 || concurrency > MAX_BULK_CONCURRENCY) {\n throw new PkioError(`Concurrency must be an integer from 1 through ${MAX_BULK_CONCURRENCY}`);\n }\n}\n\nexport async function packageOperation(\n params: Readonly<PURLParams>,\n signal?: AbortSignal,\n client?: Client,\n): Promise<ToolResult<Package>> {\n assertPURL(params.purl);\n return jsonResult(await fetchPackageFromPURL(params.purl, signal, client));\n}\n\nexport async function versionsOperation(\n params: Readonly<PURLParams>,\n signal?: AbortSignal,\n client?: Client,\n) {\n assertPURL(params.purl);\n return jsonResult(await fetchVersionsFromPURL(params.purl, signal, client));\n}\n\nexport async function dependenciesOperation(\n params: Readonly<PURLParams>,\n signal?: AbortSignal,\n client?: Client,\n) {\n assertPURL(params.purl);\n return jsonResult(await fetchDependenciesFromPURL(params.purl, signal, client));\n}\n\nexport async function maintainersOperation(\n params: Readonly<PURLParams>,\n signal?: AbortSignal,\n client?: Client,\n) {\n assertPURL(params.purl);\n return jsonResult(await fetchMaintainersFromPURL(params.purl, signal, client));\n}\n\nexport async function bulkPackagesOperation(\n params: Readonly<BulkPackagesParams>,\n signal?: AbortSignal,\n client?: Client,\n): Promise<ToolResult<Record<string, Package>>> {\n assertBulkParams(params);\n const packages = await bulkFetchPackages(params.purls, {\n concurrency: params.concurrency ?? DEFAULT_BULK_CONCURRENCY,\n signal,\n client,\n });\n return jsonResult(Object.fromEntries(packages));\n}\n\nexport async function ecosystemsOperation(): Promise<ToolResult<{ ecosystems: string[] }>> {\n return jsonResult({ ecosystems: ecosystems().sort() });\n}\n"],"mappings":";;;AAcA,MAAa,kBAAkB;AAC/B,MAAa,oBAAoB;AACjC,MAAa,2BAA2B;AACxC,MAAa,uBAAuB;AAiBpC,SAAS,WAAc,SAA2B;AAChD,QAAO;EACL,SAAS,CAAC;GAAE,MAAM;GAAQ,MAAM,KAAK,UAAU,SAAS,MAAM,EAAA;GAAI,CAAC;EACnE;EACD;;AAGH,SAAS,WAAW,MAAoB;AACtC,KAAI,OAAO,SAAS,YAAY,KAAK,WAAW,EAC9C,OAAM,IAAI,iBAAiB,OAAO,KAAK,EAAE,6BAA6B;AAExE,KAAI,KAAK,SAAS,gBAChB,OAAM,IAAI,iBACR,KAAK,MAAM,GAAG,gBAAgB,EAC9B,mBAAmB,gBAAgB,aACpC;CAGH,MAAM,SAAS,UAAU,KAAK;AAC9B,KAAI,OAAO,OAAO,OAAO,YAAY,iBAAiB,CACpD,OAAM,IAAI,iBAAiB,MAAM,+CAA+C;;AAIpF,SAAS,YAAY,OAA4C;AAC/D,QAAO,MAAM,QAAQ,MAAM,IAAI,MAAM,OAAO,SAAkB,OAAO,SAAS,SAAS;;AAGzF,SAAS,iBAAiB,QAA4C;AACpE,KAAI,CAAC,YAAY,OAAO,MAAM,IAAI,OAAO,MAAM,WAAW,EACxD,OAAM,IAAI,UAAU,8CAA8C;AAEpE,KAAI,OAAO,MAAM,SAAS,kBACxB,OAAM,IAAI,UAAU,uCAAuC,kBAAkB,QAAQ;AAEvF,MAAK,MAAM,QAAQ,OAAO,MAAO,YAAW,KAAK;CAEjD,MAAM,cAAc,OAAO,eAAe;AAC1C,KAAI,CAAC,OAAO,UAAU,YAAY,IAAI,cAAc,KAAK,cAAc,qBACrE,OAAM,IAAI,UAAU,iDAAiD,uBAAuB;;AAIhG,eAAsB,iBACpB,QACA,QACA,QAC8B;AAC9B,YAAW,OAAO,KAAK;AACvB,QAAO,WAAW,MAAM,qBAAqB,OAAO,MAAM,QAAQ,OAAO,CAAC;;AAG5E,eAAsB,kBACpB,QACA,QACA,QACA;AACA,YAAW,OAAO,KAAK;AACvB,QAAO,WAAW,MAAM,sBAAsB,OAAO,MAAM,QAAQ,OAAO,CAAC;;AAG7E,eAAsB,sBACpB,QACA,QACA,QACA;AACA,YAAW,OAAO,KAAK;AACvB,QAAO,WAAW,MAAM,0BAA0B,OAAO,MAAM,QAAQ,OAAO,CAAC;;AAGjF,eAAsB,qBACpB,QACA,QACA,QACA;AACA,YAAW,OAAO,KAAK;AACvB,QAAO,WAAW,MAAM,yBAAyB,OAAO,MAAM,QAAQ,OAAO,CAAC;;AAGhF,eAAsB,sBACpB,QACA,QACA,QAC8C;AAC9C,kBAAiB,OAAO;CACxB,MAAM,WAAW,MAAM,kBAAkB,OAAO,OAAO;EACrD,aAAa,OAAO,eAAe;EACnC;EACA;EACD,CAAC;AACF,QAAO,WAAW,OAAO,YAAY,SAAS,CAAC;;AAGjD,eAAsB,sBAAqE;AACzF,QAAO,WAAW,EAAE,YAAY,YAAY,CAAC,MAAM,EAAE,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version.mjs","names":["packageJSON.version"],"sources":["../../package.json","../../src/version.ts"],"sourcesContent":["","import packageJSON from \"../package.json\" with { type: \"json\" };\n\nexport const version = packageJSON.version;\n"],"mappings":"ACEA,MAAa"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import "
|
|
2
|
-
import "
|
|
3
|
-
import "
|
|
4
|
-
import
|
|
1
|
+
import "./registry.mjs";
|
|
2
|
+
import "./registries.mjs";
|
|
3
|
+
import "./lockfile.mjs";
|
|
4
|
+
import "./cached-registry.mjs";
|
|
5
|
+
import { n as sharedArgs, r as withErrorHandling, t as resolvePURL } from "./shared.mjs";
|
|
5
6
|
import { defineCommand } from "citty";
|
|
6
7
|
import consola from "consola";
|
|
7
8
|
function selectRecentVersions(versions, limit) {
|
|
@@ -41,18 +42,18 @@ var versions_default = defineCommand({
|
|
|
41
42
|
return;
|
|
42
43
|
}
|
|
43
44
|
consola.log("");
|
|
44
|
-
consola.log(` \
|
|
45
|
+
consola.log(` \x1B[1m${name}\x1B[0m — ${versions.length} version${versions.length === 1 ? "" : "s"}`);
|
|
45
46
|
consola.log("");
|
|
46
47
|
for (const v of shown) {
|
|
47
|
-
const status = v.status ? ` \
|
|
48
|
-
const date = v.publishedAt ? ` \
|
|
48
|
+
const status = v.status ? ` \x1B[33m[${v.status}]\x1B[0m` : "";
|
|
49
|
+
const date = v.publishedAt ? ` \x1B[90m${v.publishedAt.toISOString().slice(0, 10)}\x1B[0m` : "";
|
|
49
50
|
consola.log(` ${v.number}${status}${date}`);
|
|
50
51
|
}
|
|
51
|
-
if (versions.length > limit) consola.log(`\n \
|
|
52
|
+
if (versions.length > limit) consola.log(`\n \x1B[90m... and ${versions.length - limit} more (use --limit to show more)\x1B[0m`);
|
|
52
53
|
consola.log("");
|
|
53
54
|
});
|
|
54
55
|
}
|
|
55
56
|
});
|
|
56
|
-
export { versions_default as default
|
|
57
|
+
export { versions_default as default };
|
|
57
58
|
|
|
58
59
|
//# sourceMappingURL=versions.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"versions.mjs","names":[],"sources":["../../src/commands/versions.ts"],"sourcesContent":["import { defineCommand } from \"citty\";\nimport consola from \"consola\";\nimport type { Version } from \"../core/types.ts\";\n\nimport { sharedArgs, resolvePURL, withErrorHandling } from \"./shared.ts\";\nexport function selectRecentVersions(versions: readonly Version[], limit: number): Version[] {\n return versions\n .toSorted((a, b) => {\n if (a.publishedAt === null) return b.publishedAt === null ? 0 : 1;\n if (b.publishedAt === null) return -1;\n return b.publishedAt.getTime() - a.publishedAt.getTime();\n })\n .slice(0, limit);\n}\n\nexport default defineCommand({\n meta: {\n name: \"versions\",\n description: \"List package versions\",\n },\n args: {\n ...sharedArgs,\n purl: {\n type: \"positional\",\n description: \"Package PURL (e.g. pkg:npm/lodash, cargo/serde)\",\n required: true,\n },\n limit: {\n type: \"string\",\n alias: \"l\",\n description: \"Max versions to show\",\n default: \"20\",\n },\n },\n async run({ args }) {\n await withErrorHandling(async () => {\n const [reg, name] = resolvePURL(args.purl, !args[\"no-cache\"]);\n const versions = await reg.fetchVersions(name);\n const limit = Number.parseInt(args.limit, 10) || 20;\n const shown = selectRecentVersions(versions, limit);\n\n if (args.json) {\n console.log(JSON.stringify(shown, null, 2));\n return;\n }\n\n consola.log(\"\");\n consola.log(\n ` \\x1B[1m${name}\\x1B[0m — ${versions.length} version${versions.length === 1 ? \"\" : \"s\"}`,\n );\n consola.log(\"\");\n\n for (const v of shown) {\n const status = v.status ? ` \\x1B[33m[${v.status}]\\x1B[0m` : \"\";\n const date = v.publishedAt\n ? ` \\x1B[90m${v.publishedAt.toISOString().slice(0, 10)}\\x1B[0m`\n : \"\";\n consola.log(` ${v.number}${status}${date}`);\n }\n\n if (versions.length > limit) {\n consola.log(\n `\\n \\x1B[90m... and ${versions.length - limit} more (use --limit to show more)\\x1B[0m`,\n );\n }\n consola.log(\"\");\n });\n },\n});\n"],"mappings":";;;;;;;AAKA,SAAgB,qBAAqB,UAA8B,OAA0B;AAC3F,QAAO,SACJ,UAAU,GAAG,MAAM;AAClB,MAAI,EAAE,gBAAgB,KAAM,QAAO,EAAE,gBAAgB,OAAO,IAAI;AAChE,MAAI,EAAE,gBAAgB,KAAM,QAAO;AACnC,SAAO,EAAE,YAAY,SAAS,GAAG,EAAE,YAAY,SAAS;GACxD,CACD,MAAM,GAAG,MAAM;;AAGpB,IAAA,mBAAe,cAAc;CAC3B,MAAM;EACJ,MAAM;EACN,aAAa;EACd;CACD,MAAM;EACJ,GAAG;EACH,MAAM;GACJ,MAAM;GACN,aAAa;GACb,UAAU;GACX;EACD,OAAO;GACL,MAAM;GACN,OAAO;GACP,aAAa;GACb,SAAS;;EAEZ;CACD,MAAM,IAAI,EAAE,QAAQ;AAClB,QAAM,kBAAkB,YAAY;GAClC,MAAM,CAAC,KAAK,QAAQ,YAAY,KAAK,MAAM,CAAC,KAAK,YAAY;GAC7D,MAAM,WAAW,MAAM,IAAI,cAAc,KAAK;GAC9C,MAAM,QAAQ,OAAO,SAAS,KAAK,OAAO,GAAG,IAAI;GACjD,MAAM,QAAQ,qBAAqB,UAAU,MAAM;AAEnD,OAAI,KAAK,MAAM;AACb,YAAQ,IAAI,KAAK,UAAU,OAAO,MAAM,EAAE,CAAC;AAC3C;;AAGF,WAAQ,IAAI,GAAG;AACf,WAAQ,IACN,YAAY,KAAK,YAAY,SAAS,OAAO,UAAU,SAAS,WAAW,IAAI,KAAK,MACrF;AACD,WAAQ,IAAI,GAAG;AAEf,QAAK,MAAM,KAAK,OAAO;IACrB,MAAM,SAAS,EAAE,SAAS,aAAa,EAAE,OAAO,YAAY;IAC5D,MAAM,OAAO,EAAE,cACX,aAAa,EAAE,YAAY,aAAa,CAAC,MAAM,GAAG,GAAG,CAAC,WACtD;AACJ,YAAQ,IAAI,KAAK,EAAE,SAAS,SAAS,OAAO;;AAG9C,OAAI,SAAS,SAAS,MACpB,SAAQ,IACN,uBAAuB,SAAS,SAAS,MAAM,yCAChD;AAEH,WAAQ,IAAI,GAAG;IACf;;CAEL,CAAC"}
|