Compare commits
121 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5bd5f10f0c | ||
|
|
bfaca61deb | ||
|
|
b00c4c1503 | ||
|
|
54a722be21 | ||
|
|
bf4555ed29 | ||
|
|
46eb74109e | ||
|
|
0bde798b4a | ||
|
|
ebb4448950 | ||
|
|
b2b51bc8d2 | ||
|
|
8840a273b4 | ||
|
|
50e55dea77 | ||
|
|
f7c9e42066 | ||
|
|
ef3ee7bea7 | ||
|
|
8eb300c4fb | ||
|
|
b7b99a725c | ||
|
|
df090a89c4 | ||
|
|
856fa0ed5b | ||
|
|
9bb5a680ac | ||
|
|
2d40f7bdb3 | ||
|
|
06b210d1c9 | ||
|
|
e0e714e7b5 | ||
|
|
dfb8342d8b | ||
|
|
9d662e36a1 | ||
|
|
45159c29fe | ||
|
|
a27c68a1dd | ||
|
|
cdee1c14d9 | ||
|
|
8de6a36d86 | ||
|
|
b03a8d53bb | ||
|
|
b0510cdade | ||
|
|
7945311b4b | ||
|
|
75de4dfd3b | ||
|
|
6a838bbcb7 | ||
|
|
ab2bab8c9b | ||
|
|
9a55278d32 | ||
|
|
f9735f3b31 | ||
|
|
50be214b56 | ||
|
|
9ec8e83f41 | ||
|
|
c70984d458 | ||
|
|
a3503c0c70 | ||
|
|
ca2d16edfd | ||
|
|
722b1ad09e | ||
|
|
743845a66b | ||
|
|
2594f4c0fb | ||
|
|
639d055f9a | ||
|
|
ea2ccdd69f | ||
|
|
84a50afa83 | ||
|
|
b13c1bd2d7 | ||
|
|
7749f41d56 | ||
|
|
593a2a33d9 | ||
|
|
4f693aeaf3 | ||
|
|
c3d34184d0 | ||
|
|
4aa224de87 | ||
|
|
320b13c06b | ||
|
|
907d778c55 | ||
|
|
f3fccb86c0 | ||
|
|
bb4afb71e9 | ||
|
|
ec8d30f634 | ||
|
|
50d318641a | ||
|
|
c5267f6087 | ||
|
|
d80e8f64d1 | ||
|
|
c6f2e0cc44 | ||
|
|
99c3e8af26 | ||
|
|
30d3537c0e | ||
|
|
90cb16e3d0 | ||
|
|
5192a0f1dc | ||
|
|
bec7edf1fc | ||
|
|
051784ac0d | ||
|
|
17d715479a | ||
|
|
39a90f5ebe | ||
|
|
80c4bd5065 | ||
|
|
222d800a32 | ||
|
|
b29699fc55 | ||
|
|
fc0e5461eb | ||
|
|
75da4a709c | ||
|
|
02e388122b | ||
|
|
02fe1087de | ||
|
|
34b7943fd1 | ||
|
|
b61886f0f9 | ||
|
|
d9a8ecfd33 | ||
|
|
6c68bfaf64 | ||
|
|
5866a0570f | ||
|
|
28f5754efd | ||
|
|
ea1b286c98 | ||
|
|
22ab77de6d | ||
|
|
410bd61c75 | ||
|
|
07b422915c | ||
|
|
5b9d387ef3 | ||
|
|
5b5dd27834 | ||
|
|
79f65981a5 | ||
|
|
a52c775247 | ||
|
|
549111db3a | ||
|
|
1572974ec4 | ||
|
|
8387468607 | ||
|
|
94979d6b7a | ||
|
|
6652a2aa90 | ||
|
|
f943b220d9 | ||
|
|
0fc7016d68 | ||
|
|
9e9e6c9d55 | ||
|
|
bafa15c8f1 | ||
|
|
f669de8272 | ||
|
|
ff26835406 | ||
|
|
99892359c7 | ||
|
|
8fc25d7fd4 | ||
|
|
942bfaa708 | ||
|
|
a1fd324e82 | ||
|
|
62bf2c6e90 | ||
|
|
9f1e0c8eef | ||
|
|
027de7c865 | ||
|
|
266adabd13 | ||
|
|
7d2e43f83b | ||
|
|
294a90a7c3 | ||
|
|
af5def1551 | ||
|
|
8c63ee6a18 | ||
|
|
ee8ae8623d | ||
|
|
222e6b55c0 | ||
|
|
2050a80665 | ||
|
|
e01be14041 | ||
|
|
dd6bc580fa | ||
|
|
22ef36a185 | ||
|
|
b57ceccbe1 | ||
|
|
e9d430a4e4 |
31
.github/ISSUE_TEMPLATE/bug_report.md
vendored
31
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -2,7 +2,7 @@
|
||||
name: Bug report
|
||||
about: Topgrade is misbehaving
|
||||
title: ''
|
||||
labels: 'C-bug'
|
||||
type: Bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
@@ -14,52 +14,65 @@ Please make sure to
|
||||
before filing a new one!
|
||||
|
||||
Questions labeled with `Optional` can be skipped.
|
||||
-->
|
||||
|
||||
<!--
|
||||
If you're here to report about a "No asset found" error, please make sure that
|
||||
an hour has been passed since the last release was made.
|
||||
-->
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] I have searched the issue tracker for relevant or duplicate issues.
|
||||
|
||||
## Erroneous Behavior
|
||||
|
||||
<!--
|
||||
What actually happened?
|
||||
-->
|
||||
|
||||
## Expected Behavior
|
||||
|
||||
<!--
|
||||
Describe the expected behavior
|
||||
Describe the expected behavior.
|
||||
-->
|
||||
|
||||
## Steps to reproduce
|
||||
|
||||
<!--
|
||||
A minimal example to reproduce the issue
|
||||
A minimal example to reproduce the issue.
|
||||
-->
|
||||
|
||||
## Possible Cause (Optional)
|
||||
|
||||
<!--
|
||||
If you know the possible cause of the issue, please tell us.
|
||||
-->
|
||||
|
||||
## Problem persists without calling from topgrade
|
||||
|
||||
<!--
|
||||
Execute the erroneous command directly to see if the problem persists
|
||||
Execute the erroneous command directly to see if the problem persists.
|
||||
-->
|
||||
|
||||
- [ ] Yes
|
||||
- [ ] No
|
||||
|
||||
## Did you run topgrade through `Remote Execution`
|
||||
## Ran through `Remote Execution`
|
||||
|
||||
<!--
|
||||
Did you run topgrade through `Remote Execution`?
|
||||
-->
|
||||
|
||||
- [ ] Yes
|
||||
- [ ] No
|
||||
|
||||
If yes, does the issue still occur when you run topgrade directly in your
|
||||
remote host
|
||||
remote host?
|
||||
|
||||
- [ ] Yes
|
||||
- [ ] No
|
||||
|
||||
## Configuration file (Optional)
|
||||
|
||||
<!--
|
||||
Paste your configuration file inside the code block if you think this issue is
|
||||
related to configuration.
|
||||
@@ -70,6 +83,7 @@ related to configuration.
|
||||
```
|
||||
|
||||
## Additional Details
|
||||
|
||||
- Operation System/Version
|
||||
<!-- For example, Fedora Linux 38 -->
|
||||
|
||||
@@ -82,6 +96,7 @@ related to configuration.
|
||||
- Topgrade version (`topgrade -V`)
|
||||
|
||||
## Verbose Output (`topgrade -v`)
|
||||
|
||||
<!--
|
||||
Paste the verbose output into the pre-tags
|
||||
-->
|
||||
|
||||
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,25 +1,23 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Can you please support...?
|
||||
name: General feature request
|
||||
about: Suggest a general feature, or feature within an already existing step
|
||||
title: ''
|
||||
labels: 'C-feature request'
|
||||
type: Feature
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
## I want to suggest a new step
|
||||
## Checklist
|
||||
|
||||
* Which tool is this about? Where is its repository?
|
||||
* Which operating systems are supported by this tool?
|
||||
* What should Topgrade do to figure out if the tool needs to be invoked?
|
||||
* Which exact commands should Topgrade run?
|
||||
* Does it have a `--dry-run` option? i.e., print what should be done and exit
|
||||
* Does it need the user to confirm the execution? And does it provide a `--yes`
|
||||
option to skip this step?
|
||||
- [ ] I have searched the issue tracker for relevant or duplicate issues.
|
||||
|
||||
## I want to suggest some general feature
|
||||
|
||||
Topgrade should...
|
||||
|
||||
## More information
|
||||
|
||||
<!-- Assuming that someone else implements the feature,
|
||||
please state if you know how to test it from a side branch of Topgrade. -->
|
||||
|
||||
- [ ] I am able and willing to implement this feature myself
|
||||
|
||||
30
.github/ISSUE_TEMPLATE/step_request.md
vendored
Normal file
30
.github/ISSUE_TEMPLATE/step_request.md
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
---
|
||||
name: New step request
|
||||
about: Suggest a new step/package manager to update
|
||||
title: ''
|
||||
type: Feature
|
||||
labels: request step
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] I have searched the issue tracker for relevant or duplicate issues.
|
||||
|
||||
## I want to suggest a new step
|
||||
|
||||
* Which tool is this about? Where is its repository?
|
||||
* Which operating systems are supported by this tool?
|
||||
* What should Topgrade do to figure out if the tool needs to be invoked?
|
||||
* Which exact commands should Topgrade run?
|
||||
* Does it have a `--dry-run` option? i.e., print what should be done and exit
|
||||
* Does it need the user to confirm the execution? And does it provide a `--yes`
|
||||
option to skip this?
|
||||
|
||||
## More information
|
||||
|
||||
<!-- Assuming that someone else implements the step,
|
||||
please state if you know how to test it from a side branch of Topgrade. -->
|
||||
|
||||
- [ ] I am able and willing to implement this step myself
|
||||
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,6 +1,5 @@
|
||||
## What does this PR do
|
||||
|
||||
|
||||
## Standards checklist
|
||||
|
||||
- [ ] The PR title is descriptive
|
||||
|
||||
24
.github/dependabot.yml
vendored
24
.github/dependabot.yml
vendored
@@ -1,24 +0,0 @@
|
||||
# Set update schedule for GitHub Actions
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
- package-ecosystem: cargo
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
day: "monday"
|
||||
time: "06:00"
|
||||
timezone: "UTC"
|
||||
versioning-strategy: increase
|
||||
labels: ["dependencies", "cargo"]
|
||||
commit-message:
|
||||
prefix: "deps(cargo)"
|
||||
include: "scope"
|
||||
groups:
|
||||
cargo-minor-patch:
|
||||
update-types: ["minor", "patch"]
|
||||
@@ -15,8 +15,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5.0.0
|
||||
- run: |
|
||||
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- run: |
|
||||
CONFIG_PATH=~/.config/topgrade.toml;
|
||||
if [ -f "$CONFIG_PATH" ]; then rm $CONFIG_PATH; fi
|
||||
cargo build;
|
||||
|
||||
4
.github/workflows/check_i18n.yml
vendored
4
.github/workflows/check_i18n.yml
vendored
@@ -14,7 +14,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v5.0.0
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install checker
|
||||
# Build it with the dev profile as this is faster and the checker still works
|
||||
|
||||
@@ -24,12 +24,14 @@ jobs:
|
||||
security-events: write
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v5.0.0
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Run DevSkim scanner
|
||||
uses: microsoft/DevSkim-Action@4b5047945a44163b94642a1cecc0d93a3f428cc6 # v1.0.16
|
||||
|
||||
- name: Upload DevSkim scan results to GitHub Security tab
|
||||
uses: github/codeql-action/upload-sarif@v4.31.0
|
||||
uses: github/codeql-action/upload-sarif@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4.31.4
|
||||
with:
|
||||
sarif_file: devskim-results.sarif
|
||||
|
||||
32
.github/workflows/ci.yml
vendored
32
.github/workflows/ci.yml
vendored
@@ -13,13 +13,19 @@ env:
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
fmt:
|
||||
name: Rustfmt
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v5.0.0
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Run cargo fmt
|
||||
env:
|
||||
@@ -33,7 +39,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v5.0.0
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Check if `Step` enum is sorted
|
||||
run: |
|
||||
@@ -108,7 +116,7 @@ jobs:
|
||||
|
||||
- target: x86_64-apple-darwin
|
||||
target_name: macOS-x86_64
|
||||
os: macos-13
|
||||
os: macos-15-intel
|
||||
|
||||
- target: aarch64-apple-darwin
|
||||
target_name: macOS-aarch64
|
||||
@@ -122,9 +130,14 @@ jobs:
|
||||
- target: x86_64-pc-windows-msvc
|
||||
target_name: Windows
|
||||
os: windows-latest
|
||||
env:
|
||||
cargo_cmd: ${{ matrix.use_cross == true && 'cross' || 'cargo' }}
|
||||
matrix_target: ${{ matrix.target }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v5.0.0
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup Rust Cache
|
||||
uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
||||
@@ -133,17 +146,20 @@ jobs:
|
||||
|
||||
- name: Setup cross
|
||||
if: matrix.use_cross == true
|
||||
run: curl -fL --retry 3 https://github.com/cross-rs/cross/releases/download/v${{ env.CROSS_VER }}/cross-x86_64-unknown-linux-musl.tar.gz | tar vxz -C /usr/local/bin
|
||||
run: |
|
||||
curl -fL --retry 3 "https://github.com/cross-rs/cross/releases/download/v${CROSS_VER}/cross-x86_64-unknown-linux-musl.tar.gz" | tar vxz -C /usr/local/bin
|
||||
|
||||
- name: Run cargo/cross check
|
||||
run: ${{ matrix.use_cross == true && 'cross' || 'cargo' }} check --locked --target ${{ matrix.target }}
|
||||
run: |
|
||||
"${cargo_cmd}" check --locked --target "${matrix_target}"
|
||||
|
||||
- name: Run cargo/cross clippy
|
||||
run: |
|
||||
rustup component add clippy
|
||||
${{ matrix.use_cross == true && 'cross' || 'cargo' }} clippy --locked --target ${{ matrix.target }} --all-features -- -D warnings
|
||||
"${cargo_cmd}" 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}"
|
||||
|
||||
94
.github/workflows/create_release_assets.yml
vendored
94
.github/workflows/create_release_assets.yml
vendored
@@ -5,7 +5,12 @@ on:
|
||||
types: [ release-created ]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
# Write permissions to call the repository dispatch
|
||||
contents: write
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
# Publish release files for CD native environments
|
||||
@@ -23,10 +28,14 @@ jobs:
|
||||
# Use the Ubuntu 22.04 image to link with a low version of glibc
|
||||
#
|
||||
# https://github.com/topgrade-rs/topgrade/issues/1095
|
||||
platform: [ ubuntu-22.04, macos-latest, macos-13, windows-latest ]
|
||||
platform: [ ubuntu-22.04, macos-latest, macos-15-intel, windows-latest ]
|
||||
runs-on: ${{ matrix.platform }}
|
||||
env:
|
||||
tag: ${{ github.event.client_payload.tag }}
|
||||
steps:
|
||||
- uses: actions/checkout@v5.0.0
|
||||
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install needed components
|
||||
run: |
|
||||
@@ -66,9 +75,10 @@ jobs:
|
||||
mkdir -p "$dir"
|
||||
echo "DEPLOY_DIR=$dir" >> $GITHUB_ENV
|
||||
|
||||
- name: Generate shell completions
|
||||
- name: Generate man page and shell completions
|
||||
shell: bash
|
||||
run: |
|
||||
"$BIN" --gen-manpage > "$DEPLOY_DIR/topgrade.1"
|
||||
"$BIN" --gen-completion bash > "$DEPLOY_DIR/topgrade.bash"
|
||||
"$BIN" --gen-completion fish > "$DEPLOY_DIR/topgrade.fish"
|
||||
"$BIN" --gen-completion zsh > "$DEPLOY_DIR/_topgrade"
|
||||
@@ -80,7 +90,7 @@ jobs:
|
||||
run: |
|
||||
cargo install default-target
|
||||
mkdir -p assets
|
||||
FILENAME=topgrade-${{ github.event.client_payload.tag }}-$(default-target)
|
||||
FILENAME=topgrade-${tag}-$(default-target)
|
||||
mv target/release/topgrade assets
|
||||
cd assets
|
||||
tar --format=ustar -czf $FILENAME.tar.gz topgrade
|
||||
@@ -111,7 +121,7 @@ jobs:
|
||||
run: |
|
||||
cargo install default-target
|
||||
mkdir assets
|
||||
FILENAME=topgrade-${{ github.event.client_payload.tag }}-$(default-target)
|
||||
FILENAME=topgrade-${tag}-$(default-target)
|
||||
mv target/release/topgrade.exe assets/topgrade.exe
|
||||
cd assets
|
||||
powershell Compress-Archive -Path * -Destination ${FILENAME}.zip
|
||||
@@ -121,13 +131,13 @@ jobs:
|
||||
shell: bash
|
||||
|
||||
- name: Upload assets
|
||||
run:
|
||||
gh release upload "${{ github.event.client_payload.tag }}" assets/*
|
||||
run: |
|
||||
gh release upload "${tag}" assets/*
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Generate artifact attestations
|
||||
uses: actions/attest-build-provenance@v3.0.0
|
||||
uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # v3.0.0
|
||||
with:
|
||||
subject-path: assets/*
|
||||
|
||||
@@ -151,9 +161,17 @@ jobs:
|
||||
"aarch64-unknown-linux-musl",
|
||||
"x86_64-unknown-freebsd",
|
||||
]
|
||||
runs-on: ubuntu-latest
|
||||
# Run this one on an older version as well, to limit glibc to 2.34 instead of 2.39.
|
||||
# Even though this is cross-compiled, it links to the libc6-<arch>-cross installed on the host
|
||||
# (see the apt-get install calls below)
|
||||
runs-on: ubuntu-22.04
|
||||
env:
|
||||
matrix_target: ${{ matrix.target }}
|
||||
tag: ${{ github.event.client_payload.tag }}
|
||||
steps:
|
||||
- uses: actions/checkout@v5.0.0
|
||||
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install needed components
|
||||
run: |
|
||||
@@ -176,24 +194,29 @@ jobs:
|
||||
shell: bash
|
||||
|
||||
- name: install targets
|
||||
run: rustup target add ${{ matrix.target }}
|
||||
run: rustup target add "${matrix_target}"
|
||||
|
||||
- name: install cross
|
||||
uses: taiki-e/install-action@c5b1b6f479c32f356cc6f4ba672a47f63853b13b # v2.62.38
|
||||
with:
|
||||
tool: cross@0.2.5
|
||||
|
||||
- name: Check format
|
||||
run: cross fmt --all -- --check
|
||||
# Install from source to fix `ld: cannot find -lgeom` for freebsd build
|
||||
run: cargo +stable install --git https://github.com/cross-rs/cross cross
|
||||
|
||||
- name: Run clippy
|
||||
run: cross clippy --all-targets --locked --target ${{matrix.target}} -- -D warnings
|
||||
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
|
||||
run: cross clippy --locked --all-features --target "${matrix_target}" -- -D warnings
|
||||
|
||||
- name: Run tests
|
||||
run: cross test --target ${{matrix.target}}
|
||||
run: cross test --target "${matrix_target}"
|
||||
# Running tests on FreeBSD is impossible; see https://github.com/cross-rs/cross/wiki/FAQ#running-bsd-tests
|
||||
# Not that this is *NOT* the same as the original issue with `ld: cannot find -lgeom`, but a new issue:
|
||||
# error: test failed, to rerun pass `--lib`
|
||||
# Caused by:
|
||||
# could not execute process `/target/x86_64-unknown-freebsd/debug/deps/topgrade-9b1670d87ca863dd` (never executed)
|
||||
# Caused by:
|
||||
# No such file or directory (os error 2)
|
||||
# TODO: I have not tested this in GHA yet, only locally
|
||||
if: ${{ matrix.target != 'x86_64-unknown-freebsd' }}
|
||||
|
||||
# Used `https://github.com/BurntSushi/ripgrep/blob/master/.github/workflows/release.yml`
|
||||
# as a reference.
|
||||
@@ -214,23 +237,24 @@ jobs:
|
||||
mkdir -p "$dir"
|
||||
echo "DEPLOY_DIR=$dir" >> $GITHUB_ENV
|
||||
|
||||
- name: Generate shell completions
|
||||
- name: Generate man page and shell completions
|
||||
shell: bash
|
||||
run: |
|
||||
"$BIN" --gen-manpage > "$DEPLOY_DIR/topgrade.1"
|
||||
"$BIN" --gen-completion bash > "$DEPLOY_DIR/topgrade.bash"
|
||||
"$BIN" --gen-completion fish > "$DEPLOY_DIR/topgrade.fish"
|
||||
"$BIN" --gen-completion zsh > "$DEPLOY_DIR/_topgrade"
|
||||
|
||||
- name: Build in Release profile with all features enabled
|
||||
run: cross build --release --all-features --target ${{matrix.target}}
|
||||
run: cross build --release --all-features --target "${matrix_target}"
|
||||
|
||||
- name: Rename Release
|
||||
run: |
|
||||
mkdir -p assets
|
||||
FILENAME=topgrade-${{ github.event.client_payload.tag }}-${{matrix.target}}
|
||||
mv target/${{matrix.target}}/release/topgrade assets
|
||||
FILENAME=topgrade-${tag}-${matrix_target}
|
||||
mv "target/${matrix_target}/release/topgrade" assets
|
||||
cd assets
|
||||
tar --format=ustar -czf $FILENAME.tar.gz topgrade
|
||||
tar --format=ustar -czf "$FILENAME.tar.gz" topgrade
|
||||
rm topgrade
|
||||
ls .
|
||||
|
||||
@@ -239,39 +263,41 @@ jobs:
|
||||
# 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
|
||||
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
|
||||
mv target/"${matrix_target}"/debian/*.deb assets
|
||||
if: ${{ matrix.target == 'aarch64-unknown-linux-gnu' || matrix.target == 'armv7-unknown-linux-gnueabihf' }}
|
||||
shell: bash
|
||||
|
||||
|
||||
- name: Upload assets
|
||||
run:
|
||||
gh release upload "${{ github.event.client_payload.tag }}" assets/*
|
||||
gh release upload "${tag}" assets/*
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Generate artifact attestations
|
||||
uses: actions/attest-build-provenance@v3.0.0
|
||||
uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # v3.0.0
|
||||
with:
|
||||
subject-path: assets/*
|
||||
|
||||
triggers:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [ native_build, cross_build ]
|
||||
env:
|
||||
tag: ${{ github.event.client_payload.tag }}
|
||||
steps:
|
||||
- name: Trigger workflows
|
||||
run: |
|
||||
gh api repos/${{ github.repository }}/dispatches \
|
||||
gh api "repos/${GITHUB_REPOSITORY}/dispatches" \
|
||||
-f "event_type=release-assets-built" \
|
||||
-F "client_payload[tag]=${{ github.event.client_payload.tag }}"
|
||||
-F "client_payload[tag]=${tag}"
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
7
.github/workflows/dependency-review.yml
vendored
7
.github/workflows/dependency-review.yml
vendored
@@ -17,6 +17,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: 'Checkout Repository'
|
||||
uses: actions/checkout@v5.0.0
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: 'Dependency Review'
|
||||
uses: actions/dependency-review-action@40c09b7dc99638e5ddb0bfd91c1673effc064d8a # v4.8.1
|
||||
uses: actions/dependency-review-action@3c4e3dcb1aa7874d2c16be7d79418e9b7efd6261 # v4.8.2
|
||||
|
||||
20
.github/workflows/lint_pr.yml
vendored
Normal file
20
.github/workflows/lint_pr.yml
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
name: 'Lint PR'
|
||||
|
||||
on:
|
||||
pull_request_target: # zizmor: ignore[dangerous-triggers] this is the only way, and we're not running user code
|
||||
types:
|
||||
- opened
|
||||
- edited
|
||||
- reopened
|
||||
- synchronize
|
||||
|
||||
jobs:
|
||||
main:
|
||||
name: Validate PR title
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
pull-requests: read
|
||||
steps:
|
||||
- uses: amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50 # v6.1.1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
30
.github/workflows/release-plz.yml
vendored
30
.github/workflows/release-plz.yml
vendored
@@ -16,18 +16,16 @@ jobs:
|
||||
contents: write
|
||||
id-token: write # For trusted publishing
|
||||
steps:
|
||||
- &checkout
|
||||
name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
- &install-rust
|
||||
name: Install Rust toolchain
|
||||
- name: Install Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
- name: Run release-plz
|
||||
id: release-plz
|
||||
uses: release-plz/action@v0.5
|
||||
uses: release-plz/action@d529f731ae3e89610ada96eda34e5c6ba3b12214 # v0.5
|
||||
with:
|
||||
command: release
|
||||
env:
|
||||
@@ -35,12 +33,13 @@ jobs:
|
||||
|
||||
- name: Trigger workflows
|
||||
if: steps.release-plz.outputs.releases_created == 'true'
|
||||
run: |
|
||||
gh api repos/${{ github.repository }}/dispatches \
|
||||
-f "event_type=release-created" \
|
||||
-F "client_payload[tag]=${{ fromJSON(steps.release-plz.outputs.releases)[0].tag }}"
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
tag: ${{ fromJSON(steps.release-plz.outputs.releases)[0].tag }}
|
||||
run: |
|
||||
gh api "repos/${GITHUB_REPOSITORY}/dispatches" \
|
||||
-f "event_type=release-created" \
|
||||
-F "client_payload[tag]=${tag}"
|
||||
|
||||
# Create a PR with the new versions and changelog, preparing the next release.
|
||||
release-plz-pr:
|
||||
@@ -53,10 +52,15 @@ jobs:
|
||||
group: release-plz-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
steps:
|
||||
- *checkout
|
||||
- *install-rust
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
- name: Install Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
- name: Run release-plz
|
||||
uses: release-plz/action@v0.5
|
||||
uses: release-plz/action@d529f731ae3e89610ada96eda34e5c6ba3b12214 # v0.5
|
||||
with:
|
||||
command: release-pr
|
||||
env:
|
||||
|
||||
3
.github/workflows/release_to_aur.yml
vendored
3
.github/workflows/release_to_aur.yml
vendored
@@ -13,9 +13,10 @@ jobs:
|
||||
steps:
|
||||
- name: Determine version
|
||||
id: determine_version
|
||||
env:
|
||||
tag: ${{ github.event.client_payload.tag }}
|
||||
run: |
|
||||
# tag should be something like "v16.0.4", remove the prefix v here
|
||||
tag="${{ github.event.client_payload.tag }}"
|
||||
echo "version=${tag#v}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Publish source AUR package
|
||||
|
||||
26
.github/workflows/release_to_homebrew.yml
vendored
26
.github/workflows/release_to_homebrew.yml
vendored
@@ -11,28 +11,12 @@ jobs:
|
||||
homebrew-publish:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up Homebrew
|
||||
id: set-up-homebrew
|
||||
uses: Homebrew/actions/setup-homebrew@24a0b15df658487e137fcd20fba32757d41a9411 # master
|
||||
|
||||
- name: Cache Homebrew Bundler RubyGems
|
||||
id: cache
|
||||
uses: actions/cache@v4.3.0
|
||||
with:
|
||||
path: ${{ steps.set-up-homebrew.outputs.gems-path }}
|
||||
key: ${{ runner.os }}-rubygems-${{ steps.set-up-homebrew.outputs.gems-hash }}
|
||||
restore-keys: ${{ runner.os }}-rubygems-
|
||||
|
||||
- name: Install Homebrew Bundler RubyGems
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
run: brew install-bundler-gems
|
||||
|
||||
- name: Bump formulae
|
||||
uses: Homebrew/actions/bump-packages@24a0b15df658487e137fcd20fba32757d41a9411 # master
|
||||
continue-on-error: true
|
||||
uses: dawidd6/action-homebrew-bump-formula@c5ddc585e75f0f750a8b4f610688b4bec9e80915 # v6
|
||||
with:
|
||||
# Custom GitHub access token with only the 'public_repo' scope enabled
|
||||
token: ${{secrets.HOMEBREW_ACCESS_TOKEN}}
|
||||
# Bump only these formulae if outdated
|
||||
formulae: |
|
||||
topgrade
|
||||
formula: topgrade
|
||||
tag: ${{ github.event.client_payload.tag }}
|
||||
# We cannot use an org because org forks cannot give push access to maintainers, which Homebrew requires.
|
||||
# org: topgrade-rs
|
||||
|
||||
32
.github/workflows/release_to_pypi.yml
vendored
32
.github/workflows/release_to_pypi.yml
vendored
@@ -15,7 +15,10 @@ jobs:
|
||||
matrix:
|
||||
target: [x86_64, x86, aarch64]
|
||||
steps:
|
||||
- uses: actions/checkout@v5.0.0
|
||||
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Build wheels
|
||||
uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4
|
||||
with:
|
||||
@@ -23,7 +26,7 @@ jobs:
|
||||
args: --release --out dist
|
||||
manylinux: auto
|
||||
- name: Upload wheels
|
||||
uses: actions/upload-artifact@v5.0.0
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
with:
|
||||
name: wheels-linux-${{ matrix.target }}
|
||||
path: dist
|
||||
@@ -34,14 +37,17 @@ jobs:
|
||||
matrix:
|
||||
target: [x64, x86]
|
||||
steps:
|
||||
- uses: actions/checkout@v5.0.0
|
||||
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Build wheels
|
||||
uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
args: --release --out dist
|
||||
- name: Upload wheels
|
||||
uses: actions/upload-artifact@v5.0.0
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
with:
|
||||
name: wheels-windows-${{ matrix.target }}
|
||||
path: dist
|
||||
@@ -52,14 +58,17 @@ jobs:
|
||||
matrix:
|
||||
target: [x86_64, aarch64]
|
||||
steps:
|
||||
- uses: actions/checkout@v5.0.0
|
||||
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Build wheels
|
||||
uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
args: --release --out dist
|
||||
- name: Upload wheels
|
||||
uses: actions/upload-artifact@v5.0.0
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
with:
|
||||
name: wheels-macos-${{ matrix.target }}
|
||||
path: dist
|
||||
@@ -67,14 +76,17 @@ jobs:
|
||||
sdist:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5.0.0
|
||||
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Build sdist
|
||||
uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4
|
||||
with:
|
||||
command: sdist
|
||||
args: --out dist
|
||||
- name: Upload sdist
|
||||
uses: actions/upload-artifact@v5.0.0
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
with:
|
||||
name: wheels-sdist
|
||||
path: dist
|
||||
@@ -91,10 +103,10 @@ jobs:
|
||||
# Used to generate artifact attestation
|
||||
attestations: write
|
||||
steps:
|
||||
- uses: actions/download-artifact@v6.0.0
|
||||
- uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
||||
|
||||
- name: Generate artifact attestation
|
||||
uses: actions/attest-build-provenance@v3.0.0
|
||||
uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # v3.0.0
|
||||
with:
|
||||
subject-path: 'wheels-*/*'
|
||||
|
||||
|
||||
1
.github/workflows/release_to_winget.yml
vendored
1
.github/workflows/release_to_winget.yml
vendored
@@ -13,6 +13,7 @@ jobs:
|
||||
steps:
|
||||
- uses: vedantmgoyal2009/winget-releaser@19e706d4c9121098010096f9c495a70a7518b30f # main
|
||||
with:
|
||||
release-tag: ${{ github.event.client_payload.tag }}
|
||||
identifier: topgrade-rs.topgrade
|
||||
max-versions-to-keep: 5 # keep only latest 5 versions
|
||||
token: ${{ secrets.WINGET_TOKEN }}
|
||||
|
||||
6
.github/workflows/scorecards.yml
vendored
6
.github/workflows/scorecards.yml
vendored
@@ -36,7 +36,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: "Checkout code"
|
||||
uses: actions/checkout@v5.0.0
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -63,7 +63,7 @@ jobs:
|
||||
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
|
||||
# format to the repository Actions tab.
|
||||
- name: "Upload artifact"
|
||||
uses: actions/upload-artifact@v5.0.0
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
with:
|
||||
name: SARIF file
|
||||
path: results.sarif
|
||||
@@ -71,6 +71,6 @@ jobs:
|
||||
|
||||
# Upload the results to GitHub's code scanning dashboard.
|
||||
- name: "Upload to code-scanning"
|
||||
uses: github/codeql-action/upload-sarif@v4.31.0
|
||||
uses: github/codeql-action/upload-sarif@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4.31.4
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
repos:
|
||||
- repo: https://github.com/gitleaks/gitleaks
|
||||
rev: v8.28.0
|
||||
rev: v8.29.0
|
||||
hooks:
|
||||
- id: gitleaks
|
||||
|
||||
@@ -16,7 +16,7 @@ repos:
|
||||
- id: trailing-whitespace
|
||||
|
||||
- repo: https://github.com/crate-ci/typos
|
||||
rev: v1.38.1
|
||||
rev: v1.39.2
|
||||
hooks:
|
||||
- id: typos
|
||||
|
||||
|
||||
157
CHANGELOG.md
157
CHANGELOG.md
@@ -7,6 +7,163 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [16.4.2](https://github.com/topgrade-rs/topgrade/compare/v16.4.1...v16.4.2) - 2025-11-20
|
||||
|
||||
### Other
|
||||
|
||||
- *(deps)* update dawidd6/action-homebrew-bump-formula action to v6 ([#1543](https://github.com/topgrade-rs/topgrade/pull/1543))
|
||||
|
||||
## [16.4.1](https://github.com/topgrade-rs/topgrade/compare/v16.4.0...v16.4.1) - 2025-11-20
|
||||
|
||||
### Other
|
||||
|
||||
- refactor run_containers error handling ([#1541](https://github.com/topgrade-rs/topgrade/pull/1541))
|
||||
|
||||
## [16.4.0](https://github.com/topgrade-rs/topgrade/compare/v16.3.0...v16.4.0) - 2025-11-20
|
||||
|
||||
### Added
|
||||
|
||||
- *(os)* add Origami Linux support ([#1530](https://github.com/topgrade-rs/topgrade/pull/1530))
|
||||
- *(containers)* add option to run `system prune` ([#1523](https://github.com/topgrade-rs/topgrade/pull/1523))
|
||||
|
||||
### Fixed
|
||||
|
||||
- *(deps)* restore custom commands order ([#1535](https://github.com/topgrade-rs/topgrade/pull/1535))
|
||||
|
||||
### Other
|
||||
|
||||
- *(deps)* update clap, clap_builder, clap_complete ([#1540](https://github.com/topgrade-rs/topgrade/pull/1540))
|
||||
- *(deps)* update github/codeql-action action to v4.31.4 ([#1531](https://github.com/topgrade-rs/topgrade/pull/1531))
|
||||
- *(config)* add custom commands order test ([#1536](https://github.com/topgrade-rs/topgrade/pull/1536))
|
||||
- make Config methods more consistent by utilizing `#[derive(Default)]` ([#1534](https://github.com/topgrade-rs/topgrade/pull/1534))
|
||||
- *(issue templates)* use issue types ([#1533](https://github.com/topgrade-rs/topgrade/pull/1533))
|
||||
- *(deps)* lock file maintenance ([#1505](https://github.com/topgrade-rs/topgrade/pull/1505))
|
||||
- *(deps)* update actions/checkout digest to 93cb6ef ([#1526](https://github.com/topgrade-rs/topgrade/pull/1526))
|
||||
- *(deps)* update actions/checkout action to v5.0.1 ([#1527](https://github.com/topgrade-rs/topgrade/pull/1527))
|
||||
|
||||
## [16.3.0](https://github.com/topgrade-rs/topgrade/compare/v16.2.1...v16.3.0) - 2025-11-16
|
||||
|
||||
### Added
|
||||
|
||||
- print summary and run post commands when (q)uit is used ([#1254](https://github.com/topgrade-rs/topgrade/pull/1254))
|
||||
- run pre_sudo before pre_commands ([#1469](https://github.com/topgrade-rs/topgrade/pull/1469))
|
||||
- *(chezmoi)* add `exclude_encrypted` config ([#1453](https://github.com/topgrade-rs/topgrade/pull/1453))
|
||||
|
||||
### Fixed
|
||||
|
||||
- *(elan)* skip running elan update on elan >=4.0.0 ([#1507](https://github.com/topgrade-rs/topgrade/pull/1507))
|
||||
- *(deps)* Fix non-locked install on older version of Rust ([#1485](https://github.com/topgrade-rs/topgrade/pull/1485))
|
||||
- *(deps)* Fix non-locked install on older version of Rust ([#1482](https://github.com/topgrade-rs/topgrade/pull/1482))
|
||||
- *(bun)* skip self-update if not installed via official script ([#1476](https://github.com/topgrade-rs/topgrade/pull/1476))
|
||||
- *(openbsd)* fix compilation on OpenBSD ([#1473](https://github.com/topgrade-rs/topgrade/pull/1473))
|
||||
|
||||
### Other
|
||||
|
||||
- *(license)* switch license variant to GPL-3.0-or-later ([#1518](https://github.com/topgrade-rs/topgrade/pull/1518))
|
||||
- *(deps)* update some dependencies ([#1512](https://github.com/topgrade-rs/topgrade/pull/1512))
|
||||
- *(deps)* update github/codeql-action action to v4.31.3 ([#1483](https://github.com/topgrade-rs/topgrade/pull/1483))
|
||||
- remove unnecessary cfg-if dependency ([#1509](https://github.com/topgrade-rs/topgrade/pull/1509))
|
||||
- *(lint_pr)* run on synchronize, and add zizmor ignore ([#1508](https://github.com/topgrade-rs/topgrade/pull/1508))
|
||||
- *(pre-commit)* autoupdate ([#1464](https://github.com/topgrade-rs/topgrade/pull/1464))
|
||||
- improve issue templates ([#1235](https://github.com/topgrade-rs/topgrade/pull/1235))
|
||||
- *(deps)* bump mac-notification-sys, use main branch temporarily ([#1506](https://github.com/topgrade-rs/topgrade/pull/1506))
|
||||
- *(deps)* lock file maintenance ([#1481](https://github.com/topgrade-rs/topgrade/pull/1481))
|
||||
- *(deps)* pin dependencies ([#1478](https://github.com/topgrade-rs/topgrade/pull/1478))
|
||||
- *(deps)* update actions/dependency-review-action action to v4.8.2 ([#1479](https://github.com/topgrade-rs/topgrade/pull/1479))
|
||||
- Add Renovate ([#1477](https://github.com/topgrade-rs/topgrade/pull/1477))
|
||||
- Replace main's self update with a proper step call ([#1470](https://github.com/topgrade-rs/topgrade/pull/1470))
|
||||
- *(release)* Fix homebrew releases ([#1468](https://github.com/topgrade-rs/topgrade/pull/1468))
|
||||
|
||||
## [16.2.1](https://github.com/topgrade-rs/topgrade/compare/v16.2.0...v16.2.1) - 2025-11-10
|
||||
|
||||
### Fixed
|
||||
|
||||
- *(release)* Use bash in Windows to fix powershell issues ([#1461](https://github.com/topgrade-rs/topgrade/pull/1461))
|
||||
- *(release)* Fix .deb distribution ([#1460](https://github.com/topgrade-rs/topgrade/pull/1460))
|
||||
- *(release)* Fix .deb distribution ([#1458](https://github.com/topgrade-rs/topgrade/pull/1458))
|
||||
|
||||
## [16.2.0](https://github.com/topgrade-rs/topgrade/compare/v16.1.2...v16.2.0) - 2025-11-10
|
||||
|
||||
### Added
|
||||
|
||||
- *(mise)* run `mise self-update` ([#1450](https://github.com/topgrade-rs/topgrade/pull/1450))
|
||||
- *(falconf)* add falconf step ([#1219](https://github.com/topgrade-rs/topgrade/pull/1219))
|
||||
- *(hyprpm)* add hyprpm step ([#1213](https://github.com/topgrade-rs/topgrade/pull/1213))
|
||||
- *(doom)* add doom.aot option ([#1214](https://github.com/topgrade-rs/topgrade/pull/1214))
|
||||
- add show_distribution_summary config option ([#1259](https://github.com/topgrade-rs/topgrade/pull/1259))
|
||||
- *(rustup)* add rustup.channels config ([#1206](https://github.com/topgrade-rs/topgrade/pull/1206))
|
||||
- *(os)* add AOSC OS support ([#1424](https://github.com/topgrade-rs/topgrade/pull/1424))
|
||||
- add damp run type ([#1217](https://github.com/topgrade-rs/topgrade/pull/1217))
|
||||
|
||||
### Fixed
|
||||
|
||||
- *(release)* fix homebrew releases by migrating to dawidd6/action-homebrew-bump-formula ([#1457](https://github.com/topgrade-rs/topgrade/pull/1457))
|
||||
- *(mise)* fix mise self-update failing when installed via a package manager ([#1456](https://github.com/topgrade-rs/topgrade/pull/1456))
|
||||
- *(release)* Add man page to .deb distribution ([#1455](https://github.com/topgrade-rs/topgrade/pull/1455))
|
||||
- *(self-update)* fix windows self-update reporting failure on successful self-update ([#1452](https://github.com/topgrade-rs/topgrade/pull/1452))
|
||||
- *(pkgfile)* make pkgfile opt-in ([#1449](https://github.com/topgrade-rs/topgrade/pull/1449))
|
||||
- *(vcpkg)* fix permission denied when updating vcpkg if it's installed as root ([#1447](https://github.com/topgrade-rs/topgrade/pull/1447))
|
||||
- *(zh_TW)* fixed zh_TW strings ([#1446](https://github.com/topgrade-rs/topgrade/pull/1446))
|
||||
- *(git)* fix shellexpand::tilde in git_repos in topgrade.d/* ([#1223](https://github.com/topgrade-rs/topgrade/pull/1223))
|
||||
- *(auto-cpufreq)* skip when install script is not used ([#1215](https://github.com/topgrade-rs/topgrade/pull/1215))
|
||||
- *(vim)* change nvimrc base_dir for windows ([#1433](https://github.com/topgrade-rs/topgrade/pull/1433))
|
||||
- *(guix)* fix overcomplicated Guix step ([#1290](https://github.com/topgrade-rs/topgrade/pull/1290))
|
||||
- *(gem)* fix incorrectly placed debug message in `gem` step ([#1212](https://github.com/topgrade-rs/topgrade/pull/1212))
|
||||
- *(conda)* replace deprecated `auto_activate_base` ([#1158](https://github.com/topgrade-rs/topgrade/pull/1158))
|
||||
- *(containers)* fix panic in `containers` step ([#1150](https://github.com/topgrade-rs/topgrade/pull/1150))
|
||||
- *(jetbrains-toolbox)* fix step not dry running ([#1253](https://github.com/topgrade-rs/topgrade/pull/1253))
|
||||
|
||||
### Other
|
||||
|
||||
- comment run_config_update ([#1448](https://github.com/topgrade-rs/topgrade/pull/1448))
|
||||
- Expand LLM guidelines in CONTRIBUTING.md ([#1445](https://github.com/topgrade-rs/topgrade/pull/1445))
|
||||
- Add AI guidelines to CONTRIBUTING.md ([#1444](https://github.com/topgrade-rs/topgrade/pull/1444))
|
||||
- add comments to Config::allowed_steps ([#1291](https://github.com/topgrade-rs/topgrade/pull/1291))
|
||||
- *(nix)* Deduplicate run_nix and run_nix_self_upgrade nix --version checking ([#1376](https://github.com/topgrade-rs/topgrade/pull/1376))
|
||||
- remove commented-out library code and unnecessary bin declaration ([#1373](https://github.com/topgrade-rs/topgrade/pull/1373))
|
||||
- Simplify target cfgs ([#1346](https://github.com/topgrade-rs/topgrade/pull/1346))
|
||||
- tidy up binary-conflict code ([#1329](https://github.com/topgrade-rs/topgrade/pull/1329))
|
||||
- Improve installation section ([#1442](https://github.com/topgrade-rs/topgrade/pull/1442))
|
||||
- *(deps)* Update jetbrains-toolbox-updater ([#1438](https://github.com/topgrade-rs/topgrade/pull/1438))
|
||||
- remove template expansion in code contexts ([#1434](https://github.com/topgrade-rs/topgrade/pull/1434))
|
||||
- *(deps)* bump github/codeql-action from 4.31.0 to 4.31.2 ([#1427](https://github.com/topgrade-rs/topgrade/pull/1427))
|
||||
- don't persist credentials in actions/checkout ([#1422](https://github.com/topgrade-rs/topgrade/pull/1422))
|
||||
- Improve CONTRIBUTING.md ([#1420](https://github.com/topgrade-rs/topgrade/pull/1420))
|
||||
- Update SECURITY.md ([#1421](https://github.com/topgrade-rs/topgrade/pull/1421))
|
||||
- Enforce conventional commits in PR titles ([#1418](https://github.com/topgrade-rs/topgrade/pull/1418))
|
||||
- Improve contributing section
|
||||
- Remove roadmap
|
||||
- Reformat README.md
|
||||
- Update installation methods
|
||||
- *(release)* Fix dispatch error in create_release_assets.yml ([#1406](https://github.com/topgrade-rs/topgrade/pull/1406))
|
||||
|
||||
## [16.1.2](https://github.com/topgrade-rs/topgrade/compare/v16.1.1...v16.1.2) - 2025-11-01
|
||||
|
||||
### Fixed
|
||||
|
||||
- *(release)* Fix cross-compilation for arm requiring glibc>=2.39 ([#1405](https://github.com/topgrade-rs/topgrade/pull/1405))
|
||||
- *(release)* Fix FreeBSD build ([#1404](https://github.com/topgrade-rs/topgrade/pull/1404))
|
||||
- *(release)* Fix FreeBSD build ([#1402](https://github.com/topgrade-rs/topgrade/pull/1402))
|
||||
- *(release)* Fix manual workflow trigger ([#1401](https://github.com/topgrade-rs/topgrade/pull/1401))
|
||||
- *(release)* Fix FreeBSD build and add manual workflow trigger ([#1399](https://github.com/topgrade-rs/topgrade/pull/1399))
|
||||
|
||||
### Other
|
||||
|
||||
- *(release)* Fix cross trying to fmt ([#1403](https://github.com/topgrade-rs/topgrade/pull/1403))
|
||||
|
||||
## [16.1.1](https://github.com/topgrade-rs/topgrade/compare/v16.1.0...v16.1.1) - 2025-11-01
|
||||
|
||||
### Fixed
|
||||
|
||||
- *(typst)* Skip typst when self-update is disabled ([#1397](https://github.com/topgrade-rs/topgrade/pull/1397))
|
||||
- *(release)* Fix winget release workflow ([#1395](https://github.com/topgrade-rs/topgrade/pull/1395))
|
||||
- *(release)* Fix FreeBSD release ([#1393](https://github.com/topgrade-rs/topgrade/pull/1393))
|
||||
- *(release)* Fix FreeBSD release ([#1391](https://github.com/topgrade-rs/topgrade/pull/1391))
|
||||
|
||||
### Other
|
||||
|
||||
- Update from deprecated macos-13 to macos-15-intel ([#1394](https://github.com/topgrade-rs/topgrade/pull/1394))
|
||||
|
||||
## [16.1.0](https://github.com/topgrade-rs/topgrade/compare/v16.0.4...v16.1.0) - 2025-10-31
|
||||
|
||||
### Added
|
||||
|
||||
117
CONTRIBUTING.md
117
CONTRIBUTING.md
@@ -7,13 +7,36 @@ We welcome and encourage contributions of all kinds, such as:
|
||||
2. Documentation improvements
|
||||
3. Code (PR or PR Review)
|
||||
|
||||
Please follow the [Karma Runner guidelines](http://karma-runner.github.io/6.2/dev/git-commit-msg.html)
|
||||
for commit messages.
|
||||
### LLM/AI guidelines
|
||||
|
||||
## Adding a new `step`
|
||||
You may use LLMs (AI tools) for:
|
||||
|
||||
In `topgrade`'s term, package manager is called `step`.
|
||||
To add a new `step` to `topgrade`:
|
||||
* Inspiration, problem solving, help with Rust, translation, etc.
|
||||
* Generating small and self-contained snippets of code (e.g., shell scripts or utility functions)
|
||||
|
||||
Do **not** use LLMs to:
|
||||
|
||||
* Generate ("vibe code") entire pull requests
|
||||
* Write or generate issue or pull request descriptions
|
||||
|
||||
### General guidelines
|
||||
|
||||
**Please use [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/) for your PR title**.
|
||||
|
||||
We use [pre-commit](https://github.com/pre-commit/pre-commit). It runs in CI, but you can optionally install the hook
|
||||
locally with `pre-commit install`. If you don't want to use pre-commit, make sure the following pass before submitting
|
||||
your PR:
|
||||
|
||||
```shell
|
||||
$ cargo fmt
|
||||
$ cargo clippy
|
||||
$ cargo test
|
||||
```
|
||||
|
||||
### Adding a new step
|
||||
|
||||
In `topgrade`'s terms, a package manager (or something else that can be upgraded) is called a step.
|
||||
To add a new step to `topgrade`:
|
||||
|
||||
1. Add a new variant to
|
||||
[`enum Step`](https://github.com/topgrade-rs/topgrade/blob/main/src/step.rs)
|
||||
@@ -33,8 +56,9 @@ To add a new `step` to `topgrade`:
|
||||
|
||||
You need to find the appropriate location where this update function goes, it should be
|
||||
a file under [`src/steps`](https://github.com/topgrade-rs/topgrade/tree/main/src/steps),
|
||||
the file names are self-explanatory, for example, `step`s related to `zsh` are
|
||||
placed in [`steps/zsh.rs`](https://github.com/topgrade-rs/topgrade/blob/main/src/steps/zsh.rs).
|
||||
the file names are self-explanatory, for example, steps related to `zsh` are
|
||||
placed in [`steps/zsh.rs`](https://github.com/topgrade-rs/topgrade/blob/main/src/steps/zsh.rs), and steps that run on
|
||||
Linux only are placed in [`steps/linux.rs`](https://github.com/topgrade-rs/topgrade/blob/main/src/steps/linux.rs).
|
||||
|
||||
Then you implement the update function, and put it in the file where it belongs.
|
||||
|
||||
@@ -53,19 +77,17 @@ To add a new `step` to `topgrade`:
|
||||
}
|
||||
```
|
||||
|
||||
Such a update function would be conventionally named `run_xxx()`, where `xxx`
|
||||
is the name of the new step, and it should take a argument of type
|
||||
`&ExecutionContext`, this is adequate for most cases unless some extra stuff is
|
||||
needed (You can find some examples where extra arguments are needed
|
||||
[here](https://github.com/topgrade-rs/topgrade/blob/7e48c5dedcfd5d0124bb9f39079a03e27ed23886/src/main.rs#L201-L219)).
|
||||
Such an update function would be conventionally named `run_xxx()`, where `xxx`
|
||||
is the name of the new step, and it should take an argument of type
|
||||
`&ExecutionContext`.
|
||||
|
||||
Update function would usually do 3 things:
|
||||
The update function should usually do 3 things:
|
||||
1. Check if the step is installed
|
||||
2. Output the Separator
|
||||
3. Invoke the step
|
||||
2. Output the separator
|
||||
3. Execute commands
|
||||
|
||||
Still, this is sufficient for most tools, but you may need some extra stuff
|
||||
with complicated `step`.
|
||||
This is sufficient for most tools, but you may need some extra stuff
|
||||
for complicated steps.
|
||||
|
||||
3. Add a match arm to `Step::run()`
|
||||
|
||||
@@ -74,7 +96,7 @@ To add a new `step` to `topgrade`:
|
||||
```
|
||||
|
||||
We use [conditional compilation](https://doc.rust-lang.org/reference/conditional-compilation.html)
|
||||
to separate the steps, for example, for steps that are Linux-only, it goes
|
||||
to separate the steps. For example, for steps that are Linux-only, it goes
|
||||
like this:
|
||||
|
||||
```rust
|
||||
@@ -89,11 +111,11 @@ To add a new `step` to `topgrade`:
|
||||
```rust
|
||||
steps.push(Xxx)
|
||||
```
|
||||
Try to keep the conditional compilation the same as in the above step 3.
|
||||
Keep the conditional compilation the same as in the above step 3.
|
||||
|
||||
Congrats, you just added a new `step` :)
|
||||
Congrats, you just added a new step :)
|
||||
|
||||
## Modification to the configuration entries
|
||||
### Modification to the configuration entries
|
||||
|
||||
If your PR has the configuration options
|
||||
(in [`src/config.rs`](https://github.com/topgrade-rs/topgrade/blob/main/src/config.rs))
|
||||
@@ -106,10 +128,10 @@ Be sure to apply your changes to
|
||||
[`config.example.toml`](https://github.com/topgrade-rs/topgrade/blob/main/config.example.toml),
|
||||
and have some basic documentations guiding user how to use these options.
|
||||
|
||||
## Breaking changes
|
||||
### Breaking changes
|
||||
|
||||
If your PR introduces a breaking change, document it in [`BREAKINGCHANGES_dev.md`][bc_dev],
|
||||
it should be written in Markdown and wrapped at 80, for example:
|
||||
If your PR introduces a breaking change, document it in [`BREAKINGCHANGES_dev.md`][bc_dev].
|
||||
It should be written in Markdown and wrapped at 80, for example:
|
||||
|
||||
```md
|
||||
1. The configuration location has been updated to x.
|
||||
@@ -121,25 +143,12 @@ it should be written in Markdown and wrapped at 80, for example:
|
||||
|
||||
[bc_dev]: https://github.com/topgrade-rs/topgrade/blob/main/BREAKINGCHANGES_dev.md
|
||||
|
||||
## Before you submit your PR
|
||||
|
||||
Make sure your patch passes the following tests on your host:
|
||||
|
||||
```shell
|
||||
$ cargo build
|
||||
$ cargo fmt
|
||||
$ cargo clippy
|
||||
$ cargo test
|
||||
```
|
||||
|
||||
Don't worry about other platforms, we have most of them covered in our CI.
|
||||
|
||||
## I18n
|
||||
### 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
|
||||
(Tip: LLMs are good at translation). If a message contains
|
||||
arguments, e.g., "hello <NAME>", please follow this convention:
|
||||
|
||||
```yml
|
||||
@@ -152,24 +161,22 @@ a preceding `%` when used in translations.
|
||||
|
||||
[app_yml]: https://github.com/topgrade-rs/topgrade/blob/main/locales/app.yml
|
||||
|
||||
## Some tips
|
||||
### Locales
|
||||
|
||||
1. Locale
|
||||
Some steps respect locale, which means their output can be in language other
|
||||
than English. In those cases, we cannot rely on the output of a command.
|
||||
|
||||
Some `step` respects locale, which means their output can be in language other
|
||||
than English, we should not do check on it.
|
||||
For example, one may want to check if a tool works by doing this:
|
||||
|
||||
For example, one may want to check if a tool works by doing this:
|
||||
```rust
|
||||
let output = Command::new("xxx").arg("--help").output().unwrap();
|
||||
let stdout = from_utf8(output.stdout).expect("Assume it is UTF-8 encoded");
|
||||
|
||||
```rust
|
||||
let output = Command::new("xxx").arg("--help").output().unwrap();
|
||||
let stdout = from_utf8(output.stdout).expect("Assume it is UTF-8 encoded");
|
||||
if stdout.contains("help") {
|
||||
// xxx works
|
||||
}
|
||||
```
|
||||
|
||||
if stdout.contains("help") {
|
||||
// xxx works
|
||||
}
|
||||
```
|
||||
|
||||
If `xxx` respects locale, then the above code should work on English system,
|
||||
on a system that does not use English, e.g., it uses Chinese, that `"help"` may be
|
||||
translated to `"帮助"`, and the above code won't work.
|
||||
If `xxx` respects locale, then the above code should work on English system,
|
||||
on a system that does not use English, e.g., it uses Chinese, that `"help"` may be
|
||||
translated to `"帮助"`, and the above code won't work.
|
||||
|
||||
2457
Cargo.lock
generated
2457
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
53
Cargo.toml
53
Cargo.toml
@@ -3,44 +3,35 @@ name = "topgrade"
|
||||
description = "Upgrade all the things"
|
||||
categories = ["os"]
|
||||
keywords = ["upgrade", "update"]
|
||||
license = "GPL-3.0"
|
||||
license = "GPL-3.0-or-later"
|
||||
repository = "https://github.com/topgrade-rs/topgrade"
|
||||
rust-version = "1.84.1"
|
||||
version = "16.1.0"
|
||||
rust-version = "1.87.0"
|
||||
version = "16.4.2"
|
||||
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"
|
||||
edition = "2024"
|
||||
|
||||
readme = "README.md"
|
||||
|
||||
[[bin]]
|
||||
name = "topgrade"
|
||||
path = "src/main.rs"
|
||||
|
||||
##[lib]
|
||||
##name = "topgrade_lib"
|
||||
|
||||
[dependencies]
|
||||
home = "~0.5"
|
||||
etcetera = "~0.8"
|
||||
etcetera = "~0.11.0"
|
||||
serde = { version = "~1.0", features = ["derive"] }
|
||||
toml = "0.8"
|
||||
which_crate = { version = "~6.0", package = "which" }
|
||||
toml = { version = "~0.9.8", features = ["preserve_order"] }
|
||||
which_crate = { version = "~8.0", package = "which" }
|
||||
shellexpand = "~3.1"
|
||||
clap = { version = "~4.5", features = ["cargo", "derive"] }
|
||||
clap_complete = "~4.5"
|
||||
clap_mangen = "~0.2"
|
||||
walkdir = "~2.5"
|
||||
console = "~0.15"
|
||||
console = "~0.16"
|
||||
chrono = "~0.4"
|
||||
glob = "~0.3"
|
||||
strum = { version = "~0.26", features = ["derive"] }
|
||||
thiserror = "~1.0"
|
||||
tempfile = "~3.10"
|
||||
cfg-if = "~1.0"
|
||||
tokio = { version = "~1.47", features = ["process", "rt-multi-thread"] }
|
||||
strum = { version = "~0.27", features = ["derive"] }
|
||||
thiserror = "~2.0"
|
||||
tempfile = "~3.23"
|
||||
tokio = { version = "~1.48", features = ["process", "rt-multi-thread"] }
|
||||
futures = "~0.3"
|
||||
regex = "~1.10"
|
||||
regex = "~1.12"
|
||||
semver = "~1.0"
|
||||
shell-words = "~1.1"
|
||||
color-eyre = "~0.6"
|
||||
@@ -54,6 +45,11 @@ rust-i18n = "3.0.1"
|
||||
sys-locale = "0.3.1"
|
||||
jetbrains-toolbox-updater = "5.0.0"
|
||||
indexmap = { version = "2.9.0", features = ["serde"] }
|
||||
serde_json = "1.0.145"
|
||||
clap-cargo = "0.18.0"
|
||||
|
||||
[patch.crates-io]
|
||||
mac-notification-sys = { git = "https://github.com/h4llow3En/mac-notification-sys" }
|
||||
|
||||
[package.metadata.generate-rpm]
|
||||
assets = [{ source = "target/release/topgrade", dest = "/usr/bin/topgrade" }]
|
||||
@@ -64,7 +60,7 @@ git = "*"
|
||||
[package.metadata.deb]
|
||||
name = "topgrade"
|
||||
maintainer = "Chris Gelatt <kreeblah@gmail.com>"
|
||||
copyright = "2024, Topgrade Team"
|
||||
copyright = "2025, Topgrade Team"
|
||||
license-file = ["LICENSE", "0"]
|
||||
depends = "$auto"
|
||||
extended-description = "Keeping your system up to date usually involves invoking multiple package managers. This results in big, non-portable shell one-liners saved in your shell. To remedy this, Topgrade detects which tools you use and runs the appropriate commands to update them."
|
||||
@@ -74,22 +70,23 @@ default-features = true
|
||||
assets = [
|
||||
["target/release/topgrade", "usr/bin/", "755"],
|
||||
["README.md", "usr/share/doc/topgrade/README.md", "644"],
|
||||
# The man page is automatically generated by topgrade's build process in CI, so
|
||||
# these files aren't actually committed.
|
||||
# The man page and shell completions are automatically generated by topgrade's build process in CI,
|
||||
# so these files aren't actually committed.
|
||||
["deployment/deb/topgrade.1", "usr/share/man/man1/topgrade.1", "644"],
|
||||
["deployment/deb/topgrade.bash", "usr/share/bash-completion/completions/topgrade", "644"],
|
||||
["deployment/deb/topgrade.fish", "usr/share/fish/vendor_completions.d/topgrade.fish", "644"],
|
||||
["deployment/deb/_topgrade", "usr/share/zsh/vendor-completions/", "644"],
|
||||
]
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
nix = { version = "~0.29", features = ["hostname", "signal", "user"] }
|
||||
nix = { version = "~0.30", features = ["hostname", "signal", "user"] }
|
||||
rust-ini = "~0.21"
|
||||
self_update_crate = { version = "~0.40", default-features = false, optional = true, package = "self_update", features = ["archive-tar", "compression-flate2", "rustls"] }
|
||||
self_update_crate = { version = "~0.42", default-features = false, optional = true, package = "self_update", features = ["archive-tar", "compression-flate2", "rustls"] }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
is_elevated = "~0.1"
|
||||
parselnk = "~0.1"
|
||||
self_update_crate = { version = "~0.40", default-features = false, optional = true, package = "self_update", features = ["archive-zip", "compression-zip-deflate", "rustls"] }
|
||||
self_update_crate = { version = "~0.42", default-features = false, optional = true, package = "self_update", features = ["archive-zip", "compression-zip-deflate", "rustls"] }
|
||||
windows = { version = "~0.62", features = ["Win32_System_Console"] }
|
||||
windows-registry = "~0.6"
|
||||
|
||||
|
||||
76
README.md
76
README.md
@@ -3,15 +3,14 @@
|
||||
<img alt="Topgrade" src="doc/topgrade_transparent.png" width="850px">
|
||||
</h1>
|
||||
|
||||
<a href="https://github.com/topgrade-rs/topgrade/releases"><img alt="GitHub Release" src="https://img.shields.io/github/release/topgrade-rs/topgrade.svg"></a>
|
||||
<a href="https://crates.io/crates/topgrade"><img alt="crates.io" src="https://img.shields.io/crates/v/topgrade.svg"></a>
|
||||
<a href="https://aur.archlinux.org/packages/topgrade"><img alt="AUR" src="https://img.shields.io/aur/version/topgrade.svg"></a>
|
||||
<a href="https://formulae.brew.sh/formula/topgrade"><img alt="Homebrew" src="https://img.shields.io/homebrew/v/topgrade.svg"></a>
|
||||
<a href="https://github.com/topgrade-rs/topgrade/releases"><img alt="GitHub Release" src="https://img.shields.io/github/release/topgrade-rs/topgrade.svg"></a>
|
||||
<a href="https://crates.io/crates/topgrade"><img alt="crates.io" src="https://img.shields.io/crates/v/topgrade.svg"></a>
|
||||
<a href="https://aur.archlinux.org/packages/topgrade"><img alt="AUR" src="https://img.shields.io/aur/version/topgrade.svg"></a>
|
||||
<a href="https://formulae.brew.sh/formula/topgrade"><img alt="Homebrew" src="https://img.shields.io/homebrew/v/topgrade.svg"></a>
|
||||
|
||||
<img alt="Demo" src="doc/topgrade_demo.gif">
|
||||
</div>
|
||||
|
||||
|
||||
## Introduction
|
||||
|
||||
> **Note**
|
||||
@@ -25,20 +24,32 @@ To remedy this, **Topgrade** detects which tools you use and runs the appropriat
|
||||
|
||||
[](https://repology.org/project/topgrade/versions)
|
||||
|
||||
- Arch Linux: [AUR](https://aur.archlinux.org/packages/topgrade)
|
||||
- NixOS: [Nixpkgs](https://search.nixos.org/packages?show=topgrade)
|
||||
- Void Linux: [XBPS](https://voidlinux.org/packages/?arch=x86_64&q=topgrade)
|
||||
- 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/)
|
||||
### Official
|
||||
|
||||
[choco]: https://community.chocolatey.org/packages/topgrade
|
||||
[scoop]: https://scoop.sh/#/apps?q=topgrade
|
||||
[winget]: https://winstall.app/apps/topgrade-rs.topgrade
|
||||
- Self-updating binary (all platforms): [releases](https://github.com/topgrade-rs/topgrade/releases)
|
||||
- Install from source (all platforms): [`cargo install topgrade`](https://crates.io/crates/topgrade)
|
||||
- Debian/Ubuntu ([deb-get](https://github.com/wimpysworld/deb-get)):
|
||||
[`deb-get install topgrade`](https://github.com/wimpysworld/deb-get/blob/main/01-main/packages/topgrade)
|
||||
- Arch Linux (AUR): [topgrade](https://aur.archlinux.org/packages/topgrade)
|
||||
or [topgrade-bin](https://aur.archlinux.org/packages/topgrade-bin)
|
||||
- [PyPi](https://pypi.org/): `pip`, `pipx`, or `uv tool` [
|
||||
`install topgrade`](https://pypi.org/project/topgrade/)
|
||||
- Windows ([Winget](https://learn.microsoft.com/en-us/windows/package-manager/winget/)): [
|
||||
`winget install --id=topgrade-rs.topgrade -e`](https://winstall.app/apps/topgrade-rs.topgrade)
|
||||
- macOS or Linux ([Homebrew](https://brew.sh/)): [`brew install topgrade`](https://formulae.brew.sh/formula/topgrade)
|
||||
- Fedora/RHEL/AlmaLinux/CentOS-Stream ([Copr](https://copr.fedorainfracloud.org/)): [
|
||||
`sudo dnf copr enable lilay/topgrade && sudo dnf install topgrade`](https://copr.fedorainfracloud.org/coprs/lilay/topgrade/)
|
||||
|
||||
Other systems users can either use `cargo install` or the compiled binaries from the release page.
|
||||
The compiled binaries contain a self-upgrading feature.
|
||||
### Community-maintained
|
||||
|
||||
- Windows ([Chocolatey](https://chocolatey.org/)): [
|
||||
`choco install topgrade`](https://community.chocolatey.org/packages/topgrade)
|
||||
- Windows ([Scoop](https://scoop.sh/)): [
|
||||
`scoop bucket add main && scoop install main/topgrade`](https://scoop.sh/#/apps?q=topgrade)
|
||||
- macOS ([MacPorts](https://www.macports.org/)): [
|
||||
`sudo port install topgrade`](https://ports.macports.org/port/topgrade/)
|
||||
- NixOS or Nix (nixpkgs): [topgrade](https://search.nixos.org/packages?show=topgrade)
|
||||
- Void Linux: [`sudo xbps-install -S topgrade`](https://voidlinux.org/packages/?arch=x86_64&q=topgrade)
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -59,6 +70,7 @@ it when updated to a major release.
|
||||
### Configuration Path
|
||||
|
||||
#### `CONFIG_DIR` on each platform
|
||||
|
||||
- **Windows**: `%APPDATA%`
|
||||
- **macOS** and **other Unix systems**: `${XDG_CONFIG_HOME:-~/.config}`
|
||||
|
||||
@@ -67,16 +79,21 @@ it when updated to a major release.
|
||||
1. `CONFIG_DIR/topgrade.toml`
|
||||
2. `CONFIG_DIR/topgrade/topgrade.toml`
|
||||
|
||||
If the file with higher priority is present, no matter it is valid or not, the other configuration files will be ignored.
|
||||
If the file with higher priority is present, no matter it is valid or not, the other configuration files will be
|
||||
ignored.
|
||||
|
||||
On the first run(no configuration file exists), `topgrade` will create a configuration file at `CONFIG_DIR/topgrade.toml` for you.
|
||||
On the first run(no configuration file exists), `topgrade` will create a configuration file at
|
||||
`CONFIG_DIR/topgrade.toml` for you.
|
||||
|
||||
### Custom Commands
|
||||
|
||||
Custom commands can be defined in the config file which can be run before, during, or after the inbuilt commands, as required.
|
||||
By default, the custom commands are run using a new shell according to the `$SHELL` environment variable on unix (falls back to `sh`) or `pwsh` on windows (falls back to `powershell`).
|
||||
Custom commands can be defined in the config file which can be run before, during, or after the inbuilt commands, as
|
||||
required.
|
||||
By default, the custom commands are run using a new shell according to the `$SHELL` environment variable on unix (falls
|
||||
back to `sh`) or `pwsh` on windows (falls back to `powershell`).
|
||||
|
||||
On unix, if you want to run your command using an interactive shell, for example to source your shell's rc files, you can add `-i` at the start of your custom command.
|
||||
On unix, if you want to run your command using an interactive shell, for example to source your shell's rc files, you
|
||||
can add `-i` at the start of your custom command.
|
||||
But note that this requires the command to exit the shell correctly or else the shell will hang indefinitely.
|
||||
|
||||
## Remote Execution
|
||||
@@ -95,22 +112,13 @@ Open a new issue describing your problem and if possible provide a solution.
|
||||
### Missing a feature or found an unsupported tool/distro?
|
||||
|
||||
Just let us now what you are missing by opening an issue.
|
||||
For tools, please open an issue describing the tool, which platforms it supports and if possible, give us an example of its usage.
|
||||
For tools, please open an issue describing the tool, which platforms it supports and if possible, give us an example of
|
||||
its usage.
|
||||
|
||||
### Want to contribute to the code?
|
||||
|
||||
Just fork the repository and start coding.
|
||||
|
||||
### Contribution Guidelines
|
||||
### Want to contribute?
|
||||
|
||||
See [CONTRIBUTING.md](https://github.com/topgrade-rs/topgrade/blob/master/CONTRIBUTING.md)
|
||||
|
||||
## Roadmap
|
||||
|
||||
- [ ] Add a proper testing framework to the code base.
|
||||
- [ ] Add unit tests for package managers.
|
||||
- [ ] Split up code into more maintainable parts, eg. putting every linux package manager in a own submodule of linux.rs.
|
||||
|
||||
## Discord server
|
||||
|
||||
Welcome to [join](https://discord.gg/Q8HGGWundY) our Discord server if you want
|
||||
|
||||
11
SECURITY.md
11
SECURITY.md
@@ -1,10 +1,9 @@
|
||||
# Security Policy
|
||||
|
||||
## Reporting a vulnerability
|
||||
|
||||
To report a security vulnerability, go to [the security tab](https://github.com/topgrade-rs/topgrade/security) and click "Report a vulnerability".
|
||||
|
||||
## Supported Versions
|
||||
|
||||
We only support the latest major version and each subversion.
|
||||
|
||||
| Version | Supported |
|
||||
| -------- | ------------------ |
|
||||
| 16.0.x | :white_check_mark: |
|
||||
| < 16.0 | :x: |
|
||||
We only support the latest version of Topgrade. Fixes are not backported.
|
||||
|
||||
@@ -91,6 +91,11 @@
|
||||
# See: https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives
|
||||
# log_filters = ["topgrade::command=debug", "warn"]
|
||||
|
||||
# Whether to show a distribution-specific summary if applicable, e.g. listing
|
||||
# Pacman backup configuration files (*.pacsave and *.pacnew)
|
||||
# (default: true)
|
||||
# show_distribution_summary = false
|
||||
|
||||
|
||||
# Commands to run before anything
|
||||
[pre_commands]
|
||||
@@ -223,6 +228,13 @@
|
||||
# enable = true
|
||||
|
||||
|
||||
[pkgfile]
|
||||
# Enable the pkgfile step (to update the pkgfile database).
|
||||
# Pkgfile is sometimes installed by default, but often not used and heavy to update.
|
||||
# (default: false)
|
||||
# enable = true
|
||||
|
||||
|
||||
[git]
|
||||
# How many repos to pull at max in parallel
|
||||
# max_concurrency = 5
|
||||
@@ -274,6 +286,25 @@
|
||||
# winget_use_sudo = true
|
||||
|
||||
|
||||
[chezmoi]
|
||||
# Exclude encrypted files from update
|
||||
# (default: false)
|
||||
# exclude_encrypted = false
|
||||
|
||||
[mise]
|
||||
# Upgrades to the latest version available, bumping the version in mise.toml
|
||||
# (default: false)
|
||||
# bump = false
|
||||
|
||||
# Number of jobs to run in parallel
|
||||
# (default: 4)
|
||||
# jobs = 4
|
||||
|
||||
# Run interactively
|
||||
# (default: false)
|
||||
# interactive = false
|
||||
|
||||
|
||||
[npm]
|
||||
# Use sudo if the NPM directory isn't owned by the current user
|
||||
# use_sudo = true
|
||||
@@ -325,6 +356,10 @@
|
||||
# Specify the runtime to use for containers (default: "docker", allowed values: "docker", "podman")
|
||||
# runtime = "podman"
|
||||
|
||||
# Run 'docker system prune' to clean up unused containers, networks, and build cache
|
||||
# (default: false)
|
||||
# system_prune = false
|
||||
|
||||
[lensfun]
|
||||
# If disabled, Topgrade invokes `lensfun‑update‑data` without root privilege,
|
||||
# then the update will be only available to you. Otherwise, `sudo` is required,
|
||||
@@ -376,3 +411,14 @@
|
||||
# during the pixi step
|
||||
# (default: false)
|
||||
# include_release_notes = false
|
||||
|
||||
[doom]
|
||||
# If this is set to true, the `--aot` flag is added to `doom upgrade`,
|
||||
# which enables ahead-of-time native compilation of packages.
|
||||
# (default: false)
|
||||
# aot = true
|
||||
|
||||
[rustup]
|
||||
# If set, updates only these channels.
|
||||
# (default: [] (all channels))
|
||||
# channels = ["stable"]
|
||||
|
||||
130
locales/app.yml
130
locales/app.yml
@@ -16,6 +16,22 @@ _version: 2
|
||||
zh_CN: "正在模拟 %{program_name} %{arguments} 的运行过程"
|
||||
zh_TW: "正在模擬 %{program_name} %{arguments} 的執行過程"
|
||||
de: "Testlauf: %{program_name} %{arguments}"
|
||||
"Executing: {program_name} {arguments}":
|
||||
en: "Executing: %{program_name} %{arguments}"
|
||||
lt: "Vykdymas: %{program_name} %{arguments}"
|
||||
es: "Ejecutando: %{program_name} %{arguments}"
|
||||
fr: "Exécution: %{program_name} %{arguments}"
|
||||
zh_CN: "执行: %{program_name} %{arguments}"
|
||||
zh_TW: "执行: %{program_name} %{arguments}"
|
||||
de: "Ausführung: %{program_name} %{arguments}"
|
||||
"with env: {env}":
|
||||
en: "with env: %{env}"
|
||||
lt: "su env: %{env}"
|
||||
es: "con env: %{env}"
|
||||
fr: "avec env: %{env}"
|
||||
zh_CN: "与env: %{env}"
|
||||
zh_TW: "與env: %{env}"
|
||||
de: "mit env: %{env}"
|
||||
"in {directory}":
|
||||
en: "in %{directory}"
|
||||
lt: "kataloge %{directory}"
|
||||
@@ -70,7 +86,7 @@ _version: 2
|
||||
es: "Cambiando al shell. Arregla lo que necesites y luego sal del shell."
|
||||
fr: "Ouverture d'un shell. Réparez ce dont vous avez besoin et quittez le shell."
|
||||
zh_CN: "已切换到 Shell 环境。请修复需要的内容,完成后退出 Shell 。"
|
||||
zh_TW: "已切換到終端殼層。修復完畢後請退出殼層以繼續。"
|
||||
zh_TW: "已切換到 shell 環境。修復完畢後請退出 shell 以繼續。"
|
||||
de: "Wechsel zur Shell. Beheben Sie die Probleme und verlassen Sie dann die Shell."
|
||||
"Topgrade launched in a new tmux session":
|
||||
en: "Topgrade launched in a new tmux session"
|
||||
@@ -78,7 +94,7 @@ _version: 2
|
||||
es: "Topgrade lanzado en una nueva sesión tmux"
|
||||
fr: "Topgrade lancé dans une nouvelle session tmux"
|
||||
zh_CN: "Topgrade 已在新的 tmux 会话中启动"
|
||||
zh_TW: "Topgrade 已啟動新 tmux 程序"
|
||||
zh_TW: "Topgrade 已啟動新 tmux 程式"
|
||||
de: "Topgrade in einer neuen tmux-Sitzung gestartet"
|
||||
"Topgrade upgraded to {version}:\n":
|
||||
en: "Topgrade upgraded to %{version}:\n"
|
||||
@@ -228,7 +244,7 @@ _version: 2
|
||||
es: "La auditoría del paquete fue exitosa, pero aún quedan paquetes vulnerables en el sistema"
|
||||
fr: "L'audit des paquets a réussi, mais des paquets vulnérables restent toujours sur le système"
|
||||
zh_CN: "软件包审计已通过,但系统中仍存在含漏洞的软件包"
|
||||
zh_TW: "雖然套件檢測成功,但系統仍然包含危險套件"
|
||||
zh_TW: "雖然套件審查成功,但系統仍然殘留危險套件"
|
||||
de: "Das Paket-Audit war erfolgreich, aber es befinden sich noch anfällige Pakete im System"
|
||||
"Syncing portage":
|
||||
en: "Syncing portage"
|
||||
@@ -244,7 +260,7 @@ _version: 2
|
||||
es: "Buscando software disponible"
|
||||
fr: "Recherche de logiciels disponible"
|
||||
zh_CN: "正在寻找可用的软件"
|
||||
zh_TW: "正在尋找軟體"
|
||||
zh_TW: "正在尋找可用的軟體"
|
||||
de: "Suche nach verfügbarer Software"
|
||||
"A system update is available. Do you wish to install it?":
|
||||
en: "A system update is available. Do you wish to install it?"
|
||||
@@ -252,14 +268,14 @@ _version: 2
|
||||
es: "Hay una actualización del sistema disponible. ¿Desea instalarla?"
|
||||
fr: "Une mise à jour du système est disponible. Voulez-vous l'installer ?"
|
||||
zh_CN: "系统更新可用。是否立即安装?"
|
||||
zh_TW: "系統更新已就緒。是否現在安裝?"
|
||||
zh_TW: "系統更新已就緒。是否立即安裝?"
|
||||
de: "Ein System-Update ist verfügbar. Möchten Sie es installieren?"
|
||||
"No new software available.":
|
||||
en: "No new software available."
|
||||
lt: "Nėra naujos programinės įrangos."
|
||||
es: "No hay ningún software nuevo disponible."
|
||||
fr: "Aucun nouveau logiciel disponible."
|
||||
zh_CN: "没有新的可用的软件包"
|
||||
zh_CN: "没有新的可用的软件包。"
|
||||
zh_TW: "沒有新軟體。"
|
||||
de: "Keine neue Software verfügbar."
|
||||
"No Xcode releases installed.":
|
||||
@@ -267,7 +283,7 @@ _version: 2
|
||||
lt: "Nėra įdiegtų Xcode versijų."
|
||||
es: "No hay versiones de Xcode instaladas."
|
||||
fr: "Aucune version de Xcode installée."
|
||||
zh_CN: "尚未安装 Xcode 发行版"
|
||||
zh_CN: "尚未安装 Xcode 发行版。"
|
||||
zh_TW: "尚未安裝 Xcode 發行版。"
|
||||
de: "Keine Xcode-Versionen installiert."
|
||||
"Would you like to move the former Xcode release to the trash?":
|
||||
@@ -292,7 +308,7 @@ _version: 2
|
||||
es: "¿Le gustaría instalarlo?"
|
||||
fr: "Voulez-vous l'installer ?"
|
||||
zh_CN: "是否立即安装?"
|
||||
zh_TW: "是否現在安裝?"
|
||||
zh_TW: "是否立即安裝?"
|
||||
de: "Möchten Sie sie installieren?"
|
||||
"No global packages installed":
|
||||
en: "No global packages installed"
|
||||
@@ -324,7 +340,7 @@ _version: 2
|
||||
es: "Recolectando cajas Vagrant"
|
||||
fr: "Collecte des boîtes Vagrant"
|
||||
zh_CN: "正在收集 Vagrant 容器"
|
||||
zh_TW: "正在蒐集 Vagrant 容器"
|
||||
zh_TW: "正在收集 Vagrant 容器"
|
||||
de: "Sammle Vagrant-Boxen"
|
||||
"No Vagrant directories were specified in the configuration file":
|
||||
en: "No Vagrant directories were specified in the configuration file"
|
||||
@@ -348,7 +364,7 @@ _version: 2
|
||||
es: "Sin cajas obsoletas"
|
||||
fr: "Aucune boîte obsolète"
|
||||
zh_CN: "没有需要更新的容器"
|
||||
zh_TW: "未有需要更新的容器"
|
||||
zh_TW: "沒有需要更新的容器"
|
||||
de: "Keine veralteten Boxen"
|
||||
"Summary":
|
||||
en: "Summary"
|
||||
@@ -382,14 +398,6 @@ _version: 2
|
||||
zh_CN: "Topgrade %{version_str} 破坏性更改"
|
||||
zh_TW: "Topgrade %{version_str} 重大更改"
|
||||
de: "Topgrade %{version_str} Inkompatible Änderungen"
|
||||
"Path {path} expanded to {expanded}":
|
||||
en: "Path %{path} expanded to %{expanded}"
|
||||
lt: "Kelias %{path} išplėstas į %{expanded}"
|
||||
es: "Ruta %{path} expandida a %{expanded}"
|
||||
fr: "Le chemin %{path} a été transformé en %{expanded}"
|
||||
zh_CN: "已扩展 %{path} 至 %{expanded}"
|
||||
zh_TW: "已擴展 %{path} 至 %{expanded}"
|
||||
de: "Pfad %{path} erweitert zu %{expanded}"
|
||||
"Path {path} doesn't exist":
|
||||
en: "Path %{path} doesn't exist"
|
||||
lt: "Kelias %{path} neegzistuoja"
|
||||
@@ -476,7 +484,7 @@ _version: 2
|
||||
es: "Auditoría de DragonFly BSD"
|
||||
fr: "Audit de DragonFly BSD"
|
||||
zh_CN: "DragonFly BSD 审计"
|
||||
zh_TW: "DragonFly BSD 紀錄"
|
||||
zh_TW: "DragonFly BSD 審查"
|
||||
de: "DragonFly BSD Audit"
|
||||
"FreeBSD Update":
|
||||
en: "FreeBSD Update"
|
||||
@@ -500,7 +508,7 @@ _version: 2
|
||||
es: "Auditoría FreeBSD"
|
||||
fr: "Audit de FreeBSD"
|
||||
zh_CN: "FreeBSD 审计"
|
||||
zh_TW: "FreeBSD 紀錄"
|
||||
zh_TW: "FreeBSD 審查"
|
||||
de: "FreeBSD Audit"
|
||||
"System update":
|
||||
en: "System update"
|
||||
@@ -532,7 +540,7 @@ _version: 2
|
||||
es: "No se debe ejecutar en WSL"
|
||||
fr: "Ne doit pas être exécuté dans WSL"
|
||||
zh_CN: "不应在 WSL 中运行"
|
||||
zh_TW: "不該在 WSL 中執行"
|
||||
zh_TW: "不應在 WSL 中執行"
|
||||
de: "Sollte nicht in WSL ausgeführt werden"
|
||||
"Firmware upgrades":
|
||||
en: "Firmware upgrades"
|
||||
@@ -556,7 +564,7 @@ _version: 2
|
||||
es: "El socket Snapd no existe"
|
||||
fr: "Le socket Snapd n'existe pas"
|
||||
zh_CN: "找不到 Snapd 程序"
|
||||
zh_TW: "找不到 Snapd 程序"
|
||||
zh_TW: "找不到 Snapd 程式"
|
||||
de: "Snapd-Socket existiert nicht"
|
||||
"You need to specify at least one container":
|
||||
en: "You need to specify at least one container"
|
||||
@@ -596,7 +604,7 @@ _version: 2
|
||||
es: "Omitiendo el paso de Waydroid debido a que el usuario no quiere continuar"
|
||||
fr: "Passer l'étape Waydroid car l'utilisateur ne souhaite pas l'exécuter"
|
||||
zh_CN: "使用者指定跳过 Waydroid 程序"
|
||||
zh_TW: "使用者指定略過 Waydroid 程序"
|
||||
zh_TW: "使用者指定略過 Waydroid 程式"
|
||||
de: "Überspringe den Waydroid-Schritt, da der Benutzer nicht fortfahren möchte"
|
||||
"macOS App Store":
|
||||
en: "macOS App Store"
|
||||
@@ -686,6 +694,14 @@ _version: 2
|
||||
zh_CN: "不是专用的 macOS brew"
|
||||
zh_TW: "不是專門的 macOS brew"
|
||||
de: "Kein angepasstes Brew für macOS"
|
||||
"Homebrew cask support on Linux requires Homebrew 4.5.0 or later (found {version})":
|
||||
en: "Homebrew cask support on Linux requires Homebrew 4.5.0 or later (found %{version})"
|
||||
lt: "Homebrew cask palaikymas Linux sistemoje reikalauja Homebrew 4.5.0 arba naujesnes versijos (rasta %{version})"
|
||||
es: "El soporte de cask de Homebrew en Linux requiere Homebrew 4.5.0 o posterior (encontrado %{version})"
|
||||
fr: "Le support de cask Homebrew sur Linux nécessite Homebrew 4.5.0 ou supérieur (trouvé %{version})"
|
||||
zh_CN: "Linux 上的 Homebrew cask 支持需要 Homebrew 4.5.0 或更高版本(找到 %{version})"
|
||||
zh_TW: "Linux 上的 Homebrew cask 支援需要 Homebrew 4.5.0 或更高版本(找到 %{version})"
|
||||
de: "Homebrew-Cask-Unterstützung unter Linux erfordert Homebrew 4.5.0 oder höher (gefunden %{version})"
|
||||
"Guix Pull Failed, Skipping":
|
||||
en: "Guix Pull Failed, Skipping"
|
||||
lt: "Guix traukti nepavyko, praleidžiama"
|
||||
@@ -942,13 +958,21 @@ _version: 2
|
||||
zh_CN: "拉取 %{repo}"
|
||||
zh_TW: "拉取 %{repo}"
|
||||
de: "Würde %{repo} abrufen"
|
||||
"No Neovim config found":
|
||||
en: "No Neovim config found"
|
||||
lt: "Neovim konfigūracija nerasta"
|
||||
es: "No se encontró ninguna configuración de Neovim"
|
||||
fr: "Aucune configuration Neovim trouvée"
|
||||
zh_CN: "未找到 Neovim 配置"
|
||||
zh_TW: "未找到 Neovim 設定"
|
||||
de: "Keine Neovim-Konfiguration gefunden"
|
||||
"Node Package Manager":
|
||||
en: "Node Package Manager"
|
||||
lt: "Node paketų tvarkyklė (npm)"
|
||||
es: "Node Package Manager (npm)"
|
||||
fr: "Gestionnaire de paquets Node (npm)"
|
||||
zh_CN: "Node Package Manager(npm)"
|
||||
zh_TW: "Node 套件管理員(npm)"
|
||||
zh_TW: "Node Package Manager(npm)"
|
||||
de: "Node Package Manager (npm)"
|
||||
"Performant Node Package Manager":
|
||||
en: "Performant Node Package Manager"
|
||||
@@ -956,7 +980,7 @@ _version: 2
|
||||
es: "Performant Node Package Manager (pnpm)"
|
||||
fr: "Gestionnaire de paquets Node performant (pnpm)"
|
||||
zh_CN: "Performant Node Package Manager (pnpm)"
|
||||
zh_TW: "效能 Node 套件管理員(pnpm)"
|
||||
zh_TW: "Performant Node Package Manager(pnpm)"
|
||||
de: "Performanter Node Package Manager (pnpm)"
|
||||
"Yarn Package Manager":
|
||||
en: "Yarn Package Manager"
|
||||
@@ -1012,7 +1036,7 @@ _version: 2
|
||||
es: "Distribución de Linux desconocida"
|
||||
fr: "Distribution Linux inconnue"
|
||||
zh_CN: "未知 Linux 发行版"
|
||||
zh_TW: "未知 Linux"
|
||||
zh_TW: "未知 Linux 發行版"
|
||||
de: "Unbekannte Linux-Distribution"
|
||||
'File "/etc/os-release" does not exist or is empty':
|
||||
en: 'File "/etc/os-release" does not exist or is empty'
|
||||
@@ -1046,14 +1070,6 @@ _version: 2
|
||||
zh_CN: "模拟运行"
|
||||
zh_TW: "模擬執行"
|
||||
de: "Testlauf"
|
||||
"Topgrade Upgraded":
|
||||
en: "Topgrade Upgraded"
|
||||
lt: "Topgrade atnaujintas"
|
||||
es: "Topgrade Actualizado"
|
||||
fr: "Topgrade mis à jour"
|
||||
zh_CN: "已升级 Topgrade"
|
||||
zh_TW: "已更新 Topgrade"
|
||||
de: "Topgrade aktualisiert"
|
||||
|
||||
# Summary texts
|
||||
"OK":
|
||||
@@ -1108,7 +1124,7 @@ _version: 2
|
||||
es: "¿Reintentar? (y) Si / (N) No / (s) Shell / (q) Salir"
|
||||
fr: "Réessayer ? (y) Oui / (N) Non / (s) Shell / (q) Quitter"
|
||||
zh_CN: "再试一次?(y)是/(N)否/(s)Shell/(q)退出"
|
||||
zh_TW: "再試一次? (y)是/(N)否/(s)殼層/(q)退出"
|
||||
zh_TW: "再試一次? (y)是/(N)否/(s)Shell/(q)退出"
|
||||
de: "Wiederholen? (y) Ja / (n) Nein / (s) Shell / (q) Beenden"
|
||||
|
||||
# 'R', 'S', 'Q' have to stay the same throughout all translations. Eg German would look like "\n(R) Neustarten\n(S) Konsole\n(Q) Beenden"
|
||||
@@ -1118,7 +1134,7 @@ _version: 2
|
||||
es: "\n(R) Reiniciar\n(S) Shell\n(Q) Salir"
|
||||
fr: "\n(R) Redémarrer\n(S) Shell\n(Q) Quitter"
|
||||
zh_CN: "\n(R)重启\n(S)Shell\n(Q)退出"
|
||||
zh_TW: "\n(R)重新啟動\n(S)殼層\n(Q)退出"
|
||||
zh_TW: "\n(R)重新啟動\n(S)shell\n(Q)退出"
|
||||
de: "\n(R) Neustarten\n(S)hell\n(Q)uit beenden"
|
||||
|
||||
"Continue?":
|
||||
@@ -1160,7 +1176,7 @@ _version: 2
|
||||
es: "\nAlgunos pasos se omitieron porque no se pudo encontrar sudo o equivalente."
|
||||
fr: "\nCertaines étapes ont été ignorées car sudo ou équivalent est introuvable."
|
||||
zh_CN: "\n由于未找到 sudo 或等效工具,某些步骤被跳过。"
|
||||
zh_TW: "\n由於找不到 sudo 或等效工具,某些步驟已跳過。"
|
||||
zh_TW: "\n某些步驟因為找不到 sudo 或同等的工具而被跳過。"
|
||||
de: "\nEinige Schritte wurden übersprungen, da sudo oder ein Äquivalent nicht gefunden wurde."
|
||||
"Install one of `sudo`, `doas`, `pkexec`, `run0` or `please` to run these steps.":
|
||||
en: "Install one of `sudo`, `doas`, `pkexec`, `run0` or `please` to run these steps."
|
||||
@@ -1168,7 +1184,7 @@ _version: 2
|
||||
es: "Instale uno de `sudo`, `doas`, `pkexec`, `run0` o `please` para ejecutar estos pasos."
|
||||
fr: "Installez l’un de `sudo`, `doas`, `pkexec`, `run0` ou `please` pour exécuter ces étapes."
|
||||
zh_CN: "请安装 `sudo`、`doas`、`pkexec`、`run0` 或 `please` 之一来运行这些步骤。"
|
||||
zh_TW: "請安裝 `sudo`、`doas`、`pkexec`、`run0` 或 `please` 之一來執行這些步驟。"
|
||||
zh_TW: "請安裝 `sudo`、`doas`、`pkexec`、`run0` 或 `please` 以執行這些步驟。"
|
||||
de: "Installieren Sie `sudo`, `doas`, `pkexec`, `run0` oder `please`, um diese Schritte auszuführen."
|
||||
"Install gsudo to run these steps.":
|
||||
en: "Install gsudo to run these steps."
|
||||
@@ -1232,7 +1248,7 @@ _version: 2
|
||||
es: "sudo como usuario '%{user}'"
|
||||
fr: "sudo en tant qu'utilisateur '%{user}'"
|
||||
zh_CN: "sudo 作为用户 '%{user}'"
|
||||
zh_TW: "sudo 以使用者 '%{user}'"
|
||||
zh_TW: "sudo 作為使用者 '%{user}'"
|
||||
de: "sudo als Benutzer '%{user}'"
|
||||
"Updating aqua ...":
|
||||
en: "Updating aqua ..."
|
||||
@@ -1279,7 +1295,7 @@ _version: 2
|
||||
lt: "Vėl paleidžiama..."
|
||||
es: "Reapareciendo..."
|
||||
fr: "Relancement..."
|
||||
zh_CN: "正在重新生成"
|
||||
zh_CN: "正在重新生成..."
|
||||
zh_TW: "正在重新生成..."
|
||||
de: "Neustarten..."
|
||||
"Could not find Topgrade in any WSL distribution":
|
||||
@@ -1288,7 +1304,7 @@ _version: 2
|
||||
es: "No se pudo encontrar Topgrade en ninguna distribución WSL"
|
||||
fr: "Impossible de trouver Topgrade installé dans WSL"
|
||||
zh_CN: "无法在任何 WSL 发行版中找到 Topgrade"
|
||||
zh_TW: "在所有 WSL 中找不到 Topgrade"
|
||||
zh_TW: "無法在任何 WSL 發行版中找到 Topgrade"
|
||||
de: "Konnte Topgrade in keiner WSL-Distribution finden"
|
||||
"Windows Update":
|
||||
en: "Windows Update"
|
||||
@@ -1298,30 +1314,6 @@ _version: 2
|
||||
zh_CN: "Windows 更新"
|
||||
zh_TW: "Windows 更新"
|
||||
de: "Windows-Update"
|
||||
"Would check if OpenBSD is -current":
|
||||
en: "Would check if OpenBSD is -current"
|
||||
lt: "Patikrintų, ar OpenBSD yra -current"
|
||||
es: "Comprobaría si OpenBSD está en -current"
|
||||
fr: "Vérifierait si OpenBSD est à -curent"
|
||||
zh_CN: "将检查 OpenBSD 是否为 -current"
|
||||
zh_TW: "會檢查 OpenBSD 是否為 -current"
|
||||
de: "Würde überprüfen, ob OpenBSD -current ist"
|
||||
"Would upgrade the OpenBSD system":
|
||||
en: "Would upgrade the OpenBSD system"
|
||||
lt: "Atnaujintų OpenBSD sistemą"
|
||||
es: "Actualizaría el sistema OpenBSD"
|
||||
fr: "Mettrait à jour le système OpenBSD"
|
||||
zh_CN: "将升级 OpenBSD 系统"
|
||||
zh_TW: "會升級 OpenBSD 系統"
|
||||
de: "Würde das OpenBSD-System aktualisieren"
|
||||
"Would upgrade OpenBSD packages":
|
||||
en: "Would upgrade OpenBSD packages"
|
||||
lt: "Atnaujintų OpenBSD paketus"
|
||||
es: "Actualizaría los paquetes de OpenBSD"
|
||||
fr: "Mettrait à jour les paquets OpenBSD"
|
||||
zh_CN: "将升级 OpenBSD 软件包"
|
||||
zh_TW: "會升級 OpenBSD 套件"
|
||||
de: "Würde OpenBSD-Pakete aktualisieren"
|
||||
"Microsoft Store":
|
||||
en: "Microsoft Store"
|
||||
lt: "Microsoft parduotuvė"
|
||||
@@ -1367,7 +1359,7 @@ _version: 2
|
||||
lt: "jetbrains-toolbox-updater susidūrė su netikėta klaida ieškant:"
|
||||
es: "jetbrains-toolbox-updater encontró un error inesperado durante la búsqueda:"
|
||||
fr: "jetbrains-toolbox-updater a rencontré une erreur inattendue lors de la recherche:"
|
||||
zh_CN: "jetbrains-toolbox-updater 在寻找过程中遇到意外错误"
|
||||
zh_CN: "jetbrains-toolbox-updater 在寻找过程中遇到意外错误:"
|
||||
zh_TW: "jetbrains-toolbox-updater 在尋找過程中遇到意外錯誤:"
|
||||
de: "jetbrains-toolbox-updater ist auf einen unerwarteten Fehler bei der Suche gestoßen:"
|
||||
"jetbrains-toolbox-updater encountered an unexpected error during updating:":
|
||||
@@ -1375,7 +1367,7 @@ _version: 2
|
||||
lt: "jetbrains-toolbox-updater susidūrė su netikėta klaida atnaujinant:"
|
||||
es: "jetbrains-toolbox-updater encontró un error inesperado durante la actualización:"
|
||||
fr: "jetbrains-toolbox-updater a rencontré une erreur inattendue lors de la mise à jour:"
|
||||
zh_CN: "jetbrains-toolbox-updater 在更新过程中遇到意外错误"
|
||||
zh_CN: "jetbrains-toolbox-updater 在更新过程中遇到意外错误:"
|
||||
zh_TW: "jetbrains-toolbox-updater 在更新過程中遇到意外錯誤:"
|
||||
de: "jetbrains-toolbox-updater ist auf einen unerwarteten Fehler während der Aktualisierung gestoßen:"
|
||||
"<output from `deb-get clean` omitted>":
|
||||
@@ -1391,16 +1383,16 @@ _version: 2
|
||||
lt: "Jūs turite flake viduje $FLAKE. Tai yra pasenę nh."
|
||||
es: "Tienes un flake dentro de $FLAKE. Esto está en desuso para nh."
|
||||
fr: "Vous avez un flake à l'intérieur de $FLAKE. Ceci est obsolète pour nh."
|
||||
zh_TW: "你在 $FLAKE 裡有一個 flake。這在 nh 中已被棄用。"
|
||||
zh_CN: "你在 $FLAKE 里有一个 flake。这在 nh 中已被弃用。"
|
||||
zh_TW: "你在 $FLAKE 裡有一個 flake。這在 nh 中已被棄用。"
|
||||
de: "Sie haben ein flake in $FLAKE. Dies ist für nh veraltet."
|
||||
"nh cannot find any configured flakes":
|
||||
en: "nh cannot find any configured flakes"
|
||||
lt: "nh nepavyksta rasti jokių sukonfigūruotų flake"
|
||||
es: "nh no puede encontrar ningún flake configurado"
|
||||
fr: "nh ne peut trouver aucun flake configuré"
|
||||
zh_TW: "nh 找不到任何已設定的 flake"
|
||||
zh_CN: "nh 无法找到任何已配置的 flake"
|
||||
zh_TW: "nh 找不到任何已設定的 flake"
|
||||
de: "nh kann keine konfigurierten flakes finden"
|
||||
"System Manuals":
|
||||
en: "System Manuals"
|
||||
|
||||
@@ -5,13 +5,20 @@ build-backend = "maturin"
|
||||
[project]
|
||||
name = "topgrade"
|
||||
dynamic = ["version"]
|
||||
description = "Upgrade all the things"
|
||||
readme = "README.md"
|
||||
license = "GPL-3.0-or-later"
|
||||
requires-python = ">=3.7"
|
||||
classifiers = [
|
||||
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
|
||||
"Programming Language :: Rust",
|
||||
"Programming Language :: Python :: Implementation :: CPython",
|
||||
"Programming Language :: Python :: Implementation :: PyPy",
|
||||
]
|
||||
|
||||
urls.bugs = "https://github.com/topgrade-rs/topgrade/issues"
|
||||
urls.homepage = "https://github.com/topgrade-rs/topgrade"
|
||||
|
||||
|
||||
[tool.maturin]
|
||||
bindings = "bin"
|
||||
|
||||
10
renovate.json
Normal file
10
renovate.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": [
|
||||
"config:best-practices",
|
||||
":semanticCommits"
|
||||
],
|
||||
"lockFileMaintenance": {
|
||||
"enabled": true
|
||||
}
|
||||
}
|
||||
@@ -1,2 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "1.84.1"
|
||||
channel = "1.87.0"
|
||||
|
||||
@@ -4,17 +4,17 @@
|
||||
//! 1. The Topgrade being executed is a new major release
|
||||
//! 2. This is the first launch of that major release
|
||||
|
||||
use crate::terminal::print_separator;
|
||||
#[cfg(windows)]
|
||||
use crate::WINDOWS_DIRS;
|
||||
#[cfg(unix)]
|
||||
use crate::XDG_DIRS;
|
||||
use crate::terminal::print_separator;
|
||||
use color_eyre::eyre::Result;
|
||||
use etcetera::base_strategy::BaseStrategy;
|
||||
use rust_i18n::t;
|
||||
use std::{
|
||||
env::var,
|
||||
fs::{read_to_string, OpenOptions},
|
||||
fs::{OpenOptions, read_to_string},
|
||||
io::Write,
|
||||
path::PathBuf,
|
||||
str::FromStr,
|
||||
|
||||
@@ -5,8 +5,8 @@ use std::process::Child;
|
||||
use std::process::{Command, ExitStatus, Output};
|
||||
|
||||
use color_eyre::eyre;
|
||||
use color_eyre::eyre::eyre;
|
||||
use color_eyre::eyre::Context;
|
||||
use color_eyre::eyre::eyre;
|
||||
|
||||
use crate::error::TopgradeError;
|
||||
|
||||
@@ -115,6 +115,9 @@ pub trait CommandExt {
|
||||
///
|
||||
/// Returns an `Err` if the command failed to execute, if `succeeded` returns an `Err`, or if
|
||||
/// the output contains invalid UTF-8.
|
||||
// This function is currently unused, but is useful and makes sense with `output_checked_with`
|
||||
// and `output_checked_utf8` existing.
|
||||
#[allow(dead_code)]
|
||||
#[track_caller]
|
||||
fn output_checked_with_utf8(
|
||||
&mut self,
|
||||
|
||||
202
src/config.rs
202
src/config.rs
@@ -1,6 +1,6 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
use std::fs::{write, File};
|
||||
use std::fs::{File, write};
|
||||
use std::io::Write;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
@@ -15,17 +15,17 @@ use indexmap::IndexMap;
|
||||
use merge::Merge;
|
||||
use regex::Regex;
|
||||
use regex_split::RegexSplit;
|
||||
use rust_i18n::t;
|
||||
use serde::Deserialize;
|
||||
use strum::IntoEnumIterator;
|
||||
use tracing::{debug, error};
|
||||
use which_crate::which;
|
||||
|
||||
use super::utils::editor;
|
||||
use crate::command::CommandExt;
|
||||
use crate::execution_context::RunType;
|
||||
use crate::step::Step;
|
||||
use crate::sudo::SudoKind;
|
||||
use crate::utils::string_prepend_str;
|
||||
use tracing::{debug, error};
|
||||
|
||||
// TODO: Add i18n to this. Tracking issue: https://github.com/topgrade-rs/topgrade/issues/859
|
||||
pub static EXAMPLE_CONFIG: &str = include_str!("../config.example.toml");
|
||||
@@ -60,6 +60,7 @@ pub struct Containers {
|
||||
#[merge(strategy = crate::utils::merge_strategies::vec_prepend_opt)]
|
||||
ignored_containers: Option<Vec<String>>,
|
||||
runtime: Option<ContainerRuntime>,
|
||||
system_prune: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Default, Debug, Merge)]
|
||||
@@ -165,6 +166,22 @@ pub struct Deno {
|
||||
version: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Default, Debug, Merge)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
pub struct Chezmoi {
|
||||
exclude_encrypted: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Default, Debug, Merge)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
pub struct Mise {
|
||||
bump: Option<bool>,
|
||||
interactive: Option<bool>,
|
||||
jobs: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Default, Debug, Merge)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
@@ -196,9 +213,10 @@ pub struct Brew {
|
||||
fetch_head: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone, Copy)]
|
||||
#[derive(Debug, Deserialize, Clone, Copy, Default)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ArchPackageManager {
|
||||
#[default]
|
||||
Autodetect,
|
||||
Aura,
|
||||
GarudaUpdate,
|
||||
@@ -210,9 +228,10 @@ pub enum ArchPackageManager {
|
||||
Yay,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize)]
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Default)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ContainerRuntime {
|
||||
#[default] // defaults to a popular choice
|
||||
Docker,
|
||||
Podman,
|
||||
}
|
||||
@@ -346,12 +365,15 @@ pub struct Misc {
|
||||
no_self_update: Option<bool>,
|
||||
|
||||
log_filters: Option<Vec<String>>,
|
||||
|
||||
show_distribution_summary: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, ValueEnum)]
|
||||
#[derive(Clone, Copy, Debug, Deserialize, ValueEnum, Default)]
|
||||
#[clap(rename_all = "snake_case")]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum TmuxSessionMode {
|
||||
#[default]
|
||||
AttachIfNotInSession,
|
||||
AttachAlways,
|
||||
}
|
||||
@@ -388,6 +410,24 @@ pub struct VscodeConfig {
|
||||
profile: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Default, Debug, Merge)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct DoomConfig {
|
||||
aot: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Default, Debug, Merge)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Rustup {
|
||||
channels: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Default, Debug, Merge)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Pkgfile {
|
||||
enable: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Default, Debug, Merge)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
/// Configuration file
|
||||
@@ -437,6 +477,12 @@ pub struct ConfigFile {
|
||||
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
|
||||
npm: Option<NPM>,
|
||||
|
||||
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
|
||||
chezmoi: Option<Chezmoi>,
|
||||
|
||||
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
|
||||
mise: Option<Mise>,
|
||||
|
||||
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
|
||||
yarn: Option<Yarn>,
|
||||
|
||||
@@ -472,6 +518,15 @@ pub struct ConfigFile {
|
||||
|
||||
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
|
||||
vscode: Option<VscodeConfig>,
|
||||
|
||||
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
|
||||
doom: Option<DoomConfig>,
|
||||
|
||||
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
|
||||
rustup: Option<Rustup>,
|
||||
|
||||
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
|
||||
pkgfile: Option<Pkgfile>,
|
||||
}
|
||||
|
||||
fn config_directory() -> PathBuf {
|
||||
@@ -638,17 +693,6 @@ impl ConfigFile {
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(paths) = result.git.as_mut().and_then(|git| git.repos.as_mut()) {
|
||||
for path in paths.iter_mut() {
|
||||
let expanded = shellexpand::tilde::<&str>(&path.as_ref()).into_owned();
|
||||
debug!(
|
||||
"{}",
|
||||
t!("Path {path} expanded to {expanded}", path = path, expanded = expanded)
|
||||
);
|
||||
*path = expanded;
|
||||
}
|
||||
}
|
||||
|
||||
debug!("Loaded configuration: {:?}", result);
|
||||
Ok(result)
|
||||
}
|
||||
@@ -685,7 +729,7 @@ impl ConfigFile {
|
||||
// TODO: i18n of clap currently not easily possible. Waiting for https://github.com/clap-rs/clap/issues/380
|
||||
// Tracking issue for i18n: https://github.com/topgrade-rs/topgrade/issues/859
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(name = "topgrade", version)]
|
||||
#[command(name = "topgrade", version, styles = clap_cargo::style::CLAP_STYLING)]
|
||||
pub struct CommandLineArgs {
|
||||
/// Edit the configuration file
|
||||
#[arg(long = "edit-config")]
|
||||
@@ -708,9 +752,15 @@ pub struct CommandLineArgs {
|
||||
cleanup: bool,
|
||||
|
||||
/// Print what would be done
|
||||
///
|
||||
/// Alias for --run-type dry
|
||||
#[arg(short = 'n', long = "dry-run")]
|
||||
dry_run: bool,
|
||||
|
||||
/// Pick between just running commands, running and logging commands, and just logging commands
|
||||
#[arg(short = 'r', long = "run-type", value_enum, default_value_t)]
|
||||
run_type: RunType,
|
||||
|
||||
/// Do not ask to retry failed steps
|
||||
#[arg(long = "no-retry")]
|
||||
no_retry: bool,
|
||||
@@ -921,7 +971,16 @@ impl Config {
|
||||
.containers
|
||||
.as_ref()
|
||||
.and_then(|containers| containers.runtime)
|
||||
.unwrap_or(ContainerRuntime::Docker) // defaults to a popular choice
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Whether to run system prune for containers.
|
||||
pub fn containers_system_prune(&self) -> bool {
|
||||
self.config_file
|
||||
.containers
|
||||
.as_ref()
|
||||
.and_then(|containers| containers.system_prune)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Tell whether the specified step should run.
|
||||
@@ -933,16 +992,21 @@ impl Config {
|
||||
}
|
||||
|
||||
fn allowed_steps(opt: &CommandLineArgs, config_file: &ConfigFile) -> Vec<Step> {
|
||||
// The enabled steps are
|
||||
let mut enabled_steps: Vec<Step> = Vec::new();
|
||||
// Any steps that are passed with `--only`
|
||||
enabled_steps.extend(&opt.only);
|
||||
|
||||
// Plus any steps in the config file's `misc.only`
|
||||
if let Some(misc) = config_file.misc.as_ref() {
|
||||
if let Some(only) = misc.only.as_ref() {
|
||||
enabled_steps.extend(only);
|
||||
}
|
||||
}
|
||||
|
||||
// If neither of those contain anything
|
||||
if enabled_steps.is_empty() {
|
||||
// All steps are enabled
|
||||
enabled_steps.extend(Step::iter());
|
||||
}
|
||||
|
||||
@@ -954,6 +1018,7 @@ impl Config {
|
||||
}
|
||||
}
|
||||
|
||||
// All steps that are disabled are not enabled, except ones that are passed to `--only`
|
||||
enabled_steps.retain(|e| !disabled_steps.contains(e) || opt.only.contains(e));
|
||||
enabled_steps
|
||||
}
|
||||
@@ -987,7 +1052,7 @@ impl Config {
|
||||
.misc
|
||||
.as_ref()
|
||||
.and_then(|misc| misc.tmux_session_mode)
|
||||
.unwrap_or(TmuxSessionMode::AttachIfNotInSession)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Tell whether we should perform cleanup steps.
|
||||
@@ -1001,9 +1066,13 @@ impl Config {
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Tell whether we are dry-running.
|
||||
pub fn dry_run(&self) -> bool {
|
||||
self.opt.dry_run
|
||||
/// Get the [RunType] for the current execution
|
||||
pub fn run_type(&self) -> RunType {
|
||||
if self.opt.dry_run {
|
||||
RunType::Dry
|
||||
} else {
|
||||
self.opt.run_type
|
||||
}
|
||||
}
|
||||
|
||||
/// Tell whether we should not attempt to retry anything.
|
||||
@@ -1237,7 +1306,7 @@ impl Config {
|
||||
.vim
|
||||
.as_ref()
|
||||
.and_then(|c| c.force_plug_update)
|
||||
.unwrap_or_default()
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Whether to send a desktop notification at the beginning of every step
|
||||
@@ -1310,7 +1379,7 @@ impl Config {
|
||||
.linux
|
||||
.as_ref()
|
||||
.and_then(|s| s.arch_package_manager)
|
||||
.unwrap_or(ArchPackageManager::Autodetect)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Extra yay arguments
|
||||
@@ -1482,6 +1551,14 @@ impl Config {
|
||||
.unwrap_or(true)
|
||||
}
|
||||
|
||||
pub fn rustup_channels(&self) -> Vec<String> {
|
||||
self.config_file
|
||||
.rustup
|
||||
.as_ref()
|
||||
.and_then(|rustup| rustup.channels.clone())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn verbose(&self) -> bool {
|
||||
self.opt.verbose
|
||||
}
|
||||
@@ -1555,8 +1632,7 @@ impl Config {
|
||||
self.config_file.misc.as_ref().and_then(|misc| misc.sudo_command)
|
||||
}
|
||||
|
||||
/// If `true`, `sudo` should be called after `pre_commands` in order to elevate at the
|
||||
/// start of the session (and not in the middle).
|
||||
/// If `true`, `sudo -v` should be called to cache credentials at the start of the run
|
||||
pub fn pre_sudo(&self) -> bool {
|
||||
self.config_file
|
||||
.misc
|
||||
@@ -1565,6 +1641,14 @@ impl Config {
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn show_distribution_summary(&self) -> bool {
|
||||
self.config_file
|
||||
.misc
|
||||
.as_ref()
|
||||
.and_then(|misc| misc.show_distribution_summary)
|
||||
.unwrap_or(true)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn npm_use_sudo(&self) -> bool {
|
||||
self.config_file
|
||||
@@ -1724,6 +1808,34 @@ impl Config {
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn chezmoi_exclude_encrypted(&self) -> bool {
|
||||
self.config_file
|
||||
.chezmoi
|
||||
.as_ref()
|
||||
.and_then(|chezmoi| chezmoi.exclude_encrypted)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn mise_bump(&self) -> bool {
|
||||
self.config_file
|
||||
.mise
|
||||
.as_ref()
|
||||
.and_then(|mise| mise.bump)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn mise_jobs(&self) -> u32 {
|
||||
self.config_file.mise.as_ref().and_then(|mise| mise.jobs).unwrap_or(4)
|
||||
}
|
||||
|
||||
pub fn mise_interactive(&self) -> bool {
|
||||
self.config_file
|
||||
.mise
|
||||
.as_ref()
|
||||
.and_then(|mise| mise.interactive)
|
||||
.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()?;
|
||||
@@ -1734,6 +1846,22 @@ impl Config {
|
||||
Some(profile.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn doom_aot(&self) -> bool {
|
||||
self.config_file
|
||||
.doom
|
||||
.as_ref()
|
||||
.and_then(|doom| doom.aot)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn enable_pkgfile(&self) -> bool {
|
||||
self.config_file
|
||||
.pkgfile
|
||||
.as_ref()
|
||||
.and_then(|pkgfile| pkgfile.enable)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -1796,4 +1924,24 @@ mod test {
|
||||
config.opt = CommandLineArgs::parse_from(["topgrade", "--remote-host-limit", "other_hostname"]);
|
||||
assert!(!config.should_execute_remote(Ok("hostname".to_string()), "user@remote_hostname"));
|
||||
}
|
||||
|
||||
/// Ensure that custom commands are stored in insertion order.
|
||||
#[test]
|
||||
fn test_custom_commands_order() {
|
||||
let toml_str = r#"
|
||||
[commands]
|
||||
z = "cmd_z"
|
||||
y = "cmd_y"
|
||||
x = "cmd_x"
|
||||
"#;
|
||||
let order: Vec<_> = toml::from_str::<ConfigFile>(toml_str)
|
||||
.expect("toml parse error")
|
||||
.commands
|
||||
.expect("commands field missing")
|
||||
.keys()
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
assert_eq!(order, vec!["z", "y", "x"]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! SIGINT handling in Unix systems.
|
||||
use crate::ctrlc::interrupted::set_interrupted;
|
||||
use nix::sys::signal::{sigaction, SaFlags, SigAction, SigHandler, SigSet, Signal};
|
||||
use nix::sys::signal::{SaFlags, SigAction, SigHandler, SigSet, Signal, sigaction};
|
||||
|
||||
/// Handle SIGINT. Set the interruption flag.
|
||||
extern "C" fn handle_sigint(_: i32) {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
//! A stub for Ctrl + C handling.
|
||||
use crate::ctrlc::interrupted::set_interrupted;
|
||||
use tracing::error;
|
||||
use windows::Win32::System::Console::{CTRL_C_EVENT, SetConsoleCtrlHandler};
|
||||
use windows::core::BOOL;
|
||||
use windows::Win32::System::Console::{SetConsoleCtrlHandler, CTRL_C_EVENT};
|
||||
|
||||
extern "system" fn handler(ctrl_type: u32) -> BOOL {
|
||||
match ctrl_type {
|
||||
|
||||
11
src/error.rs
11
src/error.rs
@@ -116,14 +116,3 @@ impl Display for SkipStep {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(windows, feature = "self-update"))]
|
||||
#[derive(Error, Debug)]
|
||||
pub struct Upgraded(pub ExitStatus);
|
||||
|
||||
#[cfg(all(windows, feature = "self-update"))]
|
||||
impl Display for Upgraded {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", t!("Topgrade Upgraded"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
#![allow(dead_code)]
|
||||
use color_eyre::eyre::Result;
|
||||
use rust_i18n::t;
|
||||
use std::env::var;
|
||||
use std::ffi::OsStr;
|
||||
use std::process::Command;
|
||||
use std::sync::{LazyLock, Mutex};
|
||||
|
||||
use clap::ValueEnum;
|
||||
use color_eyre::eyre::Result;
|
||||
use rust_i18n::t;
|
||||
use serde::Deserialize;
|
||||
use strum::EnumString;
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::error::MissingSudo;
|
||||
use crate::executor::{DryCommand, Executor};
|
||||
@@ -16,30 +20,26 @@ use crate::sudo::Sudo;
|
||||
use crate::utils::require_option;
|
||||
|
||||
/// An enum telling whether Topgrade should perform dry runs or actually perform the steps.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Default, EnumString, ValueEnum)]
|
||||
pub enum RunType {
|
||||
/// Executing commands will just print the command with its argument.
|
||||
Dry,
|
||||
|
||||
/// Executing commands will perform actual execution.
|
||||
#[default]
|
||||
Wet,
|
||||
|
||||
/// Executing commands will print the command and perform actual execution.
|
||||
Damp,
|
||||
}
|
||||
|
||||
impl RunType {
|
||||
/// Create a new instance from a boolean telling whether to dry run.
|
||||
pub fn new(dry_run: bool) -> Self {
|
||||
if dry_run {
|
||||
RunType::Dry
|
||||
} else {
|
||||
RunType::Wet
|
||||
}
|
||||
}
|
||||
|
||||
/// Tells whether we're performing a dry run.
|
||||
pub fn dry(self) -> bool {
|
||||
match self {
|
||||
RunType::Dry => true,
|
||||
RunType::Wet => false,
|
||||
RunType::Damp => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -84,6 +84,7 @@ impl<'a> ExecutionContext<'a> {
|
||||
match self.run_type {
|
||||
RunType::Dry => Executor::Dry(DryCommand::new(program)),
|
||||
RunType::Wet => Executor::Wet(Command::new(program)),
|
||||
RunType::Damp => Executor::Damp(Command::new(program)),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
137
src/executor.rs
137
src/executor.rs
@@ -1,11 +1,13 @@
|
||||
//! Utilities for command execution
|
||||
use std::ffi::{OsStr, OsString};
|
||||
use std::fmt::Debug;
|
||||
use std::iter;
|
||||
use std::path::Path;
|
||||
use std::process::{Child, Command, ExitStatus, Output};
|
||||
|
||||
use color_eyre::eyre::Result;
|
||||
use rust_i18n::t;
|
||||
use tracing::debug;
|
||||
use tracing::{Level, debug, enabled};
|
||||
|
||||
use crate::command::CommandExt;
|
||||
use crate::error::DryRun;
|
||||
@@ -15,6 +17,7 @@ use crate::error::DryRun;
|
||||
/// If the enum is set to `Dry`, execution will just print the command with its arguments.
|
||||
pub enum Executor {
|
||||
Wet(Command),
|
||||
Damp(Command),
|
||||
Dry(DryCommand),
|
||||
}
|
||||
|
||||
@@ -24,7 +27,7 @@ impl Executor {
|
||||
/// Will give weird results for non-UTF-8 programs; see `to_string_lossy()`.
|
||||
pub fn get_program(&self) -> String {
|
||||
match self {
|
||||
Executor::Wet(c) => c.get_program().to_string_lossy().into_owned(),
|
||||
Executor::Wet(c) | Executor::Damp(c) => c.get_program().to_string_lossy().into_owned(),
|
||||
Executor::Dry(c) => c.program.to_string_lossy().into_owned(),
|
||||
}
|
||||
}
|
||||
@@ -32,7 +35,7 @@ impl Executor {
|
||||
/// See `std::process::Command::arg`
|
||||
pub fn arg<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut Executor {
|
||||
match self {
|
||||
Executor::Wet(c) => {
|
||||
Executor::Wet(c) | Executor::Damp(c) => {
|
||||
c.arg(arg);
|
||||
}
|
||||
Executor::Dry(c) => {
|
||||
@@ -50,7 +53,7 @@ impl Executor {
|
||||
S: AsRef<OsStr>,
|
||||
{
|
||||
match self {
|
||||
Executor::Wet(c) => {
|
||||
Executor::Wet(c) | Executor::Damp(c) => {
|
||||
c.args(args);
|
||||
}
|
||||
Executor::Dry(c) => {
|
||||
@@ -65,7 +68,7 @@ impl Executor {
|
||||
/// See `std::process::Command::current_dir`
|
||||
pub fn current_dir<P: AsRef<Path>>(&mut self, dir: P) -> &mut Executor {
|
||||
match self {
|
||||
Executor::Wet(c) => {
|
||||
Executor::Wet(c) | Executor::Damp(c) => {
|
||||
c.current_dir(dir);
|
||||
}
|
||||
Executor::Dry(c) => c.directory = Some(dir.as_ref().into()),
|
||||
@@ -81,7 +84,7 @@ impl Executor {
|
||||
K: AsRef<OsStr>,
|
||||
{
|
||||
match self {
|
||||
Executor::Wet(c) => {
|
||||
Executor::Wet(c) | Executor::Damp(c) => {
|
||||
c.env_remove(key);
|
||||
}
|
||||
Executor::Dry(_) => (),
|
||||
@@ -98,7 +101,7 @@ impl Executor {
|
||||
V: AsRef<OsStr>,
|
||||
{
|
||||
match self {
|
||||
Executor::Wet(c) => {
|
||||
Executor::Wet(c) | Executor::Damp(c) => {
|
||||
c.env(key, val);
|
||||
}
|
||||
Executor::Dry(_) => (),
|
||||
@@ -109,18 +112,16 @@ impl Executor {
|
||||
|
||||
/// See `std::process::Command::spawn`
|
||||
pub fn spawn(&mut self) -> Result<ExecutorChild> {
|
||||
self.log_command();
|
||||
let result = match self {
|
||||
Executor::Wet(c) => {
|
||||
Executor::Wet(c) | Executor::Damp(c) => {
|
||||
debug!("Running {:?}", c);
|
||||
// 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();
|
||||
ExecutorChild::Dry
|
||||
}
|
||||
Executor::Dry(_) => ExecutorChild::Dry,
|
||||
};
|
||||
|
||||
Ok(result)
|
||||
@@ -128,17 +129,15 @@ impl Executor {
|
||||
|
||||
/// See `std::process::Command::output`
|
||||
pub fn output(&mut self) -> Result<ExecutorOutput> {
|
||||
self.log_command();
|
||||
match self {
|
||||
Executor::Wet(c) => {
|
||||
Executor::Wet(c) | Executor::Damp(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)
|
||||
}
|
||||
Executor::Dry(_) => Ok(ExecutorOutput::Dry),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,18 +145,38 @@ impl Executor {
|
||||
/// that can indicate success of a script
|
||||
#[allow(dead_code)]
|
||||
pub fn status_checked_with_codes(&mut self, codes: &[i32]) -> Result<()> {
|
||||
self.log_command();
|
||||
match self {
|
||||
Executor::Wet(c) => c.status_checked_with(|status| {
|
||||
Executor::Wet(c) | Executor::Damp(c) => c.status_checked_with(|status| {
|
||||
if status.success() || status.code().as_ref().is_some_and(|c| codes.contains(c)) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}),
|
||||
Executor::Dry(c) => {
|
||||
c.dry_run();
|
||||
Ok(())
|
||||
Executor::Dry(_) => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
fn log_command(&self) {
|
||||
match self {
|
||||
Executor::Wet(_) => (),
|
||||
Executor::Damp(c) => {
|
||||
log_command(
|
||||
"Executing: {program_name} {arguments}",
|
||||
c.get_program(),
|
||||
c.get_args(),
|
||||
c.get_envs(),
|
||||
c.get_current_dir(),
|
||||
);
|
||||
}
|
||||
Executor::Dry(c) => log_command(
|
||||
"Dry running: {program_name} {arguments}",
|
||||
&c.program,
|
||||
&c.args,
|
||||
iter::empty(),
|
||||
c.directory.as_ref(),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -182,30 +201,11 @@ impl DryCommand {
|
||||
directory: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn dry_run(&self) {
|
||||
print!(
|
||||
"{}",
|
||||
t!(
|
||||
"Dry running: {program_name} {arguments}",
|
||||
program_name = self.program.to_string_lossy(),
|
||||
arguments = shell_words::join(
|
||||
self.args
|
||||
.iter()
|
||||
.map(|a| String::from(a.to_string_lossy()))
|
||||
.collect::<Vec<String>>()
|
||||
)
|
||||
)
|
||||
);
|
||||
match &self.directory {
|
||||
Some(dir) => println!(" {}", t!("in {directory}", directory = dir.to_string_lossy())),
|
||||
None => println!(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// The Result of spawn. Contains an actual `std::process::Child` if executed by a wet command.
|
||||
pub enum ExecutorChild {
|
||||
// Both RunType::Wet and RunType::Damp use this variant
|
||||
#[allow(unused)] // this type has not been used
|
||||
Wet(Child),
|
||||
Dry,
|
||||
@@ -218,22 +218,18 @@ impl CommandExt for Executor {
|
||||
// variant for wet/dry runs.
|
||||
|
||||
fn output_checked_with(&mut self, succeeded: impl Fn(&Output) -> Result<(), ()>) -> Result<Output> {
|
||||
self.log_command();
|
||||
match self {
|
||||
Executor::Wet(c) => c.output_checked_with(succeeded),
|
||||
Executor::Dry(c) => {
|
||||
c.dry_run();
|
||||
Err(DryRun().into())
|
||||
}
|
||||
Executor::Wet(c) | Executor::Damp(c) => c.output_checked_with(succeeded),
|
||||
Executor::Dry(_) => Err(DryRun().into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn status_checked_with(&mut self, succeeded: impl Fn(ExitStatus) -> Result<(), ()>) -> Result<()> {
|
||||
self.log_command();
|
||||
match self {
|
||||
Executor::Wet(c) => c.status_checked_with(succeeded),
|
||||
Executor::Dry(c) => {
|
||||
c.dry_run();
|
||||
Ok(())
|
||||
}
|
||||
Executor::Wet(c) | Executor::Damp(c) => c.status_checked_with(succeeded),
|
||||
Executor::Dry(_) => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,3 +237,42 @@ impl CommandExt for Executor {
|
||||
self.spawn()
|
||||
}
|
||||
}
|
||||
|
||||
fn log_command<
|
||||
'a,
|
||||
I: ExactSizeIterator<Item = (&'a (impl Debug + 'a + ?Sized), Option<&'a (impl Debug + 'a + ?Sized)>)>,
|
||||
>(
|
||||
prefix: &str,
|
||||
exec: &OsStr,
|
||||
args: impl IntoIterator<Item = &'a (impl AsRef<OsStr> + ?Sized + 'a)>,
|
||||
env: impl IntoIterator<Item = (&'a OsStr, Option<&'a OsStr>), IntoIter = I>,
|
||||
dir: Option<&'a (impl AsRef<Path> + ?Sized)>,
|
||||
) {
|
||||
println!(
|
||||
"{}",
|
||||
t!(
|
||||
prefix,
|
||||
program_name = exec.to_string_lossy(),
|
||||
arguments = shell_words::join(args.into_iter().map(|s| s.as_ref().to_string_lossy()))
|
||||
)
|
||||
);
|
||||
|
||||
let env_iter = env.into_iter();
|
||||
if env_iter.len() != 0 && enabled!(Level::DEBUG) {
|
||||
println!(
|
||||
" {}",
|
||||
t!(
|
||||
"with env: {env}",
|
||||
env = env_iter
|
||||
.filter(|(_, val)| val.is_some())
|
||||
.map(|(key, val)| format!("{:?}={:?}", key, val.unwrap()))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ")
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
if let Some(d) = dir {
|
||||
println!(" {}", t!("in {directory}", directory = d.as_ref().display()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
//pub mod steps;
|
||||
//pub mod utils;
|
||||
67
src/main.rs
67
src/main.rs
@@ -1,6 +1,7 @@
|
||||
#![allow(clippy::cognitive_complexity)]
|
||||
|
||||
use std::env;
|
||||
use std::env::home_dir;
|
||||
use std::io;
|
||||
use std::path::PathBuf;
|
||||
use std::process::exit;
|
||||
@@ -8,11 +9,10 @@ use std::time::Duration;
|
||||
|
||||
use crate::breaking_changes::{first_run_of_major_release, print_breaking_changes, should_skip, write_keep_file};
|
||||
use clap::CommandFactory;
|
||||
use clap::{crate_version, Parser};
|
||||
use clap::{Parser, crate_version};
|
||||
use color_eyre::eyre::Context;
|
||||
use color_eyre::eyre::Result;
|
||||
use console::Key;
|
||||
use etcetera::base_strategy::BaseStrategy;
|
||||
#[cfg(windows)]
|
||||
use etcetera::base_strategy::Windows;
|
||||
#[cfg(unix)]
|
||||
@@ -23,8 +23,6 @@ use tracing::debug;
|
||||
|
||||
use self::config::{CommandLineArgs, Config};
|
||||
use self::error::StepFailed;
|
||||
#[cfg(all(windows, feature = "self-update"))]
|
||||
use self::error::Upgraded;
|
||||
use self::runner::StepResult;
|
||||
#[allow(clippy::wildcard_imports)]
|
||||
use self::steps::{remote::*, *};
|
||||
@@ -51,7 +49,7 @@ mod sudo;
|
||||
mod terminal;
|
||||
mod utils;
|
||||
|
||||
pub(crate) static HOME_DIR: LazyLock<PathBuf> = LazyLock::new(|| home::home_dir().expect("No home directory"));
|
||||
pub(crate) static HOME_DIR: LazyLock<PathBuf> = LazyLock::new(|| home_dir().expect("No home directory"));
|
||||
#[cfg(unix)]
|
||||
pub(crate) static XDG_DIRS: LazyLock<Xdg> = LazyLock::new(|| Xdg::new().expect("No home directory"));
|
||||
|
||||
@@ -101,7 +99,7 @@ fn run() -> Result<()> {
|
||||
let mut parts = env.split('=');
|
||||
let var = parts.next().unwrap();
|
||||
let value = parts.next().unwrap();
|
||||
env::set_var(var, value);
|
||||
unsafe { env::set_var(var, value) };
|
||||
}
|
||||
|
||||
if opt.edit_config() {
|
||||
@@ -124,7 +122,7 @@ fn run() -> Result<()> {
|
||||
debug!("Version: {}", crate_version!());
|
||||
debug!("OS: {}", env!("TARGET"));
|
||||
debug!("{:?}", env::args());
|
||||
debug!("Binary path: {:?}", std::env::current_exe());
|
||||
debug!("Binary path: {:?}", env::current_exe());
|
||||
debug!("self-update Feature Enabled: {:?}", cfg!(feature = "self-update"));
|
||||
debug!("Configuration: {:?}", config);
|
||||
|
||||
@@ -163,7 +161,7 @@ fn run() -> Result<()> {
|
||||
#[cfg(target_os = "linux")]
|
||||
let distribution = linux::Distribution::detect();
|
||||
|
||||
let run_type = execution_context::RunType::new(config.dry_run());
|
||||
let run_type = config.run_type();
|
||||
let ctx = execution_context::ExecutionContext::new(
|
||||
run_type,
|
||||
sudo,
|
||||
@@ -189,17 +187,7 @@ fn run() -> Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
// Self-Update step, this will execute only if:
|
||||
// 1. the `self-update` feature is enabled
|
||||
// 2. it is not disabled from configuration (env var/CLI opt/file)
|
||||
#[cfg(feature = "self-update")]
|
||||
{
|
||||
let should_self_update = env::var("TOPGRADE_NO_SELF_UPGRADE").is_err() && !config.no_self_update();
|
||||
|
||||
if should_self_update {
|
||||
runner.execute(step::Step::SelfUpdate, "Self Update", || self_update::self_update(&ctx))?;
|
||||
}
|
||||
}
|
||||
step::Step::SelfUpdate.run(&mut runner, &ctx)?;
|
||||
|
||||
#[cfg(windows)]
|
||||
let _self_rename = if config.self_rename() {
|
||||
@@ -208,20 +196,32 @@ fn run() -> Result<()> {
|
||||
None
|
||||
};
|
||||
|
||||
if let Some(commands) = config.pre_commands() {
|
||||
for (name, command) in commands {
|
||||
generic::run_custom_command(name, command, &ctx)?;
|
||||
}
|
||||
}
|
||||
|
||||
if config.pre_sudo() {
|
||||
if let Some(sudo) = ctx.sudo() {
|
||||
sudo.elevate(&ctx)?;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(commands) = config.pre_commands() {
|
||||
for (name, command) in commands {
|
||||
generic::run_custom_command(name, command, &ctx)?;
|
||||
}
|
||||
}
|
||||
|
||||
for step in step::default_steps() {
|
||||
step.run(&mut runner, &ctx)?
|
||||
match step.run(&mut runner, &ctx) {
|
||||
Ok(()) => (),
|
||||
Err(error)
|
||||
if error
|
||||
.downcast_ref::<io::Error>()
|
||||
.is_some_and(|e| e.kind() == io::ErrorKind::Interrupted) =>
|
||||
{
|
||||
println!();
|
||||
debug!("Interrupted (possibly with 'q' during retry prompt). Printing summary.");
|
||||
break;
|
||||
}
|
||||
Err(error) => return Err(error),
|
||||
}
|
||||
}
|
||||
|
||||
let mut failed = false;
|
||||
@@ -276,7 +276,7 @@ fn run() -> Result<()> {
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
if config.show_distribution_summary() {
|
||||
if let Ok(distribution) = &distribution {
|
||||
distribution.show_summary();
|
||||
}
|
||||
@@ -322,11 +322,7 @@ fn run() -> Result<()> {
|
||||
);
|
||||
}
|
||||
|
||||
if failed {
|
||||
Err(StepFailed.into())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
if failed { Err(StepFailed.into()) } else { Ok(()) }
|
||||
}
|
||||
|
||||
fn main() {
|
||||
@@ -335,13 +331,6 @@ fn main() {
|
||||
exit(0);
|
||||
}
|
||||
Err(error) => {
|
||||
#[cfg(all(windows, feature = "self-update"))]
|
||||
{
|
||||
if let Some(Upgraded(status)) = error.downcast_ref::<Upgraded>() {
|
||||
exit(status.code().unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
let skip_print = (error.downcast_ref::<StepFailed>().is_some())
|
||||
|| (error
|
||||
.downcast_ref::<io::Error>()
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
use color_eyre::eyre::Result;
|
||||
use color_eyre::eyre::{Result, WrapErr};
|
||||
use rust_i18n::t;
|
||||
use std::borrow::Cow;
|
||||
use std::fmt::Debug;
|
||||
use std::io;
|
||||
use tracing::debug;
|
||||
|
||||
use crate::ctrlc;
|
||||
use crate::error::{DryRun, MissingSudo, SkipStep};
|
||||
use crate::execution_context::ExecutionContext;
|
||||
use crate::step::Step;
|
||||
use crate::terminal::{print_error, print_warning, should_retry};
|
||||
use crate::terminal::{ShouldRetry, print_error, print_warning, should_retry};
|
||||
|
||||
pub enum StepResult {
|
||||
Success,
|
||||
@@ -98,21 +99,28 @@ impl<'a> Runner<'a> {
|
||||
let should_ask = interrupted || !(self.ctx.config().no_retry() || ignore_failure);
|
||||
let should_retry = if should_ask {
|
||||
print_error(&key, format!("{e:?}"));
|
||||
should_retry(interrupted, key.as_ref())?
|
||||
should_retry(key.as_ref())?
|
||||
} else {
|
||||
false
|
||||
ShouldRetry::No
|
||||
};
|
||||
|
||||
if !should_retry {
|
||||
self.push_result(
|
||||
key,
|
||||
if ignore_failure {
|
||||
StepResult::Ignored
|
||||
} else {
|
||||
StepResult::Failure
|
||||
},
|
||||
);
|
||||
break;
|
||||
match should_retry {
|
||||
ShouldRetry::No | ShouldRetry::Quit => {
|
||||
self.push_result(
|
||||
key,
|
||||
if ignore_failure {
|
||||
StepResult::Ignored
|
||||
} else {
|
||||
StepResult::Failure
|
||||
},
|
||||
);
|
||||
if let ShouldRetry::Quit = should_retry {
|
||||
return Err(io::Error::from(io::ErrorKind::Interrupted))
|
||||
.context("Quit from user input");
|
||||
}
|
||||
break;
|
||||
}
|
||||
ShouldRetry::Yes => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,17 +2,18 @@ use std::env;
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::process::CommandExt as _;
|
||||
use std::process::Command;
|
||||
#[cfg(windows)]
|
||||
use std::process::exit;
|
||||
|
||||
use crate::step::Step;
|
||||
use color_eyre::eyre::{bail, Result};
|
||||
use color_eyre::eyre::Result;
|
||||
#[cfg(unix)]
|
||||
use color_eyre::eyre::bail;
|
||||
use rust_i18n::t;
|
||||
use self_update_crate::backends::github::Update;
|
||||
use self_update_crate::update::UpdateStatus;
|
||||
|
||||
use super::terminal::{print_info, print_separator};
|
||||
#[cfg(windows)]
|
||||
use crate::error::Upgraded;
|
||||
|
||||
use crate::execution_context::ExecutionContext;
|
||||
|
||||
pub fn self_update(ctx: &ExecutionContext) -> Result<()> {
|
||||
@@ -63,7 +64,7 @@ pub fn self_update(ctx: &ExecutionContext) -> Result<()> {
|
||||
{
|
||||
#[allow(clippy::disallowed_methods)]
|
||||
let status = command.status()?;
|
||||
bail!(Upgraded(status));
|
||||
exit(status.code().expect("This cannot return None on Windows"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
16
src/step.rs
16
src/step.rs
@@ -53,6 +53,7 @@ pub enum Step {
|
||||
Dotnet,
|
||||
Elan,
|
||||
Emacs,
|
||||
Falconf,
|
||||
Firmware,
|
||||
Flatpak,
|
||||
Flutter,
|
||||
@@ -69,6 +70,7 @@ pub enum Step {
|
||||
Helix,
|
||||
Helm,
|
||||
HomeManager,
|
||||
Hyprpm,
|
||||
// These names are miscapitalized on purpose, so the CLI name is
|
||||
// `jetbrains_pycharm` instead of `jet_brains_py_charm`.
|
||||
JetbrainsAqua,
|
||||
@@ -223,7 +225,7 @@ impl Step {
|
||||
Bin => runner.execute(*self, "bin", || generic::bin_update(ctx))?,
|
||||
Bob => runner.execute(*self, "Bob", || generic::run_bob(ctx))?,
|
||||
BrewCask => {
|
||||
#[cfg(target_os = "macos")]
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
runner.execute(*self, "Brew Cask", || unix::run_brew_cask(ctx, unix::BrewVariant::Path))?;
|
||||
#[cfg(target_os = "macos")]
|
||||
runner.execute(*self, "Brew Cask (Intel)", || {
|
||||
@@ -304,6 +306,7 @@ impl Step {
|
||||
Dotnet => runner.execute(*self, ".NET", || generic::run_dotnet_upgrade(ctx))?,
|
||||
Elan => runner.execute(*self, "elan", || generic::run_elan(ctx))?,
|
||||
Emacs => runner.execute(*self, "Emacs", || emacs::Emacs::new().upgrade(ctx))?,
|
||||
Falconf => runner.execute(*self, "falconf sync", || generic::run_falconf(ctx))?,
|
||||
Firmware =>
|
||||
{
|
||||
#[cfg(target_os = "linux")]
|
||||
@@ -345,6 +348,11 @@ impl Step {
|
||||
#[cfg(unix)]
|
||||
runner.execute(*self, "home-manager", || unix::run_home_manager(ctx))?
|
||||
}
|
||||
Hyprpm =>
|
||||
{
|
||||
#[cfg(unix)]
|
||||
runner.execute(*self, "hyprpm", || unix::run_hyprpm(ctx))?
|
||||
}
|
||||
JetbrainsAqua => runner.execute(*self, "JetBrains Aqua Plugins", || generic::run_jetbrains_aqua(ctx))?,
|
||||
JetbrainsClion => runner.execute(*self, "JetBrains CL", || generic::run_jetbrains_clion(ctx))?,
|
||||
JetbrainsDatagrip => {
|
||||
@@ -536,6 +544,9 @@ impl Step {
|
||||
runner.execute(*self, "SDKMAN!", || unix::run_sdkman(ctx))?
|
||||
}
|
||||
SelfUpdate => {
|
||||
// Self-Update step, this will execute only if:
|
||||
// 1. the `self-update` feature is enabled
|
||||
// 2. it is not disabled from configuration (env var/CLI opt/file)
|
||||
#[cfg(feature = "self-update")]
|
||||
{
|
||||
if std::env::var("TOPGRADE_NO_SELF_UPGRADE").is_err() && !ctx.config().no_self_update() {
|
||||
@@ -739,6 +750,7 @@ pub(crate) fn default_steps() -> Vec<Step> {
|
||||
Restarts,
|
||||
Flatpak,
|
||||
BrewFormula,
|
||||
BrewCask,
|
||||
Lure,
|
||||
Waydroid,
|
||||
AutoCpufreq,
|
||||
@@ -767,6 +779,7 @@ pub(crate) fn default_steps() -> Vec<Step> {
|
||||
Sdkman,
|
||||
Rcm,
|
||||
Maza,
|
||||
Hyprpm,
|
||||
Atuin,
|
||||
]);
|
||||
|
||||
@@ -876,6 +889,7 @@ pub(crate) fn default_steps() -> Vec<Step> {
|
||||
// JetBrains Space Desktop does not have a CLI
|
||||
JetbrainsWebstorm,
|
||||
Yazi,
|
||||
Falconf,
|
||||
Powershell,
|
||||
CustomCommands,
|
||||
Vagrant,
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::io;
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
|
||||
use color_eyre::eyre::eyre;
|
||||
use color_eyre::eyre::Context;
|
||||
use color_eyre::eyre::Result;
|
||||
use color_eyre::eyre::{OptionExt, eyre};
|
||||
use tracing::{debug, error, warn};
|
||||
use wildmatch::WildMatch;
|
||||
|
||||
use crate::command::CommandExt;
|
||||
use crate::error::{self, TopgradeError};
|
||||
use crate::error::{SkipStep, TopgradeError};
|
||||
use crate::terminal::print_separator;
|
||||
use crate::{execution_context::ExecutionContext, utils::require};
|
||||
use rust_i18n::t;
|
||||
@@ -21,6 +23,9 @@ use rust_i18n::t;
|
||||
// themselves or when using docker-compose.
|
||||
const NONEXISTENT_REPO: &str = "repository does not exist";
|
||||
|
||||
// A string found in the output of docker when Docker Desktop is not running.
|
||||
const DOCKER_NOT_RUNNING: &str = "We recommend to activate the WSL integration in Docker Desktop settings.";
|
||||
|
||||
/// Uniquely identifies a `Container`.
|
||||
#[derive(Debug)]
|
||||
struct Container {
|
||||
@@ -74,7 +79,7 @@ fn list_containers(crt: &Path, ignored_containers: Option<&Vec<String>>) -> Resu
|
||||
);
|
||||
let output = Command::new(crt)
|
||||
.args(["image", "ls", "--format", "{{.Repository}}:{{.Tag}} {{.ID}}"])
|
||||
.output_checked_with_utf8(|_| Ok(()))?;
|
||||
.output_checked_utf8()?;
|
||||
|
||||
let mut retval = vec![];
|
||||
for line in output.stdout.lines() {
|
||||
@@ -99,7 +104,12 @@ fn list_containers(crt: &Path, ignored_containers: Option<&Vec<String>>) -> Resu
|
||||
|
||||
// line is of format: `Repository:Tag ImageID`, e.g., `nixos/nix:latest d80fea9c32b4`
|
||||
let split_res = line.split(' ').collect::<Vec<&str>>();
|
||||
assert_eq!(split_res.len(), 2);
|
||||
if split_res.len() != 2 {
|
||||
return Err(eyre!(format!(
|
||||
"Got erroneous output from `{} image ls --format \"{{.Repository}}:{{.Tag}} {{.ID}}\"; Expected line to split into 2 parts",
|
||||
crt.display()
|
||||
)));
|
||||
}
|
||||
let (repo_tag, image_id) = (split_res[0], split_res[1]);
|
||||
|
||||
if let Some(ref ignored_containers) = ignored_containers {
|
||||
@@ -116,11 +126,16 @@ fn list_containers(crt: &Path, ignored_containers: Option<&Vec<String>>) -> Resu
|
||||
);
|
||||
let inspect_output = Command::new(crt)
|
||||
.args(["image", "inspect", image_id, "--format", "{{.Os}}/{{.Architecture}}"])
|
||||
.output_checked_with_utf8(|_| Ok(()))?;
|
||||
.output_checked_utf8()?;
|
||||
let mut platform = inspect_output.stdout;
|
||||
// truncate the tailing new line character
|
||||
platform.truncate(platform.len() - 1);
|
||||
assert!(platform.contains('/'));
|
||||
if !platform.contains('/') {
|
||||
return Err(eyre!(format!(
|
||||
"Got erroneous output from `{} image ls --format \"{{.Repository}}:{{.Tag}} {{.ID}}\"; Expected platform to contain '/'",
|
||||
crt.display()
|
||||
)));
|
||||
}
|
||||
|
||||
retval.push(Container::new(repo_tag.to_string(), platform));
|
||||
}
|
||||
@@ -135,7 +150,41 @@ pub fn run_containers(ctx: &ExecutionContext) -> Result<()> {
|
||||
debug!("Using container runtime '{}'", crt.display());
|
||||
|
||||
print_separator(t!("Containers"));
|
||||
let mut success = true;
|
||||
|
||||
let output = Command::new(&crt).arg("--help").output_checked_with(|_| Ok(()))?;
|
||||
let status_code = output
|
||||
.status
|
||||
.code()
|
||||
.ok_or_eyre("Couldn't get status code (terminated by signal)")?;
|
||||
let stdout = std::str::from_utf8(&output.stdout).wrap_err("Expected output to be valid UTF-8")?;
|
||||
if stdout.contains(DOCKER_NOT_RUNNING) && status_code == 1 {
|
||||
// Write the output
|
||||
io::stdout().write_all(&output.stdout)?;
|
||||
io::stderr().write_all(&output.stderr)?;
|
||||
// Don't crash, but don't be silent either.
|
||||
// This can happen in other ways than Docker Desktop not running, but even in those cases
|
||||
// we don't want to crash, since the containers step is enabled by default.
|
||||
warn!(
|
||||
"{} seems to be non-functional right now (see above). Is WSL integration enabled for Docker Desktop? Is Docker Desktop running?",
|
||||
crt.display()
|
||||
);
|
||||
return Err(SkipStep(format!(
|
||||
"{} seems to be non-functional right now. Possibly WSL integration is not enabled for Docker Desktop, or Docker Desktop is not running.",
|
||||
crt.display()
|
||||
)).into());
|
||||
} else if !output.status.success() {
|
||||
// Write the output
|
||||
io::stdout().write_all(&output.stdout)?;
|
||||
io::stderr().write_all(&output.stderr)?;
|
||||
// If we saw the message, but the code is not 1 (e.g. 0, or a non-1 failure), crash, as we expect a 1.
|
||||
// If we did not see the message, it's broken in some way we do not understand.
|
||||
return Err(eyre!(
|
||||
"{0} seems to be non-functional (`{0} --help` returned non-zero exit code {1})",
|
||||
crt.display(),
|
||||
status_code,
|
||||
));
|
||||
}
|
||||
|
||||
let containers =
|
||||
list_containers(&crt, ctx.config().containers_ignored_tags()).context("Failed to list Docker containers")?;
|
||||
debug!("Containers to inspect: {:?}", containers);
|
||||
@@ -170,22 +219,21 @@ pub fn run_containers(ctx: &ExecutionContext) -> Result<()> {
|
||||
continue;
|
||||
}
|
||||
|
||||
success = false;
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
|
||||
if ctx.config().cleanup() {
|
||||
if ctx.config().containers_system_prune() {
|
||||
// Run system prune to clean up unused containers, networks, and build cache
|
||||
ctx.execute(&crt)
|
||||
.args(["system", "prune", "--force"])
|
||||
.status_checked()?
|
||||
// Only run `image prune` if we don't run `system prune`
|
||||
} else if ctx.config().cleanup() {
|
||||
// Remove dangling images
|
||||
debug!("Removing dangling images");
|
||||
if let Err(e) = ctx.execute(&crt).args(["image", "prune", "-f"]).status_checked() {
|
||||
error!("Removing dangling images failed: {}", e);
|
||||
success = false;
|
||||
}
|
||||
ctx.execute(&crt).args(["image", "prune", "-f"]).status_checked()?
|
||||
}
|
||||
|
||||
if success {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(eyre!(error::StepFailed))
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ use crate::command::CommandExt;
|
||||
use crate::execution_context::ExecutionContext;
|
||||
use crate::step::Step;
|
||||
use crate::terminal::print_separator;
|
||||
use crate::utils::{require, require_option, PathExt};
|
||||
use crate::utils::{PathExt, require, require_option};
|
||||
|
||||
const EMACS_UPGRADE: &str = include_str!("emacs.el");
|
||||
#[cfg(windows)]
|
||||
@@ -65,7 +65,11 @@ impl Emacs {
|
||||
command.arg("--force");
|
||||
}
|
||||
|
||||
command.args(["upgrade"]);
|
||||
command.arg("upgrade");
|
||||
|
||||
if ctx.config().doom_aot() {
|
||||
command.arg("--aot");
|
||||
}
|
||||
|
||||
command.status_checked()
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
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 color_eyre::eyre::{OptionExt, eyre};
|
||||
use jetbrains_toolbox_updater::{FindError, find_jetbrains_toolbox, update_jetbrains_toolbox};
|
||||
use regex::bytes::Regex;
|
||||
use rust_i18n::t;
|
||||
use semver::Version;
|
||||
use serde::Deserialize;
|
||||
use std::ffi::OsString;
|
||||
use std::iter::once;
|
||||
use std::path::PathBuf;
|
||||
@@ -15,6 +16,7 @@ use std::{fs, io::Write};
|
||||
use tempfile::tempfile_in;
|
||||
use tracing::{debug, error, warn};
|
||||
|
||||
use crate::HOME_DIR;
|
||||
use crate::command::{CommandExt, Utf8Output};
|
||||
use crate::execution_context::ExecutionContext;
|
||||
use crate::executor::ExecutorOutput;
|
||||
@@ -22,8 +24,7 @@ use crate::output_changed_message;
|
||||
use crate::step::Step;
|
||||
use crate::sudo::SudoExecuteOpts;
|
||||
use crate::terminal::{print_separator, shell};
|
||||
use crate::utils::{check_is_python_2_or_shim, require, require_one, require_option, which, PathExt};
|
||||
use crate::HOME_DIR;
|
||||
use crate::utils::{PathExt, check_is_python_2_or_shim, require, require_one, require_option, which};
|
||||
use crate::{
|
||||
error::{SkipStep, StepFailed, TopgradeError},
|
||||
terminal::print_warning,
|
||||
@@ -64,7 +65,9 @@ pub fn run_cargo_update(ctx: &ExecutionContext) -> Result<()> {
|
||||
.or_else(|| cargo_dir.join("bin/cargo-install-update").if_exists());
|
||||
|
||||
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`");
|
||||
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());
|
||||
};
|
||||
@@ -80,7 +83,9 @@ pub fn run_cargo_update(ctx: &ExecutionContext) -> Result<()> {
|
||||
if let Some(e) = cargo_cache {
|
||||
ctx.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`");
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -105,8 +110,9 @@ pub fn run_gem(ctx: &ExecutionContext) -> Result<()> {
|
||||
command.arg("update");
|
||||
|
||||
if env::var_os("RBENV_SHELL").is_none() {
|
||||
debug!("Detected rbenv. Avoiding --user-install");
|
||||
command.arg("--user-install");
|
||||
} else {
|
||||
debug!("Detected rbenv. Avoiding --user-install");
|
||||
}
|
||||
|
||||
command.status_checked()
|
||||
@@ -226,25 +232,25 @@ impl Aqua {
|
||||
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")?;
|
||||
fn get(ctx: &ExecutionContext) -> Result<Self> {
|
||||
let aqua = require("aqua")?;
|
||||
|
||||
// Check if `aqua --help` mentions "aqua". JetBrains Aqua does not, Aqua CLI does.
|
||||
let output = ctx.execute(&aqua).arg("--help").output_checked()?;
|
||||
// Check if `aqua --help` mentions "aqua". JetBrains Aqua does not, Aqua CLI does.
|
||||
let output = ctx.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))
|
||||
if String::from_utf8(output.stdout)?.contains("aqua") {
|
||||
debug!("Detected `aqua` as Aqua CLI");
|
||||
Ok(Self::AquaCLI(aqua))
|
||||
} else {
|
||||
debug!("Detected `aqua` as JetBrains Aqua");
|
||||
Ok(Self::JetBrainsAqua(aqua))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_aqua(ctx: &ExecutionContext) -> Result<()> {
|
||||
let aqua = get_aqua(ctx)?.aqua_cli()?;
|
||||
let aqua = Aqua::get(ctx)?.aqua_cli()?;
|
||||
|
||||
print_separator("Aqua");
|
||||
if ctx.run_type().dry() {
|
||||
@@ -261,7 +267,11 @@ pub fn run_rustup(ctx: &ExecutionContext) -> Result<()> {
|
||||
let rustup = require("rustup")?;
|
||||
|
||||
print_separator("rustup");
|
||||
ctx.execute(rustup).arg("update").status_checked()
|
||||
|
||||
ctx.execute(rustup)
|
||||
.arg("update")
|
||||
.args(ctx.config().rustup_channels())
|
||||
.status_checked()
|
||||
}
|
||||
|
||||
pub fn run_rye(ctx: &ExecutionContext) -> Result<()> {
|
||||
@@ -276,6 +286,17 @@ pub fn run_elan(ctx: &ExecutionContext) -> Result<()> {
|
||||
|
||||
print_separator("elan");
|
||||
|
||||
let version_output = ctx.execute(&elan).arg("--version").output_checked_utf8()?;
|
||||
let version_string = version_output.stdout.split_whitespace().nth(1).ok_or_else(|| {
|
||||
eyre!(output_changed_message!(
|
||||
"elan --version",
|
||||
"Expected version after 'elan '"
|
||||
))
|
||||
})?;
|
||||
let version = Version::parse(version_string)
|
||||
.wrap_err_with(|| output_changed_message!("elan --version", "Invalid version"))?;
|
||||
debug!("Detected elan version as: {}", version);
|
||||
|
||||
let disabled_error_msg = "self-update is disabled";
|
||||
let executor_output = ctx.execute(&elan).args(["self", "update"]).output()?;
|
||||
match executor_output {
|
||||
@@ -304,7 +325,12 @@ pub fn run_elan(ctx: &ExecutionContext) -> Result<()> {
|
||||
ExecutorOutput::Dry => { /* nothing needed because in a dry run */ }
|
||||
}
|
||||
|
||||
ctx.execute(&elan).arg("update").status_checked()
|
||||
// In elan 4.0.0, `elan update` was removed, as toolchains are now updated automatically
|
||||
if version < Version::new(4, 0, 0) {
|
||||
ctx.execute(&elan).arg("update").status_checked()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn run_juliaup(ctx: &ExecutionContext) -> Result<()> {
|
||||
@@ -430,10 +456,10 @@ pub fn run_vcpkg_update(ctx: &ExecutionContext) -> Result<()> {
|
||||
let is_root_install = false;
|
||||
|
||||
let mut command = if is_root_install {
|
||||
ctx.execute(&vcpkg)
|
||||
} else {
|
||||
let sudo = ctx.require_sudo()?;
|
||||
sudo.execute(ctx, &vcpkg)?
|
||||
} else {
|
||||
ctx.execute(&vcpkg)
|
||||
};
|
||||
|
||||
command.args(["upgrade", "--no-dry-run"]).status_checked()
|
||||
@@ -520,7 +546,7 @@ fn run_vscode_compatible(variant: VSCodeVariant, ctx: &ExecutionContext) -> Resu
|
||||
return Err(eyre!(output_changed_message!(
|
||||
&format!("{bin_name} --version"),
|
||||
"No first line"
|
||||
)))
|
||||
)));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -601,11 +627,11 @@ pub fn run_conda_update(ctx: &ExecutionContext) -> Result<()> {
|
||||
let conda = require("conda")?;
|
||||
|
||||
let output = Command::new(&conda)
|
||||
.args(["config", "--show", "auto_activate_base"])
|
||||
.args(["config", "--show", "auto_activate"])
|
||||
.output_checked_utf8()?;
|
||||
debug!("Conda output: {}", output.stdout);
|
||||
if output.stdout.contains("False") {
|
||||
return Err(SkipStep("auto_activate_base is set to False".to_string()).into());
|
||||
return Err(SkipStep("auto_activate is set to False".to_string()).into());
|
||||
}
|
||||
|
||||
print_separator("Conda");
|
||||
@@ -867,12 +893,11 @@ pub fn run_tldr(ctx: &ExecutionContext) -> Result<()> {
|
||||
}
|
||||
|
||||
pub fn run_tlmgr_update(ctx: &ExecutionContext) -> Result<()> {
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(any(target_os = "linux", target_os = "android"))] {
|
||||
if !ctx.config().enable_tlmgr_linux() {
|
||||
return Err(SkipStep(String::from("tlmgr must be explicitly enabled in the configuration to run in Android/Linux")).into());
|
||||
}
|
||||
}
|
||||
if cfg!(any(target_os = "linux", target_os = "android")) && !ctx.config().enable_tlmgr_linux() {
|
||||
return Err(SkipStep(String::from(
|
||||
"tlmgr must be explicitly enabled in the configuration to run in Android/Linux",
|
||||
))
|
||||
.into());
|
||||
}
|
||||
|
||||
let tlmgr = require("tlmgr")?;
|
||||
@@ -910,9 +935,17 @@ pub fn run_chezmoi_update(ctx: &ExecutionContext) -> Result<()> {
|
||||
let chezmoi = require("chezmoi")?;
|
||||
HOME_DIR.join(".local/share/chezmoi").require()?;
|
||||
|
||||
let mut cmd = ctx.execute(chezmoi);
|
||||
|
||||
print_separator("chezmoi");
|
||||
|
||||
ctx.execute(chezmoi).arg("update").status_checked()
|
||||
cmd.arg("update");
|
||||
|
||||
if ctx.config().chezmoi_exclude_encrypted() {
|
||||
cmd.arg("--exclude=encrypted");
|
||||
}
|
||||
|
||||
cmd.status_checked()
|
||||
}
|
||||
|
||||
pub fn run_myrepos_update(ctx: &ExecutionContext) -> Result<()> {
|
||||
@@ -969,23 +1002,19 @@ pub fn run_composer_update(ctx: &ExecutionContext) -> Result<()> {
|
||||
print_separator(t!("Composer"));
|
||||
|
||||
if ctx.config().composer_self_update() {
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(unix)] {
|
||||
// If self-update fails without sudo then there's probably an update
|
||||
let has_update = match ctx.execute(&composer).arg("self-update").output()? {
|
||||
ExecutorOutput::Wet(output) => !output.status.success(),
|
||||
_ => false
|
||||
};
|
||||
if cfg!(unix) {
|
||||
// If self-update fails without sudo then there's probably an update
|
||||
let has_update = match ctx.execute(&composer).arg("self-update").output()? {
|
||||
ExecutorOutput::Wet(output) => !output.status.success(),
|
||||
_ => false,
|
||||
};
|
||||
|
||||
if has_update {
|
||||
let sudo = ctx.require_sudo()?;
|
||||
sudo.execute(ctx, &composer)?
|
||||
.arg("self-update")
|
||||
.status_checked()?;
|
||||
}
|
||||
} else {
|
||||
ctx.execute(&composer).arg("self-update").status_checked()?;
|
||||
if has_update {
|
||||
let sudo = ctx.require_sudo()?;
|
||||
sudo.execute(ctx, &composer)?.arg("self-update").status_checked()?;
|
||||
}
|
||||
} else {
|
||||
ctx.execute(&composer).arg("self-update").status_checked()?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1107,25 +1136,25 @@ impl Hx {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_hx(ctx: &ExecutionContext) -> Result<Hx> {
|
||||
let hx = require("hx")?;
|
||||
fn get(ctx: &ExecutionContext) -> Result<Self> {
|
||||
let hx = require("hx")?;
|
||||
|
||||
// Check if `hx --help` mentions "helix". Helix does, hx (hexdump alternative) doesn't.
|
||||
let output = ctx.execute(&hx).arg("--help").output_checked()?;
|
||||
// Check if `hx --help` mentions "helix". Helix does, hx (hexdump alternative) doesn't.
|
||||
let output = ctx.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)
|
||||
if String::from_utf8(output.stdout)?.contains("helix") {
|
||||
debug!("Detected `hx` as Helix");
|
||||
Ok(Self::Helix(hx))
|
||||
} else {
|
||||
debug!("Detected `hx` as hx (hexdump alternative)");
|
||||
Ok(Self::HxHexdump)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_helix_grammars(ctx: &ExecutionContext) -> Result<()> {
|
||||
let helix = require("helix").or(get_hx(ctx)?.helix())?;
|
||||
let helix = require("helix").or(Hx::get(ctx)?.helix())?;
|
||||
|
||||
print_separator("Helix");
|
||||
|
||||
@@ -1214,11 +1243,7 @@ pub fn run_helm_repo_update(ctx: &ExecutionContext) -> Result<()> {
|
||||
};
|
||||
}
|
||||
|
||||
if success {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(eyre!(StepFailed))
|
||||
}
|
||||
if success { Ok(()) } else { Err(eyre!(StepFailed)) }
|
||||
}
|
||||
|
||||
pub fn run_stew(ctx: &ExecutionContext) -> Result<()> {
|
||||
@@ -1403,8 +1428,7 @@ pub fn run_poetry(ctx: &ExecutionContext) -> Result<()> {
|
||||
.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 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);
|
||||
@@ -1555,9 +1579,25 @@ pub fn run_zvm(ctx: &ExecutionContext) -> Result<()> {
|
||||
pub fn run_bun(ctx: &ExecutionContext) -> Result<()> {
|
||||
let bun = require("bun")?;
|
||||
|
||||
print_separator("Bun");
|
||||
// From the official install script (both install.sh and install.ps1), Bun uses
|
||||
// the path set in this variable as the install root, and its defaults to
|
||||
// `$HOME/.bun`
|
||||
//
|
||||
// UNIX: https://bun.sh/install.sh
|
||||
// Windows: https://bun.sh/install.ps1
|
||||
let bun_install_env = env::var("BUN_INSTALL")
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or(HOME_DIR.join(".bun"));
|
||||
|
||||
ctx.execute(bun).arg("upgrade").status_checked()
|
||||
// If `bun` is a descendant of `bun_install_env`, then Bun is installed
|
||||
// through the official script
|
||||
if bun.is_descendant_of(&bun_install_env) {
|
||||
print_separator("Bun");
|
||||
|
||||
ctx.execute(bun).arg("upgrade").status_checked()
|
||||
} else {
|
||||
Err(SkipStep("Not installed through the official script".to_string()).into())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_zigup(ctx: &ExecutionContext) -> Result<()> {
|
||||
@@ -1599,7 +1639,7 @@ pub fn run_zigup(ctx: &ExecutionContext) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn run_jetbrains_toolbox(_ctx: &ExecutionContext) -> Result<()> {
|
||||
pub fn run_jetbrains_toolbox(ctx: &ExecutionContext) -> Result<()> {
|
||||
let installation = find_jetbrains_toolbox();
|
||||
match installation {
|
||||
Err(FindError::NotFound) => {
|
||||
@@ -1622,6 +1662,11 @@ pub fn run_jetbrains_toolbox(_ctx: &ExecutionContext) -> Result<()> {
|
||||
Ok(installation) => {
|
||||
print_separator("JetBrains Toolbox");
|
||||
|
||||
if ctx.run_type().dry() {
|
||||
println!("Dry running jetbrains-toolbox-updater");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
match update_jetbrains_toolbox(installation) {
|
||||
Err(e) => {
|
||||
// Unexpected error
|
||||
@@ -1662,7 +1707,10 @@ fn run_jetbrains_ide_generic<const IS_JETBRAINS: bool>(ctx: &ExecutionContext, b
|
||||
.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));
|
||||
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.");
|
||||
@@ -1696,7 +1744,7 @@ pub fn run_android_studio(ctx: &ExecutionContext) -> Result<()> {
|
||||
}
|
||||
|
||||
pub fn run_jetbrains_aqua(ctx: &ExecutionContext) -> Result<()> {
|
||||
run_jetbrains_ide(ctx, get_aqua(ctx)?.jetbrains_aqua()?, "Aqua")
|
||||
run_jetbrains_ide(ctx, Aqua::get(ctx)?.jetbrains_aqua()?, "Aqua")
|
||||
}
|
||||
|
||||
pub fn run_jetbrains_clion(ctx: &ExecutionContext) -> Result<()> {
|
||||
@@ -1779,10 +1827,49 @@ pub fn run_yazi(ctx: &ExecutionContext) -> Result<()> {
|
||||
ctx.execute(ya).args(["pkg", "upgrade"]).status_checked()
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct TypstInfo {
|
||||
build: TypstBuild,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct TypstBuild {
|
||||
settings: TypstSettings,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
struct TypstSettings {
|
||||
self_update: bool,
|
||||
}
|
||||
|
||||
pub fn run_typst(ctx: &ExecutionContext) -> Result<()> {
|
||||
let typst = require("typst")?;
|
||||
|
||||
let raw_info = ctx
|
||||
.execute(&typst)
|
||||
.args(["info", "-f", "json"])
|
||||
.output_checked_utf8()?
|
||||
.stdout;
|
||||
let info: TypstInfo = serde_json::from_str(&raw_info).wrap_err_with(|| {
|
||||
output_changed_message!(
|
||||
"typst info -f json",
|
||||
"json output invalid or does not contain .build.settings.self-update"
|
||||
)
|
||||
})?;
|
||||
if !info.build.settings.self_update {
|
||||
return Err(SkipStep("This build of typst does not have self-update enabled".to_string()).into());
|
||||
}
|
||||
|
||||
print_separator("Typst");
|
||||
|
||||
ctx.execute(typst).args(["update"]).status_checked()
|
||||
}
|
||||
|
||||
pub fn run_falconf(ctx: &ExecutionContext) -> Result<()> {
|
||||
let falconf = require("falconf")?;
|
||||
|
||||
print_separator("falconf sync");
|
||||
|
||||
ctx.execute(falconf).arg("sync").status_checked()
|
||||
}
|
||||
|
||||
@@ -4,10 +4,10 @@ use std::path::{Path, PathBuf};
|
||||
use std::process::{Command, Output, Stdio};
|
||||
|
||||
use color_eyre::eyre::Context;
|
||||
use color_eyre::eyre::{eyre, Result};
|
||||
use color_eyre::eyre::{Result, eyre};
|
||||
use console::style;
|
||||
use futures::stream::{iter, FuturesUnordered, StreamExt};
|
||||
use glob::{glob_with, MatchOptions};
|
||||
use futures::stream::{FuturesUnordered, StreamExt, iter};
|
||||
use glob::{MatchOptions, glob_with};
|
||||
use tokio::process::Command as AsyncCommand;
|
||||
use tokio::runtime;
|
||||
use tracing::{debug, error};
|
||||
@@ -17,8 +17,8 @@ use crate::execution_context::ExecutionContext;
|
||||
use crate::step::Step;
|
||||
use crate::steps::emacs::Emacs;
|
||||
use crate::terminal::print_separator;
|
||||
use crate::utils::{require, PathExt};
|
||||
use crate::{error::SkipStep, terminal::print_warning, HOME_DIR};
|
||||
use crate::utils::{PathExt, require};
|
||||
use crate::{HOME_DIR, error::SkipStep, terminal::print_warning};
|
||||
use etcetera::base_strategy::BaseStrategy;
|
||||
use rust_i18n::t;
|
||||
|
||||
@@ -93,7 +93,7 @@ pub fn run_git_pull(ctx: &ExecutionContext) -> Result<()> {
|
||||
// Handle user-defined repos
|
||||
if let Some(custom_git_repos) = config.git_repos() {
|
||||
for git_repo in custom_git_repos {
|
||||
repos.glob_insert(git_repo);
|
||||
repos.glob_insert(&shellexpand::tilde(git_repo));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ use tracing::debug;
|
||||
|
||||
use crate::command::CommandExt;
|
||||
use crate::terminal::{print_info, print_separator};
|
||||
use crate::utils::{require, PathExt};
|
||||
use crate::utils::{PathExt, require};
|
||||
use crate::{error::SkipStep, execution_context::ExecutionContext};
|
||||
|
||||
enum NPMVariant {
|
||||
@@ -65,11 +65,7 @@ impl NPM {
|
||||
/// If the “NPM” version is larger than 8.11.0, we use
|
||||
/// `--location=global`; otherwise, use `-g`.
|
||||
fn global_location_arg(&self) -> &str {
|
||||
if self.is_npm_8() {
|
||||
"--location=global"
|
||||
} else {
|
||||
"-g"
|
||||
}
|
||||
if self.is_npm_8() { "--location=global" } else { "-g" }
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
|
||||
@@ -6,6 +6,7 @@ use ini::Ini;
|
||||
use rust_i18n::t;
|
||||
use tracing::{debug, warn};
|
||||
|
||||
use crate::HOME_DIR;
|
||||
use crate::command::CommandExt;
|
||||
use crate::error::{SkipStep, TopgradeError};
|
||||
use crate::execution_context::ExecutionContext;
|
||||
@@ -14,8 +15,7 @@ use crate::steps::generic::is_wsl;
|
||||
use crate::steps::os::archlinux;
|
||||
use crate::sudo::SudoExecuteOpts;
|
||||
use crate::terminal::{print_separator, prompt_yesno};
|
||||
use crate::utils::{require, require_one, which, PathExt};
|
||||
use crate::HOME_DIR;
|
||||
use crate::utils::{PathExt, require, require_one, which};
|
||||
|
||||
static OS_RELEASE_PATH: &str = "/etc/os-release";
|
||||
|
||||
@@ -23,6 +23,7 @@ static OS_RELEASE_PATH: &str = "/etc/os-release";
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum Distribution {
|
||||
Alpine,
|
||||
AOSC,
|
||||
Wolfi,
|
||||
Arch,
|
||||
Bedrock,
|
||||
@@ -58,6 +59,7 @@ impl Distribution {
|
||||
|
||||
Ok(match id {
|
||||
Some("alpine") => Distribution::Alpine,
|
||||
Some("aosc") => Distribution::AOSC,
|
||||
Some("chimera") => Distribution::Chimera,
|
||||
Some("wolfi") => Distribution::Wolfi,
|
||||
Some("centos") | Some("rhel") | Some("ol") => Distribution::CentOS,
|
||||
@@ -76,6 +78,8 @@ impl Distribution {
|
||||
Some("neon") => Distribution::KDENeon,
|
||||
Some("openmandriva") => Distribution::OpenMandriva,
|
||||
Some("pclinuxos") => Distribution::PCLinuxOS,
|
||||
Some(id) if id.starts_with("origami") => Distribution::FedoraImmutable,
|
||||
|
||||
_ => {
|
||||
if let Some(name) = name {
|
||||
if name.contains("Vanilla") {
|
||||
@@ -161,6 +165,7 @@ impl Distribution {
|
||||
Distribution::PCLinuxOS => upgrade_pclinuxos(ctx),
|
||||
Distribution::Nobara => upgrade_nobara(ctx),
|
||||
Distribution::NILRT => upgrade_nilrt(ctx),
|
||||
Distribution::AOSC => upgrade_aosc(ctx),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,6 +202,20 @@ fn update_bedrock(ctx: &ExecutionContext) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn upgrade_aosc(ctx: &ExecutionContext) -> Result<()> {
|
||||
let oma = require("oma")?;
|
||||
let sudo = ctx.require_sudo()?;
|
||||
|
||||
let mut cmd = sudo.execute(ctx, &oma)?;
|
||||
cmd.arg("upgrade");
|
||||
|
||||
if ctx.config().yes(Step::System) {
|
||||
cmd.arg("-y");
|
||||
}
|
||||
|
||||
cmd.status_checked()
|
||||
}
|
||||
|
||||
fn upgrade_alpine_linux(ctx: &ExecutionContext) -> Result<()> {
|
||||
let apk = require("apk")?;
|
||||
let sudo = ctx.require_sudo()?;
|
||||
@@ -693,6 +712,10 @@ pub fn run_pacstall(ctx: &ExecutionContext) -> Result<()> {
|
||||
pub fn run_pkgfile(ctx: &ExecutionContext) -> Result<()> {
|
||||
let pkgfile = require("pkgfile")?;
|
||||
|
||||
if !ctx.config().enable_pkgfile() {
|
||||
return Err(SkipStep("Pkgfile isn't enabled".to_string()).into());
|
||||
}
|
||||
|
||||
print_separator("pkgfile");
|
||||
|
||||
let sudo = ctx.require_sudo()?;
|
||||
@@ -1013,6 +1036,7 @@ pub fn run_dkp_pacman_update(ctx: &ExecutionContext) -> Result<()> {
|
||||
}
|
||||
|
||||
pub fn run_config_update(ctx: &ExecutionContext) -> Result<()> {
|
||||
// The `config_update` step always requests user input, so when running with `--yes` we need to skip the step entirely
|
||||
if ctx.config().yes(Step::ConfigUpdate) {
|
||||
return Err(SkipStep(t!("Skipped in --yes").to_string()).into());
|
||||
}
|
||||
@@ -1022,6 +1046,7 @@ pub fn run_config_update(ctx: &ExecutionContext) -> Result<()> {
|
||||
let sudo = ctx.require_sudo()?;
|
||||
sudo.execute(ctx, etc_update)?.status_checked()?;
|
||||
} else if let Ok(pacdiff) = require("pacdiff") {
|
||||
// When `DIFFPROG` is unset, `pacdiff` uses `vim` by default
|
||||
if std::env::var("DIFFPROG").is_err() {
|
||||
require("vim")?;
|
||||
}
|
||||
@@ -1099,6 +1124,12 @@ pub fn run_waydroid(ctx: &ExecutionContext) -> Result<()> {
|
||||
|
||||
pub fn run_auto_cpufreq(ctx: &ExecutionContext) -> Result<()> {
|
||||
let auto_cpu_freq = require("auto-cpufreq")?;
|
||||
if auto_cpu_freq != PathBuf::from("/usr/local/bin/auto-cpufreq") {
|
||||
return Err(SkipStep(String::from(
|
||||
"`auto-cpufreq` was not installed by the official installer, but presumably by a package manager.",
|
||||
))
|
||||
.into());
|
||||
}
|
||||
|
||||
print_separator("auto-cpufreq");
|
||||
|
||||
@@ -1137,6 +1168,11 @@ mod tests {
|
||||
test_template(include_str!("os_release/arch32"), Distribution::Arch);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_aosc() {
|
||||
test_template(include_str!("os_release/aosc"), Distribution::AOSC);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_centos() {
|
||||
test_template(include_str!("os_release/centos"), Distribution::CentOS);
|
||||
@@ -1307,4 +1343,11 @@ mod tests {
|
||||
fn test_cachyos() {
|
||||
test_template(include_str!("os_release/cachyos"), Distribution::Arch);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_origami() {
|
||||
test_template(include_str!("os_release/origami"), Distribution::FedoraImmutable);
|
||||
test_template(include_str!("os_release/origami-nvidia"), Distribution::FedoraImmutable);
|
||||
test_template(include_str!("os_release/origami-test"), Distribution::FedoraImmutable);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ pub fn run_mas(ctx: &ExecutionContext) -> Result<()> {
|
||||
pub fn upgrade_macos(ctx: &ExecutionContext) -> Result<()> {
|
||||
print_separator(t!("macOS system update"));
|
||||
|
||||
let should_ask = !(ctx.config().yes(Step::System) || ctx.config().dry_run());
|
||||
let should_ask = !(ctx.config().yes(Step::System) || ctx.run_type().dry());
|
||||
if should_ask {
|
||||
println!("{}", t!("Finding available software"));
|
||||
if system_update_available()? {
|
||||
@@ -95,7 +95,7 @@ pub fn update_xcodes(ctx: &ExecutionContext) -> Result<()> {
|
||||
let xcodes = require("xcodes")?;
|
||||
print_separator("Xcodes");
|
||||
|
||||
let should_ask = !(ctx.config().yes(Step::Xcodes) || ctx.config().dry_run());
|
||||
let should_ask = !(ctx.config().yes(Step::Xcodes) || ctx.run_type().dry());
|
||||
|
||||
let releases = ctx.execute(&xcodes).args(["update"]).output_checked_utf8()?.stdout;
|
||||
|
||||
@@ -200,7 +200,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_or(true, |s| !s.contains("(Installed)")) && !releases_filtered.is_empty() {
|
||||
if releases_filtered.last().is_none_or(|s| !s.contains("(Installed)")) && !releases_filtered.is_empty() {
|
||||
println!(
|
||||
"{} {}",
|
||||
t!("New Xcode release detected:"),
|
||||
|
||||
@@ -14,7 +14,7 @@ pub mod macos;
|
||||
pub mod openbsd;
|
||||
#[cfg(unix)]
|
||||
pub mod unix;
|
||||
#[cfg(target_os = "windows")]
|
||||
#[cfg(windows)]
|
||||
pub mod windows;
|
||||
|
||||
#[cfg(windows)]
|
||||
|
||||
@@ -4,16 +4,15 @@ use crate::terminal::print_separator;
|
||||
use color_eyre::eyre::Result;
|
||||
use rust_i18n::t;
|
||||
use std::fs;
|
||||
use tracing::debug;
|
||||
|
||||
fn is_openbsd_current(ctx: &ExecutionContext) -> Result<bool> {
|
||||
fn is_openbsd_current() -> 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)
|
||||
}
|
||||
|
||||
debug!("OpenBSD is -current/-beta: {is_current}");
|
||||
|
||||
Ok(is_current)
|
||||
}
|
||||
|
||||
pub fn upgrade_openbsd(ctx: &ExecutionContext) -> Result<()> {
|
||||
@@ -21,12 +20,7 @@ pub fn upgrade_openbsd(ctx: &ExecutionContext) -> Result<()> {
|
||||
|
||||
let sudo = ctx.require_sudo()?;
|
||||
|
||||
let is_current = is_openbsd_current(ctx)?;
|
||||
|
||||
if ctx.config().dry_run() {
|
||||
println!("{}", t!("Would upgrade the OpenBSD system"));
|
||||
return Ok(());
|
||||
}
|
||||
let is_current = is_openbsd_current()?;
|
||||
|
||||
if is_current {
|
||||
sudo.execute(ctx, "/usr/sbin/sysupgrade")?.arg("-sn").status_checked()
|
||||
@@ -40,12 +34,7 @@ pub fn upgrade_packages(ctx: &ExecutionContext) -> Result<()> {
|
||||
|
||||
let sudo = ctx.require_sudo()?;
|
||||
|
||||
let is_current = is_openbsd_current(ctx)?;
|
||||
|
||||
if ctx.config().dry_run() {
|
||||
println!("{}", t!("Would upgrade OpenBSD packages"));
|
||||
return Ok(());
|
||||
}
|
||||
let is_current = is_openbsd_current()?;
|
||||
|
||||
if ctx.config().cleanup() {
|
||||
sudo.execute(ctx, "/usr/sbin/pkg_delete")?.arg("-ac").status_checked()?;
|
||||
|
||||
10
src/steps/os/os_release/aosc
Normal file
10
src/steps/os/os_release/aosc
Normal file
@@ -0,0 +1,10 @@
|
||||
PRETTY_NAME="AOSC OS (12.2.2)"
|
||||
NAME="AOSC OS"
|
||||
VERSION_ID="12.2.2"
|
||||
VERSION="12.2.2 (localhost)"
|
||||
BUILD_ID="20250916"
|
||||
ID=aosc
|
||||
ANSI_COLOR="1;36"
|
||||
HOME_URL="https://aosc.io/"
|
||||
SUPPORT_URL="https://github.com/AOSC-Dev/aosc-os-abbs"
|
||||
BUG_REPORT_URL="https://github.com/AOSC-Dev/aosc-os-abbs/issues"
|
||||
23
src/steps/os/os_release/origami
Normal file
23
src/steps/os/os_release/origami
Normal file
@@ -0,0 +1,23 @@
|
||||
NAME="Origami Linux"
|
||||
VERSION="43.20251117.0 (COSMIC Atomic)"
|
||||
RELEASE_TYPE="stable"
|
||||
ID="origami-linux"
|
||||
VERSION_ID="43"
|
||||
VERSION_CODENAME=""
|
||||
PRETTY_NAME="Origami 折り紙"
|
||||
ANSI_COLOR="0;38;2;60;110;180"
|
||||
LOGO="fedora-logo-icon"
|
||||
CPE_NAME="cpe:/o:fedoraproject:fedora:43"
|
||||
DEFAULT_HOSTNAME="origami"
|
||||
HOME_URL="https://origami.wf/"
|
||||
DOCUMENTATION_URL="https://docs.fedoraproject.org/en-US/fedora/f43/"
|
||||
SUPPORT_URL="https://ask.fedoraproject.org/"
|
||||
BUG_REPORT_URL="https://gitlab.com/groups/origami-linux/-/issues"
|
||||
REDHAT_BUGZILLA_PRODUCT="Fedora"
|
||||
REDHAT_BUGZILLA_PRODUCT_VERSION="43"
|
||||
REDHAT_SUPPORT_PRODUCT="Fedora"
|
||||
REDHAT_SUPPORT_PRODUCT_VERSION="43"
|
||||
SUPPORT_END="2026-12-02"
|
||||
VARIANT="COSMIC Atomic"
|
||||
VARIANT_ID="cosmic-atomic"
|
||||
OSTREE_VERSION="43.20251117.0"
|
||||
23
src/steps/os/os_release/origami-nvidia
Normal file
23
src/steps/os/os_release/origami-nvidia
Normal file
@@ -0,0 +1,23 @@
|
||||
NAME="Origami Linux Nvidia"
|
||||
VERSION="43.20251117.0 (COSMIC Atomic)"
|
||||
RELEASE_TYPE="stable"
|
||||
ID="origami-linux-nvidia"
|
||||
VERSION_ID="43"
|
||||
VERSION_CODENAME=""
|
||||
PRETTY_NAME="Origami 折り紙"
|
||||
ANSI_COLOR="0;38;2;60;110;180"
|
||||
LOGO="fedora-logo-icon"
|
||||
CPE_NAME="cpe:/o:fedoraproject:fedora:43"
|
||||
DEFAULT_HOSTNAME="origami"
|
||||
HOME_URL="https://origami.wf/"
|
||||
DOCUMENTATION_URL="https://docs.fedoraproject.org/en-US/fedora/f43/"
|
||||
SUPPORT_URL="https://ask.fedoraproject.org/"
|
||||
BUG_REPORT_URL="https://gitlab.com/groups/origami-linux/-/issues"
|
||||
REDHAT_BUGZILLA_PRODUCT="Fedora"
|
||||
REDHAT_BUGZILLA_PRODUCT_VERSION="43"
|
||||
REDHAT_SUPPORT_PRODUCT="Fedora"
|
||||
REDHAT_SUPPORT_PRODUCT_VERSION="43"
|
||||
SUPPORT_END="2026-12-02"
|
||||
VARIANT="COSMIC Atomic"
|
||||
VARIANT_ID="cosmic-atomic"
|
||||
OSTREE_VERSION="43.20251117.0"
|
||||
23
src/steps/os/os_release/origami-test
Normal file
23
src/steps/os/os_release/origami-test
Normal file
@@ -0,0 +1,23 @@
|
||||
NAME="Origami Linux Test"
|
||||
VERSION="43.20251117.0 (COSMIC Atomic)"
|
||||
RELEASE_TYPE="stable"
|
||||
ID="origami-linux-test"
|
||||
VERSION_ID="43"
|
||||
VERSION_CODENAME=""
|
||||
PRETTY_NAME="Origami 折り紙"
|
||||
ANSI_COLOR="0;38;2;60;110;180"
|
||||
LOGO="fedora-logo-icon"
|
||||
CPE_NAME="cpe:/o:fedoraproject:fedora:43"
|
||||
DEFAULT_HOSTNAME="origami"
|
||||
HOME_URL="https://origami.wf/"
|
||||
DOCUMENTATION_URL="https://docs.fedoraproject.org/en-US/fedora/f43/"
|
||||
SUPPORT_URL="https://ask.fedoraproject.org/"
|
||||
BUG_REPORT_URL="https://gitlab.com/groups/origami-linux/-/issues"
|
||||
REDHAT_BUGZILLA_PRODUCT="Fedora"
|
||||
REDHAT_BUGZILLA_PRODUCT_VERSION="43"
|
||||
REDHAT_SUPPORT_PRODUCT="Fedora"
|
||||
REDHAT_SUPPORT_PRODUCT_VERSION="43"
|
||||
SUPPORT_END="2026-12-02"
|
||||
VARIANT="COSMIC Atomic"
|
||||
VARIANT_ID="cosmic-atomic"
|
||||
OSTREE_VERSION="43.20251117.0"
|
||||
@@ -1,28 +1,29 @@
|
||||
use color_eyre::eyre::eyre;
|
||||
use color_eyre::eyre::Context;
|
||||
use color_eyre::eyre::Result;
|
||||
use color_eyre::eyre::{OptionExt, eyre};
|
||||
use etcetera::BaseStrategy;
|
||||
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::env::home_dir;
|
||||
use std::ffi::OsStr;
|
||||
use std::fs;
|
||||
use std::io::Write;
|
||||
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 std::{fs, io};
|
||||
use tracing::{debug, warn};
|
||||
|
||||
use crate::XDG_DIRS;
|
||||
use crate::command::CommandExt;
|
||||
use crate::sudo::SudoExecuteOpts;
|
||||
use crate::XDG_DIRS;
|
||||
use crate::{output_changed_message, HOME_DIR};
|
||||
use crate::{HOME_DIR, output_changed_message};
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
use super::linux::Distribution;
|
||||
@@ -32,7 +33,7 @@ use crate::execution_context::ExecutionContext;
|
||||
use crate::executor::Executor;
|
||||
use crate::step::Step;
|
||||
use crate::terminal::print_separator;
|
||||
use crate::utils::{require, PathExt};
|
||||
use crate::utils::{PathExt, require};
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
const INTEL_BREW: &str = "/usr/local/bin/brew";
|
||||
@@ -80,7 +81,6 @@ impl BrewVariant {
|
||||
/// Execute an "internal" brew command, i.e. one that should always be run
|
||||
/// even when dry-running. Basically just a wrapper around [`Command::new`]
|
||||
/// that uses `arch` to run using the correct architecture if needed.
|
||||
#[cfg(target_os = "macos")]
|
||||
fn execute_internal(self) -> Command {
|
||||
match self {
|
||||
BrewVariant::MacIntel if cfg!(target_arch = "aarch64") => {
|
||||
@@ -364,12 +364,48 @@ pub fn run_brew_formula(ctx: &ExecutionContext, variant: BrewVariant) -> Result<
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
pub fn run_brew_cask(ctx: &ExecutionContext, variant: BrewVariant) -> Result<()> {
|
||||
let binary_name = require(variant.binary_name())?;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
if variant.is_path() && !BrewVariant::is_macos_custom(binary_name) {
|
||||
return Err(SkipStep(t!("Not a custom brew for macOS").to_string()).into());
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
// Homebrew cask support was added in version 4.5.0
|
||||
let version_output = Command::new(&binary_name).arg("--version").output_checked_utf8()?;
|
||||
|
||||
let version_line = version_output
|
||||
.stdout
|
||||
.lines()
|
||||
.next()
|
||||
.ok_or_else(|| eyre!(output_changed_message!("brew --version", "no output lines")))?;
|
||||
|
||||
let version_str = version_line.split_whitespace().nth(1).ok_or_else(|| {
|
||||
eyre!(output_changed_message!(
|
||||
"brew --version",
|
||||
"Expected version after 'Homebrew'"
|
||||
))
|
||||
})?;
|
||||
|
||||
let version = Version::parse(version_str)
|
||||
.wrap_err_with(|| output_changed_message!("brew --version", "Invalid version"))?;
|
||||
|
||||
if version < Version::new(4, 5, 0) {
|
||||
return Err(SkipStep(
|
||||
t!(
|
||||
"Homebrew cask support on Linux requires Homebrew 4.5.0 or later (found {version})",
|
||||
version = version
|
||||
)
|
||||
.to_string(),
|
||||
)
|
||||
.into());
|
||||
}
|
||||
}
|
||||
|
||||
print_separator(format!("{} - Cask", variant.step_title()));
|
||||
|
||||
let cask_upgrade_exists = variant
|
||||
@@ -410,25 +446,88 @@ pub fn run_brew_cask(ctx: &ExecutionContext, variant: BrewVariant) -> Result<()>
|
||||
pub fn run_guix(ctx: &ExecutionContext) -> Result<()> {
|
||||
let guix = require("guix")?;
|
||||
|
||||
let output = Command::new(&guix).arg("pull").output_checked_utf8();
|
||||
debug!("guix pull output: {:?}", output);
|
||||
let should_upgrade = output.is_ok();
|
||||
debug!("Can Upgrade Guix: {:?}", should_upgrade);
|
||||
|
||||
print_separator("Guix");
|
||||
|
||||
if should_upgrade {
|
||||
return ctx.execute(&guix).args(["package", "-u"]).status_checked();
|
||||
ctx.execute(&guix).arg("pull").status_checked()?;
|
||||
ctx.execute(&guix).args(["package", "-u"]).status_checked()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct NixVersion {
|
||||
version_string: String,
|
||||
}
|
||||
|
||||
impl NixVersion {
|
||||
fn new(ctx: &ExecutionContext, nix: &Path) -> Result<Self> {
|
||||
let version_output = ctx.execute(nix).arg("--version").output_checked_utf8()?;
|
||||
|
||||
debug!(
|
||||
output=%version_output,
|
||||
"`nix --version` output"
|
||||
);
|
||||
|
||||
let version_string = version_output
|
||||
.stdout
|
||||
.lines()
|
||||
.next()
|
||||
.ok_or_else(|| eyre!("`nix --version` output is empty"))?
|
||||
.to_string();
|
||||
|
||||
if version_string.is_empty() {
|
||||
return Err(eyre!("`nix --version` output was empty"));
|
||||
}
|
||||
|
||||
Ok(Self { version_string })
|
||||
}
|
||||
|
||||
fn version(&self) -> Result<Version> {
|
||||
static NIX_VERSION_REGEX: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"^nix \([^)]*\) ([0-9.]+)").expect("Nix version regex always compiles"));
|
||||
|
||||
let captures = NIX_VERSION_REGEX
|
||||
.captures(&self.version_string)
|
||||
.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 {
|
||||
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);
|
||||
|
||||
Ok(version)
|
||||
}
|
||||
|
||||
fn is_lix(&self) -> bool {
|
||||
let is_lix = self.version_string.contains("Lix");
|
||||
debug!(?is_lix);
|
||||
is_lix
|
||||
}
|
||||
|
||||
fn is_determinate_nix(&self) -> bool {
|
||||
let is_determinate_nix = self.version_string.contains("Determinate Nix");
|
||||
debug!(?is_determinate_nix);
|
||||
is_determinate_nix
|
||||
}
|
||||
Err(SkipStep(t!("Guix Pull Failed, Skipping").to_string()).into())
|
||||
}
|
||||
|
||||
pub fn run_nix(ctx: &ExecutionContext) -> Result<()> {
|
||||
let nix = require("nix")?;
|
||||
let nix_channel = require("nix-channel")?;
|
||||
let nix_env = require("nix-env")?;
|
||||
// TODO: Is None possible here?
|
||||
let profile_path = match home::home_dir() {
|
||||
// TODO: Is None possible here? Should we use HOME_DIR instead?
|
||||
let profile_path = match home_dir() {
|
||||
Some(home) => XDG_DIRS
|
||||
.state_dir()
|
||||
.map(|d| d.join("nix/profile"))
|
||||
@@ -452,54 +551,11 @@ pub fn run_nix(ctx: &ExecutionContext) -> Result<()> {
|
||||
|
||||
ctx.execute(nix_channel).arg("--update").status_checked()?;
|
||||
|
||||
let mut get_version_cmd = ctx.execute(&nix);
|
||||
get_version_cmd.arg("--version");
|
||||
let get_version_cmd_output = get_version_cmd.output_checked_utf8()?;
|
||||
let get_version_cmd_first_line_stdout = get_version_cmd_output
|
||||
.stdout
|
||||
.lines()
|
||||
.next()
|
||||
.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 {
|
||||
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);
|
||||
let nix_version = NixVersion::new(ctx, &nix)?;
|
||||
|
||||
// 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 {
|
||||
let packages = if nix_version.version()? >= Version::new(2, 21, 0) && !nix_version.is_lix() {
|
||||
vec!["--all", "--impure"]
|
||||
} else {
|
||||
vec![".*"]
|
||||
@@ -550,22 +606,9 @@ pub fn run_nix_self_upgrade(ctx: &ExecutionContext) -> Result<()> {
|
||||
|
||||
print_separator(t!("Nix (self-upgrade)"));
|
||||
|
||||
let version_output = ctx.execute(&nix).arg("--version").output_checked_utf8()?;
|
||||
let version = version_output
|
||||
.stdout
|
||||
.lines()
|
||||
.next()
|
||||
.ok_or_else(|| eyre!("`nix --version` output is empty"))?;
|
||||
let nix_version = NixVersion::new(ctx, &nix)?;
|
||||
|
||||
let is_determinate_nix = version.contains("Determinate Nix");
|
||||
|
||||
debug!(
|
||||
output=%version_output,
|
||||
?is_determinate_nix,
|
||||
"`nix --version` output"
|
||||
);
|
||||
|
||||
if is_determinate_nix {
|
||||
if nix_version.is_determinate_nix() {
|
||||
let nixd = require("determinate-nixd");
|
||||
let nixd = match nixd {
|
||||
Err(_) => {
|
||||
@@ -796,7 +839,43 @@ pub fn run_mise(ctx: &ExecutionContext) -> Result<()> {
|
||||
|
||||
ctx.execute(&mise).args(["plugins", "update"]).status_checked()?;
|
||||
|
||||
ctx.execute(&mise).arg("upgrade").status_checked()
|
||||
let output = ctx
|
||||
.execute(&mise)
|
||||
.args(["self-update"])
|
||||
.output_checked_with(|_| Ok(()))?;
|
||||
let status_code = output
|
||||
.status
|
||||
.code()
|
||||
.ok_or_eyre("Couldn't get status code (terminated by signal)")?;
|
||||
let stderr = std::str::from_utf8(&output.stderr).wrap_err("Expected output to be valid UTF-8")?;
|
||||
if stderr.contains("mise is installed via a package manager") && status_code == 1 {
|
||||
debug!("Mise self-update not available")
|
||||
} else {
|
||||
// Write the output
|
||||
io::stdout().write_all(&output.stdout)?;
|
||||
io::stderr().write_all(&output.stderr)?;
|
||||
if status_code != 0 {
|
||||
return Err(StepFailed.into());
|
||||
}
|
||||
}
|
||||
|
||||
let mut cmd = ctx.execute(&mise);
|
||||
|
||||
cmd.arg("upgrade");
|
||||
|
||||
if ctx.config().mise_interactive() {
|
||||
cmd.arg("--interactive");
|
||||
}
|
||||
|
||||
if ctx.config().mise_bump() {
|
||||
cmd.arg("--bump");
|
||||
}
|
||||
|
||||
if ctx.config().mise_jobs() != 4 {
|
||||
cmd.args(["--jobs", &ctx.config().mise_jobs().to_string()]);
|
||||
}
|
||||
|
||||
cmd.status_checked()
|
||||
}
|
||||
|
||||
pub fn run_home_manager(ctx: &ExecutionContext) -> Result<()> {
|
||||
@@ -927,6 +1006,14 @@ pub fn run_maza(ctx: &ExecutionContext) -> Result<()> {
|
||||
ctx.execute(maza).arg("update").status_checked()
|
||||
}
|
||||
|
||||
pub fn run_hyprpm(ctx: &ExecutionContext) -> Result<()> {
|
||||
let hyprpm = require("hyprpm")?;
|
||||
|
||||
print_separator("hyprpm");
|
||||
|
||||
ctx.execute(hyprpm).arg("update").status_checked()
|
||||
}
|
||||
|
||||
pub fn run_atuin(ctx: &ExecutionContext) -> Result<()> {
|
||||
let atuin = require("atuin-update")?;
|
||||
|
||||
|
||||
@@ -254,11 +254,7 @@ pub fn microsoft_store(ctx: &ExecutionContext) -> Result<()> {
|
||||
}
|
||||
let ret_val = output.stdout.trim();
|
||||
debug!("Command return value: {}", ret_val);
|
||||
if ret_val == "0" {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
if ret_val == "0" { Ok(()) } else { Err(()) }
|
||||
})?;
|
||||
println!(
|
||||
"{}",
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
|
||||
use color_eyre::eyre::Result;
|
||||
#[cfg(windows)]
|
||||
use color_eyre::eyre::eyre;
|
||||
use color_eyre::eyre::Result;
|
||||
use tracing::debug;
|
||||
|
||||
use crate::command::CommandExt;
|
||||
use crate::execution_context::ExecutionContext;
|
||||
use crate::terminal;
|
||||
use crate::utils::{which, PathExt};
|
||||
use crate::utils::{PathExt, which};
|
||||
|
||||
pub struct Powershell {
|
||||
path: PathBuf,
|
||||
|
||||
@@ -2,20 +2,20 @@ use std::env;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
|
||||
use color_eyre::eyre::eyre;
|
||||
use color_eyre::eyre::Context;
|
||||
use color_eyre::eyre::Result;
|
||||
use color_eyre::eyre::eyre;
|
||||
use etcetera::base_strategy::BaseStrategy;
|
||||
|
||||
use crate::command::CommandExt;
|
||||
use crate::config::TmuxConfig;
|
||||
use crate::config::TmuxSessionMode;
|
||||
use crate::terminal::print_separator;
|
||||
use crate::{HOME_DIR, XDG_DIRS};
|
||||
use crate::{
|
||||
execution_context::ExecutionContext,
|
||||
utils::{which, PathExt},
|
||||
utils::{PathExt, which},
|
||||
};
|
||||
use crate::{HOME_DIR, XDG_DIRS};
|
||||
|
||||
use rust_i18n::t;
|
||||
#[cfg(unix)]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::HOME_DIR;
|
||||
use crate::command::CommandExt;
|
||||
use crate::error::{SkipStep, TopgradeError};
|
||||
use crate::HOME_DIR;
|
||||
use color_eyre::eyre::Result;
|
||||
use etcetera::base_strategy::BaseStrategy;
|
||||
|
||||
@@ -8,7 +8,7 @@ use crate::executor::{Executor, ExecutorOutput};
|
||||
use crate::terminal::print_separator;
|
||||
use crate::{
|
||||
execution_context::ExecutionContext,
|
||||
utils::{require, PathExt},
|
||||
utils::{PathExt, require},
|
||||
};
|
||||
use rust_i18n::t;
|
||||
use std::path::PathBuf;
|
||||
@@ -29,15 +29,32 @@ pub fn vimrc() -> Result<PathBuf> {
|
||||
|
||||
fn nvimrc() -> Result<PathBuf> {
|
||||
#[cfg(unix)]
|
||||
let base_dir = crate::XDG_DIRS.config_dir();
|
||||
let bases: Vec<PathBuf> = vec![crate::XDG_DIRS.config_dir()];
|
||||
|
||||
#[cfg(windows)]
|
||||
let base_dir = crate::WINDOWS_DIRS.cache_dir();
|
||||
let mut bases: Vec<PathBuf> = vec![crate::WINDOWS_DIRS.cache_dir()];
|
||||
|
||||
base_dir
|
||||
.join("nvim/init.vim")
|
||||
.require()
|
||||
.or_else(|_| base_dir.join("nvim/init.lua").require())
|
||||
#[cfg(windows)]
|
||||
{
|
||||
if let Some(xdg) = std::env::var_os("XDG_CONFIG_HOME")
|
||||
.map(PathBuf::from)
|
||||
.filter(|path| path.is_absolute())
|
||||
{
|
||||
bases.insert(0, xdg);
|
||||
}
|
||||
}
|
||||
|
||||
for base_dir in bases {
|
||||
if let Ok(p) = base_dir
|
||||
.join("nvim/init.vim")
|
||||
.require()
|
||||
.or_else(|_| base_dir.join("nvim/init.lua").require())
|
||||
{
|
||||
return Ok(p);
|
||||
}
|
||||
}
|
||||
|
||||
Err(SkipStep(format!("{}", t!("No Neovim config found"))).into())
|
||||
}
|
||||
|
||||
fn upgrade_script() -> Result<tempfile::NamedTempFile> {
|
||||
|
||||
@@ -6,13 +6,13 @@ use color_eyre::eyre::Result;
|
||||
use tracing::debug;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
use crate::HOME_DIR;
|
||||
use crate::XDG_DIRS;
|
||||
use crate::command::CommandExt;
|
||||
use crate::execution_context::ExecutionContext;
|
||||
use crate::git::RepoStep;
|
||||
use crate::terminal::print_separator;
|
||||
use crate::utils::{require, PathExt};
|
||||
use crate::HOME_DIR;
|
||||
use crate::XDG_DIRS;
|
||||
use crate::utils::{PathExt, require};
|
||||
use etcetera::base_strategy::BaseStrategy;
|
||||
|
||||
pub fn run_zr(ctx: &ExecutionContext) -> Result<()> {
|
||||
@@ -162,7 +162,7 @@ pub fn run_oh_my_zsh(ctx: &ExecutionContext) -> Result<()> {
|
||||
if let Ok(output) = res_env_zsh {
|
||||
let env_zsh = output.stdout;
|
||||
debug!("Oh-my-zsh: under SSH, setting ZSH={}", env_zsh);
|
||||
env::set_var("ZSH", env_zsh);
|
||||
unsafe { env::set_var("ZSH", env_zsh) };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,10 +4,10 @@ use std::path::PathBuf;
|
||||
|
||||
#[cfg(windows)]
|
||||
use color_eyre::eyre;
|
||||
#[cfg(windows)]
|
||||
use color_eyre::eyre::eyre;
|
||||
use color_eyre::eyre::Context;
|
||||
use color_eyre::eyre::Result;
|
||||
#[cfg(windows)]
|
||||
use color_eyre::eyre::eyre;
|
||||
use rust_i18n::t;
|
||||
use serde::Deserialize;
|
||||
use strum::Display;
|
||||
|
||||
@@ -8,7 +8,7 @@ use std::time::Duration;
|
||||
use chrono::{Local, Timelike};
|
||||
use color_eyre::eyre;
|
||||
use color_eyre::eyre::Context;
|
||||
use console::{style, Key, Term};
|
||||
use console::{Key, Term, style};
|
||||
use notify_rust::{Notification, Timeout};
|
||||
use rust_i18n::t;
|
||||
use tracing::{debug, error};
|
||||
@@ -201,10 +201,11 @@ impl Terminal {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
fn should_retry(&mut self, interrupted: bool, step_name: &str) -> eyre::Result<bool> {
|
||||
fn should_retry(&mut self, step_name: &str) -> eyre::Result<ShouldRetry> {
|
||||
if self.width.is_none() {
|
||||
return Ok(false);
|
||||
return Ok(ShouldRetry::No);
|
||||
}
|
||||
|
||||
if self.set_title {
|
||||
@@ -223,7 +224,7 @@ impl Terminal {
|
||||
|
||||
let answer = loop {
|
||||
match self.term.read_key() {
|
||||
Ok(Key::Char('y' | 'Y')) => break Ok(true),
|
||||
Ok(Key::Char('y' | 'Y')) => break Ok(ShouldRetry::Yes),
|
||||
Ok(Key::Char('s' | 'S')) => {
|
||||
println!(
|
||||
"\n\n{}\n",
|
||||
@@ -232,16 +233,16 @@ impl Terminal {
|
||||
if let Err(err) = run_shell().context("Failed to run shell") {
|
||||
self.term.write_fmt(format_args!("{err:?}\n{prompt_inner}")).ok();
|
||||
} else {
|
||||
break Ok(true);
|
||||
break Ok(ShouldRetry::Yes);
|
||||
}
|
||||
}
|
||||
Ok(Key::Char('n' | 'N') | Key::Enter) => break Ok(false),
|
||||
Ok(Key::Char('n' | 'N') | Key::Enter) => break Ok(ShouldRetry::No),
|
||||
Err(e) => {
|
||||
error!("Error reading from terminal: {}", e);
|
||||
break Ok(false);
|
||||
break Ok(ShouldRetry::No);
|
||||
}
|
||||
Ok(Key::Char('q' | 'Q')) => {
|
||||
return Err(io::Error::from(io::ErrorKind::Interrupted)).context("Quit from user input")
|
||||
break Ok(ShouldRetry::Quit);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
@@ -257,14 +258,21 @@ impl Terminal {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum ShouldRetry {
|
||||
Yes,
|
||||
No,
|
||||
Quit,
|
||||
}
|
||||
|
||||
impl Default for Terminal {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn should_retry(interrupted: bool, step_name: &str) -> eyre::Result<bool> {
|
||||
TERMINAL.lock().unwrap().should_retry(interrupted, step_name)
|
||||
pub fn should_retry(step_name: &str) -> eyre::Result<ShouldRetry> {
|
||||
TERMINAL.lock().unwrap().should_retry(step_name)
|
||||
}
|
||||
|
||||
pub fn print_separator<P: AsRef<str>>(message: P) {
|
||||
|
||||
12
src/utils.rs
12
src/utils.rs
@@ -11,8 +11,8 @@ use tracing::{debug, error};
|
||||
use tracing_subscriber::layer::SubscriberExt;
|
||||
use tracing_subscriber::reload::{Handle, Layer};
|
||||
use tracing_subscriber::util::SubscriberInitExt;
|
||||
use tracing_subscriber::{fmt, Registry};
|
||||
use tracing_subscriber::{registry, EnvFilter};
|
||||
use tracing_subscriber::{EnvFilter, registry};
|
||||
use tracing_subscriber::{Registry, fmt};
|
||||
|
||||
use crate::command::CommandExt;
|
||||
use crate::config::DEFAULT_LOG_LEVEL;
|
||||
@@ -151,7 +151,7 @@ pub fn string_prepend_str(string: &mut String, s: &str) {
|
||||
*string = new_string;
|
||||
}
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
#[cfg(unix)]
|
||||
pub fn hostname() -> Result<String> {
|
||||
match nix::unistd::gethostname() {
|
||||
Ok(os_str) => Ok(os_str
|
||||
@@ -161,7 +161,7 @@ pub fn hostname() -> Result<String> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_family = "windows")]
|
||||
#[cfg(windows)]
|
||||
pub fn hostname() -> Result<String> {
|
||||
Command::new("hostname")
|
||||
.output_checked_utf8()
|
||||
@@ -218,7 +218,7 @@ pub mod merge_strategies {
|
||||
where
|
||||
T: Merge,
|
||||
{
|
||||
if let Some(ref mut left_inner) = left {
|
||||
if let Some(left_inner) = left {
|
||||
if let Some(right_inner) = right {
|
||||
left_inner.merge(right_inner);
|
||||
}
|
||||
@@ -228,7 +228,7 @@ pub mod merge_strategies {
|
||||
}
|
||||
|
||||
pub fn commands_merge_opt(left: &mut Option<Commands>, right: Option<Commands>) {
|
||||
if let Some(ref mut left_inner) = left {
|
||||
if let Some(left_inner) = left {
|
||||
if let Some(right_inner) = right {
|
||||
left_inner.extend(right_inner);
|
||||
}
|
||||
|
||||
10
translate.sh
Executable file
10
translate.sh
Executable file
@@ -0,0 +1,10 @@
|
||||
#!/usr/bin/env bash
|
||||
## Translate the given string into $langs using translate-shell, outputting to the yaml structure expected for locales/app.yml
|
||||
|
||||
langs="en lt es fr zh_CN zh_TW de"
|
||||
|
||||
printf "\"%s\":\n" "$@"
|
||||
for lang in $langs; do
|
||||
result=$(trans -brief -no-auto -s en -t "${lang/_/-/}" "$@")
|
||||
printf " %s: \"%s\"\n" "$lang" "$result"
|
||||
done
|
||||
Reference in New Issue
Block a user