diff --git a/src/frontend/.prettierrc b/src/frontend/.prettierrc index f69ca308..cdde13ba 100644 --- a/src/frontend/.prettierrc +++ b/src/frontend/.prettierrc @@ -2,7 +2,7 @@ "semi": true, "singleQuote": true, "trailingComma": "all", - "printWidth": 180, + "printWidth": 80, "tabWidth": 2, "bracketSpacing": true, "arrowParens": "always", diff --git a/src/frontend/apps/web/src/shared/apis/.keep b/src/frontend/apps/web/src/shared/apis/.keep deleted file mode 100644 index e69de29b..00000000 diff --git a/src/frontend/apps/web/src/shared/services/apis/fetch-instance.api.ts b/src/frontend/apps/web/src/shared/services/apis/fetch-instance.api.ts new file mode 100644 index 00000000..07e5d29e --- /dev/null +++ b/src/frontend/apps/web/src/shared/services/apis/fetch-instance.api.ts @@ -0,0 +1,137 @@ +import { ERROR_MESSAGES } from '@/src/shared/services/models'; +import type { + HttpMethod, + FetchOptions, + JsonValue, + ApiErrorResponse, + ApiResponse, +} from '@/src/shared/services/models'; + +export async function fetchInstance( + url: string, + method: HttpMethod, + options: FetchOptions = {}, +): Promise { + try { + // 브라우저 환경에서만 localStorage 접근할 수 있도록 + const token = + typeof window !== 'undefined' ? localStorage.getItem('token') : null; + + /** + * options 객체에서 필요한 값들을 구조분해할당합니다 + * @template TBody - 요청 본문의 타입 + * @typedef {object} ExtractedOptions + * @property {TBody} [body] - 요청 본문 데이터 + * @property {Record} [params] - URL 쿼리 파라미터 + * @property {RequestCache} [cache] - Next.js 캐시 전략 + * @property {string[]} [tags] - 캐시 무효화 태그 + * @property {number} [revalidate] - 캐시 재검증 시간(초) + * @property {boolean} [includeAuthToken=true] - 토큰을 헤더에 추가할지 여부 + */ + const { + body, + params, + cache, + tags, + revalidate, + includeAuthToken = true, + ...restOptions + } = options; + + // URL 쿼리 파라미터 추가 + const queryParams = params + ? `?${new URLSearchParams(params).toString()}` + : ''; + const finalUrl = `${url}${queryParams}`; + + // 기본 헤더 설정 + const defaultHeaders: Record = { + 'Content-Type': 'application/json', + ...(token ? { Authorization: `Bearer ${token}` } : {}), + }; + + // includeAuthToken이 true일 때만 토큰을 헤더에 추가 + if (includeAuthToken) { + const token = + typeof window !== 'undefined' ? localStorage.getItem('token') : null; + if (token) { + defaultHeaders.Authorization = `Bearer ${token}`; + } + } + + // 최종 fetch 옵션 구성 + const finalOptions: RequestInit = { + method, + ...restOptions, + headers: { + ...defaultHeaders, + ...(restOptions.headers as Record), + }, + // cache, tags, revalidate 옵션이 있을 경우 next 프로퍼티에 추가 + ...(cache && { cache }), + ...(revalidate && { next: { revalidate } }), + ...(tags && { next: { tags } }), + }; + + // body 데이터가 있고 GET 요청이 아닐 때만 body 필드 추가 + if (body && method !== 'GET') { + finalOptions.body = JSON.stringify(body); + } + + const response = await fetch(finalUrl, finalOptions); + + // 성공 응답 처리 + if (response.ok) { + const contentType = response.headers.get('content-type'); + // JSON 응답일 경우 JSON 파싱 + if (contentType?.includes('application/json')) { + const data = await response.json(); + // ApiResponse 형태로 응답이 왔다면 data 필드를 반환 + if ( + data && + typeof data === 'object' && + 'code' in data && + 'message' in data + ) { + // ApiResponse 형태면 data 필드만 추출 + return (data as ApiResponse).data as TResponse; + } + // 일반 JSON 응답이면 그대로 반환 + return data as TResponse; + } + // JSON이 아닌 경우 텍스트로 반환 + return response.text() as unknown as TResponse; + } + + // 에러 응답 처리 + let errorResponse: ApiErrorResponse; + try { + errorResponse = (await response.json()) as ApiErrorResponse; + } catch { + // JSON 파싱 실패시 기본 에러 응답 생성 + errorResponse = { + code: String(response.status), + message: response.statusText || 'Unknown error occurred', + }; + } + + // 커스텀 에러 객체 생성 + const error = new Error( + ERROR_MESSAGES[errorResponse.code] || errorResponse.message, + ) as Error & { + status: number; + code: string; + response: ApiErrorResponse; + }; + + // 에러 객체에 상세 정보 추가 + error.status = response.status; + error.code = errorResponse.code; + error.response = errorResponse; + + throw error; + } catch (error) { + console.error('fetchInstance error:', error); + throw error; + } +} diff --git a/src/frontend/apps/web/src/shared/services/apis/fetch-method.api.ts b/src/frontend/apps/web/src/shared/services/apis/fetch-method.api.ts new file mode 100644 index 00000000..34d7ba4d --- /dev/null +++ b/src/frontend/apps/web/src/shared/services/apis/fetch-method.api.ts @@ -0,0 +1,157 @@ +import type { FetchOptions, JsonValue } from '@/src/shared/services/models'; + +import { fetchInstance } from './fetch-instance.api'; + +type RequestOptions = Omit, 'body'>; + +/** + * GET 요청을 보내는 함수입니다. + * @template TResponse - 응답 데이터의 타입 + * @typedef {object} GetRequestOptions + * @property {Record} [params] - URL 쿼리 파라미터 + * @property {RequestCache} [cache] - Next.js의 캐시 전략 ('force-cache' | 'no-store' | 'no-cache') + * @property {string[]} [tags] - 캐시 무효화를 위한 태그 배열 + * @property {number} [revalidate] - 캐시 재검증 주기(초) + * @property {boolean} [includeAuthToken] - 인증 토큰 사용 여부 + * + * @param {string} url - API 엔드포인트 URL + * @param {Omit} [options] - GET 요청 옵션 + * @returns {Promise} 응답 데이터를 포함한 Promise + * + * @example + * ```typescript + * // 기본 GET 요청 + * const data = await getRequest('/api/users/me'); + * + * // 쿼리 파라미터와 캐시 옵션과 토큰이 없을때 GET 요청 + * const users = await getRequest('/api/users', { + * params: { page: '1', size: '10' }, + * cache: 'force-cache', + * tags: ['users'], + * includeAuthToken: false, + * }); + * ``` + * + * * GET 요청을 보내는 함수입니다. + * @throws {ApiError} API 요청이 실패했을 때 발생합니다 (예: 400, 401, 404 등) + * @throws {NetworkError} 네트워크 연결에 문제가 있을 때 발생합니다 + */ + +export async function getRequest( + url: string, + options?: RequestOptions, +): Promise { + return fetchInstance(url, 'GET', options); +} + +/** + * POST 요청을 보내는 함수입니다. + * @template TResponse - 응답 데이터의 타입 + * @template TBody - 요청 본문의 타입 + * @typedef {object} PostRequestOptions + * @property {Record} [params] - URL 쿼리 파라미터 + * @property {string[]} [tags] - 캐시 무효화를 위한 태그 배열 + * @property {boolean} [includeAuthToken] - 인증 토큰 사용 여부 + * + * @param {string} url - API 엔드포인트 URL + * @param {TBody} body - 전송할 데이터 + * @param {Omit, 'body'>} [options] - POST 요청 옵션 + * @returns {Promise} 응답 데이터를 포함한 Promise + * + * @example + * ```typescript + * // 사용자 생성 요청 + * const newUser = await postRequest( + * '/api/users', + * { name: 'John', email: 'john@example.com' } + * ); + * ``` + * + * * POST 요청을 보내는 함수입니다. + * @throws {ApiError} API 요청이 실패했을 때 발생합니다 (예: 400, 401, 404 등) + * @throws {NetworkError} 네트워크 연결에 문제가 있을 때 발생합니다 + */ + +export async function postRequest( + url: string, + body: TBody, + options?: RequestOptions, +): Promise { + return fetchInstance(url, 'POST', { ...options, body }); +} + +/** + * PATCH 요청을 보내는 함수입니다. + * @template TResponse - 응답 데이터의 타입 + * @template TBody - 요청 본문의 타입 + * @typedef {object} PatchRequestOptions + * @property {Record} [params] - URL 쿼리 파라미터 + * @property {string[]} [tags] - 캐시 무효화를 위한 태그 배열 + * @property {boolean} [includeAuthToken] - 인증 토큰 사용 여부 + * + * @param {string} url - API 엔드포인트 URL + * @param {TBody} body - 업데이트할 데이터 + * @param {Omit, 'body'>} [options] - PATCH 요청 옵션 + * @returns {Promise} 응답 데이터를 포함한 Promise + * + * @example + * ```typescript + * // 사용자 정보 업데이트 + * const updatedUser = await patchRequest( + * `/api/users/${userId}`, + * { name: 'Updated Name' }, + * { tags: ['user-profile'] } + * ); + * ``` + * + * * PATCH 요청을 보내는 함수입니다. + * @throws {ApiError} API 요청이 실패했을 때 발생합니다 (예: 400, 401, 404 등) + * @throws {NetworkError} 네트워크 연결에 문제가 있을 때 발생합니다 + * + */ +export async function patchRequest( + url: string, + body: TBody, + options?: RequestOptions, +): Promise { + return fetchInstance(url, 'PATCH', { ...options, body }); +} + +/** + * DELETE 요청을 보내는 함수입니다. + * @template TResponse - 응답 데이터의 타입 + * @template TBody - 요청 본문의 타입 + * @typedef {object} DeleteRequestOptions + * @property {Record} [params] - URL 쿼리 파라미터 + * @property {string[]} [tags] - 캐시 무효화를 위한 태그 배열 + * @property {boolean} [includeAuthToken] - 인증 토큰 사용 여부 + * + * @param {string} url - API 엔드포인트 URL + * @param {TBody} [body] - 전송할 데이터 (선택사항) + * @param {Omit, 'body'>} [options] - DELETE 요청 옵션 + * @returns {Promise} 응답 데이터를 포함한 Promise + * + * @example + * ```typescript + * // 단순 삭제 요청 + * await deleteRequest(`/api/posts/${postId}`); + * + * // 본문과 함께 삭제 요청 + * await deleteRequest( + * `/api/posts/${postId}`, + * { reason: 'spam' }, + * { tags: ['posts'] } + * ); + * ``` + * + * * DELETE 요청을 보내는 함수입니다. + * @throws {ApiError} API 요청이 실패했을 때 발생합니다 (예: 400, 401, 404 등) + * @throws {NetworkError} 네트워크 연결에 문제가 있을 때 발생합니다 + */ +export async function deleteRequest( + url: string, + body?: TBody, + options?: RequestOptions, +): Promise { + return fetchInstance(url, 'DELETE', { ...options, body }); +} diff --git a/src/frontend/apps/web/src/shared/services/apis/index.ts b/src/frontend/apps/web/src/shared/services/apis/index.ts new file mode 100644 index 00000000..ef2621df --- /dev/null +++ b/src/frontend/apps/web/src/shared/services/apis/index.ts @@ -0,0 +1,7 @@ +export { fetchInstance } from './fetch-instance.api'; +export { + getRequest, + postRequest, + patchRequest, + deleteRequest, +} from './fetch-method.api'; diff --git a/src/frontend/apps/web/src/shared/services/index.ts b/src/frontend/apps/web/src/shared/services/index.ts new file mode 100644 index 00000000..20339832 --- /dev/null +++ b/src/frontend/apps/web/src/shared/services/index.ts @@ -0,0 +1,2 @@ +export * from './apis'; +export * from './models'; diff --git a/src/frontend/apps/web/src/shared/services/models/error/authorization-errors.ts b/src/frontend/apps/web/src/shared/services/models/error/authorization-errors.ts new file mode 100644 index 00000000..9728609d --- /dev/null +++ b/src/frontend/apps/web/src/shared/services/models/error/authorization-errors.ts @@ -0,0 +1,23 @@ +export type AuthErrorCode = keyof typeof AUTH_ERROR_MESSAGES; + +export const AUTH_ERROR_MESSAGES: Record = { + A40000: '요청한 값이 유효하지 않습니다.', + A40001: '잘못된 요청입니다.', + A40002: '필수 헤더가 누락되었습니다.', + A40003: '필수 파라미터가 누락되었습니다.', + A40004: '인가 코드가 만료되었습니다.', + A40005: '로그인 요청이 유효하지 않습니다.', + + A40100: '액세스 토큰이 만료되었습니다.', + A40101: '리프레시 토큰이 만료되었습니다.', + A40102: '리프레시 토큰이 유효하지 않습니다.', + A40103: '유효하지 않은 토큰입니다.', + A40104: '해당 유저의 리프레시 토큰이 존재하지 않습니다.', + + A40400: '존재하지 않는 API입니다.', + A40401: '해당 유저는 존재하지 않습니다.', + + A40500: '지원하지 않는 메소드입니다.', + + A50000: '서버 내부 오류입니다.', +} as const; diff --git a/src/frontend/apps/web/src/shared/services/models/error/index.ts b/src/frontend/apps/web/src/shared/services/models/error/index.ts new file mode 100644 index 00000000..82a3582e --- /dev/null +++ b/src/frontend/apps/web/src/shared/services/models/error/index.ts @@ -0,0 +1,11 @@ +import { AUTH_ERROR_MESSAGES } from './authorization-errors'; +import type { AuthErrorCode } from './authorization-errors'; +import { WORKSPACE_ERROR_MESSAGES } from './workspace-errors'; +import type { WorkspaceErrorCode } from './workspace-errors'; + +export type ErrorCode = AuthErrorCode | WorkspaceErrorCode; + +export const ERROR_MESSAGES: Record = { + ...AUTH_ERROR_MESSAGES, + ...WORKSPACE_ERROR_MESSAGES, +}; diff --git a/src/frontend/apps/web/src/shared/services/models/error/workspace-errors.ts b/src/frontend/apps/web/src/shared/services/models/error/workspace-errors.ts new file mode 100644 index 00000000..7d29c4cc --- /dev/null +++ b/src/frontend/apps/web/src/shared/services/models/error/workspace-errors.ts @@ -0,0 +1,22 @@ +export type WorkspaceErrorCode = keyof typeof WORKSPACE_ERROR_MESSAGES; + +export const WORKSPACE_ERROR_MESSAGES: Record = { + W00001: '알 수 없는 에러가 발생했습니다.', + W40001: '잘못된 요청입니다.', + W40002: '유효성 검증에 실패했습니다.', + W40003: '필수 파라미터가 누락되었습니다.', + W40004: '잘못된 파라미터가 포함되었습니다.', + W40005: '동일한 채널명이 이미 해당 워크스페이스에 존재합니다.', + W40006: '유저가 이미 해당 채널에 참여하고 있습니다', + + W40401: '등록되지 않은 워크스페이스입니다.', + W40402: '등록되지 않은 채널입니다.', + W40403: '등록되지 않은 유저입니다.', + + W50001: '서버 내부 오류가 발생했습니다.', + W50002: '데이터베이스 처리 중 오류가 발생했습니다.', + W50003: '파일 업로드에 실패했습니다.', + W50004: '파일 다운로드 중 오류가 발생했습니다.', + W50005: '파일 처리 중 오류가 발생했습니다.', + W50006: '예상치 못한 오류가 발생했습니다.', +} as const; diff --git a/src/frontend/apps/web/src/shared/services/models/index.ts b/src/frontend/apps/web/src/shared/services/models/index.ts new file mode 100644 index 00000000..c852b1f5 --- /dev/null +++ b/src/frontend/apps/web/src/shared/services/models/index.ts @@ -0,0 +1,2 @@ +export * from './error'; +export * from './types'; diff --git a/src/frontend/apps/web/src/shared/services/models/types/fetch-types.d.ts b/src/frontend/apps/web/src/shared/services/models/types/fetch-types.d.ts new file mode 100644 index 00000000..4a0231eb --- /dev/null +++ b/src/frontend/apps/web/src/shared/services/models/types/fetch-types.d.ts @@ -0,0 +1,23 @@ +export type HttpMethod = 'GET' | 'POST' | 'PATCH' | 'DELETE'; + +export type JsonPrimitive = string | number | boolean | null; +export type JsonArray = JsonValue[]; +export type JsonObject = { [key: string]: JsonValue }; +export type JsonValue = JsonPrimitive | JsonObject | JsonArray; + +export type FetchOptions = Omit & { + body?: TBody; + params?: Record; + cache?: RequestCache; + tags?: string[]; + revalidate?: number; + includeAuthToken?: boolean; +}; + +export type ApiResponse = { + code: string; + message: string; + data?: T; +}; + +export type ApiErrorResponse = Omit, 'data'>; diff --git a/src/frontend/apps/web/src/shared/services/models/types/index.ts b/src/frontend/apps/web/src/shared/services/models/types/index.ts new file mode 100644 index 00000000..9c42859d --- /dev/null +++ b/src/frontend/apps/web/src/shared/services/models/types/index.ts @@ -0,0 +1,10 @@ +export type { + HttpMethod, + JsonPrimitive, + JsonArray, + JsonObject, + JsonValue, + FetchOptions, + ApiErrorResponse, + ApiResponse, +} from './fetch-types.js'; diff --git a/src/frontend/packages/ui/package.json b/src/frontend/packages/ui/package.json index 2df7a6af..0d3e1b78 100644 --- a/src/frontend/packages/ui/package.json +++ b/src/frontend/packages/ui/package.json @@ -55,7 +55,7 @@ "storybook": "8.6.0-alpha.0", "tailwindcss": "^3.4.17", "typescript": "^5.7.3", - "vite": "^6.0.7" + "vite": "^6.0.9" }, "exports": { "./globals.css": "./src/styles/globals.css", diff --git a/src/frontend/pnpm-lock.yaml b/src/frontend/pnpm-lock.yaml index 2e7c30f4..ff03a8db 100644 --- a/src/frontend/pnpm-lock.yaml +++ b/src/frontend/pnpm-lock.yaml @@ -215,7 +215,7 @@ importers: version: 8.6.0-alpha.0(@storybook/test@8.6.0-alpha.0(storybook@8.6.0-alpha.0(prettier@3.4.2)))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(storybook@8.6.0-alpha.0(prettier@3.4.2))(typescript@5.7.3) '@storybook/react-vite': specifier: 8.6.0-alpha.0 - version: 8.6.0-alpha.0(@storybook/test@8.6.0-alpha.0(storybook@8.6.0-alpha.0(prettier@3.4.2)))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(rollup@4.31.0)(storybook@8.6.0-alpha.0(prettier@3.4.2))(typescript@5.7.3)(vite@6.0.7(@types/node@22.10.7)(jiti@1.21.7)(yaml@2.7.0)) + version: 8.6.0-alpha.0(@storybook/test@8.6.0-alpha.0(storybook@8.6.0-alpha.0(prettier@3.4.2)))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(rollup@4.31.0)(storybook@8.6.0-alpha.0(prettier@3.4.2))(typescript@5.7.3)(vite@6.0.11(@types/node@22.10.7)(jiti@1.21.7)(yaml@2.7.0)) '@storybook/test': specifier: 8.6.0-alpha.0 version: 8.6.0-alpha.0(storybook@8.6.0-alpha.0(prettier@3.4.2)) @@ -233,7 +233,7 @@ importers: version: 19.0.3(@types/react@19.0.7) '@vitejs/plugin-react': specifier: ^4.3.4 - version: 4.3.4(vite@6.0.7(@types/node@22.10.7)(jiti@1.21.7)(yaml@2.7.0)) + version: 4.3.4(vite@6.0.11(@types/node@22.10.7)(jiti@1.21.7)(yaml@2.7.0)) '@workspace/eslint-config': specifier: workspace:* version: link:../eslint-config @@ -256,8 +256,8 @@ importers: specifier: ^5.7.3 version: 5.7.3 vite: - specifier: ^6.0.7 - version: 6.0.7(@types/node@22.10.7)(jiti@1.21.7)(yaml@2.7.0) + specifier: ^6.0.9 + version: 6.0.11(@types/node@22.10.7)(jiti@1.21.7)(yaml@2.7.0) packages: @@ -4044,8 +4044,8 @@ packages: resolution: {integrity: sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - vite@6.0.7: - resolution: {integrity: sha512-RDt8r/7qx9940f8FcOIAH9PTViRrghKaK2K1jY3RaAURrEUbm9Du1mJ72G+jlhtG3WwodnfzY8ORQZbBavZEAQ==} + vite@6.0.11: + resolution: {integrity: sha512-4VL9mQPKoHy4+FE0NnRE/kbY51TOfaknxAjt3fJbGJxhIpBZiqVzlZDEesWWsuREXHwNdAoOFZ9MkPEVXczHwg==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true peerDependencies: @@ -4502,11 +4502,11 @@ snapshots: wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 - '@joshwooding/vite-plugin-react-docgen-typescript@0.4.2(typescript@5.7.3)(vite@6.0.7(@types/node@22.10.7)(jiti@1.21.7)(yaml@2.7.0))': + '@joshwooding/vite-plugin-react-docgen-typescript@0.4.2(typescript@5.7.3)(vite@6.0.11(@types/node@22.10.7)(jiti@1.21.7)(yaml@2.7.0))': dependencies: magic-string: 0.27.0 react-docgen-typescript: 2.2.2(typescript@5.7.3) - vite: 6.0.7(@types/node@22.10.7)(jiti@1.21.7)(yaml@2.7.0) + vite: 6.0.11(@types/node@22.10.7)(jiti@1.21.7)(yaml@2.7.0) optionalDependencies: typescript: 5.7.3 @@ -5218,13 +5218,13 @@ snapshots: react: 19.0.0 react-dom: 19.0.0(react@19.0.0) - '@storybook/builder-vite@8.6.0-alpha.0(storybook@8.6.0-alpha.0(prettier@3.4.2))(vite@6.0.7(@types/node@22.10.7)(jiti@1.21.7)(yaml@2.7.0))': + '@storybook/builder-vite@8.6.0-alpha.0(storybook@8.6.0-alpha.0(prettier@3.4.2))(vite@6.0.11(@types/node@22.10.7)(jiti@1.21.7)(yaml@2.7.0))': dependencies: '@storybook/csf-plugin': 8.6.0-alpha.0(storybook@8.6.0-alpha.0(prettier@3.4.2)) browser-assert: 1.2.1 storybook: 8.6.0-alpha.0(prettier@3.4.2) ts-dedent: 2.2.0 - vite: 6.0.7(@types/node@22.10.7)(jiti@1.21.7)(yaml@2.7.0) + vite: 6.0.11(@types/node@22.10.7)(jiti@1.21.7)(yaml@2.7.0) '@storybook/components@8.6.0-alpha.0(storybook@8.6.0-alpha.0(prettier@3.4.2))': dependencies: @@ -5297,11 +5297,11 @@ snapshots: react-dom: 19.0.0(react@19.0.0) storybook: 8.6.0-alpha.0(prettier@3.4.2) - '@storybook/react-vite@8.6.0-alpha.0(@storybook/test@8.6.0-alpha.0(storybook@8.6.0-alpha.0(prettier@3.4.2)))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(rollup@4.31.0)(storybook@8.6.0-alpha.0(prettier@3.4.2))(typescript@5.7.3)(vite@6.0.7(@types/node@22.10.7)(jiti@1.21.7)(yaml@2.7.0))': + '@storybook/react-vite@8.6.0-alpha.0(@storybook/test@8.6.0-alpha.0(storybook@8.6.0-alpha.0(prettier@3.4.2)))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(rollup@4.31.0)(storybook@8.6.0-alpha.0(prettier@3.4.2))(typescript@5.7.3)(vite@6.0.11(@types/node@22.10.7)(jiti@1.21.7)(yaml@2.7.0))': dependencies: - '@joshwooding/vite-plugin-react-docgen-typescript': 0.4.2(typescript@5.7.3)(vite@6.0.7(@types/node@22.10.7)(jiti@1.21.7)(yaml@2.7.0)) + '@joshwooding/vite-plugin-react-docgen-typescript': 0.4.2(typescript@5.7.3)(vite@6.0.11(@types/node@22.10.7)(jiti@1.21.7)(yaml@2.7.0)) '@rollup/pluginutils': 5.1.4(rollup@4.31.0) - '@storybook/builder-vite': 8.6.0-alpha.0(storybook@8.6.0-alpha.0(prettier@3.4.2))(vite@6.0.7(@types/node@22.10.7)(jiti@1.21.7)(yaml@2.7.0)) + '@storybook/builder-vite': 8.6.0-alpha.0(storybook@8.6.0-alpha.0(prettier@3.4.2))(vite@6.0.11(@types/node@22.10.7)(jiti@1.21.7)(yaml@2.7.0)) '@storybook/react': 8.6.0-alpha.0(@storybook/test@8.6.0-alpha.0(storybook@8.6.0-alpha.0(prettier@3.4.2)))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(storybook@8.6.0-alpha.0(prettier@3.4.2))(typescript@5.7.3) find-up: 5.0.0 magic-string: 0.30.17 @@ -5311,7 +5311,7 @@ snapshots: resolve: 1.22.10 storybook: 8.6.0-alpha.0(prettier@3.4.2) tsconfig-paths: 4.2.0 - vite: 6.0.7(@types/node@22.10.7)(jiti@1.21.7)(yaml@2.7.0) + vite: 6.0.11(@types/node@22.10.7)(jiti@1.21.7)(yaml@2.7.0) optionalDependencies: '@storybook/test': 8.6.0-alpha.0(storybook@8.6.0-alpha.0(prettier@3.4.2)) transitivePeerDependencies: @@ -5593,14 +5593,14 @@ snapshots: '@ungap/structured-clone@1.3.0': {} - '@vitejs/plugin-react@4.3.4(vite@6.0.7(@types/node@22.10.7)(jiti@1.21.7)(yaml@2.7.0))': + '@vitejs/plugin-react@4.3.4(vite@6.0.11(@types/node@22.10.7)(jiti@1.21.7)(yaml@2.7.0))': dependencies: '@babel/core': 7.26.0 '@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.26.0) '@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.26.0) '@types/babel__core': 7.20.5 react-refresh: 0.14.2 - vite: 6.0.7(@types/node@22.10.7)(jiti@1.21.7)(yaml@2.7.0) + vite: 6.0.11(@types/node@22.10.7)(jiti@1.21.7)(yaml@2.7.0) transitivePeerDependencies: - supports-color @@ -8493,7 +8493,7 @@ snapshots: validate-npm-package-name@5.0.1: {} - vite@6.0.7(@types/node@22.10.7)(jiti@1.21.7)(yaml@2.7.0): + vite@6.0.11(@types/node@22.10.7)(jiti@1.21.7)(yaml@2.7.0): dependencies: esbuild: 0.24.2 postcss: 8.5.1