Compare commits

..

44 Commits

Author SHA1 Message Date
Arunavo Ray
398f00aceb chore: bump version to v2.16.0 2025-06-17 15:16:10 +05:30
Arunavo Ray
50972713a3 refactor: improve version comparison logic in health API 2025-06-17 15:11:05 +05:30
Arunavo Ray
fbf3033455 refactor: remove ConnectionsForm and useMirror hook; update issue mirroring logic for starred repos 2025-06-17 15:06:54 +05:30
Arunavo Ray
cc4d8dabbc refactor: enhance starred repos content selection and improve layout 2025-06-17 14:54:23 +05:30
Arunavo Ray
8bba3d3521 chore: bump version to v2.15.0 2025-06-17 14:37:47 +05:30
Arunavo Ray
be63555e5c refactor: update titles and descriptions in strategyConfig for clarity 2025-06-17 14:34:17 +05:30
Arunavo Ray
32a906369f refactor: remove unused Building2 import in OrganizationConfiguration component 2025-06-17 14:28:49 +05:30
Arunavo Ray
064474fd13 refactor: remove unused tooltip imports in OrganizationStrategy component 2025-06-17 14:27:38 +05:30
Arunavo Ray
2ac933b599 refactor: streamline layout and improve tooltip descriptions in OrganizationConfiguration component 2025-06-17 14:26:52 +05:30
Arunavo Ray
403fe08bae refactor: update icons in GitHubMirrorSettings and OrganizationConfiguration components for improved clarity 2025-06-17 14:16:15 +05:30
Arunavo Ray
23c7ff7349 refactor: enhance layout and flexibility in GitHubConfigForm and GiteaConfigForm components 2025-06-17 14:05:10 +05:30
Arunavo Ray
3169af44cb refactor: improve layout and spacing in GitHubMirrorSettings component 2025-06-17 13:49:53 +05:30
Arunavo Ray
c1d93dbbc6 feat: enhance GitHubMirrorSettings with improved layout and metadata options 2025-06-17 13:43:17 +05:30
Arunavo Ray
047719cde9 feat: add OrganizationConfiguration component and integrate it into GiteaConfigForm 2025-06-17 13:20:03 +05:30
Arunavo Ray
13d4b03541 refactor: simplify OrganizationStrategy component by removing unused imports and details 2025-06-17 13:12:12 +05:30
Arunavo Ray
f07ae220b0 refactor: clean up imports and improve layout in OrganizationStrategy component 2025-06-17 12:59:29 +05:30
Arunavo Ray
01647445f2 Improved layout in larger screens 2025-06-17 12:48:38 +05:30
Arunavo Ray
13cbf86309 Updated Layout 2025-06-17 12:42:32 +05:30
Arunavo Ray
792096d209 Updates to Organisation Strategy Layouts 2025-06-17 11:39:41 +05:30
Arunavo Ray
e94de5c9ca fix: mirror to one org issue 2025-06-17 11:35:09 +05:30
Arunavo Ray
b3f42624d8 chore: bump version to v2.14.0 2025-06-17 10:34:59 +05:30
Arunavo Ray
d79e4fecf4 Updated Docker compose dev 2025-06-17 10:30:33 +05:30
Arunavo Ray
eb78f959c7 Updated Docs 2025-06-17 09:47:46 +05:30
Arunavo Ray
51e536c317 feat: add @radix-ui/react-accordion dependency 2025-06-17 09:35:14 +05:30
Arunavo Ray
7af1f6da17 fix: implement proper mirror strategies for starred and org repos 2025-06-17 09:26:26 +05:30
Arunavo Ray
c7e310b340 updated instructions 2025-06-16 00:35:48 +05:30
Arunavo Ray
23cfa45d89 chore: bump version to v2.13.2 2025-06-16 00:33:28 +05:30
Arunavo Ray
b1346e8c77 Updated Docs and Readme 2025-06-16 00:28:55 +05:30
Arunavo Ray
6e673249dc Docs Design updated 2025-06-16 00:01:45 +05:30
Arunavo Ray
ee801f5d0e chore: bump version to v2.13.1
- Updated package.json version from 2.13.0 to 2.13.1
- Added CHANGELOG.md entry for v2.13.1 with Docker workflow improvements
- Includes Docker Scout migration, CI/CD enhancements, and package updates
2025-06-15 23:06:05 +05:30
Arunavo Ray
caf680d999 fix: add wait step for image availability in Docker build workflow 2025-06-15 15:10:48 +05:30
Arunavo Ray
214599a5fd Dont fail workflow on security issues 2025-06-15 15:03:54 +05:30
Arunavo Ray
9e2285d614 fix: update Docker Scout image references and add wait step for image availability 2025-06-15 14:43:31 +05:30
Arunavo Ray
7f7e510400 fix: resolve Docker Scout image reference issues
- Use specific SHA-based image tags instead of multi-line tags output
- Add separate Docker Scout steps for push vs pull request workflows
- Use local image reference for PR scanning (local://gitea-mirror:scan)
- Optimize PR builds to single platform (linux/amd64) for faster scanning
- Maintain multi-platform builds for production pushes
2025-06-15 14:38:25 +05:30
Arunavo Ray
d1aa8810f7 fix: update Docker Scout action to use the latest version 2025-06-15 14:35:35 +05:30
Arunavo Ray
bfa4b4034c feat: add Docker Hub authentication for Docker Scout
- Add optional Docker Hub login for enhanced vulnerability data
- Use continue-on-error to make Docker Hub auth optional
- Requires DOCKERHUB_USERNAME and DOCKERHUB_TOKEN secrets for full functionality
2025-06-15 14:23:44 +05:30
Arunavo Ray
8fbde95f92 feat: consolidate Docker workflows into comprehensive build, push & security scan
- Merge docker-build.yml and docker-scan.yml into single efficient workflow
- Add comprehensive Docker Scout security analysis with multiple commands
- Include vulnerability scanning, recommendations, and policy evaluation
- Add comparison to latest for pull requests
- Maintain SARIF output for GitHub Security tab integration
- Add proper path-based triggers and scheduled weekly scans
- Remove redundant docker-scan.yml workflow
2025-06-15 14:20:48 +05:30
Arunavo Ray
00fb66baa7 fix: add workflow file to Docker scan triggers to enable testing 2025-06-15 14:15:27 +05:30
Arunavo Ray
5fec1e6a58 fix: update Docker Scout action to specific version v1.18.1 2025-06-15 14:14:16 +05:30
Arunavo Ray
2ec55c6070 Migrate from Trivy to Docker scout 2025-06-15 14:11:45 +05:30
Arunavo Ray
546bda8514 Updated packages 2025-06-15 14:04:12 +05:30
Arunavo Ray
d05847dfe8 Wrong bun verison 2025-06-15 13:52:45 +05:30
Arunavo Ray
6551ea719c fix: update Bun base image and enhance security scanning
- Update Bun from 1.2.14 to 1.2.18 to address CVE-2025-22874
- Pin Trivy action to stable version (0.28.0)
- Add SARIF output for GitHub Security tab integration
- Set ignore-unfixed to false for comprehensive vulnerability detection
- Add security-events permission for uploading scan results
- Include fallback table output on scan failures
2025-06-15 13:48:58 +05:30
Arunavo Ray
ae57b1b320 docs: update CHANGELOG for v2.13.0 release 2025-06-15 13:33:44 +05:30
41 changed files with 3018 additions and 1536 deletions

View File

@@ -0,0 +1,4 @@
Evaluate all the updates being made.
Update CHANGELOG.md
Use the chnages in the git log to determine if its a major, minor or a patch release.
Update the package.json first before you push the tag.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 945 KiB

After

Width:  |  Height:  |  Size: 891 KiB

View File

@@ -1,14 +1,29 @@
name: Build and Push Docker Images
name: Docker Build, Push & Security Scan
on:
push:
branches: [main]
tags: ['v*']
paths:
- 'Dockerfile'
- '.dockerignore'
- 'package.json'
- 'bun.lock*'
- '.github/workflows/docker-build.yml'
pull_request:
paths:
- 'Dockerfile'
- '.dockerignore'
- 'package.json'
- 'bun.lock*'
- '.github/workflows/docker-build.yml'
schedule:
- cron: '0 0 * * 0' # Weekly security scan on Sunday at midnight
env:
REGISTRY: ghcr.io
IMAGE: ${{ github.repository }}
SHA: ${{ github.event.pull_request.head.sha || github.event.after }}
jobs:
docker:
@@ -17,19 +32,37 @@ jobs:
permissions:
contents: write
packages: write
security-events: write
pull-requests: write
steps:
- uses: actions/checkout@v4
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: ${{ env.SHA }}
- uses: docker/setup-buildx-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
driver-opts: network=host
- uses: docker/login-action@v3
- name: Log into registry
uses: docker/login-action@v3
if: github.event_name != 'pull_request'
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# Login to Docker Hub for Docker Scout (optional - provides better vulnerability data)
# Add DOCKERHUB_USERNAME and DOCKERHUB_TOKEN secrets to enable this
- name: Log into Docker Hub
uses: docker/login-action@v3
continue-on-error: true
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
# Extract version from tag if present
- name: Extract version from tag
id: tag_version
@@ -42,12 +75,88 @@ jobs:
echo "No version tag, using 'latest'"
fi
- uses: docker/build-push-action@v5
# Extract metadata for Docker
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE }}
labels: |
org.opencontainers.image.revision=${{ env.SHA }}
tags: |
type=edge,branch=$repo.default_branch
type=semver,pattern=v{{version}}
type=sha,prefix=,suffix=,format=short
type=raw,value=latest,enable={{is_default_branch}}
type=raw,value=${{ steps.tag_version.outputs.VERSION }}
# Build and push Docker image
- name: Build and push Docker image
id: build-and-push
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64,linux/arm64
platforms: ${{ github.event_name == 'pull_request' && 'linux/amd64' || 'linux/amd64,linux/arm64' }}
push: ${{ github.event_name != 'pull_request' }}
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE }}:latest
${{ env.REGISTRY }}/${{ env.IMAGE }}:${{ github.sha }}
${{ env.REGISTRY }}/${{ env.IMAGE }}:${{ steps.tag_version.outputs.VERSION }}
load: ${{ github.event_name == 'pull_request' }}
tags: ${{ github.event_name == 'pull_request' && 'gitea-mirror:scan' || steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
# Wait for image to be available in registry
- name: Wait for image availability
if: github.event_name != 'pull_request'
run: |
echo "Waiting for image to be available in registry..."
sleep 5
# Docker Scout comprehensive security analysis
- name: Docker Scout - Vulnerability Analysis & Recommendations
uses: docker/scout-action@v1
if: github.event_name != 'pull_request'
with:
command: cves,recommendations
image: ${{ env.REGISTRY }}/${{ env.IMAGE }}:latest
sarif-file: scout-results.sarif
summary: true
exit-code: false
only-severities: critical,high
write-comment: true
github-token: ${{ secrets.GITHUB_TOKEN }}
# Docker Scout for Pull Requests (using local image)
- name: Docker Scout - Vulnerability Analysis (PR)
uses: docker/scout-action@v1
if: github.event_name == 'pull_request'
with:
command: cves,recommendations
image: local://gitea-mirror:scan
sarif-file: scout-results.sarif
summary: true
exit-code: false
only-severities: critical,high
write-comment: true
github-token: ${{ secrets.GITHUB_TOKEN }}
# Compare to latest for PRs and pushes
- name: Docker Scout - Compare to Latest
uses: docker/scout-action@v1
if: github.event_name == 'pull_request'
with:
command: compare
image: local://gitea-mirror:scan
to: ${{ env.REGISTRY }}/${{ env.IMAGE }}:latest
ignore-unchanged: true
only-severities: critical,high
write-comment: true
github-token: ${{ secrets.GITHUB_TOKEN }}
# Upload security scan results to GitHub Security tab
- name: Upload Docker Scout scan results to GitHub Security tab
uses: github/codeql-action/upload-sarif@v3
if: always()
continue-on-error: true
with:
sarif_file: scout-results.sarif

View File

@@ -1,57 +0,0 @@
name: Docker Security Scan
on:
push:
branches: [ main ]
paths:
- 'Dockerfile'
- '.dockerignore'
- 'package.json'
- 'bun.lock*'
pull_request:
branches: [ main ]
paths:
- 'Dockerfile'
- '.dockerignore'
- 'package.json'
- 'bun.lock*'
schedule:
- cron: '0 0 * * 0' # Run weekly on Sunday at midnight
permissions:
contents: read
actions: read
jobs:
scan:
name: Scan Docker Image
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
driver-opts: network=host
- name: Build Docker image
uses: docker/build-push-action@v5
with:
context: .
push: false
load: true
tags: gitea-mirror:scan
# Disable GitHub Actions cache for this workflow
no-cache: true
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: gitea-mirror:scan
format: 'table'
exit-code: '1'
ignore-unfixed: true
vuln-type: 'os,library'
severity: 'CRITICAL,HIGH'

View File

@@ -5,6 +5,114 @@ All notable changes to the Gitea Mirror project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [2.16.0] - 2025-06-17
### Added
- Enhanced OrganizationConfiguration component with improved layout and metadata options
- New GitHubMirrorSettings component with better organization and flexibility
- Enhanced starred repositories content selection and improved layout
### Improved
- Enhanced configuration interface layout and spacing across multiple components
- Streamlined OrganizationStrategy component with cleaner imports and better organization
- Improved responsive layout for larger screens in configuration forms
- Better icon usage and clarity in configuration components
- Enhanced tooltip descriptions and component organization
- Improved version comparison logic in health API
- Enhanced issue mirroring logic for starred repositories
### Fixed
- Fixed mirror to single organization functionality
- Resolved organization strategy layout issues
- Cleaned up unused imports across multiple components
### Refactored
- Simplified component structures by removing unused imports and dependencies
- Enhanced layout flexibility in GitHubConfigForm and GiteaConfigForm components
- Improved component organization and code clarity
- Removed ConnectionsForm and useMirror hook for better code organization
## [2.14.0] - 2025-06-17
### Added
- Enhanced UI components with @radix-ui/react-accordion dependency for improved configuration interface
### Fixed
- Mirror strategies now properly route repositories based on selected strategy
- Starred repositories now correctly go to the designated starred repos organization
- Organization routing for single-org and flat-user strategies
### Improved
- Documentation now explains all three mirror strategies (preserve, single-org, flat-user)
- Added detailed mirror strategy configuration guide
- Updated CLAUDE.md with mirror strategy architecture information
- Enhanced Docker Compose development configuration
## [2.13.2] - 2025-06-15
### Improved
- Enhanced documentation design and layout
- Updated README with improved formatting and content
## [2.13.1] - 2025-06-15
### Added
- Docker Hub authentication for Docker Scout security scanning
- Comprehensive Docker workflow consolidation with build, push & security scan
### Improved
- Enhanced CI/CD pipeline reliability with better error handling
- Updated Bun base image to latest version for improved security
- Migrated from Trivy to Docker Scout for more comprehensive security scanning
- Enhanced Docker workflow with wait steps for image availability
### Fixed
- Docker Scout action integration issues and image reference problems
- Workflow reliability improvements with proper error handling
- Security scanning workflow now continues on security issues without failing the build
### Changed
- Updated package dependencies to latest versions
- Consolidated multiple Docker workflows into single comprehensive workflow
- Enhanced security scanning with Docker Scout integration
## [2.13.0] - 2025-06-15
### Added
- Enhanced Configuration Interface with collapsible components and improved organization strategy UI
- Wiki Mirroring Support in configuration settings
- Auto-Save Functionality for all config forms, eliminating manual save buttons
- Live Refresh functionality with configuration status hooks and enhanced UI components
- Enhanced API Config Handling with mapping functions for UI and database structures
- Secure Error Responses with createSecureErrorResponse for consistent error handling
- Automatic Database Cleanup feature with configuration options and API support
- Enhanced Job Recovery with improved database schema and recovery mechanisms
- Fork tags to repository UI and enhanced organization cards with repository breakdown
- Skeleton loaders and better loading state management across the application
### Improved
- Navigation context and component loading states across the application
- Card components alignment and styling consistency
- Error logging and structured error message parsing
- HTTP client standardization across the application
- Database initialization and management processes
- Visual consistency with updated icons and custom logo integration
### Fixed
- Repository mirroring status inconsistencies
- Organizations getting stuck on mirroring status when empty
- JSON parsing errors and improved error handling
- Broken documentation links in README
- Various UI contrast and alignment issues
### Changed
- Migrated testing framework to Bun and updated test configurations
- Implemented graceful shutdown and enhanced job recovery capabilities
- Replaced SiGitea icons with custom logo
- Updated various dependencies for improved stability and performance
## [2.12.0] - 2025-01-27
### Fixed

View File

@@ -79,6 +79,13 @@ export async function POST({ request }: APIContext) {
- Tracks status in database
- Supports scheduled automatic mirroring
6. **Mirror Strategies**: Three ways to organize repositories in Gitea:
- **preserve**: Maintains GitHub structure (default)
- **single-org**: All repos go to one organization
- **flat-user**: All repos go under user account
- Starred repos always go to separate organization (starredReposOrg)
- Routing logic in `getGiteaRepoOwner()` function
### Database Schema (SQLite)
- `users` - User accounts and authentication
- `configs` - GitHub/Gitea connection settings

View File

@@ -1,6 +1,6 @@
# syntax=docker/dockerfile:1.4
FROM oven/bun:1.2.14-alpine AS base
FROM oven/bun:1.2.16-alpine AS base
WORKDIR /app
RUN apk add --no-cache libc6-compat python3 make g++ gcc wget sqlite openssl

View File

@@ -36,9 +36,9 @@ See the [LXC Container Deployment Guide](scripts/README-lxc.md).
## ✨ Features
- 🔁 Sync public, private, or starred GitHub repos to Gitea
- 🏢 Mirror entire organizations with structure preservation
- 🏢 Mirror entire organizations with flexible organization strategies
- 🐞 Optional mirroring of issues and labels
- 🌟 Mirror your starred repositories
- 🌟 Mirror your starred repositories to a dedicated organization
- 🕹️ Modern user interface with toast notifications and smooth experience
- 🧠 Smart filtering and job queue with detailed logs
- 🛠️ Works with personal access tokens (GitHub + Gitea)
@@ -167,36 +167,27 @@ docker compose up -d
See [Docker build documentation](./scripts/README-docker.md) for more details.
##### Using LXC Containers
##### Using LXC Containers (Proxmox VE)
Gitea Mirror offers two deployment options for LXC containers:
**1. Proxmox VE (online, recommended for production)**
For Proxmox VE users, Gitea Mirror can be deployed using the community-maintained script:
```bash
# One-command installation on Proxmox VE
# Uses the community-maintained script by Tobias ([CrazyWolf13](https://github.com/CrazyWolf13))
# at [community-scripts/ProxmoxVE](https://github.com/community-scripts/ProxmoxVE)
bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/gitea-mirror.sh)"
```
**2. Local testing (offline-friendly, works on developer laptops)**
This community script:
- Creates a privileged Alpine Linux LXC container
- Installs Bun runtime environment
- Clones and builds Gitea Mirror
- Configures a systemd service for automatic startup
- Sets up the application to run on port 4321
```bash
# Download the script
curl -fsSL https://raw.githubusercontent.com/arunavo4/gitea-mirror/main/scripts/gitea-mirror-lxc-local.sh -o gitea-mirror-lxc-local.sh
chmod +x gitea-mirror-lxc-local.sh
> [!NOTE]
> The script is maintained by the [Community Scripts for Proxmox VE](https://community-scripts.github.io/ProxmoxVE/) project.
> For more information, visit the [Gitea Mirror script documentation](https://community-scripts.github.io/ProxmoxVE/scripts?id=gitea-mirror).
# Run with your local repo directory
sudo LOCAL_REPO_DIR=~/Development/gitea-mirror ./gitea-mirror-lxc-local.sh
```
Both scripts:
- Set up a privileged Ubuntu 22.04 LXC container
- Install Bun runtime environment
- Build the application
- Configure a systemd service
- Start the service automatically
After installation, access Gitea Mirror at `http://<container-ip>:4321`
The application includes a health check endpoint at `/api/health` for monitoring.
@@ -326,6 +317,28 @@ Key configuration options include:
> [!IMPORTANT]
> **SQLite is the only database required for Gitea Mirror**, handling both data storage and real-time event notifications.
### Mirror Strategies
Gitea Mirror offers three flexible strategies for organizing your repositories in Gitea:
#### 1. **Preserve GitHub Structure** (Default)
- Personal repositories → Your Gitea username
- Organization repositories → Same organization name in Gitea
- Maintains the exact structure from GitHub
#### 2. **Single Organization**
- All repositories → One designated organization
- Simplifies management by consolidating everything
- Requires specifying a destination organization name
#### 3. **Flat User Structure**
- All repositories → Your Gitea user account
- No organizations needed
- Simplest approach for personal use
> [!NOTE]
> **Starred Repositories**: Regardless of the chosen strategy, starred repositories are always mirrored to a separate organization (default: "starred") to keep them organized separately from your own repositories.
## 🚀 Development
### Local Development Setup
@@ -363,13 +376,13 @@ docker run -d \
-e USER_UID=1000 \
-e USER_GID=1000 \
-e GITEA__database__DB_TYPE=sqlite3 \
-e GITEA__database__PATH=/data/gitea.db \
-e GITEA__database__PATH=/data/gitea/gitea.db \
-e GITEA__server__DOMAIN=localhost \
-e GITEA__server__ROOT_URL=http://localhost:3001/ \
-e GITEA__server__SSH_DOMAIN=localhost \
-e GITEA__server__SSH_PORT=2222 \
-e GITEA__server__START_SSH_SERVER=true \
-e GITEA__security__INSTALL_LOCK=true \
-e GITEA__security__INSTALL_LOCK=false \
-e GITEA__service__DISABLE_REGISTRATION=false \
gitea/gitea:latest
```

407
bun.lock
View File

@@ -4,50 +4,53 @@
"": {
"name": "gitea-mirror",
"dependencies": {
"@astrojs/mdx": "^4.2.6",
"@astrojs/node": "^9.2.1",
"@astrojs/react": "^4.2.7",
"@octokit/rest": "^21.1.1",
"@radix-ui/react-avatar": "^1.1.9",
"@radix-ui/react-checkbox": "^1.3.1",
"@astrojs/check": "^0.9.4",
"@astrojs/mdx": "^4.3.0",
"@astrojs/node": "^9.2.2",
"@astrojs/react": "^4.3.0",
"@octokit/rest": "^22.0.0",
"@radix-ui/react-accordion": "^1.2.11",
"@radix-ui/react-avatar": "^1.1.10",
"@radix-ui/react-checkbox": "^1.3.2",
"@radix-ui/react-collapsible": "^1.1.11",
"@radix-ui/react-dialog": "^1.1.13",
"@radix-ui/react-dropdown-menu": "^2.1.14",
"@radix-ui/react-label": "^2.1.6",
"@radix-ui/react-popover": "^1.1.13",
"@radix-ui/react-dialog": "^1.1.14",
"@radix-ui/react-dropdown-menu": "^2.1.15",
"@radix-ui/react-label": "^2.1.7",
"@radix-ui/react-popover": "^1.1.14",
"@radix-ui/react-radio-group": "^1.3.7",
"@radix-ui/react-scroll-area": "^1.2.9",
"@radix-ui/react-select": "^2.2.4",
"@radix-ui/react-select": "^2.2.5",
"@radix-ui/react-separator": "^1.1.7",
"@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-switch": "^1.2.5",
"@radix-ui/react-tabs": "^1.1.12",
"@radix-ui/react-tooltip": "^1.2.6",
"@tailwindcss/vite": "^4.1.7",
"@tanstack/react-virtual": "^3.13.8",
"@radix-ui/react-tooltip": "^1.2.7",
"@tailwindcss/vite": "^4.1.10",
"@tanstack/react-virtual": "^3.13.10",
"@types/canvas-confetti": "^1.9.0",
"@types/react": "^19.1.4",
"@types/react-dom": "^19.1.5",
"astro": "^5.7.13",
"@types/react": "^19.1.8",
"@types/react-dom": "^19.1.6",
"astro": "^5.9.3",
"bcryptjs": "^3.0.2",
"canvas-confetti": "^1.9.3",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
"drizzle-orm": "^0.43.1",
"drizzle-orm": "^0.44.2",
"fuse.js": "^7.1.0",
"jsonwebtoken": "^9.0.2",
"lucide-react": "^0.511.0",
"lucide-react": "^0.515.0",
"next-themes": "^0.4.6",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-icons": "^5.5.0",
"sonner": "^2.0.3",
"tailwind-merge": "^3.3.0",
"tailwindcss": "^4.1.7",
"tw-animate-css": "^1.3.0",
"sonner": "^2.0.5",
"tailwind-merge": "^3.3.1",
"tailwindcss": "^4.1.10",
"tw-animate-css": "^1.3.4",
"typescript": "^5.8.3",
"uuid": "^11.1.0",
"zod": "^3.25.7",
"zod": "^3.25.64",
},
"devDependencies": {
"@testing-library/jest-dom": "^6.6.3",
@@ -55,10 +58,10 @@
"@types/bcryptjs": "^3.0.0",
"@types/jsonwebtoken": "^9.0.9",
"@types/uuid": "^10.0.0",
"@vitejs/plugin-react": "^4.4.1",
"@vitejs/plugin-react": "^4.5.2",
"jsdom": "^26.1.0",
"tsx": "^4.19.4",
"vitest": "^3.1.4",
"tsx": "^4.20.3",
"vitest": "^3.2.3",
},
},
},
@@ -69,10 +72,14 @@
"@asamuzakjp/css-color": ["@asamuzakjp/css-color@3.2.0", "", { "dependencies": { "@csstools/css-calc": "^2.1.3", "@csstools/css-color-parser": "^3.0.9", "@csstools/css-parser-algorithms": "^3.0.4", "@csstools/css-tokenizer": "^3.0.3", "lru-cache": "^10.4.3" } }, "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw=="],
"@astrojs/compiler": ["@astrojs/compiler@2.12.0", "", {}, "sha512-7bCjW6tVDpUurQLeKBUN9tZ5kSv5qYrGmcn0sG0IwacL7isR2ZbyyA3AdZ4uxsuUFOS2SlgReTH7wkxO6zpqWA=="],
"@astrojs/check": ["@astrojs/check@0.9.4", "", { "dependencies": { "@astrojs/language-server": "^2.15.0", "chokidar": "^4.0.1", "kleur": "^4.1.5", "yargs": "^17.7.2" }, "peerDependencies": { "typescript": "^5.0.0" }, "bin": { "astro-check": "dist/bin.js" } }, "sha512-IOheHwCtpUfvogHHsvu0AbeRZEnjJg3MopdLddkJE70mULItS/Vh37BHcI00mcOJcH1vhD3odbpvWokpxam7xA=="],
"@astrojs/compiler": ["@astrojs/compiler@2.12.2", "", {}, "sha512-w2zfvhjNCkNMmMMOn5b0J8+OmUaBL1o40ipMvqcG6NRpdC+lKxmTi48DT8Xw0SzJ3AfmeFLB45zXZXtmbsjcgw=="],
"@astrojs/internal-helpers": ["@astrojs/internal-helpers@0.6.1", "", {}, "sha512-l5Pqf6uZu31aG+3Lv8nl/3s4DbUzdlxTWDof4pEpto6GUJNhhCbelVi9dEyurOVyqaelwmS9oSyOWOENSfgo9A=="],
"@astrojs/language-server": ["@astrojs/language-server@2.15.4", "", { "dependencies": { "@astrojs/compiler": "^2.10.3", "@astrojs/yaml2ts": "^0.2.2", "@jridgewell/sourcemap-codec": "^1.4.15", "@volar/kit": "~2.4.7", "@volar/language-core": "~2.4.7", "@volar/language-server": "~2.4.7", "@volar/language-service": "~2.4.7", "fast-glob": "^3.2.12", "muggle-string": "^0.4.1", "volar-service-css": "0.0.62", "volar-service-emmet": "0.0.62", "volar-service-html": "0.0.62", "volar-service-prettier": "0.0.62", "volar-service-typescript": "0.0.62", "volar-service-typescript-twoslash-queries": "0.0.62", "volar-service-yaml": "0.0.62", "vscode-html-languageservice": "^5.2.0", "vscode-uri": "^3.0.8" }, "peerDependencies": { "prettier": "^3.0.0", "prettier-plugin-astro": ">=0.11.0" }, "optionalPeers": ["prettier", "prettier-plugin-astro"], "bin": { "astro-ls": "bin/nodeServer.js" } }, "sha512-JivzASqTPR2bao9BWsSc/woPHH7OGSGc9aMxXL4U6egVTqBycB3ZHdBJPuOCVtcGLrzdWTosAqVPz1BVoxE0+A=="],
"@astrojs/markdown-remark": ["@astrojs/markdown-remark@6.3.2", "", { "dependencies": { "@astrojs/internal-helpers": "0.6.1", "@astrojs/prism": "3.3.0", "github-slugger": "^2.0.0", "hast-util-from-html": "^2.0.3", "hast-util-to-text": "^4.0.2", "import-meta-resolve": "^4.1.0", "js-yaml": "^4.1.0", "mdast-util-definitions": "^6.0.0", "rehype-raw": "^7.0.0", "rehype-stringify": "^10.0.1", "remark-gfm": "^4.0.1", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.2", "remark-smartypants": "^3.0.2", "shiki": "^3.2.1", "smol-toml": "^1.3.1", "unified": "^11.0.5", "unist-util-remove-position": "^5.0.0", "unist-util-visit": "^5.0.0", "unist-util-visit-parents": "^6.0.1", "vfile": "^6.0.3" } }, "sha512-bO35JbWpVvyKRl7cmSJD822e8YA8ThR/YbUsciWNA7yTcqpIAL2hJDToWP5KcZBWxGT6IOdOkHSXARSNZc4l/Q=="],
"@astrojs/mdx": ["@astrojs/mdx@4.3.0", "", { "dependencies": { "@astrojs/markdown-remark": "6.3.2", "@mdx-js/mdx": "^3.1.0", "acorn": "^8.14.1", "es-module-lexer": "^1.6.0", "estree-util-visit": "^2.0.0", "hast-util-to-html": "^9.0.5", "kleur": "^4.1.5", "rehype-raw": "^7.0.0", "remark-gfm": "^4.0.1", "remark-smartypants": "^3.0.2", "source-map": "^0.7.4", "unist-util-visit": "^5.0.0", "vfile": "^6.0.3" }, "peerDependencies": { "astro": "^5.0.0" } }, "sha512-OGX2KvPeBzjSSKhkCqrUoDMyzFcjKt5nTE5SFw3RdoLf0nrhyCXBQcCyclzWy1+P+XpOamn+p+hm1EhpCRyPxw=="],
@@ -85,11 +92,13 @@
"@astrojs/telemetry": ["@astrojs/telemetry@3.3.0", "", { "dependencies": { "ci-info": "^4.2.0", "debug": "^4.4.0", "dlv": "^1.1.3", "dset": "^3.1.4", "is-docker": "^3.0.0", "is-wsl": "^3.1.0", "which-pm-runs": "^1.1.0" } }, "sha512-UFBgfeldP06qu6khs/yY+q1cDAaArM2/7AEIqQ9Cuvf7B1hNLq0xDrZkct+QoIGyjq56y8IaE2I3CTvG99mlhQ=="],
"@astrojs/yaml2ts": ["@astrojs/yaml2ts@0.2.2", "", { "dependencies": { "yaml": "^2.5.0" } }, "sha512-GOfvSr5Nqy2z5XiwqTouBBpy5FyI6DEe+/g/Mk5am9SjILN1S5fOEvYK0GuWHg98yS/dobP4m8qyqw/URW35fQ=="],
"@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="],
"@babel/compat-data": ["@babel/compat-data@7.27.3", "", {}, "sha512-V42wFfx1ymFte+ecf6iXghnnP8kWTO+ZLXIyZq+1LAXHHvTZdVxicn4yiVYdYMGaCO3tmqub11AorKkv+iodqw=="],
"@babel/core": ["@babel/core@7.27.3", "", { "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.27.3", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.27.3", "@babel/helpers": "^7.27.3", "@babel/parser": "^7.27.3", "@babel/template": "^7.27.2", "@babel/traverse": "^7.27.3", "@babel/types": "^7.27.3", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-hyrN8ivxfvJ4i0fIJuV4EOlV0WDMz5Ui4StRTgVaAvWeiRCilXgwVvxJKtFQ3TKtHgJscB2YiXKGNJuVwhQMtA=="],
"@babel/core": ["@babel/core@7.27.4", "", { "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.27.3", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.27.3", "@babel/helpers": "^7.27.4", "@babel/parser": "^7.27.4", "@babel/template": "^7.27.2", "@babel/traverse": "^7.27.4", "@babel/types": "^7.27.3", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g=="],
"@babel/generator": ["@babel/generator@7.27.3", "", { "dependencies": { "@babel/parser": "^7.27.3", "@babel/types": "^7.27.3", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^3.0.2" } }, "sha512-xnlJYj5zepml8NXtjkG0WquFUv8RskFqyFcVgTBp5k+NaA/8uw/K+OSVf8AMGw5e9HKP2ETd5xpK5MLZQD6b4Q=="],
@@ -107,9 +116,9 @@
"@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="],
"@babel/helpers": ["@babel/helpers@7.27.3", "", { "dependencies": { "@babel/template": "^7.27.2", "@babel/types": "^7.27.3" } }, "sha512-h/eKy9agOya1IGuLaZ9tEUgz+uIRXcbtOhRtUyyMf8JFmn1iT13vnl/IGVWSkdOCG/pC57U4S1jnAabAavTMwg=="],
"@babel/helpers": ["@babel/helpers@7.27.6", "", { "dependencies": { "@babel/template": "^7.27.2", "@babel/types": "^7.27.6" } }, "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug=="],
"@babel/parser": ["@babel/parser@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" }, "bin": "./bin/babel-parser.js" }, "sha512-xyYxRj6+tLNDTWi0KCBcZ9V7yg3/lwL9DWh9Uwh/RIVlIfFidggcgxKX3GCXwCiswwcGRawBKbEg2LG/Y8eJhw=="],
"@babel/parser": ["@babel/parser@7.27.5", "", { "dependencies": { "@babel/types": "^7.27.3" }, "bin": "./bin/babel-parser.js" }, "sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg=="],
"@babel/plugin-transform-react-jsx-self": ["@babel/plugin-transform-react-jsx-self@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw=="],
@@ -119,7 +128,7 @@
"@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="],
"@babel/traverse": ["@babel/traverse@7.27.3", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.27.3", "@babel/parser": "^7.27.3", "@babel/template": "^7.27.2", "@babel/types": "^7.27.3", "debug": "^4.3.1", "globals": "^11.1.0" } }, "sha512-lId/IfN/Ye1CIu8xG7oKBHXd2iNb2aW1ilPszzGcJug6M8RCKfVNcYhpI5+bMvFYjK7lXIM0R+a+6r8xhHp2FQ=="],
"@babel/traverse": ["@babel/traverse@7.27.4", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.27.3", "@babel/parser": "^7.27.4", "@babel/template": "^7.27.2", "@babel/types": "^7.27.3", "debug": "^4.3.1", "globals": "^11.1.0" } }, "sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA=="],
"@babel/types": ["@babel/types@7.27.3", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-Y1GkI4ktrtvmawoSq+4FCVHNryea6uR+qUQy0AGxLSsjCX0nVmkYQMBLHDkXZuo5hGx7eYdnIaslsdBFm7zbUw=="],
@@ -135,6 +144,20 @@
"@csstools/css-tokenizer": ["@csstools/css-tokenizer@3.0.4", "", {}, "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw=="],
"@emmetio/abbreviation": ["@emmetio/abbreviation@2.3.3", "", { "dependencies": { "@emmetio/scanner": "^1.0.4" } }, "sha512-mgv58UrU3rh4YgbE/TzgLQwJ3pFsHHhCLqY20aJq+9comytTXUDNGG/SMtSeMJdkpxgXSXunBGLD8Boka3JyVA=="],
"@emmetio/css-abbreviation": ["@emmetio/css-abbreviation@2.1.8", "", { "dependencies": { "@emmetio/scanner": "^1.0.4" } }, "sha512-s9yjhJ6saOO/uk1V74eifykk2CBYi01STTK3WlXWGOepyKa23ymJ053+DNQjpFcy1ingpaO7AxCcwLvHFY9tuw=="],
"@emmetio/css-parser": ["@emmetio/css-parser@0.4.0", "", { "dependencies": { "@emmetio/stream-reader": "^2.2.0", "@emmetio/stream-reader-utils": "^0.1.0" } }, "sha512-z7wkxRSZgrQHXVzObGkXG+Vmj3uRlpM11oCZ9pbaz0nFejvCDmAiNDpY75+wgXOcffKpj4rzGtwGaZxfJKsJxw=="],
"@emmetio/html-matcher": ["@emmetio/html-matcher@1.3.0", "", { "dependencies": { "@emmetio/scanner": "^1.0.0" } }, "sha512-NTbsvppE5eVyBMuyGfVu2CRrLvo7J4YHb6t9sBFLyY03WYhXET37qA4zOYUjBWFCRHO7pS1B9khERtY0f5JXPQ=="],
"@emmetio/scanner": ["@emmetio/scanner@1.0.4", "", {}, "sha512-IqRuJtQff7YHHBk4G8YZ45uB9BaAGcwQeVzgj/zj8/UdOhtQpEIupUhSk8dys6spFIWVZVeK20CzGEnqR5SbqA=="],
"@emmetio/stream-reader": ["@emmetio/stream-reader@2.2.0", "", {}, "sha512-fXVXEyFA5Yv3M3n8sUGT7+fvecGrZP4k6FnWWMSZVQf69kAq0LLpaBQLGcPR30m3zMmKYhECP4k/ZkzvhEW5kw=="],
"@emmetio/stream-reader-utils": ["@emmetio/stream-reader-utils@0.1.0", "", {}, "sha512-ZsZ2I9Vzso3Ho/pjZFsmmZ++FWeEd/txqybHTm4OgaZzdS8V9V/YYWQwg5TC38Z7uLWUV1vavpLLbjJtKubR1A=="],
"@emnapi/runtime": ["@emnapi/runtime@1.4.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ=="],
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.5", "", { "os": "aix", "cpu": "ppc64" }, "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA=="],
@@ -247,27 +270,33 @@
"@mdx-js/mdx": ["@mdx-js/mdx@3.1.0", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdx": "^2.0.0", "collapse-white-space": "^2.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "estree-util-scope": "^1.0.0", "estree-walker": "^3.0.0", "hast-util-to-jsx-runtime": "^2.0.0", "markdown-extensions": "^2.0.0", "recma-build-jsx": "^1.0.0", "recma-jsx": "^1.0.0", "recma-stringify": "^1.0.0", "rehype-recma": "^1.0.0", "remark-mdx": "^3.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.0.0", "source-map": "^0.7.0", "unified": "^11.0.0", "unist-util-position-from-estree": "^2.0.0", "unist-util-stringify-position": "^4.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-/QxEhPAvGwbQmy1Px8F899L5Uc2KZ6JtXwlCgJmjSTBedwOZkByYcBG4GceIGPXRDsmfxhHazuS+hlOShRLeDw=="],
"@octokit/auth-token": ["@octokit/auth-token@5.1.2", "", {}, "sha512-JcQDsBdg49Yky2w2ld20IHAlwr8d/d8N6NiOXbtuoPCqzbsiJgF633mVUw3x4mo0H5ypataQIX7SFu3yy44Mpw=="],
"@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
"@octokit/core": ["@octokit/core@6.1.5", "", { "dependencies": { "@octokit/auth-token": "^5.0.0", "@octokit/graphql": "^8.2.2", "@octokit/request": "^9.2.3", "@octokit/request-error": "^6.1.8", "@octokit/types": "^14.0.0", "before-after-hook": "^3.0.2", "universal-user-agent": "^7.0.0" } }, "sha512-vvmsN0r7rguA+FySiCsbaTTobSftpIDIpPW81trAmsv9TGxg3YCujAxRYp/Uy8xmDgYCzzgulG62H7KYUFmeIg=="],
"@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="],
"@octokit/endpoint": ["@octokit/endpoint@10.1.4", "", { "dependencies": { "@octokit/types": "^14.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-OlYOlZIsfEVZm5HCSR8aSg02T2lbUWOsCQoPKfTXJwDzcHQBrVBGdGXb89dv2Kw2ToZaRtudp8O3ZIYoaOjKlA=="],
"@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="],
"@octokit/graphql": ["@octokit/graphql@8.2.2", "", { "dependencies": { "@octokit/request": "^9.2.3", "@octokit/types": "^14.0.0", "universal-user-agent": "^7.0.0" } }, "sha512-Yi8hcoqsrXGdt0yObxbebHXFOiUA+2v3n53epuOg1QUgOB6c4XzvisBNVXJSl8RYA5KrDuSL2yq9Qmqe5N0ryA=="],
"@octokit/auth-token": ["@octokit/auth-token@6.0.0", "", {}, "sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w=="],
"@octokit/core": ["@octokit/core@7.0.2", "", { "dependencies": { "@octokit/auth-token": "^6.0.0", "@octokit/graphql": "^9.0.1", "@octokit/request": "^10.0.2", "@octokit/request-error": "^7.0.0", "@octokit/types": "^14.0.0", "before-after-hook": "^4.0.0", "universal-user-agent": "^7.0.0" } }, "sha512-ODsoD39Lq6vR6aBgvjTnA3nZGliknKboc9Gtxr7E4WDNqY24MxANKcuDQSF0jzapvGb3KWOEDrKfve4HoWGK+g=="],
"@octokit/endpoint": ["@octokit/endpoint@11.0.0", "", { "dependencies": { "@octokit/types": "^14.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-hoYicJZaqISMAI3JfaDr1qMNi48OctWuOih1m80bkYow/ayPw6Jj52tqWJ6GEoFTk1gBqfanSoI1iY99Z5+ekQ=="],
"@octokit/graphql": ["@octokit/graphql@9.0.1", "", { "dependencies": { "@octokit/request": "^10.0.2", "@octokit/types": "^14.0.0", "universal-user-agent": "^7.0.0" } }, "sha512-j1nQNU1ZxNFx2ZtKmL4sMrs4egy5h65OMDmSbVyuCzjOcwsHq6EaYjOTGXPQxgfiN8dJ4CriYHk6zF050WEULg=="],
"@octokit/openapi-types": ["@octokit/openapi-types@25.1.0", "", {}, "sha512-idsIggNXUKkk0+BExUn1dQ92sfysJrje03Q0bv0e+KPLrvyqZF8MnBpFz8UNfYDwB3Ie7Z0TByjWfzxt7vseaA=="],
"@octokit/plugin-paginate-rest": ["@octokit/plugin-paginate-rest@11.6.0", "", { "dependencies": { "@octokit/types": "^13.10.0" }, "peerDependencies": { "@octokit/core": ">=6" } }, "sha512-n5KPteiF7pWKgBIBJSk8qzoZWcUkza2O6A0za97pMGVrGfPdltxrfmfF5GucHYvHGZD8BdaZmmHGz5cX/3gdpw=="],
"@octokit/plugin-paginate-rest": ["@octokit/plugin-paginate-rest@13.0.1", "", { "dependencies": { "@octokit/types": "^14.1.0" }, "peerDependencies": { "@octokit/core": ">=6" } }, "sha512-m1KvHlueScy4mQJWvFDCxFBTIdXS0K1SgFGLmqHyX90mZdCIv6gWBbKRhatxRjhGlONuTK/hztYdaqrTXcFZdQ=="],
"@octokit/plugin-request-log": ["@octokit/plugin-request-log@5.3.1", "", { "peerDependencies": { "@octokit/core": ">=6" } }, "sha512-n/lNeCtq+9ofhC15xzmJCNKP2BWTv8Ih2TTy+jatNCCq/gQP/V7rK3fjIfuz0pDWDALO/o/4QY4hyOF6TQQFUw=="],
"@octokit/plugin-request-log": ["@octokit/plugin-request-log@6.0.0", "", { "peerDependencies": { "@octokit/core": ">=6" } }, "sha512-UkOzeEN3W91/eBq9sPZNQ7sUBvYCqYbrrD8gTbBuGtHEuycE4/awMXcYvx6sVYo7LypPhmQwwpUe4Yyu4QZN5Q=="],
"@octokit/plugin-rest-endpoint-methods": ["@octokit/plugin-rest-endpoint-methods@13.5.0", "", { "dependencies": { "@octokit/types": "^13.10.0" }, "peerDependencies": { "@octokit/core": ">=6" } }, "sha512-9Pas60Iv9ejO3WlAX3maE1+38c5nqbJXV5GrncEfkndIpZrJ/WPMRd2xYDcPPEt5yzpxcjw9fWNoPhsSGzqKqw=="],
"@octokit/plugin-rest-endpoint-methods": ["@octokit/plugin-rest-endpoint-methods@16.0.0", "", { "dependencies": { "@octokit/types": "^14.1.0" }, "peerDependencies": { "@octokit/core": ">=6" } }, "sha512-kJVUQk6/dx/gRNLWUnAWKFs1kVPn5O5CYZyssyEoNYaFedqZxsfYs7DwI3d67hGz4qOwaJ1dpm07hOAD1BXx6g=="],
"@octokit/request": ["@octokit/request@9.2.3", "", { "dependencies": { "@octokit/endpoint": "^10.1.4", "@octokit/request-error": "^6.1.8", "@octokit/types": "^14.0.0", "fast-content-type-parse": "^2.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-Ma+pZU8PXLOEYzsWf0cn/gY+ME57Wq8f49WTXA8FMHp2Ps9djKw//xYJ1je8Hm0pR2lU9FUGeJRWOtxq6olt4w=="],
"@octokit/request": ["@octokit/request@10.0.2", "", { "dependencies": { "@octokit/endpoint": "^11.0.0", "@octokit/request-error": "^7.0.0", "@octokit/types": "^14.0.0", "fast-content-type-parse": "^3.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-iYj4SJG/2bbhh+iIpFmG5u49DtJ4lipQ+aPakjL9OKpsGY93wM8w06gvFbEQxcMsZcCvk5th5KkIm2m8o14aWA=="],
"@octokit/request-error": ["@octokit/request-error@6.1.8", "", { "dependencies": { "@octokit/types": "^14.0.0" } }, "sha512-WEi/R0Jmq+IJKydWlKDmryPcmdYSVjL3ekaiEL1L9eo1sUnqMJ+grqmC9cjk7CA7+b2/T397tO5d8YLOH3qYpQ=="],
"@octokit/request-error": ["@octokit/request-error@7.0.0", "", { "dependencies": { "@octokit/types": "^14.0.0" } }, "sha512-KRA7VTGdVyJlh0cP5Tf94hTiYVVqmt2f3I6mnimmaVz4UG3gQV/k4mDJlJv3X67iX6rmN7gSHCF8ssqeMnmhZg=="],
"@octokit/rest": ["@octokit/rest@21.1.1", "", { "dependencies": { "@octokit/core": "^6.1.4", "@octokit/plugin-paginate-rest": "^11.4.2", "@octokit/plugin-request-log": "^5.3.1", "@octokit/plugin-rest-endpoint-methods": "^13.3.0" } }, "sha512-sTQV7va0IUVZcntzy1q3QqPm/r8rWtDCqpRAmb8eXXnKkjoQEtFe3Nt5GTVsHft+R6jJoHeSiVLcgcvhtue/rg=="],
"@octokit/rest": ["@octokit/rest@22.0.0", "", { "dependencies": { "@octokit/core": "^7.0.2", "@octokit/plugin-paginate-rest": "^13.0.1", "@octokit/plugin-request-log": "^6.0.0", "@octokit/plugin-rest-endpoint-methods": "^16.0.0" } }, "sha512-z6tmTu9BTnw51jYGulxrlernpsQYXpui1RK21vmXn8yF5bp6iX16yfTtJYGK5Mh1qDkvDOmp2n8sRMcQmR8jiA=="],
"@octokit/types": ["@octokit/types@14.1.0", "", { "dependencies": { "@octokit/openapi-types": "^25.1.0" } }, "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g=="],
@@ -277,6 +306,8 @@
"@radix-ui/primitive": ["@radix-ui/primitive@1.1.2", "", {}, "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA=="],
"@radix-ui/react-accordion": ["@radix-ui/react-accordion@1.2.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-collapsible": "1.1.11", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-l3W5D54emV2ues7jjeG1xcyN7S3jnK3zE2zHqgn0CmMsy9lNJwmgcrmaxS+7ipw15FAivzKNzH3d5EcGoFKw0A=="],
"@radix-ui/react-arrow": ["@radix-ui/react-arrow@1.1.7", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w=="],
"@radix-ui/react-avatar": ["@radix-ui/react-avatar@1.1.10", "", { "dependencies": { "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-is-hydrated": "0.1.0", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-V8piFfWapM5OmNCXTzVQY+E1rDa53zY+MQ4Y7356v4fFz6vqCyUtIz2rUD44ZEdwg78/jKmMJHj07+C/Z/rcog=="],
@@ -359,7 +390,7 @@
"@radix-ui/rect": ["@radix-ui/rect@1.1.1", "", {}, "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw=="],
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.9", "", {}, "sha512-e9MeMtVWo186sgvFFJOPGy7/d2j2mZhLJIdVW0C/xDluuOvymEATqz6zKsP0ZmXGzQtqlyjz5sC1sYQUoJG98w=="],
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.11", "", {}, "sha512-L/gAA/hyCSuzTF1ftlzUSI/IKr2POHsv1Dd78GfqkR83KMNuswWD61JxGV2L7nRwBBBSDr6R1gCkdTmoN7W4ag=="],
"@rollup/pluginutils": ["@rollup/pluginutils@5.1.4", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ=="],
@@ -419,39 +450,39 @@
"@swc/helpers": ["@swc/helpers@0.5.17", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A=="],
"@tailwindcss/node": ["@tailwindcss/node@4.1.7", "", { "dependencies": { "@ampproject/remapping": "^2.3.0", "enhanced-resolve": "^5.18.1", "jiti": "^2.4.2", "lightningcss": "1.30.1", "magic-string": "^0.30.17", "source-map-js": "^1.2.1", "tailwindcss": "4.1.7" } }, "sha512-9rsOpdY9idRI2NH6CL4wORFY0+Q6fnx9XP9Ju+iq/0wJwGD5IByIgFmwVbyy4ymuyprj8Qh4ErxMKTUL4uNh3g=="],
"@tailwindcss/node": ["@tailwindcss/node@4.1.10", "", { "dependencies": { "@ampproject/remapping": "^2.3.0", "enhanced-resolve": "^5.18.1", "jiti": "^2.4.2", "lightningcss": "1.30.1", "magic-string": "^0.30.17", "source-map-js": "^1.2.1", "tailwindcss": "4.1.10" } }, "sha512-2ACf1znY5fpRBwRhMgj9ZXvb2XZW8qs+oTfotJ2C5xR0/WNL7UHZ7zXl6s+rUqedL1mNi+0O+WQr5awGowS3PQ=="],
"@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.7", "", { "dependencies": { "detect-libc": "^2.0.4", "tar": "^7.4.3" }, "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.7", "@tailwindcss/oxide-darwin-arm64": "4.1.7", "@tailwindcss/oxide-darwin-x64": "4.1.7", "@tailwindcss/oxide-freebsd-x64": "4.1.7", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.7", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.7", "@tailwindcss/oxide-linux-arm64-musl": "4.1.7", "@tailwindcss/oxide-linux-x64-gnu": "4.1.7", "@tailwindcss/oxide-linux-x64-musl": "4.1.7", "@tailwindcss/oxide-wasm32-wasi": "4.1.7", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.7", "@tailwindcss/oxide-win32-x64-msvc": "4.1.7" } }, "sha512-5SF95Ctm9DFiUyjUPnDGkoKItPX/k+xifcQhcqX5RA85m50jw1pT/KzjdvlqxRja45Y52nR4MR9fD1JYd7f8NQ=="],
"@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.10", "", { "dependencies": { "detect-libc": "^2.0.4", "tar": "^7.4.3" }, "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.10", "@tailwindcss/oxide-darwin-arm64": "4.1.10", "@tailwindcss/oxide-darwin-x64": "4.1.10", "@tailwindcss/oxide-freebsd-x64": "4.1.10", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.10", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.10", "@tailwindcss/oxide-linux-arm64-musl": "4.1.10", "@tailwindcss/oxide-linux-x64-gnu": "4.1.10", "@tailwindcss/oxide-linux-x64-musl": "4.1.10", "@tailwindcss/oxide-wasm32-wasi": "4.1.10", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.10", "@tailwindcss/oxide-win32-x64-msvc": "4.1.10" } }, "sha512-v0C43s7Pjw+B9w21htrQwuFObSkio2aV/qPx/mhrRldbqxbWJK6KizM+q7BF1/1CmuLqZqX3CeYF7s7P9fbA8Q=="],
"@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.1.7", "", { "os": "android", "cpu": "arm64" }, "sha512-IWA410JZ8fF7kACus6BrUwY2Z1t1hm0+ZWNEzykKmMNM09wQooOcN/VXr0p/WJdtHZ90PvJf2AIBS/Ceqx1emg=="],
"@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.1.10", "", { "os": "android", "cpu": "arm64" }, "sha512-VGLazCoRQ7rtsCzThaI1UyDu/XRYVyH4/EWiaSX6tFglE+xZB5cvtC5Omt0OQ+FfiIVP98su16jDVHDEIuH4iQ=="],
"@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.1.7", "", { "os": "darwin", "cpu": "arm64" }, "sha512-81jUw9To7fimGGkuJ2W5h3/oGonTOZKZ8C2ghm/TTxbwvfSiFSDPd6/A/KE2N7Jp4mv3Ps9OFqg2fEKgZFfsvg=="],
"@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.1.10", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ZIFqvR1irX2yNjWJzKCqTCcHZbgkSkSkZKbRM3BPzhDL/18idA8uWCoopYA2CSDdSGFlDAxYdU2yBHwAwx8euQ=="],
"@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.1.7", "", { "os": "darwin", "cpu": "x64" }, "sha512-q77rWjEyGHV4PdDBtrzO0tgBBPlQWKY7wZK0cUok/HaGgbNKecegNxCGikuPJn5wFAlIywC3v+WMBt0PEBtwGw=="],
"@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.1.10", "", { "os": "darwin", "cpu": "x64" }, "sha512-eCA4zbIhWUFDXoamNztmS0MjXHSEJYlvATzWnRiTqJkcUteSjO94PoRHJy1Xbwp9bptjeIxxBHh+zBWFhttbrQ=="],
"@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.1.7", "", { "os": "freebsd", "cpu": "x64" }, "sha512-RfmdbbK6G6ptgF4qqbzoxmH+PKfP4KSVs7SRlTwcbRgBwezJkAO3Qta/7gDy10Q2DcUVkKxFLXUQO6J3CRvBGw=="],
"@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.1.10", "", { "os": "freebsd", "cpu": "x64" }, "sha512-8/392Xu12R0cc93DpiJvNpJ4wYVSiciUlkiOHOSOQNH3adq9Gi/dtySK7dVQjXIOzlpSHjeCL89RUUI8/GTI6g=="],
"@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.1.7", "", { "os": "linux", "cpu": "arm" }, "sha512-OZqsGvpwOa13lVd1z6JVwQXadEobmesxQ4AxhrwRiPuE04quvZHWn/LnihMg7/XkN+dTioXp/VMu/p6A5eZP3g=="],
"@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.1.10", "", { "os": "linux", "cpu": "arm" }, "sha512-t9rhmLT6EqeuPT+MXhWhlRYIMSfh5LZ6kBrC4FS6/+M1yXwfCtp24UumgCWOAJVyjQwG+lYva6wWZxrfvB+NhQ=="],
"@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.1.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-voMvBTnJSfKecJxGkoeAyW/2XRToLZ227LxswLAwKY7YslG/Xkw9/tJNH+3IVh5bdYzYE7DfiaPbRkSHFxY1xA=="],
"@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.1.10", "", { "os": "linux", "cpu": "arm64" }, "sha512-3oWrlNlxLRxXejQ8zImzrVLuZ/9Z2SeKoLhtCu0hpo38hTO2iL86eFOu4sVR8cZc6n3z7eRXXqtHJECa6mFOvA=="],
"@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.1.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-PjGuNNmJeKHnP58M7XyjJyla8LPo+RmwHQpBI+W/OxqrwojyuCQ+GUtygu7jUqTEexejZHr/z3nBc/gTiXBj4A=="],
"@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.1.10", "", { "os": "linux", "cpu": "arm64" }, "sha512-saScU0cmWvg/Ez4gUmQWr9pvY9Kssxt+Xenfx1LG7LmqjcrvBnw4r9VjkFcqmbBb7GCBwYNcZi9X3/oMda9sqQ=="],
"@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.1.7", "", { "os": "linux", "cpu": "x64" }, "sha512-HMs+Va+ZR3gC3mLZE00gXxtBo3JoSQxtu9lobbZd+DmfkIxR54NO7Z+UQNPsa0P/ITn1TevtFxXTpsRU7qEvWg=="],
"@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.1.10", "", { "os": "linux", "cpu": "x64" }, "sha512-/G3ao/ybV9YEEgAXeEg28dyH6gs1QG8tvdN9c2MNZdUXYBaIY/Gx0N6RlJzfLy/7Nkdok4kaxKPHKJUlAaoTdA=="],
"@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.1.7", "", { "os": "linux", "cpu": "x64" }, "sha512-MHZ6jyNlutdHH8rd+YTdr3QbXrHXqwIhHw9e7yXEBcQdluGwhpQY2Eku8UZK6ReLaWtQ4gijIv5QoM5eE+qlsA=="],
"@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.1.10", "", { "os": "linux", "cpu": "x64" }, "sha512-LNr7X8fTiKGRtQGOerSayc2pWJp/9ptRYAa4G+U+cjw9kJZvkopav1AQc5HHD+U364f71tZv6XamaHKgrIoVzA=="],
"@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.1.7", "", { "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@emnapi/wasi-threads": "^1.0.2", "@napi-rs/wasm-runtime": "^0.2.9", "@tybys/wasm-util": "^0.9.0", "tslib": "^2.8.0" }, "cpu": "none" }, "sha512-ANaSKt74ZRzE2TvJmUcbFQ8zS201cIPxUDm5qez5rLEwWkie2SkGtA4P+GPTj+u8N6JbPrC8MtY8RmJA35Oo+A=="],
"@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.1.10", "", { "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@emnapi/wasi-threads": "^1.0.2", "@napi-rs/wasm-runtime": "^0.2.10", "@tybys/wasm-util": "^0.9.0", "tslib": "^2.8.0" }, "cpu": "none" }, "sha512-d6ekQpopFQJAcIK2i7ZzWOYGZ+A6NzzvQ3ozBvWFdeyqfOZdYHU66g5yr+/HC4ipP1ZgWsqa80+ISNILk+ae/Q=="],
"@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.1.7", "", { "os": "win32", "cpu": "arm64" }, "sha512-HUiSiXQ9gLJBAPCMVRk2RT1ZrBjto7WvqsPBwUrNK2BcdSxMnk19h4pjZjI7zgPhDxlAbJSumTC4ljeA9y0tEw=="],
"@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.1.10", "", { "os": "win32", "cpu": "arm64" }, "sha512-i1Iwg9gRbwNVOCYmnigWCCgow8nDWSFmeTUU5nbNx3rqbe4p0kRbEqLwLJbYZKmSSp23g4N6rCDmm7OuPBXhDA=="],
"@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.7", "", { "os": "win32", "cpu": "x64" }, "sha512-rYHGmvoHiLJ8hWucSfSOEmdCBIGZIq7SpkPRSqLsH2Ab2YUNgKeAPT1Fi2cx3+hnYOrAb0jp9cRyode3bBW4mQ=="],
"@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.10", "", { "os": "win32", "cpu": "x64" }, "sha512-sGiJTjcBSfGq2DVRtaSljq5ZgZS2SDHSIfhOylkBvHVjwOsodBhnb3HdmiKkVuUGKD0I7G63abMOVaskj1KpOA=="],
"@tailwindcss/vite": ["@tailwindcss/vite@4.1.7", "", { "dependencies": { "@tailwindcss/node": "4.1.7", "@tailwindcss/oxide": "4.1.7", "tailwindcss": "4.1.7" }, "peerDependencies": { "vite": "^5.2.0 || ^6" } }, "sha512-tYa2fO3zDe41I7WqijyVbRd8oWT0aEID1Eokz5hMT6wShLIHj3yvwj9XbfuloHP9glZ6H+aG2AN/+ZrxJ1Y5RQ=="],
"@tailwindcss/vite": ["@tailwindcss/vite@4.1.10", "", { "dependencies": { "@tailwindcss/node": "4.1.10", "@tailwindcss/oxide": "4.1.10", "tailwindcss": "4.1.10" }, "peerDependencies": { "vite": "^5.2.0 || ^6" } }, "sha512-QWnD5HDY2IADv+vYR82lOhqOlS1jSCUUAmfem52cXAhRTKxpDh3ARX8TTXJTCCO7Rv7cD2Nlekabv02bwP3a2A=="],
"@tanstack/react-virtual": ["@tanstack/react-virtual@3.13.9", "", { "dependencies": { "@tanstack/virtual-core": "3.13.9" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-SPWC8kwG/dWBf7Py7cfheAPOxuvIv4fFQ54PdmYbg7CpXfsKxkucak43Q0qKsxVthhUJQ1A7CIMAIplq4BjVwA=="],
"@tanstack/react-virtual": ["@tanstack/react-virtual@3.13.10", "", { "dependencies": { "@tanstack/virtual-core": "3.13.10" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-nvrzk4E9mWB4124YdJ7/yzwou7IfHxlSef6ugCFcBfRmsnsma3heciiiV97sBNxyc3VuwtZvmwXd0aB5BpucVw=="],
"@tanstack/virtual-core": ["@tanstack/virtual-core@3.13.9", "", {}, "sha512-3jztt0jpaoJO5TARe2WIHC1UQC3VMLAFUW5mmMo0yrkwtDB2AQP0+sh10BVUpWrnvHjSLvzFizydtEGLCJKFoQ=="],
"@tanstack/virtual-core": ["@tanstack/virtual-core@3.13.10", "", {}, "sha512-sPEDhXREou5HyZYqSWIqdU580rsF6FGeN7vpzijmP3KTiOGjOMZASz4Y6+QKjiFQwhWrR58OP8izYaNGVxvViA=="],
"@testing-library/dom": ["@testing-library/dom@10.4.0", "", { "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", "@types/aria-query": "^5.0.1", "aria-query": "5.3.0", "chalk": "^4.1.0", "dom-accessibility-api": "^0.5.9", "lz-string": "^1.5.0", "pretty-format": "^27.0.2" } }, "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ=="],
@@ -473,8 +504,12 @@
"@types/canvas-confetti": ["@types/canvas-confetti@1.9.0", "", {}, "sha512-aBGj/dULrimR1XDZLtG9JwxX1b4HPRF6CX9Yfwh3NvstZEm1ZL7RBnel4keCPSqs1ANRu1u2Aoz9R+VmtjYuTg=="],
"@types/chai": ["@types/chai@5.2.2", "", { "dependencies": { "@types/deep-eql": "*" } }, "sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg=="],
"@types/debug": ["@types/debug@4.1.12", "", { "dependencies": { "@types/ms": "*" } }, "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ=="],
"@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="],
"@types/estree": ["@types/estree@1.0.7", "", {}, "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ=="],
"@types/estree-jsx": ["@types/estree-jsx@1.0.5", "", { "dependencies": { "@types/estree": "*" } }, "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg=="],
@@ -495,9 +530,9 @@
"@types/node": ["@types/node@22.15.23", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-7Ec1zaFPF4RJ0eXu1YT/xgiebqwqoJz8rYPDi/O2BcZ++Wpt0Kq9cl0eg6NN6bYbPnR67ZLo7St5Q3UK0SnARw=="],
"@types/react": ["@types/react@19.1.6", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-JeG0rEWak0N6Itr6QUx+X60uQmN+5t3j9r/OVDtWzFXKaj6kD1BwJzOksD0FF6iWxZlbE1kB0q9vtnU2ekqa1Q=="],
"@types/react": ["@types/react@19.1.8", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g=="],
"@types/react-dom": ["@types/react-dom@19.1.5", "", { "peerDependencies": { "@types/react": "^19.0.0" } }, "sha512-CMCjrWucUBZvohgZxkjd6S9h0nZxXjzus6yDfUb+xLxYM7VvjKNH1tQrE9GWLql1XoOP4/Ds3bwFqShHUYraGg=="],
"@types/react-dom": ["@types/react-dom@19.1.6", "", { "peerDependencies": { "@types/react": "^19.0.0" } }, "sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw=="],
"@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
@@ -505,21 +540,37 @@
"@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="],
"@vitejs/plugin-react": ["@vitejs/plugin-react@4.5.0", "", { "dependencies": { "@babel/core": "^7.26.10", "@babel/plugin-transform-react-jsx-self": "^7.25.9", "@babel/plugin-transform-react-jsx-source": "^7.25.9", "@rolldown/pluginutils": "1.0.0-beta.9", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0" } }, "sha512-JuLWaEqypaJmOJPLWwO335Ig6jSgC1FTONCWAxnqcQthLTK/Yc9aH6hr9z/87xciejbQcnP3GnA1FWUSWeXaeg=="],
"@vitejs/plugin-react": ["@vitejs/plugin-react@4.5.2", "", { "dependencies": { "@babel/core": "^7.27.4", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.11", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" } }, "sha512-QNVT3/Lxx99nMQWJWF7K4N6apUEuT0KlZA3mx/mVaoGj3smm/8rc8ezz15J1pcbcjDK0V15rpHetVfya08r76Q=="],
"@vitest/expect": ["@vitest/expect@3.1.4", "", { "dependencies": { "@vitest/spy": "3.1.4", "@vitest/utils": "3.1.4", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" } }, "sha512-xkD/ljeliyaClDYqHPNCiJ0plY5YIcM0OlRiZizLhlPmpXWpxnGMyTZXOHFhFeG7w9P5PBeL4IdtJ/HeQwTbQA=="],
"@vitest/expect": ["@vitest/expect@3.2.3", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/spy": "3.2.3", "@vitest/utils": "3.2.3", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" } }, "sha512-W2RH2TPWVHA1o7UmaFKISPvdicFJH+mjykctJFoAkUw+SPTJTGjUNdKscFBrqM7IPnCVu6zihtKYa7TkZS1dkQ=="],
"@vitest/mocker": ["@vitest/mocker@3.1.4", "", { "dependencies": { "@vitest/spy": "3.1.4", "estree-walker": "^3.0.3", "magic-string": "^0.30.17" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^5.0.0 || ^6.0.0" }, "optionalPeers": ["msw", "vite"] }, "sha512-8IJ3CvwtSw/EFXqWFL8aCMu+YyYXG2WUSrQbViOZkWTKTVicVwZ/YiEZDSqD00kX+v/+W+OnxhNWoeVKorHygA=="],
"@vitest/mocker": ["@vitest/mocker@3.2.3", "", { "dependencies": { "@vitest/spy": "3.2.3", "estree-walker": "^3.0.3", "magic-string": "^0.30.17" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" }, "optionalPeers": ["msw", "vite"] }, "sha512-cP6fIun+Zx8he4rbWvi+Oya6goKQDZK+Yq4hhlggwQBbrlOQ4qtZ+G4nxB6ZnzI9lyIb+JnvyiJnPC2AGbKSPA=="],
"@vitest/pretty-format": ["@vitest/pretty-format@3.1.4", "", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-cqv9H9GvAEoTaoq+cYqUTCGscUjKqlJZC7PRwY5FMySVj5J+xOm1KQcCiYHJOEzOKRUhLH4R2pTwvFlWCEScsg=="],
"@vitest/pretty-format": ["@vitest/pretty-format@3.2.3", "", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-yFglXGkr9hW/yEXngO+IKMhP0jxyFw2/qys/CK4fFUZnSltD+MU7dVYGrH8rvPcK/O6feXQA+EU33gjaBBbAng=="],
"@vitest/runner": ["@vitest/runner@3.1.4", "", { "dependencies": { "@vitest/utils": "3.1.4", "pathe": "^2.0.3" } }, "sha512-djTeF1/vt985I/wpKVFBMWUlk/I7mb5hmD5oP8K9ACRmVXgKTae3TUOtXAEBfslNKPzUQvnKhNd34nnRSYgLNQ=="],
"@vitest/runner": ["@vitest/runner@3.2.3", "", { "dependencies": { "@vitest/utils": "3.2.3", "pathe": "^2.0.3", "strip-literal": "^3.0.0" } }, "sha512-83HWYisT3IpMaU9LN+VN+/nLHVBCSIUKJzGxC5RWUOsK1h3USg7ojL+UXQR3b4o4UBIWCYdD2fxuzM7PQQ1u8w=="],
"@vitest/snapshot": ["@vitest/snapshot@3.1.4", "", { "dependencies": { "@vitest/pretty-format": "3.1.4", "magic-string": "^0.30.17", "pathe": "^2.0.3" } }, "sha512-JPHf68DvuO7vilmvwdPr9TS0SuuIzHvxeaCkxYcCD4jTk67XwL45ZhEHFKIuCm8CYstgI6LZ4XbwD6ANrwMpFg=="],
"@vitest/snapshot": ["@vitest/snapshot@3.2.3", "", { "dependencies": { "@vitest/pretty-format": "3.2.3", "magic-string": "^0.30.17", "pathe": "^2.0.3" } }, "sha512-9gIVWx2+tysDqUmmM1L0hwadyumqssOL1r8KJipwLx5JVYyxvVRfxvMq7DaWbZZsCqZnu/dZedaZQh4iYTtneA=="],
"@vitest/spy": ["@vitest/spy@3.1.4", "", { "dependencies": { "tinyspy": "^3.0.2" } }, "sha512-Xg1bXhu+vtPXIodYN369M86K8shGLouNjoVI78g8iAq2rFoHFdajNvJJ5A/9bPMFcfQqdaCpOgWKEoMQg/s0Yg=="],
"@vitest/spy": ["@vitest/spy@3.2.3", "", { "dependencies": { "tinyspy": "^4.0.3" } }, "sha512-JHu9Wl+7bf6FEejTCREy+DmgWe+rQKbK+y32C/k5f4TBIAlijhJbRBIRIOCEpVevgRsCQR2iHRUH2/qKVM/plw=="],
"@vitest/utils": ["@vitest/utils@3.1.4", "", { "dependencies": { "@vitest/pretty-format": "3.1.4", "loupe": "^3.1.3", "tinyrainbow": "^2.0.0" } }, "sha512-yriMuO1cfFhmiGc8ataN51+9ooHRuURdfAZfwFd3usWynjzpLslZdYnRegTv32qdgtJTsj15FoeZe2g15fY1gg=="],
"@vitest/utils": ["@vitest/utils@3.2.3", "", { "dependencies": { "@vitest/pretty-format": "3.2.3", "loupe": "^3.1.3", "tinyrainbow": "^2.0.0" } }, "sha512-4zFBCU5Pf+4Z6v+rwnZ1HU1yzOKKvDkMXZrymE2PBlbjKJRlrOxbvpfPSvJTGRIwGoahaOGvp+kbCoxifhzJ1Q=="],
"@volar/kit": ["@volar/kit@2.4.14", "", { "dependencies": { "@volar/language-service": "2.4.14", "@volar/typescript": "2.4.14", "typesafe-path": "^0.2.2", "vscode-languageserver-textdocument": "^1.0.11", "vscode-uri": "^3.0.8" }, "peerDependencies": { "typescript": "*" } }, "sha512-kBcmHjEodtmYGJELHePZd2JdeYm4ZGOd9F/pQ1YETYIzAwy4Z491EkJ1nRSo/GTxwKt0XYwYA/dHSEgXecVHRA=="],
"@volar/language-core": ["@volar/language-core@2.4.14", "", { "dependencies": { "@volar/source-map": "2.4.14" } }, "sha512-X6beusV0DvuVseaOEy7GoagS4rYHgDHnTrdOj5jeUb49fW5ceQyP9Ej5rBhqgz2wJggl+2fDbbojq1XKaxDi6w=="],
"@volar/language-server": ["@volar/language-server@2.4.14", "", { "dependencies": { "@volar/language-core": "2.4.14", "@volar/language-service": "2.4.14", "@volar/typescript": "2.4.14", "path-browserify": "^1.0.1", "request-light": "^0.7.0", "vscode-languageserver": "^9.0.1", "vscode-languageserver-protocol": "^3.17.5", "vscode-languageserver-textdocument": "^1.0.11", "vscode-uri": "^3.0.8" } }, "sha512-P3mGbQbW0v40UYBnb3DAaNtRYx6/MGOVKzdOWmBCGwjUkCR2xBkGrCFt05XnPDwFS/cTWDh2U6Mc9lpZ8Aecfw=="],
"@volar/language-service": ["@volar/language-service@2.4.14", "", { "dependencies": { "@volar/language-core": "2.4.14", "vscode-languageserver-protocol": "^3.17.5", "vscode-languageserver-textdocument": "^1.0.11", "vscode-uri": "^3.0.8" } }, "sha512-vNC3823EJohdzLTyjZoCMPwoWCfINB5emusniCkW5CGoGHQov4VVmT6yI5ncgP/NpgAIUv2NEkJooXvLHA4VeQ=="],
"@volar/source-map": ["@volar/source-map@2.4.14", "", {}, "sha512-5TeKKMh7Sfxo8021cJfmBzcjfY1SsXsPMMjMvjY7ivesdnybqqS+GxGAoXHAOUawQTwtdUxgP65Im+dEmvWtYQ=="],
"@volar/typescript": ["@volar/typescript@2.4.14", "", { "dependencies": { "@volar/language-core": "2.4.14", "path-browserify": "^1.0.1", "vscode-uri": "^3.0.8" } }, "sha512-p8Z6f/bZM3/HyCdRNFZOEEzts51uV8WHeN8Tnfnm2EBv6FDB2TQLzfVx7aJvnl8ofKAOnS64B2O8bImBFaauRw=="],
"@vscode/emmet-helper": ["@vscode/emmet-helper@2.11.0", "", { "dependencies": { "emmet": "^2.4.3", "jsonc-parser": "^2.3.0", "vscode-languageserver-textdocument": "^1.0.1", "vscode-languageserver-types": "^3.15.1", "vscode-uri": "^3.0.8" } }, "sha512-QLxjQR3imPZPQltfbWRnHU6JecWTF1QSWhx3GAKQpslx7y3Dp6sIIXhKjiUJ/BR9FX8PVthjr9PD6pNwOJfAzw=="],
"@vscode/l10n": ["@vscode/l10n@0.0.18", "", {}, "sha512-KYSIHVmslkaCDyw013pphY+d7x1qV8IZupYfeIfzNA+nsaWHbn5uPuQRvdRFsa9zFzGeudPuoGoZ1Op4jrJXIQ=="],
"acorn": ["acorn@8.14.1", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg=="],
@@ -527,6 +578,8 @@
"agent-base": ["agent-base@7.1.3", "", {}, "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw=="],
"ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="],
"ansi-align": ["ansi-align@3.0.1", "", { "dependencies": { "string-width": "^4.1.0" } }, "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w=="],
"ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
@@ -547,7 +600,7 @@
"astring": ["astring@1.9.0", "", { "bin": { "astring": "bin/astring" } }, "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg=="],
"astro": ["astro@5.8.0", "", { "dependencies": { "@astrojs/compiler": "^2.11.0", "@astrojs/internal-helpers": "0.6.1", "@astrojs/markdown-remark": "6.3.2", "@astrojs/telemetry": "3.3.0", "@capsizecss/unpack": "^2.4.0", "@oslojs/encoding": "^1.1.0", "@rollup/pluginutils": "^5.1.4", "acorn": "^8.14.1", "aria-query": "^5.3.2", "axobject-query": "^4.1.0", "boxen": "8.0.1", "ci-info": "^4.2.0", "clsx": "^2.1.1", "common-ancestor-path": "^1.0.1", "cookie": "^1.0.2", "cssesc": "^3.0.0", "debug": "^4.4.0", "deterministic-object-hash": "^2.0.2", "devalue": "^5.1.1", "diff": "^5.2.0", "dlv": "^1.1.3", "dset": "^3.1.4", "es-module-lexer": "^1.6.0", "esbuild": "^0.25.0", "estree-walker": "^3.0.3", "flattie": "^1.1.1", "fontace": "~0.3.0", "github-slugger": "^2.0.0", "html-escaper": "3.0.3", "http-cache-semantics": "^4.1.1", "import-meta-resolve": "^4.1.0", "js-yaml": "^4.1.0", "kleur": "^4.1.5", "magic-string": "^0.30.17", "magicast": "^0.3.5", "mrmime": "^2.0.1", "neotraverse": "^0.6.18", "p-limit": "^6.2.0", "p-queue": "^8.1.0", "package-manager-detector": "^1.1.0", "picomatch": "^4.0.2", "prompts": "^2.4.2", "rehype": "^13.0.2", "semver": "^7.7.1", "shiki": "^3.2.1", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.12", "tsconfck": "^3.1.5", "ultrahtml": "^1.6.0", "unifont": "~0.5.0", "unist-util-visit": "^5.0.0", "unstorage": "^1.15.0", "vfile": "^6.0.3", "vite": "^6.3.4", "vitefu": "^1.0.6", "xxhash-wasm": "^1.1.0", "yargs-parser": "^21.1.1", "yocto-spinner": "^0.2.1", "zod": "^3.24.2", "zod-to-json-schema": "^3.24.5", "zod-to-ts": "^1.2.0" }, "optionalDependencies": { "sharp": "^0.33.3" }, "bin": { "astro": "astro.js" } }, "sha512-G57ELkdIntDiSrucA5lQaRtBOjquaZ9b9NIwoz2f471ZuuJcynLjWgItgBzlrz5UMY4WqnFbVWUCKlJb7nt9bA=="],
"astro": ["astro@5.9.3", "", { "dependencies": { "@astrojs/compiler": "^2.12.2", "@astrojs/internal-helpers": "0.6.1", "@astrojs/markdown-remark": "6.3.2", "@astrojs/telemetry": "3.3.0", "@capsizecss/unpack": "^2.4.0", "@oslojs/encoding": "^1.1.0", "@rollup/pluginutils": "^5.1.4", "acorn": "^8.14.1", "aria-query": "^5.3.2", "axobject-query": "^4.1.0", "boxen": "8.0.1", "ci-info": "^4.2.0", "clsx": "^2.1.1", "common-ancestor-path": "^1.0.1", "cookie": "^1.0.2", "cssesc": "^3.0.0", "debug": "^4.4.0", "deterministic-object-hash": "^2.0.2", "devalue": "^5.1.1", "diff": "^5.2.0", "dlv": "^1.1.3", "dset": "^3.1.4", "es-module-lexer": "^1.6.0", "esbuild": "^0.25.0", "estree-walker": "^3.0.3", "flattie": "^1.1.1", "fontace": "~0.3.0", "github-slugger": "^2.0.0", "html-escaper": "3.0.3", "http-cache-semantics": "^4.1.1", "import-meta-resolve": "^4.1.0", "js-yaml": "^4.1.0", "kleur": "^4.1.5", "magic-string": "^0.30.17", "magicast": "^0.3.5", "mrmime": "^2.0.1", "neotraverse": "^0.6.18", "p-limit": "^6.2.0", "p-queue": "^8.1.0", "package-manager-detector": "^1.1.0", "picomatch": "^4.0.2", "prompts": "^2.4.2", "rehype": "^13.0.2", "semver": "^7.7.1", "shiki": "^3.2.1", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.12", "tsconfck": "^3.1.5", "ultrahtml": "^1.6.0", "unifont": "~0.5.0", "unist-util-visit": "^5.0.0", "unstorage": "^1.15.0", "vfile": "^6.0.3", "vite": "^6.3.4", "vitefu": "^1.0.6", "xxhash-wasm": "^1.1.0", "yargs-parser": "^21.1.1", "yocto-spinner": "^0.2.1", "zod": "^3.24.2", "zod-to-json-schema": "^3.24.5", "zod-to-ts": "^1.2.0" }, "optionalDependencies": { "sharp": "^0.33.3" }, "bin": { "astro": "astro.js" } }, "sha512-VReZrpUa/3rfeiVvsQ1A2M3ujDPI+pDGIYOMtXPEZwut8tZoEyealXXLjitgCsJ+3dunKGZbg4Eak6i+r0vniw=="],
"axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="],
@@ -559,12 +612,14 @@
"bcryptjs": ["bcryptjs@3.0.2", "", { "bin": { "bcrypt": "bin/bcrypt" } }, "sha512-k38b3XOZKv60C4E2hVsXTolJWfkGRMbILBIe2IBITXciy5bOsTKot5kDrf3ZfufQtQOUN5mXceUEpU1rTl9Uog=="],
"before-after-hook": ["before-after-hook@3.0.2", "", {}, "sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A=="],
"before-after-hook": ["before-after-hook@4.0.0", "", {}, "sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ=="],
"blob-to-buffer": ["blob-to-buffer@1.2.9", "", {}, "sha512-BF033y5fN6OCofD3vgHmNtwZWRcq9NLyyxyILx9hfMy1sXYy4ojFl765hJ2lP0YaN2fuxPaLO2Vzzoxy0FLFFA=="],
"boxen": ["boxen@8.0.1", "", { "dependencies": { "ansi-align": "^3.0.1", "camelcase": "^8.0.0", "chalk": "^5.3.0", "cli-boxes": "^3.0.0", "string-width": "^7.2.0", "type-fest": "^4.21.0", "widest-line": "^5.0.0", "wrap-ansi": "^9.0.0" } }, "sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw=="],
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
"brotli": ["brotli@1.3.3", "", { "dependencies": { "base64-js": "^1.1.2" } }, "sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg=="],
"browserslist": ["browserslist@4.24.5", "", { "dependencies": { "caniuse-lite": "^1.0.30001716", "electron-to-chromium": "^1.5.149", "node-releases": "^2.0.19", "update-browserslist-db": "^1.1.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-FDToo4Wo82hIdgc1CQ+NQD0hEhmpPjrZ3hiUgwgOG6IuTdlpr8jdjyG24P6cNP1yJpTLzS5OcGgSw0xmDU1/Tw=="],
@@ -605,6 +660,8 @@
"cli-boxes": ["cli-boxes@3.0.0", "", {}, "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g=="],
"cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="],
"clone": ["clone@2.1.2", "", {}, "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w=="],
"clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
@@ -681,7 +738,7 @@
"dom-accessibility-api": ["dom-accessibility-api@0.6.3", "", {}, "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w=="],
"drizzle-orm": ["drizzle-orm@0.43.1", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "knex", "kysely", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-dUcDaZtE/zN4RV/xqGrVSMpnEczxd5cIaoDeor7Zst9wOe/HzC/7eAaulywWGYXdDEc9oBPMjayVEDg0ziTLJA=="],
"drizzle-orm": ["drizzle-orm@0.44.2", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@upstash/redis": ">=1.34.7", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@upstash/redis", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "knex", "kysely", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-zGAqBzWWkVSFjZpwPOrmCrgO++1kZ5H/rZ4qTGeGOe18iXGVJWf3WPfHOVwFIbmi8kHjfJstC6rJomzGx8g/dQ=="],
"dset": ["dset@3.1.4", "", {}, "sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA=="],
@@ -691,7 +748,9 @@
"electron-to-chromium": ["electron-to-chromium@1.5.159", "", {}, "sha512-CEvHptWAMV5p6GJ0Lq8aheyvVbfzVrv5mmidu1D3pidoVNkB3tTBsTMVtPJ+rzRK5oV229mCLz9Zj/hNvU8GBA=="],
"emoji-regex": ["emoji-regex@10.4.0", "", {}, "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw=="],
"emmet": ["emmet@2.4.11", "", { "dependencies": { "@emmetio/abbreviation": "^2.3.3", "@emmetio/css-abbreviation": "^2.1.8" } }, "sha512-23QPJB3moh/U9sT4rQzGgeyyGIrcM+GH5uVYg2C6wZIxAIJq7Ng3QLT79tl8FUwDXhyq9SusfknOrofAKqvgyQ=="],
"emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
"encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="],
@@ -735,12 +794,20 @@
"extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="],
"fast-content-type-parse": ["fast-content-type-parse@2.0.1", "", {}, "sha512-nGqtvLrj5w0naR6tDPfB4cUmYCqouzyQiz6C5y/LtcDllJdrcc6WaWW6iXyIIOErTa/XRybj28aasdn4LkVk6Q=="],
"fast-content-type-parse": ["fast-content-type-parse@3.0.0", "", {}, "sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg=="],
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
"fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="],
"fast-uri": ["fast-uri@3.0.6", "", {}, "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw=="],
"fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="],
"fdir": ["fdir@6.4.4", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg=="],
"fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
"flattie": ["flattie@1.1.1", "", {}, "sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ=="],
"fontace": ["fontace@0.3.0", "", { "dependencies": { "@types/fontkit": "^2.0.8", "fontkit": "^2.0.4" } }, "sha512-czoqATrcnxgWb/nAkfyIrRp6Q8biYj7nGnL6zfhTcX+JKKpWHFBnb8uNMw/kZr7u++3Y3wYSYoZgHkCcsuBpBg=="],
@@ -755,6 +822,8 @@
"gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="],
"get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="],
"get-east-asian-width": ["get-east-asian-width@1.3.0", "", {}, "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ=="],
"get-nonce": ["get-nonce@1.0.1", "", {}, "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q=="],
@@ -763,6 +832,8 @@
"github-slugger": ["github-slugger@2.0.0", "", {}, "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="],
"glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
"globals": ["globals@11.12.0", "", {}, "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="],
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
@@ -831,12 +902,18 @@
"is-docker": ["is-docker@3.0.0", "", { "bin": { "is-docker": "cli.js" } }, "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ=="],
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
"is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
"is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
"is-hexadecimal": ["is-hexadecimal@2.0.1", "", {}, "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg=="],
"is-inside-container": ["is-inside-container@1.0.0", "", { "dependencies": { "is-docker": "^3.0.0" }, "bin": { "is-inside-container": "cli.js" } }, "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA=="],
"is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
"is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="],
"is-potential-custom-element-name": ["is-potential-custom-element-name@1.0.1", "", {}, "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ=="],
@@ -853,8 +930,12 @@
"jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="],
"json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
"json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
"jsonc-parser": ["jsonc-parser@2.3.1", "", {}, "sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg=="],
"jsonwebtoken": ["jsonwebtoken@9.0.2", "", { "dependencies": { "jws": "^3.2.2", "lodash.includes": "^4.3.0", "lodash.isboolean": "^3.0.3", "lodash.isinteger": "^4.0.4", "lodash.isnumber": "^3.0.3", "lodash.isplainobject": "^4.0.6", "lodash.isstring": "^4.0.1", "lodash.once": "^4.0.0", "ms": "^2.1.1", "semver": "^7.5.4" } }, "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ=="],
"jwa": ["jwa@1.4.2", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw=="],
@@ -907,7 +988,7 @@
"lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
"lucide-react": ["lucide-react@0.511.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-VK5a2ydJ7xm8GvBeKLS9mu1pVK6ucef9780JVUjw6bAjJL/QXnd4Y0p7SPeOUMC27YhzNCZvm5d/QX0Tp3rc0w=="],
"lucide-react": ["lucide-react@0.515.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Sy7bY0MeicRm2pzrnoHm2h6C1iVoeHyBU2fjdQDsXGP51fhkhau1/ZV/dzrcxEmAKsxYb6bGaIsMnGHuQ5s0dw=="],
"lz-string": ["lz-string@1.5.0", "", { "bin": { "lz-string": "bin/bin.js" } }, "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ=="],
@@ -955,6 +1036,8 @@
"mdn-data": ["mdn-data@2.12.2", "", {}, "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA=="],
"merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="],
"micromark": ["micromark@4.0.2", "", { "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA=="],
"micromark-core-commonmark": ["micromark-core-commonmark@2.0.3", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-destination": "^2.0.0", "micromark-factory-label": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-factory-title": "^2.0.0", "micromark-factory-whitespace": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-html-tag-name": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg=="],
@@ -1025,6 +1108,8 @@
"micromark-util-types": ["micromark-util-types@2.0.2", "", {}, "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA=="],
"micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
"mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="],
"mime-types": ["mime-types@3.0.1", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA=="],
@@ -1041,6 +1126,8 @@
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"muggle-string": ["muggle-string@0.4.1", "", {}, "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ=="],
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
"neotraverse": ["neotraverse@0.6.18", "", {}, "sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA=="],
@@ -1087,6 +1174,8 @@
"parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="],
"path-browserify": ["path-browserify@1.0.1", "", {}, "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="],
"pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
"pathval": ["pathval@2.0.0", "", {}, "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA=="],
@@ -1097,6 +1186,8 @@
"postcss": ["postcss@8.5.3", "", { "dependencies": { "nanoid": "^3.3.8", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A=="],
"prettier": ["prettier@2.8.7", "", { "bin": { "prettier": "bin-prettier.js" } }, "sha512-yPngTo3aXUUmyuTjeTUT75txrf+aMh9FiD7q9ZE/i6r0bPb22g4FsE6Y338PQX1bmfy08i9QQCB7/rcUAVntfw=="],
"pretty-format": ["pretty-format@27.5.1", "", { "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", "react-is": "^17.0.1" } }, "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ=="],
"prismjs": ["prismjs@1.30.0", "", {}, "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw=="],
@@ -1107,6 +1198,8 @@
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
"radix3": ["radix3@1.1.2", "", {}, "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA=="],
"range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="],
@@ -1167,6 +1260,12 @@
"remark-stringify": ["remark-stringify@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-to-markdown": "^2.0.0", "unified": "^11.0.0" } }, "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw=="],
"request-light": ["request-light@0.7.0", "", {}, "sha512-lMbBMrDoxgsyO+yB3sDcrDuX85yYt7sS8BfQd11jtbW/z5ZWgLZRcEGLsLoYw7I0WSUGQBs8CC8ScIxkTX1+6Q=="],
"require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="],
"require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="],
"resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="],
"restructure": ["restructure@3.0.2", "", {}, "sha512-gSfoiOEA0VPE6Tukkrr7I0RBdE0s7H1eFCDBk05l1KIQT1UIKNc5JZy6jdyW6eYH3aR3g5b3PuL77rq0hvwtAw=="],
@@ -1179,10 +1278,14 @@
"retext-stringify": ["retext-stringify@4.0.0", "", { "dependencies": { "@types/nlcst": "^2.0.0", "nlcst-to-string": "^4.0.0", "unified": "^11.0.0" } }, "sha512-rtfN/0o8kL1e+78+uxPTqu1Klt0yPzKuQ2BfWwwfgIUSayyzxpM1PJzkKt4V8803uB9qSy32MvI7Xep9khTpiA=="],
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
"rollup": ["rollup@4.41.1", "", { "dependencies": { "@types/estree": "1.0.7" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.41.1", "@rollup/rollup-android-arm64": "4.41.1", "@rollup/rollup-darwin-arm64": "4.41.1", "@rollup/rollup-darwin-x64": "4.41.1", "@rollup/rollup-freebsd-arm64": "4.41.1", "@rollup/rollup-freebsd-x64": "4.41.1", "@rollup/rollup-linux-arm-gnueabihf": "4.41.1", "@rollup/rollup-linux-arm-musleabihf": "4.41.1", "@rollup/rollup-linux-arm64-gnu": "4.41.1", "@rollup/rollup-linux-arm64-musl": "4.41.1", "@rollup/rollup-linux-loongarch64-gnu": "4.41.1", "@rollup/rollup-linux-powerpc64le-gnu": "4.41.1", "@rollup/rollup-linux-riscv64-gnu": "4.41.1", "@rollup/rollup-linux-riscv64-musl": "4.41.1", "@rollup/rollup-linux-s390x-gnu": "4.41.1", "@rollup/rollup-linux-x64-gnu": "4.41.1", "@rollup/rollup-linux-x64-musl": "4.41.1", "@rollup/rollup-win32-arm64-msvc": "4.41.1", "@rollup/rollup-win32-ia32-msvc": "4.41.1", "@rollup/rollup-win32-x64-msvc": "4.41.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-cPmwD3FnFv8rKMBc1MxWCwVQFxwf1JEmSX3iQXrRVVG15zerAIXRjMFVWnd5Q5QvgKF7Aj+5ykXFhUl+QGnyOw=="],
"rrweb-cssom": ["rrweb-cssom@0.8.0", "", {}, "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw=="],
"run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
"safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
@@ -1211,7 +1314,7 @@
"smol-toml": ["smol-toml@1.3.4", "", {}, "sha512-UOPtVuYkzYGee0Bd2Szz8d2G3RfMfJ2t3qVdZUAozZyAk+a0Sxa+QKix0YCwjL/A1RR0ar44nCxaoN9FxdJGwA=="],
"sonner": ["sonner@2.0.3", "", { "peerDependencies": { "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-njQ4Hht92m0sMqqHVDL32V2Oun9W1+PHO9NDv9FHfJjT3JT22IG4Jpo3FPQy+mouRKCXFWO+r67v6MrHX2zeIA=="],
"sonner": ["sonner@2.0.5", "", { "peerDependencies": { "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-YwbHQO6cSso3HBXlbCkgrgzDNIhws14r4MO87Ofy+cV2X7ES4pOoAK3+veSmVTvqNx1BWUxlhPmZzP00Crk2aQ=="],
"source-map": ["source-map@0.7.4", "", {}, "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA=="],
@@ -1225,14 +1328,16 @@
"std-env": ["std-env@3.9.0", "", {}, "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw=="],
"string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
"string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
"stringify-entities": ["stringify-entities@4.0.4", "", { "dependencies": { "character-entities-html4": "^2.0.0", "character-entities-legacy": "^3.0.0" } }, "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg=="],
"strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="],
"strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"strip-indent": ["strip-indent@3.0.0", "", { "dependencies": { "min-indent": "^1.0.0" } }, "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ=="],
"strip-literal": ["strip-literal@3.0.0", "", { "dependencies": { "js-tokens": "^9.0.1" } }, "sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA=="],
"style-to-js": ["style-to-js@1.1.16", "", { "dependencies": { "style-to-object": "1.0.8" } }, "sha512-/Q6ld50hKYPH3d/r6nr117TZkHR0w0kGGIVfpG9N6D8NymRPM9RqCUv4pRpJ62E5DqOYx2AFpbZMyCPnjQCnOw=="],
"style-to-object": ["style-to-object@1.0.8", "", { "dependencies": { "inline-style-parser": "0.2.4" } }, "sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g=="],
@@ -1241,9 +1346,9 @@
"symbol-tree": ["symbol-tree@3.2.4", "", {}, "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw=="],
"tailwind-merge": ["tailwind-merge@3.3.0", "", {}, "sha512-fyW/pEfcQSiigd5SNn0nApUOxx0zB/dm6UDU/rEwc2c3sX2smWUNbapHv+QRqLGVp9GWX3THIa7MUGPo+YkDzQ=="],
"tailwind-merge": ["tailwind-merge@3.3.1", "", {}, "sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g=="],
"tailwindcss": ["tailwindcss@4.1.7", "", {}, "sha512-kr1o/ErIdNhTz8uzAYL7TpaUuzKIE6QPQ4qmSdxnoX/lo+5wmUHQA6h3L5yIqEImSRnAAURDirLu/BgiXGPAhg=="],
"tailwindcss": ["tailwindcss@4.1.10", "", {}, "sha512-P3nr6WkvKV/ONsTzj6Gb57sWPMX29EPNPopo7+FcpkQaNsrNpZ1pv8QmrYI2RqEKD7mlGqLnGovlcYnBK0IqUA=="],
"tapable": ["tapable@2.2.2", "", {}, "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg=="],
@@ -1257,16 +1362,18 @@
"tinyglobby": ["tinyglobby@0.2.14", "", { "dependencies": { "fdir": "^6.4.4", "picomatch": "^4.0.2" } }, "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ=="],
"tinypool": ["tinypool@1.0.2", "", {}, "sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA=="],
"tinypool": ["tinypool@1.1.0", "", {}, "sha512-7CotroY9a8DKsKprEy/a14aCCm8jYVmR7aFy4fpkZM8sdpNJbKkixuNjgM50yCmip2ezc8z4N7k3oe2+rfRJCQ=="],
"tinyrainbow": ["tinyrainbow@2.0.0", "", {}, "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw=="],
"tinyspy": ["tinyspy@3.0.2", "", {}, "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q=="],
"tinyspy": ["tinyspy@4.0.3", "", {}, "sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A=="],
"tldts": ["tldts@6.1.86", "", { "dependencies": { "tldts-core": "^6.1.86" }, "bin": { "tldts": "bin/cli.js" } }, "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ=="],
"tldts-core": ["tldts-core@6.1.86", "", {}, "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA=="],
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
"toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="],
"tough-cookie": ["tough-cookie@5.1.2", "", { "dependencies": { "tldts": "^6.1.32" } }, "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A=="],
@@ -1281,14 +1388,18 @@
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"tsx": ["tsx@4.19.4", "", { "dependencies": { "esbuild": "~0.25.0", "get-tsconfig": "^4.7.5" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-gK5GVzDkJK1SI1zwHf32Mqxf2tSJkNx+eYcNly5+nHvWqXUJYUkWBQtKauoESz3ymezAI++ZwT855x5p5eop+Q=="],
"tsx": ["tsx@4.20.3", "", { "dependencies": { "esbuild": "~0.25.0", "get-tsconfig": "^4.7.5" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-qjbnuR9Tr+FJOMBqJCW5ehvIo/buZq7vH7qD7JziU98h6l3qGy0a/yPFjwO+y0/T7GFpNgNAvEcPPVfyT8rrPQ=="],
"tw-animate-css": ["tw-animate-css@1.3.0", "", {}, "sha512-jrJ0XenzS9KVuDThJDvnhalbl4IYiMQ/XvpA0a2FL8KmlK+6CSMviO7ROY/I7z1NnUs5NnDhlM6fXmF40xPxzw=="],
"tw-animate-css": ["tw-animate-css@1.3.4", "", {}, "sha512-dd1Ht6/YQHcNbq0znIT6dG8uhO7Ce+VIIhZUhjsryXsMPJQz3bZg7Q2eNzLwipb25bRZslGb2myio5mScd1TFg=="],
"type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="],
"typesafe-path": ["typesafe-path@0.2.2", "", {}, "sha512-OJabfkAg1WLZSqJAJ0Z6Sdt3utnbzr/jh+NAHoyWHJe8CMSy79Gm085094M9nvTPy22KzTVn5Zq5mbapCI/hPA=="],
"typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
"typescript-auto-import-cache": ["typescript-auto-import-cache@0.3.6", "", { "dependencies": { "semver": "^7.3.8" } }, "sha512-RpuHXrknHdVdK7wv/8ug3Fr0WNsNi5l5aB8MYYuXhq2UH5lnEB1htJ1smhtD5VeCsGr2p8mUDtd83LCQDFVgjQ=="],
"ufo": ["ufo@1.6.1", "", {}, "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA=="],
"ultrahtml": ["ultrahtml@1.6.0", "", {}, "sha512-R9fBn90VTJrqqLDwyMph+HGne8eqY1iPfYhPzZrvKpIfwkWZbcYlfpsb8B9dTvBfpy1/hqAD7Wi8EKfP9e8zdw=="],
@@ -1347,11 +1458,45 @@
"vite": ["vite@6.3.5", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ=="],
"vite-node": ["vite-node@3.1.4", "", { "dependencies": { "cac": "^6.7.14", "debug": "^4.4.0", "es-module-lexer": "^1.7.0", "pathe": "^2.0.3", "vite": "^5.0.0 || ^6.0.0" }, "bin": { "vite-node": "vite-node.mjs" } }, "sha512-6enNwYnpyDo4hEgytbmc6mYWHXDHYEn0D1/rw4Q+tnHUGtKTJsn8T1YkX6Q18wI5LCrS8CTYlBaiCqxOy2kvUA=="],
"vite-node": ["vite-node@3.2.3", "", { "dependencies": { "cac": "^6.7.14", "debug": "^4.4.1", "es-module-lexer": "^1.7.0", "pathe": "^2.0.3", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" }, "bin": { "vite-node": "vite-node.mjs" } }, "sha512-gc8aAifGuDIpZHrPjuHyP4dpQmYXqWw7D1GmDnWeNWP654UEXzVfQ5IHPSK5HaHkwB/+p1atpYpSdw/2kOv8iQ=="],
"vitefu": ["vitefu@1.0.6", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0" }, "optionalPeers": ["vite"] }, "sha512-+Rex1GlappUyNN6UfwbVZne/9cYC4+R2XDk9xkNXBKMw6HQagdX9PgZ8V2v1WUSK1wfBLp7qbI1+XSNIlB1xmA=="],
"vitest": ["vitest@3.1.4", "", { "dependencies": { "@vitest/expect": "3.1.4", "@vitest/mocker": "3.1.4", "@vitest/pretty-format": "^3.1.4", "@vitest/runner": "3.1.4", "@vitest/snapshot": "3.1.4", "@vitest/spy": "3.1.4", "@vitest/utils": "3.1.4", "chai": "^5.2.0", "debug": "^4.4.0", "expect-type": "^1.2.1", "magic-string": "^0.30.17", "pathe": "^2.0.3", "std-env": "^3.9.0", "tinybench": "^2.9.0", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.13", "tinypool": "^1.0.2", "tinyrainbow": "^2.0.0", "vite": "^5.0.0 || ^6.0.0", "vite-node": "3.1.4", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@types/debug": "^4.1.12", "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "@vitest/browser": "3.1.4", "@vitest/ui": "3.1.4", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@types/debug", "@types/node", "@vitest/browser", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-Ta56rT7uWxCSJXlBtKgIlApJnT6e6IGmTYxYcmxjJ4ujuZDI59GUQgVDObXXJujOmPDBYXHK1qmaGtneu6TNIQ=="],
"vitest": ["vitest@3.2.3", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/expect": "3.2.3", "@vitest/mocker": "3.2.3", "@vitest/pretty-format": "^3.2.3", "@vitest/runner": "3.2.3", "@vitest/snapshot": "3.2.3", "@vitest/spy": "3.2.3", "@vitest/utils": "3.2.3", "chai": "^5.2.0", "debug": "^4.4.1", "expect-type": "^1.2.1", "magic-string": "^0.30.17", "pathe": "^2.0.3", "picomatch": "^4.0.2", "std-env": "^3.9.0", "tinybench": "^2.9.0", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.14", "tinypool": "^1.1.0", "tinyrainbow": "^2.0.0", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", "vite-node": "3.2.3", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@types/debug": "^4.1.12", "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "@vitest/browser": "3.2.3", "@vitest/ui": "3.2.3", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@types/debug", "@types/node", "@vitest/browser", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-E6U2ZFXe3N/t4f5BwUaVCKRLHqUpk1CBWeMh78UT4VaTPH/2dyvH6ALl29JTovEPu9dVKr/K/J4PkXgrMbw4Ww=="],
"volar-service-css": ["volar-service-css@0.0.62", "", { "dependencies": { "vscode-css-languageservice": "^6.3.0", "vscode-languageserver-textdocument": "^1.0.11", "vscode-uri": "^3.0.8" }, "peerDependencies": { "@volar/language-service": "~2.4.0" }, "optionalPeers": ["@volar/language-service"] }, "sha512-JwNyKsH3F8PuzZYuqPf+2e+4CTU8YoyUHEHVnoXNlrLe7wy9U3biomZ56llN69Ris7TTy/+DEX41yVxQpM4qvg=="],
"volar-service-emmet": ["volar-service-emmet@0.0.62", "", { "dependencies": { "@emmetio/css-parser": "^0.4.0", "@emmetio/html-matcher": "^1.3.0", "@vscode/emmet-helper": "^2.9.3", "vscode-uri": "^3.0.8" }, "peerDependencies": { "@volar/language-service": "~2.4.0" }, "optionalPeers": ["@volar/language-service"] }, "sha512-U4dxWDBWz7Pi4plpbXf4J4Z/ss6kBO3TYrACxWNsE29abu75QzVS0paxDDhI6bhqpbDFXlpsDhZ9aXVFpnfGRQ=="],
"volar-service-html": ["volar-service-html@0.0.62", "", { "dependencies": { "vscode-html-languageservice": "^5.3.0", "vscode-languageserver-textdocument": "^1.0.11", "vscode-uri": "^3.0.8" }, "peerDependencies": { "@volar/language-service": "~2.4.0" }, "optionalPeers": ["@volar/language-service"] }, "sha512-Zw01aJsZRh4GTGUjveyfEzEqpULQUdQH79KNEiKVYHZyuGtdBRYCHlrus1sueSNMxwwkuF5WnOHfvBzafs8yyQ=="],
"volar-service-prettier": ["volar-service-prettier@0.0.62", "", { "dependencies": { "vscode-uri": "^3.0.8" }, "peerDependencies": { "@volar/language-service": "~2.4.0", "prettier": "^2.2 || ^3.0" }, "optionalPeers": ["@volar/language-service", "prettier"] }, "sha512-h2yk1RqRTE+vkYZaI9KYuwpDfOQRrTEMvoHol0yW4GFKc75wWQRrb5n/5abDrzMPrkQbSip8JH2AXbvrRtYh4w=="],
"volar-service-typescript": ["volar-service-typescript@0.0.62", "", { "dependencies": { "path-browserify": "^1.0.1", "semver": "^7.6.2", "typescript-auto-import-cache": "^0.3.3", "vscode-languageserver-textdocument": "^1.0.11", "vscode-nls": "^5.2.0", "vscode-uri": "^3.0.8" }, "peerDependencies": { "@volar/language-service": "~2.4.0" }, "optionalPeers": ["@volar/language-service"] }, "sha512-p7MPi71q7KOsH0eAbZwPBiKPp9B2+qrdHAd6VY5oTo9BUXatsOAdakTm9Yf0DUj6uWBAaOT01BSeVOPwucMV1g=="],
"volar-service-typescript-twoslash-queries": ["volar-service-typescript-twoslash-queries@0.0.62", "", { "dependencies": { "vscode-uri": "^3.0.8" }, "peerDependencies": { "@volar/language-service": "~2.4.0" }, "optionalPeers": ["@volar/language-service"] }, "sha512-KxFt4zydyJYYI0kFAcWPTh4u0Ha36TASPZkAnNY784GtgajerUqM80nX/W1d0wVhmcOFfAxkVsf/Ed+tiYU7ng=="],
"volar-service-yaml": ["volar-service-yaml@0.0.62", "", { "dependencies": { "vscode-uri": "^3.0.8", "yaml-language-server": "~1.15.0" }, "peerDependencies": { "@volar/language-service": "~2.4.0" }, "optionalPeers": ["@volar/language-service"] }, "sha512-k7gvv7sk3wa+nGll3MaSKyjwQsJjIGCHFjVkl3wjaSP2nouKyn9aokGmqjrl39mi88Oy49giog2GkZH526wjig=="],
"vscode-css-languageservice": ["vscode-css-languageservice@6.3.6", "", { "dependencies": { "@vscode/l10n": "^0.0.18", "vscode-languageserver-textdocument": "^1.0.12", "vscode-languageserver-types": "3.17.5", "vscode-uri": "^3.1.0" } }, "sha512-fU4h8mT3KlvfRcbF74v/M+Gzbligav6QMx4AD/7CbclWPYOpGb9kgIswfpZVJbIcOEJJACI9iYizkNwdiAqlHw=="],
"vscode-html-languageservice": ["vscode-html-languageservice@5.5.0", "", { "dependencies": { "@vscode/l10n": "^0.0.18", "vscode-languageserver-textdocument": "^1.0.12", "vscode-languageserver-types": "^3.17.5", "vscode-uri": "^3.1.0" } }, "sha512-No6Er2P2L8IsXDnUFlp0bP4f2sdkJv+zJLZYFhtEQIp+2xNfxY8WYkhSxLJ/7bZhuV/aU55lmGSSHBVxSGer3Q=="],
"vscode-json-languageservice": ["vscode-json-languageservice@4.1.8", "", { "dependencies": { "jsonc-parser": "^3.0.0", "vscode-languageserver-textdocument": "^1.0.1", "vscode-languageserver-types": "^3.16.0", "vscode-nls": "^5.0.0", "vscode-uri": "^3.0.2" } }, "sha512-0vSpg6Xd9hfV+eZAaYN63xVVMOTmJ4GgHxXnkLCh+9RsQBkWKIghzLhW2B9ebfG+LQQg8uLtsQ2aUKjTgE+QOg=="],
"vscode-jsonrpc": ["vscode-jsonrpc@8.2.0", "", {}, "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA=="],
"vscode-languageserver": ["vscode-languageserver@9.0.1", "", { "dependencies": { "vscode-languageserver-protocol": "3.17.5" }, "bin": { "installServerIntoExtension": "bin/installServerIntoExtension" } }, "sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g=="],
"vscode-languageserver-protocol": ["vscode-languageserver-protocol@3.17.5", "", { "dependencies": { "vscode-jsonrpc": "8.2.0", "vscode-languageserver-types": "3.17.5" } }, "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg=="],
"vscode-languageserver-textdocument": ["vscode-languageserver-textdocument@1.0.12", "", {}, "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA=="],
"vscode-languageserver-types": ["vscode-languageserver-types@3.17.5", "", {}, "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg=="],
"vscode-nls": ["vscode-nls@5.2.0", "", {}, "sha512-RAaHx7B14ZU04EU31pT+rKz2/zSl7xMsfIZuo8pd+KZO6PXtQmpevpq3vxvWNcrGbdmhM/rr5Uw5Mz+NBfhVng=="],
"vscode-uri": ["vscode-uri@3.1.0", "", {}, "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ=="],
"w3c-xmlserializer": ["w3c-xmlserializer@5.0.0", "", { "dependencies": { "xml-name-validator": "^5.0.0" } }, "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA=="],
@@ -1381,8 +1526,16 @@
"xxhash-wasm": ["xxhash-wasm@1.1.0", "", {}, "sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA=="],
"y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
"yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="],
"yaml": ["yaml@2.8.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ=="],
"yaml-language-server": ["yaml-language-server@1.15.0", "", { "dependencies": { "ajv": "^8.11.0", "lodash": "4.17.21", "request-light": "^0.5.7", "vscode-json-languageservice": "4.1.8", "vscode-languageserver": "^7.0.0", "vscode-languageserver-textdocument": "^1.0.1", "vscode-languageserver-types": "^3.16.0", "vscode-nls": "^5.0.0", "vscode-uri": "^3.0.2", "yaml": "2.2.2" }, "optionalDependencies": { "prettier": "2.8.7" }, "bin": { "yaml-language-server": "bin/yaml-language-server" } }, "sha512-N47AqBDCMQmh6mBLmI6oqxryHRzi33aPFPsJhYy3VTUGCdLHYjGh4FZzpUjRlphaADBBkDmnkM/++KNIOHi5Rw=="],
"yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="],
"yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="],
"yocto-queue": ["yocto-queue@1.2.1", "", {}, "sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg=="],
@@ -1391,7 +1544,7 @@
"yoctocolors": ["yoctocolors@2.1.1", "", {}, "sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ=="],
"zod": ["zod@3.25.32", "", {}, "sha512-OSm2xTIRfW8CV5/QKgngwmQW/8aPfGdaQFlrGoErlgg/Epm7cjb6K6VEyExfe65a3VybUOnu381edLb0dfJl0g=="],
"zod": ["zod@3.25.64", "", {}, "sha512-hbP9FpSZf7pkS7hRVUrOjhwKJNyampPgtXKc3AN6DsWtoHsg2Sb4SQaS4Tcay380zSwd2VPo9G9180emBACp5g=="],
"zod-to-json-schema": ["zod-to-json-schema@3.24.5", "", { "peerDependencies": { "zod": "^3.24.1" } }, "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g=="],
@@ -1399,15 +1552,23 @@
"zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="],
"@astrojs/react/@vitejs/plugin-react": ["@vitejs/plugin-react@4.5.0", "", { "dependencies": { "@babel/core": "^7.26.10", "@babel/plugin-transform-react-jsx-self": "^7.25.9", "@babel/plugin-transform-react-jsx-source": "^7.25.9", "@rolldown/pluginutils": "1.0.0-beta.9", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0" } }, "sha512-JuLWaEqypaJmOJPLWwO335Ig6jSgC1FTONCWAxnqcQthLTK/Yc9aH6hr9z/87xciejbQcnP3GnA1FWUSWeXaeg=="],
"@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
"@babel/generator/@babel/parser": ["@babel/parser@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" }, "bin": "./bin/babel-parser.js" }, "sha512-xyYxRj6+tLNDTWi0KCBcZ9V7yg3/lwL9DWh9Uwh/RIVlIfFidggcgxKX3GCXwCiswwcGRawBKbEg2LG/Y8eJhw=="],
"@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
"@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
"@octokit/plugin-paginate-rest/@octokit/types": ["@octokit/types@13.10.0", "", { "dependencies": { "@octokit/openapi-types": "^24.2.0" } }, "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA=="],
"@babel/helper-module-imports/@babel/traverse": ["@babel/traverse@7.27.3", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.27.3", "@babel/parser": "^7.27.3", "@babel/template": "^7.27.2", "@babel/types": "^7.27.3", "debug": "^4.3.1", "globals": "^11.1.0" } }, "sha512-lId/IfN/Ye1CIu8xG7oKBHXd2iNb2aW1ilPszzGcJug6M8RCKfVNcYhpI5+bMvFYjK7lXIM0R+a+6r8xhHp2FQ=="],
"@octokit/plugin-rest-endpoint-methods/@octokit/types": ["@octokit/types@13.10.0", "", { "dependencies": { "@octokit/openapi-types": "^24.2.0" } }, "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA=="],
"@babel/helper-module-transforms/@babel/traverse": ["@babel/traverse@7.27.3", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.27.3", "@babel/parser": "^7.27.3", "@babel/template": "^7.27.2", "@babel/types": "^7.27.3", "debug": "^4.3.1", "globals": "^11.1.0" } }, "sha512-lId/IfN/Ye1CIu8xG7oKBHXd2iNb2aW1ilPszzGcJug6M8RCKfVNcYhpI5+bMvFYjK7lXIM0R+a+6r8xhHp2FQ=="],
"@babel/helpers/@babel/types": ["@babel/types@7.27.6", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q=="],
"@babel/template/@babel/parser": ["@babel/parser@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" }, "bin": "./bin/babel-parser.js" }, "sha512-xyYxRj6+tLNDTWi0KCBcZ9V7yg3/lwL9DWh9Uwh/RIVlIfFidggcgxKX3GCXwCiswwcGRawBKbEg2LG/Y8eJhw=="],
"@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
@@ -1429,14 +1590,24 @@
"@testing-library/dom/dom-accessibility-api": ["dom-accessibility-api@0.5.16", "", {}, "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg=="],
"ansi-align/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
"@types/babel__core/@babel/parser": ["@babel/parser@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" }, "bin": "./bin/babel-parser.js" }, "sha512-xyYxRj6+tLNDTWi0KCBcZ9V7yg3/lwL9DWh9Uwh/RIVlIfFidggcgxKX3GCXwCiswwcGRawBKbEg2LG/Y8eJhw=="],
"@types/babel__template/@babel/parser": ["@babel/parser@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" }, "bin": "./bin/babel-parser.js" }, "sha512-xyYxRj6+tLNDTWi0KCBcZ9V7yg3/lwL9DWh9Uwh/RIVlIfFidggcgxKX3GCXwCiswwcGRawBKbEg2LG/Y8eJhw=="],
"anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
"boxen/chalk": ["chalk@5.4.1", "", {}, "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w=="],
"boxen/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
"cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
"hast-util-to-parse5/property-information": ["property-information@6.5.0", "", {}, "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig=="],
"magicast/@babel/parser": ["@babel/parser@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" }, "bin": "./bin/babel-parser.js" }, "sha512-xyYxRj6+tLNDTWi0KCBcZ9V7yg3/lwL9DWh9Uwh/RIVlIfFidggcgxKX3GCXwCiswwcGRawBKbEg2LG/Y8eJhw=="],
"micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
"node-fetch/whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="],
"parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="],
@@ -1445,22 +1616,66 @@
"prompts/kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="],
"strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="],
"strip-literal/js-tokens": ["js-tokens@9.0.1", "", {}, "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ=="],
"vscode-json-languageservice/jsonc-parser": ["jsonc-parser@3.3.1", "", {}, "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ=="],
"widest-line/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
"wrap-ansi/ansi-styles": ["ansi-styles@6.2.1", "", {}, "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="],
"wrap-ansi/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
"wrap-ansi/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="],
"yaml-language-server/request-light": ["request-light@0.5.8", "", {}, "sha512-3Zjgh+8b5fhRJBQZoy+zbVKpAQGLyka0MPgW3zruTF4dFFJ8Fqcfu9YsAvi/rvdcaTeWG3MkbZv4WKxAn/84Lg=="],
"yaml-language-server/vscode-languageserver": ["vscode-languageserver@7.0.0", "", { "dependencies": { "vscode-languageserver-protocol": "3.16.0" }, "bin": { "installServerIntoExtension": "bin/installServerIntoExtension" } }, "sha512-60HTx5ID+fLRcgdHfmz0LDZAXYEV68fzwG0JWwEPBode9NuMYTIxuYXPg4ngO8i8+Ou0lM7y6GzaYWbiDL0drw=="],
"yaml-language-server/yaml": ["yaml@2.2.2", "", {}, "sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA=="],
"@astrojs/react/@vitejs/plugin-react/@babel/core": ["@babel/core@7.27.3", "", { "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.27.3", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.27.3", "@babel/helpers": "^7.27.3", "@babel/parser": "^7.27.3", "@babel/template": "^7.27.2", "@babel/traverse": "^7.27.3", "@babel/types": "^7.27.3", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-hyrN8ivxfvJ4i0fIJuV4EOlV0WDMz5Ui4StRTgVaAvWeiRCilXgwVvxJKtFQ3TKtHgJscB2YiXKGNJuVwhQMtA=="],
"@astrojs/react/@vitejs/plugin-react/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.9", "", {}, "sha512-e9MeMtVWo186sgvFFJOPGy7/d2j2mZhLJIdVW0C/xDluuOvymEATqz6zKsP0ZmXGzQtqlyjz5sC1sYQUoJG98w=="],
"@babel/helper-compilation-targets/lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
"@octokit/plugin-paginate-rest/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@24.2.0", "", {}, "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg=="],
"@babel/helper-module-imports/@babel/traverse/@babel/parser": ["@babel/parser@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" }, "bin": "./bin/babel-parser.js" }, "sha512-xyYxRj6+tLNDTWi0KCBcZ9V7yg3/lwL9DWh9Uwh/RIVlIfFidggcgxKX3GCXwCiswwcGRawBKbEg2LG/Y8eJhw=="],
"@octokit/plugin-rest-endpoint-methods/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@24.2.0", "", {}, "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg=="],
"@babel/helper-module-transforms/@babel/traverse/@babel/parser": ["@babel/parser@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" }, "bin": "./bin/babel-parser.js" }, "sha512-xyYxRj6+tLNDTWi0KCBcZ9V7yg3/lwL9DWh9Uwh/RIVlIfFidggcgxKX3GCXwCiswwcGRawBKbEg2LG/Y8eJhw=="],
"ansi-align/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
"boxen/string-width/emoji-regex": ["emoji-regex@10.4.0", "", {}, "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw=="],
"ansi-align/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"boxen/string-width/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="],
"node-fetch/whatwg-url/tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="],
"node-fetch/whatwg-url/webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="],
"widest-line/string-width/emoji-regex": ["emoji-regex@10.4.0", "", {}, "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw=="],
"widest-line/string-width/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="],
"wrap-ansi/string-width/emoji-regex": ["emoji-regex@10.4.0", "", {}, "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw=="],
"wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="],
"yaml-language-server/vscode-languageserver/vscode-languageserver-protocol": ["vscode-languageserver-protocol@3.16.0", "", { "dependencies": { "vscode-jsonrpc": "6.0.0", "vscode-languageserver-types": "3.16.0" } }, "sha512-sdeUoAawceQdgIfTI+sdcwkiK2KU+2cbEYA0agzM2uqaUy2UpnnGHtWTHVEtS0ES4zHU0eMFRGN+oQgDxlD66A=="],
"@astrojs/react/@vitejs/plugin-react/@babel/core/@babel/helpers": ["@babel/helpers@7.27.3", "", { "dependencies": { "@babel/template": "^7.27.2", "@babel/types": "^7.27.3" } }, "sha512-h/eKy9agOya1IGuLaZ9tEUgz+uIRXcbtOhRtUyyMf8JFmn1iT13vnl/IGVWSkdOCG/pC57U4S1jnAabAavTMwg=="],
"@astrojs/react/@vitejs/plugin-react/@babel/core/@babel/parser": ["@babel/parser@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" }, "bin": "./bin/babel-parser.js" }, "sha512-xyYxRj6+tLNDTWi0KCBcZ9V7yg3/lwL9DWh9Uwh/RIVlIfFidggcgxKX3GCXwCiswwcGRawBKbEg2LG/Y8eJhw=="],
"@astrojs/react/@vitejs/plugin-react/@babel/core/@babel/traverse": ["@babel/traverse@7.27.3", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.27.3", "@babel/parser": "^7.27.3", "@babel/template": "^7.27.2", "@babel/types": "^7.27.3", "debug": "^4.3.1", "globals": "^11.1.0" } }, "sha512-lId/IfN/Ye1CIu8xG7oKBHXd2iNb2aW1ilPszzGcJug6M8RCKfVNcYhpI5+bMvFYjK7lXIM0R+a+6r8xhHp2FQ=="],
"@astrojs/react/@vitejs/plugin-react/@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
"boxen/string-width/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="],
"widest-line/string-width/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="],
"yaml-language-server/vscode-languageserver/vscode-languageserver-protocol/vscode-jsonrpc": ["vscode-jsonrpc@6.0.0", "", {}, "sha512-wnJA4BnEjOSyFMvjZdpiOwhSq9uDoK8e/kpRJDTaMYzwlkrhG1fwDIZI94CLsLzlCK5cIbMMtFlJlfR57Lavmg=="],
"yaml-language-server/vscode-languageserver/vscode-languageserver-protocol/vscode-languageserver-types": ["vscode-languageserver-types@3.16.0", "", {}, "sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA=="],
}
}

View File

@@ -7,11 +7,12 @@ services:
image: gitea/gitea:latest
container_name: gitea
restart: unless-stopped
entrypoint: ["/tmp/gitea-dev-init.sh"]
environment:
- USER_UID=1000
- USER_GID=1000
- GITEA__database__DB_TYPE=sqlite3
- GITEA__database__PATH=/data/gitea.db
- GITEA__database__PATH=/data/gitea/gitea.db
- GITEA__server__DOMAIN=localhost
- GITEA__server__ROOT_URL=http://localhost:3001/
- GITEA__server__SSH_DOMAIN=localhost
@@ -19,20 +20,24 @@ services:
- GITEA__server__START_SSH_SERVER=true
- GITEA__security__INSTALL_LOCK=true
- GITEA__service__DISABLE_REGISTRATION=false
- GITEA__log__MODE=console
- GITEA__log__LEVEL=Info
ports:
- "3001:3000"
- "2222:22"
volumes:
- gitea-data:/data
- gitea-config:/etc/gitea
- ./scripts/gitea-app.ini:/tmp/app.ini:ro
- ./scripts/gitea-dev-init.sh:/tmp/gitea-dev-init.sh:ro
networks:
- gitea-network
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000/api/healthz"]
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000/"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
timeout: 10s
retries: 5
start_period: 60s
# Development service connected to local Gitea
gitea-mirror-dev:

View File

@@ -1,7 +1,7 @@
{
"name": "gitea-mirror",
"type": "module",
"version": "2.13.0",
"version": "2.16.0",
"engines": {
"bun": ">=1.2.9"
},
@@ -31,50 +31,53 @@
"astro": "bunx --bun astro"
},
"dependencies": {
"@astrojs/mdx": "^4.2.6",
"@astrojs/node": "^9.2.1",
"@astrojs/react": "^4.2.7",
"@octokit/rest": "^21.1.1",
"@radix-ui/react-avatar": "^1.1.9",
"@radix-ui/react-checkbox": "^1.3.1",
"@astrojs/check": "^0.9.4",
"@astrojs/mdx": "^4.3.0",
"@astrojs/node": "^9.2.2",
"@astrojs/react": "^4.3.0",
"@octokit/rest": "^22.0.0",
"@radix-ui/react-accordion": "^1.2.11",
"@radix-ui/react-avatar": "^1.1.10",
"@radix-ui/react-checkbox": "^1.3.2",
"@radix-ui/react-collapsible": "^1.1.11",
"@radix-ui/react-dialog": "^1.1.13",
"@radix-ui/react-dropdown-menu": "^2.1.14",
"@radix-ui/react-label": "^2.1.6",
"@radix-ui/react-popover": "^1.1.13",
"@radix-ui/react-dialog": "^1.1.14",
"@radix-ui/react-dropdown-menu": "^2.1.15",
"@radix-ui/react-label": "^2.1.7",
"@radix-ui/react-popover": "^1.1.14",
"@radix-ui/react-radio-group": "^1.3.7",
"@radix-ui/react-scroll-area": "^1.2.9",
"@radix-ui/react-select": "^2.2.4",
"@radix-ui/react-select": "^2.2.5",
"@radix-ui/react-separator": "^1.1.7",
"@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-switch": "^1.2.5",
"@radix-ui/react-tabs": "^1.1.12",
"@radix-ui/react-tooltip": "^1.2.6",
"@tailwindcss/vite": "^4.1.7",
"@tanstack/react-virtual": "^3.13.8",
"@radix-ui/react-tooltip": "^1.2.7",
"@tailwindcss/vite": "^4.1.10",
"@tanstack/react-virtual": "^3.13.10",
"@types/canvas-confetti": "^1.9.0",
"@types/react": "^19.1.4",
"@types/react-dom": "^19.1.5",
"astro": "^5.7.13",
"@types/react": "^19.1.8",
"@types/react-dom": "^19.1.6",
"astro": "^5.9.3",
"bcryptjs": "^3.0.2",
"canvas-confetti": "^1.9.3",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
"drizzle-orm": "^0.43.1",
"drizzle-orm": "^0.44.2",
"fuse.js": "^7.1.0",
"jsonwebtoken": "^9.0.2",
"lucide-react": "^0.511.0",
"lucide-react": "^0.515.0",
"next-themes": "^0.4.6",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-icons": "^5.5.0",
"sonner": "^2.0.3",
"tailwind-merge": "^3.3.0",
"tailwindcss": "^4.1.7",
"tw-animate-css": "^1.3.0",
"sonner": "^2.0.5",
"tailwind-merge": "^3.3.1",
"tailwindcss": "^4.1.10",
"tw-animate-css": "^1.3.4",
"typescript": "^5.8.3",
"uuid": "^11.1.0",
"zod": "^3.25.7"
"zod": "^3.25.64"
},
"devDependencies": {
"@testing-library/jest-dom": "^6.6.3",
@@ -82,10 +85,10 @@
"@types/bcryptjs": "^3.0.0",
"@types/jsonwebtoken": "^9.0.9",
"@types/uuid": "^10.0.0",
"@vitejs/plugin-react": "^4.4.1",
"@vitejs/plugin-react": "^4.5.2",
"jsdom": "^26.1.0",
"tsx": "^4.19.4",
"vitest": "^3.1.4"
"tsx": "^4.20.3",
"vitest": "^3.2.3"
},
"packageManager": "bun@1.2.9"
"packageManager": "bun@1.2.16"
}

56
scripts/README-dev.md Normal file
View File

@@ -0,0 +1,56 @@
# Development Environment Setup
This directory contains scripts to help set up a development environment with a pre-configured Gitea instance.
## Default Credentials
For development convenience, the Gitea instance is pre-configured with:
- **Admin Username**: `admin`
- **Admin Password**: `admin123`
- **Gitea URL**: http://localhost:3001
## Files
- `gitea-app.ini` - Pre-configured Gitea settings for development
- `gitea-dev-init.sh` - Initialization script that copies the config on first run
- `gitea-init.sql` - SQL script to create default admin user (not currently used)
## Usage
1. Start the development environment:
```bash
docker compose -f docker-compose.dev.yml down
docker volume rm gitea-mirror_gitea-data gitea-mirror_gitea-config
docker compose -f docker-compose.dev.yml up -d
```
2. Wait for Gitea to start (check logs):
```bash
docker logs -f gitea
```
3. Access Gitea at http://localhost:3001 and login with:
- Username: `admin`
- Password: `admin123`
4. Generate an API token:
- Go to Settings → Applications
- Generate New Token
- Give it a name like "gitea-mirror"
- Select all permissions (for development)
- Copy the token
5. Configure gitea-mirror with the token in your `.env` file or through the web UI.
## Troubleshooting
If Gitea doesn't start properly:
1. Check logs: `docker logs gitea`
2. Ensure volumes are clean: `docker volume rm gitea-mirror_gitea-data gitea-mirror_gitea-config`
3. Restart: `docker compose -f docker-compose.dev.yml up -d`
## Security Note
⚠️ **These credentials are for development only!** Never use these settings in production.

View File

@@ -1,43 +1,47 @@
# LXC Container Deployment Guide
## Overview
Run **Gitea Mirror** in an isolated LXC container, either:
Run **Gitea Mirror** in an isolated LXC container:
1. **Online, on a Proxmox VE host** script pulls everything from GitHub
2. **Offline / LAN-only, on a developer laptop** script pushes your local checkout + Bun ZIP
1. **Proxmox VE (Recommended)** Using the community-maintained script
2. **Local Development** Using the local LXC script for testing
---
## 1. Proxmox VE (online, recommended for prod)
## 1. Proxmox VE Installation (Recommended)
### Prerequisites
* Proxmox VE node with the default `vmbr0` bridge
* Root shell on the node
* Ubuntu 22.04 LXC template present (`pveam update && pveam download ...`)
* Proxmox VE host with internet access
* Root shell access on the Proxmox node
### One-command install
```bash
# Community-maintained script for Proxmox VE by Tobias ([CrazyWolf13](https://github.com/CrazyWolf13))
# at [community-scripts/ProxmoxVE](https://github.com/community-scripts/ProxmoxVE)
# Official documentation: https://community-scripts.github.io/ProxmoxVE/scripts?id=gitea-mirror
# Community-maintained script from the Proxmox VE Community Scripts project
bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/gitea-mirror.sh)"
```
What it does:
### What the script does:
* Uses the community-maintained script from [Community Scripts for Proxmox VE](https://community-scripts.github.io/ProxmoxVE/)
* Installs dependencies and Bun runtime
* Clones & builds `arunavo4/gitea-mirror`
* Creates a systemd service and starts it
* Sets up a random `JWT_SECRET` for security
* Creates a privileged Alpine Linux LXC container
* Installs Bun runtime environment
* Clones the Gitea Mirror repository
* Builds the application
* Configures a systemd service for automatic startup
* Sets up the application to run on port 4321
* Generates a secure `JWT_SECRET` automatically
Browse to:
### Accessing Gitea Mirror:
```
http://<container-ip>:4321
```
### Additional Information:
* **Script Source**: [Community Scripts for Proxmox VE](https://github.com/community-scripts/ProxmoxVE)
* **Documentation**: [Gitea Mirror Script Documentation](https://community-scripts.github.io/ProxmoxVE/scripts?id=gitea-mirror)
* **Support**: [Community Scripts Discord](https://discord.gg/fiXVvSHnBU)
---
## 2. Local testing (LXD on a workstation, works offline)

68
scripts/gitea-app.ini Normal file
View File

@@ -0,0 +1,68 @@
APP_NAME = Gitea: Git with a cup of tea
RUN_MODE = prod
[database]
DB_TYPE = sqlite3
PATH = /data/gitea/gitea.db
[repository]
ROOT = /data/git/repositories
[server]
SSH_DOMAIN = localhost
DOMAIN = localhost
HTTP_PORT = 3000
ROOT_URL = http://localhost:3001/
DISABLE_SSH = false
SSH_PORT = 22
LFS_START_SERVER = true
LFS_JWT_SECRET = _oaWNP5sCH5cSECa-K_HvCXeXhg-zN5H0cU5vVQAZr4
OFFLINE_MODE = false
[security]
INSTALL_LOCK = true
SECRET_KEY = vLu5OuX0EweZjDNxKPQ5V9DXXXX8cJiKpJyQylKkMVTrNdFAzlUlNdYLYfiCybu
INTERNAL_TOKEN = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE3MjgzMTk1MDB9.Lz0cJB_DCLmJFh8FqDX0z9IUcxfY9jPftHEGvz_WeHo
PASSWORD_HASH_ALGO = pbkdf2
[service]
DISABLE_REGISTRATION = false
REQUIRE_SIGNIN_VIEW = false
REGISTER_EMAIL_CONFIRM = false
ENABLE_NOTIFY_MAIL = false
ALLOW_ONLY_EXTERNAL_REGISTRATION = false
ENABLE_CAPTCHA = false
DEFAULT_KEEP_EMAIL_PRIVATE = false
DEFAULT_ALLOW_CREATE_ORGANIZATION = true
DEFAULT_ENABLE_TIMETRACKING = true
NO_REPLY_ADDRESS = noreply.localhost
[oauth2]
JWT_SECRET = gQXt_D8B-VJGCvFfJ9xEj5yp8mOd6fAza8TKc9rJJYw
[lfs]
PATH = /data/git/lfs
[mailer]
ENABLED = false
[openid]
ENABLE_OPENID_SIGNIN = true
ENABLE_OPENID_SIGNUP = true
[session]
PROVIDER = file
[log]
MODE = console
LEVEL = Info
ROOT_PATH = /data/gitea/log
[repository.pull-request]
DEFAULT_MERGE_STYLE = merge
[repository.signing]
DEFAULT_TRUST_MODEL = committer
[actions]
ENABLED = false

14
scripts/gitea-create-admin.sh Executable file
View File

@@ -0,0 +1,14 @@
#!/bin/sh
# Create admin user for Gitea development instance
echo "Creating admin user for Gitea..."
docker exec -u git gitea gitea admin user create \
--username admin \
--password admin123 \
--email admin@localhost \
--admin \
--must-change-password=false
echo "Admin user created!"
echo "Username: admin"
echo "Password: admin123"

32
scripts/gitea-dev-init.sh Executable file
View File

@@ -0,0 +1,32 @@
#!/bin/sh
# Initialize Gitea for development with pre-configured settings
# Create necessary directories
mkdir -p /data/gitea/conf
# Copy pre-configured app.ini if it doesn't exist
if [ ! -f /data/gitea/conf/app.ini ]; then
echo "Initializing Gitea with development configuration..."
cp /tmp/app.ini /data/gitea/conf/app.ini
chown 1000:1000 /data/gitea/conf/app.ini
fi
# Start Gitea in background
/usr/bin/entrypoint "$@" &
GITEA_PID=$!
# Wait for Gitea to be ready
echo "Waiting for Gitea to start..."
until wget --no-verbose --tries=1 --spider http://localhost:3000/ 2>/dev/null; do
sleep 2
done
# Create admin user if it doesn't exist
if [ ! -f /data/.admin_created ]; then
echo "Creating default admin user..."
su git -c "gitea admin user create --username admin --password admin123 --email admin@localhost --admin --must-change-password=false" && \
touch /data/.admin_created
fi
# Keep Gitea running in foreground
wait $GITEA_PID

View File

@@ -598,7 +598,7 @@ export function ConfigTabs() {
{/* Content section - Grid layout */}
<div className="space-y-6">
{/* GitHub & Gitea connections - Side by side */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 md:items-stretch">
<GitHubConfigForm
config={config.githubConfig}
setConfig={update =>

View File

@@ -1,47 +0,0 @@
import React from 'react';
import { GitHubConfigForm } from './GitHubConfigForm';
import { GiteaConfigForm } from './GiteaConfigForm';
import { Separator } from '../ui/separator';
import type { GitHubConfig, GiteaConfig } from '@/types/config';
interface ConnectionsFormProps {
githubConfig: GitHubConfig;
giteaConfig: GiteaConfig;
setGithubConfig: (update: GitHubConfig | ((prev: GitHubConfig) => GitHubConfig)) => void;
setGiteaConfig: (update: GiteaConfig | ((prev: GiteaConfig) => GiteaConfig)) => void;
onAutoSaveGitHub?: (config: GitHubConfig) => Promise<void>;
onAutoSaveGitea?: (config: GiteaConfig) => Promise<void>;
isAutoSavingGitHub?: boolean;
isAutoSavingGitea?: boolean;
}
export function ConnectionsForm({
githubConfig,
giteaConfig,
setGithubConfig,
setGiteaConfig,
onAutoSaveGitHub,
onAutoSaveGitea,
isAutoSavingGitHub,
isAutoSavingGitea,
}: ConnectionsFormProps) {
return (
<div className="space-y-6">
<GitHubConfigForm
config={githubConfig}
setConfig={setGithubConfig}
onAutoSave={onAutoSaveGitHub}
isAutoSaving={isAutoSavingGitHub}
/>
<Separator />
<GiteaConfigForm
config={giteaConfig}
setConfig={setGiteaConfig}
onAutoSave={onAutoSaveGitea}
isAutoSaving={isAutoSavingGitea}
/>
</div>
);
}

View File

@@ -1,23 +1,23 @@
import React, { useEffect, useState } from "react";
import React, { useState } from "react";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { githubApi } from "@/lib/api";
import type { GitHubConfig, MirrorOptions, AdvancedOptions } from "@/types/config";
import { Input } from "../ui/input";
import { Checkbox } from "../ui/checkbox";
import { toast } from "sonner";
import { AlertTriangle } from "lucide-react";
import { Alert, AlertDescription } from "../ui/alert";
import { Info } from "lucide-react";
import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip";
import { GitHubMirrorSettings } from "./GitHubMirrorSettings";
import { Separator } from "../ui/separator";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
interface GitHubConfigFormProps {
config: GitHubConfig;
@@ -87,7 +87,7 @@ export function GitHubConfigForm({
};
return (
<Card className="w-full self-start">
<Card className="w-full h-full flex flex-col">
<CardHeader className="flex flex-row items-center justify-between gap-4">
<CardTitle className="text-lg font-semibold">
GitHub Configuration
@@ -102,7 +102,7 @@ export function GitHubConfigForm({
</Button>
</CardHeader>
<CardContent className="flex flex-col gap-y-6">
<CardContent className="flex flex-col gap-y-6 flex-1">
<div>
<label
htmlFor="github-username"
@@ -123,12 +123,52 @@ export function GitHubConfigForm({
</div>
<div>
<label
htmlFor="github-token"
className="block text-sm font-medium mb-1.5"
>
GitHub Token
</label>
<div className="flex items-center gap-2 mb-1.5">
<label
htmlFor="github-token"
className="block text-sm font-medium"
>
GitHub Token
</label>
<Popover>
<PopoverTrigger asChild>
<button
type="button"
className="p-0.5 hover:bg-muted rounded-sm transition-colors"
>
<Info className="h-3.5 w-3.5 text-muted-foreground" />
</button>
</PopoverTrigger>
<PopoverContent side="right" align="start" className="w-80">
<div className="space-y-2">
<h4 className="font-medium text-sm">GitHub Token Requirements</h4>
<div className="text-sm space-y-2">
<p>
You need to create a <span className="font-medium">Classic GitHub PAT Token</span> with the following scopes:
</p>
<ul className="ml-4 space-y-1 list-disc">
<li><code className="text-xs bg-muted px-1 py-0.5 rounded">repo</code></li>
<li><code className="text-xs bg-muted px-1 py-0.5 rounded">admin:org</code></li>
</ul>
<p className="text-muted-foreground">
The organization access is required for mirroring organization repositories.
</p>
<p>
Generate tokens at{" "}
<a
href="https://github.com/settings/tokens"
target="_blank"
rel="noopener noreferrer"
className="text-primary hover:underline font-medium"
>
github.com/settings/tokens
</a>
</p>
</div>
</div>
</PopoverContent>
</Popover>
</div>
<Input
id="github-token"
name="token"
@@ -136,7 +176,7 @@ export function GitHubConfigForm({
value={config.token}
onChange={handleChange}
className="bg-background"
placeholder="Your GitHub personal access token"
placeholder="Your GitHub token (classic) with repo and admin:org scopes"
/>
<p className="text-xs text-muted-foreground mt-1">
Required for private repositories, organizations, and starred
@@ -163,45 +203,8 @@ export function GitHubConfigForm({
if (onAdvancedOptionsAutoSave) onAdvancedOptionsAutoSave(newOptions);
}}
/>
</CardContent>
</CardContent>
<CardFooter className="flex-col items-start">
<Alert variant="note" className="w-full">
<AlertTriangle className="h-4 w-4 text-blue-600 dark:text-blue-400 mr-2" />
<AlertDescription className="text-sm">
<div className="font-semibold mb-1">Note:</div>
<div className="mb-1">
You need to create a{" "}
<span className="font-semibold">Classic GitHub PAT Token</span>{" "}
with following scopes:
</div>
<ul className="ml-4 mb-1 list-disc">
<li>
<code>repo</code>
</li>
<li>
<code>admin:org</code>
</li>
</ul>
<div className="mb-1">
The organization access is required for mirroring organization
repositories.
</div>
<div>
You can generate tokens at{" "}
<a
href="https://github.com/settings/tokens"
target="_blank"
rel="noopener noreferrer"
className="underline font-medium hover:text-blue-900 dark:hover:text-blue-200"
>
github.com/settings/tokens
</a>
.
</div>
</AlertDescription>
</Alert>
</CardFooter>
</Card>
);
}

View File

@@ -3,17 +3,22 @@ import { Checkbox } from "@/components/ui/checkbox";
import { Label } from "@/components/ui/label";
import { Separator } from "@/components/ui/separator";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import {
Info,
GitBranch,
Star,
Building2,
Lock,
Archive,
GitPullRequest,
@@ -22,7 +27,9 @@ import {
MessageSquare,
Target,
BookOpen,
GitFork
GitFork,
ChevronDown,
Funnel
} from "lucide-react";
import type { GitHubConfig, MirrorOptions, AdvancedOptions } from "@/types/config";
import { cn } from "@/lib/utils";
@@ -69,6 +76,18 @@ export function GitHubMirrorSettings({
// When metadata is disabled, all components should be disabled
const isMetadataEnabled = mirrorOptions.mirrorMetadata;
// Calculate what content is included for starred repos
const starredRepoContent = {
code: true, // Always included
releases: !advancedOptions.skipStarredIssues && mirrorOptions.mirrorReleases,
issues: !advancedOptions.skipStarredIssues && mirrorOptions.mirrorMetadata && mirrorOptions.metadataComponents.issues,
pullRequests: !advancedOptions.skipStarredIssues && mirrorOptions.mirrorMetadata && mirrorOptions.metadataComponents.pullRequests,
wiki: !advancedOptions.skipStarredIssues && mirrorOptions.mirrorMetadata && mirrorOptions.metadataComponents.wiki,
};
const starredContentCount = Object.entries(starredRepoContent).filter(([key, value]) => key !== 'code' && value).length;
const totalStarredOptions = 4; // releases, issues, PRs, wiki
return (
<div className="space-y-6">
@@ -100,28 +119,162 @@ export function GitHubMirrorSettings({
Include private repositories
</Label>
<p className="text-xs text-muted-foreground">
Mirror your private repositories (requires appropriate token permissions)
Mirror your private repositories
</p>
</div>
</div>
<div className="flex items-start space-x-3">
<Checkbox
id="starred-repos"
checked={githubConfig.mirrorStarred}
onCheckedChange={(checked) => handleGitHubChange('mirrorStarred', !!checked)}
/>
<div className="space-y-0.5 flex-1">
<Label
htmlFor="starred-repos"
className="text-sm font-normal cursor-pointer flex items-center gap-2"
>
<Star className="h-3.5 w-3.5" />
Mirror starred repositories
</Label>
<p className="text-xs text-muted-foreground">
Include repositories you've starred on GitHub
</p>
<div className="flex items-start justify-between gap-4">
<div className="flex items-start space-x-3">
<Checkbox
id="starred-repos"
checked={githubConfig.mirrorStarred}
onCheckedChange={(checked) => handleGitHubChange('mirrorStarred', !!checked)}
/>
<div className="space-y-0.5 flex-1">
<Label
htmlFor="starred-repos"
className="text-sm font-normal cursor-pointer flex items-center gap-2"
>
<Star className="h-3.5 w-3.5" />
Mirror starred repositories
</Label>
<p className="text-xs text-muted-foreground">
Include repositories you've starred on GitHub
</p>
</div>
</div>
{/* Starred repos content selection - inline to prevent layout shift */}
<div className={cn(
"flex items-center justify-end transition-opacity duration-200",
githubConfig.mirrorStarred ? "opacity-100" : "opacity-0 pointer-events-none"
)}>
<Popover>
<PopoverTrigger asChild>
<Button
variant="outline"
size="sm"
disabled={!githubConfig.mirrorStarred}
className="h-8 text-xs font-normal min-w-[140px] justify-between"
>
<span>
{advancedOptions.skipStarredIssues ? (
"Code only"
) : starredContentCount === 0 ? (
"Code only"
) : starredContentCount === totalStarredOptions ? (
"Full content"
) : (
`${starredContentCount + 1} of ${totalStarredOptions + 1} selected`
)}
</span>
<ChevronDown className="ml-2 h-3 w-3 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent align="end" className="w-72">
<div className="space-y-3">
<div className="flex items-center justify-between">
<div className="text-sm font-medium">Starred repos content</div>
<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<Info className="h-3.5 w-3.5 text-muted-foreground" />
</TooltipTrigger>
<TooltipContent side="left" className="max-w-xs">
<p className="text-xs">
Choose what content to mirror from starred repositories.
Selecting "Lightweight mode" will only mirror code for better performance.
</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
<Separator className="my-2" />
<div className="space-y-3">
<div className="flex items-center space-x-3 py-1 px-1 rounded hover:bg-accent">
<Checkbox
id="starred-lightweight"
checked={advancedOptions.skipStarredIssues}
onCheckedChange={(checked) => handleAdvancedChange('skipStarredIssues', !!checked)}
/>
<Label
htmlFor="starred-lightweight"
className="text-sm font-normal cursor-pointer flex-1"
>
<div className="space-y-0.5">
<div className="font-medium">Lightweight mode</div>
<div className="text-xs text-muted-foreground">
Only mirror code, skip all metadata
</div>
</div>
</Label>
</div>
{!advancedOptions.skipStarredIssues && (
<>
<Separator className="my-2" />
<div className="space-y-2">
<p className="text-xs font-medium text-muted-foreground">
Content included for starred repos:
</p>
<div className="space-y-1.5">
<div className="flex items-center gap-2 text-xs pl-2">
<GitBranch className="h-3 w-3 text-muted-foreground" />
<span>Source code</span>
<Badge variant="secondary" className="ml-auto text-[10px] px-2 h-4">Always</Badge>
</div>
<div className={cn(
"flex items-center gap-2 text-xs pl-2",
starredRepoContent.releases ? "" : "opacity-50"
)}>
<Tag className="h-3 w-3 text-muted-foreground" />
<span>Releases & Tags</span>
{starredRepoContent.releases && <Badge variant="outline" className="ml-auto text-[10px] px-2 h-4">Included</Badge>}
</div>
<div className={cn(
"flex items-center gap-2 text-xs pl-2",
starredRepoContent.issues ? "" : "opacity-50"
)}>
<MessageSquare className="h-3 w-3 text-muted-foreground" />
<span>Issues</span>
{starredRepoContent.issues && <Badge variant="outline" className="ml-auto text-[10px] px-2 h-4">Included</Badge>}
</div>
<div className={cn(
"flex items-center gap-2 text-xs pl-2",
starredRepoContent.pullRequests ? "" : "opacity-50"
)}>
<GitPullRequest className="h-3 w-3 text-muted-foreground" />
<span>Pull Requests</span>
{starredRepoContent.pullRequests && <Badge variant="outline" className="ml-auto text-[10px] px-2 h-4">Included</Badge>}
</div>
<div className={cn(
"flex items-center gap-2 text-xs pl-2",
starredRepoContent.wiki ? "" : "opacity-50"
)}>
<BookOpen className="h-3 w-3 text-muted-foreground" />
<span>Wiki</span>
{starredRepoContent.wiki && <Badge variant="outline" className="ml-auto text-[10px] px-2 h-4">Included</Badge>}
</div>
</div>
<p className="text-[10px] text-muted-foreground mt-2">
To include more content, enable them in the Content & Data section below
</p>
</div>
</>
)}
</div>
</div>
</PopoverContent>
</Popover>
</div>
</div>
</div>
@@ -172,7 +325,7 @@ export function GitHubMirrorSettings({
</div>
</div>
<div className="space-y-3">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
<div className="flex items-start space-x-3">
<Checkbox
id="mirror-metadata"
@@ -193,92 +346,142 @@ export function GitHubMirrorSettings({
</div>
</div>
{/* Metadata sub-options */}
{mirrorOptions.mirrorMetadata && (
<div className="ml-7 space-y-2 p-3 bg-muted/30 dark:bg-muted/10 rounded-md">
<div className="grid grid-cols-2 gap-3">
<div className="flex items-center space-x-2">
<Checkbox
id="metadata-issues"
checked={mirrorOptions.metadataComponents.issues}
onCheckedChange={(checked) => handleMetadataComponentChange('issues', !!checked)}
disabled={!isMetadataEnabled}
/>
<Label
htmlFor="metadata-issues"
className="text-sm font-normal cursor-pointer flex items-center gap-1.5"
>
<MessageSquare className="h-3 w-3" />
Issues
</Label>
</div>
{/* Metadata multi-select - inline to prevent layout shift */}
<div className={cn(
"flex items-center justify-end transition-opacity duration-200",
mirrorOptions.mirrorMetadata ? "opacity-100" : "opacity-0 pointer-events-none"
)}>
<Popover>
<PopoverTrigger asChild>
<Button
variant="outline"
size="sm"
disabled={!mirrorOptions.mirrorMetadata}
className="h-8 text-xs font-normal min-w-[140px] justify-between"
>
<span>
{(() => {
const selectedCount = Object.values(mirrorOptions.metadataComponents).filter(Boolean).length;
const totalCount = Object.keys(mirrorOptions.metadataComponents).length;
if (selectedCount === 0) return "No items selected";
if (selectedCount === totalCount) return "All items selected";
return `${selectedCount} of ${totalCount} selected`;
})()}
</span>
<ChevronDown className="ml-2 h-3 w-3 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent align="end" className="w-64">
<div className="space-y-3">
<div className="flex items-center justify-between">
<div className="text-sm font-medium">Metadata to mirror</div>
<Button
variant="ghost"
size="sm"
className="h-auto px-2 py-1 text-xs font-normal text-primary hover:text-primary/80"
onClick={() => {
const allSelected = Object.values(mirrorOptions.metadataComponents).every(Boolean);
const newValue = !allSelected;
// Update all metadata components at once
onMirrorOptionsChange({
...mirrorOptions,
metadataComponents: {
issues: newValue,
pullRequests: newValue,
labels: newValue,
milestones: newValue,
wiki: newValue,
},
});
}}
>
{Object.values(mirrorOptions.metadataComponents).every(Boolean) ? 'Deselect all' : 'Select all'}
</Button>
</div>
<Separator className="my-2" />
<div className="space-y-2">
<div className="flex items-center space-x-3 py-1 px-1 rounded hover:bg-accent">
<Checkbox
id="metadata-issues-popup"
checked={mirrorOptions.metadataComponents.issues}
onCheckedChange={(checked) => handleMetadataComponentChange('issues', !!checked)}
/>
<Label
htmlFor="metadata-issues-popup"
className="text-sm font-normal cursor-pointer flex items-center gap-2 flex-1"
>
<MessageSquare className="h-3.5 w-3.5 text-muted-foreground" />
Issues
</Label>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="metadata-prs"
checked={mirrorOptions.metadataComponents.pullRequests}
onCheckedChange={(checked) => handleMetadataComponentChange('pullRequests', !!checked)}
disabled={!isMetadataEnabled}
/>
<Label
htmlFor="metadata-prs"
className="text-sm font-normal cursor-pointer flex items-center gap-1.5"
>
<GitPullRequest className="h-3 w-3" />
Pull Requests
</Label>
</div>
<div className="flex items-center space-x-3 py-1 px-1 rounded hover:bg-accent">
<Checkbox
id="metadata-prs-popup"
checked={mirrorOptions.metadataComponents.pullRequests}
onCheckedChange={(checked) => handleMetadataComponentChange('pullRequests', !!checked)}
/>
<Label
htmlFor="metadata-prs-popup"
className="text-sm font-normal cursor-pointer flex items-center gap-2 flex-1"
>
<GitPullRequest className="h-3.5 w-3.5 text-muted-foreground" />
Pull Requests
</Label>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="metadata-labels"
checked={mirrorOptions.metadataComponents.labels}
onCheckedChange={(checked) => handleMetadataComponentChange('labels', !!checked)}
disabled={!isMetadataEnabled}
/>
<Label
htmlFor="metadata-labels"
className="text-sm font-normal cursor-pointer flex items-center gap-1.5"
>
<Tag className="h-3 w-3" />
Labels
</Label>
</div>
<div className="flex items-center space-x-3 py-1 px-1 rounded hover:bg-accent">
<Checkbox
id="metadata-labels-popup"
checked={mirrorOptions.metadataComponents.labels}
onCheckedChange={(checked) => handleMetadataComponentChange('labels', !!checked)}
/>
<Label
htmlFor="metadata-labels-popup"
className="text-sm font-normal cursor-pointer flex items-center gap-2 flex-1"
>
<Tag className="h-3.5 w-3.5 text-muted-foreground" />
Labels
</Label>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="metadata-milestones"
checked={mirrorOptions.metadataComponents.milestones}
onCheckedChange={(checked) => handleMetadataComponentChange('milestones', !!checked)}
disabled={!isMetadataEnabled}
/>
<Label
htmlFor="metadata-milestones"
className="text-sm font-normal cursor-pointer flex items-center gap-1.5"
>
<Target className="h-3 w-3" />
Milestones
</Label>
</div>
<div className="flex items-center space-x-3 py-1 px-1 rounded hover:bg-accent">
<Checkbox
id="metadata-milestones-popup"
checked={mirrorOptions.metadataComponents.milestones}
onCheckedChange={(checked) => handleMetadataComponentChange('milestones', !!checked)}
/>
<Label
htmlFor="metadata-milestones-popup"
className="text-sm font-normal cursor-pointer flex items-center gap-2 flex-1"
>
<Target className="h-3.5 w-3.5 text-muted-foreground" />
Milestones
</Label>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="metadata-wiki"
checked={mirrorOptions.metadataComponents.wiki}
onCheckedChange={(checked) => handleMetadataComponentChange('wiki', !!checked)}
disabled={!isMetadataEnabled}
/>
<Label
htmlFor="metadata-wiki"
className="text-sm font-normal cursor-pointer flex items-center gap-1.5"
>
<BookOpen className="h-3 w-3" />
Wiki
</Label>
<div className="flex items-center space-x-3 py-1 px-1 rounded hover:bg-accent">
<Checkbox
id="metadata-wiki-popup"
checked={mirrorOptions.metadataComponents.wiki}
onCheckedChange={(checked) => handleMetadataComponentChange('wiki', !!checked)}
/>
<Label
htmlFor="metadata-wiki-popup"
className="text-sm font-normal cursor-pointer flex items-center gap-2 flex-1"
>
<BookOpen className="h-3.5 w-3.5 text-muted-foreground" />
Wiki
</Label>
</div>
</div>
</div>
</div>
</div>
)}
</PopoverContent>
</Popover>
</div>
</div>
</div>
</div>
@@ -289,7 +492,7 @@ export function GitHubMirrorSettings({
<div className="space-y-4">
<div>
<h4 className="text-sm font-medium mb-3 flex items-center gap-2">
<Building2 className="h-4 w-4" />
<Funnel className="h-4 w-4" />
Filtering & Behavior
</h4>
<p className="text-xs text-muted-foreground mb-4">
@@ -317,42 +520,6 @@ export function GitHubMirrorSettings({
</p>
</div>
</div>
{githubConfig.mirrorStarred && (
<div className="flex items-start space-x-3">
<Checkbox
id="skip-starred-metadata"
checked={advancedOptions.skipStarredIssues}
onCheckedChange={(checked) => handleAdvancedChange('skipStarredIssues', !!checked)}
/>
<div className="space-y-0.5 flex-1">
<Label
htmlFor="skip-starred-metadata"
className="text-sm font-normal cursor-pointer flex items-center gap-2"
>
<Star className="h-3.5 w-3.5" />
Lightweight starred repository mirroring
<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<Info className="h-3 w-3 text-muted-foreground" />
</TooltipTrigger>
<TooltipContent side="right" className="max-w-xs">
<p className="text-xs">
When enabled, starred repositories will only mirror code,
skipping issues, PRs, and other metadata to reduce storage
and improve performance.
</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</Label>
<p className="text-xs text-muted-foreground">
Only mirror code from starred repos, skip issues and metadata
</p>
</div>
</div>
)}
</div>
</div>
</div>

View File

@@ -3,24 +3,14 @@ import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "../ui/select";
import { Checkbox } from "../ui/checkbox";
import { giteaApi } from "@/lib/api";
import type { GiteaConfig, GiteaOrgVisibility, MirrorStrategy } from "@/types/config";
import type { GiteaConfig, MirrorStrategy } from "@/types/config";
import { toast } from "sonner";
import { Info } from "lucide-react";
import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip";
import { OrganizationStrategy } from "./OrganizationStrategy";
import { OrganizationConfiguration } from "./OrganizationConfiguration";
import { Separator } from "../ui/separator";
interface GiteaConfigFormProps {
@@ -133,7 +123,7 @@ export function GiteaConfigForm({ config, setConfig, onAutoSave, isAutoSaving, g
};
return (
<Card className="w-full self-start">
<Card className="w-full h-full flex flex-col">
<CardHeader className="flex flex-row items-center justify-between gap-4">
<CardTitle className="text-lg font-semibold">
Gitea Configuration
@@ -148,7 +138,7 @@ export function GiteaConfigForm({ config, setConfig, onAutoSave, isAutoSaving, g
</Button>
</CardHeader>
<CardContent className="flex flex-col gap-y-6">
<CardContent className="flex flex-col gap-y-6 flex-1">
<div>
<label
htmlFor="gitea-username"
@@ -210,13 +200,24 @@ export function GiteaConfigForm({ config, setConfig, onAutoSave, isAutoSaving, g
</p>
</div>
<Separator className="my-2" />
<Separator />
<OrganizationStrategy
strategy={mirrorStrategy}
destinationOrg={config.organization}
starredReposOrg={config.starredReposOrg}
onStrategyChange={setMirrorStrategy}
githubUsername={githubUsername}
giteaUsername={config.username}
/>
<Separator />
<OrganizationConfiguration
strategy={mirrorStrategy}
destinationOrg={config.organization}
starredReposOrg={config.starredReposOrg}
visibility={config.visibility}
onDestinationOrgChange={(org) => {
const newConfig = { ...config, organization: org };
setConfig(newConfig);
@@ -227,54 +228,13 @@ export function GiteaConfigForm({ config, setConfig, onAutoSave, isAutoSaving, g
setConfig(newConfig);
if (onAutoSave) onAutoSave(newConfig);
}}
githubUsername={githubUsername}
giteaUsername={config.username}
onVisibilityChange={(visibility) => {
const newConfig = { ...config, visibility };
setConfig(newConfig);
if (onAutoSave) onAutoSave(newConfig);
}}
/>
<Separator className="my-2" />
<div>
<label
htmlFor="visibility"
className="block text-sm font-medium mb-1.5"
>
Organization Visibility
</label>
<Select
name="visibility"
value={config.visibility}
onValueChange={(value) =>
handleChange({
target: { name: "visibility", value },
} as React.ChangeEvent<HTMLInputElement>)
}
>
<SelectTrigger className="w-full border border-input dark:bg-background dark:hover:bg-background">
<SelectValue placeholder="Select visibility" />
</SelectTrigger>
<SelectContent className="bg-background text-foreground border border-input shadow-sm">
{(["public", "private", "limited"] as GiteaOrgVisibility[]).map(
(option) => (
<SelectItem
key={option}
value={option}
className="cursor-pointer text-sm px-3 py-2 hover:bg-accent focus:bg-accent focus:text-accent-foreground"
>
{option.charAt(0).toUpperCase() + option.slice(1)}
</SelectItem>
)
)}
</SelectContent>
</Select>
<p className="text-xs text-muted-foreground mt-1">
Visibility for newly created organizations
</p>
</div>
</CardContent>
<CardFooter className="">
{/* Footer content can be added here if needed */}
</CardFooter>
</Card>
);
}

View File

@@ -0,0 +1,163 @@
import React from "react";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Star, Globe, Lock, Shield, Info, MonitorCog } from "lucide-react";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { cn } from "@/lib/utils";
import type { MirrorStrategy, GiteaOrgVisibility } from "@/types/config";
interface OrganizationConfigurationProps {
strategy: MirrorStrategy;
destinationOrg?: string;
starredReposOrg?: string;
visibility: GiteaOrgVisibility;
onDestinationOrgChange: (org: string) => void;
onStarredReposOrgChange: (org: string) => void;
onVisibilityChange: (visibility: GiteaOrgVisibility) => void;
}
const visibilityOptions = [
{ value: "public" as GiteaOrgVisibility, label: "Public", icon: Globe, description: "Visible to everyone" },
{ value: "private" as GiteaOrgVisibility, label: "Private", icon: Lock, description: "Visible to members only" },
{ value: "limited" as GiteaOrgVisibility, label: "Limited", icon: Shield, description: "Visible to logged-in users" },
];
export const OrganizationConfiguration: React.FC<OrganizationConfigurationProps> = ({
strategy,
destinationOrg,
starredReposOrg,
visibility,
onDestinationOrgChange,
onStarredReposOrgChange,
onVisibilityChange,
}) => {
return (
<div className="space-y-4">
<div>
<h4 className="text-sm font-medium mb-3 flex items-center gap-2">
<MonitorCog className="h-4 w-4" />
Organization Configuration
</h4>
</div>
{/* First row - Organization inputs with consistent layout */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{/* Left column - always shows starred repos org */}
<div className="space-y-1">
<Label htmlFor="starredReposOrg" className="text-sm font-normal flex items-center gap-2">
<Star className="h-3.5 w-3.5" />
Starred Repositories Organization
<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<Info className="h-3.5 w-3.5 text-muted-foreground" />
</TooltipTrigger>
<TooltipContent>
<p>Starred repositories will be organized separately in this organization</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</Label>
<Input
id="starredReposOrg"
value={starredReposOrg || ""}
onChange={(e) => onStarredReposOrgChange(e.target.value)}
placeholder="starred"
className=""
/>
<p className="text-xs text-muted-foreground mt-1">
Keep starred repos organized separately
</p>
</div>
{/* Right column - shows destination org for single-org, empty div for others */}
{strategy === "single-org" ? (
<div className="space-y-1">
<Label htmlFor="destinationOrg" className="text-sm font-normal flex items-center gap-2">
Destination Organization
<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<Info className="h-3.5 w-3.5 text-muted-foreground" />
</TooltipTrigger>
<TooltipContent>
<p>All repositories will be mirrored to this organization</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</Label>
<Input
id="destinationOrg"
value={destinationOrg || ""}
onChange={(e) => onDestinationOrgChange(e.target.value)}
placeholder="github-mirrors"
className=""
/>
<p className="text-xs text-muted-foreground mt-1">
Organization for consolidated repositories
</p>
</div>
) : (
<div className="hidden md:block" />
)}
</div>
{/* Second row - Organization Visibility (always shown) */}
<div className="space-y-2">
<Label className="text-sm font-normal flex items-center gap-2">
Organization Visibility
<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<Info className="h-3.5 w-3.5 text-muted-foreground" />
</TooltipTrigger>
<TooltipContent>
<p>Default visibility for newly created organizations</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</Label>
<div className="grid grid-cols-3 gap-2">
{visibilityOptions.map((option) => {
const Icon = option.icon;
const isSelected = visibility === option.value;
return (
<TooltipProvider key={option.value}>
<Tooltip>
<TooltipTrigger asChild>
<button
type="button"
onClick={() => onVisibilityChange(option.value)}
className={cn(
"flex items-center justify-between px-3 py-2 rounded-md text-sm transition-all",
"border group",
isSelected
? "bg-accent border-accent-foreground/20"
: "bg-background hover:bg-accent/50 border-input"
)}
>
<div className="flex items-center gap-2">
<Icon className="h-3.5 w-3.5" />
<span>{option.label}</span>
</div>
<Info className="h-3 w-3 text-muted-foreground opacity-50 group-hover:opacity-100 transition-opacity" />
</button>
</TooltipTrigger>
<TooltipContent>
<p className="text-xs">{option.description}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
);
})}
</div>
</div>
</div>
);
};

View File

@@ -1,22 +1,13 @@
import React, { useState } from "react";
import React from "react";
import { Card } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { Badge } from "@/components/ui/badge";
import { Info, GitBranch, FolderTree, Package, Star, Building2, User, ChevronDown, ChevronUp } from "lucide-react";
import { Info, GitBranch, FolderTree, Star, Building2, User, Building } from "lucide-react";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { cn } from "@/lib/utils";
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/components/ui/collapsible";
export type MirrorStrategy = "preserve" | "single-org" | "flat-user";
@@ -25,226 +16,201 @@ interface OrganizationStrategyProps {
destinationOrg?: string;
starredReposOrg?: string;
onStrategyChange: (strategy: MirrorStrategy) => void;
onDestinationOrgChange: (org: string) => void;
onStarredReposOrgChange: (org: string) => void;
githubUsername?: string;
giteaUsername?: string;
}
const strategyConfig = {
preserve: {
title: "Mirror GitHub Structure",
title: "Preserve Structure",
icon: FolderTree,
description: "Keep the same organization structure as GitHub",
description: "Keep the exact same organization structure as GitHub",
color: "text-blue-600 dark:text-blue-400",
bgColor: "bg-blue-50 dark:bg-blue-950/20",
borderColor: "border-blue-200 dark:border-blue-900",
details: [
"Personal repos → Your Gitea username",
"Org repos → Same org name in Gitea",
"Team structure preserved"
]
repoColors: {
bg: "bg-blue-50 dark:bg-blue-950/30",
icon: "text-blue-600 dark:text-blue-400"
}
},
"single-org": {
title: "Consolidate to One Org",
title: "Single Organization",
icon: Building2,
description: "Mirror all repositories into a single organization",
description: "Consolidate all repositories into one Gitea organization",
color: "text-purple-600 dark:text-purple-400",
bgColor: "bg-purple-50 dark:bg-purple-950/20",
borderColor: "border-purple-200 dark:border-purple-900",
details: [
"All repos in one place",
"Simplified management",
"Custom organization name"
]
repoColors: {
bg: "bg-purple-50 dark:bg-purple-950/30",
icon: "text-purple-600 dark:text-purple-400"
}
},
"flat-user": {
title: "Flat User Structure",
title: "User Repositories",
icon: User,
description: "Mirror all repositories under your user account",
description: "Place all repositories directly under your user account",
color: "text-green-600 dark:text-green-400",
bgColor: "bg-green-50 dark:bg-green-950/20",
borderColor: "border-green-200 dark:border-green-900",
details: [
"All repos under your username",
"No organizations needed",
"Simple and personal"
]
repoColors: {
bg: "bg-green-50 dark:bg-green-950/30",
icon: "text-green-600 dark:text-green-400"
}
}
};
const StrategyVisualizer: React.FC<{
strategy: MirrorStrategy;
const MappingPreview: React.FC<{
strategy: MirrorStrategy;
config: typeof strategyConfig.preserve;
destinationOrg?: string;
starredReposOrg?: string;
githubUsername?: string;
giteaUsername?: string;
}> = ({ strategy, destinationOrg, starredReposOrg, githubUsername, giteaUsername }) => {
const [isOpen, setIsOpen] = useState(false);
}> = ({ strategy, config, destinationOrg, starredReposOrg, githubUsername, giteaUsername }) => {
const displayGithubUsername = githubUsername || "<username>";
const displayGiteaUsername = giteaUsername || "<username>";
const isGithubPlaceholder = !githubUsername;
const isGiteaPlaceholder = !giteaUsername;
const renderPreserveStructure = () => (
<div className="flex items-center justify-between gap-8 p-6">
<div className="flex-1">
<div className="text-sm font-medium text-muted-foreground mb-3">GitHub</div>
<div className="space-y-2">
<div className="flex items-center gap-2 p-2 bg-gray-50 dark:bg-gray-800 rounded">
<User className="h-4 w-4" />
<span className={cn("text-sm", isGithubPlaceholder && "text-muted-foreground italic")}>{displayGithubUsername}/my-repo</span>
</div>
<div className="flex items-center gap-2 p-2 bg-gray-50 dark:bg-gray-800 rounded">
<Building2 className="h-4 w-4" />
<span className="text-sm">my-org/team-repo</span>
</div>
<div className="flex items-center gap-2 p-2 bg-gray-50 dark:bg-gray-800 rounded">
<Star className="h-4 w-4" />
<span className="text-sm">awesome/starred-repo</span>
</div>
</div>
</div>
<div className="flex items-center">
<GitBranch className="h-5 w-5 text-muted-foreground" />
</div>
<div className="flex-1">
<div className="text-sm font-medium text-muted-foreground mb-3">Gitea</div>
<div className="space-y-2">
<div className="flex items-center gap-2 p-2 bg-blue-50 dark:bg-blue-950/30 rounded">
<User className="h-4 w-4 text-blue-600 dark:text-blue-400" />
<span className={cn("text-sm", isGiteaPlaceholder && "text-muted-foreground italic")}>{displayGiteaUsername}/my-repo</span>
</div>
<div className="flex items-center gap-2 p-2 bg-blue-50 dark:bg-blue-950/30 rounded">
<Building2 className="h-4 w-4 text-blue-600 dark:text-blue-400" />
<span className="text-sm">my-org/team-repo</span>
</div>
<div className="flex items-center gap-2 p-2 bg-blue-50 dark:bg-blue-950/30 rounded">
<Building2 className="h-4 w-4 text-blue-600 dark:text-blue-400" />
<span className="text-sm">{starredReposOrg || "starred"}/starred-repo</span>
</div>
</div>
</div>
</div>
);
const renderSingleOrg = () => (
<div className="flex items-center justify-between gap-8 p-6">
<div className="flex-1">
<div className="text-sm font-medium text-muted-foreground mb-3">GitHub</div>
<div className="space-y-2">
<div className="flex items-center gap-2 p-2 bg-gray-50 dark:bg-gray-800 rounded">
<User className="h-4 w-4" />
<span className={cn("text-sm", isGithubPlaceholder && "text-muted-foreground italic")}>{displayGithubUsername}/my-repo</span>
</div>
<div className="flex items-center gap-2 p-2 bg-gray-50 dark:bg-gray-800 rounded">
<Building2 className="h-4 w-4" />
<span className="text-sm">my-org/team-repo</span>
</div>
<div className="flex items-center gap-2 p-2 bg-gray-50 dark:bg-gray-800 rounded">
<Star className="h-4 w-4" />
<span className="text-sm">awesome/starred-repo</span>
</div>
</div>
</div>
<div className="flex items-center">
<GitBranch className="h-5 w-5 text-muted-foreground" />
</div>
<div className="flex-1">
<div className="text-sm font-medium text-muted-foreground mb-3">Gitea</div>
<div className="space-y-2">
<div className="flex items-center gap-2 p-2 bg-purple-50 dark:bg-purple-950/30 rounded">
<Building2 className="h-4 w-4 text-purple-600 dark:text-purple-400" />
<span className="text-sm">{destinationOrg || "github-mirrors"}/my-repo</span>
</div>
<div className="flex items-center gap-2 p-2 bg-purple-50 dark:bg-purple-950/30 rounded">
<Building2 className="h-4 w-4 text-purple-600 dark:text-purple-400" />
<span className="text-sm">{destinationOrg || "github-mirrors"}/team-repo</span>
</div>
<div className="flex items-center gap-2 p-2 bg-purple-50 dark:bg-purple-950/30 rounded">
<Building2 className="h-4 w-4 text-purple-600 dark:text-purple-400" />
<span className="text-sm">{starredReposOrg || "starred"}/starred-repo</span>
</div>
</div>
</div>
</div>
);
const renderFlatUser = () => (
<div className="flex items-center justify-between gap-8 p-6">
<div className="flex-1">
<div className="text-sm font-medium text-muted-foreground mb-3">GitHub</div>
<div className="space-y-2">
<div className="flex items-center gap-2 p-2 bg-gray-50 dark:bg-gray-800 rounded">
<User className="h-4 w-4" />
<span className={cn("text-sm", isGithubPlaceholder && "text-muted-foreground italic")}>{displayGithubUsername}/my-repo</span>
</div>
<div className="flex items-center gap-2 p-2 bg-gray-50 dark:bg-gray-800 rounded">
<Building2 className="h-4 w-4" />
<span className="text-sm">my-org/team-repo</span>
</div>
<div className="flex items-center gap-2 p-2 bg-gray-50 dark:bg-gray-800 rounded">
<Star className="h-4 w-4" />
<span className="text-sm">awesome/starred-repo</span>
</div>
</div>
</div>
<div className="flex items-center">
<GitBranch className="h-5 w-5 text-muted-foreground" />
</div>
<div className="flex-1">
<div className="text-sm font-medium text-muted-foreground mb-3">Gitea</div>
<div className="space-y-2">
<div className="flex items-center gap-2 p-2 bg-green-50 dark:bg-green-950/30 rounded">
<User className="h-4 w-4 text-green-600 dark:text-green-400" />
<span className={cn("text-sm", isGiteaPlaceholder && "text-muted-foreground italic")}>{displayGiteaUsername}/my-repo</span>
</div>
<div className="flex items-center gap-2 p-2 bg-green-50 dark:bg-green-950/30 rounded">
<User className="h-4 w-4 text-green-600 dark:text-green-400" />
<span className={cn("text-sm", isGiteaPlaceholder && "text-muted-foreground italic")}>{displayGiteaUsername}/team-repo</span>
</div>
<div className="flex items-center gap-2 p-2 bg-green-50 dark:bg-green-950/30 rounded">
<Building2 className="h-4 w-4 text-green-600 dark:text-green-400" />
<span className="text-sm">{starredReposOrg || "starred"}/starred-repo</span>
</div>
</div>
</div>
</div>
);
return (
<div className="mt-4">
<Collapsible open={isOpen} onOpenChange={setIsOpen}>
<Card className="overflow-hidden">
<CollapsibleTrigger className="w-full">
<div className="bg-muted/50 p-3 border-b hover:bg-muted/70 transition-colors cursor-pointer">
<h4 className="text-sm font-medium flex items-center justify-between">
<span className="flex items-center gap-2">
<Package className="h-4 w-4" />
Repository Mapping Preview
</span>
{isOpen ? (
<ChevronUp className="h-4 w-4 text-muted-foreground" />
) : (
<ChevronDown className="h-4 w-4 text-muted-foreground" />
)}
</h4>
if (strategy === "preserve") {
return (
<div className="flex items-center justify-between gap-6">
<div className="flex-1">
<div className="text-xs font-medium text-muted-foreground mb-2">GitHub</div>
<div className="space-y-1.5">
<div className="flex items-center gap-2 p-1.5 bg-gray-50 dark:bg-gray-800 rounded text-xs">
<User className="h-3 w-3" />
<span className={cn(isGithubPlaceholder && "text-muted-foreground italic")}>{displayGithubUsername}/my-repo</span>
</div>
</CollapsibleTrigger>
<CollapsibleContent>
{strategy === "preserve" && renderPreserveStructure()}
{strategy === "single-org" && renderSingleOrg()}
{strategy === "flat-user" && renderFlatUser()}
</CollapsibleContent>
</Card>
</Collapsible>
</div>
);
<div className="flex items-center gap-2 p-1.5 bg-gray-50 dark:bg-gray-800 rounded text-xs">
<Building2 className="h-3 w-3" />
<span>my-org/team-repo</span>
</div>
<div className="flex items-center gap-2 p-1.5 bg-gray-50 dark:bg-gray-800 rounded text-xs">
<Star className="h-3 w-3" />
<span>awesome/starred-repo</span>
</div>
</div>
</div>
<div className="flex items-center">
<GitBranch className="h-4 w-4 text-muted-foreground" />
</div>
<div className="flex-1">
<div className="text-xs font-medium text-muted-foreground mb-2">Gitea</div>
<div className="space-y-1.5">
<div className={cn("flex items-center gap-2 p-1.5 rounded text-xs", config.repoColors.bg)}>
<User className={cn("h-3 w-3", config.repoColors.icon)} />
<span className={cn(isGiteaPlaceholder && "text-muted-foreground italic")}>{displayGiteaUsername}/my-repo</span>
</div>
<div className={cn("flex items-center gap-2 p-1.5 rounded text-xs", config.repoColors.bg)}>
<Building2 className={cn("h-3 w-3", config.repoColors.icon)} />
<span>my-org/team-repo</span>
</div>
<div className={cn("flex items-center gap-2 p-1.5 rounded text-xs", config.repoColors.bg)}>
<Building2 className={cn("h-3 w-3", config.repoColors.icon)} />
<span>{starredReposOrg || "starred"}/starred-repo</span>
</div>
</div>
</div>
</div>
);
}
if (strategy === "single-org") {
return (
<div className="flex items-center justify-between gap-6">
<div className="flex-1">
<div className="text-xs font-medium text-muted-foreground mb-2">GitHub</div>
<div className="space-y-1.5">
<div className="flex items-center gap-2 p-1.5 bg-gray-50 dark:bg-gray-800 rounded text-xs">
<User className="h-3 w-3" />
<span className={cn(isGithubPlaceholder && "text-muted-foreground italic")}>{displayGithubUsername}/my-repo</span>
</div>
<div className="flex items-center gap-2 p-1.5 bg-gray-50 dark:bg-gray-800 rounded text-xs">
<Building2 className="h-3 w-3" />
<span>my-org/team-repo</span>
</div>
<div className="flex items-center gap-2 p-1.5 bg-gray-50 dark:bg-gray-800 rounded text-xs">
<Star className="h-3 w-3" />
<span>awesome/starred-repo</span>
</div>
</div>
</div>
<div className="flex items-center">
<GitBranch className="h-4 w-4 text-muted-foreground" />
</div>
<div className="flex-1">
<div className="text-xs font-medium text-muted-foreground mb-2">Gitea</div>
<div className="space-y-1.5">
<div className={cn("flex items-center gap-2 p-1.5 rounded text-xs", config.repoColors.bg)}>
<Building2 className={cn("h-3 w-3", config.repoColors.icon)} />
<span>{destinationOrg || "github-mirrors"}/my-repo</span>
</div>
<div className={cn("flex items-center gap-2 p-1.5 rounded text-xs", config.repoColors.bg)}>
<Building2 className={cn("h-3 w-3", config.repoColors.icon)} />
<span>{destinationOrg || "github-mirrors"}/team-repo</span>
</div>
<div className={cn("flex items-center gap-2 p-1.5 rounded text-xs", config.repoColors.bg)}>
<Building2 className={cn("h-3 w-3", config.repoColors.icon)} />
<span>{starredReposOrg || "starred"}/starred-repo</span>
</div>
</div>
</div>
</div>
);
}
if (strategy === "flat-user") {
return (
<div className="flex items-center justify-between gap-6">
<div className="flex-1">
<div className="text-xs font-medium text-muted-foreground mb-2">GitHub</div>
<div className="space-y-1.5">
<div className="flex items-center gap-2 p-1.5 bg-gray-50 dark:bg-gray-800 rounded text-xs">
<User className="h-3 w-3" />
<span className={cn(isGithubPlaceholder && "text-muted-foreground italic")}>{displayGithubUsername}/my-repo</span>
</div>
<div className="flex items-center gap-2 p-1.5 bg-gray-50 dark:bg-gray-800 rounded text-xs">
<Building2 className="h-3 w-3" />
<span>my-org/team-repo</span>
</div>
<div className="flex items-center gap-2 p-1.5 bg-gray-50 dark:bg-gray-800 rounded text-xs">
<Star className="h-3 w-3" />
<span>awesome/starred-repo</span>
</div>
</div>
</div>
<div className="flex items-center">
<GitBranch className="h-4 w-4 text-muted-foreground" />
</div>
<div className="flex-1">
<div className="text-xs font-medium text-muted-foreground mb-2">Gitea</div>
<div className="space-y-1.5">
<div className={cn("flex items-center gap-2 p-1.5 rounded text-xs", config.repoColors.bg)}>
<User className={cn("h-3 w-3", config.repoColors.icon)} />
<span className={cn(isGiteaPlaceholder && "text-muted-foreground italic")}>{displayGiteaUsername}/my-repo</span>
</div>
<div className={cn("flex items-center gap-2 p-1.5 rounded text-xs", config.repoColors.bg)}>
<User className={cn("h-3 w-3", config.repoColors.icon)} />
<span className={cn(isGiteaPlaceholder && "text-muted-foreground italic")}>{displayGiteaUsername}/team-repo</span>
</div>
<div className={cn("flex items-center gap-2 p-1.5 rounded text-xs", config.repoColors.bg)}>
<Building2 className={cn("h-3 w-3", config.repoColors.icon)} />
<span>{starredReposOrg || "starred"}/starred-repo</span>
</div>
</div>
</div>
</div>
);
}
return null;
};
export const OrganizationStrategy: React.FC<OrganizationStrategyProps> = ({
@@ -252,23 +218,23 @@ export const OrganizationStrategy: React.FC<OrganizationStrategyProps> = ({
destinationOrg,
starredReposOrg,
onStrategyChange,
onDestinationOrgChange,
onStarredReposOrgChange,
githubUsername,
giteaUsername,
}) => {
return (
<div className="space-y-6">
<div className="space-y-4">
<div>
<h3 className="text-lg font-semibold mb-1">Organization Strategy</h3>
<p className="text-sm text-muted-foreground">
<h4 className="text-sm font-medium mb-3 flex items-center gap-2">
<Building className="h-4 w-4" />
Organization Strategy
</h4>
<p className="text-xs text-muted-foreground mb-4">
Choose how your repositories will be organized in Gitea
</p>
</div>
<RadioGroup value={strategy} onValueChange={onStrategyChange}>
<div className="grid gap-4">
<div className="grid grid-cols-1 2xl:grid-cols-2 gap-4">
{(Object.entries(strategyConfig) as [MirrorStrategy, typeof strategyConfig.preserve][]).map(([key, config]) => {
const isSelected = strategy === key;
const Icon = config.icon;
@@ -283,12 +249,11 @@ export const OrganizationStrategy: React.FC<OrganizationStrategyProps> = ({
!isSelected && "border-muted"
)}
>
<div className="p-4">
<div className="flex items-start gap-4">
<div className="p-3">
<div className="flex items-center gap-3">
<RadioGroupItem
value={key}
id={key}
className="mt-1"
/>
<div className={cn(
@@ -296,36 +261,44 @@ export const OrganizationStrategy: React.FC<OrganizationStrategyProps> = ({
isSelected ? config.bgColor : "bg-muted dark:bg-muted/50"
)}>
<Icon className={cn(
"h-5 w-5",
"h-4 w-4",
isSelected ? config.color : "text-muted-foreground dark:text-muted-foreground/70"
)} />
</div>
<div className="flex-1">
<div className="flex items-center gap-2 mb-1">
<h4 className="font-medium">{config.title}</h4>
{isSelected && (
<Badge variant="secondary" className="text-xs">
Selected
</Badge>
)}
<div className="flex items-center gap-2">
<h4 className="font-medium text-sm">{config.title}</h4>
</div>
<p className="text-sm text-muted-foreground mb-3">
<p className="text-xs text-muted-foreground mt-0.5">
{config.description}
</p>
<div className="space-y-1">
{config.details.map((detail, idx) => (
<div key={idx} className="flex items-center gap-2">
<div className={cn(
"h-1.5 w-1.5 rounded-full",
isSelected ? config.bgColor : "bg-muted dark:bg-muted/50"
)} />
<span className="text-xs text-muted-foreground">{detail}</span>
</div>
))}
</div>
</div>
<Popover>
<PopoverTrigger asChild>
<button
type="button"
className="p-1.5 hover:bg-muted rounded-md transition-colors"
onClick={(e) => e.stopPropagation()}
>
<Info className="h-4 w-4 text-muted-foreground" />
</button>
</PopoverTrigger>
<PopoverContent side="left" align="center" className="w-[500px]">
<div className="space-y-3">
<h4 className="font-medium text-sm">Repository Mapping Preview</h4>
<MappingPreview
strategy={key}
config={config}
destinationOrg={destinationOrg}
starredReposOrg={starredReposOrg}
githubUsername={githubUsername}
giteaUsername={giteaUsername}
/>
</div>
</PopoverContent>
</Popover>
</div>
</div>
</Card>
@@ -335,76 +308,6 @@ export const OrganizationStrategy: React.FC<OrganizationStrategyProps> = ({
})}
</div>
</RadioGroup>
{strategy === "single-org" && (
<div className="space-y-4">
<Card className="p-4 border-purple-200 dark:border-purple-900 bg-purple-50/50 dark:bg-purple-950/20">
<div className="space-y-3">
<div>
<Label htmlFor="destinationOrg" className="flex items-center gap-2">
Destination Organization
<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<Info className="h-3.5 w-3.5 text-muted-foreground" />
</TooltipTrigger>
<TooltipContent>
<p>All repositories will be mirrored to this organization</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</Label>
<Input
id="destinationOrg"
value={destinationOrg || ""}
onChange={(e) => onDestinationOrgChange(e.target.value)}
placeholder="github-mirrors"
className="mt-1.5"
/>
</div>
</div>
</Card>
</div>
)}
<Card className="p-4 border-orange-200 dark:border-orange-900 bg-orange-50/50 dark:bg-orange-950/20">
<div className="space-y-3">
<div>
<Label htmlFor="starredReposOrg" className="flex items-center gap-2">
<Star className="h-4 w-4 text-orange-600 dark:text-orange-400" />
Starred Repositories Organization
<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<Info className="h-3.5 w-3.5 text-muted-foreground" />
</TooltipTrigger>
<TooltipContent>
<p>Starred repositories will be organized separately in this organization</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</Label>
<Input
id="starredReposOrg"
value={starredReposOrg || ""}
onChange={(e) => onStarredReposOrgChange(e.target.value)}
placeholder="starred"
className="mt-1.5"
/>
<p className="text-xs text-muted-foreground dark:text-muted-foreground/70 mt-1">
Keep starred repos organized separately from your own repositories
</p>
</div>
</div>
</Card>
<StrategyVisualizer
strategy={strategy}
destinationOrg={destinationOrg}
starredReposOrg={starredReposOrg}
githubUsername={githubUsername}
giteaUsername={giteaUsername}
/>
</div>
);
};

View File

@@ -19,7 +19,11 @@ function TooltipProvider({
function Tooltip({
...props
}: React.ComponentProps<typeof TooltipPrimitive.Root>) {
return <TooltipPrimitive.Root data-slot="tooltip" {...props} />
return (
<TooltipProvider>
<TooltipPrimitive.Root data-slot="tooltip" {...props} />
</TooltipProvider>
)
}
function TooltipTrigger({
@@ -40,7 +44,7 @@ function TooltipContent({
data-slot="tooltip-content"
sideOffset={sideOffset}
className={cn(
"bg-primary text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-[var(--radix-tooltip-content-transform-origin)] rounded-md px-3 py-1.5 text-xs text-balance",
"bg-primary text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance",
className
)}
{...props}

View File

@@ -1,17 +1,4 @@
import { defineCollection, z } from 'astro:content';
// Define a schema for the documentation collection
const docsCollection = defineCollection({
type: 'content',
schema: z.object({
title: z.string(),
description: z.string(),
order: z.number().optional(),
updatedDate: z.date().optional(),
}),
});
// Export the collections
export const collections = {
'docs': docsCollection,
};
// Export empty collections since docs have been moved
export const collections = {};

View File

@@ -1,123 +0,0 @@
---
title: "Architecture"
description: "Comprehensive overview of the Gitea Mirror application architecture."
order: 1
updatedDate: 2025-05-22
---
<div class="mb-6">
<h1 class="text-2xl font-bold text-foreground">Gitea Mirror Architecture</h1>
<p class="text-muted-foreground mt-2">This document provides a comprehensive overview of the Gitea Mirror application architecture, including component diagrams, project structure, and detailed explanations of each part of the system.</p>
</div>
## System Overview
<div class="mb-4">
<p class="text-muted-foreground">Gitea Mirror is a web application that automates the mirroring of GitHub repositories to Gitea instances. It provides a user-friendly interface for configuring, monitoring, and managing mirroring operations without requiring users to edit configuration files or run Docker commands.</p>
</div>
The application is built using:
- <span class="font-semibold text-foreground">Astro</span>: Web framework for the frontend
- <span class="font-semibold text-foreground">React</span>: Component library for interactive UI elements
- <span class="font-semibold text-foreground">Shadcn UI</span>: UI component library built on Tailwind CSS
- <span class="font-semibold text-foreground">SQLite</span>: Database for storing configuration, state, and events
- <span class="font-semibold text-foreground">Bun</span>: Runtime environment for the backend
- <span class="font-semibold text-foreground">Drizzle ORM</span>: Type-safe ORM for database interactions
## Architecture Diagram
```mermaid
graph TD
subgraph "Gitea Mirror"
Frontend["Frontend<br/>(Astro + React)"]
Backend["Backend<br/>(Bun)"]
Database["Database<br/>(SQLite + Drizzle)"]
Frontend <--> Backend
Backend <--> Database
end
subgraph "External APIs"
GitHub["GitHub API"]
Gitea["Gitea API"]
end
Backend --> GitHub
Backend --> Gitea
```
## Component Breakdown
### Frontend (Astro + React)
The frontend is built with Astro, a modern web framework that allows for server-side rendering and partial hydration. React components are used for interactive elements, providing a responsive and dynamic user interface.
Key frontend components:
- **Dashboard**: Overview of mirroring status and recent activity
- **Repository Management**: Interface for managing repositories to mirror
- **Organization Management**: Interface for managing GitHub organizations
- **Configuration**: Settings for GitHub and Gitea connections
- **Activity Log**: Detailed log of mirroring operations
### Backend (Bun)
The backend is built with Bun and provides API endpoints for the frontend to interact with. It handles:
- Authentication and user management
- GitHub API integration
- Gitea API integration
- Mirroring operations
- Database interactions
### Database (SQLite + Drizzle ORM)
SQLite with Bun's native SQLite driver is used for data persistence, with Drizzle ORM providing type-safe database interactions. The database stores:
- User accounts and authentication data
- GitHub and Gitea configuration
- Repository and organization information
- Mirroring job history and status
- Event notifications and their read status
## Data Flow
1. **User Authentication**: Users authenticate through the frontend, which communicates with the backend to validate credentials.
2. **Configuration**: Users configure GitHub and Gitea settings through the UI, which are stored in the SQLite database.
3. **Repository Discovery**: The backend queries the GitHub API to discover repositories based on user configuration.
4. **Mirroring Process**: When triggered, the backend fetches repository data from GitHub and pushes it to Gitea.
5. **Status Tracking**: All operations are logged in the database and displayed in the Activity Log.
## Project Structure
```
gitea-mirror/
├── src/ # Source code
│ ├── components/ # React components
│ ├── content/ # Documentation and content
│ ├── layouts/ # Astro layout components
│ ├── lib/ # Utility functions and database
│ ├── pages/ # Astro pages and API routes
│ └── styles/ # CSS and Tailwind styles
├── public/ # Static assets
├── data/ # Database and persistent data
├── docker/ # Docker configuration
└── scripts/ # Utility scripts for deployment and maintenance
├── gitea-mirror-lxc-local.sh # Local LXC deployment script
└── manage-db.ts # Database management tool
```
## Deployment Options
Gitea Mirror supports multiple deployment options:
1. **Docker**: Run as a containerized application using Docker and docker-compose
2. **LXC Containers**: Deploy in Linux Containers (LXC) on Proxmox VE (using community script by [Tobias/CrazyWolf13](https://github.com/CrazyWolf13)) or local workstations
3. **Native**: Run directly on the host system using Bun runtime
Each deployment method has its own advantages:
- **Docker**: Isolation, easy updates, consistent environment
- **LXC**: Lightweight virtualization, better performance than Docker, system-level isolation
- **Native**: Best performance, direct access to system resources

View File

@@ -1,177 +0,0 @@
---
title: "Configuration"
description: "Guide to configuring Gitea Mirror for your environment."
order: 2
updatedDate: 2025-05-22
---
<div class="mb-6">
<h1 class="text-2xl font-bold text-foreground">Gitea Mirror Configuration Guide</h1>
<p class="text-muted-foreground mt-2">This guide provides detailed information on how to configure Gitea Mirror for your environment.</p>
</div>
## Configuration Methods
Gitea Mirror can be configured using:
1. <span class="font-semibold text-foreground">Environment Variables</span>: Set configuration options through environment variables
2. <span class="font-semibold text-foreground">Web UI</span>: Configure the application through the web interface after installation
## Environment Variables
The following environment variables can be used to configure Gitea Mirror:
| Variable | Description | Default Value | Example |
|----------|-------------|---------------|---------|
| `NODE_ENV` | Runtime environment (development, production, test) | `development` | `production` |
| `DATABASE_URL` | SQLite database URL | `file:data/gitea-mirror.db` | `file:path/to/your/database.db` |
| `JWT_SECRET` | Secret key for JWT authentication | Auto-generated secure random string | `your-secure-random-string` |
| `HOST` | Server host | `localhost` | `0.0.0.0` |
| `PORT` | Server port | `4321` | `8080` |
### Important Security Note
The application will automatically generate a secure random `JWT_SECRET` on first run if one isn't provided or if the default value is used. This generated secret is stored in the data directory for persistence across container restarts.
While this auto-generation feature provides good security by default, you can still explicitly set your own `JWT_SECRET` for complete control over your deployment.
## Web UI Configuration
After installing and starting Gitea Mirror, you can configure it through the web interface:
1. Navigate to `http://your-server:port/`
2. If this is your first time, you'll be guided through creating an admin account
3. Log in with your credentials
4. Go to the Configuration page
### GitHub Configuration
The GitHub configuration section allows you to connect to GitHub and specify which repositories to mirror.
| Option | Description | Default |
|--------|-------------|---------|
| Username | Your GitHub username | - |
| Token | GitHub personal access token | - |
| Skip Forks | Skip forked repositories | `false` |
| Private Repositories | Include private repositories | `false` |
| Mirror Issues | Mirror issues from GitHub to Gitea | `false` |
| Mirror Wiki | Mirror wiki pages from GitHub to Gitea | `false` |
| Mirror Starred | Mirror starred repositories | `false` |
| Mirror Organizations | Mirror organization repositories | `false` |
| Only Mirror Orgs | Only mirror organization repositories | `false` |
| Preserve Org Structure | Maintain organization structure in Gitea | `false` |
| Skip Starred Issues | Skip mirroring issues for starred repositories | `false` |
#### GitHub Token Permissions
Your GitHub token needs the following permissions:
- `repo` - Full control of private repositories
- `read:org` - Read organization membership
- `read:user` - Read user profile data
To create a GitHub token:
1. Go to [GitHub Settings > Developer settings > Personal access tokens](https://github.com/settings/tokens)
2. Click "Generate new token"
3. Select the required permissions
4. Copy the generated token and paste it into Gitea Mirror
### Gitea Configuration
The Gitea configuration section allows you to connect to your Gitea instance and specify how repositories should be mirrored.
| Option | Description | Default |
|--------|-------------|---------|
| URL | Gitea server URL | - |
| Token | Gitea access token | - |
| Organization | Default organization for mirrored repositories | - |
| Visibility | Default visibility for mirrored repositories | `public` |
| Starred Repos Org | Organization for starred repositories | `github` |
#### Gitea Token Creation
To create a Gitea access token:
1. Log in to your Gitea instance
2. Go to Settings > Applications
3. Under "Generate New Token", enter a name for your token
4. Click "Generate Token"
5. Copy the generated token and paste it into Gitea Mirror
### Schedule Configuration
You can configure automatic mirroring on a schedule:
| Option | Description | Default |
|--------|-------------|---------|
| Enable Scheduling | Enable automatic mirroring | `false` |
| Interval (seconds) | Time between mirroring operations | `3600` (1 hour) |
## Advanced Configuration
### Repository Filtering
You can include or exclude specific repositories using patterns:
- Include patterns: Only repositories matching these patterns will be mirrored
- Exclude patterns: Repositories matching these patterns will be skipped
Example patterns:
- `*` - All repositories
- `org-name/*` - All repositories in a specific organization
- `username/repo-name` - A specific repository
### Database Management
Gitea Mirror includes several database management tools that can be run from the command line:
```bash
# Initialize the database (only if it doesn't exist)
bun run init-db
# Check database status
bun run check-db
# Fix database location issues
bun run fix-db
# Reset all users (for testing signup flow)
bun run reset-users
# Remove database files completely
bun run cleanup-db
```
### Event Management
Events in Gitea Mirror (such as repository mirroring operations) are stored in the SQLite database. You can manage these events using the following scripts:
```bash
# View all events in the database
bun scripts/check-events.ts
# Mark all events as read
bun scripts/mark-events-read.ts
```
For cleaning up old activities and events, use the cleanup button in the Activity Log page of the web interface.
### Health Check Endpoint
Gitea Mirror includes a built-in health check endpoint at `/api/health` that provides:
- System status and uptime
- Database connectivity check
- Memory usage statistics
- Environment information
You can use this endpoint for monitoring your deployment:
```bash
# Basic check (returns 200 OK if healthy)
curl -I http://your-server:port/api/health
# Detailed health information (JSON)
curl http://your-server:port/api/health
```

View File

@@ -1,182 +0,0 @@
---
title: "Quick Start Guide"
description: "Get started with Gitea Mirror quickly."
order: 3
updatedDate: 2025-05-22
---
<div class="mb-6">
<h1 class="text-2xl font-bold text-foreground">Gitea Mirror Quick Start Guide</h1>
<p class="text-muted-foreground mt-2">This guide will help you get Gitea Mirror up and running quickly.</p>
</div>
## Prerequisites
Before you begin, make sure you have:
1. <span class="font-semibold text-foreground">A GitHub account with a personal access token</span>
2. <span class="font-semibold text-foreground">A Gitea instance with an access token</span>
3. <span class="font-semibold text-foreground">One of the following:</span>
- Docker and docker-compose (for Docker deployment)
- Bun 1.2.9+ (for native deployment)
- Proxmox VE or LXD (for LXC container deployment)
## Installation Options
Choose the installation method that works best for your environment.
### Using Docker (Recommended for most users)
Docker provides the easiest way to get started with minimal configuration.
1. Clone the repository:
```bash
git clone https://github.com/arunavo4/gitea-mirror.git
cd gitea-mirror
```
2. Start the application in production mode:
```bash
docker compose up -d
```
3. Access the application at [http://localhost:4321](http://localhost:4321)
### Using Bun (Native Installation)
If you prefer to run the application directly on your system:
1. Clone the repository:
```bash
git clone https://github.com/arunavo4/gitea-mirror.git
cd gitea-mirror
```
2. Run the quick setup script:
```bash
bun run setup
```
This installs dependencies and initializes the database.
3. Choose how to run the application:
**Development Mode:**
```bash
bun run dev
```
Note: For Bun-specific features, use:
```bash
bunx --bun astro dev
```
**Production Mode:**
```bash
bun run build
bun run start
```
4. Access the application at [http://localhost:4321](http://localhost:4321)
### Using LXC Containers (Recommended for server deployments)
#### Proxmox VE (Online Installation)
For deploying on a Proxmox VE host with internet access:
```bash
# Optional env overrides: CTID HOSTNAME STORAGE DISK_SIZE CORES MEMORY BRIDGE IP_CONF
sudo bash -c "$(curl -fsSL https://raw.githubusercontent.com/arunavo4/gitea-mirror/main/scripts/gitea-mirror-lxc-proxmox.sh)"
```
This script:
- Creates a privileged LXC container
- Installs Bun and dependencies
- Clones and builds the application
- Sets up a systemd service
#### Local LXD (Offline-friendly Installation)
For testing on a local workstation or in environments without internet access:
1. Clone the repository locally:
```bash
git clone https://github.com/arunavo4/gitea-mirror.git
```
2. Download the Bun installer once:
```bash
curl -L -o /tmp/bun-linux-x64.zip https://github.com/oven-sh/bun/releases/latest/download/bun-linux-x64.zip
```
3. Run the local LXC installer:
```bash
sudo LOCAL_REPO_DIR=~/path/to/gitea-mirror ./gitea-mirror/scripts/gitea-mirror-lxc-local.sh
```
For more details on LXC deployment, see the [LXC Container Deployment Guide](https://github.com/arunavo4/gitea-mirror/blob/main/scripts/README-lxc.md).
## Initial Configuration
Follow these steps to configure Gitea Mirror for first use:
1. **Create Admin Account**
- Upon first access, you'll be prompted to create an admin account
- Choose a secure username and password
- This will be your administrator account
2. **Configure GitHub Connection**
- Navigate to the Configuration page
- Enter your GitHub username
- Enter your GitHub personal access token
- Select which repositories to mirror (all, starred, organizations)
- Configure repository filtering options
3. **Configure Gitea Connection**
- Enter your Gitea server URL
- Enter your Gitea access token
- Configure organization and visibility settings
4. **Set Up Scheduling (Optional)**
- Enable automatic mirroring if desired
- Set the mirroring interval (in seconds)
5. **Save Configuration**
- Click the "Save" button to store your settings
## Performing Your First Mirror
After completing the configuration, you can start mirroring repositories:
1. Click "Import GitHub Data" to fetch repositories from GitHub
2. Go to the Repositories page to view your imported repositories
3. Select the repositories you want to mirror
4. Click "Mirror Selected" to start the mirroring process
5. Monitor the progress on the Activity page
6. You'll receive toast notifications about the success or failure of operations
## Troubleshooting
If you encounter any issues:
- Check the Activity Log for detailed error messages
- Verify your GitHub and Gitea tokens have the correct permissions
- Ensure your Gitea instance is accessible from the machine running Gitea Mirror
- Check logs based on your deployment method:
- Docker: `docker logs gitea-mirror`
- Native: Check the terminal output or system logs
- LXC: `systemctl status gitea-mirror` or `journalctl -u gitea-mirror -f`
- Use the health check endpoint to verify system status: `curl http://your-server:4321/api/health`
- For database issues, try the database management tools: `bun run check-db` or `bun run fix-db`
## Next Steps
After your initial setup:
- Explore the dashboard for an overview of your mirroring status
- Set up automatic mirroring schedules for hands-off operation
- Configure organization mirroring for team repositories
- Check out the [Configuration Guide](/configuration) for advanced settings
- Review the [Architecture Documentation](/architecture) to understand the system
- For server deployments, set up monitoring using the health check endpoint
- Use the cleanup button in the Activity Log page to manage old events and activities

View File

@@ -1,83 +0,0 @@
import { useState } from 'react';
import { mirrorApi } from '@/lib/api';
import type { MirrorJob } from '@/lib/db/schema';
export function useMirror() {
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [currentJob, setCurrentJob] = useState<MirrorJob | null>(null);
const [jobs, setJobs] = useState<MirrorJob[]>([]);
const startMirror = async (configId: string, repositoryIds?: string[]) => {
setIsLoading(true);
setError(null);
try {
const job = await mirrorApi.startMirror(configId, repositoryIds);
setCurrentJob(job);
return job;
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to start mirroring');
throw err;
} finally {
setIsLoading(false);
}
};
const getMirrorJobs = async (configId: string) => {
setIsLoading(true);
setError(null);
try {
const fetchedJobs = await mirrorApi.getMirrorJobs(configId);
setJobs(fetchedJobs);
return fetchedJobs;
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to fetch mirror jobs');
throw err;
} finally {
setIsLoading(false);
}
};
const getMirrorJob = async (jobId: string) => {
setIsLoading(true);
setError(null);
try {
const job = await mirrorApi.getMirrorJob(jobId);
setCurrentJob(job);
return job;
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to fetch mirror job');
throw err;
} finally {
setIsLoading(false);
}
};
const cancelMirrorJob = async (jobId: string) => {
setIsLoading(true);
setError(null);
try {
const result = await mirrorApi.cancelMirrorJob(jobId);
if (result.success && currentJob?.id === jobId) {
setCurrentJob({ ...currentJob, status: 'failed' });
}
return result;
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to cancel mirror job');
throw err;
} finally {
setIsLoading(false);
}
};
return {
isLoading,
error,
currentJob,
jobs,
startMirror,
getMirrorJobs,
getMirrorJob,
cancelMirrorJob,
};
}

View File

@@ -26,13 +26,39 @@ export const getGiteaRepoOwner = ({
throw new Error("Gitea username is required.");
}
// if the config has preserveOrgStructure set to true, then use the org name as the owner
if (config.githubConfig.preserveOrgStructure && repository.organization) {
return repository.organization;
// Check if repository is starred - starred repos always go to starredReposOrg
if (repository.isStarred && config.giteaConfig.starredReposOrg) {
return config.giteaConfig.starredReposOrg;
}
// if the config has preserveOrgStructure set to false, then use the gitea username as the owner
return config.giteaConfig.username;
// Get the mirror strategy - use preserveOrgStructure for backward compatibility
const mirrorStrategy = config.giteaConfig.mirrorStrategy ||
(config.githubConfig.preserveOrgStructure ? "preserve" : "flat-user");
switch (mirrorStrategy) {
case "preserve":
// Keep GitHub structure - org repos go to same org, personal repos to user
if (repository.organization) {
return repository.organization;
}
return config.giteaConfig.username;
case "single-org":
// All non-starred repos go to the destination organization
if (config.giteaConfig.organization) {
return config.giteaConfig.organization;
}
// Fallback to username if no organization specified
return config.giteaConfig.username;
case "flat-user":
// All non-starred repos go under the user account
return config.giteaConfig.username;
default:
// Default fallback
return config.giteaConfig.username;
}
};
export const isRepoPresentInGitea = async ({
@@ -237,7 +263,11 @@ export const mirrorGithubRepoToGitea = async ({
});
// clone issues
if (config.githubConfig.mirrorIssues) {
// Skip issues for starred repos if skipStarredIssues is enabled
const shouldMirrorIssues = config.githubConfig.mirrorIssues &&
!(repository.isStarred && config.githubConfig.skipStarredIssues);
if (shouldMirrorIssues) {
await mirrorGitRepoIssuesToGitea({
config,
octokit,
@@ -390,7 +420,7 @@ export async function getOrCreateGiteaOrg({
username: orgName,
full_name: `${orgName} Org`,
description: `Mirrored organization from GitHub ${orgName}`,
visibility: "public",
visibility: config.giteaConfig?.visibility || "public",
}),
});
@@ -582,7 +612,11 @@ export async function mirrorGitHubRepoToGiteaOrg({
});
// Clone issues
if (config.githubConfig?.mirrorIssues) {
// Skip issues for starred repos if skipStarredIssues is enabled
const shouldMirrorIssues = config.githubConfig?.mirrorIssues &&
!(repository.isStarred && config.githubConfig?.skipStarredIssues);
if (shouldMirrorIssues) {
await mirrorGitRepoIssuesToGitea({
config,
octokit,

View File

@@ -58,7 +58,7 @@ export const GET: APIRoute = async () => {
latestVersion: latestVersion,
updateAvailable: latestVersion !== "unknown" &&
currentVersion !== "unknown" &&
latestVersion !== currentVersion,
compareVersions(currentVersion, latestVersion) < 0,
database: dbStatus,
recovery: recoveryStatus,
system: systemInfo,
@@ -174,6 +174,28 @@ function formatBytes(bytes: number): string {
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
/**
* Compare semantic versions
* Returns:
* -1 if v1 < v2
* 0 if v1 = v2
* 1 if v1 > v2
*/
function compareVersions(v1: string, v2: string): number {
const parts1 = v1.split('.').map(Number);
const parts2 = v2.split('.').map(Number);
for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
const part1 = parts1[i] || 0;
const part2 = parts2[i] || 0;
if (part1 < part2) return -1;
if (part1 > part2) return 1;
}
return 0;
}
/**
* Check for the latest version from GitHub releases
*/

View File

@@ -6,6 +6,7 @@ import { repositoryVisibilityEnum, repoStatusEnum } from "@/types/Repository";
import {
mirrorGithubRepoToGitea,
mirrorGitHubOrgRepoToGiteaOrg,
getGiteaRepoOwner,
} from "@/lib/gitea";
import { createGitHubClient } from "@/lib/github";
import { processWithResilience } from "@/lib/utils/concurrency";
@@ -96,12 +97,29 @@ export const POST: APIRoute = async ({ request }) => {
// Log the start of mirroring
console.log(`Starting mirror for repository: ${repo.name}`);
// Mirror the repository based on whether it's in an organization
if (repo.organization && config.githubConfig.preserveOrgStructure) {
// Determine where the repository should be mirrored
const owner = getGiteaRepoOwner({
config,
repository: repoData,
});
console.log(`Repository ${repo.name} will be mirrored to owner: ${owner}`);
// For single-org and starred repos strategies, or when mirroring to an org,
// always use the org mirroring function to ensure proper organization handling
const mirrorStrategy = config.giteaConfig?.mirrorStrategy ||
(config.githubConfig?.preserveOrgStructure ? "preserve" : "flat-user");
const shouldUseOrgMirror =
owner !== config.giteaConfig?.username || // Different owner means org
mirrorStrategy === "single-org" || // Single-org strategy always uses org
repoData.isStarred; // Starred repos always go to org
if (shouldUseOrgMirror) {
await mirrorGitHubOrgRepoToGiteaOrg({
config,
octokit,
orgName: repo.organization,
orgName: owner,
repository: repoData,
});
} else {

View File

@@ -93,6 +93,7 @@ export const POST: APIRoute = async ({ request }) => {
lastMirrored: repo.lastMirrored ?? undefined,
errorMessage: repo.errorMessage ?? undefined,
forkedFrom: repo.forkedFrom ?? undefined,
mirroredLocation: repo.mirroredLocation || "",
};
// Log the start of retry operation
@@ -134,13 +135,23 @@ export const POST: APIRoute = async ({ request }) => {
throw new Error("Octokit client is not initialized.");
}
console.log(`Importing repo: ${repo.name} ${owner}`);
console.log(`Importing repo: ${repo.name} to owner: ${owner}`);
if (repo.organization && config.githubConfig.preserveOrgStructure) {
// For single-org and starred repos strategies, or when mirroring to an org,
// always use the org mirroring function to ensure proper organization handling
const mirrorStrategy = config.giteaConfig?.mirrorStrategy ||
(config.githubConfig?.preserveOrgStructure ? "preserve" : "flat-user");
const shouldUseOrgMirror =
owner !== config.giteaConfig?.username || // Different owner means org
mirrorStrategy === "single-org" || // Single-org strategy always uses org
repoData.isStarred; // Starred repos always go to org
if (shouldUseOrgMirror) {
await mirrorGitHubOrgRepoToGiteaOrg({
config,
octokit,
orgName: repo.organization,
orgName: owner,
repository: {
...repoData,
status: repoStatusEnum.parse("imported"),

View File

@@ -112,6 +112,7 @@ export const POST: APIRoute = async ({ request }) => {
organization: repo.organization ?? undefined,
lastMirrored: repo.lastMirrored ?? undefined,
errorMessage: repo.errorMessage ?? undefined,
mirroredLocation: repo.mirroredLocation || "",
forkedFrom: repo.forkedFrom ?? undefined,
visibility: repositoryVisibilityEnum.parse(repo.visibility),
},
@@ -133,6 +134,7 @@ export const POST: APIRoute = async ({ request }) => {
errorMessage: repo.errorMessage ?? undefined,
forkedFrom: repo.forkedFrom ?? undefined,
visibility: repositoryVisibilityEnum.parse(repo.visibility),
mirroredLocation: repo.mirroredLocation || "",
})),
};

View File

@@ -6,7 +6,6 @@ import { repositoryVisibilityEnum, repoStatusEnum } from "@/types/Repository";
import { syncGiteaRepo } from "@/lib/gitea";
import type { SyncRepoResponse } from "@/types/sync";
import { processWithResilience } from "@/lib/utils/concurrency";
import { v4 as uuidv4 } from "uuid";
import { createSecureErrorResponse } from "@/lib/utils";
export const POST: APIRoute = async ({ request }) => {
@@ -68,9 +67,6 @@ export const POST: APIRoute = async ({ request }) => {
// Define the concurrency limit - adjust based on API rate limits
const CONCURRENCY_LIMIT = 5;
// Generate a batch ID to group related repositories
const batchId = uuidv4();
// Process repositories in parallel with resilience to container restarts
await processWithResilience(
repos,
@@ -84,6 +80,7 @@ export const POST: APIRoute = async ({ request }) => {
errorMessage: repo.errorMessage ?? undefined,
forkedFrom: repo.forkedFrom ?? undefined,
visibility: repositoryVisibilityEnum.parse(repo.visibility),
mirroredLocation: repo.mirroredLocation || "",
};
// Log the start of syncing
@@ -100,7 +97,6 @@ export const POST: APIRoute = async ({ request }) => {
{
userId: config.userId || "",
jobType: "sync",
batchId,
getItemId: (repo) => repo.id,
getItemName: (repo) => repo.name,
concurrencyLimit: CONCURRENCY_LIMIT,
@@ -135,6 +131,7 @@ export const POST: APIRoute = async ({ request }) => {
errorMessage: repo.errorMessage ?? undefined,
forkedFrom: repo.forkedFrom ?? undefined,
visibility: repositoryVisibilityEnum.parse(repo.visibility),
mirroredLocation: repo.mirroredLocation || "",
})),
};

View File

@@ -97,6 +97,7 @@ export const POST: APIRoute = async ({ request }) => {
status: "imported" as Repository["status"],
lastMirrored: undefined,
errorMessage: undefined,
mirroredLocation: "",
createdAt: repoData.created_at
? new Date(repoData.created_at)
: new Date(),

View File

@@ -1,63 +0,0 @@
---
import { getCollection } from 'astro:content';
import MainLayout from '../../layouts/main.astro';
// Enable prerendering for this dynamic route
export const prerender = true;
// Generate static paths for all documentation pages
export async function getStaticPaths() {
const docs = await getCollection('docs');
return docs.map(entry => ({
params: { slug: entry.slug },
props: { entry },
}));
}
// Get the documentation entry from props
const { entry } = Astro.props;
const { Content } = await entry.render();
---
<MainLayout title={entry.data.title}>
<main class="max-w-5xl mx-auto px-4 py-12">
<div class="sticky top-4 z-10 mb-6">
<a
href="/docs/"
class="inline-flex items-center gap-2 px-3 py-1.5 rounded-md bg-card text-foreground hover:bg-muted transition-colors border border-border focus:ring-2 focus:ring-ring outline-none"
>
<span aria-hidden="true">&larr;</span> Back to Documentation
</a>
</div>
<article class="bg-card rounded-2xl shadow-lg p-6 border border-border">
<div class="prose prose-neutral dark:prose-invert prose-code:bg-muted prose-code:text-foreground prose-pre:bg-muted prose-pre:text-foreground prose-pre:rounded-lg prose-pre:p-4 prose-table:rounded-lg prose-table:bg-muted prose-th:text-foreground prose-td:text-muted-foreground prose-blockquote:border-l-4 prose-blockquote:border-muted prose-blockquote:bg-muted/50 prose-blockquote:p-4">
<Content />
</div>
</article>
<script type="module">
// Mermaid diagram rendering for code blocks
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs';
mermaid.initialize({ startOnLoad: false, theme: document.documentElement.classList.contains('dark') ? 'dark' : 'default' });
function renderMermaidDiagrams() {
document.querySelectorAll('pre code.language-mermaid').forEach((block, i) => {
const parent = block.parentElement;
if (!parent) return;
const code = block.textContent;
const id = `mermaid-diagram-${i}`;
const container = document.createElement('div');
container.className = 'my-6';
container.id = id;
parent.replaceWith(container);
mermaid.render(id, code, (svgCode) => {
container.innerHTML = svgCode;
});
});
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', renderMermaidDiagrams);
} else {
renderMermaidDiagrams();
}
</script>
</main>
</MainLayout>

View File

@@ -0,0 +1,335 @@
---
import MainLayout from '../../layouts/main.astro';
---
<MainLayout title="Architecture - Gitea Mirror">
<main class="max-w-5xl mx-auto px-4 py-12">
<div class="sticky top-4 z-10 mb-6">
<a
href="/docs/"
class="inline-flex items-center gap-2 px-3 py-1.5 rounded-md bg-card text-foreground hover:bg-muted transition-colors border border-border focus:ring-2 focus:ring-ring outline-none"
>
<span aria-hidden="true">&larr;</span> Back to Documentation
</a>
</div>
<article class="bg-card rounded-2xl shadow-lg p-6 md:p-8 border border-border">
<!-- Header -->
<div class="mb-12 space-y-4">
<div class="flex items-center gap-2 text-sm text-muted-foreground mb-2">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-1 4h1m-1 4h1"/>
</svg>
<span>Architecture Overview</span>
</div>
<h1 class="text-4xl font-bold tracking-tight">Gitea Mirror Architecture</h1>
<p class="text-lg text-muted-foreground leading-relaxed max-w-4xl">
This document provides a comprehensive overview of the Gitea Mirror application architecture, including component diagrams, project structure, and detailed explanations of each part of the system.
</p>
</div>
<!-- System Overview -->
<section class="mb-12">
<h2 class="text-2xl font-bold mb-6">System Overview</h2>
<div class="bg-card/50 border border-border/50 rounded-lg p-6 mb-8">
<p class="text-base leading-relaxed mb-6">
Gitea Mirror is a web application that automates the mirroring of GitHub repositories to Gitea instances. It provides a user-friendly interface for configuring, monitoring, and managing mirroring operations without requiring users to edit configuration files or run Docker commands.
</p>
<div class="space-y-1">
<h3 class="text-sm font-semibold text-muted-foreground uppercase tracking-wider mb-3">Technology Stack</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
{[
{ name: 'Astro', desc: 'Web framework for Server-Side Rendering (SSR)' },
{ name: 'React', desc: 'Component library for interactive UI elements' },
{ name: 'Tailwind CSS v4', desc: 'Utility-first CSS framework (with Vite plugin)' },
{ name: 'Shadcn UI', desc: 'UI component library built on Tailwind CSS' },
{ name: 'SQLite', desc: 'Database for storing configuration, state, and events' },
{ name: 'Bun', desc: 'JavaScript runtime and package manager' },
{ name: 'Drizzle ORM', desc: 'Type-safe ORM for database interactions' }
].map(tech => (
<div class="flex items-start gap-3">
<div class="w-2 h-2 rounded-full bg-primary mt-2"></div>
<div>
<span class="font-semibold text-foreground">{tech.name}</span>
<p class="text-sm text-muted-foreground">{tech.desc}</p>
</div>
</div>
))}
</div>
</div>
</div>
</section>
<div class="my-12 h-px bg-border/50"></div>
<!-- Architecture Diagram -->
<section class="mb-12">
<h2 class="text-2xl font-bold mb-6">Architecture Diagram</h2>
<div class="my-8">
<div class="architecture-diagram bg-muted/30 rounded-xl p-8 border border-border">
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
<!-- Gitea Mirror System -->
<div class="bg-card rounded-lg border-2 border-primary/20 p-6">
<h3 class="text-lg font-semibold mb-6 text-center text-primary">Gitea Mirror System</h3>
<div class="space-y-4">
{[
{ icon: '🎨', name: 'Frontend', tech: 'Astro + React' },
{ icon: '⚙️', name: 'Backend', tech: 'Bun Runtime' },
{ icon: '🗄️', name: 'Database', tech: 'SQLite + Drizzle' }
].map((component, index) => (
<>
<div class="bg-primary/10 rounded-lg p-4 text-center">
<div class="text-2xl mb-2">{component.icon}</div>
<h4 class="font-semibold">{component.name}</h4>
<p class="text-sm text-muted-foreground mt-1">{component.tech}</p>
</div>
{index < 2 && (
<div class="flex justify-center">
<div class="text-muted-foreground">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16V4m0 0L3 8m4-4l4 4m6 0v12m0 0l4-4m-4 4l-4-4"/>
</svg>
</div>
</div>
)}
</>
))}
</div>
</div>
<!-- External APIs -->
<div class="space-y-4">
<div class="bg-card rounded-lg border-2 border-amber-500/20 p-6">
<h3 class="text-lg font-semibold mb-6 text-center text-amber-600 dark:text-amber-500">External APIs</h3>
<div class="space-y-4">
<div class="bg-amber-500/10 rounded-lg p-4 text-center">
<div class="text-2xl mb-2">🐙</div>
<h4 class="font-semibold">GitHub API</h4>
<p class="text-sm text-muted-foreground mt-1">Repository Data Source</p>
</div>
<div class="bg-amber-500/10 rounded-lg p-4 text-center">
<div class="text-2xl mb-2">🍵</div>
<h4 class="font-semibold">Gitea API</h4>
<p class="text-sm text-muted-foreground mt-1">Mirror Destination</p>
</div>
</div>
</div>
<div class="flex items-center justify-center gap-4 mt-6">
<div class="text-center">
<div class="text-sm text-muted-foreground mb-2">Data Flow</div>
<div class="flex items-center gap-2">
<span class="text-primary font-semibold">Backend</span>
<svg class="w-6 h-6 text-muted-foreground" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 8l4 4m0 0l-4 4m4-4H3"/>
</svg>
<span class="text-amber-600 dark:text-amber-500 font-semibold">APIs</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<div class="my-12 h-px bg-border/50"></div>
<!-- Component Breakdown -->
<section class="mb-12">
<h2 class="text-2xl font-bold mb-6">Component Breakdown</h2>
<!-- Frontend -->
<div class="mb-8">
<h3 class="text-xl font-semibold mb-4">Frontend (Astro + React)</h3>
<div class="pl-4 border-l-2 border-primary/20">
<p class="text-muted-foreground mb-4">
The frontend is built with Astro, a modern web framework that allows for server-side rendering and partial hydration. React components are used for interactive elements, providing a responsive and dynamic user interface.
</p>
<h4 class="font-semibold mb-3">Key Frontend Components</h4>
<div class="space-y-3">
{[
{ name: 'Dashboard', desc: 'Overview of mirroring status and recent activity' },
{ name: 'Repository Management', desc: 'Interface for managing repositories to mirror' },
{ name: 'Organization Management', desc: 'Interface for managing GitHub organizations' },
{ name: 'Configuration', desc: 'Settings for GitHub and Gitea connections' },
{ name: 'Activity Log', desc: 'Detailed log of mirroring operations' }
].map(component => (
<div class="flex gap-3">
<span class="text-primary font-mono text-sm">▸</span>
<div>
<strong>{component.name}</strong>
<p class="text-sm text-muted-foreground mt-1">{component.desc}</p>
</div>
</div>
))}
</div>
</div>
</div>
<!-- Backend -->
<div class="mb-8">
<h3 class="text-xl font-semibold mb-4">Backend (Bun)</h3>
<div class="pl-4 border-l-2 border-primary/20">
<p class="text-muted-foreground mb-4">
The backend is built with Bun and provides API endpoints for the frontend to interact with. It handles:
</p>
<div class="space-y-3">
{[
'Authentication and user management',
'GitHub API integration',
'Gitea API integration',
'Mirroring operations and job queue',
'Real-time updates via Server-Sent Events (SSE) at /api/sse/',
'Job recovery system for interrupted operations',
'Graceful shutdown handling',
'Scheduled automatic mirroring',
'Database interactions with Drizzle ORM'
].map(item => (
<div class="flex gap-3">
<span class="text-primary font-mono text-sm">▸</span>
<span>{item}</span>
</div>
))}
</div>
</div>
</div>
<!-- Database -->
<div>
<h3 class="text-xl font-semibold mb-4">Database (SQLite + Drizzle ORM)</h3>
<div class="pl-4 border-l-2 border-primary/20">
<p class="text-muted-foreground mb-4">
SQLite with Bun's native SQLite driver is used for data persistence, with Drizzle ORM providing type-safe database interactions. The database stores:
</p>
<div class="space-y-3">
{[
'User accounts and authentication data',
'GitHub and Gitea configuration',
'Repository and organization information',
'Mirroring job history and status',
'Event notifications and their read status'
].map(item => (
<div class="flex gap-3">
<span class="text-primary font-mono text-sm">▸</span>
<span>{item}</span>
</div>
))}
</div>
</div>
</div>
</section>
<div class="my-12 h-px bg-border/50"></div>
<!-- Data Flow -->
<section class="mb-12">
<h2 class="text-2xl font-bold mb-6">Data Flow</h2>
<div class="bg-gradient-to-r from-primary/5 to-transparent rounded-lg p-6 border-l-4 border-primary">
<ol class="space-y-4">
{[
{ title: 'User Authentication', desc: 'Users authenticate through the frontend, which communicates with the backend to validate credentials.' },
{ title: 'Configuration', desc: 'Users configure GitHub and Gitea settings through the UI, which are stored in the SQLite database.' },
{ title: 'Repository Discovery', desc: 'The backend queries the GitHub API to discover repositories based on user configuration.' },
{ title: 'Mirroring Process', desc: 'When triggered, the backend fetches repository data from GitHub and pushes it to Gitea.' },
{ title: 'Status Tracking', desc: 'All operations are logged in the database and displayed in the Activity Log.' }
].map((step, index) => (
<li class="flex gap-4">
<span class="flex-shrink-0 w-8 h-8 bg-primary/10 rounded-full flex items-center justify-center text-sm font-semibold">
{index + 1}
</span>
<div>
<strong class="text-foreground">{step.title}</strong>
<p class="text-sm text-muted-foreground mt-1">{step.desc}</p>
</div>
</li>
))}
</ol>
</div>
</section>
<div class="my-12 h-px bg-border/50"></div>
<!-- Project Structure -->
<section class="mb-12">
<h2 class="text-2xl font-bold mb-6">Project Structure</h2>
<div class="bg-muted/30 rounded-lg p-4">
<pre class="text-sm"><code>{`gitea-mirror/
├── src/ # Source code
│ ├── components/ # React components
│ ├── content/ # Documentation and content
│ ├── layouts/ # Astro layout components
│ ├── lib/ # Utility functions and database
│ ├── pages/ # Astro pages and API routes
│ └── styles/ # CSS and Tailwind styles
├── public/ # Static assets
├── data/ # Database and persistent data
├── docker/ # Docker configuration
└── scripts/ # Utility scripts for deployment and maintenance
├── gitea-mirror-lxc-local.sh # Local LXC deployment script
└── manage-db.ts # Database management tool`}</code></pre>
</div>
</section>
<div class="my-12 h-px bg-border/50"></div>
<!-- Deployment Options -->
<section>
<h2 class="text-2xl font-bold mb-6">Deployment Options</h2>
<p class="text-muted-foreground mb-6">Gitea Mirror supports multiple deployment options:</p>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 mb-6">
{[
{ icon: '🐳', name: 'Docker', desc: 'Run as a containerized application using Docker and docker-compose' },
{ icon: '📦', name: 'LXC Containers', desc: 'Deploy in Linux Containers on Proxmox VE using community script' },
{ icon: '🏃', name: 'Native', desc: 'Run directly on the host system using Bun runtime' }
].map(option => (
<div class="bg-card rounded-lg border border-border p-4 hover:border-primary/50 transition-colors">
<div class="flex items-start gap-3">
<div class="text-2xl">{option.icon}</div>
<div>
<h4 class="font-semibold mb-1">{option.name}</h4>
<p class="text-sm text-muted-foreground">{option.desc}</p>
</div>
</div>
</div>
))}
</div>
<div class="bg-muted/30 rounded-lg p-4 mt-6">
<h4 class="font-semibold mb-3">Deployment Advantages</h4>
<div class="space-y-2">
<div class="flex gap-3">
<strong class="text-primary">Docker:</strong>
<span class="text-muted-foreground">Isolation, easy updates, consistent environment</span>
</div>
<div class="flex gap-3">
<strong class="text-primary">LXC:</strong>
<span class="text-muted-foreground">Lightweight virtualization, better performance than Docker, system-level isolation</span>
</div>
<div class="flex gap-3">
<strong class="text-primary">Native:</strong>
<span class="text-muted-foreground">Best performance, direct access to system resources</span>
</div>
</div>
</div>
<div class="mt-4 text-sm text-muted-foreground">
<p><strong>Note:</strong> LXC deployment is available through the <a href="https://community-scripts.github.io/ProxmoxVE/scripts?id=gitea-mirror" class="text-primary hover:underline">Proxmox VE Community Scripts</a> project.</p>
</div>
</section>
</article>
</main>
</MainLayout>

View File

@@ -0,0 +1,512 @@
---
import MainLayout from '../../layouts/main.astro';
const envVars = [
{ name: 'NODE_ENV', desc: 'Runtime environment', default: 'development', example: 'production' },
{ name: 'DATABASE_URL', desc: 'SQLite database URL', default: 'file:data/gitea-mirror.db', example: 'file:path/to/database.db' },
{ name: 'JWT_SECRET', desc: 'Secret key for JWT auth', default: 'Auto-generated', example: 'your-secure-string' },
{ name: 'HOST', desc: 'Server host', default: 'localhost', example: '0.0.0.0' },
{ name: 'PORT', desc: 'Server port', default: '4321', example: '8080' }
];
const githubOptions = [
{ name: 'Username', desc: 'Your GitHub username', default: '-' },
{ name: 'Token', desc: 'GitHub personal access token (Classic PAT)', default: '-' },
{ name: 'Private Repositories', desc: 'Include private repositories', default: 'false' },
{ name: 'Mirror Starred', desc: 'Mirror repositories you\'ve starred', default: 'false' },
{ name: 'Mirror Issues', desc: 'Mirror issues from GitHub to Gitea', default: 'false' },
{ name: 'Mirror Wiki', desc: 'Mirror wiki pages from GitHub to Gitea', default: 'false' },
{ name: 'Mirror Organizations', desc: 'Mirror organization repositories', default: 'false' },
{ name: 'Only Mirror Orgs', desc: 'Only mirror organization repositories', default: 'false' },
{ name: 'Skip Forks', desc: 'Exclude repositories that are forks', default: 'false' },
{ name: 'Skip Starred Issues', desc: 'Skip issues for starred repositories', default: 'false' }
];
const giteaOptions = [
{ name: 'URL', desc: 'Gitea server URL', default: '-' },
{ name: 'Token', desc: 'Gitea access token', default: '-' },
{ name: 'Organization', desc: 'Default organization for mirrored repositories', default: '-' },
{ name: 'Visibility', desc: 'Default visibility for mirrored repositories', default: 'public' },
{ name: 'Starred Repos Org', desc: 'Organization for starred repositories', default: 'github' }
];
---
<MainLayout title="Configuration - Gitea Mirror">
<main class="max-w-5xl mx-auto px-4 py-12">
<div class="sticky top-4 z-10 mb-6">
<a
href="/docs/"
class="inline-flex items-center gap-2 px-3 py-1.5 rounded-md bg-card text-foreground hover:bg-muted transition-colors border border-border focus:ring-2 focus:ring-ring outline-none"
>
<span aria-hidden="true">&larr;</span> Back to Documentation
</a>
</div>
<article class="bg-card rounded-2xl shadow-lg p-6 md:p-8 border border-border">
<!-- Header -->
<div class="mb-12 space-y-4">
<div class="flex items-center gap-2 text-sm text-muted-foreground mb-2">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
</svg>
<span>Configuration Guide</span>
</div>
<h1 class="text-4xl font-bold tracking-tight">Gitea Mirror Configuration</h1>
<p class="text-lg text-muted-foreground leading-relaxed max-w-4xl">
This guide provides detailed information on how to configure Gitea Mirror for your environment.
</p>
</div>
<!-- Configuration Methods -->
<section class="mb-12">
<h2 class="text-2xl font-bold mb-6">Configuration Methods</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-8">
<div class="bg-card rounded-lg border border-border p-6 hover:border-primary/50 transition-colors">
<div class="flex items-start gap-4">
<div class="text-2xl">🔧</div>
<div>
<h3 class="font-semibold text-lg mb-2">Environment Variables</h3>
<p class="text-sm text-muted-foreground">Set configuration options through environment variables for automated deployments</p>
</div>
</div>
</div>
<div class="bg-card rounded-lg border border-border p-6 hover:border-primary/50 transition-colors">
<div class="flex items-start gap-4">
<div class="text-2xl">🖥️</div>
<div>
<h3 class="font-semibold text-lg mb-2">Web UI</h3>
<p class="text-sm text-muted-foreground">Configure the application through the web interface after installation</p>
</div>
</div>
</div>
</div>
</section>
<div class="my-12 h-px bg-border/50"></div>
<!-- Environment Variables -->
<section class="mb-12">
<h2 class="text-2xl font-bold mb-6">Environment Variables</h2>
<p class="text-muted-foreground mb-6">The following environment variables can be used to configure Gitea Mirror:</p>
<div class="overflow-x-auto">
<table class="w-full border-collapse">
<thead>
<tr class="border-b border-border">
<th class="text-left py-3 px-4 font-semibold">Variable</th>
<th class="text-left py-3 px-4 font-semibold">Description</th>
<th class="text-left py-3 px-4 font-semibold">Default</th>
<th class="text-left py-3 px-4 font-semibold">Example</th>
</tr>
</thead>
<tbody>
{envVars.map((v, i) => (
<tr class={`border-b border-border/50 hover:bg-muted/30 ${i === envVars.length - 1 ? 'border-b-0' : ''}`}>
<td class="py-3 px-4">
<code class="text-sm bg-muted px-1.5 py-0.5 rounded">{v.name}</code>
</td>
<td class="py-3 px-4 text-sm text-muted-foreground">{v.desc}</td>
<td class="py-3 px-4 text-sm"><code>{v.default}</code></td>
<td class="py-3 px-4 text-sm"><code>{v.example}</code></td>
</tr>
))}
</tbody>
</table>
</div>
<!-- Security Note -->
<div class="bg-amber-500/10 border border-amber-500/20 rounded-lg p-4 mt-6">
<div class="flex gap-3">
<div class="text-amber-600 dark:text-amber-500">
<svg class="w-5 h-5 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/>
</svg>
</div>
<div>
<h4 class="font-semibold text-amber-600 dark:text-amber-500 mb-1">Security Note</h4>
<p class="text-sm">The application will automatically generate a secure random <code class="bg-amber-500/10 px-1 py-0.5 rounded">JWT_SECRET</code> on first run if one isn't provided. This generated secret is stored in the data directory for persistence across container restarts.</p>
<p class="text-sm mt-2">While this auto-generation feature provides good security by default, you can still explicitly set your own <code class="bg-amber-500/10 px-1 py-0.5 rounded">JWT_SECRET</code> for complete control over your deployment.</p>
</div>
</div>
</div>
</section>
<div class="my-12 h-px bg-border/50"></div>
<!-- Web UI Configuration -->
<section class="mb-12">
<h2 class="text-2xl font-bold mb-6">Web UI Configuration</h2>
<p class="text-muted-foreground mb-6">After installing and starting Gitea Mirror, you can configure it through the web interface:</p>
<div class="bg-gradient-to-r from-primary/5 to-transparent rounded-lg p-6 border-l-4 border-primary mb-8">
<ol class="space-y-3">
{[
'Navigate to <code class="bg-muted px-1.5 py-0.5 rounded text-sm">http://your-server:port/</code>',
'If this is your first time, you\'ll be guided through creating an admin account',
'Log in with your credentials',
'Go to the Configuration page'
].map((step, i) => (
<li class="flex gap-3">
<span class="flex-shrink-0 w-6 h-6 bg-primary/10 rounded-full flex items-center justify-center text-sm font-semibold">{i + 1}</span>
<span set:html={step}></span>
</li>
))}
</ol>
</div>
<!-- GitHub Configuration -->
<div class="mb-8">
<h3 class="text-xl font-semibold mb-4">GitHub Configuration</h3>
<p class="text-muted-foreground mb-4">The GitHub configuration section allows you to connect to GitHub and specify which repositories to mirror.</p>
<div class="overflow-x-auto mb-6">
<table class="w-full border-collapse">
<thead>
<tr class="border-b border-border">
<th class="text-left py-3 px-4 font-semibold">Option</th>
<th class="text-left py-3 px-4 font-semibold">Description</th>
<th class="text-left py-3 px-4 font-semibold">Default</th>
</tr>
</thead>
<tbody>
{githubOptions.map((opt, i) => (
<tr class={`border-b border-border/50 hover:bg-muted/30 ${i === githubOptions.length - 1 ? 'border-b-0' : ''}`}>
<td class="py-3 px-4 font-medium">{opt.name}</td>
<td class="py-3 px-4 text-sm text-muted-foreground">{opt.desc}</td>
<td class="py-3 px-4 text-sm"><code>{opt.default}</code></td>
</tr>
))}
</tbody>
</table>
</div>
<!-- GitHub Token Permissions -->
<div class="bg-blue-500/10 border border-blue-500/20 rounded-lg p-4 mb-6">
<div class="flex gap-3">
<div class="text-blue-600 dark:text-blue-500">
<svg class="w-5 h-5 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
</div>
<div>
<h4 class="font-semibold text-blue-600 dark:text-blue-500 mb-2">Required Permissions</h4>
<p class="text-sm mb-3">You need to create a <span class="font-semibold">Classic GitHub PAT Token</span> with the following scopes:</p>
<ul class="space-y-1 text-sm">
<li class="flex gap-2">
<span class="text-blue-600 dark:text-blue-500">•</span>
<span><code class="bg-blue-500/10 px-1 py-0.5 rounded">repo</code> - Full control of private repositories</span>
</li>
<li class="flex gap-2">
<span class="text-blue-600 dark:text-blue-500">•</span>
<span><code class="bg-blue-500/10 px-1 py-0.5 rounded">admin:org</code> - Full control of orgs and teams, read and write org projects</span>
</li>
</ul>
<p class="text-sm mt-2">The organization access is required for mirroring organization repositories.</p>
</div>
</div>
</div>
<div class="pl-4 border-l-2 border-primary/20">
<h5 class="font-semibold mb-3">To create a GitHub token:</h5>
<ol class="space-y-2 text-sm">
<li>Go to <a href="https://github.com/settings/tokens" class="text-primary hover:underline">GitHub Settings > Developer settings > Personal access tokens</a></li>
<li>Click "Generate new token"</li>
<li>Select the required permissions</li>
<li>Copy the generated token and paste it into Gitea Mirror</li>
</ol>
</div>
</div>
<!-- Gitea Configuration -->
<div class="mb-8">
<h3 class="text-xl font-semibold mb-4">Gitea Configuration</h3>
<p class="text-muted-foreground mb-4">The Gitea configuration section allows you to connect to your Gitea instance and specify how repositories should be mirrored.</p>
<div class="overflow-x-auto mb-6">
<table class="w-full border-collapse">
<thead>
<tr class="border-b border-border">
<th class="text-left py-3 px-4 font-semibold">Option</th>
<th class="text-left py-3 px-4 font-semibold">Description</th>
<th class="text-left py-3 px-4 font-semibold">Default</th>
</tr>
</thead>
<tbody>
{giteaOptions.map((opt, i) => (
<tr class={`border-b border-border/50 hover:bg-muted/30 ${i === giteaOptions.length - 1 ? 'border-b-0' : ''}`}>
<td class="py-3 px-4 font-medium">{opt.name}</td>
<td class="py-3 px-4 text-sm text-muted-foreground">{opt.desc}</td>
<td class="py-3 px-4 text-sm"><code>{opt.default}</code></td>
</tr>
))}
</tbody>
</table>
</div>
<!-- Mirror Strategies -->
<div class="bg-gradient-to-r from-primary/5 to-transparent rounded-lg p-6 border-l-4 border-primary mb-6">
<h4 class="font-semibold text-lg mb-4">Mirror Strategies</h4>
<p class="text-sm text-muted-foreground mb-4">Choose how your repositories will be organized in Gitea:</p>
<div class="space-y-4">
<div class="bg-card rounded-lg border border-border p-4">
<h5 class="font-semibold text-base mb-2 flex items-center gap-2">
<span class="text-blue-600 dark:text-blue-500">📁</span>
Preserve GitHub Structure
</h5>
<p class="text-sm text-muted-foreground mb-2">Maintains the exact structure from GitHub:</p>
<ul class="space-y-1 text-sm ml-4">
<li class="flex gap-2">
<span class="text-muted-foreground">•</span>
<span>Personal repos → Your Gitea username</span>
</li>
<li class="flex gap-2">
<span class="text-muted-foreground">•</span>
<span>Organization repos → Same organization name in Gitea</span>
</li>
</ul>
</div>
<div class="bg-card rounded-lg border border-border p-4">
<h5 class="font-semibold text-base mb-2 flex items-center gap-2">
<span class="text-purple-600 dark:text-purple-500">🏢</span>
Single Organization
</h5>
<p class="text-sm text-muted-foreground mb-2">Consolidates all repositories into one organization:</p>
<ul class="space-y-1 text-sm ml-4">
<li class="flex gap-2">
<span class="text-muted-foreground">•</span>
<span>All repos → One designated organization</span>
</li>
<li class="flex gap-2">
<span class="text-muted-foreground">•</span>
<span>Requires setting "Organization" field</span>
</li>
</ul>
</div>
<div class="bg-card rounded-lg border border-border p-4">
<h5 class="font-semibold text-base mb-2 flex items-center gap-2">
<span class="text-green-600 dark:text-green-500">👤</span>
Flat User Structure
</h5>
<p class="text-sm text-muted-foreground mb-2">Mirrors all repositories under your user account:</p>
<ul class="space-y-1 text-sm ml-4">
<li class="flex gap-2">
<span class="text-muted-foreground">•</span>
<span>All repos → Your Gitea username</span>
</li>
<li class="flex gap-2">
<span class="text-muted-foreground">•</span>
<span>No organizations needed</span>
</li>
</ul>
</div>
</div>
<div class="bg-amber-500/10 border border-amber-500/20 rounded-lg p-3 mt-4">
<p class="text-sm">
<span class="font-semibold">Note:</span> Starred repositories are always mirrored to the "Starred Repos Org" (default: "starred") regardless of the chosen strategy.
</p>
</div>
</div>
<div class="pl-4 border-l-2 border-primary/20">
<h5 class="font-semibold mb-3">To create a Gitea access token:</h5>
<ol class="space-y-2 text-sm">
<li>Log in to your Gitea instance</li>
<li>Go to Settings > Applications</li>
<li>Under "Generate New Token", enter a name for your token</li>
<li>Click "Generate Token"</li>
<li>Copy the generated token and paste it into Gitea Mirror</li>
</ol>
</div>
</div>
<!-- Schedule Configuration -->
<div>
<h3 class="text-xl font-semibold mb-4">Schedule Configuration</h3>
<p class="text-muted-foreground mb-4">You can configure automatic mirroring on a schedule:</p>
<div class="overflow-x-auto">
<table class="w-full border-collapse">
<thead>
<tr class="border-b border-border">
<th class="text-left py-3 px-4 font-semibold">Option</th>
<th class="text-left py-3 px-4 font-semibold">Description</th>
<th class="text-left py-3 px-4 font-semibold">Default</th>
</tr>
</thead>
<tbody>
<tr class="border-b border-border/50 hover:bg-muted/30">
<td class="py-3 px-4 font-medium">Enable Scheduling</td>
<td class="py-3 px-4 text-sm text-muted-foreground">Enable automatic mirroring</td>
<td class="py-3 px-4 text-sm"><code>false</code></td>
</tr>
<tr class="hover:bg-muted/30">
<td class="py-3 px-4 font-medium">Interval (seconds)</td>
<td class="py-3 px-4 text-sm text-muted-foreground">Time between mirroring operations</td>
<td class="py-3 px-4 text-sm"><code>3600</code> (1 hour)</td>
</tr>
</tbody>
</table>
</div>
</div>
</section>
<div class="my-12 h-px bg-border/50"></div>
<!-- Advanced Configuration -->
<section class="mb-12">
<h2 class="text-2xl font-bold mb-6">Advanced Configuration</h2>
<!-- Database Management -->
<div class="mb-8">
<h3 class="text-xl font-semibold mb-4">Database Management</h3>
<p class="text-muted-foreground mb-4">Gitea Mirror includes several database management tools that can be run from the command line:</p>
<div class="bg-muted/30 rounded-lg p-4 overflow-x-auto">
<pre class="text-sm whitespace-pre-wrap break-all"><code>{`# Initialize the database (only if it doesn't exist)
bun run init-db
# Check database status
bun run check-db
# Fix database location issues
bun run fix-db
# Reset all users (for testing signup flow)
bun run reset-users
# Remove database files completely
bun run cleanup-db`}</code></pre>
</div>
</div>
<!-- Event Management -->
<div class="mb-8">
<h3 class="text-xl font-semibold mb-4">Event Management</h3>
<p class="text-muted-foreground mb-4">Events in Gitea Mirror (such as repository mirroring operations) are stored in the SQLite database and can be viewed in the Activity Log page.</p>
<div class="bg-green-500/10 border border-green-500/20 rounded-lg p-4">
<div class="flex gap-3">
<div class="text-green-600 dark:text-green-500">
<svg class="w-5 h-5 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
</div>
<div>
<p class="text-sm mb-2">Event Management Features:</p>
<ul class="space-y-1 text-sm">
<li class="flex gap-2">
<span class="text-green-600 dark:text-green-500">•</span>
<span>View all events with filtering by type, status, and search</span>
</li>
<li class="flex gap-2">
<span class="text-green-600 dark:text-green-500">•</span>
<span>Real-time updates via Server-Sent Events (SSE)</span>
</li>
<li class="flex gap-2">
<span class="text-green-600 dark:text-green-500">•</span>
<span>Clean up old events using the cleanup button in the Activity Log</span>
</li>
<li class="flex gap-2">
<span class="text-green-600 dark:text-green-500">•</span>
<span>Automatic cleanup with configurable retention period</span>
</li>
</ul>
</div>
</div>
</div>
</div>
<!-- Automatic Recovery System -->
<div class="mb-8">
<h3 class="text-xl font-semibold mb-4">Automatic Recovery System</h3>
<p class="text-muted-foreground mb-4">Gitea Mirror includes a robust recovery system that automatically handles interrupted operations:</p>
<div class="bg-card rounded-lg border border-border p-6">
<ul class="space-y-3">
<li class="flex gap-2">
<span class="text-primary">✓</span>
<div>
<span class="font-semibold">Startup Recovery:</span>
<p class="text-sm text-muted-foreground mt-1">Automatically recovers interrupted jobs when the application starts</p>
</div>
</li>
<li class="flex gap-2">
<span class="text-primary">✓</span>
<div>
<span class="font-semibold">Graceful Shutdown:</span>
<p class="text-sm text-muted-foreground mt-1">Saves job state before shutting down to enable recovery on restart</p>
</div>
</li>
<li class="flex gap-2">
<span class="text-primary">✓</span>
<div>
<span class="font-semibold">Job State Persistence:</span>
<p class="text-sm text-muted-foreground mt-1">Stores mirror job progress in the database for resilience</p>
</div>
</li>
</ul>
<div class="mt-4 bg-muted/30 rounded-lg p-4 overflow-x-auto">
<p class="text-sm font-medium mb-2">Manual recovery tools:</p>
<pre class="text-sm whitespace-pre-wrap break-all"><code>{`# Run startup recovery manually
bun run startup-recovery
# Fix interrupted jobs
bun scripts/fix-interrupted-jobs.ts
# Test recovery system
bun run test-recovery`}</code></pre>
</div>
</div>
</div>
<!-- Health Check Endpoint -->
<div>
<h3 class="text-xl font-semibold mb-4">Health Check Endpoint</h3>
<div class="bg-card rounded-lg border border-border p-6">
<h4 class="font-semibold mb-3">System Health Monitoring</h4>
<p class="text-muted-foreground mb-4">Gitea Mirror includes a built-in health check endpoint at <code class="bg-muted px-1.5 py-0.5 rounded">/api/health</code> that provides:</p>
<ul class="space-y-2 mb-6">
<li class="flex gap-2">
<span class="text-primary">✓</span>
<span>System status and uptime</span>
</li>
<li class="flex gap-2">
<span class="text-primary">✓</span>
<span>Database connectivity check</span>
</li>
<li class="flex gap-2">
<span class="text-primary">✓</span>
<span>Memory usage statistics</span>
</li>
<li class="flex gap-2">
<span class="text-primary">✓</span>
<span>Environment information</span>
</li>
</ul>
<div class="bg-muted/30 rounded-lg p-4 overflow-x-auto">
<pre class="text-sm whitespace-pre-wrap break-all"><code>{`# Basic check (returns 200 OK if healthy)
curl -I http://your-server:port/api/health
# Detailed health information (JSON)
curl http://your-server:port/api/health`}</code></pre>
</div>
</div>
</div>
</section>
</article>
</main>
</MainLayout>

View File

@@ -1,18 +1,37 @@
---
import { getCollection } from 'astro:content';
import MainLayout from '../../layouts/main.astro';
import { LuSettings, LuRocket, LuBookOpen } from 'react-icons/lu';
// Helper to pick an icon based on doc.slug
// We'll use inline conditional rendering instead of this function
// Define our documentation pages directly
const docs = [
{
slug: 'architecture',
title: 'Architecture',
description: 'Comprehensive overview of the Gitea Mirror application architecture.',
order: 1,
icon: LuBookOpen,
href: '/docs/architecture'
},
{
slug: 'configuration',
title: 'Configuration',
description: 'Guide to configuring Gitea Mirror for your environment.',
order: 2,
icon: LuSettings,
href: '/docs/configuration'
},
{
slug: 'quickstart',
title: 'Quick Start Guide',
description: 'Get started with Gitea Mirror quickly.',
order: 3,
icon: LuRocket,
href: '/docs/quickstart'
}
];
// Get all documentation entries, sorted by order
const docs = await getCollection('docs');
const sortedDocs = docs.sort((a, b) => {
const orderA = a.data.order || 999;
const orderB = b.data.order || 999;
return orderA - orderB;
});
// Sort by order
const sortedDocs = docs.sort((a, b) => a.order - b.order);
---
<MainLayout title="Documentation">
@@ -21,24 +40,25 @@ const sortedDocs = docs.sort((a, b) => {
<p class="mb-10 text-lg text-muted-foreground text-center">Browse guides and technical docs for Gitea Mirror.</p>
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
{sortedDocs.map(doc => (
<a
href={`/docs/${doc.slug}`}
class="group block p-7 border border-border rounded-2xl bg-card hover:bg-muted transition-colors shadow-lg focus:ring-2 focus:ring-ring outline-none"
tabindex="0"
>
<div class="flex items-center gap-3 mb-2">
<div class="w-10 h-10 bg-muted rounded-full flex items-center justify-center text-muted-foreground">
{doc.slug === 'architecture' && <LuBookOpen className="w-5 h-5" />}
{doc.slug === 'configuration' && <LuSettings className="w-5 h-5" />}
{doc.slug === 'quickstart' && <LuRocket className="w-5 h-5" />}
{!['architecture', 'configuration', 'quickstart'].includes(doc.slug) && <LuBookOpen className="w-5 h-5" />}
{sortedDocs.map(doc => {
const Icon = doc.icon;
return (
<a
href={doc.href}
class="group block p-7 border border-border rounded-2xl bg-card hover:bg-muted transition-colors shadow-lg focus:ring-2 focus:ring-ring outline-none"
tabindex="0"
>
<div class="flex items-center gap-3 mb-2">
<div class="w-10 h-10 bg-muted rounded-full flex items-center justify-center text-muted-foreground">
<Icon className="w-5 h-5" />
</div>
<h2 class="text-xl font-semibold group-hover:text-foreground transition">{doc.title}</h2>
</div>
<h2 class="text-xl font-semibold group-hover:text-foreground transition">{doc.data.title}</h2>
</div>
<p class="text-muted-foreground">{doc.data.description}</p>
</a>
))}
<p class="text-muted-foreground">{doc.description}</p>
</a>
);
})}
</div>
</main>
</MainLayout>
</MainLayout>

View File

@@ -0,0 +1,437 @@
---
import MainLayout from '../../layouts/main.astro';
---
<MainLayout title="Quick Start Guide - Gitea Mirror">
<main class="max-w-5xl mx-auto px-4 py-12">
<div class="sticky top-4 z-10 mb-6">
<a
href="/docs/"
class="inline-flex items-center gap-2 px-3 py-1.5 rounded-md bg-card text-foreground hover:bg-muted transition-colors border border-border focus:ring-2 focus:ring-ring outline-none"
>
<span aria-hidden="true">&larr;</span> Back to Documentation
</a>
</div>
<article class="bg-card rounded-2xl shadow-lg p-6 md:p-8 border border-border">
<!-- Header -->
<div class="mb-12 space-y-4">
<div class="flex items-center gap-2 text-sm text-muted-foreground mb-2">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/>
</svg>
<span>Quick Start</span>
</div>
<h1 class="text-4xl font-bold tracking-tight">Gitea Mirror Quick Start Guide</h1>
<p class="text-lg text-muted-foreground leading-relaxed max-w-4xl">
This guide will help you get Gitea Mirror up and running quickly.
</p>
</div>
<!-- Prerequisites -->
<section class="mb-12">
<h2 class="text-2xl font-bold mb-6">Prerequisites</h2>
<div class="bg-gradient-to-r from-primary/5 to-transparent rounded-lg p-6 border-l-4 border-primary mb-8">
<h3 class="font-semibold mb-4">Before you begin, make sure you have:</h3>
<div class="space-y-3">
<div class="flex items-start gap-3">
<div class="flex-shrink-0 w-6 h-6 bg-primary/10 rounded-full flex items-center justify-center text-sm font-semibold">1</div>
<div>
<span class="font-semibold text-foreground">A GitHub account with a personal access token</span>
<p class="text-sm text-muted-foreground mt-1">Create one at <a href="https://github.com/settings/tokens" class="text-primary hover:underline">GitHub Settings</a></p>
</div>
</div>
<div class="flex items-start gap-3">
<div class="flex-shrink-0 w-6 h-6 bg-primary/10 rounded-full flex items-center justify-center text-sm font-semibold">2</div>
<div>
<span class="font-semibold text-foreground">A Gitea instance with an access token</span>
<p class="text-sm text-muted-foreground mt-1">Available in your Gitea Settings > Applications</p>
</div>
</div>
<div class="flex items-start gap-3">
<div class="flex-shrink-0 w-6 h-6 bg-primary/10 rounded-full flex items-center justify-center text-sm font-semibold">3</div>
<div>
<span class="font-semibold text-foreground">One of the following:</span>
<ul class="mt-2 space-y-1 text-sm text-muted-foreground pl-4">
<li class="flex gap-2"><span>•</span> Docker and docker-compose (for Docker deployment)</li>
<li class="flex gap-2"><span>•</span> Bun 1.2.9+ (for native deployment)</li>
<li class="flex gap-2"><span>•</span> Proxmox VE or LXD (for LXC container deployment)</li>
</ul>
</div>
</div>
</div>
</div>
</section>
<div class="my-12 h-px bg-border/50"></div>
<!-- Installation Options -->
<section class="mb-12">
<h2 class="text-2xl font-bold mb-6">Installation Options</h2>
<p class="text-muted-foreground mb-8">Choose the installation method that works best for your environment.</p>
<!-- Docker Installation -->
<div class="mb-8">
<h3 class="text-xl font-semibold mb-4 flex items-center gap-2">
🐳 Using Docker <span class="text-sm text-muted-foreground font-normal">(Recommended for most users)</span>
</h3>
<div class="bg-card rounded-lg border border-border p-6">
<p class="text-muted-foreground mb-4">Docker provides the easiest way to get started with minimal configuration.</p>
<div class="space-y-4">
{[
{ step: 'Clone the repository', cmd: 'git clone https://github.com/arunavo4/gitea-mirror.git\ncd gitea-mirror' },
{ step: 'Start the application in production mode', cmd: 'docker compose up -d' },
{ step: 'Access the application', cmd: null, text: 'Open your browser and navigate to <a href="http://localhost:4321" class="text-primary hover:underline font-medium">http://localhost:4321</a>' }
].map((item, i) => (
<div>
<div class="flex items-center gap-2 mb-2">
<span class="text-sm font-semibold text-muted-foreground">STEP {i + 1}</span>
<span class="text-sm text-muted-foreground">{item.step}</span>
</div>
{item.cmd ? (
<div class="bg-muted/30 rounded-lg p-4">
<pre class="text-sm"><code>{item.cmd}</code></pre>
</div>
) : (
<p class="text-sm" set:html={item.text}></p>
)}
</div>
))}
</div>
</div>
</div>
<!-- Bun Installation -->
<div class="mb-8">
<h3 class="text-xl font-semibold mb-4 flex items-center gap-2">
🏃 Using Bun <span class="text-sm text-muted-foreground font-normal">(Native Installation)</span>
</h3>
<div class="bg-card rounded-lg border border-border p-6">
<p class="text-muted-foreground mb-4">If you prefer to run the application directly on your system:</p>
<div class="space-y-4">
<div>
<div class="flex items-center gap-2 mb-2">
<span class="text-sm font-semibold text-muted-foreground">STEP 1</span>
<span class="text-sm text-muted-foreground">Clone the repository</span>
</div>
<div class="bg-muted/30 rounded-lg p-4">
<pre class="text-sm"><code>git clone https://github.com/arunavo4/gitea-mirror.git
cd gitea-mirror</code></pre>
</div>
</div>
<div>
<div class="flex items-center gap-2 mb-2">
<span class="text-sm font-semibold text-muted-foreground">STEP 2</span>
<span class="text-sm text-muted-foreground">Run the quick setup script</span>
</div>
<div class="bg-muted/30 rounded-lg p-4">
<pre class="text-sm"><code>bun run setup</code></pre>
</div>
<p class="text-sm text-muted-foreground mt-2">This installs dependencies and initializes the database.</p>
</div>
<div>
<div class="flex items-center gap-2 mb-2">
<span class="text-sm font-semibold text-muted-foreground">STEP 3</span>
<span class="text-sm text-muted-foreground">Choose how to run the application</span>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mt-3">
<div class="border border-border rounded-lg p-4">
<h5 class="font-semibold mb-2">Development Mode</h5>
<div class="bg-muted/30 rounded-lg p-3 text-sm">
<pre><code>bun run dev</code></pre>
</div>
<p class="text-xs text-muted-foreground mt-2">For Bun-specific features, use: <code class="bg-muted px-1 rounded">bunx --bun astro dev</code></p>
</div>
<div class="border border-border rounded-lg p-4">
<h5 class="font-semibold mb-2">Production Mode</h5>
<div class="bg-muted/30 rounded-lg p-3 text-sm">
<pre><code>bun run build
bun run start</code></pre>
</div>
</div>
</div>
</div>
<div>
<div class="flex items-center gap-2 mb-2">
<span class="text-sm font-semibold text-muted-foreground">STEP 4</span>
<span class="text-sm text-muted-foreground">Access the application</span>
</div>
<p class="text-sm">Open your browser and navigate to <a href="http://localhost:4321" class="text-primary hover:underline font-medium">http://localhost:4321</a></p>
</div>
</div>
</div>
</div>
<!-- LXC Installation -->
<div>
<h3 class="text-xl font-semibold mb-4 flex items-center gap-2">
📦 Using LXC Containers <span class="text-sm text-muted-foreground font-normal">(Recommended for Proxmox VE)</span>
</h3>
<div class="bg-card rounded-lg border border-border p-6">
<div class="space-y-4">
<h4 class="font-semibold flex items-center gap-2">
<span>Proxmox VE Installation</span>
<span class="text-xs text-muted-foreground font-normal">(Community Script)</span>
</h4>
<p class="text-sm text-muted-foreground mb-4">Deploy Gitea Mirror on Proxmox VE using the community-maintained script:</p>
<div class="bg-muted/30 rounded-lg p-4 overflow-x-auto">
<pre class="text-sm whitespace-pre-wrap break-all"><code>bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/gitea-mirror.sh)"</code></pre>
</div>
<div class="mt-4 text-sm text-muted-foreground">
<p class="font-medium mb-2">This script will:</p>
<ul class="space-y-1 pl-4">
<li class="flex gap-2"><span>•</span> Create a privileged Alpine Linux LXC container</li>
<li class="flex gap-2"><span>•</span> Install Bun runtime and dependencies</li>
<li class="flex gap-2"><span>•</span> Clone and build Gitea Mirror</li>
<li class="flex gap-2"><span>•</span> Configure systemd service for automatic startup</li>
<li class="flex gap-2"><span>•</span> Set up the application to run on port 4321</li>
</ul>
</div>
<div class="mt-4 p-3 bg-blue-500/10 border border-blue-500/20 rounded-lg">
<div class="flex gap-3">
<div class="text-blue-600 dark:text-blue-500">
<svg class="w-5 h-5 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
</div>
<div class="text-sm">
<p class="font-semibold text-blue-600 dark:text-blue-500 mb-1">Note</p>
<p>After installation, access Gitea Mirror at <code class="bg-blue-500/10 px-1 py-0.5 rounded">http://&lt;container-ip&gt;:4321</code></p>
</div>
</div>
</div>
</div>
<div class="mt-4 pt-4 border-t border-border">
<p class="text-sm text-muted-foreground">
For more information about the community script, visit the
<a href="https://community-scripts.github.io/ProxmoxVE/scripts?id=gitea-mirror" class="text-primary hover:underline">Community Scripts documentation</a>.
</p>
</div>
</div>
</div>
</section>
<div class="my-12 h-px bg-border/50"></div>
<!-- Initial Configuration -->
<section class="mb-12">
<h2 class="text-2xl font-bold mb-6">Initial Configuration</h2>
<p class="text-muted-foreground mb-6">Follow these steps to configure Gitea Mirror for first use:</p>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
{[
{
num: '1',
title: 'Create Admin Account',
items: [
"You'll be prompted on first access",
'Choose a secure username and password',
'This will be your administrator account'
]
},
{
num: '2',
title: 'Configure GitHub Connection',
items: [
'Navigate to the Configuration page',
'Enter your GitHub credentials',
'Select repositories to mirror',
'Configure filtering options'
]
},
{
num: '3',
title: 'Configure Gitea Connection',
items: [
'Enter your Gitea server URL',
'Enter your Gitea access token',
'Configure organization settings',
'Set default visibility'
]
},
{
num: '4',
title: 'Set Up Scheduling',
items: [
'Enable automatic mirroring',
'Set the mirroring interval',
'Save your configuration'
]
}
].map(step => (
<div class="bg-card rounded-lg border border-border p-4 hover:border-primary/50 transition-colors">
<div class="flex items-start gap-3">
<div class="flex-shrink-0 w-8 h-8 bg-primary/10 rounded-full flex items-center justify-center text-sm font-semibold">{step.num}</div>
<div>
<h4 class="font-semibold mb-1">{step.title}</h4>
<ul class="text-sm text-muted-foreground space-y-1">
{step.items.map(item => (
<li>• {item}</li>
))}
</ul>
</div>
</div>
</div>
))}
</div>
</section>
<div class="my-12 h-px bg-border/50"></div>
<!-- Performing Your First Mirror -->
<section class="mb-12">
<h2 class="text-2xl font-bold mb-6">Performing Your First Mirror</h2>
<div class="bg-gradient-to-r from-primary/5 to-transparent rounded-lg p-6 border-l-4 border-primary">
<h3 class="font-semibold mb-4">After completing the configuration, you can start mirroring repositories:</h3>
<ol class="space-y-3">
{[
'Click <strong>"Import GitHub Data"</strong> to fetch repositories from GitHub',
'Go to the <strong>Repositories</strong> page to view your imported repositories',
'Select the repositories you want to mirror',
'Click <strong>"Mirror Selected"</strong> to start the mirroring process',
'Monitor the progress on the <strong>Activity</strong> page',
"You'll receive toast notifications about the success or failure of operations"
].map((step, i) => (
<li class="flex gap-3">
<span class="flex-shrink-0 w-6 h-6 bg-primary/10 rounded-full flex items-center justify-center text-sm font-semibold">{i + 1}</span>
<span set:html={step}></span>
</li>
))}
</ol>
</div>
</section>
<div class="my-12 h-px bg-border/50"></div>
<!-- Troubleshooting -->
<section class="mb-12">
<h2 class="text-2xl font-bold mb-6">Troubleshooting</h2>
<div class="bg-amber-500/10 border border-amber-500/20 rounded-lg p-4">
<div class="flex gap-3">
<div class="text-amber-600 dark:text-amber-500">
<svg class="w-5 h-5 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/>
</svg>
</div>
<div class="space-y-3">
<h4 class="font-semibold text-amber-600 dark:text-amber-500">If you encounter any issues:</h4>
<div class="space-y-2 text-sm">
<div class="flex gap-2">
<span class="text-amber-600 dark:text-amber-500">•</span>
<span>Check the <strong>Activity Log</strong> for detailed error messages</span>
</div>
<div class="flex gap-2">
<span class="text-amber-600 dark:text-amber-500">•</span>
<span>Verify your GitHub and Gitea tokens have the correct permissions</span>
</div>
<div class="flex gap-2">
<span class="text-amber-600 dark:text-amber-500">•</span>
<span>Ensure your Gitea instance is accessible from the machine running Gitea Mirror</span>
</div>
</div>
<div class="mt-4 space-y-3">
<h5 class="font-semibold">Check logs based on your deployment method:</h5>
<div class="bg-amber-500/5 rounded-lg p-3 space-y-2">
<div class="grid grid-cols-1 gap-2 text-sm">
<div>
<span class="font-medium">Docker:</span>
<code class="bg-amber-500/10 px-1.5 py-0.5 rounded ml-2">docker logs gitea-mirror</code>
</div>
<div>
<span class="font-medium">Native:</span>
<span class="text-muted-foreground ml-2">Check terminal output or system logs</span>
</div>
<div>
<span class="font-medium">LXC:</span>
<code class="bg-amber-500/10 px-1.5 py-0.5 rounded ml-2">systemctl status gitea-mirror</code>
<span class="text-muted-foreground ml-1">or</span>
<code class="bg-amber-500/10 px-1.5 py-0.5 rounded ml-1">journalctl -u gitea-mirror -f</code>
</div>
</div>
</div>
<div class="space-y-2 text-sm">
<div class="flex gap-2">
<span class="text-amber-600 dark:text-amber-500">•</span>
<span>Use the health check endpoint: <code class="bg-amber-500/10 px-1.5 py-0.5 rounded">curl http://your-server:4321/api/health</code></span>
</div>
<div class="flex gap-2">
<span class="text-amber-600 dark:text-amber-500">•</span>
<span>For database issues: <code class="bg-amber-500/10 px-1.5 py-0.5 rounded">bun run check-db</code> or <code class="bg-amber-500/10 px-1.5 py-0.5 rounded">bun run fix-db</code></span>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<div class="my-12 h-px bg-border/50"></div>
<!-- Next Steps -->
<section>
<h2 class="text-2xl font-bold mb-6">Next Steps</h2>
<div class="bg-gradient-to-br from-primary/5 via-transparent to-primary/5 rounded-lg p-6 border border-border">
<h3 class="font-semibold mb-4">After your initial setup:</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="space-y-3">
{[
'Explore the dashboard for an overview of your mirroring status',
'Set up automatic mirroring schedules for hands-off operation',
'Configure organization mirroring for team repositories',
'Use the cleanup button in Activity Log to manage old events'
].map(item => (
<div class="flex gap-3">
<span class="text-primary">✓</span>
<span>{item}</span>
</div>
))}
</div>
<div class="space-y-3">
<div class="flex gap-3">
<span class="text-primary">📖</span>
<span>Check out the <a href="/docs/configuration" class="text-primary hover:underline font-medium">Configuration Guide</a> for advanced settings</span>
</div>
<div class="flex gap-3">
<span class="text-primary">🏗️</span>
<span>Review the <a href="/docs/architecture" class="text-primary hover:underline font-medium">Architecture Documentation</a> to understand the system</span>
</div>
<div class="flex gap-3">
<span class="text-primary">📊</span>
<span>For server deployments, set up monitoring using the health check endpoint</span>
</div>
</div>
</div>
</div>
</section>
</article>
</main>
</MainLayout>