Compare commits
84 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ef0a0d69bb | ||
|
|
4b3a3e74f8 | ||
|
|
2c4751c7b2 | ||
|
|
30941ed26d | ||
|
|
c7163b63db | ||
|
|
6e6b3dcbfe | ||
|
|
1d136a6635 | ||
|
|
0ee67d78ef | ||
|
|
7356b920d4 | ||
|
|
ce8a325c1f | ||
|
|
a2f57e4769 | ||
|
|
751f41bc5e | ||
|
|
fd406f0f82 | ||
|
|
801dddacd4 | ||
|
|
397a537eef | ||
|
|
0423c836eb | ||
|
|
3250337e70 | ||
|
|
9dcd7fffe2 | ||
|
|
30b727b138 | ||
|
|
b86d6981ab | ||
|
|
2bf6a2b100 | ||
|
|
3dc8d31d57 | ||
|
|
b308fb92c0 | ||
|
|
bc9746455e | ||
|
|
109a9c76e3 | ||
|
|
4488f3d5d3 | ||
|
|
5a7958d20e | ||
|
|
481a942b76 | ||
|
|
a601d8429d | ||
|
|
a4a2d52a6d | ||
|
|
47fa3ba7de | ||
|
|
e6bb6709b3 | ||
|
|
c421742c4f | ||
|
|
1312cc8f6e | ||
|
|
ed37763d30 | ||
|
|
583bbf65e2 | ||
|
|
5770a5caa7 | ||
|
|
722903fec3 | ||
|
|
30f1c3c1b4 | ||
|
|
ef7d146282 | ||
|
|
20667a23d3 | ||
|
|
26f05827ae | ||
|
|
b1ffe7d553 | ||
|
|
368a060529 | ||
|
|
b40bffb1f2 | ||
|
|
488ae149f7 | ||
|
|
fa3e4726b7 | ||
|
|
66a12cc8bf | ||
|
|
3e0c21e981 | ||
|
|
da270ae7d9 | ||
|
|
4624f11ba5 | ||
|
|
224bb96a98 | ||
|
|
9a6fe8eea9 | ||
|
|
aebc035ec0 | ||
|
|
bd348c328e | ||
|
|
c5f2d7b473 | ||
|
|
dc9d8d55f2 | ||
|
|
b172ba7f03 | ||
|
|
8227890808 | ||
|
|
a0963fe3fc | ||
|
|
4df30c2587 | ||
|
|
305a5fbcae | ||
|
|
4f4dcbb643 | ||
|
|
202897ba35 | ||
|
|
444689c899 | ||
|
|
98ec13f8db | ||
|
|
39f76a3a71 | ||
|
|
f181a795a6 | ||
|
|
ea2f3e07e9 | ||
|
|
8aad6eae0d | ||
|
|
e86e5fe3e7 | ||
|
|
2c2569c4f8 | ||
|
|
9ffdc9649e | ||
|
|
a5d4f2eec9 | ||
|
|
a5df40e01d | ||
|
|
0573fc97c6 | ||
|
|
1ae95f41a1 | ||
|
|
8a7af2e14d | ||
|
|
c36da89933 | ||
|
|
bbb84c2ee7 | ||
|
|
36fd4b13c0 | ||
|
|
49327000fc | ||
|
|
9c25cd7426 | ||
|
|
9767e4169c |
3
.github/PULL_REQUEST_TEMPLATE.md
vendored
3
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -3,9 +3,10 @@
|
||||
|
||||
## Standards checklist
|
||||
|
||||
- [ ] The PR title is descriptive.
|
||||
- [ ] The PR title is descriptive
|
||||
- [ ] I have read `CONTRIBUTING.md`
|
||||
- [ ] *Optional:* I have tested the code myself
|
||||
- [ ] If this PR introduces new user-facing messages they are translated
|
||||
|
||||
## For new steps
|
||||
|
||||
|
||||
26
.github/workflows/ci.yml
vendored
26
.github/workflows/ci.yml
vendored
@@ -22,10 +22,28 @@ jobs:
|
||||
env:
|
||||
TERM: xterm-256color
|
||||
run: |
|
||||
rustup component add rustfmt
|
||||
cargo fmt --all -- --check
|
||||
|
||||
step-enum-sorted:
|
||||
name: Step enum sorted
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Check if `Step` enum is sorted
|
||||
run: |
|
||||
ENUM_NAME="Step"
|
||||
FILE="src/config.rs"
|
||||
awk "/enum $ENUM_NAME/,/}/" "$FILE" | \
|
||||
grep -E '^\s*[A-Za-z_][A-Za-z0-9_]*\s*,?$' | \
|
||||
sed 's/[, ]//g' > original.txt
|
||||
sort original.txt > sorted.txt
|
||||
diff original.txt sorted.txt
|
||||
|
||||
main:
|
||||
needs: fmt
|
||||
needs: [fmt, step-enum-sorted]
|
||||
name: ${{ matrix.target_name }} (check, clippy)
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
@@ -79,9 +97,11 @@ jobs:
|
||||
run: ${{ matrix.use_cross == true && 'cross' || 'cargo' }} check --locked --target ${{ matrix.target }}
|
||||
|
||||
- name: Run cargo/cross clippy
|
||||
run: ${{ matrix.use_cross == true && 'cross' || 'cargo' }} clippy --locked --target ${{ matrix.target }} --all-features -- -D warnings
|
||||
run: |
|
||||
rustup component add clippy
|
||||
${{ matrix.use_cross == true && 'cross' || 'cargo' }} clippy --locked --target ${{ matrix.target }} --all-features -- -D warnings
|
||||
|
||||
- name: Run cargo test
|
||||
# ONLY run test with cargo
|
||||
if: matrix.use_cross == false
|
||||
run: cargo test --locked --target ${{ matrix.target }}
|
||||
run: cargo test --locked --target ${{ matrix.target }}
|
||||
|
||||
105
.github/workflows/create_release_assets.yml
vendored
105
.github/workflows/create_release_assets.yml
vendored
@@ -1,26 +1,23 @@
|
||||
name: Publish release files for CD native environments
|
||||
name: Publish release files for CD native and non-cd-native environments
|
||||
|
||||
on:
|
||||
# workflow_run:
|
||||
# workflows: ["Check SemVer compliance"]
|
||||
# types:
|
||||
# - completed
|
||||
release:
|
||||
types: [ created ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
# Publish release files for CD native environments
|
||||
native_build:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: [ ubuntu-latest, macos-latest, macos-13, windows-latest ]
|
||||
platform: [ ubuntu-22.04, macos-latest, macos-13, windows-latest ]
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install cargo-deb
|
||||
run: cargo install cargo-deb
|
||||
if: ${{ matrix.platform == 'ubuntu-latest' }}
|
||||
if: ${{ startsWith(matrix.platform, 'ubuntu-') }}
|
||||
shell: bash
|
||||
|
||||
- name: Check format
|
||||
@@ -59,14 +56,14 @@ jobs:
|
||||
rm -rf target/release
|
||||
cargo build --release
|
||||
cargo deb --no-build --no-strip
|
||||
if: ${{ matrix.platform == 'ubuntu-latest' }}
|
||||
if: ${{ startsWith(matrix.platform, 'ubuntu-') }}
|
||||
shell: bash
|
||||
|
||||
- name: Move Debian-based system package
|
||||
run: |
|
||||
mkdir -p assets
|
||||
mv target/debian/*.deb assets
|
||||
if: ${{ matrix.platform == 'ubuntu-latest' }}
|
||||
if: ${{ startsWith(matrix.platform, 'ubuntu-') }}
|
||||
shell: bash
|
||||
|
||||
- name: Rename Release (Windows)
|
||||
@@ -86,3 +83,91 @@ jobs:
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: assets/*
|
||||
|
||||
# Publish release files for non-CD-native environments
|
||||
cross_build:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target:
|
||||
[
|
||||
"aarch64-unknown-linux-gnu",
|
||||
"armv7-unknown-linux-gnueabihf",
|
||||
"x86_64-unknown-linux-musl",
|
||||
"aarch64-unknown-linux-musl",
|
||||
"x86_64-unknown-freebsd",
|
||||
]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install cargo-deb cross compilation dependencies
|
||||
run: sudo apt-get install libc6-arm64-cross libgcc-s1-arm64-cross
|
||||
if: ${{ matrix.target == 'aarch64-unknown-linux-gnu' }}
|
||||
shell: bash
|
||||
|
||||
- name: Install cargo-deb cross compilation dependencies for armv7
|
||||
run: sudo apt-get install libc6-armhf-cross libgcc-s1-armhf-cross
|
||||
if: ${{ matrix.target == 'armv7-unknown-linux-gnueabihf' }}
|
||||
shell: bash
|
||||
|
||||
- name: Install cargo-deb
|
||||
run: cargo install cargo-deb
|
||||
if: ${{ matrix.target == 'aarch64-unknown-linux-gnu' || matrix.target == 'armv7-unknown-linux-gnueabihf' }}
|
||||
shell: bash
|
||||
|
||||
- name: install targets
|
||||
run: rustup target add ${{ matrix.target }}
|
||||
|
||||
- name: install cross
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: cross@0.2.5
|
||||
|
||||
- name: Check format
|
||||
run: cross fmt --all -- --check
|
||||
|
||||
- name: Run clippy
|
||||
run: cross clippy --all-targets --locked --target ${{matrix.target}} -- -D warnings
|
||||
|
||||
- name: Run clippy (All features)
|
||||
run: cross clippy --locked --all-features --target ${{matrix.target}} -- -D warnings
|
||||
|
||||
- name: Run tests
|
||||
run: cross test --target ${{matrix.target}}
|
||||
|
||||
- name: Build in Release profile with all features enabled
|
||||
run: cross build --release --all-features --target ${{matrix.target}}
|
||||
|
||||
- name: Rename Release
|
||||
run: |
|
||||
mkdir -p assets
|
||||
FILENAME=topgrade-${{github.event.release.tag_name}}-${{matrix.target}}
|
||||
mv target/${{matrix.target}}/release/topgrade assets
|
||||
cd assets
|
||||
tar --format=ustar -czf $FILENAME.tar.gz topgrade
|
||||
rm topgrade
|
||||
ls .
|
||||
|
||||
- name: Build Debian-based system package without autoupdate feature
|
||||
# First remove the binary built by previous steps
|
||||
# because we don't want the auto-update feature,
|
||||
# then build the new binary without auto-updating.
|
||||
run: |
|
||||
rm -rf target/${{matrix.target}}
|
||||
cross build --release --target ${{matrix.target}}
|
||||
cargo deb --target=${{matrix.target}} --no-build --no-strip
|
||||
if: ${{ matrix.target == 'aarch64-unknown-linux-gnu' || matrix.target == 'armv7-unknown-linux-gnueabihf' }}
|
||||
shell: bash
|
||||
|
||||
- name: Move Debian-based system package
|
||||
run: |
|
||||
mkdir -p assets
|
||||
mv target/${{matrix.target}}/debian/*.deb assets
|
||||
if: ${{ matrix.target == 'aarch64-unknown-linux-gnu' || matrix.target == 'armv7-unknown-linux-gnueabihf' }}
|
||||
shell: bash
|
||||
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: assets/*
|
||||
|
||||
@@ -1,91 +0,0 @@
|
||||
name: Publish release files for non-cd-native environments
|
||||
|
||||
on:
|
||||
# workflow_run:
|
||||
# workflows: ["Check SemVer compliance"]
|
||||
# types:
|
||||
# - completed
|
||||
release:
|
||||
types: [ created ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target: [
|
||||
"aarch64-unknown-linux-gnu",
|
||||
"armv7-unknown-linux-gnueabihf",
|
||||
"x86_64-unknown-linux-musl",
|
||||
"aarch64-unknown-linux-musl",
|
||||
"x86_64-unknown-freebsd",
|
||||
]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install cargo-deb cross compilation dependencies
|
||||
run: sudo apt-get install libc6-arm64-cross libgcc-s1-arm64-cross
|
||||
if: ${{ matrix.target == 'aarch64-unknown-linux-gnu' }}
|
||||
shell: bash
|
||||
|
||||
- name: Install cargo-deb
|
||||
run: cargo install cargo-deb
|
||||
if: ${{ matrix.target == 'aarch64-unknown-linux-gnu' }}
|
||||
shell: bash
|
||||
|
||||
- name: install targets
|
||||
run: rustup target add ${{ matrix.target }}
|
||||
|
||||
- name: install cross
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: cross@0.2.5
|
||||
|
||||
- name: Check format
|
||||
run: cross fmt --all -- --check
|
||||
|
||||
- name: Run clippy
|
||||
run: cross clippy --all-targets --locked --target ${{matrix.target}} -- -D warnings
|
||||
|
||||
- name: Run clippy (All features)
|
||||
run: cross clippy --locked --all-features --target ${{matrix.target}} -- -D warnings
|
||||
|
||||
- name: Run tests
|
||||
run: cross test --target ${{matrix.target}}
|
||||
|
||||
- name: Build in Release profile with all features enabled
|
||||
run: cross build --release --all-features --target ${{matrix.target}}
|
||||
|
||||
- name: Rename Release
|
||||
run: |
|
||||
mkdir -p assets
|
||||
FILENAME=topgrade-${{github.event.release.tag_name}}-${{matrix.target}}
|
||||
mv target/${{matrix.target}}/release/topgrade assets
|
||||
cd assets
|
||||
tar --format=ustar -czf $FILENAME.tar.gz topgrade
|
||||
rm topgrade
|
||||
ls .
|
||||
|
||||
- name: Build Debian-based system package without autoupdate feature
|
||||
# First remove the binary built by previous steps
|
||||
# because we don't want the auto-update feature,
|
||||
# then build the new binary without auto-updating.
|
||||
run: |
|
||||
rm -rf target/${{matrix.target}}
|
||||
cross build --release --target ${{matrix.target}}
|
||||
cargo deb --target=${{matrix.target}} --no-build --no-strip
|
||||
if: ${{ matrix.target == 'aarch64-unknown-linux-gnu' }}
|
||||
shell: bash
|
||||
|
||||
- name: Move Debian-based system package
|
||||
run: |
|
||||
mkdir -p assets
|
||||
mv target/${{matrix.target}}/debian/*.deb assets
|
||||
if: ${{ matrix.target == 'aarch64-unknown-linux-gnu' }}
|
||||
shell: bash
|
||||
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: assets/*
|
||||
23
.github/workflows/release_to_aur.yml
vendored
23
.github/workflows/release_to_aur.yml
vendored
@@ -1,19 +1,18 @@
|
||||
name: Publish to AUR
|
||||
|
||||
on:
|
||||
# workflow_run:
|
||||
# workflows: ["Check SemVer compliance"]
|
||||
# types:
|
||||
# - completed
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
# Step "Publish binary AUR package" needs the binaries built by the following
|
||||
# workflow, so we wait for it to complete.
|
||||
workflow_run:
|
||||
workflows: ["Publish release files for CD native and non-cd-native environments"]
|
||||
types:
|
||||
- completed
|
||||
|
||||
jobs:
|
||||
aur-publish:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Publish AUR package
|
||||
- name: Publish source AUR package
|
||||
uses: aksh1618/update-aur-package@v1.0.5
|
||||
with:
|
||||
tag_version_prefix: v
|
||||
@@ -21,3 +20,11 @@ jobs:
|
||||
commit_username: "Thomas Schönauer"
|
||||
commit_email: t.schoenauer@hgs-wt.at
|
||||
ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }}
|
||||
- name: Publish binary AUR package
|
||||
uses: aksh1618/update-aur-package@v1.0.5
|
||||
with:
|
||||
tag_version_prefix: v
|
||||
package_name: topgrade-bin
|
||||
commit_username: "Thomas Schönauer"
|
||||
commit_email: t.schoenauer@hgs-wt.at
|
||||
ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }}
|
||||
|
||||
10
.github/workflows/release_to_pypi.yml
vendored
10
.github/workflows/release_to_pypi.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
||||
sccache: 'true'
|
||||
manylinux: auto
|
||||
- name: Upload wheels
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: wheels
|
||||
path: dist
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
args: --release --out dist
|
||||
sccache: 'true'
|
||||
- name: Upload wheels
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: wheels
|
||||
path: dist
|
||||
@@ -61,7 +61,7 @@ jobs:
|
||||
args: --release --out dist
|
||||
sccache: 'true'
|
||||
- name: Upload wheels
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: wheels
|
||||
path: dist
|
||||
@@ -76,7 +76,7 @@ jobs:
|
||||
command: sdist
|
||||
args: --out dist
|
||||
- name: Upload sdist
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: wheels
|
||||
path: dist
|
||||
@@ -87,7 +87,7 @@ jobs:
|
||||
if: "startsWith(github.ref, 'refs/tags/')"
|
||||
needs: [linux, windows, macos, sdist]
|
||||
steps:
|
||||
- uses: actions/download-artifact@v4
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: wheels
|
||||
- name: Publish to PyPI
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
# Containers step
|
||||
|
||||
* New default behavior: In the previous versions, if you have both Docker and
|
||||
Podman installed, Podman will be used by Topgrade. Now the default option
|
||||
has been changed to Docker. This can be overridden by setting the
|
||||
`containers.runtime` option in the configuration TOML to "podman".
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
1. The `jet_brains_toolbox` step was renamed to `jetbrains_toolbox`. If you're
|
||||
using the old name in your configuration file in the `disable` or `only`
|
||||
fields, simply change it to `jetbrains_toolbox`.
|
||||
|
||||
@@ -20,11 +20,11 @@ To add a new `step` to `topgrade`:
|
||||
|
||||
```rust
|
||||
pub enum Step {
|
||||
// Existed steps
|
||||
// Existing steps
|
||||
// ...
|
||||
|
||||
// Your new step here!
|
||||
// You may want it to be sorted alphabetically because that looks great:)
|
||||
// Make sure it stays sorted alphabetically because that looks great :)
|
||||
Xxx,
|
||||
}
|
||||
```
|
||||
@@ -78,7 +78,7 @@ To add a new `step` to `topgrade`:
|
||||
to separate the steps, for example, for steps that are Linux-only, it goes
|
||||
like this:
|
||||
|
||||
```
|
||||
```rust
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
// Xxx is Linux-only
|
||||
@@ -86,7 +86,7 @@ To add a new `step` to `topgrade`:
|
||||
}
|
||||
```
|
||||
|
||||
Congrats, you just added a new `step`:)
|
||||
Congrats, you just added a new `step` :)
|
||||
|
||||
## Modification to the configuration entries
|
||||
|
||||
@@ -129,6 +129,24 @@ $ cargo test
|
||||
|
||||
Don't worry about other platforms, we have most of them covered in our CI.
|
||||
|
||||
## I18n
|
||||
|
||||
If your PR introduces user-facing messages, we need to ensure they are translated.
|
||||
Please add the translations to [`locales/app.yml`][app_yml]. For simple messages
|
||||
without arguments (e.g., "hello world"), we can simply translate them according
|
||||
(Tip: ChatGPT or similar LLMs is good at translation). If a message contains
|
||||
arguments, e.g., "hello <NAME>", please follow this convention:
|
||||
|
||||
```yml
|
||||
"hello {name}": # key
|
||||
en: "hello %{name}" # translation
|
||||
```
|
||||
|
||||
Arguments in the key should be in format `{argument_name}`, and they will have
|
||||
a preceeding `%` when used in translations.
|
||||
|
||||
[app_yml]: https://github.com/topgrade-rs/topgrade/blob/main/locales/app.yml
|
||||
|
||||
## Some tips
|
||||
|
||||
1. Locale
|
||||
|
||||
293
Cargo.lock
generated
293
Cargo.lock
generated
@@ -1,6 +1,6 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "addr2line"
|
||||
@@ -202,7 +202,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.66",
|
||||
"syn 2.0.99",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -237,7 +237,7 @@ checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.66",
|
||||
"syn 2.0.99",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -355,9 +355,12 @@ checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.99"
|
||||
version = "1.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96c51067fd44124faa7f870b4b1c969379ad32b2ba805aa959430ceaa384f695"
|
||||
checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c"
|
||||
dependencies = [
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
@@ -388,7 +391,7 @@ dependencies = [
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"wasm-bindgen",
|
||||
"windows-targets 0.52.5",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -431,7 +434,7 @@ dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.66",
|
||||
"syn 2.0.99",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -533,9 +536,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.6"
|
||||
version = "0.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
|
||||
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
@@ -620,7 +623,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.66",
|
||||
"syn 2.0.99",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -658,7 +661,16 @@ version = "5.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225"
|
||||
dependencies = [
|
||||
"dirs-sys",
|
||||
"dirs-sys 0.4.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs"
|
||||
version = "6.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e"
|
||||
dependencies = [
|
||||
"dirs-sys 0.5.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -679,10 +691,22 @@ checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"option-ext",
|
||||
"redox_users",
|
||||
"redox_users 0.4.5",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-sys"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"option-ext",
|
||||
"redox_users 0.5.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-sys-next"
|
||||
version = "0.1.2"
|
||||
@@ -690,7 +714,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"redox_users",
|
||||
"redox_users 0.4.5",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
@@ -702,7 +726,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.66",
|
||||
"syn 2.0.99",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -775,7 +799,7 @@ checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.66",
|
||||
"syn 2.0.99",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -963,7 +987,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.66",
|
||||
"syn 2.0.99",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1326,7 +1350,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.66",
|
||||
"syn 2.0.99",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1423,6 +1447,17 @@ version = "1.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
|
||||
|
||||
[[package]]
|
||||
name = "jetbrains-toolbox-updater"
|
||||
version = "5.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c6bb35a4c18ced364ba2a3952bf5ca2b9231451974f5c2a4c8fa14f300a545b"
|
||||
dependencies = [
|
||||
"dirs 6.0.0",
|
||||
"json",
|
||||
"sysinfo",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.69"
|
||||
@@ -1432,6 +1467,12 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "json"
|
||||
version = "0.12.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd"
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
@@ -1440,9 +1481,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.155"
|
||||
version = "0.2.172"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
|
||||
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
|
||||
|
||||
[[package]]
|
||||
name = "libredox"
|
||||
@@ -1619,6 +1660,15 @@ dependencies = [
|
||||
"zbus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ntapi"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nu-ansi-term"
|
||||
version = "0.46.0"
|
||||
@@ -1680,6 +1730,15 @@ dependencies = [
|
||||
"objc_id",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-core-foundation"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "daeaf60f25471d26948a1c2f840e3f7d86f4109e3af4e8e4b5cd70c39690d925"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc_id"
|
||||
version = "0.1.1"
|
||||
@@ -1757,7 +1816,7 @@ dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"byteorder",
|
||||
"chrono",
|
||||
"thiserror",
|
||||
"thiserror 1.0.61",
|
||||
"widestring",
|
||||
]
|
||||
|
||||
@@ -1784,7 +1843,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.66",
|
||||
"syn 2.0.99",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1888,9 +1947,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.85"
|
||||
version = "1.0.94"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23"
|
||||
checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@@ -1969,7 +2028,18 @@ checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"libredox",
|
||||
"thiserror",
|
||||
"thiserror 1.0.61",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_users"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"libredox",
|
||||
"thiserror 2.0.12",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2069,15 +2139,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.17.8"
|
||||
version = "0.17.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d"
|
||||
checksum = "70ac5d832aa16abd7d1def883a8545280c20a60f523a370aa3a9617c2b8550ee"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"getrandom",
|
||||
"libc",
|
||||
"spin",
|
||||
"untrusted",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
@@ -2116,7 +2185,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_yaml",
|
||||
"syn 2.0.66",
|
||||
"syn 2.0.99",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2298,7 +2367,7 @@ checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.66",
|
||||
"syn 2.0.99",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2320,7 +2389,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.66",
|
||||
"syn 2.0.99",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2399,9 +2468,15 @@ version = "3.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da03fa3b94cc19e3ebfc88c4229c49d8f08cdbd1228870a45f0ffdf84988e14b"
|
||||
dependencies = [
|
||||
"dirs",
|
||||
"dirs 5.0.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.2"
|
||||
@@ -2446,12 +2521,6 @@ dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
version = "0.9.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
|
||||
|
||||
[[package]]
|
||||
name = "spki"
|
||||
version = "0.7.3"
|
||||
@@ -2499,7 +2568,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
"syn 2.0.66",
|
||||
"syn 2.0.99",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2521,9 +2590,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.66"
|
||||
version = "2.0.99"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5"
|
||||
checksum = "e02e925281e18ffd9d640e234264753c43edc62d64b2d4cf898f1bc5e75f3fc2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -2544,7 +2613,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.66",
|
||||
"syn 2.0.99",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2556,6 +2625,19 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sysinfo"
|
||||
version = "0.34.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4b93974b3d3aeaa036504b8eefd4c039dced109171c1ae973f1dc63b2c7e4b2"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"memchr",
|
||||
"ntapi",
|
||||
"objc2-core-foundation",
|
||||
"windows",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tar"
|
||||
version = "0.4.41"
|
||||
@@ -2596,7 +2678,16 @@ version = "1.0.61"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
"thiserror-impl 1.0.61",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
|
||||
dependencies = [
|
||||
"thiserror-impl 2.0.12",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2607,7 +2698,18 @@ checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.66",
|
||||
"syn 2.0.99",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "2.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.99",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2672,9 +2774,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.38.0"
|
||||
version = "1.38.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a"
|
||||
checksum = "68722da18b0fc4a05fdc1120b302b82051265792a1e1b399086e9b204b10ad3d"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"bytes",
|
||||
@@ -2770,7 +2872,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "topgrade"
|
||||
version = "16.0.0"
|
||||
version = "16.0.4"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"chrono",
|
||||
@@ -2783,7 +2885,7 @@ dependencies = [
|
||||
"futures",
|
||||
"glob",
|
||||
"home",
|
||||
"lazy_static",
|
||||
"jetbrains-toolbox-updater",
|
||||
"merge",
|
||||
"nix 0.29.0",
|
||||
"notify-rust",
|
||||
@@ -2801,7 +2903,7 @@ dependencies = [
|
||||
"strum",
|
||||
"sys-locale",
|
||||
"tempfile",
|
||||
"thiserror",
|
||||
"thiserror 1.0.61",
|
||||
"tokio",
|
||||
"toml 0.8.14",
|
||||
"tracing",
|
||||
@@ -2859,7 +2961,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.66",
|
||||
"syn 2.0.99",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3063,7 +3165,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.66",
|
||||
"syn 2.0.99",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
@@ -3097,7 +3199,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.66",
|
||||
"syn 2.0.99",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
@@ -3189,7 +3291,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1de69df01bdf1ead2f4ac895dc77c9351aefff65b2f3db429a343f9cbf05e132"
|
||||
dependencies = [
|
||||
"windows-core 0.56.0",
|
||||
"windows-targets 0.52.5",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3198,7 +3300,7 @@ version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.5",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3210,7 +3312,7 @@ dependencies = [
|
||||
"windows-implement",
|
||||
"windows-interface",
|
||||
"windows-result",
|
||||
"windows-targets 0.52.5",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3221,7 +3323,7 @@ checksum = "f6fc35f58ecd95a9b71c4f2329b911016e6bec66b3f2e6a4aad86bd2e99e2f9b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.66",
|
||||
"syn 2.0.99",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3232,7 +3334,7 @@ checksum = "08990546bf4edef8f431fa6326e032865f27138718c587dc21bc0265bbcb57cc"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.66",
|
||||
"syn 2.0.99",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3241,7 +3343,7 @@ version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.5",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3259,7 +3361,16 @@ version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.5",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.59.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3279,18 +3390,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.5"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb"
|
||||
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.52.5",
|
||||
"windows_aarch64_msvc 0.52.5",
|
||||
"windows_i686_gnu 0.52.5",
|
||||
"windows_aarch64_gnullvm 0.52.6",
|
||||
"windows_aarch64_msvc 0.52.6",
|
||||
"windows_i686_gnu 0.52.6",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_msvc 0.52.5",
|
||||
"windows_x86_64_gnu 0.52.5",
|
||||
"windows_x86_64_gnullvm 0.52.5",
|
||||
"windows_x86_64_msvc 0.52.5",
|
||||
"windows_i686_msvc 0.52.6",
|
||||
"windows_x86_64_gnu 0.52.6",
|
||||
"windows_x86_64_gnullvm 0.52.6",
|
||||
"windows_x86_64_msvc 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3299,7 +3410,7 @@ version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6998aa457c9ba8ff2fb9f13e9d2a930dabcea28f1d0ab94d687d8b3654844515"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.5",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3310,9 +3421,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.5"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263"
|
||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
@@ -3322,9 +3433,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.5"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6"
|
||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
@@ -3334,15 +3445,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.5"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670"
|
||||
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.52.5"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9"
|
||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
@@ -3352,9 +3463,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.5"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf"
|
||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
@@ -3364,9 +3475,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.5"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9"
|
||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
@@ -3376,9 +3487,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.5"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596"
|
||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
@@ -3388,9 +3499,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.5"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
@@ -3488,7 +3599,7 @@ checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.66",
|
||||
"syn 2.0.99",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
@@ -3539,7 +3650,7 @@ dependencies = [
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.66",
|
||||
"syn 2.0.99",
|
||||
"zvariant_utils",
|
||||
]
|
||||
|
||||
@@ -3571,7 +3682,7 @@ checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.66",
|
||||
"syn 2.0.99",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
@@ -3600,7 +3711,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.66",
|
||||
"syn 2.0.99",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3624,7 +3735,7 @@ checksum = "2ba5aa1827d6b1a35a29b3413ec69ce5f796e4d897e3e5b38f461bef41d225ea"
|
||||
dependencies = [
|
||||
"base64 0.21.7",
|
||||
"ed25519-dalek",
|
||||
"thiserror",
|
||||
"thiserror 1.0.61",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3649,7 +3760,7 @@ dependencies = [
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.66",
|
||||
"syn 2.0.99",
|
||||
"zvariant_utils",
|
||||
]
|
||||
|
||||
@@ -3661,5 +3772,5 @@ checksum = "fc242db087efc22bd9ade7aa7809e4ba828132edc312871584a6b4391bdf8786"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.66",
|
||||
"syn 2.0.99",
|
||||
]
|
||||
|
||||
@@ -5,8 +5,8 @@ categories = ["os"]
|
||||
keywords = ["upgrade", "update"]
|
||||
license = "GPL-3.0"
|
||||
repository = "https://github.com/topgrade-rs/topgrade"
|
||||
rust-version = "1.76.0"
|
||||
version = "16.0.0"
|
||||
rust-version = "1.84.1"
|
||||
version = "16.0.4"
|
||||
authors = ["Roey Darwish Dror <roey.ghost@gmail.com>", "Thomas Schönauer <t.schoenauer@hgs-wt.at>"]
|
||||
exclude = ["doc/screenshot.gif", "BREAKINGCHANGES_dev.md"]
|
||||
edition = "2021"
|
||||
@@ -33,7 +33,6 @@ clap_complete = "~4.5"
|
||||
clap_mangen = "~0.2"
|
||||
walkdir = "~2.5"
|
||||
console = "~0.15"
|
||||
lazy_static = "~1.4"
|
||||
chrono = "~0.4"
|
||||
glob = "~0.3"
|
||||
strum = { version = "~0.26", features = ["derive"] }
|
||||
@@ -54,6 +53,7 @@ notify-rust = "~4.11"
|
||||
wildmatch = "2.3.0"
|
||||
rust-i18n = "3.0.1"
|
||||
sys-locale = "0.3.1"
|
||||
jetbrains-toolbox-updater = "5.0.0"
|
||||
|
||||
[package.metadata.generate-rpm]
|
||||
assets = [{ source = "target/release/topgrade", dest = "/usr/bin/topgrade" }]
|
||||
|
||||
@@ -31,6 +31,7 @@ To remedy this, **Topgrade** detects which tools you use and runs the appropriat
|
||||
- macOS: [Homebrew](https://formulae.brew.sh/formula/topgrade) or [MacPorts](https://ports.macports.org/port/topgrade/)
|
||||
- Windows: [Chocolatey][choco], [Scoop][scoop] or [Winget][winget]
|
||||
- PyPi: [pip](https://pypi.org/project/topgrade/)
|
||||
- Fedora: [Copr](https://copr.fedorainfracloud.org/coprs/lilay/topgrade/)
|
||||
|
||||
[choco]: https://community.chocolatey.org/packages/topgrade
|
||||
[scoop]: https://scoop.sh/#/apps?q=topgrade
|
||||
|
||||
@@ -9,7 +9,11 @@
|
||||
|
||||
> If there are breaking changes, the major version number should be increased.
|
||||
|
||||
2. Overwrite [`BREAKINGCHANGES`][breaking_changes] with
|
||||
2. If the major versioin number gets bumped, update [SECURITY.md][SECURITY_file_link].
|
||||
|
||||
[SECURITY_file_link]: https://github.com/topgrade-rs/topgrade/blob/main/SECURITY.md
|
||||
|
||||
3. Overwrite [`BREAKINGCHANGES`][breaking_changes] with
|
||||
[`BREAKINGCHANGES_dev`][breaking_changes_dev], and create a new dev file:
|
||||
|
||||
```sh'
|
||||
|
||||
@@ -6,6 +6,6 @@ We only support the latest major version and each subversion.
|
||||
|
||||
| Version | Supported |
|
||||
| -------- | ------------------ |
|
||||
| 15.0.x | :white_check_mark: |
|
||||
| < 15.0 | :x: |
|
||||
| 16.0.x | :white_check_mark: |
|
||||
| < 16.0 | :x: |
|
||||
|
||||
|
||||
@@ -103,6 +103,13 @@
|
||||
# enable_pipupgrade = true ###disabled by default
|
||||
# pipupgrade_arguments = "-y -u --pip-path pip" ###disabled by default
|
||||
|
||||
# For the poetry step, by default, Topgrade skips its update if poetry is not
|
||||
# installed with the official script. This configuration entry forces Topgrade
|
||||
# to run the update in this case.
|
||||
#
|
||||
# (default: false)
|
||||
# poetry_force_self_update = true
|
||||
|
||||
|
||||
[composer]
|
||||
# self_update = true
|
||||
@@ -172,6 +179,11 @@
|
||||
|
||||
# rpm_ostree = false
|
||||
|
||||
# For Fedora/CentOS/RHEL Atomic variants, if `bootc` is available and this configuration entry is set to true, use
|
||||
# it to do the update - Will also supercede rpm-ostree if enabled
|
||||
# (default: false)
|
||||
# bootc = false
|
||||
|
||||
# nix_arguments = "--flake"
|
||||
|
||||
# nix_env_arguments = "--prebuilt-only"
|
||||
@@ -207,6 +219,10 @@
|
||||
|
||||
# wsl_update_use_web_download = true
|
||||
|
||||
# The default for winget_install_silently is true,
|
||||
# this example turns off silent install.
|
||||
# winget_install_silently = false
|
||||
|
||||
# Causes Topgrade to rename itself during the run to allow package managers
|
||||
# to upgrade it. Use this only if you installed Topgrade by using a package
|
||||
# manager such as Scoop or Cargo
|
||||
@@ -223,6 +239,11 @@
|
||||
# use_sudo = true
|
||||
|
||||
|
||||
[deno]
|
||||
# Upgrade deno executable to the given version.
|
||||
# version = "stable"
|
||||
|
||||
|
||||
[vim]
|
||||
# For `vim-plug`, execute `PlugUpdate!` instead of `PlugUpdate`
|
||||
# force_plug_update = true
|
||||
@@ -265,3 +286,42 @@
|
||||
# and the update will be installed system-wide, i.e., available to all users.
|
||||
# (default: false)
|
||||
# use_sudo = false
|
||||
|
||||
[julia]
|
||||
# If disabled, Topgrade invokes julia with the --startup-file=no CLI option.
|
||||
#
|
||||
# This may be desirable to avoid loading outdated packages with "using" directives
|
||||
# in the startup file, which might cause the update run to fail.
|
||||
# (default: true)
|
||||
# startup_file = true
|
||||
|
||||
[zigup]
|
||||
# Version strings passed to zigup.
|
||||
# These may be pinned versions such as "0.13.0" or branches such as "master".
|
||||
# Each one will be updated in its own zigup invocation.
|
||||
# (default: ["master"])
|
||||
# target_versions = ["master", "0.13.0"]
|
||||
|
||||
# Specifies the directory that the zig files will be installed to.
|
||||
# If defined, passed with the --install-dir command line flag.
|
||||
# If not defined, zigup will use its default behaviour.
|
||||
# (default: not defined)
|
||||
# install_dir = "~/.zig"
|
||||
|
||||
# Specifies the path of the symlink which will be set to point at the default compiler version.
|
||||
# If defined, passed with the --path-link command line flag.
|
||||
# If not defined, zigup will use its default behaviour.
|
||||
# This is not meaningful if set_default is not enabled.
|
||||
# (default: not defined)
|
||||
# path_link = "~/.bin/zig"
|
||||
|
||||
# If enabled, run `zigup clean` after updating all versions.
|
||||
# If enabled, each updated version above will be marked with `zigup keep`.
|
||||
# (default: false)
|
||||
# cleanup = false
|
||||
|
||||
[vscode]
|
||||
# If this is set and is a non-empty string, it specifies the profile the
|
||||
# extensions should be updated for.
|
||||
# (default: this won't be set by default)
|
||||
# profile = ""
|
||||
|
||||
1027
locales/app.yml
1027
locales/app.yml
File diff suppressed because it is too large
Load Diff
@@ -1,2 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "1.76.0"
|
||||
channel = "1.84.1"
|
||||
|
||||
@@ -45,13 +45,13 @@ impl TryFrom<&Output> for Utf8Output {
|
||||
type Error = eyre::Error;
|
||||
|
||||
fn try_from(Output { status, stdout, stderr }: &Output) -> Result<Self, Self::Error> {
|
||||
let stdout = String::from_utf8(stdout.to_vec()).map_err(|err| {
|
||||
let stdout = String::from_utf8(stdout.clone()).map_err(|err| {
|
||||
eyre!(
|
||||
"Stdout contained invalid UTF-8: {}",
|
||||
String::from_utf8_lossy(err.as_bytes())
|
||||
)
|
||||
})?;
|
||||
let stderr = String::from_utf8(stderr.to_vec()).map_err(|err| {
|
||||
let stderr = String::from_utf8(stderr.clone()).map_err(|err| {
|
||||
eyre!(
|
||||
"Stderr contained invalid UTF-8: {}",
|
||||
String::from_utf8_lossy(err.as_bytes())
|
||||
@@ -149,6 +149,7 @@ pub trait CommandExt {
|
||||
/// Like [`Command::spawn`], but gives a nice error message if the command fails to
|
||||
/// execute.
|
||||
#[track_caller]
|
||||
#[allow(dead_code)]
|
||||
fn spawn_checked(&mut self) -> eyre::Result<Self::Child>;
|
||||
}
|
||||
|
||||
|
||||
190
src/config.rs
190
src/config.rs
@@ -52,10 +52,11 @@ pub type Commands = BTreeMap<String, String>;
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
pub enum Step {
|
||||
AM,
|
||||
AndroidStudio,
|
||||
AppMan,
|
||||
Aqua,
|
||||
Asdf,
|
||||
Atom,
|
||||
Aqua,
|
||||
Audit,
|
||||
AutoCpufreq,
|
||||
Bin,
|
||||
@@ -69,6 +70,7 @@ pub enum Step {
|
||||
Chezmoi,
|
||||
Chocolatey,
|
||||
Choosenim,
|
||||
CinnamonSpices,
|
||||
ClamAvDb,
|
||||
Composer,
|
||||
Conda,
|
||||
@@ -89,28 +91,46 @@ pub enum Step {
|
||||
Gcloud,
|
||||
Gem,
|
||||
Ghcup,
|
||||
GithubCliExtensions,
|
||||
GitRepos,
|
||||
GithubCliExtensions,
|
||||
GnomeShellExtensions,
|
||||
Go,
|
||||
Guix,
|
||||
Haxelib,
|
||||
Helix,
|
||||
Helm,
|
||||
HomeManager,
|
||||
// These names are miscapitalized on purpose, so the CLI name is
|
||||
// `jetbrains_pycharm` instead of `jet_brains_py_charm`.
|
||||
JetbrainsAqua,
|
||||
JetbrainsClion,
|
||||
JetbrainsDatagrip,
|
||||
JetbrainsDataspell,
|
||||
JetbrainsGateway,
|
||||
JetbrainsGoland,
|
||||
JetbrainsIdea,
|
||||
JetbrainsMps,
|
||||
JetbrainsPhpstorm,
|
||||
JetbrainsPycharm,
|
||||
JetbrainsRider,
|
||||
JetbrainsRubymine,
|
||||
JetbrainsRustrover,
|
||||
JetbrainsToolbox,
|
||||
JetbrainsWebstorm,
|
||||
Jetpack,
|
||||
Julia,
|
||||
Juliaup,
|
||||
Kakoune,
|
||||
Helix,
|
||||
Krew,
|
||||
Lure,
|
||||
Lensfun,
|
||||
Lure,
|
||||
Macports,
|
||||
Mamba,
|
||||
Miktex,
|
||||
Mas,
|
||||
Maza,
|
||||
Micro,
|
||||
MicrosoftStore,
|
||||
Miktex,
|
||||
Mise,
|
||||
Myrepos,
|
||||
Nix,
|
||||
@@ -124,6 +144,7 @@ pub enum Step {
|
||||
PipReviewLocal,
|
||||
Pipupgrade,
|
||||
Pipx,
|
||||
Pipxu,
|
||||
Pixi,
|
||||
Pkg,
|
||||
Pkgin,
|
||||
@@ -162,6 +183,7 @@ pub enum Step {
|
||||
Vim,
|
||||
VoltaPackages,
|
||||
Vscode,
|
||||
Vscodium,
|
||||
Waydroid,
|
||||
Winget,
|
||||
Wsl,
|
||||
@@ -169,6 +191,8 @@ pub enum Step {
|
||||
Xcodes,
|
||||
Yadm,
|
||||
Yarn,
|
||||
Yazi,
|
||||
Zigup,
|
||||
Zvm,
|
||||
}
|
||||
|
||||
@@ -219,6 +243,7 @@ pub struct Windows {
|
||||
open_remotes_in_new_terminal: Option<bool>,
|
||||
wsl_update_pre_release: Option<bool>,
|
||||
wsl_update_use_web_download: Option<bool>,
|
||||
winget_silent_install: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Default, Debug, Merge)]
|
||||
@@ -228,6 +253,7 @@ pub struct Python {
|
||||
enable_pip_review_local: Option<bool>,
|
||||
enable_pipupgrade: Option<bool>,
|
||||
pipupgrade_arguments: Option<String>,
|
||||
poetry_force_self_update: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Default, Debug, Merge)]
|
||||
@@ -254,6 +280,13 @@ pub struct NPM {
|
||||
use_sudo: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Default, Debug, Merge)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
pub struct Deno {
|
||||
version: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Default, Debug, Merge)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
@@ -350,6 +383,7 @@ pub struct Linux {
|
||||
redhat_distro_sync: Option<bool>,
|
||||
suse_dup: Option<bool>,
|
||||
rpm_ostree: Option<bool>,
|
||||
bootc: Option<bool>,
|
||||
|
||||
#[merge(strategy = crate::utils::merge_strategies::string_append_opt)]
|
||||
emerge_sync_flags: Option<String>,
|
||||
@@ -444,6 +478,27 @@ pub struct Lensfun {
|
||||
use_sudo: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Default, Debug, Merge)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct JuliaConfig {
|
||||
startup_file: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Default, Debug, Merge)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Zigup {
|
||||
target_versions: Option<Vec<String>>,
|
||||
install_dir: Option<String>,
|
||||
path_link: Option<String>,
|
||||
cleanup: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Default, Debug, Merge)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct VscodeConfig {
|
||||
profile: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Default, Debug, Merge)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
/// Configuration file
|
||||
@@ -490,6 +545,9 @@ pub struct ConfigFile {
|
||||
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
|
||||
yarn: Option<Yarn>,
|
||||
|
||||
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
|
||||
deno: Option<Deno>,
|
||||
|
||||
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
|
||||
vim: Option<Vim>,
|
||||
|
||||
@@ -507,6 +565,15 @@ pub struct ConfigFile {
|
||||
|
||||
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
|
||||
lensfun: Option<Lensfun>,
|
||||
|
||||
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
|
||||
julia: Option<JuliaConfig>,
|
||||
|
||||
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
|
||||
zigup: Option<Zigup>,
|
||||
|
||||
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
|
||||
vscode: Option<VscodeConfig>,
|
||||
}
|
||||
|
||||
fn config_directory() -> PathBuf {
|
||||
@@ -538,7 +605,7 @@ impl ConfigFile {
|
||||
];
|
||||
|
||||
// Search for the main config file
|
||||
for path in possible_config_paths.iter() {
|
||||
for path in &possible_config_paths {
|
||||
if path.exists() {
|
||||
debug!("Configuration at {}", path.display());
|
||||
res.0.clone_from(path);
|
||||
@@ -802,7 +869,7 @@ pub struct CommandLineArgs {
|
||||
|
||||
/// Tracing filter directives.
|
||||
///
|
||||
/// See: https://docs.rs/tracing-subscriber/latest/tracing_subscriber/struct.EnvFilter.html
|
||||
/// See: https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives
|
||||
#[arg(long, default_value = DEFAULT_LOG_LEVEL)]
|
||||
pub log_filter: String,
|
||||
|
||||
@@ -1437,14 +1504,22 @@ impl Config {
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Use bootc in *when bootc is detected* (default: false)
|
||||
pub fn bootc(&self) -> bool {
|
||||
self.config_file
|
||||
.linux
|
||||
.as_ref()
|
||||
.and_then(|linux| linux.bootc)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Determine if we should ignore failures for this step
|
||||
pub fn ignore_failure(&self, step: Step) -> bool {
|
||||
self.config_file
|
||||
.misc
|
||||
.as_ref()
|
||||
.and_then(|misc| misc.ignore_failures.as_ref())
|
||||
.map(|v| v.contains(&step))
|
||||
.unwrap_or(false)
|
||||
.is_some_and(|v| v.contains(&step))
|
||||
}
|
||||
|
||||
pub fn use_predefined_git_repos(&self) -> bool {
|
||||
@@ -1494,6 +1569,14 @@ impl Config {
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn winget_silent_install(&self) -> bool {
|
||||
self.config_file
|
||||
.windows
|
||||
.as_ref()
|
||||
.and_then(|windows| windows.winget_silent_install)
|
||||
.unwrap_or(true)
|
||||
}
|
||||
|
||||
pub fn sudo_command(&self) -> Option<SudoKind> {
|
||||
self.config_file.misc.as_ref().and_then(|misc| misc.sudo_command)
|
||||
}
|
||||
@@ -1525,6 +1608,10 @@ impl Config {
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn deno_version(&self) -> Option<&str> {
|
||||
self.config_file.deno.as_ref().and_then(|deno| deno.version.as_deref())
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn firmware_upgrade(&self) -> bool {
|
||||
self.config_file
|
||||
@@ -1566,12 +1653,11 @@ impl Config {
|
||||
}
|
||||
|
||||
pub fn enable_pipupgrade(&self) -> bool {
|
||||
return self
|
||||
.config_file
|
||||
self.config_file
|
||||
.python
|
||||
.as_ref()
|
||||
.and_then(|python| python.enable_pipupgrade)
|
||||
.unwrap_or(false);
|
||||
.unwrap_or(false)
|
||||
}
|
||||
pub fn pipupgrade_arguments(&self) -> &str {
|
||||
self.config_file
|
||||
@@ -1581,20 +1667,25 @@ impl Config {
|
||||
.unwrap_or("")
|
||||
}
|
||||
pub fn enable_pip_review(&self) -> bool {
|
||||
return self
|
||||
.config_file
|
||||
self.config_file
|
||||
.python
|
||||
.as_ref()
|
||||
.and_then(|python| python.enable_pip_review)
|
||||
.unwrap_or(false);
|
||||
.unwrap_or(false)
|
||||
}
|
||||
pub fn enable_pip_review_local(&self) -> bool {
|
||||
return self
|
||||
.config_file
|
||||
self.config_file
|
||||
.python
|
||||
.as_ref()
|
||||
.and_then(|python| python.enable_pip_review_local)
|
||||
.unwrap_or(false);
|
||||
.unwrap_or(false)
|
||||
}
|
||||
pub fn poetry_force_self_update(&self) -> bool {
|
||||
self.config_file
|
||||
.python
|
||||
.as_ref()
|
||||
.and_then(|python| python.poetry_force_self_update)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn display_time(&self) -> bool {
|
||||
@@ -1620,6 +1711,55 @@ impl Config {
|
||||
.and_then(|lensfun| lensfun.use_sudo)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn julia_use_startup_file(&self) -> bool {
|
||||
self.config_file
|
||||
.julia
|
||||
.as_ref()
|
||||
.and_then(|julia| julia.startup_file)
|
||||
.unwrap_or(true)
|
||||
}
|
||||
|
||||
pub fn zigup_target_versions(&self) -> Vec<String> {
|
||||
self.config_file
|
||||
.zigup
|
||||
.as_ref()
|
||||
.and_then(|zigup| zigup.target_versions.clone())
|
||||
.unwrap_or(vec!["master".to_owned()])
|
||||
}
|
||||
|
||||
pub fn zigup_install_dir(&self) -> Option<&str> {
|
||||
self.config_file
|
||||
.zigup
|
||||
.as_ref()
|
||||
.and_then(|zigup| zigup.install_dir.as_deref())
|
||||
}
|
||||
|
||||
pub fn zigup_path_link(&self) -> Option<&str> {
|
||||
self.config_file
|
||||
.zigup
|
||||
.as_ref()
|
||||
.and_then(|zigup| zigup.path_link.as_deref())
|
||||
}
|
||||
|
||||
pub fn zigup_cleanup(&self) -> bool {
|
||||
self.config_file
|
||||
.zigup
|
||||
.as_ref()
|
||||
.and_then(|zigup| zigup.cleanup)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn vscode_profile(&self) -> Option<&str> {
|
||||
let vscode_cfg = self.config_file.vscode.as_ref()?;
|
||||
let profile = vscode_cfg.profile.as_ref()?;
|
||||
|
||||
if profile.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(profile.as_str())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -1646,40 +1786,40 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn test_should_execute_remote_different_hostname() {
|
||||
assert!(config().should_execute_remote(Ok("hostname".to_string()), "remote_hostname"))
|
||||
assert!(config().should_execute_remote(Ok("hostname".to_string()), "remote_hostname"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_should_execute_remote_different_hostname_with_user() {
|
||||
assert!(config().should_execute_remote(Ok("hostname".to_string()), "user@remote_hostname"))
|
||||
assert!(config().should_execute_remote(Ok("hostname".to_string()), "user@remote_hostname"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_should_execute_remote_unknown_hostname() {
|
||||
assert!(config().should_execute_remote(Err(eyre!("failed to get hostname")), "remote_hostname"))
|
||||
assert!(config().should_execute_remote(Err(eyre!("failed to get hostname")), "remote_hostname"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_should_not_execute_remote_same_hostname() {
|
||||
assert!(!config().should_execute_remote(Ok("hostname".to_string()), "hostname"))
|
||||
assert!(!config().should_execute_remote(Ok("hostname".to_string()), "hostname"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_should_not_execute_remote_same_hostname_with_user() {
|
||||
assert!(!config().should_execute_remote(Ok("hostname".to_string()), "user@hostname"))
|
||||
assert!(!config().should_execute_remote(Ok("hostname".to_string()), "user@hostname"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_should_execute_remote_matching_limit() {
|
||||
let mut config = config();
|
||||
config.opt = CommandLineArgs::parse_from(["topgrade", "--remote-host-limit", "remote_hostname"]);
|
||||
assert!(config.should_execute_remote(Ok("hostname".to_string()), "user@remote_hostname"))
|
||||
assert!(config.should_execute_remote(Ok("hostname".to_string()), "user@remote_hostname"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_should_not_execute_remote_not_matching_limit() {
|
||||
let mut config = config();
|
||||
config.opt = CommandLineArgs::parse_from(["topgrade", "--remote-host-limit", "other_hostname"]);
|
||||
assert!(!config.should_execute_remote(Ok("hostname".to_string()), "user@remote_hostname"))
|
||||
assert!(!config.should_execute_remote(Ok("hostname".to_string()), "user@remote_hostname"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,9 +11,9 @@ pub fn interrupted() -> bool {
|
||||
/// Clears the interrupted flag
|
||||
pub fn unset_interrupted() {
|
||||
debug_assert!(INTERRUPTED.load(Ordering::SeqCst));
|
||||
INTERRUPTED.store(false, Ordering::SeqCst)
|
||||
INTERRUPTED.store(false, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
pub fn set_interrupted() {
|
||||
INTERRUPTED.store(true, Ordering::SeqCst)
|
||||
INTERRUPTED.store(true, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ use nix::sys::signal::{sigaction, SaFlags, SigAction, SigHandler, SigSet, Signal
|
||||
|
||||
/// Handle SIGINT. Set the interruption flag.
|
||||
extern "C" fn handle_sigint(_: i32) {
|
||||
set_interrupted()
|
||||
set_interrupted();
|
||||
}
|
||||
|
||||
/// Set the necessary signal handlers.
|
||||
|
||||
@@ -27,7 +27,7 @@ impl Display for TopgradeError {
|
||||
f,
|
||||
"{}",
|
||||
t!(
|
||||
"`{process}` failed: {exit_satus}",
|
||||
"`{process}` failed: {exit_status}",
|
||||
process = process,
|
||||
exit_status = exit_status
|
||||
)
|
||||
@@ -38,7 +38,7 @@ impl Display for TopgradeError {
|
||||
f,
|
||||
"{}",
|
||||
t!(
|
||||
"`{process}` failed: {exit_satus} with {output}",
|
||||
"`{process}` failed: {exit_status} with {output}",
|
||||
process = process,
|
||||
exit_status = exit_status,
|
||||
output = output
|
||||
|
||||
@@ -152,7 +152,10 @@ impl Executor {
|
||||
let result = match self {
|
||||
Executor::Wet(c) => {
|
||||
debug!("Running {:?}", c);
|
||||
c.spawn_checked().map(ExecutorChild::Wet)?
|
||||
// We should use `spawn()` here rather than `spawn_checked()` since
|
||||
// their semantics and behaviors are different.
|
||||
#[allow(clippy::disallowed_methods)]
|
||||
c.spawn().map(ExecutorChild::Wet)?
|
||||
}
|
||||
Executor::Dry(c) => {
|
||||
c.dry_run();
|
||||
@@ -166,7 +169,12 @@ impl Executor {
|
||||
/// See `std::process::Command::output`
|
||||
pub fn output(&mut self) -> Result<ExecutorOutput> {
|
||||
match self {
|
||||
Executor::Wet(c) => Ok(ExecutorOutput::Wet(c.output_checked()?)),
|
||||
Executor::Wet(c) => {
|
||||
// We should use `output()` here rather than `output_checked()` since
|
||||
// their semantics and behaviors are different.
|
||||
#[allow(clippy::disallowed_methods)]
|
||||
Ok(ExecutorOutput::Wet(c.output()?))
|
||||
}
|
||||
Executor::Dry(c) => {
|
||||
c.dry_run();
|
||||
Ok(ExecutorOutput::Dry)
|
||||
@@ -180,7 +188,7 @@ impl Executor {
|
||||
pub fn status_checked_with_codes(&mut self, codes: &[i32]) -> Result<()> {
|
||||
match self {
|
||||
Executor::Wet(c) => c.status_checked_with(|status| {
|
||||
if status.success() || status.code().as_ref().map(|c| codes.contains(c)).unwrap_or(false) {
|
||||
if status.success() || status.code().as_ref().is_some_and(|c| codes.contains(c)) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(())
|
||||
|
||||
79
src/main.rs
79
src/main.rs
@@ -25,7 +25,9 @@ use self::config::{CommandLineArgs, Config, Step};
|
||||
use self::error::StepFailed;
|
||||
#[cfg(all(windows, feature = "self-update"))]
|
||||
use self::error::Upgraded;
|
||||
#[allow(clippy::wildcard_imports)]
|
||||
use self::steps::{remote::*, *};
|
||||
#[allow(clippy::wildcard_imports)]
|
||||
use self::terminal::*;
|
||||
|
||||
use self::utils::{hostname, install_color_eyre, install_tracing, update_tracing};
|
||||
@@ -58,6 +60,7 @@ pub(crate) static WINDOWS_DIRS: Lazy<Windows> = Lazy::new(|| Windows::new().expe
|
||||
// Init and load the i18n files
|
||||
i18n!("locales", fallback = "en");
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
fn run() -> Result<()> {
|
||||
install_color_eyre()?;
|
||||
ctrlc::set_handler();
|
||||
@@ -206,6 +209,9 @@ fn run() -> Result<()> {
|
||||
runner.execute(Step::Scoop, "Scoop", || windows::run_scoop(&ctx))?;
|
||||
runner.execute(Step::Winget, "Winget", || windows::run_winget(&ctx))?;
|
||||
runner.execute(Step::System, "Windows update", || windows::windows_update(&ctx))?;
|
||||
runner.execute(Step::MicrosoftStore, "Microsoft Store", || {
|
||||
windows::microsoft_store(&ctx)
|
||||
})?;
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
@@ -245,6 +251,9 @@ fn run() -> Result<()> {
|
||||
runner.execute(Step::Lure, "LURE", || linux::run_lure_update(&ctx))?;
|
||||
runner.execute(Step::Waydroid, "Waydroid", || linux::run_waydroid(&ctx))?;
|
||||
runner.execute(Step::AutoCpufreq, "auto-cpufreq", || linux::run_auto_cpufreq(&ctx))?;
|
||||
runner.execute(Step::CinnamonSpices, "Cinnamon spices", || {
|
||||
linux::run_cinnamon_spices_updater(&ctx)
|
||||
})?;
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
@@ -366,9 +375,13 @@ fn run() -> Result<()> {
|
||||
runner.execute(Step::Opam, "opam", || generic::run_opam_update(&ctx))?;
|
||||
runner.execute(Step::Vcpkg, "vcpkg", || generic::run_vcpkg_update(&ctx))?;
|
||||
runner.execute(Step::Pipx, "pipx", || generic::run_pipx_update(&ctx))?;
|
||||
runner.execute(Step::Pipxu, "pipxu", || generic::run_pipxu_update(&ctx))?;
|
||||
runner.execute(Step::Vscode, "Visual Studio Code extensions", || {
|
||||
generic::run_vscode_extensions_update(&ctx)
|
||||
})?;
|
||||
runner.execute(Step::Vscodium, "VSCodium extensions", || {
|
||||
generic::run_vscodium_extensions_update(&ctx)
|
||||
})?;
|
||||
runner.execute(Step::Conda, "conda", || generic::run_conda_update(&ctx))?;
|
||||
runner.execute(Step::Mamba, "mamba", || generic::run_mamba_update(&ctx))?;
|
||||
runner.execute(Step::Pixi, "pixi", || generic::run_pixi_update(&ctx))?;
|
||||
@@ -432,6 +445,64 @@ fn run() -> Result<()> {
|
||||
runner.execute(Step::Zvm, "ZVM", || generic::run_zvm(&ctx))?;
|
||||
runner.execute(Step::Aqua, "aqua", || generic::run_aqua(&ctx))?;
|
||||
runner.execute(Step::Bun, "bun", || generic::run_bun(&ctx))?;
|
||||
runner.execute(Step::Zigup, "zigup", || generic::run_zigup(&ctx))?;
|
||||
runner.execute(Step::JetbrainsToolbox, "JetBrains Toolbox", || {
|
||||
generic::run_jetbrains_toolbox(&ctx)
|
||||
})?;
|
||||
runner.execute(Step::AndroidStudio, "Android Studio plugins", || {
|
||||
generic::run_android_studio(&ctx)
|
||||
})?;
|
||||
runner.execute(Step::JetbrainsAqua, "JetBrains Aqua plugins", || {
|
||||
generic::run_jetbrains_aqua(&ctx)
|
||||
})?;
|
||||
runner.execute(Step::JetbrainsClion, "JetBrains CLion plugins", || {
|
||||
generic::run_jetbrains_clion(&ctx)
|
||||
})?;
|
||||
runner.execute(Step::JetbrainsDatagrip, "JetBrains DataGrip plugins", || {
|
||||
generic::run_jetbrains_datagrip(&ctx)
|
||||
})?;
|
||||
runner.execute(Step::JetbrainsDataspell, "JetBrains DataSpell plugins", || {
|
||||
generic::run_jetbrains_dataspell(&ctx)
|
||||
})?;
|
||||
// JetBrains dotCover has no CLI
|
||||
// JetBrains dotMemory has no CLI
|
||||
// JetBrains dotPeek has no CLI
|
||||
// JetBrains dotTrace has no CLI
|
||||
// JetBrains Fleet has a different CLI without a `fleet update` command.
|
||||
runner.execute(Step::JetbrainsGateway, "JetBrains Gateway plugins", || {
|
||||
generic::run_jetbrains_gateway(&ctx)
|
||||
})?;
|
||||
runner.execute(Step::JetbrainsGoland, "JetBrains GoLand plugins", || {
|
||||
generic::run_jetbrains_goland(&ctx)
|
||||
})?;
|
||||
runner.execute(Step::JetbrainsIdea, "JetBrains IntelliJ IDEA plugins", || {
|
||||
generic::run_jetbrains_idea(&ctx)
|
||||
})?;
|
||||
runner.execute(Step::JetbrainsMps, "JetBrains MPS plugins", || {
|
||||
generic::run_jetbrains_mps(&ctx)
|
||||
})?;
|
||||
runner.execute(Step::JetbrainsPhpstorm, "JetBrains PhpStorm plugins", || {
|
||||
generic::run_jetbrains_phpstorm(&ctx)
|
||||
})?;
|
||||
runner.execute(Step::JetbrainsPycharm, "JetBrains PyCharm plugins", || {
|
||||
generic::run_jetbrains_pycharm(&ctx)
|
||||
})?;
|
||||
// JetBrains ReSharper has no CLI (it's a VSCode extension)
|
||||
// JetBrains ReSharper C++ has no CLI (it's a VSCode extension)
|
||||
runner.execute(Step::JetbrainsRider, "JetBrains Rider plugins", || {
|
||||
generic::run_jetbrains_rider(&ctx)
|
||||
})?;
|
||||
runner.execute(Step::JetbrainsRubymine, "JetBrains RubyMine plugins", || {
|
||||
generic::run_jetbrains_rubymine(&ctx)
|
||||
})?;
|
||||
runner.execute(Step::JetbrainsRustrover, "JetBrains RustRover plugins", || {
|
||||
generic::run_jetbrains_rustrover(&ctx)
|
||||
})?;
|
||||
// JetBrains Space Desktop does not have a CLI
|
||||
runner.execute(Step::JetbrainsWebstorm, "JetBrains WebStorm plugins", || {
|
||||
generic::run_jetbrains_webstorm(&ctx)
|
||||
})?;
|
||||
runner.execute(Step::Yazi, "Yazi packages", || generic::run_yazi(&ctx))?;
|
||||
|
||||
if should_run_powershell {
|
||||
runner.execute(Step::Powershell, "Powershell Modules Update", || {
|
||||
@@ -488,13 +559,13 @@ fn run() -> Result<()> {
|
||||
print_info(t!("\n(R)eboot\n(S)hell\n(Q)uit"));
|
||||
loop {
|
||||
match get_key() {
|
||||
Ok(Key::Char('s')) | Ok(Key::Char('S')) => {
|
||||
Ok(Key::Char('s' | 'S')) => {
|
||||
run_shell().context("Failed to execute shell")?;
|
||||
}
|
||||
Ok(Key::Char('r')) | Ok(Key::Char('R')) => {
|
||||
Ok(Key::Char('r' | 'R')) => {
|
||||
reboot().context("Failed to reboot")?;
|
||||
}
|
||||
Ok(Key::Char('q')) | Ok(Key::Char('Q')) => (),
|
||||
Ok(Key::Char('q' | 'Q')) => (),
|
||||
_ => {
|
||||
continue;
|
||||
}
|
||||
@@ -513,7 +584,7 @@ fn run() -> Result<()> {
|
||||
t!("Topgrade finished successfully")
|
||||
},
|
||||
Some(Duration::from_secs(10)),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if failed {
|
||||
|
||||
@@ -9,7 +9,7 @@ use rust_i18n::t;
|
||||
use self_update_crate::backends::github::Update;
|
||||
use self_update_crate::update::UpdateStatus;
|
||||
|
||||
use super::terminal::*;
|
||||
use super::terminal::{print_info, print_separator};
|
||||
#[cfg(windows)]
|
||||
use crate::error::Upgraded;
|
||||
|
||||
|
||||
@@ -140,7 +140,7 @@ pub fn run_containers(ctx: &ExecutionContext) -> Result<()> {
|
||||
list_containers(&crt, ctx.config().containers_ignored_tags()).context("Failed to list Docker containers")?;
|
||||
debug!("Containers to inspect: {:?}", containers);
|
||||
|
||||
for container in containers.iter() {
|
||||
for container in &containers {
|
||||
debug!("Pulling container '{}'", container);
|
||||
let args = vec![
|
||||
"pull",
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
(when (fboundp 'paradox-upgrade-packages)
|
||||
(progn
|
||||
(unless (boundp 'paradox-github-token)
|
||||
(setq paradox-github-token t))
|
||||
(paradox-upgrade-packages)
|
||||
(princ
|
||||
(if (get-buffer "*Paradox Report*")
|
||||
(with-current-buffer "*Paradox Report*" (buffer-string))
|
||||
"\nNothing to upgrade\n"))))
|
||||
(when (featurep 'package)
|
||||
(if (fboundp 'package-upgrade-all)
|
||||
(package-upgrade-all nil)
|
||||
(message "Your Emacs version doesn't support unattended packages upgrade")))
|
||||
|
||||
@@ -1,29 +1,30 @@
|
||||
#![allow(unused_imports)]
|
||||
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
use std::{env, path::Path};
|
||||
use std::{fs, io::Write};
|
||||
|
||||
use color_eyre::eyre::eyre;
|
||||
use color_eyre::eyre::Context;
|
||||
use color_eyre::eyre::Result;
|
||||
use color_eyre::eyre::{eyre, OptionExt};
|
||||
use jetbrains_toolbox_updater::{find_jetbrains_toolbox, update_jetbrains_toolbox, FindError};
|
||||
use regex::bytes::Regex;
|
||||
use rust_i18n::t;
|
||||
use semver::Version;
|
||||
use std::ffi::OsString;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
use std::sync::LazyLock;
|
||||
use std::{env, path::Path};
|
||||
use std::{fs, io::Write};
|
||||
use tempfile::tempfile_in;
|
||||
use tracing::{debug, error};
|
||||
use tracing::{debug, error, warn};
|
||||
|
||||
use crate::command::{CommandExt, Utf8Output};
|
||||
use crate::execution_context::ExecutionContext;
|
||||
use crate::executor::ExecutorOutput;
|
||||
use crate::terminal::{print_separator, shell};
|
||||
use crate::utils::{self, check_is_python_2_or_shim, get_require_sudo_string, require, require_option, which, PathExt};
|
||||
use crate::Step;
|
||||
use crate::utils::{check_is_python_2_or_shim, get_require_sudo_string, require, require_option, which, PathExt};
|
||||
use crate::HOME_DIR;
|
||||
use crate::{
|
||||
error::{SkipStep, StepFailed, TopgradeError},
|
||||
terminal::print_warning,
|
||||
};
|
||||
use crate::{output_changed_message, Step};
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn is_wsl() -> Result<bool> {
|
||||
@@ -39,8 +40,7 @@ pub fn is_wsl() -> Result<bool> {
|
||||
|
||||
pub fn run_cargo_update(ctx: &ExecutionContext) -> Result<()> {
|
||||
let cargo_dir = env::var_os("CARGO_HOME")
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|| HOME_DIR.join(".cargo"))
|
||||
.map_or_else(|| HOME_DIR.join(".cargo"), PathBuf::from)
|
||||
.require()?;
|
||||
require("cargo").or_else(|_| {
|
||||
require_option(
|
||||
@@ -59,13 +59,11 @@ pub fn run_cargo_update(ctx: &ExecutionContext) -> Result<()> {
|
||||
let cargo_update = require("cargo-install-update")
|
||||
.ok()
|
||||
.or_else(|| cargo_dir.join("bin/cargo-install-update").if_exists());
|
||||
let cargo_update = match cargo_update {
|
||||
Some(e) => e,
|
||||
None => {
|
||||
let message = String::from("cargo-update isn't installed so Topgrade can't upgrade cargo packages.\nInstall cargo-update by running `cargo install cargo-update`");
|
||||
print_warning(&message);
|
||||
return Err(SkipStep(message).into());
|
||||
}
|
||||
|
||||
let Some(cargo_update) = cargo_update else {
|
||||
let message = String::from("cargo-update isn't installed so Topgrade can't upgrade cargo packages.\nInstall cargo-update by running `cargo install cargo-update`");
|
||||
print_warning(&message);
|
||||
return Err(SkipStep(message).into());
|
||||
};
|
||||
|
||||
ctx.run_type()
|
||||
@@ -77,14 +75,11 @@ pub fn run_cargo_update(ctx: &ExecutionContext) -> Result<()> {
|
||||
let cargo_cache = require("cargo-cache")
|
||||
.ok()
|
||||
.or_else(|| cargo_dir.join("bin/cargo-cache").if_exists());
|
||||
match cargo_cache {
|
||||
Some(e) => {
|
||||
ctx.run_type().execute(e).args(["-a"]).status_checked()?;
|
||||
}
|
||||
None => {
|
||||
let message = String::from("cargo-cache isn't installed so Topgrade can't cleanup cargo packages.\nInstall cargo-cache by running `cargo install cargo-cache`");
|
||||
print_warning(message);
|
||||
}
|
||||
if let Some(e) = cargo_cache {
|
||||
ctx.run_type().execute(e).args(["-a"]).status_checked()?;
|
||||
} else {
|
||||
let message = String::from("cargo-cache isn't installed so Topgrade can't cleanup cargo packages.\nInstall cargo-cache by running `cargo install cargo-cache`");
|
||||
print_warning(message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,9 +220,47 @@ pub fn run_apm(ctx: &ExecutionContext) -> Result<()> {
|
||||
.status_checked()
|
||||
}
|
||||
|
||||
pub fn run_aqua(ctx: &ExecutionContext) -> Result<()> {
|
||||
enum Aqua {
|
||||
JetBrainsAqua(PathBuf),
|
||||
AquaCLI(PathBuf),
|
||||
}
|
||||
|
||||
impl Aqua {
|
||||
fn aqua_cli(self) -> Result<PathBuf> {
|
||||
match self {
|
||||
Aqua::AquaCLI(aqua) => Ok(aqua),
|
||||
Aqua::JetBrainsAqua(_) => {
|
||||
Err(SkipStep("Command `aqua` probably points to JetBrains Aqua".to_string()).into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn jetbrains_aqua(self) -> Result<PathBuf> {
|
||||
match self {
|
||||
Aqua::JetBrainsAqua(path) => Ok(path),
|
||||
Aqua::AquaCLI(_) => Err(SkipStep("Command `aqua` probably points to Aqua CLI".to_string()).into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_aqua(ctx: &ExecutionContext) -> Result<Aqua> {
|
||||
let aqua = require("aqua")?;
|
||||
|
||||
// Check if `aqua --help` mentions "aqua". JetBrains Aqua does not, Aqua CLI does.
|
||||
let output = ctx.run_type().execute(&aqua).arg("--help").output_checked()?;
|
||||
|
||||
if String::from_utf8(output.stdout)?.contains("aqua") {
|
||||
debug!("Detected `aqua` as Aqua CLI");
|
||||
Ok(Aqua::AquaCLI(aqua))
|
||||
} else {
|
||||
debug!("Detected `aqua` as JetBrains Aqua");
|
||||
Ok(Aqua::JetBrainsAqua(aqua))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_aqua(ctx: &ExecutionContext) -> Result<()> {
|
||||
let aqua = get_aqua(ctx)?.aqua_cli()?;
|
||||
|
||||
print_separator("Aqua");
|
||||
if ctx.run_type().dry() {
|
||||
println!("{}", t!("Updating aqua ..."));
|
||||
@@ -257,10 +290,35 @@ pub fn run_elan(ctx: &ExecutionContext) -> Result<()> {
|
||||
let elan = require("elan")?;
|
||||
|
||||
print_separator("elan");
|
||||
ctx.run_type()
|
||||
.execute(&elan)
|
||||
.args(["self", "update"])
|
||||
.status_checked()?;
|
||||
|
||||
let disabled_error_msg = "self-update is disabled";
|
||||
let executor_output = ctx.run_type().execute(&elan).args(["self", "update"]).output()?;
|
||||
match executor_output {
|
||||
ExecutorOutput::Wet(command_output) => {
|
||||
if command_output.status.success() {
|
||||
// Flush the captured output
|
||||
std::io::stdout().lock().write_all(&command_output.stdout).unwrap();
|
||||
std::io::stderr().lock().write_all(&command_output.stderr).unwrap();
|
||||
} else {
|
||||
let stderr_as_str = std::str::from_utf8(&command_output.stderr).unwrap();
|
||||
if stderr_as_str.contains(disabled_error_msg) {
|
||||
// `elan` is externally managed, we cannot do the update. Users
|
||||
// won't see any error message because Topgrade captures them
|
||||
// all.
|
||||
} else {
|
||||
// `elan` is NOT externally managed, `elan self update` can
|
||||
// be performed, but the invocation failed, so we report the
|
||||
// error to the user and error out.
|
||||
std::io::stdout().lock().write_all(&command_output.stdout).unwrap();
|
||||
std::io::stderr().lock().write_all(&command_output.stderr).unwrap();
|
||||
|
||||
return Err(StepFailed.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
ExecutorOutput::Dry => { /* nothing needed because in a dry run */ }
|
||||
}
|
||||
|
||||
ctx.run_type().execute(&elan).arg("update").status_checked()
|
||||
}
|
||||
|
||||
@@ -276,7 +334,13 @@ pub fn run_juliaup(ctx: &ExecutionContext) -> Result<()> {
|
||||
.status_checked()?;
|
||||
}
|
||||
|
||||
ctx.run_type().execute(&juliaup).arg("update").status_checked()
|
||||
ctx.run_type().execute(&juliaup).arg("update").status_checked()?;
|
||||
|
||||
if ctx.config().cleanup() {
|
||||
ctx.run_type().execute(&juliaup).arg("gc").status_checked()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn run_choosenim(ctx: &ExecutionContext) -> Result<()> {
|
||||
@@ -374,37 +438,86 @@ pub fn run_vcpkg_update(ctx: &ExecutionContext) -> Result<()> {
|
||||
command.args(["upgrade", "--no-dry-run"]).status_checked()
|
||||
}
|
||||
|
||||
pub fn run_vscode_extensions_update(ctx: &ExecutionContext) -> Result<()> {
|
||||
// Calling vscode in WSL may install a server instead of updating extensions (https://github.com/topgrade-rs/topgrade/issues/594#issuecomment-1782157367)
|
||||
/// This functions runs for both VSCode and VSCodium, as most of the process is the same for both.
|
||||
fn run_vscode_compatible<const VSCODIUM: bool>(ctx: &ExecutionContext) -> Result<()> {
|
||||
// Calling VSCode/VSCodium in WSL may install a server instead of updating extensions (https://github.com/topgrade-rs/topgrade/issues/594#issuecomment-1782157367)
|
||||
if is_wsl()? {
|
||||
return Err(SkipStep(String::from("Should not run in WSL")).into());
|
||||
}
|
||||
|
||||
let vscode = require("code")?;
|
||||
let name = if VSCODIUM { "VSCodium" } else { "VSCode" };
|
||||
let bin_name = if VSCODIUM { "codium" } else { "code" };
|
||||
let bin = require(bin_name)?;
|
||||
|
||||
// Vscode has update command only since 1.86 version ("january 2024" update), disable the update for prior versions
|
||||
// VSCode has update command only since 1.86 version ("january 2024" update), disable the update for prior versions
|
||||
// Use command `code --version` which returns 3 lines: version, git commit, instruction set. We parse only the first one
|
||||
let version: Result<Version> = match Command::new(&vscode)
|
||||
//
|
||||
// This should apply to VSCodium as well.
|
||||
let version: Result<Version> = match Command::new(&bin)
|
||||
.arg("--version")
|
||||
.output_checked_utf8()?
|
||||
.stdout
|
||||
.lines()
|
||||
.next()
|
||||
{
|
||||
Some(item) => Version::parse(item).map_err(|err| err.into()),
|
||||
_ => return Err(SkipStep(String::from("Cannot find vscode version")).into()),
|
||||
Some(item) => {
|
||||
// Strip leading zeroes because `semver` does not allow them, but VSCodium uses them sometimes.
|
||||
// This is not the case for VSCode, but just in case, and it can't really cause any issues.
|
||||
let item = item
|
||||
.split('.')
|
||||
.map(|s| if s == "0" { "0" } else { s.trim_start_matches('0') })
|
||||
.collect::<Vec<_>>()
|
||||
.join(".");
|
||||
Version::parse(&item).map_err(std::convert::Into::into)
|
||||
}
|
||||
None => {
|
||||
return Err(eyre!(output_changed_message!(
|
||||
&format!("{bin_name} --version"),
|
||||
"No first line"
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
if !matches!(version, Ok(version) if version >= Version::new(1, 86, 0)) {
|
||||
return Err(SkipStep(String::from("Too old vscode version to have update extensions command")).into());
|
||||
// Raise any errors in parsing the version
|
||||
// The benefit of handling VSCodium versions so old that the version format is something
|
||||
// unexpected is outweighed by the benefit of failing fast on new breaking versions
|
||||
let version =
|
||||
version.wrap_err_with(|| output_changed_message!(&format!("{bin_name} --version"), "Invalid version"))?;
|
||||
debug!("Detected {name} version as: {version}");
|
||||
|
||||
if version < Version::new(1, 86, 0) {
|
||||
return Err(SkipStep(format!("Too old {name} version to have update extensions command")).into());
|
||||
}
|
||||
|
||||
print_separator("Visual Studio Code extensions");
|
||||
print_separator(if VSCODIUM {
|
||||
"VSCodium extensions"
|
||||
} else {
|
||||
"Visual Studio Code extensions"
|
||||
});
|
||||
|
||||
ctx.run_type()
|
||||
.execute(vscode)
|
||||
.arg("--update-extensions")
|
||||
.status_checked()
|
||||
let mut cmd = ctx.run_type().execute(bin);
|
||||
// If its VSCode (not VSCodium)
|
||||
if !VSCODIUM {
|
||||
// And we have configured use of a profile
|
||||
if let Some(profile) = ctx.config().vscode_profile() {
|
||||
// Add the profile argument
|
||||
cmd.arg("--profile").arg(profile);
|
||||
}
|
||||
}
|
||||
|
||||
cmd.arg("--update-extensions").status_checked()
|
||||
}
|
||||
|
||||
/// Make VSCodium a separate step because:
|
||||
///
|
||||
/// 1. Users could use both VSCode and VSCodium
|
||||
/// 2. Just in case, VSCodium could have incompatible changes with VSCode
|
||||
pub fn run_vscodium_extensions_update(ctx: &ExecutionContext) -> Result<()> {
|
||||
run_vscode_compatible::<true>(ctx)
|
||||
}
|
||||
|
||||
pub fn run_vscode_extensions_update(ctx: &ExecutionContext) -> Result<()> {
|
||||
run_vscode_compatible::<false>(ctx)
|
||||
}
|
||||
|
||||
pub fn run_pipx_update(ctx: &ExecutionContext) -> Result<()> {
|
||||
@@ -421,12 +534,22 @@ pub fn run_pipx_update(ctx: &ExecutionContext) -> Result<()> {
|
||||
.map(|s| s.stdout.trim().to_owned());
|
||||
let version = Version::parse(&version_str?);
|
||||
if matches!(version, Ok(version) if version >= Version::new(1, 4, 0)) {
|
||||
command_args.push("--quiet")
|
||||
command_args.push("--quiet");
|
||||
}
|
||||
|
||||
ctx.run_type().execute(pipx).args(command_args).status_checked()
|
||||
}
|
||||
|
||||
pub fn run_pipxu_update(ctx: &ExecutionContext) -> Result<()> {
|
||||
let pipxu = require("pipxu")?;
|
||||
print_separator("pipxu");
|
||||
|
||||
ctx.run_type()
|
||||
.execute(pipxu)
|
||||
.args(["upgrade", "--all"])
|
||||
.status_checked()
|
||||
}
|
||||
|
||||
pub fn run_conda_update(ctx: &ExecutionContext) -> Result<()> {
|
||||
let conda = require("conda")?;
|
||||
|
||||
@@ -440,19 +563,45 @@ pub fn run_conda_update(ctx: &ExecutionContext) -> Result<()> {
|
||||
|
||||
print_separator("Conda");
|
||||
|
||||
let mut command = ctx.run_type().execute(conda);
|
||||
let mut command = ctx.run_type().execute(&conda);
|
||||
command.args(["update", "--all", "-n", "base"]);
|
||||
if ctx.config().yes(Step::Conda) {
|
||||
command.arg("--yes");
|
||||
}
|
||||
command.status_checked()
|
||||
command.status_checked()?;
|
||||
|
||||
if ctx.config().cleanup() {
|
||||
let mut command = ctx.run_type().execute(conda);
|
||||
command.args(["clean", "--all"]);
|
||||
if ctx.config().yes(Step::Conda) {
|
||||
command.arg("--yes");
|
||||
}
|
||||
command.status_checked()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn run_pixi_update(ctx: &ExecutionContext) -> Result<()> {
|
||||
let pixi = require("pixi")?;
|
||||
print_separator("Pixi");
|
||||
|
||||
ctx.run_type().execute(pixi).args(["self-update"]).status_checked()
|
||||
// Check if `pixi --help` mentions self-update, if yes, self-update must be enabled.
|
||||
// pixi self-update --help works regardless of whether the feature is enabled.
|
||||
let output = ctx.run_type().execute(&pixi).arg("--help").output_checked()?;
|
||||
|
||||
if String::from_utf8(output.stdout)?.contains("self-update") {
|
||||
ctx.run_type()
|
||||
.execute(&pixi)
|
||||
.args(["self-update"])
|
||||
.status_checked()
|
||||
.ok();
|
||||
}
|
||||
|
||||
ctx.run_type()
|
||||
.execute(&pixi)
|
||||
.args(["global", "update"])
|
||||
.status_checked()
|
||||
}
|
||||
|
||||
pub fn run_mamba_update(ctx: &ExecutionContext) -> Result<()> {
|
||||
@@ -460,12 +609,23 @@ pub fn run_mamba_update(ctx: &ExecutionContext) -> Result<()> {
|
||||
|
||||
print_separator("Mamba");
|
||||
|
||||
let mut command = ctx.run_type().execute(mamba);
|
||||
let mut command = ctx.run_type().execute(&mamba);
|
||||
command.args(["update", "--all", "-n", "base"]);
|
||||
if ctx.config().yes(Step::Mamba) {
|
||||
command.arg("--yes");
|
||||
}
|
||||
command.status_checked()
|
||||
command.status_checked()?;
|
||||
|
||||
if ctx.config().cleanup() {
|
||||
let mut command = ctx.run_type().execute(&mamba);
|
||||
command.args(["clean", "--all"]);
|
||||
if ctx.config().yes(Step::Mamba) {
|
||||
command.arg("--yes");
|
||||
}
|
||||
command.status_checked()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn run_miktex_packages_update(ctx: &ExecutionContext) -> Result<()> {
|
||||
@@ -487,7 +647,7 @@ pub fn run_pip3_update(ctx: &ExecutionContext) -> Result<()> {
|
||||
(Ok(py), _) => py,
|
||||
(Err(_), Ok(py3)) => py3,
|
||||
(Err(py_err), Err(py3_err)) => {
|
||||
return Err(SkipStep(format!("Skip due to following reasons: {} {}", py_err, py3_err)).into());
|
||||
return Err(SkipStep(format!("Skip due to following reasons: {py_err} {py3_err}")).into());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -513,9 +673,12 @@ pub fn run_pip3_update(ctx: &ExecutionContext) -> Result<()> {
|
||||
{
|
||||
Ok(output) => {
|
||||
let stdout = output.stdout.trim();
|
||||
stdout
|
||||
.parse::<bool>()
|
||||
.expect("unexpected output that is not `true` or `false`")
|
||||
stdout.parse::<bool>().wrap_err_with(|| {
|
||||
output_changed_message!(
|
||||
"pip config get global.break-system-packages",
|
||||
"unexpected output that is not `true` or `false`"
|
||||
)
|
||||
})?
|
||||
}
|
||||
// it can fail because this key may not be set
|
||||
//
|
||||
@@ -836,14 +999,45 @@ pub fn run_dotnet_upgrade(ctx: &ExecutionContext) -> Result<()> {
|
||||
.execute(&dotnet)
|
||||
.args(["tool", "update", package_name, "--global"])
|
||||
.status_checked()
|
||||
.with_context(|| format!("Failed to update .NET package {:?}", package_name))?;
|
||||
.with_context(|| format!("Failed to update .NET package {package_name:?}"))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
enum Hx {
|
||||
Helix(PathBuf),
|
||||
HxHexdump,
|
||||
}
|
||||
|
||||
impl Hx {
|
||||
fn helix(self) -> Result<PathBuf> {
|
||||
match self {
|
||||
Hx::Helix(hx) => Ok(hx),
|
||||
Hx::HxHexdump => {
|
||||
Err(SkipStep("Command `hx` probably points to hx (hexdump alternative)".to_string()).into())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_hx(ctx: &ExecutionContext) -> Result<Hx> {
|
||||
let hx = require("hx")?;
|
||||
|
||||
// Check if `hx --help` mentions "helix". Helix does, hx (hexdump alternative) doesn't.
|
||||
let output = ctx.run_type().execute(&hx).arg("--help").output_checked()?;
|
||||
|
||||
if String::from_utf8(output.stdout)?.contains("helix") {
|
||||
debug!("Detected `hx` as Helix");
|
||||
Ok(Hx::Helix(hx))
|
||||
} else {
|
||||
debug!("Detected `hx` as hx (hexdump alternative)");
|
||||
Ok(Hx::HxHexdump)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_helix_grammars(ctx: &ExecutionContext) -> Result<()> {
|
||||
let helix = require("helix").or(require("hx"))?;
|
||||
let helix = require("helix").or(get_hx(ctx)?.helix())?;
|
||||
|
||||
print_separator("Helix");
|
||||
|
||||
@@ -908,10 +1102,15 @@ pub fn update_julia_packages(ctx: &ExecutionContext) -> Result<()> {
|
||||
|
||||
print_separator(t!("Julia Packages"));
|
||||
|
||||
ctx.run_type()
|
||||
.execute(julia)
|
||||
.args(["-e", "using Pkg; Pkg.update()"])
|
||||
.status_checked()
|
||||
let mut executor = ctx.run_type().execute(julia);
|
||||
|
||||
executor.arg(if ctx.config().julia_use_startup_file() {
|
||||
"--startup-file=yes"
|
||||
} else {
|
||||
"--startup-file=no"
|
||||
});
|
||||
|
||||
executor.args(["-e", "using Pkg; Pkg.update()"]).status_checked()
|
||||
}
|
||||
|
||||
pub fn run_helm_repo_update(ctx: &ExecutionContext) -> Result<()> {
|
||||
@@ -1024,28 +1223,229 @@ pub fn run_lensfun_update_data(ctx: &ExecutionContext) -> Result<()> {
|
||||
|
||||
pub fn run_poetry(ctx: &ExecutionContext) -> Result<()> {
|
||||
let poetry = require("poetry")?;
|
||||
|
||||
#[cfg(unix)]
|
||||
fn get_interpreter(poetry: &PathBuf) -> Result<(PathBuf, Option<OsString>)> {
|
||||
// Parse the standard Unix shebang line: #!interpreter [optional-arg]
|
||||
// Spaces and tabs on either side of interpreter are ignored.
|
||||
|
||||
use std::ffi::OsStr;
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
|
||||
static SHEBANG_REGEX: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"^#![ \t]*([^ \t\n]+)(?:[ \t]+([^\n]+)?)?").unwrap());
|
||||
|
||||
let script = fs::read(poetry)?;
|
||||
if let Some(c) = SHEBANG_REGEX.captures(&script) {
|
||||
let interpreter = OsStr::from_bytes(&c[1]).into();
|
||||
let args = c.get(2).map(|args| OsStr::from_bytes(args.as_bytes()).into());
|
||||
return Ok((interpreter, args));
|
||||
}
|
||||
|
||||
Err(eyre!("Could not find shebang"))
|
||||
}
|
||||
#[cfg(windows)]
|
||||
fn get_interpreter(poetry: &PathBuf) -> Result<(PathBuf, Option<OsString>)> {
|
||||
// Parse the shebang line from scripts using https://bitbucket.org/vinay.sajip/simple_launcher,
|
||||
// such as those created by pip. In contrast to Unix shebang lines, interpreter paths can
|
||||
// contain spaces, if they are double-quoted.
|
||||
|
||||
use std::str;
|
||||
|
||||
static SHEBANG_REGEX: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r#"^#![ \t]*(?:"([^"\n]+)"|([^" \t\n]+))(?:[ \t]+([^\n]+)?)?"#).unwrap());
|
||||
|
||||
let data = fs::read(poetry)?;
|
||||
|
||||
let pos = match data.windows(4).rposition(|b| b == b"PK\x05\x06") {
|
||||
Some(i) => i,
|
||||
None => return Err(eyre!("Not a ZIP archive")),
|
||||
};
|
||||
|
||||
let cdr_size = match data.get(pos + 12..pos + 16) {
|
||||
Some(b) => u32::from_le_bytes(b.try_into().unwrap()) as usize,
|
||||
None => return Err(eyre!("Invalid CDR size")),
|
||||
};
|
||||
let cdr_offset = match data.get(pos + 16..pos + 20) {
|
||||
Some(b) => u32::from_le_bytes(b.try_into().unwrap()) as usize,
|
||||
None => return Err(eyre!("Invalid CDR offset")),
|
||||
};
|
||||
if pos < cdr_size + cdr_offset {
|
||||
return Err(eyre!("Invalid ZIP archive"));
|
||||
}
|
||||
let arc_pos = pos - cdr_size - cdr_offset;
|
||||
match data[..arc_pos].windows(2).rposition(|b| b == b"#!") {
|
||||
Some(l) => {
|
||||
let line = &data[l..arc_pos - 1];
|
||||
if let Some(c) = SHEBANG_REGEX.captures(line) {
|
||||
let interpreter = c.get(1).or_else(|| c.get(2)).unwrap();
|
||||
// shebang line should be valid utf8
|
||||
let interpreter = str::from_utf8(interpreter.as_bytes())?.into();
|
||||
let args = match c.get(3) {
|
||||
Some(args) => Some(str::from_utf8(args.as_bytes())?.into()),
|
||||
None => None,
|
||||
};
|
||||
Ok((interpreter, args))
|
||||
} else {
|
||||
Err(eyre!("Invalid shebang line"))
|
||||
}
|
||||
}
|
||||
None => Err(eyre!("Could not find shebang")),
|
||||
}
|
||||
}
|
||||
|
||||
if ctx.config().poetry_force_self_update() {
|
||||
debug!("forcing poetry self update");
|
||||
} else {
|
||||
let (interp, interp_args) = get_interpreter(&poetry)
|
||||
.map_err(|e| SkipStep(format!("Could not find interpreter for {}: {}", poetry.display(), e)))?;
|
||||
debug!("poetry interpreter: {:?}, args: {:?}", interp, interp_args);
|
||||
|
||||
let check_official_install_script =
|
||||
"import sys; from os import path; print('Y') if path.isfile(path.join(sys.prefix, 'poetry_env')) else print('N')";
|
||||
let mut command = Command::new(&interp);
|
||||
if let Some(args) = interp_args {
|
||||
command.arg(args);
|
||||
}
|
||||
let output = command
|
||||
.args(["-c", check_official_install_script])
|
||||
.output_checked_utf8()?;
|
||||
let stdout = output.stdout.trim();
|
||||
let official_install = match stdout {
|
||||
"N" => false,
|
||||
"Y" => true,
|
||||
_ => unreachable!("unexpected output from `check_official_install_script`"),
|
||||
};
|
||||
|
||||
debug!("poetry is official install: {}", official_install);
|
||||
|
||||
if !official_install {
|
||||
return Err(SkipStep("Not installed with the official script".to_string()).into());
|
||||
}
|
||||
}
|
||||
|
||||
print_separator("Poetry");
|
||||
ctx.run_type().execute(poetry).args(["self", "update"]).status_checked()
|
||||
ctx.run_type()
|
||||
.execute(&poetry)
|
||||
.args(["self", "update"])
|
||||
.status_checked()
|
||||
}
|
||||
|
||||
pub fn run_uv(ctx: &ExecutionContext) -> Result<()> {
|
||||
let uv_exec = require("uv")?;
|
||||
print_separator("uv");
|
||||
|
||||
ctx.run_type()
|
||||
// 1. Run `uv self update` if the `uv` binary is built with the `self-update`
|
||||
// cargo feature enabled.
|
||||
//
|
||||
// To check if this feature is enabled or not, different version of `uv` need
|
||||
// different approaches, we need to know the version first and handle them
|
||||
// separately.
|
||||
let uv_version_output = ctx
|
||||
.run_type()
|
||||
.execute(&uv_exec)
|
||||
.args(["self", "update"])
|
||||
.status_checked()
|
||||
.ok();
|
||||
.arg("--version")
|
||||
.output_checked_utf8()?;
|
||||
// Multiple possible output formats are possible according to uv source code
|
||||
//
|
||||
// https://github.com/astral-sh/uv/blob/6b7f60c1eaa840c2e933a0fb056ab46f99c991a5/crates/uv-cli/src/version.rs#L28-L42
|
||||
//
|
||||
// For example:
|
||||
// "uv 0.5.11 (c4d0caaee 2024-12-19)\n"
|
||||
// "uv 0.5.11+1 (xxxd0cee 2024-12-20)\n"
|
||||
// "uv 0.6.14\n"
|
||||
|
||||
// ignoring self-update errors, because they are likely due to uv's
|
||||
// installation being managed by another package manager, in which
|
||||
// case another step will handle the update.
|
||||
let uv_version_output_stdout = uv_version_output.stdout;
|
||||
|
||||
let version_str = {
|
||||
// Trim the starting "uv" and " " (whitespace)
|
||||
let start_trimmed = uv_version_output_stdout
|
||||
.trim_start_matches("uv")
|
||||
.trim_start_matches(' ');
|
||||
// Remove the tailing part " (c4d0caaee 2024-12-19)\n", if it's there
|
||||
match start_trimmed.find(' ') {
|
||||
None => start_trimmed.trim_end_matches('\n'), // Otherwise, just strip the newline
|
||||
Some(i) => &start_trimmed[..i],
|
||||
}
|
||||
|
||||
// After trimming, it should be a string in 2 possible formats, both can be handled by `Version::parse()`
|
||||
//
|
||||
// 1. "0.5.11"
|
||||
// 2. "0.5.11+1"
|
||||
};
|
||||
let version =
|
||||
Version::parse(version_str).wrap_err_with(|| output_changed_message!("uv --version", "Invalid version"))?;
|
||||
|
||||
if version < Version::new(0, 4, 25) {
|
||||
// For uv before version 0.4.25 (exclusive), the `self` sub-command only
|
||||
// exists under the `self-update` feature, we run `uv self --help` to check
|
||||
// the feature gate.
|
||||
let self_update_feature_enabled = ctx
|
||||
.run_type()
|
||||
.execute(&uv_exec)
|
||||
.args(["self", "--help"])
|
||||
.output_checked()
|
||||
.is_ok();
|
||||
|
||||
if self_update_feature_enabled {
|
||||
ctx.run_type()
|
||||
.execute(&uv_exec)
|
||||
.args(["self", "update"])
|
||||
.status_checked()?;
|
||||
}
|
||||
} else {
|
||||
// After 0.4.25 (inclusive), running `uv self` succeeds regardless of the
|
||||
// feature gate, so the above approach won't work.
|
||||
//
|
||||
// We run `uv self update` directly, if it outputs:
|
||||
//
|
||||
// "uv was installed through an external package manager, and self-update is not available. Please use your package manager to update uv.\n"
|
||||
|
||||
const ERROR_MSG: &str = "uv was installed through an external package manager, and self-update is not available. Please use your package manager to update uv.";
|
||||
|
||||
let output = ctx
|
||||
.run_type()
|
||||
.execute(&uv_exec)
|
||||
.args(["self", "update"])
|
||||
// `output()` captures the output so that users won't see it for now.
|
||||
.output()
|
||||
.expect("this should be ok regardless of this child process's exit code");
|
||||
let output = match output {
|
||||
ExecutorOutput::Wet(wet) => wet,
|
||||
ExecutorOutput::Dry => unreachable!("the whole function returns when we run `uv --version` under dry-run"),
|
||||
};
|
||||
let stderr = std::str::from_utf8(&output.stderr).expect("output should be UTF-8 encoded");
|
||||
|
||||
if stderr.contains(ERROR_MSG) {
|
||||
// Feature `self-update` is disabled, nothing to do.
|
||||
} else {
|
||||
// Feature is enabled, flush the captured output so that users know we did the self-update.
|
||||
|
||||
std::io::stdout().write_all(&output.stdout)?;
|
||||
std::io::stderr().write_all(&output.stderr)?;
|
||||
|
||||
// And, if self update failed, fail the step as well.
|
||||
if !output.status.success() {
|
||||
return Err(eyre!("uv self update failed"));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 2. Update the installed tools
|
||||
ctx.run_type()
|
||||
.execute(&uv_exec)
|
||||
.args(["tool", "upgrade", "--all"])
|
||||
.status_checked()
|
||||
.status_checked()?;
|
||||
|
||||
if ctx.config().cleanup() {
|
||||
// 3. Prune cache
|
||||
ctx.run_type()
|
||||
.execute(&uv_exec)
|
||||
.args(["cache", "prune"])
|
||||
.status_checked()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Involve `zvm upgrade` to update ZVM
|
||||
@@ -1064,3 +1464,199 @@ pub fn run_bun(ctx: &ExecutionContext) -> Result<()> {
|
||||
|
||||
ctx.run_type().execute(bun).arg("upgrade").status_checked()
|
||||
}
|
||||
|
||||
pub fn run_zigup(ctx: &ExecutionContext) -> Result<()> {
|
||||
let zigup = require("zigup")?;
|
||||
let config = ctx.config();
|
||||
|
||||
print_separator("zigup");
|
||||
|
||||
let mut path_args = Vec::new();
|
||||
if let Some(path) = config.zigup_path_link() {
|
||||
path_args.push("--path-link".to_owned());
|
||||
path_args.push(shellexpand::tilde(path).into_owned());
|
||||
}
|
||||
if let Some(path) = config.zigup_install_dir() {
|
||||
path_args.push("--install-dir".to_owned());
|
||||
path_args.push(shellexpand::tilde(path).into_owned());
|
||||
}
|
||||
|
||||
for zig_version in config.zigup_target_versions() {
|
||||
ctx.run_type()
|
||||
.execute(&zigup)
|
||||
.args(&path_args)
|
||||
.arg("fetch")
|
||||
.arg(&zig_version)
|
||||
.status_checked()?;
|
||||
|
||||
if config.zigup_cleanup() {
|
||||
ctx.run_type()
|
||||
.execute(&zigup)
|
||||
.args(&path_args)
|
||||
.arg("keep")
|
||||
.arg(&zig_version)
|
||||
.status_checked()?;
|
||||
}
|
||||
}
|
||||
|
||||
if config.zigup_cleanup() {
|
||||
ctx.run_type()
|
||||
.execute(zigup)
|
||||
.args(&path_args)
|
||||
.arg("clean")
|
||||
.status_checked()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn run_jetbrains_toolbox(_ctx: &ExecutionContext) -> Result<()> {
|
||||
let installation = find_jetbrains_toolbox();
|
||||
match installation {
|
||||
Err(FindError::NotFound) => {
|
||||
// Skip
|
||||
Err(SkipStep(format!("{}", t!("No JetBrains Toolbox installation found"))).into())
|
||||
}
|
||||
Err(FindError::UnsupportedOS(os)) => {
|
||||
// Skip
|
||||
Err(SkipStep(format!("{}", t!("Unsupported operating system {os}", os = os))).into())
|
||||
}
|
||||
Err(e) => {
|
||||
// Unexpected error
|
||||
println!(
|
||||
"{}",
|
||||
t!("jetbrains-toolbox-updater encountered an unexpected error during finding:")
|
||||
);
|
||||
println!("{e:?}");
|
||||
Err(StepFailed.into())
|
||||
}
|
||||
Ok(installation) => {
|
||||
print_separator("JetBrains Toolbox");
|
||||
|
||||
match update_jetbrains_toolbox(installation) {
|
||||
Err(e) => {
|
||||
// Unexpected error
|
||||
println!(
|
||||
"{}",
|
||||
t!("jetbrains-toolbox-updater encountered an unexpected error during updating:")
|
||||
);
|
||||
println!("{e:?}");
|
||||
Err(StepFailed.into())
|
||||
}
|
||||
Ok(()) => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn run_jetbrains_ide_generic<const IS_JETBRAINS: bool>(ctx: &ExecutionContext, bin: PathBuf, name: &str) -> Result<()> {
|
||||
let prefix = if IS_JETBRAINS { "JetBrains " } else { "" };
|
||||
print_separator(format!("{prefix}{name} plugins"));
|
||||
|
||||
// The `update` command is undocumented, but tested on all of the below.
|
||||
let output = ctx.run_type().execute(&bin).arg("update").output()?;
|
||||
let output = match output {
|
||||
ExecutorOutput::Dry => return Ok(()),
|
||||
ExecutorOutput::Wet(output) => output,
|
||||
};
|
||||
// Write the output which we swallowed in all cases
|
||||
std::io::stdout().lock().write_all(&output.stdout).unwrap();
|
||||
std::io::stderr().lock().write_all(&output.stderr).unwrap();
|
||||
|
||||
let stdout = String::from_utf8(output.stdout.clone()).wrap_err("Expected valid UTF-8 output")?;
|
||||
|
||||
// "Only one instance of RustRover can be run at a time."
|
||||
if stdout.contains("Only one instance of ") && stdout.contains(" can be run at a time.") {
|
||||
// It's always paired with status code 1
|
||||
let status_code = output
|
||||
.status
|
||||
.code()
|
||||
.ok_or_eyre("Failed to get status code; was killed with signal")?;
|
||||
if status_code != 1 {
|
||||
return Err(eyre!("Expected status code 1 ('Only one instance of <IDE> can be run at a time.'), but found status code {}. Output: {output:?}", status_code));
|
||||
}
|
||||
// Don't crash, but don't be silent either
|
||||
warn!("{name} is already running, can't update it now.");
|
||||
Err(SkipStep(format!("{name} is already running, can't update it now.")).into())
|
||||
} else if !output.status.success() {
|
||||
// Unknown failure
|
||||
Err(eyre!("Running `{bin:?} update` failed. Output: {output:?}"))
|
||||
} else {
|
||||
// Success. Output was already written above
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn run_jetbrains_ide(ctx: &ExecutionContext, bin: PathBuf, name: &str) -> Result<()> {
|
||||
run_jetbrains_ide_generic::<true>(ctx, bin, name)
|
||||
}
|
||||
|
||||
pub fn run_android_studio(ctx: &ExecutionContext) -> Result<()> {
|
||||
// We don't use `run_jetbrains_ide` here because that would print "JetBrains Android Studio",
|
||||
// which is incorrect as Android Studio is made by Google. Just "Android Studio" is fine.
|
||||
run_jetbrains_ide_generic::<false>(ctx, require("studio")?, "Android Studio")
|
||||
}
|
||||
|
||||
pub fn run_jetbrains_aqua(ctx: &ExecutionContext) -> Result<()> {
|
||||
run_jetbrains_ide(ctx, get_aqua(ctx)?.jetbrains_aqua()?, "Aqua")
|
||||
}
|
||||
|
||||
pub fn run_jetbrains_clion(ctx: &ExecutionContext) -> Result<()> {
|
||||
run_jetbrains_ide(ctx, require("clion")?, "CLion")
|
||||
}
|
||||
|
||||
pub fn run_jetbrains_datagrip(ctx: &ExecutionContext) -> Result<()> {
|
||||
run_jetbrains_ide(ctx, require("datagrip")?, "DataGrip")
|
||||
}
|
||||
|
||||
pub fn run_jetbrains_dataspell(ctx: &ExecutionContext) -> Result<()> {
|
||||
run_jetbrains_ide(ctx, require("dataspell")?, "DataSpell")
|
||||
}
|
||||
|
||||
pub fn run_jetbrains_gateway(ctx: &ExecutionContext) -> Result<()> {
|
||||
run_jetbrains_ide(ctx, require("gateway")?, "Gateway")
|
||||
}
|
||||
|
||||
pub fn run_jetbrains_goland(ctx: &ExecutionContext) -> Result<()> {
|
||||
run_jetbrains_ide(ctx, require("goland")?, "Goland")
|
||||
}
|
||||
|
||||
pub fn run_jetbrains_idea(ctx: &ExecutionContext) -> Result<()> {
|
||||
run_jetbrains_ide(ctx, require("idea")?, "IntelliJ IDEA")
|
||||
}
|
||||
|
||||
pub fn run_jetbrains_mps(ctx: &ExecutionContext) -> Result<()> {
|
||||
run_jetbrains_ide(ctx, require("mps")?, "MPS")
|
||||
}
|
||||
|
||||
pub fn run_jetbrains_phpstorm(ctx: &ExecutionContext) -> Result<()> {
|
||||
run_jetbrains_ide(ctx, require("phpstorm")?, "PhpStorm")
|
||||
}
|
||||
|
||||
pub fn run_jetbrains_pycharm(ctx: &ExecutionContext) -> Result<()> {
|
||||
run_jetbrains_ide(ctx, require("pycharm")?, "PyCharm")
|
||||
}
|
||||
|
||||
pub fn run_jetbrains_rider(ctx: &ExecutionContext) -> Result<()> {
|
||||
run_jetbrains_ide(ctx, require("rider")?, "Rider")
|
||||
}
|
||||
|
||||
pub fn run_jetbrains_rubymine(ctx: &ExecutionContext) -> Result<()> {
|
||||
run_jetbrains_ide(ctx, require("rubymine")?, "RubyMine")
|
||||
}
|
||||
|
||||
pub fn run_jetbrains_rustrover(ctx: &ExecutionContext) -> Result<()> {
|
||||
run_jetbrains_ide(ctx, require("rustrover")?, "RustRover")
|
||||
}
|
||||
|
||||
pub fn run_jetbrains_webstorm(ctx: &ExecutionContext) -> Result<()> {
|
||||
run_jetbrains_ide(ctx, require("webstorm")?, "WebStorm")
|
||||
}
|
||||
|
||||
pub fn run_yazi(ctx: &ExecutionContext) -> Result<()> {
|
||||
let ya = require("ya")?;
|
||||
|
||||
print_separator("Yazi packages");
|
||||
|
||||
ctx.run_type().execute(ya).args(["pack", "-u"]).status_checked()
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ pub fn run_git_pull(ctx: &ExecutionContext) -> Result<()> {
|
||||
print_warning(t!(
|
||||
"Path {pattern} did not contain any git repositories",
|
||||
pattern = pattern
|
||||
))
|
||||
));
|
||||
});
|
||||
|
||||
if repos.is_repos_empty() {
|
||||
@@ -207,10 +207,13 @@ impl RepoStep {
|
||||
|
||||
return output;
|
||||
}
|
||||
Err(e) => match e.kind() {
|
||||
io::ErrorKind::NotFound => debug!("{} does not exist", path.as_ref().display()),
|
||||
_ => error!("Error looking for {}: {e}", path.as_ref().display(),),
|
||||
},
|
||||
Err(e) => {
|
||||
if e.kind() == io::ErrorKind::NotFound {
|
||||
debug!("{} does not exist", path.as_ref().display());
|
||||
} else {
|
||||
error!("Error looking for {}: {e}", path.as_ref().display());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
@@ -321,7 +324,7 @@ impl RepoStep {
|
||||
.output()
|
||||
.await?;
|
||||
let result = output_checked_utf8(pull_output)
|
||||
.and_then(|_| output_checked_utf8(submodule_output))
|
||||
.and_then(|()| output_checked_utf8(submodule_output))
|
||||
.wrap_err_with(|| format!("Failed to pull {}", repo.as_ref().display()));
|
||||
|
||||
if result.is_err() {
|
||||
@@ -359,7 +362,7 @@ impl RepoStep {
|
||||
}
|
||||
}
|
||||
|
||||
result.map(|_| ())
|
||||
result
|
||||
}
|
||||
|
||||
/// Pull the repositories specified in `self.repos`.
|
||||
@@ -410,7 +413,7 @@ impl RepoStep {
|
||||
let basic_rt = runtime::Runtime::new()?;
|
||||
let results = basic_rt.block_on(async { stream_of_futures.collect::<Vec<Result<()>>>().await });
|
||||
|
||||
let error = results.into_iter().find(|r| r.is_err());
|
||||
let error = results.into_iter().find(std::result::Result::is_err);
|
||||
error.unwrap_or(Ok(()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ impl NPM {
|
||||
.args(["--version"])
|
||||
.output_checked_utf8()
|
||||
.map(|s| s.stdout.trim().to_owned());
|
||||
Version::parse(&version_str?).map_err(|err| err.into())
|
||||
Version::parse(&version_str?).map_err(std::convert::Into::into)
|
||||
}
|
||||
|
||||
fn upgrade(&self, ctx: &ExecutionContext, use_sudo: bool) -> Result<()> {
|
||||
@@ -184,6 +184,92 @@ impl Yarn {
|
||||
}
|
||||
}
|
||||
|
||||
struct Deno {
|
||||
command: PathBuf,
|
||||
}
|
||||
|
||||
impl Deno {
|
||||
fn new(command: PathBuf) -> Self {
|
||||
Self { command }
|
||||
}
|
||||
|
||||
fn upgrade(&self, ctx: &ExecutionContext) -> Result<()> {
|
||||
let mut args = vec![];
|
||||
|
||||
let version = ctx.config().deno_version();
|
||||
if let Some(version) = version {
|
||||
let bin_version = self.version()?;
|
||||
|
||||
if bin_version >= Version::new(2, 0, 0) {
|
||||
args.push(version);
|
||||
} else if bin_version >= Version::new(1, 6, 0) {
|
||||
match version {
|
||||
"stable" => { /* do nothing, as stable is the default channel to upgrade */ }
|
||||
"rc" => {
|
||||
return Err(SkipStep(
|
||||
"Deno (1.6.0-2.0.0) cannot be upgraded to a release candidate".to_string(),
|
||||
)
|
||||
.into());
|
||||
}
|
||||
"canary" => args.push("--canary"),
|
||||
_ => {
|
||||
if Version::parse(version).is_err() {
|
||||
return Err(SkipStep("Invalid Deno version".to_string()).into());
|
||||
}
|
||||
|
||||
args.push("--version");
|
||||
args.push(version);
|
||||
}
|
||||
}
|
||||
} else if bin_version >= Version::new(1, 0, 0) {
|
||||
match version {
|
||||
"stable" | "rc" | "canary" => {
|
||||
// Prior to v1.6.0, `deno upgrade` is not able fetch the latest tag version.
|
||||
return Err(
|
||||
SkipStep("Deno (1.0.0-1.6.0) cannot be upgraded to a named channel".to_string()).into(),
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
if Version::parse(version).is_err() {
|
||||
return Err(SkipStep("Invalid Deno version".to_string()).into());
|
||||
}
|
||||
|
||||
args.push("--version");
|
||||
args.push(version);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// v0.x cannot be upgraded with `deno upgrade` to v1.x or v2.x
|
||||
// nor can be upgraded to a specific version.
|
||||
return Err(SkipStep("Unsupported Deno version".to_string()).into());
|
||||
}
|
||||
}
|
||||
|
||||
ctx.run_type()
|
||||
.execute(&self.command)
|
||||
.arg("upgrade")
|
||||
.args(args)
|
||||
.status_checked()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get the version of Deno.
|
||||
///
|
||||
/// This function will return the version of Deno installed on the system.
|
||||
/// The version is parsed from the output of `deno -V`.
|
||||
///
|
||||
/// ```sh
|
||||
/// deno -V # deno 1.6.0
|
||||
/// ```
|
||||
fn version(&self) -> Result<Version> {
|
||||
let version_str = Command::new(&self.command)
|
||||
.args(["-V"])
|
||||
.output_checked_utf8()
|
||||
.map(|s| s.stdout.trim().to_owned().split_off(5)); // remove "deno " prefix
|
||||
Version::parse(&version_str?).map_err(std::convert::Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn should_use_sudo(npm: &NPM, ctx: &ExecutionContext) -> Result<bool> {
|
||||
if npm.should_use_sudo()? {
|
||||
@@ -266,16 +352,16 @@ pub fn run_yarn_upgrade(ctx: &ExecutionContext) -> Result<()> {
|
||||
}
|
||||
|
||||
pub fn deno_upgrade(ctx: &ExecutionContext) -> Result<()> {
|
||||
let deno = require("deno")?;
|
||||
let deno = require("deno").map(Deno::new)?;
|
||||
let deno_dir = HOME_DIR.join(".deno");
|
||||
|
||||
if !deno.canonicalize()?.is_descendant_of(&deno_dir) {
|
||||
if !deno.command.canonicalize()?.is_descendant_of(&deno_dir) {
|
||||
let skip_reason = SkipStep(t!("Deno installed outside of .deno directory").to_string());
|
||||
return Err(skip_reason.into());
|
||||
}
|
||||
|
||||
print_separator("Deno");
|
||||
ctx.run_type().execute(&deno).arg("upgrade").status_checked()
|
||||
deno.upgrade(ctx)
|
||||
}
|
||||
|
||||
/// There is no `volta upgrade` command, so we need to upgrade each package
|
||||
@@ -313,7 +399,7 @@ pub fn run_volta_packages_upgrade(ctx: &ExecutionContext) -> Result<()> {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
for package in installed_packages.iter() {
|
||||
for package in &installed_packages {
|
||||
ctx.run_type()
|
||||
.execute(&volta)
|
||||
.args(["install", package])
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::ffi::OsString;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use color_eyre::eyre;
|
||||
use color_eyre::eyre::Result;
|
||||
use color_eyre::eyre::{Context, Result};
|
||||
use rust_i18n::t;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
@@ -12,7 +12,7 @@ use crate::error::TopgradeError;
|
||||
use crate::execution_context::ExecutionContext;
|
||||
use crate::utils::require_option;
|
||||
use crate::utils::which;
|
||||
use crate::{config, Step};
|
||||
use crate::{config, output_changed_message, Step};
|
||||
|
||||
fn get_execution_path() -> OsString {
|
||||
let mut path = OsString::from("/usr/bin:");
|
||||
@@ -285,7 +285,8 @@ impl ArchPackageManager for Aura {
|
||||
// Output will be something like: "aura x.x.x\n"
|
||||
let version_cmd_stdout = version_cmd_output.stdout;
|
||||
let version_str = version_cmd_stdout.trim_start_matches("aura ").trim_end();
|
||||
let version = Version::parse(version_str).expect("invalid version");
|
||||
let version = Version::parse(version_str)
|
||||
.wrap_err_with(|| output_changed_message!("aura --version", "invalid version"))?;
|
||||
|
||||
// Aura, since version 4.0.6, no longer needs sudo.
|
||||
//
|
||||
|
||||
@@ -60,24 +60,12 @@ impl Distribution {
|
||||
Some("wolfi") => Distribution::Wolfi,
|
||||
Some("centos") | Some("rhel") | Some("ol") => Distribution::CentOS,
|
||||
Some("clear-linux-os") => Distribution::ClearLinux,
|
||||
Some("fedora") => {
|
||||
return if let Some(variant) = variant {
|
||||
match variant {
|
||||
"Silverblue" | "Kinoite" | "Sericea" | "Onyx" | "IoT Edition" | "Sway Atomic" => {
|
||||
Ok(Distribution::FedoraImmutable)
|
||||
}
|
||||
_ => Ok(Distribution::Fedora),
|
||||
}
|
||||
} else {
|
||||
Ok(Distribution::Fedora)
|
||||
};
|
||||
}
|
||||
|
||||
Some("fedora") => Distribution::match_fedora_variant(&variant),
|
||||
Some("nilrt") => Distribution::NILRT,
|
||||
Some("nobara") => Distribution::Nobara,
|
||||
Some("void") => Distribution::Void,
|
||||
Some("debian") | Some("pureos") | Some("Deepin") | Some("linuxmint") => Distribution::Debian,
|
||||
Some("arch") | Some("manjaro-arm") | Some("garuda") | Some("artix") => Distribution::Arch,
|
||||
Some("arch") | Some("manjaro-arm") | Some("garuda") | Some("artix") | Some("cachyos") => Distribution::Arch,
|
||||
Some("solus") => Distribution::Solus,
|
||||
Some("gentoo") | Some("funtoo") => Distribution::Gentoo,
|
||||
Some("exherbo") => Distribution::Exherbo,
|
||||
@@ -109,7 +97,7 @@ impl Distribution {
|
||||
} else if id_like.contains(&"alpine") {
|
||||
return Ok(Distribution::Alpine);
|
||||
} else if id_like.contains(&"fedora") {
|
||||
return Ok(Distribution::Fedora);
|
||||
return Ok(Distribution::match_fedora_variant(&variant));
|
||||
}
|
||||
}
|
||||
return Err(TopgradeError::UnknownLinuxDistribution.into());
|
||||
@@ -117,6 +105,15 @@ impl Distribution {
|
||||
})
|
||||
}
|
||||
|
||||
fn match_fedora_variant(variant: &Option<&str>) -> Self {
|
||||
if let Some("Silverblue" | "Kinoite" | "Sericea" | "Onyx" | "IoT Edition" | "Sway Atomic" | "CoreOS") = variant
|
||||
{
|
||||
Distribution::FedoraImmutable
|
||||
} else {
|
||||
Distribution::Fedora
|
||||
}
|
||||
}
|
||||
|
||||
pub fn detect() -> Result<Self> {
|
||||
if PathBuf::from("/bedrock").exists() {
|
||||
return Ok(Distribution::Bedrock);
|
||||
@@ -225,6 +222,13 @@ fn upgrade_wolfi_linux(ctx: &ExecutionContext) -> Result<()> {
|
||||
}
|
||||
|
||||
fn upgrade_redhat(ctx: &ExecutionContext) -> Result<()> {
|
||||
if let Some(bootc) = which("bootc") {
|
||||
if ctx.config().bootc() {
|
||||
let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
|
||||
return ctx.run_type().execute(sudo).arg(&bootc).arg("upgrade").status_checked();
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(ostree) = which("rpm-ostree") {
|
||||
if ctx.config().rpm_ostree() {
|
||||
let mut command = ctx.run_type().execute(ostree);
|
||||
@@ -298,6 +302,13 @@ fn upgrade_nilrt(ctx: &ExecutionContext) -> Result<()> {
|
||||
}
|
||||
|
||||
fn upgrade_fedora_immutable(ctx: &ExecutionContext) -> Result<()> {
|
||||
if let Some(bootc) = which("bootc") {
|
||||
if ctx.config().bootc() {
|
||||
let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
|
||||
return ctx.run_type().execute(sudo).arg(&bootc).arg("upgrade").status_checked();
|
||||
}
|
||||
}
|
||||
|
||||
let ostree = require("rpm-ostree")?;
|
||||
let mut command = ctx.run_type().execute(ostree);
|
||||
command.arg("upgrade");
|
||||
@@ -576,7 +587,11 @@ pub fn run_deb_get(ctx: &ExecutionContext) -> Result<()> {
|
||||
ctx.run_type().execute(&deb_get).arg("upgrade").status_checked()?;
|
||||
|
||||
if ctx.config().cleanup() {
|
||||
ctx.run_type().execute(&deb_get).arg("clean").status_checked()?;
|
||||
let output = ctx.run_type().execute(&deb_get).arg("clean").output_checked()?;
|
||||
// Swallow the output, as it's very noisy and not useful.
|
||||
// The output is automatically printed as part of `output_checked` when an error occurs.
|
||||
println!("{}", t!("<output from `deb-get clean` omitted>"));
|
||||
debug!("`deb-get clean` output: {output:?}");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -1106,6 +1121,17 @@ pub fn run_auto_cpufreq(ctx: &ExecutionContext) -> Result<()> {
|
||||
.status_checked()
|
||||
}
|
||||
|
||||
pub fn run_cinnamon_spices_updater(ctx: &ExecutionContext) -> Result<()> {
|
||||
let cinnamon_spice_updater = require("cinnamon-spice-updater")?;
|
||||
|
||||
print_separator("Cinnamon spices");
|
||||
|
||||
ctx.run_type()
|
||||
.execute(cinnamon_spice_updater)
|
||||
.arg("--update-all")
|
||||
.status_checked()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -1274,4 +1300,29 @@ mod tests {
|
||||
fn test_nilrt() {
|
||||
test_template(include_str!("os_release/nilrt"), Distribution::NILRT);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_coreos() {
|
||||
test_template(include_str!("os_release/coreos"), Distribution::FedoraImmutable);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_aurora() {
|
||||
test_template(include_str!("os_release/aurora"), Distribution::FedoraImmutable);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bluefin() {
|
||||
test_template(include_str!("os_release/bluefin"), Distribution::FedoraImmutable);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bazzite() {
|
||||
test_template(include_str!("os_release/bazzite"), Distribution::FedoraImmutable);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cachyos() {
|
||||
test_template(include_str!("os_release/cachyos"), Distribution::Arch);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,7 +203,7 @@ pub fn update_xcodes(ctx: &ExecutionContext) -> Result<()> {
|
||||
.execute(&xcodes)
|
||||
.args([
|
||||
"uninstall",
|
||||
releases_new_installed.iter().next().cloned().unwrap_or_default(),
|
||||
releases_new_installed.iter().next().copied().unwrap_or_default(),
|
||||
])
|
||||
.status_checked();
|
||||
}
|
||||
@@ -216,12 +216,7 @@ pub fn update_xcodes(ctx: &ExecutionContext) -> Result<()> {
|
||||
pub fn process_xcodes_releases(releases_filtered: Vec<String>, should_ask: bool, ctx: &ExecutionContext) -> Result<()> {
|
||||
let xcodes = require("xcodes")?;
|
||||
|
||||
if releases_filtered
|
||||
.last()
|
||||
.map(|s| !s.contains("(Installed)"))
|
||||
.unwrap_or(true)
|
||||
&& !releases_filtered.is_empty()
|
||||
{
|
||||
if releases_filtered.last().map_or(true, |s| !s.contains("(Installed)")) && !releases_filtered.is_empty() {
|
||||
println!(
|
||||
"{} {}",
|
||||
t!("New Xcode release detected:"),
|
||||
|
||||
@@ -3,20 +3,51 @@ use crate::execution_context::ExecutionContext;
|
||||
use crate::terminal::print_separator;
|
||||
use crate::utils::{get_require_sudo_string, require_option};
|
||||
use color_eyre::eyre::Result;
|
||||
use rust_i18n::t;
|
||||
use std::fs;
|
||||
|
||||
fn is_openbsd_current(ctx: &ExecutionContext) -> Result<bool> {
|
||||
let motd_content = fs::read_to_string("/etc/motd")?;
|
||||
let is_current = ["-current", "-beta"].iter().any(|&s| motd_content.contains(s));
|
||||
if ctx.config().dry_run() {
|
||||
println!("{}", t!("Would check if OpenBSD is -current"));
|
||||
Ok(is_current)
|
||||
} else {
|
||||
Ok(is_current)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn upgrade_openbsd(ctx: &ExecutionContext) -> Result<()> {
|
||||
let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
|
||||
print_separator(t!("OpenBSD Update"));
|
||||
ctx.run_type()
|
||||
.execute(sudo)
|
||||
.args(["/usr/sbin/sysupgrade", "-n"])
|
||||
.status_checked()
|
||||
|
||||
let is_current = is_openbsd_current(ctx)?;
|
||||
|
||||
if ctx.config().dry_run() {
|
||||
println!("{}", t!("Would upgrade the OpenBSD system"));
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let args = if is_current {
|
||||
vec!["/usr/sbin/sysupgrade", "-sn"]
|
||||
} else {
|
||||
vec!["/usr/sbin/syspatch"]
|
||||
};
|
||||
|
||||
ctx.run_type().execute(sudo).args(&args).status_checked()
|
||||
}
|
||||
|
||||
pub fn upgrade_packages(ctx: &ExecutionContext) -> Result<()> {
|
||||
let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
|
||||
print_separator(t!("OpenBSD Packages"));
|
||||
|
||||
let is_current = is_openbsd_current(ctx)?;
|
||||
|
||||
if ctx.config().dry_run() {
|
||||
println!("{}", t!("Would upgrade OpenBSD packages"));
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if ctx.config().cleanup() {
|
||||
ctx.run_type()
|
||||
.execute(sudo)
|
||||
@@ -24,10 +55,12 @@ pub fn upgrade_packages(ctx: &ExecutionContext) -> Result<()> {
|
||||
.status_checked()?;
|
||||
}
|
||||
|
||||
ctx.run_type()
|
||||
.execute(sudo)
|
||||
.args(["/usr/sbin/pkg_add", "-u"])
|
||||
.status_checked()?;
|
||||
let mut args = vec!["/usr/sbin/pkg_add", "-u"];
|
||||
if is_current {
|
||||
args.push("-Dsnap");
|
||||
}
|
||||
|
||||
ctx.run_type().execute(sudo).args(&args).status_checked()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
23
src/steps/os/os_release/aurora
Normal file
23
src/steps/os/os_release/aurora
Normal file
@@ -0,0 +1,23 @@
|
||||
NAME="Aurora"
|
||||
VERSION="latest-41.20250210.4 (Kinoite)"
|
||||
RELEASE_TYPE=stable
|
||||
ID=aurora
|
||||
ID_LIKE="fedora"
|
||||
VERSION_ID=41
|
||||
VERSION_CODENAME=""
|
||||
PLATFORM_ID="platform:f41"
|
||||
PRETTY_NAME="Aurora (Version: latest-41.20250210.4 / FROM Fedora Kinoite 41)"
|
||||
ANSI_COLOR="0;38;2;60;110;180"
|
||||
LOGO=fedora-logo-icon
|
||||
CPE_NAME="cpe:/o:universal-blue:aurora:41"
|
||||
DEFAULT_HOSTNAME="aurora"
|
||||
HOME_URL="https://getaurora.dev/"
|
||||
DOCUMENTATION_URL="https://docs.getaurora.dev"
|
||||
SUPPORT_URL="https://github.com/ublue-os/aurora/issues/"
|
||||
BUG_REPORT_URL="https://github.com/ublue-os/aurora/issues/"
|
||||
SUPPORT_END=2025-12-15
|
||||
VARIANT="Kinoite"
|
||||
VARIANT_ID=aurora
|
||||
OSTREE_VERSION='latest-41.20250210.4'
|
||||
BUILD_ID="fc1570c"
|
||||
IMAGE_ID="aurora"
|
||||
25
src/steps/os/os_release/bazzite
Normal file
25
src/steps/os/os_release/bazzite
Normal file
@@ -0,0 +1,25 @@
|
||||
NAME="Bazzite"
|
||||
VERSION="41.20250208.0 (Kinoite)"
|
||||
RELEASE_TYPE=stable
|
||||
ID=bazzite
|
||||
ID_LIKE="fedora"
|
||||
VERSION_ID=41
|
||||
VERSION_CODENAME="Holographic"
|
||||
PLATFORM_ID="platform:f41"
|
||||
PRETTY_NAME="Bazzite 41 (FROM Fedora Kinoite)"
|
||||
ANSI_COLOR="0;38;2;138;43;226"
|
||||
LOGO=bazzite-logo-icon
|
||||
CPE_NAME="cpe:/o:universal-blue:bazzite:41"
|
||||
DEFAULT_HOSTNAME="bazzite"
|
||||
HOME_URL="https://bazzite.gg"
|
||||
DOCUMENTATION_URL="https://docs.bazzite.gg"
|
||||
SUPPORT_URL="https://discord.bazzite.gg"
|
||||
BUG_REPORT_URL="https://github.com/ublue-os/bazzite/issues/"
|
||||
SUPPORT_END=2025-12-15
|
||||
VARIANT="Kinoite"
|
||||
VARIANT_ID=bazzite-nvidia-open
|
||||
OSTREE_VERSION='41.20250208.0'
|
||||
BUILD_ID="Stable (F41.20250208)"
|
||||
BOOTLOADER_NAME="Bazzite Stable (F41.20250208)"
|
||||
BUILD_ID="Stable (F41.20250208)"
|
||||
BOOTLOADER_NAME="Bazzite Stable (F41.20250208)"
|
||||
24
src/steps/os/os_release/bluefin
Normal file
24
src/steps/os/os_release/bluefin
Normal file
@@ -0,0 +1,24 @@
|
||||
NAME="Bluefin"
|
||||
VERSION="41.20250216.1 (Silverblue)"
|
||||
RELEASE_TYPE=stable
|
||||
ID=bluefin
|
||||
ID_LIKE="fedora"
|
||||
VERSION_ID=41
|
||||
VERSION_CODENAME="Archaeopteryx"
|
||||
PLATFORM_ID="platform:f41"
|
||||
PRETTY_NAME="Bluefin (Version: 41.20250216.1 / FROM Fedora Silverblue 41)"
|
||||
ANSI_COLOR="0;38;2;60;110;180"
|
||||
LOGO=fedora-logo-icon
|
||||
CPE_NAME="cpe:/o:universal-blue:bluefin:41"
|
||||
DEFAULT_HOSTNAME="bluefin"
|
||||
HOME_URL="https://projectbluefin.io"
|
||||
DOCUMENTATION_URL="https://docs.projectbluefin.io"
|
||||
SUPPORT_URL="https://github.com/ublue-os/bluefin/issues/"
|
||||
BUG_REPORT_URL="https://github.com/ublue-os/bluefin/issues/"
|
||||
SUPPORT_END=2025-12-15
|
||||
VARIANT="Silverblue"
|
||||
VARIANT_ID=bluefin
|
||||
OSTREE_VERSION='41.20250216.1'
|
||||
BUILD_ID="185146a"
|
||||
IMAGE_ID="bluefin"
|
||||
IMAGE_VERSION="41.20250216.1"
|
||||
11
src/steps/os/os_release/cachyos
Normal file
11
src/steps/os/os_release/cachyos
Normal file
@@ -0,0 +1,11 @@
|
||||
NAME="CachyOS Linux"
|
||||
PRETTY_NAME="CachyOS"
|
||||
ID=cachyos
|
||||
BUILD_ID=rolling
|
||||
ANSI_COLOR="38;2;23;147;209"
|
||||
HOME_URL="https://cachyos.org/"
|
||||
DOCUMENTATION_URL="https://wiki.cachyos.org/"
|
||||
SUPPORT_URL="https://discuss.cachyos.org/"
|
||||
BUG_REPORT_URL="https://github.com/cachyos"
|
||||
PRIVACY_POLICY_URL="https://terms.archlinux.org/docs/privacy-policy/"
|
||||
LOGO=cachyos
|
||||
23
src/steps/os/os_release/coreos
Normal file
23
src/steps/os/os_release/coreos
Normal file
@@ -0,0 +1,23 @@
|
||||
NAME="Fedora Linux"
|
||||
VERSION="41.20250117.3.0 (CoreOS)"
|
||||
RELEASE_TYPE=stable
|
||||
ID=fedora
|
||||
VERSION_ID=41
|
||||
VERSION_CODENAME=""
|
||||
PLATFORM_ID="platform:f41"
|
||||
PRETTY_NAME="Fedora CoreOS 41.20250117.3.0 (uCore)"
|
||||
ANSI_COLOR="0;38;2;60;110;180"
|
||||
LOGO=fedora-logo-icon
|
||||
CPE_NAME="cpe:/o:fedoraproject:fedora:41"
|
||||
HOME_URL="https://getfedora.org/coreos/"
|
||||
DOCUMENTATION_URL="https://docs.fedoraproject.org/en-US/fedora-coreos/"
|
||||
SUPPORT_URL="https://github.com/coreos/fedora-coreos-tracker/"
|
||||
BUG_REPORT_URL="https://github.com/coreos/fedora-coreos-tracker/"
|
||||
REDHAT_BUGZILLA_PRODUCT="Fedora"
|
||||
REDHAT_BUGZILLA_PRODUCT_VERSION=41
|
||||
REDHAT_SUPPORT_PRODUCT="Fedora"
|
||||
REDHAT_SUPPORT_PRODUCT_VERSION=41
|
||||
SUPPORT_END=2025-12-15
|
||||
VARIANT="CoreOS"
|
||||
VARIANT_ID=coreos
|
||||
OSTREE_VERSION='41.20250117.3.0'
|
||||
@@ -1,13 +1,5 @@
|
||||
use std::ffi::OsStr;
|
||||
use std::fs;
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
use std::path::Component;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
use std::{env::var, path::Path};
|
||||
|
||||
use crate::command::CommandExt;
|
||||
use crate::{Step, HOME_DIR};
|
||||
use crate::{output_changed_message, Step, HOME_DIR};
|
||||
use color_eyre::eyre::eyre;
|
||||
use color_eyre::eyre::Context;
|
||||
use color_eyre::eyre::Result;
|
||||
@@ -15,8 +7,17 @@ use home;
|
||||
use ini::Ini;
|
||||
#[cfg(target_os = "linux")]
|
||||
use nix::unistd::Uid;
|
||||
use regex::Regex;
|
||||
use rust_i18n::t;
|
||||
use semver::Version;
|
||||
use std::ffi::OsStr;
|
||||
use std::fs;
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
use std::path::Component;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
use std::sync::LazyLock;
|
||||
use std::{env::var, path::Path};
|
||||
use tracing::debug;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
@@ -236,7 +237,7 @@ pub fn upgrade_gnome_extensions(ctx: &ExecutionContext) -> Result<()> {
|
||||
let gdbus = require("gdbus")?;
|
||||
require_option(
|
||||
var("XDG_CURRENT_DESKTOP").ok().filter(|p| p.contains("GNOME")),
|
||||
t!("Desktop doest not appear to be gnome").to_string(),
|
||||
t!("Desktop does not appear to be GNOME").to_string(),
|
||||
)?;
|
||||
let output = Command::new("gdbus")
|
||||
.args([
|
||||
@@ -251,12 +252,12 @@ pub fn upgrade_gnome_extensions(ctx: &ExecutionContext) -> Result<()> {
|
||||
])
|
||||
.output_checked_utf8()?;
|
||||
|
||||
debug!("Checking for gnome extensions: {}", output);
|
||||
debug!("Checking for GNOME extensions: {}", output);
|
||||
if !output.stdout.contains("org.gnome.Shell.Extensions") {
|
||||
return Err(SkipStep(t!("Gnome shell extensions are unregistered in DBus").to_string()).into());
|
||||
return Err(SkipStep(t!("GNOME shell extensions are unregistered in DBus").to_string()).into());
|
||||
}
|
||||
|
||||
print_separator(t!("Gnome Shell extensions"));
|
||||
print_separator(t!("GNOME Shell extensions"));
|
||||
|
||||
ctx.run_type()
|
||||
.execute(gdbus)
|
||||
@@ -449,18 +450,47 @@ pub fn run_nix(ctx: &ExecutionContext) -> Result<()> {
|
||||
.stdout
|
||||
.lines()
|
||||
.next()
|
||||
.expect("nix --version gives an empty output");
|
||||
let splitted: Vec<&str> = get_version_cmd_first_line_stdout.split_whitespace().collect();
|
||||
let version = if splitted.len() >= 3 {
|
||||
Version::parse(splitted[2]).expect("invalid version")
|
||||
.ok_or_else(|| eyre!("`nix --version` output is empty"))?;
|
||||
|
||||
let is_lix = get_version_cmd_first_line_stdout.contains("Lix");
|
||||
|
||||
debug!(
|
||||
output=%get_version_cmd_output,
|
||||
?is_lix,
|
||||
"`nix --version` output"
|
||||
);
|
||||
|
||||
static NIX_VERSION_REGEX: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"^nix \([^)]*\) ([0-9.]+)").expect("Nix version regex always compiles"));
|
||||
|
||||
if get_version_cmd_first_line_stdout.is_empty() {
|
||||
return Err(eyre!("`nix --version` output was empty"));
|
||||
}
|
||||
|
||||
let captures = NIX_VERSION_REGEX
|
||||
.captures(get_version_cmd_first_line_stdout)
|
||||
.ok_or_else(|| eyre!(output_changed_message!("nix --version", "regex did not match")))?;
|
||||
let raw_version = &captures[1];
|
||||
|
||||
debug!("Raw Nix version: {raw_version}");
|
||||
|
||||
// Nix 2.29.0 outputs "2.29" instead of "2.29.0", so we need to add that if necessary.
|
||||
let corrected_raw_version = if raw_version.chars().filter(|&c| c == '.').count() == 1 {
|
||||
&format!("{raw_version}.0")
|
||||
} else {
|
||||
panic!("nix --version output format changed, file an issue to Topgrade!")
|
||||
raw_version
|
||||
};
|
||||
|
||||
debug!("Corrected raw Nix version: {corrected_raw_version}");
|
||||
|
||||
let version = Version::parse(corrected_raw_version)
|
||||
.wrap_err_with(|| output_changed_message!("nix --version", "Invalid version"))?;
|
||||
|
||||
debug!("Nix version: {:?}", version);
|
||||
|
||||
// Nix since 2.21.0 uses `--all --impure` rather than `.*` to upgrade all packages
|
||||
let packages = if version >= Version::new(2, 21, 0) {
|
||||
// Nix since 2.21.0 uses `--all --impure` rather than `.*` to upgrade all packages.
|
||||
// Lix is based on Nix 2.18, so it doesn't!
|
||||
let packages = if version >= Version::new(2, 21, 0) && !is_lix {
|
||||
vec!["--all", "--impure"]
|
||||
} else {
|
||||
vec![".*"]
|
||||
@@ -583,8 +613,7 @@ fn nix_profile_dir(nix: &Path) -> Result<Option<PathBuf>> {
|
||||
if user_env
|
||||
.file_name()
|
||||
.and_then(|name| name.to_str())
|
||||
.map(|name| name.ends_with("user-environment"))
|
||||
.unwrap_or(false)
|
||||
.is_some_and(|name| name.ends_with("user-environment"))
|
||||
{
|
||||
Some(profile_dir)
|
||||
} else {
|
||||
@@ -609,10 +638,37 @@ pub fn run_asdf(ctx: &ExecutionContext) -> Result<()> {
|
||||
let asdf = require("asdf")?;
|
||||
|
||||
print_separator("asdf");
|
||||
ctx.run_type()
|
||||
.execute(&asdf)
|
||||
.arg("update")
|
||||
.status_checked_with_codes(&[42])?;
|
||||
|
||||
// asdf (>= 0.15.0) won't support the self-update command
|
||||
//
|
||||
// https://github.com/topgrade-rs/topgrade/issues/1007
|
||||
let version_output = Command::new(&asdf).arg("version").output_checked_utf8()?;
|
||||
// Example output
|
||||
//
|
||||
// ```
|
||||
// $ asdf version
|
||||
// v0.15.0-31e8c93
|
||||
//
|
||||
// ```
|
||||
// ```
|
||||
// $ asdf version
|
||||
// v0.16.7
|
||||
// ```
|
||||
let version_stdout = version_output.stdout.trim();
|
||||
// trim the starting 'v'
|
||||
let mut remaining = version_stdout.trim_start_matches('v');
|
||||
// remove the hash part if present
|
||||
if let Some(idx) = remaining.find('-') {
|
||||
remaining = &remaining[..idx];
|
||||
}
|
||||
let version =
|
||||
Version::parse(remaining).wrap_err_with(|| output_changed_message!("asdf version", "invalid version"))?;
|
||||
if version < Version::new(0, 15, 0) {
|
||||
ctx.run_type()
|
||||
.execute(&asdf)
|
||||
.arg("update")
|
||||
.status_checked_with_codes(&[42])?;
|
||||
}
|
||||
|
||||
ctx.run_type()
|
||||
.execute(&asdf)
|
||||
@@ -625,12 +681,12 @@ pub fn run_mise(ctx: &ExecutionContext) -> Result<()> {
|
||||
|
||||
print_separator("mise");
|
||||
|
||||
ctx.run_type().execute(&mise).arg("upgrade").status_checked()?;
|
||||
|
||||
ctx.run_type()
|
||||
.execute(&mise)
|
||||
.args(["plugins", "update"])
|
||||
.status_checked()
|
||||
.status_checked()?;
|
||||
|
||||
ctx.run_type().execute(&mise).arg("upgrade").status_checked()
|
||||
}
|
||||
|
||||
pub fn run_home_manager(ctx: &ExecutionContext) -> Result<()> {
|
||||
@@ -666,9 +722,7 @@ pub fn run_pyenv(ctx: &ExecutionContext) -> Result<()> {
|
||||
let pyenv = require("pyenv")?;
|
||||
print_separator("pyenv");
|
||||
|
||||
let pyenv_dir = var("PYENV_ROOT")
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|_| HOME_DIR.join(".pyenv"));
|
||||
let pyenv_dir = var("PYENV_ROOT").map_or_else(|_| HOME_DIR.join(".pyenv"), PathBuf::from);
|
||||
|
||||
if !pyenv_dir.exists() {
|
||||
return Err(SkipStep(t!("Pyenv is installed, but $PYENV_ROOT is not set correctly").to_string()).into());
|
||||
@@ -689,8 +743,7 @@ pub fn run_sdkman(ctx: &ExecutionContext) -> Result<()> {
|
||||
let bash = require("bash")?;
|
||||
|
||||
let sdkman_init_path = var("SDKMAN_DIR")
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|_| HOME_DIR.join(".sdkman"))
|
||||
.map_or_else(|_| HOME_DIR.join(".sdkman"), PathBuf::from)
|
||||
.join("bin")
|
||||
.join("sdkman-init.sh")
|
||||
.require()
|
||||
@@ -699,8 +752,7 @@ pub fn run_sdkman(ctx: &ExecutionContext) -> Result<()> {
|
||||
print_separator("SDKMAN!");
|
||||
|
||||
let sdkman_config_path = var("SDKMAN_DIR")
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|_| HOME_DIR.join(".sdkman"))
|
||||
.map_or_else(|_| HOME_DIR.join(".sdkman"), PathBuf::from)
|
||||
.join("etc")
|
||||
.join("config")
|
||||
.require()?;
|
||||
@@ -753,9 +805,7 @@ pub fn run_bun_packages(ctx: &ExecutionContext) -> Result<()> {
|
||||
|
||||
print_separator(t!("Bun Packages"));
|
||||
|
||||
let mut package_json: PathBuf = var("BUN_INSTALL")
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|_| HOME_DIR.join(".bun"));
|
||||
let mut package_json: PathBuf = var("BUN_INSTALL").map_or_else(|_| HOME_DIR.join(".bun"), PathBuf::from);
|
||||
package_json.push("install/global/package.json");
|
||||
|
||||
if !package_json.exists() {
|
||||
|
||||
@@ -43,9 +43,17 @@ pub fn run_winget(ctx: &ExecutionContext) -> Result<()> {
|
||||
print_separator("winget");
|
||||
|
||||
ctx.run_type()
|
||||
.execute(winget)
|
||||
.args(["upgrade", "--all"])
|
||||
.status_checked()
|
||||
.execute(&winget)
|
||||
.args(["source", "update"])
|
||||
.status_checked()?;
|
||||
|
||||
let mut args = vec!["upgrade", "--all"];
|
||||
if ctx.config().winget_silent_install() {
|
||||
args.push("--silent");
|
||||
}
|
||||
|
||||
ctx.run_type().execute(&winget).args(args).status_checked()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn run_scoop(ctx: &ExecutionContext) -> Result<()> {
|
||||
@@ -63,7 +71,6 @@ pub fn run_scoop(ctx: &ExecutionContext) -> Result<()> {
|
||||
.args(["cache", "rm", "-a"])
|
||||
.status_checked()?
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -221,6 +228,14 @@ pub fn windows_update(ctx: &ExecutionContext) -> Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn microsoft_store(ctx: &ExecutionContext) -> Result<()> {
|
||||
let powershell = powershell::Powershell::windows_powershell();
|
||||
|
||||
print_separator(t!("Microsoft Store"));
|
||||
|
||||
powershell.microsoft_store(ctx)
|
||||
}
|
||||
|
||||
pub fn reboot() -> Result<()> {
|
||||
// If this works, it won't return, but if it doesn't work, it may return a useful error
|
||||
// message.
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
#[cfg(windows)]
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
|
||||
@@ -9,7 +7,7 @@ use rust_i18n::t;
|
||||
use crate::command::CommandExt;
|
||||
use crate::execution_context::ExecutionContext;
|
||||
use crate::terminal::{is_dumb, print_separator};
|
||||
use crate::utils::{require_option, which, PathExt};
|
||||
use crate::utils::{require_option, which};
|
||||
use crate::Step;
|
||||
|
||||
pub struct Powershell {
|
||||
@@ -18,22 +16,9 @@ pub struct Powershell {
|
||||
}
|
||||
|
||||
impl Powershell {
|
||||
/// Returns a powershell instance.
|
||||
///
|
||||
/// If the powershell binary is not found, or the current terminal is dumb
|
||||
/// then the instance of this struct will skip all the powershell steps.
|
||||
pub fn new() -> Self {
|
||||
let path = which("pwsh").or_else(|| which("powershell")).filter(|_| !is_dumb());
|
||||
|
||||
let profile = path.as_ref().and_then(|path| {
|
||||
Command::new(path)
|
||||
.args(["-NoProfile", "-Command", "Split-Path $profile"])
|
||||
.output_checked_utf8()
|
||||
.map(|output| PathBuf::from(output.stdout.trim()))
|
||||
.and_then(|p| p.require())
|
||||
.ok()
|
||||
});
|
||||
|
||||
let profile = path.as_ref().and_then(Self::get_profile);
|
||||
Powershell { path, profile }
|
||||
}
|
||||
|
||||
@@ -45,78 +30,178 @@ impl Powershell {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn profile(&self) -> Option<&PathBuf> {
|
||||
self.profile.as_ref()
|
||||
}
|
||||
|
||||
fn get_profile(path: &PathBuf) -> Option<PathBuf> {
|
||||
Self::execute_with_command(path, &["-NoProfile", "-Command", "Split-Path $PROFILE"], |stdout| {
|
||||
Ok(stdout)
|
||||
})
|
||||
.ok() // Convert the Result<String> to Option<String>
|
||||
.and_then(|s| super::super::utils::PathExt::require(PathBuf::from(s)).ok())
|
||||
}
|
||||
|
||||
fn execute_with_command<F>(path: &PathBuf, args: &[&str], f: F) -> Result<String>
|
||||
where
|
||||
F: FnOnce(String) -> Result<String>,
|
||||
{
|
||||
let output = Command::new(path).args(args).output_checked_utf8()?;
|
||||
let stdout = output.stdout.trim().to_string();
|
||||
f(stdout)
|
||||
}
|
||||
|
||||
/// Builds a command with common arguments and optional sudo support.
|
||||
fn build_command_internal<'a>(
|
||||
&self,
|
||||
ctx: &'a ExecutionContext,
|
||||
additional_args: &[&str],
|
||||
) -> Result<impl CommandExt + 'a> {
|
||||
let powershell = require_option(self.path.as_ref(), t!("Powershell is not installed").to_string())?;
|
||||
let executor = &mut ctx.run_type();
|
||||
let mut command = if let Some(sudo) = ctx.sudo() {
|
||||
let mut cmd = executor.execute(sudo);
|
||||
cmd.arg(powershell);
|
||||
cmd
|
||||
} else {
|
||||
executor.execute(powershell)
|
||||
};
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
// Check execution policy and return early if it's not set correctly
|
||||
self.execution_policy_args_if_needed()?;
|
||||
}
|
||||
|
||||
command.args(Self::common_args()).args(additional_args);
|
||||
Ok(command)
|
||||
}
|
||||
|
||||
pub fn update_modules(&self, ctx: &ExecutionContext) -> Result<()> {
|
||||
print_separator(t!("Powershell Modules Update"));
|
||||
let mut cmd_args = vec!["Update-Module"];
|
||||
|
||||
if ctx.config().verbose() {
|
||||
cmd_args.push("-Verbose");
|
||||
}
|
||||
if ctx.config().yes(Step::Powershell) {
|
||||
cmd_args.push("-Force");
|
||||
}
|
||||
println!("{}", t!("Updating modules..."));
|
||||
self.build_command_internal(ctx, &cmd_args)?.status_checked()
|
||||
}
|
||||
|
||||
fn common_args() -> &'static [&'static str] {
|
||||
&["-NoProfile"]
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub fn has_module(powershell: &Path, command: &str) -> bool {
|
||||
pub fn execution_policy_args_if_needed(&self) -> Result<()> {
|
||||
if !self.is_execution_policy_set("RemoteSigned") {
|
||||
Err(color_eyre::eyre::eyre!(
|
||||
"PowerShell execution policy is too restrictive. \
|
||||
Please run 'Set-ExecutionPolicy RemoteSigned -Scope CurrentUser' in PowerShell \
|
||||
(or use Unrestricted/Bypass if you're sure about the security implications)"
|
||||
))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn is_execution_policy_set(&self, policy: &str) -> bool {
|
||||
if let Some(powershell) = &self.path {
|
||||
// These policies are ordered from most restrictive to least restrictive
|
||||
let valid_policies = ["Restricted", "AllSigned", "RemoteSigned", "Unrestricted", "Bypass"];
|
||||
|
||||
// Find the index of our target policy
|
||||
let target_idx = valid_policies.iter().position(|&p| p == policy);
|
||||
|
||||
let output = Command::new(powershell)
|
||||
.args(["-NoProfile", "-Command", "Get-ExecutionPolicy"])
|
||||
.output_checked_utf8();
|
||||
|
||||
if let Ok(output) = output {
|
||||
let current_policy = output.stdout.trim();
|
||||
|
||||
// Find the index of the current policy
|
||||
let current_idx = valid_policies.iter().position(|&p| p == current_policy);
|
||||
|
||||
// Check if current policy exists and is at least as permissive as the target
|
||||
return match (current_idx, target_idx) {
|
||||
(Some(current), Some(target)) => current >= target,
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
impl Powershell {
|
||||
pub fn supports_windows_update(&self) -> bool {
|
||||
windows::supports_windows_update(self)
|
||||
}
|
||||
|
||||
pub fn windows_update(&self, ctx: &ExecutionContext) -> Result<()> {
|
||||
windows::windows_update(self, ctx)
|
||||
}
|
||||
|
||||
pub fn microsoft_store(&self, ctx: &ExecutionContext) -> Result<()> {
|
||||
windows::microsoft_store(self, ctx)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
mod windows {
|
||||
use super::*;
|
||||
|
||||
pub fn supports_windows_update(powershell: &Powershell) -> bool {
|
||||
powershell
|
||||
.path
|
||||
.as_ref()
|
||||
.map(|p| has_module(p, "PSWindowsUpdate"))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub fn windows_update(powershell: &Powershell, ctx: &ExecutionContext) -> Result<()> {
|
||||
debug_assert!(supports_windows_update(powershell));
|
||||
|
||||
// Build the full command string
|
||||
let mut command_str = "Install-WindowsUpdate -Verbose".to_string();
|
||||
if ctx.config().accept_all_windows_updates() {
|
||||
command_str.push_str(" -AcceptAll");
|
||||
}
|
||||
|
||||
// Pass the command string using the -Command flag
|
||||
powershell
|
||||
.build_command_internal(ctx, &["-Command", &command_str])?
|
||||
.status_checked()
|
||||
}
|
||||
|
||||
pub fn microsoft_store(powershell: &Powershell, ctx: &ExecutionContext) -> Result<()> {
|
||||
println!("{}", t!("Scanning for updates..."));
|
||||
let update_command = "Start-Process powershell -Verb RunAs -ArgumentList '-Command', \
|
||||
'(Get-CimInstance -Namespace \"Root\\cimv2\\mdm\\dmmap\" \
|
||||
-ClassName \"MDM_EnterpriseModernAppManagement_AppManagement01\" | \
|
||||
Invoke-CimMethod -MethodName UpdateScanMethod).ReturnValue'";
|
||||
|
||||
powershell
|
||||
.build_command_internal(ctx, &["-Command", update_command])?
|
||||
.status_checked()
|
||||
}
|
||||
|
||||
fn has_module(powershell: &PathBuf, command: &str) -> bool {
|
||||
Command::new(powershell)
|
||||
.args([
|
||||
"-NoProfile",
|
||||
"-Command",
|
||||
&format!("Get-Module -ListAvailable {command}"),
|
||||
&format!("Get-Module -ListAvailable {}", command),
|
||||
])
|
||||
.output_checked_utf8()
|
||||
.map(|result| !result.stdout.is_empty())
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn profile(&self) -> Option<&PathBuf> {
|
||||
self.profile.as_ref()
|
||||
}
|
||||
|
||||
pub fn update_modules(&self, ctx: &ExecutionContext) -> Result<()> {
|
||||
let powershell = require_option(self.path.as_ref(), t!("Powershell is not installed").to_string())?;
|
||||
|
||||
print_separator(t!("Powershell Modules Update"));
|
||||
|
||||
let mut cmd = vec!["Update-Module"];
|
||||
|
||||
if ctx.config().verbose() {
|
||||
cmd.push("-Verbose")
|
||||
}
|
||||
|
||||
if ctx.config().yes(Step::Powershell) {
|
||||
cmd.push("-Force")
|
||||
}
|
||||
|
||||
println!("{}", t!("Updating modules..."));
|
||||
ctx.run_type()
|
||||
.execute(powershell)
|
||||
// This probably doesn't need `shell_words::join`.
|
||||
.args(["-NoProfile", "-Command", &cmd.join(" ")])
|
||||
.status_checked()
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub fn supports_windows_update(&self) -> bool {
|
||||
self.path
|
||||
.as_ref()
|
||||
.map(|p| Self::has_module(p, "PSWindowsUpdate"))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub fn windows_update(&self, ctx: &ExecutionContext) -> Result<()> {
|
||||
let powershell = require_option(self.path.as_ref(), t!("Powershell is not installed").to_string())?;
|
||||
|
||||
debug_assert!(self.supports_windows_update());
|
||||
|
||||
let accept_all = if ctx.config().accept_all_windows_updates() {
|
||||
"-AcceptAll"
|
||||
} else {
|
||||
""
|
||||
};
|
||||
|
||||
let install_windowsupdate_verbose = "Install-WindowsUpdate -Verbose".to_string();
|
||||
|
||||
let mut command = if let Some(sudo) = ctx.sudo() {
|
||||
let mut command = ctx.run_type().execute(sudo);
|
||||
command.arg(powershell);
|
||||
command
|
||||
} else {
|
||||
ctx.run_type().execute(powershell)
|
||||
};
|
||||
|
||||
command
|
||||
.args(["-NoProfile", &install_windowsupdate_verbose, accept_all])
|
||||
.status_checked()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,7 +126,7 @@ impl<'a> TemporaryPowerOn<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Drop for TemporaryPowerOn<'a> {
|
||||
impl Drop for TemporaryPowerOn<'_> {
|
||||
fn drop(&mut self) {
|
||||
let subcommand = if self.ctx.config().vagrant_always_suspend().unwrap_or(false) {
|
||||
"suspend"
|
||||
@@ -232,7 +232,7 @@ pub fn upgrade_vagrant_boxes(ctx: &ExecutionContext) -> Result<()> {
|
||||
}
|
||||
|
||||
if !found {
|
||||
println!("{}", t!("No outdated boxes"))
|
||||
println!("{}", t!("No outdated boxes"));
|
||||
} else {
|
||||
ctx.run_type()
|
||||
.execute(&vagrant)
|
||||
|
||||
@@ -128,7 +128,7 @@ impl Tmux {
|
||||
.output_checked_utf8()?
|
||||
.stdout
|
||||
.lines()
|
||||
.map(|l| l.parse())
|
||||
.map(str::parse)
|
||||
.collect::<Result<Vec<usize>, _>>()
|
||||
.context("Failed to compute tmux windows")
|
||||
}
|
||||
@@ -162,7 +162,7 @@ pub fn run_in_tmux(config: TmuxConfig) -> Result<()> {
|
||||
println!("{}", t!("Topgrade launched in a new tmux session"));
|
||||
return Ok(());
|
||||
} else {
|
||||
tmux.build().args(["attach-client", "-t", &session]).exec()
|
||||
tmux.build().args(["attach-session", "-t", &session]).exec()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,7 +170,7 @@ pub fn run_in_tmux(config: TmuxConfig) -> Result<()> {
|
||||
if is_inside_tmux {
|
||||
tmux.build().args(["switch-client", "-t", &session]).exec()
|
||||
} else {
|
||||
tmux.build().args(["attach-client", "-t", &session]).exec()
|
||||
tmux.build().args(["attach-session", "-t", &session]).exec()
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -181,19 +181,16 @@ pub fn run_in_tmux(config: TmuxConfig) -> Result<()> {
|
||||
pub fn run_command(ctx: &ExecutionContext, window_name: &str, command: &str) -> Result<()> {
|
||||
let tmux = Tmux::new(ctx.config().tmux_config()?.args);
|
||||
|
||||
match ctx.get_tmux_session() {
|
||||
Some(session_name) => {
|
||||
let indices = tmux.window_indices(&session_name)?;
|
||||
let last_window = indices
|
||||
.iter()
|
||||
.last()
|
||||
.ok_or_else(|| eyre!("tmux session {session_name} has no windows"))?;
|
||||
tmux.new_window(&session_name, &format!("{last_window}"), command)?;
|
||||
}
|
||||
None => {
|
||||
let name = tmux.new_unique_session("topgrade", window_name, command)?;
|
||||
ctx.set_tmux_session(name);
|
||||
}
|
||||
if let Some(session_name) = ctx.get_tmux_session() {
|
||||
let indices = tmux.window_indices(&session_name)?;
|
||||
let last_window = indices
|
||||
.iter()
|
||||
.last()
|
||||
.ok_or_else(|| eyre!("tmux session {session_name} has no windows"))?;
|
||||
tmux.new_window(&session_name, &format!("{last_window}"), command)?;
|
||||
} else {
|
||||
let name = tmux.new_unique_session("topgrade", window_name, command)?;
|
||||
ctx.set_tmux_session(name);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ fn upgrade(command: &mut Executor, ctx: &ExecutionContext) -> Result<()> {
|
||||
if !status.success() {
|
||||
return Err(TopgradeError::ProcessFailed(command.get_program(), status).into());
|
||||
} else {
|
||||
println!("{}", t!("Plugins upgraded"))
|
||||
println!("{}", t!("Plugins upgraded"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,9 +30,7 @@ pub fn run_zr(ctx: &ExecutionContext) -> Result<()> {
|
||||
}
|
||||
|
||||
fn zdotdir() -> PathBuf {
|
||||
env::var("ZDOTDIR")
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|_| HOME_DIR.clone())
|
||||
env::var("ZDOTDIR").map_or_else(|_| HOME_DIR.clone(), PathBuf::from)
|
||||
}
|
||||
|
||||
pub fn zshrc() -> PathBuf {
|
||||
@@ -66,8 +64,7 @@ pub fn run_antigen(ctx: &ExecutionContext) -> Result<()> {
|
||||
let zsh = require("zsh")?;
|
||||
let zshrc = zshrc().require()?;
|
||||
env::var("ADOTDIR")
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|_| HOME_DIR.join("antigen.zsh"))
|
||||
.map_or_else(|_| HOME_DIR.join("antigen.zsh"), PathBuf::from)
|
||||
.require()?;
|
||||
|
||||
print_separator("antigen");
|
||||
@@ -83,8 +80,7 @@ pub fn run_zgenom(ctx: &ExecutionContext) -> Result<()> {
|
||||
let zsh = require("zsh")?;
|
||||
let zshrc = zshrc().require()?;
|
||||
env::var("ZGEN_SOURCE")
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|_| HOME_DIR.join(".zgenom"))
|
||||
.map_or_else(|_| HOME_DIR.join(".zgenom"), PathBuf::from)
|
||||
.require()?;
|
||||
|
||||
print_separator("zgenom");
|
||||
@@ -101,8 +97,7 @@ pub fn run_zplug(ctx: &ExecutionContext) -> Result<()> {
|
||||
zshrc().require()?;
|
||||
|
||||
env::var("ZPLUG_HOME")
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|_| HOME_DIR.join(".zplug"))
|
||||
.map_or_else(|_| HOME_DIR.join(".zplug"), PathBuf::from)
|
||||
.require()?;
|
||||
|
||||
print_separator("zplug");
|
||||
@@ -118,8 +113,7 @@ pub fn run_zinit(ctx: &ExecutionContext) -> Result<()> {
|
||||
let zshrc = zshrc().require()?;
|
||||
|
||||
env::var("ZINIT_HOME")
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|_| XDG_DIRS.data_dir().join("zinit"))
|
||||
.map_or_else(|_| XDG_DIRS.data_dir().join("zinit"), PathBuf::from)
|
||||
.require()?;
|
||||
|
||||
print_separator("zinit");
|
||||
@@ -153,8 +147,7 @@ pub fn run_zim(ctx: &ExecutionContext) -> Result<()> {
|
||||
.output_checked_utf8()
|
||||
.map(|o| o.stdout)
|
||||
})
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|_| HOME_DIR.join(".zim"))
|
||||
.map_or_else(|_| HOME_DIR.join(".zim"), PathBuf::from)
|
||||
.require()?;
|
||||
|
||||
print_separator("zim");
|
||||
|
||||
31
src/sudo.rs
31
src/sudo.rs
@@ -22,13 +22,28 @@ pub struct Sudo {
|
||||
}
|
||||
|
||||
impl Sudo {
|
||||
/// Get the `sudo` binary or the `gsudo` binary in the case of `gsudo`
|
||||
/// masquerading as the `sudo` binary.
|
||||
fn determine_sudo_variant(sudo_p: PathBuf) -> (PathBuf, SudoKind) {
|
||||
match which("gsudo") {
|
||||
Some(gsudo_p) => {
|
||||
match std::fs::canonicalize(&gsudo_p).unwrap() == std::fs::canonicalize(&sudo_p).unwrap() {
|
||||
true => (gsudo_p, SudoKind::Gsudo),
|
||||
false => (sudo_p, SudoKind::Sudo),
|
||||
}
|
||||
}
|
||||
None => (sudo_p, SudoKind::Sudo),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the `sudo` binary for this platform.
|
||||
pub fn detect() -> Option<Self> {
|
||||
which("doas")
|
||||
.map(|p| (p, SudoKind::Doas))
|
||||
.or_else(|| which("sudo").map(|p| (p, SudoKind::Sudo)))
|
||||
.or_else(|| which("sudo").map(Self::determine_sudo_variant))
|
||||
.or_else(|| which("gsudo").map(|p| (p, SudoKind::Gsudo)))
|
||||
.or_else(|| which("pkexec").map(|p| (p, SudoKind::Pkexec)))
|
||||
.or_else(|| which("run0").map(|p| (p, SudoKind::Run0)))
|
||||
.or_else(|| which("please").map(|p| (p, SudoKind::Please)))
|
||||
.map(|(path, kind)| Self { path, kind })
|
||||
}
|
||||
@@ -65,9 +80,11 @@ impl Sudo {
|
||||
cmd.arg("-v");
|
||||
}
|
||||
SudoKind::Gsudo => {
|
||||
// Shows current user, cache and console status.
|
||||
// `gsudo` doesn't have anything like `sudo -v` to cache credentials,
|
||||
// so we just execute a dummy `echo` command so we have something
|
||||
// unobtrusive to run.
|
||||
// See: https://gerardog.github.io/gsudo/docs/usage
|
||||
cmd.arg("status");
|
||||
cmd.arg("echo");
|
||||
}
|
||||
SudoKind::Pkexec => {
|
||||
// I don't think this does anything; `pkexec` usually asks for
|
||||
@@ -79,6 +96,13 @@ impl Sudo {
|
||||
// See: https://linux.die.net/man/1/pkexec
|
||||
cmd.arg("echo");
|
||||
}
|
||||
SudoKind::Run0 => {
|
||||
// `run0` uses polkit for authentication
|
||||
// and thus has the same issues as `pkexec`.
|
||||
//
|
||||
// See: https://www.freedesktop.org/software/systemd/man/devel/run0.html
|
||||
cmd.arg("echo");
|
||||
}
|
||||
SudoKind::Please => {
|
||||
// From `man please`
|
||||
// -w, --warm
|
||||
@@ -115,6 +139,7 @@ pub enum SudoKind {
|
||||
Sudo,
|
||||
Gsudo,
|
||||
Pkexec,
|
||||
Run0,
|
||||
Please,
|
||||
}
|
||||
|
||||
|
||||
@@ -2,14 +2,13 @@ use std::cmp::{max, min};
|
||||
use std::env;
|
||||
use std::io::{self, Write};
|
||||
use std::process::Command;
|
||||
use std::sync::Mutex;
|
||||
use std::sync::{LazyLock, Mutex};
|
||||
use std::time::Duration;
|
||||
|
||||
use chrono::{Local, Timelike};
|
||||
use color_eyre::eyre;
|
||||
use color_eyre::eyre::Context;
|
||||
use console::{style, Key, Term};
|
||||
use lazy_static::lazy_static;
|
||||
use notify_rust::{Notification, Timeout};
|
||||
use rust_i18n::t;
|
||||
use tracing::{debug, error};
|
||||
@@ -19,9 +18,7 @@ use which_crate::which;
|
||||
use crate::command::CommandExt;
|
||||
use crate::report::StepResult;
|
||||
|
||||
lazy_static! {
|
||||
static ref TERMINAL: Mutex<Terminal> = Mutex::new(Terminal::new());
|
||||
}
|
||||
static TERMINAL: LazyLock<Mutex<Terminal>> = LazyLock::new(|| Mutex::new(Terminal::new()));
|
||||
|
||||
#[cfg(unix)]
|
||||
pub fn shell() -> String {
|
||||
@@ -52,9 +49,7 @@ impl Terminal {
|
||||
Self {
|
||||
width: term.size_checked().map(|(_, w)| w),
|
||||
term,
|
||||
prefix: env::var("TOPGRADE_PREFIX")
|
||||
.map(|prefix| format!("({prefix}) "))
|
||||
.unwrap_or_else(|_| String::new()),
|
||||
prefix: env::var("TOPGRADE_PREFIX").map_or_else(|_| String::new(), |prefix| format!("({prefix}) ")),
|
||||
set_title: true,
|
||||
display_time: true,
|
||||
desktop_notification: false,
|
||||
@@ -62,15 +57,15 @@ impl Terminal {
|
||||
}
|
||||
|
||||
fn set_desktop_notifications(&mut self, desktop_notifications: bool) {
|
||||
self.desktop_notification = desktop_notifications
|
||||
self.desktop_notification = desktop_notifications;
|
||||
}
|
||||
|
||||
fn set_title(&mut self, set_title: bool) {
|
||||
self.set_title = set_title
|
||||
self.set_title = set_title;
|
||||
}
|
||||
|
||||
fn display_time(&mut self, display_time: bool) {
|
||||
self.display_time = display_time
|
||||
self.display_time = display_time;
|
||||
}
|
||||
|
||||
fn notify_desktop<P: AsRef<str>>(&self, message: P, timeout: Option<Duration>) {
|
||||
@@ -223,8 +218,8 @@ impl Terminal {
|
||||
|
||||
let answer = loop {
|
||||
match self.term.read_key() {
|
||||
Ok(Key::Char('y')) | Ok(Key::Char('Y')) => break Ok(true),
|
||||
Ok(Key::Char('s')) | Ok(Key::Char('S')) => {
|
||||
Ok(Key::Char('y' | 'Y')) => break Ok(true),
|
||||
Ok(Key::Char('s' | 'S')) => {
|
||||
println!(
|
||||
"\n\n{}\n",
|
||||
t!("Dropping you to shell. Fix what you need and then exit the shell.")
|
||||
@@ -235,12 +230,12 @@ impl Terminal {
|
||||
break Ok(true);
|
||||
}
|
||||
}
|
||||
Ok(Key::Char('n')) | Ok(Key::Char('N')) | Ok(Key::Enter) => break Ok(false),
|
||||
Ok(Key::Char('n' | 'N') | Key::Enter) => break Ok(false),
|
||||
Err(e) => {
|
||||
error!("Error reading from terminal: {}", e);
|
||||
break Ok(false);
|
||||
}
|
||||
Ok(Key::Char('q')) | Ok(Key::Char('Q')) => {
|
||||
Ok(Key::Char('q' | 'Q')) => {
|
||||
return Err(io::Error::from(io::ErrorKind::Interrupted)).context("Quit from user input")
|
||||
}
|
||||
_ => (),
|
||||
@@ -268,26 +263,26 @@ pub fn should_retry(interrupted: bool, step_name: &str) -> eyre::Result<bool> {
|
||||
}
|
||||
|
||||
pub fn print_separator<P: AsRef<str>>(message: P) {
|
||||
TERMINAL.lock().unwrap().print_separator(message)
|
||||
TERMINAL.lock().unwrap().print_separator(message);
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn print_error<P: AsRef<str>, Q: AsRef<str>>(key: Q, message: P) {
|
||||
TERMINAL.lock().unwrap().print_error(key, message)
|
||||
TERMINAL.lock().unwrap().print_error(key, message);
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn print_warning<P: AsRef<str>>(message: P) {
|
||||
TERMINAL.lock().unwrap().print_warning(message)
|
||||
TERMINAL.lock().unwrap().print_warning(message);
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn print_info<P: AsRef<str>>(message: P) {
|
||||
TERMINAL.lock().unwrap().print_info(message)
|
||||
TERMINAL.lock().unwrap().print_info(message);
|
||||
}
|
||||
|
||||
pub fn print_result<P: AsRef<str>>(key: P, result: &StepResult) {
|
||||
TERMINAL.lock().unwrap().print_result(key, result)
|
||||
TERMINAL.lock().unwrap().print_result(key, result);
|
||||
}
|
||||
|
||||
/// Tells whether the terminal is dumb.
|
||||
@@ -316,7 +311,7 @@ pub fn prompt_yesno(question: &str) -> Result<bool, io::Error> {
|
||||
}
|
||||
|
||||
pub fn notify_desktop<P: AsRef<str>>(message: P, timeout: Option<Duration>) {
|
||||
TERMINAL.lock().unwrap().notify_desktop(message, timeout)
|
||||
TERMINAL.lock().unwrap().notify_desktop(message, timeout);
|
||||
}
|
||||
|
||||
pub fn display_time(display_time: bool) {
|
||||
|
||||
14
src/utils.rs
14
src/utils.rs
@@ -86,7 +86,7 @@ pub fn editor() -> Vec<String> {
|
||||
env::var("EDITOR")
|
||||
.unwrap_or_else(|_| String::from(if cfg!(windows) { "notepad" } else { "vi" }))
|
||||
.split_whitespace()
|
||||
.map(|s| s.to_owned())
|
||||
.map(std::borrow::ToOwned::to_owned)
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -282,3 +282,15 @@ pub fn install_color_eyre() -> Result<()> {
|
||||
.display_location_section(true)
|
||||
.install()
|
||||
}
|
||||
|
||||
/// Macro to construct an error message for when the output of a command is unexpected.
|
||||
#[macro_export]
|
||||
macro_rules! output_changed_message {
|
||||
($command:expr, $message:expr) => {
|
||||
format!(
|
||||
"The output of `{}` changed: {}. This is not your fault, this is an issue in Topgrade. Please open an issue at: https://github.com/topgrade-rs/topgrade/issues/new?template=bug_report.md",
|
||||
$command,
|
||||
$message,
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user