Skip to content

Commit b14bd49

Browse files
author
EanHD
committed
feat: Add comprehensive update functionality to CLI
Implemented full update system with multiple access methods: 🔄 Update Features: - Added check_for_updates() function to detect new versions - Added update_learn() function to pull latest changes - Shows current version, latest version, commits behind - User confirmation before updating - Automatic CLI reinstallation after update - Clear success/error messages 📋 Command Line Interface: - learn --check-updates : Check if updates available - learn --update : Update to latest version 🎛️ Interactive Menu: - Option 6: Check for updates - Option 7: Update to latest version 📚 Documentation Updates: - Updated README.md with new update commands - Simplified update instructions - Fixed hardcoded paths to use cd commands - Added check-updates command reference 🛠️ Technical Details: - Uses git fetch and pull from origin/main - Shows commit hash versions (7-char short) - Counts commits behind remote - Handles git errors gracefully - Cross-platform compatible This replaces the need to manually run install scripts with --update flag. Users can now update directly from within the CLI!
1 parent df2a97f commit b14bd49

File tree

2 files changed

+271
-9
lines changed

2 files changed

+271
-9
lines changed

CLI/learn_cli.py

Lines changed: 256 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2195,8 +2195,10 @@ def _system_diagnostics(self):
21952195
" [cyan]3[/cyan] Reset progress",
21962196
" [cyan]4[/cyan] Show configuration",
21972197
" [cyan]5[/cyan] Install dependencies",
2198-
" [cyan]6[/cyan] Reset all data (progress + workspaces)",
2199-
" [cyan]7[/cyan] Uninstall LEARN CLI",
2198+
" [cyan]6[/cyan] Check for updates",
2199+
" [cyan]7[/cyan] Update to latest version",
2200+
" [cyan]8[/cyan] Reset all data (progress + workspaces)",
2201+
" [cyan]9[/cyan] Uninstall LEARN CLI",
22002202
" [cyan]b[/cyan] Back"
22012203
]
22022204
for item in diag_items:
@@ -2209,8 +2211,10 @@ def _system_diagnostics(self):
22092211
print(" 3 Reset progress")
22102212
print(" 4 Show configuration")
22112213
print(" 5 Install dependencies")
2212-
print(" 6 Reset all data (progress + workspaces)")
2213-
print(" 7 Uninstall LEARN CLI")
2214+
print(" 6 Check for updates")
2215+
print(" 7 Update to latest version")
2216+
print(" 8 Reset all data (progress + workspaces)")
2217+
print(" 9 Uninstall LEARN CLI")
22142218
print(" b Back")
22152219

22162220
choice = input("\n→ Select option: ").strip().lower()
@@ -2257,12 +2261,49 @@ def _system_diagnostics(self):
22572261
self._install_dependencies()
22582262

22592263
elif choice == "6":
2264+
# Check for updates
2265+
self._clear_screen()
2266+
update_info = check_for_updates(self.learn_dir)
2267+
2268+
if "error" in update_info:
2269+
if console:
2270+
console.print(f"[bold red]Error:[/bold red] {update_info['error']}")
2271+
else:
2272+
print(f"Error: {update_info['error']}")
2273+
elif update_info["has_update"]:
2274+
if console:
2275+
console.print(f"[bold yellow]Update available![/bold yellow]")
2276+
console.print(f" Current: [cyan]{update_info['current']}[/cyan]")
2277+
console.print(f" Latest: [green]{update_info['latest']}[/green]")
2278+
console.print(f" Commits behind: [yellow]{update_info['commits_behind']}[/yellow]")
2279+
else:
2280+
print(f"Update available!")
2281+
print(f" Current: {update_info['current']}")
2282+
print(f" Latest: {update_info['latest']}")
2283+
print(f" Commits behind: {update_info['commits_behind']}")
2284+
else:
2285+
if console:
2286+
console.print(f"[bold green]✓ You're on the latest version[/bold green]")
2287+
console.print(f"[dim]Version: {update_info['current']}[/dim]")
2288+
else:
2289+
print(f"✓ You're on the latest version")
2290+
print(f"Version: {update_info['current']}")
2291+
2292+
input("\nPress Enter to continue...")
2293+
2294+
elif choice == "7":
2295+
# Update to latest version
2296+
self._clear_screen()
2297+
update_learn(self.learn_dir)
2298+
input("\nPress Enter to continue...")
2299+
2300+
elif choice == "8":
22602301
# Reset all data
22612302
self._clear_screen()
22622303
reset_user_data(self.learn_dir)
22632304
input("\nPress Enter to continue...")
22642305

