diff --git a/.eslintrc.cjs b/.eslintrc.cjs deleted file mode 100644 index 1a2389b6e..000000000 --- a/.eslintrc.cjs +++ /dev/null @@ -1,20 +0,0 @@ -module.exports = { - root: true, - env: { browser: true, es2020: true, node: true }, - extends: [ - 'eslint:recommended', - 'plugin:@typescript-eslint/recommended', - 'plugin:react-hooks/recommended', - ], - ignorePatterns: ['dist', 'dist-electron', '.eslintrc.cjs'], - parser: '@typescript-eslint/parser', - plugins: ['react-refresh'], - rules: { - 'react-refresh/only-export-components': [ - 'warn', - { allowConstantExport: true }, - ], - '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }], - '@typescript-eslint/no-explicit-any': 'warn', - }, -}; diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 812910099..742689450 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,7 +28,7 @@ jobs: - name: Setup pnpm uses: pnpm/action-setup@v4 with: - version: 9 + version: 10 - name: Install dependencies run: pnpm install --frozen-lockfile @@ -50,7 +50,7 @@ jobs: - name: Setup pnpm uses: pnpm/action-setup@v4 with: - version: 9 + version: 10 - name: Install dependencies run: pnpm install --frozen-lockfile @@ -72,7 +72,7 @@ jobs: - name: Setup pnpm uses: pnpm/action-setup@v4 with: - version: 9 + version: 10 - name: Install dependencies run: pnpm install --frozen-lockfile @@ -98,7 +98,7 @@ jobs: - name: Setup pnpm uses: pnpm/action-setup@v4 with: - version: 9 + version: 10 - name: Get pnpm store directory shell: bash diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2aa4ee7a1..2da6e5615 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -44,7 +44,7 @@ jobs: - name: Setup pnpm uses: pnpm/action-setup@v4 with: - version: 9 + version: 10 - name: Get pnpm store directory shell: bash diff --git a/electron/gateway/manager.ts b/electron/gateway/manager.ts index 953d4b726..55753c019 100644 --- a/electron/gateway/manager.ts +++ b/electron/gateway/manager.ts @@ -698,10 +698,11 @@ export class GatewayManager extends EventEmitter { this.emit('chat:message', notification.params as { message: unknown }); break; - case GatewayEventType.ERROR: + case GatewayEventType.ERROR: { const errorData = notification.params as { message?: string }; this.emit('error', new Error(errorData.message || 'Gateway error')); break; + } default: // Unknown notification type, just log it diff --git a/electron/main/index.ts b/electron/main/index.ts index a772329d8..207d2ac9a 100644 --- a/electron/main/index.ts +++ b/electron/main/index.ts @@ -2,13 +2,13 @@ * Electron Main Process Entry * Manages window creation, system tray, and IPC handlers */ -import { app, BrowserWindow, ipcMain, session, shell } from 'electron'; +import { app, BrowserWindow, session, shell } from 'electron'; import { join } from 'path'; import { GatewayManager } from '../gateway/manager'; import { registerIpcHandlers } from './ipc-handlers'; import { createTray } from './tray'; import { createMenu } from './menu'; -import { PORTS } from '../utils/config'; + import { appUpdater, registerUpdateHandlers } from './updater'; // Disable GPU acceleration for better compatibility diff --git a/electron/main/ipc-handlers.ts b/electron/main/ipc-handlers.ts index 5002ca3fd..85e130abc 100644 --- a/electron/main/ipc-handlers.ts +++ b/electron/main/ipc-handlers.ts @@ -11,7 +11,7 @@ import { hasApiKey, saveProvider, getProvider, - getAllProviders, + deleteProvider, setDefaultProvider, getDefaultProvider, diff --git a/electron/main/window.ts b/electron/main/window.ts index 505edd8a0..1e8819810 100644 --- a/electron/main/window.ts +++ b/electron/main/window.ts @@ -13,6 +13,7 @@ interface WindowState { } // Lazy-load electron-store (ESM module) +// eslint-disable-next-line @typescript-eslint/no-explicit-any let windowStateStore: any = null; async function getStore() { @@ -89,6 +90,7 @@ export async function saveWindowState(win: BrowserWindow): Promise { export function trackWindowState(win: BrowserWindow): void { // Save state on window events ['resize', 'move', 'close'].forEach((event) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any win.on(event as any, () => saveWindowState(win)); }); } diff --git a/electron/preload/index.ts b/electron/preload/index.ts index 4d1d92a43..c4850bef7 100644 --- a/electron/preload/index.ts +++ b/electron/preload/index.ts @@ -161,6 +161,7 @@ const electronAPI = { */ off: (channel: string, callback?: (...args: unknown[]) => void) => { if (callback) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any ipcRenderer.removeListener(channel, callback as any); } else { ipcRenderer.removeAllListeners(channel); diff --git a/electron/utils/logger.ts b/electron/utils/logger.ts index 11884f633..cf1716715 100644 --- a/electron/utils/logger.ts +++ b/electron/utils/logger.ts @@ -70,7 +70,7 @@ function writeToFile(formatted: string): void { if (logFilePath) { try { appendFileSync(logFilePath, formatted + '\n'); - } catch (error) { + } catch { // Silently fail if we can't write to file } } diff --git a/electron/utils/secure-storage.ts b/electron/utils/secure-storage.ts index 140140728..a9b28d6c8 100644 --- a/electron/utils/secure-storage.ts +++ b/electron/utils/secure-storage.ts @@ -5,7 +5,9 @@ import { safeStorage } from 'electron'; // Lazy-load electron-store (ESM module) +// eslint-disable-next-line @typescript-eslint/no-explicit-any let store: any = null; +// eslint-disable-next-line @typescript-eslint/no-explicit-any let providerStore: any = null; async function getStore() { diff --git a/electron/utils/store.ts b/electron/utils/store.ts index a0d923d7c..1b86c9293 100644 --- a/electron/utils/store.ts +++ b/electron/utils/store.ts @@ -6,6 +6,7 @@ import { randomBytes } from 'crypto'; // Lazy-load electron-store (ESM module) +// eslint-disable-next-line @typescript-eslint/no-explicit-any let settingsStoreInstance: any = null; /** @@ -142,7 +143,7 @@ export async function importSettings(json: string): Promise { const settings = JSON.parse(json); const store = await getSettingsStore(); store.set(settings); - } catch (error) { + } catch { throw new Error('Invalid settings JSON'); } } diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 000000000..2fd94af65 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,43 @@ +import js from '@eslint/js'; +import tsPlugin from '@typescript-eslint/eslint-plugin'; +import tsParser from '@typescript-eslint/parser'; +import reactHooks from 'eslint-plugin-react-hooks'; +import reactRefresh from 'eslint-plugin-react-refresh'; +import globals from 'globals'; + +export default [ + { + ignores: ['dist/**', 'dist-electron/**', 'openclaw/**'], + }, + { + files: ['**/*.{ts,tsx}'], + languageOptions: { + parser: tsParser, + ecmaVersion: 2020, + sourceType: 'module', + globals: { + ...globals.browser, + ...globals.es2020, + ...globals.node, + }, + }, + plugins: { + '@typescript-eslint': tsPlugin, + 'react-hooks': reactHooks, + 'react-refresh': reactRefresh, + }, + rules: { + ...js.configs.recommended.rules, + ...tsPlugin.configs.recommended.rules, + ...reactHooks.configs.recommended.rules, + // TypeScript handles these checks natively, disable ESLint duplicates + 'no-undef': 'off', + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_', varsIgnorePattern: '^_', destructuredArrayIgnorePattern: '^_' }], + '@typescript-eslint/no-explicit-any': 'warn', + }, + }, +]; diff --git a/package.json b/package.json index 5fe0f6a90..8d45c8d85 100644 --- a/package.json +++ b/package.json @@ -19,8 +19,8 @@ "build:vite": "vite build", "build:electron": "tsc -p tsconfig.node.json", "preview": "vite preview", - "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", - "lint:fix": "eslint . --ext ts,tsx --fix", + "lint": "eslint . --report-unused-disable-directives --max-warnings 0", + "lint:fix": "eslint . --fix", "typecheck": "tsc --noEmit", "test": "vitest run", "test:watch": "vitest", @@ -53,6 +53,7 @@ "ws": "^8.18.0" }, "devDependencies": { + "@eslint/js": "^9.39.2", "@playwright/test": "^1.49.1", "@radix-ui/react-dialog": "^1.1.4", "@radix-ui/react-dropdown-menu": "^2.1.4", @@ -83,6 +84,7 @@ "eslint-plugin-react-hooks": "^5.1.0", "eslint-plugin-react-refresh": "^0.4.16", "framer-motion": "^11.15.0", + "globals": "^17.3.0", "jsdom": "^25.0.1", "lucide-react": "^0.469.0", "postcss": "^8.4.49", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 97fa8c0ea..f569f9e03 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -24,6 +24,9 @@ importers: specifier: ^8.18.0 version: 8.19.0 devDependencies: + '@eslint/js': + specifier: ^9.39.2 + version: 9.39.2 '@playwright/test': specifier: ^1.49.1 version: 1.58.1 @@ -114,6 +117,9 @@ importers: framer-motion: specifier: ^11.15.0 version: 11.18.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + globals: + specifier: ^17.3.0 + version: 17.3.0 jsdom: specifier: ^25.0.1 version: 25.0.1 @@ -2443,6 +2449,10 @@ packages: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} engines: {node: '>=18'} + globals@17.3.0: + resolution: {integrity: sha512-yMqGUQVVCkD4tqjOJf3TnrvaaHDMYp4VlUSObbkIiuCPe/ofdMBFIAcBbCSRFWOnos6qRiTVStDwqPLUclaxIw==} + engines: {node: '>=18'} + globalthis@1.0.4: resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} engines: {node: '>= 0.4'} @@ -6519,6 +6529,8 @@ snapshots: globals@14.0.0: {} + globals@17.3.0: {} + globalthis@1.0.4: dependencies: define-properties: 1.2.1 diff --git a/src/components/layout/Sidebar.tsx b/src/components/layout/Sidebar.tsx index 72ed83bba..e8172e92d 100644 --- a/src/components/layout/Sidebar.tsx +++ b/src/components/layout/Sidebar.tsx @@ -69,7 +69,7 @@ export function Sidebar() { const gatewayStatus = useGatewayStore((state) => state.status); const [versionClicks, setVersionClicks] = useState(0); - const [appVersion, setAppVersion] = useState('0.1.0'); + const [, setAppVersion] = useState('0.1.0'); // Get app version useEffect(() => { @@ -79,7 +79,7 @@ export function Sidebar() { }, []); // Handle version click for dev mode unlock - const handleVersionClick = () => { + const _handleVersionClick = () => { const clicks = versionClicks + 1; setVersionClicks(clicks); diff --git a/src/components/ui/badge.tsx b/src/components/ui/badge.tsx index a26462e33..95cd098bb 100644 --- a/src/components/ui/badge.tsx +++ b/src/components/ui/badge.tsx @@ -1,3 +1,4 @@ +/* eslint-disable react-refresh/only-export-components */ /** * Badge Component * Based on shadcn/ui badge diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx index 6c14738ad..1f976c548 100644 --- a/src/components/ui/button.tsx +++ b/src/components/ui/button.tsx @@ -1,3 +1,4 @@ +/* eslint-disable react-refresh/only-export-components */ /** * Button Component * Based on shadcn/ui button diff --git a/src/components/ui/input.tsx b/src/components/ui/input.tsx index 5bf37a28c..64cefd9b9 100644 --- a/src/components/ui/input.tsx +++ b/src/components/ui/input.tsx @@ -5,8 +5,7 @@ import * as React from 'react'; import { cn } from '@/lib/utils'; -export interface InputProps - extends React.InputHTMLAttributes {} +export type InputProps = React.InputHTMLAttributes; const Input = React.forwardRef( ({ className, type, ...props }, ref) => { diff --git a/src/pages/Cron/index.tsx b/src/pages/Cron/index.tsx index 5c17af92e..d8a3531c4 100644 --- a/src/pages/Cron/index.tsx +++ b/src/pages/Cron/index.tsx @@ -447,7 +447,7 @@ export function Cron() { try { await toggleJob(id, enabled); toast.success(enabled ? 'Task enabled' : 'Task paused'); - } catch (err) { + } catch { toast.error('Failed to update task'); } }, [toggleJob]); @@ -456,7 +456,7 @@ export function Cron() { try { await deleteJob(id); toast.success('Task deleted'); - } catch (err) { + } catch { toast.error('Failed to delete task'); } }, [deleteJob]); diff --git a/src/pages/Skills/index.tsx b/src/pages/Skills/index.tsx index 7316499f1..f3dd0cd9e 100644 --- a/src/pages/Skills/index.tsx +++ b/src/pages/Skills/index.tsx @@ -322,7 +322,7 @@ export function Skills() { } } toast.success(allEnabled ? 'Bundle disabled' : 'Bundle enabled'); - } catch (err) { + } catch { toast.error('Failed to apply bundle'); } }, [skills, enableSkill, disableSkill]); diff --git a/src/stores/channels.ts b/src/stores/channels.ts index 820c7a8c1..1a6ea5ef9 100644 --- a/src/stores/channels.ts +++ b/src/stores/channels.ts @@ -66,7 +66,7 @@ export const useChannelsStore = create((set, get) => ({ })); return newChannel; } - } catch (error) { + } catch { // Create local channel if gateway unavailable const newChannel: Channel = { id: `local-${Date.now()}`, diff --git a/tests/unit/stores.test.ts b/tests/unit/stores.test.ts index 8a5dbe126..662c365b2 100644 --- a/tests/unit/stores.test.ts +++ b/tests/unit/stores.test.ts @@ -1,7 +1,7 @@ /** * Zustand Stores Tests */ -import { describe, it, expect, beforeEach, vi } from 'vitest'; +import { describe, it, expect, beforeEach } from 'vitest'; import { useSettingsStore } from '@/stores/settings'; import { useGatewayStore } from '@/stores/gateway'; diff --git a/tests/unit/utils.test.ts b/tests/unit/utils.test.ts index 4454a2f92..86e9dcd81 100644 --- a/tests/unit/utils.test.ts +++ b/tests/unit/utils.test.ts @@ -2,7 +2,7 @@ * Utility Functions Tests */ import { describe, it, expect } from 'vitest'; -import { cn, formatRelativeTime, formatDuration, truncate } from '@/lib/utils'; +import { cn, formatDuration, truncate } from '@/lib/utils'; describe('cn (class name merge)', () => { it('should merge class names', () => { @@ -10,8 +10,8 @@ describe('cn (class name merge)', () => { }); it('should handle conditional classes', () => { - expect(cn('base', true && 'active')).toBe('base active'); - expect(cn('base', false && 'active')).toBe('base'); + expect(cn('base', 'active')).toBe('base active'); + expect(cn('base', false)).toBe('base'); }); it('should merge tailwind classes correctly', () => {