@vltpkg/registry-client 1.0.0-rc.22 → 1.0.0-rc.24

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 (43) hide show
  1. package/dist/add-header.d.ts +1 -0
  2. package/dist/add-header.js +26 -0
  3. package/dist/auth.d.ts +15 -0
  4. package/dist/auth.js +53 -0
  5. package/dist/cache-entry.d.ts +140 -0
  6. package/dist/cache-entry.js +517 -0
  7. package/dist/cache-revalidate.d.ts +1 -0
  8. package/dist/cache-revalidate.js +56 -0
  9. package/dist/delete-header.d.ts +1 -0
  10. package/dist/delete-header.js +31 -0
  11. package/dist/env.d.ts +6 -0
  12. package/dist/env.js +12 -0
  13. package/dist/get-header.d.ts +1 -0
  14. package/dist/get-header.js +36 -0
  15. package/dist/handle-304-response.d.ts +3 -0
  16. package/dist/handle-304-response.js +8 -0
  17. package/dist/index.d.ts +145 -0
  18. package/dist/index.js +326 -0
  19. package/dist/is-cacheable.d.ts +4 -0
  20. package/dist/is-cacheable.js +9 -0
  21. package/dist/is-iterable.d.ts +1 -0
  22. package/dist/is-iterable.js +1 -0
  23. package/dist/oidc.d.ts +17 -0
  24. package/dist/oidc.js +151 -0
  25. package/dist/otplease.d.ts +3 -0
  26. package/dist/otplease.js +62 -0
  27. package/dist/raw-header.d.ts +8 -0
  28. package/dist/raw-header.js +33 -0
  29. package/dist/redirect.d.ts +22 -0
  30. package/dist/redirect.js +64 -0
  31. package/dist/revalidate.d.ts +4 -0
  32. package/dist/revalidate.js +50 -0
  33. package/dist/set-cache-headers.d.ts +3 -0
  34. package/dist/set-cache-headers.js +16 -0
  35. package/dist/set-raw-header.d.ts +5 -0
  36. package/dist/set-raw-header.js +20 -0
  37. package/dist/string-encoding.d.ts +8 -0
  38. package/dist/string-encoding.js +24 -0
  39. package/dist/token-response.d.ts +4 -0
  40. package/dist/token-response.js +7 -0
  41. package/dist/web-auth-challenge.d.ts +5 -0
  42. package/dist/web-auth-challenge.js +13 -0
  43. package/package.json +12 -12