2265-
elif choice == "7":
2306+
elif choice == "9":
22662307
# Uninstall
22672308
self._clear_screen()
22682309
uninstall_learn(self.learn_dir)
@@ -2377,6 +2418,179 @@ def _install_dependencies(self):
23772418
input("Press Enter to continue...")
23782419

23792420

2421+
def check_for_updates(learn_dir: Path) -> dict:
2422+
"""Check if updates are available from GitHub
2423+
2424+
Returns:
2425+
dict with keys: has_update (bool), current (str), latest (str), commits_behind (int)
2426+
"""
2427+
try:
2428+
import subprocess
2429+
2430+
# Get current commit hash
2431+
result = subprocess.run(
2432+
["git", "-C", str(learn_dir), "rev-parse", "HEAD"],
2433+
capture_output=True,
2434+
text=True,
2435+
timeout=5
2436+
)
2437+
current_hash = result.stdout.strip() if result.returncode == 0 else None
2438+
2439+
# Fetch latest from remote
2440+
subprocess.run(
2441+
["git", "-C", str(learn_dir), "fetch", "origin", "main"],
2442+
capture_output=True,
2443+
timeout=10
2444+
)
2445+
2446+
# Get remote commit hash
2447+
result = subprocess.run(
2448+
["git", "-C", str(learn_dir), "rev-parse", "origin/main"],
2449+
capture_output=True,
2450+
text=True,
2451+
timeout=5
2452+
)
2453+
remote_hash = result.stdout.strip() if result.returncode == 0 else None
2454+
2455+
if not current_hash or not remote_hash:
2456+
return {"has_update": False, "current": "unknown", "latest": "unknown", "commits_behind": 0}
2457+
2458+
# Count commits behind
2459+
result = subprocess.run(
2460+
["git", "-C", str(learn_dir), "rev-list", "--count", f"{current_hash}..{remote_hash}"],
2461+
capture_output=True,
2462+
text=True,
2463+
timeout=5
2464+
)
2465+
commits_behind = int(result.stdout.strip()) if result.returncode == 0 else 0
2466+
2467+
return {
2468+
"has_update": current_hash != remote_hash,
2469+
"current": current_hash[:7],
2470+
"latest": remote_hash[:7],
2471+
"commits_behind": commits_behind
2472+
}
2473+
2474+
except Exception as e:
2475+
return {"has_update": False, "current": "error", "latest": "error", "commits_behind": 0, "error": str(e)}
2476+
2477+
2478+
def update_learn(learn_dir: Path):
2479+
"""Update LEARN to the latest version"""
2480+
if console:
2481+
console.print("[bold cyan]╔══════════════════════════════════════════════════════════════╗[/bold cyan]")
2482+
console.print("[bold cyan]║ UPDATE LEARN CLI ║[/bold cyan]")
2483+
console.print("[bold cyan]╚══════════════════════════════════════════════════════════════╝[/bold cyan]")
2484+
console.print("\n[bold]Checking for updates...[/bold]")
2485+
else:
2486+
print("=" * 70)
2487+
print(" UPDATE LEARN CLI")
2488+
print("=" * 70)
2489+
print("\nChecking for updates...")
2490+
2491+
update_info = check_for_updates(learn_dir)
2492+
2493+
if "error" in update_info:
2494+
if console:
2495+
console.print(f"\n[bold red]Error checking for updates:[/bold red] {update_info['error']}")
2496+
else:
2497+
print(f"\nError checking for updates: {update_info['error']}")
2498+
return
2499+
2500+
if not update_info["has_update"]:
2501+
if console:
2502+
console.print("\n[bold green]✓ You're already on the latest version![/bold green]")
2503+
console.print(f"[dim]Current version: {update_info['current']}[/dim]")
2504+
else:
2505+
print("\n✓ You're already on the latest version!")
2506+
print(f"Current version: {update_info['current']}")
2507+
return
2508+
2509+
# Show update info
2510+
if console:
2511+
console.print(f"\n[bold yellow]Update available![/bold yellow]")
2512+
console.print(f" Current version: [cyan]{update_info['current']}[/cyan]")
2513+
console.print(f" Latest version: [green]{update_info['latest']}[/green]")
2514+
console.print(f" Commits behind: [yellow]{update_info['commits_behind']}[/yellow]")
2515+
else:
2516+
print(f"\nUpdate available!")
2517+
print(f" Current version: {update_info['current']}")
2518+
print(f" Latest version: {update_info['latest']}")
2519+
print(f" Commits behind: {update_info['commits_behind']}")
2520+
2521+
confirm = input("\nDo you want to update now? (Y/n): ").strip().lower()
2522+
2523+
if confirm == 'n':
2524+
if console:
2525+
console.print("\n[bold]Update cancelled.[/bold]")
2526+
else:
2527+
print("\nUpdate cancelled.")
2528+
return
2529+
2530+
# Perform update
2531+
if console:
2532+
console.print("\n[bold cyan]Updating...[/bold cyan]")
2533+
else:
2534+
print("\nUpdating...")
2535+
2536+
try:
2537+
# Pull latest changes
2538+
result = subprocess.run(
2539+
["git", "-C", str(learn_dir), "pull", "origin", "main"],
2540+
capture_output=True,
2541+
text=True,
2542+
timeout=30
2543+
)
2544+
2545+
if result.returncode != 0:
2546+
if console:
2547+
console.print(f"\n[bold red]Update failed:[/bold red]")
2548+
console.print(f"[dim]{result.stderr}[/dim]")
2549+
else:
2550+
print(f"\nUpdate failed:")
2551+
print(result.stderr)
2552+
return
2553+
2554+
# Show what was updated
2555+
if console:
2556+
console.print("\n[bold green]✓ Update complete![/bold green]")
2557+
console.print("\n[bold]Changes:[/bold]")
2558+
if result.stdout:
2559+
for line in result.stdout.split('\n')[:10]: # Show first 10 lines
2560+
if line.strip():
2561+
console.print(f" [dim]{line}[/dim]")
2562+
else:
2563+
print("\n✓ Update complete!")
2564+
print("\nChanges:")
2565+
if result.stdout:
2566+
for line in result.stdout.split('\n')[:10]:
2567+
if line.strip():
2568+
print(f" {line}")
2569+
2570+
# Reinstall CLI if needed
2571+
if console:
2572+
console.print("\n[bold cyan]Updating CLI...[/bold cyan]")
2573+
else:
2574+
print("\nUpdating CLI...")
2575+
2576+
cli_install = learn_dir / "CLI" / "install.sh"
2577+
if cli_install.exists():
2578+
subprocess.run(["bash", str(cli_install)], capture_output=True)
2579+
2580+
if console:
2581+
console.print("\n[bold green]✓ All updates applied successfully![/bold green]")
2582+
console.print("\n[bold yellow]Note:[/bold yellow] You may need to restart the CLI for all changes to take effect.")
2583+
else:
2584+
print("\n✓ All updates applied successfully!")
2585+
print("\nNote: You may need to restart the CLI for all changes to take effect.")
2586+
2587+
except Exception as e:
2588+
if console:
2589+
console.print(f"\n[bold red]Update error:[/bold red] {str(e)}")
2590+
else:
2591+
print(f"\nUpdate error: {str(e)}")
2592+
2593+
23802594
def reset_user_data(learn_dir: Path):
23812595
"""Reset all user data including progress and workspaces"""
23822596
if console:
@@ -2661,6 +2875,8 @@ def main():
26612875
parser.add_argument("--run", nargs=3, metavar=("LANG", "STAGE", "LEVEL"), help="Compile and run workspace code")
26622876
parser.add_argument("--reset-data", action="store_true", help="Reset all user data (progress and workspaces)")
26632877
parser.add_argument("--uninstall", action="store_true", help="Uninstall LEARN CLI")
2878+
parser.add_argument("--update", action="store_true", help="Update LEARN to the latest version")
2879+
parser.add_argument("--check-updates", action="store_true", help="Check if updates are available")
26642880

