@gjsify/npm-registry 0.3.15 → 0.3.17
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/lib/esm/index.js +1 -250
- package/package.json +3 -3
package/lib/esm/index.js
CHANGED
|
@@ -1,250 +1 @@
|
|
|
1
|
-
|
|
2
|
-
const DEFAULT_REGISTRY = "https://registry.npmjs.org/";
|
|
3
|
-
/** Strict-validate a packument shape. Throws on schema mismatch. */
|
|
4
|
-
function assertPackument(name, body) {
|
|
5
|
-
if (!body || typeof body !== "object") {
|
|
6
|
-
throw new TypeError(`registry: ${name} packument is not an object`);
|
|
7
|
-
}
|
|
8
|
-
const p = body;
|
|
9
|
-
if (typeof p.name !== "string") {
|
|
10
|
-
throw new TypeError(`registry: ${name} packument missing string name`);
|
|
11
|
-
}
|
|
12
|
-
if (!p.versions || typeof p.versions !== "object") {
|
|
13
|
-
throw new TypeError(`registry: ${name} packument missing versions map`);
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
/** Pick the right registry URL for a package name (scoped overrides win). */
|
|
17
|
-
function registryFor(name, npmrc) {
|
|
18
|
-
if (npmrc && name.startsWith("@")) {
|
|
19
|
-
const scope = name.slice(0, name.indexOf("/"));
|
|
20
|
-
const override = npmrc.scopes[scope];
|
|
21
|
-
if (override) return ensureTrailingSlash(override);
|
|
22
|
-
}
|
|
23
|
-
if (npmrc?.registry) return ensureTrailingSlash(npmrc.registry);
|
|
24
|
-
return DEFAULT_REGISTRY;
|
|
25
|
-
}
|
|
26
|
-
/** Build the GET URL for a packument. Handles `@scope/name` URL-encoding. */
|
|
27
|
-
function packumentUrl(name, registry) {
|
|
28
|
-
const base = ensureTrailingSlash(registry);
|
|
29
|
-
if (name.startsWith("@")) {
|
|
30
|
-
const slash = name.indexOf("/");
|
|
31
|
-
if (slash < 0) throw new TypeError(`Invalid scoped package name: ${name}`);
|
|
32
|
-
const scope = name.slice(0, slash);
|
|
33
|
-
const rest = name.slice(slash + 1);
|
|
34
|
-
return `${base}${encodeURIComponent(scope)}/${encodeURIComponent(rest)}`;
|
|
35
|
-
}
|
|
36
|
-
return `${base}${encodeURIComponent(name)}`;
|
|
37
|
-
}
|
|
38
|
-
/** Fetch + parse a packument. */
|
|
39
|
-
async function fetchPackument(name, opts = {}) {
|
|
40
|
-
const registry = opts.registry ?? registryFor(name, opts.npmrc);
|
|
41
|
-
const url = packumentUrl(name, registry);
|
|
42
|
-
const headers = buildHeaders(url, opts);
|
|
43
|
-
headers["accept"] ??= "application/vnd.npm.install-v1+json";
|
|
44
|
-
const fetchImpl = opts.fetch ?? globalThis.fetch;
|
|
45
|
-
if (!fetchImpl) throw new Error("@gjsify/npm-registry: globalThis.fetch is missing");
|
|
46
|
-
const res = await fetchImpl(url, {
|
|
47
|
-
headers,
|
|
48
|
-
signal: opts.signal
|
|
49
|
-
});
|
|
50
|
-
if (!res.ok) {
|
|
51
|
-
if (res.status === 404) throw new PackageNotFoundError(name, url);
|
|
52
|
-
throw new Error(`registry GET ${url} -> ${res.status} ${res.statusText}`);
|
|
53
|
-
}
|
|
54
|
-
const body = await res.json();
|
|
55
|
-
assertPackument(name, body);
|
|
56
|
-
return body;
|
|
57
|
-
}
|
|
58
|
-
/** Download a tarball as bytes. Verifies SRI `integrity` when supplied. */
|
|
59
|
-
async function fetchTarball(url, opts = {}) {
|
|
60
|
-
const headers = buildHeaders(url, opts);
|
|
61
|
-
headers["accept"] ??= "application/octet-stream";
|
|
62
|
-
const fetchImpl = opts.fetch ?? globalThis.fetch;
|
|
63
|
-
if (!fetchImpl) throw new Error("@gjsify/npm-registry: globalThis.fetch is missing");
|
|
64
|
-
const res = await fetchImpl(url, {
|
|
65
|
-
headers,
|
|
66
|
-
signal: opts.signal
|
|
67
|
-
});
|
|
68
|
-
if (!res.ok) throw new Error(`tarball GET ${url} -> ${res.status} ${res.statusText}`);
|
|
69
|
-
const buf = new Uint8Array(await res.arrayBuffer());
|
|
70
|
-
if (opts.integrity) {
|
|
71
|
-
const ok = await verifyIntegrity(buf, opts.integrity);
|
|
72
|
-
if (!ok) throw new IntegrityError(url, opts.integrity);
|
|
73
|
-
}
|
|
74
|
-
return buf;
|
|
75
|
-
}
|
|
76
|
-
/**
|
|
77
|
-
* Verify an SRI string (e.g. `sha512-base64==`) against bytes.
|
|
78
|
-
* Multiple hashes (space-separated) accepted; any match passes.
|
|
79
|
-
*/
|
|
80
|
-
async function verifyIntegrity(data, integrity) {
|
|
81
|
-
const parts = integrity.trim().split(/\s+/);
|
|
82
|
-
for (const part of parts) {
|
|
83
|
-
const dash = part.indexOf("-");
|
|
84
|
-
if (dash < 0) continue;
|
|
85
|
-
const algo = part.slice(0, dash).toLowerCase();
|
|
86
|
-
const expected = part.slice(dash + 1);
|
|
87
|
-
const subtle = globalThis.crypto?.subtle;
|
|
88
|
-
if (!subtle) throw new Error("@gjsify/npm-registry: globalThis.crypto.subtle is missing");
|
|
89
|
-
const algoName = subriToWebCryptoAlgo(algo);
|
|
90
|
-
if (!algoName) continue;
|
|
91
|
-
const digest = await subtle.digest(algoName, dataAsArrayBuffer(data));
|
|
92
|
-
const got = bytesToBase64(new Uint8Array(digest));
|
|
93
|
-
if (got === expected) return true;
|
|
94
|
-
}
|
|
95
|
-
return false;
|
|
96
|
-
}
|
|
97
|
-
function subriToWebCryptoAlgo(sri) {
|
|
98
|
-
switch (sri) {
|
|
99
|
-
case "sha1": return "SHA-1";
|
|
100
|
-
case "sha256": return "SHA-256";
|
|
101
|
-
case "sha384": return "SHA-384";
|
|
102
|
-
case "sha512": return "SHA-512";
|
|
103
|
-
default: return null;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
function dataAsArrayBuffer(data) {
|
|
107
|
-
if (data.byteOffset === 0 && data.byteLength === data.buffer.byteLength) {
|
|
108
|
-
return data.buffer;
|
|
109
|
-
}
|
|
110
|
-
const copy = new Uint8Array(data.byteLength);
|
|
111
|
-
copy.set(data);
|
|
112
|
-
return copy.buffer;
|
|
113
|
-
}
|
|
114
|
-
function bytesToBase64(bytes) {
|
|
115
|
-
let bin = "";
|
|
116
|
-
for (let i = 0; i < bytes.length; i++) {
|
|
117
|
-
bin += String.fromCharCode(bytes[i]);
|
|
118
|
-
}
|
|
119
|
-
return btoa(bin);
|
|
120
|
-
}
|
|
121
|
-
/** Parse a `.npmrc` text body. Unknown keys are kept on the result for callers. */
|
|
122
|
-
function parseNpmrc(text) {
|
|
123
|
-
const out = {
|
|
124
|
-
registry: DEFAULT_REGISTRY,
|
|
125
|
-
scopes: {},
|
|
126
|
-
authTokens: {},
|
|
127
|
-
basicAuth: {}
|
|
128
|
-
};
|
|
129
|
-
const lines = text.split(/\r?\n/);
|
|
130
|
-
const basic = {};
|
|
131
|
-
for (const raw of lines) {
|
|
132
|
-
const line = raw.replace(/^\s+|\s+$/g, "");
|
|
133
|
-
if (!line || line.startsWith("#") || line.startsWith(";")) continue;
|
|
134
|
-
const eq = line.indexOf("=");
|
|
135
|
-
if (eq < 0) continue;
|
|
136
|
-
const key = line.slice(0, eq).trim();
|
|
137
|
-
const value = expandEnv(stripQuotes(line.slice(eq + 1).trim()));
|
|
138
|
-
if (key === "registry") {
|
|
139
|
-
out.registry = ensureTrailingSlash(value);
|
|
140
|
-
continue;
|
|
141
|
-
}
|
|
142
|
-
const scopeRegistry = key.match(/^(@[^:]+):registry$/);
|
|
143
|
-
if (scopeRegistry) {
|
|
144
|
-
out.scopes[scopeRegistry[1]] = ensureTrailingSlash(value);
|
|
145
|
-
continue;
|
|
146
|
-
}
|
|
147
|
-
const tokenMatch = key.match(/^\/\/(.+):_authToken$/);
|
|
148
|
-
if (tokenMatch) {
|
|
149
|
-
out.authTokens[normalizeAuthHost(tokenMatch[1])] = value;
|
|
150
|
-
continue;
|
|
151
|
-
}
|
|
152
|
-
const userMatch = key.match(/^\/\/(.+):username$/);
|
|
153
|
-
if (userMatch) {
|
|
154
|
-
(basic[normalizeAuthHost(userMatch[1])] ??= {}).user = value;
|
|
155
|
-
continue;
|
|
156
|
-
}
|
|
157
|
-
const passMatch = key.match(/^\/\/(.+):_password$/);
|
|
158
|
-
if (passMatch) {
|
|
159
|
-
const decoded = base64Decode(value);
|
|
160
|
-
(basic[normalizeAuthHost(passMatch[1])] ??= {}).pass = decoded;
|
|
161
|
-
continue;
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
for (const [host, creds] of Object.entries(basic)) {
|
|
165
|
-
if (creds.user && creds.pass !== undefined) {
|
|
166
|
-
out.basicAuth[host] = {
|
|
167
|
-
username: creds.user,
|
|
168
|
-
password: creds.pass
|
|
169
|
-
};
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
return out;
|
|
173
|
-
}
|
|
174
|
-
/** Build auth + UA headers for a request URL. Pure (no I/O). */
|
|
175
|
-
function buildHeaders(url, opts) {
|
|
176
|
-
const headers = { "user-agent": "gjsify-install/0.3.7" };
|
|
177
|
-
if (opts.npmrc) {
|
|
178
|
-
const auth = resolveAuthForUrl(url, opts.npmrc);
|
|
179
|
-
if (auth) headers["authorization"] = auth;
|
|
180
|
-
}
|
|
181
|
-
if (opts.headers) {
|
|
182
|
-
for (const [k, v] of Object.entries(opts.headers)) headers[k.toLowerCase()] = v;
|
|
183
|
-
}
|
|
184
|
-
return headers;
|
|
185
|
-
}
|
|
186
|
-
/** Resolve an `Authorization` header for a URL given a parsed .npmrc. */
|
|
187
|
-
function resolveAuthForUrl(url, npmrc) {
|
|
188
|
-
const u = new URL(url);
|
|
189
|
-
const candidates = pathPrefixes(u);
|
|
190
|
-
for (const prefix of candidates) {
|
|
191
|
-
const token = npmrc.authTokens[prefix];
|
|
192
|
-
if (token) return `Bearer ${token}`;
|
|
193
|
-
const basic = npmrc.basicAuth[prefix];
|
|
194
|
-
if (basic) {
|
|
195
|
-
const enc = btoa(`${basic.username}:${basic.password}`);
|
|
196
|
-
return `Basic ${enc}`;
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
return null;
|
|
200
|
-
}
|
|
201
|
-
function pathPrefixes(u) {
|
|
202
|
-
const segments = u.pathname.split("/").filter(Boolean);
|
|
203
|
-
const prefixes = [];
|
|
204
|
-
for (let i = segments.length; i >= 0; i--) {
|
|
205
|
-
const tail = segments.slice(0, i).join("/");
|
|
206
|
-
prefixes.push(tail ? `//${u.host}/${tail}` : `//${u.host}`);
|
|
207
|
-
}
|
|
208
|
-
return prefixes;
|
|
209
|
-
}
|
|
210
|
-
function normalizeAuthHost(captured) {
|
|
211
|
-
const trimmed = captured.replace(/\/+$/, "");
|
|
212
|
-
return `//${trimmed}`;
|
|
213
|
-
}
|
|
214
|
-
function ensureTrailingSlash(s) {
|
|
215
|
-
return s.endsWith("/") ? s : s + "/";
|
|
216
|
-
}
|
|
217
|
-
function stripQuotes(s) {
|
|
218
|
-
if (s.startsWith("\"") && s.endsWith("\"") || s.startsWith("'") && s.endsWith("'")) {
|
|
219
|
-
return s.slice(1, -1);
|
|
220
|
-
}
|
|
221
|
-
return s;
|
|
222
|
-
}
|
|
223
|
-
function expandEnv(s) {
|
|
224
|
-
return s.replace(/\$\{([A-Z0-9_]+)\}/gi, (_m, name) => {
|
|
225
|
-
const env = globalThis.process?.env;
|
|
226
|
-
return env?.[name] ?? "";
|
|
227
|
-
});
|
|
228
|
-
}
|
|
229
|
-
function base64Decode(s) {
|
|
230
|
-
return atob(s);
|
|
231
|
-
}
|
|
232
|
-
var PackageNotFoundError = class extends Error {
|
|
233
|
-
constructor(name, url) {
|
|
234
|
-
super(`Package not found in registry: ${name} (${url})`);
|
|
235
|
-
this.name = name;
|
|
236
|
-
this.url = url;
|
|
237
|
-
this.name = "PackageNotFoundError";
|
|
238
|
-
}
|
|
239
|
-
};
|
|
240
|
-
var IntegrityError = class extends Error {
|
|
241
|
-
constructor(url, integrity) {
|
|
242
|
-
super(`Tarball integrity mismatch for ${url} (expected ${integrity})`);
|
|
243
|
-
this.url = url;
|
|
244
|
-
this.integrity = integrity;
|
|
245
|
-
this.name = "IntegrityError";
|
|
246
|
-
}
|
|
247
|
-
};
|
|
248
|
-
|
|
249
|
-
//#endregion
|
|
250
|
-
export { DEFAULT_REGISTRY, IntegrityError, PackageNotFoundError, assertPackument, buildHeaders, fetchPackument, fetchTarball, packumentUrl, parseNpmrc, registryFor, resolveAuthForUrl, verifyIntegrity };
|
|
1
|
+
const e=`https://registry.npmjs.org/`;function t(e,t){if(!t||typeof t!=`object`)throw TypeError(`registry: ${e} packument is not an object`);let n=t;if(typeof n.name!=`string`)throw TypeError(`registry: ${e} packument missing string name`);if(!n.versions||typeof n.versions!=`object`)throw TypeError(`registry: ${e} packument missing versions map`)}function n(t,n){if(n&&t.startsWith(`@`)){let e=t.slice(0,t.indexOf(`/`)),r=n.scopes[e];if(r)return h(r)}return n?.registry?h(n.registry):e}function r(e,t){let n=h(t);if(e.startsWith(`@`)){let t=e.indexOf(`/`);if(t<0)throw TypeError(`Invalid scoped package name: ${e}`);let r=e.slice(0,t),i=e.slice(t+1);return`${n}${encodeURIComponent(r)}/${encodeURIComponent(i)}`}return`${n}${encodeURIComponent(e)}`}async function i(e,i={}){let a=r(e,i.registry??n(e,i.npmrc)),o=d(a,i);o.accept??=`application/vnd.npm.install-v1+json`;let s=i.fetch??globalThis.fetch;if(!s)throw Error(`@gjsify/npm-registry: globalThis.fetch is missing`);let c=await s(a,{headers:o,signal:i.signal});if(!c.ok)throw c.status===404?new y(e,a):Error(`registry GET ${a} -> ${c.status} ${c.statusText}`);let l=await c.json();return t(e,l),l}async function a(e,t={}){let n=d(e,t);n.accept??=`application/octet-stream`;let r=t.fetch??globalThis.fetch;if(!r)throw Error(`@gjsify/npm-registry: globalThis.fetch is missing`);let i=await r(e,{headers:n,signal:t.signal});if(!i.ok)throw Error(`tarball GET ${e} -> ${i.status} ${i.statusText}`);let a=new Uint8Array(await i.arrayBuffer());if(t.integrity&&!await o(a,t.integrity))throw new b(e,t.integrity);return a}async function o(e,t){let n=t.trim().split(/\s+/);for(let t of n){let n=t.indexOf(`-`);if(n<0)continue;let r=t.slice(0,n).toLowerCase(),i=t.slice(n+1),a=globalThis.crypto?.subtle;if(!a)throw Error(`@gjsify/npm-registry: globalThis.crypto.subtle is missing`);let o=s(r);if(!o)continue;let u=await a.digest(o,c(e));if(l(new Uint8Array(u))===i)return!0}return!1}function s(e){switch(e){case`sha1`:return`SHA-1`;case`sha256`:return`SHA-256`;case`sha384`:return`SHA-384`;case`sha512`:return`SHA-512`;default:return null}}function c(e){if(e.byteOffset===0&&e.byteLength===e.buffer.byteLength)return e.buffer;let t=new Uint8Array(e.byteLength);return t.set(e),t.buffer}function l(e){let t=``;for(let n=0;n<e.length;n++)t+=String.fromCharCode(e[n]);return btoa(t)}function u(t){let n={registry:e,scopes:{},authTokens:{},basicAuth:{}},r=t.split(/\r?\n/),i={};for(let e of r){let t=e.replace(/^\s+|\s+$/g,``);if(!t||t.startsWith(`#`)||t.startsWith(`;`))continue;let r=t.indexOf(`=`);if(r<0)continue;let a=t.slice(0,r).trim(),o=_(g(t.slice(r+1).trim()));if(a===`registry`){n.registry=h(o);continue}let s=a.match(/^(@[^:]+):registry$/);if(s){n.scopes[s[1]]=h(o);continue}let c=a.match(/^\/\/(.+):_authToken$/);if(c){n.authTokens[m(c[1])]=o;continue}let l=a.match(/^\/\/(.+):username$/);if(l){(i[m(l[1])]??={}).user=o;continue}let u=a.match(/^\/\/(.+):_password$/);if(u){let e=v(o);(i[m(u[1])]??={}).pass=e;continue}}for(let[e,t]of Object.entries(i))t.user&&t.pass!==void 0&&(n.basicAuth[e]={username:t.user,password:t.pass});return n}function d(e,t){let n={"user-agent":`gjsify-install/0.3.7`};if(t.npmrc){let r=f(e,t.npmrc);r&&(n.authorization=r)}if(t.headers)for(let[e,r]of Object.entries(t.headers))n[e.toLowerCase()]=r;return n}function f(e,t){let n=p(new URL(e));for(let e of n){let n=t.authTokens[e];if(n)return`Bearer ${n}`;let r=t.basicAuth[e];if(r)return`Basic ${btoa(`${r.username}:${r.password}`)}`}return null}function p(e){let t=e.pathname.split(`/`).filter(Boolean),n=[];for(let r=t.length;r>=0;r--){let i=t.slice(0,r).join(`/`);n.push(i?`//${e.host}/${i}`:`//${e.host}`)}return n}function m(e){return`//${e.replace(/\/+$/,``)}`}function h(e){return e.endsWith(`/`)?e:e+`/`}function g(e){return e.startsWith(`"`)&&e.endsWith(`"`)||e.startsWith(`'`)&&e.endsWith(`'`)?e.slice(1,-1):e}function _(e){return e.replace(/\$\{([A-Z0-9_]+)\}/gi,(e,t)=>globalThis.process?.env?.[t]??``)}function v(e){return atob(e)}var y=class extends Error{name;url;constructor(e,t){super(`Package not found in registry: ${e} (${t})`),this.name=e,this.url=t,this.name=`PackageNotFoundError`}},b=class extends Error{url;integrity;constructor(e,t){super(`Tarball integrity mismatch for ${e} (expected ${t})`),this.url=e,this.integrity=t,this.name=`IntegrityError`}};export{e as DEFAULT_REGISTRY,b as IntegrityError,y as PackageNotFoundError,t as assertPackument,d as buildHeaders,i as fetchPackument,a as fetchTarball,r as packumentUrl,u as parseNpmrc,n as registryFor,f as resolveAuthForUrl,o as verifyIntegrity};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjsify/npm-registry",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.17",
|
|
4
4
|
"description": "npm registry client for the gjsify install backend — packuments, tarballs, .npmrc auth (Node + GJS)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "lib/esm/index.js",
|
|
@@ -34,8 +34,8 @@
|
|
|
34
34
|
],
|
|
35
35
|
"license": "MIT",
|
|
36
36
|
"devDependencies": {
|
|
37
|
-
"@gjsify/cli": "^0.3.
|
|
38
|
-
"@gjsify/unit": "^0.3.
|
|
37
|
+
"@gjsify/cli": "^0.3.17",
|
|
38
|
+
"@gjsify/unit": "^0.3.17",
|
|
39
39
|
"typescript": "^6.0.3"
|
|
40
40
|
}
|
|
41
41
|
}
|