From be430092d9b15f077d2a4387589725e1d27867e5 Mon Sep 17 00:00:00 2001 From: Priom Chowdhury Date: Mon, 16 Mar 2026 15:16:49 -0400 Subject: [PATCH 01/10] perf(build): reduce CF Pages worker bundle size below 3 MiB Co-Authored-By: Claude Sonnet 4.6 --- .claude/settings.local.json | 9 +++++ public/manifest.json | 8 ++-- src/components/trade/header/top-nav.tsx | 4 +- src/config/constants.ts | 10 ++--- src/config/hyperliquid.ts | 6 +-- vite.config.ts | 52 +++++++++++++++++++++++++ 6 files changed, 75 insertions(+), 14 deletions(-) create mode 100644 .claude/settings.local.json diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 00000000..0277bb81 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,9 @@ +{ + "permissions": { + "allow": [ + "Bash(find node_modules/.pnpm -path */start-plugin-core/dist/esm/index.d.ts)", + "Bash(find node_modules/.pnpm -path */start-plugin-core/dist/esm/*.d.ts)", + "Bash(NITRO_PRESET=cloudflare-pages pnpm build)" + ] + } +} diff --git a/public/manifest.json b/public/manifest.json index 0b7a8956..6457d99b 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -1,6 +1,6 @@ { - "short_name": "HypeTerminal", - "name": "HypeTerminal", + "short_name": "HyperOdd", + "name": "HyperOdd", "icons": [ { "src": "favicon.ico", @@ -16,6 +16,6 @@ ], "start_url": "/", "display": "standalone", - "theme_color": "#0a0a0a", - "background_color": "#0a0a0a" + "theme_color": "#1c1c1d", + "background_color": "#1c1c1d" } diff --git a/src/components/trade/header/top-nav.tsx b/src/components/trade/header/top-nav.tsx index 2d55fe84..20567067 100644 --- a/src/components/trade/header/top-nav.tsx +++ b/src/components/trade/header/top-nav.tsx @@ -63,8 +63,8 @@ export function TopNav() { - HYPE - TERMINAL + HyperOdd + Terminal
diff --git a/src/config/constants.ts b/src/config/constants.ts index 78565692..ac42a18c 100644 --- a/src/config/constants.ts +++ b/src/config/constants.ts @@ -1,6 +1,6 @@ import type { ChartingLibraryFeatureset, ResolutionString, TimeFrameItem } from "@/types/charting_library"; -export const APP_NAME = "HypeTerminal"; +export const APP_NAME = "HyperOdd"; export const APP_VERSION = "v0.1.0"; export const QUICK_PERCENT_OPTIONS = [25, 50, 100, 200, 400] as const; @@ -48,7 +48,7 @@ export const STORAGE_KEYS = { ORDER_ENTRY: "order-entry-v2", } as const; -export const GITHUB_URL = "https://github.com/vipineth/hypeterminal/"; +export const GITHUB_URL = "https://github.com/hyperodd/terminal/"; export const TOKEN_ICON_BASE_URL = "https://app.hyperliquid.xyz/coins"; export const PANEL_LAYOUT = { @@ -78,11 +78,11 @@ export const SIDEBAR_LAYOUT = { export const SEO_DEFAULTS = { siteName: APP_NAME, - siteUrl: "https://hypeterminal.xyz", - defaultTitle: "HypeTerminal - Hyperliquid Trading Terminal", + siteUrl: "https://hyperodd.com", + defaultTitle: "HyperOdd - HyperOdd Trading Terminal", defaultDescription: "A professional trading terminal for Hyperliquid DEX. Trade perpetuals and spot markets with real-time data, advanced charting, and seamless wallet connectivity.", - twitterHandle: "@hypeterminal", + twitterHandle: "@hyperoddx", locale: "en_US", themeColor: "#0a0a0a", } as const; diff --git a/src/config/hyperliquid.ts b/src/config/hyperliquid.ts index e2fd9377..34c4a7e3 100644 --- a/src/config/hyperliquid.ts +++ b/src/config/hyperliquid.ts @@ -1,7 +1,7 @@ import type { BuilderConfig } from "@/lib/hyperliquid"; -export const PROJECT_NAME = "HypeTerminal"; +export const PROJECT_NAME = "HyperOdd"; export const DEFAULT_BUILDER_CONFIG: BuilderConfig = { - b: "0x744e2f0b69456B42278B3e797a58Ff57e5180A7E", // builder - f: 10, // fee rate 0.01% + b: "0xe2E7BFE54DDfe5983807C187A57A939C33Eaeb2e", // builder + f: 1, // fee rate 0.001% }; diff --git a/vite.config.ts b/vite.config.ts index 5e68a54d..d1f37288 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -9,6 +9,49 @@ import { visualizer } from 'rollup-plugin-visualizer' const isAnalyze = process.env.ANALYZE === 'true' +const browserOnlyModules: Record = { + klinecharts: ` + export const init = () => ({}); + export const dispose = () => {}; + export const FormatDateType = {}; + export const LoadDataType = {}; + export const CandleType = {}; + export const LineType = {}; + export const TooltipShowRule = {}; + export const TooltipShowType = {}; + export const YAxisPosition = {}; + export default {}; + `, + 'motion/react': ` + import { createElement, forwardRef } from 'react'; + const el = (tag) => forwardRef(({ children, animate, initial, exit, transition, variants, + whileHover, whileTap, whileFocus, whileInView, whileDrag, drag, dragConstraints, + layout, layoutId, onAnimationStart, onAnimationComplete, ...rest }, ref) => + createElement(tag, { ...rest, ref }, children)); + export const motion = new Proxy({}, { get: (_, tag) => el(tag) }); + export const m = motion; + export const AnimatePresence = ({ children }) => children; + export const MotionConfig = ({ children }) => children; + export const LazyMotion = ({ children }) => children; + export const useReducedMotion = () => null; + export const domAnimation = {}; + export const domMax = {}; + export default {}; + `, +} + +const ssrStubPlugin = { + name: 'ssr-stub-browser-only-modules', + enforce: 'pre' as const, + resolveId(id: string, _: string | undefined, options: { ssr?: boolean } | undefined) { + if (options?.ssr && id in browserOnlyModules) return `\0virtual:${id}` + }, + load(id: string) { + const name = id.replace('\0virtual:', '') + if (name in browserOnlyModules) return browserOnlyModules[name] + }, +} + function createManualChunks(id: string) { if (id.includes('node_modules')) { if (id.includes('@radix-ui')) return 'vendor-radix' @@ -38,8 +81,17 @@ const config = defineConfig({ }, }, plugins: [ + ssrStubPlugin, nitro({ compressPublicAssets: true, + minify: true, + rollupConfig: { + treeshake: { + moduleSideEffects: false, + propertyReadSideEffects: false, + unknownGlobalSideEffects: false, + }, + }, routeRules: { '/assets/**': { headers: { 'cache-control': 'public, max-age=31536000, immutable' }, From 0fc4e4900b0d334e45bdcb876257eef0530c819c Mon Sep 17 00:00:00 2001 From: Priom Chowdhury Date: Mon, 16 Mar 2026 20:31:05 -0400 Subject: [PATCH 02/10] perf(build): stub wagmi/connectors in SSR to reduce worker bundle Co-Authored-By: Claude Sonnet 4.6 --- vite.config.ts | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/vite.config.ts b/vite.config.ts index d1f37288..048b1381 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -38,6 +38,39 @@ const browserOnlyModules: Record = { export const domMax = {}; export default {}; `, + wagmi: ` + import { createElement } from 'react'; + export const WagmiProvider = ({ children }) => children; + export const createConfig = () => ({}); + export const http = () => ({}); + export const useAccount = () => ({ address: undefined, isConnected: false, status: 'disconnected' }); + export const useBalance = () => ({ data: undefined, isLoading: false, error: null }); + export const useConnect = () => ({ connect: () => {}, connectors: [], status: 'idle', error: null }); + export const useConnectors = () => []; + export const useConnection = () => ({ address: undefined, isConnected: false, connector: null, status: 'disconnected' }); + export const useDisconnect = () => ({ disconnect: () => {} }); + export const useEnsName = () => ({ data: undefined, isLoading: false }); + export const useSwitchChain = () => ({ switchChain: () => {}, chains: [], status: 'idle', error: null }); + export const useWalletClient = () => ({ data: null, isLoading: false, error: null }); + export const usePublicClient = () => undefined; + export const useChainId = () => 42161; + export const useSignMessage = () => ({ signMessage: () => {}, status: 'idle', data: undefined }); + export const useWriteContract = () => ({ writeContract: () => {}, status: 'idle', data: undefined }); + export const useWaitForTransactionReceipt = () => ({ data: undefined, isLoading: false, status: 'idle', error: null }); + export const useReadContract = () => ({ data: undefined, isLoading: false, error: null }); + export const serialize = (x) => JSON.stringify(x); + export const deserialize = JSON.parse; + export const cookieToInitialState = () => undefined; + export const cookieStorage = {}; + export default {}; + `, + 'wagmi/connectors': ` + export const injected = () => ({}); + export const coinbaseWallet = () => ({}); + export const walletConnect = () => ({}); + export const mock = () => ({}); + export default {}; + `, } const ssrStubPlugin = { From 73b60350e638de2be31ba6f6af84324a9ee0b79f Mon Sep 17 00:00:00 2001 From: alex luu Date: Tue, 17 Mar 2026 20:34:59 -0400 Subject: [PATCH 03/10] feat: add privy --- .env.example | 3 + package.json | 3 + .../trade/components/wallet-dialog.tsx | 373 +----------------- src/components/trade/header/user-menu.tsx | 33 +- .../trade/mobile/mobile-account-view.tsx | 22 +- .../trade/mobile/mobile-trade-view.tsx | 12 +- .../trade/positions/send-dialog.tsx | 4 +- .../trade/tradebox/account-panel.tsx | 15 +- src/components/trade/tradebox/trade-panel.tsx | 18 +- src/config/constants.ts | 2 + src/config/privy.ts | 15 + src/config/wagmi.ts | 51 +-- src/domain/trade/balances.ts | 18 +- src/domain/trade/order/derive.ts | 16 +- src/domain/trade/order/size.ts | 14 + src/domain/trade/swap.ts | 17 +- src/hooks/trade/use-asset-leverage.ts | 24 +- src/hooks/use-privy-wagmi-sync.ts | 26 ++ src/lib/hyperliquid/clients.ts | 4 +- .../exchange/useExchangeUserSetAbstraction.ts | 37 ++ .../hooks/info/useInfoUserAbstraction.ts | 62 +++ src/lib/hyperliquid/hooks/useTradingGuard.ts | 5 +- src/lib/hyperliquid/signing/types.ts | 8 +- .../signing/use-agent-registration.ts | 14 +- src/lib/trade/use-button-content.ts | 2 + src/lib/wallet-utils.ts | 49 +-- src/locales/ar/messages.po | 54 +-- src/locales/en/messages.po | 54 +-- src/locales/es/messages.po | 54 +-- src/locales/fr/messages.po | 54 +-- src/locales/hi/messages.po | 54 +-- src/locales/zh/messages.po | 54 +-- src/providers/root.tsx | 27 +- vite.config.ts | 21 +- 34 files changed, 527 insertions(+), 692 deletions(-) create mode 100644 .env.example create mode 100644 src/config/privy.ts create mode 100644 src/hooks/use-privy-wagmi-sync.ts create mode 100644 src/lib/hyperliquid/hooks/exchange/useExchangeUserSetAbstraction.ts create mode 100644 src/lib/hyperliquid/hooks/info/useInfoUserAbstraction.ts diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..8a6cf76d --- /dev/null +++ b/.env.example @@ -0,0 +1,3 @@ +VITE_PRIVY_APP_ID=your-privy-app-id +# Set to "true" for Hyperliquid testnet, "false" for mainnet +VITE_HYPERLIQUID_TESTNET=true diff --git a/package.json b/package.json index 8f64fec1..7700fecc 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,8 @@ "@lingui/react": "5.7.0", "@nktkas/hyperliquid": "0.31.0", "@phosphor-icons/react": "2.1.10", + "@privy-io/react-auth": "^3.17.0", + "@privy-io/wagmi": "^4.0.2", "@tanstack/react-query": "5.66.5", "@tanstack/react-router": "1.132.0", "@tanstack/react-router-ssr-query": "1.131.7", @@ -49,6 +51,7 @@ "tw-animate-css": "1.4.0", "vaul": "1.1.2", "viem": "2.42.1", + "vite-plugin-node-polyfills": "^0.25.0", "wagmi": "3.1.0", "zod": "4.3.4", "zustand": "5.0.9" diff --git a/src/components/trade/components/wallet-dialog.tsx b/src/components/trade/components/wallet-dialog.tsx index 29c74a31..c171ee12 100644 --- a/src/components/trade/components/wallet-dialog.tsx +++ b/src/components/trade/components/wallet-dialog.tsx @@ -1,24 +1,5 @@ -import { Trans } from "@lingui/react/macro"; -import { - ArrowSquareOutIcon, - FlaskIcon, - QuestionIcon, - ShieldIcon, - SpinnerGapIcon, - WalletIcon, - WarningCircleIcon, -} from "@phosphor-icons/react"; -import { useMemo, useState } from "react"; -import type { Address } from "viem"; -import { isAddress } from "viem"; -import { type Connector, useConnect, useConnectors } from "wagmi"; -import { mock } from "wagmi/connectors"; -import { Button } from "@/components/ui/button"; -import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog"; -import { Input } from "@/components/ui/input"; -import { MOCK_WALLETS } from "@/config/wagmi"; -import { cn } from "@/lib/cn"; -import { getLastUsedWallet, getWalletInfo, isMockConnector, setLastUsedWallet } from "@/lib/wallet-utils"; +import { useLogin } from "@privy-io/react-auth"; +import { useEffect } from "react"; interface Props { open: boolean; @@ -26,346 +7,16 @@ interface Props { } export function WalletDialog({ open, onOpenChange }: Props) { - const connectors = useConnectors(); - const { mutateAsync: connectAsync, isPending, error } = useConnect(); - const [connectingId, setConnectingId] = useState(null); - const [showHelp, setShowHelp] = useState(false); - const [lastUsedWallet] = useState(() => getLastUsedWallet()); - const [customAddress, setCustomAddress] = useState(""); - const [customAddressError, setCustomAddressError] = useState(null); - - const { mockConnectors, regularConnectors } = useMemo(() => { - const mocks: Connector[] = []; - const regular: Connector[] = []; - - for (const connector of connectors) { - if (isMockConnector(connector)) { - mocks.push(connector); - } else { - regular.push(connector); - } - } - - return { mockConnectors: mocks, regularConnectors: regular }; - }, [connectors]); - - const availableConnectors = useMemo(() => { - const sortByPriority = (a: Connector, b: Connector) => { - if (lastUsedWallet) { - if (a.id === lastUsedWallet) return -1; - if (b.id === lastUsedWallet) return 1; - } - const priorityA = getWalletInfo(a).priority ?? 50; - const priorityB = getWalletInfo(b).priority ?? 50; - return priorityA - priorityB; - }; - - const popular = regularConnectors.filter((c) => getWalletInfo(c).popular).sort(sortByPriority); - const other = regularConnectors.filter((c) => !getWalletInfo(c).popular).sort(sortByPriority); - return { popular, other, all: regularConnectors }; - }, [regularConnectors, lastUsedWallet]); - - const handleConnect = async (connector: Connector) => { - setConnectingId(connector.uid); - setLastUsedWallet(connector.id); - try { - await connectAsync({ connector }); - onOpenChange(false); - } finally { - setConnectingId(null); - } - }; - - const handleCustomAddressConnect = async () => { - const trimmed = customAddress.trim(); - if (!trimmed) { - setCustomAddressError("Please enter an address"); - return; - } - if (!isAddress(trimmed)) { - setCustomAddressError("Invalid Ethereum address"); - return; - } - setCustomAddressError(null); - - const mockWalletIndex = MOCK_WALLETS.findIndex((w) => w.address.toLowerCase() === trimmed.toLowerCase()); - - if (mockWalletIndex !== -1 && mockConnectors[mockWalletIndex]) { - handleConnect(mockConnectors[mockWalletIndex]); - } else { - const customMockConnector = mock({ - accounts: [trimmed as Address], - features: { reconnect: true }, - }); - setConnectingId("custom-mock"); - try { - await connectAsync({ connector: customMockConnector }); - onOpenChange(false); - } catch { - setCustomAddressError("Failed to connect with custom address"); - } finally { - setConnectingId(null); - } + const { login } = useLogin({ + onComplete: () => onOpenChange(false), + onError: () => onOpenChange(false), + }); + + useEffect(() => { + if (open) { + login(); } - }; - - const hasConnectors = availableConnectors.all.length > 0 || mockConnectors.length > 0; - - return ( - - -
- - - - Connect Wallet - - - Connect your wallet to start trading on Hyperliquid - - -
- -
- {availableConnectors.popular.length > 0 && ( -
-

- Popular -

-
- {availableConnectors.popular.map((connector) => { - const walletInfo = getWalletInfo(connector); - const Icon = walletInfo.icon; - const isConnecting = connectingId === connector.uid; - - return ( - - ); - })} -
-
- )} - - {availableConnectors.other.length > 0 && ( -
-

- Other Options -

-
- {availableConnectors.other.map((connector) => { - const walletInfo = getWalletInfo(connector); - const Icon = walletInfo.icon; - const isConnecting = connectingId === connector.uid; - - return ( - - ); - })} -
-
- )} - - {mockConnectors.length > 0 && ( -
-

- Mock Wallet (Testing) -

-
- {mockConnectors.map((connector, index) => { - const config = MOCK_WALLETS[index]; - const isConnecting = connectingId === connector.uid; - - return ( - - ); - })} -
-
-
- { - setCustomAddress(e.target.value); - setCustomAddressError(null); - }} - className="font-mono text-xs" - /> - -
- {customAddressError &&

{customAddressError}

} -
-
- )} - - {!hasConnectors && ( -
-
- -
-
-

- No wallets found -

-

- Install a wallet extension to continue -

-
-
- )} - - {error && ( -
- -

{error.message}

-
- )} -
- -
- + }, [open, login]); - {showHelp && ( -
-
- -
-

- Secure & Private -

-

- Only you control your funds. No email or password required. -

-
-
-
- -
-

- What is a wallet? -

-

- A crypto wallet lets you store and manage your digital assets securely. -

-
-
- -
- )} -
-
-
- ); + return null; } diff --git a/src/components/trade/header/user-menu.tsx b/src/components/trade/header/user-menu.tsx index aea5ba19..5ec00257 100644 --- a/src/components/trade/header/user-menu.tsx +++ b/src/components/trade/header/user-menu.tsx @@ -8,6 +8,7 @@ import { SpinnerGapIcon, WalletIcon, } from "@phosphor-icons/react"; +import { useLogin, useLogout, usePrivy } from "@privy-io/react-auth"; import { useEffect, useState } from "react"; import { useConnection, useDisconnect, useEnsName } from "wagmi"; import { Button } from "@/components/ui/button"; @@ -20,7 +21,6 @@ import { } from "@/components/ui/dropdown-menu"; import { useCopyToClipboard } from "@/hooks/ui/use-copy-to-clipboard"; import { shortenAddress } from "@/lib/format"; -import { WalletDialog } from "../components/wallet-dialog"; function CopyAddressMenuItem({ address }: { address: string }) { const { copied, copy } = useCopyToClipboard(); @@ -46,15 +46,25 @@ function CopyAddressMenuItem({ address }: { address: string }) { export function UserMenu() { const { address, isConnected, isConnecting } = useConnection(); - const disconnect = useDisconnect(); + const { authenticated } = usePrivy(); + const { login } = useLogin(); + const { logout } = useLogout(); + const { disconnect } = useDisconnect(); const { data: ensName } = useEnsName({ address }); - const [isOpen, setIsOpen] = useState(false); const [mounted, setMounted] = useState(false); useEffect(() => { setMounted(true); }, []); + function handleLogout() { + if (authenticated) { + logout(); + } else { + disconnect(); + } + } + if (!mounted || isConnecting) { return ( - - + ); } @@ -95,11 +102,7 @@ export function UserMenu() { - disconnect.mutate()} - > + Disconnect diff --git a/src/components/trade/mobile/mobile-account-view.tsx b/src/components/trade/mobile/mobile-account-view.tsx index 0431d981..d2896520 100644 --- a/src/components/trade/mobile/mobile-account-view.tsx +++ b/src/components/trade/mobile/mobile-account-view.tsx @@ -1,5 +1,5 @@ import { ArrowSquareOutIcon, CopyIcon, LightningIcon, SignOutIcon, WalletIcon } from "@phosphor-icons/react"; -import { useState } from "react"; +import { useLogin, useLogout, usePrivy } from "@privy-io/react-auth"; import { useConnection, useDisconnect } from "wagmi"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; @@ -11,7 +11,6 @@ import { cn } from "@/lib/cn"; import { formatPercent, formatUSD } from "@/lib/format"; import { toNumber, toNumberOrZero } from "@/lib/trade/numbers"; import { useDepositModalActions } from "@/stores/use-global-modal-store"; -import { WalletDialog } from "../components/wallet-dialog"; import { MobileBottomNavSpacer } from "./mobile-bottom-nav"; const ACCOUNT_TEXT = UI_TEXT.ACCOUNT_PANEL; @@ -22,11 +21,21 @@ interface MobileAccountViewProps { export function MobileAccountView({ className }: MobileAccountViewProps) { const { address, isConnected } = useConnection(); - const disconnect = useDisconnect(); + const { authenticated } = usePrivy(); + const { login } = useLogin(); + const { logout } = useLogout(); + const { disconnect } = useDisconnect(); const { perpSummary, perpPositions, isLoading } = useAccountBalances(); - const [walletDialogOpen, setWalletDialogOpen] = useState(false); + function handleLogout() { + if (authenticated) { + logout(); + } else { + disconnect(); + } + } + const { copied, copy } = useCopyToClipboard(); const { open: openDepositModal } = useDepositModalActions(); @@ -64,7 +73,7 @@ export function MobileAccountView({ className }: MobileAccountViewProps) {
- ); } @@ -113,7 +121,7 @@ export function MobileAccountView({ className }: MobileAccountViewProps) { */ -export function createLazyComponent, K extends keyof T>( +export function createLazyComponent, K extends keyof T>( importer: () => Promise, exportName: K, ) { diff --git a/src/lib/trade/use-order-validation.ts b/src/lib/trade/use-order-validation.ts index 7d4e0b6a..918cb7b1 100644 --- a/src/lib/trade/use-order-validation.ts +++ b/src/lib/trade/use-order-validation.ts @@ -56,7 +56,12 @@ export function perpInput(base: BaseOrderInput, perp: PerpOrderFields): PerpVali return { ...base, ...perp, isSpotMarket: false }; } -function toResult(result: { valid: boolean; errors: { message: string }[]; canSubmit: boolean; needsApproval: boolean }): ValidationResult { +function toResult(result: { + valid: boolean; + errors: { message: string }[]; + canSubmit: boolean; + needsApproval: boolean; +}): ValidationResult { return { valid: result.valid, errors: result.errors.map((e) => e.message), From 95e2b95cef4cce8f1988adf1ae8545b7027c14f7 Mon Sep 17 00:00:00 2001 From: alex luu Date: Tue, 17 Mar 2026 21:52:09 -0400 Subject: [PATCH 10/10] fix: tests --- src/lib/test-setup/lingui-macro-stub.ts | 7 +++++++ vitest.config.ts | 12 +++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 src/lib/test-setup/lingui-macro-stub.ts diff --git a/src/lib/test-setup/lingui-macro-stub.ts b/src/lib/test-setup/lingui-macro-stub.ts new file mode 100644 index 00000000..33df89b1 --- /dev/null +++ b/src/lib/test-setup/lingui-macro-stub.ts @@ -0,0 +1,7 @@ +/** + * Stub for @lingui/core/macro in tests. Avoids requiring babel-plugin-macros at runtime. + * Template tag returns the literal string (no translation). + */ +export function t(strings: TemplateStringsArray, ...values: unknown[]): string { + return strings.reduce((acc, s, i) => acc + s + (values[i] ?? ""), ""); +} diff --git a/vitest.config.ts b/vitest.config.ts index ba7485eb..a999d9e4 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -1,8 +1,18 @@ +import { lingui } from "@lingui/vite-plugin"; +import path from "node:path"; import { defineConfig } from "vitest/config"; import viteTsConfigPaths from "vite-tsconfig-paths"; export default defineConfig({ - plugins: [viteTsConfigPaths({ projects: ["./tsconfig.json"] })], + plugins: [ + viteTsConfigPaths({ projects: ["./tsconfig.json"] }), + lingui(), + ], + resolve: { + alias: { + "@lingui/core/macro": path.resolve(__dirname, "src/lib/test-setup/lingui-macro-stub.ts"), + }, + }, test: { environment: "node", },