Compare commits

...

13 Commits

Author SHA1 Message Date
Thomas Schönauer
7256aaffc8 Resolve clippy errors (#131)
* Resolves clippy errors

* Fixes clippy errors

* Changes get_wsl_distributions arguments from pointer to value
2022-11-03 19:04:06 +00:00
Thomas Schönauer
66e0b94e85 Resolve clippy errors (#130)
* Resolves clippy errors

* Fixes clippy errors
2022-11-03 18:54:40 +00:00
Thomas Schönauer
4dcb5a214b GitHub action cleanups (#129)
* Changed clippy args for PR pipeline

* changes crates-publish toolchain version to stable

* Enhanced clippy for PRs
2022-11-03 18:53:37 +00:00
Thomas Schönauer
8731fd2b3f Resolves clippy errors (#128) 2022-11-03 18:29:22 +00:00
Thomas Schönauer
9233846479 Update release-cross.yml 2022-11-03 17:11:47 +00:00
Thomas Schönauer
94bdb8c3fd Update release.yml 2022-11-03 17:11:28 +00:00
Thomas Schönauer
632fcb5b77 Changes version requirements to latest path of minor version (#127) 2022-11-03 17:05:09 +00:00
Rebecca Turner
55ba2d30c1 Quote arguments when executing in a shell (#118)
* Quote arguments when executing in a shell

Fixes #107

* Parse quotes in `tmux_arguments`

This makes it possible to encode spaces in arguments. Maybe the config
value should be an array instead?

* Print error causes

Co-authored-by: Thomas Schönauer <37108907+DottoDev@users.noreply.github.com>
2022-11-03 16:46:43 +00:00
0xMRTT
ff66611ec0 fix: update debian os release (8->11) (#123) 2022-11-03 15:57:16 +01:00
pan93412
edc3dd6b0b refactor(ctrlc): Remove reduntant lazy_static (#109) 2022-11-03 15:57:16 +01:00
pan93412
623a11cf21 refactor!(steps/node): make PNPM a separated step (#114) 2022-11-03 15:57:16 +01:00
pan93412
7a83f38ca8 fix(steps/node): Only run global upgrade on Yarn 1.x (#112) 2022-11-03 15:57:16 +01:00
Thomas Schönauer
9a19b547c6 Revert clap version bump (#111)
* Update README.md

* Update release-cross.yml

* Update release.yml

* style(self_update): Run cargo fmt (#108)

The commit 9105a8aac is not formatted, which breaks the CI check.

Co-authored-by: Thomas Schönauer <37108907+DottoDev@users.noreply.github.com>

* Clap dependencie change

* Revert clap changes

Co-authored-by: pan93412 <pan93412@gmail.com>
2022-11-03 15:57:16 +01:00
17 changed files with 283 additions and 155 deletions

View File

@@ -10,7 +10,9 @@ name: Check and Lint
jobs: jobs:
check: check:
name: Check name: Check
runs-on: ubuntu-latest matrix:
platform: [ ubuntu-latest, macos-latest, windows-latest ]
runs-on: ${{ matrix.platform }}
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1 - uses: actions-rs/toolchain@v1
@@ -24,7 +26,9 @@ jobs:
fmt: fmt:
name: Rustfmt name: Rustfmt
runs-on: ubuntu-latest matrix:
platform: [ ubuntu-latest, macos-latest, windows-latest ]
runs-on: ${{ matrix.platform }}
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1 - uses: actions-rs/toolchain@v1
@@ -40,7 +44,9 @@ jobs:
clippy: clippy:
name: Clippy name: Clippy
runs-on: ubuntu-latest matrix:
platform: [ ubuntu-latest, macos-latest, windows-latest ]
runs-on: ${{ matrix.platform }}
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1 - uses: actions-rs/toolchain@v1
@@ -51,5 +57,10 @@ jobs:
- uses: actions-rs/clippy-check@v1 - uses: actions-rs/clippy-check@v1
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
args: --all-features args: --all-targets --locked -- -D warnings
name: Clippy Output name: Clippy Output
- uses: actions-rs/clippy-check@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --all-targets --locked --all-features -- -D warnings
name: Clippy (All features) Output

View File

@@ -15,7 +15,7 @@ jobs:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1 - uses: actions-rs/toolchain@v1
with: with:
toolchain: nightly-2022-08-03 toolchain: stable
override: true override: true
publish: publish:

View File

@@ -19,7 +19,7 @@ jobs:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1 - uses: actions-rs/toolchain@v1
with: with:
toolchain: 1.57.0 toolchain: stable
profile: minimal profile: minimal
default: true default: true
override: true override: true

View File

@@ -19,7 +19,7 @@ jobs:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1 - uses: actions-rs/toolchain@v1
with: with:
toolchain: 1.57.0 toolchain: stable
profile: minimal profile: minimal
override: true override: true
components: rustfmt, clippy components: rustfmt, clippy

87
Cargo.lock generated
View File

@@ -225,24 +225,26 @@ dependencies = [
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.0.18" version = "3.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "335867764ed2de42325fafe6d18b8af74ba97ee0c590fa016f157535b42ab04b" checksum = "d2dbdf4bdacb33466e854ce889eee8dfd5729abf7ccd7664d0a2d60cd384440b"
dependencies = [ dependencies = [
"atty", "atty",
"bitflags", "bitflags",
"clap_derive", "clap_derive",
"clap_lex", "clap_lex",
"once_cell", "indexmap",
"lazy_static",
"strsim", "strsim",
"termcolor", "termcolor",
"textwrap",
] ]
[[package]] [[package]]
name = "clap_derive" name = "clap_derive"
version = "4.0.18" version = "3.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16a1b0f6422af32d5da0c58e2703320f379216ee70198241c84173a8c5ac28f3" checksum = "25320346e922cffe59c0bbc5410c8d8784509efb321488971081313cb1e1a33c"
dependencies = [ dependencies = [
"heck 0.4.0", "heck 0.4.0",
"proc-macro-error", "proc-macro-error",
@@ -253,9 +255,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_lex" name = "clap_lex"
version = "0.3.0" version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d4198f73e42b4936b35b5bb248d81d2b595ecb170da0bac7655c54eedfa8da8" checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5"
dependencies = [ dependencies = [
"os_str_bytes", "os_str_bytes",
] ]
@@ -658,9 +660,9 @@ checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
[[package]] [[package]]
name = "h2" name = "h2"
version = "0.3.15" version = "0.3.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4" checksum = "62eeb471aa3e3c9197aa4bfeabfe02982f6dc96f750486c0bb0009ac58b26d2b"
dependencies = [ dependencies = [
"bytes", "bytes",
"fnv", "fnv",
@@ -983,14 +985,24 @@ dependencies = [
[[package]] [[package]]
name = "mio" name = "mio"
version = "0.8.5" version = "0.7.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc"
dependencies = [ dependencies = [
"libc", "libc",
"log", "log",
"wasi 0.11.0+wasi-snapshot-preview1", "miow",
"windows-sys", "ntapi",
"winapi",
]
[[package]]
name = "miow"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21"
dependencies = [
"winapi",
] ]
[[package]] [[package]]
@@ -1032,6 +1044,15 @@ dependencies = [
"zvariant_derive", "zvariant_derive",
] ]
[[package]]
name = "ntapi"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f"
dependencies = [
"winapi",
]
[[package]] [[package]]
name = "num-integer" name = "num-integer"
version = "0.1.45" version = "0.1.45"
@@ -1356,9 +1377,9 @@ dependencies = [
[[package]] [[package]]
name = "regex" name = "regex"
version = "1.6.0" version = "1.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" checksum = "d83f127d94bdbcda4c8cc2e50f6f84f4b611f69c902699ca385a39c3a75f9ff1"
dependencies = [ dependencies = [
"aho-corasick", "aho-corasick",
"memchr", "memchr",
@@ -1604,6 +1625,12 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012"
[[package]]
name = "shell-words"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde"
[[package]] [[package]]
name = "shellexpand" name = "shellexpand"
version = "2.1.2" version = "2.1.2"
@@ -1753,13 +1780,13 @@ dependencies = [
[[package]] [[package]]
name = "tempfile" name = "tempfile"
version = "3.3.0" version = "3.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"fastrand",
"libc", "libc",
"rand",
"redox_syscall", "redox_syscall",
"remove_dir_all", "remove_dir_all",
"winapi", "winapi",
@@ -1784,6 +1811,12 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "textwrap"
version = "0.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "949517c0cf1bf4ee812e2e07e08ab448e3ae0d23472aee8a06c985f0c8815b16"
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.37" version = "1.0.37"
@@ -1861,9 +1894,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.21.2" version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9e03c497dc955702ba729190dc4aac6f2a0ce97f913e5b1b5912fc5039d9099" checksum = "cd3b82e6e823a9ee7d7f64b08f8ac3d5f08ac988f23157194bd32af3f2f92767"
dependencies = [ dependencies = [
"autocfg", "autocfg",
"bytes", "bytes",
@@ -1871,9 +1904,9 @@ dependencies = [
"memchr", "memchr",
"mio", "mio",
"num_cpus", "num_cpus",
"once_cell",
"pin-project-lite", "pin-project-lite",
"signal-hook-registry", "signal-hook-registry",
"socket2",
"winapi", "winapi",
] ]
@@ -1890,16 +1923,16 @@ dependencies = [
[[package]] [[package]]
name = "tokio-util" name = "tokio-util"
version = "0.7.4" version = "0.6.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" checksum = "9e99e1983e5d376cd8eb4b66604d2e99e79f5bd988c3055891dcd8c9e2604cc0"
dependencies = [ dependencies = [
"bytes", "bytes",
"futures-core", "futures-core",
"futures-sink", "futures-sink",
"log",
"pin-project-lite", "pin-project-lite",
"tokio", "tokio",
"tracing",
] ]
[[package]] [[package]]
@@ -1935,6 +1968,7 @@ dependencies = [
"self_update", "self_update",
"semver", "semver",
"serde", "serde",
"shell-words",
"shellexpand", "shellexpand",
"strum 0.24.1", "strum 0.24.1",
"sys-info", "sys-info",
@@ -2202,13 +2236,12 @@ dependencies = [
[[package]] [[package]]
name = "which" name = "which"
version = "4.3.0" version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c831fbbee9e129a8cf93e7747a82da9d95ba8e16621cae60ec2cdc849bacb7b" checksum = "b55551e42cbdf2ce2bedd2203d0cc08dba002c27510f86dab6d0ce304cba3dfe"
dependencies = [ dependencies = [
"either", "either",
"libc", "libc",
"once_cell",
] ]
[[package]] [[package]]

View File

@@ -20,43 +20,44 @@ path = "src/main.rs"
##name = "topgrade_lib" ##name = "topgrade_lib"
[dependencies] [dependencies]
home = "0.5" home = "~0.5"
directories = "4.0" directories = "~4.0"
serde = { version = "1.0", features = ["derive"] } serde = { version = "~1.0", features = ["derive"] }
toml = "0.5" toml = "0.5"
which_crate = { version = "4.1", package = "which" } which_crate = { version = "~4.1", package = "which" }
shellexpand = "2.1" shellexpand = "~2.1"
clap = { version = "4.0.18", features = ["cargo", "derive"] } clap = { version = "~3.1", features = ["cargo", "derive"] }
log = "0.4" log = "~0.4"
walkdir = "2.3" walkdir = "~2.3"
console = "0.15" console = "~0.15"
lazy_static = "1.4" lazy_static = "~1.4"
chrono = "0.4" chrono = "~0.4"
pretty_env_logger = "0.4" pretty_env_logger = "~0.4"
glob = "0.3" glob = "~0.3"
strum = { version = "0.24", features = ["derive"] } strum = { version = "~0.24", features = ["derive"] }
thiserror = "1.0" thiserror = "~1.0"
anyhow = "1.0" anyhow = "~1.0"
tempfile = "3.2" tempfile = "~3.2"
cfg-if = "1.0" cfg-if = "~1.0"
tokio = { version = "1.5", features = ["process", "rt-multi-thread"] } tokio = { version = "~1.5", features = ["process", "rt-multi-thread"] }
futures = "0.3" futures = "~0.3"
regex = "1.5" regex = "~1.5"
sys-info = "0.9" sys-info = "~0.9"
semver = "1.0" semver = "~1.0"
shell-words = "~1.1"
[target.'cfg(target_os = "macos")'.dependencies] [target.'cfg(target_os = "macos")'.dependencies]
notify-rust = "4.5" notify-rust = "~4.5"
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]
nix = "0.24" nix = "~0.24"
rust-ini = "0.18" rust-ini = "~0.18"
self_update_crate = { version = "0.30", default-features = false, optional = true, package = "self_update", features = ["archive-tar", "compression-flate2", "rustls"] } self_update_crate = { version = "~0.30", default-features = false, optional = true, package = "self_update", features = ["archive-tar", "compression-flate2", "rustls"] }
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
self_update_crate = { version = "0.30", default-features = false, optional = true, package = "self_update", features = ["archive-zip", "compression-zip-deflate", "rustls"] } self_update_crate = { version = "~0.30", default-features = false, optional = true, package = "self_update", features = ["archive-zip", "compression-zip-deflate", "rustls"] }
winapi = "0.3" winapi = "~0.3"
parselnk = "0.1" parselnk = "~0.1"
[profile.release] [profile.release]
lto = true lto = true

View File

@@ -1,16 +1,16 @@
#![allow(dead_code)] #![allow(dead_code)]
use anyhow::Context;
use anyhow::Result;
use clap::{ArgEnum, Parser};
use directories::BaseDirs;
use log::debug;
use regex::Regex;
use serde::Deserialize;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::fs::write; use std::fs::write;
use std::path::PathBuf; use std::path::PathBuf;
use std::process::Command; use std::process::Command;
use std::{env, fs}; use std::{env, fs};
use anyhow::Result;
use clap::{Parser, ValueEnum};
use directories::BaseDirs;
use log::debug;
use regex::Regex;
use serde::Deserialize;
use strum::{EnumIter, EnumString, EnumVariantNames, IntoEnumIterator}; use strum::{EnumIter, EnumString, EnumVariantNames, IntoEnumIterator};
use sys_info::hostname; use sys_info::hostname;
use which_crate::which; use which_crate::which;
@@ -62,7 +62,7 @@ macro_rules! get_deprecated {
type Commands = BTreeMap<String, String>; type Commands = BTreeMap<String, String>;
#[derive(ValueEnum, EnumString, EnumVariantNames, Debug, Clone, PartialEq, Eq, Deserialize, EnumIter, Copy)] #[derive(ArgEnum, EnumString, EnumVariantNames, Debug, Clone, PartialEq, Eq, Deserialize, EnumIter, Copy)]
#[clap(rename_all = "snake_case")] #[clap(rename_all = "snake_case")]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
#[strum(serialize_all = "snake_case")] #[strum(serialize_all = "snake_case")]
@@ -397,78 +397,78 @@ impl ConfigFile {
// Command line arguments // Command line arguments
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
#[command(name = "Topgrade", version)] #[clap(name = "Topgrade", version)]
pub struct CommandLineArgs { pub struct CommandLineArgs {
/// Edit the configuration file /// Edit the configuration file
#[arg(long = "edit-config")] #[clap(long = "edit-config")]
edit_config: bool, edit_config: bool,
/// Show config reference /// Show config reference
#[arg(long = "config-reference")] #[clap(long = "config-reference")]
show_config_reference: bool, show_config_reference: bool,
/// Run inside tmux /// Run inside tmux
#[arg(short = 't', long = "tmux")] #[clap(short = 't', long = "tmux")]
run_in_tmux: bool, run_in_tmux: bool,
/// Cleanup temporary or old files /// Cleanup temporary or old files
#[arg(short = 'c', long = "cleanup")] #[clap(short = 'c', long = "cleanup")]
cleanup: bool, cleanup: bool,
/// Print what would be done /// Print what would be done
#[arg(short = 'n', long = "dry-run")] #[clap(short = 'n', long = "dry-run")]
dry_run: bool, dry_run: bool,
/// Do not ask to retry failed steps /// Do not ask to retry failed steps
#[arg(long = "no-retry")] #[clap(long = "no-retry")]
no_retry: bool, no_retry: bool,
/// Do not perform upgrades for the given steps /// Do not perform upgrades for the given steps
#[arg(long = "disable", value_name = "STEP", value_enum, num_args = 1..)] #[clap(long = "disable", arg_enum, multiple_values = true)]
disable: Vec<Step>, disable: Vec<Step>,
/// Perform only the specified steps (experimental) /// Perform only the specified steps (experimental)
#[arg(long = "only", value_name = "STEP", value_enum, num_args = 1..)] #[clap(long = "only", arg_enum, multiple_values = true)]
only: Vec<Step>, only: Vec<Step>,
/// Run only specific custom commands /// Run only specific custom commands
#[arg(long = "custom-commands", value_name = "NAME", num_args = 1..)] #[clap(long = "custom-commands")]
custom_commands: Vec<String>, custom_commands: Vec<String>,
/// Set environment variables /// Set environment variables
#[arg(long = "env", value_name = "NAME=VALUE", num_args = 1..)] #[clap(long = "env", multiple_values = true)]
env: Vec<String>, env: Vec<String>,
/// Output logs /// Output logs
#[arg(short = 'v', long = "verbose")] #[clap(short = 'v', long = "verbose")]
pub verbose: bool, pub verbose: bool,
/// Prompt for a key before exiting /// Prompt for a key before exiting
#[arg(short = 'k', long = "keep")] #[clap(short = 'k', long = "keep")]
keep_at_end: bool, keep_at_end: bool,
/// Skip sending a notification at the end of a run /// Skip sending a notification at the end of a run
#[arg(long = "skip-notify")] #[clap(long = "skip-notify")]
skip_notify: bool, skip_notify: bool,
/// Say yes to package manager's prompt /// Say yes to package manager's prompt
#[arg(short = 'y', long = "yes", value_name = "STEP", value_enum, num_args = 0..)] #[clap(short = 'y', long = "yes", arg_enum, multiple_values = true, min_values = 0)]
yes: Option<Vec<Step>>, yes: Option<Vec<Step>>,
/// Don't pull the predefined git repos /// Don't pull the predefined git repos
#[arg(long = "disable-predefined-git-repos")] #[clap(long = "disable-predefined-git-repos")]
disable_predefined_git_repos: bool, disable_predefined_git_repos: bool,
/// Alternative configuration file /// Alternative configuration file
#[arg(long = "config", value_name = "PATH")] #[clap(long = "config")]
config: Option<PathBuf>, config: Option<PathBuf>,
/// A regular expression for restricting remote host execution /// A regular expression for restricting remote host execution
#[arg(long = "remote-host-limit", value_name = "REGEX")] #[clap(long = "remote-host-limit")]
remote_host_limit: Option<Regex>, remote_host_limit: Option<Regex>,
/// Show the reason for skipped steps /// Show the reason for skipped steps
#[arg(long = "show-skipped")] #[clap(long = "show-skipped")]
show_skipped: bool, show_skipped: bool,
} }
@@ -626,8 +626,16 @@ impl Config {
} }
/// Extra Tmux arguments /// Extra Tmux arguments
pub fn tmux_arguments(&self) -> &Option<String> { pub fn tmux_arguments(&self) -> anyhow::Result<Vec<String>> {
&self.config_file.tmux_arguments let args = &self.config_file.tmux_arguments.as_deref().unwrap_or_default();
shell_words::split(args)
// The only time the parse failed is in case of a missing close quote.
// The error message looks like this:
// Error: Failed to parse `tmux_arguments`: `'foo`
//
// Caused by:
// missing closing quote
.with_context(|| format!("Failed to parse `tmux_arguments`: `{args}`"))
} }
/// Prompt for a key before exiting /// Prompt for a key before exiting

View File

@@ -1,10 +1,7 @@
use lazy_static::lazy_static;
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
lazy_static! { /// A global variable telling whether the application has been interrupted.
/// A global variable telling whether the application has been interrupted. static INTERRUPTED: AtomicBool = AtomicBool::new(false);
static ref INTERRUPTED: AtomicBool = AtomicBool::new(false);
}
/// Tells whether the program has been interrupted /// Tells whether the program has been interrupted
pub fn interrupted() -> bool { pub fn interrupted() -> bool {

View File

@@ -194,11 +194,12 @@ impl DryCommand {
print!( print!(
"Dry running: {} {}", "Dry running: {} {}",
self.program.to_string_lossy(), self.program.to_string_lossy(),
self.args shell_words::join(
.iter() self.args
.map(|a| String::from(a.to_string_lossy())) .iter()
.collect::<Vec<String>>() .map(|a| String::from(a.to_string_lossy()))
.join(" ") .collect::<Vec<String>>()
)
); );
match &self.directory { match &self.directory {
Some(dir) => println!(" in {}", dir.to_string_lossy()), Some(dir) => println!(" in {}", dir.to_string_lossy()),

View File

@@ -79,7 +79,7 @@ fn run() -> Result<()> {
if config.run_in_tmux() && env::var("TOPGRADE_INSIDE_TMUX").is_err() { if config.run_in_tmux() && env::var("TOPGRADE_INSIDE_TMUX").is_err() {
#[cfg(unix)] #[cfg(unix)]
{ {
tmux::run_in_tmux(config.tmux_arguments()); tmux::run_in_tmux(config.tmux_arguments()?);
} }
} }
@@ -351,6 +351,7 @@ fn run() -> Result<()> {
runner.execute(Step::Kakoune, "Kakoune", || kakoune::upgrade_kak_plug(&ctx))?; runner.execute(Step::Kakoune, "Kakoune", || kakoune::upgrade_kak_plug(&ctx))?;
runner.execute(Step::Node, "npm", || node::run_npm_upgrade(&ctx))?; runner.execute(Step::Node, "npm", || node::run_npm_upgrade(&ctx))?;
runner.execute(Step::Node, "yarn", || node::run_yarn_upgrade(&ctx))?; runner.execute(Step::Node, "yarn", || node::run_yarn_upgrade(&ctx))?;
runner.execute(Step::Node, "pnpm", || node::run_pnpm_upgrade(&ctx))?;
runner.execute(Step::Containers, "Containers", || containers::run_containers(&ctx))?; runner.execute(Step::Containers, "Containers", || containers::run_containers(&ctx))?;
runner.execute(Step::Deno, "deno", || node::deno_upgrade(&ctx))?; runner.execute(Step::Deno, "deno", || node::deno_upgrade(&ctx))?;
runner.execute(Step::Composer, "composer", || generic::run_composer_update(&ctx))?; runner.execute(Step::Composer, "composer", || generic::run_composer_update(&ctx))?;
@@ -523,7 +524,10 @@ fn main() {
.is_some()); .is_some());
if !skip_print { if !skip_print {
println!("Error: {}", error); // The `Debug` implementation of `anyhow::Result` prints a multi-line
// error message that includes all the 'causes' added with
// `.with_context(...)` calls.
println!("Error: {:?}", error);
} }
exit(1); exit(1);
} }

View File

@@ -1,5 +1,6 @@
#![allow(unused_imports)] #![allow(unused_imports)]
use std::fmt::Display;
#[cfg(unix)] #[cfg(unix)]
use std::os::unix::prelude::MetadataExt; use std::os::unix::prelude::MetadataExt;
use std::path::PathBuf; use std::path::PathBuf;
@@ -17,24 +18,71 @@ use crate::terminal::print_separator;
use crate::utils::{require, PathExt}; use crate::utils::{require, PathExt};
use crate::{error::SkipStep, execution_context::ExecutionContext}; use crate::{error::SkipStep, execution_context::ExecutionContext};
enum NPMVariant {
Npm,
Pnpm,
}
impl NPMVariant {
const fn long_name(&self) -> &str {
match self {
NPMVariant::Npm => "Node Package Manager",
NPMVariant::Pnpm => "PNPM",
}
}
const fn short_name(&self) -> &str {
match self {
NPMVariant::Npm => "npm",
NPMVariant::Pnpm => "pnpm",
}
}
const fn is_npm(&self) -> bool {
matches!(self, NPMVariant::Npm)
}
}
impl Display for NPMVariant {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.short_name())
}
}
#[allow(clippy::upper_case_acronyms)] #[allow(clippy::upper_case_acronyms)]
struct NPM { struct NPM {
command: PathBuf, command: PathBuf,
variant: NPMVariant,
} }
impl NPM { impl NPM {
fn new(command: PathBuf) -> Self { fn new(command: PathBuf, variant: NPMVariant) -> Self {
Self { command } Self { command, variant }
}
/// Is the “NPM” version larger than 8.11.0?
fn is_npm_8(&self) -> bool {
let v = self.version();
self.variant.is_npm() && matches!(v, Ok(v) if v >= Version::new(8, 11, 0))
}
/// Get the most suitable “global location” argument
/// of this NPM instance.
///
/// If the “NPM” version is larger than 8.11.0, we use
/// `--location=global`; otherwise, use `-g`.
fn global_location_arg(&self) -> &str {
if self.is_npm_8() {
"--location=global"
} else {
"-g"
}
} }
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
fn root(&self) -> Result<PathBuf> { fn root(&self) -> Result<PathBuf> {
let version = self.version()?; let args = ["root", self.global_location_arg()];
let args = if version < Version::new(8, 11, 0) {
["root", "-g"]
} else {
["root", "--location=global"]
};
Command::new(&self.command) Command::new(&self.command)
.args(args) .args(args)
.check_output() .check_output()
@@ -50,13 +98,8 @@ impl NPM {
} }
fn upgrade(&self, run_type: RunType, use_sudo: bool) -> Result<()> { fn upgrade(&self, run_type: RunType, use_sudo: bool) -> Result<()> {
print_separator("Node Package Manager"); print_separator(self.variant.long_name());
let version = self.version()?; let args = ["update", self.global_location_arg()];
let args = if version < Version::new(8, 11, 0) {
["update", "-g"]
} else {
["update", "--location=global"]
};
if use_sudo { if use_sudo {
run_type.execute("sudo").args(args).check_run()?; run_type.execute("sudo").args(args).check_run()?;
} else { } else {
@@ -70,7 +113,7 @@ impl NPM {
pub fn should_use_sudo(&self) -> Result<bool> { pub fn should_use_sudo(&self) -> Result<bool> {
let npm_root = self.root()?; let npm_root = self.root()?;
if !npm_root.exists() { if !npm_root.exists() {
return Err(SkipStep(format!("NPM root at {} doesn't exist", npm_root.display(),)).into()); return Err(SkipStep(format!("{} root at {} doesn't exist", self.variant, npm_root.display())).into());
} }
let metadata = std::fs::metadata(&npm_root)?; let metadata = std::fs::metadata(&npm_root)?;
@@ -93,6 +136,17 @@ impl Yarn {
} }
} }
fn has_global_subcmd(&self) -> bool {
// Get the version of Yarn. After Yarn 2.x (berry),
// “yarn global” has been replaced with “yarn dlx”.
//
// As “yarn dlx” don't need to “upgrade”, we
// ignore the whole task if Yarn is 2.x or above.
let version = Command::new(&self.command).args(["--version"]).check_output();
matches!(version, Ok(ver) if ver.starts_with('1') || ver.starts_with('0'))
}
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
fn root(&self) -> Result<PathBuf> { fn root(&self) -> Result<PathBuf> {
let args = ["global", "dir"]; let args = ["global", "dir"];
@@ -123,7 +177,7 @@ impl Yarn {
pub fn should_use_sudo(&self) -> Result<bool> { pub fn should_use_sudo(&self) -> Result<bool> {
let yarn_root = self.root()?; let yarn_root = self.root()?;
if !yarn_root.exists() { if !yarn_root.exists() {
return Err(SkipStep(format!("NPM root at {} doesn't exist", yarn_root.display(),)).into()); return Err(SkipStep(format!("Yarn root at {} doesn't exist", yarn_root.display(),)).into());
} }
let metadata = std::fs::metadata(&yarn_root)?; let metadata = std::fs::metadata(&yarn_root)?;
@@ -162,7 +216,7 @@ fn should_use_sudo_yarn(yarn: &Yarn, ctx: &ExecutionContext) -> Result<bool> {
} }
pub fn run_npm_upgrade(ctx: &ExecutionContext) -> Result<()> { pub fn run_npm_upgrade(ctx: &ExecutionContext) -> Result<()> {
let npm = require("pnpm").or_else(|_| require("npm")).map(NPM::new)?; let npm = require("npm").map(|b| NPM::new(b, NPMVariant::Npm))?;
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
{ {
@@ -175,9 +229,28 @@ pub fn run_npm_upgrade(ctx: &ExecutionContext) -> Result<()> {
} }
} }
pub fn run_pnpm_upgrade(ctx: &ExecutionContext) -> Result<()> {
let pnpm = require("pnpm").map(|b| NPM::new(b, NPMVariant::Pnpm))?;
#[cfg(target_os = "linux")]
{
pnpm.upgrade(ctx.run_type(), should_use_sudo(&pnpm, ctx)?)
}
#[cfg(not(target_os = "linux"))]
{
pnpm.upgrade(ctx.run_type(), false)
}
}
pub fn run_yarn_upgrade(ctx: &ExecutionContext) -> Result<()> { pub fn run_yarn_upgrade(ctx: &ExecutionContext) -> Result<()> {
let yarn = require("yarn").map(Yarn::new)?; let yarn = require("yarn").map(Yarn::new)?;
if !yarn.has_global_subcmd() {
debug!("Yarn is 2.x or above, skipping global upgrade");
return Ok(());
}
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
{ {
yarn.upgrade(ctx.run_type(), should_use_sudo_yarn(&yarn, ctx)?) yarn.upgrade(ctx.run_type(), should_use_sudo_yarn(&yarn, ctx)?)

View File

@@ -245,7 +245,7 @@ impl Aura {
impl ArchPackageManager for Aura { impl ArchPackageManager for Aura {
fn upgrade(&self, ctx: &ExecutionContext) -> Result<()> { fn upgrade(&self, ctx: &ExecutionContext) -> Result<()> {
let sudo = which("sudo").unwrap_or(PathBuf::new()); let sudo = which("sudo").unwrap_or_else(PathBuf::new);
let mut aur_update = ctx.run_type().execute(&sudo); let mut aur_update = ctx.run_type().execute(&sudo);
if sudo.ends_with("sudo") { if sudo.ends_with("sudo") {

View File

@@ -1,8 +1,9 @@
PRETTY_NAME="Debian GNU/Linux 8 (jessie)" PRETTY_NAME="Debian GNU/Linux 11 (bullseye)"
NAME="Debian GNU/Linux" NAME="Debian GNU/Linux"
VERSION_ID="8" VERSION_ID="11"
VERSION="8 (jessie)" VERSION="11 (bullseye)"
VERSION_CODENAME=bullseye
ID=debian ID=debian
HOME_URL="http://www.debian.org/" HOME_URL="https://www.debian.org/"
SUPPORT_URL="http://www.debian.org/support" SUPPORT_URL="https://www.debian.org/support"
BUG_REPORT_URL="https://bugs.debian.org/" BUG_REPORT_URL="https://bugs.debian.org/"

View File

@@ -26,7 +26,7 @@ pub fn run_chocolatey(ctx: &ExecutionContext) -> Result<()> {
args.insert(0, "choco"); args.insert(0, "choco");
} }
let mut command = ctx.run_type().execute(&cmd); let mut command = ctx.run_type().execute(cmd);
command.args(&args); command.args(&args);
@@ -47,7 +47,7 @@ pub fn run_winget(ctx: &ExecutionContext) -> Result<()> {
return Err(SkipStep(String::from("Winget is disabled by default")).into()); return Err(SkipStep(String::from("Winget is disabled by default")).into());
} }
ctx.run_type().execute(&winget).args(&["upgrade", "--all"]).check_run() ctx.run_type().execute(&winget).args(["upgrade", "--all"]).check_run()
} }
pub fn run_scoop(cleanup: bool, run_type: RunType) -> Result<()> { pub fn run_scoop(cleanup: bool, run_type: RunType) -> Result<()> {
@@ -55,34 +55,34 @@ pub fn run_scoop(cleanup: bool, run_type: RunType) -> Result<()> {
print_separator("Scoop"); print_separator("Scoop");
run_type.execute(&scoop).args(&["update"]).check_run()?; run_type.execute(&scoop).args(["update"]).check_run()?;
run_type.execute(&scoop).args(&["update", "*"]).check_run()?; run_type.execute(&scoop).args(["update", "*"]).check_run()?;
if cleanup { if cleanup {
run_type.execute(&scoop).args(&["cleanup", "*"]).check_run()?; run_type.execute(&scoop).args(["cleanup", "*"]).check_run()?;
} }
Ok(()) Ok(())
} }
fn get_wsl_distributions(wsl: &Path) -> Result<Vec<String>> { fn get_wsl_distributions(wsl: Path) -> Result<Vec<String>> {
let output = Command::new(wsl).args(&["--list", "-q"]).check_output()?; let output = Command::new(wsl).args(["--list", "-q"]).check_output()?;
Ok(output Ok(output
.lines() .lines()
.filter(|s| !s.is_empty()) .filter(|s| !s.is_empty())
.map(|x| x.replace('\u{0}', "").replace('\r', "")) .map(|x| x.replace(['\u{0}', '\r'], ""))
.collect()) .collect())
} }
fn upgrade_wsl_distribution(wsl: &Path, dist: &str, ctx: &ExecutionContext) -> Result<()> { fn upgrade_wsl_distribution(wsl: &Path, dist: &str, ctx: &ExecutionContext) -> Result<()> {
let topgrade = Command::new(&wsl) let topgrade = Command::new(wsl)
.args(&["-d", dist, "bash", "-lc", "which topgrade"]) .args(["-d", dist, "bash", "-lc", "which topgrade"])
.check_output() .check_output()
.map_err(|_| SkipStep(String::from("Could not find Topgrade installed in WSL")))?; .map_err(|_| SkipStep(String::from("Could not find Topgrade installed in WSL")))?;
let mut command = ctx.run_type().execute(&wsl); let mut command = ctx.run_type().execute(&wsl);
command command
.args(&["-d", dist, "bash", "-c"]) .args(["-d", dist, "bash", "-c"])
.arg(format!("TOPGRADE_PREFIX={} exec {}", dist, topgrade)); .arg(format!("TOPGRADE_PREFIX={} exec {}", dist, topgrade));
if ctx.config().yes(Step::Wsl) { if ctx.config().yes(Step::Wsl) {
@@ -94,7 +94,7 @@ fn upgrade_wsl_distribution(wsl: &Path, dist: &str, ctx: &ExecutionContext) -> R
pub fn run_wsl_topgrade(ctx: &ExecutionContext) -> Result<()> { pub fn run_wsl_topgrade(ctx: &ExecutionContext) -> Result<()> {
let wsl = require("wsl")?; let wsl = require("wsl")?;
let wsl_distributions = get_wsl_distributions(&wsl)?; let wsl_distributions = get_wsl_distributions(wsl)?;
let mut ran = false; let mut ran = false;
debug!("WSL distributions: {:?}", wsl_distributions); debug!("WSL distributions: {:?}", wsl_distributions);
@@ -134,7 +134,7 @@ pub fn windows_update(ctx: &ExecutionContext) -> Result<()> {
} }
pub fn reboot() { pub fn reboot() {
Command::new("shutdown").args(&["/R", "/T", "0"]).spawn().ok(); Command::new("shutdown").args(["/R", "/T", "0"]).spawn().ok();
} }
pub fn insert_startup_scripts(ctx: &ExecutionContext, git_repos: &mut Repositories) -> Result<()> { pub fn insert_startup_scripts(ctx: &ExecutionContext, git_repos: &mut Repositories) -> Result<()> {

View File

@@ -46,8 +46,8 @@ impl Powershell {
#[cfg(windows)] #[cfg(windows)]
pub fn has_module(powershell: &Path, command: &str) -> bool { pub fn has_module(powershell: &Path, command: &str) -> bool {
Command::new(&powershell) Command::new(powershell)
.args(&[ .args([
"-NoProfile", "-NoProfile",
"-Command", "-Command",
&format!("Get-Module -ListAvailable {}", command), &format!("Get-Module -ListAvailable {}", command),
@@ -79,6 +79,7 @@ impl Powershell {
println!("Updating modules..."); println!("Updating modules...");
ctx.run_type() ctx.run_type()
.execute(powershell) .execute(powershell)
// This probably doesn't need `shell_words::join`.
.args(["-NoProfile", "-Command", &cmd.join(" ")]) .args(["-NoProfile", "-Command", &cmd.join(" ")])
.check_run() .check_run()
} }
@@ -99,14 +100,14 @@ impl Powershell {
let mut command = if let Some(sudo) = ctx.sudo() { let mut command = if let Some(sudo) = ctx.sudo() {
let mut command = ctx.run_type().execute(sudo); let mut command = ctx.run_type().execute(sudo);
command.arg(&powershell); command.arg(powershell);
command command
} else { } else {
ctx.run_type().execute(&powershell) ctx.run_type().execute(powershell)
}; };
command command
.args(&[ .args([
"-NoProfile", "-NoProfile",
"-Command", "-Command",
&format!( &format!(

View File

@@ -24,7 +24,7 @@ pub fn ssh_step(ctx: &ExecutionContext, hostname: &str) -> Result<()> {
#[cfg(unix)] #[cfg(unix)]
{ {
prepare_async_ssh_command(&mut args); prepare_async_ssh_command(&mut args);
crate::tmux::run_command(ctx, &args.join(" "))?; crate::tmux::run_command(ctx, &shell_words::join(args))?;
Err(SkipStep(String::from("Remote Topgrade launched in Tmux")).into()) Err(SkipStep(String::from("Remote Topgrade launched in Tmux")).into())
} }

View File

@@ -29,12 +29,10 @@ struct Tmux {
} }
impl Tmux { impl Tmux {
fn new(args: &Option<String>) -> Self { fn new(args: Vec<String>) -> Self {
Self { Self {
tmux: which("tmux").expect("Could not find tmux"), tmux: which("tmux").expect("Could not find tmux"),
args: args args: if args.is_empty() { None } else { Some(args) },
.as_ref()
.map(|args| args.split_whitespace().map(String::from).collect()),
} }
} }
@@ -75,7 +73,7 @@ impl Tmux {
} }
} }
pub fn run_in_tmux(args: &Option<String>) -> ! { pub fn run_in_tmux(args: Vec<String>) -> ! {
let command = { let command = {
let mut command = vec![ let mut command = vec![
String::from("env"), String::from("env"),
@@ -83,7 +81,7 @@ pub fn run_in_tmux(args: &Option<String>) -> ! {
String::from("TOPGRADE_INSIDE_TMUX=1"), String::from("TOPGRADE_INSIDE_TMUX=1"),
]; ];
command.extend(env::args()); command.extend(env::args());
command.join(" ") shell_words::join(command)
}; };
let tmux = Tmux::new(args); let tmux = Tmux::new(args);
@@ -108,7 +106,7 @@ pub fn run_in_tmux(args: &Option<String>) -> ! {
} }
pub fn run_command(ctx: &ExecutionContext, command: &str) -> Result<()> { pub fn run_command(ctx: &ExecutionContext, command: &str) -> Result<()> {
Tmux::new(ctx.config().tmux_arguments()) Tmux::new(ctx.config().tmux_arguments()?)
.build() .build()
.args(["new-window", "-a", "-t", "topgrade:1", command]) .args(["new-window", "-a", "-t", "topgrade:1", command])
.env_remove("TMUX") .env_remove("TMUX")