@@ -0,0 +1,62 @@
1
+ import { error } from '@vltpkg/error-cause';
2
+ import { getWebAuthChallenge } from "./web-auth-challenge.js";
3
+ import { urlOpen } from '@vltpkg/url-open';
4
+ import { createInterface } from 'node:readline/promises';
5
+ // eslint-disable-next-line no-console
6
+ const log = (msg) => console.error(msg);
7
+ const question = async (text) => {
8
+ const rl = createInterface({
9
+ input: process.stdin,
10
+ output: process.stdout,
11
+ });
12
+ const answer = await rl.question(text);
13
+ rl.close();
14
+ return answer;
15
+ };
16
+ const otpChallengeNotice = /^Open ([^ ]+) to use your security key for authentication or enter OTP from your authenticator app/i;
17
+ export const otplease = async (client, options, response) => {
18
+ const waHeader = String(response.headers['www-authenticate'] ?? '');
19
+ const wwwAuth = new Set(waHeader ? waHeader.toLowerCase().split(/,\s*/) : []);
20
+ if (wwwAuth.has('ipaddress')) {
21
+ throw error('Authorization is not allowed from your ip address', {
22
+ response,
23
+ });
24
+ }
25
+ if (wwwAuth.has('otp')) {
26
+ // do a web auth opener to get otp token
27
+ const challenge = getWebAuthChallenge(await response.body.json().catch(() => null));
28
+ if (challenge) {
29
+ return {
30
+ ...options,
31
+ otp: (await client.webAuthOpener(challenge)).token,
32
+ };
33
+ }
34
+ const { 'npm-notice': npmNotice } = response.headers;
35
+ if (npmNotice) {
36
+ const notice = String(npmNotice);
37
+ const match = otpChallengeNotice.exec(notice);
38
+ if (match?.[1]) {
39
+ await urlOpen(match[1]);
40
+ log(notice);
41
+ return {
42
+ ...options,
43
+ otp: await question('OTP: '),
44
+ };
45
+ }
46
+ }
47
+ throw error('Unrecognized OTP authentication challenge', {
48
+ response,
49
+ });
50
+ }
51
+ if (wwwAuth.size) {
52
+ throw error('Unknown authentication challenge', { response });
53
+ }
54
+ // see if the body is prompting for otp
55
+ const text = await response.body.text().catch(() => '');
56
+ if (text.toLowerCase().includes('one-time pass')) {
57
+ return {
58
+ ...options,
59
+ otp: await question(text),
60
+ };
61
+ }
62
+ };
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Give it a key, and it'll return the value of that header as a Uint8Array
3
+ */
4
+ export declare const getRawHeader: (headers: Uint8Array[], key: string) => Uint8Array | undefined;
5
+ /**
6
+ * Give it a key and value, and it'll overwrite or add the header entry
7
+ */
8
+ export declare const setRawHeader: (headers: Uint8Array[], key: string, value: Uint8Array | string) => Uint8Array[];
@@ -0,0 +1,33 @@
1
+ import { getDecodedValue, getEncondedValue, } from "./string-encoding.js";
2
+ /**
3
+ * Give it a key, and it'll return the value of that header as a Uint8Array
4
+ */
5
+ export const getRawHeader = (headers, key) => {
6
+ const k = key.toLowerCase();
7
+ for (let i = 0; i < headers.length; i += 2) {
8
+ const name = headers[i];
9
+ if (name?.length === key.length &&
10
+ getDecodedValue(name).toLowerCase() === k) {
11
+ return headers[i + 1];
12
+ }
13
+ }
14
+ };
15
+ /**
16
+ * Give it a key and value, and it'll overwrite or add the header entry
17
+ */
18
+ export const setRawHeader = (headers, key, value) => {
19
+ const k = key.toLowerCase();
20
+ const encVal = typeof value === 'string' ? getEncondedValue(value) : value;
21
+ for (let i = 0; i < headers.length; i += 2) {
22
+ const name = headers[i];
23
+ if (name?.length === k.length &&
24
+ getDecodedValue(name).toLowerCase() === k) {
25
+ return [
26
+ ...headers.slice(0, i + 1),
27
+ encVal,
28
+ ...headers.slice(i + 2),
29
+ ];
30
+ }
31
+ }
32
+ return [...headers, getEncondedValue(k), encVal];
33
+ };
@@ -0,0 +1,22 @@
1
+ import type { CacheEntry } from './cache-entry.ts';
2
+ import type { RegistryClientRequestOptions } from './index.ts';
3
+ export type RedirectStatus = 301 | 302 | 303 | 307 | 308;
4
+ export type RedirectResponse = CacheEntry & {
5
+ statusCode: RedirectStatus;
6
+ getHeader(key: 'location'): Uint8Array | undefined;
7
+ };
8
+ export declare const isRedirect: (response: CacheEntry) => response is RedirectResponse;
9
+ /**
10
+ * If this response is allowed to follow the redirect (because max has not
11
+ * been hit, and the new location has not been seen already), then return
12
+ * the [url, options] to use for the subsequent request.
13
+ *
14
+ * Return [] if the response should be returned as-is.
15
+ *
16
+ * Throws an error if maxRedirections is hit or the redirections set already
17
+ * contains the new location.
18
+ *
19
+ * Ensure that the response is in fact a redirection first, by calling
20
+ * {@link isRedirect} on it.
21
+ */
22
+ export declare const redirect: (options: RegistryClientRequestOptions, response: RedirectResponse, from: URL) => [] | [URL, RegistryClientRequestOptions];
@@ -0,0 +1,64 @@
1
+ // given a RegistryClientOptions object, and a redirection response,
2
+ import { error } from '@vltpkg/error-cause';
3
+ const redirectStatuses = new Set([301, 302, 303, 307, 308]);
4
+ export const isRedirect = (response) => redirectStatuses.has(response.statusCode) &&
5
+ !!response.getHeader('location')?.length;
6
+ /**
7
+ * If this response is allowed to follow the redirect (because max has not
8
+ * been hit, and the new location has not been seen already), then return
9
+ * the [url, options] to use for the subsequent request.
10
+ *
11
+ * Return [] if the response should be returned as-is.
12
+ *
13
+ * Throws an error if maxRedirections is hit or the redirections set already
14
+ * contains the new location.
15
+ *
16
+ * Ensure that the response is in fact a redirection first, by calling
17
+ * {@link isRedirect} on it.
18
+ */
19
+ export const redirect = (options, response, from) => {
20
+ const { redirections = new Set(), maxRedirections = 10 } = options;
21
+ if (maxRedirections <= 0)
22
+ return [];
23
+ if (redirections.size >= maxRedirections) {
24
+ throw error('Maximum redirections exceeded', {
25
+ max: maxRedirections,
26
+ found: [...redirections],
27
+ url: from,
28
+ });
29
+ }
30
+ const location = response.getHeaderString('location');
31
+ /* c8 ignore start */
32
+ if (!location)
33
+ throw error('Location header missing from redirect response');
34
+ /* c8 ignore stop */
35
+ const nextURL = new URL(location, from);
36
+ if (redirections.has(String(nextURL))) {
37
+ throw error('Redirection cycle detected', {
38
+ max: maxRedirections,
39
+ found: [...redirections],
40
+ url: from,
41
+ });
42
+ }
43
+ const nextOptions = {
44
+ ...options,
45
+ redirections,
46
+ };
47
+ // eslint-disable-next-line @typescript-eslint/no-deprecated -- thats why we are deleting it
48
+ delete nextOptions.path;
49
+ redirections.add(String(nextURL));
50
+ switch (response.statusCode) {
51
+ case 303: {
52
+ // drop body, change method to GET
53
+ nextOptions.method = 'GET';
54
+ nextOptions.body = undefined;
55
+ // fallthrough
56
+ }
57
+ case 301:
58
+ case 302: // some user agents treat as 303, but they're wrong
59
+ case 307:
60
+ case 308: {
61
+ return [nextURL, nextOptions];
62
+ }
63
+ }
64
+ };
@@ -0,0 +1,4 @@
1
+ export declare const __CODE_SPLIT_SCRIPT_NAME: string;
2
+ export declare const main: (cache?: string, input?: NodeJS.ReadStream & {
3
+ fd: 0;
4
+ }) => Promise<boolean>;
@@ -0,0 +1,50 @@
1
+ // This needs to live in the same workspace as the RegistryClient, because
2
+ // otherwise we have a cyclical dependency cycle of dependencies in a cycle,
3
+ // which is even more cyclical than this description describing it.
4
+ import { pathToFileURL } from 'node:url';
5
+ import { RegistryClient } from "./index.js";
6
+ export const __CODE_SPLIT_SCRIPT_NAME = import.meta.filename;
7
+ const isMain = (path) => path === __CODE_SPLIT_SCRIPT_NAME ||
8
+ path === pathToFileURL(__CODE_SPLIT_SCRIPT_NAME).toString();
9
+ export const main = async (cache, input = process.stdin) => {
10
+ if (!cache) {
11
+ return false;
12
+ }
13
+ const reqs = await new Promise(res => {
14
+ const chunks = [];
15
+ let chunkLen = 0;
16
+ input.on('data', chunk => {
17
+ chunks.push(chunk);
18
+ chunkLen += chunk.length;
19
+ });
20
+ input.on('end', () => {
21
+ const reqs = Buffer.concat(chunks, chunkLen)
22
+ .toString()
23
+ .split('\0')
24
+ .filter(i => !!i && (i.startsWith('GET ') || i.startsWith('HEAD ')))
25
+ .map(i => i.startsWith('GET ') ?
26
+ ['GET', new URL(i.substring('GET '.length))]
27
+ : ['HEAD', new URL(i.substring('HEAD '.length))]);
28
+ res(reqs);
29
+ });
30
+ });
31
+ if (!reqs.length) {
32
+ return false;
33
+ }
34
+ const rc = new RegistryClient({ cache });
35
+ await Promise.all(reqs.map(async ([method, url]) => {
36
+ await rc.request(url, {
37
+ method,
38
+ staleWhileRevalidate: false,
39
+ });
40
+ }));
41
+ return true;
42
+ };
43
+ if (isMain(process.argv[1])) {
44
+ process.title = 'vlt-cache-revalidate';
45
+ const cacheFolder = process.argv.length === 2 ? undefined : process.argv.at(-1);
46
+ const res = await main(cacheFolder, process.stdin);
47
+ if (!res) {
48
+ process.exit(1);
49
+ }
50
+ }
@@ -0,0 +1,3 @@
1
+ import type { CacheEntry } from './cache-entry.ts';
2
+ import type { RegistryClientRequestOptions } from './index.ts';
3
+ export declare const setCacheHeaders: (options: RegistryClientRequestOptions, entry?: CacheEntry) => void;
@@ -0,0 +1,16 @@
1
+ // Set the cache headers on a request options object
2
+ // based on what we know from a CacheEntry
3
+ import { addHeader } from "./add-header.js";
4
+ export const setCacheHeaders = (options, entry) => {
5
+ if (!entry)
6
+ return;
7
+ const etag = entry.getHeader('etag')?.toString();
8
+ if (etag) {
9
+ options.headers = addHeader(options.headers, 'if-none-match', etag);
10
+ }
11
+ const date = entry.getHeader('date')?.toString() ??
12
+ entry.getHeader('last-modified')?.toString();
13
+ if (date) {
14
+ options.headers = addHeader(options.headers, 'if-modified-since', date);
15
+ }
16
+ };
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Given a rawHeaders array of [key, value, key2, value2, ...],
3
+ * overwrite the current value of a header, or if not found, append
4
+ */
5
+ export declare const setRawHeader: (headers: Uint8Array[], key: string, value: Uint8Array | string) => Uint8Array[];
@@ -0,0 +1,20 @@
1
+ import { getDecodedValue, getEncondedValue, } from "./string-encoding.js";
2
+ /**
3
+ * Given a rawHeaders array of [key, value, key2, value2, ...],
4
+ * overwrite the current value of a header, or if not found, append
5
+ */
6
+ export const setRawHeader = (headers, key, value) => {
7
+ key = key.toLowerCase();
8
+ const keyBuf = getEncondedValue(key);
9
+ const valBuf = getEncondedValue(value);
10
+ for (let i = 0; i < headers.length; i += 2) {
11
+ const k = headers[i];
12
+ if (k?.length === keyBuf.length &&
13
+ getDecodedValue(k).toLowerCase() === key) {
14
+ headers[i + 1] = valBuf;
15
+ return headers;
16
+ }
17
+ }
18
+ headers.push(keyBuf, valBuf);
19
+ return headers;
20
+ };
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Decodes a string from a Uint8Array
3
+ */
4
+ export declare const getDecodedValue: (value: string | Uint8Array | undefined) => string;
5
+ /**
6
+ * Encodes a string to a Uint8Array
7
+ */
8
+ export declare const getEncondedValue: (value: Uint8Array | string | undefined) => Uint8Array;
@@ -0,0 +1,24 @@
1
+ const decoder = new TextDecoder();
2
+ /**
3
+ * Decodes a string from a Uint8Array
4
+ */
5
+ export const getDecodedValue = (value) => {
6
+ if (value == undefined)
7
+ return '';
8
+ if (typeof value === 'string')
9
+ return value;
10
+ const res = decoder.decode(value);
11
+ return res;
12
+ };
13
+ /**
14
+ * Encodes a string to a Uint8Array
15
+ */
16
+ export const getEncondedValue = (value) => {
17
+ if (value == undefined)
18
+ return new Uint8Array(0);
19
+ if (typeof value === 'string') {
20
+ const res = Buffer.from(value);
21
+ return res;
22
+ }
23
+ return value;
24
+ };
@@ -0,0 +1,4 @@
1
+ export type TokenResponse = {
2
+ token: string;
3
+ };
4
+ export declare const getTokenResponse: (b: unknown) => TokenResponse | undefined;
@@ -0,0 +1,7 @@
1
+ export const getTokenResponse = (b) => (!!b &&
2
+ typeof b === 'object' &&
3
+ 'token' in b &&
4
+ typeof b.token === 'string' &&
5
+ b.token) ?
6
+ { token: b.token }
7
+ : undefined;
@@ -0,0 +1,5 @@
1
+ export type WebAuthChallenge = {
2
+ doneUrl: string;
3
+ authUrl: string;
4
+ };
5
+ export declare const getWebAuthChallenge: (o: unknown) => WebAuthChallenge | undefined;
@@ -0,0 +1,13 @@
1
+ export const getWebAuthChallenge = (o) => {
2
+ if (!!o &&
3
+ typeof o === 'object' &&
4
+ 'doneUrl' in o &&
5
+ typeof o.doneUrl === 'string' &&
6
+ o.doneUrl) {
7
+ // Publishes use authUrl, but login uses loginUrl
8
+ const authUrl = 'authUrl' in o && typeof o.authUrl === 'string' ? o.authUrl
9
+ : 'loginUrl' in o && typeof o.loginUrl === 'string' ? o.loginUrl
10
+ : undefined;
11
+ return authUrl ? { doneUrl: o.doneUrl, authUrl } : undefined;
12
+ }
13
+ };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@vltpkg/registry-client",
3
3
  "description": "Fetch package artifacts and metadata from registries",
