|
| 1 | +#' Check for newer version of Quarto |
| 2 | +#' |
| 3 | +#' Checks if a newer version of Quarto is available and informs the user about |
| 4 | +#' their current version status. The function compares the current Quarto version |
| 5 | +#' against the latest stable and prerelease versions available online. |
| 6 | +#' |
| 7 | +#' @param version Character string specifying the Quarto version to check. |
| 8 | +#' Defaults to the currently installed version detected by [quarto_version()]. |
| 9 | +#' Use "99.9.9" to indicate a development version. |
| 10 | +#' |
| 11 | +#' @return Invisibly returns `NULL`. The function is called for its side effects |
| 12 | +#' of printing informational messages about version status. |
| 13 | +#' |
| 14 | +#' @details |
| 15 | +#' The function handles three scenarios: |
| 16 | +#' - **Development version** (99.9.9): Skips version check with informational message |
| 17 | +#' - **Prerelease version**: Compares against latest prerelease and informs about updates |
| 18 | +#' - **Stable version**: Compares against latest stable version and suggests updates if needed |
| 19 | +#' |
| 20 | +#' Version information is fetched from Quarto's download JSON endpoints and cached in current session |
| 21 | +#' for up to 24 hours to avoid repeated network requests. |
| 22 | +#' |
| 23 | +#' @section Network Requirements: |
| 24 | +#' This function requires an internet connection to fetch the latest version |
| 25 | +#' information from quarto.org. If the network request fails, an error will be thrown. |
| 26 | +#' |
| 27 | +#' @examplesIf quarto::quarto_available() && has_internet("https://www.quarto.org") |
| 28 | +#' # Check current Quarto version |
| 29 | +#' check_newer_version() |
| 30 | +#' |
| 31 | +#' # Check a specific version |
| 32 | +#' check_newer_version("1.7.30") |
| 33 | +#' |
| 34 | +#' # Check development version (will skip check) |
| 35 | +#' check_newer_version("99.9.9") |
| 36 | +#' |
| 37 | +#' @seealso |
| 38 | +#' [quarto_version()] for getting the current Quarto version, |
| 39 | +#' |
| 40 | +#' @export |
| 41 | +check_newer_version <- function(version = quarto_version()) { |
| 42 | + if (version == "99.9.9") { |
| 43 | + rlang::inform(c( |
| 44 | + "i" = "Skipping version check for development version.", |
| 45 | + ">" = "Please update using development mode." |
| 46 | + )) |
| 47 | + return(invisible()) |
| 48 | + } |
| 49 | + stable <- latest_available_version("stable") |
| 50 | + if (version > stable) { |
| 51 | + prerelease <- latest_available_version("prerelease") |
| 52 | + if (version < prerelease) { |
| 53 | + update <- TRUE |
| 54 | + } else { |
| 55 | + update <- FALSE |
| 56 | + } |
| 57 | + cli::cli_inform(c( |
| 58 | + "i" = "You are using prerelease version of Quarto: {version}.", |
| 59 | + if (update) { |
| 60 | + ">" = "A newer version is available: {prerelease}. You can download it from {.url https://quarto.org/docs/download/prerelease.html} or your preferred package manager if available." |
| 61 | + } else { |
| 62 | + "v" = "You are using the latest prerelease version." |
| 63 | + } |
| 64 | + )) |
| 65 | + } else if (version < stable) { |
| 66 | + cli::cli_inform(c( |
| 67 | + "i" = "You are using an older version of Quarto: {version}.", |
| 68 | + " " = "The latest stable version is: {stable}.", |
| 69 | + ">" = "You can download new version from https://quarto.org/docs/download/ or your preferred package manager if available." |
| 70 | + )) |
| 71 | + } else { |
| 72 | + cli::cli_inform(c( |
| 73 | + "i" = "You are using the latest stable version of Quarto: {version}." |
| 74 | + )) |
| 75 | + } |
| 76 | +} |
| 77 | + |
| 78 | +versions_urls <- list( |
| 79 | + stable = "https://quarto.org/docs/download/_download.json", |
| 80 | + prerelease = "https://quarto.org/docs/download/_prerelease.json" |
| 81 | +) |
| 82 | + |
| 83 | +get_json <- function(url) { |
| 84 | + jsonlite::fromJSON(url) |
| 85 | +} |
| 86 | + |
| 87 | +get_latest_info <- function( |
| 88 | + type = c("stable", "prerelease"), |
| 89 | + .call = rlang::caller_env() |
| 90 | +) { |
| 91 | + type <- match.arg(type) |
| 92 | + res <- tryCatch( |
| 93 | + get_json(versions_urls[[type]]), |
| 94 | + error = function(e) { |
| 95 | + rlang::abort( |
| 96 | + "Failed to fetch latest versions: ", |
| 97 | + parent = e, |
| 98 | + call = .call |
| 99 | + ) |
| 100 | + return(NULL) |
| 101 | + } |
| 102 | + ) |
| 103 | + |
| 104 | + if (!is.null(res)) { |
| 105 | + return(res) |
| 106 | + } |
| 107 | +} |
| 108 | + |
| 109 | +default_infos <- function(type) { |
| 110 | + list(date = Sys.Date(), infos = get_latest_info(type)) |
| 111 | +} |
| 112 | + |
| 113 | +latest_available_infos <- function(type = c("stable", "prerelease")) { |
| 114 | + type <- match.arg(type) |
| 115 | + type_name <- paste0("latest_", type) |
| 116 | + latest <- rlang::env_cache( |
| 117 | + the, |
| 118 | + type_name, |
| 119 | + default_infos(type) |
| 120 | + ) |
| 121 | + # add a time check to invalidate the cache if the date is older than 1 day |
| 122 | + if ( |
| 123 | + !is.null(latest$infos) && |
| 124 | + !is.null(latest$date) && |
| 125 | + latest$date > Sys.Date() - 1 |
| 126 | + ) { |
| 127 | + return(latest$infos) |
| 128 | + } else { |
| 129 | + rlang::env_poke( |
| 130 | + the, |
| 131 | + type_name, |
| 132 | + default_infos(type) |
| 133 | + ) |
| 134 | + } |
| 135 | +} |
| 136 | + |
| 137 | +latest_available_version <- function(type = c("stable", "prerelease")) { |
| 138 | + type <- match.arg(type) |
| 139 | + infos <- latest_available_infos(type) |
| 140 | + if (is.null(infos)) { |
| 141 | + return(NULL) |
| 142 | + } |
| 143 | + return(infos$version) |
| 144 | +} |
| 145 | + |
| 146 | + |
| 147 | +latest_available_published <- function(type = c("stable", "prerelease")) { |
| 148 | + type <- match.arg(type) |
| 149 | + infos <- latest_available_infos(type) |
| 150 | + if (is.null(infos)) { |
| 151 | + return(NULL) |
| 152 | + } |
| 153 | + return(infos$published) |
| 154 | +} |
0 commit comments