26652881
args = parser.parse_args()
26662882

@@ -2681,6 +2897,41 @@ def main():
26812897
print("Run 'learn init' to install missing components.")
26822898
return
26832899

2900+
# Check for updates
2901+
if args.check_updates:
2902+
update_info = check_for_updates(learn_dir)
2903+
2904+
if "error" in update_info:
2905+
print(f"Error checking for updates: {update_info['error']}")
2906+
return
2907+
2908+
if console:
2909+
if update_info["has_update"]:
2910+
console.print(f"[bold yellow]Update available![/bold yellow]")
2911+
console.print(f" Current: [cyan]{update_info['current']}[/cyan]")
2912+
console.print(f" Latest: [green]{update_info['latest']}[/green]")
2913+
console.print(f" Commits behind: [yellow]{update_info['commits_behind']}[/yellow]")
2914+
console.print(f"\nRun [cyan]learn --update[/cyan] to update")
2915+
else:
2916+
console.print(f"[bold green]✓ You're on the latest version[/bold green]")
2917+
console.print(f"[dim]Version: {update_info['current']}[/dim]")
2918+
else:
2919+
if update_info["has_update"]:
2920+
print(f"Update available!")
2921+
print(f" Current: {update_info['current']}")
2922+
print(f" Latest: {update_info['latest']}")
2923+
print(f" Commits behind: {update_info['commits_behind']}")
2924+
print(f"\nRun 'learn --update' to update")
2925+
else:
2926+
print(f"✓ You're on the latest version")
2927+
print(f"Version: {update_info['current']}")
2928+
return
2929+
2930+
# Update
2931+
if args.update:
2932+
update_learn(learn_dir)
2933+
return
2934+
26842935
# Reset data
26852936
if args.reset_data:
26862937
reset_user_data(learn_dir)

