diff --git a/backend/src/dal/leaderboards.ts b/backend/src/dal/leaderboards.ts index f2a48658f6af..2bc20a4329ce 100644 --- a/backend/src/dal/leaderboards.ts +++ b/backend/src/dal/leaderboards.ts @@ -115,15 +115,16 @@ export async function getCount( cachedCounts.set(key, count); return count; } else { - return ( - await aggregateWithAcceptedConnections( - { - collectionName: getCollectionName({ language, mode, mode2 }), - uid, - }, - [{ $project: { _id: true } }], - ) - ).length; + const result = await aggregateWithAcceptedConnections<{ + total: number; + }>( + { + collectionName: getCollectionName({ language, mode, mode2 }), + uid, + }, + [{ $count: "total" }], + ); + return result[0]?.total ?? 0; } } } diff --git a/backend/src/dal/logs.ts b/backend/src/dal/logs.ts index 9680ab9c761d..a7fb5641438a 100644 --- a/backend/src/dal/logs.ts +++ b/backend/src/dal/logs.ts @@ -56,7 +56,6 @@ export async function addImportantLog( message: string | Record, uid = "", ): Promise { - console.log("log", event, message, uid); await insertIntoDb(event, message, uid, true); } diff --git a/backend/src/dal/user.ts b/backend/src/dal/user.ts index 8349bf8bb674..ada92f0ee764 100644 --- a/backend/src/dal/user.ts +++ b/backend/src/dal/user.ts @@ -493,17 +493,14 @@ export async function checkIfPb( if (!pb.isPb) return false; - await getUsersCollection().updateOne( - { uid }, - { $set: { personalBests: pb.personalBests } }, - ); - + const setFields: Record = { + personalBests: pb.personalBests, + }; if (pb.lbPersonalBests) { - await getUsersCollection().updateOne( - { uid }, - { $set: { lbPersonalBests: pb.lbPersonalBests } }, - ); + setFields["lbPersonalBests"] = pb.lbPersonalBests; } + + await getUsersCollection().updateOne({ uid }, { $set: setFields }); return true; } diff --git a/frontend/__tests__/controllers/preset-controller.spec.ts b/frontend/__tests__/controllers/preset-controller.spec.ts index ed1ed72e3ab4..ad250c5f494e 100644 --- a/frontend/__tests__/controllers/preset-controller.spec.ts +++ b/frontend/__tests__/controllers/preset-controller.spec.ts @@ -56,6 +56,7 @@ describe("PresetController", () => { dbGetSnapshotMock.mockReturnValue({} as any); configApplyMock.mockResolvedValue(); + tagsClearMock.mockResolvedValue(); }); it("should apply for full preset", async () => { diff --git a/frontend/package.json b/frontend/package.json index e90f7a3721c0..59d12ce58300 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -76,6 +76,7 @@ "zod-urlsearchparams": "0.0.16" }, "devDependencies": { + "@actions/core": "3.0.1", "@eslint/json": "1.2.0", "@fortawesome/fontawesome-free": "5.15.4", "@monkeytype/oxlint-config": "workspace:*", diff --git a/frontend/scripts/check-assets.ts b/frontend/scripts/check-assets.ts index f1246caa68fd..a0b267d5ee31 100644 --- a/frontend/scripts/check-assets.ts +++ b/frontend/scripts/check-assets.ts @@ -22,6 +22,9 @@ import { ChallengeSchema, Challenge } from "@monkeytype/schemas/challenges"; import { LayoutObject, LayoutObjectSchema } from "@monkeytype/schemas/layouts"; import { QuoteDataSchema, QuoteData } from "@monkeytype/schemas/quotes"; import { clickSoundConfig } from "../src/ts/constants/sounds"; +import * as ghCore from "@actions/core"; + +const stepSummary = ghCore.summary; class Problems { private type: string; @@ -51,10 +54,21 @@ class Problems { return Object.keys(this.problems).length !== 0; } public toString(): string { + stepSummary.addHeading(`${this.type} Checks`, 2); if (!this.hasError()) { + stepSummary.addRaw("✅ all checks passed").addEOL(); return `${this.type} are all \u001b[32mvalid\u001b[0m`; } + Object.entries(this.problems).forEach(([key, problems]) => { + let label: string = this.labels[key as T] ?? `${key}`; + stepSummary + .addRaw(`❌ ${label}`) + .addEOL() + .addList(problems as string[]) + .addEOL(); + }); + return ( `${this.type} are \u001b[31minvalid\u001b[0m\n` + Object.entries(this.problems) @@ -513,8 +527,15 @@ async function main(): Promise { } if (tasks.size > 0) { - await Promise.all([...tasks].map(async (validator) => validator())); - return; + const results = await Promise.allSettled( + [...tasks].map(async (validator) => validator()), + ); + + await stepSummary.write(); + + if (results.find((it) => it.status === "rejected") !== undefined) { + throw new Error("One or more checks failed."); + } } } void main(); diff --git a/frontend/src/styles/media-queries.scss b/frontend/src/styles/media-queries.scss index e9358c2d5290..b080cdc9b3b7 100644 --- a/frontend/src/styles/media-queries.scss +++ b/frontend/src/styles/media-queries.scss @@ -43,7 +43,12 @@ body { @media (prefers-reduced-motion) { body:not(.ignore-reduced-motion) - *:not(.fa-spin, .animate-\[loader\], .preloader) { + *:not( + .fa-spin, + .animate-\[loader\], + .preloader, + .typed-effect-fade .word.typed + ) { animation: none !important; transition: none !important; diff --git a/frontend/src/ts/ape/user.ts b/frontend/src/ts/ape/user.ts new file mode 100644 index 000000000000..2b15084c1a70 --- /dev/null +++ b/frontend/src/ts/ape/user.ts @@ -0,0 +1,54 @@ +import { GetUserResponse } from "@monkeytype/contracts/users"; +import Ape from "."; +import { createEffectOn } from "../hooks/effects"; +import { isAuthenticated } from "../states/core"; +import { SnapshotInitError } from "../utils/snapshot-init-error"; + +type CacheType = GetUserResponse["data"]; + +let fetchPromise: Promise | null = null; +let cache: CacheType | undefined = undefined; + +export async function fetchUserFromApi(): Promise { + await sync(); + return cache; +} + +async function sync(): Promise { + if (!isAuthenticated()) { + return; + } + + if (cache !== undefined) return; + + fetchPromise ??= (async () => { + const response = await Ape.users.get(); + + if (response.status !== 200) { + throw new SnapshotInitError( + `${response.body.message} (user)`, + response.status, + ); + } + + cache = response.body.data; + })(); + + try { + await fetchPromise; + } finally { + fetchPromise = null; + } +} + +function reset(): void { + cache = undefined; + fetchPromise = null; +} + +// clear cache + reset promise on logout +createEffectOn(isAuthenticated, (isAuthenticated) => { + if (!isAuthenticated) { + reset(); + } +}); diff --git a/frontend/src/ts/auth.tsx b/frontend/src/ts/auth.tsx index 9986737851ba..b0e684ec60bd 100644 --- a/frontend/src/ts/auth.tsx +++ b/frontend/src/ts/auth.tsx @@ -9,6 +9,7 @@ import { } from "firebase/auth"; import Ape from "./ape"; +import { waitForPresetsReady } from "./collections/presets"; import { updateFromServer as updateConfigFromServer } from "./config/remote"; import * as DB from "./db"; import { authEvent } from "./events/auth"; @@ -30,6 +31,7 @@ import { showSuccessNotification, } from "./states/notifications"; import { createErrorMessage } from "./utils/error"; +import { SnapshotInitError } from "./utils/snapshot-init-error"; export type AuthResult = | { @@ -64,6 +66,8 @@ async function getDataAndInit(): Promise { try { console.log("getting account data"); const snapshot = await DB.initSnapshot(); + //TODO: always load presets for now, remove when __nonReactive is removed from presets collection + await waitForPresetsReady(); if (snapshot === false) { throw new Error( @@ -77,7 +81,7 @@ async function getDataAndInit(): Promise { return true; } catch (error) { console.error(error); - if (error instanceof DB.SnapshotInitError) { + if (error instanceof SnapshotInitError) { if (error.responseCode === 429) { showNoticeNotification( "Doing so will save you bandwidth, make the next test be ready faster and will not sign you out (which could mean your new personal best would not save to your account).", diff --git a/frontend/src/ts/collections/presets.ts b/frontend/src/ts/collections/presets.ts index a06a7318895b..98a9a0b6725b 100644 --- a/frontend/src/ts/collections/presets.ts +++ b/frontend/src/ts/collections/presets.ts @@ -10,6 +10,8 @@ import { queryClient } from "../queries"; import { baseKey } from "../queries/utils/keys"; import { ConfigGroupName } from "@monkeytype/schemas/configs"; import { tempId } from "./utils/misc"; +import { isAuthenticated } from "../states/core"; +import { replaceUnderscoresWithSpaces } from "../utils/strings"; export type PresetItem = Preset; @@ -29,13 +31,21 @@ export function usePresetsLiveQuery() { const presetsCollection = createCollection( queryCollectionOptions({ staleTime: Infinity, - startSync: true, queryKey: queryKeys.root(), - queryClient, getKey: (it) => it._id, queryFn: async () => { - return [] as PresetItem[]; + if (!isAuthenticated()) return []; + const response = await Ape.presets.get(); + + if (response.status !== 200) { + throw new Error("Error fetching presets:" + response.body.message); + } + + return response.body.data.map((it) => ({ + ...it, + name: replaceUnderscoresWithSpaces(it.name), + })); }, }), ); @@ -149,6 +159,9 @@ const actions = { }; // --- Public API --- +export async function waitForPresetsReady(): Promise { + await presetsCollection.stateWhenReady(); +} function getPresets(): PresetItem[] { return [...presetsCollection.values()].sort((a, b) => @@ -160,21 +173,6 @@ function getPreset(id: string): PresetItem | undefined { return presetsCollection.get(id); } -export function fillPresetsCollection(presets: Preset[]): void { - const presetItems = presets.map((preset) => ({ - _id: preset._id, - name: preset.name.replace(/_/g, " "), - config: preset.config, - settingGroups: preset.settingGroups, - })); - - presetsCollection.utils.writeBatch(() => { - presetItems.forEach((item) => { - presetsCollection.utils.writeInsert(item); - }); - }); -} - export async function addPreset( params: ActionType["addPreset"], ): Promise { diff --git a/frontend/src/ts/collections/result-filter-presets.ts b/frontend/src/ts/collections/result-filter-presets.ts index d283c86d0a6c..774771d67930 100644 --- a/frontend/src/ts/collections/result-filter-presets.ts +++ b/frontend/src/ts/collections/result-filter-presets.ts @@ -13,6 +13,7 @@ import { replaceUnderscoresWithSpaces, } from "../utils/strings"; import { tempId } from "./utils/misc"; +import { fetchUserFromApi } from "../ape/user"; const queryKeys = { root: () => [...baseKey("resultFilterPresets", { isUserSpecific: true })], @@ -27,8 +28,13 @@ const resultFilterPresetsCollection = createCollection( queryClient, getKey: (it) => it._id, queryFn: async () => { - //return emtpy array. We load the user with the snapshot and fill the collection from there - return [] as ResultFilters[]; + const userData = await fetchUserFromApi(); + if (userData === undefined) return []; + + return (userData.resultFilterPresets ?? []).map((it) => ({ + ...it, + name: replaceUnderscoresWithSpaces(it.name), + })); }, }), ); @@ -118,13 +124,3 @@ export async function deleteResultFilterPreset( const transaction = actions.deleteResultFilterPreset(params); await transaction.isPersisted.promise; } - -export function fillResultFilterPresetsCollection( - presets: ResultFilters[], -): void { - resultFilterPresetsCollection.utils.writeBatch(() => { - presets - .map((it) => ({ ...it, name: replaceUnderscoresWithSpaces(it.name) })) - .forEach((it) => resultFilterPresetsCollection.utils.writeInsert(it)); - }); -} diff --git a/frontend/src/ts/collections/tags.ts b/frontend/src/ts/collections/tags.ts index 311d148aff1f..b4f3bf45b174 100644 --- a/frontend/src/ts/collections/tags.ts +++ b/frontend/src/ts/collections/tags.ts @@ -21,6 +21,7 @@ import { import { Difficulty } from "@monkeytype/schemas/configs"; import { Language } from "@monkeytype/schemas/languages"; import { tempId } from "./utils/misc"; +import { fetchUserFromApi } from "../ape/user"; export type TagItem = UserTag & { active: boolean }; @@ -31,13 +32,19 @@ const queryKeys = { const tagsCollection = createCollection( queryCollectionOptions({ staleTime: Infinity, - startSync: true, queryKey: queryKeys.root(), - queryClient, getKey: (it) => it._id, queryFn: async () => { - return [] as TagItem[]; + const activeIds = activeTagsLS.get(); + const userData = await fetchUserFromApi(); + if (userData === undefined) return []; + + return (userData.tags ?? []).map((tag) => ({ + ...tag, + name: tag.name.replace(/_/g, " "), + active: activeIds.includes(tag._id), + })); }, }), ); @@ -205,22 +212,6 @@ function getActiveTags(): TagItem[] { return getTags().filter((tag) => tag.active); } -export function fillTagsCollection(userTags: UserTag[]): void { - const activeIds = activeTagsLS.get(); - - const tagItems = userTags.map((tag) => ({ - ...tag, - name: tag.name.replace(/_/g, " "), - active: activeIds.includes(tag._id), - })); - - tagsCollection.utils.writeBatch(() => { - tagItems.forEach((item) => { - tagsCollection.utils.writeInsert(item); - }); - }); -} - // --- Active state --- const activeTagsLS = new LocalStorageWithSchema({ diff --git a/frontend/src/ts/components/pages/profile/ProfileSearchPage.tsx b/frontend/src/ts/components/pages/profile/ProfileSearchPage.tsx index 3b168dde5153..d3989caae5e5 100644 --- a/frontend/src/ts/components/pages/profile/ProfileSearchPage.tsx +++ b/frontend/src/ts/components/pages/profile/ProfileSearchPage.tsx @@ -42,10 +42,11 @@ export function ProfileSearchPage(): JSXElement { createEffect(() => { if (isOpen()) { - form.reset(); requestAnimationFrame(() => { inputEl()?.qs("input")?.focus({ preventScroll: true }); }); + } else { + form.reset(); } }); @@ -77,7 +78,7 @@ export function ProfileSearchPage(): JSXElement { getUserProfile(field.value), ); return result !== null ? undefined : "Unknown user"; - } catch (e) { + } catch { return "Unknown user"; } }, diff --git a/frontend/src/ts/config/remote.ts b/frontend/src/ts/config/remote.ts index 2fb74f71400b..91f27a53b044 100644 --- a/frontend/src/ts/config/remote.ts +++ b/frontend/src/ts/config/remote.ts @@ -4,7 +4,7 @@ import { migrateConfig } from "./utils"; import { applyConfig } from "./lifecycle"; import { saveFullConfigToLocalStorage } from "./persistence"; import Ape from "../ape"; -import { SnapshotInitError } from "../db"; +import { SnapshotInitError } from "../utils/snapshot-init-error"; import { getDefaultConfig } from "../constants/default-config"; import { Config } from "./store"; diff --git a/frontend/src/ts/db.ts b/frontend/src/ts/db.ts index 8538ccd13799..02f2119d7913 100644 --- a/frontend/src/ts/db.ts +++ b/frontend/src/ts/db.ts @@ -33,7 +33,6 @@ import { } from "./ape/server-configuration"; import { Connection } from "@monkeytype/schemas/connections"; import { insertLocalResult } from "./collections/results"; -import { fillResultFilterPresetsCollection } from "./collections/result-filter-presets"; import { setLastResult, setSnapshot as setSolidSnapshot, @@ -41,22 +40,14 @@ import { import { XpBreakdown } from "@monkeytype/schemas/results"; import { setXpBarData } from "./states/header"; import { FunboxMetadata } from "@monkeytype/funbox"; -import { fillTagsCollection, __nonReactive } from "./collections/tags"; +import { __nonReactive } from "./collections/tags"; import { updateTagsInFilterStorage } from "./states/result-filters"; -import { fillPresetsCollection } from "./collections/presets"; +import { fetchUserFromApi } from "./ape/user"; +import { SnapshotInitError } from "./utils/snapshot-init-error"; let dbSnapshot: Snapshot | undefined; const firstDayOfTheWeek = getFirstDayOfTheWeek(); -export class SnapshotInitError extends Error { - public responseCode: number; - constructor(message: string, responseCode: number) { - super(message); - this.name = "SnapshotInitError"; - this.responseCode = responseCode; - } -} - export function getSnapshot(): Snapshot | undefined { return dbSnapshot; } @@ -105,25 +96,12 @@ export async function initSnapshot(): Promise { ? Ape.connections.get() : { status: 200, body: { message: "", data: [] } }; - const [userResponse, presetsResponse, connectionsResponse] = - await Promise.all([ - Ape.users.get(), - Ape.presets.get(), - connectionsRequest, - ]); + const [userData, connectionsResponse] = await Promise.all([ + fetchUserFromApi(), + + connectionsRequest, + ]); - if (userResponse.status !== 200) { - throw new SnapshotInitError( - `${userResponse.body.message} (user)`, - userResponse.status, - ); - } - if (presetsResponse.status !== 200) { - throw new SnapshotInitError( - `${presetsResponse.body.message} (presets)`, - presetsResponse.status, - ); - } if (connectionsResponse.status !== 200) { throw new SnapshotInitError( `${connectionsResponse.body.message} (connections)`, @@ -131,13 +109,11 @@ export async function initSnapshot(): Promise { ); } - const userData = userResponse.body.data; - const presetsData = presetsResponse.body.data; const connectionsData = connectionsResponse.body.data; - if (userData === null) { + if (userData === null || userData === undefined) { throw new SnapshotInitError( - `Request was successful but user data is null`, + `Request was successful but user data is null/undefined`, 200, ); } @@ -197,14 +173,9 @@ export async function initSnapshot(): Promise { snap.customThemes = userData.customThemes ?? []; - fillTagsCollection(userData.tags ?? []); - fillPresetsCollection(presetsData ?? []); - - fillResultFilterPresetsCollection(userData.resultFilterPresets ?? []); updateTagsInFilterStorage(userData.tags?.map((it) => it._id) ?? []); snap.connections = convertConnections(connectionsData); - dbSnapshot = snap; return dbSnapshot; diff --git a/frontend/src/ts/queries/index.ts b/frontend/src/ts/queries/index.ts index 5a8c223d5853..3cacc98d43b3 100644 --- a/frontend/src/ts/queries/index.ts +++ b/frontend/src/ts/queries/index.ts @@ -4,9 +4,9 @@ import { isAuthenticated } from "../states/core"; export const queryClient = new QueryClient(); -createEffectOn(isAuthenticated, (state) => { - if (!state) { - console.debug("QueryClient clear all user related queries."); - void queryClient.resetQueries({ queryKey: ["user"] }); - } +createEffectOn(isAuthenticated, () => { + //reset user related queries and collections whenever the state changes. + //for legacy access we initialize some user-bound collections without a user being present (e.g. tags, presets). + //to avoid empty collections after login, reset the queries on every state change not just logout + void queryClient.resetQueries({ queryKey: ["user"] }); }); diff --git a/frontend/src/ts/utils/snapshot-init-error.ts b/frontend/src/ts/utils/snapshot-init-error.ts new file mode 100644 index 000000000000..83774f40e1e7 --- /dev/null +++ b/frontend/src/ts/utils/snapshot-init-error.ts @@ -0,0 +1,8 @@ +export class SnapshotInitError extends Error { + public responseCode: number; + constructor(message: string, responseCode: number) { + super(message); + this.name = "SnapshotInitError"; + this.responseCode = responseCode; + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 77e98b7f1bd5..1d50e9ca12b1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -427,6 +427,9 @@ importers: specifier: 0.0.16 version: 0.0.16(zod@3.23.8) devDependencies: + '@actions/core': + specifier: 3.0.1 + version: 3.0.1 '@eslint/json': specifier: 1.2.0 version: 1.2.0 @@ -677,7 +680,7 @@ importers: version: 6.0.2 vitest: specifier: 4.1.0 - version: 4.1.0(@types/node@24.9.1)(happy-dom@20.8.9)(jsdom@27.4.0)(vite@8.0.5(@emnapi/core@1.9.0)(@emnapi/runtime@1.9.0)(@types/node@24.9.1)(esbuild@0.25.0)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.2)(tsx@4.21.0)(yaml@2.8.3)) + version: 4.1.0(@types/node@24.9.1)(happy-dom@20.8.9)(jsdom@27.4.0)(vite@8.0.5(@emnapi/core@1.9.0)(@emnapi/runtime@1.9.0)(@types/node@24.9.1)(esbuild@0.27.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.2)(tsx@4.21.0)(yaml@2.8.3)) packages/funbox: dependencies: @@ -840,6 +843,18 @@ packages: '@acemir/cssom@0.9.30': resolution: {integrity: sha512-9CnlMCI0LmCIq0olalQqdWrJHPzm0/tw3gzOA9zJSgvFX7Xau3D24mAGa4BtwxwY69nsuJW6kQqqCzf/mEcQgg==} + '@actions/core@3.0.1': + resolution: {integrity: sha512-a6d/Nwahm9fliVGRhdhofo40HjHQasUPusmc7vBfyky+7Z+P2A1J68zyFVaNcEclc/Se+eO595oAr5nwEIoIUA==} + + '@actions/exec@3.0.0': + resolution: {integrity: sha512-6xH/puSoNBXb72VPlZVm7vQ+svQpFyA96qdDBvhB8eNZOE8LtPf9L4oAsfzK/crCL8YZ+19fKYVnM63Sl+Xzlw==} + + '@actions/http-client@4.0.1': + resolution: {integrity: sha512-+Nvd1ImaOZBSoPbsUtEhv+1z99H12xzncCkz0a3RuehINE81FZSe2QTj3uvAPTcJX/SCzUQHQ0D1GrPMbrPitg==} + + '@actions/io@3.0.2': + resolution: {integrity: sha512-nRBchcMM+QK1pdjO7/idu86rbJI5YHUKCvKs0KxnSYbVe3F51UfGxuZX4Qy/fWlp6l7gWFwIkrOzN+oUK03kfw==} + '@adobe/css-tools@4.4.4': resolution: {integrity: sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==} @@ -10885,6 +10900,10 @@ packages: engines: {node: '>=18.0.0'} hasBin: true + tunnel@0.0.6: + resolution: {integrity: sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==} + engines: {node: '>=0.6.11 <=0.7.0 || >=0.7.3'} + turbo-darwin-64@2.7.5: resolution: {integrity: sha512-nN3wfLLj4OES/7awYyyM7fkU8U8sAFxsXau2bYJwAWi6T09jd87DgHD8N31zXaJ7LcpyppHWPRI2Ov9MuZEwnQ==} cpu: [x64] @@ -11698,6 +11717,22 @@ snapshots: '@acemir/cssom@0.9.30': {} + '@actions/core@3.0.1': + dependencies: + '@actions/exec': 3.0.0 + '@actions/http-client': 4.0.1 + + '@actions/exec@3.0.0': + dependencies: + '@actions/io': 3.0.2 + + '@actions/http-client@4.0.1': + dependencies: + tunnel: 0.0.6 + undici: 6.24.0 + + '@actions/io@3.0.2': {} + '@adobe/css-tools@4.4.4': {} '@anatine/zod-openapi@1.14.2(openapi3-ts@2.0.2)(zod@3.23.8)': @@ -16054,14 +16089,6 @@ snapshots: optionalDependencies: vite: 7.3.2(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.2)(tsx@4.21.0)(yaml@2.8.3) - '@vitest/mocker@4.1.0(vite@8.0.5(@emnapi/core@1.9.0)(@emnapi/runtime@1.9.0)(@types/node@24.9.1)(esbuild@0.25.0)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.2)(tsx@4.21.0)(yaml@2.8.3))': - dependencies: - '@vitest/spy': 4.1.0 - estree-walker: 3.0.3 - magic-string: 0.30.21 - optionalDependencies: - vite: 8.0.5(@emnapi/core@1.9.0)(@emnapi/runtime@1.9.0)(@types/node@24.9.1)(esbuild@0.25.0)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.2)(tsx@4.21.0)(yaml@2.8.3) - '@vitest/mocker@4.1.0(vite@8.0.5(@emnapi/core@1.9.0)(@emnapi/runtime@1.9.0)(@types/node@24.9.1)(esbuild@0.27.7)(jiti@2.6.1)(sass@1.70.0)(terser@5.46.2)(tsx@4.21.0)(yaml@2.8.3))': dependencies: '@vitest/spy': 4.1.0 @@ -23154,6 +23181,8 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + tunnel@0.0.6: {} + turbo-darwin-64@2.7.5: optional: true @@ -23516,26 +23545,6 @@ snapshots: tsx: 4.21.0 yaml: 2.8.3 - vite@8.0.5(@emnapi/core@1.9.0)(@emnapi/runtime@1.9.0)(@types/node@24.9.1)(esbuild@0.25.0)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.2)(tsx@4.21.0)(yaml@2.8.3): - dependencies: - lightningcss: 1.32.0 - picomatch: 4.0.4 - postcss: 8.5.8 - rolldown: 1.0.0-rc.12(@emnapi/core@1.9.0)(@emnapi/runtime@1.9.0) - tinyglobby: 0.2.15 - optionalDependencies: - '@types/node': 24.9.1 - esbuild: 0.25.0 - fsevents: 2.3.3 - jiti: 2.6.1 - sass: 1.98.0 - terser: 5.46.2 - tsx: 4.21.0 - yaml: 2.8.3 - transitivePeerDependencies: - - '@emnapi/core' - - '@emnapi/runtime' - vite@8.0.5(@emnapi/core@1.9.0)(@emnapi/runtime@1.9.0)(@types/node@24.9.1)(esbuild@0.27.7)(jiti@2.6.1)(sass@1.70.0)(terser@5.46.2)(tsx@4.21.0)(yaml@2.8.3): dependencies: lightningcss: 1.32.0 @@ -23664,35 +23673,6 @@ snapshots: transitivePeerDependencies: - msw - vitest@4.1.0(@types/node@24.9.1)(happy-dom@20.8.9)(jsdom@27.4.0)(vite@8.0.5(@emnapi/core@1.9.0)(@emnapi/runtime@1.9.0)(@types/node@24.9.1)(esbuild@0.25.0)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.2)(tsx@4.21.0)(yaml@2.8.3)): - dependencies: - '@vitest/expect': 4.1.0 - '@vitest/mocker': 4.1.0(vite@8.0.5(@emnapi/core@1.9.0)(@emnapi/runtime@1.9.0)(@types/node@24.9.1)(esbuild@0.25.0)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.2)(tsx@4.21.0)(yaml@2.8.3)) - '@vitest/pretty-format': 4.1.0 - '@vitest/runner': 4.1.0 - '@vitest/snapshot': 4.1.0 - '@vitest/spy': 4.1.0 - '@vitest/utils': 4.1.0 - es-module-lexer: 2.0.0 - expect-type: 1.3.0 - magic-string: 0.30.21 - obug: 2.1.1 - pathe: 2.0.3 - picomatch: 4.0.3 - std-env: 4.0.0 - tinybench: 2.9.0 - tinyexec: 1.0.2 - tinyglobby: 0.2.15 - tinyrainbow: 3.0.3 - vite: 8.0.5(@emnapi/core@1.9.0)(@emnapi/runtime@1.9.0)(@types/node@24.9.1)(esbuild@0.25.0)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.2)(tsx@4.21.0)(yaml@2.8.3) - why-is-node-running: 2.3.0 - optionalDependencies: - '@types/node': 24.9.1 - happy-dom: 20.8.9 - jsdom: 27.4.0 - transitivePeerDependencies: - - msw - vitest@4.1.0(@types/node@24.9.1)(happy-dom@20.8.9)(jsdom@27.4.0)(vite@8.0.5(@emnapi/core@1.9.0)(@emnapi/runtime@1.9.0)(@types/node@24.9.1)(esbuild@0.27.7)(jiti@2.6.1)(sass@1.70.0)(terser@5.46.2)(tsx@4.21.0)(yaml@2.8.3)): dependencies: '@vitest/expect': 4.1.0