@mnemom/mnemom 0.17.31-next.1 → 0.17.31
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/dist/commands/auth.js +20 -0
- package/dist/lib/oauth.d.ts +30 -0
- package/dist/lib/oauth.js +155 -43
- package/package.json +4 -4
package/dist/commands/auth.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getAuthInfo, clearAuthTokens, loginWithBrowser, loginWithDeviceFlow, resolveAuth, } from "../lib/auth.js";
|
|
2
|
+
import { getMe } from "../lib/api.js";
|
|
2
3
|
import { fmt } from "../lib/format.js";
|
|
3
4
|
export async function loginCommand(options = {}) {
|
|
4
5
|
try {
|
|
@@ -7,6 +8,7 @@ export async function loginCommand(options = {}) {
|
|
|
7
8
|
const tokens = options.noBrowser ? await loginWithDeviceFlow() : await loginWithBrowser();
|
|
8
9
|
console.log();
|
|
9
10
|
console.log(fmt.success("Logged in successfully!"));
|
|
11
|
+
await printSignedInAs();
|
|
10
12
|
printGrant(tokens);
|
|
11
13
|
console.log();
|
|
12
14
|
}
|
|
@@ -16,6 +18,24 @@ export async function loginCommand(options = {}) {
|
|
|
16
18
|
process.exit(1);
|
|
17
19
|
}
|
|
18
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* Name the account this login signed in as, so a login finished in the wrong
|
|
23
|
+
* account (an approval from another browser or device that was signed in as
|
|
24
|
+
* someone else) is visible straight away. The OAuth token carries no identity,
|
|
25
|
+
* so this asks the API; if that fails the login still succeeded, so it prints
|
|
26
|
+
* nothing rather than an error.
|
|
27
|
+
*/
|
|
28
|
+
async function printSignedInAs() {
|
|
29
|
+
let email;
|
|
30
|
+
try {
|
|
31
|
+
email = (await getMe()).email;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
if (email)
|
|
37
|
+
console.log(`Signed in as ${email}`);
|
|
38
|
+
}
|
|
19
39
|
/**
|
|
20
40
|
* Print what the issued token actually grants. OAuth access tokens are scoped
|
|
21
41
|
* and (deliberately) carry no user identity, so we show scope + expiry rather
|
package/dist/lib/oauth.d.ts
CHANGED
|
@@ -89,10 +89,35 @@ export interface LoopbackResult {
|
|
|
89
89
|
* scoped tokens plus the client_id that was used (so the caller can persist it
|
|
90
90
|
* for refresh). `openUrl` is injectable so tests can drive the flow without
|
|
91
91
|
* spawning a real browser.
|
|
92
|
+
*
|
|
93
|
+
* Cross-device completion: when the AS advertises a device authorization
|
|
94
|
+
* endpoint, this also starts an RFC 8628 device authorization for the same
|
|
95
|
+
* client and prints its code with the verification URI. The login then
|
|
96
|
+
* finishes on whichever comes first: the loopback redirect (same machine) or an
|
|
97
|
+
* approval of the device code from any browser (e.g. the phone that opened the
|
|
98
|
+
* sign-up confirmation email), where the person types the code from this
|
|
99
|
+
* terminal. The code is never put in the authorize URL or any other link, so
|
|
100
|
+
* nothing that opens in a browser can approve this login without the person
|
|
101
|
+
* typing it. Tokens only ever come back to this process: the loopback code
|
|
102
|
+
* needs our PKCE verifier and the device grant needs our device_code. If the
|
|
103
|
+
* device authorization cannot be started, the login is exactly the loopback
|
|
104
|
+
* flow it always was.
|
|
92
105
|
*/
|
|
93
106
|
export declare function loginWithLoopback(openUrl?: (url: string) => void, opts?: {
|
|
94
107
|
inviteToken?: string;
|
|
108
|
+
sleep?: (ms: number) => Promise<void>;
|
|
95
109
|
}): Promise<LoopbackResult>;
|
|
110
|
+
/** How long the loopback listener waits while a device code is live. */
|
|
111
|
+
export declare function crossDeviceWaitMs(expiresInSec: number): number;
|
|
112
|
+
/**
|
|
113
|
+
* Settle with whichever login path finishes first.
|
|
114
|
+
* - The loopback path settles the login either way, exactly as before: a
|
|
115
|
+
* denial, a state mismatch or the timeout in the browser ends it.
|
|
116
|
+
* - The device path ends it on success, or when the person explicitly denies
|
|
117
|
+
* the device code. Any other device failure (expiry, a transport error)
|
|
118
|
+
* only drops that path and the loopback wait continues.
|
|
119
|
+
*/
|
|
120
|
+
export declare function firstLogin(viaLoopback: Promise<OAuthTokens>, viaDevice: Promise<OAuthTokens> | null): Promise<OAuthTokens>;
|
|
96
121
|
export interface DeviceResult {
|
|
97
122
|
tokens: OAuthTokens;
|
|
98
123
|
clientId: string;
|
|
@@ -169,6 +194,11 @@ export declare function browserOpenerFor(platform: NodeJS.Platform, href: string
|
|
|
169
194
|
* Returns whether a launch was attempted.
|
|
170
195
|
*/
|
|
171
196
|
export declare function openBrowser(url: string): boolean;
|
|
197
|
+
/**
|
|
198
|
+
* setTimeout-based sleep that resolves early when `signal` aborts, so an
|
|
199
|
+
* abandoned device poll does not hold the process open after login finishes.
|
|
200
|
+
*/
|
|
201
|
+
export declare function abortableSleep(ms: number, signal: AbortSignal): Promise<void>;
|
|
172
202
|
/**
|
|
173
203
|
* Await `promise` while emitting a heartbeat tick every `intervalMs`, so a long
|
|
174
204
|
* silent wait (a browser sign-in round-trip) doesn't look hung. `onTick` is
|
package/dist/lib/oauth.js
CHANGED
|
@@ -35,6 +35,12 @@ const DEVICE_CODE_GRANT = "urn:ietf:params:oauth:grant-type:device_code";
|
|
|
35
35
|
// Loopback login waits at most this long for the browser round-trip before
|
|
36
36
|
// giving up and freeing the port.
|
|
37
37
|
const LOOPBACK_TIMEOUT_MS = 5 * 60 * 1000;
|
|
38
|
+
// Browser login also starts a device authorization (RFC 8628) when the AS offers
|
|
39
|
+
// one, so the login can be approved from ANOTHER device: someone who signs up in
|
|
40
|
+
// the browser and confirms the email on their phone. While that is live the
|
|
41
|
+
// loopback listener waits as long as the device code is valid, capped here, so
|
|
42
|
+
// the port is never left open indefinitely.
|
|
43
|
+
const CROSS_DEVICE_MAX_WAIT_MS = 15 * 60 * 1000;
|
|
38
44
|
// Cadence for the "…still waiting for you to finish signing in" heartbeat that
|
|
39
45
|
// both interactive login poll loops emit, so a multi-minute browser sign-in
|
|
40
46
|
// doesn't read as a hung CLI (matches the card-write retry tick in try-me).
|
|
@@ -208,46 +214,120 @@ function tokensFromResponse(data) {
|
|
|
208
214
|
* scoped tokens plus the client_id that was used (so the caller can persist it
|
|
209
215
|
* for refresh). `openUrl` is injectable so tests can drive the flow without
|
|
210
216
|
* spawning a real browser.
|
|
217
|
+
*
|
|
218
|
+
* Cross-device completion: when the AS advertises a device authorization
|
|
219
|
+
* endpoint, this also starts an RFC 8628 device authorization for the same
|
|
220
|
+
* client and prints its code with the verification URI. The login then
|
|
221
|
+
* finishes on whichever comes first: the loopback redirect (same machine) or an
|
|
222
|
+
* approval of the device code from any browser (e.g. the phone that opened the
|
|
223
|
+
* sign-up confirmation email), where the person types the code from this
|
|
224
|
+
* terminal. The code is never put in the authorize URL or any other link, so
|
|
225
|
+
* nothing that opens in a browser can approve this login without the person
|
|
226
|
+
* typing it. Tokens only ever come back to this process: the loopback code
|
|
227
|
+
* needs our PKCE verifier and the device grant needs our device_code. If the
|
|
228
|
+
* device authorization cannot be started, the login is exactly the loopback
|
|
229
|
+
* flow it always was.
|
|
211
230
|
*/
|
|
212
231
|
export async function loginWithLoopback(openUrl = openBrowser, opts = {}) {
|
|
213
232
|
const meta = await discover();
|
|
214
233
|
const pkce = generatePkce();
|
|
215
234
|
const state = crypto.randomBytes(16).toString("hex");
|
|
216
|
-
const { port, codePromise, close } = await startLoopbackServer(state);
|
|
235
|
+
const { port, codePromise, close, setTimeoutMs } = await startLoopbackServer(state);
|
|
217
236
|
const redirectUri = `http://127.0.0.1:${port}/callback`;
|
|
218
|
-
|
|
219
|
-
// requires exact redirect_uri matching, and the loopback port is ephemeral,
|
|
220
|
-
// so we register per-login (cheap, public client, no secret).
|
|
221
|
-
const clientId = await registerClient([redirectUri]);
|
|
222
|
-
const authUrl = new URL(meta.authorization_endpoint);
|
|
223
|
-
authUrl.searchParams.set("response_type", "code");
|
|
224
|
-
authUrl.searchParams.set("client_id", clientId);
|
|
225
|
-
authUrl.searchParams.set("redirect_uri", redirectUri);
|
|
226
|
-
authUrl.searchParams.set("scope", negotiateScopes(meta));
|
|
227
|
-
authUrl.searchParams.set("state", state);
|
|
228
|
-
authUrl.searchParams.set("code_challenge", pkce.challenge);
|
|
229
|
-
authUrl.searchParams.set("code_challenge_method", pkce.method);
|
|
230
|
-
// A Mnemom Agent invitation (`mnemom agent --invite`) rides along so a NEW
|
|
231
|
-
// invitee who signs up in this browser round-trip skips the confirmation
|
|
232
|
-
// email: the AS ignores the param but bounces a signed-out browser to /login
|
|
233
|
-
// with this exact URL as return_to, and sign-up forwards the token to the api.
|
|
234
|
-
if (opts.inviteToken)
|
|
235
|
-
authUrl.searchParams.set("invite_token", opts.inviteToken);
|
|
236
|
-
console.log("Opening browser to authenticate...");
|
|
237
|
-
console.log(`If the browser doesn't open, visit:\n ${authUrl.toString()}\n`);
|
|
238
|
-
openUrl(authUrl.toString());
|
|
239
|
-
console.log("Waiting for authentication...");
|
|
237
|
+
const stopPolling = new AbortController();
|
|
240
238
|
try {
|
|
241
|
-
//
|
|
242
|
-
//
|
|
243
|
-
|
|
244
|
-
|
|
239
|
+
// Register a client bound to the exact loopback redirect URI. OAuth 2.1
|
|
240
|
+
// requires exact redirect_uri matching, and the loopback port is ephemeral,
|
|
241
|
+
// so we register per-login (cheap, public client, no secret). The same
|
|
242
|
+
// client also runs the device grant, so either path refreshes the same way.
|
|
243
|
+
const clientId = await registerClient([redirectUri]);
|
|
244
|
+
const device = await startCrossDeviceAuthorization(meta, clientId);
|
|
245
|
+
const authUrl = new URL(meta.authorization_endpoint);
|
|
246
|
+
authUrl.searchParams.set("response_type", "code");
|
|
247
|
+
authUrl.searchParams.set("client_id", clientId);
|
|
248
|
+
authUrl.searchParams.set("redirect_uri", redirectUri);
|
|
249
|
+
authUrl.searchParams.set("scope", negotiateScopes(meta));
|
|
250
|
+
authUrl.searchParams.set("state", state);
|
|
251
|
+
authUrl.searchParams.set("code_challenge", pkce.challenge);
|
|
252
|
+
authUrl.searchParams.set("code_challenge_method", pkce.method);
|
|
253
|
+
// A Mnemom Agent invitation (`mnemom agent --invite`) rides along so a NEW
|
|
254
|
+
// invitee who signs up in this browser round-trip skips the confirmation
|
|
255
|
+
// email: the AS ignores the param but bounces a signed-out browser to /login
|
|
256
|
+
// with this exact URL as return_to, and sign-up forwards the token to the api.
|
|
257
|
+
if (opts.inviteToken)
|
|
258
|
+
authUrl.searchParams.set("invite_token", opts.inviteToken);
|
|
259
|
+
console.log("Opening browser to authenticate...");
|
|
260
|
+
console.log(`If the browser doesn't open, visit:\n ${authUrl.toString()}\n`);
|
|
261
|
+
if (device) {
|
|
262
|
+
setTimeoutMs(crossDeviceWaitMs(device.expires_in));
|
|
263
|
+
// The plain verification_uri, never verification_uri_complete: the person
|
|
264
|
+
// types the code there, so a link alone can never approve this login.
|
|
265
|
+
console.log(`Confirming on another device? Open ${device.verification_uri} and enter: ${device.user_code}\n`);
|
|
266
|
+
}
|
|
267
|
+
openUrl(authUrl.toString());
|
|
268
|
+
console.log("Waiting for authentication...");
|
|
269
|
+
const viaLoopback = codePromise.then((code) => exchangeCode(meta, clientId, code, pkce.verifier, redirectUri));
|
|
270
|
+
const viaDevice = device
|
|
271
|
+
? pollDeviceToken(meta, clientId, device, opts.sleep ?? ((ms) => abortableSleep(ms, stopPolling.signal)),
|
|
272
|
+
// The loopback wait below already prints the heartbeat.
|
|
273
|
+
() => { }, stopPolling.signal)
|
|
274
|
+
: null;
|
|
275
|
+
// Heartbeat while we wait — otherwise the sign-in round-trip is dead
|
|
276
|
+
// silent and reads as a hung CLI.
|
|
277
|
+
const tokens = await withHeartbeat(firstLogin(viaLoopback, viaDevice), LOGIN_HEARTBEAT_MS, (s) => console.log(`…still waiting for you to finish signing in (${s}s)`));
|
|
245
278
|
return { tokens, clientId };
|
|
246
279
|
}
|
|
247
280
|
finally {
|
|
281
|
+
stopPolling.abort();
|
|
248
282
|
close();
|
|
249
283
|
}
|
|
250
284
|
}
|
|
285
|
+
/**
|
|
286
|
+
* Start the device authorization that lets a browser login be approved from
|
|
287
|
+
* another device. Best-effort: an AS without the endpoint, or any failure,
|
|
288
|
+
* returns null and the login carries on as loopback only.
|
|
289
|
+
*/
|
|
290
|
+
async function startCrossDeviceAuthorization(meta, clientId) {
|
|
291
|
+
if (!meta.device_authorization_endpoint)
|
|
292
|
+
return null;
|
|
293
|
+
try {
|
|
294
|
+
const authz = await requestDeviceAuthorization(meta.device_authorization_endpoint, meta, clientId);
|
|
295
|
+
if (typeof authz.device_code !== "string" ||
|
|
296
|
+
typeof authz.user_code !== "string" ||
|
|
297
|
+
typeof authz.verification_uri !== "string" ||
|
|
298
|
+
typeof authz.expires_in !== "number") {
|
|
299
|
+
return null;
|
|
300
|
+
}
|
|
301
|
+
return authz;
|
|
302
|
+
}
|
|
303
|
+
catch {
|
|
304
|
+
return null;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
/** How long the loopback listener waits while a device code is live. */
|
|
308
|
+
export function crossDeviceWaitMs(expiresInSec) {
|
|
309
|
+
const ms = Number.isFinite(expiresInSec) ? expiresInSec * 1000 : 0;
|
|
310
|
+
return Math.min(Math.max(ms, LOOPBACK_TIMEOUT_MS), CROSS_DEVICE_MAX_WAIT_MS);
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Settle with whichever login path finishes first.
|
|
314
|
+
* - The loopback path settles the login either way, exactly as before: a
|
|
315
|
+
* denial, a state mismatch or the timeout in the browser ends it.
|
|
316
|
+
* - The device path ends it on success, or when the person explicitly denies
|
|
317
|
+
* the device code. Any other device failure (expiry, a transport error)
|
|
318
|
+
* only drops that path and the loopback wait continues.
|
|
319
|
+
*/
|
|
320
|
+
export function firstLogin(viaLoopback, viaDevice) {
|
|
321
|
+
if (!viaDevice)
|
|
322
|
+
return viaLoopback;
|
|
323
|
+
return new Promise((resolve, reject) => {
|
|
324
|
+
viaLoopback.then(resolve, reject);
|
|
325
|
+
viaDevice.then(resolve, (err) => {
|
|
326
|
+
if (err instanceof Error && err.message === DEVICE_ACCESS_DENIED)
|
|
327
|
+
reject(err);
|
|
328
|
+
});
|
|
329
|
+
});
|
|
330
|
+
}
|
|
251
331
|
/**
|
|
252
332
|
* Start a loopback HTTP listener that captures the OAuth redirect
|
|
253
333
|
* (GET /callback?code=...&state=...). Resolves with the authorization code once
|
|
@@ -293,13 +373,18 @@ function startLoopbackServer(expectedState) {
|
|
|
293
373
|
return new Promise((resolve) => {
|
|
294
374
|
server.listen(0, "127.0.0.1", () => {
|
|
295
375
|
const port = server.address().port;
|
|
296
|
-
const
|
|
376
|
+
const giveUp = () => {
|
|
297
377
|
rejectCode(new Error("Login timed out. Please try again."));
|
|
298
378
|
server.close();
|
|
299
|
-
}
|
|
379
|
+
};
|
|
380
|
+
let timeout = setTimeout(giveUp, LOOPBACK_TIMEOUT_MS);
|
|
300
381
|
resolve({
|
|
301
382
|
port,
|
|
302
383
|
codePromise,
|
|
384
|
+
setTimeoutMs: (ms) => {
|
|
385
|
+
clearTimeout(timeout);
|
|
386
|
+
timeout = setTimeout(giveUp, ms);
|
|
387
|
+
},
|
|
303
388
|
close: () => {
|
|
304
389
|
clearTimeout(timeout);
|
|
305
390
|
server.close();
|
|
@@ -358,18 +443,7 @@ export async function loginWithDevice(opts) {
|
|
|
358
443
|
// clients"). Register a loopback placeholder — it satisfies validation and is
|
|
359
444
|
// never used, since the device grant never redirects.
|
|
360
445
|
const clientId = await registerClient(["http://127.0.0.1/callback"]);
|
|
361
|
-
const
|
|
362
|
-
method: "POST",
|
|
363
|
-
headers: {
|
|
364
|
-
"Content-Type": "application/x-www-form-urlencoded",
|
|
365
|
-
Accept: "application/json",
|
|
366
|
-
},
|
|
367
|
-
body: new URLSearchParams({ client_id: clientId, scope: negotiateScopes(meta) }),
|
|
368
|
-
});
|
|
369
|
-
if (!authzRes.ok) {
|
|
370
|
-
throw new Error(`Device authorization failed: ${await oauthError(authzRes)}`);
|
|
371
|
-
}
|
|
372
|
-
const authz = (await authzRes.json());
|
|
446
|
+
const authz = await requestDeviceAuthorization(meta.device_authorization_endpoint, meta, clientId);
|
|
373
447
|
display("");
|
|
374
448
|
display("To authenticate, visit:");
|
|
375
449
|
display(` ${authz.verification_uri}`);
|
|
@@ -385,7 +459,26 @@ export async function loginWithDevice(opts) {
|
|
|
385
459
|
const tokens = await pollDeviceToken(meta, clientId, authz, sleep, display);
|
|
386
460
|
return { tokens, clientId };
|
|
387
461
|
}
|
|
388
|
-
|
|
462
|
+
/** POST the device authorization request (RFC 8628 §3.1) for `clientId`. */
|
|
463
|
+
async function requestDeviceAuthorization(endpoint, meta, clientId) {
|
|
464
|
+
const authzRes = await oauthFetch("Device authorization at", endpoint, {
|
|
465
|
+
method: "POST",
|
|
466
|
+
headers: {
|
|
467
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
468
|
+
Accept: "application/json",
|
|
469
|
+
},
|
|
470
|
+
body: new URLSearchParams({ client_id: clientId, scope: negotiateScopes(meta) }),
|
|
471
|
+
});
|
|
472
|
+
if (!authzRes.ok) {
|
|
473
|
+
throw new Error(`Device authorization failed: ${await oauthError(authzRes)}`);
|
|
474
|
+
}
|
|
475
|
+
return (await authzRes.json());
|
|
476
|
+
}
|
|
477
|
+
/** The message a device-code denial rejects with (matched by firstLogin). */
|
|
478
|
+
const DEVICE_ACCESS_DENIED = "Authorization was denied.";
|
|
479
|
+
/** Thrown into an abandoned device poll once the other login path has won. */
|
|
480
|
+
const POLL_STOPPED = "Device polling stopped.";
|
|
481
|
+
async function pollDeviceToken(meta, clientId, authz, sleep, display, signal) {
|
|
389
482
|
// RFC 8628 §3.5: default interval is 5s if the server omits it; on slow_down
|
|
390
483
|
// we increase the interval by 5s and keep that as the new minimum.
|
|
391
484
|
let intervalMs = (authz.interval ?? 5) * 1000;
|
|
@@ -399,6 +492,8 @@ async function pollDeviceToken(meta, clientId, authz, sleep, display) {
|
|
|
399
492
|
throw new Error("Device authorization expired before approval. Please try again.");
|
|
400
493
|
}
|
|
401
494
|
await sleep(intervalMs);
|
|
495
|
+
if (signal?.aborted)
|
|
496
|
+
throw new Error(POLL_STOPPED);
|
|
402
497
|
waitedMs += intervalMs;
|
|
403
498
|
let res;
|
|
404
499
|
try {
|
|
@@ -437,7 +532,7 @@ async function pollDeviceToken(meta, clientId, authz, sleep, display) {
|
|
|
437
532
|
case "expired_token":
|
|
438
533
|
throw new Error("Device authorization expired before approval. Please try again.");
|
|
439
534
|
case "access_denied":
|
|
440
|
-
throw new Error(
|
|
535
|
+
throw new Error(DEVICE_ACCESS_DENIED);
|
|
441
536
|
default:
|
|
442
537
|
throw new Error(`Device authorization failed: ${body.error ?? `HTTP ${res.status}`}` +
|
|
443
538
|
(body.error_description ? ` — ${body.error_description}` : ""));
|
|
@@ -623,6 +718,23 @@ export function openBrowser(url) {
|
|
|
623
718
|
function defaultSleep(ms) {
|
|
624
719
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
625
720
|
}
|
|
721
|
+
/**
|
|
722
|
+
* setTimeout-based sleep that resolves early when `signal` aborts, so an
|
|
723
|
+
* abandoned device poll does not hold the process open after login finishes.
|
|
724
|
+
*/
|
|
725
|
+
export function abortableSleep(ms, signal) {
|
|
726
|
+
return new Promise((resolve) => {
|
|
727
|
+
if (signal.aborted)
|
|
728
|
+
return resolve();
|
|
729
|
+
const done = () => {
|
|
730
|
+
clearTimeout(timer);
|
|
731
|
+
signal.removeEventListener("abort", done);
|
|
732
|
+
resolve();
|
|
733
|
+
};
|
|
734
|
+
const timer = setTimeout(done, ms);
|
|
735
|
+
signal.addEventListener("abort", done, { once: true });
|
|
736
|
+
});
|
|
737
|
+
}
|
|
626
738
|
/**
|
|
627
739
|
* Await `promise` while emitting a heartbeat tick every `intervalMs`, so a long
|
|
628
740
|
* silent wait (a browser sign-in round-trip) doesn't look hung. `onTick` is
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mnemom/mnemom",
|
|
3
|
-
"version": "0.17.31
|
|
3
|
+
"version": "0.17.31",
|
|
4
4
|
"description": "Transparent AI agent tracing",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -26,10 +26,10 @@
|
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"@types/js-yaml": "^4.0.9",
|
|
28
28
|
"@types/node": "^20.10.0",
|
|
29
|
-
"@vitest/coverage-v8": "^4.1.
|
|
29
|
+
"@vitest/coverage-v8": "^4.1.12",
|
|
30
30
|
"tsx": "^4.23.1",
|
|
31
31
|
"typescript": "^6.0.3",
|
|
32
|
-
"vitest": "^4.1.
|
|
32
|
+
"vitest": "^4.1.12"
|
|
33
33
|
},
|
|
34
34
|
"files": [
|
|
35
35
|
"dist",
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
"node": ">=18.0.0"
|
|
72
72
|
},
|
|
73
73
|
"overrides": {
|
|
74
|
-
"@vitest/mocker": "^4.1.
|
|
74
|
+
"@vitest/mocker": "^4.1.12",
|
|
75
75
|
"nanoid": "^3.3.18"
|
|
76
76
|
}
|
|
77
77
|
}
|