README.md

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,14 +58,26 @@ This installs everything: CLI, Neovim config, and all dependencies.
5858

5959
### Update anytime
6060

61+
**Simple update (recommended):**
62+
```bash
63+
learn --update
64+
```
65+
66+
**Or use the install scripts:**
67+
6168
**Linux/Mac:**
6269
```bash
63-
bash ~/LEARN/install.sh --update
70+
cd ~/LEARN && bash install.sh --update
6471
```
6572

6673
**Windows:**
6774
```powershell
68-
powershell -File ~\LEARN\install.ps1 -Update
75+
cd ~\LEARN; powershell -File install.ps1 -Update
76+
```
77+
78+
**Check for updates without installing:**
79+
```bash
80+
learn --check-updates
6981
```
7082

7183
### Launch
@@ -448,8 +460,7 @@ Students can track their own progress with the built-in progress system!
448460
| Problem | Solution |
449461
|---------|----------|
450462
| Command not found | Linux/Mac: \`bash install.sh\` • Windows: Run \`install.ps1\` |
451-
| Need to update | Linux/Mac: \`bash ~/LEARN/install.sh --update\` |
452-
| | Windows: \`powershell -File ~\LEARN\install.ps1 -Update\` |
463+
| Need to update | Run: \`learn --update\` (or use install scripts) |
453464
| Neovim issues | See: [MODE_VIM/README.md](MODE_VIM/README.md) |
454465
| CLI help | Run: \`learn --help\` |
455466
| Vim basics | Read: [VIM_CHEATSHEET.md](VIM_CHEATSHEET.md) |

0 commit comments

Comments
 (0)