@apifuse/provider-sdk 2.2.0-beta.7 → 2.2.0-beta.9
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/CHANGELOG.md +8 -0
- package/dist/auth-turn/index.d.ts +2 -2
- package/dist/config/loader.d.ts +147 -6
- package/dist/config/loader.js +360 -48
- package/dist/define.js +63 -8
- package/dist/runtime/choice.js +20 -2
- package/dist/runtime/http.js +115 -9
- package/dist/runtime/proxy-errors.js +6 -2
- package/dist/runtime/proxy-nodemaven.d.ts +35 -0
- package/dist/runtime/proxy-nodemaven.js +128 -0
- package/dist/runtime/proxy-telemetry.d.ts +2 -1
- package/dist/runtime/proxy-telemetry.js +39 -4
- package/dist/runtime/state.js +12 -1
- package/dist/runtime/stealth.js +60 -18
- package/dist/server/serve.js +11 -0
- package/dist/server/types.d.ts +9 -9
- package/dist/types.d.ts +30 -1
- package/package.json +4 -3
- package/src/config/loader.ts +514 -61
- package/src/define.ts +76 -13
- package/src/runtime/choice.ts +25 -4
- package/src/runtime/http.ts +134 -11
- package/src/runtime/proxy-errors.ts +12 -4
- package/src/runtime/proxy-nodemaven.ts +178 -0
- package/src/runtime/proxy-telemetry.ts +56 -5
- package/src/runtime/state.ts +11 -1
- package/src/runtime/stealth.ts +68 -22
- package/src/server/serve.ts +11 -0
- package/src/types.ts +30 -1
package/dist/runtime/stealth.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { Impit } from "impit";
|
|
3
|
-
import { DEFAULT_SMARTPROXY_POOL_SIZE, invalidateProxyResolutionCacheAsync, ProxyResolutionError, resolveProxyConfigAsync,
|
|
3
|
+
import { DEFAULT_SMARTPROXY_POOL_SIZE, invalidateProxyResolutionCacheAsync, policyResolvesRegistryVendorChain, ProxyResolutionError, resolvePolicyProxyPoolSpan, resolvePolicyTransportAttemptCap, resolveProxyConfigAsync, vendorFromResolvedSource, } from "../config/loader.js";
|
|
4
4
|
import { SDKError, TransportError } from "../errors.js";
|
|
5
5
|
import { getStealthProfile } from "../stealth/profiles.js";
|
|
6
6
|
import { createProxyAuthIpDeniedError, createProxyEdgeAuthRejectedError, createProxyEdgeTlsRejectedError, createProxyPoolExhaustedError, createProxyPoolStaleError, isProxyAuthIpDeniedMessage, isProxyEdgeAuthRejectedMessage, isProxyEdgeTlsRejectedResponse, isProxyPoolRefreshableError, isProxyPoolStaleMessage, isProxyPoolStaleStatus, PROXY_EDGE_AUTH_REJECTED_CODE, PROXY_POOL_STALE_CODE, } from "./proxy-errors.js";
|
|
@@ -8,7 +8,6 @@ import { computeProxyAttemptIndex, computeProxyTransportRetryDelayMs, createDefa
|
|
|
8
8
|
import { appendQueryParams } from "./request-options.js";
|
|
9
9
|
const DEFAULT_PROFILE = "chrome-146";
|
|
10
10
|
const MISSING_PROXY_WARNING = "[provider-sdk] Provider requested proxy routing, but no proxy URL was configured. Continuing without proxy.";
|
|
11
|
-
const MAX_POLICY_PROXY_RETRY_ATTEMPTS = SMARTPROXY_MAX_POOL_SIZE;
|
|
12
11
|
const MAX_POLICY_PROXY_POOL_REFRESHES = 1;
|
|
13
12
|
const PROXY_CONNECT_FAILURE_CODE = "proxy_connect_failed";
|
|
14
13
|
const PROXY_CONNECT_FAILURE_BODY_PATTERN = /\bproxy\b.*\b(non[\s-]?200|connect|tunnel)|\bconnect\b.*\bproxy\b|\btunnel\b/i;
|
|
@@ -463,7 +462,7 @@ function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
|
|
|
463
462
|
}
|
|
464
463
|
return client;
|
|
465
464
|
}
|
|
466
|
-
async function resolveRequestProxy(options, proxyAttempt) {
|
|
465
|
+
async function resolveRequestProxy(options, proxyAttempt, refreshEpoch) {
|
|
467
466
|
const resolvedProxy = await resolveProxyConfigAsync({
|
|
468
467
|
proxy: options?.proxy ?? clientOptions.proxy,
|
|
469
468
|
upstream: clientOptions.upstream,
|
|
@@ -474,6 +473,10 @@ function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
|
|
|
474
473
|
proxyAttemptOffset: options?.proxyAttemptOffset,
|
|
475
474
|
retryAttemptOffset: proxyAttempt,
|
|
476
475
|
}),
|
|
476
|
+
// The impit stealth transport tunnels both HTTP CONNECT and SOCKS5,
|
|
477
|
+
// preserving the client TLS fingerprint end-to-end.
|
|
478
|
+
transportProtocols: ["http", "socks5"],
|
|
479
|
+
...(refreshEpoch === undefined ? {} : { proxyRefreshEpoch: refreshEpoch }),
|
|
477
480
|
telemetry: clientOptions.telemetry,
|
|
478
481
|
});
|
|
479
482
|
if (resolvedProxy.shouldWarn && !hasWarnedMissingProxy) {
|
|
@@ -484,6 +487,7 @@ function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
|
|
|
484
487
|
url: resolvedProxy.url,
|
|
485
488
|
poolIndex: proxyPoolIndexFromDiagnostics(resolvedProxy.diagnostics),
|
|
486
489
|
proxyHash: proxyEndpointHash(resolvedProxy.url),
|
|
490
|
+
vendor: vendorFromResolvedSource(resolvedProxy.source),
|
|
487
491
|
};
|
|
488
492
|
}
|
|
489
493
|
const session = {
|
|
@@ -506,12 +510,28 @@ function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
|
|
|
506
510
|
const hasPolicyProxy = isPolicyManagedProxy(clientOptions);
|
|
507
511
|
const usesPolicyAllocator = hasPolicyProxy && !options.proxy && !clientOptions.proxy;
|
|
508
512
|
const retryAttemptCap = Math.max(1, stealthRetryOptions?.attempts ?? 1);
|
|
509
|
-
|
|
513
|
+
// Span the whole vendor chain: successive attempts rotate one vendor's
|
|
514
|
+
// pool, then fail over to the next vendor via the flat attempt index.
|
|
515
|
+
const policyProxy = clientOptions.proxyPolicy ??
|
|
510
516
|
(typeof clientOptions.upstream?.proxy === "object"
|
|
511
|
-
? clientOptions.upstream.proxy
|
|
512
|
-
: undefined)
|
|
513
|
-
|
|
514
|
-
|
|
517
|
+
? clientOptions.upstream.proxy
|
|
518
|
+
: undefined);
|
|
519
|
+
// The pool span is already bounded by each vendor's max pool size
|
|
520
|
+
// (smartproxy ≤20, nodemaven ≤50), so the configured span never exceeds
|
|
521
|
+
// the chain's true maximum — a large NodeMaven pool stays fully
|
|
522
|
+
// reachable rather than being truncated at an arbitrary ceiling.
|
|
523
|
+
const policyProxyAttemptCap = Math.max(1, policyProxy ? resolvePolicyProxyPoolSpan(policyProxy) : DEFAULT_SMARTPROXY_POOL_SIZE);
|
|
524
|
+
// A registry vendor chain (smartproxy/nodemaven) is the only policy whose
|
|
525
|
+
// successive attempts resolve a *different* endpoint, so it is the only one
|
|
526
|
+
// that may widen the attempt cap to the pool span, de-duplicate endpoints,
|
|
527
|
+
// and drive allocator stale-pool refresh. A static custom/decodo policy
|
|
528
|
+
// resolves the same URL every attempt: widening/refreshing it would resend
|
|
529
|
+
// the request dozens of times (up to maxAttempts × refreshes) and bypass
|
|
530
|
+
// retry:false and unsafe-method controls. Static policies therefore follow
|
|
531
|
+
// the ordinary transport-retry budget instead.
|
|
532
|
+
const rotatesRegistryChain = usesPolicyAllocator && policyResolvesRegistryVendorChain(policyProxy);
|
|
533
|
+
const maxAttempts = rotatesRegistryChain ? policyProxyAttemptCap : retryAttemptCap;
|
|
534
|
+
const dedupeAllocatorEndpoints = rotatesRegistryChain;
|
|
515
535
|
let lastError;
|
|
516
536
|
for (let refreshAttempt = 0; refreshAttempt <= MAX_POLICY_PROXY_POOL_REFRESHES; refreshAttempt += 1) {
|
|
517
537
|
let stalePoolError;
|
|
@@ -527,7 +547,7 @@ function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
|
|
|
527
547
|
return;
|
|
528
548
|
attemptRecorded = true;
|
|
529
549
|
clientOptions.telemetry?.recordProxyAttempt?.({
|
|
530
|
-
provider: "smartproxy",
|
|
550
|
+
provider: attemptProxy?.vendor ?? "smartproxy",
|
|
531
551
|
attempt: attempt + 1,
|
|
532
552
|
...(attemptProxy?.poolIndex === undefined
|
|
533
553
|
? {}
|
|
@@ -541,11 +561,16 @@ function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
|
|
|
541
561
|
};
|
|
542
562
|
try {
|
|
543
563
|
assertNoUnsupportedFingerprintOverrides(options);
|
|
544
|
-
attemptProxy = await resolveRequestProxy(options, attempt);
|
|
564
|
+
attemptProxy = await resolveRequestProxy(options, attempt, refreshAttempt);
|
|
545
565
|
proxy = attemptProxy.url;
|
|
546
|
-
if (proxy &&
|
|
566
|
+
if (proxy && dedupeAllocatorEndpoints) {
|
|
567
|
+
// An under-filled allocation repeats endpoints (via the modulo
|
|
568
|
+
// pool mapping) before the flat offset crosses into the next
|
|
569
|
+
// vendor. Skip an already-tried endpoint and advance the offset
|
|
570
|
+
// rather than breaking — breaking here would strand the request on
|
|
571
|
+
// the primary vendor and never reach the fallback leg.
|
|
547
572
|
if (attemptedProxies.has(proxy)) {
|
|
548
|
-
|
|
573
|
+
continue;
|
|
549
574
|
}
|
|
550
575
|
attemptedProxies.add(proxy);
|
|
551
576
|
}
|
|
@@ -605,7 +630,7 @@ function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
|
|
|
605
630
|
const normalizedError = normalizeStealthTransportError(error);
|
|
606
631
|
recordProxyAttempt("error", proxyAttemptErrorCode(normalizedError), proxyAttemptStatus(normalizedError));
|
|
607
632
|
lastError = normalizedError;
|
|
608
|
-
if (proxy &&
|
|
633
|
+
if (proxy && rotatesRegistryChain && isProxyPoolRefreshableError(normalizedError)) {
|
|
609
634
|
stalePoolError = normalizedError;
|
|
610
635
|
if (shouldRunProxyAuthDiagnostic(normalizedError)) {
|
|
611
636
|
stalePoolDiagnosticProxy = proxy;
|
|
@@ -615,10 +640,27 @@ function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
|
|
|
615
640
|
}
|
|
616
641
|
break;
|
|
617
642
|
}
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
643
|
+
// Cap the number of transport retries. For a policy-allocator chain,
|
|
644
|
+
// every attempt resolves a *different* endpoint/vendor (poolIndex
|
|
645
|
+
// rotates across the concatenated vendor pool spans), so a transport
|
|
646
|
+
// failure is a signal to advance to the next endpoint — potentially
|
|
647
|
+
// crossing into the fallback vendor — not to retry the same endpoint.
|
|
648
|
+
// Truncating that rotation at the per-endpoint retry budget would
|
|
649
|
+
// strand the request on the primary vendor and never reach the
|
|
650
|
+
// fallback, since the crossover only happens once the flat attempt
|
|
651
|
+
// index exceeds the primary vendor's pool size (~10-20).
|
|
652
|
+
// resolvePolicyTransportAttemptCap widens to the full chain span only
|
|
653
|
+
// for implicit, safe-method allocator requests; explicit retry
|
|
654
|
+
// policies (their documented `attempts` ceiling), unsafe methods, and
|
|
655
|
+
// static/non-registry vendors keep the per-endpoint retry budget.
|
|
656
|
+
const transportRetryCap = resolvePolicyTransportAttemptCap({
|
|
657
|
+
policy: policyProxy,
|
|
658
|
+
usesPolicyAllocator,
|
|
659
|
+
retryAttempts: stealthRetryOptions?.attempts ?? 1,
|
|
660
|
+
explicitRetry: hasExplicitRetryPolicy,
|
|
661
|
+
method,
|
|
662
|
+
});
|
|
663
|
+
if (attempt + 1 < transportRetryCap &&
|
|
622
664
|
shouldRetryProxyTransportAttempt({
|
|
623
665
|
error: normalizedError,
|
|
624
666
|
explicitRetry: hasExplicitRetryPolicy,
|
|
@@ -634,7 +676,7 @@ function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
|
|
|
634
676
|
throw normalizedError;
|
|
635
677
|
}
|
|
636
678
|
}
|
|
637
|
-
if (
|
|
679
|
+
if (rotatesRegistryChain &&
|
|
638
680
|
stalePoolError &&
|
|
639
681
|
refreshAttempt < MAX_POLICY_PROXY_POOL_REFRESHES) {
|
|
640
682
|
await invalidateProxyResolutionCacheAsync({
|
package/dist/server/serve.js
CHANGED
|
@@ -312,11 +312,22 @@ function toErrorResponse(error, requestId) {
|
|
|
312
312
|
},
|
|
313
313
|
};
|
|
314
314
|
}
|
|
315
|
+
// A masked internal error MUST NOT be advertised as retryable: without an
|
|
316
|
+
// explicit retryable:false the hub (bori provider-backed engine) defaults 5xx
|
|
317
|
+
// to retryable:true, which turns a deterministic pre-upstream crash into an
|
|
318
|
+
// infinite START->CONTINUE->restart loop (2026-07-22 catchtable reserve RCA).
|
|
319
|
+
// We still refuse to leak message/stack — only the error class name (or the
|
|
320
|
+
// primitive type for non-Error throwables) is surfaced for ops triage.
|
|
315
321
|
return {
|
|
316
322
|
error: {
|
|
317
323
|
code: "internal_error",
|
|
318
324
|
message: "Internal error",
|
|
319
325
|
...(requestId ? { requestId } : {}),
|
|
326
|
+
details: {
|
|
327
|
+
retryable: false,
|
|
328
|
+
category: "internal_error",
|
|
329
|
+
errorClass: error instanceof Error ? error.name : typeof error,
|
|
330
|
+
},
|
|
320
331
|
},
|
|
321
332
|
};
|
|
322
333
|
}
|
package/dist/server/types.d.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
export declare const ConnectionModeSchema: z.ZodEnum<{
|
|
3
|
-
credentials: "credentials";
|
|
4
3
|
none: "none";
|
|
4
|
+
credentials: "credentials";
|
|
5
5
|
oauth2: "oauth2";
|
|
6
6
|
"platform-managed": "platform-managed";
|
|
7
7
|
}>;
|
|
8
8
|
export declare const OperationConnectionSchema: z.ZodObject<{
|
|
9
9
|
id: z.ZodString;
|
|
10
10
|
mode: z.ZodEnum<{
|
|
11
|
-
credentials: "credentials";
|
|
12
11
|
none: "none";
|
|
12
|
+
credentials: "credentials";
|
|
13
13
|
oauth2: "oauth2";
|
|
14
14
|
"platform-managed": "platform-managed";
|
|
15
15
|
}>;
|
|
@@ -25,8 +25,8 @@ export declare const OperationRequestSchema: z.ZodObject<{
|
|
|
25
25
|
connection: z.ZodOptional<z.ZodObject<{
|
|
26
26
|
id: z.ZodString;
|
|
27
27
|
mode: z.ZodEnum<{
|
|
28
|
-
credentials: "credentials";
|
|
29
28
|
none: "none";
|
|
29
|
+
credentials: "credentials";
|
|
30
30
|
oauth2: "oauth2";
|
|
31
31
|
"platform-managed": "platform-managed";
|
|
32
32
|
}>;
|
|
@@ -55,21 +55,21 @@ export declare const OperationSuccessResponseSchema: z.ZodObject<{
|
|
|
55
55
|
stale: z.ZodBoolean;
|
|
56
56
|
keys: z.ZodArray<z.ZodString>;
|
|
57
57
|
source: z.ZodOptional<z.ZodEnum<{
|
|
58
|
-
loader: "loader";
|
|
59
|
-
memory: "memory";
|
|
60
58
|
mixed: "mixed";
|
|
61
59
|
redis: "redis";
|
|
60
|
+
memory: "memory";
|
|
61
|
+
loader: "loader";
|
|
62
62
|
}>>;
|
|
63
63
|
}, z.core.$strip>>;
|
|
64
64
|
retry: z.ZodOptional<z.ZodObject<{
|
|
65
65
|
attempts: z.ZodNumber;
|
|
66
66
|
retries: z.ZodNumber;
|
|
67
67
|
preset: z.ZodOptional<z.ZodEnum<{
|
|
68
|
-
aggressive_read: "aggressive_read";
|
|
69
68
|
off: "off";
|
|
70
|
-
rate_limit_aware: "rate_limit_aware";
|
|
71
|
-
safe_read: "safe_read";
|
|
72
69
|
transport_transient: "transport_transient";
|
|
70
|
+
safe_read: "safe_read";
|
|
71
|
+
aggressive_read: "aggressive_read";
|
|
72
|
+
rate_limit_aware: "rate_limit_aware";
|
|
73
73
|
}>>;
|
|
74
74
|
transport: z.ZodEnum<{
|
|
75
75
|
native: "native";
|
|
@@ -101,8 +101,8 @@ export declare const AuthFlowRequestSchema: z.ZodObject<{
|
|
|
101
101
|
connection: z.ZodOptional<z.ZodObject<{
|
|
102
102
|
id: z.ZodString;
|
|
103
103
|
mode: z.ZodEnum<{
|
|
104
|
-
credentials: "credentials";
|
|
105
104
|
none: "none";
|
|
105
|
+
credentials: "credentials";
|
|
106
106
|
oauth2: "oauth2";
|
|
107
107
|
"platform-managed": "platform-managed";
|
|
108
108
|
}>;
|
package/dist/types.d.ts
CHANGED
|
@@ -650,7 +650,25 @@ export type ConnectionMode = AuthMode;
|
|
|
650
650
|
export type ProviderReviewed = "first-party" | "community" | "staging";
|
|
651
651
|
export type ProviderAccessVisibility = "public" | "early_access";
|
|
652
652
|
export type ProviderProxyMode = "disabled" | "optional" | "required";
|
|
653
|
-
|
|
653
|
+
/**
|
|
654
|
+
* Proxy egress vendors. These are FOUR DISTINCT services — do not conflate them
|
|
655
|
+
* (a common mistake because the names collide with a well-known rebrand):
|
|
656
|
+
*
|
|
657
|
+
* - `smartproxy` — **api.smartproxy.org**, a residential proxy with an IP
|
|
658
|
+
* *extraction/allocation* API (app_key → a pool of raw `ip:port` CONNECT
|
|
659
|
+
* endpoints). This is our own vendor. It is NOT the company formerly named
|
|
660
|
+
* "Smartproxy". Credentials: `APIFUSE__PROXY__SMARTPROXY_APP_KEY`.
|
|
661
|
+
* - `nodemaven` — **gate.nodemaven.com**, a *gateway* proxy with static
|
|
662
|
+
* credentials; geo/session encoded in the username, no allocation API.
|
|
663
|
+
* - `decodo` — **decodo.com**, the *gateway* proxy that was named "Smartproxy"
|
|
664
|
+
* (smartproxy.com) before its 2025 rebrand to Decodo. Sticky sessions via
|
|
665
|
+
* username params. A different company from `smartproxy` above.
|
|
666
|
+
* **@deprecated** — unused; no managed adapter. Use `smartproxy`/`nodemaven`,
|
|
667
|
+
* or the `APIFUSE__PROXY__URL` bring-your-own escape hatch.
|
|
668
|
+
* - `custom` — **@deprecated** bring-your-own static proxy URL marker. The
|
|
669
|
+
* `APIFUSE__PROXY__URL` env still works without declaring this value.
|
|
670
|
+
*/
|
|
671
|
+
export type ProviderProxyProvider = "smartproxy" | "nodemaven" | "decodo" | "custom";
|
|
654
672
|
export type ProviderProxySessionAffinity = "request" | "operation" | "auth-flow" | "connection";
|
|
655
673
|
export interface ProviderProxyPolicy {
|
|
656
674
|
/**
|
|
@@ -658,7 +676,18 @@ export interface ProviderProxyPolicy {
|
|
|
658
676
|
* certificate verification, and vendor allocator endpoints are SDK-owned.
|
|
659
677
|
*/
|
|
660
678
|
mode: ProviderProxyMode;
|
|
679
|
+
/**
|
|
680
|
+
* @deprecated Use `providers: [...]` to declare an ordered vendor fallback
|
|
681
|
+
* chain. A single-element `providers` list is equivalent to this field.
|
|
682
|
+
*/
|
|
661
683
|
provider?: ProviderProxyProvider;
|
|
684
|
+
/**
|
|
685
|
+
* Ordered proxy-vendor fallback chain. The SDK tries each vendor in order and
|
|
686
|
+
* fails over to the next when a vendor lacks credentials or its allocation /
|
|
687
|
+
* transport is exhausted. When omitted, `provider` (or the platform default)
|
|
688
|
+
* is used as a single-vendor chain.
|
|
689
|
+
*/
|
|
690
|
+
providers?: ProviderProxyProvider[];
|
|
662
691
|
geo?: {
|
|
663
692
|
/** ISO 3166-1 alpha-2 country code, for example KR or US. */
|
|
664
693
|
country?: Iso3166Alpha2CountryCode;
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "2.2.0-beta.
|
|
2
|
+
"version": "2.2.0-beta.9",
|
|
3
3
|
"name": "@apifuse/provider-sdk",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
@@ -75,10 +75,11 @@
|
|
|
75
75
|
"scripts": {
|
|
76
76
|
"lint": "biome lint .",
|
|
77
77
|
"lint:fix": "biome lint --write",
|
|
78
|
+
"lint:deprecated": "bun run scripts/lint-deprecated-usage.ts",
|
|
78
79
|
"format": "biome format --write",
|
|
79
80
|
"type-check": "tsc --noEmit",
|
|
80
81
|
"test": "bun test",
|
|
81
|
-
"check": "bun run lint && bun run type-check && bun run build",
|
|
82
|
+
"check": "bun run lint && bun run type-check && bun run lint:deprecated && bun run build",
|
|
82
83
|
"pack:check": "bun run build && bun bin/apifuse-pack-check.ts",
|
|
83
84
|
"pack:smoke": "bun run build && bun bin/apifuse-pack-smoke.ts",
|
|
84
85
|
"pack:types": "bun run build && bun bin/apifuse-pack-types.ts",
|
|
@@ -91,7 +92,7 @@
|
|
|
91
92
|
"@biomejs/biome": "^2.5.0",
|
|
92
93
|
"@types/bun": "latest",
|
|
93
94
|
"@types/node": "^25.9.3",
|
|
94
|
-
"typescript": "
|
|
95
|
+
"typescript": "6.0.3"
|
|
95
96
|
},
|
|
96
97
|
"dependencies": {
|
|
97
98
|
"@clack/prompts": "^1.5.1",
|