diff --git a/.changeset/shaky-eels-shine.md b/.changeset/shaky-eels-shine.md new file mode 100644 index 00000000000..b2120713cae --- /dev/null +++ b/.changeset/shaky-eels-shine.md @@ -0,0 +1,5 @@ +--- +"@thirdweb-dev/service-utils": patch +--- + +Prioritize JWT over service API keys in authentication diff --git a/packages/service-utils/.gitignore b/packages/service-utils/.gitignore new file mode 100644 index 00000000000..ccad391d78a --- /dev/null +++ b/packages/service-utils/.gitignore @@ -0,0 +1,4 @@ +dist/ +coverage/ +.watchmanconfig +*storybook.log \ No newline at end of file diff --git a/packages/service-utils/package.json b/packages/service-utils/package.json index 76bda51e30a..4abb4ec40b2 100644 --- a/packages/service-utils/package.json +++ b/packages/service-utils/package.json @@ -52,6 +52,7 @@ "devDependencies": { "@cloudflare/workers-types": "4.20250421.0", "@types/node": "22.14.1", + "@vitest/coverage-v8": "3.1.2", "typescript": "5.8.3", "vitest": "3.1.2" }, @@ -64,6 +65,7 @@ "build:cjs": "tsc --noCheck --project ./tsconfig.build.json --module commonjs --outDir ./dist/cjs --verbatimModuleSyntax false && printf '{\"type\":\"commonjs\"}' > ./dist/cjs/package.json", "build:esm": "tsc --noCheck --project ./tsconfig.build.json --module es2020 --outDir ./dist/esm && printf '{\"type\": \"module\",\"sideEffects\":false}' > ./dist/esm/package.json", "build:types": "tsc --project ./tsconfig.build.json --module esnext --declarationDir ./dist/types --emitDeclarationOnly --declaration --declarationMap", - "test": "vitest run" + "test": "vitest run", + "coverage": "vitest run --coverage" } } diff --git a/packages/service-utils/src/core/api.ts b/packages/service-utils/src/core/api.ts index 256fccc087e..105a05eac56 100644 --- a/packages/service-utils/src/core/api.ts +++ b/packages/service-utils/src/core/api.ts @@ -1,4 +1,5 @@ import type { AuthorizationInput } from "./authorize/index.js"; +import { getAuthHeaders } from "./get-auth-headers.js"; import type { ServiceName } from "./services.js"; export type UserOpData = { @@ -240,7 +241,7 @@ export async function fetchTeamAndProject( config: CoreServiceConfig, ): Promise { const { apiUrl, serviceApiKey } = config; - const { teamId, clientId, incomingServiceApiKey } = authData; + const { teamId, clientId } = authData; const url = new URL("/v2/keys/use", apiUrl); if (clientId) { @@ -250,6 +251,9 @@ export async function fetchTeamAndProject( url.searchParams.set("teamId", teamId); } + // compute the appropriate auth headers based on the auth data + const authHeaders = getAuthHeaders(authData, serviceApiKey); + const retryCount = config.retryCount ?? 3; let error: unknown | undefined; for (let i = 0; i < retryCount; i++) { @@ -257,13 +261,7 @@ export async function fetchTeamAndProject( const response = await fetch(url, { method: "GET", headers: { - ...(authData.secretKey ? { "x-secret-key": authData.secretKey } : {}), - ...(authData.jwt ? { Authorization: `Bearer ${authData.jwt}` } : {}), - // use the incoming service api key if it exists, otherwise use the service api key - // this is done to ensure that the incoming service API key is VALID in the first place - "x-service-api-key": incomingServiceApiKey - ? incomingServiceApiKey - : serviceApiKey, + ...authHeaders, "content-type": "application/json", }, }); diff --git a/packages/service-utils/src/core/get-auth-headers.test.ts b/packages/service-utils/src/core/get-auth-headers.test.ts new file mode 100644 index 00000000000..df6042f06eb --- /dev/null +++ b/packages/service-utils/src/core/get-auth-headers.test.ts @@ -0,0 +1,114 @@ +import { describe, expect, it } from "vitest"; + +import type { AuthorizationInput } from "./authorize/index.js"; +import { getAuthHeaders } from "./get-auth-headers.js"; + +describe("getAuthHeaders", () => { + const mockServiceApiKey = "test-service-api-key"; + const defaultAuthData: AuthorizationInput = { + incomingServiceApiKey: null, + incomingServiceApiKeyHash: null, + secretKey: null, + clientId: null, + ecosystemId: null, + ecosystemPartnerId: null, + origin: null, + bundleId: null, + secretKeyHash: null, + jwt: null, + hashedJWT: null, + }; + + it("should use secret key when provided", () => { + const authData: AuthorizationInput = { + ...defaultAuthData, + secretKey: "test-secret-key", + }; + + const headers = getAuthHeaders(authData, mockServiceApiKey); + + expect(headers).toEqual({ + "x-secret-key": "test-secret-key", + }); + }); + + it("should use JWT when both JWT and teamId are provided", () => { + const authData: AuthorizationInput = { + ...defaultAuthData, + jwt: "test-jwt", + teamId: "test-team-id", + }; + + const headers = getAuthHeaders(authData, mockServiceApiKey); + + expect(headers).toEqual({ + Authorization: "Bearer test-jwt", + }); + }); + + it("should use JWT when both JWT and clientId are provided", () => { + const authData: AuthorizationInput = { + ...defaultAuthData, + jwt: "test-jwt", + clientId: "test-client-id", + }; + + const headers = getAuthHeaders(authData, mockServiceApiKey); + + expect(headers).toEqual({ + Authorization: "Bearer test-jwt", + }); + }); + + it("should use incoming service api key when provided", () => { + const authData: AuthorizationInput = { + ...defaultAuthData, + incomingServiceApiKey: "test-incoming-service-api-key", + }; + + const headers = getAuthHeaders(authData, mockServiceApiKey); + + expect(headers).toEqual({ + "x-service-api-key": "test-incoming-service-api-key", + }); + }); + + it("should fall back to service api key when no other auth method is provided", () => { + const headers = getAuthHeaders(defaultAuthData, mockServiceApiKey); + + expect(headers).toEqual({ + "x-service-api-key": mockServiceApiKey, + }); + }); + + it("should prioritize secret key over other auth methods", () => { + const authData: AuthorizationInput = { + ...defaultAuthData, + secretKey: "test-secret-key", + jwt: "test-jwt", + teamId: "test-team-id", + incomingServiceApiKey: "test-incoming-service-api-key", + }; + + const headers = getAuthHeaders(authData, mockServiceApiKey); + + expect(headers).toEqual({ + "x-secret-key": "test-secret-key", + }); + }); + + it("should prioritize JWT over incoming service api key when teamId is present", () => { + const authData: AuthorizationInput = { + ...defaultAuthData, + jwt: "test-jwt", + teamId: "test-team-id", + incomingServiceApiKey: "test-incoming-service-api-key", + }; + + const headers = getAuthHeaders(authData, mockServiceApiKey); + + expect(headers).toEqual({ + Authorization: "Bearer test-jwt", + }); + }); +}); diff --git a/packages/service-utils/src/core/get-auth-headers.ts b/packages/service-utils/src/core/get-auth-headers.ts new file mode 100644 index 00000000000..4f0c5e37adc --- /dev/null +++ b/packages/service-utils/src/core/get-auth-headers.ts @@ -0,0 +1,43 @@ +import type { AuthorizationInput } from "./authorize/index.js"; + +/** + * Computes the appropriate auth headers based on the auth data. + * + * @param authData - The auth data to use. + * @param serviceApiKey - The service api key to use. + * @returns The auth headers. + */ +export function getAuthHeaders( + authData: AuthorizationInput, + serviceApiKey: string, +): Record { + const { teamId, clientId, jwt, secretKey, incomingServiceApiKey } = authData; + + switch (true) { + // 1. if we have a secret key, we'll use it + case !!secretKey: + return { + "x-secret-key": secretKey, + } as Record; + + // 2. if we have a JWT AND either a teamId or clientId, we'll use the JWT for auth + case !!(jwt && (teamId || clientId)): + return { + Authorization: `Bearer ${jwt}`, + } as Record; + + // 3. if we have an incoming service api key, we'll use it + case !!incomingServiceApiKey: { + return { + "x-service-api-key": incomingServiceApiKey, + } as Record; + } + + // 4. if nothing else is present, we'll use the service api key + default: { + return { + "x-service-api-key": serviceApiKey, + }; + } + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0ae5c5440a3..28b449a0203 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1069,6 +1069,9 @@ importers: '@types/node': specifier: 22.14.1 version: 22.14.1 + '@vitest/coverage-v8': + specifier: 3.1.2 + version: 3.1.2(vitest@3.1.2(@types/debug@4.1.12)(@types/node@22.14.1)(@vitest/ui@3.1.2)(happy-dom@17.4.4)(jiti@2.4.2)(lightningcss@1.29.3)(msw@2.7.5(@types/node@22.14.1)(typescript@5.8.3))(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1)) typescript: specifier: 5.8.3 version: 5.8.3 @@ -1324,7 +1327,7 @@ importers: dependencies: '@noble/ciphers': specifier: ^1.2.1 - version: 1.2.1 + version: 1.3.0 '@noble/curves': specifier: 1.8.2 version: 1.8.2 @@ -8972,10 +8975,6 @@ packages: engines: {node: '>=0.10'} hasBin: true - detect-libc@2.0.3: - resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} - engines: {node: '>=8'} - detect-libc@2.0.4: resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==} engines: {node: '>=8'} @@ -12227,11 +12226,6 @@ packages: engines: {node: ^18 || >=20} hasBin: true - nanoid@5.1.2: - resolution: {integrity: sha512-b+CiXQCNMUGe0Ri64S9SXFcP9hogjAJ2Rd6GdVxhPLRm7mhGaM7VgOvCAJ1ZshfHbqVDI3uqTI5C8/GaKuLI7g==} - engines: {node: ^18 || >=20} - hasBin: true - nanoid@5.1.5: resolution: {integrity: sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==} engines: {node: ^18 || >=20} @@ -15567,46 +15561,6 @@ packages: engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true - vite@6.3.2: - resolution: {integrity: sha512-ZSvGOXKGceizRQIZSz7TGJ0pS3QLlVY/9hwxVh17W3re67je1RKYzFHivZ/t0tubU78Vkyb9WnHPENSBCzbckg==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} - hasBin: true - peerDependencies: - '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 - jiti: '>=1.21.0' - less: '*' - lightningcss: ^1.21.0 - sass: '*' - sass-embedded: '*' - stylus: '*' - sugarss: '*' - terser: ^5.16.0 - tsx: ^4.8.1 - yaml: ^2.4.2 - peerDependenciesMeta: - '@types/node': - optional: true - jiti: - optional: true - less: - optional: true - lightningcss: - optional: true - sass: - optional: true - sass-embedded: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - tsx: - optional: true - yaml: - optional: true - vite@6.3.4: resolution: {integrity: sha512-BiReIiMS2fyFqbqNT/Qqt4CVITDU9M9vE+DKcVAsB+ZV0wvTKd+3hMbkpxz1b+NmEDMegpVbisKiAZOnvO92Sw==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} @@ -18883,7 +18837,7 @@ snapshots: dependencies: '@ethersproject/bytes': 5.8.0 '@ethersproject/logger': 5.8.0 - bn.js: 5.2.1 + bn.js: 5.2.2 '@ethersproject/bytes@5.8.0': dependencies: @@ -19016,7 +18970,7 @@ snapshots: '@ethersproject/bytes': 5.8.0 '@ethersproject/logger': 5.8.0 '@ethersproject/properties': 5.8.0 - bn.js: 5.2.1 + bn.js: 5.2.2 elliptic: 6.6.1 hash.js: 1.1.7 @@ -19978,7 +19932,7 @@ snapshots: '@mapbox/node-pre-gyp@1.0.11(encoding@0.1.13)': dependencies: - detect-libc: 2.0.3 + detect-libc: 2.0.4 https-proxy-agent: 5.0.1 make-dir: 3.1.0 node-fetch: 2.7.0(encoding@0.1.13) @@ -20232,8 +20186,8 @@ snapshots: '@mobile-wallet-protocol/client@1.0.0(dgnjcbusqvetbc3a6onowh5cfm)': dependencies: '@noble/ciphers': 0.5.3 - '@noble/curves': 1.8.1 - '@noble/hashes': 1.7.1 + '@noble/curves': 1.8.2 + '@noble/hashes': 1.7.2 '@react-native-async-storage/async-storage': 2.1.2(react-native@0.78.1(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@19.1.2)(bufferutil@4.0.9)(react@19.1.0)(utf-8-validate@5.0.10)) eventemitter3: 5.0.1 expo: 52.0.46(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(bufferutil@4.0.9)(encoding@0.1.13)(graphql@16.10.0)(react-native@0.78.1(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@19.1.2)(bufferutil@4.0.9)(react@19.1.0)(utf-8-validate@5.0.10))(react@19.1.0)(utf-8-validate@5.0.10) @@ -22820,7 +22774,7 @@ snapshots: '@size-limit/webpack@11.2.0(size-limit@11.2.0)': dependencies: - nanoid: 5.1.2 + nanoid: 5.1.5 size-limit: 11.2.0 webpack: 5.99.6 transitivePeerDependencies: @@ -24959,14 +24913,14 @@ snapshots: chai: 5.2.0 tinyrainbow: 2.0.0 - '@vitest/mocker@3.1.2(msw@2.7.5(@types/node@22.14.1)(typescript@5.8.3))(vite@6.3.2(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1))': + '@vitest/mocker@3.1.2(msw@2.7.5(@types/node@22.14.1)(typescript@5.8.3))(vite@6.3.4(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1))': dependencies: '@vitest/spy': 3.1.2 estree-walker: 3.0.3 magic-string: 0.30.17 optionalDependencies: msw: 2.7.5(@types/node@22.14.1)(typescript@5.8.3) - vite: 6.3.2(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1) + vite: 6.3.4(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1) '@vitest/pretty-format@2.0.5': dependencies: @@ -26562,13 +26516,13 @@ snapshots: browserify-rsa@4.1.1: dependencies: - bn.js: 5.2.1 + bn.js: 5.2.2 randombytes: 2.1.0 safe-buffer: 5.2.1 browserify-sign@4.2.3: dependencies: - bn.js: 5.2.1 + bn.js: 5.2.2 browserify-rsa: 4.1.1 create-hash: 1.2.0 create-hmac: 1.1.7 @@ -27547,10 +27501,7 @@ snapshots: detect-libc@1.0.3: {} - detect-libc@2.0.3: {} - - detect-libc@2.0.4: - optional: true + detect-libc@2.0.4: {} detect-node-es@1.1.0: {} @@ -28026,8 +27977,8 @@ snapshots: '@typescript-eslint/parser': 7.14.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3) eslint: 9.24.0(jiti@2.4.2) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.14.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.24.0(jiti@2.4.2)))(eslint@9.24.0(jiti@2.4.2)) - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.14.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.14.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.24.0(jiti@2.4.2)))(eslint@9.24.0(jiti@2.4.2)))(eslint@9.24.0(jiti@2.4.2)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0)(eslint@9.24.0(jiti@2.4.2)) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.14.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.24.0(jiti@2.4.2)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.24.0(jiti@2.4.2)) eslint-plugin-react: 7.37.5(eslint@9.24.0(jiti@2.4.2)) eslint-plugin-react-hooks: 5.2.0(eslint@9.24.0(jiti@2.4.2)) @@ -28046,33 +27997,33 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.14.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.24.0(jiti@2.4.2)))(eslint@9.24.0(jiti@2.4.2)): + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0)(eslint@8.57.0): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.0(supports-color@8.1.1) - eslint: 9.24.0(jiti@2.4.2) + eslint: 8.57.0 get-tsconfig: 4.10.0 is-bun-module: 2.0.0 stable-hash: 0.0.5 tinyglobby: 0.2.13 unrs-resolver: 1.6.3 optionalDependencies: - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.14.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.14.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.24.0(jiti@2.4.2)))(eslint@9.24.0(jiti@2.4.2)))(eslint@9.24.0(jiti@2.4.2)) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.0) transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0)(eslint@8.57.0): + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0)(eslint@9.24.0(jiti@2.4.2)): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.0(supports-color@8.1.1) - eslint: 8.57.0 + eslint: 9.24.0(jiti@2.4.2) get-tsconfig: 4.10.0 is-bun-module: 2.0.0 stable-hash: 0.0.5 tinyglobby: 0.2.13 unrs-resolver: 1.6.3 optionalDependencies: - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.0) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.14.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.24.0(jiti@2.4.2)) transitivePeerDependencies: - supports-color @@ -28108,14 +28059,14 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@7.14.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.14.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.24.0(jiti@2.4.2)))(eslint@9.24.0(jiti@2.4.2)))(eslint@9.24.0(jiti@2.4.2)): + eslint-module-utils@2.12.0(@typescript-eslint/parser@7.14.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0)(eslint@9.24.0(jiti@2.4.2)))(eslint@9.24.0(jiti@2.4.2)): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 7.14.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3) eslint: 9.24.0(jiti@2.4.2) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.14.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.24.0(jiti@2.4.2)))(eslint@9.24.0(jiti@2.4.2)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0)(eslint@9.24.0(jiti@2.4.2)) transitivePeerDependencies: - supports-color @@ -28148,7 +28099,7 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.14.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.14.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.24.0(jiti@2.4.2)))(eslint@9.24.0(jiti@2.4.2)))(eslint@9.24.0(jiti@2.4.2)): + eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.14.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.24.0(jiti@2.4.2)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.8 @@ -28159,7 +28110,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.24.0(jiti@2.4.2) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.14.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.14.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.24.0(jiti@2.4.2)))(eslint@9.24.0(jiti@2.4.2)))(eslint@9.24.0(jiti@2.4.2)) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.14.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0)(eslint@9.24.0(jiti@2.4.2)))(eslint@9.24.0(jiti@2.4.2)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -32094,8 +32045,6 @@ snapshots: nanoid@5.0.7: {} - nanoid@5.1.2: {} - nanoid@5.1.5: {} nanospinner@1.2.2: @@ -34379,7 +34328,7 @@ snapshots: sharp@0.33.5: dependencies: color: 4.2.3 - detect-libc: 2.0.3 + detect-libc: 2.0.4 semver: 7.7.1 optionalDependencies: '@img/sharp-darwin-arm64': 0.33.5 @@ -34405,7 +34354,7 @@ snapshots: sharp@0.34.1: dependencies: color: 4.2.3 - detect-libc: 2.0.3 + detect-libc: 2.0.4 semver: 7.7.1 optionalDependencies: '@img/sharp-darwin-arm64': 0.34.1 @@ -35980,23 +35929,6 @@ snapshots: - tsx - yaml - vite@6.3.2(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1): - dependencies: - esbuild: 0.25.2 - fdir: 6.4.4(picomatch@4.0.2) - picomatch: 4.0.2 - postcss: 8.5.3 - rollup: 4.40.0 - tinyglobby: 0.2.13 - optionalDependencies: - '@types/node': 22.14.1 - fsevents: 2.3.3 - jiti: 2.4.2 - lightningcss: 1.29.3 - terser: 5.39.0 - tsx: 4.19.3 - yaml: 2.7.1 - vite@6.3.4(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1): dependencies: esbuild: 0.25.2 @@ -36017,7 +35949,7 @@ snapshots: vitest@3.1.2(@types/debug@4.1.12)(@types/node@22.14.1)(@vitest/ui@3.1.2)(happy-dom@17.4.4)(jiti@2.4.2)(lightningcss@1.29.3)(msw@2.7.5(@types/node@22.14.1)(typescript@5.8.3))(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1): dependencies: '@vitest/expect': 3.1.2 - '@vitest/mocker': 3.1.2(msw@2.7.5(@types/node@22.14.1)(typescript@5.8.3))(vite@6.3.2(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1)) + '@vitest/mocker': 3.1.2(msw@2.7.5(@types/node@22.14.1)(typescript@5.8.3))(vite@6.3.4(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1)) '@vitest/pretty-format': 3.1.2 '@vitest/runner': 3.1.2 '@vitest/snapshot': 3.1.2 @@ -36034,7 +35966,7 @@ snapshots: tinyglobby: 0.2.13 tinypool: 1.0.2 tinyrainbow: 2.0.0 - vite: 6.3.2(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1) + vite: 6.3.4(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1) vite-node: 3.1.2(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1) why-is-node-running: 2.3.0 optionalDependencies: