diff --git a/.prettierrc b/.prettierrc index 76f930a..92102b9 100644 --- a/.prettierrc +++ b/.prettierrc @@ -8,5 +8,4 @@ "tabWidth": 2, "proseWrap": "preserve", "tailwindFunctions": ["clsx", "tw"] - } diff --git a/CHANGELOG.md b/CHANGELOG.md index ddce091..4c2725e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,39 +8,46 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [0.2.0] - 2026-01-16 ### New Features + - Enhanced cloudHandler to inject minimal auth state when database entry is missing, improving onboarding reliability. - Implemented stability fixes and enhanced error handling across the application. ### Improvements + - Upgraded Electron from 32.3.3 to 37.3.1 for improved performance and security. - Conditionally include plugins based on start command in forge.config.ts for better build flexibility. ### Bug Fixes + - Fixed "Converting circular structure to JSON" error. ### Documentation + - Added curly brace constraints for conditional statements. - Fixed incorrect reference documentation name. ## [0.1.1] - 2026-01-11 ### Bug Fixes + - Fix Antigravity visibility issue on account switch. (Fixed [#19](https://github.com/Draculabo/AntigravityManager/issues/19)) ## [0.1.0] - 2026-01-10 ### New Features + - LAN Connection Support: Users can now connect via Local Area Network (LAN) for improved flexibility and internal environment support. - Antigravity Integration: Added native support and adaptation for Antigravity, enhancing overall compatibility. - Local API Proxy: Built-in OpenAI/Anthropic compatible proxy server. ### Bug Fixes -- Reverse Proxy Issue: Resolved a critical error occurring during reverse proxy configurations. (Fixed [#11](https://github.com/Draculabo/AntigravityManager/issues/11)) +- Reverse Proxy Issue: Resolved a critical error occurring during reverse proxy configurations. (Fixed [#11](https://github.com/Draculabo/AntigravityManager/issues/11)) ## [0.0.1] - 2025-12-22 ### Added + - Initial release of Antigravity Manager - Multi-account management for Google Gemini and Claude - Real-time quota monitoring diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 4cb82e9..01d67af 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -8,19 +8,19 @@ In the interest of fostering an open and welcoming environment, we as contributo Examples of behavior that contributes to creating a positive environment include: -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community +- Showing empathy towards other community members Examples of unacceptable behavior by participants include: -* The use of sexualized language or imagery and unwelcome sexual attention or advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a professional setting +- The use of sexualized language or imagery and unwelcome sexual attention or advances +- Trolling, insulting/derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or electronic address, without explicit permission +- Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities @@ -45,4 +45,5 @@ This Code of Conduct is adapted from the [Contributor Covenant][homepage], versi [homepage]: https://www.contributor-covenant.org --- + **Note**: Please replace `[INSERT EMAIL ADDRESS]` with your actual contact email if you wish to receive reports. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e731c79..e729381 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -52,20 +52,21 @@ npm start ### Available Scripts -| Command | Description | -|---------|-------------| -| `npm start` | Start the app in development mode | -| `npm run lint` | Run ESLint to check for code issues | -| `npm run format:write` | Format code with Prettier | -| `npm run test:unit` | Run unit tests with Vitest | -| `npm run test:e2e` | Run E2E tests with Playwright | -| `npm run test:all` | Run all tests | -| `npm run type-check` | Run TypeScript type checking | -| `npm run make` | Build production packages | +| Command | Description | +| ---------------------- | ----------------------------------- | +| `npm start` | Start the app in development mode | +| `npm run lint` | Run ESLint to check for code issues | +| `npm run format:write` | Format code with Prettier | +| `npm run test:unit` | Run unit tests with Vitest | +| `npm run test:e2e` | Run E2E tests with Playwright | +| `npm run test:all` | Run all tests | +| `npm run type-check` | Run TypeScript type checking | +| `npm run make` | Build production packages | ## ✏️ Making Changes 1. **Create a new branch** from `main`: + ```bash git checkout -b feature/your-feature-name # or @@ -73,6 +74,7 @@ npm start ``` 2. **Make your changes** and commit them: + ```bash git add . git commit -m "feat: add amazing new feature" @@ -97,6 +99,7 @@ We follow [Conventional Commits](https://www.conventionalcommits.org/). Each com ``` **Types:** + - `feat`: A new feature - `fix`: A bug fix - `docs`: Documentation only changes @@ -107,6 +110,7 @@ We follow [Conventional Commits](https://www.conventionalcommits.org/). Each com - `chore`: Changes to build process or auxiliary tools **Examples:** + ``` feat(auth): add Google OAuth support fix(quota): resolve quota refresh timeout issue diff --git a/README.zh-CN.md b/README.zh-CN.md index 6e90336..09f3c40 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -174,7 +174,6 @@ 设置界面

