Update designs/grel-rs/TECHNICAL_DESIGN.md

This commit is contained in:
2026-04-12 00:08:25 +08:00
parent 90393ee190
commit 7882ccc435

View File

@@ -1,312 +1,339 @@
# 📘 `grel-rs` Technical Design Document (v3.0) # 📘 `grel-rs` Technical Design Document
**Binary:** `grel` | **Repository:** `grel-rs` **Binary:** `grel` | **Repository:** `grel-rs`
**Target Platforms:** Linux, Windows (macOS optional) **Target Platforms:** Linux, Windows (macOS optional)
**Core Philosophy:** Pure CLI, deterministic asset resolution, pacman/Artix-familiar UX, explicit over implicit, robust upgrade edge-case handling. **Core Philosophy:** Pure CLI, deterministic asset resolution, explicit over implicit, robust upgrade handling, transparent user control.
---
---
## 1. Overview & Goals
`grel` (Global/General Release) is a terminal-native, high-performance package manager for downloading and managing binary releases from Git forges. It abstracts provider-specific APIs into a unified resolution pipeline, supports HTTP/SOCKS5 proxies, caches optimal DNS endpoints, downloads assets in parallel, and delivers a transparent, scriptable, pacman-compatible UX. ## 1. Overview & Goals
`grel` is a terminal-native, high-performance release downloader and package manager for Git forges. It abstracts provider APIs into a unified pipeline, supports proxies, caches DNS/IPs for CDN routing, downloads in parallel, and delivers a transparent, scriptable, pacman-compatible UX.
**Key Requirements Met:**
- ✅ Pure CLI (no TUI/GUI), standard `std::io` prompts **Key Requirements Met:**
-Deterministic asset resolution: **filter → sort → explicit prompt** (zero scoring magic) -Pure CLI (no TUI/GUI), standard `std::io` prompts & warnings
-Robust parsing of non-standard release names via `Os`/`Arch` enums + alias mapping -Deterministic resolution: **strict filters → priority sorting → explicit policy fallback** (zero scoring)
-Format control: `.deb`/`.rpm`/`.msi`/`.dmg` disabled by default -`exclude_keywords` config to block installer/setup/bundle artifacts
-Pacman/Artix-style numbered selection with `[default]`, TTY/CI fallback, `-y`/`--noconfirm` -Warning system for unmanaged or extra-step packages (applies to `ignore_formats` overrides & keyword matches)
-`-Syu` resilience: detects filename renames, handles repo 404s via orphan tracking, explicit migration path -Configurable `download_dir` for unmanaged packages (defaults to OS `Downloads/`)
-Cross-platform PATH integration (`grel path add`), XDG-compliant, no `sudo` -`default_selection_policy` (`first` | `largest`) as explicit fallback, overridden by detailed flags
-Forge-agnostic provider routing (public + self-hosted URLs) -`-Syu` resilience: filename rename detection, orphan tracking, explicit migration
- ✅ Cross-platform PATH integration, XDG-compliant, no `sudo`
---
## 2. Workspace Architecture ---
```
grel-rs/ ## 2. Workspace Architecture
├── Cargo.toml # Workspace root (resolver = "2") ```
├── crates/ grel-rs/
│ ├── grel-cli/ # CLI parsing, pure-text prompts, progress routing ├── Cargo.toml # Workspace root (resolver = "2")
│ ├── grel-core/ # Resolution, tokenization, filter/sort pipeline, upgrade state ├── crates/
│ ├── grel-providers/ # Forge trait, registry, API implementations (modules) │ ├── grel-cli/ # CLI parsing, pure-text prompts, progress routing
│ ├── grel-network/ # HTTP client, proxy routing, IP cache resolver, parallel downloader │ ├── grel-core/ # Resolution, tokenization, filter/sort pipeline, upgrade state
│ ├── grel-cache/ # SQLite state, IP cache, artifact storage, TTL eviction │ ├── grel-providers/ # Forge trait, registry, API implementations (modules)
── grel-config/ # Layered config, migrations, asset priority matrices ── grel-network/ # HTTP client, proxy routing, IP cache resolver, parallel downloader
├── tests/ # Integration, mock servers, e2e fixtures │ ├── grel-cache/ # SQLite state, IP cache, artifact storage, TTL eviction
└── scripts/ # CI, release, benchmark helpers │ └── grel-config/ # Layered config, migrations, asset priority matrices
``` ├── tests/ # Integration, mock servers, e2e fixtures
└── scripts/ # CI, release, benchmark helpers
| Crate | Responsibility | ```
|-------|----------------|
| `grel-cli` | Subcommand routing, pure CLI prompt loops, `indicatif` progress, `tabled` output | | Crate | Responsibility |
| `grel-core` | `PackageRef` parsing, `AssetTokens` extraction, deterministic filter/sort, upgrade planning | |-------|----------------|
| `grel-providers` | `ReleaseProvider` trait, GitHub/GitLab/Gitea/Codeberg modules, self-hosted auto-detection | | `grel-cli` | Subcommand routing, pure CLI prompt loops, `indicatif` progress, `tabled` output |
| `grel-network` | Proxy chaining, DNS/IP cache resolver, `JoinSet` parallel downloads, streaming extraction | | `grel-core` | `PackageRef` parsing, `AssetTokens` extraction, deterministic filter/sort, upgrade planning |
| `grel-cache` | `state.sqlite` management, ETag/IP cache, LRU artifact eviction, atomic install temp dirs | | `grel-providers` | `ReleaseProvider` trait, GitHub/GitLab/Gitea/Codeberg modules, self-hosted auto-detection |
| `grel-config` | TOML loading, env/CLI overrides, schema validation, config migrations | | `grel-network` | Proxy chaining, DNS/IP cache resolver, `JoinSet` parallel downloads, streaming extraction |
| `grel-cache` | `state.sqlite` management, ETag/IP cache, LRU artifact eviction, atomic install temp dirs |
--- | `grel-config` | TOML loading, env/CLI overrides, schema validation, config migrations |
## 3. CLI Specification & Pure UX ---
No TUI frameworks. All interaction uses standard terminal I/O with predictable, pipe-safe behavior.
## 3. CLI Specification & Warning Flow
### Commands & Aliases All interaction uses standard terminal I/O. Unmanaged packages trigger explicit warnings & confirmation.
| Command | Aliases | Description |
|---------|---------|-------------| ### Warning & Confirmation Flow
| `grel sync foo/bar` | `S`, `-S` | Download & install latest | When an asset matches `exclude_keywords` or falls under an overridden `ignore_formats`:
| `grel sync foo/bar@1.2.3` | `-S` | Pin version | ```
| `grel search keyword` | `Ss`, `-Ss` | Search across registered forges | ⚠️ Asset "foo-setup-1.0.0.exe" matches excluded keyword "setup".
| `grel upgrade` | `Syu`, `-Syu` | Refresh + upgrade installed | grel cannot manage installers directly. Package will download to ~/Downloads/.
| `grel info foo/bar` | `Si`, `-Si` | Show release metadata | :: Proceed with download? [y/N]: _
| `grel list` | `Q`, `-Q` | List installed packages (shows status) | ```
| `grel remove foo/bar` | `R`, `-R` | Uninstall & clean DB | - `y`/`Enter` → Downloads to `download_dir`, marks `is_managed = false` in DB
| `grel clean` | `Sc`, `-Sc` | Purge artifact/metadata cache | - `N`/`n`/`Esc` → Aborts sync for this package, continues with others
| `grel path add` | - | Generate shell/registry snippets for `$PATH` | - `--noconfirm` / `-y` → Auto-accepts, prints ` Non-interactive: accepted unmanaged asset` to `stderr`
| `grel migrate old/path new/path` | - | Remap repo path in state DB | - **Never blocks CI/pipes:** `!std::io::stdin().is_terminal()` → auto-accepts, logs to `stderr`
### Pure CLI Prompt (Artix/Pacman Compatible) ---
```
:: 3 compatible asset(s) found for linux/x86_64 ## 4. Asset Resolution Pipeline (Deterministic)
1) foo-1.0.0-linux-x86_64.tar.gz | 12.4 MB | tar.gz (default) **No scoring. No fuzzy logic.** Strict filtering → transparent priority → policy fallback.
2) foo-1.0.0-linux-x86_64-musl.tar.gz | 10.2 MB | tar.gz
3) foo-1.0.0-linux-amd64.tar.gz | 11.8 MB | tar.gz ### Precedence Rules (Strict → Override → Fallback)
:: Select asset to download [1]: _ | Priority | Mechanism | Override Capability |
``` |----------|-----------|---------------------|
- Accepts `1``3` or `Enter` (selects `[1]`) | 1⃣ | OS/Arch exact match or `Unknown` | None |
- Invalid input → `:: Invalid selection. Enter a number [1-3]: ` | 2⃣ | `exclude_keywords` filter | None (hard block unless CLI `--allow-keyword`) |
- `--noconfirm` / `-y` → skips prompt, selects `[1]` | 3⃣ | `ignore_formats` filter | Overridable via CLI/config |
- **Non-Interactive Fallback:** `!std::io::stdin().is_terminal()` → auto-selects `[1]`, prints ` Non-interactive mode. Using: <filename>` to `stderr`, never blocks CI/pipes. | 4⃣ | `arch_priority` index | Overrides `default_selection_policy` |
| 5⃣ | `prefer_formats` index | Overrides `default_selection_policy` |
--- | 6⃣ | `default_selection_policy` | **Only applies to remaining ties** |
## 4. Asset Resolution Pipeline (Deterministic) ### Step 1: Strict Filtering
**No scoring. No fuzzy logic.** Strict filtering → transparent sorting → explicit selection. ```rust
assets.iter()
### Step 1: Tokenization .map(|a| AssetTokens::from_filename(&a.filename))
Filenames are split on non-alphanumeric characters. Tokens are matched against case-insensitive alias maps for `Os` and `Arch`. .filter(|t| t.os == target.os || t.os == Os::Unknown)
```rust .filter(|t| !keyword_excluded(t, &config.exclude_keywords))
// foo-v1.2.3-win64-setup.exe → Os::Windows, Arch::X86_64 .filter(|t| arch_matches_priority(t, &config.arch_priority, config.fallback_to_32bit))
// bar_1.0.0_linux_amd64.tar.gz → Os::Linux, Arch::X86_64 .filter(|t| !format_ignored(t, &config.ignore_formats))
// app.Darwin.arm64.zip → Os::MacOS, Arch::Aarch64 .collect()
``` ```
Unknown tokens fall back to `Os::Unknown("...")` or `Arch::Unknown("...")` → never crash, just filter out later.
### Step 2: Deterministic Sorting
### Step 2: Strict Filtering Sorted by explicit cascade:
Assets are filtered in fixed order. Failure at any step → discard. 1. `arch_priority` index
```rust 2. `prefer_formats` index
1. OS matches target OR is Unknown 3. Lexicographic filename
2. Arch matches priority list OR fallback allowed (32-bit on 64-bit) 4. Size descending
3. Format NOT in `ignore_formats` (deb/rpm/msi/dmg disabled by default)
``` ### Step 3: Policy Fallback (`default_selection_policy`)
Only applied if `>1` asset survives sorting and remains tied.
### Step 3: Deterministic Sorting - `first` → picks top of sorted list
Remaining assets sorted by explicit cascade: - `largest` → picks by `size_bytes` descending
1. `arch_priority` index (lower = better) - **Never overrides** arch/format priority or keyword filters.
2. `prefer_formats` index
3. Lexicographic filename ### Step 4: Selection & Warning
4. Size descending (tie-breaker) - `0``❌ No compatible assets`
- `1` → Check if unmanaged → warn/confirm → auto-select or prompt
### Step 4: Selection - `>1` → Pure CLI numbered prompt → warn/confirm if unmanaged
- `0` assets → `❌ No compatible assets. Run grel sync --list-assets`
- `1` asset → Auto-select, log ` Selected: <filename>` ---
- `>1` assets → Pure CLI numbered prompt (see §3)
## 5. Platform Enums & Alias Mapping
--- Strongly-typed, exhaustive, infallible parsing.
```rust
## 5. Platform Enums & Alias Mapping #[derive(Debug, Clone, PartialEq, Eq, Hash)]
Strongly-typed, exhaustive, infallible parsing. #[non_exhaustive]
```rust pub enum Os { Linux, Windows, MacOS, FreeBSD, Android, iOS, Unknown(String) }
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[non_exhaustive] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Os { Linux, Windows, MacOS, FreeBSD, Android, iOS, Unknown(String) } #[non_exhaustive]
pub enum Arch { X86_64, Aarch64, I686, ArmV7, ArmV6, Riscv64, S390x, PowerPC64, Unknown(String) }
#[derive(Debug, Clone, PartialEq, Eq, Hash)] ```
#[non_exhaustive] - `FromStr` implemented with `to_lowercase()` + alias table
pub enum Arch { X86_64, Aarch64, I686, ArmV7, ArmV6, Riscv64, S390x, PowerPC64, Unknown(String) } - `serde` ready for TOML config
``` - `Display` outputs canonical names for DB/CLI consistency
- `FromStr` implemented with `to_lowercase()` + alias table
- `serde` ready for TOML config ---
- `Display` outputs canonical names for DB/CLI consistency
## 6. Configuration Structure (Updated)
--- Strict separation of human-editable TOML and machine-managed SQLite.
## 6. Configuration vs State Management ```toml
**Strict separation.** Humans edit TOML. Machines manage SQLite. # ~/.config/grel/config.toml
[general]
### `~/.config/grel/config.toml` version = 1
```toml max_concurrent = 4
[general] proxy = "http://127.0.0.1:7890"
version = 1
max_concurrent = 4 [assets]
proxy = "http://127.0.0.1:7890" default_selection_policy = "largest" # first | largest (tiebreaker only)
exclude_keywords = ["setup", "installer", "portable", "bundle", "nupkg"]
[assets] ignore_formats = ["*.deb", "*.rpm", "*.msi", "*.dmg", "*.pkg", "*.AppImage"]
ignore_formats = ["*.deb", "*.rpm", "*.msi", "*.dmg", "*.pkg", "*.AppImage"] prefer_formats = ["*.tar.gz", "*.tar.xz", "*.zip", "*.exe"]
prefer_formats = ["*.tar.gz", "*.tar.xz", "*.zip", "*.exe"] arch_priority = ["x86_64", "aarch64", "x86", "armv7"]
arch_priority = ["x86_64", "aarch64", "x86", "armv7"] fallback_to_32bit = true
fallback_to_32bit = true prefer_musl = false
prefer_musl = false
[paths]
[migrations] install_root = "~/.local/share/grel"
"legacy/old-tool" = "new-org/old-tool" bin_dir = "~/.local/share/grel/bin"
download_dir = "~/Downloads" # Fallback for unmanaged/extra-op packages
[upgrade]
check_interval_hours = 6 [upgrade]
max_parallel_checks = 10 check_interval_hours = 6
``` max_parallel_checks = 10
### `state.sqlite` Schema (`installed` table) [migrations]
```sql "legacy/old-tool" = "new-org/old-tool"
CREATE TABLE installed ( ```
id INTEGER PRIMARY KEY AUTOINCREMENT,
forge TEXT NOT NULL, -- 'github', 'gitlab', or self-hosted base URL ---
owner TEXT NOT NULL,
repo TEXT NOT NULL, ## 7. State Database Schema (`state.sqlite`)
version TEXT NOT NULL, -- Strict semver ```sql
asset_filename TEXT NOT NULL, -- Exact remote filename used CREATE TABLE installed (
checksum TEXT, id INTEGER PRIMARY KEY AUTOINCREMENT,
install_path TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'orphaned', 'migrated')), forge TEXT NOT NULL,
orphaned_at INTEGER, -- Unix timestamp when marked unreachable owner TEXT NOT NULL,
last_checked INTEGER, -- For smart polling intervals repo TEXT NOT NULL,
installed_at INTEGER DEFAULT (strftime('%s', 'now')) version TEXT NOT NULL,
); asset_filename TEXT NOT NULL,
CREATE UNIQUE INDEX idx_pkg_unique ON installed(forge, owner, repo); checksum TEXT,
CREATE INDEX idx_status ON installed(status); install_path TEXT NOT NULL, -- Actual path (bin/ or download_dir)
``` is_managed BOOLEAN NOT NULL DEFAULT 1,
status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'orphaned', 'migrated')),
--- orphaned_at INTEGER,
last_checked INTEGER,
## 7. `-Syu` Upgrade Logic & Edge Cases installed_at INTEGER DEFAULT (strftime('%s', 'now'))
### 7.1 Filename Change Detection );
Authors often rename assets (`x86_64``amd64`, `.tar.gz``.tar.xz`). CREATE UNIQUE INDEX idx_pkg_unique ON installed(forge, owner, repo);
1. Load stored `asset_filename` from DB CREATE INDEX idx_status ON installed(status);
2. Resolve new release → filter → sort → get `new_best` ```
3. If `new_best.filename != stored.filename`: - `is_managed`: `true` = auto-extracted/linked to `bin/`; `false` = left in `download_dir`
``` - `install_path`: Stores actual destination for accurate cleanup/migration
⚠️ Remote asset renamed: old-name.tar.gz → new-name.tar.xz
Proceeding with update... ---
```
4. Download → verify → extract → **update DB record** with new filename. Proceeds safely. ## 8. Download Path & Installation Behavior
| Asset Type | Destination | Management |
### 7.2 Repository Rename / 404 Handling |------------|-------------|------------|
1. Provider fetch → `404` or unreachable | Standard binary/archive (`.tar.gz`, `.zip`, `.exe`) | `bin_dir/` (Linux) / `bin\` (Win) | ✅ Managed (extracted, checksummed, linked) |
2. Mark `status = 'orphaned'`, set `orphaned_at = now()` | Unmanaged/Extra-op (`.msi`, `.deb`, matched keywords) | `download_dir` (default: `~/Downloads`) | ⚠️ Download-only, no extraction/execution |
3. Skip in future `-Syu` runs | CLI Override (`--output-dir ~/tmp`) | User-specified path | ✅ Respected regardless of type |
4. Warn user:
``` **Cross-Platform Default Resolution:**
⚠️ Package unreachable: foo/bar (HTTP 404) ```rust
Skipped. Run `grel migrate foo/bar new/path` or `grel remove foo/bar` fn default_download_dir() -> PathBuf {
``` directories::UserDirs::new()
5. **No auto-migration.** Explicit user action required. .map(|d| d.download_dir().clone())
.unwrap_or_else(|| std::env::temp_dir().join("grel-downloads"))
### 7.3 Orphaned Visibility }
```bash ```
$ grel list ---
:: Installed packages (3 active, 1 orphaned)
🟢 bar/fuzz 1.2.3 linux/x86_64 tar.gz ## 9. `-Syu` Upgrade Logic & Edge Cases
🟢 foo/tool 0.9.1 windows/amd64 exe ### 9.1 Filename Change Detection
🟡 legacy/old-proj 2.0.0 linux/x86_64 tar.gz [orphaned since 2024-05-01] Authors often rename assets (`x86_64``amd64`, `.tar.gz``.tar.xz`).
``` 1. Load stored `asset_filename` from DB
2. Resolve new release → filter → sort → get `new_best`
--- 3. If `new_best.filename != stored.filename`:
```
## 8. Networking, Proxy & DNS/IP Caching ⚠️ Remote asset renamed: old-name.tar.gz → new-name.tar.xz
- **Proxy Priority:** `--proxy` > `GREL_PROXY` env > `http_proxy`/`all_proxy` > `config.toml` Proceeding with update...
- **DNS/IP Cache:** ```
1. Resolve `A/AAAA` via `hickory-resolver` 4. Download → verify → extract → **update DB record** with new filename. Proceeds safely.
2. Parallel `TcpStream::connect` probes on `443`
3. Store fastest IP + RTT in SQLite with `300s` TTL ### 9.2 Repository Rename / 404 Handling
4. `reqwest::ClientBuilder::resolve(host, ip)` forces routing 1. Provider fetch → `404` or unreachable
- **Background Refresh:** Idle task pre-warms hot domains, evicts stale entries 2. Mark `status = 'orphaned'`, set `orphaned_at = now()`
3. Skip in future `-Syu` runs
--- 4. Warn user:
```
## 9. Rate Limiting & ETag Optimization ⚠️ Package unreachable: foo/bar (HTTP 404)
GitHub: 60/hr unauth, 5000/hr auth. `grel` handles gracefully: Skipped. Run `grel migrate foo/bar new/path` or `grel remove foo/bar`
1. **ETag Caching:** `If-None-Match` → `304 Not Modified` **free**. Cached in DB. ```
2. **Smart Polling:** Only checks packages where `last_checked < now() - check_interval_hours` 5. **No auto-migration.** Explicit user action required.
3. **Rate Limit Parsing:** Reads `X-RateLimit-Remaining`/`Reset`. If `< 50` → sleep/queue.
4. **Auth Prompt:** First run suggests `GREL_GITHUB_TOKEN` for 5000/hr. ### 9.3 Orphaned Visibility
5. **`--no-api` Fallback:** Skips remote checks, uses local timestamps only. ```bash
$ grel list
--- :: Installed packages (3 active, 1 orphaned)
🟢 bar/fuzz 1.2.3 linux/x86_64 tar.gz
## 10. PATH Integration & Post-Install 🟢 foo/tool 0.9.1 windows/amd64 exe
- Installs to `~/.local/share/grel/bin/` (Linux) / `%LOCALAPPDATA%\grel\bin\` (Windows) 🟡 legacy/old-proj 2.0.0 linux/x86_64 tar.gz [orphaned since 2024-05-01]
- `grel path add` prints exact shell/registry snippets: ```
```bash
export PATH="$HOME/.local/share/grel/bin:$PATH" ---
```
```powershell ## 10. Networking, Proxy & DNS/IP Caching
[Environment]::SetEnvironmentVariable("Path", "$env:LOCALAPPDATA\grel\bin;$env:Path", "User") - **Proxy Priority:** `--proxy` > `GREL_PROXY` env > `http_proxy`/`all_proxy` > `config.toml`
``` - **DNS/IP Cache:**
- First run: `💡 Run: grel path add to enable global command access` 1. Resolve `A/AAAA` via `hickory-resolver`
- XDG-compliant, no `sudo`, no system conflicts. 2. Parallel `TcpStream::connect` probes on `443`
3. Store fastest IP + RTT in SQLite with `300s` TTL
--- 4. `reqwest::ClientBuilder::resolve(host, ip)` forces routing
- **Background Refresh:** Idle task pre-warms hot domains, evicts stale entries
## 11. Security & Reliability
- ✅ TLS: `rustls` only (no native OpenSSL) ---
- ✅ Checksums: `sha256` verification before extraction
- ✅ Archive safety: Reject `..`, absolute paths, external symlinks ## 11. Rate Limiting & ETag Optimization
- ✅ Atomic installs: Temp dir → verify → `rename` → DB update GitHub: 60/hr unauth, 5000/hr auth. `grel` handles gracefully:
- ✅ No auto-exec: Binaries installed but not run unless invoked 1. **ETag Caching:** `If-None-Match` → `304 Not Modified` **free**. Cached in DB.
- ✅ Graceful degradation: Network errors → retry/backoff, rate limits → sleep/queue, missing assets → clear error + suggestions 2. **Smart Polling:** Only checks packages where `last_checked < now() - check_interval_hours`
3. **Rate Limit Parsing:** Reads `X-RateLimit-Remaining`/`Reset`. If `< 50` → sleep/queue.
--- 4. **Auth Prompt:** First run suggests `GREL_GITHUB_TOKEN` for 5000/hr.
5. **`--no-api` Fallback:** Skips remote checks, uses local timestamps only.
## 12. Dependency Matrix
```toml ---
# Core & Async
tokio = { version = "1", features = ["full"] } ## 12. PATH Integration & Post-Install
futures = "0.3" - Installs to `~/.local/share/grel/bin/` (Linux) / `%LOCALAPPDATA%\grel\bin\` (Windows)
async-trait = "0.1" - `grel path add` prints exact shell/registry snippets:
```bash
# CLI & UX (Pure CLI, no TUI) export PATH="$HOME/.local/share/grel/bin:$PATH"
clap = { version = "4", features = ["derive", "wrap_help", "string"] } ```
indicatif = { version = "0.17", features = ["tokio"] } ```powershell
tracing-indicatif = "0.3" [Environment]::SetEnvironmentVariable("Path", "$env:LOCALAPPDATA\grel\bin;$env:Path", "User")
tabled = "0.16" ```
owo-colors = "4" - First run: `💡 Run: grel path add to enable global command access`
- XDG-compliant, no `sudo`, no system conflicts.
# Network & Proxy
reqwest = { version = "0.12", features = ["rustls-tls", "json", "socks", "stream"] } ---
hickory-resolver = "0.24"
dashmap = "6" ## 13. Security & Reliability
- ✅ TLS: `rustls` only
# Cache & Storage - ✅ Checksums: `sha256` before extraction (managed packages only)
sqlx = { version = "0.8", features = ["runtime-tokio-rustls", "sqlite"] } - ✅ Archive safety: Reject `..`, absolute paths, external symlinks
sha2 = "0.10" - ✅ Atomic installs: Temp dir → verify → `rename` → DB update
directories = "5" - ✅ Unmanaged warnings: Explicit user consent required (unless `--noconfirm`)
tar = "0.4" - ✅ Graceful degradation: Network errors → retry/backoff, rate limits → sleep/queue, missing assets → clear error + suggestions
zip = "2"
zstd = "0.13" ---
xz2 = "0.1"
bzip2 = "0.5" ## 14. Dependency Matrix
sanitize-filename = "0.5" ```toml
# Core & Async
# Config & Utils tokio = { version = "1", features = ["full"] }
figment = { version = "0.10", features = ["toml", "env"] } futures = "0.3"
semver = "1" async-trait = "0.1"
url = "2"
chrono = { version = "0.4", features = ["serde"] } # CLI & UX (Pure CLI, no TUI)
regex = "1" clap = { version = "4", features = ["derive", "wrap_help", "string"] }
serde = { version = "1", features = ["derive"] } indicatif = { version = "0.17", features = ["tokio"] }
anyhow = "1" tracing-indicatif = "0.3"
thiserror = "2" tabled = "0.16"
tracing = "0.1" owo-colors = "4"
tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] }
``` # Network & Proxy
reqwest = { version = "0.12", features = ["rustls-tls", "json", "socks", "stream"] }
--- hickory-resolver = "0.24"
dashmap = "6"
## 13. Testing & CI Strategy
- **Unit:** Token extraction from 20+ messy filenames, filter/sort determinism, config parsing, migration logic # Cache & Storage
- **Mocked Providers:** `wiremock` for 200/304/403/429/500, rate limit header injection sqlx = { version = "0.8", features = ["runtime-tokio-rustls", "sqlite"] }
- **Integration:** Download fixtures → extract → verify checksums & paths, `grel migrate` DB state changes sha2 = "0.10"
- **Non-Interactive:** `echo "" | grel sync foo/bar` must not hang, must log to stderr directories = "5"
- **Cross-Platform CI:** `ubuntu-latest`, `windows-latest` (GitHub Actions) tar = "0.4"
- **Performance:** `criterion` for DNS/IP cache + parallel download throughput zip = "2"
- **Fuzzing:** `cargo-fuzz` on `AssetTokens::from_filename()` + archive path validation zstd = "0.13"
xz2 = "0.1"
--- bzip2 = "0.5"
sanitize-filename = "0.5"
# Config & Utils
figment = { version = "0.10", features = ["toml", "env"] }
semver = "1"
url = "2"
chrono = { version = "0.4", features = ["serde"] }
regex = "1"
serde = { version = "1", features = ["derive"] }
anyhow = "1"
thiserror = "2"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] }
```
---
## 15. Testing & CI Strategy
- **Unit:** Keyword exclusion, policy fallback precedence, filter/sort determinism, config parsing
- **Mocked Providers:** `wiremock` for 200/304/403/429/500, rate limit headers
- **Integration:** Unmanaged package download → `download_dir` verification, `grel migrate` state changes
- **Non-Interactive:** `echo "" | grel sync foo/bar` must auto-accept with stderr warning
- **Cross-Platform CI:** `ubuntu-latest`, `windows-latest`
- **Performance:** `criterion` for DNS/IP cache + parallel download throughput
- **Fuzzing:** `cargo-fuzz` on `AssetTokens::from_filename()` + archive path validation
---