diff --git a/apps/to-do-webapp/README.md b/apps/to-do-webapp/README.md index fa5dac1b..4316b025 100644 --- a/apps/to-do-webapp/README.md +++ b/apps/to-do-webapp/README.md @@ -18,6 +18,26 @@ Open [http://localhost:3000](http://localhost:3000) with your browser to see the You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. +## Environment + +This app expects several server-only environment variables to be set before running. To avoid accidental leaks, do not commit `.env.local` with secrets. Create a local file from your environment or use your secrets manager. + +Required variables (server-only): + +- `API_BASE_URL` — base URL of the backend API +- `API_BASE_PATH` — base path used by the generated API client +- `API_KEY` — API key for server-to-server calls (keep secret) + +When running locally you can copy a provided example (if present) or create `.env.local` yourself: + +```bash +# create a local env and edit values +cp .env.local.example .env.local 2>/dev/null || true +# then edit .env.local to set API_BASE_URL/API_BASE_PATH/API_KEY +``` + +The app validates required env vars on the server at startup and will fail fast with a clear error if any are missing. + This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. ## Learn More diff --git a/apps/to-do-webapp/package.json b/apps/to-do-webapp/package.json index c245cbda..23581d3c 100644 --- a/apps/to-do-webapp/package.json +++ b/apps/to-do-webapp/package.json @@ -21,7 +21,8 @@ "io-ts": "catalog:", "next": "catalog:next", "react": "catalog:react", - "react-dom": "catalog:react" + "react-dom": "catalog:react", + "zod": "^4.3.5" }, "devDependencies": { "@eslint/eslintrc": "^3.3.3", diff --git a/apps/to-do-webapp/src/app/api/tasks/[taskId]/route.ts b/apps/to-do-webapp/src/app/api/tasks/[taskId]/route.ts new file mode 100644 index 00000000..c9e3a926 --- /dev/null +++ b/apps/to-do-webapp/src/app/api/tasks/[taskId]/route.ts @@ -0,0 +1,23 @@ +import { NextResponse } from "next/server"; + +import { completeTask } from "@/lib/api"; + +export async function DELETE( + request: Request, + context: { params: Promise> | Record }, +) { + try { + // In Next.js App Router dynamic API handlers `context.params` may be a Promise. + // Await it before accessing properties to avoid the sync dynamic APIs error. + const resolvedParams = await context.params; + const taskId: string = resolvedParams?.taskId as string; + + const { status } = await completeTask(taskId); + return new NextResponse(null, { status }); + } catch { + return NextResponse.json( + { message: "Error deleting task" }, + { status: 500 }, + ); + } +} diff --git a/apps/to-do-webapp/src/app/api/tasks/route.ts b/apps/to-do-webapp/src/app/api/tasks/route.ts new file mode 100644 index 00000000..209b74cf --- /dev/null +++ b/apps/to-do-webapp/src/app/api/tasks/route.ts @@ -0,0 +1,17 @@ +import { NextResponse } from "next/server"; + +import { insertTask } from "@/lib/api"; + +export async function POST(req: Request) { + try { + const body = await req.json(); + const { title } = body; + const { status, value } = await insertTask(title); + return NextResponse.json(value, { status }); + } catch { + return NextResponse.json( + { message: "Error creating task" }, + { status: 500 }, + ); + } +} diff --git a/apps/to-do-webapp/src/app/page.tsx b/apps/to-do-webapp/src/app/page.tsx index b09a6eff..52b771de 100644 --- a/apps/to-do-webapp/src/app/page.tsx +++ b/apps/to-do-webapp/src/app/page.tsx @@ -1,62 +1,11 @@ -"use client"; - -import { Alert, Container, Divider, Typography } from "@mui/material"; -import { useEffect, useState } from "react"; - -import ToDoList from "@/components/ToDoList"; -import ToDoTextArea from "@/components/ToDoTextArea"; +import TasksClient from "@/components/TasksClient"; import { getTaskList } from "@/lib/api"; -import { TaskItem } from "@/lib/client/TaskItem"; -import { TaskItemList } from "@/lib/client/TaskItemList"; -export const dynamic = "force-dynamic"; - -export default function Home() { - const [tasks, setTasks] = useState([]); - const [error, setError] = useState(null); - useEffect(() => { - const fetchTasks = async () => { - try { - const { status, value: taskList } = await getTaskList(); - if (status === 200) { - setError(null); - setTasks(taskList); - } else { - setError("Failed to fetch tasks. Please try again later."); - setTasks([]); - } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - } catch (error) { - setError("Failed to fetch tasks. Please try again later."); - } - }; - fetchTasks(); - }, []); - - // Update the list of tasks with the new task - const addTask = (task: TaskItem) => { - setTasks((prevTasks) => [...prevTasks, task]); - }; - - // Remove the completed task from the list - const completeTask = (taskId: string) => { - setTasks((tasks) => tasks.filter(({ id }) => id !== taskId)); - }; - - return ( - - - My Tasks - - +export const dynamic = "force-dynamic"; - +export default async function Home() { + const { status, value: taskList } = await getTaskList(); + const initialTasks = status === 200 ? taskList : []; - {error ? ( - {error} - ) : ( - - )} - - ); + return ; } diff --git a/apps/to-do-webapp/src/components/TasksClient.tsx b/apps/to-do-webapp/src/components/TasksClient.tsx new file mode 100644 index 00000000..7c7496b4 --- /dev/null +++ b/apps/to-do-webapp/src/components/TasksClient.tsx @@ -0,0 +1,71 @@ +"use client"; + +import { Alert, Container, Divider, Typography } from "@mui/material"; +import { useState } from "react"; + +import type { TaskItemList } from "@/lib/client/TaskItemList"; + +import ToDoList from "@/components/ToDoList"; +import ToDoTextArea from "@/components/ToDoTextArea"; + +export default function TasksClient({ + initialTasks, +}: { + initialTasks: TaskItemList; +}) { + const [tasks, setTasks] = useState(initialTasks); + const [error, setError] = useState(null); + + const addTask = async (title: string): Promise => { + setError(null); + try { + const res = await fetch("/api/tasks", { + body: JSON.stringify({ title }), + headers: { "Content-Type": "application/json" }, + method: "POST", + }); + if (res.ok) { + const created = await res.json(); + setTasks((p) => [...p, created]); + } else { + const body = await res.json().catch(() => null); + setError(body?.message ?? "Failed creating task"); + } + } catch (err) { + console.error("TasksClient.addTask error", err); + setError(err instanceof Error ? err.message : "Failed creating task"); + } + }; + + const completeTask = async (id: string): Promise => { + setError(null); + try { + const res = await fetch(`/api/tasks/${id}`, { method: "DELETE" }); + if (res.ok) { + setTasks((p) => p.filter((t) => t.id !== id)); + } else { + setError("Failed deleting task"); + } + } catch (err) { + console.error("TasksClient.completeTask error", err); + setError(err instanceof Error ? err.message : "Failed deleting task"); + } + }; + + return ( + + + My Tasks + + + + + + {error ? ( + {error} + ) : ( + + )} + + ); +} diff --git a/apps/to-do-webapp/src/components/ToDoItem.tsx b/apps/to-do-webapp/src/components/ToDoItem.tsx index 8881c8de..bccbee0c 100644 --- a/apps/to-do-webapp/src/components/ToDoItem.tsx +++ b/apps/to-do-webapp/src/components/ToDoItem.tsx @@ -1,13 +1,11 @@ import { Checkbox, ListItem, ListItemText } from "@mui/material"; import React from "react"; -import { completeTask } from "@/lib/api"; import { TaskId } from "@/lib/client/TaskId"; interface TodoItemProps { id: TaskId; - // Function to execute when a task is completed - onComplete: (id: TaskId) => void; + onComplete: (id: TaskId) => Promise; state: "COMPLETED" | "DELETED" | "INCOMPLETE"; title: string; } @@ -18,22 +16,23 @@ const ToDoItem: React.FC = ({ state, title, }) => { - const handleTaskComplete = async (taskId: TaskId) => { + const [isLoading, setIsLoading] = React.useState(false); + + const handleChange = async () => { + setIsLoading(true); try { - const { status } = await completeTask(taskId); - if (status === 204) { - onComplete(id); - } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - } catch (error) { - /* error when deleting the task */ + await onComplete(id); + } finally { + setIsLoading(false); } }; + return ( - + handleTaskComplete(id)} + disabled={isLoading} + onChange={handleChange} /> diff --git a/apps/to-do-webapp/src/components/ToDoList.tsx b/apps/to-do-webapp/src/components/ToDoList.tsx index b5cbdd50..4f08acef 100644 --- a/apps/to-do-webapp/src/components/ToDoList.tsx +++ b/apps/to-do-webapp/src/components/ToDoList.tsx @@ -7,7 +7,8 @@ import { TaskItemList } from "@/lib/client/TaskItemList"; interface TaskListProps { // Function to execute when a task is completed - onTaskComplete: (id: TaskId) => void; + // Function to execute when a task is completed (async) + onTaskComplete: (id: TaskId) => Promise; tasks: TaskItemList; } @@ -19,7 +20,13 @@ const ToDoList: React.FC = ({ {tasks .filter(({ state }) => state === "INCOMPLETE") .map((item) => ( - + ))} ); diff --git a/apps/to-do-webapp/src/components/ToDoTextArea.tsx b/apps/to-do-webapp/src/components/ToDoTextArea.tsx index 9f1c40a1..b14c6ad9 100644 --- a/apps/to-do-webapp/src/components/ToDoTextArea.tsx +++ b/apps/to-do-webapp/src/components/ToDoTextArea.tsx @@ -1,13 +1,12 @@ import { Button, TextField } from "@mui/material"; import React, { useState } from "react"; -import { insertTask } from "@/lib/api"; -import { TaskItem } from "@/lib/client/TaskItem"; +// TaskItem type removed; this component is presentational and only deals with titles interface Props { label: string; - // Function to update the list of tasks with the new task - onAddTask: (task: TaskItem) => void; + // Parent must return a promise so the component can await and show loading + onAddTask: (taskTitle: string) => Promise; } const ToDoTextArea = ({ label, onAddTask }: Props) => { @@ -18,14 +17,10 @@ const ToDoTextArea = ({ label, onAddTask }: Props) => { const handleAddTask = async (text: string) => { setIsLoading(true); try { - const { status, value } = await insertTask(text); - if (status === 201) { - setTaskText(""); - onAddTask(value); - } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - } catch (error) { - /* error when creating a task */ + await onAddTask(text); + setTaskText(""); + } catch { + // let parent show error } finally { setIsLoading(false); } @@ -52,10 +47,10 @@ const ToDoTextArea = ({ label, onAddTask }: Props) => { ); diff --git a/apps/to-do-webapp/src/lib/client.ts b/apps/to-do-webapp/src/lib/client.ts index e8614326..65e082bf 100644 --- a/apps/to-do-webapp/src/lib/client.ts +++ b/apps/to-do-webapp/src/lib/client.ts @@ -1,18 +1,51 @@ -import { createClient, WithDefaultsT } from "@/lib/client/client"; +import { z } from "zod"; -const withApiKey: WithDefaultsT<"ApiKeyAuth"> = - (wrappedOperation) => (params) => - wrappedOperation({ - ...params, - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - ApiKeyAuth: process.env.API_KEY!, - }); +import { createClient, WithDefaultsT } from "@/lib/client/client"; -export const client = createClient({ - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - basePath: process.env.API_BASE_PATH!, - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - baseUrl: process.env.API_BASE_URL!, - fetchApi: fetch as unknown as typeof fetch, - withDefaults: withApiKey, +// Server-only environment validation: ensure required env vars exist and +// fail-fast with a clear error message. This module must remain server-side +// to avoid leaking secrets into client bundles. +const EnvSchema = z.object({ + API_BASE_PATH: z.string().nonempty(), + API_BASE_URL: z.string().nonempty(), + API_KEY: z.string().nonempty(), + APPINSIGHTS_SAMPLING_PERCENTAGE: z.preprocess((v) => { + if (v == null || v === "") return undefined; + return Number(v); + }, z.number().optional()), + APPLICATIONINSIGHTS_CONNECTION_STRING: z.string().optional(), + OTEL_SERVICE_NAME: z.string().optional(), }); + +// Create the API client on the server only. This will parse and validate +// environment variables and throw immediately if any required value is +// missing or invalid — preventing the app from starting with a bad config. +function createServerClient() { + const env = EnvSchema.parse(process.env); // throws on validation error + + const withApiKey: WithDefaultsT<"ApiKeyAuth"> = + (wrappedOperation) => (params) => + wrappedOperation({ + ...params, + ApiKeyAuth: env.API_KEY, + }); + + return createClient<"ApiKeyAuth">({ + basePath: env.API_BASE_PATH, + baseUrl: env.API_BASE_URL, + fetchApi: fetch as unknown as typeof fetch, + withDefaults: withApiKey, + }); +} + +// Export a server-only client. If this module is imported on the client, +// accessing `client` will throw a clear error (and it will not contain +// server secrets). This keeps env values out of client bundles. +export const client = + typeof window === "undefined" + ? createServerClient() + : ((): never => { + throw new Error( + "Attempted to initialize server-only API client on the client. Ensure this module is only imported from server code.", + ); + })(); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4492a113..6628035d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -227,7 +227,7 @@ importers: version: 9.39.2 eslint-config-next: specifier: catalog:next - version: 16.1.6(@typescript-eslint/parser@8.53.0(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2)(typescript@5.9.3) + version: 16.1.6(eslint@9.39.2)(typescript@5.9.3) typescript: specifier: 'catalog:' version: 5.9.3 @@ -447,6 +447,9 @@ importers: react-dom: specifier: catalog:react version: 19.2.4(react@19.2.4) + zod: + specifier: ^4.3.5 + version: 4.3.5 devDependencies: '@eslint/eslintrc': specifier: ^3.3.3 @@ -477,7 +480,7 @@ importers: version: 9.39.2 eslint-config-next: specifier: catalog:next - version: 16.1.6(eslint@9.39.2)(typescript@5.9.3) + version: 16.1.6(@typescript-eslint/parser@8.53.0(eslint@9.39.2)(typescript@5.8.3))(eslint@9.39.2)(typescript@5.9.3) prettier: specifier: 'catalog:' version: 3.8.1 @@ -7928,10 +7931,10 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/eslint-plugin@8.53.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2)(typescript@5.9.3)': + '@typescript-eslint/eslint-plugin@8.53.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2)(typescript@5.8.3))(eslint@9.39.2)(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.53.0(eslint@9.39.2)(typescript@5.9.3) + '@typescript-eslint/parser': 8.53.0(eslint@9.39.2)(typescript@5.8.3) '@typescript-eslint/scope-manager': 8.53.0 '@typescript-eslint/type-utils': 8.53.0(eslint@9.39.2)(typescript@5.9.3) '@typescript-eslint/utils': 8.53.0(eslint@9.39.2)(typescript@5.9.3) @@ -7985,7 +7988,7 @@ snapshots: dependencies: '@typescript-eslint/scope-manager': 8.53.0 '@typescript-eslint/types': 8.53.0 - '@typescript-eslint/typescript-estree': 8.53.0(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.53.0(typescript@5.8.3) '@typescript-eslint/visitor-keys': 8.53.0 debug: 4.4.3 eslint: 9.39.2 @@ -8288,14 +8291,6 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.0.3 - '@vitest/mocker@4.0.18(vite@7.3.1(@types/node@22.19.7))': - dependencies: - '@vitest/spy': 4.0.18 - estree-walker: 3.0.3 - magic-string: 0.30.21 - optionalDependencies: - vite: 7.3.1(@types/node@22.19.7) - '@vitest/mocker@4.0.18(vite@7.3.1(@types/node@25.0.10))': dependencies: '@vitest/spy': 4.0.18 @@ -9112,13 +9107,13 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-config-next@16.1.6(@typescript-eslint/parser@8.53.0(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2)(typescript@5.9.3): + eslint-config-next@16.1.6(@typescript-eslint/parser@8.53.0(eslint@9.39.2)(typescript@5.8.3))(eslint@9.39.2)(typescript@5.9.3): dependencies: '@next/eslint-plugin-next': 16.1.6 eslint: 9.39.2 eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2))(eslint@9.39.2) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2))(eslint@9.39.2))(eslint@9.39.2) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2))(eslint@9.39.2))(eslint@9.39.2) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.2) eslint-plugin-react: 7.37.5(eslint@9.39.2) eslint-plugin-react-hooks: 7.0.1(eslint@9.39.2) @@ -9175,7 +9170,7 @@ snapshots: tinyglobby: 0.2.15 unrs-resolver: 1.11.1 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2))(eslint@9.39.2))(eslint@9.39.2) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2))(eslint@9.39.2))(eslint@9.39.2) transitivePeerDependencies: - supports-color @@ -9194,11 +9189,11 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.53.0(eslint@9.39.2)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2))(eslint@9.39.2))(eslint@9.39.2): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.53.0(eslint@9.39.2)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2))(eslint@9.39.2))(eslint@9.39.2): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 8.53.0(eslint@9.39.2)(typescript@5.9.3) + '@typescript-eslint/parser': 8.53.0(eslint@9.39.2)(typescript@5.8.3) eslint: 9.39.2 eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2))(eslint@9.39.2) @@ -9215,7 +9210,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2))(eslint@9.39.2))(eslint@9.39.2): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2))(eslint@9.39.2))(eslint@9.39.2): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -9226,7 +9221,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.39.2 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.53.0(eslint@9.39.2)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2))(eslint@9.39.2))(eslint@9.39.2) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.53.0(eslint@9.39.2)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2))(eslint@9.39.2))(eslint@9.39.2) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -9238,7 +9233,7 @@ snapshots: string.prototype.trimend: 1.0.9 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.53.0(eslint@9.39.2)(typescript@5.9.3) + '@typescript-eslint/parser': 8.53.0(eslint@9.39.2)(typescript@5.8.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack @@ -11380,7 +11375,7 @@ snapshots: typescript-eslint@8.53.0(eslint@9.39.2)(typescript@5.9.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.53.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2)(typescript@5.9.3) + '@typescript-eslint/eslint-plugin': 8.53.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2)(typescript@5.8.3))(eslint@9.39.2)(typescript@5.9.3) '@typescript-eslint/parser': 8.53.0(eslint@9.39.2)(typescript@5.9.3) '@typescript-eslint/typescript-estree': 8.53.0(typescript@5.9.3) '@typescript-eslint/utils': 8.53.0(eslint@9.39.2)(typescript@5.9.3) @@ -11499,7 +11494,7 @@ snapshots: vitest@4.0.18(@opentelemetry/api@1.9.0)(@types/node@22.19.7): dependencies: '@vitest/expect': 4.0.18 - '@vitest/mocker': 4.0.18(vite@7.3.1(@types/node@22.19.7)) + '@vitest/mocker': 4.0.18(vite@7.3.1(@types/node@25.0.10)) '@vitest/pretty-format': 4.0.18 '@vitest/runner': 4.0.18 '@vitest/snapshot': 4.0.18