Repository: typst/typst
File: crates/typst-cli/src/update.rs (Rust)

Code before:

    Ok(buffer)
}

/// Extract the Typst binary from a `.tar.xz` archive.
fn extract_binary_from_tar_xz(data: &[u8]) -> StrResult<Vec<u8>> {
    let mut archive = tar::Archive::new(XzDecoder::new(Cursor::new(data)));

    let mut file = archive
        .entries()
        .map_err(|err| eco_format!("failed to extract tar.xz archive ({err})"))?
        .filter_map(Result::ok)
        .find(|e| e.path().unwrap_or_default().ends_with("typst"))
        .ok_or("tar.xz archive did not contain Typst binary")?;

    let mut buffer = vec![];
    file.read_to_end(&mut buffer).map_err(|err| {
        eco_format!("failed to read binary data from tar.xz archive ({err})")
    })?;

    Ok(buffer)
}

/// Compare the release version to the CLI version to see if an update is needed.
fn update_needed(release: &Release) -> StrResult<bool> {
    // NOTE: Although [`typst_utils::TypstVersion`] uses the [`semver`] crate under the hood right
    // now, we consider this an implementation detail that is not currently exposed.
    let current_tag: Version = typst::version().raw().parse().unwrap();
    let new_tag: Version = release
        .tag_name
        .strip_prefix('v')
        .unwrap_or(&release.tag_name)
        .parse()
        .map_err(|_| "release tag not in semver format")?;

    Ok(new_tag > current_tag)
}

/// Path to a potential backup file in the system.
///
/// The backup will be placed as `typst_backup.part` in one of the following
/// directories, depending on the platform:
///  - `$XDG_STATE_HOME` or `~/.local/state` on Linux
///    - `$XDG_DATA_HOME` or `~/.local/share` if the above path isn't available
///  - `~/Library/Application Support` on macOS
///  - `%APPDATA%` on Windows
///
/// If a custom backup path is provided via the environment variable
/// `TYPST_UPDATE_BACKUP_PATH`, it will be used instead of the default
/// directories determined by the platform. In that case, this function
/// shouldn't be called.

Review comment (suggestion):
This comment in multiple places feels redundant and a bit too much. If anything, we can document this on the private field in `TypstVersion`, but I'd also be fine with removing it entirely.

Code after:
}

/// Extract the Typst binary from a `.tar.xz` archive.
fn extract_binary_from_tar_xz(data: &[u8]) -> StrResult<Vec<u8>> {
    let mut archive = tar::Archive::new(XzDecoder::new(Cursor::new(data)));

    let mut file = archive
        .entries()
        .map_err(|err| eco_format!("failed to extract tar.xz archive ({err})"))?
        .filter_map(Result::ok)
        .find(|e| e.path().unwrap_or_default().ends_with("typst"))
        .ok_or("tar.xz archive did not contain Typst binary")?;

    let mut buffer = vec![];
    file.read_to_end(&mut buffer).map_err(|err| {
        eco_format!("failed to read binary data from tar.xz archive ({err})")
    })?;

    Ok(buffer)
}

/// Compare the release version to the CLI version to see if an update is needed.
fn update_needed(release: &Release) -> StrResult<bool> {
    let current_tag: Version = typst::utils::version().raw().parse().unwrap();
    let new_tag: Version = release
        .tag_name
        .strip_prefix('v')
        .unwrap_or(&release.tag_name)
        .parse()
        .map_err(|_| "release tag not in semver format")?;

    Ok(new_tag > current_tag)
}

/// Path to a potential backup file in the system.
///
/// The backup will be placed as `typst_backup.part` in one of the following
/// directories, depending on the platform:
///  - `$XDG_STATE_HOME` or `~/.local/state` on Linux
///    - `$XDG_DATA_HOME` or `~/.local/share` if the above path isn't available
///  - `~/Library/Application Support` on macOS
///  - `%APPDATA%` on Windows
///
/// If a custom backup path is provided via the environment variable
/// `TYPST_UPDATE_BACKUP_PATH`, it will be used instead of the default
/// directories determined by the platform. In that case, this function
/// shouldn't be called.
fn backup_path() -> StrResult<PathBuf> {
    #[cfg(target_os = "linux")]
    let root_backup_dir = dirs::state_dir()
        .or_else(dirs::data_dir)