Skip to content

Commit 20db3d9

Browse files
committed
Add auto-update system for release builds
- New src/update.rs module: checks GitHub Releases API for updates, downloads platform-specific binaries, atomic install with symlink swap - Only release builds (JCODE_RELEASE_BUILD=1) auto-update; dev builds keep existing git-based update check - build.rs forwards JCODE_RELEASE_BUILD env var at compile time - release.yml sets JCODE_RELEASE_BUILD=1 for CI-built binaries - Version comparison (semver) determines if update is newer - Supports .tar.gz asset extraction - Rollback support via previous binary tracking - jcode update command works for both release and dev builds
1 parent 0e48448 commit 20db3d9

6 files changed

Lines changed: 533 additions & 18 deletions

File tree

.github/workflows/release.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ jobs:
4545

4646
- name: Build release binary
4747
run: cargo build --release --target ${{ matrix.target }}
48+
env:
49+
JCODE_RELEASE_BUILD: "1"
4850

4951
- name: Package binary
5052
run: |

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,10 @@ mail-parser = "0.9"
9595
# PDF parsing
9696
pdf-extract = "0.8"
9797

98+
# Archive extraction (for auto-update)
99+
flate2 = "1"
100+
tar = "0.4"
101+
98102
# Mermaid diagram rendering (stable v0.2.0 tag)
99103
mermaid-rs-renderer = { git = "ssh://git@github.com/1jehuang/mermaid-rs-renderer.git", tag = "v0.2.0" }
100104

build.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,15 @@ fn main() {
8787
println!("cargo:rustc-env=JCODE_GIT_TAG={}", git_tag);
8888
println!("cargo:rustc-env=JCODE_CHANGELOG={}", changelog);
8989

90+
// Forward JCODE_RELEASE_BUILD env var if set (CI sets this for release binaries)
91+
if std::env::var("JCODE_RELEASE_BUILD").is_ok() {
92+
println!("cargo:rustc-env=JCODE_RELEASE_BUILD=1");
93+
}
94+
9095
// Re-run if git HEAD changes
9196
println!("cargo:rerun-if-changed=.git/HEAD");
9297
println!("cargo:rerun-if-changed=.git/index");
98+
println!("cargo:rerun-if-env-changed=JCODE_RELEASE_BUILD");
9399
}
94100

95101
/// Get and increment the build number stored in ~/.jcode/build_number

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,5 +35,6 @@ pub mod telegram;
3535
pub mod todo;
3636
pub mod tool;
3737
pub mod tui;
38+
pub mod update;
3839
pub mod usage;
3940
pub mod util;

src/main.rs

Lines changed: 76 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ mod telegram;
3838
mod todo;
3939
mod tool;
4040
mod tui;
41+
mod update;
4142
mod usage;
4243
mod util;
4344

@@ -481,26 +482,63 @@ async fn main() -> Result<()> {
481482
let auto_update = args.auto_update;
482483

483484
if check_updates {
484-
// Spawn update check in background to avoid blocking startup
485-
std::thread::spawn(move || {
486-
if let Some(update_available) = check_for_updates() {
487-
if update_available {
488-
if auto_update {
489-
eprintln!("Update available - auto-updating...");
490-
if let Err(e) = run_auto_update() {
485+
if update::is_release_build() {
486+
// Release build: check GitHub Releases for newer version
487+
std::thread::spawn(move || {
488+
match update::check_and_maybe_update(auto_update) {
489+
update::UpdateCheckResult::UpdateAvailable {
490+
current,
491+
latest,
492+
..
493+
} => {
494+
eprintln!(
495+
"\n📦 Update available: {} → {}. Run `jcode update` to install.\n",
496+
current, latest
497+
);
498+
}
499+
update::UpdateCheckResult::UpdateInstalled { version, path } => {
500+
eprintln!(
501+
"✅ Updated to {}. Restarting from {}...",
502+
version,
503+
path.display()
504+
);
505+
// Exec into the new binary
506+
use std::os::unix::process::CommandExt;
507+
let args: Vec<String> = std::env::args().skip(1).collect();
508+
let err = ProcessCommand::new(&path)
509+
.args(&args)
510+
.arg("--no-update")
511+
.exec();
512+
eprintln!("Failed to exec new binary: {}", err);
513+
}
514+
update::UpdateCheckResult::Error(e) => {
515+
logging::info(&format!("Update check failed: {}", e));
516+
}
517+
update::UpdateCheckResult::NoUpdate => {}
518+
}
519+
});
520+
} else {
521+
// Dev build: check git remote for updates
522+
std::thread::spawn(move || {
523+
if let Some(update_available) = check_for_updates() {
524+
if update_available {
525+
if auto_update {
526+
eprintln!("Update available - auto-updating...");
527+
if let Err(e) = run_auto_update() {
528+
eprintln!(
529+
"Auto-update failed: {}. Continuing with current version.",
530+
e
531+
);
532+
}
533+
} else {
491534
eprintln!(
492-
"Auto-update failed: {}. Continuing with current version.",
493-
e
535+
"\n📦 Update available! Run `jcode update` or `/reload` to update.\n"
494536
);
495537
}
496-
} else {
497-
eprintln!(
498-
"\n📦 Update available! Run `jcode update` or `/reload` to update.\n"
499-
);
500538
}
501539
}
502-
}
503-
});
540+
});
541+
}
504542
}
505543

506544
if let Err(e) = run_main(args).await {
@@ -2375,12 +2413,34 @@ fn run_auto_update() -> Result<()> {
23752413

23762414
/// Run the update process (manual)
23772415
fn run_update() -> Result<()> {
2416+
if update::is_release_build() {
2417+
eprintln!("Checking GitHub for latest release...");
2418+
match update::check_for_update_blocking() {
2419+
Ok(Some(release)) => {
2420+
eprintln!(
2421+
"Downloading {} → {}...",
2422+
env!("JCODE_VERSION"),
2423+
release.tag_name
2424+
);
2425+
let path = update::download_and_install_blocking(&release)?;
2426+
eprintln!("✅ Updated to {} at {}", release.tag_name, path.display());
2427+
eprintln!("Restart jcode to use the new version.");
2428+
}
2429+
Ok(None) => {
2430+
eprintln!("Already up to date ({})", env!("JCODE_VERSION"));
2431+
}
2432+
Err(e) => {
2433+
anyhow::bail!("Update check failed: {}", e);
2434+
}
2435+
}
2436+
return Ok(());
2437+
}
2438+
23782439
let repo_dir =
23792440
get_repo_dir().ok_or_else(|| anyhow::anyhow!("Could not find jcode repository"))?;
23802441

23812442
eprintln!("Updating jcode from {}...", repo_dir.display());
23822443

2383-
// Git pull
23842444
eprintln!("Pulling latest changes...");
23852445
let pull = ProcessCommand::new("git")
23862446
.args(["pull"])
@@ -2391,7 +2451,6 @@ fn run_update() -> Result<()> {
23912451
anyhow::bail!("git pull failed");
23922452
}
23932453

2394-
// Cargo build --release
23952454
eprintln!("Building...");
23962455
let build = ProcessCommand::new("cargo")
23972456
.args(["build", "--release"])
@@ -2406,7 +2465,6 @@ fn run_update() -> Result<()> {
24062465
eprintln!("Warning: install failed: {}", e);
24072466
}
24082467

2409-
// Get new version hash
24102468
let hash = ProcessCommand::new("git")
24112469
.args(["rev-parse", "--short", "HEAD"])
24122470
.current_dir(&repo_dir)

0 commit comments

Comments
 (0)