From 56338373b6c8489a947580a0b6b745b84cb9550b Mon Sep 17 00:00:00 2001 From: leeleeleeleejun Date: Fri, 15 Aug 2025 23:10:55 +0900 Subject: [PATCH 01/10] =?UTF-8?q?remove:=20public=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EA=B8=B0=EB=B3=B8=20=EC=9D=B4=EB=AF=B8=EC=A7=80=EB=93=A4=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/public/circles.svg | 17 ----------------- apps/web/public/next.svg | 1 - apps/web/public/turborepo.svg | 32 -------------------------------- apps/web/public/vercel.svg | 1 - 4 files changed, 51 deletions(-) delete mode 100644 apps/web/public/circles.svg delete mode 100644 apps/web/public/next.svg delete mode 100644 apps/web/public/turborepo.svg delete mode 100644 apps/web/public/vercel.svg diff --git a/apps/web/public/circles.svg b/apps/web/public/circles.svg deleted file mode 100644 index 6533be5..0000000 --- a/apps/web/public/circles.svg +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/apps/web/public/next.svg b/apps/web/public/next.svg deleted file mode 100644 index 5174b28..0000000 --- a/apps/web/public/next.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/apps/web/public/turborepo.svg b/apps/web/public/turborepo.svg deleted file mode 100644 index 2f9aa1f..0000000 --- a/apps/web/public/turborepo.svg +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/apps/web/public/vercel.svg b/apps/web/public/vercel.svg deleted file mode 100644 index d2f8422..0000000 --- a/apps/web/public/vercel.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file From 21e59322f60127e0cd6268a5d4eab1b9b3b60c1c Mon Sep 17 00:00:00 2001 From: leeleeleeleejun Date: Fri, 15 Aug 2025 23:11:09 +0900 Subject: [PATCH 02/10] =?UTF-8?q?fix:=20tsconfig.json=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EA=B2=BD=EB=A1=9C=20alias=EB=A5=BC=20app/*=EB=A1=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json index fe120fc..c0f0d59 100644 --- a/apps/web/tsconfig.json +++ b/apps/web/tsconfig.json @@ -10,7 +10,7 @@ /* alias */ "baseUrl": ".", "paths": { - "@/*": ["src/*"], + "@/*": ["app/*"], "@repo/ui/components/*": ["../../packages/ui/src/components/*"] }, From eb2a6a760d4551d7814db7d1617ccd1d02a52497 Mon Sep 17 00:00:00 2001 From: leeleeleeleejun Date: Sat, 16 Aug 2025 00:16:30 +0900 Subject: [PATCH 03/10] =?UTF-8?q?fix:=20.gitignore=EC=97=90=20.env=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index b215d7a..19d81c9 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,7 @@ yarn-debug.log* yarn-error.log* # local env files +.env .env.local .env.development.local .env.test.local From 23ec850fea8b0cf82ff4dcfe4e1149637b688702 Mon Sep 17 00:00:00 2001 From: leeleeleeleejun Date: Sat, 16 Aug 2025 15:08:49 +0900 Subject: [PATCH 04/10] =?UTF-8?q?feat:=20MSW=20=EC=84=A4=EC=B9=98=20?= =?UTF-8?q?=EB=B0=8F=20=EC=B4=88=EA=B8=B0=20=EC=84=A4=EC=A0=95=20=20-=20te?= =?UTF-8?q?st=20category=20data=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/app/_constants/path.ts | 3 + apps/web/app/_mocks/MSWProvider.tsx | 23 ++ apps/web/app/_mocks/data/category.ts | 17 + .../app/_mocks/handlers/categoryHandlers.ts | 9 + apps/web/app/_mocks/handlers/index.ts | 3 + apps/web/app/_mocks/initMSW.ts | 20 + apps/web/app/_mocks/server.ts | 4 + apps/web/app/_mocks/worker.ts | 4 + apps/web/app/layout.tsx | 24 +- apps/web/package.json | 11 +- apps/web/public/mockServiceWorker.js | 344 ++++++++++++++++++ pnpm-lock.yaml | 274 +++++++++++++- turbo.json | 1 + 13 files changed, 722 insertions(+), 15 deletions(-) create mode 100644 apps/web/app/_constants/path.ts create mode 100644 apps/web/app/_mocks/MSWProvider.tsx create mode 100644 apps/web/app/_mocks/data/category.ts create mode 100644 apps/web/app/_mocks/handlers/categoryHandlers.ts create mode 100644 apps/web/app/_mocks/handlers/index.ts create mode 100644 apps/web/app/_mocks/initMSW.ts create mode 100644 apps/web/app/_mocks/server.ts create mode 100644 apps/web/app/_mocks/worker.ts create mode 100644 apps/web/public/mockServiceWorker.js diff --git a/apps/web/app/_constants/path.ts b/apps/web/app/_constants/path.ts new file mode 100644 index 0000000..a4a0fe9 --- /dev/null +++ b/apps/web/app/_constants/path.ts @@ -0,0 +1,3 @@ +export const API_PATH = { + CATEGORY: '/categories', +} diff --git a/apps/web/app/_mocks/MSWProvider.tsx b/apps/web/app/_mocks/MSWProvider.tsx new file mode 100644 index 0000000..af923aa --- /dev/null +++ b/apps/web/app/_mocks/MSWProvider.tsx @@ -0,0 +1,23 @@ +'use client' + +import { useEffect, useState } from 'react' +import { initBrowserMSW } from '@/_mocks/initMSW' + +export function MSWProvider({ children }: { children: React.ReactNode }) { + const [mswReady, setMSWReady] = useState(false) + + useEffect(() => { + const init = async () => { + await initBrowserMSW() + setMSWReady(true) + } + + if (!mswReady) { + init() + } + }, [mswReady]) + + if (!mswReady) return null + + return <>{children} +} diff --git a/apps/web/app/_mocks/data/category.ts b/apps/web/app/_mocks/data/category.ts new file mode 100644 index 0000000..dfedaf0 --- /dev/null +++ b/apps/web/app/_mocks/data/category.ts @@ -0,0 +1,17 @@ +export const category = [ + { id: 1, name: '치킨', iconKey: 'chicken' }, + { id: 2, name: '햄버거', iconKey: 'burger' }, + { id: 3, name: '한식', iconKey: 'korean' }, + { id: 4, name: '분식', iconKey: 'bunsik' }, + { id: 5, name: '중식', iconKey: 'chinese' }, + { id: 6, name: '양식', iconKey: 'western' }, + { id: 7, name: '샐러드', iconKey: 'salad' }, + { id: 8, name: '일식', iconKey: 'japanese' }, + { id: 9, name: '카페', iconKey: 'cafe' }, + { id: 10, name: '피자', iconKey: 'pizza' }, + { id: 11, name: '멕시칸', iconKey: 'mexican' }, + { id: 12, name: '아시안', iconKey: 'asian' }, + { id: 13, name: '찜·탕', iconKey: 'soup' }, + { id: 14, name: '고기·구이', iconKey: 'meat' }, + { id: 15, name: '도시락', iconKey: 'lunchbox' }, +] diff --git a/apps/web/app/_mocks/handlers/categoryHandlers.ts b/apps/web/app/_mocks/handlers/categoryHandlers.ts new file mode 100644 index 0000000..076de31 --- /dev/null +++ b/apps/web/app/_mocks/handlers/categoryHandlers.ts @@ -0,0 +1,9 @@ +import { http, HttpResponse } from 'msw' +import { category } from '../data/category' +import { API_PATH } from '@/_constants/path' + +export const CategoryHandlers = [ + http.get('https://example.com' + API_PATH.CATEGORY, () => { + return HttpResponse.json(category) + }), +] diff --git a/apps/web/app/_mocks/handlers/index.ts b/apps/web/app/_mocks/handlers/index.ts new file mode 100644 index 0000000..31e9e0b --- /dev/null +++ b/apps/web/app/_mocks/handlers/index.ts @@ -0,0 +1,3 @@ +import { CategoryHandlers } from '@/_mocks/handlers/categoryHandlers' + +export const handlers = [...CategoryHandlers] diff --git a/apps/web/app/_mocks/initMSW.ts b/apps/web/app/_mocks/initMSW.ts new file mode 100644 index 0000000..9cc0245 --- /dev/null +++ b/apps/web/app/_mocks/initMSW.ts @@ -0,0 +1,20 @@ +export const initServerMSW = () => { + // 서버에서만 실행되는 MSW 초기화 + if (typeof window === 'undefined' && process.env.NODE_ENV === 'development') { + import('./server').then(({ server }) => { + server.listen({ + onUnhandledRequest: 'warn', + }) + console.log('✅ MSW 서버 시작됨') + }) + } +} + +export const initBrowserMSW = async () => { + if (typeof window !== 'undefined') { + const { worker } = await import('./worker') + await worker.start({ + onUnhandledRequest: 'bypass', + }) + } +} diff --git a/apps/web/app/_mocks/server.ts b/apps/web/app/_mocks/server.ts new file mode 100644 index 0000000..86f7d61 --- /dev/null +++ b/apps/web/app/_mocks/server.ts @@ -0,0 +1,4 @@ +import { setupServer } from 'msw/node' +import { handlers } from './handlers' + +export const server = setupServer(...handlers) diff --git a/apps/web/app/_mocks/worker.ts b/apps/web/app/_mocks/worker.ts new file mode 100644 index 0000000..4dd03f0 --- /dev/null +++ b/apps/web/app/_mocks/worker.ts @@ -0,0 +1,4 @@ +import { setupWorker } from 'msw/browser' +import { handlers } from './handlers' + +export const worker = setupWorker(...handlers) diff --git a/apps/web/app/layout.tsx b/apps/web/app/layout.tsx index 1deb397..c770c82 100644 --- a/apps/web/app/layout.tsx +++ b/apps/web/app/layout.tsx @@ -3,6 +3,8 @@ import './globals.css' import type { Metadata } from 'next' import QueryProvider from './QueryClientProvider' import localFont from 'next/font/local' +import { initServerMSW } from '@/_mocks/initMSW' +import { MSWProvider } from '@/_mocks/MSWProvider' import { Column } from '@repo/ui/components/Layout/Column' export const metadata: Metadata = { @@ -16,21 +18,25 @@ const pretendard = localFont({ weight: '45 920', }) -export default function RootLayout({ +export default async function RootLayout({ children, }: { children: React.ReactNode }) { + initServerMSW() + return ( - + - -
- - {children} - -
-
+ + +
+ + {children} + +
+
+
) diff --git a/apps/web/package.json b/apps/web/package.json index a460802..238192b 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -3,6 +3,9 @@ "version": "1.0.0", "type": "module", "private": true, + "msw": { + "workerDirectory": "./public" + }, "scripts": { "dev": "next dev --port 3000 --turbopack", "build": "next build", @@ -40,10 +43,16 @@ "autoprefixer": "^10.4.20", "eslint": "^9.32.0", "jsdom": "^26.1.0", + "msw": "^2.10.5", "postcss": "^8.5.3", "tailwindcss": "^4.1.5", "typescript": "5.8.2", "vite-tsconfig-paths": "^5.1.4", "vitest": "^3.2.4" + }, + "msw": { + "workerDirectory": [ + "public" + ] } -} +} \ No newline at end of file diff --git a/apps/web/public/mockServiceWorker.js b/apps/web/public/mockServiceWorker.js new file mode 100644 index 0000000..723b071 --- /dev/null +++ b/apps/web/public/mockServiceWorker.js @@ -0,0 +1,344 @@ +/* eslint-disable */ +/* tslint:disable */ + +/** + * Mock Service Worker. + * @see https://github.com/mswjs/msw + * - Please do NOT modify this file. + */ + +const PACKAGE_VERSION = '2.10.5' +const INTEGRITY_CHECKSUM = 'f5825c521429caf22a4dd13b66e243af' +const IS_MOCKED_RESPONSE = Symbol('isMockedResponse') +const activeClientIds = new Set() + +addEventListener('install', function () { + self.skipWaiting() +}) + +addEventListener('activate', function (event) { + event.waitUntil(self.clients.claim()) +}) + +addEventListener('message', async function (event) { + const clientId = Reflect.get(event.source || {}, 'id') + + if (!clientId || !self.clients) { + return + } + + const client = await self.clients.get(clientId) + + if (!client) { + return + } + + const allClients = await self.clients.matchAll({ + type: 'window', + }) + + switch (event.data) { + case 'KEEPALIVE_REQUEST': { + sendToClient(client, { + type: 'KEEPALIVE_RESPONSE', + }) + break + } + + case 'INTEGRITY_CHECK_REQUEST': { + sendToClient(client, { + type: 'INTEGRITY_CHECK_RESPONSE', + payload: { + packageVersion: PACKAGE_VERSION, + checksum: INTEGRITY_CHECKSUM, + }, + }) + break + } + + case 'MOCK_ACTIVATE': { + activeClientIds.add(clientId) + + sendToClient(client, { + type: 'MOCKING_ENABLED', + payload: { + client: { + id: client.id, + frameType: client.frameType, + }, + }, + }) + break + } + + case 'MOCK_DEACTIVATE': { + activeClientIds.delete(clientId) + break + } + + case 'CLIENT_CLOSED': { + activeClientIds.delete(clientId) + + const remainingClients = allClients.filter((client) => { + return client.id !== clientId + }) + + // Unregister itself when there are no more clients + if (remainingClients.length === 0) { + self.registration.unregister() + } + + break + } + } +}) + +addEventListener('fetch', function (event) { + // Bypass navigation requests. + if (event.request.mode === 'navigate') { + return + } + + // Opening the DevTools triggers the "only-if-cached" request + // that cannot be handled by the worker. Bypass such requests. + if ( + event.request.cache === 'only-if-cached' && + event.request.mode !== 'same-origin' + ) { + return + } + + // Bypass all requests when there are no active clients. + // Prevents the self-unregistered worked from handling requests + // after it's been deleted (still remains active until the next reload). + if (activeClientIds.size === 0) { + return + } + + const requestId = crypto.randomUUID() + event.respondWith(handleRequest(event, requestId)) +}) + +/** + * @param {FetchEvent} event + * @param {string} requestId + */ +async function handleRequest(event, requestId) { + const client = await resolveMainClient(event) + const requestCloneForEvents = event.request.clone() + const response = await getResponse(event, client, requestId) + + // Send back the response clone for the "response:*" life-cycle events. + // Ensure MSW is active and ready to handle the message, otherwise + // this message will pend indefinitely. + if (client && activeClientIds.has(client.id)) { + const serializedRequest = await serializeRequest(requestCloneForEvents) + + // Clone the response so both the client and the library could consume it. + const responseClone = response.clone() + + sendToClient( + client, + { + type: 'RESPONSE', + payload: { + isMockedResponse: IS_MOCKED_RESPONSE in response, + request: { + id: requestId, + ...serializedRequest, + }, + response: { + type: responseClone.type, + status: responseClone.status, + statusText: responseClone.statusText, + headers: Object.fromEntries(responseClone.headers.entries()), + body: responseClone.body, + }, + }, + }, + responseClone.body ? [serializedRequest.body, responseClone.body] : [], + ) + } + + return response +} + +/** + * Resolve the main client for the given event. + * Client that issues a request doesn't necessarily equal the client + * that registered the worker. It's with the latter the worker should + * communicate with during the response resolving phase. + * @param {FetchEvent} event + * @returns {Promise} + */ +async function resolveMainClient(event) { + const client = await self.clients.get(event.clientId) + + if (activeClientIds.has(event.clientId)) { + return client + } + + if (client?.frameType === 'top-level') { + return client + } + + const allClients = await self.clients.matchAll({ + type: 'window', + }) + + return allClients + .filter((client) => { + // Get only those clients that are currently visible. + return client.visibilityState === 'visible' + }) + .find((client) => { + // Find the client ID that's recorded in the + // set of clients that have registered the worker. + return activeClientIds.has(client.id) + }) +} + +/** + * @param {FetchEvent} event + * @param {Client | undefined} client + * @param {string} requestId + * @returns {Promise} + */ +async function getResponse(event, client, requestId) { + // Clone the request because it might've been already used + // (i.e. its body has been read and sent to the client). + const requestClone = event.request.clone() + + function passthrough() { + // Cast the request headers to a new Headers instance + // so the headers can be manipulated with. + const headers = new Headers(requestClone.headers) + + // Remove the "accept" header value that marked this request as passthrough. + // This prevents request alteration and also keeps it compliant with the + // user-defined CORS policies. + const acceptHeader = headers.get('accept') + if (acceptHeader) { + const values = acceptHeader.split(',').map((value) => value.trim()) + const filteredValues = values.filter( + (value) => value !== 'msw/passthrough', + ) + + if (filteredValues.length > 0) { + headers.set('accept', filteredValues.join(', ')) + } else { + headers.delete('accept') + } + } + + return fetch(requestClone, { headers }) + } + + // Bypass mocking when the client is not active. + if (!client) { + return passthrough() + } + + // Bypass initial page load requests (i.e. static assets). + // The absence of the immediate/parent client in the map of the active clients + // means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet + // and is not ready to handle requests. + if (!activeClientIds.has(client.id)) { + return passthrough() + } + + // Notify the client that a request has been intercepted. + const serializedRequest = await serializeRequest(event.request) + const clientMessage = await sendToClient( + client, + { + type: 'REQUEST', + payload: { + id: requestId, + ...serializedRequest, + }, + }, + [serializedRequest.body], + ) + + switch (clientMessage.type) { + case 'MOCK_RESPONSE': { + return respondWithMock(clientMessage.data) + } + + case 'PASSTHROUGH': { + return passthrough() + } + } + + return passthrough() +} + +/** + * @param {Client} client + * @param {any} message + * @param {Array} transferrables + * @returns {Promise} + */ +function sendToClient(client, message, transferrables = []) { + return new Promise((resolve, reject) => { + const channel = new MessageChannel() + + channel.port1.onmessage = (event) => { + if (event.data && event.data.error) { + return reject(event.data.error) + } + + resolve(event.data) + } + + client.postMessage(message, [ + channel.port2, + ...transferrables.filter(Boolean), + ]) + }) +} + +/** + * @param {Response} response + * @returns {Response} + */ +function respondWithMock(response) { + // Setting response status code to 0 is a no-op. + // However, when responding with a "Response.error()", the produced Response + // instance will have status code set to 0. Since it's not possible to create + // a Response instance with status code 0, handle that use-case separately. + if (response.status === 0) { + return Response.error() + } + + const mockedResponse = new Response(response.body, response) + + Reflect.defineProperty(mockedResponse, IS_MOCKED_RESPONSE, { + value: true, + enumerable: true, + }) + + return mockedResponse +} + +/** + * @param {Request} request + */ +async function serializeRequest(request) { + return { + url: request.url, + mode: request.mode, + method: request.method, + headers: Object.fromEntries(request.headers.entries()), + cache: request.cache, + credentials: request.credentials, + destination: request.destination, + integrity: request.integrity, + redirect: request.redirect, + referrer: request.referrer, + referrerPolicy: request.referrerPolicy, + body: await request.arrayBuffer(), + keepalive: request.keepalive, + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6ef3ab6..171a701 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -190,6 +190,9 @@ importers: jsdom: specifier: ^26.1.0 version: 26.1.0 + msw: + specifier: ^2.10.5 + version: 2.10.5(@types/node@22.15.30)(typescript@5.8.2) postcss: specifier: ^8.5.3 version: 8.5.3 @@ -204,7 +207,7 @@ importers: version: 5.1.4(typescript@5.8.2)(vite@7.0.6) vitest: specifier: ^3.2.4 - version: 3.2.4(@types/node@22.15.30)(jsdom@26.1.0) + version: 3.2.4(@types/node@22.15.30)(jsdom@26.1.0)(msw@2.10.5) packages/eslint-config: devDependencies: @@ -1540,6 +1543,25 @@ packages: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.27.1 + /@bundled-es-modules/cookie@2.0.1: + resolution: {integrity: sha512-8o+5fRPLNbjbdGRRmJj3h6Hh1AQJf2dk3qQ/5ZFb+PXkRNiSoMGGUKlsgLfrxneb72axVJyIYji64E2+nNfYyw==} + dependencies: + cookie: 0.7.2 + dev: true + + /@bundled-es-modules/statuses@1.0.1: + resolution: {integrity: sha512-yn7BklA5acgcBr+7w064fGV+SGIFySjCKpqjcWgBAIfrAkY+4GQTJJHQMeT3V/sgz23VTEVV8TtOmkvJAhFVfg==} + dependencies: + statuses: 2.0.2 + dev: true + + /@bundled-es-modules/tough-cookie@0.1.6: + resolution: {integrity: sha512-dvMHbL464C0zI+Yqxbz6kZ5TOEp7GLW+pry/RWndAR8MJQAXZ2rPmIs8tziTZjeIyhSNZgZbCePtfSbdWqStJw==} + dependencies: + '@types/tough-cookie': 4.0.5 + tough-cookie: 4.1.4 + dev: true + /@chromatic-com/storybook@4.1.0(storybook@9.1.0): resolution: {integrity: sha512-B9XesFX5lQUdP81/QBTtkiYOFqEsJwQpzkZlcYPm2n/L1S/8ZabSPbz6NoY8hOJTXWZ2p7grygUlxyGy+gAvfQ==} engines: {node: '>=20.0.0', yarn: '>=1.22.18'} @@ -2312,6 +2334,57 @@ packages: requiresBuild: true optional: true + /@inquirer/confirm@5.1.14(@types/node@22.15.30): + resolution: {integrity: sha512-5yR4IBfe0kXe59r1YCTG8WXkUbl7Z35HK87Sw+WUyGD8wNUx7JvY7laahzeytyE1oLn74bQnL7hstctQxisQ8Q==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + dependencies: + '@inquirer/core': 10.1.15(@types/node@22.15.30) + '@inquirer/type': 3.0.8(@types/node@22.15.30) + '@types/node': 22.15.30 + dev: true + + /@inquirer/core@10.1.15(@types/node@22.15.30): + resolution: {integrity: sha512-8xrp836RZvKkpNbVvgWUlxjT4CraKk2q+I3Ksy+seI2zkcE+y6wNs1BVhgcv8VyImFecUhdQrYLdW32pAjwBdA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + dependencies: + '@inquirer/figures': 1.0.13 + '@inquirer/type': 3.0.8(@types/node@22.15.30) + '@types/node': 22.15.30 + ansi-escapes: 4.3.2 + cli-width: 4.1.0 + mute-stream: 2.0.0 + signal-exit: 4.1.0 + wrap-ansi: 6.2.0 + yoctocolors-cjs: 2.1.2 + dev: true + + /@inquirer/figures@1.0.13: + resolution: {integrity: sha512-lGPVU3yO9ZNqA7vTYz26jny41lE7yoQansmqdMLBEfqaGsmdg7V3W9mK9Pvb5IL4EVZ9GnSDGMO/cJXud5dMaw==} + engines: {node: '>=18'} + dev: true + + /@inquirer/type@3.0.8(@types/node@22.15.30): + resolution: {integrity: sha512-lg9Whz8onIHRthWaN1Q9EGLa/0LFJjyM8mEUbL1eTi6yMGvBf8gvyDLtxSXztQsxMvhxxNpJYrwa1YHdq+w4Jw==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + dependencies: + '@types/node': 22.15.30 + dev: true + /@jridgewell/gen-mapping@0.3.12: resolution: {integrity: sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==} dependencies: @@ -2349,6 +2422,18 @@ packages: react: 19.1.0 dev: true + /@mswjs/interceptors@0.39.6: + resolution: {integrity: sha512-bndDP83naYYkfayr/qhBHMhk0YGwS1iv6vaEGcr0SQbO0IZtbOPqjKjds/WcG+bJA+1T5vCx6kprKOzn5Bg+Vw==} + engines: {node: '>=18'} + dependencies: + '@open-draft/deferred-promise': 2.2.0 + '@open-draft/logger': 0.3.0 + '@open-draft/until': 2.1.0 + is-node-process: 1.2.0 + outvariant: 1.4.3 + strict-event-emitter: 0.5.1 + dev: true + /@napi-rs/wasm-runtime@0.2.12: resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==} requiresBuild: true @@ -2553,6 +2638,21 @@ packages: engines: {node: '>=12.4.0'} dev: true + /@open-draft/deferred-promise@2.2.0: + resolution: {integrity: sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==} + dev: true + + /@open-draft/logger@0.3.0: + resolution: {integrity: sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==} + dependencies: + is-node-process: 1.2.0 + outvariant: 1.4.3 + dev: true + + /@open-draft/until@2.1.0: + resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==} + dev: true + /@parcel/watcher-android-arm64@2.5.1: resolution: {integrity: sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==} engines: {node: '>= 10.0.0'} @@ -3657,6 +3757,10 @@ packages: '@types/node': 22.15.30 dev: true + /@types/cookie@0.6.0: + resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} + dev: true + /@types/deep-eql@4.0.2: resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} dev: true @@ -3736,6 +3840,14 @@ packages: resolution: {integrity: sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA==} dev: true + /@types/statuses@2.0.6: + resolution: {integrity: sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==} + dev: true + + /@types/tough-cookie@4.0.5: + resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==} + dev: true + /@typescript-eslint/eslint-plugin@8.37.0(@typescript-eslint/parser@8.37.0)(eslint@9.32.0)(typescript@5.8.2): resolution: {integrity: sha512-jsuVWeIkb6ggzB+wPCsR4e6loj+rM72ohW6IBn2C+5NCvfUVY8s33iFPySSVXqtm5Hu29Ne/9bnA0JmyLmgenA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -4201,7 +4313,7 @@ packages: tinyrainbow: 2.0.0 dev: true - /@vitest/mocker@3.2.4(vite@7.0.6): + /@vitest/mocker@3.2.4(msw@2.10.5)(vite@7.0.6): resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==} peerDependencies: msw: ^2.4.9 @@ -4215,6 +4327,7 @@ packages: '@vitest/spy': 3.2.4 estree-walker: 3.0.3 magic-string: 0.30.17 + msw: 2.10.5(@types/node@22.15.30)(typescript@5.8.2) vite: 7.0.6(@types/node@22.15.30) dev: true @@ -4464,6 +4577,13 @@ packages: require-from-string: 2.0.2 dev: true + /ansi-escapes@4.3.2: + resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} + engines: {node: '>=8'} + dependencies: + type-fest: 0.21.3 + dev: true + /ansi-html-community@0.0.8: resolution: {integrity: sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==} engines: {'0': node >= 0.8.0} @@ -5055,6 +5175,11 @@ packages: source-map: 0.6.1 dev: true + /cli-width@4.1.0: + resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} + engines: {node: '>= 12'} + dev: true + /client-only@0.0.1: resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} @@ -5171,6 +5296,11 @@ packages: /convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + /cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + dev: true + /core-js-compat@3.44.0: resolution: {integrity: sha512-JepmAj2zfl6ogy34qfWtcE7nHKAJnKsQFRn++scjVS2bZFllwptzw61BZcZFYBPpUznLfAvh0LGhxKppk04ClA==} dependencies: @@ -6704,6 +6834,11 @@ packages: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} dev: true + /graphql@16.11.0: + resolution: {integrity: sha512-mS1lbMsxgQj6hge1XZ6p7GPhbrtFwUFYi3wRzXAC/FmYnyXMTvvI3td3rjmQ2u8ewXueaSvRPWaEcgVVOT9Jnw==} + engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} + dev: true + /has-bigints@1.1.0: resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} engines: {node: '>= 0.4'} @@ -6772,6 +6907,10 @@ packages: hasBin: true dev: true + /headers-polyfill@4.0.3: + resolution: {integrity: sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ==} + dev: true + /hmac-drbg@1.0.1: resolution: {integrity: sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==} dependencies: @@ -7094,6 +7233,10 @@ packages: engines: {node: '>= 0.4'} dev: true + /is-node-process@1.2.0: + resolution: {integrity: sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==} + dev: true + /is-number-object@1.1.1: resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} engines: {node: '>= 0.4'} @@ -7766,6 +7909,45 @@ packages: /ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + /msw@2.10.5(@types/node@22.15.30)(typescript@5.8.2): + resolution: {integrity: sha512-0EsQCrCI1HbhpBWd89DvmxY6plmvrM96b0sCIztnvcNHQbXn5vqwm1KlXslo6u4wN9LFGLC1WFjjgljcQhe40A==} + engines: {node: '>=18'} + hasBin: true + requiresBuild: true + peerDependencies: + typescript: '>= 4.8.x' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@bundled-es-modules/cookie': 2.0.1 + '@bundled-es-modules/statuses': 1.0.1 + '@bundled-es-modules/tough-cookie': 0.1.6 + '@inquirer/confirm': 5.1.14(@types/node@22.15.30) + '@mswjs/interceptors': 0.39.6 + '@open-draft/deferred-promise': 2.2.0 + '@open-draft/until': 2.1.0 + '@types/cookie': 0.6.0 + '@types/statuses': 2.0.6 + graphql: 16.11.0 + headers-polyfill: 4.0.3 + is-node-process: 1.2.0 + outvariant: 1.4.3 + path-to-regexp: 6.3.0 + picocolors: 1.1.1 + strict-event-emitter: 0.5.1 + type-fest: 4.41.0 + typescript: 5.8.2 + yargs: 17.7.2 + transitivePeerDependencies: + - '@types/node' + dev: true + + /mute-stream@2.0.0: + resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==} + engines: {node: ^18.17.0 || >=20.5.0} + dev: true + /nanoid@3.3.11: resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -8098,6 +8280,10 @@ packages: resolution: {integrity: sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A==} dev: true + /outvariant@1.4.3: + resolution: {integrity: sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==} + dev: true + /own-keys@1.0.1: resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} engines: {node: '>= 0.4'} @@ -8235,6 +8421,10 @@ packages: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} dev: true + /path-to-regexp@6.3.0: + resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} + dev: true + /path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} @@ -8517,6 +8707,12 @@ packages: react-is: 16.13.1 dev: true + /psl@1.15.0: + resolution: {integrity: sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==} + dependencies: + punycode: 2.3.1 + dev: true + /public-encrypt@4.0.3: resolution: {integrity: sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==} dependencies: @@ -8549,6 +8745,10 @@ packages: engines: {node: '>=0.4.x'} dev: true + /querystringify@2.2.0: + resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} + dev: true + /queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} dev: true @@ -8786,6 +8986,10 @@ packages: engines: {node: '>=0.10.0'} dev: true + /requires-port@1.0.0: + resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} + dev: true + /resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -9138,6 +9342,11 @@ packages: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} dev: true + /signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + dev: true + /simple-swizzle@0.2.2: resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} requiresBuild: true @@ -9183,6 +9392,11 @@ packages: resolution: {integrity: sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==} dev: true + /statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + dev: true + /std-env@3.9.0: resolution: {integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==} dev: true @@ -9208,7 +9422,7 @@ packages: '@testing-library/jest-dom': 6.6.4 '@testing-library/user-event': 14.6.1(@testing-library/dom@10.4.1) '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@7.0.6) + '@vitest/mocker': 3.2.4(msw@2.10.5)(vite@7.0.6) '@vitest/spy': 3.2.4 better-opn: 3.0.2 esbuild: 0.25.8 @@ -9242,6 +9456,10 @@ packages: xtend: 4.0.2 dev: true + /strict-event-emitter@0.5.1: + resolution: {integrity: sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==} + dev: true + /string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} @@ -9605,6 +9823,16 @@ packages: is-number: 7.0.0 dev: true + /tough-cookie@4.1.4: + resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==} + engines: {node: '>=6'} + dependencies: + psl: 1.15.0 + punycode: 2.3.1 + universalify: 0.2.0 + url-parse: 1.5.10 + dev: true + /tough-cookie@5.1.2: resolution: {integrity: sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==} engines: {node: '>=16'} @@ -9748,11 +9976,21 @@ packages: prelude-ls: 1.2.1 dev: true + /type-fest@0.21.3: + resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} + engines: {node: '>=10'} + dev: true + /type-fest@2.19.0: resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} engines: {node: '>=12.20'} dev: true + /type-fest@4.41.0: + resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} + engines: {node: '>=16'} + dev: true + /typed-array-buffer@1.0.3: resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} engines: {node: '>= 0.4'} @@ -9863,6 +10101,11 @@ packages: engines: {node: '>=18'} dev: true + /universalify@0.2.0: + resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} + engines: {node: '>= 4.0.0'} + dev: true + /universalify@2.0.1: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} engines: {node: '>= 10.0.0'} @@ -9930,6 +10173,13 @@ packages: punycode: 2.3.1 dev: true + /url-parse@1.5.10: + resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} + dependencies: + querystringify: 2.2.0 + requires-port: 1.0.0 + dev: true + /url@0.11.4: resolution: {integrity: sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg==} engines: {node: '>= 0.4'} @@ -10049,7 +10299,7 @@ packages: fsevents: 2.3.3 dev: true - /vitest@3.2.4(@types/node@22.15.30)(jsdom@26.1.0): + /vitest@3.2.4(@types/node@22.15.30)(jsdom@26.1.0)(msw@2.10.5): resolution: {integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true @@ -10080,7 +10330,7 @@ packages: '@types/chai': 5.2.2 '@types/node': 22.15.30 '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@7.0.6) + '@vitest/mocker': 3.2.4(msw@2.10.5)(vite@7.0.6) '@vitest/pretty-format': 3.2.4 '@vitest/runner': 3.2.4 '@vitest/snapshot': 3.2.4 @@ -10364,6 +10614,15 @@ packages: engines: {node: '>=0.10.0'} dev: true + /wrap-ansi@6.2.0: + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + dev: true + /wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} @@ -10445,6 +10704,11 @@ packages: engines: {node: '>=12.20'} dev: true + /yoctocolors-cjs@2.1.2: + resolution: {integrity: sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==} + engines: {node: '>=18'} + dev: true + /zustand@5.0.7(@types/react@19.1.0)(react@19.1.0): resolution: {integrity: sha512-Ot6uqHDW/O2VdYsKLLU8GQu8sCOM1LcoE8RwvLv9uuRT9s6SOHCKs0ZEOhxg+I1Ld+A1Q5lwx+UlKXXUoCZITg==} engines: {node: '>=12.20.0'} diff --git a/turbo.json b/turbo.json index ec088be..6b84d5f 100644 --- a/turbo.json +++ b/turbo.json @@ -1,6 +1,7 @@ { "$schema": "https://turborepo.com/schema.json", "ui": "tui", + "globalEnv": ["NODE_ENV"], "tasks": { "build": { "dependsOn": ["^build"], From 647083d86ec2b78b9c5a64f8b0513a1362d192fa Mon Sep 17 00:00:00 2001 From: leeleeleeleejun Date: Sat, 16 Aug 2025 16:06:41 +0900 Subject: [PATCH 05/10] =?UTF-8?q?feat:=20Axios=20=EC=9D=B8=EC=8A=A4?= =?UTF-8?q?=ED=84=B4=EC=8A=A4=20=EA=B8=B0=EB=B3=B8=20URL=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/app/_lib/axiosInstance.ts | 8 +++ .../app/_mocks/handlers/categoryHandlers.ts | 8 ++- apps/web/package.json | 6 +- pnpm-lock.yaml | 70 ++++++++++++++----- turbo.json | 2 +- 5 files changed, 71 insertions(+), 23 deletions(-) create mode 100644 apps/web/app/_lib/axiosInstance.ts diff --git a/apps/web/app/_lib/axiosInstance.ts b/apps/web/app/_lib/axiosInstance.ts new file mode 100644 index 0000000..99e5510 --- /dev/null +++ b/apps/web/app/_lib/axiosInstance.ts @@ -0,0 +1,8 @@ +import axios from 'axios' + +const axiosInstance = axios.create({ + baseURL: process.env.NEXT_PUBLIC_API_URL, + withCredentials: true, +}) + +export default axiosInstance diff --git a/apps/web/app/_mocks/handlers/categoryHandlers.ts b/apps/web/app/_mocks/handlers/categoryHandlers.ts index 076de31..51bd46d 100644 --- a/apps/web/app/_mocks/handlers/categoryHandlers.ts +++ b/apps/web/app/_mocks/handlers/categoryHandlers.ts @@ -2,8 +2,14 @@ import { http, HttpResponse } from 'msw' import { category } from '../data/category' import { API_PATH } from '@/_constants/path' +const BASE_URL = process.env.NEXT_PUBLIC_API_URL + +const addBaseUrl = (path: string) => { + return `${BASE_URL}${path}` +} + export const CategoryHandlers = [ - http.get('https://example.com' + API_PATH.CATEGORY, () => { + http.get(addBaseUrl(API_PATH.CATEGORY), () => { return HttpResponse.json(category) }), ] diff --git a/apps/web/package.json b/apps/web/package.json index 238192b..20f9d4c 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -20,6 +20,7 @@ "@suspensive/react": "^3.3.2", "@tanstack/react-query": "^5.84.1", "@tanstack/react-query-devtools": "^5.84.1", + "axios": "^1.11.0", "motion": "^12.23.12", "next": "^15.4.2", "react": "^19.1.0", @@ -49,10 +50,5 @@ "typescript": "5.8.2", "vite-tsconfig-paths": "^5.1.4", "vitest": "^3.2.4" - }, - "msw": { - "workerDirectory": [ - "public" - ] } } \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 171a701..6a93304 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -126,6 +126,9 @@ importers: '@tanstack/react-query-devtools': specifier: ^5.84.1 version: 5.84.1(@tanstack/react-query@5.84.1)(react@19.1.0) + axios: + specifier: ^1.11.0 + version: 1.11.0 motion: specifier: ^12.23.12 version: 12.23.12(react-dom@19.1.0)(react@19.1.0) @@ -4787,6 +4790,10 @@ packages: engines: {node: '>= 0.4'} dev: true + /asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + dev: false + /autoprefixer@10.4.20(postcss@8.5.3): resolution: {integrity: sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==} engines: {node: ^10 || ^12 || >=14} @@ -4815,6 +4822,16 @@ packages: engines: {node: '>=4'} dev: true + /axios@1.11.0: + resolution: {integrity: sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==} + dependencies: + follow-redirects: 1.15.11 + form-data: 4.0.4 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + dev: false + /axobject-query@4.1.0: resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} engines: {node: '>= 0.4'} @@ -5038,7 +5055,6 @@ packages: dependencies: es-errors: 1.3.0 function-bind: 1.1.2 - dev: true /call-bind@1.0.8: resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} @@ -5228,6 +5244,13 @@ packages: resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} dev: true + /combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + dependencies: + delayed-stream: 1.0.0 + dev: false + /commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} dev: true @@ -5614,6 +5637,11 @@ packages: object-keys: 1.1.1 dev: true + /delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + dev: false + /dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} @@ -5736,7 +5764,6 @@ packages: call-bind-apply-helpers: 1.0.2 es-errors: 1.3.0 gopd: 1.2.0 - dev: true /electron-to-chromium@1.5.110: resolution: {integrity: sha512-/p/OvOm6AfLtQteAHTUWwf+Vhh76PlluagzQlSnxMoOJ4R6SmAScWBrVev6rExJoUhP9zudN9+lBxoYUEmC1HQ==} @@ -5932,12 +5959,10 @@ packages: /es-define-property@1.0.1: resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} engines: {node: '>= 0.4'} - dev: true /es-errors@1.3.0: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} - dev: true /es-iterator-helpers@1.2.1: resolution: {integrity: sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==} @@ -5970,7 +5995,6 @@ packages: engines: {node: '>= 0.4'} dependencies: es-errors: 1.3.0 - dev: true /es-set-tostringtag@2.1.0: resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} @@ -5980,7 +6004,6 @@ packages: get-intrinsic: 1.3.0 has-tostringtag: 1.0.2 hasown: 2.0.2 - dev: true /es-shim-unscopables@1.1.0: resolution: {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==} @@ -6583,6 +6606,16 @@ packages: resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} dev: true + /follow-redirects@1.15.11: + resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + dev: false + /for-each@0.3.5: resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} engines: {node: '>= 0.4'} @@ -6613,6 +6646,17 @@ packages: webpack: 5.101.0(esbuild@0.25.8) dev: true + /form-data@4.0.4: + resolution: {integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==} + engines: {node: '>= 6'} + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.2 + mime-types: 2.1.35 + dev: false + /fraction.js@4.3.7: resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} dev: true @@ -6686,7 +6730,6 @@ packages: /function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - dev: true /function.prototype.name@1.1.8: resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} @@ -6727,7 +6770,6 @@ packages: has-symbols: 1.1.0 hasown: 2.0.2 math-intrinsics: 1.1.0 - dev: true /get-proto@1.0.1: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} @@ -6735,7 +6777,6 @@ packages: dependencies: dunder-proto: 1.0.1 es-object-atoms: 1.1.1 - dev: true /get-symbol-description@1.1.0: resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} @@ -6824,7 +6865,6 @@ packages: /gopd@1.2.0: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} - dev: true /graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} @@ -6865,14 +6905,12 @@ packages: /has-symbols@1.1.0: resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} engines: {node: '>= 0.4'} - dev: true /has-tostringtag@1.0.2: resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} engines: {node: '>= 0.4'} dependencies: has-symbols: 1.1.0 - dev: true /hash-base@2.0.2: resolution: {integrity: sha512-0TROgQ1/SxE6KmxWSvXHvRj90/Xo1JvZShofnYF+f6ZsGtR4eES7WfrQzPalmyagfKZCXpVnitiRebZulWsbiw==} @@ -6900,7 +6938,6 @@ packages: engines: {node: '>= 0.4'} dependencies: function-bind: 1.1.2 - dev: true /he@1.2.0: resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} @@ -7762,7 +7799,6 @@ packages: /math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} - dev: true /md5.js@1.3.5: resolution: {integrity: sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==} @@ -7812,14 +7848,12 @@ packages: /mime-db@1.52.0: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} engines: {node: '>= 0.6'} - dev: true /mime-types@2.1.35: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} engines: {node: '>= 0.6'} dependencies: mime-db: 1.52.0 - dev: true /min-indent@1.0.1: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} @@ -8707,6 +8741,10 @@ packages: react-is: 16.13.1 dev: true + /proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + dev: false + /psl@1.15.0: resolution: {integrity: sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==} dependencies: diff --git a/turbo.json b/turbo.json index 6b84d5f..5c6f8b7 100644 --- a/turbo.json +++ b/turbo.json @@ -1,7 +1,7 @@ { "$schema": "https://turborepo.com/schema.json", "ui": "tui", - "globalEnv": ["NODE_ENV"], + "globalEnv": ["NODE_ENV","NEXT_PUBLIC_API_URL"], "tasks": { "build": { "dependsOn": ["^build"], From 05922859ff153492d50be0d3026caeb4c9ca8a95 Mon Sep 17 00:00:00 2001 From: leeleeleeleejun Date: Sat, 16 Aug 2025 21:01:43 +0900 Subject: [PATCH 06/10] =?UTF-8?q?feat:=20query=20prefetching=EC=9A=A9=20Hy?= =?UTF-8?q?drationBoundaryPage=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/app/HydrationBoundaryPage.tsx | 58 ++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 apps/web/app/HydrationBoundaryPage.tsx diff --git a/apps/web/app/HydrationBoundaryPage.tsx b/apps/web/app/HydrationBoundaryPage.tsx new file mode 100644 index 0000000..9f208cf --- /dev/null +++ b/apps/web/app/HydrationBoundaryPage.tsx @@ -0,0 +1,58 @@ +import { + dehydrate, + HydrationBoundary, + QueryClient, +} from '@tanstack/react-query' +import { ReactNode } from 'react' + +/** + * React Query 쿼리 구성 타입 + * + * @property queryKey - React Query에서 사용할 쿼리 키 + * @property queryFn - 데이터를 가져오는 비동기 함수 + */ +type QueryConfig = { + queryKey: string[] + queryFn: () => Promise +} + +/** + * 서버 컴포넌트에서 React Query 쿼리를 미리 요청(prefetch)한 뒤, + * dehydrate 상태를 클라이언트에 전달하기 위한 컴포넌트. + * + * @example + * ```tsx + * + * + * + * ``` + * + * @param queries - 사전 요청할 쿼리들의 배열 + * @param children - HydrationBoundary로 감쌀 React 노드 + */ +export const HydrationBoundaryPage = async ({ + queries, + children, +}: { + queries: QueryConfig[] + children: ReactNode +}) => { + const queryClient = new QueryClient() + + await Promise.all( + queries.map(({ queryKey, queryFn }) => + queryClient.prefetchQuery({ queryKey, queryFn }), + ), + ) + + return ( + + {children} + + ) +} From 3f07b0bee70c4008a32680946ff2c05dee3d17b6 Mon Sep 17 00:00:00 2001 From: leeleeleeleejun Date: Sat, 16 Aug 2025 21:13:47 +0900 Subject: [PATCH 07/10] =?UTF-8?q?feat:=20MSW=20=EB=B8=8C=EB=9D=BC=EC=9A=B0?= =?UTF-8?q?=EC=A0=80=20=EC=B4=88=EA=B8=B0=ED=99=94=20=EC=8B=9C=20=EA=B0=9C?= =?UTF-8?q?=EB=B0=9C=20=ED=99=98=EA=B2=BD=EC=97=90=EC=84=9C=EB=A7=8C=20?= =?UTF-8?q?=EC=8B=9C=EC=9E=91=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/app/_mocks/initMSW.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/web/app/_mocks/initMSW.ts b/apps/web/app/_mocks/initMSW.ts index 9cc0245..bf20603 100644 --- a/apps/web/app/_mocks/initMSW.ts +++ b/apps/web/app/_mocks/initMSW.ts @@ -11,10 +11,11 @@ export const initServerMSW = () => { } export const initBrowserMSW = async () => { - if (typeof window !== 'undefined') { + if (typeof window !== 'undefined' && process.env.NODE_ENV === 'development') { const { worker } = await import('./worker') await worker.start({ onUnhandledRequest: 'bypass', }) + console.log('✅ MSW 브라우저 시작됨') } } From a1c9dff57015fc8db6885adbe2d436a33974a9e5 Mon Sep 17 00:00:00 2001 From: leeleeleeleejun Date: Sat, 16 Aug 2025 23:05:48 +0900 Subject: [PATCH 08/10] =?UTF-8?q?feat:=20MSWProvider=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EA=B0=9C=EB=B0=9C=20=ED=99=98=EA=B2=BD=EC=97=90=EC=84=9C?= =?UTF-8?q?=EB=A7=8C=20MSW=20=EC=B4=88=EA=B8=B0=ED=99=94=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/app/_mocks/MSWProvider.tsx | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/apps/web/app/_mocks/MSWProvider.tsx b/apps/web/app/_mocks/MSWProvider.tsx index af923aa..d8086f5 100644 --- a/apps/web/app/_mocks/MSWProvider.tsx +++ b/apps/web/app/_mocks/MSWProvider.tsx @@ -4,18 +4,17 @@ import { useEffect, useState } from 'react' import { initBrowserMSW } from '@/_mocks/initMSW' export function MSWProvider({ children }: { children: React.ReactNode }) { - const [mswReady, setMSWReady] = useState(false) + const isDev = process.env.NODE_ENV === 'development' + const [mswReady, setMSWReady] = useState(!isDev) useEffect(() => { + if (!isDev || mswReady) return const init = async () => { await initBrowserMSW() setMSWReady(true) } - - if (!mswReady) { - init() - } - }, [mswReady]) + init() + }, [isDev, mswReady]) if (!mswReady) return null From e4d2df8ca422f7aa3a22c056a1721d9169188789 Mon Sep 17 00:00:00 2001 From: leeleeleeleejun Date: Sat, 16 Aug 2025 23:48:50 +0900 Subject: [PATCH 09/10] =?UTF-8?q?feat:=20BASE=5FURL=EC=97=90=20=EA=B8=B0?= =?UTF-8?q?=EB=B3=B8=EA=B0=92=20=EC=B6=94=EA=B0=80=ED=95=98=EC=97=AC=20?= =?UTF-8?q?=EB=B9=88=20=EB=AC=B8=EC=9E=90=EC=97=B4=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/app/_mocks/handlers/categoryHandlers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/app/_mocks/handlers/categoryHandlers.ts b/apps/web/app/_mocks/handlers/categoryHandlers.ts index 51bd46d..dfb5de9 100644 --- a/apps/web/app/_mocks/handlers/categoryHandlers.ts +++ b/apps/web/app/_mocks/handlers/categoryHandlers.ts @@ -2,7 +2,7 @@ import { http, HttpResponse } from 'msw' import { category } from '../data/category' import { API_PATH } from '@/_constants/path' -const BASE_URL = process.env.NEXT_PUBLIC_API_URL +const BASE_URL = process.env.NEXT_PUBLIC_API_URL || '' const addBaseUrl = (path: string) => { return `${BASE_URL}${path}` From d0c23ef3d416a63244f8ad90adf05fae316b6248 Mon Sep 17 00:00:00 2001 From: leeleeleeleejun Date: Sat, 16 Aug 2025 23:53:20 +0900 Subject: [PATCH 10/10] =?UTF-8?q?feat:=20initServerMSW=20=ED=95=A8?= =?UTF-8?q?=EC=88=98=EB=A5=BC=20=EB=B9=84=EB=8F=99=EA=B8=B0=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=ED=95=98=EC=97=AC=20=EC=84=9C=EB=B2=84=20?= =?UTF-8?q?=EC=B4=88=EA=B8=B0=ED=99=94=20=EC=8B=9C=20=EB=8C=80=EA=B8=B0?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/app/_mocks/initMSW.ts | 2 +- apps/web/app/layout.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/web/app/_mocks/initMSW.ts b/apps/web/app/_mocks/initMSW.ts index bf20603..138cf1b 100644 --- a/apps/web/app/_mocks/initMSW.ts +++ b/apps/web/app/_mocks/initMSW.ts @@ -1,4 +1,4 @@ -export const initServerMSW = () => { +export const initServerMSW = async () => { // 서버에서만 실행되는 MSW 초기화 if (typeof window === 'undefined' && process.env.NODE_ENV === 'development') { import('./server').then(({ server }) => { diff --git a/apps/web/app/layout.tsx b/apps/web/app/layout.tsx index c770c82..84b9428 100644 --- a/apps/web/app/layout.tsx +++ b/apps/web/app/layout.tsx @@ -23,7 +23,7 @@ export default async function RootLayout({ }: { children: React.ReactNode }) { - initServerMSW() + await initServerMSW() return (