diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 99216e2..dfd3d82 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -24,4 +24,4 @@ jobs: run: go build ./... - name: Test - run: go test ./... + run: cd pkg && go test ./... diff --git a/.gitignore b/.gitignore index 75713e0..a6f9c08 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,45 @@ starknode-kit.exe *.exe *.so *.dylib + +# Node.js dependencies +node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* +package-lock.json +yarn.lock + +# Documentation build outputs +docs/_book/ +docs/.gitbook/ +docs/dist/ +docs/build/ + +# Next.js build outputs +docs/.next/ +docs/out/ +docs/next-env.d.ts +.next/ +out/ +next-env.d.ts + +# IDE and editor files +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS generated files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Temporary files +*.tmp +*.temp diff --git a/README.md b/README.md index 038d5b3..9086f81 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ 1. Download and run the installation script: ```bash - /bin/bash -c "$(curl -sSL https://raw.githubusercontent.com/thebuidl-grid/starknode-kit/main/install.sh)" + /bin/bash -c "$(curl -sSL https://raw.githubusercontent.com/thebuidl-grid/starknode-kit/main/install.sh)" ``` 2. Or download the script first and then run it: @@ -53,6 +53,7 @@ After installation, verify that `starknode-kit` is working: ```bash starknode-kit --help ``` + #### Generate Config file ```bash @@ -70,7 +71,6 @@ rm -rf ~/.config/starknode-kit > **Note**: This will not remove any of the client data (e.g., blockchain data). The data is stored in the locations specified in your `~/.starknode-kit/starknode.yml` file. - --- ## 📘 Available Commands @@ -98,20 +98,20 @@ rm -rf ~/.config/starknode-kit #### Add a client pair (consensus + execution) ```bash -starknode-kit add --consensus_client lighthouse --execution_client geth +starknode-kit add --consensus-client lighthouse --execution-client geth ``` #### Add a Starknet client ```bash -starknode-kit add --starknet_client juno +starknode-kit add --starknet-client juno ``` #### Remove a configured client ```bash -starknode-kit remove --consensus_client lighthouse -starknode-kit remove --starknet_client juno +starknode-kit remove --consensus-client lighthouse +starknode-kit remove --starknet-client juno ``` #### Change network @@ -163,11 +163,13 @@ starknode-kit run lighthouse Manage the Starknet validator client. - **Get validator status:** + ```bash starknode-kit validator status ``` - **Get validator version:** + ```bash starknode-kit validator --version ``` @@ -197,33 +199,33 @@ starknode-kit help add Make sure the following are installed on your system before using or building `starknode-kit`: -* **Go**: Version **1.24 or later** +- **Go**: Version **1.24 or later** Install from: [https://go.dev/dl/](https://go.dev/dl/) -* **Rust**: Recommended for building Starknet clients (e.g., Juno) +- **Rust**: Recommended for building Starknet clients (e.g., Juno) Install with: ```bash curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh ``` -* **Make**: Required to build certain clients and scripts +- **Make**: Required to build certain clients and scripts Install via package manager: - * Ubuntu/Debian: `sudo apt install make` - * macOS (with Homebrew): `brew install make` - * Windows (WSL): included or `sudo apt install make` + - Ubuntu/Debian: `sudo apt install make` + - macOS (with Homebrew): `brew install make` + - Windows (WSL): included or `sudo apt install make` ### 🖥️ Hardware Requirements See this [Rocket Pool Hardware Guide](https://docs.rocketpool.net/guides/node/hardware.html) for a detailed breakdown of node hardware requirements. -* **CPU**: Node operation doesn't require heavy CPU power. The BG Client has run well on both i3 and i5 models of the ASUS NUC 13 PRO. Be cautious if using Celeron processors, as they may have limitations. -* **RAM**: At least **32 GB** is recommended for good performance with overhead. -* **Storage (SSD)**: The most critical component. Use a **2 TB+ NVMe SSD** with: +- **CPU**: Node operation doesn't require heavy CPU power. The BG Client has run well on both i3 and i5 models of the ASUS NUC 13 PRO. Be cautious if using Celeron processors, as they may have limitations. +- **RAM**: At least **32 GB** is recommended for good performance with overhead. +- **Storage (SSD)**: The most critical component. Use a **2 TB+ NVMe SSD** with: - * A **DRAM cache** - * **No Quad-Level Cell (QLC)** NAND architecture + - A **DRAM cache** + - **No Quad-Level Cell (QLC)** NAND architecture See this [SSD List Gist](https://gist.github.com/bkase/fab02c5b3c404e9ef8e5c2071ac1558c) for tested options. --- diff --git a/cli/commands/configCommand/newConfigCommand.go b/cli/commands/configCommand/newConfigCommand.go index 3a2b754..ed10063 100644 --- a/cli/commands/configCommand/newConfigCommand.go +++ b/cli/commands/configCommand/newConfigCommand.go @@ -206,9 +206,8 @@ func installClients(starknetNode, validator bool, consensusClient, executionClie if starknetNode { err := options.Installer.InstallClient(types.ClientJuno) if errors.Is(err, pkg.ErrClientIsInstalled) { - fmt.Println(utils.Yellow(fmt.Sprintf("🤔 Client Juno is already installed. Skipping."))) - } else { - + fmt.Println(utils.Yellow("🤔 Client Juno is already installed. Skipping.")) + } else if err != nil { fmt.Println(utils.Red(fmt.Sprintf("❌ Could not install client Juno: %v", err))) return } diff --git a/docs/.gitignore b/docs/.gitignore deleted file mode 100644 index fd3dbb5..0000000 --- a/docs/.gitignore +++ /dev/null @@ -1,36 +0,0 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -# dependencies -/node_modules -/.pnp -.pnp.js -.yarn/install-state.gz - -# testing -/coverage - -# next.js -/.next/ -/out/ - -# production -/build - -# misc -.DS_Store -*.pem - -# debug -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -# local env files -.env*.local - -# vercel -.vercel - -# typescript -*.tsbuildinfo -next-env.d.ts diff --git a/docs/DOCS_GUIDE.md b/docs/DOCS_GUIDE.md deleted file mode 100644 index 160be49..0000000 --- a/docs/DOCS_GUIDE.md +++ /dev/null @@ -1,291 +0,0 @@ -# Starknode-kit Documentation Guide - -This guide explains how to run and develop the starknode-kit documentation site. - -## Quick Start - -### Installation - -```bash -cd docs -npm install -``` - -### Development Server - -Run the development server: - -```bash -npm run dev -``` - -Open [http://localhost:3000](http://localhost:3000) in your browser to see the documentation. - -The page auto-updates as you edit files. - -### Production Build - -Build for production: - -```bash -npm run build -npm start -``` - -## Documentation Structure - -``` -docs/ -├── src/ -│ ├── app/ # Next.js pages (App Router) -│ │ ├── page.tsx # Homepage -│ │ ├── layout.tsx # Root layout with sidebar/header -│ │ ├── globals.css # Global styles -│ │ ├── getting-started/ # Getting started guide -│ │ ├── installation/ # Installation guide -│ │ ├── configuration/ # Configuration docs -│ │ ├── commands/ # Command reference -│ │ ├── clients/ # Client documentation -│ │ ├── validator/ # Validator setup guide -│ │ ├── requirements/ # Requirements page -│ │ └── contributing/ # Contributing guide -│ └── components/ # Reusable components -│ ├── Sidebar.tsx # Navigation sidebar -│ ├── Header.tsx # Top header with search -│ └── CodeBlock.tsx # Code block component -├── public/ # Static assets -├── package.json # Dependencies -└── README.md # Documentation README - -``` - -## Features - -### GitBook-Style Layout - -- **Fixed Sidebar** - Navigation always visible on the left -- **Header** - Search and links at the top -- **Content Area** - Main documentation content -- **Responsive** - Works on mobile and desktop - -### Components - -#### Sidebar (`components/Sidebar.tsx`) - -- Hierarchical navigation -- Active page highlighting -- Collapsible sections -- Links to all documentation pages - -#### Header (`components/Header.tsx`) - -- Search bar (placeholder, can be enhanced) -- GitHub link -- Telegram link -- Dark mode support - -#### CodeBlock (`components/CodeBlock.tsx`) - -- Syntax-highlighted code blocks -- Copy-to-clipboard button -- Language support -- Dark theme optimized - -### Styling - -- **Tailwind CSS** - Utility-first CSS framework -- **Dark Mode** - Automatic based on system preference -- **Custom Scrollbars** - Styled for better UX -- **Prose** - Typography optimized for documentation - -## Adding New Pages - -### 1. Create Page File - -Create a new `page.tsx` in the appropriate directory: - -```bash -mkdir -p src/app/your-page -``` - -```tsx -// src/app/your-page/page.tsx -import CodeBlock from '@/components/CodeBlock'; - -export default function YourPage() { - return ( -
-

Your Page Title

-

Your content here...

