@pnpm/network.web-auth 1000.0.0
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/LICENSE +22 -0
- package/lib/WebAuthTimeoutError.d.ts +7 -0
- package/lib/WebAuthTimeoutError.js +15 -0
- package/lib/generateQrCode.d.ts +1 -0
- package/lib/generateQrCode.js +12 -0
- package/lib/index.d.ts +4 -0
- package/lib/index.js +5 -0
- package/lib/pollForWebAuthToken.d.ts +44 -0
- package/lib/pollForWebAuthToken.js +66 -0
- package/lib/withOtpHandling.d.ts +74 -0
- package/lib/withOtpHandling.js +117 -0
- package/package.json +48 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2015-2016 Rico Sta. Cruz and other contributors
|
|
4
|
+
Copyright (c) 2016-2026 Zoltan Kochan and other contributors
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { PnpmError } from '@pnpm/error';
|
|
2
|
+
export class WebAuthTimeoutError extends PnpmError {
|
|
3
|
+
endTime;
|
|
4
|
+
startTime;
|
|
5
|
+
timeout;
|
|
6
|
+
constructor(endTime, startTime, timeout) {
|
|
7
|
+
super('WEBAUTH_TIMEOUT', 'Web-based authentication timed out before it could be completed', {
|
|
8
|
+
hint: 'Re-run this command and complete the authentication step in your browser before the time limit is reached',
|
|
9
|
+
});
|
|
10
|
+
this.endTime = endTime;
|
|
11
|
+
this.startTime = startTime;
|
|
12
|
+
this.timeout = timeout;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=WebAuthTimeoutError.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function generateQrCode(text: string): string;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import qrcodeTerminal from 'qrcode-terminal';
|
|
2
|
+
export function generateQrCode(text) {
|
|
3
|
+
let qrCode;
|
|
4
|
+
qrcodeTerminal.generate(text, { small: true }, (code) => {
|
|
5
|
+
qrCode = code;
|
|
6
|
+
});
|
|
7
|
+
if (qrCode != null)
|
|
8
|
+
return qrCode;
|
|
9
|
+
/* istanbul ignore next */
|
|
10
|
+
throw new Error('we were expecting qrcode-terminal to be fully synchronous, but it fails to execute the callback');
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=generateQrCode.js.map
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { generateQrCode } from './generateQrCode.js';
|
|
2
|
+
export { pollForWebAuthToken, type PollForWebAuthTokenParams, type WebAuthContext, type WebAuthFetchOptions, type WebAuthFetchResponse, type WebAuthFetchResponseHeaders, } from './pollForWebAuthToken.js';
|
|
3
|
+
export { WebAuthTimeoutError } from './WebAuthTimeoutError.js';
|
|
4
|
+
export { isOtpError, type OtpContext, type OtpEnquirer, type OtpHandlingParams, OtpNonInteractiveError, type OtpPromptOptions, type OtpPromptResponse, OtpSecondChallengeError, SyntheticOtpError, withOtpHandling, } from './withOtpHandling.js';
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { generateQrCode } from './generateQrCode.js';
|
|
2
|
+
export { pollForWebAuthToken, } from './pollForWebAuthToken.js';
|
|
3
|
+
export { WebAuthTimeoutError } from './WebAuthTimeoutError.js';
|
|
4
|
+
export { isOtpError, OtpNonInteractiveError, OtpSecondChallengeError, SyntheticOtpError, withOtpHandling, } from './withOtpHandling.js';
|
|
5
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export interface WebAuthFetchOptions {
|
|
2
|
+
method: 'GET';
|
|
3
|
+
retry?: {
|
|
4
|
+
factor?: number;
|
|
5
|
+
maxTimeout?: number;
|
|
6
|
+
minTimeout?: number;
|
|
7
|
+
randomize?: boolean;
|
|
8
|
+
retries?: number;
|
|
9
|
+
};
|
|
10
|
+
timeout?: number;
|
|
11
|
+
}
|
|
12
|
+
export interface WebAuthFetchResponseHeaders {
|
|
13
|
+
get: (name: string) => string | null;
|
|
14
|
+
}
|
|
15
|
+
export interface WebAuthFetchResponse {
|
|
16
|
+
readonly headers: WebAuthFetchResponseHeaders;
|
|
17
|
+
readonly json: () => Promise<unknown>;
|
|
18
|
+
readonly ok: boolean;
|
|
19
|
+
readonly status: number;
|
|
20
|
+
}
|
|
21
|
+
export interface WebAuthContext {
|
|
22
|
+
Date: {
|
|
23
|
+
now: () => number;
|
|
24
|
+
};
|
|
25
|
+
setTimeout: (cb: () => void, ms: number) => void;
|
|
26
|
+
fetch: (url: string, options: WebAuthFetchOptions) => Promise<WebAuthFetchResponse>;
|
|
27
|
+
}
|
|
28
|
+
export interface PollForWebAuthTokenParams {
|
|
29
|
+
context: WebAuthContext;
|
|
30
|
+
doneUrl: string;
|
|
31
|
+
fetchOptions: WebAuthFetchOptions;
|
|
32
|
+
timeoutMs?: number;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Polls a registry's "done" URL until an authentication token is returned.
|
|
36
|
+
*
|
|
37
|
+
* The caller is responsible for displaying the authentication URL (and optional
|
|
38
|
+
* QR code) to the user before calling this function.
|
|
39
|
+
*
|
|
40
|
+
* @returns The authentication token string.
|
|
41
|
+
*
|
|
42
|
+
* @throws {@link WebAuthTimeoutError} if the timeout is exceeded.
|
|
43
|
+
*/
|
|
44
|
+
export declare function pollForWebAuthToken({ context: { Date, fetch, setTimeout }, doneUrl, fetchOptions, timeoutMs }: PollForWebAuthTokenParams): Promise<string>;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { WebAuthTimeoutError } from './WebAuthTimeoutError.js';
|
|
2
|
+
/**
|
|
3
|
+
* Polls a registry's "done" URL until an authentication token is returned.
|
|
4
|
+
*
|
|
5
|
+
* The caller is responsible for displaying the authentication URL (and optional
|
|
6
|
+
* QR code) to the user before calling this function.
|
|
7
|
+
*
|
|
8
|
+
* @returns The authentication token string.
|
|
9
|
+
*
|
|
10
|
+
* @throws {@link WebAuthTimeoutError} if the timeout is exceeded.
|
|
11
|
+
*/
|
|
12
|
+
export async function pollForWebAuthToken({ context: { Date, fetch, setTimeout }, doneUrl, fetchOptions, timeoutMs = 5 * 60 * 1000, }) {
|
|
13
|
+
const startTime = Date.now();
|
|
14
|
+
const pollIntervalMs = 1000;
|
|
15
|
+
while (true) {
|
|
16
|
+
const now = Date.now();
|
|
17
|
+
if (now - startTime > timeoutMs) {
|
|
18
|
+
throw new WebAuthTimeoutError(now, startTime, timeoutMs);
|
|
19
|
+
}
|
|
20
|
+
// eslint-disable-next-line no-await-in-loop
|
|
21
|
+
await new Promise(resolve => setTimeout(resolve, pollIntervalMs));
|
|
22
|
+
let response;
|
|
23
|
+
try {
|
|
24
|
+
// eslint-disable-next-line no-await-in-loop
|
|
25
|
+
response = await fetch(doneUrl, fetchOptions);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
if (!response.ok)
|
|
31
|
+
continue;
|
|
32
|
+
if (response.status === 202) {
|
|
33
|
+
// Registry is still waiting for authentication.
|
|
34
|
+
// Respect Retry-After header if present by waiting the additional time
|
|
35
|
+
// beyond the default poll interval already elapsed above, but do not
|
|
36
|
+
// exceed the overall timeout.
|
|
37
|
+
const retryAfterSeconds = Number(response.headers.get('retry-after'));
|
|
38
|
+
if (Number.isFinite(retryAfterSeconds)) {
|
|
39
|
+
const additionalMs = retryAfterSeconds * 1000 - pollIntervalMs;
|
|
40
|
+
if (additionalMs > 0) {
|
|
41
|
+
const nowAfterPoll = Date.now();
|
|
42
|
+
const remainingMs = timeoutMs - (nowAfterPoll - startTime);
|
|
43
|
+
if (remainingMs <= 0) {
|
|
44
|
+
throw new WebAuthTimeoutError(nowAfterPoll, startTime, timeoutMs);
|
|
45
|
+
}
|
|
46
|
+
const sleepMs = Math.min(additionalMs, remainingMs);
|
|
47
|
+
// eslint-disable-next-line no-await-in-loop
|
|
48
|
+
await new Promise(resolve => setTimeout(resolve, sleepMs));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
let body;
|
|
54
|
+
try {
|
|
55
|
+
// eslint-disable-next-line no-await-in-loop
|
|
56
|
+
body = await response.json();
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (body.token) {
|
|
62
|
+
return body.token;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=pollForWebAuthToken.js.map
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { PnpmError } from '@pnpm/error';
|
|
2
|
+
import type { WebAuthFetchOptions, WebAuthFetchResponse } from './pollForWebAuthToken.js';
|
|
3
|
+
export interface OtpEnquirer {
|
|
4
|
+
prompt: (options: OtpPromptOptions) => Promise<OtpPromptResponse | undefined>;
|
|
5
|
+
}
|
|
6
|
+
export interface OtpPromptOptions {
|
|
7
|
+
message: string;
|
|
8
|
+
name: 'otp';
|
|
9
|
+
type: 'input';
|
|
10
|
+
}
|
|
11
|
+
export interface OtpPromptResponse {
|
|
12
|
+
otp?: string;
|
|
13
|
+
}
|
|
14
|
+
interface OtpDate {
|
|
15
|
+
now: () => number;
|
|
16
|
+
}
|
|
17
|
+
export interface OtpContext {
|
|
18
|
+
Date: OtpDate;
|
|
19
|
+
setTimeout: (cb: () => void, ms: number) => void;
|
|
20
|
+
enquirer: OtpEnquirer;
|
|
21
|
+
fetch: (url: string, options: WebAuthFetchOptions) => Promise<WebAuthFetchResponse>;
|
|
22
|
+
globalInfo: (message: string) => void;
|
|
23
|
+
globalWarn: (message: string) => void;
|
|
24
|
+
process: Record<'stdin' | 'stdout', {
|
|
25
|
+
isTTY?: boolean;
|
|
26
|
+
}>;
|
|
27
|
+
}
|
|
28
|
+
interface OtpErrorBody {
|
|
29
|
+
authUrl?: string;
|
|
30
|
+
doneUrl?: string;
|
|
31
|
+
}
|
|
32
|
+
interface OtpError {
|
|
33
|
+
code: string;
|
|
34
|
+
body?: OtpErrorBody;
|
|
35
|
+
}
|
|
36
|
+
export declare const isOtpError: (error: unknown) => error is OtpError;
|
|
37
|
+
export interface OtpHandlingParams<T> {
|
|
38
|
+
context: OtpContext;
|
|
39
|
+
fetchOptions: WebAuthFetchOptions;
|
|
40
|
+
operation: (otp?: string) => Promise<T>;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Wraps an operation with OTP (one-time password) challenge handling.
|
|
44
|
+
*
|
|
45
|
+
* When the operation throws an error with `code: 'EOTP'`, this function:
|
|
46
|
+
* 1. Uses the web-based authentication flow if the error body contains
|
|
47
|
+
* `authUrl` and `doneUrl`.
|
|
48
|
+
* 2. Falls back to prompting the user for a classic OTP code.
|
|
49
|
+
* 3. Retries the operation with the obtained OTP.
|
|
50
|
+
*
|
|
51
|
+
* @throws {@link OtpNonInteractiveError} if OTP is required but the terminal is not interactive.
|
|
52
|
+
* @throws {@link OtpSecondChallengeError} if the registry requests OTP a second time after one was submitted.
|
|
53
|
+
* @throws the original error if OTP handling is not applicable.
|
|
54
|
+
*
|
|
55
|
+
* @see https://github.com/npm/cli/blob/7d900c46/lib/utils/otplease.js for npm's implementation.
|
|
56
|
+
*/
|
|
57
|
+
export declare function withOtpHandling<T>({ context, fetchOptions, operation }: OtpHandlingParams<T>): Promise<T>;
|
|
58
|
+
/**
|
|
59
|
+
* Synthetic instance of {@link OtpError} meant to be thrown by the callbacks of {@link withOtpHandling}
|
|
60
|
+
* and caught and handled by {@link withOtpHandling}.
|
|
61
|
+
*/
|
|
62
|
+
export declare class SyntheticOtpError extends Error implements OtpError {
|
|
63
|
+
readonly code = "EOTP";
|
|
64
|
+
readonly body?: OtpErrorBody;
|
|
65
|
+
constructor(body: OtpErrorBody | undefined);
|
|
66
|
+
static fromUnknownBody(globalWarn: OtpContext['globalWarn'], body: unknown): SyntheticOtpError;
|
|
67
|
+
}
|
|
68
|
+
export declare class OtpNonInteractiveError extends PnpmError {
|
|
69
|
+
constructor();
|
|
70
|
+
}
|
|
71
|
+
export declare class OtpSecondChallengeError extends PnpmError {
|
|
72
|
+
constructor();
|
|
73
|
+
}
|
|
74
|
+
export {};
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { PnpmError } from '@pnpm/error';
|
|
2
|
+
import { generateQrCode } from './generateQrCode.js';
|
|
3
|
+
import { pollForWebAuthToken } from './pollForWebAuthToken.js';
|
|
4
|
+
export const isOtpError = (error) => error != null &&
|
|
5
|
+
typeof error === 'object' &&
|
|
6
|
+
'code' in error &&
|
|
7
|
+
error.code === 'EOTP';
|
|
8
|
+
/**
|
|
9
|
+
* Wraps an operation with OTP (one-time password) challenge handling.
|
|
10
|
+
*
|
|
11
|
+
* When the operation throws an error with `code: 'EOTP'`, this function:
|
|
12
|
+
* 1. Uses the web-based authentication flow if the error body contains
|
|
13
|
+
* `authUrl` and `doneUrl`.
|
|
14
|
+
* 2. Falls back to prompting the user for a classic OTP code.
|
|
15
|
+
* 3. Retries the operation with the obtained OTP.
|
|
16
|
+
*
|
|
17
|
+
* @throws {@link OtpNonInteractiveError} if OTP is required but the terminal is not interactive.
|
|
18
|
+
* @throws {@link OtpSecondChallengeError} if the registry requests OTP a second time after one was submitted.
|
|
19
|
+
* @throws the original error if OTP handling is not applicable.
|
|
20
|
+
*
|
|
21
|
+
* @see https://github.com/npm/cli/blob/7d900c46/lib/utils/otplease.js for npm's implementation.
|
|
22
|
+
*/
|
|
23
|
+
export async function withOtpHandling({ context, fetchOptions, operation, }) {
|
|
24
|
+
const { enquirer, globalInfo, process, } = context;
|
|
25
|
+
try {
|
|
26
|
+
return await operation();
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
if (!isOtpError(error))
|
|
30
|
+
throw error;
|
|
31
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
32
|
+
throw new OtpNonInteractiveError();
|
|
33
|
+
}
|
|
34
|
+
let otp;
|
|
35
|
+
if (error.body?.authUrl && error.body?.doneUrl) {
|
|
36
|
+
const qrCode = generateQrCode(error.body.authUrl);
|
|
37
|
+
globalInfo(`Authenticate your account at:\n${error.body.authUrl}\n\n${qrCode}`);
|
|
38
|
+
otp = await pollForWebAuthToken({
|
|
39
|
+
context,
|
|
40
|
+
doneUrl: error.body.doneUrl,
|
|
41
|
+
fetchOptions,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
const enquirerResponse = await enquirer.prompt({
|
|
46
|
+
message: 'This operation requires a one-time password.\nEnter OTP:',
|
|
47
|
+
name: 'otp',
|
|
48
|
+
type: 'input',
|
|
49
|
+
});
|
|
50
|
+
// Use || (not ??) so that empty-string input is treated as "no OTP provided"
|
|
51
|
+
otp = enquirerResponse?.otp || undefined;
|
|
52
|
+
}
|
|
53
|
+
if (otp != null) {
|
|
54
|
+
try {
|
|
55
|
+
return await operation(otp);
|
|
56
|
+
}
|
|
57
|
+
catch (retryError) {
|
|
58
|
+
if (isOtpError(retryError)) {
|
|
59
|
+
throw new OtpSecondChallengeError();
|
|
60
|
+
}
|
|
61
|
+
throw retryError;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
throw error;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Synthetic instance of {@link OtpError} meant to be thrown by the callbacks of {@link withOtpHandling}
|
|
69
|
+
* and caught and handled by {@link withOtpHandling}.
|
|
70
|
+
*/
|
|
71
|
+
export class SyntheticOtpError extends Error {
|
|
72
|
+
code = 'EOTP';
|
|
73
|
+
body;
|
|
74
|
+
constructor(body) {
|
|
75
|
+
super('This error was meant to be caught by `withOtpHandling`, not to propagate to other parts of the code');
|
|
76
|
+
this.body = body;
|
|
77
|
+
}
|
|
78
|
+
static fromUnknownBody(globalWarn, body) {
|
|
79
|
+
if (body == null || typeof body !== 'object') {
|
|
80
|
+
return new SyntheticOtpError(undefined);
|
|
81
|
+
}
|
|
82
|
+
let authUrl;
|
|
83
|
+
let doneUrl;
|
|
84
|
+
if ('authUrl' in body) {
|
|
85
|
+
if (typeof body.authUrl === 'string') {
|
|
86
|
+
authUrl = body.authUrl;
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
globalWarn(`OTP error body: authUrl has type ${typeof body.authUrl}, expected string`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if ('doneUrl' in body) {
|
|
93
|
+
if (typeof body.doneUrl === 'string') {
|
|
94
|
+
doneUrl = body.doneUrl;
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
globalWarn(`OTP error body: doneUrl has type ${typeof body.doneUrl}, expected string`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return new SyntheticOtpError({ authUrl, doneUrl });
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
export class OtpNonInteractiveError extends PnpmError {
|
|
104
|
+
constructor() {
|
|
105
|
+
super('OTP_NON_INTERACTIVE', 'The registry requires additional authentication, but pnpm is not running in an interactive terminal', {
|
|
106
|
+
hint: 'Re-run this command in an interactive terminal to complete authentication, or provide the --otp option if you are using a classic one-time password (OTP)',
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
export class OtpSecondChallengeError extends PnpmError {
|
|
111
|
+
constructor() {
|
|
112
|
+
super('OTP_SECOND_CHALLENGE', 'The registry requested a one-time password (OTP) a second time after one was already provided', {
|
|
113
|
+
hint: 'This is unexpected behavior from the registry. Try the command again later and, if the issue persists, verify that your registry supports OTP-based authentication or contact the registry administrator.',
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=withOtpHandling.js.map
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pnpm/network.web-auth",
|
|
3
|
+
"version": "1000.0.0",
|
|
4
|
+
"description": "Web-based authentication flow with QR code display and token polling",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"pnpm",
|
|
7
|
+
"pnpm11",
|
|
8
|
+
"auth"
|
|
9
|
+
],
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"funding": "https://opencollective.com/pnpm",
|
|
12
|
+
"repository": "https://github.com/pnpm/pnpm/tree/main/network/web-auth",
|
|
13
|
+
"homepage": "https://github.com/pnpm/pnpm/tree/main/network/web-auth#readme",
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/pnpm/pnpm/issues"
|
|
16
|
+
},
|
|
17
|
+
"type": "module",
|
|
18
|
+
"main": "lib/index.js",
|
|
19
|
+
"types": "lib/index.d.ts",
|
|
20
|
+
"exports": {
|
|
21
|
+
".": "./lib/index.js"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"lib",
|
|
25
|
+
"!*.map"
|
|
26
|
+
],
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"qrcode-terminal": "^0.12.0",
|
|
29
|
+
"@pnpm/error": "1000.0.5"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@jest/globals": "30.3.0",
|
|
33
|
+
"@types/qrcode-terminal": "^0.12.2",
|
|
34
|
+
"@pnpm/network.web-auth": "1000.0.0"
|
|
35
|
+
},
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=22.13"
|
|
38
|
+
},
|
|
39
|
+
"jest": {
|
|
40
|
+
"preset": "@pnpm/jest-config"
|
|
41
|
+
},
|
|
42
|
+
"scripts": {
|
|
43
|
+
"lint": "eslint \"src/**/*.ts\" \"test/**/*.ts\"",
|
|
44
|
+
"test": "pn compile && pn .test",
|
|
45
|
+
"compile": "tsgo --build && pn lint --fix",
|
|
46
|
+
".test": "cross-env NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules --disable-warning=ExperimentalWarning --disable-warning=DEP0169\" jest"
|
|
47
|
+
}
|
|
48
|
+
}
|