4
- "version": "1.0.0-rc.22",
4
+ "version": "1.0.0-rc.24",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://github.com/vltpkg/vltpkg.git",
@@ -12,14 +12,14 @@
12
12
  "email": "support@vlt.sh"
13
13
  },
14
14
  "dependencies": {
15
- "@vltpkg/cache": "1.0.0-rc.22",
16
- "@vltpkg/cache-unzip": "1.0.0-rc.22",
17
- "@vltpkg/error-cause": "1.0.0-rc.22",
18
- "@vltpkg/keychain": "1.0.0-rc.22",
19
- "@vltpkg/output": "1.0.0-rc.22",
20
- "@vltpkg/types": "1.0.0-rc.22",
21
- "@vltpkg/url-open": "1.0.0-rc.22",
22
- "@vltpkg/xdg": "1.0.0-rc.22",
15
+ "@vltpkg/cache": "1.0.0-rc.24",
16
+ "@vltpkg/cache-unzip": "1.0.0-rc.24",
17
+ "@vltpkg/error-cause": "1.0.0-rc.24",
18
+ "@vltpkg/keychain": "1.0.0-rc.24",
19
+ "@vltpkg/output": "1.0.0-rc.24",
20
+ "@vltpkg/types": "1.0.0-rc.24",
21
+ "@vltpkg/url-open": "1.0.0-rc.24",
22
+ "@vltpkg/xdg": "1.0.0-rc.24",
23
23
  "cache-control-parser": "^2.0.6",
24
24
  "package-json-from-dist": "^1.0.1",
25
25
  "undici": "^7.16.0"
@@ -53,18 +53,18 @@
53
53
  "extends": "../../tap-config.yaml"
54
54
  },
55
55
  "prettier": "../../.prettierrc.js",
56
- "module": "./src/index.ts",
56
+ "module": "./dist/index.js",
57
57
  "type": "module",
58
58
  "exports": {
59
59
  "./package.json": "./package.json",
60
60
  ".": {
61
61
  "import": {
62
- "default": "./src/index.ts"
62
+ "default": "./dist/index.js"
63
63
  }
64
64
  },
65
65
  "./cache-entry": {
66
66
  "import": {
67
- "default": "./src/cache-entry.ts"
67
+ "default": "./dist/cache-entry.js"
68
68
  }
69
69
  }
70
70
  },