- - -
- ); -} -``` - -### 2. Add to Navigation - -Update `src/components/Sidebar.tsx`: - -```tsx -const navigation: NavItem[] = [ - // ... existing items - { title: 'Your Page', href: '/your-page' }, -]; -``` - -### 3. Test - -```bash -npm run dev -``` - -Visit `http://localhost:3000/your-page` - -## Deployment - -### Vercel (Recommended) - -1. Push to GitHub -2. Import project in Vercel -3. Deploy automatically - -### Self-Hosted - -```bash -npm run build -npm start -``` - -Or use a process manager like PM2: - -```bash -pm2 start npm --name "starknode-docs" -- start -``` - -### Static Export - -For static hosting (GitHub Pages, etc.): - -```bash -# Add to next.config.ts: -# output: 'export' - -npm run build -# Output will be in 'out/' directory -``` - -## Customization - -### Colors - -Edit `src/app/globals.css`: - -```css -:root { - --background: #ffffff; - --foreground: #171717; -} -``` - -### Fonts - -Edit `src/app/layout.tsx`: - -```tsx -import { Inter } from "next/font/google"; - -const inter = Inter({ - subsets: ["latin"], - variable: "--font-inter", -}); -``` - -### Navigation - -Edit `src/components/Sidebar.tsx`: - -```tsx -const navigation: NavItem[] = [ - // Add/remove/reorder items -]; -``` - -## Tips - -### Use CodeBlock Component - -```tsx -import CodeBlock from '@/components/CodeBlock'; - - -``` - -### Use Prose Styling - -Always wrap content in prose div: - -```tsx -
- {/* Your content */} -
-``` - -### Add Info Boxes - -```tsx -
-

💡 Tip

-

Your tip here

-
-``` - -### Link Between Pages - -```tsx -import Link from 'next/link'; - -Other Page -``` - -## Troubleshooting - -### Port Already in Use - -```bash -# Kill process on port 3000 -lsof -i :3000 -kill -9 [PID] - -# Or use different port -PORT=3001 npm run dev -``` - -### Build Errors - -```bash -# Clean and rebuild -rm -rf .next node_modules -npm install -npm run build -``` - -### Styling Not Updating - -```bash -# Clear Next.js cache -rm -rf .next -npm run dev -``` - -## Resources - -- [Next.js Documentation](https://nextjs.org/docs) -- [Tailwind CSS](https://tailwindcss.com/docs) -- [React Documentation](https://react.dev/) - -## Contributing - -See the main [Contributing Guide](../CONTRIBUTING.md) for guidelines on contributing to the documentation. - diff --git a/docs/package-lock.json b/docs/package-lock.json deleted file mode 100644 index 281b15d..0000000 --- a/docs/package-lock.json +++ /dev/null @@ -1,1667 +0,0 @@ -{ - "name": "docs", - "version": "0.1.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "docs", - "version": "0.1.0", - "dependencies": { - "next": "15.5.4", - "react": "19.1.0", - "react-dom": "19.1.0" - }, - "devDependencies": { - "@tailwindcss/postcss": "^4", - "@types/node": "^20", - "@types/react": "^19", - "@types/react-dom": "^19", - "tailwindcss": "^4", - "typescript": "^5" - } - }, - "node_modules/@alloc/quick-lru": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", - "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.5.0.tgz", - "integrity": "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@img/colour": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz", - "integrity": "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@img/sharp-darwin-arm64": { - "version": "0.34.4", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.4.tgz", - "integrity": "sha512-sitdlPzDVyvmINUdJle3TNHl+AG9QcwiAMsXmccqsCOMZNIdW2/7S26w0LyU8euiLVzFBL3dXPwVCq/ODnf2vA==", - "cpu": [ - "arm64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-darwin-arm64": "1.2.3" - } - }, - "node_modules/@img/sharp-darwin-x64": { - "version": "0.34.4", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.4.tgz", - "integrity": "sha512-rZheupWIoa3+SOdF/IcUe1ah4ZDpKBGWcsPX6MT0lYniH9micvIU7HQkYTfrx5Xi8u+YqwLtxC/3vl8TQN6rMg==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-darwin-x64": "1.2.3" - } - }, - "node_modules/@img/sharp-libvips-darwin-arm64": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.3.tgz", - "integrity": "sha512-QzWAKo7kpHxbuHqUC28DZ9pIKpSi2ts2OJnoIGI26+HMgq92ZZ4vk8iJd4XsxN+tYfNJxzH6W62X5eTcsBymHw==", - "cpu": [ - "arm64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "darwin" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-darwin-x64": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.3.tgz", - "integrity": "sha512-Ju+g2xn1E2AKO6YBhxjj+ACcsPQRHT0bhpglxcEf+3uyPY+/gL8veniKoo96335ZaPo03bdDXMv0t+BBFAbmRA==", - "cpu": [ - "x64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "darwin" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-arm": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.3.tgz", - "integrity": "sha512-x1uE93lyP6wEwGvgAIV0gP6zmaL/a0tGzJs/BIDDG0zeBhMnuUPm7ptxGhUbcGs4okDJrk4nxgrmxpib9g6HpA==", - "cpu": [ - "arm" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-arm64": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.3.tgz", - "integrity": "sha512-I4RxkXU90cpufazhGPyVujYwfIm9Nk1QDEmiIsaPwdnm013F7RIceaCc87kAH+oUB1ezqEvC6ga4m7MSlqsJvQ==", - "cpu": [ - "arm64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-ppc64": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.3.tgz", - "integrity": "sha512-Y2T7IsQvJLMCBM+pmPbM3bKT/yYJvVtLJGfCs4Sp95SjvnFIjynbjzsa7dY1fRJX45FTSfDksbTp6AGWudiyCg==", - "cpu": [ - "ppc64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-s390x": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.3.tgz", - "integrity": "sha512-RgWrs/gVU7f+K7P+KeHFaBAJlNkD1nIZuVXdQv6S+fNA6syCcoboNjsV2Pou7zNlVdNQoQUpQTk8SWDHUA3y/w==", - "cpu": [ - "s390x" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-x64": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.3.tgz", - "integrity": "sha512-3JU7LmR85K6bBiRzSUc/Ff9JBVIFVvq6bomKE0e63UXGeRw2HPVEjoJke1Yx+iU4rL7/7kUjES4dZ/81Qjhyxg==", - "cpu": [ - "x64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linuxmusl-arm64": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.3.tgz", - "integrity": "sha512-F9q83RZ8yaCwENw1GieztSfj5msz7GGykG/BA+MOUefvER69K/ubgFHNeSyUu64amHIYKGDs4sRCMzXVj8sEyw==", - "cpu": [ - "arm64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linuxmusl-x64": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.3.tgz", - "integrity": "sha512-U5PUY5jbc45ANM6tSJpsgqmBF/VsL6LnxJmIf11kB7J5DctHgqm0SkuXzVWtIY90GnJxKnC/JT251TDnk1fu/g==", - "cpu": [ - "x64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-linux-arm": { - "version": "0.34.4", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.4.tgz", - "integrity": "sha512-Xyam4mlqM0KkTHYVSuc6wXRmM7LGN0P12li03jAnZ3EJWZqj83+hi8Y9UxZUbxsgsK1qOEwg7O0Bc0LjqQVtxA==", - "cpu": [ - "arm" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-arm": "1.2.3" - } - }, - "node_modules/@img/sharp-linux-arm64": { - "version": "0.34.4", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.4.tgz", - "integrity": "sha512-YXU1F/mN/Wu786tl72CyJjP/Ngl8mGHN1hST4BGl+hiW5jhCnV2uRVTNOcaYPs73NeT/H8Upm3y9582JVuZHrQ==", - "cpu": [ - "arm64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-arm64": "1.2.3" - } - }, - "node_modules/@img/sharp-linux-ppc64": { - "version": "0.34.4", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.4.tgz", - "integrity": "sha512-F4PDtF4Cy8L8hXA2p3TO6s4aDt93v+LKmpcYFLAVdkkD3hSxZzee0rh6/+94FpAynsuMpLX5h+LRsSG3rIciUQ==", - "cpu": [ - "ppc64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-ppc64": "1.2.3" - } - }, - "node_modules/@img/sharp-linux-s390x": { - "version": "0.34.4", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.4.tgz", - "integrity": "sha512-qVrZKE9Bsnzy+myf7lFKvng6bQzhNUAYcVORq2P7bDlvmF6u2sCmK2KyEQEBdYk+u3T01pVsPrkj943T1aJAsw==", - "cpu": [ - "s390x" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-s390x": "1.2.3" - } - }, - "node_modules/@img/sharp-linux-x64": { - "version": "0.34.4", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.4.tgz", - "integrity": "sha512-ZfGtcp2xS51iG79c6Vhw9CWqQC8l2Ot8dygxoDoIQPTat/Ov3qAa8qpxSrtAEAJW+UjTXc4yxCjNfxm4h6Xm2A==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-x64": "1.2.3" - } - }, - "node_modules/@img/sharp-linuxmusl-arm64": { - "version": "0.34.4", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.4.tgz", - "integrity": "sha512-8hDVvW9eu4yHWnjaOOR8kHVrew1iIX+MUgwxSuH2XyYeNRtLUe4VNioSqbNkB7ZYQJj9rUTT4PyRscyk2PXFKA==", - "cpu": [ - "arm64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-arm64": "1.2.3" - } - }, - "node_modules/@img/sharp-linuxmusl-x64": { - "version": "0.34.4", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.4.tgz", - "integrity": "sha512-lU0aA5L8QTlfKjpDCEFOZsTYGn3AEiO6db8W5aQDxj0nQkVrZWmN3ZP9sYKWJdtq3PWPhUNlqehWyXpYDcI9Sg==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-x64": "1.2.3" - } - }, - "node_modules/@img/sharp-wasm32": { - "version": "0.34.4", - "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.4.tgz", - "integrity": "sha512-33QL6ZO/qpRyG7woB/HUALz28WnTMI2W1jgX3Nu2bypqLIKx/QKMILLJzJjI+SIbvXdG9fUnmrxR7vbi1sTBeA==", - "cpu": [ - "wasm32" - ], - "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", - "optional": true, - "dependencies": { - "@emnapi/runtime": "^1.5.0" - }, - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-win32-arm64": { - "version": "0.34.4", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.4.tgz", - "integrity": "sha512-2Q250do/5WXTwxW3zjsEuMSv5sUU4Tq9VThWKlU2EYLm4MB7ZeMwF+SFJutldYODXF6jzc6YEOC+VfX0SZQPqA==", - "cpu": [ - "arm64" - ], - "license": "Apache-2.0 AND LGPL-3.0-or-later", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-win32-ia32": { - "version": "0.34.4", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.4.tgz", - "integrity": "sha512-3ZeLue5V82dT92CNL6rsal6I2weKw1cYu+rGKm8fOCCtJTR2gYeUfY3FqUnIJsMUPIH68oS5jmZ0NiJ508YpEw==", - "cpu": [ - "ia32" - ], - "license": "Apache-2.0 AND LGPL-3.0-or-later", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-win32-x64": { - "version": "0.34.4", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.4.tgz", - "integrity": "sha512-xIyj4wpYs8J18sVN3mSQjwrw7fKUqRw+Z5rnHNCy5fYTxigBz81u5mOMPmFumwjcn8+ld1ppptMBCLic1nz6ig==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0 AND LGPL-3.0-or-later", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@isaacs/fs-minipass": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", - "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", - "dev": true, - "license": "ISC", - "dependencies": { - "minipass": "^7.0.4" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@next/env": { - "version": "15.5.4", - "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.4.tgz", - "integrity": "sha512-27SQhYp5QryzIT5uO8hq99C69eLQ7qkzkDPsk3N+GuS2XgOgoYEeOav7Pf8Tn4drECOVDsDg8oj+/DVy8qQL2A==", - "license": "MIT" - }, - "node_modules/@next/swc-darwin-arm64": { - "version": "15.5.4", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.4.tgz", - "integrity": "sha512-nopqz+Ov6uvorej8ndRX6HlxCYWCO3AHLfKK2TYvxoSB2scETOcfm/HSS3piPqc3A+MUgyHoqE6je4wnkjfrOA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-darwin-x64": { - "version": "15.5.4", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.4.tgz", - "integrity": "sha512-QOTCFq8b09ghfjRJKfb68kU9k2K+2wsC4A67psOiMn849K9ZXgCSRQr0oVHfmKnoqCbEmQWG1f2h1T2vtJJ9mA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-arm64-gnu": { - "version": "15.5.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.4.tgz", - "integrity": "sha512-eRD5zkts6jS3VfE/J0Kt1VxdFqTnMc3QgO5lFE5GKN3KDI/uUpSyK3CjQHmfEkYR4wCOl0R0XrsjpxfWEA++XA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-arm64-musl": { - "version": "15.5.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.4.tgz", - "integrity": "sha512-TOK7iTxmXFc45UrtKqWdZ1shfxuL4tnVAOuuJK4S88rX3oyVV4ZkLjtMT85wQkfBrOOvU55aLty+MV8xmcJR8A==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-x64-gnu": { - "version": "15.5.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.4.tgz", - "integrity": "sha512-7HKolaj+481FSW/5lL0BcTkA4Ueam9SPYWyN/ib/WGAFZf0DGAN8frNpNZYFHtM4ZstrHZS3LY3vrwlIQfsiMA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-x64-musl": { - "version": "15.5.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.4.tgz", - "integrity": "sha512-nlQQ6nfgN0nCO/KuyEUwwOdwQIGjOs4WNMjEUtpIQJPR2NUfmGpW2wkJln1d4nJ7oUzd1g4GivH5GoEPBgfsdw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-arm64-msvc": { - "version": "15.5.4", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.4.tgz", - "integrity": "sha512-PcR2bN7FlM32XM6eumklmyWLLbu2vs+D7nJX8OAIoWy69Kef8mfiN4e8TUv2KohprwifdpFKPzIP1njuCjD0YA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-x64-msvc": { - "version": "15.5.4", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.4.tgz", - "integrity": "sha512-1ur2tSHZj8Px/KMAthmuI9FMp/YFusMMGoRNJaRZMOlSkgvLjzosSdQI0cJAKogdHl3qXUQKL9MGaYvKwA7DXg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@swc/helpers": { - "version": "0.5.15", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", - "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.8.0" - } - }, - "node_modules/@tailwindcss/node": { - "version": "4.1.14", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.14.tgz", - "integrity": "sha512-hpz+8vFk3Ic2xssIA3e01R6jkmsAhvkQdXlEbRTk6S10xDAtiQiM3FyvZVGsucefq764euO/b8WUW9ysLdThHw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/remapping": "^2.3.4", - "enhanced-resolve": "^5.18.3", - "jiti": "^2.6.0", - "lightningcss": "1.30.1", - "magic-string": "^0.30.19", - "source-map-js": "^1.2.1", - "tailwindcss": "4.1.14" - } - }, - "node_modules/@tailwindcss/oxide": { - "version": "4.1.14", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.14.tgz", - "integrity": "sha512-23yx+VUbBwCg2x5XWdB8+1lkPajzLmALEfMb51zZUBYaYVPDQvBSD/WYDqiVyBIo2BZFa3yw1Rpy3G2Jp+K0dw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "detect-libc": "^2.0.4", - "tar": "^7.5.1" - }, - "engines": { - "node": ">= 10" - }, - "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.1.14", - "@tailwindcss/oxide-darwin-arm64": "4.1.14", - "@tailwindcss/oxide-darwin-x64": "4.1.14", - "@tailwindcss/oxide-freebsd-x64": "4.1.14", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.14", - "@tailwindcss/oxide-linux-arm64-gnu": "4.1.14", - "@tailwindcss/oxide-linux-arm64-musl": "4.1.14", - "@tailwindcss/oxide-linux-x64-gnu": "4.1.14", - "@tailwindcss/oxide-linux-x64-musl": "4.1.14", - "@tailwindcss/oxide-wasm32-wasi": "4.1.14", - "@tailwindcss/oxide-win32-arm64-msvc": "4.1.14", - "@tailwindcss/oxide-win32-x64-msvc": "4.1.14" - } - }, - "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.1.14", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.14.tgz", - "integrity": "sha512-a94ifZrGwMvbdeAxWoSuGcIl6/DOP5cdxagid7xJv6bwFp3oebp7y2ImYsnZBMTwjn5Ev5xESvS3FFYUGgPODQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.1.14", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.14.tgz", - "integrity": "sha512-HkFP/CqfSh09xCnrPJA7jud7hij5ahKyWomrC3oiO2U9i0UjP17o9pJbxUN0IJ471GTQQmzwhp0DEcpbp4MZTA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.1.14", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.14.tgz", - "integrity": "sha512-eVNaWmCgdLf5iv6Qd3s7JI5SEFBFRtfm6W0mphJYXgvnDEAZ5sZzqmI06bK6xo0IErDHdTA5/t7d4eTfWbWOFw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.1.14", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.14.tgz", - "integrity": "sha512-QWLoRXNikEuqtNb0dhQN6wsSVVjX6dmUFzuuiL09ZeXju25dsei2uIPl71y2Ic6QbNBsB4scwBoFnlBfabHkEw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.1.14", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.14.tgz", - "integrity": "sha512-VB4gjQni9+F0VCASU+L8zSIyjrLLsy03sjcR3bM0V2g4SNamo0FakZFKyUQ96ZVwGK4CaJsc9zd/obQy74o0Fw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.1.14", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.14.tgz", - "integrity": "sha512-qaEy0dIZ6d9vyLnmeg24yzA8XuEAD9WjpM5nIM1sUgQ/Zv7cVkharPDQcmm/t/TvXoKo/0knI3me3AGfdx6w1w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.1.14", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.14.tgz", - "integrity": "sha512-ISZjT44s59O8xKsPEIesiIydMG/sCXoMBCqsphDm/WcbnuWLxxb+GcvSIIA5NjUw6F8Tex7s5/LM2yDy8RqYBQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.1.14", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.14.tgz", - "integrity": "sha512-02c6JhLPJj10L2caH4U0zF8Hji4dOeahmuMl23stk0MU1wfd1OraE7rOloidSF8W5JTHkFdVo/O7uRUJJnUAJg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.1.14", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.14.tgz", - "integrity": "sha512-TNGeLiN1XS66kQhxHG/7wMeQDOoL0S33x9BgmydbrWAb9Qw0KYdd8o1ifx4HOGDWhVmJ+Ul+JQ7lyknQFilO3Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.1.14", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.14.tgz", - "integrity": "sha512-uZYAsaW/jS/IYkd6EWPJKW/NlPNSkWkBlaeVBi/WsFQNP05/bzkebUL8FH1pdsqx4f2fH/bWFcUABOM9nfiJkQ==", - "bundleDependencies": [ - "@napi-rs/wasm-runtime", - "@emnapi/core", - "@emnapi/runtime", - "@tybys/wasm-util", - "@emnapi/wasi-threads", - "tslib" - ], - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.5.0", - "@emnapi/runtime": "^1.5.0", - "@emnapi/wasi-threads": "^1.1.0", - "@napi-rs/wasm-runtime": "^1.0.5", - "@tybys/wasm-util": "^0.10.1", - "tslib": "^2.4.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.1.14", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.14.tgz", - "integrity": "sha512-Az0RnnkcvRqsuoLH2Z4n3JfAef0wElgzHD5Aky/e+0tBUxUhIeIqFBTMNQvmMRSP15fWwmvjBxZ3Q8RhsDnxAA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.1.14", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.14.tgz", - "integrity": "sha512-ttblVGHgf68kEE4om1n/n44I0yGPkCPbLsqzjvybhpwa6mKKtgFfAzy6btc3HRmuW7nHe0OOrSeNP9sQmmH9XA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/postcss": { - "version": "4.1.14", - "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.14.tgz", - "integrity": "sha512-BdMjIxy7HUNThK87C7BC8I1rE8BVUsfNQSI5siQ4JK3iIa3w0XyVvVL9SXLWO//CtYTcp1v7zci0fYwJOjB+Zg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@alloc/quick-lru": "^5.2.0", - "@tailwindcss/node": "4.1.14", - "@tailwindcss/oxide": "4.1.14", - "postcss": "^8.4.41", - "tailwindcss": "4.1.14" - } - }, - "node_modules/@types/node": { - "version": "20.19.19", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.19.tgz", - "integrity": "sha512-pb1Uqj5WJP7wrcbLU7Ru4QtA0+3kAXrkutGiD26wUKzSMgNNaPARTUDQmElUXp64kh3cWdou3Q0C7qwwxqSFmg==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.21.0" - } - }, - "node_modules/@types/react": { - "version": "19.2.0", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.0.tgz", - "integrity": "sha512-1LOH8xovvsKsCBq1wnT4ntDUdCJKmnEakhsuoUSy6ExlHCkGP2hqnatagYTgFk6oeL0VU31u7SNjunPN+GchtA==", - "dev": true, - "license": "MIT", - "dependencies": { - "csstype": "^3.0.2" - } - }, - "node_modules/@types/react-dom": { - "version": "19.2.0", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.0.tgz", - "integrity": "sha512-brtBs0MnE9SMx7px208g39lRmC5uHZs96caOJfTjFcYSLHNamvaSMfJNagChVNkup2SdtOxKX1FDBkRSJe1ZAg==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "@types/react": "^19.2.0" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001747", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001747.tgz", - "integrity": "sha512-mzFa2DGIhuc5490Nd/G31xN1pnBnYMadtkyTjefPI7wzypqgCEpeWu9bJr0OnDsyKrW75zA9ZAt7pbQFmwLsQg==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/chownr": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", - "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/client-only": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", - "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", - "license": "MIT" - }, - "node_modules/csstype": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true, - "license": "MIT" - }, - "node_modules/detect-libc": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.1.tgz", - "integrity": "sha512-ecqj/sy1jcK1uWrwpR67UhYrIFQ+5WlGxth34WquCbamhFA6hkkwiu37o6J5xCHdo1oixJRfVRw+ywV+Hq/0Aw==", - "devOptional": true, - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, - "node_modules/enhanced-resolve": { - "version": "5.18.3", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", - "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/jiti": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", - "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", - "dev": true, - "license": "MIT", - "bin": { - "jiti": "lib/jiti-cli.mjs" - } - }, - "node_modules/lightningcss": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz", - "integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==", - "dev": true, - "license": "MPL-2.0", - "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-darwin-arm64": "1.30.1", - "lightningcss-darwin-x64": "1.30.1", - "lightningcss-freebsd-x64": "1.30.1", - "lightningcss-linux-arm-gnueabihf": "1.30.1", - "lightningcss-linux-arm64-gnu": "1.30.1", - "lightningcss-linux-arm64-musl": "1.30.1", - "lightningcss-linux-x64-gnu": "1.30.1", - "lightningcss-linux-x64-musl": "1.30.1", - "lightningcss-win32-arm64-msvc": "1.30.1", - "lightningcss-win32-x64-msvc": "1.30.1" - } - }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.1.tgz", - "integrity": "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.1.tgz", - "integrity": "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.1.tgz", - "integrity": "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.1.tgz", - "integrity": "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.1.tgz", - "integrity": "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.1.tgz", - "integrity": "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.1.tgz", - "integrity": "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.1.tgz", - "integrity": "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.1.tgz", - "integrity": "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.1.tgz", - "integrity": "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/magic-string": { - "version": "0.30.19", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.19.tgz", - "integrity": "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" - } - }, - "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/minizlib": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", - "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "minipass": "^7.1.2" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/next": { - "version": "15.5.4", - "resolved": "https://registry.npmjs.org/next/-/next-15.5.4.tgz", - "integrity": "sha512-xH4Yjhb82sFYQfY3vbkJfgSDgXvBB6a8xPs9i35k6oZJRoQRihZH+4s9Yo2qsWpzBmZ3lPXaJ2KPXLfkvW4LnA==", - "license": "MIT", - "dependencies": { - "@next/env": "15.5.4", - "@swc/helpers": "0.5.15", - "caniuse-lite": "^1.0.30001579", - "postcss": "8.4.31", - "styled-jsx": "5.1.6" - }, - "bin": { - "next": "dist/bin/next" - }, - "engines": { - "node": "^18.18.0 || ^19.8.0 || >= 20.0.0" - }, - "optionalDependencies": { - "@next/swc-darwin-arm64": "15.5.4", - "@next/swc-darwin-x64": "15.5.4", - "@next/swc-linux-arm64-gnu": "15.5.4", - "@next/swc-linux-arm64-musl": "15.5.4", - "@next/swc-linux-x64-gnu": "15.5.4", - "@next/swc-linux-x64-musl": "15.5.4", - "@next/swc-win32-arm64-msvc": "15.5.4", - "@next/swc-win32-x64-msvc": "15.5.4", - "sharp": "^0.34.3" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.1.0", - "@playwright/test": "^1.51.1", - "babel-plugin-react-compiler": "*", - "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", - "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", - "sass": "^1.3.0" - }, - "peerDependenciesMeta": { - "@opentelemetry/api": { - "optional": true - }, - "@playwright/test": { - "optional": true - }, - "babel-plugin-react-compiler": { - "optional": true - }, - "sass": { - "optional": true - } - } - }, - "node_modules/next/node_modules/postcss": { - "version": "8.4.31", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", - "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.6", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "license": "ISC" - }, - "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/react": { - "version": "19.1.0", - "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", - "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-dom": { - "version": "19.1.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", - "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", - "license": "MIT", - "dependencies": { - "scheduler": "^0.26.0" - }, - "peerDependencies": { - "react": "^19.1.0" - } - }, - "node_modules/scheduler": { - "version": "0.26.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", - "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", - "license": "MIT" - }, - "node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "license": "ISC", - "optional": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/sharp": { - "version": "0.34.4", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.4.tgz", - "integrity": "sha512-FUH39xp3SBPnxWvd5iib1X8XY7J0K0X7d93sie9CJg2PO8/7gmg89Nve6OjItK53/MlAushNNxteBYfM6DEuoA==", - "hasInstallScript": true, - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@img/colour": "^1.0.0", - "detect-libc": "^2.1.0", - "semver": "^7.7.2" - }, - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-darwin-arm64": "0.34.4", - "@img/sharp-darwin-x64": "0.34.4", - "@img/sharp-libvips-darwin-arm64": "1.2.3", - "@img/sharp-libvips-darwin-x64": "1.2.3", - "@img/sharp-libvips-linux-arm": "1.2.3", - "@img/sharp-libvips-linux-arm64": "1.2.3", - "@img/sharp-libvips-linux-ppc64": "1.2.3", - "@img/sharp-libvips-linux-s390x": "1.2.3", - "@img/sharp-libvips-linux-x64": "1.2.3", - "@img/sharp-libvips-linuxmusl-arm64": "1.2.3", - "@img/sharp-libvips-linuxmusl-x64": "1.2.3", - "@img/sharp-linux-arm": "0.34.4", - "@img/sharp-linux-arm64": "0.34.4", - "@img/sharp-linux-ppc64": "0.34.4", - "@img/sharp-linux-s390x": "0.34.4", - "@img/sharp-linux-x64": "0.34.4", - "@img/sharp-linuxmusl-arm64": "0.34.4", - "@img/sharp-linuxmusl-x64": "0.34.4", - "@img/sharp-wasm32": "0.34.4", - "@img/sharp-win32-arm64": "0.34.4", - "@img/sharp-win32-ia32": "0.34.4", - "@img/sharp-win32-x64": "0.34.4" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/styled-jsx": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", - "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", - "license": "MIT", - "dependencies": { - "client-only": "0.0.1" - }, - "engines": { - "node": ">= 12.0.0" - }, - "peerDependencies": { - "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" - }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, - "babel-plugin-macros": { - "optional": true - } - } - }, - "node_modules/tailwindcss": { - "version": "4.1.14", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.14.tgz", - "integrity": "sha512-b7pCxjGO98LnxVkKjaZSDeNuljC4ueKUddjENJOADtubtdo8llTaJy7HwBMeLNSSo2N5QIAgklslK1+Ir8r6CA==", - "dev": true, - "license": "MIT" - }, - "node_modules/tapable": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", - "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/tar": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.1.tgz", - "integrity": "sha512-nlGpxf+hv0v7GkWBK2V9spgactGOp0qvfWRxUMjqHyzrt3SgwE48DIv/FhqPHJYLHpgW1opq3nERbz5Anq7n1g==", - "dev": true, - "license": "ISC", - "dependencies": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.1.0", - "yallist": "^5.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/yallist": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", - "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - } - } -} diff --git a/docs/package.json b/docs/package.json index e0da70a..1e7d8b8 100644 --- a/docs/package.json +++ b/docs/package.json @@ -8,16 +8,17 @@ "start": "next start" }, "dependencies": { + "next": "15.5.4", + "next-themes": "^0.4.6", "react": "19.1.0", - "react-dom": "19.1.0", - "next": "15.5.4" + "react-dom": "19.1.0" }, "devDependencies": { - "typescript": "^5", + "@tailwindcss/postcss": "^4", "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", - "@tailwindcss/postcss": "^4", - "tailwindcss": "^4" + "tailwindcss": "^4", + "typescript": "^5" } } diff --git a/docs/src/app/clients/page.tsx b/docs/src/app/clients/page.tsx index e83e87c..c0602ba 100644 --- a/docs/src/app/clients/page.tsx +++ b/docs/src/app/clients/page.tsx @@ -5,7 +5,7 @@ export default function Clients() {

Supported Clients

-

+

starknode-kit supports multiple client implementations for both Ethereum and Starknet networks.

@@ -27,19 +27,19 @@ export default function Clients() {

-
-

Execution Clients

-

Handle transaction execution and state management

-
    +
    +

    Execution Clients

    +

    Handle transaction execution and state management

    +
    • • Geth (Go)
    • • Reth (Rust)
    -
    -

    Consensus Clients

    -

    Handle proof-of-stake consensus mechanism

    -
      +
      +

      Consensus Clients

      +

      Handle proof-of-stake consensus mechanism

      +
      • • Lighthouse (Rust)
      • • Prysm (Go)
      @@ -53,10 +53,10 @@ export default function Clients() {

      -
      -

      Starknet Clients

      -

      Full node implementations for Starknet

      -
        +
        +

        Starknet Clients

        +

        Full node implementations for Starknet

        +
        • • Juno (Go) - Full node client
        • • Starknet Validator - Validator client for staking
        @@ -67,38 +67,42 @@ export default function Clients() {

        Popular client combinations for Ethereum nodes:

        -
        - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        ExecutionConsensusCharacteristics
        GethLighthouseMost popular, well-tested
        RethLighthouseHigh performance, modern
        GethPrysmStable, feature-rich
        RethPrysmPerformance-focused
        +
        +
        +
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        ExecutionConsensusCharacteristics
        GethLighthouseMost popular, well-tested
        RethLighthouseHigh performance, modern
        GethPrysmStable, feature-rich
        RethPrysmPerformance-focused
        +
        +

        Choosing Clients

        @@ -158,54 +162,58 @@ export default function Clients() {

        Resource Requirements by Client

        -
        - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        ClientRAMDiskCPU
        Geth16+ GB~1.2 TB4+ cores
        Reth16+ GB~900 GB4+ cores
        Lighthouse8+ GB~200 GB2+ cores
        Prysm8+ GB~250 GB2+ cores
        Juno8+ GB~300 GB2+ cores
        +
        +
        +
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        ClientRAMDiskCPU
        Geth16+ GB~1.2 TB4+ cores
        Reth16+ GB~900 GB4+ cores
        Lighthouse8+ GB~200 GB2+ cores
        Prysm8+ GB~250 GB2+ cores
        Juno8+ GB~300 GB2+ cores
        +
        +
        -
        -

        💡 Recommendation

        -

        +

        +

        💡 Recommendation

        +

        For most users, we recommend Reth + Lighthouse for Ethereum (best performance) and Juno for Starknet.

        @@ -231,21 +239,21 @@ export default function Clients() {
      • Start nodes: starknode-kit start
      • -
        -

        ⚠️ Note

        -

        +

        +

        ⚠️ Note

        +

        Switching clients may require re-syncing from scratch, which can take several days. Plan accordingly and ensure you have sufficient disk space.

        -
        -

        📖 Next Steps

        -

        +

        +

        📖 Next Steps

        +

        Ready to dive deeper? Check out our validator guide:

        - Validator Guide + Validator Guide
        diff --git a/docs/src/app/commands/page.tsx b/docs/src/app/commands/page.tsx index 3fd0b4f..7fcdabb 100644 --- a/docs/src/app/commands/page.tsx +++ b/docs/src/app/commands/page.tsx @@ -6,72 +6,76 @@ export default function Commands() {

        Commands Reference

        -

        +

        Complete reference for all starknode-kit commands. Each command helps you manage different aspects of your Ethereum and Starknet nodes.

        Command Overview

        -
        - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        CommandDescription
        addAdd an Ethereum or Starknet client to the config
        completionGenerate the autocompletion script for the specified shell
        configCreate, show, and update your Starknet node configuration
        monitorLaunch real-time monitoring dashboard
        removeRemove a specified resource
        runRun a specific local infrastructure service
        startRun the configured Ethereum clients
        statusDisplay status of running clients
        stopStop the configured Ethereum clients
        updateCheck for and install client updates
        validatorManage the Starknet validator client
        versionShow version of starknode-kit or a specific client
        +
        +
        +
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        CommandDescription
        addAdd an Ethereum or Starknet client to the config
        completionGenerate the autocompletion script for the specified shell
        configCreate, show, and update your Starknet node configuration
        monitorLaunch real-time monitoring dashboard
        removeRemove a specified resource
        runRun a specific local infrastructure service
        startRun the configured Ethereum clients
        statusDisplay status of running clients
        stopStop the configured Ethereum clients
        updateCheck for and install client updates
        validatorManage the Starknet validator client
        versionShow version of starknode-kit or a specific client
        +
        +

        Quick Examples

        @@ -142,13 +146,13 @@ starknode-kit completion zsh > "\${fpath[1]}/_starknode-kit" starknode-kit completion fish > ~/.config/fish/completions/starknode-kit.fish`} /> -
        -

        📖 Next Steps

        -

        +

        +

        📖 Next Steps

        +

        Ready to dive deeper? Check out our comprehensive guides:

        - Supported Clients + Supported Clients
        diff --git a/docs/src/app/configuration/page.tsx b/docs/src/app/configuration/page.tsx index 3cee155..c46d147 100644 --- a/docs/src/app/configuration/page.tsx +++ b/docs/src/app/configuration/page.tsx @@ -6,7 +6,7 @@ export default function Configuration() {

        Configuration

        -

        +

        Learn how to configure starknode-kit for your Ethereum and Starknet nodes.

        @@ -171,9 +171,9 @@ starknode-kit config set juno eth_node=http://localhost:8545`}
      -
      -

      ⚠️ Important

      -

      +

      +

      ⚠️ Important

      +

      Changing the network will affect all clients. Make sure to stop your nodes before changing networks.

      @@ -187,43 +187,47 @@ starknode-kit config set juno eth_node=http://localhost:8545`}

      Default ports for each client:

      -
      - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      ClientPortsPurpose
      Geth8545, 8546, 30303HTTP RPC, WS RPC, P2P
      Reth8545, 8546, 30303HTTP RPC, WS RPC, P2P
      Lighthouse5052, 9000HTTP API, P2P
      Prysm4000, 13000HTTP API, P2P
      Juno6060RPC
      +
      +
      +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      ClientPortsPurpose
      Geth8545, 8546, 30303HTTP RPC, WS RPC, P2P
      Reth8545, 8546, 30303HTTP RPC, WS RPC, P2P
      Lighthouse5052, 9000HTTP API, P2P
      Prysm4000, 13000HTTP API, P2P
      Juno6060RPC
      +
      +

      @@ -316,13 +320,13 @@ starknode-kit config set juno eth_node=http://localhost:8545`}
    • Stop conflicting services
    -
    -

    📖 Next Steps

    -

    +

    +

    📖 Next Steps

    +

    Ready to dive deeper? Check out our comprehensive guides:

    - Commands Reference + Commands Reference
    diff --git a/docs/src/app/contributing/page.tsx b/docs/src/app/contributing/page.tsx index d003597..afd9a7e 100644 --- a/docs/src/app/contributing/page.tsx +++ b/docs/src/app/contributing/page.tsx @@ -6,7 +6,7 @@ export default function Contributing() {

    Contributing

    -

    +

    We welcome contributions to starknode-kit! This guide will help you get started with contributing to the project.

    @@ -251,9 +251,9 @@ Error log output here
  • Conventional Commits
-
-

🎉 Thank You!

-

+

+

🎉 Thank You!

+

Thank you for considering contributing to starknode-kit! Your contributions help make this tool better for everyone.

diff --git a/docs/src/app/getting-started/page.tsx b/docs/src/app/getting-started/page.tsx index f7e0251..871d648 100644 --- a/docs/src/app/getting-started/page.tsx +++ b/docs/src/app/getting-started/page.tsx @@ -6,7 +6,7 @@ export default function GettingStarted() {

Getting Started

-

+

Welcome to starknode-kit! This guide will help you get up and running with your Ethereum and Starknet nodes in just a few minutes.

@@ -33,9 +33,9 @@ export default function GettingStarted() { -
-

📝 Note

-

+

+

📝 Note

+

For detailed hardware requirements, check out our{" "} Requirements page @@ -127,9 +127,9 @@ export default function GettingStarted() { -

-

⚠️ Important

-

+

+

⚠️ Important

+

The start command only launches Ethereum clients (execution + consensus). It does not start Starknet clients.

@@ -164,42 +164,42 @@ starknode-kit run lighthouse`}

The monitoring dashboard provides real-time insights:

-
+
🔄
-

Node Sync Status

-

Real-time synchronization progress and health

+

Node Sync Status

+

Real-time synchronization progress and health

-
+
📊
-

Current Block Height

-

Latest block number and sync progress

+

Current Block Height

+

Latest block number and sync progress

-
+
🌐
-

Network Statistics

-

Peer connections and network performance

+

Network Statistics

+

Peer connections and network performance

-
+
💻
-

System Resources

-

CPU, RAM, and disk usage metrics

+

System Resources

+

CPU, RAM, and disk usage metrics

@@ -213,13 +213,13 @@ starknode-kit run lighthouse`} -
-

📖 Next Steps

-

+

+

📖 Next Steps

+

Ready to dive deeper? Check out our installation guide:

- Installation Guide + Installation Guide
diff --git a/docs/src/app/globals.css b/docs/src/app/globals.css index b60e14f..02dbce5 100644 --- a/docs/src/app/globals.css +++ b/docs/src/app/globals.css @@ -34,19 +34,57 @@ body { margin-bottom: 1.5rem; } +.prose h1 { + font-size: 2rem; + line-height: 1.2; +} + +@media (min-width: 640px) { + .prose h1 { + font-size: 2.5rem; + } +} + .prose h2 { - margin-top: 3rem; + margin-top: 2rem; margin-bottom: 1.5rem; + font-size: 1.5rem; +} + +@media (min-width: 640px) { + .prose h2 { + margin-top: 3rem; + font-size: 1.875rem; + } } .prose h3 { - margin-bottom: 1.25rem; + margin-bottom: 1rem; + font-size: 1.25rem; +} + +@media (min-width: 640px) { + .prose h3 { + margin-bottom: 1.25rem; + font-size: 1.5rem; + } +} + +.prose h4 { + font-size: 1.125rem; +} + +@media (min-width: 640px) { + .prose h4 { + font-size: 1.25rem; + } } .prose a { color: #2563eb; text-decoration: none; transition: color 0.2s; + word-break: break-word; } .prose a:hover { @@ -55,11 +93,12 @@ body { } .prose code { - background: #f3f4f6; + background: #040811; padding: 0.2em 0.4em; border-radius: 0.25rem; font-size: 0.875em; font-family: 'Courier New', monospace; + word-break: break-word; } .prose pre { @@ -73,11 +112,20 @@ body { background: transparent; padding: 0; color: #e2e8f0; + word-break: normal; } .prose table { width: 100%; border-collapse: collapse; + display: block; + overflow-x: auto; +} + +@media (min-width: 768px) { + .prose table { + display: table; + } } .prose th { @@ -91,3 +139,26 @@ body { font-style: italic; color: #6b7280; } + +.prose ul, .prose ol { + padding-left: 1.5rem; +} + +@media (min-width: 640px) { + .prose ul, .prose ol { + padding-left: 2rem; + } +} + +/* Responsive table wrapper */ +.table-wrapper { + overflow-x: auto; + -webkit-overflow-scrolling: touch; + margin: 1.5rem 0; +} + +/* Ensure images are responsive */ +.prose img { + max-width: 100%; + height: auto; +} diff --git a/docs/src/app/installation/page.tsx b/docs/src/app/installation/page.tsx index 9dcd313..342df95 100644 --- a/docs/src/app/installation/page.tsx +++ b/docs/src/app/installation/page.tsx @@ -6,7 +6,7 @@ export default function Installation() {

Installation

-

+

There are multiple ways to install starknode-kit. Choose the method that best suits your needs.

@@ -132,9 +132,9 @@ Use "starknode [command] --help" for more information about a command.`} rm -rf ~/.config/starknode-kit`} /> -
-

⚠️ Note

-

+

+

⚠️ Note

+

This will not remove any client data (e.g., blockchain data). The data is stored in the locations specified in your{" "} ~/.starknode-kit/starknode.yml file. @@ -164,13 +164,13 @@ rm -rf ~/.config/starknode-kit`} have sudo access or contact your system administrator.

-
-

📖 Next Steps

-

+

+

📖 Next Steps

+

Ready to dive deeper? Check out our configuration guide:

- Configuration Guide + Configuration Guide
diff --git a/docs/src/app/layout.tsx b/docs/src/app/layout.tsx index 66099db..d6c8eea 100644 --- a/docs/src/app/layout.tsx +++ b/docs/src/app/layout.tsx @@ -1,8 +1,10 @@ -import type { Metadata } from "next"; +"use client"; + import { Inter, Source_Code_Pro } from "next/font/google"; import "./globals.css"; import Sidebar from "@/components/Sidebar"; import Header from "@/components/Header"; +import { useState, useCallback } from "react"; const inter = Inter({ subsets: ["latin"], @@ -11,26 +13,39 @@ const inter = Inter({ const source_code_pro = Source_Code_Pro({ subsets: ["latin"], - variable: "--font-source-code" -}) - -export const metadata: Metadata = { - title: "starknode-kit Documentation", - description: "Complete documentation for starknode-kit - A CLI tool for setting up and managing Ethereum and Starknet nodes", -}; + variable: "--font-source-code", +}); export default function RootLayout({ children, }: Readonly<{ children: React.ReactNode; }>) { + const [isSidebarOpen, setIsSidebarOpen] = useState(false); + + const toggleSidebar = useCallback(() => { + setIsSidebarOpen((prev) => !prev); + }, []); + + const closeSidebar = useCallback(() => { + setIsSidebarOpen(false); + }, []); + return ( - - - -
-
-
+ + + starknode-kit Documentation + + + + + +
+
+
{children}
diff --git a/docs/src/app/page.tsx b/docs/src/app/page.tsx index ea7a66d..429834a 100644 --- a/docs/src/app/page.tsx +++ b/docs/src/app/page.tsx @@ -6,7 +6,7 @@ export default function Home() {

Welcome to starknode-kit

-

+

A powerful command-line tool to help developers and node operators easily set up, manage, and maintain Ethereum and Starknet nodes.

@@ -14,12 +14,12 @@ export default function Home() {
-

+

🚀 Getting Started

-

+

Learn how to install and configure starknode-kit for your node setup.

@@ -27,36 +27,36 @@ export default function Home() { -

+

📘 Commands

-

+

Explore all available commands and their usage.

-

+

⚙️ Configuration

-

+

Configure your Ethereum and Starknet clients.

-

+

🔐 Validator Setup

-

+

Set up and manage your Starknet validator node.

@@ -107,36 +107,36 @@ export default function Home() {

Supported Clients

-
-

Execution Layer

-
    +
    +

    Execution Layer

    +
    • • Geth
    • • Reth
    -
    -

    Consensus Layer

    -
      +
      +

      Consensus Layer

      +
      • • Lighthouse
      • • Prysm
      -
      -

      Starknet

      -
        +
        +

        Starknet

        +
        • • Juno
        • • Starknet Validator
      -
      -

      📖 Next Steps

      -

      +

      +

      📖 Next Steps

      +

      Ready to dive deeper? Check out our comprehensive guides:

      - Installation Guide + Installation Guide
      diff --git a/docs/src/app/requirements/page.tsx b/docs/src/app/requirements/page.tsx index 8ef1256..9219f30 100644 --- a/docs/src/app/requirements/page.tsx +++ b/docs/src/app/requirements/page.tsx @@ -6,59 +6,63 @@ export default function Requirements() {

      Requirements

      -

      +

      Hardware and software requirements for running Ethereum and Starknet nodes with starknode-kit.

      Hardware Requirements

      -
      -

      📚 Reference

      -

      +

      +

      📚 Reference

      +

      For a detailed breakdown of node hardware requirements, see the Rocket Pool Hardware Guide.

      Minimum Requirements

      -
      - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      ComponentRequirementNotes
      CPU4+ coresIntel i3/i5 or AMD equivalent. Avoid Celeron.
      RAM32 GBMinimum 16GB, 32GB recommended for comfort
      Storage2+ TB NVMe SSDMust have DRAM cache, no QLC NAND
      Network100+ MbpsStable connection, unlimited data preferred
      Power24/7 uptimeUPS recommended for validators
      +
      +
      +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      ComponentRequirementNotes
      CPU4+ coresIntel i3/i5 or AMD equivalent. Avoid Celeron.
      RAM32 GBMinimum 16GB, 32GB recommended for comfort
      Storage2+ TB NVMe SSDMust have DRAM cache, no QLC NAND
      Network100+ MbpsStable connection, unlimited data preferred
      Power24/7 uptimeUPS recommended for validators
      +
      +

      Recommended Specifications

      @@ -77,43 +81,47 @@ export default function Requirements() {

      Storage Size

      -
      - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      ClientCurrent SizeGrowth Rate
      Ethereum (Geth)~1.2 TB~150 GB/year
      Ethereum (Reth)~900 GB~120 GB/year
      Lighthouse~200 GB~50 GB/year
      Prysm~250 GB~60 GB/year
      Juno (Starknet)~300 GB~100 GB/year
      +
      +
      +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      ClientCurrent SizeGrowth Rate
      Ethereum (Geth)~1.2 TB~150 GB/year
      Ethereum (Reth)~900 GB~120 GB/year
      Lighthouse~200 GB~50 GB/year
      Prysm~250 GB~60 GB/year
      Juno (Starknet)~300 GB~100 GB/year
      +
      +

      SSD Requirements

      @@ -127,9 +135,9 @@ export default function Requirements() {
    • NVMe interface - SATA SSDs are too slow
    -
    -

    ⚠️ Warning

    -

    +

    +

    ⚠️ Warning

    +

    Using a QLC SSD or SSD without DRAM cache will result in poor performance and potential node failures. See the tested SSD list for recommendations.

    @@ -195,38 +203,42 @@ make --version`} />

    Ensure these ports are accessible:

    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    PortProtocolPurpose
    30303TCP/UDPEthereum execution P2P
    9000TCP/UDPLighthouse consensus P2P
    13000TCPPrysm consensus P2P
    6060TCPJuno RPC (localhost only)
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    PortProtocolPurpose
    30303TCP/UDPEthereum execution P2P
    9000TCP/UDPLighthouse consensus P2P
    13000TCPPrysm consensus P2P
    6060TCPJuno RPC (localhost only)
    +
    +

    For Validator Nodes

    @@ -278,38 +290,42 @@ make --version`} />

    If running in the cloud, recommended specifications:

    -
    - - - - - - - - - - - - - - - - - - - - - - - - - -
    ProviderInstance TypeEst. Cost/Month
    AWSm5.2xlarge + 4TB gp3~$500-700
    Google Cloudn2-standard-8 + 4TB SSD~$600-800
    AzureStandard_D8s_v3 + 4TB Premium SSD~$550-750
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    ProviderInstance TypeEst. Cost/Month
    AWSm5.2xlarge + 4TB gp3~$500-700
    Google Cloudn2-standard-8 + 4TB SSD~$600-800
    AzureStandard_D8s_v3 + 4TB Premium SSD~$550-750
    +
    +
    -
    -

    💡 Cost Consideration

    -

    +

    +

    💡 Cost Consideration

    +

    Running on dedicated hardware is often more cost-effective long-term than cloud hosting, especially for validators.

    diff --git a/docs/src/app/validator/page.tsx b/docs/src/app/validator/page.tsx index 156aa27..2e41527 100644 --- a/docs/src/app/validator/page.tsx +++ b/docs/src/app/validator/page.tsx @@ -5,13 +5,13 @@ export default function Validator() {

    Validator Setup

    -

    +

    Set up and manage your Starknet validator node using starknode-kit.

    -
    -

    ⚠️ Important

    -

    +

    +

    ⚠️ Important

    +

    Running a validator requires significant responsibility. Make sure you understand the requirements and risks before proceeding.

    @@ -113,7 +113,7 @@ export STARKNET_SALT="0x..."`} />

    Start your validator client:

    - +

    Monitoring Your Validator

    @@ -209,9 +209,9 @@ starknode-kit monitor`} />
  • Penalties - For downtime or malicious behavior
-
-

💡 Tip

-

+

+

💡 Tip

+

Start on the testnet (Sepolia) to familiarize yourself with validator operations before running on mainnet.

@@ -224,13 +224,13 @@ starknode-kit monitor`} />
  • GitHub Repository
  • -
    -

    📖 Next Steps

    -

    +

    +

    📖 Next Steps

    +

    Ready to dive deeper? Check out our comprehensive guides:

    - System Requirements + System Requirements
    diff --git a/docs/src/components/CodeBlock.tsx b/docs/src/components/CodeBlock.tsx index 8f6f33e..f12527e 100644 --- a/docs/src/components/CodeBlock.tsx +++ b/docs/src/components/CodeBlock.tsx @@ -17,17 +17,17 @@ export default function CodeBlock({ code, language = 'bash' }: CodeBlockProps) { }; return ( -
    +
    -
    -        

    {code}

    +
    +        

    {code}

    ); diff --git a/docs/src/components/Header.tsx b/docs/src/components/Header.tsx index 7be998d..973e120 100644 --- a/docs/src/components/Header.tsx +++ b/docs/src/components/Header.tsx @@ -2,16 +2,49 @@ import Link from 'next/link'; -export default function Header() { +interface HeaderProps { + onMenuClick: () => void; +} + +export default function Header({ onMenuClick }: HeaderProps) { return ( -
    -
    -
    +
    +
    + {/* Hamburger button for mobile */} + + + {/* Mobile title - shown only on mobile */} +
    + + starknode-kit + +
    + + {/* Social links */} +
    @@ -26,7 +59,7 @@ export default function Header() { href="https://t.me/+SCPbza9fk8dkYWI0" target="_blank" rel="noopener noreferrer" - className="text-gray-600 hover:text-gray-900 transition-colors" + className="text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white transition-colors" aria-label="Telegram" > diff --git a/docs/src/components/Sidebar.tsx b/docs/src/components/Sidebar.tsx index 0c7c350..f6d3f17 100644 --- a/docs/src/components/Sidebar.tsx +++ b/docs/src/components/Sidebar.tsx @@ -2,6 +2,7 @@ import Link from 'next/link'; import { usePathname } from 'next/navigation'; +import { useState, useEffect } from 'react'; interface NavItem { title: string; @@ -20,7 +21,12 @@ const navigation: NavItem[] = [ { title: 'Contributing', href: '/contributing' }, ]; -export default function Sidebar() { +interface SidebarProps { + isOpen: boolean; + onClose: () => void; +} + +export default function Sidebar({ isOpen, onClose }: SidebarProps) { const pathname = usePathname(); const isActive = (href: string) => { @@ -28,33 +34,87 @@ export default function Sidebar() { return pathname.startsWith(href); }; + // Close sidebar when route changes on mobile + useEffect(() => { + onClose(); + }, [pathname, onClose]); + + // Prevent body scroll when mobile menu is open + useEffect(() => { + if (isOpen) { + document.body.style.overflow = 'hidden'; + } else { + document.body.style.overflow = 'unset'; + } + return () => { + document.body.style.overflow = 'unset'; + }; + }, [isOpen]); + return ( -
    - -

    - starknode-kit -

    -

    Documentation

    - -
    - - - + <> + {/* Backdrop for mobile */} + {isOpen && ( +
    + )} + + {/* Sidebar */} + + ); } diff --git a/docs/tailwind.config.ts b/docs/tailwind.config.ts index f67c807..e85b68c 100644 --- a/docs/tailwind.config.ts +++ b/docs/tailwind.config.ts @@ -1,6 +1,7 @@ import type { Config } from "tailwindcss"; const config: Config = { + darkMode: "class", content: [ "./src/pages/**/*.{js,ts,jsx,tsx,mdx}", "./src/components/**/*.{js,ts,jsx,tsx,mdx}", diff --git a/install.sh b/install.sh index c1c008c..352d6ea 100755 --- a/install.sh +++ b/install.sh @@ -74,9 +74,12 @@ show_node_selection() { echo -e "${YELLOW}3)${NC} Starknet Validator Node" echo -e " ${CYAN}└── Participate in Starknet consensus and earn rewards${NC}" echo - echo -e "${RED}4)${NC} Exit" + echo -e "${YELLOW}4)${NC} Update Starknode-kit" + echo -e " ${CYAN}└── Update starknode-kit${NC}" echo - echo -n -e "${GREEN}Enter your choice [1-4]: ${NC}" + echo -e "${RED}5)${NC} Exit" + echo + echo -n -e "${GREEN}Enter your choice [1-5]: ${NC}" read -r choice @@ -97,12 +100,17 @@ show_node_selection() { break ;; 4) + SELECTED_NODE_TYPE="update" + print_status "Update client" + break + ;; + 5) clear print_status "Installation cancelled by user" exit 0 ;; *) - print_error "Invalid choice. Please select 1-4." + print_error "Invalid choice. Please select 1-5." sleep 2 ;; esac @@ -549,6 +557,175 @@ show_completion() { echo } +# Function to update the package +perform_update() { + print_status "Checking for updates..." + + # Check for current installation + if ! command_exists "$BINARY_NAME"; then + print_error "$BINARY_NAME is not installed. Please run the full installation first." + exit 1 + fi + + # Get current version + CURRENT_VERSION_FULL=$($BINARY_NAME version 2>/dev/null) + CURRENT_VERSION=$(echo "$CURRENT_VERSION_FULL" | grep -o -E 'v?[0-9]+\.[0-9]+\.[0-9]+' | head -n1) + if [ -z "$CURRENT_VERSION" ]; then + print_warning "Could not determine current version from output: '$CURRENT_VERSION_FULL'. Proceeding with update." + else + print_status "Current version: $CURRENT_VERSION" + fi + + # Get latest version from GitHub using git tags + if ! command_exists git; then + print_warning "git is not installed, cannot check for latest version. Proceeding with update." + else + LATEST_VERSION=$(git ls-remote --tags "https://github.com/$GITHUB_REPO.git" | awk '{print $2}' | grep 'refs/tags/v' | sed 's|refs/tags/||' | sort -V | tail -n 1) + if [ -z "$LATEST_VERSION" ]; then + print_warning "Could not determine latest version from git tags. Proceeding with update." + else + print_status "Latest version available: $LATEST_VERSION" + fi + fi + + # Compare versions + if [ -n "$CURRENT_VERSION" ] && [ -n "$LATEST_VERSION" ]; then + # Strip 'v' prefix for comparison + if [ "${CURRENT_VERSION#v}" == "${LATEST_VERSION#v}" ]; then + print_status "You already have the latest version ($CURRENT_VERSION)." + exit 0 + fi + print_status "New version available. Proceeding with update..." + fi + + print_status "Starting update process..." + + # Read node type from config + CONFIG_FILE="$HOME/.config/starknode-kit/config/starknode.yaml" + if [ -f "$CONFIG_FILE" ]; then + # shellcheck source=/dev/null + . "$CONFIG_FILE" + print_status "Existing configuration found. Node type: $node_type" + SELECTED_NODE_TYPE=$node_type + else + print_error "No existing configuration found at $CONFIG_FILE." + print_error "Cannot determine node type for update. Please run the full installation first." + exit 1 + fi + + # Create temporary directory + TEMP_DIR=$(mktemp -d) + print_status "Created temporary directory: $TEMP_DIR" + + # Cleanup function + cleanup() { + print_status "Cleaning up temporary files..." + rm -rf "$TEMP_DIR" + } + + # Set trap to cleanup on exit + trap cleanup EXIT + + # Clone the repository + print_status "Cloning repository: https://github.com/$GITHUB_REPO" + if ! git clone "https://github.com/$GITHUB_REPO.git" "$TEMP_DIR/$BINARY_NAME"; then + print_error "Failed to clone repository" + exit 1 + fi + + # Change to project directory + cd "$TEMP_DIR/$BINARY_NAME" || { + print_error "Failed to change to project directory" + exit 1 + } + + # Check if go.mod exists (Go modules) + if [ -f "go.mod" ]; then + print_status "Go modules detected, downloading dependencies..." + go mod download + else + print_status "No go.mod found, assuming GOPATH mode..." + fi + + # Get version from git tag + VERSION=$(git describe --tags --abbrev=0 2>/dev/null || echo "dev") + print_status "Building version: $VERSION" + + # Construct linker flags + LDFLAGS="-X github.com/thebuidl-grid/starknode-kit/pkg/versions.StarkNodeVersion=$VERSION" + + # Build the application with node type flag + print_status "Building the application for $SELECTED_NODE_TYPE node..." + BUILD_FLAGS="" + case $SELECTED_NODE_TYPE in + "ethereum") BUILD_FLAGS="-tags ethereum" ;; + "starknet") BUILD_FLAGS="-tags starknet" ;; + "validator") BUILD_FLAGS="-tags validator" ;; + esac + + if ! go build $BUILD_FLAGS -ldflags="$LDFLAGS" -o "$BINARY_NAME" .; then + print_error "Failed to build the application" + exit 1 + fi + + # Check if binary was created + if [ ! -f "$BINARY_NAME" ]; then + print_error "Binary was not created successfully" + exit 1 + fi + + # Create install directory if it doesn't exist + if [ ! -d "$INSTALL_DIR" ]; then + print_warning "Install directory $INSTALL_DIR does not exist, creating it..." + sudo mkdir -p "$INSTALL_DIR" + fi + + # Install the binary + print_status "Installing $BINARY_NAME to $INSTALL_DIR..." + if ! sudo cp "$BINARY_NAME" "$INSTALL_DIR/"; then + print_error "Failed to install binary to $INSTALL_DIR" + exit 1 + fi + + # Make it executable + sudo chmod +x "$INSTALL_DIR/$BINARY_NAME" + + # Verify installation + if command_exists "$BINARY_NAME"; then + echo + print_status "✓ Update successful!" + print_status "You can now use '$BINARY_NAME' from anywhere in your terminal" + + # Show version if available + if "$BINARY_NAME" --version >/dev/null 2>&1; then + VERSION=$("$BINARY_NAME" --version) + print_status "Installed version: $VERSION" + elif "$BINARY_NAME" -version >/dev/null 2>&1; then + VERSION=$("$BINARY_NAME" -version) + print_status "Installed version: $VERSION" + fi + else + print_warning "Update completed but '$BINARY_NAME' is not in PATH" + print_warning "You may need to add $INSTALL_DIR to your PATH or restart your terminal" + fi +} + +# Function to show update completion message +show_update_completion() { + echo + echo -e "${GREEN}════════════════════════════════════════════════════════════════════${NC}" + echo -e "${GREEN} Update Complete! ${NC}" + echo -e "${GREEN}════════════════════════════════════════════════════════════════════${NC}" + echo + echo -e "${CYAN}Next Steps:${NC}" + echo "1. Run '$BINARY_NAME --help' to see available commands" + echo "2. To start your node, run: '$BINARY_NAME start'" + echo + echo -e "${YELLOW}For support and documentation, visit:${NC}" + echo "https://github.com/$GITHUB_REPO" + echo +} + # Main execution main() { USE_LOCAL=false @@ -563,6 +740,12 @@ main() { check_prerequisites show_node_selection + if [ "$SELECTED_NODE_TYPE" == "update" ]; then + perform_update + show_update_completion + exit 0 + fi + handle_ethereum_selection case $SELECTED_NODE_TYPE in "starknet") diff --git a/pkg/clients/client_test.go b/pkg/clients/client_test.go new file mode 100644 index 0000000..bf9b63d --- /dev/null +++ b/pkg/clients/client_test.go @@ -0,0 +1,248 @@ +package clients + +import ( + "path/filepath" + "testing" + + "github.com/thebuidl-grid/starknode-kit/pkg/constants" + "github.com/thebuidl-grid/starknode-kit/pkg/types" +) + +func TestGethClient(t *testing.T) { + config := &gethConfig{ + port: 30303, + executionType: "full", + network: "mainnet", + } + + args := config.buildArgs() + + expectedArgs := []string{ + "--mainnet", + "--port=30303", + "--discovery.port=30303", + "--http", + "--http.api=eth,net,engine,admin", + "--http.corsdomain=*", + "--http.addr=0.0.0.0", + "--http.port=8545", + "--authrpc.jwtsecret=" + constants.JWTPath, + "--authrpc.addr=0.0.0.0", + "--authrpc.port=8551", + "--authrpc.vhosts=*", + "--metrics", + "--metrics.addr=0.0.0.0", + "--metrics.port=7878", + "--syncmode=snap", + "--datadir=" + filepath.Join(constants.InstallClientsDir, "geth", "database"), + } + + if len(args) != len(expectedArgs) { + t.Errorf("Expected %d arguments, got %d", len(expectedArgs), len(args)) + } + + for i, expected := range expectedArgs { + if args[i] != expected { + t.Errorf("Expected argument %d to be '%s', got '%s'", i, expected, args[i]) + } + } + + +} + +func TestRethClient(t *testing.T) { + config := &rethConfig{ + port: 30303, + executionType: "full", + network: "mainnet", + } + + args := config.buildArgs() + + expectedArgs := []string{ + "node", + "--chain", "mainnet", + "--http", + "--http.addr", "0.0.0.0", + "--http.port", "8545", + "--http.api", "eth,net,admin", + "--http.corsdomain", "*", + "--authrpc.addr", "0.0.0.0", + "--authrpc.port", "8551", + "--authrpc.jwtsecret", constants.JWTPath, + "--port", "30303", + "--metrics", "0.0.0.0:7878", + "--datadir", filepath.Join(constants.InstallClientsDir, "reth", "database"), + } + + if len(args) != len(expectedArgs) { + t.Errorf("Expected %d arguments, got %d", len(expectedArgs), len(args)) + } + + for i, expected := range expectedArgs { + if args[i] != expected { + t.Errorf("Expected argument %d to be '%s', got '%s'", i, expected, args[i]) + } + } + + +} + +func TestLighthouseClient(t *testing.T) { + config := &lightHouseConfig{ + port: []int{9000, 9001}, + consensusCheckpoint: "https://checkpoint.sync", + network: "mainnet", + } + + args := config.buildArgs() + + expectedArgs := []string{ + "bn", + "--network", + "mainnet", + "--port=9000", + "--quic-port=9001", + "--execution-endpoint", + "http://localhost:8551", + "--checkpoint-sync-url", + "https://checkpoint.sync", + "--checkpoint-sync-url-timeout", + "1200", + "--disable-deposit-contract-sync", + "--execution-jwt", + constants.JWTPath, + "--metrics", + "--metrics-address", + "127.0.0.1", + "--metrics-port", + "5054", + "--http", + "--disable-upnp", + "--datadir=" + filepath.Join(constants.InstallClientsDir, "lighthouse", "database"), + } + + if len(args) != len(expectedArgs) { + t.Errorf("Expected %d arguments, got %d", len(expectedArgs), len(args)) + } + + for i, expected := range expectedArgs { + if args[i] != expected { + t.Errorf("Expected argument %d to be '%s', got '%s'", i, expected, args[i]) + } + } + + +} + +func TestPrysmClient(t *testing.T) { + config := &prysmConfig{ + port: []int{9000, 9001}, + consensusCheckpoint: "https://checkpoint.sync", + network: "mainnet", + } + + args := config.buildArgs() + + expectedArgs := []string{ + "beacon-chain", + "--mainnet", + "--p2p-udp-port=9001", + "--p2p-quic-port=9000", + "--p2p-tcp-port=9000", + "--execution-endpoint", + "http://localhost:8551", + "--grpc-gateway-host=0.0.0.0", + "--grpc-gateway-port=5052", + "--checkpoint-sync-url=https://checkpoint.sync", + "--genesis-beacon-api-url=https://checkpoint.sync", + "--accept-terms-of-use=true", + "--jwt-secret", + constants.JWTPath, + "--monitoring-host", + "127.0.0.1", + "--monitoring-port", + "5054", + "--datadir=" + filepath.Join(constants.InstallClientsDir, "prsym", "database"), + } + + if len(args) != len(expectedArgs) { + t.Errorf("Expected %d arguments, got %d", len(expectedArgs), len(args)) + } + + for i, expected := range expectedArgs { + if args[i] != expected { + t.Errorf("Expected argument %d to be '%s', got '%s'", i, expected, args[i]) + } + } + + +} + +func TestJunoClient(t *testing.T) { + config := types.JunoConfig{ + Port: 6060, + EthNode: "ws://localhost:8546", + } + + client := &JunoClient{config: config, network: "mainnet"} + args := client.buildJunoArgs() + + expectedArgs := []string{ + "--http", + "--http-port=6060", + "--http-host=0.0.0.0", + "--db-path=" + filepath.Join(constants.InstallStarknetDir, "juno", "database"), + "--eth-node=ws://localhost:8546", + "--ws=false", + "--ws-port=6061", + "--ws-host=0.0.0.0", + "--network=mainnet", + } + + if len(args) != len(expectedArgs) { + t.Errorf("Expected %d arguments, got %d", len(expectedArgs), len(args)) + } + + for i, expected := range expectedArgs { + if args[i] != expected { + t.Errorf("Expected argument %d to be '%s', got '%s'", i, expected, args[i]) + } + } + + +} + +func TestStarknetValidatorClient(t *testing.T) { + config := &StakingValidator{ + Provider: stakingValidatorProviderConfig{ + starknetHttp: "http://localhost:6060", + starkentWS: "ws://localhost:6061", + }, + Wallet: stakingValidatorWalletConfig{ + address: "0x123", + privatekey: "0x456", + }, + } + + args := config.buildArgs() + + expectedArgs := []string{ + "--provider-http", "http://localhost:6060", + "--provider-ws", "ws://localhost:6061", + "--signer-op-address", "0x123", + "--signer-priv-key", "0x456", + } + + if len(args) != len(expectedArgs) { + t.Errorf("Expected %d arguments, got %d", len(expectedArgs), len(args)) + } + + for i, expected := range expectedArgs { + if args[i] != expected { + t.Errorf("Expected argument %d to be '%s', got '%s'", i, expected, args[i]) + } + } + + +} diff --git a/pkg/clients/geth_test.go b/pkg/clients/geth_test.go deleted file mode 100644 index 4be3c32..0000000 --- a/pkg/clients/geth_test.go +++ /dev/null @@ -1,174 +0,0 @@ -package clients - -import ( - "runtime" - "strings" - "testing" -) - -// Mock configuration for testing -func createTestGethConfig() *gethConfig { - return &gethConfig{ - port: 30303, - executionType: "full", - } -} - -func TestGethConfig_Creation(t *testing.T) { - config := createTestGethConfig() - - if config.executionType != "full" { - t.Errorf("executionType = %v, want %v", config.executionType, "full") - } - - if config.port != 30303 { - t.Errorf("port = %v, want %v", config.port, 30303) - } - -} - -func TestGetGethCommand(t *testing.T) { - t.Run("current platform", func(t *testing.T) { - geth := gethConfig{ - port: 30303, - executionType: "full", - } - result := geth.getCommand() - - if runtime.GOOS == "windows" { - expectedSuffix := "geth.exe" - if !strings.HasSuffix(result, expectedSuffix) { - t.Errorf("On Windows, command should end with %s, got %s", expectedSuffix, result) - } - } else { - expectedSuffix := "geth" - if !strings.HasSuffix(result, expectedSuffix) && !strings.HasSuffix(result, "geth.exe") { - t.Errorf("On non-Windows, command should end with geth, got %s", result) - } - } - - // Check that the path contains the expected directory structure - expectedParts := []string{"geth"} - for _, part := range expectedParts { - if !strings.Contains(result, part) { - t.Errorf("Command should contain %s, got %s", part, result) - } - } - }) -} - -func TestStartGeth_Parameters(t *testing.T) { - t.Run("parameter validation", func(t *testing.T) { - // Test that StartGeth accepts the correct parameters - - // We can't actually start geth in tests, but we can verify the function signature - // and basic parameter handling by checking it doesn't panic with valid inputs - geth := gethConfig{ - port: 30303, - executionType: "full", - } - err := geth.Start() - - // We expect this to fail since geth binary likely doesn't exist in test environment - // but it shouldn't panic and should return an error - if err == nil { - t.Log("StartGeth succeeded (geth binary must be present)") - } else { - t.Logf("StartGeth failed as expected in test environment: %v", err) - } - }) - - t.Run("different execution types", func(t *testing.T) { - executionTypes := []string{"full", "archive"} - - for _, execType := range executionTypes { - geth := gethConfig{ - port: 30303, - executionType: execType, - } - err := geth.Start() - // Again, we expect this to fail in test environment, but shouldn't panic - if err != nil { - t.Logf("StartGeth with type %s failed as expected: %v", execType, err) - } - } - }) - - t.Run("different ports", func(t *testing.T) { - testPorts := []int{30303, 30304, 31313} - - for _, port := range testPorts { - geth := gethConfig{ - port: port, - executionType: "full", - } - err := geth.Start() - // Expected to fail in test environment - if err != nil { - t.Logf("StartGeth with ports %v failed as expected: %v", port, err) - } - } - }) -} - -func TestGethConfig_Validation(t *testing.T) { - t.Run("valid config", func(t *testing.T) { - config := createTestGethConfig() - - if config.executionType == "" { - t.Error("executionType should not be empty") - } - - if config.port <= 0 { - t.Error("port should be positive") - } - - }) - - t.Run("execution types", func(t *testing.T) { - validTypes := []string{"full", "archive"} - - for _, execType := range validTypes { - config := createTestGethConfig() - config.executionType = execType - - // Verify the config accepts valid execution types - if config.executionType != execType { - t.Errorf("Failed to set executionType to %s", execType) - } - } - }) - - t.Run("port ranges", func(t *testing.T) { - config := createTestGethConfig() - - // Test various port values - testPorts := []int{1024, 30303, 30304, 65535} - - for _, port := range testPorts { - config.port = port - if config.port != port { - t.Errorf("Failed to set port to %d", port) - } - } - }) -} - -// Benchmark tests -func BenchmarkGetGethCommand(b *testing.B) { - b.ResetTimer() - for i := 0; i < b.N; i++ { - geth := gethConfig{ - port: 30303, - executionType: "full", - } - geth.getCommand() - } -} - -func BenchmarkGethConfigCreation(b *testing.B) { - b.ResetTimer() - for i := 0; i < b.N; i++ { - createTestGethConfig() - } -} diff --git a/pkg/clients/juno_test.go b/pkg/clients/juno_test.go deleted file mode 100644 index b19783a..0000000 --- a/pkg/clients/juno_test.go +++ /dev/null @@ -1,510 +0,0 @@ -package clients - -import ( - "context" - "os" - "path/filepath" - "strings" - "testing" - "time" - - "github.com/thebuidl-grid/starknode-kit/pkg/constants" -) - -func TestDefaultJunoConfig(t *testing.T) { - config := DefaultJunoConfig() - - // Test default values - if config.Network != "mainnet" { - t.Errorf("Expected Network to be 'mainnet', got '%s'", config.Network) - } - - if config.Port != "6060" { - t.Errorf("Expected Port to be '6060', got '%s'", config.Port) - } - - if !config.UseSnapshot { - t.Error("Expected UseSnapshot to be true") - } - - if config.DataDir != "./juno-data" { - t.Errorf("Expected DataDir to be './juno-data', got '%s'", config.DataDir) - } - - if config.EthNode != "ws://localhost:8546" { - t.Errorf("Expected EthNode to be 'ws://localhost:8546', got '%s'", config.EthNode) - } - - // Test environment variables - expectedEnv := []string{ - "JUNO_NETWORK=mainnet", - "JUNO_HTTP_PORT=6060", - "JUNO_HTTP_HOST=0.0.0.0", - } - - if len(config.Environment) != len(expectedEnv) { - t.Errorf("Expected %d environment variables, got %d", len(expectedEnv), len(config.Environment)) - } - - for i, expected := range expectedEnv { - if config.Environment[i] != expected { - t.Errorf("Expected environment variable %d to be '%s', got '%s'", i, expected, config.Environment[i]) - } - } -} - -func TestNewJunoClient(t *testing.T) { - // Mock the presence of the Juno binary - tempDir := t.TempDir() - junoDir := filepath.Join(tempDir, "juno") - if err := os.MkdirAll(junoDir, 0755); err != nil { - t.Fatalf("Failed to create juno dir: %v", err) - } - junoPath := filepath.Join(junoDir, "juno") - if err := os.WriteFile(junoPath, []byte("#!/bin/sh\necho 'juno version 0.14.6'\n"), 0755); err != nil { - t.Fatalf("Failed to create dummy juno binary: %v", err) - } - // Save and override InstallClientsDir - origInstallClientsDir := constants.InstallClientsDir - constants.InstallClientsDir = tempDir - defer func() { constants.InstallClientsDir = origInstallClientsDir }() - - // Test with nil config - client, err := NewJunoClient(nil) - if err != nil { - t.Errorf("Expected no error when config is nil, got: %v", err) - } - if client == nil { - t.Error("Expected client to be created when config is nil") - } - - // Test with custom config - customConfig := &JunoConfig{ - Network: "sepolia", - Port: "6061", - UseSnapshot: false, - DataDir: "/custom/path", - EthNode: "ws://custom:8546", - Environment: []string{"CUSTOM_VAR=value"}, - } - - client, err = NewJunoClient(customConfig) - if err != nil { - t.Errorf("Expected no error with custom config, got: %v", err) - } - if client == nil { - t.Error("Expected client to be created with custom config") - } - - if client.config.Network != "sepolia" { - t.Errorf("Expected Network to be 'sepolia', got '%s'", client.config.Network) - } - - if client.config.Port != "6061" { - t.Errorf("Expected Port to be '6061', got '%s'", client.config.Port) - } - - if client.config.UseSnapshot { - t.Error("Expected UseSnapshot to be false") - } - - if client.config.DataDir != "/custom/path" { - t.Errorf("Expected DataDir to be '/custom/path', got '%s'", client.config.DataDir) - } - - if client.config.EthNode != "ws://custom:8546" { - t.Errorf("Expected EthNode to be 'ws://custom:8546', got '%s'", client.config.EthNode) - } -} - -func TestGetJunoPath(t *testing.T) { - // Test when Juno is not installed - path := getJunoPath() - if path != "" { - t.Errorf("Expected empty path when Juno is not installed, got '%s'", path) - } -} - -func TestBuildJunoArgs(t *testing.T) { - config := &JunoConfig{ - Network: "mainnet", - Port: "6060", - UseSnapshot: true, - DataDir: "/test/data", - EthNode: "ws://localhost:8546", - } - - client := &JunoClient{config: config} - args := client.buildJunoArgs() - - // Test required arguments - expectedArgs := []string{ - "--http", - "--http-port=6060", - "--http-host=0.0.0.0", - "--db-path=/test/data", - "--eth-node=ws://localhost:8546", - "--network=mainnet", - "--snapshot", - "--metrics", - "--metrics-port=6060", - } - - if len(args) != len(expectedArgs) { - t.Errorf("Expected %d arguments, got %d", len(expectedArgs), len(args)) - } - - for i, expected := range expectedArgs { - if args[i] != expected { - t.Errorf("Expected argument %d to be '%s', got '%s'", i, expected, args[i]) - } - } -} - -func TestBuildJunoArgsSepolia(t *testing.T) { - config := &JunoConfig{ - Network: "sepolia", - Port: "6061", - UseSnapshot: false, - DataDir: "/test/sepolia", - EthNode: "ws://sepolia:8546", - } - - client := &JunoClient{config: config} - args := client.buildJunoArgs() - - // Test sepolia network arguments - expectedArgs := []string{ - "--http", - "--http-port=6061", - "--http-host=0.0.0.0", - "--db-path=/test/sepolia", - "--eth-node=ws://sepolia:8546", - "--network=sepolia", - "--metrics", - "--metrics-port=6060", - } - - if len(args) != len(expectedArgs) { - t.Errorf("Expected %d arguments, got %d", len(expectedArgs), len(args)) - } - - for i, expected := range expectedArgs { - if args[i] != expected { - t.Errorf("Expected argument %d to be '%s', got '%s'", i, expected, args[i]) - } - } -} - -func TestBuildJunoArgsSepoliaIntegration(t *testing.T) { - config := &JunoConfig{ - Network: "sepolia-integration", - Port: "6062", - UseSnapshot: true, - DataDir: "/test/sepolia-integration", - EthNode: "ws://sepolia-integration:8546", - } - - client := &JunoClient{config: config} - args := client.buildJunoArgs() - - // Test sepolia-integration network arguments - expectedArgs := []string{ - "--http", - "--http-port=6062", - "--http-host=0.0.0.0", - "--db-path=/test/sepolia-integration", - "--eth-node=ws://sepolia-integration:8546", - "--network=sepolia-integration", - "--snapshot", - "--metrics", - "--metrics-port=6060", - } - - if len(args) != len(expectedArgs) { - t.Errorf("Expected %d arguments, got %d", len(expectedArgs), len(args)) - } - - for i, expected := range expectedArgs { - if args[i] != expected { - t.Errorf("Expected argument %d to be '%s', got '%s'", i, expected, args[i]) - } - } -} - -func TestJunoClientStartNode(t *testing.T) { - // Create temporary directory for test - tempDir := t.TempDir() - - config := &JunoConfig{ - Network: "mainnet", - Port: "6060", - UseSnapshot: true, - DataDir: tempDir, - EthNode: "ws://localhost:8546", - } - - client := &JunoClient{config: config} - - // Mock the junoPath to avoid actual binary execution - client.junoPath = "echo" // Use echo as a mock command - - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - - // The dummy binary (echo) will start successfully, so we do not expect an error - err := client.StartNode(ctx) - if err != nil { - t.Errorf("Expected no error when starting with dummy binary, got: %v", err) - } - - // Test that directories were created - if _, err := os.Stat(tempDir); os.IsNotExist(err) { - t.Error("Expected data directory to be created") - } - - logsDir := filepath.Join(filepath.Dir(tempDir), "logs") - if _, err := os.Stat(logsDir); os.IsNotExist(err) { - t.Error("Expected logs directory to be created") - } -} - -func TestJunoClientStopNode(t *testing.T) { - client := &JunoClient{} - - // Test stopping when no process is running - _ = client.StopNode(context.Background()) // Should not panic - - // Test with mock process - client.process = &os.Process{Pid: 99999} // Non-existent PID - _ = client.StopNode(context.Background()) // Should not panic -} - -func TestJunoClientGetNodeStatus(t *testing.T) { - client := &JunoClient{} - - // Test when no process is running - status, err := client.GetNodeStatus(context.Background()) - if err != nil { - t.Errorf("Expected no error when getting status of non-existent process, got: %v", err) - } - if status != "not running" { - t.Errorf("Expected status 'not running', got '%s'", status) - } - - // Test with mock process - client.process = &os.Process{Pid: 99999} // Non-existent PID - status, err = client.GetNodeStatus(context.Background()) - if err != nil { - t.Errorf("Expected no error when getting status of invalid process, got: %v", err) - } - if status != "stopped" { - t.Errorf("Expected status 'stopped', got '%s'", status) - } -} - -func TestJunoClientClose(t *testing.T) { - client := &JunoClient{} - - // Test closing when no log file is open - err := client.Close() - if err != nil { - t.Errorf("Expected no error when closing without log file, got: %v", err) - } - - // Test closing with log file - tempFile, err := os.CreateTemp("", "juno_test") - if err != nil { - t.Fatalf("Failed to create temp file: %v", err) - } - defer os.Remove(tempFile.Name()) - - client.logFile = tempFile - err = client.Close() - if err != nil { - t.Errorf("Expected no error when closing with log file, got: %v", err) - } -} - -func TestJunoConfigValidation(t *testing.T) { - tests := []struct { - name string - config *JunoConfig - wantErr bool - }{ - { - name: "valid mainnet config", - config: &JunoConfig{ - Network: "mainnet", - Port: "6060", - UseSnapshot: true, - DataDir: "/test/data", - EthNode: "ws://localhost:8546", - }, - wantErr: false, - }, - { - name: "valid sepolia config", - config: &JunoConfig{ - Network: "sepolia", - Port: "6061", - UseSnapshot: false, - DataDir: "/test/sepolia", - EthNode: "ws://sepolia:8546", - }, - wantErr: false, - }, - { - name: "valid sepolia-integration config", - config: &JunoConfig{ - Network: "sepolia-integration", - Port: "6062", - UseSnapshot: true, - DataDir: "/test/sepolia-integration", - EthNode: "ws://sepolia-integration:8546", - }, - wantErr: false, - }, - { - name: "invalid network", - config: &JunoConfig{ - Network: "invalid", - Port: "6060", - UseSnapshot: true, - DataDir: "/test/data", - EthNode: "ws://localhost:8546", - }, - wantErr: false, // We don't validate network in buildJunoArgs - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - client := &JunoClient{config: tt.config} - args := client.buildJunoArgs() - - // Check that required arguments are present - requiredArgs := []string{ - "--http", - "--http-host=0.0.0.0", - "--metrics", - "--metrics-port=6060", - } - - for _, required := range requiredArgs { - found := false - for _, arg := range args { - if arg == required { - found = true - break - } - } - if !found { - t.Errorf("Required argument '%s' not found in args", required) - } - } - - // Check that eth-node is present (substring match) - ethNodeFound := false - for _, arg := range args { - if strings.Contains(arg, "--eth-node=") { - ethNodeFound = true - break - } - } - if !ethNodeFound { - t.Error("--eth-node argument not found in args") - } - }) - } -} - -func TestJunoClientIntegration(t *testing.T) { - if testing.Short() { - t.Skip("Skipping integration test in short mode") - } - - // Create temporary directory for test - tempDir := t.TempDir() - - config := &JunoConfig{ - Network: "mainnet", - Port: "6060", - UseSnapshot: true, - DataDir: tempDir, - EthNode: "ws://localhost:8546", - } - - client := &JunoClient{config: config} - - // Test that client can be created - if client == nil { - t.Fatal("Failed to create Juno client") - } - - // Test that config is properly set - if client.config.Network != "mainnet" { - t.Errorf("Expected Network to be 'mainnet', got '%s'", client.config.Network) - } - - // Test that arguments can be built - args := client.buildJunoArgs() - if len(args) == 0 { - t.Error("Expected non-empty arguments list") - } - - // Test that required arguments are present - hasHttp := false - hasEthNode := false - for _, arg := range args { - if arg == "--http" { - hasHttp = true - } - if strings.Contains(arg, "--eth-node=") { - hasEthNode = true - } - } - - if !hasHttp { - t.Error("Expected --http argument to be present") - } - if !hasEthNode { - t.Error("Expected --eth-node argument to be present") - } -} - -// Benchmark tests -func BenchmarkBuildJunoArgs(b *testing.B) { - config := &JunoConfig{ - Network: "mainnet", - Port: "6060", - UseSnapshot: true, - DataDir: "/test/data", - EthNode: "ws://localhost:8546", - } - - client := &JunoClient{config: config} - - b.ResetTimer() - for i := 0; i < b.N; i++ { - client.buildJunoArgs() - } -} - -func BenchmarkNewJunoClient(b *testing.B) { - config := &JunoConfig{ - Network: "mainnet", - Port: "6060", - UseSnapshot: true, - DataDir: "/test/data", - EthNode: "ws://localhost:8546", - } - - b.ResetTimer() - for i := 0; i < b.N; i++ { - _, err := NewJunoClient(config) - if err != nil { - b.Fatalf("Failed to create client: %v", err) - } - } -} diff --git a/pkg/clients/reth_test.go b/pkg/clients/reth_test.go deleted file mode 100644 index 0a2bacd..0000000 --- a/pkg/clients/reth_test.go +++ /dev/null @@ -1,112 +0,0 @@ -package clients - -import ( - "runtime" - "strings" - "testing" -) - -// Mock configuration for testing -func createTestRethConfig() *rethConfig { - return &rethConfig{ - executionType: "full", - port: 30303, - } -} - -func TestRethConfig_Creation(t *testing.T) { - config := createTestRethConfig() - - if config.executionType != "full" { - t.Errorf("executionType = %v, want %v", config.executionType, "full") - } - - if config.port != 30303 { - t.Errorf("port = %v, want %v", config.port, 30303) - } -} - -func TestGetRethCommand(t *testing.T) { - t.Run("current platform", func(t *testing.T) { - config := createTestRethConfig() - result := config.getCommand() - - if runtime.GOOS == "windows" { - if !strings.HasSuffix(result, "reth.exe") { - t.Errorf("On Windows, command should end with reth.exe, got %s", result) - } - } else { - if !strings.HasSuffix(result, "reth") { - t.Errorf("On non-Windows, command should end with reth, got %s", result) - } - } - - if !strings.Contains(result, "reth") { - t.Errorf("Command should contain 'reth', got %s", result) - } - }) -} - -func TestBuildRethArgs(t *testing.T) { - tests := []struct { - name string - config *rethConfig - expected string // partial string match for simplicity - }{ - { - name: "full sync mode", - config: &rethConfig{ - executionType: "full", - port: 30303, - }, - expected: "--port 30303", - }, - { - name: "archive mode", - config: &rethConfig{ - executionType: "archive", - port: 30304, - }, - expected: "--archive", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - args := tt.config.buildArgs() - argsStr := strings.Join(args, " ") - - if !strings.Contains(argsStr, tt.expected) { - t.Errorf("Expected args to contain %s, got %s", tt.expected, argsStr) - } - - if tt.config.executionType == "archive" && !strings.Contains(argsStr, "--archive") { - t.Error("Expected --archive flag for archive mode") - } - }) - } -} - -func TestStartReth_DoesNotPanic(t *testing.T) { - t.Run("start simulation", func(t *testing.T) { - config := createTestRethConfig() - err := config.Start() - if err != nil { - t.Logf("Start failed as expected (e.g., binary/log path may be missing): %v", err) - } - }) -} - -func BenchmarkBuildRethArgs(b *testing.B) { - config := createTestRethConfig() - for i := 0; i < b.N; i++ { - config.buildArgs() - } -} - -func BenchmarkGetRethCommand(b *testing.B) { - config := createTestRethConfig() - for i := 0; i < b.N; i++ { - config.getCommand() - } -} diff --git a/pkg/installer.go b/pkg/installer.go index 9b11440..3ad90eb 100644 --- a/pkg/installer.go +++ b/pkg/installer.go @@ -151,7 +151,7 @@ func (i *installer) getClientFileName(client types.ClientType, version string) ( return "", err } fileName = fmt.Sprintf("geth-%s-%s-%s-%s", - goos, gethArch, version, gethHash[:8]) + goos, gethArch, version, gethHash[:8]) case types.ClientReth: fileName = fmt.Sprintf("reth-v%s-%s", version, archName) case types.ClientLighthouse: @@ -243,7 +243,7 @@ func (i *installer) InstallClient(client types.ClientType) error { func (i *installer) UpdateClient(client types.ClientType) error { clientDir := i.getClientDirectory(client) clientPath := i.getClientPath(client, clientDir) - return i.installClient(client, clientDir, clientPath) + return i.installClient(client, clientPath, clientDir) } // getClientDirectory returns the appropriate directory for the client @@ -359,7 +359,6 @@ func (i *installer) installStandardClient(client types.ClientType, clientDir, do // Download file fmt.Printf("Downloading %s.\n", client) if err := downloadFile(downloadURL, archivePath); err != nil { - fmt.Println(err) return err } diff --git a/pkg/installer_test.go b/pkg/installer_test.go deleted file mode 100644 index 97ee2af..0000000 --- a/pkg/installer_test.go +++ /dev/null @@ -1,283 +0,0 @@ -package pkg - -import ( - "fmt" - "os" - "os/exec" - "strings" - "testing" - - "github.com/thebuidl-grid/starknode-kit/pkg/types" - "github.com/thebuidl-grid/starknode-kit/pkg/versions" -) - -func TestCompareClientVersions(t *testing.T) { - installed := "1.2.3" - - // We're testing the reth client which has a hardcoded LatestRethVersion - expectedVersion := versions.LatestRethVersion - - isLatest := CompareClientVersions("reth", installed, expectedVersion) - if compareVersions(installed, expectedVersion) >= 0 && !isLatest { - t.Errorf("Expected latest, got not latest") - } - if compareVersions(installed, expectedVersion) < 0 && isLatest { - t.Errorf("Expected not latest, got latest") - } -} - -// -------------------- exec.Command Mocking -------------------- - -func fakeExecCommand(command string, args ...string) *exec.Cmd { - cs := []string{"-test.run=TestHelperProcess", "--", command} - cs = append(cs, args...) - cmd := exec.Command(os.Args[0], cs...) - cmd.Env = append(os.Environ(), "GO_WANT_HELPER_PROCESS=1") - return cmd -} - -// Test helper function that simulates binary output -func TestHelperProcess(t *testing.T) { - if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" { - return - } - - args := os.Args - if len(args) > 3 { - if strings.Contains(args[3], "reth") { - fmt.Fprint(os.Stdout, "reth Version: 1.2.3") - } else if strings.Contains(args[3], "lighthouse") { - fmt.Fprint(os.Stdout, "Lighthouse v2.3.4") - } else if strings.Contains(args[3], "geth") { - fmt.Fprint(os.Stdout, "geth version 3.4.5") - } else if strings.Contains(args[3], "prysm") { - fmt.Fprint(os.Stdout, "beacon-chain-v4.5.6-commit") - } - } - - os.Exit(0) -} - -func TestGetVersionNumber(t *testing.T) { - // Override execCommand with our mock - execCommand = fakeExecCommand - defer func() { execCommand = exec.Command }() - - // Note: InstallClientsDir is used by GetVersionNumber internally - - tests := []struct { - client string - expected string - }{ - {"reth", "1.2.3"}, - {"lighthouse", "2.3.4"}, - {"geth", "3.4.5"}, - {"prysm", "4.5.6"}, - } - - for _, tt := range tests { - t.Run(tt.client, func(t *testing.T) { - version := versions.GetVersionNumber(tt.client) - if version != tt.expected { - t.Errorf("expected %s, got %s", tt.expected, version) - } - }) - } -} - -func TestGetClientFileName(t *testing.T) { - installer := NewInstaller() - - tests := []struct { - client types.ClientType - version string - wantErr bool - }{ - {types.ClientGeth, "1.15.10", false}, - {types.ClientReth, "1.3.4", false}, - {types.ClientLighthouse, "7.0.1", false}, - {types.ClientPrysm, "4.5.6", false}, - {types.ClientJuno, "0.11.7", false}, - {"unknown", "1.0.0", true}, - } - - for _, tt := range tests { - t.Run(string(tt.client), func(t *testing.T) { - fileName, err := installer.getClientFileName(tt.client, tt.version) - if (err != nil) != tt.wantErr { - t.Errorf("GetClientFileName() error = %v, wantErr %v", err, tt.wantErr) - return - } - - if !tt.wantErr && fileName == "" { - t.Errorf("GetClientFileName() returned empty filename") - } - }) - } -} - -func TestGetDownloadURL(t *testing.T) { - installer := NewInstaller() - - tests := []struct { - client types.ClientType - fileName string - version string - wantErr bool - }{ - {types.ClientGeth, "geth-linux-amd64-1.15.10-2bf8a789", "1.15.10", false}, - {types.ClientReth, "reth-v1.3.4-x86_64-unknown-linux-gnu", "1.3.4", false}, - {types.ClientLighthouse, "lighthouse-v7.0.1-x86_64-unknown-linux-gnu", "7.0.1", false}, - {types.ClientPrysm, "prysm.sh", "4.5.6", false}, - {types.ClientJuno, "juno-" + versions.LatestJunoVersion, versions.LatestJunoVersion, false}, - {"unknown", "unknown", "1.0.0", true}, - } - - for _, tt := range tests { - t.Run(string(tt.client), func(t *testing.T) { - url, err := installer.getDownloadURL(tt.client, tt.fileName, tt.version) - if (err != nil) != tt.wantErr { - t.Errorf("getDownloadURL() error = %v, wantErr %v", err, tt.wantErr) - return - } - - if !tt.wantErr && url == "" { - t.Errorf("getDownloadURL() returned empty url") - } - }) - } -} - -func TestNewInstaller(t *testing.T) { - installer := NewInstaller() - - // Since installer is a struct, it cannot be nil - // We can test that it has the expected InstallDir set - if installer.InstallDir == "" { - t.Error("NewInstaller() returned installer with empty InstallDir") - } -} - -func TestCompareVersions(t *testing.T) { - tests := []struct { - v1 string - v2 string - expected int - }{ - {"1.2.3", "1.2.3", 0}, - {"1.2.3", "1.2.4", -1}, - {"1.2.4", "1.2.3", 1}, - {"1.3.0", "1.2.9", 1}, - {"2.0.0", "1.9.9", 1}, - {"1.10.0", "1.9.0", 1}, - } - - for _, tt := range tests { - t.Run(fmt.Sprintf("%s_vs_%s", tt.v1, tt.v2), func(t *testing.T) { - result := compareVersions(tt.v1, tt.v2) - if result != tt.expected { - t.Errorf("compareVersions(%s, %s) = %d, want %d", tt.v1, tt.v2, result, tt.expected) - } - }) - } -} - -// TestIsClientLatestVersion tests the CompareClientVersions function -func TestIsClientLatestVersion(t *testing.T) { - tests := []struct { - client types.ClientType - version string - latest string - wantLatest bool - }{ - {types.ClientReth, "0.1.0", versions.LatestRethVersion, false}, // Older version - {types.ClientReth, versions.LatestRethVersion, versions.LatestRethVersion, true}, // Latest version - {types.ClientReth, "999.999.999", versions.LatestRethVersion, true}, // Future version - {types.ClientGeth, "1.0.0", versions.LatestGethVersion, false}, // Older version - {types.ClientGeth, versions.LatestGethVersion, versions.LatestGethVersion, true}, // Latest version - {types.ClientLighthouse, "1.0.0", versions.LatestLighthouseVersion, false}, // Older version - {types.ClientLighthouse, versions.LatestLighthouseVersion, versions.LatestLighthouseVersion, true}, // Latest version - } - - for _, tt := range tests { - t.Run(fmt.Sprintf("%s_%s", tt.client, tt.version), func(t *testing.T) { - isLatest := CompareClientVersions(string(tt.client), tt.version, tt.latest) - if isLatest != tt.wantLatest { - t.Errorf("CompareClientVersions() isLatest = %v, want %v", isLatest, tt.wantLatest) - } - }) - } -} - -// MockInstaller represents a mock installer for testing -type MockInstaller struct { - *installer - RemoveClientCalled bool - InstallClientCalled bool - SetupJWTSecretCalled bool -} - -// Override RemoveClient for testing -func (m *MockInstaller) RemoveClient(client types.ClientType) error { - m.RemoveClientCalled = true - return nil -} - -// TestCommandLineRun tests the CommandLine.Run method -// func TestCommandLineRun(t *testing.T) { -//installDir := "/tmp" -//baseInstaller := NewInstaller(installDir) - -//mockInstaller := &MockInstaller{ -// installer: baseInstaller, -//} - -//cmdLine := &CommandLine{ -//installer: mockInstaller.installer, -//} - -// Test with remove flag -//args := []string{"installer", "--client", "geth", "--remove"} -//err := cmdLine.Run(args) -//if err != nil { -//t.Errorf("CommandLine.Run() error = %v", err) -//} - -// Test with invalid client -//args = []string{"installer", "--client", "unknown"} -//err = cmdLine.Run(args) -//if err == nil { -//t.Errorf("CommandLine.Run() with invalid client should return error") -//} - -// Test with missing client -//args = []string{"installer"} -//err = cmdLine.Run(args) -//if err == nil { -//t.Errorf("CommandLine.Run() with missing client should return error") -//} -//} - -// TestDownloadFile tests the downloadFile function with a mock HTTP server -func TestDownloadFile(t *testing.T) { - // Skip this test for now as it requires setting up a mock HTTP server - t.Skip("Skipping downloadFile test as it requires a mock HTTP server") -} - -// TestInstallClient tests the InstallClient method -func TestInstallClient(t *testing.T) { - // Skip this test as it requires filesystem operations - t.Skip("Skipping InstallClient test as it requires filesystem operations") -} - -// TestSetupJWTSecret tests the SetupJWTSecret method -func TestSetupJWTSecret(t *testing.T) { - // Skip this test as it requires filesystem operations - t.Skip("Skipping SetupJWTSecret test as it requires filesystem operations") -} - -// TestRemoveClient tests the RemoveClient method -func TestRemoveClient(t *testing.T) { - // Skip this test as it requires filesystem operations - t.Skip("Skipping RemoveClient test as it requires filesystem operations") -} diff --git a/pkg/monitoring/dynamic_test.go b/pkg/monitoring/dynamic_test.go new file mode 100644 index 0000000..f20c5b1 --- /dev/null +++ b/pkg/monitoring/dynamic_test.go @@ -0,0 +1,189 @@ +package monitoring + +import ( + "testing" + "time" + + "github.com/thebuidl-grid/starknode-kit/pkg/types" + "github.com/thebuidl-grid/starknode-kit/pkg/utils" +) + +// TestDynamicLayout tests that the layout rebuilds correctly based on running clients +func TestDynamicLayout(t *testing.T) { + app := NewMonitorApp() + + // Test 1: Initial layout should have all panels created + if app.ExecutionLogBox == nil { + t.Error("ExecutionLogBox should be created") + } + if app.ConsensusLogBox == nil { + t.Error("ConsensusLogBox should be created") + } + if app.JunoLogBox == nil { + t.Error("JunoLogBox should be created") + } + if app.ValidatorLogBox == nil { + t.Error("ValidatorLogBox should be created") + } + if app.NoClientsBox == nil { + t.Error("NoClientsBox should be created") + } +} + +// TestValidatorLogChannel tests that the validator log channel is created and working +func TestValidatorLogChannel(t *testing.T) { + app := NewMonitorApp() + + // Test that channel is created + if app.ValidatorLogChan == nil { + t.Fatal("ValidatorLogChan should be created") + } + + // Test that we can send to the channel + testMessage := "Test validator log message" + select { + case app.ValidatorLogChan <- testMessage: + // Successfully sent + case <-time.After(time.Second): + t.Error("Failed to send to ValidatorLogChan") + } + + // Test that we can receive from the channel + select { + case msg := <-app.ValidatorLogChan: + if msg != testMessage { + t.Errorf("Expected '%s', got '%s'", testMessage, msg) + } + case <-time.After(time.Second): + t.Error("Failed to receive from ValidatorLogChan") + } +} + +// TestRebuildDynamicLayoutNoClients tests layout when no clients are running +func TestRebuildDynamicLayoutNoClients(t *testing.T) { + app := NewMonitorApp() + + // Mock no running clients + // Note: This test assumes GetRunningClients returns empty array when no clients running + app.rebuildDynamicLayout() + + // The grid should be rebuilt but we can't easily test the internal state + // We just verify it doesn't panic + t.Log("Dynamic layout rebuild completed without panics") +} + +// TestValidatorClientDetection tests that validator is properly detected +func TestValidatorClientDetection(t *testing.T) { + // Get running clients + clients := utils.GetRunningClients() + + // Check if Validator is in the supported client types + hasValidator := false + for _, client := range clients { + if client.Name == "Validator" || client.Name == "StarknetValidator" { + hasValidator = true + t.Logf("Validator client detected: %s (PID: %d)", client.Name, client.PID) + break + } + } + + if !hasValidator { + t.Log("No validator client currently running (expected if not started)") + } +} + +// TestValidatorClientType tests the ClientStarkValidator constant +func TestValidatorClientType(t *testing.T) { + if types.ClientStarkValidator != "starknet-staking-v2" { + t.Errorf("Expected ClientStarkValidator to be 'starknet-staking-v2', got '%s'", types.ClientStarkValidator) + } +} + +// TestAllLogChannelsCreated tests that all log channels are properly initialized +func TestAllLogChannelsCreated(t *testing.T) { + app := NewMonitorApp() + + channels := map[string]chan string{ + "ExecutionLogChan": app.ExecutionLogChan, + "ConsensusLogChan": app.ConsensusLogChan, + "JunoLogChan": app.JunoLogChan, + "ValidatorLogChan": app.ValidatorLogChan, + "StatusChan": app.StatusChan, + "NetworkChan": app.NetworkChan, + "JunoStatusChan": app.JunoStatusChan, + "ChainInfoChan": app.ChainInfoChan, + "SystemStatsChan": app.SystemStatsChan, + "RPCInfoChan": app.RPCInfoChan, + } + + for name, ch := range channels { + if ch == nil { + t.Errorf("Channel %s should be initialized", name) + } + } +} + +// TestNoClientsMessage tests the "No Clients Running" message box +func TestNoClientsMessage(t *testing.T) { + app := NewMonitorApp() + + if app.NoClientsBox == nil { + t.Fatal("NoClientsBox should be created") + } + + text := app.NoClientsBox.GetText(false) + if text == "" { + t.Error("NoClientsBox should have a message") + } + + // Check that it contains the expected warning message + if !contains(text, "NO CLIENTS RUNNING") { + t.Error("NoClientsBox should contain 'NO CLIENTS RUNNING' message") + } +} + +// TestLogFormatting tests that log lines are properly formatted +func TestLogFormatting(t *testing.T) { + testCases := []struct { + input string + contains []string + }{ + { + input: "INFO [12-07|15:32:22.145] Test message", + contains: []string{"INFO", "Test message"}, + }, + { + input: "WARN something happened", + contains: []string{"WARN", "something happened"}, + }, + { + input: "ERROR critical failure", + contains: []string{"ERROR", "critical failure"}, + }, + } + + for _, tc := range testCases { + formatted := formatLogLines(tc.input) + for _, expected := range tc.contains { + if !contains(formatted, expected) { + t.Errorf("Formatted log should contain '%s'\nInput: %s\nOutput: %s", expected, tc.input, formatted) + } + } + } +} + +// Helper function to check if a string contains a substring +func contains(s, substr string) bool { + return len(s) >= len(substr) && (s == substr || len(s) > len(substr) && + (s[:len(substr)] == substr || s[len(s)-len(substr):] == substr || + indexInString(s, substr) >= 0)) +} + +func indexInString(s, substr string) int { + for i := 0; i <= len(s)-len(substr); i++ { + if s[i:i+len(substr)] == substr { + return i + } + } + return -1 +} diff --git a/pkg/monitoring/layout.go b/pkg/monitoring/layout.go new file mode 100644 index 0000000..c268623 --- /dev/null +++ b/pkg/monitoring/layout.go @@ -0,0 +1,156 @@ +package monitoring + +import ( + "context" + "time" + + "github.com/rivo/tview" + "github.com/thebuidl-grid/starknode-kit/pkg/utils" +) + +// rebuildDynamicLayout rebuilds the entire grid layout based on running clients +func (m *MonitorApp) rebuildDynamicLayout() { + runningClients := utils.GetRunningClients() + + // Determine which clients are running + hasExecution := false + hasConsensus := false + hasJuno := false + hasValidator := false + + for _, client := range runningClients { + switch client.Name { + case "Geth", "Reth": + hasExecution = true + case "Lighthouse", "Prysm": + hasConsensus = true + case "Juno": + hasJuno = true + case "Validator": + hasValidator = true + } + } + + // Count active log panels + activeLogPanels := 0 + if hasExecution { + activeLogPanels++ + } + if hasConsensus { + activeLogPanels++ + } + if hasJuno { + activeLogPanels++ + } + if hasValidator { + activeLogPanels++ + } + + // Clear the grid + m.Grid.Clear() + + // If no clients are running, show "No Clients Running" message + if activeLogPanels == 0 { + m.Grid.SetRows(-1). + SetColumns(-1). + SetBorders(false) + m.Grid.AddItem(m.NoClientsBox, 0, 0, 1, 1, 0, 0, false) + return + } + + // Create dynamic row configuration based on number of active clients + rows := make([]int, activeLogPanels) + for i := range rows { + rows[i] = -1 // Equal height for all rows + } + + m.Grid.SetRows(rows...). + SetColumns(-3, -2). // LEFT(60%), RIGHT(40%) + SetBorders(false). + SetGap(0, 0) + + // Add active log panels to the left side + currentRow := 0 + if hasExecution { + m.Grid.AddItem(m.ExecutionLogBox, currentRow, 0, 1, 1, 0, 0, false) + currentRow++ + } + if hasConsensus { + m.Grid.AddItem(m.ConsensusLogBox, currentRow, 0, 1, 1, 0, 0, false) + currentRow++ + } + if hasJuno { + m.Grid.AddItem(m.JunoLogBox, currentRow, 0, 1, 1, 0, 0, false) + currentRow++ + } + if hasValidator { + m.Grid.AddItem(m.ValidatorLogBox, currentRow, 0, 1, 1, 0, 0, false) + currentRow++ + } + + // RIGHT SIDE - Create sub-grid for info panels (5 rows total) + rightGrid := tview.NewGrid(). + SetRows(-1, -1, -1, -1, -1). // 5 equal rows + SetColumns(-1). // Single column + SetBorders(false). + SetGap(0, 0) + + // Create status grid for ETH and Starknet status side by side + statusGrid := tview.NewGrid(). + SetRows(-1). // Single row + SetColumns(-1, -1). // 2 equal columns for ETH and Starknet + SetBorders(false). + SetGap(1, 0) // Small gap between status panels + + // Add ETH and Starknet status to the status grid + statusGrid.AddItem(m.StatusBox, 0, 0, 1, 1, 0, 0, false) // ETH Status (left) + statusGrid.AddItem(m.StarknetStatusBox, 0, 1, 1, 1, 0, 0, false) // Starknet Status (right) + + // Add all panels to the right side sub-grid + rightGrid.AddItem(m.NetworkBox, 0, 0, 1, 1, 0, 0, false) // Row 0: Network + rightGrid.AddItem(statusGrid, 1, 0, 1, 1, 0, 0, false) // Row 1: Status grid (ETH + Starknet) + rightGrid.AddItem(m.ChainInfoBox, 2, 0, 1, 1, 0, 0, false) // Row 2: Chain Info + rightGrid.AddItem(m.RPCInfoBox, 3, 0, 1, 1, 0, 0, false) // Row 3: RPC Info + rightGrid.AddItem(m.SystemStatsBox, 4, 0, 1, 1, 0, 0, false) // Row 4: System Stats + + // Add the right side sub-grid to main grid (spans all rows on right) + m.Grid.AddItem(rightGrid, 0, 1, activeLogPanels, 1, 0, 0, false) +} + +// updateLayoutDynamically periodically checks for running clients and updates the layout +func (m *MonitorApp) updateLayoutDynamically(ctx context.Context) { + ticker := time.NewTicker(5 * time.Second) // Check every 5 seconds + defer ticker.Stop() + + previousState := "" + + for { + select { + case <-ctx.Done(): + return + case <-m.StopChan: + return + case <-ticker.C: + if m.paused { + continue + } + + // Get current running clients + runningClients := utils.GetRunningClients() + + // Create a state signature based on running clients + currentState := "" + for _, client := range runningClients { + currentState += client.Name + "," + } + + // Only rebuild layout if the state has changed + if currentState != previousState { + m.App.QueueUpdateDraw(func() { + m.rebuildDynamicLayout() + }) + previousState = currentState + } + } + } +} diff --git a/pkg/monitoring/monitor.go b/pkg/monitoring/monitor.go index 72af68e..58866d9 100644 --- a/pkg/monitoring/monitor.go +++ b/pkg/monitoring/monitor.go @@ -22,6 +22,7 @@ func NewMonitorApp() *MonitorApp { ExecutionLogChan: make(chan string, 100), ConsensusLogChan: make(chan string, 100), JunoLogChan: make(chan string, 100), + ValidatorLogChan: make(chan string, 100), // New validator channel StatusChan: make(chan string, 10), JunoStatusChan: make(chan string, 10), NetworkChan: make(chan string, 10), @@ -113,17 +114,34 @@ func (m *MonitorApp) detectAndUpdateClientTitles() { } else { m.JunoLogBox.SetTitle(" Juno (Not Running) ❌ ") } + + // Check for Validator + var validatorClient *types.ClientStatus + for _, client := range runningClients { + if client.Name == "Validator" || client.Name == "StarknetValidator" { + validatorClient = &client + break + } + } + + if validatorClient != nil { + m.ValidatorLogBox.SetTitle(" Starknet Validator 🛡️ ") + } else { + m.ValidatorLogBox.SetTitle(" Validator (Not Running) ❌ ") + } } func (m *MonitorApp) Start(ctx context.Context) error { // Start new update goroutines matching JavaScript components exactly - go m.updateExecutionLogs(ctx) // executionLog.js equivalent - go m.updateConsensusLogs(ctx) // consensusLog.js equivalent - go m.updateJunoLogs(ctx) // junoLog.js equivalent (Starknet client) - go m.updateStatusBox(ctx) // statusBox.js equivalent - go m.updateChainInfoBox(ctx) // chainInfoBox.js equivalent - go m.updateSystemStatsGauge(ctx) // systemStatsGauge.js equivalent - go m.updateRPCInfo(ctx) // RPC info component + go m.updateExecutionLogs(ctx) // executionLog.js equivalent + go m.updateConsensusLogs(ctx) // consensusLog.js equivalent + go m.updateJunoLogs(ctx) // junoLog.js equivalent (Starknet client) + go m.updateValidatorLogs(ctx) // validatorLog.js equivalent (Starknet validator) + go m.updateStatusBox(ctx) // statusBox.js equivalent + go m.updateChainInfoBox(ctx) // chainInfoBox.js equivalent + go m.updateSystemStatsGauge(ctx) // systemStatsGauge.js equivalent + go m.updateRPCInfo(ctx) // RPC info component + go m.updateLayoutDynamically(ctx) // Dynamic layout updater // Removed: go m.updateBandwidthGauge(ctx) // Bandwidth component removed // Removed: go m.updatePeerCountGauge(ctx) // Peer count component removed @@ -164,6 +182,11 @@ func (m *MonitorApp) handleUpdates(ctx context.Context) { m.JunoLogBox.SetText(text) m.JunoLogBox.ScrollToEnd() }) + case text := <-m.ValidatorLogChan: + m.App.QueueUpdateDraw(func() { + m.ValidatorLogBox.SetText(text) + m.ValidatorLogBox.ScrollToEnd() + }) case text := <-m.StatusChan: m.App.QueueUpdateDraw(func() { m.StatusBox.SetText(text) diff --git a/pkg/monitoring/types.go b/pkg/monitoring/types.go index 3f12d3a..97310f2 100644 --- a/pkg/monitoring/types.go +++ b/pkg/monitoring/types.go @@ -14,6 +14,7 @@ type MonitorApp struct { ExecutionLogBox *tview.TextView ConsensusLogBox *tview.TextView JunoLogBox *tview.TextView + ValidatorLogBox *tview.TextView // New validator log panel StatusBox *tview.TextView NetworkBox *tview.TextView StarknetStatusBox *tview.TextView @@ -21,6 +22,7 @@ type MonitorApp struct { SystemStatsBox *tview.TextView RPCInfoBox *tview.TextView StatusBar *tview.TextView + NoClientsBox *tview.TextView // Message box when no clients are running // Legacy panels (for backward compatibility during transition) SystemBox *tview.TextView @@ -39,6 +41,7 @@ type MonitorApp struct { ExecutionLogChan chan string ConsensusLogChan chan string JunoLogChan chan string + ValidatorLogChan chan string // New validator log channel NetworkChan chan string StatusChan chan string JunoStatusChan chan string diff --git a/pkg/monitoring/ui.go b/pkg/monitoring/ui.go index 85d141a..c431a5f 100644 --- a/pkg/monitoring/ui.go +++ b/pkg/monitoring/ui.go @@ -41,6 +41,28 @@ func (m *MonitorApp) setupUI() { SetTitle(" Juno (Detecting...) 🌟 "). SetTitleAlign(tview.AlignLeft) + // Create Validator log panel + m.ValidatorLogBox = m.createVibrantPanel("Validator", tcell.ColorTeal) + m.ValidatorLogBox.SetBorder(true). + SetBorderColor(tcell.ColorTeal). + SetTitle(" Validator (Detecting...) 🛡️ "). + SetTitleAlign(tview.AlignLeft) + + // Create "No Clients Running" message box + m.NoClientsBox = m.createVibrantPanel("Status", tcell.ColorYellow) + m.NoClientsBox.SetBorder(true). + SetBorderColor(tcell.ColorYellow). + SetTitle(" System Status ⚠️ "). + SetTitleAlign(tview.AlignCenter) + m.NoClientsBox.SetText("\n\n[yellow]━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n" + + "[white] ⚠️ NO CLIENTS RUNNING ⚠️\n\n" + + "[dim]No Ethereum or Starknet clients are currently active.\n\n" + + "[yellow]To start clients, use:[white]\n" + + " • starknode-kit start\n" + + " • starknode-kit run\n\n" + + "[yellow]━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━[white]"). + SetTextAlign(tview.AlignCenter) + // Create status boxes m.StatusBox = m.createVibrantPanel("L1 Status", tcell.ColorTeal) m.StatusBox.SetText("INITIALIZING...") @@ -56,44 +78,8 @@ func (m *MonitorApp) setupUI() { m.SystemStatsBox = m.createVibrantPanel("System Stats", tcell.ColorTeal) m.RPCInfoBox = m.createVibrantPanel("RPC Info", tcell.ColorTeal) - // Setup main grid: LEFT (60%) for logs, RIGHT (40%) for info panels - m.Grid.SetRows(-1, -1, -1). // 3 rows for the 3 log panels - SetColumns(-3, -2). // LEFT(60%), RIGHT(40%) - SetBorders(false). - SetGap(0, 0) - - // LEFT SIDE - Add log panels directly to main grid - m.Grid.AddItem(m.ExecutionLogBox, 0, 0, 1, 1, 0, 0, false) // Row 0, left col - m.Grid.AddItem(m.ConsensusLogBox, 1, 0, 1, 1, 0, 0, false) // Row 1, left col - m.Grid.AddItem(m.JunoLogBox, 2, 0, 1, 1, 0, 0, false) // Row 2, left col - - // RIGHT SIDE - Create sub-grid for info panels (5 rows total) - rightGrid := tview.NewGrid(). - SetRows(-1, -1, -1, -1, -1). // 5 equal rows - SetColumns(-1). // Single column - SetBorders(false). - SetGap(0, 0) - - // Create status grid for ETH and Starknet status side by side - statusGrid := tview.NewGrid(). - SetRows(-1). // Single row - SetColumns(-1, -1). // 2 equal columns for ETH and Starknet - SetBorders(false). - SetGap(1, 0) // Small gap between status panels - - // Add ETH and Starknet status to the status grid - statusGrid.AddItem(m.StatusBox, 0, 0, 1, 1, 0, 0, false) // ETH Status (left) - statusGrid.AddItem(m.StarknetStatusBox, 0, 1, 1, 1, 0, 0, false) // Starknet Status (right) - - // Add all panels to the right side sub-grid - rightGrid.AddItem(m.NetworkBox, 0, 0, 1, 1, 0, 0, false) // Row 0: Network - rightGrid.AddItem(statusGrid, 1, 0, 1, 1, 0, 0, false) // Row 1: Status grid (ETH + Starknet) - rightGrid.AddItem(m.ChainInfoBox, 2, 0, 1, 1, 0, 0, false) // Row 2: Chain Info - rightGrid.AddItem(m.RPCInfoBox, 3, 0, 1, 1, 0, 0, false) // Row 3: RPC Info - rightGrid.AddItem(m.SystemStatsBox, 4, 0, 1, 1, 0, 0, false) // Row 4: System Stats - - // Add the right side sub-grid to main grid (spans all 3 rows on right) - m.Grid.AddItem(rightGrid, 0, 1, 3, 1, 0, 0, false) // Spans rows 0-2 on right column + // Initial setup with placeholder - will be rebuilt dynamically + m.rebuildDynamicLayout() // Enhanced input handling m.App.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { @@ -156,7 +142,7 @@ func (m *MonitorApp) createVibrantPanel(title string, borderColor tcell.Color) * panel.SetBorderColor(borderColor) // Set TextView specific properties - panel.SetWrap(true) + panel.SetWrap(true).SetWordWrap(true) panel.SetBackgroundColor(tcell.ColorBlack) panel.SetTextColor(tcell.ColorWhite) panel.SetDynamicColors(true) diff --git a/pkg/monitoring/updaters.go b/pkg/monitoring/updaters.go index 826f6fc..0b4a378 100644 --- a/pkg/monitoring/updaters.go +++ b/pkg/monitoring/updaters.go @@ -637,6 +637,168 @@ func (m *MonitorApp) updateJunoLogs(ctx context.Context) { } } +// updateValidatorLogs updates the validator client logs +func (m *MonitorApp) updateValidatorLogs(ctx context.Context) { + ticker := time.NewTicker(3 * time.Second) + defer ticker.Stop() + + // Placeholder validator logs (fallback when no real logs available) + validatorLogs := []string{ + "INFO [12-07|15:32:22.145] Validator initialized address=0x1234...5678", + "INFO [12-07|15:32:23.256] Connected to Juno RPC endpoint=http://localhost:6060", + "INFO [12-07|15:32:24.367] Validator account loaded public_key=0xabcd...ef01", + "INFO [12-07|15:32:25.478] Starting attestation service slot=1234567", + "INFO [12-07|15:32:26.589] Listening for new blocks height=650328", + "INFO [12-07|15:32:27.690] Block attestation submitted block=650329 status=pending", + "INFO [12-07|15:32:28.801] Attestation confirmed block=650329 tx_hash=0x7890...abcd", + "INFO [12-07|15:32:29.912] Validator balance updated balance=1000.50 STRK", + "INFO [12-07|15:32:31.023] New epoch started epoch=1234 validators=150", + "INFO [12-07|15:32:32.134] Proposer duty assigned slot=1234570 block=650330", + } + + var logBuffer []string + logIndex := 0 + var currentClientName string + + for { + select { + case <-ctx.Done(): + return + case <-m.StopChan: + return + case <-ticker.C: + if m.paused { + continue + } + + // Detect if Validator client is running + runningClients := utils.GetRunningClients() + var validatorClient *types.ClientStatus + + for _, client := range runningClients { + if client.Name == "Validator" || client.Name == "StarknetValidator" { + validatorClient = &client + break + } + } + + // Update panel title and logs based on detected client + if validatorClient != nil { + if currentClientName != validatorClient.Name { + // Client changed, reset everything + currentClientName = validatorClient.Name + logIndex = 0 + logBuffer = []string{} + + // Update panel title to show it's running + m.App.QueueUpdateDraw(func() { + m.ValidatorLogBox.SetTitle(" Starknet Validator 🛡️ (Running) ") + }) + } + + // Try to get real logs first from Validator log directory + realLogs := GetLatestLogs("starknet-staking-v2", 10) + if len(realLogs) > 0 && realLogs[0] != "No log files found for starknet-staking-v2" { + // Use real logs from Validator client + var formattedRealLogs []string + for _, logLine := range realLogs { + if strings.TrimSpace(logLine) != "" { + formattedLine := formatLogLines(logLine) + formattedRealLogs = append(formattedRealLogs, formattedLine) + } + } + + content := strings.Join(formattedRealLogs, "\n") + select { + case m.ValidatorLogChan <- content: + default: + // Channel full, skip update + } + } else { + // Fall back to simulated logs if real logs aren't available + if logIndex < len(validatorLogs) { + currentEntry := validatorLogs[logIndex] + + // Dynamic updates for realistic logs + if strings.Contains(currentEntry, "block=") { + // Update block numbers progressively + baseBlock := 650328 + currentBlock := baseBlock + int(time.Now().Unix()%100) + currentEntry = strings.ReplaceAll(currentEntry, "650328", fmt.Sprintf("%d", currentBlock)) + currentEntry = strings.ReplaceAll(currentEntry, "650329", fmt.Sprintf("%d", currentBlock+1)) + currentEntry = strings.ReplaceAll(currentEntry, "650330", fmt.Sprintf("%d", currentBlock+2)) + } + + // Update slot numbers progressively + if strings.Contains(currentEntry, "slot=") { + baseSlot := 1234567 + currentSlot := baseSlot + int(time.Now().Unix()%1000) + currentEntry = strings.ReplaceAll(currentEntry, "1234567", fmt.Sprintf("%d", currentSlot)) + currentEntry = strings.ReplaceAll(currentEntry, "1234570", fmt.Sprintf("%d", currentSlot+3)) + } + + // Update epoch numbers + if strings.Contains(currentEntry, "epoch=") { + baseEpoch := 1234 + currentEpoch := baseEpoch + int(time.Now().Unix()/86400) // Changes daily + currentEntry = strings.ReplaceAll(currentEntry, "1234", fmt.Sprintf("%d", currentEpoch)) + } + + // Update timestamps to current time + if strings.Contains(currentEntry, "15:32:") { + now := time.Now() + timeStr := now.Format("15:04:05") + // Replace the timestamp part + parts := strings.Split(currentEntry, "] ") + if len(parts) >= 2 { + parts[0] = fmt.Sprintf("INFO [12-07|%s.%03d", timeStr, now.Nanosecond()/1000000) + currentEntry = strings.Join(parts, "] ") + } + } + + // Format the log line + formattedLine := formatLogLines(currentEntry) + + // Add to buffer + logBuffer = append(logBuffer, formattedLine) + + // Keep buffer size manageable + if len(logBuffer) > 50 { + logBuffer = logBuffer[len(logBuffer)-45:] + } + + // Send to Validator log channel + content := strings.Join(logBuffer, "\n") + select { + case m.ValidatorLogChan <- content: + default: + // Channel full, skip update + } + + logIndex++ + } else { + // Reset to beginning for continuous simulation + logIndex = 0 + } + } + } else { + // No Validator client running + if currentClientName != "None" { + currentClientName = "None" + m.App.QueueUpdateDraw(func() { + m.ValidatorLogBox.SetTitle(" Validator (Not Running) ❌ ") + }) + + select { + case m.ValidatorLogChan <- "[red]No Validator client detected.[white]\n[yellow]Start Starknet Validator to see live logs.[white]": + default: + } + } + } + } + } +} + // Legacy update methods for backward compatibility func (m *MonitorApp) updateSystemStats(ctx context.Context) { diff --git a/pkg/updater/updater.go b/pkg/updater/updater.go index 8ee21e3..8424265 100644 --- a/pkg/updater/updater.go +++ b/pkg/updater/updater.go @@ -115,7 +115,7 @@ func (u *UpdateChecker) UpdateClient(client string) *UpdateResult { installer := pkg.NewInstaller() clientType := types.GetClientType(client) - // Remove old version (using RemoveClient function if available) + // TODO Remove old version (using RemoveClient function if available) fmt.Printf("Removing old %s installation...\n", client) // Install new version @@ -124,8 +124,7 @@ func (u *UpdateChecker) UpdateClient(client string) *UpdateResult { return result } - // Get new version (placeholder - you'd implement actual version detection) - newVersion, err := versions.FetchOnlineVersion(client) // Placeholder + newVersion, err := versions.FetchOnlineVersion(client) if err != nil { fmt.Println(err) return nil diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 720f476..d03c02f 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -198,6 +198,18 @@ func GetRunningClients() []types.ClientStatus { clients = append(clients, status) } + // Check for Starknet Validator + if validatorInfo := process.GetProcessInfo("starknet-staking-v2"); validatorInfo != nil { + status := types.ClientStatus{ + Name: "Validator", + Status: validatorInfo.Status, + PID: validatorInfo.PID, + Uptime: validatorInfo.Uptime, + Version: versions.GetVersionNumber("starknet-staking-v2"), + } + clients = append(clients, status) + } + return clients } diff --git a/pkg/versions/versions.go b/pkg/versions/versions.go index dffa087..3e3a0bd 100644 --- a/pkg/versions/versions.go +++ b/pkg/versions/versions.go @@ -100,7 +100,7 @@ func GetVersionNumber(client string) string { switch client { case "reth": - versionMatch = regexp.MustCompile(`reth-ethereum-cli Version: (\d+\.\d+\.\d+)`).FindStringSubmatch(versionOutput) + versionMatch = regexp.MustCompile(`Reth Version:\s+(\d+\.\d+\.\d+)`).FindStringSubmatch(versionOutput) case "lighthouse": versionMatch = regexp.MustCompile(`Lighthouse v(\d+\.\d+\.\d+)`).FindStringSubmatch(versionOutput) case "geth":