From e9daf98e61d243846ec363d000cf2b687f3d35eb Mon Sep 17 00:00:00 2001 From: Akshit Sinha Date: Thu, 6 Aug 2026 15:08:02 +0530 Subject: [PATCH 1/6] feat(typescript): injectable fetch transport and caller abort propagation Add `FlagshipProviderOptions.fetch` and per-call `evaluate(..., { fetch, signal })` so consumers can route evaluations through a Workers service binding or stub the transport in tests without mutating `globalThis.fetch`. Caller signals are now merged with the request timeout and with `fetchOptions.signal` (previously discarded), so an abort cancels the in-flight HTTP request. Caller aborts surface as the new `FlagshipErrorCode.ABORTED` and are never retried; timeout aborts keep their existing retry behaviour. `FlagshipError.retryable` exposes whether a failure was transient. 408, 425, 429 and 5xx are retryable; other non-2xx responses are now terminal instead of retried. --- .changeset/quiet-pianos-listen.md | 16 + sdks/typescript/API.md | 61 +++- sdks/typescript/README.md | 2 + sdks/typescript/src/client.ts | 134 +++++-- sdks/typescript/src/index.ts | 1 + sdks/typescript/src/server-provider.ts | 2 + sdks/typescript/src/server.ts | 1 + sdks/typescript/src/types.ts | 58 ++- sdks/typescript/src/web.ts | 8 +- .../typescript/tests/binding-provider.test.ts | 11 + sdks/typescript/tests/client.test.ts | 331 ++++++++++++++++++ sdks/typescript/tests/server-provider.test.ts | 47 +++ sdks/typescript/tests/types.test.ts | 6 + 13 files changed, 640 insertions(+), 38 deletions(-) create mode 100644 .changeset/quiet-pianos-listen.md diff --git a/.changeset/quiet-pianos-listen.md b/.changeset/quiet-pianos-listen.md new file mode 100644 index 0000000..f58a280 --- /dev/null +++ b/.changeset/quiet-pianos-listen.md @@ -0,0 +1,16 @@ +--- +'@cloudflare/flagship': minor +--- + +Add an injectable `fetch` transport and caller `AbortSignal` propagation to `FlagshipClient` + +- `FlagshipProviderOptions.fetch?: typeof globalThis.fetch` sets the transport for a client. It defaults to `globalThis.fetch`, resolved at call time, and the SDK never assigns to the global — so routing evaluations through a Workers service binding or stubbing the transport in tests no longer requires mutating `globalThis.fetch` and exposing unrelated traffic in the same isolate. +- `evaluate(flagKey, context, { fetch?, signal? })` adds per-call overrides. `signal` is merged with the request timeout and with `fetchOptions.signal` (previously silently discarded), so a caller abort now aborts the in-flight HTTP request instead of only abandoning the promise. An already-aborted signal rejects without issuing a request. +- New `FlagshipErrorCode.ABORTED` distinguishes caller cancellation from `TIMEOUT_ERROR`. Caller aborts are never retried; timeout aborts are still retried as before. +- New `FlagshipError.retryable` reports whether a failure was transient. `408`, `425`, `429`, `5xx`, connection failures, timeouts, and malformed bodies are retryable; other non-2xx responses (`400`, `401`, `403`, `404`, `422`, …) and caller aborts are terminal. This lets consumers implement fail-closed-without-caching instead of guessing from `NETWORK_ERROR` alone. +- `FlagshipServerProvider` and `FlagshipClientProvider` accept and forward `fetch` in HTTP mode; combining it with `binding` throws like the other HTTP-only options. + +Behaviour changes for existing callers, who are otherwise unaffected: + +- Previously every non-2xx except `400` and `404` was retried. Definitively terminal statuses such as `401`, `403`, and `422` are now propagated immediately. +- The request timeout now also covers reading the response body, so a stalled body read no longer holds the request open past `timeout`. diff --git a/sdks/typescript/API.md b/sdks/typescript/API.md index 5325029..1bdbbf6 100644 --- a/sdks/typescript/API.md +++ b/sdks/typescript/API.md @@ -156,13 +156,30 @@ new FlagshipServerProvider({ retries: 1, // retry attempts on transient errors (default: 1, max: 10) retryDelay: 1000, // delay between retries in ms (default: 1000, max: 30000) + // Custom transport (default: globalThis.fetch, resolved at call time). + // Useful for routing evaluations through a service binding, or for tests. + // fetch: env.FLAGS_SERVICE.fetch.bind(env.FLAGS_SERVICE), + // Caching — opt-in, off by default. See "Caching" below. cacheTtl: 30000, // ms; enables the cache when > 0 cacheMaxSize: 1000, // max cached entries (default: 1000) }); ``` -404 and 400 responses are never retried. Only transient server errors (5xx) and network failures trigger the retry logic. +Only transient failures are retried. Everything else is treated as a definitive answer and propagated immediately: + +| Outcome | Retried? | `FlagshipError.code` | `retryable` | +| ------------------------------------------ | -------- | -------------------- | ----------- | +| 408, 425, 429 | yes | `NETWORK_ERROR` | `true` | +| 5xx | yes | `NETWORK_ERROR` | `true` | +| Connection failure | yes | `NETWORK_ERROR` | `true` | +| Timeout | yes | `TIMEOUT_ERROR` | `true` | +| Malformed response body | yes | `PARSE_ERROR` | `true` | +| Other non-2xx (400, 401, 403, 404, 422, …) | no | `NETWORK_ERROR` | `false` | +| Caller abort | no | `ABORTED` | `false` | +| Unserializable evaluation context | no | `INVALID_CONTEXT` | `false` | + +For HTTP failures the `FlagshipError.cause` is the underlying `Response`, so the status is available for inspection. Only a `retryable: false` failure is a definitive answer that is safe to cache; a `retryable: true` failure means "ask again later". ### Caching @@ -343,6 +360,40 @@ When enabled, the server provider logs via the OpenFeature-injected `Logger` (de > Note: `logging` only controls Flagship SDK logs. OpenFeature's own framework-level logs are controlled separately via `OpenFeature.setLogger(myLogger)`. +## Custom transport and cancellation + +`FlagshipClient` resolves its transport from `options.fetch`, falling back to `globalThis.fetch` at call time. The SDK never assigns to `globalThis.fetch`, so injecting a transport cannot affect unrelated traffic in the same isolate. + +`evaluate()` also accepts per-call overrides: + +```typescript +import { FlagshipClient, FlagshipErrorCode, FlagshipError } from '@cloudflare/flagship'; + +const client = new FlagshipClient({ appId: 'your-app-id', accountId: 'your-account-id' }); + +try { + const result = await client.evaluate('my-flag', context, { + // Aborting this signal aborts the in-flight HTTP request. + signal: request.signal, + // Optional per-call transport override. + fetch: env.FLAGS_SERVICE.fetch.bind(env.FLAGS_SERVICE), + }); +} catch (error) { + if (error instanceof FlagshipError && error.code === FlagshipErrorCode.ABORTED) { + // The caller cancelled — not a Flagship failure, and never retried. + } +} +``` + +Signal semantics: + +- A caller signal is **merged** with the request timeout and with `fetchOptions.signal` — whichever fires first aborts the request. None of them is discarded. +- An already-aborted signal rejects with `ABORTED` before any request is issued. +- A caller abort is never retried; a timeout abort is retried as usual. +- Caller aborts (`ABORTED`) and timeouts (`TIMEOUT_ERROR`) are distinct error codes. + +Both providers accept `fetch` in HTTP mode and forward it to the underlying client. It must not be combined with `binding`. + ## Error handling The provider always returns a valid `ResolutionDetails` — it never throws. On error, the default value is returned alongside an `errorCode` and `errorMessage` describing what went wrong. @@ -364,7 +415,7 @@ if (details.errorCode) { | `TYPE_MISMATCH` | The flag's resolved value type does not match the requested type | | `INVALID_CONTEXT` | The evaluation context contains objects or arrays | | `PARSE_ERROR` | The API response was not a valid evaluation response | -| `GENERAL` | Network error, timeout, or any other transient failure | +| `GENERAL` | Network error, timeout, caller abort, or any other transient failure | ## Hooks @@ -451,11 +502,11 @@ Each sub-path re-exports core utilities alongside its provider-specific classes. - `FlagshipClient` — HTTP client with retry, timeout, AbortController - `ContextTransformer` — converts evaluation context to query parameters -- `FlagshipError` — error class with `code` and `cause` properties -- `FlagshipErrorCode` — enum: `NETWORK_ERROR`, `TIMEOUT_ERROR`, `PARSE_ERROR`, `INVALID_CONTEXT` +- `FlagshipError` — error class with `code`, `cause`, and `retryable` properties +- `FlagshipErrorCode` — enum: `NETWORK_ERROR`, `TIMEOUT_ERROR`, `ABORTED`, `PARSE_ERROR`, `INVALID_CONTEXT` - `isBindingOptions()` — type guard for binding options - `FLAGSHIP_DEFAULT_BASE_URL` — default base URL constant -- Types: `FlagshipProviderOptions`, `FlagshipClientProviderOptions`, `FlagshipEvaluationResponse`, `CachedFlag`, `FlagshipBinding`, `FlagshipBindingEvaluationDetails`, `FlagshipBindingProviderOptions`, `FlagshipServerProviderOptions`, `FlagshipCacheOptions` +- Types: `FlagshipProviderOptions`, `FlagshipRequestOptions`, `FlagshipClientProviderOptions`, `FlagshipEvaluationResponse`, `CachedFlag`, `FlagshipBinding`, `FlagshipBindingEvaluationDetails`, `FlagshipBindingProviderOptions`, `FlagshipServerProviderOptions`, `FlagshipCacheOptions` **`@cloudflare/flagship/server`** (core value exports + server-relevant types, plus): diff --git a/sdks/typescript/README.md b/sdks/typescript/README.md index 87a1753..735df67 100644 --- a/sdks/typescript/README.md +++ b/sdks/typescript/README.md @@ -145,6 +145,8 @@ const darkMode = client.getBooleanValue('dark-mode', false); | Logging | `logging` option surfaces fetch errors and cache misses (off by default) | | Response caching | Opt-in per-context TTL + LRU cache via `cacheTtl` (off by default) | | Retries + timeouts | Configurable retry logic with `AbortController`-based timeouts (HTTP only) | +| Custom transport | Inject `fetch` per client or per call — no global mutation (HTTP only) | +| Cancellation | Caller `AbortSignal` aborts the in-flight request, never retried (HTTP only) | | Hooks | Built-in `LoggingHook` and `TelemetryHook` | | Tree-shakeable | Server and client bundles are fully isolated | | TypeScript | Strict types throughout | diff --git a/sdks/typescript/src/client.ts b/sdks/typescript/src/client.ts index 2e2bb78..4eb2fe9 100644 --- a/sdks/typescript/src/client.ts +++ b/sdks/typescript/src/client.ts @@ -6,16 +6,24 @@ import { FLAGSHIP_DEFAULT_BASE_URL, type FlagshipEvaluationResponse, type FlagshipProviderOptions, + type FlagshipRequestOptions, } from './types.js'; interface ResolvedOptions { endpoint: string; fetchOptions: RequestInit; + fetch: typeof globalThis.fetch | undefined; timeout: number; retries: number; retryDelay: number; } +/** + * Non-2xx statuses that represent a transient condition. Everything else in + * the 4xx range is treated as a definitive answer and never retried. + */ +const RETRYABLE_STATUSES = new Set([408, 425, 429]); + export class FlagshipClient { private readonly options: ResolvedOptions; @@ -23,6 +31,7 @@ export class FlagshipClient { this.options = { endpoint: resolveEndpoint(options), fetchOptions: buildFetchOptions(options), + fetch: options.fetch, timeout: options.timeout || 5000, retries: Math.min(options.retries !== undefined ? options.retries : 1, 10), retryDelay: Math.min(options.retryDelay !== undefined ? options.retryDelay : 1000, 30_000), @@ -35,8 +44,12 @@ export class FlagshipClient { * Throws a `FlagshipError` with `FlagshipErrorCode.INVALID_CONTEXT` if the * evaluation context contains complex values (objects or arrays) that cannot * be serialized to query parameters. + * + * `options.fetch` overrides the transport for this call, and + * `options.signal` cancels the in-flight HTTP request — aborting rejects + * with `FlagshipErrorCode.ABORTED` and is never retried. */ - async evaluate(flagKey: string, context: EvaluationContext): Promise { + async evaluate(flagKey: string, context: EvaluationContext, options?: FlagshipRequestOptions): Promise { const droppedKeys: string[] = []; const url = ContextTransformer.buildUrl(this.options.endpoint, flagKey, context, droppedKeys); @@ -48,29 +61,25 @@ export class FlagshipClient { ); } - return this.fetchWithRetry(url, this.options.retries); + return this.fetchWithRetry(url, this.options.retries, options); } /** - * Fetch with retry logic. Only retries on transient network/server errors — - * 404 and 400 responses are terminal and propagated immediately. + * Fetch with retry logic. Only retries failures marked as retryable — + * terminal responses (400, 401, 403, 404, …) and caller aborts are + * propagated immediately. */ - private async fetchWithRetry(url: string, retriesLeft: number): Promise { + private async fetchWithRetry(url: string, retriesLeft: number, options?: FlagshipRequestOptions): Promise { try { - return await this.fetchWithTimeout(url, this.options.timeout); + return await this.fetchWithTimeout(url, this.options.timeout, options); } catch (error) { - // Do not retry on client errors — 404 (flag not found) and 400 (bad request) - // are deterministic and retrying will not change the outcome. - if (error instanceof FlagshipError && error.cause instanceof Response) { - const status = error.cause.status; - if (status === 404 || status === 400) { - throw error; - } + if (error instanceof FlagshipError && !error.retryable) { + throw error; } if (retriesLeft > 0) { await new Promise((resolve) => setTimeout(resolve, this.options.retryDelay)); - return this.fetchWithRetry(url, retriesLeft - 1); + return this.fetchWithRetry(url, retriesLeft - 1, options); } throw error; @@ -78,47 +87,112 @@ export class FlagshipClient { } /** - * Fetch with timeout using AbortController + * Issues a single request against the resolved transport, aborting it when + * the timeout elapses or when any caller-supplied signal fires. */ - private async fetchWithTimeout(url: string, timeout: number): Promise { - const controller = new AbortController(); - const timeoutId = setTimeout(() => controller.abort(), timeout); + private async fetchWithTimeout(url: string, timeout: number, options?: FlagshipRequestOptions): Promise { + const callerSignals = [options?.signal, this.options.fetchOptions.signal].filter((signal): signal is AbortSignal => Boolean(signal)); + + const alreadyAborted = callerSignals.find((signal) => signal.aborted); + if (alreadyAborted) { + throw abortedError(alreadyAborted.reason); + } + + const transport = options?.fetch ?? this.options.fetch ?? globalThis.fetch.bind(globalThis); + + const timeoutController = new AbortController(); + let timedOut = false; + const timeoutId = setTimeout(() => { + timedOut = true; + timeoutController.abort(); + }, timeout); + const merged = mergeSignals([timeoutController.signal, ...callerSignals]); try { - const response = await fetch(url, { + const response = await transport(url, { ...this.options.fetchOptions, - signal: controller.signal, + signal: merged.signal, }); - clearTimeout(timeoutId); - if (!response.ok) { - throw new FlagshipError(`HTTP ${response.status}: ${response.statusText}`, FlagshipErrorCode.NETWORK_ERROR, response); + throw new FlagshipError( + `HTTP ${response.status}: ${response.statusText}`, + FlagshipErrorCode.NETWORK_ERROR, + response, + isRetryableStatus(response.status), + ); } const data = await response.json(); if (!data || typeof data !== 'object' || !('flagKey' in data) || !('value' in data)) { - throw new FlagshipError('Invalid response format from Flagship API', FlagshipErrorCode.PARSE_ERROR); + throw new FlagshipError('Invalid response format from Flagship API', FlagshipErrorCode.PARSE_ERROR, undefined, true); } return data as FlagshipEvaluationResponse; } catch (error) { - clearTimeout(timeoutId); + if (error instanceof FlagshipError) { + throw error; + } - if (error instanceof Error && error.name === 'AbortError') { - throw new FlagshipError(`Request timeout after ${timeout}ms`, FlagshipErrorCode.TIMEOUT_ERROR, error); + const abortedBy = callerSignals.find((signal) => signal.aborted); + if (abortedBy) { + throw abortedError(abortedBy.reason ?? error); } - if (error instanceof FlagshipError) { - throw error; + if (timedOut || (error instanceof Error && error.name === 'AbortError')) { + throw new FlagshipError(`Request timeout after ${timeout}ms`, FlagshipErrorCode.TIMEOUT_ERROR, error, true); } - throw new FlagshipError(`Network error: ${error}`, FlagshipErrorCode.NETWORK_ERROR, error); + throw new FlagshipError(`Network error: ${error}`, FlagshipErrorCode.NETWORK_ERROR, error, true); + } finally { + clearTimeout(timeoutId); + merged.dispose(); } } } +function abortedError(cause: unknown): FlagshipError { + return new FlagshipError('Request aborted by caller', FlagshipErrorCode.ABORTED, cause, false); +} + +/** 408, 425, 429 and any 5xx are transient; every other non-2xx is definitive. */ +function isRetryableStatus(status: number): boolean { + return status >= 500 || RETRYABLE_STATUSES.has(status); +} + +/** + * Combines signals into one. Prefers `AbortSignal.any` where available and + * falls back to a manually linked `AbortController` on older runtimes. + */ +function mergeSignals(signals: AbortSignal[]): { signal: AbortSignal; dispose: () => void } { + const noop = (): void => {}; + + if (signals.length === 1) { + return { signal: signals[0]!, dispose: noop }; + } + + if (typeof AbortSignal.any === 'function') { + return { signal: AbortSignal.any(signals), dispose: noop }; + } + + const controller = new AbortController(); + const onAbort = (event: Event): void => controller.abort((event.target as AbortSignal).reason); + + for (const signal of signals) { + signal.addEventListener('abort', onAbort); + } + + return { + signal: controller.signal, + dispose: () => { + for (const signal of signals) { + signal.removeEventListener('abort', onAbort); + } + }, + }; +} + /** * Merge `authToken` and `fetchOptions` into a single `RequestInit`. * diff --git a/sdks/typescript/src/index.ts b/sdks/typescript/src/index.ts index ba731b2..8df4230 100644 --- a/sdks/typescript/src/index.ts +++ b/sdks/typescript/src/index.ts @@ -15,6 +15,7 @@ // Export types export type { FlagshipProviderOptions, + FlagshipRequestOptions, FlagshipClientProviderOptions, FlagshipEvaluationResponse, CachedFlag, diff --git a/sdks/typescript/src/server-provider.ts b/sdks/typescript/src/server-provider.ts index 8d366a7..785c31d 100644 --- a/sdks/typescript/src/server-provider.ts +++ b/sdks/typescript/src/server-provider.ts @@ -25,6 +25,7 @@ const HTTP_ONLY_FIELDS = [ 'authToken', 'baseUrl', 'fetchOptions', + 'fetch', 'timeout', 'retries', 'retryDelay', @@ -250,6 +251,7 @@ export class FlagshipServerProvider implements Provider { errorCode = error.cause instanceof Response && error.cause.status === 404 ? ErrorCode.FLAG_NOT_FOUND : ErrorCode.GENERAL; break; case FlagshipErrorCode.TIMEOUT_ERROR: + case FlagshipErrorCode.ABORTED: errorCode = ErrorCode.GENERAL; break; case FlagshipErrorCode.PARSE_ERROR: diff --git a/sdks/typescript/src/server.ts b/sdks/typescript/src/server.ts index 12ba6b0..2f64494 100644 --- a/sdks/typescript/src/server.ts +++ b/sdks/typescript/src/server.ts @@ -45,6 +45,7 @@ export { } from './index.js'; export type { FlagshipProviderOptions, + FlagshipRequestOptions, FlagshipEvaluationResponse, FlagshipBinding, FlagshipBindingEvaluationDetails, diff --git a/sdks/typescript/src/types.ts b/sdks/typescript/src/types.ts index e2796dc..dc9b48a 100644 --- a/sdks/typescript/src/types.ts +++ b/sdks/typescript/src/types.ts @@ -76,9 +76,26 @@ export interface FlagshipProviderOptions { * Headers provided here are merged with any headers derived from other * options (e.g. `authToken`), with values in `fetchOptions.headers` * taking precedence. + * + * A `signal` provided here is merged with the request timeout and with any + * per-call signal — it is never dropped. */ fetchOptions?: RequestInit; + /** + * Custom `fetch` implementation used for every request. Defaults to + * `globalThis.fetch`, resolved at call time so a host that installs a + * polyfill after construction still works. The SDK never assigns to + * `globalThis.fetch`. + * + * Useful for routing evaluations through a Cloudflare Workers service + * binding, or for stubbing the transport in tests without touching globals. + * + * @example + * { appId: 'app-abc123', accountId: 'my-account', fetch: env.FLAGS_SERVICE.fetch.bind(env.FLAGS_SERVICE) } + */ + fetch?: typeof globalThis.fetch; + /** * Request timeout in milliseconds. * @default 5000 @@ -87,7 +104,10 @@ export interface FlagshipProviderOptions { /** * Number of retry attempts on transient errors. Capped at 10. - * 404 and 400 responses are never retried. + * + * Only retryable failures are retried — see `FlagshipError.retryable`. + * Terminal responses (e.g. 400, 401, 403, 404) and caller aborts are + * propagated immediately. * @default 1 */ retries?: number; @@ -99,6 +119,30 @@ export interface FlagshipProviderOptions { retryDelay?: number; } +/** + * Per-call options accepted by `FlagshipClient.evaluate`. + * + * Both fields are optional and additive — omitting them preserves the + * client-level configuration exactly. + */ +export interface FlagshipRequestOptions { + /** + * Overrides the client-level `fetch` for this call only. + */ + fetch?: typeof globalThis.fetch; + + /** + * Caller cancellation signal. Aborting it aborts the underlying HTTP + * request, not just the pending promise. The signal is merged with the + * request timeout and with `fetchOptions.signal`, so whichever fires first + * wins. + * + * A caller abort rejects with `FlagshipErrorCode.ABORTED` and is never + * retried. An already-aborted signal rejects without issuing a request. + */ + signal?: AbortSignal; +} + /** * Configuration options for `FlagshipClientProvider` (browser / static-context environments). */ @@ -251,10 +295,12 @@ export function isBindingOptions(options: FlagshipServerProviderOptions): option * These are mapped to OpenFeature `ErrorCode` values by the providers. */ export enum FlagshipErrorCode { - /** HTTP or fetch-level failure (non-404/400 status, connection refused, etc.) */ + /** HTTP or fetch-level failure (non-2xx status, connection refused, etc.) */ NETWORK_ERROR = 'NETWORK_ERROR', /** The request was aborted because the configured timeout elapsed. */ TIMEOUT_ERROR = 'TIMEOUT_ERROR', + /** The request was aborted through a caller-supplied `AbortSignal`. */ + ABORTED = 'ABORTED', /** The response body was not a valid evaluation response. */ PARSE_ERROR = 'PARSE_ERROR', /** The evaluation context contained complex values that cannot be serialized to query parameters. */ @@ -266,12 +312,20 @@ export enum FlagshipErrorCode { * Carries a `code` for programmatic handling and an optional `cause` which * is the underlying `Response` object for HTTP errors, allowing callers to * inspect the status code (e.g. to distinguish 404 → `FLAG_NOT_FOUND`). + * + * `retryable` tells callers whether the failure was transient. The SDK uses it + * to decide whether to retry; consumers can use it to distinguish + * "ask again later" (`true` — 408, 425, 429, 5xx, timeouts, connection + * failures) from a definitive answer (`false` — 400, 401, 403, 404, other + * terminal 4xx, caller aborts, unserializable context). Only a non-retryable + * failure is safe to treat as authoritative and cache. */ export class FlagshipError extends Error { constructor( message: string, public code: FlagshipErrorCode, public cause?: unknown, + public readonly retryable: boolean = false, ) { super(message); this.name = 'FlagshipError'; diff --git a/sdks/typescript/src/web.ts b/sdks/typescript/src/web.ts index 9ab5597..82e77a4 100644 --- a/sdks/typescript/src/web.ts +++ b/sdks/typescript/src/web.ts @@ -24,7 +24,13 @@ // Re-export core utilities export { FlagshipClient, ContextTransformer, FlagshipError, FlagshipErrorCode, FLAGSHIP_DEFAULT_BASE_URL } from './index.js'; -export type { FlagshipProviderOptions, FlagshipClientProviderOptions, FlagshipEvaluationResponse, CachedFlag } from './index.js'; +export type { + FlagshipProviderOptions, + FlagshipRequestOptions, + FlagshipClientProviderOptions, + FlagshipEvaluationResponse, + CachedFlag, +} from './index.js'; // Export client provider export { FlagshipClientProvider } from './client-provider.js'; diff --git a/sdks/typescript/tests/binding-provider.test.ts b/sdks/typescript/tests/binding-provider.test.ts index a39dbc8..8e1ed51 100644 --- a/sdks/typescript/tests/binding-provider.test.ts +++ b/sdks/typescript/tests/binding-provider.test.ts @@ -93,6 +93,17 @@ describe('FlagshipServerProvider (binding mode)', () => { ).toThrow('must not be provided'); }); + it('should throw when binding and fetch are both provided', () => { + const binding = createMockBinding(); + expect( + () => + new FlagshipServerProvider({ + binding, + fetch: globalThis.fetch, + } as any), + ).toThrow('must not be provided'); + }); + it('should throw when binding and authToken are both provided', () => { const binding = createMockBinding(); expect( diff --git a/sdks/typescript/tests/client.test.ts b/sdks/typescript/tests/client.test.ts index 6e19b9f..d485509 100644 --- a/sdks/typescript/tests/client.test.ts +++ b/sdks/typescript/tests/client.test.ts @@ -522,4 +522,335 @@ describe('FlagshipClient', () => { expect(global.fetch).toHaveBeenCalledTimes(1); }); }); + + describe('injectable transport', () => { + const okResponse = () => ({ ok: true, json: async () => ({ flagKey: 'my-flag', value: true }) }) as unknown as Response; + + it('uses the client-level fetch instead of the global one', async () => { + const transport = vi.fn(async () => okResponse()); + + const client = new FlagshipClient({ + endpoint: 'https://api.example.com/evaluate', + fetch: transport, + }); + + const result = await client.evaluate('my-flag', { targetingKey: 'user-1' }); + + expect(result.value).toBe(true); + expect(transport).toHaveBeenCalledTimes(1); + expect(global.fetch).not.toHaveBeenCalled(); + }); + + it('lets a per-call fetch override the client-level one', async () => { + const clientTransport = vi.fn(async () => okResponse()); + const callTransport = vi.fn(async () => okResponse()); + + const client = new FlagshipClient({ + endpoint: 'https://api.example.com/evaluate', + fetch: clientTransport, + }); + + await client.evaluate('my-flag', {}, { fetch: callTransport }); + + expect(callTransport).toHaveBeenCalledTimes(1); + expect(clientTransport).not.toHaveBeenCalled(); + }); + + it('never reassigns globalThis.fetch, observed while a call is in flight', async () => { + const ambient = globalThis.fetch; + let globalDuringCall: typeof globalThis.fetch | undefined; + + const transport = vi.fn(async () => { + globalDuringCall = globalThis.fetch; + return okResponse(); + }); + + const client = new FlagshipClient({ + endpoint: 'https://api.example.com/evaluate', + fetch: transport, + }); + + await client.evaluate('my-flag', {}); + + expect(globalDuringCall).toBe(ambient); + expect(globalThis.fetch).toBe(ambient); + }); + + it('serves unrelated traffic with the ambient transport during an evaluation', async () => { + (global.fetch as any).mockResolvedValue(okResponse()); + + const transport = vi.fn(async () => { + await globalThis.fetch('https://unrelated.example/ping'); + return okResponse(); + }); + + const client = new FlagshipClient({ + endpoint: 'https://api.example.com/evaluate', + fetch: transport, + }); + + await client.evaluate('my-flag', {}); + + expect(transport).toHaveBeenCalledTimes(1); + expect(global.fetch).toHaveBeenCalledTimes(1); + expect((global.fetch as any).mock.calls[0][0]).toBe('https://unrelated.example/ping'); + }); + + it('resolves globalThis.fetch at call time when no transport is configured', async () => { + const client = new FlagshipClient({ endpoint: 'https://api.example.com/evaluate' }); + + const original = globalThis.fetch; + const lateInstalled = vi.fn(async () => okResponse()); + globalThis.fetch = lateInstalled as unknown as typeof globalThis.fetch; + + try { + await client.evaluate('my-flag', {}); + expect(lateInstalled).toHaveBeenCalledTimes(1); + } finally { + globalThis.fetch = original; + } + }); + }); + + describe('abort signal', () => { + /** Transport that only settles when the signal it was handed aborts. */ + function pendingTransport() { + const seen: (AbortSignal | undefined)[] = []; + const fetchImpl = vi.fn((_url: any, init?: RequestInit) => { + const signal = (init?.signal ?? undefined) as AbortSignal | undefined; + seen.push(signal); + return new Promise((_resolve, reject) => { + const fail = () => { + const error = new Error('The operation was aborted'); + error.name = 'AbortError'; + reject(error); + }; + if (signal?.aborted) fail(); + else signal?.addEventListener('abort', fail); + }); + }); + return { fetchImpl: fetchImpl as unknown as typeof globalThis.fetch, calls: fetchImpl, seen }; + } + + it('aborts the underlying request when the caller signal fires', async () => { + const { fetchImpl, seen } = pendingTransport(); + const controller = new AbortController(); + + const client = new FlagshipClient({ + endpoint: 'https://api.example.com/evaluate', + fetch: fetchImpl, + retries: 0, + }); + + const pending = client.evaluate('my-flag', {}, { signal: controller.signal }); + controller.abort(); + + const error = await pending.catch((e) => e); + + expect(seen[0]?.aborted).toBe(true); + expect(error).toBeInstanceOf(FlagshipError); + expect(error.code).toBe(FlagshipErrorCode.ABORTED); + expect(error.retryable).toBe(false); + }); + + it('does not retry a caller abort', async () => { + const { fetchImpl, calls } = pendingTransport(); + const controller = new AbortController(); + + const client = new FlagshipClient({ + endpoint: 'https://api.example.com/evaluate', + fetch: fetchImpl, + retries: 3, + retryDelay: 0, + }); + + const pending = client.evaluate('my-flag', {}, { signal: controller.signal }); + controller.abort(); + const error = await pending.catch((e) => e); + + expect(error.code).toBe(FlagshipErrorCode.ABORTED); + expect(calls).toHaveBeenCalledTimes(1); + }); + + it('still retries a timeout abort', async () => { + const { fetchImpl, calls } = pendingTransport(); + + const client = new FlagshipClient({ + endpoint: 'https://api.example.com/evaluate', + fetch: fetchImpl, + timeout: 10, + retries: 1, + retryDelay: 0, + }); + + const error = await client.evaluate('my-flag', {}).catch((e) => e); + + expect(error.code).toBe(FlagshipErrorCode.TIMEOUT_ERROR); + expect(error.retryable).toBe(true); + expect(calls).toHaveBeenCalledTimes(2); + }); + + it('short-circuits an already-aborted signal without issuing a request', async () => { + const { fetchImpl, calls } = pendingTransport(); + + const client = new FlagshipClient({ + endpoint: 'https://api.example.com/evaluate', + fetch: fetchImpl, + retries: 3, + retryDelay: 0, + }); + + const error = await client.evaluate('my-flag', {}, { signal: AbortSignal.abort('gone') }).catch((e) => e); + + expect(error).toBeInstanceOf(FlagshipError); + expect(error.code).toBe(FlagshipErrorCode.ABORTED); + expect(error.cause).toBe('gone'); + expect(calls).not.toHaveBeenCalled(); + }); + + it('honours a signal supplied through fetchOptions', async () => { + const { fetchImpl, seen } = pendingTransport(); + const controller = new AbortController(); + + const client = new FlagshipClient({ + endpoint: 'https://api.example.com/evaluate', + fetch: fetchImpl, + fetchOptions: { signal: controller.signal }, + retries: 0, + }); + + const pending = client.evaluate('my-flag', {}); + controller.abort(); + const error = await pending.catch((e) => e); + + expect(seen[0]?.aborted).toBe(true); + expect(error.code).toBe(FlagshipErrorCode.ABORTED); + }); + + it('aborts when either the fetchOptions signal or the per-call signal fires', async () => { + const fetchOptionsController = new AbortController(); + const { fetchImpl, seen } = pendingTransport(); + + const client = new FlagshipClient({ + endpoint: 'https://api.example.com/evaluate', + fetch: fetchImpl, + fetchOptions: { signal: fetchOptionsController.signal }, + retries: 0, + }); + + // Per-call signal aborts. + const callController = new AbortController(); + const first = client.evaluate('my-flag', {}, { signal: callController.signal }); + callController.abort(); + expect((await first.catch((e) => e)).code).toBe(FlagshipErrorCode.ABORTED); + expect(seen[0]?.aborted).toBe(true); + + // fetchOptions signal aborts. + const second = client.evaluate('my-flag', {}, { signal: new AbortController().signal }); + fetchOptionsController.abort(); + expect((await second.catch((e) => e)).code).toBe(FlagshipErrorCode.ABORTED); + expect(seen[1]?.aborted).toBe(true); + }); + + it('falls back to a linked controller on runtimes without AbortSignal.any', async () => { + const { fetchImpl, seen } = pendingTransport(); + const original = AbortSignal.any; + (AbortSignal as any).any = undefined; + + try { + const controller = new AbortController(); + const client = new FlagshipClient({ + endpoint: 'https://api.example.com/evaluate', + fetch: fetchImpl, + retries: 0, + }); + + const pending = client.evaluate('my-flag', {}, { signal: controller.signal }); + controller.abort(); + const error = await pending.catch((e) => e); + + expect(seen[0]).not.toBe(controller.signal); + expect(seen[0]?.aborted).toBe(true); + expect(error.code).toBe(FlagshipErrorCode.ABORTED); + } finally { + (AbortSignal as any).any = original; + } + }); + + it('leaves the timeout signal intact when no caller signal is supplied', async () => { + const { fetchImpl, seen } = pendingTransport(); + + const client = new FlagshipClient({ + endpoint: 'https://api.example.com/evaluate', + fetch: fetchImpl, + timeout: 10, + retries: 0, + }); + + const error = await client.evaluate('my-flag', {}).catch((e) => e); + + expect(seen[0]).toBeInstanceOf(AbortSignal); + expect(seen[0]?.aborted).toBe(true); + expect(error.code).toBe(FlagshipErrorCode.TIMEOUT_ERROR); + }); + }); + + describe('retryable classification', () => { + function statusTransport(status: number) { + return vi.fn(async () => new Response(null, { status, statusText: `Status ${status}` })); + } + + it.each([408, 425, 429, 500, 503])('treats %i as retryable', async (status) => { + const transport = statusTransport(status); + + const client = new FlagshipClient({ + endpoint: 'https://api.example.com/evaluate', + fetch: transport as unknown as typeof globalThis.fetch, + retries: 1, + retryDelay: 0, + }); + + const error = await client.evaluate('my-flag', {}).catch((e) => e); + + expect(error.code).toBe(FlagshipErrorCode.NETWORK_ERROR); + expect(error.retryable).toBe(true); + expect(error.cause).toBeInstanceOf(Response); + expect(transport).toHaveBeenCalledTimes(2); + }); + + it.each([400, 401, 403, 404, 422])('treats %i as terminal', async (status) => { + const transport = statusTransport(status); + + const client = new FlagshipClient({ + endpoint: 'https://api.example.com/evaluate', + fetch: transport as unknown as typeof globalThis.fetch, + retries: 3, + retryDelay: 0, + }); + + const error = await client.evaluate('my-flag', {}).catch((e) => e); + + expect(error.code).toBe(FlagshipErrorCode.NETWORK_ERROR); + expect(error.retryable).toBe(false); + expect((error.cause as Response).status).toBe(status); + expect(transport).toHaveBeenCalledTimes(1); + }); + + it('marks transport failures as retryable', async () => { + const transport = vi.fn(async () => { + throw new Error('connection refused'); + }); + + const client = new FlagshipClient({ + endpoint: 'https://api.example.com/evaluate', + fetch: transport as unknown as typeof globalThis.fetch, + retries: 0, + }); + + const error = await client.evaluate('my-flag', {}).catch((e) => e); + + expect(error.code).toBe(FlagshipErrorCode.NETWORK_ERROR); + expect(error.retryable).toBe(true); + }); + }); }); diff --git a/sdks/typescript/tests/server-provider.test.ts b/sdks/typescript/tests/server-provider.test.ts index 7501a72..0dd9a8b 100644 --- a/sdks/typescript/tests/server-provider.test.ts +++ b/sdks/typescript/tests/server-provider.test.ts @@ -860,4 +860,51 @@ describe('FlagshipServerProvider', () => { expect(result.errorCode).toBeUndefined(); }); }); + describe('injectable transport', () => { + it('routes HTTP-mode evaluations through the supplied fetch', async () => { + const transport = vi.fn( + async () => ({ ok: true, json: async () => ({ flagKey: 'my-flag', value: true, variant: 'on', reason: 'DEFAULT' }) }) as any, + ); + + const provider = new FlagshipServerProvider({ + endpoint: 'https://api.example.com/evaluate', + fetch: transport, + }); + + const result = await provider.resolveBooleanEvaluation('my-flag', false, {}, noopLogger); + + expect(result.value).toBe(true); + expect(transport).toHaveBeenCalledTimes(1); + expect(global.fetch).not.toHaveBeenCalled(); + }); + + it('maps a caller abort to ErrorCode.GENERAL', async () => { + const controller = new AbortController(); + const transport = vi.fn( + (_url: any, init?: RequestInit) => + new Promise((_resolve, reject) => { + (init?.signal as AbortSignal | undefined)?.addEventListener('abort', () => { + const error = new Error('The operation was aborted'); + error.name = 'AbortError'; + reject(error); + }); + }), + ); + + const provider = new FlagshipServerProvider({ + endpoint: 'https://api.example.com/evaluate', + fetch: transport as any, + fetchOptions: { signal: controller.signal }, + retries: 0, + }); + + const pending = provider.resolveBooleanEvaluation('my-flag', false, {}, noopLogger); + controller.abort(); + const result = await pending; + + expect(result.value).toBe(false); + expect(result.errorCode).toBe(ErrorCode.GENERAL); + expect(result.errorMessage).toContain('aborted'); + }); + }); }); diff --git a/sdks/typescript/tests/types.test.ts b/sdks/typescript/tests/types.test.ts index 19d0652..d1ad045 100644 --- a/sdks/typescript/tests/types.test.ts +++ b/sdks/typescript/tests/types.test.ts @@ -40,6 +40,11 @@ describe('FlagshipError', () => { expect(err.cause).toBeUndefined(); }); + it('is non-retryable by default and honours an explicit flag', () => { + expect(new FlagshipError('oops', FlagshipErrorCode.NETWORK_ERROR).retryable).toBe(false); + expect(new FlagshipError('oops', FlagshipErrorCode.NETWORK_ERROR, undefined, true).retryable).toBe(true); + }); + it('instanceof check works after prototype fix', () => { function throwFlagshipError() { throw new FlagshipError('test', FlagshipErrorCode.NETWORK_ERROR); @@ -57,6 +62,7 @@ describe('FlagshipErrorCode', () => { it('has all expected string values', () => { expect(FlagshipErrorCode.NETWORK_ERROR).toBe('NETWORK_ERROR'); expect(FlagshipErrorCode.TIMEOUT_ERROR).toBe('TIMEOUT_ERROR'); + expect(FlagshipErrorCode.ABORTED).toBe('ABORTED'); expect(FlagshipErrorCode.PARSE_ERROR).toBe('PARSE_ERROR'); expect(FlagshipErrorCode.INVALID_CONTEXT).toBe('INVALID_CONTEXT'); }); From f2f6b2384fc5739b6b2283b728f216264ae4558d Mon Sep 17 00:00:00 2001 From: Akshit Sinha Date: Thu, 6 Aug 2026 16:15:43 +0530 Subject: [PATCH 2/6] perf(typescript): cancel retry waits and optimize evaluation requests --- .changeset/quiet-pianos-listen.md | 2 +- sdks/typescript/src/client.ts | 90 ++++++++++++++++++++------- sdks/typescript/src/context.ts | 80 ++++++++++++------------ sdks/typescript/tests/client.test.ts | 43 +++++++++++++ sdks/typescript/tests/context.test.ts | 23 +++++++ 5 files changed, 175 insertions(+), 63 deletions(-) diff --git a/.changeset/quiet-pianos-listen.md b/.changeset/quiet-pianos-listen.md index f58a280..b451e57 100644 --- a/.changeset/quiet-pianos-listen.md +++ b/.changeset/quiet-pianos-listen.md @@ -6,7 +6,7 @@ Add an injectable `fetch` transport and caller `AbortSignal` propagation to `Fla - `FlagshipProviderOptions.fetch?: typeof globalThis.fetch` sets the transport for a client. It defaults to `globalThis.fetch`, resolved at call time, and the SDK never assigns to the global — so routing evaluations through a Workers service binding or stubbing the transport in tests no longer requires mutating `globalThis.fetch` and exposing unrelated traffic in the same isolate. - `evaluate(flagKey, context, { fetch?, signal? })` adds per-call overrides. `signal` is merged with the request timeout and with `fetchOptions.signal` (previously silently discarded), so a caller abort now aborts the in-flight HTTP request instead of only abandoning the promise. An already-aborted signal rejects without issuing a request. -- New `FlagshipErrorCode.ABORTED` distinguishes caller cancellation from `TIMEOUT_ERROR`. Caller aborts are never retried; timeout aborts are still retried as before. +- New `FlagshipErrorCode.ABORTED` distinguishes caller cancellation from `TIMEOUT_ERROR`. Caller aborts interrupt in-flight requests and retry delays and are never retried; timeout aborts are still retried as before. - New `FlagshipError.retryable` reports whether a failure was transient. `408`, `425`, `429`, `5xx`, connection failures, timeouts, and malformed bodies are retryable; other non-2xx responses (`400`, `401`, `403`, `404`, `422`, …) and caller aborts are terminal. This lets consumers implement fail-closed-without-caching instead of guessing from `NETWORK_ERROR` alone. - `FlagshipServerProvider` and `FlagshipClientProvider` accept and forward `fetch` in HTTP mode; combining it with `binding` throws like the other HTTP-only options. diff --git a/sdks/typescript/src/client.ts b/sdks/typescript/src/client.ts index 4eb2fe9..a90c3d5 100644 --- a/sdks/typescript/src/client.ts +++ b/sdks/typescript/src/client.ts @@ -1,5 +1,5 @@ import type { EvaluationContext } from '@openfeature/core'; -import { ContextTransformer } from './context.js'; +import { buildEvaluationUrl } from './context.js'; import { FlagshipError, FlagshipErrorCode, @@ -12,6 +12,7 @@ import { interface ResolvedOptions { endpoint: string; fetchOptions: RequestInit; + signal: AbortSignal | undefined; fetch: typeof globalThis.fetch | undefined; timeout: number; retries: number; @@ -23,14 +24,17 @@ interface ResolvedOptions { * the 4xx range is treated as a definitive answer and never retried. */ const RETRYABLE_STATUSES = new Set([408, 425, 429]); +const noop = (): void => {}; export class FlagshipClient { private readonly options: ResolvedOptions; constructor(options: FlagshipProviderOptions) { + const fetchOptions = buildFetchOptions(options); this.options = { endpoint: resolveEndpoint(options), - fetchOptions: buildFetchOptions(options), + fetchOptions, + signal: fetchOptions.signal ?? undefined, fetch: options.fetch, timeout: options.timeout || 5000, retries: Math.min(options.retries !== undefined ? options.retries : 1, 10), @@ -51,7 +55,7 @@ export class FlagshipClient { */ async evaluate(flagKey: string, context: EvaluationContext, options?: FlagshipRequestOptions): Promise { const droppedKeys: string[] = []; - const url = ContextTransformer.buildUrl(this.options.endpoint, flagKey, context, droppedKeys); + const url = buildEvaluationUrl(this.options.endpoint, flagKey, context, droppedKeys); if (droppedKeys.length > 0) { throw new FlagshipError( @@ -61,7 +65,10 @@ export class FlagshipClient { ); } - return this.fetchWithRetry(url, this.options.retries, options); + const signals = [options?.signal, this.options.signal].filter((signal): signal is AbortSignal => Boolean(signal)); + const transport = options?.fetch ?? this.options.fetch ?? globalThis.fetch.bind(globalThis); + + return this.fetchWithRetry(url, this.options.retries, transport, signals); } /** @@ -69,17 +76,23 @@ export class FlagshipClient { * terminal responses (400, 401, 403, 404, …) and caller aborts are * propagated immediately. */ - private async fetchWithRetry(url: string, retriesLeft: number, options?: FlagshipRequestOptions): Promise { + private async fetchWithRetry( + url: string, + retriesLeft: number, + transport: typeof globalThis.fetch, + signals: AbortSignal[], + ): Promise { try { - return await this.fetchWithTimeout(url, this.options.timeout, options); + return await this.fetchWithTimeout(url, this.options.timeout, transport, signals); } catch (error) { if (error instanceof FlagshipError && !error.retryable) { throw error; } if (retriesLeft > 0) { - await new Promise((resolve) => setTimeout(resolve, this.options.retryDelay)); - return this.fetchWithRetry(url, retriesLeft - 1, options); + discardResponse(error); + await waitForRetry(this.options.retryDelay, signals); + return this.fetchWithRetry(url, retriesLeft - 1, transport, signals); } throw error; @@ -90,23 +103,24 @@ export class FlagshipClient { * Issues a single request against the resolved transport, aborting it when * the timeout elapses or when any caller-supplied signal fires. */ - private async fetchWithTimeout(url: string, timeout: number, options?: FlagshipRequestOptions): Promise { - const callerSignals = [options?.signal, this.options.fetchOptions.signal].filter((signal): signal is AbortSignal => Boolean(signal)); - - const alreadyAborted = callerSignals.find((signal) => signal.aborted); + private async fetchWithTimeout( + url: string, + timeout: number, + transport: typeof globalThis.fetch, + signals: AbortSignal[], + ): Promise { + const alreadyAborted = signals.find((signal) => signal.aborted); if (alreadyAborted) { throw abortedError(alreadyAborted.reason); } - const transport = options?.fetch ?? this.options.fetch ?? globalThis.fetch.bind(globalThis); - const timeoutController = new AbortController(); let timedOut = false; const timeoutId = setTimeout(() => { timedOut = true; timeoutController.abort(); }, timeout); - const merged = mergeSignals([timeoutController.signal, ...callerSignals]); + const merged = mergeSignals([timeoutController.signal, ...signals]); try { const response = await transport(url, { @@ -135,7 +149,7 @@ export class FlagshipClient { throw error; } - const abortedBy = callerSignals.find((signal) => signal.aborted); + const abortedBy = signals.find((signal) => signal.aborted); if (abortedBy) { throw abortedError(abortedBy.reason ?? error); } @@ -156,6 +170,41 @@ function abortedError(cause: unknown): FlagshipError { return new FlagshipError('Request aborted by caller', FlagshipErrorCode.ABORTED, cause, false); } +function discardResponse(error: unknown): void { + if (!(error instanceof FlagshipError) || typeof error.cause !== 'object' || error.cause === null || !('body' in error.cause)) return; + const body = error.cause.body; + if (typeof body !== 'object' || body === null || !('cancel' in body) || typeof body.cancel !== 'function') return; + + try { + void Promise.resolve(body.cancel()).catch(noop); + } catch {} +} + +function waitForRetry(delay: number, signals: AbortSignal[]): Promise { + const alreadyAborted = signals.find((signal) => signal.aborted); + if (alreadyAborted) return Promise.reject(abortedError(alreadyAborted.reason)); + if (signals.length === 0) return new Promise((resolve) => setTimeout(resolve, delay)); + + const merged = mergeSignals(signals); + return new Promise((resolve, reject) => { + const cleanup = (): void => { + clearTimeout(timeoutId); + merged.signal.removeEventListener('abort', onAbort); + merged.dispose(); + }; + const onAbort = (): void => { + cleanup(); + reject(abortedError(merged.signal.reason)); + }; + const timeoutId = setTimeout(() => { + cleanup(); + resolve(); + }, delay); + merged.signal.addEventListener('abort', onAbort, { once: true }); + if (merged.signal.aborted) onAbort(); + }); +} + /** 408, 425, 429 and any 5xx are transient; every other non-2xx is definitive. */ function isRetryableStatus(status: number): boolean { return status >= 500 || RETRYABLE_STATUSES.has(status); @@ -166,8 +215,6 @@ function isRetryableStatus(status: number): boolean { * falls back to a manually linked `AbortController` on older runtimes. */ function mergeSignals(signals: AbortSignal[]): { signal: AbortSignal; dispose: () => void } { - const noop = (): void => {}; - if (signals.length === 1) { return { signal: signals[0]!, dispose: noop }; } @@ -236,11 +283,10 @@ function resolveEndpoint(options: FlagshipProviderOptions): string { if (endpoint) { try { - new URL(endpoint); + return new URL(endpoint).toString(); } catch { throw new Error(`Flagship: invalid endpoint URL: ${endpoint}`); } - return endpoint; } if (!accountId) { @@ -251,10 +297,8 @@ function resolveEndpoint(options: FlagshipProviderOptions): string { const resolved = `${base}/client/v4/accounts/${encodeURIComponent(accountId)}/flagship/apps/${encodeURIComponent(appId!)}/evaluate`; try { - new URL(resolved); + return new URL(resolved).toString(); } catch { throw new Error(`Flagship: resolved endpoint is not a valid URL: ${resolved}`); } - - return resolved; } diff --git a/sdks/typescript/src/context.ts b/sdks/typescript/src/context.ts index 04afeb6..9571cdc 100644 --- a/sdks/typescript/src/context.ts +++ b/sdks/typescript/src/context.ts @@ -22,39 +22,7 @@ export class ContextTransformer { * @param droppedKeys - Optional collector array; skipped key names are pushed here */ static toQueryParams(context: EvaluationContext, droppedKeys?: string[]): Record { - const params: Record = {}; - - for (const [key, value] of Object.entries(context)) { - if (value === undefined || value === null) { - continue; - } - - if (value instanceof Date) { - params[key] = value.toISOString(); - continue; - } - - if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') { - params[key] = String(value); - continue; - } - - if (typeof value === 'object') { - if (droppedKeys) { - // Caller is collecting dropped keys and will handle the situation. - droppedKeys.push(key); - } else { - // No collector — warn so the issue is visible in development. - console.warn( - `[Flagship] Context key "${key}" is a complex object/array and cannot be serialized to a query parameter. ` + - 'This value will be ignored during flag evaluation.', - ); - } - continue; - } - } - - return params; + return Object.fromEntries(toSearchParams(context, droppedKeys)); } /** @@ -66,14 +34,48 @@ export class ContextTransformer { * @param droppedKeys - Optional collector array; skipped context key names are pushed here */ static buildUrl(baseUrl: string, flagKey: string, context: EvaluationContext, droppedKeys?: string[]): string { - const url = new URL(baseUrl); - url.searchParams.set('flagKey', flagKey); + return buildEvaluationUrl(new URL(baseUrl).toString(), flagKey, context, droppedKeys); + } +} - const params = this.toQueryParams(context, droppedKeys); - for (const [key, value] of Object.entries(params)) { - url.searchParams.set(key, value); +export function buildEvaluationUrl(baseUrl: string, flagKey: string, context: EvaluationContext, droppedKeys?: string[]): string { + const params = toSearchParams(context, droppedKeys, flagKey); + + if (!baseUrl.includes('?') && !baseUrl.includes('#')) return `${baseUrl}?${params}`; + + const url = new URL(baseUrl); + for (const [key, value] of params) url.searchParams.set(key, value); + return url.toString(); +} + +function toSearchParams(context: EvaluationContext, droppedKeys?: string[], flagKey?: string): URLSearchParams { + const params = new URLSearchParams(); + if (flagKey !== undefined) params.set('flagKey', flagKey); + + for (const [key, value] of Object.entries(context)) { + if (value === undefined || value === null) continue; + + if (value instanceof Date) { + params.set(key, value.toISOString()); + continue; } - return url.toString(); + if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') { + params.set(key, String(value)); + continue; + } + + if (typeof value === 'object') { + if (droppedKeys) { + droppedKeys.push(key); + } else { + console.warn( + `[Flagship] Context key "${key}" is a complex object/array and cannot be serialized to a query parameter. ` + + 'This value will be ignored during flag evaluation.', + ); + } + } } + + return params; } diff --git a/sdks/typescript/tests/client.test.ts b/sdks/typescript/tests/client.test.ts index d485509..57ee530 100644 --- a/sdks/typescript/tests/client.test.ts +++ b/sdks/typescript/tests/client.test.ts @@ -454,6 +454,49 @@ describe('FlagshipClient', () => { vi.useRealTimers(); }); + it('aborts while waiting to retry', async () => { + const transport = vi.fn(async () => { + throw new Error('flaky'); + }); + const controller = new AbortController(); + const setTimeoutSpy = vi.spyOn(globalThis, 'setTimeout'); + const client = new FlagshipClient({ + endpoint: 'https://api.example.com/evaluate', + fetch: transport as unknown as typeof globalThis.fetch, + retries: 1, + retryDelay: 10_000, + }); + + const pending = client.evaluate('my-flag', {}, { signal: controller.signal }); + await vi.waitFor(() => expect(setTimeoutSpy.mock.calls.some((call) => call[1] === 10_000)).toBe(true)); + controller.abort('cancelled'); + + const error = await pending.catch((e) => e); + expect(error.code).toBe(FlagshipErrorCode.ABORTED); + expect(error.cause).toBe('cancelled'); + expect(transport).toHaveBeenCalledTimes(1); + }); + + it('cancels a retryable response body before retrying', async () => { + const cancel = vi.fn(); + const retryableResponse = { ok: false, status: 500, statusText: 'Server Error', body: { cancel } }; + const transport = vi + .fn() + .mockResolvedValueOnce(retryableResponse) + .mockResolvedValueOnce({ ok: true, json: async () => ({ flagKey: 'my-flag', value: true }) }); + const client = new FlagshipClient({ + endpoint: 'https://api.example.com/evaluate', + fetch: transport as unknown as typeof globalThis.fetch, + retries: 1, + retryDelay: 0, + }); + + await client.evaluate('my-flag', {}); + + expect(cancel).toHaveBeenCalledTimes(1); + expect(transport).toHaveBeenCalledTimes(2); + }); + it('uses default baseUrl when only appId and accountId provided', async () => { (global.fetch as any).mockResolvedValueOnce({ ok: true, diff --git a/sdks/typescript/tests/context.test.ts b/sdks/typescript/tests/context.test.ts index ed50a5b..02375a1 100644 --- a/sdks/typescript/tests/context.test.ts +++ b/sdks/typescript/tests/context.test.ts @@ -153,6 +153,24 @@ describe('ContextTransformer', () => { expect(url.searchParams.get('email')).toBe('user@example.com'); }); + it('preserves parameter order and encoding', () => { + const result = ContextTransformer.buildUrl('https://api.example.com/evaluate', 'my flag/name', { + targetingKey: 'user-123', + email: 'user+test@example.com', + active: false, + }); + + expect(result).toBe( + 'https://api.example.com/evaluate?flagKey=my+flag%2Fname&targetingKey=user-123&email=user%2Btest%40example.com&active=false', + ); + }); + + it('preserves existing query parameters and fragments', () => { + const result = ContextTransformer.buildUrl('https://api.example.com/evaluate?source=sdk#result', 'my-flag', { source: 'context' }); + + expect(result).toBe('https://api.example.com/evaluate?source=context&flagKey=my-flag#result'); + }); + it('should handle special characters in values', () => { const baseUrl = 'https://api.example.com/evaluate'; const flagKey = 'my-flag'; @@ -178,6 +196,11 @@ describe('ContextTransformer', () => { expect(result).toBe('https://api.example.com/api/v1/apps/my-app/evaluate?flagKey=my-flag'); }); + it('preserves URL canonicalization and validation', () => { + expect(ContextTransformer.buildUrl('https://api.example.com', 'my flag', {})).toBe('https://api.example.com/?flagKey=my+flag'); + expect(() => ContextTransformer.buildUrl('not-a-url', 'my-flag', {})).toThrow(); + }); + it('should encode special characters in flagKey', () => { const result = ContextTransformer.buildUrl('https://api.example.com/evaluate', 'my flag/name', {}); const url = new URL(result); From 16c1c13695ad4c038cb489f55cdf4473262a3bac Mon Sep 17 00:00:00 2001 From: Akshit Sinha Date: Fri, 7 Aug 2026 11:38:37 +0530 Subject: [PATCH 3/6] chore: upgrade dependencies --- package.json | 18 +- pnpm-lock.yaml | 1579 +++++++++++++++++----------------- sdks/typescript/package.json | 20 +- 3 files changed, 787 insertions(+), 830 deletions(-) diff --git a/package.json b/package.json index fd3bc5d..1e07016 100644 --- a/package.json +++ b/package.json @@ -26,19 +26,19 @@ }, "devDependencies": { "@changesets/changelog-github": "^0.7.0", - "@changesets/cli": "^2.31.0", + "@changesets/cli": "^2.31.1", "@changesets/read": "^0.6.7", "@changesets/types": "^6.1.0", - "@decimalturn/toml-patch": "^2.0.0", + "@decimalturn/toml-patch": "^2.1.0", "@manypkg/get-packages": "^3.1.0", - "@types/node": "^24.13.2", + "@types/node": "^24.13.3", "husky": "^9.1.7", - "lint-staged": "^17.0.7", - "oxfmt": "^0.55.0", - "oxlint": "^1.70.0", - "pkg-pr-new": "^0.0.75", - "sherif": "^1.11.1", - "tsx": "^4.22.4", + "lint-staged": "^17.3.0", + "oxfmt": "^0.62.0", + "oxlint": "^1.77.0", + "pkg-pr-new": "^0.0.87", + "sherif": "^1.13.0", + "tsx": "^4.23.8", "typescript": "^5.9.3" }, "overrides": {}, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 75a59f8..c9c9402 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,8 +12,8 @@ importers: specifier: ^0.7.0 version: 0.7.0 '@changesets/cli': - specifier: ^2.31.0 - version: 2.31.0(@types/node@24.13.2) + specifier: ^2.31.1 + version: 2.31.1(@types/node@24.13.3) '@changesets/read': specifier: ^0.6.7 version: 0.6.7 @@ -21,35 +21,35 @@ importers: specifier: ^6.1.0 version: 6.1.0 '@decimalturn/toml-patch': - specifier: ^2.0.0 - version: 2.0.0 + specifier: ^2.1.0 + version: 2.1.0 '@manypkg/get-packages': specifier: ^3.1.0 version: 3.1.0 '@types/node': - specifier: ^24.13.2 - version: 24.13.2 + specifier: ^24.13.3 + version: 24.13.3 husky: specifier: ^9.1.7 version: 9.1.7 lint-staged: - specifier: ^17.0.7 - version: 17.0.7 + specifier: ^17.3.0 + version: 17.3.0 oxfmt: - specifier: ^0.55.0 - version: 0.55.0 + specifier: ^0.62.0 + version: 0.62.0 oxlint: - specifier: ^1.70.0 - version: 1.70.0 + specifier: ^1.77.0 + version: 1.77.0 pkg-pr-new: - specifier: ^0.0.75 - version: 0.0.75 + specifier: ^0.0.87 + version: 0.0.87 sherif: - specifier: ^1.11.1 - version: 1.11.1 + specifier: ^1.13.0 + version: 1.13.0 tsx: - specifier: ^4.22.4 - version: 4.22.4 + specifier: ^4.23.8 + version: 4.23.8 typescript: specifier: ^5.9.3 version: 5.9.3 @@ -61,84 +61,63 @@ importers: sdks/typescript: dependencies: lru-cache: - specifier: ^11.5.1 - version: 11.5.1 + specifier: ^11.5.2 + version: 11.5.2 devDependencies: '@cloudflare/workers-types': - specifier: ^4.20260617.1 - version: 4.20260617.1 + specifier: ^4.20260702.1 + version: 4.20260702.1 '@openfeature/core': - specifier: ^1.11.0 - version: 1.11.0 + specifier: ^1.12.0 + version: 1.12.0 '@openfeature/server-sdk': - specifier: ^1.22.0 - version: 1.22.0(@openfeature/core@1.11.0) + specifier: ^1.23.0 + version: 1.23.0(@openfeature/core@1.12.0) '@openfeature/web-sdk': - specifier: ^1.9.0 - version: 1.9.0(@openfeature/core@1.11.0) + specifier: ^1.10.0 + version: 1.10.0(@openfeature/core@1.12.0) '@types/node': - specifier: ^24.13.2 - version: 24.13.2 + specifier: ^24.13.3 + version: 24.13.3 '@vitest/coverage-v8': - specifier: ^4.1.9 - version: 4.1.9(vitest@4.1.9) + specifier: ^4.1.10 + version: 4.1.10(vitest@4.1.10) tsdown: - specifier: ^0.22.3 - version: 0.22.3(tsx@4.22.4)(typescript@5.9.3) + specifier: ^0.22.14 + version: 0.22.14(tsx@4.23.8)(typescript@5.9.3) tsx: - specifier: ^4.22.4 - version: 4.22.4 + specifier: ^4.23.8 + version: 4.23.8 typescript: specifier: ^5.9.3 version: 5.9.3 vitest: - specifier: ^4.1.9 - version: 4.1.9(@types/node@24.13.2)(@vitest/coverage-v8@4.1.9)(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(tsx@4.22.4)(yaml@2.9.0)) + specifier: ^4.1.10 + version: 4.1.10(@types/node@24.13.3)(@vitest/coverage-v8@4.1.10)(vite@8.0.16(@types/node@24.13.3)(esbuild@0.28.1)(tsx@4.23.8)(yaml@2.9.0)) packages: - '@babel/generator@8.0.0': - resolution: {integrity: sha512-NT9NrVwJsbSV6Y2FSstWa71EETOnzrjkL5/wX3D2mYHtKM+qvqB1DvR4D0Setb/gDBsHzRICifwEWMO8CnTF6g==} - engines: {node: ^22.18.0 || >=24.11.0} - '@babel/helper-string-parser@7.29.7': resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} engines: {node: '>=6.9.0'} - '@babel/helper-string-parser@8.0.0': - resolution: {integrity: sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg==} - engines: {node: ^22.18.0 || >=24.11.0} - '@babel/helper-validator-identifier@7.29.7': resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@8.0.0': - resolution: {integrity: sha512-kXxQVZHNOctSJJsqzmcbPSCEkM6oHNnDIkua7g9RCO9xRHj2eCiKvRx2KPdfWR9QxcGWnK/oArrtunmie3rL9g==} - engines: {node: ^22.18.0 || >=24.11.0} - - '@babel/parser@7.29.7': - resolution: {integrity: sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==} + '@babel/parser@7.29.8': + resolution: {integrity: sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==} engines: {node: '>=6.0.0'} hasBin: true - '@babel/parser@8.0.0': - resolution: {integrity: sha512-aLxAE+imI9bCcyaPrUDjBv3uSkWieifjLe0kuFOZF0zli0L6GCsTmsePnTr55adbIAgYz2zhN1vnFimCBUYcRQ==} - engines: {node: ^22.18.0 || >=24.11.0} - hasBin: true - '@babel/runtime@7.29.7': resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==} engines: {node: '>=6.9.0'} - '@babel/types@7.29.7': - resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==} + '@babel/types@7.29.8': + resolution: {integrity: sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==} engines: {node: '>=6.9.0'} - '@babel/types@8.0.0': - resolution: {integrity: sha512-K8ponJDxBwDHigkeFqaqT5wLGl4bTlwMafR8k7b5CPxr6Ww+UG9ls8Yx6Tcpboxu97eeGVEEyKcHmEyOwN1vSw==} - engines: {node: ^22.18.0 || >=24.11.0} - '@bcoe/v8-coverage@1.0.2': resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} engines: {node: '>=18'} @@ -155,8 +134,8 @@ packages: '@changesets/changelog-github@0.7.0': resolution: {integrity: sha512-rBsbRvc4TVn+FvFnOVM3LxlFJfTXXCp8gfVJ+0BubxWNSVnLuAzowi5j+IEraLLP52w8AAs9QfKbPS3MMiXQJA==} - '@changesets/cli@2.31.0': - resolution: {integrity: sha512-AhI4enNTgHu2IZr6K4WZyf0EPch4XVMn1yOMFmCD9gsfBGqMYaHXls5HyDv6/CL5axVQABz68eG30eCtbr2wFg==} + '@changesets/cli@2.31.1': + resolution: {integrity: sha512-uO05WTcRBwuVOJVSW8Cmpqw6q0WDL53ajGCMyszutvOe5toOnunbpM4jZzf+qxBOz7i0AzopZ8diBuewjmF40w==} hasBin: true '@changesets/config@3.1.4': @@ -204,31 +183,22 @@ packages: '@changesets/write@0.4.0': resolution: {integrity: sha512-CdTLvIOPiCNuH71pyDu3rA+Q0n65cmAbXnwWH84rKGiFumFzkmHNT8KHTMEchcxN+Kl8I54xGUhJ7l3E7X396Q==} - '@cloudflare/workers-types@4.20260617.1': - resolution: {integrity: sha512-HdbP3CNcdMZBwegitFDjWvzv+6wPkFXvV9gBXMnf6RjV2Cy3W8TJL3IhSEGul0S6F1DHjnucP7lrpIsvkzNEjA==, tarball: https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20260617.1.tgz} + '@cloudflare/workers-types@4.20260702.1': + resolution: {integrity: sha512-mOhf5TUEB1m2vPrxtqoIGfz0fUC9xyxRDx5gWHy5s+OCo6dcV+g7wI1R7gYCMFohhqF/2y2xeKVwMwCJjfn/WA==, tarball: https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20260702.1.tgz} - '@decimalturn/toml-patch@2.0.0': - resolution: {integrity: sha512-nVU9bcndpePHaDalbqA3Rbx/46onD9fcKgUI5dUsvbIpFMYXp/78ScUwdFUCXjcjN9DD+pNitTfDiwJJTPb4ig==} + '@decimalturn/toml-patch@2.1.0': + resolution: {integrity: sha512-IICXlX7hcD9lGN7k+vDXcVnOfcbOEqMpeQHRnrhnmm7tZ8HluFrSgBKLNC72fQpNXynbJTC7pjtfNCaKrKlGyA==} engines: {node: '>=14'} '@emnapi/core@1.10.0': resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} - '@emnapi/core@1.11.0': - resolution: {integrity: sha512-l9Oo58x0HOP5znGzVhYW9U3e5wVuA4LAZU2AGezTmkhO1CgQRFDhDg4nneHsu/t3WniXg9QrG2nIXL/ZS8ln8Q==} - '@emnapi/runtime@1.10.0': resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} - '@emnapi/runtime@1.11.0': - resolution: {integrity: sha512-55coeOFKHv1ywEcUXJtWU5f+Jr/W5tZDvZig8DLKSwUN1JpROQ4rk/SNOQiFWmaR/VKF4zuFyW1B8JduOSv6Pg==} - '@emnapi/wasi-threads@1.2.1': resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} - '@emnapi/wasi-threads@1.2.2': - resolution: {integrity: sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==} - '@esbuild/aix-ppc64@0.28.1': resolution: {integrity: sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==} engines: {node: '>=18'} @@ -394,9 +364,6 @@ packages: '@types/node': optional: true - '@jridgewell/gen-mapping@0.3.13': - resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} - '@jridgewell/resolve-uri@3.1.2': resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} engines: {node: '>=6.0.0'} @@ -443,266 +410,266 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} - '@openfeature/core@1.11.0': - resolution: {integrity: sha512-P0u3/ht/oZCQT89fOed+laLk0kZR529a825cS02uPDglxXbE97irWYpDAeRGGVETIzKfuy+H2g8c3Ccv/tXJNQ==} + '@openfeature/core@1.12.0': + resolution: {integrity: sha512-7PCPzyd1OC19begz30+CRknFB0ChtyaZUhk7YWrk+Bov1fpVw0HYkTXtoxxsvPfDZB9P9WsT7uixN+Z8f9+bcA==} - '@openfeature/server-sdk@1.22.0': - resolution: {integrity: sha512-YBrf6SQkn0FNB/dRAtLEs41dvFMUE8CrQTwI+iLaMFUIqWlqGNJfGnulKSneEKS+2OgKTAC6DdmKcZ6tK7kBcg==} + '@openfeature/server-sdk@1.23.0': + resolution: {integrity: sha512-JWeLvltJIV0AFgOfbw7hK9b9Rw/5wWq+RMhCIU9sJtAqarxr8hSPglG+JAGGhYdElAz3Qwe9W3ApWyPNkQEyqg==} engines: {node: '>=20'} peerDependencies: - '@openfeature/core': ^1.11.0 + '@openfeature/core': ^1.12.0 - '@openfeature/web-sdk@1.9.0': - resolution: {integrity: sha512-FCrNfqvE/thHVfCNU0KKx1SD7rk+1wE2UaR5B5OPZl917QJv6AsKRwaI3N+SVgwXWI07GgtXP6hNlTVb49PGhg==} + '@openfeature/web-sdk@1.10.0': + resolution: {integrity: sha512-qf+JmJSnaslhegNwoDDLn2Cf72P6cIobuAQPUb61MSkzJOZuVLU6f9pv/90FlLcK6UDtp6od//xZHW712rBrmg==} peerDependencies: - '@openfeature/core': ^1.11.0 + '@openfeature/core': ^1.12.0 '@oxc-project/types@0.133.0': resolution: {integrity: sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==} - '@oxc-project/types@0.135.0': - resolution: {integrity: sha512-wR+xRdFkUBMvcAjBJ2q2kcZM6d+DKu2NgoOyxZgYwZdLhmiv6+rnO8PZ/P68kMiZtIKm+pW7zyEJ4kSOs0vo+Q==} + '@oxc-project/types@0.143.0': + resolution: {integrity: sha512-u6JZdLBTLotrNC9Vd6vPssINdzcCzleKAH6EJKImQb7GtYvX5keN2dxkoK44stCc4tffE6QQRtZTXVSzsLUlWA==} - '@oxfmt/binding-android-arm-eabi@0.55.0': - resolution: {integrity: sha512-+rFDOqQe5LOWgxrAJaZgLRudr6GQm0wGI6gtu7vVkrdLGjNMUSGbAlaCr8j7F2H2Er97vYQCU8WDb30onqMM1g==} + '@oxfmt/binding-android-arm-eabi@0.62.0': + resolution: {integrity: sha512-pdsv0C4gPjJ8H1+sd8u0BDx+yLACTL+rgeMIOL1ln4ihSnhw8CWXtYWgvcSkyTfgGBIzFKab+d8rx9Xl4en/Kw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [android] - '@oxfmt/binding-android-arm64@0.55.0': - resolution: {integrity: sha512-ctulLq8s3x8Zmvw6+iccB09TIKERAklRSmbJ10gk8mlAn05qZxoyo52dj3Hi9IJcmDSwF54fQaTVh2CbL6PInw==} + '@oxfmt/binding-android-arm64@0.62.0': + resolution: {integrity: sha512-WC3YQ7uS/KtDrjmqwBviwFKe9qeoi+eXx8aX1z/ffG23Md75myjrJaQqTuJvdOLPoa4EYTjDWH0dHXfwulCVog==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@oxfmt/binding-darwin-arm64@0.55.0': - resolution: {integrity: sha512-xDQczLH9pw/RBk1h/GH0qcGMm8hQtmtVHBNLSH3lk1gEIR09hZ4L+mJQl4VqiVAvPK9VG9PYrWWuSQLt7xTbiA==} + '@oxfmt/binding-darwin-arm64@0.62.0': + resolution: {integrity: sha512-GM8Yf3LjjaR1I8PD0SfeoIlwhsh9GvSF+cQ8sf624Yxnjsyumn95aFzYfKJVefblfDIiOAnZ7QVm2sa21Er/0Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@oxfmt/binding-darwin-x64@0.55.0': - resolution: {integrity: sha512-JaNoFCkF2CJdGgpPSMbuO9HVyXyoNGIhMHPvp6NYAjeVKw9XEYc0HcUWJLPQa3Q69WV5wMa9m5jPMJPtbLtcRg==} + '@oxfmt/binding-darwin-x64@0.62.0': + resolution: {integrity: sha512-d5THp7F8bCxLqNogEXDORRsQD6dosf3EyFtnXfBer6v+8tGdcWIjoDX9WaXrrF/26zOmL8qHpPTKCEvpBDmZkQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@oxfmt/binding-freebsd-x64@0.55.0': - resolution: {integrity: sha512-DNbszhpg6S2MIzax5azdHFTTBIVkR5xr8yyRZuA4yoDAwOkzIp3tmldgKZM2+VlT+hJIG0xUksA+elISzMEAfA==} + '@oxfmt/binding-freebsd-x64@0.62.0': + resolution: {integrity: sha512-1DnrtXGZooOZ0fHgAXZUaDQzBVh1CM2MNW4oBXyQ2aWKvCHjyljvT9fgBkOM0fEOb96X5eqtcfJ0YUVt9jj66g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@oxfmt/binding-linux-arm-gnueabihf@0.55.0': - resolution: {integrity: sha512-2snoaoRfFFyGnbOcKUK36rREBYxe/Xgz3uHbiA5zbCB/s6R4DQj4mHqYAaWWhgizCUSDxV8cE9zAZ0XleNpKGw==} + '@oxfmt/binding-linux-arm-gnueabihf@0.62.0': + resolution: {integrity: sha512-4pQDHOYRH+Huqe0StIaWyvk2CVl/aTaqSrbZpA3/pLS2xH24ME7lBgYprhQF2fRkHBzhGGGKliwxFsDdHwx59g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxfmt/binding-linux-arm-musleabihf@0.55.0': - resolution: {integrity: sha512-q1aktHF/WRpSK81BX1dE/9vWrS2jGw1Nax2kb4DBLGAewubCLcoNyp4Zl/NSMgbv3vUS46Z33wIQkBVYOP3PYg==} + '@oxfmt/binding-linux-arm-musleabihf@0.62.0': + resolution: {integrity: sha512-X0jAaZJFMCVKhB6YyWVTQ/wN2DLsBcZKSMqTS76bF6riT+XZdtg2FPEdjDvdVbunO9cG+tWiVaEs4Zs38lxYog==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxfmt/binding-linux-arm64-gnu@0.55.0': - resolution: {integrity: sha512-VD0y36aENezl/3tsclA/4G53Cc7iV+7Uoh7gz4yvcOTaEYBtJpQsE6PKDGTtUtOvGS4kv51ybfXY/nWZejO5IA==} + '@oxfmt/binding-linux-arm64-gnu@0.62.0': + resolution: {integrity: sha512-682Z8T5s8T5ATArYtsejKvbIfd8LEAXyyDkKkoZVq8HND7Vx8TYLlrDjDSeYfodMeVwHOgkj13lJYR8cj6vUSg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-arm64-musl@0.55.0': - resolution: {integrity: sha512-r8xlKJFcsRmn0H5jZrdORae6RX9jDBrZVvOoxF+bCQtampQJClv80aZEHsv+NsLsp2KCE5ql79O7DpPVzYWpXA==} + '@oxfmt/binding-linux-arm64-musl@0.62.0': + resolution: {integrity: sha512-lk25fAl7KWaLWVJcW0CHEXB7QlQZtx5eDkjpaGMK0hzXTjUe0Wmlu8IKuFHoviSOcEJedRTs4VE/506VqGxGew==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@oxfmt/binding-linux-ppc64-gnu@0.55.0': - resolution: {integrity: sha512-GRKv/HXHcwIVld/WU61rF0g0R16hl5EJ+ScKdpjevT57lnLnagj/U2YUbXf2mT+2Pg1uCzWC+mvGicPV3CDdLQ==} + '@oxfmt/binding-linux-ppc64-gnu@0.62.0': + resolution: {integrity: sha512-SFyNqHQLwySceWNLhiSldx7wPXRAzP0L0WcW9GegP3uWrpZGJiZlQO85NbHAFPEfxR9PhZ9qSnZryEh7+v+4Gw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-riscv64-gnu@0.55.0': - resolution: {integrity: sha512-rdv57enTiPtpSYRMKfAiEbQb0Puw5t9N7isVinDoo5qeLDScro2gznmZqSgSWbVZRzLisTeCTW8Qwgw0bOHv3A==} + '@oxfmt/binding-linux-riscv64-gnu@0.62.0': + resolution: {integrity: sha512-KYj55C1ywJfHo6+aKDuEmUtVEdJALsC5GwayDGsI6FGz2GxFqNr/mA8nxVsNbJzm7sE5MRqTQ9ziImSzhYXysA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-riscv64-musl@0.55.0': - resolution: {integrity: sha512-7v1nNrlD43VY6+sYQ6efYyb3lE6QY182304PD/768ZxTjOmFd/3dQa3u/nGBUAXYdGSWOQc5N3PnS0QzUXyEIA==} + '@oxfmt/binding-linux-riscv64-musl@0.62.0': + resolution: {integrity: sha512-BhZDNo5GOU5nC378RhD0/XpvaEBHsH3HLgJp8YZX3A0InC7oivzA63HsRmiXFLtLSHAstEVrDf6fbC7Rs8Jh/A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [musl] - '@oxfmt/binding-linux-s390x-gnu@0.55.0': - resolution: {integrity: sha512-f4lJLUSPOgScjFl9LiflKCTocyNRwE25JmTMbN4XQdDjoZzEHjqf3wA3VESF1/csg7i8m7+EQLbrZyYDqe10UQ==} + '@oxfmt/binding-linux-s390x-gnu@0.62.0': + resolution: {integrity: sha512-UyAFmyHkgSgUJ/wOM4p3U8AC2yAFvRH5PNBs7TnK0fObTT/XSWcdr/lAzPSWaekHaZFaMeFZyk9n93Joq3J93A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-x64-gnu@0.55.0': - resolution: {integrity: sha512-MihqiPziJNoWy4MqNSV+jVA1g+07iQDjZiR0vaCaDoPgFEiJpCMsxamktzLV07cEeQsSJ04vQaU4CzCQwIvtDA==} + '@oxfmt/binding-linux-x64-gnu@0.62.0': + resolution: {integrity: sha512-1iYMP0leytWazFubD/WnINJuIrzRPuoL1aWEJdlGezEzDbTxcd29R4r8IUzP2oWeKst5V02uMJgR2NILlPlG6w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-x64-musl@0.55.0': - resolution: {integrity: sha512-Yqghym7KYAVjP9MmSrNZiDeerMuoejNjo0r3ox5H3GDKk8eAfl8VyJm9i+pWCLDCTnAbcTUMMN2ZKjUYXH1v3g==} + '@oxfmt/binding-linux-x64-musl@0.62.0': + resolution: {integrity: sha512-4rA/URtJSTVNVAQz6Q8wf7SaRvOXVy+TizriT9hs/Y1XhLR/R+92uWKRQG8yFWRAIEBbFHJ6WevQcl/G9SXEfw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@oxfmt/binding-openharmony-arm64@0.55.0': - resolution: {integrity: sha512-s5SDvVVSbyQl1V5UU3Yl12M+XLUQ3rl5SglNqgAA2K4PXUtQhyNSS00wivONPEnNo5W01rCou8WkDNyvI/RGHg==} + '@oxfmt/binding-openharmony-arm64@0.62.0': + resolution: {integrity: sha512-mSZuFHU2ar1KLUjXpI2QBQcJ1VsOB3mOCgQXuXCpKs19dgh4u+OaovNfrWDfiJb+ihJ2+f7YFcaO9bS2dlTCXA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@oxfmt/binding-win32-arm64-msvc@0.55.0': - resolution: {integrity: sha512-7p9FB5R32tw2KyyNX3wpQrR2WHwEHvMEiBlGXxeTCaRMCVNx3UtFMAUbaQ/pRNWIrEUZmYhJ6tcUH52uPTRYjQ==} + '@oxfmt/binding-win32-arm64-msvc@0.62.0': + resolution: {integrity: sha512-OfwuhkcjDlqC4EgDojtiV9mzpLqeB9KqTOWPOjLEYBVdDCVSxqW3qzp/xcIxsbtI0UgGCnKvAqYKyY25kf5JZw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@oxfmt/binding-win32-ia32-msvc@0.55.0': - resolution: {integrity: sha512-ZYqj3fDnOT1IaVGMP5kpmkQl4F3tQIm2ZyAxvqkJYmI0xgWWak4ss4XYwv3VDfM+TWXeC9K4uQ/wW5jm/5XABA==} + '@oxfmt/binding-win32-ia32-msvc@0.62.0': + resolution: {integrity: sha512-P9uDDNFRzghO3X8QAzhkjKhK7JvtABsVn8UYtFX7uor12IAnwNt8nNIctvfWj1JkQU/kE+fmLRPiw7XlrIHsZw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ia32] os: [win32] - '@oxfmt/binding-win32-x64-msvc@0.55.0': - resolution: {integrity: sha512-eEYT5tivGnGbPHuOHuQpi6CGLObhh0re/5jcNQHihD2GRYkTM85dyi5a19zjP8Q00t1uqAx+/QGLUGdHeqzWyg==} + '@oxfmt/binding-win32-x64-msvc@0.62.0': + resolution: {integrity: sha512-dlI5SY7XYQCiCBafntWagCR6HcAJB/NpsLtdlPx8x08+Osz8Ok1HHz1GZuusegCe/VoJ6pAnF5a4pd5OZAq7qQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] - '@oxlint/binding-android-arm-eabi@1.70.0': - resolution: {integrity: sha512-zFh0P4cswmRvw6nkyb89dr18rRanuaCPAsEXsFDoQY8WdaquI8Pt4NWFjaMJg6L23cy5NeN8J9cBnREbWzZhaw==} + '@oxlint/binding-android-arm-eabi@1.77.0': + resolution: {integrity: sha512-E06sKWS6PiI6HRxS1wyQg22HvApt01hI7fV+T3wUk3OSbaaP4a3hYGY/MIQDmASqCiRjBdpRQYkgMkqH82cWmQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [android] - '@oxlint/binding-android-arm64@1.70.0': - resolution: {integrity: sha512-qI8o4HZjeGiBrWv+pJv4lH0Yi2Gl/JSp/EumBUApezJprIKa5PS4nU0lQsQngtky8k+SplQIOjv6hwu0SSxeyg==} + '@oxlint/binding-android-arm64@1.77.0': + resolution: {integrity: sha512-NvsKz0KZxTp9cYWPLf+FXaSZwB3oO3peAjtukpOMBgse2vhQSoIIVqeO1yR0lEo/UcdZIDL18uq+kL0LzQ0ytA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@oxlint/binding-darwin-arm64@1.70.0': - resolution: {integrity: sha512-8KjgVVHI5F9nVwHCRwwA78Ty7zNKP4Wd9OeN5PSv3iu/F/u1RVXoOCgLhWqust6HmwQG6xc8c+RCyaWENy24+w==} + '@oxlint/binding-darwin-arm64@1.77.0': + resolution: {integrity: sha512-bgjTn6nW4bQCFBvSvuHCpDD+sONvmpo4lGI4PxzMt1quBA+xYxhczk6RiCn3GZ9gY8uhaBbwhj9MdKGfu6T9DA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@oxlint/binding-darwin-x64@1.70.0': - resolution: {integrity: sha512-WVydssv5PSUBXFJTdNBWlmGkbNmvPGaFt/2SUT/EZRB6bq6bEOHmMlbnupZD5jmlEvi9+mZJHi8TCw15lyfSfQ==} + '@oxlint/binding-darwin-x64@1.77.0': + resolution: {integrity: sha512-aotaIttH1R6j1Rwhx0M0htgeZyGtVQqYNTVEYMN/UcgHPquGA6kmk9OyuDc3a2GKUQBC+3C3GVQCcrRPMYqAFA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@oxlint/binding-freebsd-x64@1.70.0': - resolution: {integrity: sha512-hJucmUf8OlinHNb1R7fI4Fw6WsAstOz7i8nmkWQfiHoZXtbufNm+MxiDTIMk1ggh2Ro4vLzgQ+bKvRY54MZoRA==} + '@oxlint/binding-freebsd-x64@1.77.0': + resolution: {integrity: sha512-nNx/wta7ksRAdYvq+l4AWjXkLxEXHALhENxjj2cYbQAIR4ybaA5L+hCbE63HOmft5czQ6ks+hb8vmEAnn7YGPg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@oxlint/binding-linux-arm-gnueabihf@1.70.0': - resolution: {integrity: sha512-1BnS7wbCYDSXwWzJJ+mc3NURoha6m6m6RT5c6vgAY3oz7C3OVXP+S0awo2mRq97arrJkVvO3qRQfyAHL+76xtQ==} + '@oxlint/binding-linux-arm-gnueabihf@1.77.0': + resolution: {integrity: sha512-tMLLjM7xXtzXisVCzkOTXNCy9bZVId2wteNwjohlFDR/jY6WagpEDA1c1wu4xRc20Hojaxj+V6DSR7gbKxijWA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxlint/binding-linux-arm-musleabihf@1.70.0': - resolution: {integrity: sha512-yKy/UdbR55+M2yEcuiV5DCNC/gdQAjr/GioUy50QwBzSrKm8ueWADqyRLS9Xk+qjNeCYGg6A8FvUBds56ttfqg==} + '@oxlint/binding-linux-arm-musleabihf@1.77.0': + resolution: {integrity: sha512-MiAFDFaqR0tmHTAyo0YDcZ5hyLREdYw/RQhc2R3cbT+8O3tB+zqPM2th9TTQ+Uo3jn/embS+DO+HyX9ztCPkOQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxlint/binding-linux-arm64-gnu@1.70.0': - resolution: {integrity: sha512-0A5XJ4alvmqFUFP/4oYSyaO+qLto/HrKEWTSaegiVl+HOufFngK2BjYw9x4RbwBt/du5QG6l5q1zeWiJYYG5yg==} + '@oxlint/binding-linux-arm64-gnu@1.77.0': + resolution: {integrity: sha512-/xqQ3B16i1T4cyt/9Mn+4CpzhUXoBXp7kVpIwzOXNFLj5JmK1bIjsbSnX296Gg8A/o7oDtKWikFgBx0SLwztkw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-arm64-musl@1.70.0': - resolution: {integrity: sha512-JiylyurlB0CLSedNtx1gzv3FvfWPF1h/2Y3BJszPLNt5XQFlBsH5ke0Jle3iJb3uqu5m2e7A/DwzpuCAHdiU+A==} + '@oxlint/binding-linux-arm64-musl@1.77.0': + resolution: {integrity: sha512-LSbwuRKiNCenPDcbARqAZ5RfBy7gmj7vOvfJRLeCDU3gFtSxWbhv/+VTlaUqzUhNj1gFLHB8h7ALnxa/Az6z6g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@oxlint/binding-linux-ppc64-gnu@1.70.0': - resolution: {integrity: sha512-J8VPG7I3/HmgaU4u8pNU2kFx2+0U+vPLS1dXFxXOaR/2TQ0f8AC7DRz0SRGRI1bfphnX2hVYTTtLuhL4nYKL+Q==} + '@oxlint/binding-linux-ppc64-gnu@1.77.0': + resolution: {integrity: sha512-QWdcH31mXEUe5Nq1s0CfCpceaKjIo9uZtwDjAuL681g1axf+5x8xrg/eXWaw//4NCxYZ4V4e5Hu5tvdR+pTBlg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-riscv64-gnu@1.70.0': - resolution: {integrity: sha512-N2+4lV2KLN+oXTIIIwmWDhwkrnvqf5oX7Hw0zPjk+RuIVgiBQSOlJWF7uQoFx2siEYX0ZQ5cfSbEAHm+J3t7Wg==} + '@oxlint/binding-linux-riscv64-gnu@1.77.0': + resolution: {integrity: sha512-GnOfYgJxbcElOiPZaDFDl406ONddwvOWk2jvAAAEjwAl4GofNoHF+/HHUIBYa6bFCArlcGPi0XjC4cU1pkgF/Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-riscv64-musl@1.70.0': - resolution: {integrity: sha512-1e2L7cFCvx9QDzq6NPP+0tABKb5z6nWHyddWTNKprEsjO9xNrAtPowuCGpjNXxkTdsMiZ4jc8YQ5SstZd4XK6g==} + '@oxlint/binding-linux-riscv64-musl@1.77.0': + resolution: {integrity: sha512-AyEMTUCf0xY+hHF+IxqXFQIX0yQOIR8ykpY0lJNOw9xYqOzUX8dyZfRvlG0RfXwuQn2eonf/8NrMmDSZJjdqsA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [musl] - '@oxlint/binding-linux-s390x-gnu@1.70.0': - resolution: {integrity: sha512-Kwu/l/8GcYibCWA9m9N5pRXMIKVSsL/YbgpLzYkqDhWTiqdRfnNJ/+nqIKRKQiFbHWsdlHEhzMwruJK+qcEruA==} + '@oxlint/binding-linux-s390x-gnu@1.77.0': + resolution: {integrity: sha512-sPLzEcNvxd/oyVQ5oZo92CiHkFkpBeRop13E/P3TPY+hZfXHKCOWKI70TE2RYwMKFJDc20EMjH16L7NZICtKTw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@oxlint/binding-linux-x64-gnu@1.70.0': - resolution: {integrity: sha512-tap04CsHYOl0nSAQJfPNIuBxqEPB2HnhQqwaOXLg1jnp2XfRo8Fa814dA4QC4zpvTWXCjAAaCY1W5LOORkEQuQ==} + '@oxlint/binding-linux-x64-gnu@1.77.0': + resolution: {integrity: sha512-1Oh2ssH2L7lwyvkdSqaMUfsGfwU2Wfvew+obBUYjRVqhpBcUpwnsPSEr1IzVi9XqkuY10geiLsNKecqaZC34Dw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-x64-musl@1.70.0': - resolution: {integrity: sha512-hzJa/WgvtJpbBD9rgfy0qe+MjbxOXNUT0bfR1S6EQQzfTtBFA9xg5q8KSwRrQ2QfSS+TaP4j+4mVPQrfNc6UNg==} + '@oxlint/binding-linux-x64-musl@1.77.0': + resolution: {integrity: sha512-0j/2wRgNGO+Qj/M1uu/p57h/hFTTWWcfie0ufkbabeus2s5+/QqkCflnMOwLLN5m2GsNeWp4xdl4cPa4n7QCOQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@oxlint/binding-openharmony-arm64@1.70.0': - resolution: {integrity: sha512-xbsaNSNzVSnaJACCUYr1HQMyY/Q/Q1LkePmHG3UvZPvGCYGNxrsZp9OmtA6ick8xH47ltRRbRrPCM1YXYcyC+A==} + '@oxlint/binding-openharmony-arm64@1.77.0': + resolution: {integrity: sha512-BJ/j54qS0usEnyDkLYURMj2iiD9h5Cyy+ppzeMSXBGRXaGRNWnj1Mw14NqWMR5E/PzdgB30OOCCzLzbRoduafw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@oxlint/binding-win32-arm64-msvc@1.70.0': - resolution: {integrity: sha512-icAEsUI7JbW1TMRdEXV83mVAInhRVQYuuAlPpxdGwJ95chNdnCzjloRW8GglT0WvzOEZSio6fnYSk2DJ2Hv7LQ==} + '@oxlint/binding-win32-arm64-msvc@1.77.0': + resolution: {integrity: sha512-Yh8w+g2Lpx7StrvtYkoz9JJvXjB9wxgFChFNb85nrXm/wj/XTwGWS1hve9+900HL7llrntYB3YP+y32E3tRqzA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@oxlint/binding-win32-ia32-msvc@1.70.0': - resolution: {integrity: sha512-FHMSWbVsPVs/f+Jcl04ws4JJ2wUnauyTzlpxWRG/lSO/8GpX08Fo2gQZqdA6CrRFI+zvkxl+N/KwJGWfUwYVZA==} + '@oxlint/binding-win32-ia32-msvc@1.77.0': + resolution: {integrity: sha512-zja5b7+6a7UsRFgAQSrnax5vrzliEyNPLCjfXONu/vTWswaIVZGFajJZptaeRvPE4LghtFdAzVFlexTm7MVTGA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ia32] os: [win32] - '@oxlint/binding-win32-x64-msvc@1.70.0': - resolution: {integrity: sha512-ptOlKwCz7n4AKs5VweMqG6DAg677FmKOK+vBkkL9DMNgFATIQ+upqUYBTOEwRQyRAx1ncGlPlXleV2hIcm3z4g==} + '@oxlint/binding-win32-x64-msvc@1.77.0': + resolution: {integrity: sha512-+teyvPDZ2RjUvo+SuCqS/UhaJl1QtdW5fWT5NJTV61V5MIuIS90Db9LixmtEGvXixyttiK62P96MSu3UlpviBw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] @@ -716,8 +683,8 @@ packages: cpu: [arm64] os: [android] - '@rolldown/binding-android-arm64@1.1.1': - resolution: {integrity: sha512-BLf9Wak/gfwVb7NQTQW4wBgL3oAfPy7ArEkhwV543OVw/uY6B47z5xYsqPSZ9PDOorvURPinws6ThaFuNgGLgA==} + '@rolldown/binding-android-arm64@1.2.3': + resolution: {integrity: sha512-zrJtHDcaZJ1Fp7xf4hNl+7seH9Cn/N5TwLYkhgXREtBwAd/jaqW3uqeHxpDugJLVICWg4eW44kOQEGJ1r6jCGw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] @@ -728,8 +695,8 @@ packages: cpu: [arm64] os: [darwin] - '@rolldown/binding-darwin-arm64@1.1.1': - resolution: {integrity: sha512-rRZRPy/Ynb+Mxu0O6tfPldHeDgAn0sRij+IOUy6sFdUlv3hArGW/DloE3GfAxtqpOJuRNgF74Nr5gM4xBeU2jQ==} + '@rolldown/binding-darwin-arm64@1.2.3': + resolution: {integrity: sha512-ieIiibVCp0tX7TLu2cafoNPv8wJyYi01ekXpbf8q2j7F4rGAhhXb/eQh7ge9DRBY78GwmRQtvjZDux7EDbA8kA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] @@ -740,8 +707,8 @@ packages: cpu: [x64] os: [darwin] - '@rolldown/binding-darwin-x64@1.1.1': - resolution: {integrity: sha512-/MtefPxhKPyWWFM8L45OWiEqRf+eSU2Qv9ZAyTaoZOoGcoPKxbbhjTJO2/U2IThv0uDZ4NWHc3/oTsR6IEOtww==} + '@rolldown/binding-darwin-x64@1.2.3': + resolution: {integrity: sha512-Zh9tCon19eDXJoihx0rqKhMUlMYqzwj3aPsSuHmI4RWZh62dWUL+DJN4C5YQya5TcQBJU/Fe8+rY0jhXTQITqA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] @@ -752,8 +719,8 @@ packages: cpu: [x64] os: [freebsd] - '@rolldown/binding-freebsd-x64@1.1.1': - resolution: {integrity: sha512-202K+cpIi1kx/Zn7AtxBi4LTXSY67Aszb2K9rNsuW7FeBeh0nqoNmYLOSZidV0p88VPBzMmTZcHAdPNo3kRYzQ==} + '@rolldown/binding-freebsd-x64@1.2.3': + resolution: {integrity: sha512-nGbJWewA1wrXXZiQhjAT5rhibGfns5ZNkDVqxsO6zJ3f3YvpoDNNmGMSbbhLuXKjNScaBJVOAboztAWVespQMg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] @@ -764,8 +731,8 @@ packages: cpu: [arm] os: [linux] - '@rolldown/binding-linux-arm-gnueabihf@1.1.1': - resolution: {integrity: sha512-wl9NfeXNUwrXtUc063tddmZFUI6qiNs1CNOwni0OL4vC7MqVSYugra3ZgtDmtVy8e0DluJTENmzIv2BwqLzT4Q==} + '@rolldown/binding-linux-arm-gnueabihf@1.2.3': + resolution: {integrity: sha512-QNniJr5Kml0kDEB98jiDOJjXNroxIIi0IXIbdYzY26Xt1pVbeP62+KnoIZLwirOymX/0jDk/2gI/bNUv7A7OIw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] @@ -777,8 +744,8 @@ packages: os: [linux] libc: [glibc] - '@rolldown/binding-linux-arm64-gnu@1.1.1': - resolution: {integrity: sha512-at2EO4o7D/PJLC4Xik16bU4CcjQE2tSv1LfqMA0TRYQYQihRm3gZeDB8xaX28A9SFedibcAk5DeMCKt4REKG0A==} + '@rolldown/binding-linux-arm64-gnu@1.2.3': + resolution: {integrity: sha512-TkqEAcmmvH3I/q4114NB4RVt6241Dao48pF45uLcFGrwAaIn0iITgTAKP/dLjbN0R4buJjGb91+UHSoFmpgIWw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] @@ -791,8 +758,8 @@ packages: os: [linux] libc: [musl] - '@rolldown/binding-linux-arm64-musl@1.1.1': - resolution: {integrity: sha512-5PUjZx366h9tkJTPJF5eibxOlK3sGoeRiBJLLjjEB5/kLDuhr6qB3LkhqLz1smXNgsX+pBhnbcJBrPE30HznAA==} + '@rolldown/binding-linux-arm64-musl@1.2.3': + resolution: {integrity: sha512-NHqjnxpsndf4MPymxteFAWHHfkTL8HjWh1KB7z23ofZ6QO2euONuxDXjat69dKZRALnGypg8k8SsK8vZJoXv1Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] @@ -805,8 +772,8 @@ packages: os: [linux] libc: [glibc] - '@rolldown/binding-linux-ppc64-gnu@1.1.1': - resolution: {integrity: sha512-1WK84XPeio3tjP1sM/TMXiC0G1i1iq1qGZ71KfNQjEFLU1kwD+Cv5T8nGySg/JUFwLbaScu6ve9DmeXlmqpkFA==} + '@rolldown/binding-linux-ppc64-gnu@1.2.3': + resolution: {integrity: sha512-6tbrbwfz5GB9DQ4Jwo6hy9v+vR31xZlvzZ6n5Xut6Hhx5PvrA9q/HsK8KMaYQp063iqZGXwNvZtYNLD7EM/x0w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] @@ -819,8 +786,8 @@ packages: os: [linux] libc: [glibc] - '@rolldown/binding-linux-s390x-gnu@1.1.1': - resolution: {integrity: sha512-1nS1X5z1uMJ369RU25hTpKCFvUwXZp12dIzlzk4S+UxCTcSVGsAE6tzkOSufv/7jnmAtK0ZlrsJxh2fGmsnVSw==} + '@rolldown/binding-linux-s390x-gnu@1.2.3': + resolution: {integrity: sha512-oyuXxXmoZHjXC917IAPFAAv4wWAa0cM9afk8nx1+9/jNNOX1uPf8yDA6p7G0RypOfw/X0PQt5IfoquY1um+zSg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] @@ -833,8 +800,8 @@ packages: os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-gnu@1.1.1': - resolution: {integrity: sha512-NwX/wspnq4vYyMFsqbYvzums3ki/Tk8FZbMzMAovPDp3OfLeYKby/D+9osokadXuYEV3OvpeHlwnr/bG8QMixA==} + '@rolldown/binding-linux-x64-gnu@1.2.3': + resolution: {integrity: sha512-TytMwF2KVGqP2tgd0I1OY0PAv78dZRAYcF5ssDzjM34SUXCED3uXvSd5+lHoC0bTD6eEdFz7LdQNCO1y0oVk9w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] @@ -847,8 +814,8 @@ packages: os: [linux] libc: [musl] - '@rolldown/binding-linux-x64-musl@1.1.1': - resolution: {integrity: sha512-+n46LhDrJFQM+229y4oXtVpj1G50U/+XuHMlpnisFTEXhrg9f/YIjp/HymX+PVJjBEr7XHRs3CFLelV464pqwA==} + '@rolldown/binding-linux-x64-musl@1.2.3': + resolution: {integrity: sha512-/E9m3qstrJFVPoULV25mVQblSNExY2+kBsYe4sy0Tn0yOOgJ8wZbZt3KnRbF/XeU2Gl1STKUQnDNTqhIE5MD4A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] @@ -860,8 +827,8 @@ packages: cpu: [arm64] os: [openharmony] - '@rolldown/binding-openharmony-arm64@1.1.1': - resolution: {integrity: sha512-qGwEu47zOWYo7LdRHhCWTNhzwGtxXpdY6CERs8QEOqC0PXGGics/e3vHnyEUKt8xK6YkbZXFUCeklrpB6js8ag==} + '@rolldown/binding-openharmony-arm64@1.2.3': + resolution: {integrity: sha512-Kr0OcsoQI816i6HOl3vFHpd1K0eZyh76zgfj4c1nTyaTsd5r2Mj1lwM4R90y/qaCfmTn9eHy0SKwi98eitRxug==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] @@ -871,19 +838,14 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} cpu: [wasm32] - '@rolldown/binding-wasm32-wasi@1.1.1': - resolution: {integrity: sha512-qczfgEH8u0wHGGOXtA7UMAybNKuQjjEXairyQaw4WzjiMztfbgatG1h4OKays/smhtwbWltpKCRGtVhU6h40Sg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [wasm32] - '@rolldown/binding-win32-arm64-msvc@1.0.3': resolution: {integrity: sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@rolldown/binding-win32-arm64-msvc@1.1.1': - resolution: {integrity: sha512-4psXSh63mSbwJF+mB8/9yfUUEzBiHYcUjxa32EO9ZwKy0Ypwjcg4F10D8SvVXgd+isy2UUUjF9HJJnDu1T/4Gg==} + '@rolldown/binding-win32-arm64-msvc@1.2.3': + resolution: {integrity: sha512-hOtMwTqnME+/gJcH/PCZ0wn0zPUjiWOgkHpxbSJpfGKMezHltx1S7/k1SitzVa7Ww2cqrDDaFbZEhcJZO8o+Jw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] @@ -894,8 +856,8 @@ packages: cpu: [x64] os: [win32] - '@rolldown/binding-win32-x64-msvc@1.1.1': - resolution: {integrity: sha512-MUvC/HLXVjzkQkWiExdVTEEWf0py+GfWm8WKSZsekG3ih6a21iy0BHPF07X3JIf3ifoklZXTIaHTLPBgH1C3dw==} + '@rolldown/binding-win32-x64-msvc@1.2.3': + resolution: {integrity: sha512-ekcqMMkI2PlhYnfzQnB/cEdYUVVJViWvoUyLrbzgDoi3Snfc1mVBwdnc306ufA5ejy8JSPjT2RlW1nQSjW7efg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] @@ -918,29 +880,26 @@ packages: '@types/estree@1.0.9': resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} - '@types/jsesc@2.5.1': - resolution: {integrity: sha512-9VN+6yxLOPLOav+7PwjZbxiID2bVaeq0ED4qSQmdQTdjnXJSaCVKTR58t15oqH1H5t8Ng2ZX1SabJVoN9Q34bw==} - '@types/node@12.20.55': resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} - '@types/node@24.13.2': - resolution: {integrity: sha512-fRa09kZTgu8o71KFcDjUFuc7F+dEbZYZmkI0mg5YBTRs0yMKjYHsq/c0urDKeDb+D5qVgXOdFcuu+DZPKOITwA==} + '@types/node@24.13.3': + resolution: {integrity: sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==} - '@vitest/coverage-v8@4.1.9': - resolution: {integrity: sha512-G9/lgqibheLVBDRuya45EbsEXTYcWoSG+TLg7i2axuzx0Eq62eXn+aWXyaVdV5vKvFSWd6ywcX8hA7la9Pvu8g==} + '@vitest/coverage-v8@4.1.10': + resolution: {integrity: sha512-IM49HmthevbgAO4anp1hwtoT9wYe59w0LR00gr+eagHE+ZJ5lK4sLPeO0ubgoJcwLk6dehU3R24N+FbEEKDc8g==} peerDependencies: - '@vitest/browser': 4.1.9 - vitest: 4.1.9 + '@vitest/browser': 4.1.10 + vitest: 4.1.10 peerDependenciesMeta: '@vitest/browser': optional: true - '@vitest/expect@4.1.9': - resolution: {integrity: sha512-vl/rYsUKcBr3SnQn166+XR5ZQcgMx3DQhFWdfli/cWpLnLUmbxZvyrJZotLFUryib+LtArYMSTJ5RbQ57ZqrlA==} + '@vitest/expect@4.1.10': + resolution: {integrity: sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==} - '@vitest/mocker@4.1.9': - resolution: {integrity: sha512-EVkXzBjrPGM+cK8/ANWgBrkUCfJfb38/EfTSO8h7pWvKkyPkpWxvR7BkD2MyItMF62C97zAEoqdpUixwR/e+Rw==} + '@vitest/mocker@4.1.10': + resolution: {integrity: sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==} peerDependencies: msw: ^2.4.9 vite: ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -950,41 +909,164 @@ packages: vite: optional: true - '@vitest/pretty-format@4.1.9': - resolution: {integrity: sha512-s0iufns3iIFitdgm+YR7g1whCAaGtXz459VS9/PqyKDEEFgYIhsHOQmXgIgDuYCt7DeQmiZT0Qe2OA2p4ZPu5A==} + '@vitest/pretty-format@4.1.10': + resolution: {integrity: sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==} - '@vitest/runner@4.1.9': - resolution: {integrity: sha512-KXLMDtc7oe70+3mJfGrPUWPesswH+3sTxAMAMl8DG7I8IUQT4XW718dY5ID3vPUcmlu27CcKfY4P3h3I29SLJg==} + '@vitest/runner@4.1.10': + resolution: {integrity: sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==} - '@vitest/snapshot@4.1.9': - resolution: {integrity: sha512-Jc7RKGNBo8Z28WYIm0Niej4xdSPByRf6mU58VpHQkd6Zh05rlnA+twjbK5HyeIGHxrzsc3mJgS43uM0CZKzaIA==} + '@vitest/snapshot@4.1.10': + resolution: {integrity: sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==} - '@vitest/spy@4.1.9': - resolution: {integrity: sha512-fHpsS6mIi+PiEW+vcRVOMkX1oSaPKne3VOclSFICPcGOmfKgXPU5iAah+wcNcj2xPrCCmfq99IDGf+EojhhvhA==} + '@vitest/spy@4.1.10': + resolution: {integrity: sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==} - '@vitest/utils@4.1.9': - resolution: {integrity: sha512-A51o8ymO5PpqlWNnBP9ZHPXDIpuMtTLlGSjN7la4US+LJzoUMyhwjA5QXlm39JexgwHKW4Xjs8Z2d3dLCXOeuA==} + '@vitest/utils@4.1.10': + resolution: {integrity: sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==} + + '@yuku-codegen/binding-android-arm64@0.8.3': + resolution: {integrity: sha512-/EKnnqwvN7xYoVDhQEIEJTdPDwGW1wkFz/2Eku3ES/IJd4lcQh/OaIDFBmoJKvpe12enrb1TIoYh1fxasGXolA==} + cpu: [arm64] + os: [android] + + '@yuku-codegen/binding-darwin-arm64@0.8.3': + resolution: {integrity: sha512-DFAOliF5YIPv3ayNHGOJhIun6Af4kMaL/YXxf8ZtD1qrOIMFnX/AQBhwfvLalhwmmxuGA8AUteaKRHBvdKZFVA==} + cpu: [arm64] + os: [darwin] + + '@yuku-codegen/binding-darwin-x64@0.8.3': + resolution: {integrity: sha512-WlMh4/oEibaTzE9j5Zq8qnsrH4Ii4kWdcDv/Pj2Rb/MYSrKghtg+bxbWpPe/6zJD21p9zZBApQUxl8ECpZOJuQ==} + cpu: [x64] + os: [darwin] + + '@yuku-codegen/binding-freebsd-x64@0.8.3': + resolution: {integrity: sha512-hoDOpPP0FTxPSD+6w0Gs4p8iL1yXe6jjIXcdzNxyT1KE6B3JI6O0gTIWQISJ+8QyNpNjIwBb7nHCdRavktJM6A==} + cpu: [x64] + os: [freebsd] + + '@yuku-codegen/binding-linux-arm-gnu@0.8.3': + resolution: {integrity: sha512-nNW0GGMJyF04pK4A7Kq7WAYtUWU9uI5ugDAoXl9yHpd3IIZ8UI+zFlM01e+ZGWnQcdxYYLumeRe/EjzZT9bVfQ==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@yuku-codegen/binding-linux-arm-musl@0.8.3': + resolution: {integrity: sha512-/jpxKhO8AV5TmXgT3R2Gv3YctKRUhyDzd5bQw8TiJ3O4z7qerHzoW2kE40fPAO3L434/IZtZbdhr8HuOqiwECA==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@yuku-codegen/binding-linux-arm64-gnu@0.8.3': + resolution: {integrity: sha512-CYhLJfnCknabfLvUjsanxC5s3BBtZHUwfzdDL7GcqShIRQh2qqgG7pPfFrFJ6Jp56kkjKXkfluFGn9nnIv0nZg==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@yuku-codegen/binding-linux-arm64-musl@0.8.3': + resolution: {integrity: sha512-c6gEdnI0MgA7/rVw6CACMciSbAcxVwLyD/jSBbMLWUeqqbysCNGrGPAHdpSaadpz3W1bd+OdXt9XWjfm66708w==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@yuku-codegen/binding-linux-x64-gnu@0.8.3': + resolution: {integrity: sha512-CRVZ9Rw5lIah/PpWeShWv7XiUCMY15N6rZRA2sEZrQvc5Az7Dv9/wsDMa6oBMkfQLXuDkFo4G1QOYyWbebjejg==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@yuku-codegen/binding-linux-x64-musl@0.8.3': + resolution: {integrity: sha512-G12Nhecjmv7OlbCX6Y4HU4wYYePd111kTE+yTjbitnt+P3m8bNegtYG4ZGo4scGTq8cKsLF4xcda1XNzCUA6nQ==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@yuku-codegen/binding-win32-arm64@0.8.3': + resolution: {integrity: sha512-i8bpXWaMlik9DvFl+89emEx3RZFtSd21Vlt0UrnPvUC7h8NGElP2SwQcdcG+pPmihFIYJAoIuJLw7YdQcFcDkA==} + cpu: [arm64] + os: [win32] + + '@yuku-codegen/binding-win32-x64@0.8.3': + resolution: {integrity: sha512-vlYymeTSsx+qxZoNvdl6KehgYDaQC4Sk/9KUnM3V2mriyCwSdhW7lqdpQGl+RLGsDTxyuRGjzGIjgRWk3lohmA==} + cpu: [x64] + os: [win32] + + '@yuku-parser/binding-android-arm64@0.8.3': + resolution: {integrity: sha512-vySYRsMeul9ssvxeHdxgS9ZUIcq7gqljWNqgokjJE0uQWvVvOprihJ6hOsiifVqWsla0BMc3vAFBvNS9QqCw7g==} + cpu: [arm64] + os: [android] + + '@yuku-parser/binding-darwin-arm64@0.8.3': + resolution: {integrity: sha512-+wpB/wqhiZ685Y77I+lj6v9pHSAJ3Y+QMHJmvch0Q0ahIMbNwtKk3s54MhtjCMKO1qpjPbyN/PjuHDg2hbKaVQ==} + cpu: [arm64] + os: [darwin] + + '@yuku-parser/binding-darwin-x64@0.8.3': + resolution: {integrity: sha512-jKqiWejj4zVy7pPtEGu4/Ty+pG1h7ooQOXIkm7shKZTSwTU9X8X+eoH11uIeKHZi2SQWV0GhNz0J56eerseysQ==} + cpu: [x64] + os: [darwin] + + '@yuku-parser/binding-freebsd-x64@0.8.3': + resolution: {integrity: sha512-FC7zSwzFzd4z9bsId07CiHLR+Iw6yW/LzIQhL5AUtPUuVXLgEyx0rilgbRUYkl1CT3GJcLpkh63WuPZUSgCDzw==} + cpu: [x64] + os: [freebsd] + + '@yuku-parser/binding-linux-arm-gnu@0.8.3': + resolution: {integrity: sha512-So61j88b9/ygDnUPlWCm1EUPw4HSxAyDjrNHKgud5N3aRDQ3kw94nW7TriXbo7GBXID9oBHCMNm1r1Fof/Df5Q==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@yuku-parser/binding-linux-arm-musl@0.8.3': + resolution: {integrity: sha512-Nmnn20yJvSSKL8ZdtqReBRSGCDkSMqR5jEk/Sk/cdIdZmqVD49Z6M7w2GbMjdrxMI1MBPbsWFMMWxa93cd5t5g==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@yuku-parser/binding-linux-arm64-gnu@0.8.3': + resolution: {integrity: sha512-Lfgw7AXJ0rxu6BMPGgfc8HLJWEIr8BHhCzcQp/75k+NM90uCLkHlBNqIg/K42KlSvBgAvu9euOvjdswib+4qJA==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@yuku-parser/binding-linux-arm64-musl@0.8.3': + resolution: {integrity: sha512-cfRyu87xsJ0tFkHNsnMC4Rq6+xsFJ6i2dc4VAH52d2qLvykEJU/Mdi3ul1O2PyOApX/LoLT3uQZ0fWs3D5XE4w==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@yuku-parser/binding-linux-x64-gnu@0.8.3': + resolution: {integrity: sha512-GcQQCUuYxbm6P1n+io/A50rvWKDeWHutIp6rW0ycDOZuEQjOb8hDVgS88+NDyOnd9FfS0/Z6GXopcRFDyKpzOg==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@yuku-parser/binding-linux-x64-musl@0.8.3': + resolution: {integrity: sha512-rMkImBGZzg7GZlj8krYtdiezyjYI4igjKWMut5T65jHyNWFigMQrEpn9mDIBflloW9FKhGE3mN6yTZ/N+4HRwg==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@yuku-parser/binding-win32-arm64@0.8.3': + resolution: {integrity: sha512-/2Pl2cAzCXWxah8FqJapEj/ikpt9cEutEZFCa0hnbfrshkn5+C+aBM3ZDq62d1jsgQjBMmqr5HVhJUA4OAG/Tg==} + cpu: [arm64] + os: [win32] + + '@yuku-parser/binding-win32-x64@0.8.3': + resolution: {integrity: sha512-Ntnvjoan9jnfLhn7Kn3h8j/bhsbVdQSVmKUqFULKtmwImLCJVHOJbLL4qbEJyrOQ7r/FBL1/c/dRvx/AQWzzXg==} + cpu: [x64] + os: [win32] + + '@yuku-toolchain/types@0.8.3': + resolution: {integrity: sha512-9LN3HYs3A9qSPVFunsxlbfwBcUgexti3TmhOzIxB/UH8zFuaHQJXTRDcN17DW6cp1GsyZtiZA7f18uIra36Jag==} ansi-colors@4.1.3: resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} engines: {node: '>=6'} - ansi-escapes@7.3.0: - resolution: {integrity: sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==} - engines: {node: '>=18'} - ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} - ansi-regex@6.2.2: - resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} - engines: {node: '>=12'} - - ansi-styles@6.2.3: - resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} - engines: {node: '>=12'} - ansis@4.3.1: resolution: {integrity: sha512-BJ8/l4R5LRE7hW9WdSuGYrLSHi2ynxeFpDFbH0K/CgNeY/tyhk+vO6TYxXC5r5CpUhNVX310xzPsN/H9lCdfOA==} engines: {node: '>=14'} @@ -1003,20 +1085,13 @@ packages: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} - ast-kit@3.0.0: - resolution: {integrity: sha512-8OG92q3R35qjC/4i6BLBMg8IB+fClWu/1PEwg2Z9Rn+BuNaiEgJzpzn+pxWOdHJWDCAwu2JP0wCDTozAM4QirQ==} - engines: {node: ^22.18.0 || >=24.11.0} - - ast-v8-to-istanbul@1.0.4: - resolution: {integrity: sha512-0bC0/4bTSrnwdhU3IsZDwEdojvuPrSg59OYZfKsLRtJZ0u8VBx9DebfqqG8bRdCC0I7vjgxmPi41P0lpkhJHtA==} + ast-v8-to-istanbul@1.0.5: + resolution: {integrity: sha512-UPAgKJFSEGMWSDr3LX4tqnAb4f7KGT8O40Tyx8wbYmmZ/yn58lNCm8h3svs3eXgiGd5AXxz8NDOvXWvicq+rJA==} better-path-resolve@1.0.0: resolution: {integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==} engines: {node: '>=4'} - birpc@4.0.0: - resolution: {integrity: sha512-LShSxJP0KTmd101b6DRyGBj57LZxSDYWKitQNW/mi8GRMvZb078Uf9+pveax1DrVL89vm7mWe+TovdI/UDOuPw==} - braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} @@ -1029,16 +1104,8 @@ packages: resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} engines: {node: '>=18'} - chardet@2.1.1: - resolution: {integrity: sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==} - - cli-cursor@5.0.0: - resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} - engines: {node: '>=18'} - - cli-truncate@5.2.0: - resolution: {integrity: sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==} - engines: {node: '>=20'} + chardet@2.2.0: + resolution: {integrity: sha512-rddelWYNPRrXq6PtNEN2S3f6t9ILzvqaN5pVgi4kqt9jHQaXIial9PznB5iSPVlQSLNaaH22ItWz3EJtQ10+OA==} convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} @@ -1078,9 +1145,6 @@ packages: oxc-resolver: optional: true - emoji-regex@10.6.0: - resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} - empathic@2.0.1: resolution: {integrity: sha512-YGRs8knHhKHVShLkFET/rWAU8kmHbOV5LwN938RHI0pljAJ1Gf6SzXsSmRaEzcXTtOOmVqJ5+WtQPL5uigY50Q==} engines: {node: '>=14'} @@ -1089,12 +1153,8 @@ packages: resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} engines: {node: '>=8.6'} - environment@1.1.0: - resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} - engines: {node: '>=18'} - - es-module-lexer@2.1.0: - resolution: {integrity: sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==} + es-module-lexer@2.3.1: + resolution: {integrity: sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==} esbuild@0.28.1: resolution: {integrity: sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==} @@ -1109,11 +1169,8 @@ packages: estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} - eventemitter3@5.0.4: - resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} - - expect-type@1.3.0: - resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} + expect-type@1.4.0: + resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==} engines: {node: '>=12.0.0'} extendable-error@0.1.7: @@ -1156,10 +1213,6 @@ packages: engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] - get-east-asian-width@1.6.0: - resolution: {integrity: sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==} - engines: {node: '>=18'} - get-tsconfig@5.0.0-beta.5: resolution: {integrity: sha512-/6gFNr0N04nob252sTQxyFLi3eKFRqIg1I87YcqAMT1i6SQrSF6KujUEQrtrjMV0H/eejTCltLdDSTEMzHbnsQ==} engines: {node: '>=20.20.0'} @@ -1194,8 +1247,8 @@ packages: engines: {node: '>=18'} hasBin: true - iconv-lite@0.7.2: - resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} + iconv-lite@0.7.3: + resolution: {integrity: sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==} engines: {node: '>=0.10.0'} ignore@5.3.2: @@ -1210,10 +1263,6 @@ packages: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} - is-fullwidth-code-point@5.1.0: - resolution: {integrity: sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==} - engines: {node: '>=18'} - is-glob@4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} @@ -1251,17 +1300,12 @@ packages: js-tokens@10.0.0: resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==} - js-yaml@3.14.2: - resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==} - hasBin: true - - js-yaml@4.2.0: - resolution: {integrity: sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==} + js-yaml@3.15.1: + resolution: {integrity: sha512-S99WuO3HlhO3XN41EtYUNl9zzXjoJx7QvmipxsJVxtCBT0YHEFy+iOJhjSvrmV12nYhWpZaM8lPHkJm0yUMbag==} hasBin: true - jsesc@3.1.0: - resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} - engines: {node: '>=6'} + js-yaml@4.3.1: + resolution: {integrity: sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==} hasBin: true jsonfile@4.0.0: @@ -1341,15 +1385,11 @@ packages: resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} engines: {node: '>= 12.0.0'} - lint-staged@17.0.7: - resolution: {integrity: sha512-JrSobt+tW3rH8IOMi8tDZd3foorM5yPEkLD/V2NxobgHrFfHWGee4MOLVuZeScgxftEwbHrPHIFA/ZL+nUJeuA==} + lint-staged@17.3.0: + resolution: {integrity: sha512-woZS3vNe3UKqBaLPvbLOtKRY4tLANpWQhom12MGWqC8Mh1lCOO+WgSwmX2amjJAqTY9BkXYW87fCUH5H9Ph6xw==} engines: {node: '>=22.22.1'} hasBin: true - listr2@10.2.1: - resolution: {integrity: sha512-7I5knELsJKTUjXG+A6BkKAiGkW1i25fNa/xlUl9hFtk15WbE9jndA89xu5FzQKrY5llajE1hfZZFMILXkDHk/Q==} - engines: {node: '>=22.13.0'} - locate-path@5.0.0: resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} engines: {node: '>=8'} @@ -1357,19 +1397,15 @@ packages: lodash.startcase@4.4.0: resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==} - log-update@6.1.0: - resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==} - engines: {node: '>=18'} - - lru-cache@11.5.1: - resolution: {integrity: sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==} + lru-cache@11.5.2: + resolution: {integrity: sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==} engines: {node: 20 || >=22} magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} - magicast@0.5.3: - resolution: {integrity: sha512-pVKE4UdSQ7DvHzivsCIFx2BJn1mHG6KsyrFcaxFx6tONdneEuThrDx0Cj3AMg58KyN4pzYT+LHOotxDQDjNvkw==} + magicast@0.5.4: + resolution: {integrity: sha512-llBEhWm1SacoRwgHUoQJYtwp4PBLF4faQi5TCpIGyGs9n4y5+juI0tDgyKIfpqxckRHaHzouUEph3THklWh03w==} make-dir@4.0.0: resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} @@ -1383,10 +1419,6 @@ packages: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} - mimic-function@5.0.1: - resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} - engines: {node: '>=18'} - mri@1.2.0: resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} engines: {node: '>=4'} @@ -1405,19 +1437,15 @@ packages: encoding: optional: true - obug@2.1.3: - resolution: {integrity: sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==} + obug@2.1.4: + resolution: {integrity: sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==} engines: {node: '>=12.20.0'} - onetime@7.0.0: - resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} - engines: {node: '>=18'} - outdent@0.5.0: resolution: {integrity: sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==} - oxfmt@0.55.0: - resolution: {integrity: sha512-jSj2wCTakwgPMxkfiVZX0jf+nX+Nz6xlyAZjqNE0qXTFdCBPYlP6JAN+ODjmealw7DXBjOzYbdsqwBMAZnPZ6A==} + oxfmt@0.62.0: + resolution: {integrity: sha512-vxgGHTmnDU9j4CX7dDBLzxgmHxfda/yPcgJkGCMUSCwRmz+euo/V08xXLNgXTeqAB9Fhf3Pe2nO1RNKLCVgphQ==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -1429,12 +1457,12 @@ packages: vite-plus: optional: true - oxlint@1.70.0: - resolution: {integrity: sha512-D6JgHtzkhRwvEC+A0Nw5AEc5bk8x5i1pHzvZIEf/a0C4hOzmAACNGtkDGPyFaxxX3ZVGxCPeig3P3rMM8XU3/g==} + oxlint@1.77.0: + resolution: {integrity: sha512-qnGh8XJHaQ0dprrDXNQZgS0FgjI6v+V3+X8DwmaV++5Aamy6jGKfDdQ1TUvhUxtmKFAbEf4/WeO5QZX+5WSngg==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: - oxlint-tsgolint: '>=0.22.1' + oxlint-tsgolint: '>=7.0.2001' vite-plus: '*' peerDependenciesMeta: oxlint-tsgolint: @@ -1487,16 +1515,16 @@ packages: resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} engines: {node: '>=8.6'} - picomatch@4.0.4: - resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + picomatch@4.0.5: + resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==} engines: {node: '>=12'} pify@4.0.1: resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} engines: {node: '>=6'} - pkg-pr-new@0.0.75: - resolution: {integrity: sha512-u9mdErTewKSMsr+ceCt8VcNuNP0ro5AXiPXhUVApuEyqr2Zlvt+DdCFBcm+yGWN8mhOdZJ27meIDbnoZgfzpOw==} + pkg-pr-new@0.0.87: + resolution: {integrity: sha512-nm+30Py1csXWfyMH1ueQyTR11IZGHS5oW8Qok/MxMwjPx9g1jX3wMRrJf8TgEvNo0a0M4i14T0zQsEPWdZfAhg==} hasBin: true postcss@8.5.15: @@ -1528,31 +1556,24 @@ packages: resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} - restore-cursor@5.1.0: - resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} - engines: {node: '>=18'} - reusify@1.1.0: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - rfdc@1.4.1: - resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} - - rolldown-plugin-dts@0.26.0: - resolution: {integrity: sha512-e+kEPtUiDES0htk5iqkSeF4EzAV7R+vugGB44iPDuw1Kw9E+WyL1VG7PaV0IIjGHLiacztMBcMTyrr8ON9CT1Q==} + rolldown-plugin-dts@0.27.14: + resolution: {integrity: sha512-ZvuDDwoIpRK9RPxDXratCpklFO9QZZWndf/sd0VBFb4LEj0jj07UcHK9OCh7V4XiFz2Z89ziyBC2K6tJiDjrbw==} engines: {node: ^22.18.0 || >=24.11.0} peerDependencies: - '@ts-macro/tsc': ^0.3.6 - '@typescript/native-preview': '>=7.0.0-dev.20260325.1' + '@typescript/native-preview': '*' + '@volar/typescript': ~2.4.0 rolldown: ^1.0.0 - typescript: ^5.0.0 || ^6.0.0 + typescript: ^5.0.0 || ^6.0.0 || ~7.0.0 vue-tsc: ~3.2.0 || ~3.3.0 peerDependenciesMeta: - '@ts-macro/tsc': - optional: true '@typescript/native-preview': optional: true + '@volar/typescript': + optional: true typescript: optional: true vue-tsc: @@ -1563,8 +1584,8 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} hasBin: true - rolldown@1.1.1: - resolution: {integrity: sha512-IN750c0p+s3jqJIsFLRZrQazmbAB1kkQDTtQjSt/gbS2ywLhlv4R5Shazer0FZKmuo/BsO3/w2UoYnUjuOZqHg==} + rolldown@1.2.3: + resolution: {integrity: sha512-rn9wpmxplLf7NLNyCk9FyWh3FM43DbY8jOzCdEPzH7uflhTftRbCEpqi6Ly2osgoU8OwObtmavMbWLaWy4LX7A==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true @@ -1574,8 +1595,8 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - semver@7.8.4: - resolution: {integrity: sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==} + semver@7.8.5: + resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} engines: {node: '>=10'} hasBin: true @@ -1587,48 +1608,52 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} - sherif-darwin-arm64@1.11.1: - resolution: {integrity: sha512-VoMrUv5QY6hQ2rByNa3AAhr/KGQsCb6pvAUNKa1iCh1jvnY836hQr6zNBw9hYCDkVv6t9sITFGJljwdTCQD4xw==} + sherif-darwin-arm64@1.13.0: + resolution: {integrity: sha512-k38jpGpZIEWS5dpSRLSvVi53LZuGO3hDqB88jgdLMDN11LZM6yTDcP0GytnQ8OpE6Br/Js6bDCLsdWDarZdV9g==} cpu: [arm64] os: [darwin] - sherif-darwin-x64@1.11.1: - resolution: {integrity: sha512-7j3yOCBkvVbltVT3lXoiazGfG2nb36FteYT5VZPEBSf8sTn1pPTScukAQ1Fdl+MphadGyici7XlRbDrtZ/wnvA==} + sherif-darwin-x64@1.13.0: + resolution: {integrity: sha512-W2bOj0Ya9A0fPSWzbeaaNg2RRRr48l2lArN8sGbfc9SZuJSJWXcdWkTVC3sEUiiXwpxNeLY/CsGNyQz5Izbfig==} cpu: [x64] os: [darwin] - sherif-linux-arm64-musl@1.11.1: - resolution: {integrity: sha512-DCf87RFqBh8ZrYgu3y+fv0x4kFn/np84m2jAEgygznwozH/VCfrXbHFVdhxW7762JCYkXbHO9dUj/ff5fkvkvw==} + sherif-linux-arm64-musl@1.13.0: + resolution: {integrity: sha512-9IDAlwYT5Ay2nex9Da3R98cvnPKVveT57i7pJXnnY/DC90qdqqz6Twlp7STRv+7qo4k4ES+2J9zQ0k6SDe888A==} cpu: [arm64] os: [linux] + libc: [musl] - sherif-linux-arm64@1.11.1: - resolution: {integrity: sha512-vCZFS7RxhZ/8g9bdj3UPNVPTcZiKiWigW+FIlVQEUKEKfG0MfSOMBJqEWPVVUniyJa3rdIxtmZKSdWkG0e1x3w==} + sherif-linux-arm64@1.13.0: + resolution: {integrity: sha512-Z94SgKNClWarVJj4LsayOmL7zccCUAvy3ugZnIO860FoJofh812KAyF8CdWrSho8qfmmoAzNcqSSc1OZh7+ckQ==} cpu: [arm64] os: [linux] + libc: [glibc] - sherif-linux-x64-musl@1.11.1: - resolution: {integrity: sha512-f8xitqXdHObUFPZo4QVbz3o30Y4+gHA3B5ZobsOWocnSfJBaUGutBzJsUsjG6w2tccSRn6+mugiMUGKIbIPZmQ==} + sherif-linux-x64-musl@1.13.0: + resolution: {integrity: sha512-2L+A8jF5ylojFwl7eAePR1h3Bzx24IcMgEoWObXgD70k6xF+GOJaM/n+q4MQ9YDntAKbJfMDr6ETjt6Bt/hosA==} cpu: [x64] os: [linux] + libc: [musl] - sherif-linux-x64@1.11.1: - resolution: {integrity: sha512-9t+p1X3SyhU75BrJNHBbj9i/aQxHC/sF+Mdkf17V5AlokCznFgYKQUXq5EVmcmRDDhDl69RMzCTLD95EBqUSYA==} + sherif-linux-x64@1.13.0: + resolution: {integrity: sha512-xGB9iiet8l/i8/YLZje8icpZyt4pVgoxB2+SoC6JCB8iDWsCVSuixebwX2t9yeGqXBXO1kphaW0TLf8Tmwy9JQ==} cpu: [x64] os: [linux] + libc: [glibc] - sherif-windows-arm64@1.11.1: - resolution: {integrity: sha512-Dnffgcyz9zLq/8UTY2REchJzRJWcWAuMWo5Vl5O17IZGkhl71dwa7/Vi2wC3EQd8WAVK/O82yArOYggWA0dj5w==} + sherif-windows-arm64@1.13.0: + resolution: {integrity: sha512-jHzJ/EcG4HuNLf/3AEDzbRkAofLPBHRJKJJMjAosampAe9861atu4q+Z2IZ1/sHg5oVnNgaNg/Xc5DDhGcQ/3w==} cpu: [arm64] os: [win32] - sherif-windows-x64@1.11.1: - resolution: {integrity: sha512-xjfYUL/IQ65DwHkRsWIxiZWtglKtL5/E3UHpnLwOui3jqW1V2K88SMct415dnlBQiL3U9VEIVUo1i+KmToOBgQ==} + sherif-windows-x64@1.13.0: + resolution: {integrity: sha512-KyMqQY62Eb3O+wePlw433mH2XBPVPLXdDjWPneg9pB6jeK0B1sgEzM2yks+aAP5asZ1Y8Q7C7iR7ZtbJKdsAtA==} cpu: [x64] os: [win32] - sherif@1.11.1: - resolution: {integrity: sha512-HBFce8NGaPuWPg5NXb6+aI7hJQFjTilhtbrgo+Y/BvtGlkuJAzLnkmC8nyD+p3v7oIAq4KQeA8qySKGga28xZg==} + sherif@1.13.0: + resolution: {integrity: sha512-Ld2nUOlwW1nmYDA2Q/5o7SC8WcCzVS7XjImmzW4a4z1o8DXJnt+2xYLvI42N5UYlNb/EevPahdC/XxIP6C38TQ==} hasBin: true siginfo@2.0.0: @@ -1642,14 +1667,6 @@ packages: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} - slice-ansi@7.1.2: - resolution: {integrity: sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==} - engines: {node: '>=18'} - - slice-ansi@8.0.0: - resolution: {integrity: sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==} - engines: {node: '>=20'} - source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -1663,29 +1680,17 @@ packages: stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} - std-env@4.1.0: - resolution: {integrity: sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==} + std-env@4.2.0: + resolution: {integrity: sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==} string-argv@0.3.2: resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} engines: {node: '>=0.6.19'} - string-width@7.2.0: - resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} - engines: {node: '>=18'} - - string-width@8.2.1: - resolution: {integrity: sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==} - engines: {node: '>=20'} - strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} - strip-ansi@7.2.0: - resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} - engines: {node: '>=12'} - strip-bom@3.0.0: resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} engines: {node: '>=4'} @@ -1701,8 +1706,8 @@ packages: tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} - tinyexec@1.2.4: - resolution: {integrity: sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==} + tinyexec@1.3.0: + resolution: {integrity: sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==} engines: {node: '>=18'} tinyglobby@0.2.17: @@ -1713,8 +1718,8 @@ packages: resolution: {integrity: sha512-Pugqs6M0m7Lv1I7FtxN4aoyToKg1C4tu+/381vH35y8oENM/Ai7f7C4StcoK4/+BSw9ebcS8jRiVrORFKCALLw==} engines: {node: ^20.0.0 || >=22.0.0} - tinyrainbow@3.1.0: - resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} + tinyrainbow@3.1.1: + resolution: {integrity: sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==} engines: {node: '>=14.0.0'} to-regex-range@5.0.1: @@ -1728,18 +1733,18 @@ packages: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true - tsdown@0.22.3: - resolution: {integrity: sha512-louqbfA8Qf//B9jTTL0FPtXTNpjCWv1VPkbcmQMph2pTpzs+LnB1tbe4tDDRVpo2BjF5SgUXaTZe45SxB8pWHg==} + tsdown@0.22.14: + resolution: {integrity: sha512-ule7Y+fsAN2iZbLDoo7C4KYljFJNJJ+fLshyn+9gozeTspVersWHxwdGB+Dm2hzA38s6muFnUTl0jK3vJm9ifQ==} engines: {node: ^22.18.0 || >=24.11.0} hasBin: true peerDependencies: '@arethetypeswrong/core': ^0.18.1 - '@tsdown/css': 0.22.3 - '@tsdown/exe': 0.22.3 + '@tsdown/css': 0.22.14 + '@tsdown/exe': 0.22.14 '@vitejs/devtools': '*' publint: ^0.3.8 tsx: '*' - typescript: ^5.0.0 || ^6.0.0 + typescript: ^5.0.0 || ^6.0.0 || ^7.0.0 unplugin-unused: ^0.5.0 unrun: '*' peerDependenciesMeta: @@ -1765,8 +1770,8 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - tsx@4.22.4: - resolution: {integrity: sha512-X8EX+XV4QR5xCsrgxaED954zTDfY8KqlDtskKEL0cHhyS/P8b4IFOvGDQpsC9Q1XnLq915wEfwwY/zzskCtmhg==} + tsx@4.23.8: + resolution: {integrity: sha512-8W675THjbzfFmLOQzjDBIBna+WjqMGIxmSZ1mMc1+o9qoVsEuAgQu5j5ueLhau8inOkDu9OslVg0FmfBs1RIHw==} engines: {node: '>=18.0.0'} hasBin: true @@ -1785,6 +1790,10 @@ packages: resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} engines: {node: '>= 4.0.0'} + verkit@0.3.2: + resolution: {integrity: sha512-zj/ob3UsvJGN0whEAKFp53REA5X66hvffVqoCtVQAakJKnKlH+/PcOfMoFwIG/o4rElqLv/ycAFlx8ZlXUorCg==} + engines: {node: '>=18.12.0'} + vite@8.0.16: resolution: {integrity: sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1828,20 +1837,20 @@ packages: yaml: optional: true - vitest@4.1.9: - resolution: {integrity: sha512-nE3/LEyc0z87uHYLZebqCUOaJr2hdtuPp7BQ4BosVFnfltxgAvMG08NyrSGlPpOUWvR27c5flSmYFTNr78L9GQ==} + vitest@4.1.10: + resolution: {integrity: sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==} engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@opentelemetry/api': ^1.9.0 '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 - '@vitest/browser-playwright': 4.1.9 - '@vitest/browser-preview': 4.1.9 - '@vitest/browser-webdriverio': 4.1.9 - '@vitest/coverage-istanbul': 4.1.9 - '@vitest/coverage-v8': 4.1.9 - '@vitest/ui': 4.1.9 + '@vitest/browser-playwright': 4.1.10 + '@vitest/browser-preview': 4.1.10 + '@vitest/browser-webdriverio': 4.1.10 + '@vitest/coverage-istanbul': 4.1.10 + '@vitest/coverage-v8': 4.1.10 + '@vitest/ui': 4.1.10 happy-dom: '*' jsdom: '*' vite: ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -1885,58 +1894,37 @@ packages: engines: {node: '>=8'} hasBin: true - wrap-ansi@10.0.0: - resolution: {integrity: sha512-SGcvg80f0wUy2/fXES19feHMz8E0JoXv2uNgHOu4Dgi2OrCy1lqwFYEJz1BLbDI0exjPMe/ZdzZ/YpGECBG/aQ==} - engines: {node: '>=20'} - - wrap-ansi@9.0.2: - resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} - engines: {node: '>=18'} - yaml@2.9.0: resolution: {integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==} engines: {node: '>= 14.6'} hasBin: true -snapshots: + yuku-ast@0.8.3: + resolution: {integrity: sha512-8x34yU5uhHUnJXzy2Qvjvec/vE9BzS0/2khVT1MsLmSLO/P8Q1Wp8IxHv+IhD+HMYETk6kherOSvP4JPWw2joQ==} - '@babel/generator@8.0.0': - dependencies: - '@babel/parser': 8.0.0 - '@babel/types': 8.0.0 - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - '@types/jsesc': 2.5.1 - jsesc: 3.1.0 - - '@babel/helper-string-parser@7.29.7': {} + yuku-codegen@0.8.3: + resolution: {integrity: sha512-okdo5bb+TfebQa4JOjz9QxeT34D6CcBxu8dxaPUdFEKRdLkp+D2Fah2OanepK+XTyPXdmAJzAo9iXvYvZ/5rmg==} - '@babel/helper-string-parser@8.0.0': {} + yuku-parser@0.8.3: + resolution: {integrity: sha512-KPQcpF9aj77ywlJBIkQWCQ9DObdxnCA8AJdUOmA5CZZx042Xt4+dvbQmPJfWxF3E+KG5dVAZ2fBKuDJ8VsKWgA==} - '@babel/helper-validator-identifier@7.29.7': {} +snapshots: - '@babel/helper-validator-identifier@8.0.0': {} + '@babel/helper-string-parser@7.29.7': {} - '@babel/parser@7.29.7': - dependencies: - '@babel/types': 7.29.7 + '@babel/helper-validator-identifier@7.29.7': {} - '@babel/parser@8.0.0': + '@babel/parser@7.29.8': dependencies: - '@babel/types': 8.0.0 + '@babel/types': 7.29.8 '@babel/runtime@7.29.7': {} - '@babel/types@7.29.7': + '@babel/types@7.29.8': dependencies: '@babel/helper-string-parser': 7.29.7 '@babel/helper-validator-identifier': 7.29.7 - '@babel/types@8.0.0': - dependencies: - '@babel/helper-string-parser': 8.0.0 - '@babel/helper-validator-identifier': 8.0.0 - '@bcoe/v8-coverage@1.0.2': {} '@changesets/apply-release-plan@7.1.1': @@ -1953,7 +1941,7 @@ snapshots: outdent: 0.5.0 prettier: 2.8.8 resolve-from: 5.0.0 - semver: 7.8.4 + semver: 7.8.5 '@changesets/assemble-release-plan@6.0.10': dependencies: @@ -1962,7 +1950,7 @@ snapshots: '@changesets/should-skip-package': 0.1.2 '@changesets/types': 6.1.0 '@manypkg/get-packages': 1.1.3 - semver: 7.8.4 + semver: 7.8.5 '@changesets/changelog-git@0.2.1': dependencies: @@ -1976,7 +1964,7 @@ snapshots: transitivePeerDependencies: - encoding - '@changesets/cli@2.31.0(@types/node@24.13.2)': + '@changesets/cli@2.31.1(@types/node@24.13.3)': dependencies: '@changesets/apply-release-plan': 7.1.1 '@changesets/assemble-release-plan': 6.0.10 @@ -1992,7 +1980,7 @@ snapshots: '@changesets/should-skip-package': 0.1.2 '@changesets/types': 6.1.0 '@changesets/write': 0.4.0 - '@inquirer/external-editor': 1.0.3(@types/node@24.13.2) + '@inquirer/external-editor': 1.0.3(@types/node@24.13.3) '@manypkg/get-packages': 1.1.3 ansi-colors: 4.1.3 enquirer: 2.4.1 @@ -2001,7 +1989,7 @@ snapshots: package-manager-detector: 0.2.11 picocolors: 1.1.1 resolve-from: 5.0.0 - semver: 7.8.4 + semver: 7.8.5 spawndamnit: 3.0.1 term-size: 2.2.1 transitivePeerDependencies: @@ -2027,7 +2015,7 @@ snapshots: '@changesets/types': 6.1.0 '@manypkg/get-packages': 1.1.3 picocolors: 1.1.1 - semver: 7.8.4 + semver: 7.8.5 '@changesets/get-github-info@0.8.0': dependencies: @@ -2062,7 +2050,7 @@ snapshots: '@changesets/parse@0.4.3': dependencies: '@changesets/types': 6.1.0 - js-yaml: 4.2.0 + js-yaml: 4.3.1 '@changesets/pre@2.0.2': dependencies: @@ -2097,9 +2085,9 @@ snapshots: human-id: 4.2.0 prettier: 2.8.8 - '@cloudflare/workers-types@4.20260617.1': {} + '@cloudflare/workers-types@4.20260702.1': {} - '@decimalturn/toml-patch@2.0.0': {} + '@decimalturn/toml-patch@2.1.0': {} '@emnapi/core@1.10.0': dependencies: @@ -2107,32 +2095,16 @@ snapshots: tslib: 2.8.1 optional: true - '@emnapi/core@1.11.0': - dependencies: - '@emnapi/wasi-threads': 1.2.2 - tslib: 2.8.1 - optional: true - '@emnapi/runtime@1.10.0': dependencies: tslib: 2.8.1 optional: true - '@emnapi/runtime@1.11.0': - dependencies: - tslib: 2.8.1 - optional: true - '@emnapi/wasi-threads@1.2.1': dependencies: tslib: 2.8.1 optional: true - '@emnapi/wasi-threads@1.2.2': - dependencies: - tslib: 2.8.1 - optional: true - '@esbuild/aix-ppc64@0.28.1': optional: true @@ -2211,17 +2183,12 @@ snapshots: '@esbuild/win32-x64@0.28.1': optional: true - '@inquirer/external-editor@1.0.3(@types/node@24.13.2)': + '@inquirer/external-editor@1.0.3(@types/node@24.13.3)': dependencies: - chardet: 2.1.1 - iconv-lite: 0.7.2 + chardet: 2.2.0 + iconv-lite: 0.7.3 optionalDependencies: - '@types/node': 24.13.2 - - '@jridgewell/gen-mapping@0.3.13': - dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 - '@jridgewell/trace-mapping': 0.3.31 + '@types/node': 24.13.3 '@jridgewell/resolve-uri@3.1.2': {} @@ -2270,13 +2237,6 @@ snapshots: '@tybys/wasm-util': 0.10.2 optional: true - '@napi-rs/wasm-runtime@1.1.5(@emnapi/core@1.11.0)(@emnapi/runtime@1.11.0)': - dependencies: - '@emnapi/core': 1.11.0 - '@emnapi/runtime': 1.11.0 - '@tybys/wasm-util': 0.10.2 - optional: true - '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -2289,132 +2249,132 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.20.1 - '@openfeature/core@1.11.0': {} + '@openfeature/core@1.12.0': {} - '@openfeature/server-sdk@1.22.0(@openfeature/core@1.11.0)': + '@openfeature/server-sdk@1.23.0(@openfeature/core@1.12.0)': dependencies: - '@openfeature/core': 1.11.0 + '@openfeature/core': 1.12.0 - '@openfeature/web-sdk@1.9.0(@openfeature/core@1.11.0)': + '@openfeature/web-sdk@1.10.0(@openfeature/core@1.12.0)': dependencies: - '@openfeature/core': 1.11.0 + '@openfeature/core': 1.12.0 '@oxc-project/types@0.133.0': {} - '@oxc-project/types@0.135.0': {} + '@oxc-project/types@0.143.0': {} - '@oxfmt/binding-android-arm-eabi@0.55.0': + '@oxfmt/binding-android-arm-eabi@0.62.0': optional: true - '@oxfmt/binding-android-arm64@0.55.0': + '@oxfmt/binding-android-arm64@0.62.0': optional: true - '@oxfmt/binding-darwin-arm64@0.55.0': + '@oxfmt/binding-darwin-arm64@0.62.0': optional: true - '@oxfmt/binding-darwin-x64@0.55.0': + '@oxfmt/binding-darwin-x64@0.62.0': optional: true - '@oxfmt/binding-freebsd-x64@0.55.0': + '@oxfmt/binding-freebsd-x64@0.62.0': optional: true - '@oxfmt/binding-linux-arm-gnueabihf@0.55.0': + '@oxfmt/binding-linux-arm-gnueabihf@0.62.0': optional: true - '@oxfmt/binding-linux-arm-musleabihf@0.55.0': + '@oxfmt/binding-linux-arm-musleabihf@0.62.0': optional: true - '@oxfmt/binding-linux-arm64-gnu@0.55.0': + '@oxfmt/binding-linux-arm64-gnu@0.62.0': optional: true - '@oxfmt/binding-linux-arm64-musl@0.55.0': + '@oxfmt/binding-linux-arm64-musl@0.62.0': optional: true - '@oxfmt/binding-linux-ppc64-gnu@0.55.0': + '@oxfmt/binding-linux-ppc64-gnu@0.62.0': optional: true - '@oxfmt/binding-linux-riscv64-gnu@0.55.0': + '@oxfmt/binding-linux-riscv64-gnu@0.62.0': optional: true - '@oxfmt/binding-linux-riscv64-musl@0.55.0': + '@oxfmt/binding-linux-riscv64-musl@0.62.0': optional: true - '@oxfmt/binding-linux-s390x-gnu@0.55.0': + '@oxfmt/binding-linux-s390x-gnu@0.62.0': optional: true - '@oxfmt/binding-linux-x64-gnu@0.55.0': + '@oxfmt/binding-linux-x64-gnu@0.62.0': optional: true - '@oxfmt/binding-linux-x64-musl@0.55.0': + '@oxfmt/binding-linux-x64-musl@0.62.0': optional: true - '@oxfmt/binding-openharmony-arm64@0.55.0': + '@oxfmt/binding-openharmony-arm64@0.62.0': optional: true - '@oxfmt/binding-win32-arm64-msvc@0.55.0': + '@oxfmt/binding-win32-arm64-msvc@0.62.0': optional: true - '@oxfmt/binding-win32-ia32-msvc@0.55.0': + '@oxfmt/binding-win32-ia32-msvc@0.62.0': optional: true - '@oxfmt/binding-win32-x64-msvc@0.55.0': + '@oxfmt/binding-win32-x64-msvc@0.62.0': optional: true - '@oxlint/binding-android-arm-eabi@1.70.0': + '@oxlint/binding-android-arm-eabi@1.77.0': optional: true - '@oxlint/binding-android-arm64@1.70.0': + '@oxlint/binding-android-arm64@1.77.0': optional: true - '@oxlint/binding-darwin-arm64@1.70.0': + '@oxlint/binding-darwin-arm64@1.77.0': optional: true - '@oxlint/binding-darwin-x64@1.70.0': + '@oxlint/binding-darwin-x64@1.77.0': optional: true - '@oxlint/binding-freebsd-x64@1.70.0': + '@oxlint/binding-freebsd-x64@1.77.0': optional: true - '@oxlint/binding-linux-arm-gnueabihf@1.70.0': + '@oxlint/binding-linux-arm-gnueabihf@1.77.0': optional: true - '@oxlint/binding-linux-arm-musleabihf@1.70.0': + '@oxlint/binding-linux-arm-musleabihf@1.77.0': optional: true - '@oxlint/binding-linux-arm64-gnu@1.70.0': + '@oxlint/binding-linux-arm64-gnu@1.77.0': optional: true - '@oxlint/binding-linux-arm64-musl@1.70.0': + '@oxlint/binding-linux-arm64-musl@1.77.0': optional: true - '@oxlint/binding-linux-ppc64-gnu@1.70.0': + '@oxlint/binding-linux-ppc64-gnu@1.77.0': optional: true - '@oxlint/binding-linux-riscv64-gnu@1.70.0': + '@oxlint/binding-linux-riscv64-gnu@1.77.0': optional: true - '@oxlint/binding-linux-riscv64-musl@1.70.0': + '@oxlint/binding-linux-riscv64-musl@1.77.0': optional: true - '@oxlint/binding-linux-s390x-gnu@1.70.0': + '@oxlint/binding-linux-s390x-gnu@1.77.0': optional: true - '@oxlint/binding-linux-x64-gnu@1.70.0': + '@oxlint/binding-linux-x64-gnu@1.77.0': optional: true - '@oxlint/binding-linux-x64-musl@1.70.0': + '@oxlint/binding-linux-x64-musl@1.77.0': optional: true - '@oxlint/binding-openharmony-arm64@1.70.0': + '@oxlint/binding-openharmony-arm64@1.77.0': optional: true - '@oxlint/binding-win32-arm64-msvc@1.70.0': + '@oxlint/binding-win32-arm64-msvc@1.77.0': optional: true - '@oxlint/binding-win32-ia32-msvc@1.70.0': + '@oxlint/binding-win32-ia32-msvc@1.77.0': optional: true - '@oxlint/binding-win32-x64-msvc@1.70.0': + '@oxlint/binding-win32-x64-msvc@1.77.0': optional: true '@quansync/fs@1.0.0': @@ -2424,73 +2384,73 @@ snapshots: '@rolldown/binding-android-arm64@1.0.3': optional: true - '@rolldown/binding-android-arm64@1.1.1': + '@rolldown/binding-android-arm64@1.2.3': optional: true '@rolldown/binding-darwin-arm64@1.0.3': optional: true - '@rolldown/binding-darwin-arm64@1.1.1': + '@rolldown/binding-darwin-arm64@1.2.3': optional: true '@rolldown/binding-darwin-x64@1.0.3': optional: true - '@rolldown/binding-darwin-x64@1.1.1': + '@rolldown/binding-darwin-x64@1.2.3': optional: true '@rolldown/binding-freebsd-x64@1.0.3': optional: true - '@rolldown/binding-freebsd-x64@1.1.1': + '@rolldown/binding-freebsd-x64@1.2.3': optional: true '@rolldown/binding-linux-arm-gnueabihf@1.0.3': optional: true - '@rolldown/binding-linux-arm-gnueabihf@1.1.1': + '@rolldown/binding-linux-arm-gnueabihf@1.2.3': optional: true '@rolldown/binding-linux-arm64-gnu@1.0.3': optional: true - '@rolldown/binding-linux-arm64-gnu@1.1.1': + '@rolldown/binding-linux-arm64-gnu@1.2.3': optional: true '@rolldown/binding-linux-arm64-musl@1.0.3': optional: true - '@rolldown/binding-linux-arm64-musl@1.1.1': + '@rolldown/binding-linux-arm64-musl@1.2.3': optional: true '@rolldown/binding-linux-ppc64-gnu@1.0.3': optional: true - '@rolldown/binding-linux-ppc64-gnu@1.1.1': + '@rolldown/binding-linux-ppc64-gnu@1.2.3': optional: true '@rolldown/binding-linux-s390x-gnu@1.0.3': optional: true - '@rolldown/binding-linux-s390x-gnu@1.1.1': + '@rolldown/binding-linux-s390x-gnu@1.2.3': optional: true '@rolldown/binding-linux-x64-gnu@1.0.3': optional: true - '@rolldown/binding-linux-x64-gnu@1.1.1': + '@rolldown/binding-linux-x64-gnu@1.2.3': optional: true '@rolldown/binding-linux-x64-musl@1.0.3': optional: true - '@rolldown/binding-linux-x64-musl@1.1.1': + '@rolldown/binding-linux-x64-musl@1.2.3': optional: true '@rolldown/binding-openharmony-arm64@1.0.3': optional: true - '@rolldown/binding-openharmony-arm64@1.1.1': + '@rolldown/binding-openharmony-arm64@1.2.3': optional: true '@rolldown/binding-wasm32-wasi@1.0.3': @@ -2500,23 +2460,16 @@ snapshots: '@napi-rs/wasm-runtime': 1.1.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) optional: true - '@rolldown/binding-wasm32-wasi@1.1.1': - dependencies: - '@emnapi/core': 1.11.0 - '@emnapi/runtime': 1.11.0 - '@napi-rs/wasm-runtime': 1.1.5(@emnapi/core@1.11.0)(@emnapi/runtime@1.11.0) - optional: true - '@rolldown/binding-win32-arm64-msvc@1.0.3': optional: true - '@rolldown/binding-win32-arm64-msvc@1.1.1': + '@rolldown/binding-win32-arm64-msvc@1.2.3': optional: true '@rolldown/binding-win32-x64-msvc@1.0.3': optional: true - '@rolldown/binding-win32-x64-msvc@1.1.1': + '@rolldown/binding-win32-x64-msvc@1.2.3': optional: true '@rolldown/pluginutils@1.0.1': {} @@ -2537,80 +2490,144 @@ snapshots: '@types/estree@1.0.9': {} - '@types/jsesc@2.5.1': {} - '@types/node@12.20.55': {} - '@types/node@24.13.2': + '@types/node@24.13.3': dependencies: undici-types: 7.18.2 - '@vitest/coverage-v8@4.1.9(vitest@4.1.9)': + '@vitest/coverage-v8@4.1.10(vitest@4.1.10)': dependencies: '@bcoe/v8-coverage': 1.0.2 - '@vitest/utils': 4.1.9 - ast-v8-to-istanbul: 1.0.4 + '@vitest/utils': 4.1.10 + ast-v8-to-istanbul: 1.0.5 istanbul-lib-coverage: 3.2.2 istanbul-lib-report: 3.0.1 istanbul-reports: 3.2.0 - magicast: 0.5.3 - obug: 2.1.3 - std-env: 4.1.0 - tinyrainbow: 3.1.0 - vitest: 4.1.9(@types/node@24.13.2)(@vitest/coverage-v8@4.1.9)(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(tsx@4.22.4)(yaml@2.9.0)) + magicast: 0.5.4 + obug: 2.1.4 + std-env: 4.2.0 + tinyrainbow: 3.1.1 + vitest: 4.1.10(@types/node@24.13.3)(@vitest/coverage-v8@4.1.10)(vite@8.0.16(@types/node@24.13.3)(esbuild@0.28.1)(tsx@4.23.8)(yaml@2.9.0)) - '@vitest/expect@4.1.9': + '@vitest/expect@4.1.10': dependencies: '@standard-schema/spec': 1.1.0 '@types/chai': 5.2.3 - '@vitest/spy': 4.1.9 - '@vitest/utils': 4.1.9 + '@vitest/spy': 4.1.10 + '@vitest/utils': 4.1.10 chai: 6.2.2 - tinyrainbow: 3.1.0 + tinyrainbow: 3.1.1 - '@vitest/mocker@4.1.9(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(tsx@4.22.4)(yaml@2.9.0))': + '@vitest/mocker@4.1.10(vite@8.0.16(@types/node@24.13.3)(esbuild@0.28.1)(tsx@4.23.8)(yaml@2.9.0))': dependencies: - '@vitest/spy': 4.1.9 + '@vitest/spy': 4.1.10 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(tsx@4.22.4)(yaml@2.9.0) + vite: 8.0.16(@types/node@24.13.3)(esbuild@0.28.1)(tsx@4.23.8)(yaml@2.9.0) - '@vitest/pretty-format@4.1.9': + '@vitest/pretty-format@4.1.10': dependencies: - tinyrainbow: 3.1.0 + tinyrainbow: 3.1.1 - '@vitest/runner@4.1.9': + '@vitest/runner@4.1.10': dependencies: - '@vitest/utils': 4.1.9 + '@vitest/utils': 4.1.10 pathe: 2.0.3 - '@vitest/snapshot@4.1.9': + '@vitest/snapshot@4.1.10': dependencies: - '@vitest/pretty-format': 4.1.9 - '@vitest/utils': 4.1.9 + '@vitest/pretty-format': 4.1.10 + '@vitest/utils': 4.1.10 magic-string: 0.30.21 pathe: 2.0.3 - '@vitest/spy@4.1.9': {} + '@vitest/spy@4.1.10': {} - '@vitest/utils@4.1.9': + '@vitest/utils@4.1.10': dependencies: - '@vitest/pretty-format': 4.1.9 + '@vitest/pretty-format': 4.1.10 convert-source-map: 2.0.0 - tinyrainbow: 3.1.0 + tinyrainbow: 3.1.1 - ansi-colors@4.1.3: {} + '@yuku-codegen/binding-android-arm64@0.8.3': + optional: true - ansi-escapes@7.3.0: - dependencies: - environment: 1.1.0 + '@yuku-codegen/binding-darwin-arm64@0.8.3': + optional: true - ansi-regex@5.0.1: {} + '@yuku-codegen/binding-darwin-x64@0.8.3': + optional: true + + '@yuku-codegen/binding-freebsd-x64@0.8.3': + optional: true + + '@yuku-codegen/binding-linux-arm-gnu@0.8.3': + optional: true + + '@yuku-codegen/binding-linux-arm-musl@0.8.3': + optional: true + + '@yuku-codegen/binding-linux-arm64-gnu@0.8.3': + optional: true - ansi-regex@6.2.2: {} + '@yuku-codegen/binding-linux-arm64-musl@0.8.3': + optional: true - ansi-styles@6.2.3: {} + '@yuku-codegen/binding-linux-x64-gnu@0.8.3': + optional: true + + '@yuku-codegen/binding-linux-x64-musl@0.8.3': + optional: true + + '@yuku-codegen/binding-win32-arm64@0.8.3': + optional: true + + '@yuku-codegen/binding-win32-x64@0.8.3': + optional: true + + '@yuku-parser/binding-android-arm64@0.8.3': + optional: true + + '@yuku-parser/binding-darwin-arm64@0.8.3': + optional: true + + '@yuku-parser/binding-darwin-x64@0.8.3': + optional: true + + '@yuku-parser/binding-freebsd-x64@0.8.3': + optional: true + + '@yuku-parser/binding-linux-arm-gnu@0.8.3': + optional: true + + '@yuku-parser/binding-linux-arm-musl@0.8.3': + optional: true + + '@yuku-parser/binding-linux-arm64-gnu@0.8.3': + optional: true + + '@yuku-parser/binding-linux-arm64-musl@0.8.3': + optional: true + + '@yuku-parser/binding-linux-x64-gnu@0.8.3': + optional: true + + '@yuku-parser/binding-linux-x64-musl@0.8.3': + optional: true + + '@yuku-parser/binding-win32-arm64@0.8.3': + optional: true + + '@yuku-parser/binding-win32-x64@0.8.3': + optional: true + + '@yuku-toolchain/types@0.8.3': {} + + ansi-colors@4.1.3: {} + + ansi-regex@5.0.1: {} ansis@4.3.1: {} @@ -2624,13 +2641,7 @@ snapshots: assertion-error@2.0.1: {} - ast-kit@3.0.0: - dependencies: - '@babel/parser': 8.0.0 - estree-walker: 3.0.3 - pathe: 2.0.3 - - ast-v8-to-istanbul@1.0.4: + ast-v8-to-istanbul@1.0.5: dependencies: '@jridgewell/trace-mapping': 0.3.31 estree-walker: 3.0.3 @@ -2640,8 +2651,6 @@ snapshots: dependencies: is-windows: 1.0.2 - birpc@4.0.0: {} - braces@3.0.3: dependencies: fill-range: 7.1.1 @@ -2650,16 +2659,7 @@ snapshots: chai@6.2.2: {} - chardet@2.1.1: {} - - cli-cursor@5.0.0: - dependencies: - restore-cursor: 5.1.0 - - cli-truncate@5.2.0: - dependencies: - slice-ansi: 8.0.0 - string-width: 8.2.1 + chardet@2.2.0: {} convert-source-map@2.0.0: {} @@ -2685,8 +2685,6 @@ snapshots: dts-resolver@3.0.0: {} - emoji-regex@10.6.0: {} - empathic@2.0.1: {} enquirer@2.4.1: @@ -2694,9 +2692,7 @@ snapshots: ansi-colors: 4.1.3 strip-ansi: 6.0.1 - environment@1.1.0: {} - - es-module-lexer@2.1.0: {} + es-module-lexer@2.3.1: {} esbuild@0.28.1: optionalDependencies: @@ -2733,9 +2729,7 @@ snapshots: dependencies: '@types/estree': 1.0.9 - eventemitter3@5.0.4: {} - - expect-type@1.3.0: {} + expect-type@1.4.0: {} extendable-error@0.1.7: {} @@ -2751,9 +2745,9 @@ snapshots: dependencies: reusify: 1.1.0 - fdir@6.5.0(picomatch@4.0.4): + fdir@6.5.0(picomatch@4.0.5): optionalDependencies: - picomatch: 4.0.4 + picomatch: 4.0.5 fill-range@7.1.1: dependencies: @@ -2779,8 +2773,6 @@ snapshots: fsevents@2.3.3: optional: true - get-east-asian-width@1.6.0: {} - get-tsconfig@5.0.0-beta.5: dependencies: resolve-pkg-maps: 1.0.0 @@ -2810,7 +2802,7 @@ snapshots: husky@9.1.7: {} - iconv-lite@0.7.2: + iconv-lite@0.7.3: dependencies: safer-buffer: 2.1.2 @@ -2820,10 +2812,6 @@ snapshots: is-extglob@2.1.1: {} - is-fullwidth-code-point@5.1.0: - dependencies: - get-east-asian-width: 1.6.0 - is-glob@4.0.3: dependencies: is-extglob: 2.1.1 @@ -2855,17 +2843,15 @@ snapshots: js-tokens@10.0.0: {} - js-yaml@3.14.2: + js-yaml@3.15.1: dependencies: argparse: 1.0.10 esprima: 4.0.1 - js-yaml@4.2.0: + js-yaml@4.3.1: dependencies: argparse: 2.0.1 - jsesc@3.1.0: {} - jsonfile@4.0.0: optionalDependencies: graceful-fs: 4.2.11 @@ -2919,52 +2905,35 @@ snapshots: lightningcss-win32-arm64-msvc: 1.32.0 lightningcss-win32-x64-msvc: 1.32.0 - lint-staged@17.0.7: + lint-staged@17.3.0: dependencies: - listr2: 10.2.1 - picomatch: 4.0.4 + picomatch: 4.0.5 string-argv: 0.3.2 - tinyexec: 1.2.4 + tinyexec: 1.3.0 optionalDependencies: yaml: 2.9.0 - listr2@10.2.1: - dependencies: - cli-truncate: 5.2.0 - eventemitter3: 5.0.4 - log-update: 6.1.0 - rfdc: 1.4.1 - wrap-ansi: 10.0.0 - locate-path@5.0.0: dependencies: p-locate: 4.1.0 lodash.startcase@4.4.0: {} - log-update@6.1.0: - dependencies: - ansi-escapes: 7.3.0 - cli-cursor: 5.0.0 - slice-ansi: 7.1.2 - strip-ansi: 7.2.0 - wrap-ansi: 9.0.2 - - lru-cache@11.5.1: {} + lru-cache@11.5.2: {} magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 - magicast@0.5.3: + magicast@0.5.4: dependencies: - '@babel/parser': 7.29.7 - '@babel/types': 7.29.7 + '@babel/parser': 7.29.8 + '@babel/types': 7.29.8 source-map-js: 1.2.1 make-dir@4.0.0: dependencies: - semver: 7.8.4 + semver: 7.8.5 merge2@1.4.1: {} @@ -2973,8 +2942,6 @@ snapshots: braces: 3.0.3 picomatch: 2.3.2 - mimic-function@5.0.1: {} - mri@1.2.0: {} nanoid@3.3.12: {} @@ -2983,59 +2950,55 @@ snapshots: dependencies: whatwg-url: 5.0.0 - obug@2.1.3: {} - - onetime@7.0.0: - dependencies: - mimic-function: 5.0.1 + obug@2.1.4: {} outdent@0.5.0: {} - oxfmt@0.55.0: + oxfmt@0.62.0: dependencies: tinypool: 2.1.0 optionalDependencies: - '@oxfmt/binding-android-arm-eabi': 0.55.0 - '@oxfmt/binding-android-arm64': 0.55.0 - '@oxfmt/binding-darwin-arm64': 0.55.0 - '@oxfmt/binding-darwin-x64': 0.55.0 - '@oxfmt/binding-freebsd-x64': 0.55.0 - '@oxfmt/binding-linux-arm-gnueabihf': 0.55.0 - '@oxfmt/binding-linux-arm-musleabihf': 0.55.0 - '@oxfmt/binding-linux-arm64-gnu': 0.55.0 - '@oxfmt/binding-linux-arm64-musl': 0.55.0 - '@oxfmt/binding-linux-ppc64-gnu': 0.55.0 - '@oxfmt/binding-linux-riscv64-gnu': 0.55.0 - '@oxfmt/binding-linux-riscv64-musl': 0.55.0 - '@oxfmt/binding-linux-s390x-gnu': 0.55.0 - '@oxfmt/binding-linux-x64-gnu': 0.55.0 - '@oxfmt/binding-linux-x64-musl': 0.55.0 - '@oxfmt/binding-openharmony-arm64': 0.55.0 - '@oxfmt/binding-win32-arm64-msvc': 0.55.0 - '@oxfmt/binding-win32-ia32-msvc': 0.55.0 - '@oxfmt/binding-win32-x64-msvc': 0.55.0 - - oxlint@1.70.0: + '@oxfmt/binding-android-arm-eabi': 0.62.0 + '@oxfmt/binding-android-arm64': 0.62.0 + '@oxfmt/binding-darwin-arm64': 0.62.0 + '@oxfmt/binding-darwin-x64': 0.62.0 + '@oxfmt/binding-freebsd-x64': 0.62.0 + '@oxfmt/binding-linux-arm-gnueabihf': 0.62.0 + '@oxfmt/binding-linux-arm-musleabihf': 0.62.0 + '@oxfmt/binding-linux-arm64-gnu': 0.62.0 + '@oxfmt/binding-linux-arm64-musl': 0.62.0 + '@oxfmt/binding-linux-ppc64-gnu': 0.62.0 + '@oxfmt/binding-linux-riscv64-gnu': 0.62.0 + '@oxfmt/binding-linux-riscv64-musl': 0.62.0 + '@oxfmt/binding-linux-s390x-gnu': 0.62.0 + '@oxfmt/binding-linux-x64-gnu': 0.62.0 + '@oxfmt/binding-linux-x64-musl': 0.62.0 + '@oxfmt/binding-openharmony-arm64': 0.62.0 + '@oxfmt/binding-win32-arm64-msvc': 0.62.0 + '@oxfmt/binding-win32-ia32-msvc': 0.62.0 + '@oxfmt/binding-win32-x64-msvc': 0.62.0 + + oxlint@1.77.0: optionalDependencies: - '@oxlint/binding-android-arm-eabi': 1.70.0 - '@oxlint/binding-android-arm64': 1.70.0 - '@oxlint/binding-darwin-arm64': 1.70.0 - '@oxlint/binding-darwin-x64': 1.70.0 - '@oxlint/binding-freebsd-x64': 1.70.0 - '@oxlint/binding-linux-arm-gnueabihf': 1.70.0 - '@oxlint/binding-linux-arm-musleabihf': 1.70.0 - '@oxlint/binding-linux-arm64-gnu': 1.70.0 - '@oxlint/binding-linux-arm64-musl': 1.70.0 - '@oxlint/binding-linux-ppc64-gnu': 1.70.0 - '@oxlint/binding-linux-riscv64-gnu': 1.70.0 - '@oxlint/binding-linux-riscv64-musl': 1.70.0 - '@oxlint/binding-linux-s390x-gnu': 1.70.0 - '@oxlint/binding-linux-x64-gnu': 1.70.0 - '@oxlint/binding-linux-x64-musl': 1.70.0 - '@oxlint/binding-openharmony-arm64': 1.70.0 - '@oxlint/binding-win32-arm64-msvc': 1.70.0 - '@oxlint/binding-win32-ia32-msvc': 1.70.0 - '@oxlint/binding-win32-x64-msvc': 1.70.0 + '@oxlint/binding-android-arm-eabi': 1.77.0 + '@oxlint/binding-android-arm64': 1.77.0 + '@oxlint/binding-darwin-arm64': 1.77.0 + '@oxlint/binding-darwin-x64': 1.77.0 + '@oxlint/binding-freebsd-x64': 1.77.0 + '@oxlint/binding-linux-arm-gnueabihf': 1.77.0 + '@oxlint/binding-linux-arm-musleabihf': 1.77.0 + '@oxlint/binding-linux-arm64-gnu': 1.77.0 + '@oxlint/binding-linux-arm64-musl': 1.77.0 + '@oxlint/binding-linux-ppc64-gnu': 1.77.0 + '@oxlint/binding-linux-riscv64-gnu': 1.77.0 + '@oxlint/binding-linux-riscv64-musl': 1.77.0 + '@oxlint/binding-linux-s390x-gnu': 1.77.0 + '@oxlint/binding-linux-x64-gnu': 1.77.0 + '@oxlint/binding-linux-x64-musl': 1.77.0 + '@oxlint/binding-openharmony-arm64': 1.77.0 + '@oxlint/binding-win32-arm64-msvc': 1.77.0 + '@oxlint/binding-win32-ia32-msvc': 1.77.0 + '@oxlint/binding-win32-x64-msvc': 1.77.0 p-filter@2.1.0: dependencies: @@ -3069,11 +3032,11 @@ snapshots: picomatch@2.3.2: {} - picomatch@4.0.4: {} + picomatch@4.0.5: {} pify@4.0.1: {} - pkg-pr-new@0.0.75: {} + pkg-pr-new@0.0.87: {} postcss@8.5.15: dependencies: @@ -3092,7 +3055,7 @@ snapshots: read-yaml-file@1.1.0: dependencies: graceful-fs: 4.2.11 - js-yaml: 3.14.2 + js-yaml: 3.15.1 pify: 4.0.1 strip-bom: 3.0.0 @@ -3100,26 +3063,17 @@ snapshots: resolve-pkg-maps@1.0.0: {} - restore-cursor@5.1.0: - dependencies: - onetime: 7.0.0 - signal-exit: 4.1.0 - reusify@1.1.0: {} - rfdc@1.4.1: {} - - rolldown-plugin-dts@0.26.0(rolldown@1.1.1)(typescript@5.9.3): + rolldown-plugin-dts@0.27.14(rolldown@1.2.3)(typescript@5.9.3): dependencies: - '@babel/generator': 8.0.0 - '@babel/helper-validator-identifier': 8.0.0 - '@babel/parser': 8.0.0 - ast-kit: 3.0.0 - birpc: 4.0.0 dts-resolver: 3.0.0 get-tsconfig: 5.0.0-beta.5 - obug: 2.1.3 - rolldown: 1.1.1 + obug: 2.1.4 + rolldown: 1.2.3 + yuku-ast: 0.8.3 + yuku-codegen: 0.8.3 + yuku-parser: 0.8.3 optionalDependencies: typescript: 5.9.3 transitivePeerDependencies: @@ -3146,26 +3100,25 @@ snapshots: '@rolldown/binding-win32-arm64-msvc': 1.0.3 '@rolldown/binding-win32-x64-msvc': 1.0.3 - rolldown@1.1.1: + rolldown@1.2.3: dependencies: - '@oxc-project/types': 0.135.0 + '@oxc-project/types': 0.143.0 '@rolldown/pluginutils': 1.0.1 optionalDependencies: - '@rolldown/binding-android-arm64': 1.1.1 - '@rolldown/binding-darwin-arm64': 1.1.1 - '@rolldown/binding-darwin-x64': 1.1.1 - '@rolldown/binding-freebsd-x64': 1.1.1 - '@rolldown/binding-linux-arm-gnueabihf': 1.1.1 - '@rolldown/binding-linux-arm64-gnu': 1.1.1 - '@rolldown/binding-linux-arm64-musl': 1.1.1 - '@rolldown/binding-linux-ppc64-gnu': 1.1.1 - '@rolldown/binding-linux-s390x-gnu': 1.1.1 - '@rolldown/binding-linux-x64-gnu': 1.1.1 - '@rolldown/binding-linux-x64-musl': 1.1.1 - '@rolldown/binding-openharmony-arm64': 1.1.1 - '@rolldown/binding-wasm32-wasi': 1.1.1 - '@rolldown/binding-win32-arm64-msvc': 1.1.1 - '@rolldown/binding-win32-x64-msvc': 1.1.1 + '@rolldown/binding-android-arm64': 1.2.3 + '@rolldown/binding-darwin-arm64': 1.2.3 + '@rolldown/binding-darwin-x64': 1.2.3 + '@rolldown/binding-freebsd-x64': 1.2.3 + '@rolldown/binding-linux-arm-gnueabihf': 1.2.3 + '@rolldown/binding-linux-arm64-gnu': 1.2.3 + '@rolldown/binding-linux-arm64-musl': 1.2.3 + '@rolldown/binding-linux-ppc64-gnu': 1.2.3 + '@rolldown/binding-linux-s390x-gnu': 1.2.3 + '@rolldown/binding-linux-x64-gnu': 1.2.3 + '@rolldown/binding-linux-x64-musl': 1.2.3 + '@rolldown/binding-openharmony-arm64': 1.2.3 + '@rolldown/binding-win32-arm64-msvc': 1.2.3 + '@rolldown/binding-win32-x64-msvc': 1.2.3 run-parallel@1.2.0: dependencies: @@ -3173,7 +3126,7 @@ snapshots: safer-buffer@2.1.2: {} - semver@7.8.4: {} + semver@7.8.5: {} shebang-command@2.0.0: dependencies: @@ -3181,40 +3134,40 @@ snapshots: shebang-regex@3.0.0: {} - sherif-darwin-arm64@1.11.1: + sherif-darwin-arm64@1.13.0: optional: true - sherif-darwin-x64@1.11.1: + sherif-darwin-x64@1.13.0: optional: true - sherif-linux-arm64-musl@1.11.1: + sherif-linux-arm64-musl@1.13.0: optional: true - sherif-linux-arm64@1.11.1: + sherif-linux-arm64@1.13.0: optional: true - sherif-linux-x64-musl@1.11.1: + sherif-linux-x64-musl@1.13.0: optional: true - sherif-linux-x64@1.11.1: + sherif-linux-x64@1.13.0: optional: true - sherif-windows-arm64@1.11.1: + sherif-windows-arm64@1.13.0: optional: true - sherif-windows-x64@1.11.1: + sherif-windows-x64@1.13.0: optional: true - sherif@1.11.1: + sherif@1.13.0: optionalDependencies: - sherif-darwin-arm64: 1.11.1 - sherif-darwin-x64: 1.11.1 - sherif-linux-arm64: 1.11.1 - sherif-linux-arm64-musl: 1.11.1 - sherif-linux-x64: 1.11.1 - sherif-linux-x64-musl: 1.11.1 - sherif-windows-arm64: 1.11.1 - sherif-windows-x64: 1.11.1 + sherif-darwin-arm64: 1.13.0 + sherif-darwin-x64: 1.13.0 + sherif-linux-arm64: 1.13.0 + sherif-linux-arm64-musl: 1.13.0 + sherif-linux-x64: 1.13.0 + sherif-linux-x64-musl: 1.13.0 + sherif-windows-arm64: 1.13.0 + sherif-windows-x64: 1.13.0 siginfo@2.0.0: {} @@ -3222,16 +3175,6 @@ snapshots: slash@3.0.0: {} - slice-ansi@7.1.2: - dependencies: - ansi-styles: 6.2.3 - is-fullwidth-code-point: 5.1.0 - - slice-ansi@8.0.0: - dependencies: - ansi-styles: 6.2.3 - is-fullwidth-code-point: 5.1.0 - source-map-js@1.2.1: {} spawndamnit@3.0.1: @@ -3243,29 +3186,14 @@ snapshots: stackback@0.0.2: {} - std-env@4.1.0: {} + std-env@4.2.0: {} string-argv@0.3.2: {} - string-width@7.2.0: - dependencies: - emoji-regex: 10.6.0 - get-east-asian-width: 1.6.0 - strip-ansi: 7.2.0 - - string-width@8.2.1: - dependencies: - get-east-asian-width: 1.6.0 - strip-ansi: 7.2.0 - strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 - strip-ansi@7.2.0: - dependencies: - ansi-regex: 6.2.2 - strip-bom@3.0.0: {} supports-color@7.2.0: @@ -3276,16 +3204,16 @@ snapshots: tinybench@2.9.0: {} - tinyexec@1.2.4: {} + tinyexec@1.3.0: {} tinyglobby@0.2.17: dependencies: - fdir: 6.5.0(picomatch@4.0.4) - picomatch: 4.0.4 + fdir: 6.5.0(picomatch@4.0.5) + picomatch: 4.0.5 tinypool@2.1.0: {} - tinyrainbow@3.1.0: {} + tinyrainbow@3.1.1: {} to-regex-range@5.0.1: dependencies: @@ -3295,7 +3223,7 @@ snapshots: tree-kill@1.2.2: {} - tsdown@0.22.3(tsx@4.22.4)(typescript@5.9.3): + tsdown@0.22.14(tsx@4.23.8)(typescript@5.9.3): dependencies: ansis: 4.3.1 cac: 7.0.0 @@ -3303,28 +3231,28 @@ snapshots: empathic: 2.0.1 hookable: 6.1.1 import-without-cache: 0.4.0 - obug: 2.1.3 - picomatch: 4.0.4 - rolldown: 1.1.1 - rolldown-plugin-dts: 0.26.0(rolldown@1.1.1)(typescript@5.9.3) - semver: 7.8.4 - tinyexec: 1.2.4 + obug: 2.1.4 + picomatch: 4.0.5 + rolldown: 1.2.3 + rolldown-plugin-dts: 0.27.14(rolldown@1.2.3)(typescript@5.9.3) + tinyexec: 1.3.0 tinyglobby: 0.2.17 tree-kill: 1.2.2 unconfig-core: 7.5.0 + verkit: 0.3.2 optionalDependencies: - tsx: 4.22.4 + tsx: 4.23.8 typescript: 5.9.3 transitivePeerDependencies: - - '@ts-macro/tsc' - '@typescript/native-preview' + - '@volar/typescript' - oxc-resolver - vue-tsc tslib@2.8.1: optional: true - tsx@4.22.4: + tsx@4.23.8: dependencies: esbuild: 0.28.1 optionalDependencies: @@ -3341,45 +3269,47 @@ snapshots: universalify@0.1.2: {} - vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(tsx@4.22.4)(yaml@2.9.0): + verkit@0.3.2: {} + + vite@8.0.16(@types/node@24.13.3)(esbuild@0.28.1)(tsx@4.23.8)(yaml@2.9.0): dependencies: lightningcss: 1.32.0 - picomatch: 4.0.4 + picomatch: 4.0.5 postcss: 8.5.15 rolldown: 1.0.3 tinyglobby: 0.2.17 optionalDependencies: - '@types/node': 24.13.2 + '@types/node': 24.13.3 esbuild: 0.28.1 fsevents: 2.3.3 - tsx: 4.22.4 + tsx: 4.23.8 yaml: 2.9.0 - vitest@4.1.9(@types/node@24.13.2)(@vitest/coverage-v8@4.1.9)(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(tsx@4.22.4)(yaml@2.9.0)): - dependencies: - '@vitest/expect': 4.1.9 - '@vitest/mocker': 4.1.9(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(tsx@4.22.4)(yaml@2.9.0)) - '@vitest/pretty-format': 4.1.9 - '@vitest/runner': 4.1.9 - '@vitest/snapshot': 4.1.9 - '@vitest/spy': 4.1.9 - '@vitest/utils': 4.1.9 - es-module-lexer: 2.1.0 - expect-type: 1.3.0 + vitest@4.1.10(@types/node@24.13.3)(@vitest/coverage-v8@4.1.10)(vite@8.0.16(@types/node@24.13.3)(esbuild@0.28.1)(tsx@4.23.8)(yaml@2.9.0)): + dependencies: + '@vitest/expect': 4.1.10 + '@vitest/mocker': 4.1.10(vite@8.0.16(@types/node@24.13.3)(esbuild@0.28.1)(tsx@4.23.8)(yaml@2.9.0)) + '@vitest/pretty-format': 4.1.10 + '@vitest/runner': 4.1.10 + '@vitest/snapshot': 4.1.10 + '@vitest/spy': 4.1.10 + '@vitest/utils': 4.1.10 + es-module-lexer: 2.3.1 + expect-type: 1.4.0 magic-string: 0.30.21 - obug: 2.1.3 + obug: 2.1.4 pathe: 2.0.3 - picomatch: 4.0.4 - std-env: 4.1.0 + picomatch: 4.0.5 + std-env: 4.2.0 tinybench: 2.9.0 - tinyexec: 1.2.4 + tinyexec: 1.3.0 tinyglobby: 0.2.17 - tinyrainbow: 3.1.0 - vite: 8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(tsx@4.22.4)(yaml@2.9.0) + tinyrainbow: 3.1.1 + vite: 8.0.16(@types/node@24.13.3)(esbuild@0.28.1)(tsx@4.23.8)(yaml@2.9.0) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 24.13.2 - '@vitest/coverage-v8': 4.1.9(vitest@4.1.9) + '@types/node': 24.13.3 + '@vitest/coverage-v8': 4.1.10(vitest@4.1.10) transitivePeerDependencies: - msw @@ -3399,16 +3329,43 @@ snapshots: siginfo: 2.0.0 stackback: 0.0.2 - wrap-ansi@10.0.0: - dependencies: - ansi-styles: 6.2.3 - string-width: 8.2.1 - strip-ansi: 7.2.0 + yaml@2.9.0: {} - wrap-ansi@9.0.2: + yuku-ast@0.8.3: dependencies: - ansi-styles: 6.2.3 - string-width: 7.2.0 - strip-ansi: 7.2.0 + '@yuku-toolchain/types': 0.8.3 - yaml@2.9.0: {} + yuku-codegen@0.8.3: + dependencies: + '@yuku-toolchain/types': 0.8.3 + optionalDependencies: + '@yuku-codegen/binding-android-arm64': 0.8.3 + '@yuku-codegen/binding-darwin-arm64': 0.8.3 + '@yuku-codegen/binding-darwin-x64': 0.8.3 + '@yuku-codegen/binding-freebsd-x64': 0.8.3 + '@yuku-codegen/binding-linux-arm-gnu': 0.8.3 + '@yuku-codegen/binding-linux-arm-musl': 0.8.3 + '@yuku-codegen/binding-linux-arm64-gnu': 0.8.3 + '@yuku-codegen/binding-linux-arm64-musl': 0.8.3 + '@yuku-codegen/binding-linux-x64-gnu': 0.8.3 + '@yuku-codegen/binding-linux-x64-musl': 0.8.3 + '@yuku-codegen/binding-win32-arm64': 0.8.3 + '@yuku-codegen/binding-win32-x64': 0.8.3 + + yuku-parser@0.8.3: + dependencies: + '@yuku-toolchain/types': 0.8.3 + yuku-ast: 0.8.3 + optionalDependencies: + '@yuku-parser/binding-android-arm64': 0.8.3 + '@yuku-parser/binding-darwin-arm64': 0.8.3 + '@yuku-parser/binding-darwin-x64': 0.8.3 + '@yuku-parser/binding-freebsd-x64': 0.8.3 + '@yuku-parser/binding-linux-arm-gnu': 0.8.3 + '@yuku-parser/binding-linux-arm-musl': 0.8.3 + '@yuku-parser/binding-linux-arm64-gnu': 0.8.3 + '@yuku-parser/binding-linux-arm64-musl': 0.8.3 + '@yuku-parser/binding-linux-x64-gnu': 0.8.3 + '@yuku-parser/binding-linux-x64-musl': 0.8.3 + '@yuku-parser/binding-win32-arm64': 0.8.3 + '@yuku-parser/binding-win32-x64': 0.8.3 diff --git a/sdks/typescript/package.json b/sdks/typescript/package.json index efde19a..3b6b402 100644 --- a/sdks/typescript/package.json +++ b/sdks/typescript/package.json @@ -87,18 +87,18 @@ } }, "devDependencies": { - "@cloudflare/workers-types": "^4.20260617.1", - "@openfeature/core": "^1.11.0", - "@openfeature/server-sdk": "^1.22.0", - "@openfeature/web-sdk": "^1.9.0", - "@types/node": "^24.13.2", - "@vitest/coverage-v8": "^4.1.9", - "tsdown": "^0.22.3", - "tsx": "^4.22.4", + "@cloudflare/workers-types": "^4.20260702.1", + "@openfeature/core": "^1.12.0", + "@openfeature/server-sdk": "^1.23.0", + "@openfeature/web-sdk": "^1.10.0", + "@types/node": "^24.13.3", + "@vitest/coverage-v8": "^4.1.10", + "tsdown": "^0.22.14", + "tsx": "^4.23.8", "typescript": "^5.9.3", - "vitest": "^4.1.9" + "vitest": "^4.1.10" }, "dependencies": { - "lru-cache": "^11.5.1" + "lru-cache": "^11.5.2" } } From fb6c3991b2cb5d60df279eeaa9a304558b3ba891 Mon Sep 17 00:00:00 2001 From: Akshit Sinha Date: Fri, 7 Aug 2026 12:43:27 +0530 Subject: [PATCH 4/6] ci: publish SDKs only when their source changes --- .changeset/README.md | 2 +- .github/release-sdks.test.ts | 157 +++++++++++++++++++++++++++++ .github/release-sdks.ts | 106 +++++++++++++++++++ .github/workflows/publish-pypi.yml | 30 +++++- .github/workflows/release.yml | 46 +++++++-- AGENTS.md | 12 ++- package.json | 5 +- 7 files changed, 341 insertions(+), 17 deletions(-) create mode 100644 .github/release-sdks.test.ts create mode 100644 .github/release-sdks.ts diff --git a/.changeset/README.md b/.changeset/README.md index 1ce400e..f04677c 100644 --- a/.changeset/README.md +++ b/.changeset/README.md @@ -8,6 +8,6 @@ We have a quick list of common questions to get you started engaging with this p ## Flagship SDK releases -This repo uses Changesets for every SDK language. Release automation opens one SDK version PR and expands any SDK changeset so all SDK packages are versioned together. +This repo uses Changesets for every SDK language. Release automation opens one SDK version PR and expands any SDK changeset so all SDK packages are versioned together. Publishing is independent: npm, PyPI, and Go releases are produced only when publish-relevant source or package configuration changed since that SDK's previous release tag. Use `pnpm changeset` for any published SDK change. You only need to select the SDK package you changed; the release workflow adds the other SDK packages during versioning. diff --git a/.github/release-sdks.test.ts b/.github/release-sdks.test.ts new file mode 100644 index 0000000..d4a01cd --- /dev/null +++ b/.github/release-sdks.test.ts @@ -0,0 +1,157 @@ +import { execFileSync } from 'node:child_process'; +import { mkdtempSync, mkdirSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { dirname, join } from 'node:path'; +import assert from 'node:assert/strict'; +import test from 'node:test'; +import { classifySdkChanges, detectSdkChanges, publishCommands } from './release-sdks.js'; + +test('classifies changes by SDK directory', () => { + assert.deepEqual(classifySdkChanges(['sdks/typescript/src/client.ts', 'sdks/go/client.go', 'sdks/python/README.md']), { + typescript: true, + python: false, + go: true, + }); +}); + +test('ignores tests, examples, and package documentation', () => { + assert.deepEqual( + classifySdkChanges([ + 'sdks/typescript/tests/client.test.ts', + 'sdks/typescript/README.md', + 'sdks/python/tests/test_client.py', + 'sdks/python/LICENSE', + 'sdks/go/client_test.go', + 'sdks/go/examples/basic/main.go', + ]), + { typescript: false, python: false, go: false }, + ); +}); + +test('includes package and build configuration changes but excludes lockfiles', () => { + assert.deepEqual(classifySdkChanges(['sdks/typescript/package.json', 'sdks/python/pyproject.toml', 'sdks/go/go.mod']), { + typescript: true, + python: true, + go: true, + }); + assert.deepEqual(classifySdkChanges(['sdks/python/uv.lock', 'sdks/go/go.sum']), { + typescript: false, + python: false, + go: false, + }); +}); + +test('publishes npm only for TypeScript changes', () => { + assert.deepEqual(publishCommands({ typescript: true, python: false, go: false }), [ + ['changeset', 'publish'], + ['changeset', 'tag'], + ]); + assert.deepEqual(publishCommands({ typescript: false, python: true, go: false }), [['changeset', 'tag']]); + assert.deepEqual(publishCommands({ typescript: false, python: false, go: true }), [['changeset', 'tag']]); + assert.deepEqual(publishCommands({ typescript: false, python: false, go: false }), []); +}); + +test('ignores mechanical SDK version changes in the release commit', () => { + const repo = createRepository(); + write(repo, 'sdks/python/src/client.py', 'changed\n'); + commit(repo, 'change python'); + + for (const sdk of ['typescript', 'python', 'go']) write(repo, `sdks/${sdk}/package.json`, '{"version":"0.2.0"}\n'); + commit(repo, 'version SDKs'); + + assert.deepEqual(detectSdkChanges('HEAD', repo), { typescript: false, python: true, go: false }); +}); + +test('reports no SDK changes after the release is tagged', () => { + const repo = createRepository(); + write(repo, 'sdks/go/client.go', 'changed\n'); + commit(repo, 'change go'); + write(repo, 'sdks/go/package.json', '{"version":"0.2.0"}\n'); + commit(repo, 'version SDKs'); + git(repo, 'tag', '@cloudflare/flagship@0.2.0'); + write(repo, 'README.md', 'docs\n'); + commit(repo, 'update docs'); + + assert.deepEqual(detectSdkChanges('HEAD', repo), { typescript: false, python: false, go: false }); +}); + +test('uses the first parent of a merged release PR', () => { + const repo = createRepository(); + const mainBranch = git(repo, 'branch', '--show-current'); + write(repo, 'sdks/go/client.go', 'changed\n'); + commit(repo, 'change go'); + git(repo, 'checkout', '-b', 'release'); + for (const sdk of ['typescript', 'python', 'go']) write(repo, `sdks/${sdk}/package.json`, '{"version":"0.2.0"}\n'); + commit(repo, 'version SDKs'); + git(repo, 'checkout', mainBranch); + git(repo, 'merge', '--no-ff', 'release', '-m', 'merge release PR'); + + assert.deepEqual(detectSdkChanges('HEAD', repo), { typescript: false, python: false, go: true }); +}); + +test('fails safely when the canonical baseline tag is missing', () => { + const repo = createRepository(); + git(repo, 'tag', '-d', '@cloudflare/flagship@0.1.0'); + write(repo, 'README.md', 'changed\n'); + commit(repo, 'change docs'); + + assert.throws(() => detectSdkChanges('HEAD', repo)); +}); + +test('retains unpublished SDK changes across canonical releases', () => { + const repo = createRepository(); + git(repo, 'tag', 'sdks/go/v0.1.0'); + write(repo, 'sdks/go/client.go', 'changed\n'); + commit(repo, 'change go'); + write(repo, 'sdks/go/package.json', '{"version":"0.2.0"}\n'); + commit(repo, 'version SDKs'); + git(repo, 'tag', '@cloudflare/flagship@0.2.0'); + write(repo, 'README.md', 'next release\n'); + commit(repo, 'prepare next release'); + write(repo, 'sdks/typescript/package.json', '{"version":"0.3.0"}\n'); + commit(repo, 'version SDKs again'); + + assert.deepEqual(detectSdkChanges('HEAD', repo), { typescript: false, python: false, go: true }); +}); + +test('ignores an SDK tag that is not reachable from the release parent', () => { + const repo = createRepository(); + write(repo, 'sdks/python/src/client.py', 'changed\n'); + commit(repo, 'change python'); + write(repo, 'sdks/python/package.json', '{"version":"0.2.0"}\n'); + commit(repo, 'version SDKs'); + git(repo, 'tag', '@cloudflare/flagship@0.2.0'); + git(repo, 'tag', 'sdks/python/v0.2.0'); + + assert.deepEqual(detectSdkChanges('HEAD', repo), { typescript: false, python: true, go: false }); +}); + +function createRepository(): string { + const repo = mkdtempSync(join(tmpdir(), 'flagship-release-')); + git(repo, 'init'); + git(repo, 'config', 'user.email', 'test@example.com'); + git(repo, 'config', 'user.name', 'Test'); + + for (const sdk of ['typescript', 'python', 'go']) { + write(repo, `sdks/${sdk}/package.json`, '{"version":"0.1.0"}\n'); + write(repo, `sdks/${sdk}/src/initial`, 'initial\n'); + } + commit(repo, 'initial release'); + git(repo, 'tag', '@cloudflare/flagship@0.1.0'); + return repo; +} + +function write(repo: string, path: string, content: string): void { + const file = join(repo, path); + mkdirSync(dirname(file), { recursive: true }); + writeFileSync(file, content); +} + +function commit(repo: string, message: string): void { + git(repo, 'add', '.'); + git(repo, 'commit', '-m', message); +} + +function git(repo: string, ...args: string[]): string { + return execFileSync('git', args, { cwd: repo, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }).trim(); +} diff --git a/.github/release-sdks.ts b/.github/release-sdks.ts new file mode 100644 index 0000000..6995833 --- /dev/null +++ b/.github/release-sdks.ts @@ -0,0 +1,106 @@ +import { execFileSync, spawnSync } from 'node:child_process'; +import { appendFileSync } from 'node:fs'; +import { pathToFileURL } from 'node:url'; + +const SDKS = ['typescript', 'python', 'go'] as const; +type Sdk = (typeof SDKS)[number]; +export type SdkChanges = Record; + +export function classifySdkChanges(paths: string[]): SdkChanges { + const relative = (sdk: Sdk): string[] => + paths.filter((path) => path.startsWith(`sdks/${sdk}/`)).map((path) => path.slice(`sdks/${sdk}/`.length)); + const typescript = relative('typescript'); + const python = relative('python'); + const go = relative('go'); + + return { + typescript: typescript.some((path) => path.startsWith('src/') || ['package.json', 'tsconfig.json', 'tsdown.config.ts'].includes(path)), + python: python.some((path) => path.startsWith('src/') || path === 'pyproject.toml'), + go: go.some((path) => (!path.includes('/') && path.endsWith('.go') && !path.endsWith('_test.go')) || path === 'go.mod'), + }; +} + +export function detectSdkChanges(releaseCommit = 'HEAD', cwd = process.cwd()): SdkChanges { + const releaseParent = `${releaseCommit}^1`; + const canonicalTag = describeTag(cwd, '@cloudflare/flagship@*', releaseParent); + const baselines: Record = { + typescript: canonicalTag, + python: findSdkTag(cwd, 'sdks/python/v*', releaseParent) ?? canonicalTag, + go: findSdkTag(cwd, 'sdks/go/v*', releaseParent) ?? canonicalTag, + }; + + return Object.fromEntries( + SDKS.map((sdk) => { + const paths = git(cwd, 'diff', '--name-only', `${baselines[sdk]}..${releaseParent}`).split('\n').filter(Boolean); + return [sdk, classifySdkChanges(paths)[sdk]]; + }), + ) as SdkChanges; +} + +type ChangesetCommand = ['changeset', 'publish' | 'tag']; + +export function publishCommands(changes: SdkChanges): ChangesetCommand[] { + if (changes.typescript) + return [ + ['changeset', 'publish'], + ['changeset', 'tag'], + ]; + if (changes.python || changes.go) return [['changeset', 'tag']]; + return []; +} + +function git(cwd: string, ...args: string[]): string { + return execFileSync('git', args, { cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }).trim(); +} + +function describeTag(cwd: string, pattern: string, commit: string): string { + return git(cwd, 'describe', '--first-parent', '--tags', '--match', pattern, '--exclude', '*-*', '--abbrev=0', commit); +} + +function findSdkTag(cwd: string, pattern: string, commit: string): string | undefined { + try { + return describeTag(cwd, pattern, commit); + } catch { + return undefined; + } +} + +function writeChanges(changes: SdkChanges): void { + const lines = [...SDKS.map((sdk) => `${sdk}=${changes[sdk]}`), `any=${Object.values(changes).some(Boolean)}`]; + const output = process.env.GITHUB_OUTPUT; + + if (output) appendFileSync(output, `${lines.join('\n')}\n`); + console.log(lines.join('\n')); +} + +function publish(): void { + const changes = Object.fromEntries(SDKS.map((sdk) => [sdk, process.env[`${sdk.toUpperCase()}_SDK_CHANGED`] === 'true'])) as SdkChanges; + const commands = publishCommands(changes); + + if (commands.length === 0) { + console.log('No SDK source changes detected; skipping release.'); + return; + } + + if (!changes.typescript) console.log('TypeScript SDK unchanged; creating the canonical release tag without publishing to npm.'); + for (const command of commands) { + const result = spawnSync('pnpm', command, { stdio: 'inherit' }); + if (result.error) throw result.error; + if (result.status !== 0) process.exit(result.status ?? 1); + } +} + +async function main(): Promise { + switch (process.argv[2]) { + case 'detect': + writeChanges(detectSdkChanges(process.env.GITHUB_SHA)); + break; + case 'publish': + publish(); + break; + default: + throw new Error('Expected mode: detect or publish'); + } +} + +if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) await main(); diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml index 5f0884a..3bafb37 100644 --- a/.github/workflows/publish-pypi.yml +++ b/.github/workflows/publish-pypi.yml @@ -15,19 +15,45 @@ jobs: name: pypi url: https://pypi.org/project/cloudflare-flagship/ permissions: - contents: read + contents: write id-token: write steps: - uses: actions/checkout@v6 + with: + fetch-depth: 0 - uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: working-directory: sdks/python + - name: Bootstrap Python release baseline + run: | + if [ -z "$(git tag --list 'sdks/python/v*')" ]; then + BASE=$(git describe --first-parent --tags --match '@cloudflare/flagship@*' --exclude '*-*' --abbrev=0 "${GITHUB_SHA}^1") + TAG="sdks/python/v${BASE##*@}" + git tag "${TAG}" "${BASE}" + git push origin "${TAG}" + fi + - name: Build working-directory: sdks/python run: uv build - name: Publish working-directory: sdks/python - run: uv publish + run: uv publish --check-url https://pypi.org/simple + + - name: Tag Python SDK release + run: | + VERSION=$(uv version --project sdks/python --short) + TAG="sdks/python/v${VERSION}" + if git rev-parse --verify --quiet "refs/tags/${TAG}"; then + test "$(git rev-list -n 1 "${TAG}")" = "${GITHUB_SHA}" || { + echo "${TAG} exists on a different commit." >&2 + exit 1 + } + echo "${TAG} already exists on this release; skipping." + else + git tag "${TAG}" + git push origin "${TAG}" + fi diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1defc2b..68150d7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,7 +4,8 @@ name: Release # - If there are pending changesets, opens/updates a release PR that bumps # versions and rewrites changelogs. # - If the previous commit was a merge of that release PR (no pending -# changesets remain), publishes to npm and triggers PyPI publish. +# changesets remain), publishes only the SDKs changed since their previous +# successful publication. # # PR CI (`pull-request.yml`) is the source of truth for correctness; this # workflow does not re-run checks. Branch protection on `main` requires @@ -30,6 +31,9 @@ jobs: timeout-minutes: 15 outputs: published: ${{ steps.changesets.outputs.published }} + has_changesets: ${{ steps.changesets.outputs.hasChangesets }} + python_changed: ${{ steps.sdk-changes.outputs.python }} + go_changed: ${{ steps.sdk-changes.outputs.go }} permissions: id-token: write contents: write @@ -55,32 +59,60 @@ jobs: working-directory: sdks/python - run: pnpm install --frozen-lockfile + + - name: Detect changed SDKs + id: sdk-changes + run: pnpm tsx .github/release-sdks.ts detect + - run: pnpm run build - id: changesets uses: changesets/action@v1.9.0 with: version: pnpm tsx .github/changeset-version.ts - publish: pnpm changeset publish + publish: pnpm tsx .github/release-sdks.ts publish title: 'chore(release): version SDK packages' commit: 'chore(release): version SDK packages' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} NPM_CONFIG_PROVENANCE: true + TYPESCRIPT_SDK_CHANGED: ${{ steps.sdk-changes.outputs.typescript }} + PYTHON_SDK_CHANGED: ${{ steps.sdk-changes.outputs.python }} + GO_SDK_CHANGED: ${{ steps.sdk-changes.outputs.go }} + + tag-go: + name: Tag Go SDK + needs: release + if: ${{ needs.release.outputs.has_changesets == 'false' && needs.release.outputs.go_changed == 'true' }} + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 - name: Tag Go SDK for pkg.go.dev - if: steps.changesets.outputs.published == 'true' run: | VERSION=$(node -p "require('./sdks/go/package.json').version") - git tag "sdks/go/v${VERSION}" - git push origin "sdks/go/v${VERSION}" + TAG="sdks/go/v${VERSION}" + if git rev-parse --verify --quiet "refs/tags/${TAG}"; then + test "$(git rev-list -n 1 "${TAG}")" = "${GITHUB_SHA}" || { + echo "${TAG} exists on a different commit." >&2 + exit 1 + } + echo "${TAG} already exists on this release; skipping." + else + git tag "${TAG}" + git push origin "${TAG}" + fi publish-pypi: name: Publish PyPI needs: release - if: ${{ needs.release.outputs.published == 'true' }} + if: ${{ needs.release.outputs.has_changesets == 'false' && needs.release.outputs.python_changed == 'true' }} uses: ./.github/workflows/publish-pypi.yml permissions: - contents: read + contents: write id-token: write diff --git a/AGENTS.md b/AGENTS.md index c0775cb..ed676dc 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -206,10 +206,12 @@ The release pipeline runs `.github/changeset-version.ts`, which: 5. Deletes duplicate changelogs generated for private SDK packages. 6. Re-runs `pnpm install` to refresh the lockfile. -After merge the same workflow runs `pnpm changeset publish` and: +After merge the same workflow compares each SDK directory against the parent of the release commit, starting at that SDK's previous publication tag. This excludes mechanical version updates in the release PR and keeps failed native publishes eligible for the next release. It then: -- Publishes public npm SDKs (currently `@cloudflare/flagship`) and creates the canonical `@cloudflare/flagship@*` git tag. -- Skips npm publish and tag creation for `private: true` SDKs (`privatePackages.tag: false`). The Python PyPI workflow subscribes to the canonical `@cloudflare/flagship@*` tag and publishes via PyPI trusted publishing (OIDC, no PyPI token). For Go, the canonical release tag is the version signal — no additional file sync is needed. +- Publishes `@cloudflare/flagship` to npm only when `sdks/typescript/` changed. +- Publishes to PyPI and creates `sdks/python/v*` only when publish-relevant files in `sdks/python/` changed, using trusted publishing (OIDC, no PyPI token). +- Creates `sdks/go/v*` only when publish-relevant files in `sdks/go/` changed. +- Creates the canonical `@cloudflare/flagship@*` tag for every release, even when npm is skipped, so versions and future change detection retain one shared baseline. Every releasable SDK must have a `package.json` so Changesets can discover and version it, even if the actual package is published to PyPI, crates.io, Go modules, or another registry. Non-npm SDK packages should use `private: true` and keep their native manifest beside it: @@ -235,8 +237,8 @@ A `CI Success` aggregator job depends on all of the above and is the single requ Publishing is split across two workflows; `pull-request.yml` is the source of truth for correctness and is never re-run during release: -- `release.yml` runs on pushes to `main`. Changesets opens or updates a release PR that bumps versions; merging that PR triggers the same workflow, which then publishes npm packages and reports `published: true`. -- `publish-pypi.yml` is a reusable workflow (`workflow_call`) invoked by `release.yml` only when `published == 'true'`. It builds the Python SDK and publishes to PyPI via OIDC trusted publishing — never runs on every push. +- `release.yml` runs on pushes to `main`. Changesets opens or updates a release PR that bumps versions; merging that PR triggers the same workflow, which publishes or tags only SDKs whose directories changed. +- `publish-pypi.yml` is a reusable workflow (`workflow_call`) invoked by `release.yml` only for a canonical release that includes Python SDK changes. ## Boundaries diff --git a/package.json b/package.json index 1e07016..76684b0 100644 --- a/package.json +++ b/package.json @@ -21,8 +21,9 @@ "lint": "oxlint .", "typecheck": "tsc --noEmit && pnpm -r run typecheck", "check": "sherif -r root-package-manager-field && oxfmt --check . && oxlint . && pnpm run typecheck", - "test": "pnpm -r run test", - "prepare": "husky" + "test": "pnpm run release:test && pnpm -r run test", + "prepare": "husky", + "release:test": "tsx --test .github/release-sdks.test.ts" }, "devDependencies": { "@changesets/changelog-github": "^0.7.0", From 80c5c32a01b803a412771b7f81e7ccb1f4972051 Mon Sep 17 00:00:00 2001 From: Akshit Sinha Date: Fri, 7 Aug 2026 12:47:31 +0530 Subject: [PATCH 5/6] style(typescript): format binding provider tests --- .../typescript/tests/binding-provider.test.ts | 20 ++++++++----------- .../tests/server-provider-cache.test.ts | 20 ++++++++----------- 2 files changed, 16 insertions(+), 24 deletions(-) diff --git a/sdks/typescript/tests/binding-provider.test.ts b/sdks/typescript/tests/binding-provider.test.ts index 8e1ed51..980d9b8 100644 --- a/sdks/typescript/tests/binding-provider.test.ts +++ b/sdks/typescript/tests/binding-provider.test.ts @@ -16,21 +16,17 @@ function createMockBinding(): FlagshipBinding { getStringValue: vi.fn((_flagKey: string, defaultValue: string) => Promise.resolve(defaultValue)), getNumberValue: vi.fn((_flagKey: string, defaultValue: number) => Promise.resolve(defaultValue)), getObjectValue: vi.fn((_flagKey: string, defaultValue: T) => Promise.resolve(defaultValue)), - getBooleanDetails: vi.fn( - (flagKey: string, defaultValue: boolean): Promise> => - Promise.resolve({ flagKey, value: defaultValue, reason: 'DEFAULT' }), + getBooleanDetails: vi.fn((flagKey: string, defaultValue: boolean): Promise> => + Promise.resolve({ flagKey, value: defaultValue, reason: 'DEFAULT' }), ), - getStringDetails: vi.fn( - (flagKey: string, defaultValue: string): Promise> => - Promise.resolve({ flagKey, value: defaultValue, reason: 'DEFAULT' }), + getStringDetails: vi.fn((flagKey: string, defaultValue: string): Promise> => + Promise.resolve({ flagKey, value: defaultValue, reason: 'DEFAULT' }), ), - getNumberDetails: vi.fn( - (flagKey: string, defaultValue: number): Promise> => - Promise.resolve({ flagKey, value: defaultValue, reason: 'DEFAULT' }), + getNumberDetails: vi.fn((flagKey: string, defaultValue: number): Promise> => + Promise.resolve({ flagKey, value: defaultValue, reason: 'DEFAULT' }), ), - getObjectDetails: vi.fn( - (flagKey: string, defaultValue: T): Promise> => - Promise.resolve({ flagKey, value: defaultValue, reason: 'DEFAULT' }), + getObjectDetails: vi.fn((flagKey: string, defaultValue: T): Promise> => + Promise.resolve({ flagKey, value: defaultValue, reason: 'DEFAULT' }), ), }; } diff --git a/sdks/typescript/tests/server-provider-cache.test.ts b/sdks/typescript/tests/server-provider-cache.test.ts index 6cac94d..90893ab 100644 --- a/sdks/typescript/tests/server-provider-cache.test.ts +++ b/sdks/typescript/tests/server-provider-cache.test.ts @@ -26,21 +26,17 @@ function createMockBinding(): FlagshipBinding { getStringValue: vi.fn((_flagKey: string, defaultValue: string) => Promise.resolve(defaultValue)), getNumberValue: vi.fn((_flagKey: string, defaultValue: number) => Promise.resolve(defaultValue)), getObjectValue: vi.fn((_flagKey: string, defaultValue: T) => Promise.resolve(defaultValue)), - getBooleanDetails: vi.fn( - (flagKey: string): Promise> => - Promise.resolve({ flagKey, value: true, variant: 'on', reason: 'TARGETING_MATCH' }), + getBooleanDetails: vi.fn((flagKey: string): Promise> => + Promise.resolve({ flagKey, value: true, variant: 'on', reason: 'TARGETING_MATCH' }), ), - getStringDetails: vi.fn( - (flagKey: string, defaultValue: string): Promise> => - Promise.resolve({ flagKey, value: defaultValue, reason: 'DEFAULT' }), + getStringDetails: vi.fn((flagKey: string, defaultValue: string): Promise> => + Promise.resolve({ flagKey, value: defaultValue, reason: 'DEFAULT' }), ), - getNumberDetails: vi.fn( - (flagKey: string, defaultValue: number): Promise> => - Promise.resolve({ flagKey, value: defaultValue, reason: 'DEFAULT' }), + getNumberDetails: vi.fn((flagKey: string, defaultValue: number): Promise> => + Promise.resolve({ flagKey, value: defaultValue, reason: 'DEFAULT' }), ), - getObjectDetails: vi.fn( - (flagKey: string, defaultValue: T): Promise> => - Promise.resolve({ flagKey, value: defaultValue, reason: 'DEFAULT' }), + getObjectDetails: vi.fn((flagKey: string, defaultValue: T): Promise> => + Promise.resolve({ flagKey, value: defaultValue, reason: 'DEFAULT' }), ), }; } From e91a1366443da45f578b174118489aabe07cbb49 Mon Sep 17 00:00:00 2001 From: Akshit Sinha Date: Fri, 7 Aug 2026 13:02:04 +0530 Subject: [PATCH 6/6] ci: release SDKs for documentation changes --- .github/release-sdks.test.ts | 21 +++++++++++---------- .github/release-sdks.ts | 15 ++++++++++++--- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/.github/release-sdks.test.ts b/.github/release-sdks.test.ts index d4a01cd..dac31d8 100644 --- a/.github/release-sdks.test.ts +++ b/.github/release-sdks.test.ts @@ -7,27 +7,28 @@ import test from 'node:test'; import { classifySdkChanges, detectSdkChanges, publishCommands } from './release-sdks.js'; test('classifies changes by SDK directory', () => { - assert.deepEqual(classifySdkChanges(['sdks/typescript/src/client.ts', 'sdks/go/client.go', 'sdks/python/README.md']), { + assert.deepEqual(classifySdkChanges(['sdks/typescript/src/client.ts', 'sdks/go/client.go', 'README.md']), { typescript: true, python: false, go: true, }); }); -test('ignores tests, examples, and package documentation', () => { +test('ignores test-only changes', () => { assert.deepEqual( - classifySdkChanges([ - 'sdks/typescript/tests/client.test.ts', - 'sdks/typescript/README.md', - 'sdks/python/tests/test_client.py', - 'sdks/python/LICENSE', - 'sdks/go/client_test.go', - 'sdks/go/examples/basic/main.go', - ]), + classifySdkChanges(['sdks/typescript/tests/client.test.ts', 'sdks/python/tests/test_client.py', 'sdks/go/client_test.go']), { typescript: false, python: false, go: false }, ); }); +test('includes examples, documentation, and licenses', () => { + assert.deepEqual(classifySdkChanges(['sdks/typescript/README.md', 'sdks/python/LICENSE', 'sdks/go/examples/basic/main.go']), { + typescript: true, + python: true, + go: true, + }); +}); + test('includes package and build configuration changes but excludes lockfiles', () => { assert.deepEqual(classifySdkChanges(['sdks/typescript/package.json', 'sdks/python/pyproject.toml', 'sdks/go/go.mod']), { typescript: true, diff --git a/.github/release-sdks.ts b/.github/release-sdks.ts index 6995833..11c7176 100644 --- a/.github/release-sdks.ts +++ b/.github/release-sdks.ts @@ -12,11 +12,20 @@ export function classifySdkChanges(paths: string[]): SdkChanges { const typescript = relative('typescript'); const python = relative('python'); const go = relative('go'); + const isDocumentation = (path: string): boolean => + path.startsWith('examples/') || + path.startsWith('docs/') || + path.endsWith('.md') || + path.slice(path.lastIndexOf('/') + 1).startsWith('LICENSE'); return { - typescript: typescript.some((path) => path.startsWith('src/') || ['package.json', 'tsconfig.json', 'tsdown.config.ts'].includes(path)), - python: python.some((path) => path.startsWith('src/') || path === 'pyproject.toml'), - go: go.some((path) => (!path.includes('/') && path.endsWith('.go') && !path.endsWith('_test.go')) || path === 'go.mod'), + typescript: typescript.some( + (path) => isDocumentation(path) || path.startsWith('src/') || ['package.json', 'tsconfig.json', 'tsdown.config.ts'].includes(path), + ), + python: python.some((path) => isDocumentation(path) || path.startsWith('src/') || path === 'pyproject.toml'), + go: go.some( + (path) => isDocumentation(path) || (!path.includes('/') && path.endsWith('.go') && !path.endsWith('_test.go')) || path === 'go.mod', + ), }; }