- --- ## 🚀 快速开始 @@ -183,11 +182,11 @@ 从 [Releases](https://github.com/Draculabo/AntigravityManager/releases) 页面下载适合你平台的最新版本。 -| 平台 | 下载链接 | -|------|----------| +| 平台 | 下载链接 | +| ------- | ------------------------------------------------------------------------------ | | Windows | [.exe 安装包](https://github.com/Draculabo/AntigravityManager/releases/latest) | -| macOS | [.dmg 安装包](https://github.com/Draculabo/AntigravityManager/releases/latest) | -| Linux | [.deb / .rpm](https://github.com/Draculabo/AntigravityManager/releases/latest) | +| macOS | [.dmg 安装包](https://github.com/Draculabo/AntigravityManager/releases/latest) | +| Linux | [.deb / .rpm](https://github.com/Draculabo/AntigravityManager/releases/latest) | ### 从源码构建 @@ -217,14 +216,14 @@ npm run make ## 🛠️ 技术栈 -| 类别 | 技术 | -|------|------| -| **核心** | [Electron](https://www.electronjs.org/)、[React](https://react.dev/)、[TypeScript](https://www.typescriptlang.org/) | -| **构建工具** | [Vite](https://vitejs.dev/) | -| **样式** | [TailwindCSS](https://tailwindcss.com/)、[Shadcn UI](https://ui.shadcn.com/) | -| **状态管理** | [TanStack Query](https://tanstack.com/query/latest)、[TanStack Router](https://tanstack.com/router/latest) | -| **数据库** | [Better-SQLite3](https://github.com/WiseLibs/better-sqlite3) | -| **测试** | [Vitest](https://vitest.dev/)、[Playwright](https://playwright.dev/) | +| 类别 | 技术 | +| ------------ | ------------------------------------------------------------------------------------------------------------------- | +| **核心** | [Electron](https://www.electronjs.org/)、[React](https://react.dev/)、[TypeScript](https://www.typescriptlang.org/) | +| **构建工具** | [Vite](https://vitejs.dev/) | +| **样式** | [TailwindCSS](https://tailwindcss.com/)、[Shadcn UI](https://ui.shadcn.com/) | +| **状态管理** | [TanStack Query](https://tanstack.com/query/latest)、[TanStack Router](https://tanstack.com/router/latest) | +| **数据库** | [Better-SQLite3](https://github.com/WiseLibs/better-sqlite3) | +| **测试** | [Vitest](https://vitest.dev/)、[Playwright](https://playwright.dev/) | --- @@ -232,16 +231,16 @@ npm run make ### 可用脚本 -| 命令 | 描述 | -|------|------| -| `npm start` | 启动开发模式 | -| `npm run lint` | 运行 ESLint 检查 | +| 命令 | 描述 | +| ---------------------- | ------------------------ | +| `npm start` | 启动开发模式 | +| `npm run lint` | 运行 ESLint 检查 | | `npm run format:write` | 使用 Prettier 格式化代码 | -| `npm run test:unit` | 运行单元测试 | -| `npm run test:e2e` | 运行 E2E 测试 | -| `npm run test:all` | 运行所有测试 | -| `npm run type-check` | TypeScript 类型检查 | -| `npm run make` | 构建生产包 | +| `npm run test:unit` | 运行单元测试 | +| `npm run test:e2e` | 运行 E2E 测试 | +| `npm run test:all` | 运行所有测试 | +| `npm run type-check` | TypeScript 类型检查 | +| `npm run make` | 构建生产包 | ### 项目结构 @@ -265,6 +264,7 @@ AntigravityManager/ Q: 程序无法启动怎么办? 请检查: + 1. 确保已安装所有依赖:`npm install` 2. 检查 Node.js 版本是否 >= 18 3. 尝试删除 `node_modules` 后重新安装 diff --git a/SECURITY.md b/SECURITY.md index 2248b95..9b0133a 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -26,10 +26,10 @@ Master keys are stored in the OS native credential manager via `keytar`, never i ## 📋 Supported Versions -| Version | Supported | -| ------- | ------------------ | -| Latest | :white_check_mark: | -| < Latest | :x: | +| Version | Supported | +| -------- | ------------------ | +| Latest | :white_check_mark: | +| < Latest | :x: | We only provide security updates for the latest version. Please ensure you're running the most recent release. diff --git a/docs/cloud_features.md b/docs/cloud_features.md index 1f89110..61dad72 100644 --- a/docs/cloud_features.md +++ b/docs/cloud_features.md @@ -5,17 +5,20 @@ ## 1. 核心功能 ### 1.1 账号池管理 + - **添加账号**:支持通过 Google OAuth 授权码添加账号。 - **列表展示**:展示所有已添加的账号,包括头像、邮箱、最后使用时间。 - **状态监控**:实时显示账号状态(Active, Rate Limited, Expired)以及是否为当前活动账号。 - **删除账号**:支持从本地数据库中移除账号。 ### 1.2 配额实时监控 + - **多模型支持**:分别监控 `gemini-pro`, `claude-3-5-sonnet` 等模型的配额使用情况。 - **可视化展示**:通过进度条和颜色(绿/黄/红)直观展示剩余配额百分比。 - **自动/手动刷新**:支持手动刷新配额,系统也会在切换前自动检查。 ### 1.4 智能自动切换 (Intelligent Auto-Switching) + - **无限池模式**:当检测到当前账号配额不足(<5%)或被限流时,系统会自动寻找最佳备用账号并切换。 - **后台监控**:内置 `CloudMonitorService`, 默认每 5 分钟轮询一次所有账号的 Quota 状态。 - **全局开关**:用户可在界面上一键开启或关闭此功能。 @@ -23,6 +26,7 @@ ## 2. 技术实现 ### 2.1 数据库设计 (`cloud_accounts.db`) + 使用 SQLite 存储账号信息,独立于 IDE 的本地数据库。 ```sql @@ -48,6 +52,7 @@ CREATE TABLE settings ( ``` ### 2.2 IPC 接口设计 + 后端通过 `src/ipc/cloud` 提供以下 oRPC 接口: - `addGoogleAccount(authCode: string)`: 交换 Token 并保存账号。 @@ -59,15 +64,18 @@ CREATE TABLE settings ( - `forcePollCloudMonitor()`: 手动触发后台轮询。 ### 2.3 Token 注入原理 (`ProtobufUtils`) + IDE 将鉴权信息存储在 `state.vscdb` 的 `ItemTable` 表中,key 为 `jetskiStateSync.agentManagerInitState`。 value 是一个 Base64 编码的 Protobuf 二进制数据。 我们实现了 `src/utils/protobuf.ts` 工具类,能够: + 1. **解码**:解析 Varint 和 Length-Delimited 字段。 2. **修改**:定位并移除旧的 Field 6 (OAuthTokenInfo)。 3. **重构**:根据新的 Access Token 和 Refresh Token 构建新的 Field 6 并插入。 ### 2.4 切换流程 + 1. **Token 检查**:检查目标账号 Token 是否临期,如需刷新则自动刷新。 2. **停止进程**:优雅关闭 Antigravity 进程。 3. **注入 Token**:调用 `CloudAccountRepo.injectCloudToken` 修改 IDE 数据库。 @@ -75,12 +83,14 @@ value 是一个 Base64 编码的 Protobuf 二进制数据。 5. **重启进程**:重新启动 IDE,使其加载新的凭证。 ### 2.5 自动切换逻辑 (`AutoSwitchService`) + 1. **监控**:`CloudMonitorService` 定时(5分钟)轮询所有账号配额。 2. **判断**:如果当前活动账号的所有关键模型配额低于 5%,或状态为 `rate_limited`。 3. **择优**:从账号池中筛选状态为 `active` 且配额充足的账号,按配额剩余量排序。 4. **执行**:自动调用 `switchCloudAccount` 完成切换,并通知用户。 ### 2.6 安全加固 (Security Hardening) + - **密钥管理**:使用操作系统原生凭据管理器(Windows Credential Manager / macOS Keychain)通过 `keytar` 库安全存储 Master Key(AES-256)。 - **数据加密**:所有敏感数据(`token_json`, `quota_json`)在存入 SQLite 数据库前均使用 `AES-256-GCM` 算法加密。 - **自动迁移**:应用启动时会自动检测并迁移旧的明文数据,确保安全性平滑升级。 diff --git a/playwright.config.ts b/playwright.config.ts index b937b2b..3b69cd5 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -1,23 +1,23 @@ -import { defineConfig, devices } from "@playwright/test"; +import { defineConfig, devices } from '@playwright/test'; /** * See https://playwright.dev/docs/test-configuration. */ export default defineConfig({ - testDir: "./src/tests/e2e", + testDir: './src/tests/e2e', fullyParallel: false, forbidOnly: !!process.env.CI, retries: process.env.CI ? 2 : 0, workers: process.env.CI ? 1 : undefined, - reporter: "html", + reporter: 'html', use: { - trace: "on-first-retry", + trace: 'on-first-retry', }, projects: [ { - name: "chromium", - use: { ...devices["Desktop Chrome"] }, + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, }, ], }); diff --git a/src/actions/account.ts b/src/actions/account.ts index 0e6ee32..d72a63a 100644 --- a/src/actions/account.ts +++ b/src/actions/account.ts @@ -1,4 +1,4 @@ -import { ipc } from "@/ipc/manager"; +import { ipc } from '@/ipc/manager'; export function listAccounts() { return ipc.client.account.listAccounts(); diff --git a/src/actions/app.ts b/src/actions/app.ts index 1ba987c..2b0ce48 100644 --- a/src/actions/app.ts +++ b/src/actions/app.ts @@ -1,4 +1,4 @@ -import { ipc } from "@/ipc/manager"; +import { ipc } from '@/ipc/manager'; export function getPlatform() { return ipc.client.app.currentPlatfom(); diff --git a/src/actions/database.ts b/src/actions/database.ts index 9c1647e..e3f58d2 100644 --- a/src/actions/database.ts +++ b/src/actions/database.ts @@ -1,5 +1,5 @@ -import { ipc } from "@/ipc/manager"; -import { AccountBackupData, Account } from "@/types/account"; +import { ipc } from '@/ipc/manager'; +import { AccountBackupData, Account } from '@/types/account'; export function backupAccount(account: Account) { return ipc.client.database.backupAccount(account); diff --git a/src/actions/theme.ts b/src/actions/theme.ts index c5e61c3..79a22f1 100644 --- a/src/actions/theme.ts +++ b/src/actions/theme.ts @@ -1,6 +1,6 @@ -import { ThemeMode } from "@/types/theme-mode"; -import { ipc } from "@/ipc/manager"; -import { LOCAL_STORAGE_KEYS } from "@/constants"; +import { ThemeMode } from '@/types/theme-mode'; +import { ipc } from '@/ipc/manager'; +import { LOCAL_STORAGE_KEYS } from '@/constants'; export interface ThemePreferences { system: ThemeMode; @@ -9,9 +9,7 @@ export interface ThemePreferences { export async function getCurrentTheme(): Promise { const currentTheme = await ipc.client.theme.getCurrentThemeMode(); - const localTheme = localStorage.getItem( - LOCAL_STORAGE_KEYS.THEME, - ) as ThemeMode | null; + const localTheme = localStorage.getItem(LOCAL_STORAGE_KEYS.THEME) as ThemeMode | null; return { system: currentTheme, @@ -20,7 +18,7 @@ export async function getCurrentTheme(): Promise { } export async function setTheme(newTheme: ThemeMode) { - const isDarkMode = newTheme === "dark"; + const isDarkMode = newTheme === 'dark'; await ipc.client.theme.setThemeMode(newTheme); localStorage.setItem(LOCAL_STORAGE_KEYS.THEME, newTheme); updateDocumentTheme(isDarkMode); @@ -28,7 +26,7 @@ export async function setTheme(newTheme: ThemeMode) { export async function toggleTheme() { const isDarkMode = await ipc.client.theme.toggleThemeMode(); - const newTheme = isDarkMode ? "dark" : "light"; + const newTheme = isDarkMode ? 'dark' : 'light'; updateDocumentTheme(isDarkMode); localStorage.setItem(LOCAL_STORAGE_KEYS.THEME, newTheme); @@ -37,7 +35,7 @@ export async function toggleTheme() { export async function syncWithLocalTheme() { const { local } = await getCurrentTheme(); if (!local) { - setTheme("system"); + setTheme('system'); return; } @@ -46,8 +44,8 @@ export async function syncWithLocalTheme() { function updateDocumentTheme(isDarkMode: boolean) { if (isDarkMode) { - document.documentElement.classList.add("dark"); + document.documentElement.classList.add('dark'); } else { - document.documentElement.classList.remove("dark"); + document.documentElement.classList.remove('dark'); } } diff --git a/src/actions/window.ts b/src/actions/window.ts index ecbf853..f0b46df 100644 --- a/src/actions/window.ts +++ b/src/actions/window.ts @@ -1,4 +1,4 @@ -import { ipc } from "../ipc/manager"; +import { ipc } from '../ipc/manager'; export async function minimizeWindow() { await ipc.client.window.minimizeWindow(); diff --git a/src/components/StatusBar.tsx b/src/components/StatusBar.tsx index 87ece0f..a34098c 100644 --- a/src/components/StatusBar.tsx +++ b/src/components/StatusBar.tsx @@ -1,21 +1,17 @@ -import React from "react"; -import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; -import { - isProcessRunning, - startAntigravity, - closeAntigravity, -} from "@/actions/process"; -import { Button } from "@/components/ui/button"; -import { cn } from "@/lib/utils"; -import { Play, Square, Loader2 } from "lucide-react"; -import { useTranslation } from "react-i18next"; +import React from 'react'; +import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; +import { isProcessRunning, startAntigravity, closeAntigravity } from '@/actions/process'; +import { Button } from '@/components/ui/button'; +import { cn } from '@/lib/utils'; +import { Play, Square, Loader2 } from 'lucide-react'; +import { useTranslation } from 'react-i18next'; export const StatusBar: React.FC = () => { const { t } = useTranslation(); const queryClient = useQueryClient(); const { data: isRunning, isLoading } = useQuery({ - queryKey: ["process", "status"], + queryKey: ['process', 'status'], queryFn: isProcessRunning, refetchInterval: 2000, }); @@ -23,14 +19,14 @@ export const StatusBar: React.FC = () => { const startMutation = useMutation({ mutationFn: startAntigravity, onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ["process", "status"] }); + queryClient.invalidateQueries({ queryKey: ['process', 'status'] }); }, }); const stopMutation = useMutation({ mutationFn: closeAntigravity, onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ["process", "status"] }); + queryClient.invalidateQueries({ queryKey: ['process', 'status'] }); }, }); @@ -47,10 +43,10 @@ export const StatusBar: React.FC = () => { return (
@@ -62,11 +58,7 @@ export const StatusBar: React.FC = () => {
)} - {isLoading - ? t("status.checking") - : isRunning - ? t("status.running") - : t("status.stopped")} + {isLoading ? t('status.checking') : isRunning ? t('status.running') : t('status.stopped')}
); diff --git a/src/components/navigation-menu.tsx b/src/components/navigation-menu.tsx index 6b02c27..68fa5c2 100644 --- a/src/components/navigation-menu.tsx +++ b/src/components/navigation-menu.tsx @@ -1,12 +1,12 @@ -import { Link } from "@tanstack/react-router"; +import { Link } from '@tanstack/react-router'; import { NavigationMenu as NavigationMenuBase, NavigationMenuItem, NavigationMenuLink, NavigationMenuList, navigationMenuTriggerStyle, -} from "@/components/ui/navigation-menu"; -import { useTranslation } from "react-i18next"; +} from '@/components/ui/navigation-menu'; +import { useTranslation } from 'react-i18next'; export default function NavigationMenu() { const { t } = useTranslation(); @@ -16,7 +16,7 @@ export default function NavigationMenu() { - {t("titleHomePage")} + {t('titleHomePage')} diff --git a/src/components/toggle-theme.tsx b/src/components/toggle-theme.tsx index 8e333f1..1f5d9a3 100644 --- a/src/components/toggle-theme.tsx +++ b/src/components/toggle-theme.tsx @@ -1,6 +1,6 @@ -import { Moon } from "lucide-react"; -import { Button } from "@/components/ui/button"; -import { toggleTheme } from "@/actions/theme"; +import { Moon } from 'lucide-react'; +import { Button } from '@/components/ui/button'; +import { toggleTheme } from '@/actions/theme'; export default function ToggleTheme() { return ( diff --git a/src/components/ui/avatar.tsx b/src/components/ui/avatar.tsx index 6ea05cf..65e206c 100644 --- a/src/components/ui/avatar.tsx +++ b/src/components/ui/avatar.tsx @@ -1,6 +1,6 @@ -import * as React from "react"; -import * as AvatarPrimitive from "@radix-ui/react-avatar"; -import { cn } from "@/lib/utils"; +import * as React from 'react'; +import * as AvatarPrimitive from '@radix-ui/react-avatar'; +import { cn } from '@/lib/utils'; const Avatar = React.forwardRef< React.ElementRef, @@ -8,10 +8,7 @@ const Avatar = React.forwardRef< >(({ className, ...props }, ref) => ( )); @@ -23,7 +20,7 @@ const AvatarImage = React.forwardRef< >(({ className, ...props }, ref) => ( )); @@ -36,7 +33,7 @@ const AvatarFallback = React.forwardRef< , - VariantProps {} + extends React.HTMLAttributes, VariantProps {} function Badge({ className, variant, ...props }: BadgeProps) { - return ( -
- ); + return
; } export { Badge, badgeVariants }; diff --git a/src/components/ui/card.tsx b/src/components/ui/card.tsx index b26ba27..996c411 100644 --- a/src/components/ui/card.tsx +++ b/src/components/ui/card.tsx @@ -1,82 +1,55 @@ -import * as React from "react"; -import { cn } from "@/lib/utils"; +import * as React from 'react'; +import { cn } from '@/lib/utils'; -const Card = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -
-)); -Card.displayName = "Card"; +const Card = React.forwardRef>( + ({ className, ...props }, ref) => ( +
+ ), +); +Card.displayName = 'Card'; -const CardHeader = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -
-)); -CardHeader.displayName = "CardHeader"; +const CardHeader = React.forwardRef>( + ({ className, ...props }, ref) => ( +
+ ), +); +CardHeader.displayName = 'CardHeader'; -const CardTitle = React.forwardRef< - HTMLParagraphElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -

-)); -CardTitle.displayName = "CardTitle"; +const CardTitle = React.forwardRef>( + ({ className, ...props }, ref) => ( +

+ ), +); +CardTitle.displayName = 'CardTitle'; const CardDescription = React.forwardRef< HTMLParagraphElement, React.HTMLAttributes >(({ className, ...props }, ref) => ( -

+

)); -CardDescription.displayName = "CardDescription"; +CardDescription.displayName = 'CardDescription'; -const CardContent = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -

-)); -CardContent.displayName = "CardContent"; +const CardContent = React.forwardRef>( + ({ className, ...props }, ref) => ( +
+ ), +); +CardContent.displayName = 'CardContent'; -const CardFooter = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -
-)); -CardFooter.displayName = "CardFooter"; +const CardFooter = React.forwardRef>( + ({ className, ...props }, ref) => ( +
+ ), +); +CardFooter.displayName = 'CardFooter'; -export { - Card, - CardHeader, - CardFooter, - CardTitle, - CardDescription, - CardContent, -}; +export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }; diff --git a/src/components/ui/dropdown-menu.tsx b/src/components/ui/dropdown-menu.tsx index ff88f54..af552ea 100644 --- a/src/components/ui/dropdown-menu.tsx +++ b/src/components/ui/dropdown-menu.tsx @@ -1,7 +1,7 @@ -import * as React from "react"; -import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"; -import { Check, ChevronRight, Circle } from "lucide-react"; -import { cn } from "@/lib/utils"; +import * as React from 'react'; +import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'; +import { Check, ChevronRight, Circle } from 'lucide-react'; +import { cn } from '@/lib/utils'; const DropdownMenu = DropdownMenuPrimitive.Root; const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger; @@ -19,8 +19,8 @@ const DropdownMenuSubTrigger = React.forwardRef< )); -DropdownMenuSubTrigger.displayName = - DropdownMenuPrimitive.SubTrigger.displayName; +DropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName; const DropdownMenuSubContent = React.forwardRef< React.ElementRef, @@ -39,14 +38,13 @@ const DropdownMenuSubContent = React.forwardRef< )); -DropdownMenuSubContent.displayName = - DropdownMenuPrimitive.SubContent.displayName; +DropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayName; const DropdownMenuContent = React.forwardRef< React.ElementRef, @@ -57,8 +55,8 @@ const DropdownMenuContent = React.forwardRef< ref={ref} sideOffset={sideOffset} className={cn( - "bg-popover text-popover-foreground z-50 min-w-[8rem] overflow-hidden rounded-md border p-1 shadow-md", - "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", + 'bg-popover text-popover-foreground z-50 min-w-[8rem] overflow-hidden rounded-md border p-1 shadow-md', + 'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2', className, )} {...props} @@ -76,8 +74,8 @@ const DropdownMenuItem = React.forwardRef< )); -DropdownMenuCheckboxItem.displayName = - DropdownMenuPrimitive.CheckboxItem.displayName; +DropdownMenuCheckboxItem.displayName = DropdownMenuPrimitive.CheckboxItem.displayName; const DropdownMenuRadioItem = React.forwardRef< React.ElementRef, @@ -116,7 +113,7 @@ const DropdownMenuRadioItem = React.forwardRef< (({ className, inset, ...props }, ref) => ( )); @@ -155,24 +148,18 @@ const DropdownMenuSeparator = React.forwardRef< >(({ className, ...props }, ref) => ( )); DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName; -const DropdownMenuShortcut = ({ - className, - ...props -}: React.HTMLAttributes) => { +const DropdownMenuShortcut = ({ className, ...props }: React.HTMLAttributes) => { return ( - + ); }; -DropdownMenuShortcut.displayName = "DropdownMenuShortcut"; +DropdownMenuShortcut.displayName = 'DropdownMenuShortcut'; export { DropdownMenu, diff --git a/src/components/ui/label.tsx b/src/components/ui/label.tsx index 22a57fb..d0cdbe8 100644 --- a/src/components/ui/label.tsx +++ b/src/components/ui/label.tsx @@ -1,22 +1,17 @@ -import * as React from "react"; -import * as LabelPrimitive from "@radix-ui/react-label"; -import { cva, type VariantProps } from "class-variance-authority"; -import { cn } from "@/lib/utils"; +import * as React from 'react'; +import * as LabelPrimitive from '@radix-ui/react-label'; +import { cva, type VariantProps } from 'class-variance-authority'; +import { cn } from '@/lib/utils'; const labelVariants = cva( - "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70", + 'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70', ); const Label = React.forwardRef< React.ElementRef, - React.ComponentPropsWithoutRef & - VariantProps + React.ComponentPropsWithoutRef & VariantProps >(({ className, ...props }, ref) => ( - + )); Label.displayName = LabelPrimitive.Root.displayName; diff --git a/src/components/ui/select.tsx b/src/components/ui/select.tsx index 39e7854..b7b4156 100644 --- a/src/components/ui/select.tsx +++ b/src/components/ui/select.tsx @@ -1,10 +1,10 @@ -"use client"; +'use client'; -import * as React from "react"; -import * as SelectPrimitive from "@radix-ui/react-select"; -import { Check, ChevronDown, ChevronUp } from "lucide-react"; +import * as React from 'react'; +import * as SelectPrimitive from '@radix-ui/react-select'; +import { Check, ChevronDown, ChevronUp } from 'lucide-react'; -import { cn } from "@/lib/utils"; +import { cn } from '@/lib/utils'; const Select = SelectPrimitive.Root; @@ -19,7 +19,7 @@ const SelectTrigger = React.forwardRef< span]:line-clamp-1", + 'border-input bg-background ring-offset-background placeholder:text-muted-foreground focus:ring-ring flex h-10 w-full items-center justify-between rounded-md border px-3 py-2 text-sm focus:ring-2 focus:ring-offset-2 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1', className, )} {...props} @@ -38,10 +38,7 @@ const SelectScrollUpButton = React.forwardRef< >(({ className, ...props }, ref) => ( @@ -55,29 +52,25 @@ const SelectScrollDownButton = React.forwardRef< >(({ className, ...props }, ref) => ( )); -SelectScrollDownButton.displayName = - SelectPrimitive.ScrollDownButton.displayName; +SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName; const SelectContent = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef ->(({ className, children, position = "popper", ...props }, ref) => ( +>(({ className, children, position = 'popper', ...props }, ref) => ( {children} @@ -105,7 +98,7 @@ const SelectLabel = React.forwardRef< >(({ className, ...props }, ref) => ( )); @@ -118,7 +111,7 @@ const SelectItem = React.forwardRef< (({ className, ...props }, ref) => ( )); diff --git a/src/components/ui/switch.tsx b/src/components/ui/switch.tsx index c721830..22204c9 100644 --- a/src/components/ui/switch.tsx +++ b/src/components/ui/switch.tsx @@ -1,6 +1,6 @@ -import * as React from "react"; -import * as SwitchPrimitive from "@radix-ui/react-switch"; -import { cn } from "@/lib/utils"; +import * as React from 'react'; +import * as SwitchPrimitive from '@radix-ui/react-switch'; +import { cn } from '@/lib/utils'; const Switch = React.forwardRef< React.ElementRef, @@ -8,7 +8,7 @@ const Switch = React.forwardRef< >(({ className, ...props }, ref) => ( diff --git a/src/components/ui/toast.tsx b/src/components/ui/toast.tsx index 2780250..f3f0a1d 100644 --- a/src/components/ui/toast.tsx +++ b/src/components/ui/toast.tsx @@ -1,8 +1,8 @@ -import * as React from "react"; -import * as ToastPrimitives from "@radix-ui/react-toast"; -import { cva, type VariantProps } from "class-variance-authority"; -import { X } from "lucide-react"; -import { cn } from "@/lib/utils"; +import * as React from 'react'; +import * as ToastPrimitives from '@radix-ui/react-toast'; +import { cva, type VariantProps } from 'class-variance-authority'; +import { X } from 'lucide-react'; +import { cn } from '@/lib/utils'; const ToastProvider = ToastPrimitives.Provider; @@ -13,7 +13,7 @@ const ToastViewport = React.forwardRef< , - React.ComponentPropsWithoutRef & - VariantProps + React.ComponentPropsWithoutRef & VariantProps >(({ className, variant, ...props }, ref) => { return ( (({ className, ...props }, ref) => ( )); @@ -103,7 +102,7 @@ const ToastDescription = React.forwardRef< >(({ className, ...props }, ref) => ( )); diff --git a/src/components/ui/use-toast.ts b/src/components/ui/use-toast.ts index 25edf11..7b77a4c 100644 --- a/src/components/ui/use-toast.ts +++ b/src/components/ui/use-toast.ts @@ -1,5 +1,5 @@ -import * as React from "react"; -import { Toast, ToastActionElement, ToastProps } from "@/components/ui/toast"; +import * as React from 'react'; +import { Toast, ToastActionElement, ToastProps } from '@/components/ui/toast'; const TOAST_LIMIT = 1; const TOAST_REMOVE_DELAY = 1000000; @@ -12,10 +12,10 @@ type ToasterToast = ToastProps & { }; const actionTypes = { - ADD_TOAST: "ADD_TOAST", - UPDATE_TOAST: "UPDATE_TOAST", - DISMISS_TOAST: "DISMISS_TOAST", - REMOVE_TOAST: "REMOVE_TOAST", + ADD_TOAST: 'ADD_TOAST', + UPDATE_TOAST: 'UPDATE_TOAST', + DISMISS_TOAST: 'DISMISS_TOAST', + REMOVE_TOAST: 'REMOVE_TOAST', } as const; let count = 0; @@ -29,20 +29,20 @@ type ActionType = typeof actionTypes; type Action = | { - type: ActionType["ADD_TOAST"]; + type: ActionType['ADD_TOAST']; toast: ToasterToast; } | { - type: ActionType["UPDATE_TOAST"]; + type: ActionType['UPDATE_TOAST']; toast: Partial; } | { - type: ActionType["DISMISS_TOAST"]; - toastId?: ToasterToast["id"]; + type: ActionType['DISMISS_TOAST']; + toastId?: ToasterToast['id']; } | { - type: ActionType["REMOVE_TOAST"]; - toastId?: ToasterToast["id"]; + type: ActionType['REMOVE_TOAST']; + toastId?: ToasterToast['id']; }; interface State { @@ -59,7 +59,7 @@ const addToRemoveQueue = (toastId: string) => { const timeout = setTimeout(() => { toastTimeouts.delete(toastId); dispatch({ - type: "REMOVE_TOAST", + type: 'REMOVE_TOAST', toastId, }); }, TOAST_REMOVE_DELAY); @@ -69,21 +69,19 @@ const addToRemoveQueue = (toastId: string) => { export const reducer = (state: State, action: Action): State => { switch (action.type) { - case "ADD_TOAST": + case 'ADD_TOAST': return { ...state, toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT), }; - case "UPDATE_TOAST": + case 'UPDATE_TOAST': return { ...state, - toasts: state.toasts.map((t) => - t.id === action.toast.id ? { ...t, ...action.toast } : t, - ), + toasts: state.toasts.map((t) => (t.id === action.toast.id ? { ...t, ...action.toast } : t)), }; - case "DISMISS_TOAST": { + case 'DISMISS_TOAST': { const { toastId } = action; // ! Side effects ! - This could be extracted into a dismissToast() action, @@ -108,7 +106,7 @@ export const reducer = (state: State, action: Action): State => { ), }; } - case "REMOVE_TOAST": + case 'REMOVE_TOAST': if (action.toastId === undefined) { return { ...state, @@ -133,20 +131,20 @@ function dispatch(action: Action) { }); } -type Toast = Omit; +type Toast = Omit; function toast({ ...props }: Toast) { const id = genId(); const update = (props: ToasterToast) => dispatch({ - type: "UPDATE_TOAST", + type: 'UPDATE_TOAST', toast: { ...props, id }, }); - const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id }); + const dismiss = () => dispatch({ type: 'DISMISS_TOAST', toastId: id }); dispatch({ - type: "ADD_TOAST", + type: 'ADD_TOAST', toast: { ...props, id, @@ -180,7 +178,7 @@ function useToast() { return { ...state, toast, - dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }), + dismiss: (toastId?: string) => dispatch({ type: 'DISMISS_TOAST', toastId }), }; } diff --git a/src/ipc/account/router.ts b/src/ipc/account/router.ts index ee900c3..f329c8b 100644 --- a/src/ipc/account/router.ts +++ b/src/ipc/account/router.ts @@ -1,12 +1,7 @@ -import { z } from "zod"; -import { os } from "@orpc/server"; -import { - listAccountsData, - addAccountSnapshot, - switchAccount, - deleteAccount, -} from "./handler"; -import { AccountSchema } from "../../types/account"; +import { z } from 'zod'; +import { os } from '@orpc/server'; +import { listAccountsData, addAccountSnapshot, switchAccount, deleteAccount } from './handler'; +import { AccountSchema } from '../../types/account'; export const accountRouter = os.router({ listAccounts: os.output(z.array(AccountSchema)).handler(async () => { diff --git a/src/ipc/app/handlers.ts b/src/ipc/app/handlers.ts index 2b73ac3..3077b34 100644 --- a/src/ipc/app/handlers.ts +++ b/src/ipc/app/handlers.ts @@ -1,5 +1,5 @@ -import { os } from "@orpc/server"; -import { app } from "electron"; +import { os } from '@orpc/server'; +import { app } from 'electron'; export const currentPlatfom = os.handler(() => { return process.platform; diff --git a/src/ipc/database/router.ts b/src/ipc/database/router.ts index d69fa13..fac8b59 100644 --- a/src/ipc/database/router.ts +++ b/src/ipc/database/router.ts @@ -1,15 +1,7 @@ -import { z } from "zod"; -import { os } from "@orpc/server"; -import { - backupAccount, - restoreAccount, - getCurrentAccountInfo, -} from "./handler"; -import { - AccountBackupDataSchema, - AccountInfoSchema, - AccountSchema, -} from "../../types/account"; +import { z } from 'zod'; +import { os } from '@orpc/server'; +import { backupAccount, restoreAccount, getCurrentAccountInfo } from './handler'; +import { AccountBackupDataSchema, AccountInfoSchema, AccountSchema } from '../../types/account'; export const databaseRouter = os.router({ backupAccount: os diff --git a/src/ipc/theme/handlers.ts b/src/ipc/theme/handlers.ts index 52619cc..373db75 100644 --- a/src/ipc/theme/handlers.ts +++ b/src/ipc/theme/handlers.ts @@ -1,6 +1,6 @@ -import { os } from "@orpc/server"; -import { nativeTheme } from "electron"; -import { setThemeModeInputSchema } from "./schemas"; +import { os } from '@orpc/server'; +import { nativeTheme } from 'electron'; +import { setThemeModeInputSchema } from './schemas'; export const getCurrentThemeMode = os.handler(() => { return nativeTheme.themeSource; @@ -8,28 +8,26 @@ export const getCurrentThemeMode = os.handler(() => { export const toggleThemeMode = os.handler(() => { if (nativeTheme.shouldUseDarkColors) { - nativeTheme.themeSource = "light"; + nativeTheme.themeSource = 'light'; } else { - nativeTheme.themeSource = "dark"; + nativeTheme.themeSource = 'dark'; } return nativeTheme.shouldUseDarkColors; }); -export const setThemeMode = os - .input(setThemeModeInputSchema) - .handler(({ input: mode }) => { - switch (mode) { - case "light": - nativeTheme.themeSource = "light"; - break; - case "dark": - nativeTheme.themeSource = "dark"; - break; - case "system": - nativeTheme.themeSource = "system"; - break; - } +export const setThemeMode = os.input(setThemeModeInputSchema).handler(({ input: mode }) => { + switch (mode) { + case 'light': + nativeTheme.themeSource = 'light'; + break; + case 'dark': + nativeTheme.themeSource = 'dark'; + break; + case 'system': + nativeTheme.themeSource = 'system'; + break; + } - return nativeTheme.themeSource; - }); + return nativeTheme.themeSource; +}); diff --git a/src/ipc/theme/schemas.ts b/src/ipc/theme/schemas.ts index 8acc0ff..d0041f0 100644 --- a/src/ipc/theme/schemas.ts +++ b/src/ipc/theme/schemas.ts @@ -1,3 +1,3 @@ -import { z } from "zod"; +import { z } from 'zod'; -export const setThemeModeInputSchema = z.enum(["light", "dark", "system"]); +export const setThemeModeInputSchema = z.enum(['light', 'dark', 'system']); diff --git a/src/ipc/window/handlers.ts b/src/ipc/window/handlers.ts index af752d4..5bc44d9 100644 --- a/src/ipc/window/handlers.ts +++ b/src/ipc/window/handlers.ts @@ -1,33 +1,26 @@ -import { os } from "@orpc/server"; -import { ipcContext } from "../context"; -import { logger } from "../../utils/logger"; +import { os } from '@orpc/server'; +import { ipcContext } from '../context'; +import { logger } from '../../utils/logger'; -export const minimizeWindow = os - .use(ipcContext.mainWindowContext) - .handler(({ context }) => { - const { window } = context; +export const minimizeWindow = os.use(ipcContext.mainWindowContext).handler(({ context }) => { + const { window } = context; - window.minimize(); - }); + window.minimize(); +}); -export const maximizeWindow = os - .use(ipcContext.mainWindowContext) - .handler(({ context }) => { - const { window } = context; +export const maximizeWindow = os.use(ipcContext.mainWindowContext).handler(({ context }) => { + const { window } = context; - if (window.isMaximized()) { - window.unmaximize(); - } else { - window.maximize(); - } - }); + if (window.isMaximized()) { + window.unmaximize(); + } else { + window.maximize(); + } +}); -export const closeWindow = os - .use(ipcContext.mainWindowContext) - .handler(({ context }) => { - logger.info('IPC: closeWindow called'); - const { window } = context; - - window.close(); - }); +export const closeWindow = os.use(ipcContext.mainWindowContext).handler(({ context }) => { + logger.info('IPC: closeWindow called'); + const { window } = context; + window.close(); +}); diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 365058c..9ad0df4 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -1,5 +1,5 @@ -import { type ClassValue, clsx } from "clsx"; -import { twMerge } from "tailwind-merge"; +import { type ClassValue, clsx } from 'clsx'; +import { twMerge } from 'tailwind-merge'; export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); diff --git a/src/renderer.ts b/src/renderer.ts index f9393a8..57eb0d0 100644 --- a/src/renderer.ts +++ b/src/renderer.ts @@ -1 +1 @@ -import "@/App"; +import '@/App'; diff --git a/src/routes/__root.tsx b/src/routes/__root.tsx index 3c85c81..8ac9d2a 100644 --- a/src/routes/__root.tsx +++ b/src/routes/__root.tsx @@ -1,5 +1,5 @@ -import { MainLayout } from "@/layouts/MainLayout"; -import { createRootRoute } from "@tanstack/react-router"; +import { MainLayout } from '@/layouts/MainLayout'; +import { createRootRoute } from '@tanstack/react-router'; /* import { TanStackRouterDevtools } from '@tanstack/react-router-devtools' */ /* diff --git a/src/styles/global.css b/src/styles/global.css index bc67050..8645b76 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -1,6 +1,6 @@ -@import "tailwindcss"; +@import 'tailwindcss'; -@plugin "tailwindcss-animate"; +@plugin 'tailwindcss-animate'; @custom-variant dark (&:is(.dark *)); @@ -32,7 +32,7 @@ --color-card: hsl(var(--card)); --color-card-foreground: hsl(var(--card-foreground)); - --font-sans: Geist, "Microsoft YaHei", "Heiti SC", "WenQuanYi Micro Hei", sans-serif; + --font-sans: Geist, 'Microsoft YaHei', 'Heiti SC', 'WenQuanYi Micro Hei', sans-serif; --font-mono: Geist Mono, monospace; --font-tomorrow: Tomorrow, sans-serif; @@ -74,45 +74,44 @@ @layer base { @font-face { - font-family: "Geist"; + font-family: 'Geist'; - src: url("../assets/fonts/geist/geist.ttf") format("truetype"); + src: url('../assets/fonts/geist/geist.ttf') format('truetype'); } @font-face { - font-family: "Geist Mono"; + font-family: 'Geist Mono'; font-display: swap; - src: url("../assets/fonts/geist-mono/geist-mono.ttf") format("truetype"); + src: url('../assets/fonts/geist-mono/geist-mono.ttf') format('truetype'); } @font-face { - font-family: "Tomorrow"; + font-family: 'Tomorrow'; font-weight: 400; font-style: normal; - src: url("../assets/fonts/tomorrow/tomorrow-regular.ttf") format("truetype"); + src: url('../assets/fonts/tomorrow/tomorrow-regular.ttf') format('truetype'); } @font-face { - font-family: "Tomorrow"; + font-family: 'Tomorrow'; font-weight: 400; font-style: italic; - src: url("../assets/fonts/tomorrow/tomorrow-italic.ttf") format("truetype"); + src: url('../assets/fonts/tomorrow/tomorrow-italic.ttf') format('truetype'); } @font-face { - font-family: "Tomorrow"; + font-family: 'Tomorrow'; font-weight: 700; font-style: normal; - src: url("../assets/fonts/tomorrow/tomorrow-bold.ttf") format("truetype"); + src: url('../assets/fonts/tomorrow/tomorrow-bold.ttf') format('truetype'); } @font-face { - font-family: "Tomorrow"; + font-family: 'Tomorrow'; font-weight: 700; font-style: italic; - src: url("../assets/fonts/tomorrow/tomorrow-bold-italic.ttf") - format("truetype"); + src: url('../assets/fonts/tomorrow/tomorrow-bold-italic.ttf') format('truetype'); } } diff --git a/src/tests/unit/account.test.ts b/src/tests/unit/account.test.ts index a01a626..1a72df1 100644 --- a/src/tests/unit/account.test.ts +++ b/src/tests/unit/account.test.ts @@ -1,45 +1,45 @@ -import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { listAccountsData, addAccountSnapshot, switchAccount, deleteAccount, -} from "../../ipc/account/handler"; -import fs from "fs"; -import path from "path"; +} from '../../ipc/account/handler'; +import fs from 'fs'; +import path from 'path'; // Mock dependencies -vi.mock("../../utils/paths", async () => { - const path = await import("path"); - const agentDir = path.join(process.cwd(), "temp_test_agent"); +vi.mock('../../utils/paths', async () => { + const path = await import('path'); + const agentDir = path.join(process.cwd(), 'temp_test_agent'); return { getAgentDir: vi.fn(() => agentDir), - getAccountsFilePath: vi.fn(() => path.join(agentDir, "accounts.json")), - getBackupsDir: vi.fn(() => path.join(agentDir, "backups")), - getAntigravityDbPath: vi.fn(() => path.join(agentDir, "state.vscdb")), - getAntigravityExecutablePath: vi.fn(() => "mock_exec_path"), + getAccountsFilePath: vi.fn(() => path.join(agentDir, 'accounts.json')), + getBackupsDir: vi.fn(() => path.join(agentDir, 'backups')), + getAntigravityDbPath: vi.fn(() => path.join(agentDir, 'state.vscdb')), + getAntigravityExecutablePath: vi.fn(() => 'mock_exec_path'), }; }); -vi.mock("../../ipc/database/handler", () => ({ +vi.mock('../../ipc/database/handler', () => ({ getCurrentAccountInfo: vi.fn(() => ({ - email: "test@example.com", - name: "Test User", + email: 'test@example.com', + name: 'Test User', isAuthenticated: true, })), - backupAccount: vi.fn((account) => ({ version: "1.0", account, data: {} })), + backupAccount: vi.fn((account) => ({ version: '1.0', account, data: {} })), restoreAccount: vi.fn(), getDatabaseConnection: vi.fn(), })); -vi.mock("../../ipc/process/handler", () => ({ +vi.mock('../../ipc/process/handler', () => ({ closeAntigravity: vi.fn(), startAntigravity: vi.fn(), isProcessRunning: vi.fn(() => Promise.resolve(false)), })); -describe("Account Handler", () => { - const testAgentDir = path.join(process.cwd(), "temp_test_agent"); +describe('Account Handler', () => { + const testAgentDir = path.join(process.cwd(), 'temp_test_agent'); beforeEach(() => { if (fs.existsSync(testAgentDir)) { @@ -54,22 +54,22 @@ describe("Account Handler", () => { } }); - it("should add account snapshot", async () => { + it('should add account snapshot', async () => { const account = await addAccountSnapshot(); - expect(account.email).toBe("test@example.com"); + expect(account.email).toBe('test@example.com'); const accounts = await listAccountsData(); expect(accounts).toHaveLength(1); - expect(accounts[0].email).toBe("test@example.com"); + expect(accounts[0].email).toBe('test@example.com'); }); - it("should switch account", async () => { + it('should switch account', async () => { const account = await addAccountSnapshot(); await switchAccount(account.id); // Mocks should be called, verified by implementation logic not throwing }); - it("should delete account", async () => { + it('should delete account', async () => { const account = await addAccountSnapshot(); await deleteAccount(account.id); diff --git a/src/tests/unit/logger.test.ts b/src/tests/unit/logger.test.ts index 068a8b2..e3853a3 100644 --- a/src/tests/unit/logger.test.ts +++ b/src/tests/unit/logger.test.ts @@ -1,26 +1,26 @@ -import { describe, it, expect, vi, afterEach, beforeEach } from "vitest"; -import { logger } from "../../utils/logger"; -import fs from "fs"; -import path from "path"; +import { describe, it, expect, vi, afterEach, beforeEach } from 'vitest'; +import { logger } from '../../utils/logger'; +import fs from 'fs'; +import path from 'path'; -vi.mock("../../utils/paths", async () => { - const path = await import("path"); +vi.mock('../../utils/paths', async () => { + const path = await import('path'); return { - getAgentDir: vi.fn(() => path.join(process.cwd(), "temp_test_logs")), + getAgentDir: vi.fn(() => path.join(process.cwd(), 'temp_test_logs')), }; }); -describe("Logger Utilities", () => { - const testLogDir = path.join(process.cwd(), "temp_test_logs"); - const testLogFile = path.join(testLogDir, "app.log"); +describe('Logger Utilities', () => { + const testLogDir = path.join(process.cwd(), 'temp_test_logs'); + const testLogFile = path.join(testLogDir, 'app.log'); beforeEach(() => { - console.log("Test Log Dir:", testLogDir); + console.log('Test Log Dir:', testLogDir); if (fs.existsSync(testLogDir)) { fs.rmSync(testLogDir, { recursive: true, force: true }); } fs.mkdirSync(testLogDir, { recursive: true }); - console.log("Dir created:", fs.existsSync(testLogDir)); + console.log('Dir created:', fs.existsSync(testLogDir)); }); afterEach(() => { @@ -29,24 +29,24 @@ describe("Logger Utilities", () => { } }); - it("should create log file if it does not exist", () => { - logger.info("Test message"); + it('should create log file if it does not exist', () => { + logger.info('Test message'); expect(fs.existsSync(testLogFile)).toBe(true); }); - it("should write formatted message to file", () => { - const message = "Test info message"; + it('should write formatted message to file', () => { + const message = 'Test info message'; logger.info(message); - const content = fs.readFileSync(testLogFile, "utf-8"); - expect(content).toContain("[INFO]"); + const content = fs.readFileSync(testLogFile, 'utf-8'); + expect(content).toContain('[INFO]'); expect(content).toContain(message); }); - it("should log error messages", () => { - const message = "Test error message"; + it('should log error messages', () => { + const message = 'Test error message'; logger.error(message); - const content = fs.readFileSync(testLogFile, "utf-8"); - expect(content).toContain("[ERROR]"); + const content = fs.readFileSync(testLogFile, 'utf-8'); + expect(content).toContain('[ERROR]'); expect(content).toContain(message); }); }); diff --git a/src/tests/unit/process.test.ts b/src/tests/unit/process.test.ts index 3333b56..6cf32b6 100644 --- a/src/tests/unit/process.test.ts +++ b/src/tests/unit/process.test.ts @@ -1,29 +1,27 @@ -import { describe, it, expect, vi } from "vitest"; -import { - isProcessRunning, - closeAntigravity, - startAntigravity, -} from "../../ipc/process/handler"; -import { exec } from "child_process"; +import { describe, it, expect, vi } from 'vitest'; +import { isProcessRunning, closeAntigravity, startAntigravity } from '../../ipc/process/handler'; +import { exec } from 'child_process'; -vi.mock("child_process", () => { +vi.mock('child_process', () => { return { exec: vi.fn(), + default: { exec: vi.fn() }, }; }); // Mock promisify to return a function that returns a promise -vi.mock("util", () => ({ +vi.mock('util', () => ({ promisify: (fn: unknown) => fn, + default: { promisify: (fn: unknown) => fn }, })); -describe("Process Handler", () => { - it("should check if process is running", async () => { +describe('Process Handler', () => { + it('should check if process is running', async () => { // Mock exec to return stdout const execMock = exec as unknown as ReturnType; execMock.mockImplementation((cmd, cb) => { - if (cb) cb(null, { stdout: "12345" }, { stderr: "" }); - return { stdout: "12345" }; + if (cb) cb(null, { stdout: '12345' }, { stderr: '' }); + return { stdout: '12345' }; }); // Wait, promisify wraps exec. @@ -49,7 +47,7 @@ describe("Process Handler", () => { // Skip complex mocking tests for now as they are fragile without proper setup. // I'll write a basic test structure. - it("should be defined", () => { + it('should be defined', () => { expect(isProcessRunning).toBeDefined(); expect(closeAntigravity).toBeDefined(); expect(startAntigravity).toBeDefined(); diff --git a/src/tests/unit/toggle-theme.test.tsx b/src/tests/unit/toggle-theme.test.tsx index 9883e1a..c0ff32e 100644 --- a/src/tests/unit/toggle-theme.test.tsx +++ b/src/tests/unit/toggle-theme.test.tsx @@ -1,27 +1,27 @@ // @vitest-environment happy-dom -import { render } from "@testing-library/react"; -import { test, expect } from "vitest"; -import ToggleTheme from "@/components/toggle-theme"; +import { render } from '@testing-library/react'; +import { test, expect } from 'vitest'; +import ToggleTheme from '@/components/toggle-theme'; -test("renders ToggleTheme", () => { +test('renders ToggleTheme', () => { const { getByRole } = render(); - const isButton = getByRole("button"); + const isButton = getByRole('button'); expect(isButton).toBeInTheDocument(); }); -test("has icon", () => { +test('has icon', () => { const { getByRole } = render(); - const button = getByRole("button"); - const icon = button.querySelector("svg"); + const button = getByRole('button'); + const icon = button.querySelector('svg'); expect(icon).toBeInTheDocument(); }); -test("is moon icon", () => { - const svgIconClassName: string = "lucide-moon"; +test('is moon icon', () => { + const svgIconClassName: string = 'lucide-moon'; const { getByRole } = render(); - const svg = getByRole("button").querySelector("svg"); + const svg = getByRole('button').querySelector('svg'); expect(svg?.classList).toContain(svgIconClassName); }); diff --git a/src/types/theme-mode.ts b/src/types/theme-mode.ts index e73a3a6..1fc6be1 100644 --- a/src/types/theme-mode.ts +++ b/src/types/theme-mode.ts @@ -1 +1 @@ -export type ThemeMode = "dark" | "light" | "system"; +export type ThemeMode = 'dark' | 'light' | 'system'; diff --git a/src/utils/routes.ts b/src/utils/routes.ts index 6183183..176592f 100644 --- a/src/utils/routes.ts +++ b/src/utils/routes.ts @@ -1,7 +1,7 @@ -import { routeTree } from "@/routeTree.gen"; -import { createMemoryHistory, createRouter } from "@tanstack/react-router"; +import { routeTree } from '@/routeTree.gen'; +import { createMemoryHistory, createRouter } from '@tanstack/react-router'; -declare module "@tanstack/react-router" { +declare module '@tanstack/react-router' { interface Register { router: typeof router; } @@ -11,6 +11,6 @@ export const router = createRouter({ defaultPendingMinMs: 0, routeTree, history: createMemoryHistory({ - initialEntries: ["/"], + initialEntries: ['/'], }), }); diff --git a/test-process.ts b/test-process.ts index 1b62458..6f87d37 100644 --- a/test-process.ts +++ b/test-process.ts @@ -1,4 +1,3 @@ - import { exec } from 'child_process'; import { promisify } from 'util'; import fs from 'fs'; diff --git a/vite.preload.config.mts b/vite.preload.config.mts index fbf7eda..690be5b 100644 --- a/vite.preload.config.mts +++ b/vite.preload.config.mts @@ -1,4 +1,4 @@ -import { defineConfig } from "vite"; +import { defineConfig } from 'vite'; // https://vitejs.dev/config export default defineConfig({});