diff --git a/.coderabbit.yaml b/.coderabbit.yaml new file mode 100644 index 0000000000..d82aa64e12 --- /dev/null +++ b/.coderabbit.yaml @@ -0,0 +1,91 @@ + +language: "en-US" + +# Customize tone so CodeRabbit reviews with clear guidelines for FastStore +tone_instructions: > + Review FastStore PRs focusing on performance, SEO, accessibility, and TypeScript safety. Be concise and actionable. + +early_access: false +enable_free_tier: true + +reviews: + auto_review: + enabled: true + base_branches: + - "dev" + - ".*" + # Review tone (chill vs assertive) + profile: "chill" + + # Provide a high level summary of PR changes + high_level_summary: true + + # Optional: include extra review behaviours + request_changes_workflow: false + high_level_summary_in_walkthrough: false + auto_title_placeholder: "@coderabbitai" + + # Helpful flags + review_status: true + commit_status: false + collapse_walkthrough: false + changed_files_summary: true + +# Path filters to exclude things like output, generated files, and build folders + path_filters: + - "packages/**" + - "!**/dist/**" + - "!**/.next/**" + - "!**/node_modules/**" + - "!**/@generated/**" + - "!**/storybook-static/**" + - "src/**" + - "apps/**" + - "!dist/**" + - "!.next/**" + - "!node_modules/**" + + # Optional path specific instructions (useful for FastStore code patterns) + path_instructions: + - path: "packages/**/src/components/**" + instructions: | + Component code: + - Ensure React hooks follow rules of hooks (no conditional hooks, proper dependencies) + - Check rendering performance (avoid heavy operations on render, use memo/useMemo/useCallback when appropriate) + - Favor lazy loading for large components using dynamic imports + - Use semantic HTML and proper ARIA labels for accessibility + - Ensure components are properly typed with TypeScript + + - path: "packages/core/src/pages/**" + instructions: | + Page-level code (Model in MVC - data fetching): + - Ensure correct use of Next.js conventions (getStaticProps, getServerSideProps, File System Routing) + - Validate SEO-related tags (meta tags, structured data, Open Graph) + - Performance implications: static generation vs SSR vs ISR + - Proper error handling (404, 500 pages) + - Static data fetching should happen here, dynamic enrichment in components + + - path: "packages/**/src/sdk/**" + instructions: | + SDK code: + - Custom hooks should follow React hooks conventions + - GraphQL queries should be optimized (avoid over-fetching, use fragments) + - Business logic should be separated from UI concerns + + - path: "packages/api/src/**" + instructions: | + GraphQL API code: + - TypeDefs should follow GraphQL best practices + - Resolvers should be platform-agnostic when possible + - Proper error handling and validation + - Consider performance implications of resolvers + - Type safety with generated types + + - path: "packages/**/*.ts" + instructions: | + TypeScript files: + - Ensure type safety and avoid type assertions when possible + - Consider bundle size impact of dependencies + +chat: + auto_reply: true diff --git a/CHANGELOG.md b/CHANGELOG.md index b3c701f97b..e4246d0c09 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,24 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline - version 20251223 ([#3161](https://github.com/vtex/faststore/issues/3161)) ([ad5e8d1](https://github.com/vtex/faststore/commit/ad5e8d12fe45f1fb8e1134165172bf1c6b2a91e2)) +# [3.96.0-dev.21](https://github.com/vtex/faststore/compare/v3.96.0-dev.20...v3.96.0-dev.21) (2026-01-08) + +### Bug Fixes + +- Prevent Partytown cross-origin errors in iframe environments ([#3155](https://github.com/vtex/faststore/issues/3155)) ([997252e](https://github.com/vtex/faststore/commit/997252e9a18807f4c26c350197d4f2d139de58f8)) + +# [3.96.0-dev.20](https://github.com/vtex/faststore/compare/v3.96.0-dev.19...v3.96.0-dev.20) (2026-01-08) + +### Bug Fixes + +- Add slug field to landing page schema - CROC-185 ([#3166](https://github.com/vtex/faststore/issues/3166)) ([41b9878](https://github.com/vtex/faststore/commit/41b987825ee5ebb7d311eef33e2ca65034dc130d)) + +# [3.96.0-dev.19](https://github.com/vtex/faststore/compare/v3.96.0-dev.18...v3.96.0-dev.19) (2026-01-08) + +### Features + +- logout clear storage ([#3163](https://github.com/vtex/faststore/issues/3163)) ([1fbacc3](https://github.com/vtex/faststore/commit/1fbacc3b3e32b4f83a2f56c1f97eb0f8ff843f61)) + # 3.96.0-dev.18 (2025-12-24) **Note:** Version bump only for package faststore diff --git a/packages/api/src/platforms/vtex/clients/commerce/index.ts b/packages/api/src/platforms/vtex/clients/commerce/index.ts index 1400d7c68f..9843069b4d 100644 --- a/packages/api/src/platforms/vtex/clients/commerce/index.ts +++ b/packages/api/src/platforms/vtex/clients/commerce/index.ts @@ -224,26 +224,27 @@ export const VtexCommerce = ( refreshOutdatedData = true, channel = ctx.storage.channel, }: { - id: string + id?: string refreshOutdatedData?: boolean channel?: Required }): Promise => { const { salesChannel } = channel - const params = new URLSearchParams({ - refreshOutdatedData: refreshOutdatedData.toString(), - sc: salesChannel, - }) - const headers: HeadersInit = withCookie({ 'content-type': 'application/json', 'X-FORWARDED-HOST': forwardedHost, }) + const params = new URLSearchParams({ sc: salesChannel }) + if (id) { + params.set('refreshOutdatedData', refreshOutdatedData.toString()) + } + const url = `${base}/api/checkout/pub/orderForm${id ? `/${id}` : ''}?${params.toString()}` return fetchAPI( - `${base}/api/checkout/pub/orderForm/${id}?${params.toString()}`, + url, { ...BASE_INIT, headers, + ...(id ? {} : { body: '{}' }), }, { storeCookies } ) diff --git a/packages/api/src/platforms/vtex/resolvers/validateCart.ts b/packages/api/src/platforms/vtex/resolvers/validateCart.ts index deb8b3715a..c7068fbd27 100644 --- a/packages/api/src/platforms/vtex/resolvers/validateCart.ts +++ b/packages/api/src/platforms/vtex/resolvers/validateCart.ts @@ -254,13 +254,6 @@ const isOrderFormStale = (form: OrderForm, sessionJwt: SessionJwt) => { return newEtag !== oldEtag } -// Returns the regionalized orderForm -const getOrderForm = async (id: string, { clients: { commerce } }: Context) => { - return commerce.checkout.orderForm({ - id, - }) -} - const clearOrderFormMessages = async ( id: string, { clients: { commerce } }: Context @@ -360,10 +353,6 @@ export const validateCart = async ( ctx.headers.cookie, 'checkout.vtex.com' ) - const orderNumber = - orderFormIdFromCookie !== '' ? orderFormIdFromCookie : order?.orderNumber - - const { acceptedOffer, shouldSplitItem } = order const { clients: { commerce }, loaders: { skuLoader }, @@ -381,7 +370,11 @@ export const validateCart = async ( } // Step1: Get OrderForm from VTEX Commerce - const orderForm = await getOrderForm(orderNumber, ctx) + const orderForm = await commerce.checkout.orderForm({ + id: orderFormIdFromCookie || undefined, + channel: ctx.storage.channel, + }) + const orderNumber = orderForm.orderFormId // Clear messages so it doesn't keep populating toasts on a loop // In the next validateCart mutation it will only have messages if a new message is created on orderForm @@ -392,6 +385,8 @@ export const validateCart = async ( const sessionCookie = parse(ctx?.headers?.cookie ?? '')?.vtex_session const sessionJwt = parseJwt(sessionCookie) + const { acceptedOffer, shouldSplitItem } = order + // Step1.5: Check if another system changed the orderForm with this orderNumber // If so, this means the user interacted with this cart elsewhere and expects // to see this new cart state instead of what's stored on the user's browser. diff --git a/packages/api/test/integration/mutations.test.ts b/packages/api/test/integration/mutations.test.ts index c574bde082..5399ea1999 100644 --- a/packages/api/test/integration/mutations.test.ts +++ b/packages/api/test/integration/mutations.test.ts @@ -41,6 +41,8 @@ const createRunner = () => { return async (query: string, variables?: any) => { const schema = await schemaPromise const context = contextFactory({}) + const orderFormCookie = + 'checkout.vtex.com=__ofid=edbe3b03c8c94827a37ec5a6a4648fd2' return execute( schema, @@ -48,7 +50,10 @@ const createRunner = () => { null, { ...context, - headers: { 'content-type': 'application/json', cookie: '' }, + headers: { + 'content-type': 'application/json', + cookie: orderFormCookie, + }, }, variables ) diff --git a/packages/api/test/mocks/ValidateCartMutation.ts b/packages/api/test/mocks/ValidateCartMutation.ts index 2dea2a783d..f8466a4876 100644 --- a/packages/api/test/mocks/ValidateCartMutation.ts +++ b/packages/api/test/mocks/ValidateCartMutation.ts @@ -261,7 +261,7 @@ export const InvalidCart = { // Valid Cart export const checkoutOrderFormValidFetch = { - info: 'https://storeframework.vtexcommercestable.com.br/api/checkout/pub/orderForm/edbe3b03c8c94827a37ec5a6a4648fd2?refreshOutdatedData=true&sc=1', + info: 'https://storeframework.vtexcommercestable.com.br/api/checkout/pub/orderForm/edbe3b03c8c94827a37ec5a6a4648fd2?sc=1&refreshOutdatedData=true', init: { method: 'POST', headers: { 'content-type': 'application/json', 'X-FORWARDED-HOST': '' }, @@ -288,7 +288,7 @@ export const checkoutOrderFormCustomDataValidFetch = { // "Invalid" Cart export const checkoutOrderFormInvalidFetch = { - info: 'https://storeframework.vtexcommercestable.com.br/api/checkout/pub/orderForm/edbe3b03c8c94827a37ec5a6a4648fd2?refreshOutdatedData=true&sc=1', + info: 'https://storeframework.vtexcommercestable.com.br/api/checkout/pub/orderForm/edbe3b03c8c94827a37ec5a6a4648fd2?sc=1&refreshOutdatedData=true', init: { method: 'POST', headers: { 'content-type': 'application/json', 'X-FORWARDED-HOST': '' }, @@ -300,7 +300,7 @@ export const checkoutOrderFormInvalidFetch = { } export const checkoutOrderFormItemsInvalidFetch = { - info: 'https://storeframework.vtexcommercestable.com.br/api/checkout/pub/orderForm/edbe3b03c8c94827a37ec5a6a4648fd2/items?allowOutdatedData=paymentData&sc=1', + info: 'https://storeframework.vtexcommercestable.com.br/api/checkout/pub/orderForm/edbe3b03c8c94827a37ec5a6a4648fd2/items?sc=1&allowOutdatedData=paymentData', init: { method: 'PATCH', headers: { @@ -531,7 +531,7 @@ export const productSearchPage1Count1Fetch = { // Stale Cart export const checkoutOrderFormStaleFetch = { - info: 'https://storeframework.vtexcommercestable.com.br/api/checkout/pub/orderForm/edbe3b03c8c94827a37ec5a6a4648fd2?refreshOutdatedData=true&sc=1', + info: 'https://storeframework.vtexcommercestable.com.br/api/checkout/pub/orderForm/edbe3b03c8c94827a37ec5a6a4648fd2?sc=1&refreshOutdatedData=true', init: { method: 'POST', headers: { 'content-type': 'application/json', 'X-FORWARDED-HOST': '' }, diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index e6b10b910f..2c3a338b3e 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -17,6 +17,18 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline - version 20251223 ([#3161](https://github.com/vtex/faststore/issues/3161)) ([ad5e8d1](https://github.com/vtex/faststore/commit/ad5e8d12fe45f1fb8e1134165172bf1c6b2a91e2)) +# [3.96.0-dev.21](https://github.com/vtex/faststore/compare/v3.96.0-dev.20...v3.96.0-dev.21) (2026-01-08) + +**Note:** Version bump only for package @faststore/cli + +# [3.96.0-dev.20](https://github.com/vtex/faststore/compare/v3.96.0-dev.19...v3.96.0-dev.20) (2026-01-08) + +**Note:** Version bump only for package @faststore/cli + +# [3.96.0-dev.19](https://github.com/vtex/faststore/compare/v3.96.0-dev.18...v3.96.0-dev.19) (2026-01-08) + +**Note:** Version bump only for package @faststore/cli + # 3.96.0-dev.18 (2025-12-24) **Note:** Version bump only for package @faststore/cli diff --git a/packages/cli/README.md b/packages/cli/README.md index 9374d29080..464dba1477 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -25,6 +25,7 @@ npm install -g @faststore/cli ``` + ```sh-session $ npm install -g @faststore/cli $ faststore COMMAND @@ -36,19 +37,23 @@ USAGE $ faststore COMMAND ... ``` + ## Commands -* [`faststore build [ACCOUNT] [PATH]`](#faststore-build-account-path) -* [`faststore cms-sync [PATH]`](#faststore-cms-sync-path) -* [`faststore create [PATH]`](#faststore-create-path) -* [`faststore dev [ACCOUNT] [PATH] [PORT]`](#faststore-dev-account-path-port) -* [`faststore generate-graphql [PATH]`](#faststore-generate-graphql-path) -* [`faststore help [COMMAND]`](#faststore-help-command) -* [`faststore start [ACCOUNT] [PATH] [PORT]`](#faststore-start-account-path-port) -* [`faststore test [PATH]`](#faststore-test-path) + +- [Installation](#installation) +- [Commands](#commands) +- [`faststore build [ACCOUNT] [PATH]`](#faststore-build-account-path) +- [`faststore cms-sync [PATH]`](#faststore-cms-sync-path) +- [`faststore create [PATH]`](#faststore-create-path) +- [`faststore dev [ACCOUNT] [PATH] [PORT]`](#faststore-dev-account-path-port) +- [`faststore generate-graphql [PATH]`](#faststore-generate-graphql-path) +- [`faststore help [COMMAND]`](#faststore-help-command) +- [`faststore start [ACCOUNT] [PATH] [PORT]`](#faststore-start-account-path-port) +- [`faststore test [PATH]`](#faststore-test-path) ## `faststore build [ACCOUNT] [PATH]` @@ -176,4 +181,5 @@ ARGUMENTS ``` _See code: [dist/commands/test.js](https://github.com/vtex/faststore/blob/v3.96.2/dist/commands/test.js)_ + diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index f7b8c9108e..9ae9e0e331 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -17,6 +17,24 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline - version 20251223 ([#3161](https://github.com/vtex/faststore/issues/3161)) ([ad5e8d1](https://github.com/vtex/faststore/commit/ad5e8d12fe45f1fb8e1134165172bf1c6b2a91e2)) +# [3.96.0-dev.21](https://github.com/vtex/faststore/compare/v3.96.0-dev.20...v3.96.0-dev.21) (2026-01-08) + +### Bug Fixes + +- Prevent Partytown cross-origin errors in iframe environments ([#3155](https://github.com/vtex/faststore/issues/3155)) ([997252e](https://github.com/vtex/faststore/commit/997252e9a18807f4c26c350197d4f2d139de58f8)) + +# [3.96.0-dev.20](https://github.com/vtex/faststore/compare/v3.96.0-dev.19...v3.96.0-dev.20) (2026-01-08) + +### Bug Fixes + +- Add slug field to landing page schema - CROC-185 ([#3166](https://github.com/vtex/faststore/issues/3166)) ([41b9878](https://github.com/vtex/faststore/commit/41b987825ee5ebb7d311eef33e2ca65034dc130d)) + +# [3.96.0-dev.19](https://github.com/vtex/faststore/compare/v3.96.0-dev.18...v3.96.0-dev.19) (2026-01-08) + +### Features + +- logout clear storage ([#3163](https://github.com/vtex/faststore/issues/3163)) ([1fbacc3](https://github.com/vtex/faststore/commit/1fbacc3b3e32b4f83a2f56c1f97eb0f8ff843f61)) + # 3.96.0-dev.18 (2025-12-24) **Note:** Version bump only for package @faststore/core diff --git a/packages/core/cms/faststore/pages/cms_content_type__landingpage.jsonc b/packages/core/cms/faststore/pages/cms_content_type__landingpage.jsonc index 96660a7d7d..a676667b3f 100644 --- a/packages/core/cms/faststore/pages/cms_content_type__landingpage.jsonc +++ b/packages/core/cms/faststore/pages/cms_content_type__landingpage.jsonc @@ -5,6 +5,12 @@ "type": "object", "title": "Landing Page", "properties": { + "slug": { + "widget": { + "ui:widget": "slug" + }, + "type": "string" + }, "seo": { "title": "SEO", "description": "Search Engine Optimization options", diff --git a/packages/core/src/components/ThirdPartyScripts/ThirdPartyScripts.tsx b/packages/core/src/components/ThirdPartyScripts/ThirdPartyScripts.tsx index 7a613e96a5..a7e8f0d953 100644 --- a/packages/core/src/components/ThirdPartyScripts/ThirdPartyScripts.tsx +++ b/packages/core/src/components/ThirdPartyScripts/ThirdPartyScripts.tsx @@ -37,7 +37,10 @@ function ThirdPartyScripts() { {includeGTM && } {includeVTEX && } - + {/* Only render Partytown when not in an iframe to prevent cross-origin errors. */} + {typeof window !== 'undefined' && window.self === window.top && ( + + )} ) } diff --git a/packages/core/src/components/account/MyAccountDrawer/OrganizationDrawer/OrganizationDrawer.tsx b/packages/core/src/components/account/MyAccountDrawer/OrganizationDrawer/OrganizationDrawer.tsx index b89b9d4686..bab242d653 100644 --- a/packages/core/src/components/account/MyAccountDrawer/OrganizationDrawer/OrganizationDrawer.tsx +++ b/packages/core/src/components/account/MyAccountDrawer/OrganizationDrawer/OrganizationDrawer.tsx @@ -1,6 +1,12 @@ import { SlideOver, useFadeEffect } from '@faststore/ui' import { useSession } from 'src/sdk/session' +import { + expireCookieClient, + getCookieDomains, + getCookiePaths, + getVtexCookieNames, +} from 'src/utils/clearCookies' import storeConfig from '../../../../../discovery.config' import { ProfileSummary } from '../ProfileSummary/ProfileSummary' import { OrganizationDrawerBody } from './OrganizationDrawerBody' @@ -13,11 +19,105 @@ type OrganizationDrawerProps = { isRepresentative: boolean } -export const doLogout = () => { +const clearBrowserStorageForCurrentDomain = async () => { + if (typeof window === 'undefined' || !storeConfig) return + + // Clear Faststore-specific sessionStorage keys + try { + const sessionStorageKeys = [ + 'faststore_session_ready', + 'faststore_auth_cookie_value', + 'faststore_cache_bust_last_value', + ] + + for (const key of sessionStorageKeys) { + try { + window.sessionStorage?.removeItem(key) + } catch {} + } + + // Remove all keys starting with __fs_gallery_page_ (used for PLP pagination) + try { + const keysToRemove: string[] = [] + for (let i = 0; i < window.sessionStorage.length; i++) { + const key = window.sessionStorage.key(i) + if (key && key.startsWith('__fs_gallery_page_')) { + keysToRemove.push(key) + } + } + for (const key of keysToRemove) { + try { + window.sessionStorage.removeItem(key) + } catch {} + } + } catch {} + } catch {} + + // Clear all cookies containing 'vtex' in the name (case-insensitive) + try { + const hostname = window.location.hostname + const secure = window.location.protocol === 'https:' + + // Extract all cookie names from document.cookie + const allCookieNames = document.cookie + .split(';') + .map((c) => c.trim()) + .filter(Boolean) + .map((c) => c.split('=')[0]) + .filter(Boolean) + + const vtexCookieNames = getVtexCookieNames(allCookieNames) + const paths = getCookiePaths(window.location.pathname || '/') + const domains = getCookieDomains(hostname) + + for (const name of vtexCookieNames) { + for (const path of paths) { + for (const domain of domains) { + try { + expireCookieClient({ name, path, domain, secure }) + } catch {} + } + } + } + } catch {} + + // Clear IndexedDB (keyval-store) + try { + if (!('indexedDB' in window)) return + + const idb = window.indexedDB + if (!idb) return + + await new Promise((resolve) => { + const req = idb.deleteDatabase('keyval-store') + req.onsuccess = () => resolve() + req.onerror = () => resolve() + req.onblocked = () => resolve() + }) + } catch {} +} + +export const doLogout = async (_event?: unknown) => { if (!storeConfig) return - window.location.assign( - `${storeConfig.secureSubdomain}/api/vtexid/pub/logout?scope=${storeConfig.api.storeId}&returnUrl=${storeConfig.storeUrl}` - ) + + try { + // Clear client-side storage (sessionStorage, localStorage, IndexedDB, non-HttpOnly cookies) + await clearBrowserStorageForCurrentDomain() + + // Clear HttpOnly cookies via API endpoint (server-side) + try { + await fetch('/api/fs/logout', { + method: 'POST', + credentials: 'include', + }) + } catch { + // Continue even if API call fails + } + } finally { + window.location.assign( + `${storeConfig.secureSubdomain}/api/vtexid/pub/logout?scope=${storeConfig.api.storeId}&returnUrl=${storeConfig.storeUrl}` + ) + } } export const OrganizationDrawer = ({ diff --git a/packages/core/src/pages/api/fs/logout.ts b/packages/core/src/pages/api/fs/logout.ts new file mode 100644 index 0000000000..688915e18a --- /dev/null +++ b/packages/core/src/pages/api/fs/logout.ts @@ -0,0 +1,76 @@ +import { parse } from 'cookie' +import type { NextApiHandler, NextApiRequest, NextApiResponse } from 'next' + +import discoveryConfig from 'discovery.config' +import { + expireCookieServer, + getCookieDomains, + getVtexCookieNames, +} from 'src/utils/clearCookies' + +const ADDITIONAL_COOKIES = ['CheckoutOrderFormOwnership'] as const + +/** + * Clears all cookies containing 'vtex' in the name (case-insensitive) + ADDITIONAL_COOKIES + * This endpoint handles HttpOnly cookies that cannot be cleared via JavaScript + */ +const handler: NextApiHandler = async ( + request: NextApiRequest, + response: NextApiResponse +) => { + if (request.method !== 'POST') { + response.status(405).end() + return + } + + try { + const hostname = request.headers.host?.split(':')[0] ?? '' + const cookies = parse(request.headers.cookie ?? '') + const domains = getCookieDomains(hostname) + const clearedCookies: string[] = [] + + const vtexCookieNames = getVtexCookieNames(Object.keys(cookies)) + + // Clear vid_rt cookie with specific path (only if refreshToken is enabled) + if (discoveryConfig.experimental?.refreshToken && cookies.vid_rt) { + for (const domain of domains) { + const clearedCookie = expireCookieServer({ + name: 'vid_rt', + path: '/api/vtexid/refreshtoken/webstore', + domain, + }) + clearedCookies.push(clearedCookie) + } + } + + // Clear other cookies with path / + const otherCookieNames = [ + ...vtexCookieNames, + ...ADDITIONAL_COOKIES.filter((name) => cookies[name]), + ] + + for (const cookieName of otherCookieNames) { + for (const domain of domains) { + const clearedCookie = expireCookieServer({ + name: cookieName, + path: '/', + domain, + }) + clearedCookies.push(clearedCookie) + } + } + + if (clearedCookies.length > 0) { + response.setHeader('set-cookie', clearedCookies) + } + + response.status(200).json({ success: true }) + } catch (error) { + console.error('Error clearing cookies:', error) + response + .status(500) + .json({ success: false, error: 'Failed to clear cookies' }) + } +} + +export default handler diff --git a/packages/core/src/utils/clearCookies.ts b/packages/core/src/utils/clearCookies.ts new file mode 100644 index 0000000000..6a1feb83c5 --- /dev/null +++ b/packages/core/src/utils/clearCookies.ts @@ -0,0 +1,79 @@ +type ExpireCookieClientParams = { + name: string + path: string + domain?: string + secure?: boolean +} + +type ExpireCookieServerParams = { + name: string + path: string + domain?: string +} + +/** + * Client-side: Expires a cookie by setting it to expire in the past + */ +export const expireCookieClient = ({ + name, + path, + domain, + secure = false, +}: ExpireCookieClientParams): void => { + const domainAttr = domain ? `; domain=${domain}` : '' + const secureAttr = secure ? '; secure' : '' + document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; max-age=0; path=${path}${domainAttr}; samesite=lax${secureAttr}` +} + +/** + * Server-side: Generates a Set-Cookie header string to expire a cookie + */ +export const expireCookieServer = ({ + name, + path, + domain, +}: ExpireCookieServerParams): string => { + const domainAttr = domain ? `; domain=${domain}` : '' + return `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; max-age=0; path=${path}${domainAttr}; samesite=lax; httponly` +} + +/** + * Utility functions for clearing cookies + * Shared logic between client-side and server-side cookie clearing + */ + +/** + * Generates domain variations for cookie clearing + * Tries multiple domain combinations to ensure cookies are cleared regardless of how they were set + */ +export const getCookieDomains = ( + hostname: string +): Array => [ + undefined, // host-only cookie + hostname, + hostname.startsWith('.') ? hostname : `.${hostname}`, +] + +/** + * Filters cookie names that contain 'vtex' (case-insensitive) + */ +export const getVtexCookieNames = (cookieNames: string[]): string[] => { + return cookieNames.filter((name) => name.toLowerCase().includes('vtex')) +} + +/** + * Generates paths to try when clearing cookies + * Includes root path and all parent paths from current pathname + */ +export const getCookiePaths = (pathname: string): string[] => { + const paths: string[] = ['/'] + const pathParts = pathname.split('/').filter(Boolean) + + let current = '' + for (const part of pathParts) { + current += `/${part}` + if (!paths.includes(current)) paths.push(current) + } + + return paths +} diff --git a/packages/core/test/utils/clearCookies.test.ts b/packages/core/test/utils/clearCookies.test.ts new file mode 100644 index 0000000000..2141717114 --- /dev/null +++ b/packages/core/test/utils/clearCookies.test.ts @@ -0,0 +1,232 @@ +/** + * @jest-environment jsdom + */ + +import { + expireCookieClient, + expireCookieServer, + getCookieDomains, + getCookiePaths, + getVtexCookieNames, +} from '../../src/utils/clearCookies' + +describe('clearCookies', () => { + beforeEach(() => { + // Clear all cookies before each test + document.cookie.split(';').forEach((cookie) => { + const name = cookie.split('=')[0].trim() + document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/` + }) + }) + + describe('getCookieDomains', () => { + it('should return domain variations for a hostname', () => { + const hostname = 'example.com' + const domains = getCookieDomains(hostname) + + expect(domains).toEqual([undefined, 'example.com', '.example.com']) + }) + + it('should handle hostname that already starts with dot', () => { + const hostname = '.example.com' + const domains = getCookieDomains(hostname) + + expect(domains).toEqual([undefined, '.example.com', '.example.com']) + }) + + it('should handle localhost', () => { + const hostname = 'localhost' + const domains = getCookieDomains(hostname) + + expect(domains).toEqual([undefined, 'localhost', '.localhost']) + }) + }) + + describe('getVtexCookieNames', () => { + it('should filter cookies containing "vtex" (case-insensitive)', () => { + const cookieNames = [ + 'vtex-search-anonymous', + 'VtexIdclientAutCookie_store', + 'other-cookie', + 'VTEX_SESSION', + 'vtex_segment', + ] + + const result = getVtexCookieNames(cookieNames) + + expect(result).toEqual([ + 'vtex-search-anonymous', + 'VtexIdclientAutCookie_store', + 'VTEX_SESSION', + 'vtex_segment', + ]) + }) + + it('should return empty array when no vtex cookies found', () => { + const cookieNames = ['other-cookie', 'another-cookie'] + + const result = getVtexCookieNames(cookieNames) + + expect(result).toEqual([]) + }) + + it('should handle empty array', () => { + const result = getVtexCookieNames([]) + + expect(result).toEqual([]) + }) + }) + + describe('getCookiePaths', () => { + it('should return root path for empty pathname', () => { + const paths = getCookiePaths('') + + expect(paths).toEqual(['/']) + }) + + it('should return root path for root pathname', () => { + const paths = getCookiePaths('/') + + expect(paths).toEqual(['/']) + }) + + it('should return paths for nested pathname', () => { + const paths = getCookiePaths('/api/vtexid/logout') + + expect(paths).toEqual(['/', '/api', '/api/vtexid', '/api/vtexid/logout']) + }) + + it('should handle pathname without leading slash', () => { + const paths = getCookiePaths('api/vtexid') + + expect(paths).toEqual(['/', '/api', '/api/vtexid']) + }) + + it('should handle single segment pathname', () => { + const paths = getCookiePaths('/api') + + expect(paths).toEqual(['/', '/api']) + }) + }) + + describe('expireCookieClient', () => { + it('should expire a cookie with default parameters', () => { + // Set a cookie first + document.cookie = 'test-cookie=value; path=/' + + expireCookieClient({ name: 'test-cookie', path: '/' }) + + expect(document.cookie).not.toContain('test-cookie=value') + }) + + it('should expire a cookie with domain', () => { + document.cookie = 'test-cookie=value; path=/; domain=.example.com' + + expireCookieClient({ + name: 'test-cookie', + path: '/', + domain: '.example.com', + }) + + // Note: We can't easily verify domain-specific cookies in jsdom, + // but we can verify the function doesn't throw + expect(() => { + expireCookieClient({ + name: 'test-cookie', + path: '/', + domain: '.example.com', + }) + }).not.toThrow() + }) + + it('should expire a cookie with secure flag', () => { + document.cookie = 'secure-cookie=value; path=/; secure' + + expireCookieClient({ + name: 'secure-cookie', + path: '/', + secure: true, + }) + + expect(() => { + expireCookieClient({ + name: 'secure-cookie', + path: '/', + secure: true, + }) + }).not.toThrow() + }) + + it('should expire a cookie with all parameters', () => { + expireCookieClient({ + name: 'full-cookie', + path: '/api', + domain: '.example.com', + secure: true, + }) + + expect(() => { + expireCookieClient({ + name: 'full-cookie', + path: '/api', + domain: '.example.com', + secure: true, + }) + }).not.toThrow() + }) + }) + + describe('expireCookieServer', () => { + it('should generate Set-Cookie header string for basic cookie', () => { + const result = expireCookieServer({ name: 'test-cookie', path: '/' }) + + expect(result).toContain('test-cookie=') + expect(result).toContain('expires=Thu, 01 Jan 1970 00:00:00 GMT') + expect(result).toContain('max-age=0') + expect(result).toContain('path=/') + expect(result).toContain('samesite=lax') + expect(result).toContain('httponly') + }) + + it('should generate Set-Cookie header string with domain', () => { + const result = expireCookieServer({ + name: 'test-cookie', + path: '/', + domain: '.example.com', + }) + + expect(result).toContain('test-cookie=') + expect(result).toContain('domain=.example.com') + expect(result).toContain('path=/') + expect(result).toContain('httponly') + }) + + it('should generate Set-Cookie header string without domain when undefined', () => { + const result = expireCookieServer({ + name: 'test-cookie', + path: '/', + domain: undefined, + }) + + expect(result).toContain('test-cookie=') + expect(result).not.toContain('domain=') + expect(result).toContain('path=/') + }) + + it('should generate Set-Cookie header string for specific path', () => { + const result = expireCookieServer({ + name: 'vid_rt', + path: '/api/vtexid/refreshtoken/webstore', + }) + + expect(result).toContain('vid_rt=') + expect(result).toContain('path=/api/vtexid/refreshtoken/webstore') + }) + + it('should always include httponly flag', () => { + const result = expireCookieServer({ name: 'test-cookie', path: '/' }) + + expect(result).toContain('httponly') + }) + }) +}) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c7c2dcc8a3..e5e773c064 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2358,6 +2358,66 @@ packages: integrity: sha512-fERDVz7topgNjtXsJTTW1JKLy0rhuLRcquYqNR9rF7OcVpCa2OVW49ZPDIhaRRCaUuvVxI+N416xUoF76HNSXA==, } + "@faststore/api@3.96.0": + resolution: + { + integrity: sha512-vDLocEb9hEILO3j1VZq2aKXX4DmLA6R3zhEyE06HsvZYZbd/+MwLdjKiCy3ypxqPJ8RMsxbg/a7aZZ5ePKw9mA==, + } + engines: { node: ">=18" } + peerDependencies: + graphql: ^15.6.0 + + "@faststore/components@3.96.0": + resolution: + { + integrity: sha512-EnaDQKyli9eziuqcVhObfs2G/ED2iod3UTK+sruWT29yT3DoAM/pE6KqmpbkyOrBm439HuMLe2NVwR9MLWi4og==, + } + peerDependencies: + react: ^18.2.0 + react-dom: ^18.2.0 + + "@faststore/core@3.96.0": + resolution: + { + integrity: sha512-u1XecmUGdDAsaK9Mg/OJmWF6BkngFMxEy27n5xfJdS0w3YrnXs34jZ6QbFp55b66eO7nuZGkvSHiSEsrMmaveQ==, + } + engines: { node: ">=18" } + + "@faststore/graphql-utils@3.96.0": + resolution: + { + integrity: sha512-A7qo2oX0UwBM/jWve3c5nRzlHQaew7RHuTnHxz8BG8hykkuH9JyCWJtrQ+Lwd7QR1SLaVIBmbeYrYxz+BYQDKw==, + } + peerDependencies: + graphql: ^15.6.1 + + "@faststore/lighthouse@3.96.0": + resolution: + { + integrity: sha512-8H/lQJxvG22nimAt5JFrT2cWQ0OjyFEfu1HpSdnR+rM6zwynyIzcqzjWRCdW0FSfxl93tz8D3iphapd3QHEWmA==, + } + engines: { node: ">=10" } + + "@faststore/sdk@3.96.0": + resolution: + { + integrity: sha512-k2z3apmeEcEkNJJKj5MdEA9OHfA/gY/kGbqlH8ahbZ1ysMa1IW/pnVBms60Ry3eIOdtZOMJ8x2piN8nwhU/37g==, + } + engines: { node: ">=10" } + peerDependencies: + react: ^18.2.0 + + "@faststore/ui@3.96.0": + resolution: + { + integrity: sha512-LGCx1IR0V+Y3XZSrQ1RZGDp8jd+ATaU0NZeeeUw/0D17/nEHFrCFrD5Kw55kNf14flgXDY13G3Z0TkqLLJt4Rw==, + } + engines: { node: ">=10" } + peerDependencies: + "@faststore/components": 3.x + react: ^18.2.0 + react-dom: ^18.2.0 + "@floating-ui/core@1.7.3": resolution: { @@ -19838,6 +19898,140 @@ snapshots: dependencies: fast-deep-equal: 3.1.3 + "@faststore/api@3.96.0(encoding@0.1.13)(graphql@15.8.0)(rollup@2.79.2)": + dependencies: + "@graphql-tools/load-files": 7.0.0(graphql@15.8.0) + "@graphql-tools/schema": 9.0.19(graphql@15.8.0) + "@graphql-tools/utils": 10.2.0(graphql@15.8.0) + "@rollup/plugin-graphql": 1.1.0(graphql@15.8.0)(rollup@2.79.2) + cookie: 0.7.2 + dataloader: 2.2.2 + fast-deep-equal: 3.1.3 + graphql: 15.8.0 + isomorphic-unfetch: 3.1.0(encoding@0.1.13) + p-limit: 3.1.0 + sanitize-html: 2.12.1 + transitivePeerDependencies: + - encoding + - rollup + + "@faststore/components@3.96.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)": + dependencies: + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-intersection-observer: 8.34.0(react@18.3.1) + react-swipeable: 7.0.0(react@18.3.1) + tabbable: 5.3.3 + + "@faststore/core@3.96.0(@babel/core@7.26.7)(@faststore/components@3.96.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@opentelemetry/api@1.4.1)(@types/node@16.18.57)(encoding@0.1.13)(rollup@2.79.2)(typescript@5.3.2)(use-sync-external-store@1.4.0(react@18.3.1))(webpack@5.97.1)": + dependencies: + "@antfu/ni": 0.21.12 + "@builder.io/partytown": 0.6.4 + "@envelop/core": 5.0.2 + "@envelop/graphql-jit": 8.0.3(@envelop/core@5.0.2)(graphql@15.8.0) + "@envelop/parser-cache": 6.0.4(@envelop/core@5.0.2)(graphql@15.8.0) + "@envelop/validation-cache": 6.0.4(@envelop/core@5.0.2)(graphql@15.8.0) + "@faststore/api": 3.96.0(encoding@0.1.13)(graphql@15.8.0)(rollup@2.79.2) + "@faststore/graphql-utils": 3.96.0(graphql@15.8.0) + "@faststore/lighthouse": 3.96.0 + "@faststore/sdk": 3.96.0(react@18.3.1)(use-sync-external-store@1.4.0(react@18.3.1)) + "@faststore/ui": 3.96.0(@faststore/components@3.96.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + "@graphql-codegen/cli": 5.0.2(@parcel/watcher@2.5.1)(@types/node@16.18.57)(encoding@0.1.13)(graphql@15.8.0)(typescript@5.3.2) + "@graphql-codegen/client-preset": 4.2.6(encoding@0.1.13)(graphql@15.8.0) + "@graphql-codegen/typescript": 4.0.7(encoding@0.1.13)(graphql@15.8.0) + "@graphql-codegen/typescript-operations": 4.2.1(encoding@0.1.13)(graphql@15.8.0) + "@graphql-tools/load-files": 7.0.0(graphql@15.8.0) + "@graphql-tools/merge": 9.0.4(graphql@15.8.0) + "@graphql-tools/schema": 10.0.3(graphql@15.8.0) + "@graphql-tools/utils": 10.2.0(graphql@15.8.0) + "@graphql-typed-document-node/core": 3.2.0(graphql@15.8.0) + "@lexical/code": 0.34.0 + "@lexical/headless": 0.34.0 + "@lexical/html": 0.34.0 + "@lexical/link": 0.34.0 + "@lexical/list": 0.34.0 + "@lexical/react": 0.34.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(yjs@13.6.27) + "@lexical/rich-text": 0.34.0 + "@parcel/watcher": 2.5.1 + "@types/react": 18.2.42 + "@vtex/client-cms": 0.2.16(encoding@0.1.13) + "@vtex/client-cp": 0.3.5 + "@vtex/prettier-config": 1.0.0(prettier@2.8.3) + autoprefixer: 10.4.13(postcss@8.5.1) + cookie: 0.7.2 + css-loader: 6.11.0(webpack@5.97.1) + deepmerge: 4.3.1 + draftjs-to-html: 0.9.1 + fast-deep-equal: 3.1.3 + fs-extra: 10.1.0 + graphql: 15.8.0 + include-media: 1.4.10 + isomorphic-unfetch: 3.1.0(encoding@0.1.13) + lexical: 0.34.0 + next: 13.5.11(@babel/core@7.26.7)(@opentelemetry/api@1.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.83.4) + next-seo: 6.6.0(next@13.5.11(@babel/core@7.26.7)(@opentelemetry/api@1.4.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.83.4))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + postcss: 8.5.1 + prettier: 2.8.3 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-intersection-observer: 8.34.0(react@18.3.1) + sass: 1.83.4 + sass-loader: 12.6.0(sass@1.83.4)(webpack@5.97.1) + sharp: 0.32.6 + style-loader: 3.3.1(webpack@5.97.1) + swr: 2.3.1(react@18.3.1) + tsx: 4.6.2 + transitivePeerDependencies: + - "@babel/core" + - "@faststore/components" + - "@opentelemetry/api" + - "@rspack/core" + - "@types/node" + - babel-plugin-macros + - bufferutil + - cosmiconfig-toml-loader + - debug + - encoding + - enquirer + - fibers + - immer + - node-sass + - rollup + - sass-embedded + - supports-color + - typescript + - use-sync-external-store + - utf-8-validate + - webpack + - yjs + + "@faststore/graphql-utils@3.96.0(graphql@15.8.0)": + dependencies: + graphql: 15.8.0 + + "@faststore/lighthouse@3.96.0": {} + + "@faststore/sdk@3.96.0(react@18.3.1)(use-sync-external-store@1.4.0(react@18.3.1))": + dependencies: + "@types/react": 18.2.42 + fast-deep-equal: 3.1.3 + idb-keyval: 5.1.5 + react: 18.3.1 + zustand: 5.0.6(@types/react@18.2.42)(react@18.3.1)(use-sync-external-store@1.4.0(react@18.3.1)) + transitivePeerDependencies: + - immer + - use-sync-external-store + + "@faststore/ui@3.96.0(@faststore/components@3.96.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)": + dependencies: + "@faststore/components": 3.96.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + include-media: 1.4.10 + modern-normalize: 1.1.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-swipeable: 7.0.0(react@18.3.1) + tabbable: 5.3.3 + "@floating-ui/core@1.7.3": dependencies: "@floating-ui/utils": 0.2.10 @@ -19925,6 +20119,56 @@ snapshots: - zen-observable - zenObservable + "@graphql-codegen/cli@5.0.2(@parcel/watcher@2.5.1)(@types/node@16.18.57)(encoding@0.1.13)(graphql@15.8.0)(typescript@5.3.2)": + dependencies: + "@babel/generator": 7.26.5 + "@babel/template": 7.25.9 + "@babel/types": 7.26.7 + "@graphql-codegen/client-preset": 4.2.6(encoding@0.1.13)(graphql@15.8.0) + "@graphql-codegen/core": 4.0.2(graphql@15.8.0) + "@graphql-codegen/plugin-helpers": 5.0.4(graphql@15.8.0) + "@graphql-tools/apollo-engine-loader": 8.0.1(encoding@0.1.13)(graphql@15.8.0) + "@graphql-tools/code-file-loader": 8.1.2(graphql@15.8.0) + "@graphql-tools/git-loader": 8.0.6(graphql@15.8.0) + "@graphql-tools/github-loader": 8.0.1(@types/node@16.18.57)(encoding@0.1.13)(graphql@15.8.0) + "@graphql-tools/graphql-file-loader": 8.0.1(graphql@15.8.0) + "@graphql-tools/json-file-loader": 8.0.1(graphql@15.8.0) + "@graphql-tools/load": 8.0.2(graphql@15.8.0) + "@graphql-tools/prisma-loader": 8.0.4(@types/node@16.18.57)(encoding@0.1.13)(graphql@15.8.0) + "@graphql-tools/url-loader": 8.0.2(@types/node@16.18.57)(encoding@0.1.13)(graphql@15.8.0) + "@graphql-tools/utils": 10.2.0(graphql@15.8.0) + "@whatwg-node/fetch": 0.8.8 + chalk: 4.1.2 + cosmiconfig: 8.3.6(typescript@5.3.2) + debounce: 1.2.1 + detect-indent: 6.1.0 + graphql: 15.8.0 + graphql-config: 5.0.3(@types/node@16.18.57)(encoding@0.1.13)(graphql@15.8.0)(typescript@5.3.2) + inquirer: 8.2.6 + is-glob: 4.0.3 + jiti: 1.21.7 + json-to-pretty-yaml: 1.2.2 + listr2: 4.0.5(enquirer@2.3.6) + log-symbols: 4.1.0 + micromatch: 4.0.8 + shell-quote: 1.7.4 + string-env-interpolation: 1.0.1 + ts-log: 2.2.5 + tslib: 2.8.1 + yaml: 2.7.0 + yargs: 17.7.2 + optionalDependencies: + "@parcel/watcher": 2.5.1 + transitivePeerDependencies: + - "@types/node" + - bufferutil + - cosmiconfig-toml-loader + - encoding + - enquirer + - supports-color + - typescript + - utf-8-validate + "@graphql-codegen/cli@5.0.2(@parcel/watcher@2.5.1)(@types/node@20.14.9)(encoding@0.1.13)(enquirer@2.3.6)(graphql@15.8.0)(typescript@5.3.2)": dependencies: "@babel/generator": 7.26.5 @@ -20258,6 +20502,19 @@ snapshots: transitivePeerDependencies: - "@types/node" + "@graphql-tools/executor-http@1.0.9(@types/node@16.18.57)(graphql@15.8.0)": + dependencies: + "@graphql-tools/utils": 10.2.0(graphql@15.8.0) + "@repeaterjs/repeater": 3.0.4 + "@whatwg-node/fetch": 0.9.17 + extract-files: 11.0.0 + graphql: 15.8.0 + meros: 1.2.1(@types/node@16.18.57) + tslib: 2.8.1 + value-or-promise: 1.0.12 + transitivePeerDependencies: + - "@types/node" + "@graphql-tools/executor-http@1.0.9(@types/node@20.14.9)(graphql@15.8.0)": dependencies: "@graphql-tools/utils": 10.2.0(graphql@15.8.0) @@ -20354,6 +20611,21 @@ snapshots: - encoding - supports-color + "@graphql-tools/github-loader@8.0.1(@types/node@16.18.57)(encoding@0.1.13)(graphql@15.8.0)": + dependencies: + "@ardatan/sync-fetch": 0.0.1(encoding@0.1.13) + "@graphql-tools/executor-http": 1.0.9(@types/node@16.18.57)(graphql@15.8.0) + "@graphql-tools/graphql-tag-pluck": 8.3.1(graphql@15.8.0) + "@graphql-tools/utils": 10.2.0(graphql@15.8.0) + "@whatwg-node/fetch": 0.9.17 + graphql: 15.8.0 + tslib: 2.8.1 + value-or-promise: 1.0.12 + transitivePeerDependencies: + - "@types/node" + - encoding + - supports-color + "@graphql-tools/github-loader@8.0.1(@types/node@20.14.9)(encoding@0.1.13)(graphql@15.8.0)": dependencies: "@ardatan/sync-fetch": 0.0.1(encoding@0.1.13) @@ -20522,6 +20794,32 @@ snapshots: - supports-color - utf-8-validate + "@graphql-tools/prisma-loader@8.0.4(@types/node@16.18.57)(encoding@0.1.13)(graphql@15.8.0)": + dependencies: + "@graphql-tools/url-loader": 8.0.2(@types/node@16.18.57)(encoding@0.1.13)(graphql@15.8.0) + "@graphql-tools/utils": 10.2.0(graphql@15.8.0) + "@types/js-yaml": 4.0.5 + "@whatwg-node/fetch": 0.9.17 + chalk: 4.1.2 + debug: 4.4.0(supports-color@8.1.1) + dotenv: 16.4.5 + graphql: 15.8.0 + graphql-request: 6.1.0(encoding@0.1.13)(graphql@15.8.0) + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.4 + jose: 5.3.0 + js-yaml: 4.1.0 + lodash: 4.17.21 + scuid: 1.1.0 + tslib: 2.8.1 + yaml-ast-parser: 0.0.43 + transitivePeerDependencies: + - "@types/node" + - bufferutil + - encoding + - supports-color + - utf-8-validate + "@graphql-tools/prisma-loader@8.0.4(@types/node@20.14.9)(encoding@0.1.13)(graphql@15.8.0)": dependencies: "@graphql-tools/url-loader": 8.0.2(@types/node@20.14.9)(encoding@0.1.13)(graphql@15.8.0) @@ -20614,6 +20912,28 @@ snapshots: - encoding - utf-8-validate + "@graphql-tools/url-loader@8.0.2(@types/node@16.18.57)(encoding@0.1.13)(graphql@15.8.0)": + dependencies: + "@ardatan/sync-fetch": 0.0.1(encoding@0.1.13) + "@graphql-tools/delegate": 10.0.10(graphql@15.8.0) + "@graphql-tools/executor-graphql-ws": 1.1.2(graphql@15.8.0) + "@graphql-tools/executor-http": 1.0.9(@types/node@16.18.57)(graphql@15.8.0) + "@graphql-tools/executor-legacy-ws": 1.0.6(graphql@15.8.0) + "@graphql-tools/utils": 10.2.0(graphql@15.8.0) + "@graphql-tools/wrap": 10.0.5(graphql@15.8.0) + "@types/ws": 8.5.4 + "@whatwg-node/fetch": 0.9.17 + graphql: 15.8.0 + isomorphic-ws: 5.0.0(ws@8.18.0) + tslib: 2.8.1 + value-or-promise: 1.0.12 + ws: 8.18.0 + transitivePeerDependencies: + - "@types/node" + - bufferutil + - encoding + - utf-8-validate + "@graphql-tools/url-loader@8.0.2(@types/node@20.14.9)(encoding@0.1.13)(graphql@15.8.0)": dependencies: "@ardatan/sync-fetch": 0.0.1(encoding@0.1.13) @@ -26092,6 +26412,27 @@ snapshots: - encoding - utf-8-validate + graphql-config@5.0.3(@types/node@16.18.57)(encoding@0.1.13)(graphql@15.8.0)(typescript@5.3.2): + dependencies: + "@graphql-tools/graphql-file-loader": 8.0.1(graphql@15.8.0) + "@graphql-tools/json-file-loader": 8.0.1(graphql@15.8.0) + "@graphql-tools/load": 8.0.2(graphql@15.8.0) + "@graphql-tools/merge": 9.0.4(graphql@15.8.0) + "@graphql-tools/url-loader": 8.0.2(@types/node@16.18.57)(encoding@0.1.13)(graphql@15.8.0) + "@graphql-tools/utils": 10.2.0(graphql@15.8.0) + cosmiconfig: 8.3.6(typescript@5.3.2) + graphql: 15.8.0 + jiti: 1.21.7 + minimatch: 4.2.3 + string-env-interpolation: 1.0.1 + tslib: 2.8.1 + transitivePeerDependencies: + - "@types/node" + - bufferutil + - encoding + - typescript + - utf-8-validate + graphql-config@5.0.3(@types/node@20.14.9)(encoding@0.1.13)(graphql@15.8.0)(typescript@5.3.2): dependencies: "@graphql-tools/graphql-file-loader": 8.0.1(graphql@15.8.0) @@ -28264,6 +28605,10 @@ snapshots: merge2@1.4.1: {} + meros@1.2.1(@types/node@16.18.57): + optionalDependencies: + "@types/node": 16.18.57 + meros@1.2.1(@types/node@18.19.74): optionalDependencies: "@types/node": 18.19.74