mirror of https://github.com/jdx/mise
feat: added --bump option for outdated/upgrade (#2667)
This commit is contained in:
parent
8f5b9b3da8
commit
0dd91666f5
|
@ -17,6 +17,7 @@ concurrency:
|
|||
|
||||
env:
|
||||
MISE_EXPERIMENTAL: 1
|
||||
NPM_CONFIG_FUND: false
|
||||
|
||||
jobs:
|
||||
release-plz:
|
||||
|
|
|
@ -20,11 +20,6 @@ jobs:
|
|||
runs-on: ${{matrix.runs-on}}
|
||||
timeout-minutes: 45
|
||||
env:
|
||||
#RUSTC_WRAPPER: sccache
|
||||
SCCACHE_ENDPOINT: minio.jdx.dev
|
||||
SCCACHE_BUCKET: sccache
|
||||
SCCACHE_REGION: auto
|
||||
SCCACHE_S3_NO_CREDENTIALS: ${{ secrets.MINIO_AWS_ACCESS_KEY_ID && '0' || '1' }}
|
||||
MINIO_AWS_ACCESS_KEY_ID: ${{ secrets.MINIO_AWS_ACCESS_KEY_ID }}
|
||||
MINIO_AWS_SECRET_ACCESS_KEY: ${{ secrets.MINIO_AWS_SECRET_ACCESS_KEY }}
|
||||
strategy:
|
||||
|
@ -74,11 +69,6 @@ jobs:
|
|||
p12-file-base64: ${{ secrets.APPLE_DEVELOPER_ID_APPLICATION_CERTS_P12 }}
|
||||
p12-password: ${{ secrets.APPLE_DEVELOPER_ID_APPLICATION_CERTS_P12_PASS }}
|
||||
- uses: actions/checkout@v4
|
||||
- if: ${{ env.SCCACHE_S3_NO_CREDENTIALS == '0' }}
|
||||
run: |
|
||||
echo "AWS_ACCESS_KEY_ID=$MINIO_AWS_ACCESS_KEY_ID" >> "$GITHUB_ENV"
|
||||
echo "AWS_SECRET_ACCESS_KEY=$MINIO_AWS_SECRET_ACCESS_KEY" >> "$GITHUB_ENV"
|
||||
- uses: mozilla-actions/sccache-action@v0.0.4
|
||||
- name: cache crates
|
||||
id: cache-crates
|
||||
uses: actions/cache@v4
|
||||
|
@ -109,19 +99,10 @@ jobs:
|
|||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- if: steps.cache-crates.outputs.cache-hit != 'true'
|
||||
run: cargo cache --autoclean
|
||||
- run: ${SCCACHE_PATH} --show-stats
|
||||
build-tarball-windows:
|
||||
name: build-tarball-windows-${{matrix.arch}}
|
||||
runs-on: windows-latest
|
||||
timeout-minutes: 45
|
||||
env:
|
||||
#RUSTC_WRAPPER: sccache
|
||||
SCCACHE_ENDPOINT: minio.jdx.dev
|
||||
SCCACHE_BUCKET: sccache
|
||||
SCCACHE_REGION: auto
|
||||
SCCACHE_S3_NO_CREDENTIALS: ${{ secrets.MINIO_AWS_ACCESS_KEY_ID && '0' || '1' }}
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.MINIO_AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.MINIO_AWS_SECRET_ACCESS_KEY }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
|
@ -133,7 +114,6 @@ jobs:
|
|||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- run: rustup target add ${{matrix.target}}
|
||||
- uses: mozilla-actions/sccache-action@v0.0.4
|
||||
- name: cache crates
|
||||
id: cache-crates
|
||||
uses: actions/cache@v4
|
||||
|
|
21
Cross.toml
21
Cross.toml
|
@ -1,26 +1,5 @@
|
|||
target.x86_64-unknown-linux-gnu.pre-build = "./scripts/sccache.sh"
|
||||
target.x86_64-unknown-linux-musl.pre-build = "./scripts/sccache.sh"
|
||||
#target.aarch64-unknown-linux-gnu.pre-build = "./scripts/sccache.sh"
|
||||
#target.aarch64-unknown-linux-musl.pre-build = "./scripts/sccache.sh"
|
||||
#target.armv7-unknown-linux-gnueabihf.pre-build = "./scripts/sccache.sh"
|
||||
#target.armv7-unknown-linux-musleabihf.pre-build = "./scripts/sccache.sh"
|
||||
#target.arm-unknown-linux-gnueabi.pre-build = "./scripts/sccache.sh"
|
||||
#target.arm-unknown-linux-musleabi.pre-build = "./scripts/sccache.sh"
|
||||
#target.x86_64-apple-darwin.pre-build = "./scripts/sccache.sh"
|
||||
#target.aarch64-apple-darwin.pre-build = "./scripts/sccache.sh"
|
||||
#target.aarch64-pc-windows-msvc.pre-build = "./scripts/sccache.sh"
|
||||
#target.x86_64-pc-windows-msvc.pre-build = "./scripts/sccache.sh"
|
||||
|
||||
[build.env]
|
||||
passthrough = [
|
||||
"AWS_ACCESS_KEY_ID",
|
||||
"AWS_SECRET_ACCESS_KEY",
|
||||
"RUSTC_WRAPPER",
|
||||
"SCCACHE_BUCKET",
|
||||
"SCCACHE_DIR",
|
||||
"SCCACHE_ENDPOINT",
|
||||
"SCCACHE_ERROR_LOG",
|
||||
"SCCACHE_LOG",
|
||||
"SCCACHE_REGION",
|
||||
"SCCACHE_S3_NO_CREDENTIALS",
|
||||
]
|
||||
|
|
|
@ -792,9 +792,20 @@ Arguments:
|
|||
If not specified, all tools in global and local configs will be shown
|
||||
|
||||
Options:
|
||||
-l, --bump
|
||||
Compares against the latest versions available, not what matches the current config
|
||||
|
||||
For example, if you have `node = "20"` in your config by default `mise outdated` will only
|
||||
show other 20.x versions, not 21.x or 22.x versions.
|
||||
|
||||
Using this flag, if there are 21.x or newer versions it will display those instead of 20.x.
|
||||
|
||||
-J, --json
|
||||
Output in JSON format
|
||||
|
||||
--no-header
|
||||
Don't show table header
|
||||
|
||||
Examples:
|
||||
|
||||
$ mise outdated
|
||||
|
@ -1763,14 +1774,23 @@ Options:
|
|||
-n, --dry-run
|
||||
Just print what would be done, don't actually do it
|
||||
|
||||
-i, --interactive
|
||||
Display multiselect menu to choose which tools to upgrade
|
||||
|
||||
-j, --jobs <JOBS>
|
||||
Number of jobs to run in parallel
|
||||
[default: 4]
|
||||
|
||||
[env: MISE_JOBS=]
|
||||
|
||||
-i, --interactive
|
||||
Display multiselect menu to choose which tools to upgrade
|
||||
-l, --bump
|
||||
Upgrades to the latest version available, bumping the version in mise.toml
|
||||
|
||||
For example, if you have `node = "20.0.0"` in your mise.toml but 22.1.0 is the latest available,
|
||||
this will install 22.1.0 and set `node = "22.1.0"` in your config.
|
||||
|
||||
It keeps the same precision as what was there before, so if you instead had `node = "20"`, it
|
||||
would change your config to `node = "22"`.
|
||||
|
||||
--raw
|
||||
Directly pipe stdin/stdout/stderr from plugin to user Sets --jobs=1
|
||||
|
|
|
@ -12,9 +12,20 @@ Arguments:
|
|||
If not specified, all tools in global and local configs will be shown
|
||||
|
||||
Options:
|
||||
-l, --bump
|
||||
Compares against the latest versions available, not what matches the current config
|
||||
|
||||
For example, if you have `node = "20"` in your config by default `mise outdated` will only
|
||||
show other 20.x versions, not 21.x or 22.x versions.
|
||||
|
||||
Using this flag, if there are 21.x or newer versions it will display those instead of 20.x.
|
||||
|
||||
-J, --json
|
||||
Output in JSON format
|
||||
|
||||
--no-header
|
||||
Don't show table header
|
||||
|
||||
Examples:
|
||||
|
||||
$ mise outdated
|
||||
|
|
|
@ -17,14 +17,23 @@ Options:
|
|||
-n, --dry-run
|
||||
Just print what would be done, don't actually do it
|
||||
|
||||
-i, --interactive
|
||||
Display multiselect menu to choose which tools to upgrade
|
||||
|
||||
-j, --jobs <JOBS>
|
||||
Number of jobs to run in parallel
|
||||
[default: 4]
|
||||
|
||||
[env: MISE_JOBS=]
|
||||
|
||||
-i, --interactive
|
||||
Display multiselect menu to choose which tools to upgrade
|
||||
-l, --bump
|
||||
Upgrades to the latest version available, bumping the version in mise.toml
|
||||
|
||||
For example, if you have `node = "20.0.0"` in your mise.toml but 22.1.0 is the latest available,
|
||||
this will install 22.1.0 and set `node = "22.1.0"` in your config.
|
||||
|
||||
It keeps the same precision as what was there before, so if you instead had `node = "20"`, it
|
||||
would change your config to `node = "22"`.
|
||||
|
||||
--raw
|
||||
Directly pipe stdin/stdout/stderr from plugin to user Sets --jobs=1
|
||||
|
|
|
@ -10,3 +10,7 @@ mise upgrade dummy
|
|||
|
||||
assert_contains "mise ls --installed dummy" "1.1.0"
|
||||
assert_not_contains "mise ls --installed dummy" "1.0.0"
|
||||
|
||||
mise upgrade dummy --bump
|
||||
assert_contains "mise ls --installed dummy" "2.0.0"
|
||||
assert_not_contains "mise ls --installed dummy" "1.1.0"
|
||||
|
|
|
@ -29,8 +29,7 @@ rm -rf "$MISE_DATA_DIR/installs/tiny"
|
|||
mise use tiny@3
|
||||
mv "$MISE_DATA_DIR/installs/tiny/"{3.1.0,3.0.0}
|
||||
assert "mise current tiny" "3.0.0"
|
||||
assert "mise outdated tiny" "Tool Requested Current Latest
|
||||
tiny 3 3.0.0 3.1.0"
|
||||
assert "mise outdated tiny" "tiny 3 3.0.0 3.1.0 ~/workdir/.mise.toml"
|
||||
mise upgrade tiny
|
||||
assert "mise current tiny" "3.1.0"
|
||||
assert "mise outdated tiny" ""
|
||||
|
|
|
@ -627,7 +627,11 @@ cmd "outdated" help="Shows outdated tool versions" {
|
|||
$ mise outdated --json
|
||||
{"python": {"requested": "3.11", "current": "3.11.0", "latest": "3.11.1"}, ...}
|
||||
"#
|
||||
flag "-l --bump" help="Compares against the latest versions available, not what matches the current config" {
|
||||
long_help "Compares against the latest versions available, not what matches the current config\n\nFor example, if you have `node = \"20\"` in your config by default `mise outdated` will only\nshow other 20.x versions, not 21.x or 22.x versions.\n\nUsing this flag, if there are 21.x or newer versions it will display those instead of 20.x."
|
||||
}
|
||||
flag "-J --json" help="Output in JSON format"
|
||||
flag "--no-header" help="Don't show table header"
|
||||
arg "[TOOL@VERSION]..." help="Tool(s) to show outdated versions for\ne.g.: node@20 python@3.10\nIf not specified, all tools in global and local configs will be shown" var=true
|
||||
}
|
||||
cmd "plugins" help="Manage plugins" {
|
||||
|
@ -1222,10 +1226,13 @@ By default this command modifies ".mise.toml" in the current directory."#
|
|||
cmd "upgrade" help="Upgrades outdated tool versions" {
|
||||
alias "up"
|
||||
flag "-n --dry-run" help="Just print what would be done, don't actually do it"
|
||||
flag "-i --interactive" help="Display multiselect menu to choose which tools to upgrade"
|
||||
flag "-j --jobs" help="Number of jobs to run in parallel\n[default: 4]" {
|
||||
arg "<JOBS>"
|
||||
}
|
||||
flag "-i --interactive" help="Display multiselect menu to choose which tools to upgrade"
|
||||
flag "-l --bump" help="Upgrades to the latest version available, bumping the version in mise.toml" {
|
||||
long_help "Upgrades to the latest version available, bumping the version in mise.toml\n\nFor example, if you have `node = \"20.0.0\"` in your mise.toml but 22.1.0 is the latest available,\nthis will install 22.1.0 and set `node = \"22.1.0\"` in your config.\n\nIt keeps the same precision as what was there before, so if you instead had `node = \"20\"`, it\nwould change your config to `node = \"22\"`."
|
||||
}
|
||||
flag "--raw" help="Directly pipe stdin/stdout/stderr from plugin to user Sets --jobs=1"
|
||||
arg "[TOOL@VERSION]..." help="Tool(s) to upgrade\ne.g.: node@20 python@3.10\nIf not specified, all current tools will be upgraded" var=true
|
||||
}
|
||||
|
|
|
@ -92,7 +92,7 @@ fi
|
|||
cd dist
|
||||
|
||||
if [[ "$os" == "macos" ]]; then
|
||||
codesign -f -s "Developer ID Application: Jeffrey Dickey (4993Y37DX6)" mise/bin/mise
|
||||
codesign -f -p dev.jdx. -s "Developer ID Application: Jeffrey Dickey (4993Y37DX6)" mise/bin/mise
|
||||
fi
|
||||
|
||||
if [[ "$os" == "windows" ]]; then
|
||||
|
|
|
@ -1,48 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -x
|
||||
set -euo pipefail
|
||||
|
||||
# shellcheck disable=SC1091
|
||||
. lib.sh
|
||||
|
||||
main() {
|
||||
local triple
|
||||
local tag
|
||||
local td
|
||||
local url="https://github.com/mozilla/sccache"
|
||||
triple="${1}"
|
||||
triple="${triple%%-musl}"
|
||||
triple="${triple%%-musleabi}"
|
||||
triple="${triple%%-musleabihf}"
|
||||
triple="${triple%%-gnu}"
|
||||
triple="${triple%%-gnueabi}"
|
||||
triple="${triple%%-gnueabihf}"
|
||||
triple="$triple-musl"
|
||||
|
||||
install_packages unzip tar
|
||||
|
||||
# Download our package, then install our binary.
|
||||
td="$(mktemp -d)"
|
||||
pushd "${td}"
|
||||
tag=$(git ls-remote --tags --refs --exit-code \
|
||||
"${url}" \
|
||||
| cut -d/ -f3 \
|
||||
| grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' \
|
||||
| sort --version-sort \
|
||||
| tail -n1)
|
||||
curl -LSfs "${url}/releases/download/${tag}/sccache-${tag}-${triple}.tar.gz" \
|
||||
-o sccache.tar.gz
|
||||
tar -xvf sccache.tar.gz
|
||||
rm sccache.tar.gz
|
||||
cp "sccache-${tag}-${triple}/sccache" "/usr/bin/sccache"
|
||||
chmod +x "/usr/bin/sccache"
|
||||
|
||||
# clean up our install
|
||||
purge_packages
|
||||
popd
|
||||
rm -rf "${td}"
|
||||
rm "${0}"
|
||||
}
|
||||
|
||||
main "${@}"
|
|
@ -17,6 +17,7 @@ use versions::Versioning;
|
|||
use self::backend_meta::BackendMeta;
|
||||
use crate::cli::args::{BackendArg, ToolVersionType};
|
||||
use crate::cmd::CmdLineRunner;
|
||||
use crate::config::settings::SETTINGS;
|
||||
use crate::config::{Config, Settings, CONFIG};
|
||||
use crate::file::{display_path, remove_all, remove_all_with_warning};
|
||||
use crate::install_context::InstallContext;
|
||||
|
@ -364,7 +365,6 @@ pub trait Backend: Debug + Send + Sync {
|
|||
plugin.is_installed_err()?;
|
||||
}
|
||||
let config = Config::get();
|
||||
let settings = Settings::try_get()?;
|
||||
if self.is_version_installed(&ctx.tv, true) {
|
||||
if ctx.force {
|
||||
self.uninstall_version(&ctx.tv, ctx.pr.as_ref(), false)?;
|
||||
|
@ -377,13 +377,13 @@ pub trait Backend: Debug + Send + Sync {
|
|||
self.create_install_dirs(&ctx.tv)?;
|
||||
|
||||
if let Err(e) = self.install_version_impl(&ctx) {
|
||||
self.cleanup_install_dirs_on_error(&settings, &ctx.tv);
|
||||
self.cleanup_install_dirs_on_error(&SETTINGS, &ctx.tv);
|
||||
return Err(e);
|
||||
}
|
||||
|
||||
BackendMeta::write(&ctx.tv.backend)?;
|
||||
|
||||
self.cleanup_install_dirs(&settings, &ctx.tv);
|
||||
self.cleanup_install_dirs(&SETTINGS, &ctx.tv);
|
||||
// attempt to touch all the .tool-version files to trigger updates in hook-env
|
||||
let mut touch_dirs = vec![dirs::DATA.to_path_buf()];
|
||||
touch_dirs.extend(config.config_files.keys().cloned());
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
use std::collections::HashSet;
|
||||
use std::sync::Arc;
|
||||
|
||||
use console::{pad_str, style, Alignment};
|
||||
use eyre::Result;
|
||||
|
||||
use crate::backend::Backend;
|
||||
use crate::cli::args::ToolArg;
|
||||
use crate::config::Config;
|
||||
use crate::toolset::{ToolVersion, ToolsetBuilder};
|
||||
use crate::toolset::{OutdatedInfo, ToolsetBuilder};
|
||||
use crate::ui::table;
|
||||
use eyre::Result;
|
||||
use indexmap::IndexMap;
|
||||
|
||||
/// Shows outdated tool versions
|
||||
#[derive(Debug, clap::Args)]
|
||||
|
@ -19,9 +17,22 @@ pub struct Outdated {
|
|||
#[clap(value_name = "TOOL@VERSION", verbatim_doc_comment)]
|
||||
pub tool: Vec<ToolArg>,
|
||||
|
||||
/// Compares against the latest versions available, not what matches the current config
|
||||
///
|
||||
/// For example, if you have `node = "20"` in your config by default `mise outdated` will only
|
||||
/// show other 20.x versions, not 21.x or 22.x versions.
|
||||
///
|
||||
/// Using this flag, if there are 21.x or newer versions it will display those instead of 20.x.
|
||||
#[clap(long, short = 'l', verbatim_doc_comment)]
|
||||
pub bump: bool,
|
||||
|
||||
/// Output in JSON format
|
||||
#[clap(short = 'J', long, verbatim_doc_comment)]
|
||||
pub json: bool,
|
||||
|
||||
/// Don't show table header
|
||||
#[clap(long)]
|
||||
pub no_header: bool,
|
||||
}
|
||||
|
||||
impl Outdated {
|
||||
|
@ -35,7 +46,7 @@ impl Outdated {
|
|||
.collect::<HashSet<_>>();
|
||||
ts.versions
|
||||
.retain(|_, tvl| tool_set.is_empty() || tool_set.contains(&tvl.backend));
|
||||
let outdated = ts.list_outdated_versions();
|
||||
let outdated = ts.list_outdated_versions(self.bump);
|
||||
if outdated.is_empty() {
|
||||
info!("All tools are up to date");
|
||||
} else if self.json {
|
||||
|
@ -47,87 +58,23 @@ impl Outdated {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn display(&self, outdated: OutputVec) -> Result<()> {
|
||||
// TODO: make a generic table printer in src/ui/table
|
||||
let plugins = outdated.iter().map(|(t, _, _)| t.id()).collect::<Vec<_>>();
|
||||
let requests = outdated
|
||||
.iter()
|
||||
.map(|(_, tv, _)| tv.request.version())
|
||||
.collect::<Vec<_>>();
|
||||
let currents = outdated
|
||||
.iter()
|
||||
.map(|(t, tv, _)| {
|
||||
if t.is_version_installed(tv, true) {
|
||||
tv.version.clone()
|
||||
} else {
|
||||
"MISSING".to_string()
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let latests = outdated
|
||||
.iter()
|
||||
.map(|(_, _, c)| c.clone())
|
||||
.collect::<Vec<_>>();
|
||||
let plugin_width = plugins
|
||||
.iter()
|
||||
.map(|s| s.len())
|
||||
.max()
|
||||
.unwrap_or_default()
|
||||
.max(6)
|
||||
+ 1;
|
||||
let requested_width = requests
|
||||
.iter()
|
||||
.map(|s| s.len())
|
||||
.max()
|
||||
.unwrap_or_default()
|
||||
.max(9)
|
||||
+ 1;
|
||||
let current_width = currents
|
||||
.iter()
|
||||
.map(|s| s.len())
|
||||
.max()
|
||||
.unwrap_or_default()
|
||||
.max(7)
|
||||
+ 1;
|
||||
let pad_plugin = |s| pad_str(s, plugin_width, Alignment::Left, None);
|
||||
let pad_requested = |s| pad_str(s, requested_width, Alignment::Left, None);
|
||||
let pad_current = |s| pad_str(s, current_width, Alignment::Left, None);
|
||||
miseprintln!(
|
||||
"{} {} {} {}",
|
||||
style(pad_plugin("Tool")).dim(),
|
||||
style(pad_requested("Requested")).dim(),
|
||||
style(pad_current("Current")).dim(),
|
||||
style("Latest").dim(),
|
||||
);
|
||||
for i in 0..outdated.len() {
|
||||
miseprintln!(
|
||||
"{} {} {} {}",
|
||||
pad_plugin(plugins[i]),
|
||||
pad_requested(&requests[i]),
|
||||
pad_current(¤ts[i]),
|
||||
latests[i]
|
||||
);
|
||||
}
|
||||
fn display(&self, outdated: Vec<OutdatedInfo>) -> Result<()> {
|
||||
let mut table = tabled::Table::new(outdated);
|
||||
table::default_style(&mut table, self.no_header);
|
||||
miseprintln!("{table}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn display_json(&self, outdated: OutputVec) -> Result<()> {
|
||||
let mut map = serde_json::Map::new();
|
||||
for (t, tv, c) in outdated {
|
||||
let mut inner = serde_json::Map::new();
|
||||
inner.insert("requested".to_string(), tv.request.version().into());
|
||||
inner.insert("current".to_string(), tv.version.clone().into());
|
||||
inner.insert("latest".to_string(), c.into());
|
||||
map.insert(t.id().to_string(), serde_json::Value::Object(inner));
|
||||
fn display_json(&self, outdated: Vec<OutdatedInfo>) -> Result<()> {
|
||||
let mut map = IndexMap::new();
|
||||
for o in outdated {
|
||||
map.insert(o.name.to_string(), o);
|
||||
}
|
||||
let json = serde_json::Value::Object(map);
|
||||
miseprintln!("{}", serde_json::to_string_pretty(&json)?);
|
||||
miseprintln!("{}", serde_json::to_string_pretty(&map)?);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
type OutputVec = Vec<(Arc<dyn Backend>, ToolVersion, String)>;
|
||||
|
||||
static AFTER_LONG_HELP: &str = color_print::cstr!(
|
||||
r#"<bold><underline>Examples:</underline></bold>
|
||||
|
||||
|
@ -170,4 +117,11 @@ mod tests {
|
|||
assert_cli_snapshot!("outdated", "tiny", "--json");
|
||||
change_installed_version("tiny", "3.0.0", "3.1.0");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_outdated_json_bump() {
|
||||
reset();
|
||||
assert_cli!("use", "tiny@2");
|
||||
assert_cli_snapshot!("outdated", "tiny", "--json", "--bump");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,4 +4,3 @@ expression: err
|
|||
---
|
||||
mise is not activated in this shell session.
|
||||
Please run `mise activate` first in your shell rc file.
|
||||
|
||||
|
|
|
@ -4,8 +4,14 @@ expression: output
|
|||
---
|
||||
{
|
||||
"tiny": {
|
||||
"name": "tiny",
|
||||
"requested": "3",
|
||||
"current": "3.0.0",
|
||||
"bump": null,
|
||||
"latest": "3.1.0",
|
||||
"requested": "3"
|
||||
"source": {
|
||||
"type": ".tool-versions",
|
||||
"path": "~/cwd/.test-tool-versions"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
---
|
||||
source: src/cli/outdated.rs
|
||||
expression: output
|
||||
---
|
||||
{
|
||||
"tiny": {
|
||||
"name": "tiny",
|
||||
"requested": "2",
|
||||
"current": "2.1.0",
|
||||
"bump": "3",
|
||||
"latest": "3.1.0",
|
||||
"source": {
|
||||
"type": ".tool-versions",
|
||||
"path": "~/cwd/.test-tool-versions"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,4 +4,3 @@ expression: "file::read_to_string(cf_path).unwrap()"
|
|||
---
|
||||
[env]
|
||||
FOO = "bar"
|
||||
|
||||
|
|
|
@ -4,4 +4,3 @@ expression: "file::read_to_string(cf_path).unwrap()"
|
|||
---
|
||||
[env]
|
||||
FOO = "bar"
|
||||
|
||||
|
|
|
@ -3,4 +3,3 @@ source: src/cli/set.rs
|
|||
expression: "file::read_to_string(cf_path).unwrap()"
|
||||
---
|
||||
[env]
|
||||
|
||||
|
|
|
@ -3,4 +3,3 @@ source: src/cli/set.rs
|
|||
expression: "file::read_to_string(cf_path).unwrap()"
|
||||
---
|
||||
[env]
|
||||
|
||||
|
|
|
@ -4,4 +4,3 @@ expression: err
|
|||
---
|
||||
mise is not activated in this shell session.
|
||||
Please run `mise activate` first in your shell rc file.
|
||||
|
||||
|
|
|
@ -3,4 +3,3 @@ source: src/cli/unset.rs
|
|||
expression: "file::read_to_string(cf_path).unwrap()"
|
||||
---
|
||||
[env]
|
||||
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
source: src/cli/upgrade.rs
|
||||
expression: output
|
||||
---
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
source: src/cli/upgrade.rs
|
||||
expression: output
|
||||
---
|
||||
mise Would uninstall tiny@3.0.0
|
||||
mise Would uninstall dummy@ref:master
|
||||
mise Would install tiny@3.1.0
|
||||
mise Would install dummy@2.0.0
|
|
@ -1,16 +1,12 @@
|
|||
use std::collections::HashSet;
|
||||
use std::sync::Arc;
|
||||
|
||||
use demand::DemandOption;
|
||||
use eyre::{Context, Result};
|
||||
|
||||
use crate::backend::Backend;
|
||||
use crate::cli::args::ToolArg;
|
||||
use crate::config::Config;
|
||||
use crate::toolset::{InstallOptions, ToolVersion, ToolsetBuilder};
|
||||
use crate::config::{config_file, Config};
|
||||
use crate::file::display_path;
|
||||
use crate::toolset::{InstallOptions, OutdatedInfo, ToolVersion, ToolsetBuilder};
|
||||
use crate::ui::multi_progress_report::MultiProgressReport;
|
||||
use crate::ui::progress_report::SingleReport;
|
||||
use crate::{runtime_symlinks, shims, ui};
|
||||
use demand::DemandOption;
|
||||
use eyre::{Context, Result};
|
||||
|
||||
/// Upgrades outdated tool versions
|
||||
#[derive(Debug, clap::Args)]
|
||||
|
@ -26,14 +22,24 @@ pub struct Upgrade {
|
|||
#[clap(long, short = 'n', verbatim_doc_comment)]
|
||||
dry_run: bool,
|
||||
|
||||
/// Display multiselect menu to choose which tools to upgrade
|
||||
#[clap(long, short, verbatim_doc_comment, conflicts_with = "tool")]
|
||||
interactive: bool,
|
||||
|
||||
/// Number of jobs to run in parallel
|
||||
/// [default: 4]
|
||||
#[clap(long, short, env = "MISE_JOBS", verbatim_doc_comment)]
|
||||
jobs: Option<usize>,
|
||||
|
||||
/// Display multiselect menu to choose which tools to upgrade
|
||||
#[clap(long, short, verbatim_doc_comment, conflicts_with = "tool")]
|
||||
interactive: bool,
|
||||
/// Upgrades to the latest version available, bumping the version in mise.toml
|
||||
///
|
||||
/// For example, if you have `node = "20.0.0"` in your mise.toml but 22.1.0 is the latest available,
|
||||
/// this will install 22.1.0 and set `node = "22.1.0"` in your config.
|
||||
///
|
||||
/// It keeps the same precision as what was there before, so if you instead had `node = "20"`, it
|
||||
/// would change your config to `node = "22"`.
|
||||
#[clap(long, short = 'l', verbatim_doc_comment)]
|
||||
bump: bool,
|
||||
|
||||
/// Directly pipe stdin/stdout/stderr from plugin to user
|
||||
/// Sets --jobs=1
|
||||
|
@ -45,17 +51,15 @@ impl Upgrade {
|
|||
pub fn run(self) -> Result<()> {
|
||||
let config = Config::try_get()?;
|
||||
let ts = ToolsetBuilder::new().with_args(&self.tool).build(&config)?;
|
||||
let mut outdated = ts.list_outdated_versions();
|
||||
let mut outdated = ts.list_outdated_versions(self.bump);
|
||||
if self.interactive && !outdated.is_empty() {
|
||||
let tvs = self.get_interactive_tool_set(&outdated)?;
|
||||
outdated.retain(|(_, tv, _)| tvs.contains(tv));
|
||||
} else {
|
||||
let tool_set = self
|
||||
.tool
|
||||
.iter()
|
||||
.map(|t| t.backend.clone())
|
||||
.collect::<HashSet<_>>();
|
||||
outdated.retain(|(p, _, _)| tool_set.is_empty() || tool_set.contains(p.fa()));
|
||||
outdated = self.get_interactive_tool_set(&outdated)?;
|
||||
} else if !self.tool.is_empty() {
|
||||
outdated.retain(|o| {
|
||||
self.tool
|
||||
.iter()
|
||||
.any(|t| t.backend == o.tool_version.backend)
|
||||
});
|
||||
}
|
||||
if outdated.is_empty() {
|
||||
info!("All tools are up to date");
|
||||
|
@ -66,31 +70,57 @@ impl Upgrade {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn upgrade(&self, config: &Config, outdated: OutputVec) -> Result<()> {
|
||||
fn upgrade(&self, config: &Config, outdated: Vec<OutdatedInfo>) -> Result<()> {
|
||||
let mpr = MultiProgressReport::get();
|
||||
let mut ts = ToolsetBuilder::new().with_args(&self.tool).build(config)?;
|
||||
|
||||
let new_versions = outdated
|
||||
let config_file_updates = outdated
|
||||
.iter()
|
||||
.map(|(_, tv, latest)| {
|
||||
let mut tv = tv.clone();
|
||||
tv.version.clone_from(latest);
|
||||
tv
|
||||
.filter_map(|o| {
|
||||
if let (Some(path), Some(bump)) = (o.source.path(), &o.bump) {
|
||||
match config_file::parse(path) {
|
||||
Ok(cf) => Some((o, bump.clone(), cf)),
|
||||
Err(e) => {
|
||||
warn!("failed to parse {}: {e}", display_path(path));
|
||||
None
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.filter(|(o, _bump, cf)| {
|
||||
if let Ok(trs) = cf.to_tool_request_set() {
|
||||
if let Some(versions) = trs.tools.get(o.tool_request.backend()) {
|
||||
if versions.len() != 1 {
|
||||
warn!("upgrading multiple versions with --bump is not yet supported");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
true
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let to_remove = outdated
|
||||
.into_iter()
|
||||
.filter(|(tool, tv, _)| tool.is_version_installed(tv, true))
|
||||
.map(|(tool, tv, _)| (tool, tv))
|
||||
.iter()
|
||||
.filter_map(|o| o.current.as_ref().map(|current| (o, current)))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if self.dry_run {
|
||||
for (_, tv) in &to_remove {
|
||||
info!("Would uninstall {tv}");
|
||||
for (o, current) in &to_remove {
|
||||
info!("Would uninstall {}@{}", o.name, current);
|
||||
}
|
||||
for tv in &new_versions {
|
||||
info!("Would install {tv}");
|
||||
for o in &outdated {
|
||||
info!("Would install {}@{}", o.name, o.latest);
|
||||
}
|
||||
for (o, bump, cf) in &config_file_updates {
|
||||
info!(
|
||||
"Would bump {}@{} in {}",
|
||||
o.name,
|
||||
bump,
|
||||
display_path(cf.get_path())
|
||||
);
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
@ -100,11 +130,17 @@ impl Upgrade {
|
|||
raw: self.raw,
|
||||
latest_versions: true,
|
||||
};
|
||||
let new_versions = new_versions.into_iter().map(|tv| tv.request).collect();
|
||||
let new_versions = outdated.iter().map(|o| o.tool_request.clone()).collect();
|
||||
ts.install_versions(config, new_versions, &mpr, &opts)?;
|
||||
for (tool, tv) in to_remove {
|
||||
let pr = mpr.add(&tv.style());
|
||||
self.uninstall_old_version(tool.clone(), &tv, pr.as_ref())?;
|
||||
|
||||
for (o, bump, mut cf) in config_file_updates {
|
||||
cf.replace_versions(o.tool_request.backend(), &[bump])?;
|
||||
cf.save()?;
|
||||
}
|
||||
|
||||
for (o, tv) in to_remove {
|
||||
let pr = mpr.add(&format!("Uninstalling {}@{}", o.name, tv));
|
||||
self.uninstall_old_version(&o.tool_version, pr.as_ref())?;
|
||||
}
|
||||
|
||||
let ts = ToolsetBuilder::new().with_args(&self.tool).build(config)?;
|
||||
|
@ -113,38 +149,27 @@ impl Upgrade {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn uninstall_old_version(
|
||||
&self,
|
||||
tool: Arc<dyn Backend>,
|
||||
tv: &ToolVersion,
|
||||
pr: &dyn SingleReport,
|
||||
) -> Result<()> {
|
||||
tool.uninstall_version(tv, pr, self.dry_run)
|
||||
fn uninstall_old_version(&self, tv: &ToolVersion, pr: &dyn SingleReport) -> Result<()> {
|
||||
tv.get_backend()
|
||||
.uninstall_version(tv, pr, self.dry_run)
|
||||
.wrap_err_with(|| format!("failed to uninstall {tv}"))?;
|
||||
pr.finish();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_interactive_tool_set(&self, outdated: &OutputVec) -> Result<HashSet<ToolVersion>> {
|
||||
fn get_interactive_tool_set(&self, outdated: &Vec<OutdatedInfo>) -> Result<Vec<OutdatedInfo>> {
|
||||
let _ctrlc = ui::ctrlc::handle_ctrlc()?;
|
||||
let mut ms = demand::MultiSelect::new("mise upgrade")
|
||||
.description("Select tools to upgrade")
|
||||
.filterable(true)
|
||||
.min(1);
|
||||
for (_, tv, latest) in outdated {
|
||||
let label = if &tv.version == latest {
|
||||
tv.to_string()
|
||||
} else {
|
||||
format!("{tv} -> {latest}")
|
||||
};
|
||||
ms = ms.option(DemandOption::new(tv).label(&label));
|
||||
for out in outdated {
|
||||
ms = ms.option(DemandOption::new(out.clone()));
|
||||
}
|
||||
Ok(ms.run()?.into_iter().cloned().collect())
|
||||
Ok(ms.run()?.into_iter().collect())
|
||||
}
|
||||
}
|
||||
|
||||
type OutputVec = Vec<(Arc<dyn Backend>, ToolVersion, String)>;
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use crate::dirs;
|
||||
|
@ -158,4 +183,13 @@ pub mod tests {
|
|||
assert_cli_snapshot!("upgrade");
|
||||
assert!(dirs::INSTALLS.join("tiny").join("3.1.0").exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_upgrade_bump() {
|
||||
reset();
|
||||
change_installed_version("tiny", "3.1.0", "3.0.0");
|
||||
assert_cli_snapshot!("upgrade", "--dry-run", "--bump");
|
||||
assert_cli_snapshot!("upgrade");
|
||||
assert!(dirs::INSTALLS.join("tiny").join("3.1.0").exists());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,4 +2,4 @@
|
|||
source: src/config/config_file/mise_toml.rs
|
||||
expression: cf
|
||||
---
|
||||
~/cwd/.test.mise.toml:
|
||||
~/cwd/.test.mise.toml:
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
source: src/config/config_file/mise_toml.rs
|
||||
expression: cf.plugins()
|
||||
expression: cf.plugins().unwrap()
|
||||
---
|
||||
{
|
||||
"node": "https://github.com/jdx/rtx-node",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
source: src/config/config_file/mise_toml.rs
|
||||
expression: cf.env_entries()
|
||||
expression: cf.env_entries().unwrap()
|
||||
---
|
||||
[
|
||||
Val(
|
||||
|
|
|
@ -4,4 +4,3 @@ expression: cf.dump().unwrap()
|
|||
---
|
||||
[alias.node]
|
||||
18 = "18.0.0"
|
||||
|
||||
|
|
|
@ -2,4 +2,4 @@
|
|||
source: src/config/config_file/mise_toml.rs
|
||||
expression: cf
|
||||
---
|
||||
~/cwd/.test.mise.toml:
|
||||
~/cwd/.test.mise.toml:
|
||||
|
|
|
@ -2,4 +2,4 @@
|
|||
source: src/config/config_file/mise_toml.rs
|
||||
expression: cf
|
||||
---
|
||||
/tmp/.mise.toml:
|
||||
/tmp/.mise.toml:
|
||||
|
|
|
@ -4,4 +4,3 @@ expression: cf.dump().unwrap()
|
|||
---
|
||||
[tools]
|
||||
node = ["16.0.1", "18.0.1"]
|
||||
|
||||
|
|
|
@ -2,4 +2,4 @@
|
|||
source: src/config/config_file/mise_toml.rs
|
||||
expression: cf
|
||||
---
|
||||
~/cwd/.test.mise.toml:
|
||||
~/cwd/.test.mise.toml:
|
||||
|
|
|
@ -395,7 +395,7 @@ impl Backend for JavaPlugin {
|
|||
}
|
||||
|
||||
fn fuzzy_match_filter(&self, versions: Vec<String>, query: &str) -> eyre::Result<Vec<String>> {
|
||||
let escaped_query = regex::escape(query);
|
||||
let escaped_query = regex::escape(query.trim_end_matches('-'));
|
||||
let query = if query == "latest" {
|
||||
"[0-9].*"
|
||||
} else {
|
||||
|
|
|
@ -53,4 +53,3 @@ if [ -z "${_mise_cmd_not_found:-}" ]; then
|
|||
fi
|
||||
}
|
||||
fi
|
||||
|
||||
|
|
|
@ -7,4 +7,3 @@ PROMPT_COMMAND="${PROMPT_COMMAND//_mise_hook/}"
|
|||
unset _mise_hook
|
||||
unset mise
|
||||
unset MISE_SHELL
|
||||
|
||||
|
|
|
@ -3,4 +3,3 @@ source: src/shell/bash.rs
|
|||
expression: "replace_path(&bash.prepend_env(\"PATH\", \"/some/dir:/2/dir\"))"
|
||||
---
|
||||
export PATH="/some/dir:/2/dir:$PATH"
|
||||
|
||||
|
|
|
@ -3,4 +3,3 @@ source: src/shell/bash.rs
|
|||
expression: "Bash::default().set_env(\"FOO\", \"1\")"
|
||||
---
|
||||
export FOO=1
|
||||
|
||||
|
|
|
@ -3,4 +3,3 @@ source: src/shell/bash.rs
|
|||
expression: "Bash::default().unset_env(\"FOO\")"
|
||||
---
|
||||
unset FOO
|
||||
|
||||
|
|
|
@ -7,4 +7,3 @@ functions --erase __mise_env_eval_2
|
|||
functions --erase __mise_cd_hook
|
||||
functions --erase mise
|
||||
set -e MISE_SHELL
|
||||
|
||||
|
|
|
@ -3,4 +3,3 @@ source: src/shell/fish.rs
|
|||
expression: "replace_path(&sh.prepend_env(\"PATH\", \"/some/dir:/2/dir\"))"
|
||||
---
|
||||
set -gx PATH '/some/dir:/2/dir' $PATH
|
||||
|
||||
|
|
|
@ -3,4 +3,3 @@ source: src/shell/fish.rs
|
|||
expression: "Fish::default().set_env(\"FOO\", \"1\")"
|
||||
---
|
||||
set -gx FOO 1
|
||||
|
||||
|
|
|
@ -3,4 +3,3 @@ source: src/shell/fish.rs
|
|||
expression: "Fish::default().unset_env(\"FOO\")"
|
||||
---
|
||||
set -e FOO
|
||||
|
||||
|
|
|
@ -3,4 +3,3 @@ source: src/shell/nushell.rs
|
|||
expression: replace_path(&deactivate)
|
||||
---
|
||||
hide,MISE_SHELL,
|
||||
|
||||
|
|
|
@ -53,5 +53,3 @@ def --env mise_hook [] {
|
|||
| parse vars
|
||||
| update-env
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -3,4 +3,3 @@ source: src/shell/nushell.rs
|
|||
expression: "replace_path(&sh.prepend_env(\"PATH\", \"/some/dir:/2/dir\"))"
|
||||
---
|
||||
$env.PATH = ($env.PATH | prepend '/some/dir:/2/dir')
|
||||
|
||||
|
|
|
@ -3,4 +3,3 @@ source: src/shell/nushell.rs
|
|||
expression: "Nushell::default().set_env(\"FOO\", \"1\")"
|
||||
---
|
||||
set,FOO,1
|
||||
|
||||
|
|
|
@ -3,4 +3,3 @@ source: src/shell/nushell.rs
|
|||
expression: "Nushell::default().unset_env(\"FOO\")"
|
||||
---
|
||||
hide,FOO,
|
||||
|
||||
|
|
|
@ -8,4 +8,3 @@ from xonsh.built_ins import XSH
|
|||
envx = XSH.env
|
||||
envx[ 'PATH'].add('/some/dir:/2/dir', front=True)
|
||||
environ['PATH'] = envx.get_detyped('PATH')
|
||||
|
||||
|
|
|
@ -8,4 +8,3 @@ from xonsh.built_ins import XSH
|
|||
envx = XSH.env
|
||||
envx[ 'FOO'] = '1'
|
||||
environ['FOO'] = envx.get_detyped('FOO')
|
||||
|
||||
|
|
|
@ -8,4 +8,3 @@ from xonsh.built_ins import XSH
|
|||
envx = XSH.env
|
||||
envx.pop( 'FOO',None)
|
||||
environ.pop('FOO',None)
|
||||
|
||||
|
|
|
@ -54,4 +54,3 @@ if [ -z "${_mise_cmd_not_found:-}" ]; then
|
|||
fi
|
||||
}
|
||||
fi
|
||||
|
||||
|
|
|
@ -7,4 +7,3 @@ chpwd_functions=( ${chpwd_functions:#_mise_hook} )
|
|||
unset -f _mise_hook
|
||||
unset -f mise
|
||||
unset MISE_SHELL
|
||||
|
||||
|
|
|
@ -3,4 +3,3 @@ source: src/shell/zsh.rs
|
|||
expression: "replace_path(&sh.prepend_env(\"PATH\", \"/some/dir:/2/dir\"))"
|
||||
---
|
||||
export PATH="/some/dir:/2/dir:$PATH"
|
||||
|
||||
|
|
|
@ -3,4 +3,3 @@ source: src/shell/zsh.rs
|
|||
expression: "Zsh::default().set_env(\"FOO\", \"1\")"
|
||||
---
|
||||
export FOO=1
|
||||
|
||||
|
|
|
@ -3,4 +3,3 @@ source: src/shell/zsh.rs
|
|||
expression: "Zsh::default().unset_env(\"FOO\")"
|
||||
---
|
||||
unset FOO
|
||||
|
||||
|
|
|
@ -6,24 +6,25 @@ use std::thread::sleep;
|
|||
use std::time::Duration;
|
||||
use std::{panic, thread};
|
||||
|
||||
pub use builder::ToolsetBuilder;
|
||||
use console::truncate_str;
|
||||
use eyre::{eyre, Result};
|
||||
use indexmap::IndexMap;
|
||||
use itertools::Itertools;
|
||||
use rayon::prelude::*;
|
||||
|
||||
pub use builder::ToolsetBuilder;
|
||||
use serde_derive::Serialize;
|
||||
use tabled::Tabled;
|
||||
pub use tool_request::ToolRequest;
|
||||
pub use tool_request_set::{ToolRequestSet, ToolRequestSetBuilder};
|
||||
pub use tool_source::ToolSource;
|
||||
pub use tool_version::ToolVersion;
|
||||
pub use tool_version_list::ToolVersionList;
|
||||
use versions::Version;
|
||||
use versions::{Version, Versioning};
|
||||
|
||||
use crate::backend::Backend;
|
||||
use crate::cli::args::BackendArg;
|
||||
use crate::config::settings::SettingsStatusMissingTools;
|
||||
use crate::config::{Config, Settings};
|
||||
use crate::config::settings::{SettingsStatusMissingTools, SETTINGS};
|
||||
use crate::config::Config;
|
||||
use crate::env::{PATH_KEY, TERM_WIDTH};
|
||||
use crate::errors::Error;
|
||||
use crate::install_context::InstallContext;
|
||||
|
@ -50,10 +51,9 @@ pub struct InstallOptions {
|
|||
|
||||
impl InstallOptions {
|
||||
pub fn new() -> Self {
|
||||
let settings = Settings::get();
|
||||
InstallOptions {
|
||||
jobs: Some(settings.jobs),
|
||||
raw: settings.raw,
|
||||
jobs: Some(SETTINGS.jobs),
|
||||
raw: SETTINGS.raw,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
@ -161,7 +161,6 @@ impl Toolset {
|
|||
self.install_versions(config, leaf_deps.into_iter().cloned().collect(), mpr, opts)?;
|
||||
}
|
||||
debug!("install_versions: {}", versions.iter().join(" "));
|
||||
let settings = Settings::try_get()?;
|
||||
let queue: Vec<_> = versions
|
||||
.into_iter()
|
||||
.rev()
|
||||
|
@ -183,10 +182,10 @@ impl Toolset {
|
|||
}
|
||||
}
|
||||
let queue = Arc::new(Mutex::new(queue));
|
||||
let raw = opts.raw || settings.raw;
|
||||
let raw = opts.raw || SETTINGS.raw;
|
||||
let jobs = match raw {
|
||||
true => 1,
|
||||
false => opts.jobs.unwrap_or(settings.jobs),
|
||||
false => opts.jobs.unwrap_or(SETTINGS.jobs),
|
||||
};
|
||||
let installing: HashSet<String> = HashSet::new();
|
||||
let installing = Arc::new(Mutex::new(installing));
|
||||
|
@ -316,28 +315,77 @@ impl Toolset {
|
|||
.filter(|(p, v)| p.is_version_installed(v, true))
|
||||
.collect()
|
||||
}
|
||||
pub fn list_outdated_versions(&self) -> Vec<(Arc<dyn Backend>, ToolVersion, String)> {
|
||||
pub fn list_outdated_versions(&self, bump: bool) -> Vec<OutdatedInfo> {
|
||||
self.list_current_versions()
|
||||
.into_iter()
|
||||
.filter_map(|(t, tv)| {
|
||||
if t.symlink_path(&tv).is_some() {
|
||||
trace!("skipping symlinked version {tv}");
|
||||
// do not consider symlinked versions to be outdated
|
||||
return None;
|
||||
}
|
||||
let latest = match tv.latest_version(t.as_ref()) {
|
||||
Ok(latest) => latest,
|
||||
// prefix is something like "temurin-" or "corretto-"
|
||||
let prefix = regex!(r"^[a-zA-Z]+-")
|
||||
.find(&tv.request.version())
|
||||
.map(|m| m.as_str().to_string());
|
||||
let latest_result = if bump {
|
||||
t.latest_version(prefix.clone())
|
||||
} else {
|
||||
tv.latest_version(t.as_ref()).map(Option::from)
|
||||
};
|
||||
let mut out =
|
||||
OutdatedInfo::new(tv.clone(), self.find_source(&tv.request).unwrap().clone());
|
||||
out.current = if t.is_version_installed(&tv, true) {
|
||||
Some(tv.version.clone())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
out.latest = match latest_result {
|
||||
Ok(Some(latest)) => latest,
|
||||
Ok(None) => {
|
||||
warn!("Error getting latest version for {t}: no latest version found");
|
||||
return None;
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("Error getting latest version for {t}: {e:#}");
|
||||
return None;
|
||||
}
|
||||
};
|
||||
if !t.is_version_installed(&tv, true)
|
||||
|| is_outdated_version(tv.version.as_str(), latest.as_str())
|
||||
if t.is_version_installed(&tv, true)
|
||||
&& !is_outdated_version(tv.version.as_str(), out.latest.as_str())
|
||||
{
|
||||
Some((t, tv, latest))
|
||||
} else {
|
||||
None
|
||||
trace!("skipping up-to-date version {tv}");
|
||||
return None;
|
||||
}
|
||||
if bump {
|
||||
let prefix = prefix.unwrap_or_default();
|
||||
let old = tv.request.version();
|
||||
let old = old.strip_prefix(&prefix).unwrap_or_default();
|
||||
let new = out.latest.strip_prefix(&prefix).unwrap_or_default();
|
||||
if let Some(bumped_version) = check_semver_bump(old, new) {
|
||||
if bumped_version != tv.request.version() {
|
||||
out.bump = Some(format!("{prefix}{bumped_version}"));
|
||||
match out.tool_request.clone() {
|
||||
ToolRequest::Version {
|
||||
backend,
|
||||
version: _version,
|
||||
options,
|
||||
} => {
|
||||
out.tool_request = ToolRequest::Version {
|
||||
backend,
|
||||
options,
|
||||
version: out.bump.clone().unwrap(),
|
||||
};
|
||||
}
|
||||
_ => {
|
||||
warn!("upgrading non-version tool requests");
|
||||
out.bump = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(out)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
@ -468,11 +516,10 @@ impl Toolset {
|
|||
// shows a warning if any versions are missing
|
||||
// only displays for tools which have at least one version already installed
|
||||
pub fn notify_if_versions_missing(&self) {
|
||||
let settings = Settings::get();
|
||||
let missing = self
|
||||
.list_missing_versions()
|
||||
.into_iter()
|
||||
.filter(|tv| match settings.status.missing_tools() {
|
||||
.filter(|tv| match SETTINGS.status.missing_tools() {
|
||||
SettingsStatusMissingTools::Never => false,
|
||||
SettingsStatusMissingTools::Always => true,
|
||||
SettingsStatusMissingTools::IfOtherVersionsInstalled => tv
|
||||
|
@ -496,9 +543,12 @@ impl Toolset {
|
|||
}
|
||||
|
||||
fn is_disabled(&self, fa: &BackendArg) -> bool {
|
||||
let settings = Settings::get();
|
||||
let fa = fa.to_string();
|
||||
settings.disable_tools.iter().any(|s| s == &fa)
|
||||
SETTINGS.disable_tools.iter().any(|s| s == &fa)
|
||||
}
|
||||
|
||||
pub fn find_source(&self, tr: &ToolRequest) -> Option<&ToolSource> {
|
||||
self.versions.get(tr.backend()).map(|tvl| &tvl.source)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -574,13 +624,117 @@ fn is_outdated_version(current: &str, latest: &str) -> bool {
|
|||
current != latest
|
||||
}
|
||||
|
||||
fn check_semver_bump(old: &str, new: &str) -> Option<String> {
|
||||
let old = Versioning::new(old);
|
||||
let new = Versioning::new(new);
|
||||
let chunkify = |v: &Versioning| {
|
||||
let mut chunks = vec![];
|
||||
while let Some(chunk) = v.nth(chunks.len()) {
|
||||
chunks.push(chunk);
|
||||
}
|
||||
chunks
|
||||
};
|
||||
if let (Some(old), Some(new)) = (old, new) {
|
||||
let old = chunkify(&old);
|
||||
let new = chunkify(&new);
|
||||
if old.len() > new.len() {
|
||||
warn!(
|
||||
"something weird happened with versioning, old: {old}, new: {new}, skipping",
|
||||
old = old
|
||||
.iter()
|
||||
.map(|c| c.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join("."),
|
||||
new = new
|
||||
.iter()
|
||||
.map(|c| c.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join("."),
|
||||
);
|
||||
return None;
|
||||
}
|
||||
let bump = new.into_iter().take(old.len()).collect::<Vec<_>>();
|
||||
if bump == old {
|
||||
None
|
||||
} else {
|
||||
Some(
|
||||
bump.iter()
|
||||
.map(|c| c.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join("."),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Clone, Tabled)]
|
||||
pub struct OutdatedInfo {
|
||||
pub name: String,
|
||||
#[serde(skip)]
|
||||
#[tabled(skip)]
|
||||
pub tool_request: ToolRequest,
|
||||
#[serde(skip)]
|
||||
#[tabled(skip)]
|
||||
pub tool_version: ToolVersion,
|
||||
pub requested: String,
|
||||
#[tabled(display_with("Self::display_current", self))]
|
||||
pub current: Option<String>,
|
||||
#[tabled(skip)]
|
||||
pub bump: Option<String>,
|
||||
pub latest: String,
|
||||
pub source: ToolSource,
|
||||
}
|
||||
|
||||
impl OutdatedInfo {
|
||||
fn new(tv: ToolVersion, source: ToolSource) -> Self {
|
||||
Self {
|
||||
name: tv.request.backend().name.clone(),
|
||||
current: None,
|
||||
requested: tv.request.version(),
|
||||
tool_request: tv.request.clone(),
|
||||
tool_version: tv,
|
||||
bump: None,
|
||||
latest: "".to_string(),
|
||||
source,
|
||||
}
|
||||
}
|
||||
|
||||
fn display_current(&self) -> String {
|
||||
if let Some(current) = &self.current {
|
||||
current.clone()
|
||||
} else {
|
||||
"[MISSING]".to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for OutdatedInfo {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
|
||||
if let Some(current) = &self.current {
|
||||
write!(
|
||||
f,
|
||||
"{:<20} {:<10} -> {:<10} ({})",
|
||||
self.name, current, self.latest, self.source
|
||||
)
|
||||
} else {
|
||||
write!(
|
||||
f,
|
||||
"{:<20} {:<10} -> {:<10} ({})",
|
||||
self.name, "MISSING", self.latest, self.source
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::backend::reset;
|
||||
use pretty_assertions::assert_eq;
|
||||
use test_log::test;
|
||||
|
||||
use super::is_outdated_version;
|
||||
use super::{check_semver_bump, is_outdated_version};
|
||||
|
||||
#[test]
|
||||
fn test_is_outdated_version() {
|
||||
|
@ -607,4 +761,21 @@ mod tests {
|
|||
false
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_check_semver_bump() {
|
||||
crate::test::reset();
|
||||
std::assert_eq!(check_semver_bump("20", "20.0.0"), None);
|
||||
std::assert_eq!(check_semver_bump("20.0", "20.0.0"), None);
|
||||
std::assert_eq!(check_semver_bump("20.0.0", "20.0.0"), None);
|
||||
std::assert_eq!(check_semver_bump("20", "21.0.0"), Some("21".to_string()));
|
||||
std::assert_eq!(
|
||||
check_semver_bump("20.0", "20.1.0"),
|
||||
Some("20.1".to_string())
|
||||
);
|
||||
std::assert_eq!(
|
||||
check_semver_bump("20.0.0", "20.0.1"),
|
||||
Some("20.0.1".to_string())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
use serde::ser::{Serialize, SerializeStruct, Serializer};
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::path::PathBuf;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use indexmap::{indexmap, IndexMap};
|
||||
use serde_derive::Serialize;
|
||||
|
||||
use crate::file::display_path;
|
||||
|
||||
/// where a tool version came from (e.g.: .tool-versions)
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ToolSource {
|
||||
ToolVersions(PathBuf),
|
||||
MiseToml(PathBuf),
|
||||
|
@ -29,6 +29,15 @@ impl Display for ToolSource {
|
|||
}
|
||||
|
||||
impl ToolSource {
|
||||
pub fn path(&self) -> Option<&Path> {
|
||||
match self {
|
||||
ToolSource::ToolVersions(path) => Some(path),
|
||||
ToolSource::MiseToml(path) => Some(path),
|
||||
ToolSource::LegacyVersionFile(path) => Some(path),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_json(&self) -> IndexMap<String, String> {
|
||||
match self {
|
||||
ToolSource::ToolVersions(path) => indexmap! {
|
||||
|
@ -36,7 +45,7 @@ impl ToolSource {
|
|||
"path".to_string() => path.to_string_lossy().to_string(),
|
||||
},
|
||||
ToolSource::MiseToml(path) => indexmap! {
|
||||
"type".to_string() => ".mise.toml".to_string(),
|
||||
"type".to_string() => "mise.toml".to_string(),
|
||||
"path".to_string() => path.to_string_lossy().to_string(),
|
||||
},
|
||||
ToolSource::LegacyVersionFile(path) => indexmap! {
|
||||
|
@ -55,6 +64,39 @@ impl ToolSource {
|
|||
}
|
||||
}
|
||||
|
||||
impl Serialize for ToolSource {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let mut s = serializer.serialize_struct("ToolSource", 3)?;
|
||||
match self {
|
||||
ToolSource::ToolVersions(path) => {
|
||||
s.serialize_field("type", ".tool-versions")?;
|
||||
s.serialize_field("path", path)?;
|
||||
}
|
||||
ToolSource::MiseToml(path) => {
|
||||
s.serialize_field("type", "mise.toml")?;
|
||||
s.serialize_field("path", path)?;
|
||||
}
|
||||
ToolSource::LegacyVersionFile(path) => {
|
||||
s.serialize_field("type", "legacy-version-file")?;
|
||||
s.serialize_field("path", path)?;
|
||||
}
|
||||
ToolSource::Argument => {
|
||||
s.serialize_field("type", "argument")?;
|
||||
}
|
||||
ToolSource::Environment(key, value) => {
|
||||
s.serialize_field("type", "environment")?;
|
||||
s.serialize_field("key", key)?;
|
||||
s.serialize_field("value", value)?;
|
||||
}
|
||||
}
|
||||
|
||||
s.end()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use pretty_assertions::{assert_eq, assert_str_eq};
|
||||
|
@ -96,7 +138,7 @@ mod tests {
|
|||
assert_eq!(
|
||||
ts.as_json(),
|
||||
indexmap! {
|
||||
"type".to_string() => ".mise.toml".to_string(),
|
||||
"type".to_string() => "mise.toml".to_string(),
|
||||
"path".to_string() => "/home/user/.mise.toml".to_string(),
|
||||
}
|
||||
);
|
||||
|
|
Loading…
Reference in New Issue