Compare commits

...

87 Commits

Author SHA1 Message Date
renovate[bot]
4afd872ec8 fix(deps): update rust crate home to v0.5.12 2025-11-15 16:07:16 +00:00
Ehren Bendler
9ec8e83f41 chore(deps): update some dependencies (#1512) 2025-11-15 17:05:48 +01:00
renovate[bot]
c70984d458 chore(deps): update github/codeql-action action to v4.31.3 (#1483)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-15 13:02:36 +01:00
Gideon
a3503c0c70 refactor: remove unnecessary cfg-if dependency (#1509) 2025-11-15 12:47:54 +01:00
Gideon
ca2d16edfd ci(lint_pr): run on synchronize, and add zizmor ignore (#1508) 2025-11-15 10:56:42 +01:00
pre-commit-ci[bot]
722b1ad09e chore(pre-commit): autoupdate (#1464)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Gideon <87426140+GideonBear@users.noreply.github.com>
2025-11-15 10:55:29 +01:00
Gideon
743845a66b fix(elan): skip running elan update on elan >=4.0.0 (#1507) 2025-11-15 10:46:18 +01:00
Gideon
2594f4c0fb docs: improve issue templates (#1235)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2025-11-15 10:07:55 +01:00
Gideon
639d055f9a feat: print summary and run post commands when (q)uit is used (#1254) 2025-11-15 10:03:46 +01:00
Gideon
ea2ccdd69f chore(deps): bump mac-notification-sys, use main branch temporarily (#1506) 2025-11-15 08:58:37 +01:00
Gideon
84a50afa83 chore: replace Dependabot with Renovate (#1486) 2025-11-15 08:22:27 +01:00
renovate[bot]
b13c1bd2d7 chore(deps): lock file maintenance (#1481)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: GideonBear <87426140+GideonBear@users.noreply.github.com>
2025-11-14 19:52:50 +01:00
Gideon
7749f41d56 fix(deps): Fix non-locked install on older version of Rust (#1485) 2025-11-14 19:33:56 +01:00
Gideon
593a2a33d9 fix(deps): Fix non-locked install on older version of Rust (#1482) 2025-11-14 19:20:12 +01:00
renovate[bot]
4f693aeaf3 chore(deps): pin dependencies (#1478)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-14 18:35:35 +01:00
renovate[bot]
c3d34184d0 chore(deps): update actions/dependency-review-action action to v4.8.2 (#1479)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-14 18:34:49 +01:00
Gideon
4aa224de87 chore: Add Renovate (#1477) 2025-11-14 18:23:17 +01:00
Andre Toerien
320b13c06b fix(bun): skip self-update if not installed via official script (#1476)
Co-authored-by: Steve Lau <stevelauc@outlook.com>
Co-authored-by: Gideon <87426140+GideonBear@users.noreply.github.com>
2025-11-14 08:51:31 +01:00
Izzy Meyer
907d778c55 fix(openbsd): fix compilation on OpenBSD (#1473)
Co-authored-by: Gideon <87426140+GideonBear@users.noreply.github.com>
2025-11-12 18:38:41 +01:00
Stuart Reilly
f3fccb86c0 refactor: Replace main's self update with a proper step call (#1470)
Co-authored-by: Stuart Reilly <sreilly@scottlogic.com>
2025-11-11 17:31:04 +01:00
Andre Toerien
bb4afb71e9 feat: run pre_sudo before pre_commands (#1469) 2025-11-11 16:17:00 +01:00
Gideon
ec8d30f634 ci(release): Fix homebrew releases (#1468) 2025-11-11 16:12:50 +01:00
Rubin Bhandari
50d318641a feat(chezmoi): add exclude_encrypted config (#1453)
Co-authored-by: Gideon <87426140+GideonBear@users.noreply.github.com>
2025-11-10 21:12:40 +01:00
github-actions[bot]
c5267f6087 chore: release v16.2.1 (#1462)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-11-10 20:07:08 +01:00
Gideon
d80e8f64d1 fix(release): Use bash in Windows to fix powershell issues (#1461) 2025-11-10 19:18:51 +01:00
Gideon
c6f2e0cc44 fix(release): Fix .deb distribution (#1460) 2025-11-10 19:18:29 +01:00
Gideon
99c3e8af26 fix(release): Fix .deb distribution (#1458) 2025-11-10 18:47:55 +01:00
github-actions[bot]
30d3537c0e chore: release v16.2.0 (#1407)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-11-10 18:31:28 +01:00
Gideon
90cb16e3d0 fix(release): fix homebrew releases by migrating to dawidd6/action-homebrew-bump-formula (#1457) 2025-11-10 18:27:32 +01:00
Gideon
5192a0f1dc fix(mise): fix mise self-update failing when installed via a package manager (#1456) 2025-11-10 18:20:40 +01:00
Gideon
bec7edf1fc fix(release): Add man page to .deb distribution (#1455) 2025-11-10 18:12:49 +01:00
Bodebojo
051784ac0d fix(self-update): fix windows self-update reporting failure on successful self-update (#1452) 2025-11-10 17:04:03 +01:00
PandaMimo
17d715479a feat(mise): run mise self-update (#1450)
Co-authored-by: Milo <milo@example.com>
2025-11-10 14:16:49 +01:00
Bodebojo
39a90f5ebe fix(pkgfile): make pkgfile opt-in (#1449)
Co-authored-by: Gideon <87426140+GideonBear@users.noreply.github.com>
2025-11-10 14:11:03 +01:00
PandaMimo
80c4bd5065 ref: comment run_config_update (#1448)
Co-authored-by: Milo <milo@example.com>
Co-authored-by: Gideon <87426140+GideonBear@users.noreply.github.com>
2025-11-10 13:44:17 +01:00
Bodebojo
222d800a32 fix(vcpkg): fix permission denied when updating vcpkg if it's installed as root (#1447) 2025-11-10 12:22:44 +01:00
Gideon
b29699fc55 docs: Expand LLM guidelines in CONTRIBUTING.md (#1445) 2025-11-09 09:13:27 +01:00
Oliver Tzeng
fc0e5461eb fix(zh_TW): fixed zh_TW strings (#1446) 2025-11-09 08:58:45 +01:00
Gideon
75da4a709c docs: Add AI guidelines to CONTRIBUTING.md (#1444) 2025-11-08 19:42:04 +01:00
Gideon
02e388122b ref: add comments to Config::allowed_steps (#1291) 2025-11-08 11:12:25 +01:00
Gideon
02fe1087de feat(falconf): add falconf step (#1219)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2025-11-08 11:10:45 +01:00
Gideon
34b7943fd1 fix(git): fix shellexpand::tilde in git_repos in topgrade.d/* (#1223) 2025-11-08 11:10:24 +01:00
Gideon
b61886f0f9 ref(nix): Deduplicate run_nix and run_nix_self_upgrade nix --version checking (#1376) 2025-11-08 11:07:01 +01:00
Gideon
d9a8ecfd33 ref: remove commented-out library code and unnecessary bin declaration (#1373) 2025-11-08 11:05:13 +01:00
Gideon
6c68bfaf64 ref: Simplify target cfgs (#1346) 2025-11-08 11:04:52 +01:00
Gideon
5866a0570f feat(hyprpm): add hyprpm step (#1213) 2025-11-08 11:04:19 +01:00
Gideon
28f5754efd feat(doom): add doom.aot option (#1214) 2025-11-08 11:03:08 +01:00
Gideon
ea1b286c98 fix(auto-cpufreq): skip when install script is not used (#1215) 2025-11-08 11:00:03 +01:00
Gideon
22ab77de6d feat: add show_distribution_summary config option (#1259) 2025-11-08 10:58:36 +01:00
Gideon
410bd61c75 ref: tidy up binary-conflict code (#1329) 2025-11-08 10:57:31 +01:00
Gideon
07b422915c docs: Improve installation section (#1442) 2025-11-07 12:12:52 +01:00
SteveCoding125
5b9d387ef3 fix(vim): change nvimrc base_dir for windows (#1433) 2025-11-06 11:07:19 +01:00
Gideon
5b5dd27834 fix(guix): fix overcomplicated Guix step (#1290) 2025-11-06 11:05:38 +01:00
Gideon
79f65981a5 fix(gem): fix incorrectly placed debug message in gem step (#1212) 2025-11-06 11:04:20 +01:00
Gideon
a52c775247 feat(rustup): add rustup.channels config (#1206) 2025-11-06 11:04:00 +01:00
Gideon
549111db3a fix(conda): replace deprecated auto_activate_base (#1158) 2025-11-06 11:03:13 +01:00
Gideon
1572974ec4 fix(containers): fix panic in containers step (#1150) 2025-11-06 10:25:52 +01:00
Gideon
8387468607 fix(jetbrains-toolbox): fix step not dry running (#1253) 2025-11-06 10:22:28 +01:00
Gideon
94979d6b7a chore(deps): Update jetbrains-toolbox-updater (#1438) 2025-11-05 17:32:11 +01:00
Daniel Hast
6652a2aa90 ci: remove template expansion in code contexts (#1434) 2025-11-04 20:54:03 +01:00
Mag Mell
f943b220d9 feat(os): add AOSC OS support (#1424) 2025-11-04 09:10:15 +01:00
dependabot[bot]
0fc7016d68 chore(deps): bump github/codeql-action from 4.31.0 to 4.31.2 (#1427)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-03 16:41:28 +01:00
Daniel Hast
9e9e6c9d55 ci: don't persist credentials in actions/checkout (#1422) 2025-11-03 12:29:07 +01:00
Gideon
bafa15c8f1 docs: Improve CONTRIBUTING.md (#1420) 2025-11-02 16:40:11 +01:00
Gideon
f669de8272 docs: Update SECURITY.md (#1421) 2025-11-02 15:59:45 +01:00
Gideon
ff26835406 chore(pre-commit): Run pre-commit (#1419) 2025-11-02 15:14:18 +01:00
Stuart Reilly
99892359c7 feat: add damp run type (#1217)
Co-authored-by: Stuart Reilly <sreilly@scottlogic.com>
2025-11-02 15:06:35 +01:00
Gideon
8fc25d7fd4 ci: Enforce conventional commits in PR titles (#1418) 2025-11-02 15:01:22 +01:00
GideonBear
942bfaa708 docs: Improve contributing section 2025-11-02 12:48:28 +01:00
GideonBear
a1fd324e82 docs: Remove roadmap 2025-11-02 12:48:28 +01:00
GideonBear
62bf2c6e90 docs: Reformat README.md 2025-11-02 12:48:28 +01:00
GideonBear
9f1e0c8eef docs: Update installation methods 2025-11-02 12:48:28 +01:00
Gideon
027de7c865 chore(release): Fix dispatch error in create_release_assets.yml (#1406) 2025-11-01 19:38:10 +01:00
github-actions[bot]
266adabd13 chore: release v16.1.2 (#1400)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-11-01 19:18:37 +01:00
Gideon
7d2e43f83b fix(release): Fix cross-compilation for arm requiring glibc>=2.39 (#1405) 2025-11-01 19:15:54 +01:00
Gideon
294a90a7c3 fix(release): Fix FreeBSD build (#1404) 2025-11-01 16:43:22 +01:00
Gideon
af5def1551 chore(release): Fix cross trying to fmt (#1403) 2025-11-01 16:30:56 +01:00
Gideon
8c63ee6a18 fix(release): Fix FreeBSD build (#1402) 2025-11-01 16:22:00 +01:00
Gideon
ee8ae8623d fix(release): Fix manual workflow trigger (#1401) 2025-11-01 16:19:31 +01:00
Gideon
222e6b55c0 fix(release): Fix FreeBSD build and add manual workflow trigger (#1399) 2025-11-01 16:03:15 +01:00
github-actions[bot]
2050a80665 chore: release v16.1.1 (#1392)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-11-01 15:12:40 +01:00
Gideon
e01be14041 fix(typst): Skip typst when self-update is disabled (#1397) 2025-11-01 14:53:26 +01:00
Gideon
dd6bc580fa fix(release): Fix winget release workflow (#1395) 2025-11-01 12:18:38 +01:00
Gideon
22ef36a185 chore: Update from deprecated macos-13 to macos-15-intel (#1394) 2025-11-01 12:04:53 +01:00
Gideon
b57ceccbe1 fix(release): Fix FreeBSD release (#1393) 2025-11-01 11:25:02 +01:00
Gideon
e9d430a4e4 fix(release): Fix FreeBSD release (#1391) 2025-11-01 11:22:24 +01:00
github-actions[bot]
8a247fba95 chore: release v16.1.0 (#1389)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2025-11-01 10:11:43 +01:00
52 changed files with 2682 additions and 1771 deletions

View File

@@ -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
-->

View File

@@ -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'
labels: C-feature request
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

29
.github/ISSUE_TEMPLATE/step_request.md vendored Normal file
View File

@@ -0,0 +1,29 @@
---
name: New step request
about: Suggest a new step/package manager to update
title: ''
labels: C-feature request, 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

View File

@@ -1,6 +1,5 @@
## What does this PR do
## Standards checklist
- [ ] The PR title is descriptive

View File

@@ -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"]

View File

@@ -15,8 +15,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5.0.0
- run: |
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- run: |
CONFIG_PATH=~/.config/topgrade.toml;
if [ -f "$CONFIG_PATH" ]; then rm $CONFIG_PATH; fi
cargo build;

View File

@@ -14,7 +14,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v5.0.0
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- name: Install checker
# Build it with the dev profile as this is faster and the checker still works

View File

@@ -24,12 +24,14 @@ jobs:
security-events: write
steps:
- name: Checkout code
uses: actions/checkout@v5.0.0
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.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@014f16e7ab1402f30e7c3329d33797e7948572db # v4.31.3
with:
sarif_file: devskim-results.sarif

View File

@@ -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@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.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@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.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@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.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}"

View File

@@ -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@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.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@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.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 }}

View File

@@ -17,6 +17,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: 'Checkout Repository'
uses: actions/checkout@v5.0.0
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.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
View 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 }}

View File

@@ -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@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
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@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
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:

View File

@@ -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

View File

@@ -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@3428a0601bba3173ec0bdcc945be23fa27aa4c31 # v5
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

View File

@@ -15,7 +15,10 @@ jobs:
matrix:
target: [x86_64, x86, aarch64]
steps:
- uses: actions/checkout@v5.0.0
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.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@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.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@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.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@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.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-*/*'

View File

@@ -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 }}

View File

@@ -36,7 +36,7 @@ jobs:
steps:
- name: "Checkout code"
uses: actions/checkout@v5.0.0
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.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@014f16e7ab1402f30e7c3329d33797e7948572db # v4.31.3
with:
sarif_file: results.sarif

View File

@@ -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

229
CHANGELOG.md Normal file
View File

@@ -0,0 +1,229 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [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
- *(deb-get)* Skip non-deb-get packages by passing --dg-only ([#1386](https://github.com/topgrade-rs/topgrade/pull/1386))
- *(typst)* add typst step ([#1374](https://github.com/topgrade-rs/topgrade/pull/1374))
- *(step)* Add atuin step ([#1367](https://github.com/topgrade-rs/topgrade/pull/1367))
- *(nix)* support upgrading Determinate Nix ([#1366](https://github.com/topgrade-rs/topgrade/pull/1366))
- *(sudo)* print warning if Windows Sudo is misconfigured
- *(sudo)* print warning if steps were skipped due to missing sudo
- *(sudo)* add SudoKind::Null
- detect and warn if running as root
- add `--no-tmux` flag ([#1328](https://github.com/topgrade-rs/topgrade/pull/1328))
- add step for mandb - user and system (update man entries) ([#1319](https://github.com/topgrade-rs/topgrade/pull/1319))
- support for pkgfile ([#1306](https://github.com/topgrade-rs/topgrade/pull/1306))
- add "show_skipped" option in config file #1280 ([#1286](https://github.com/topgrade-rs/topgrade/pull/1286))
- fix typos ([#1221](https://github.com/topgrade-rs/topgrade/pull/1221))
- *(conda)* allow configuring additional envs to update ([#1048](https://github.com/topgrade-rs/topgrade/pull/1048))
- *(step)* nix-helper ([#1045](https://github.com/topgrade-rs/topgrade/pull/1045))
- *(winget)* winget uses sudo when `[windows] winget_use_sudo = true` ([#1061](https://github.com/topgrade-rs/topgrade/pull/1061))
- suppress pixi release notes by default ([#1225](https://github.com/topgrade-rs/topgrade/pull/1225))
### Fixed
- *(freshclam)* run with sudo when running without sudo fails ([#1118](https://github.com/topgrade-rs/topgrade/pull/1118))
- *(tldr)* move tldr to be a generic step ([#1370](https://github.com/topgrade-rs/topgrade/pull/1370))
- *(nix)* fix nix upgrade command selection for profiles in XDG_STATE_HOME ([#1354](https://github.com/topgrade-rs/topgrade/pull/1354))
- *(containers)* Docker update fails on M Macs due to platform / ([#1360](https://github.com/topgrade-rs/topgrade/pull/1360))
- *(sudo)* reorder require_sudo() after print_separator()
- *(sudo)* use require_sudo for windows commands
- *(sudo)* prevent sudo_command = "sudo" finding gsudo
- *(sudo)* set sudo flags depending on kind
- skip gcloud update step if component manager is disabled ([#1237](https://github.com/topgrade-rs/topgrade/pull/1237))
- *(i18n)* use double-quotes for translations with newlines
- *(powershell)* run microsoft_store command directly
- *(powershell)* remove mentions of USOClient
- *(powershell)* execution policy check breaks when run in pwsh
- *(powershell)* don't use sudo with Update-Module for pwsh
- *(powershell)* add -Command to module update cmdline
- *(tmux)* support all default `tpm` locations (xdg and both hardcoded locations) ([#1146](https://github.com/topgrade-rs/topgrade/pull/1146))
- fixed the German translation for "y/n/s/q" ([#1220](https://github.com/topgrade-rs/topgrade/pull/1220))
### Other
- *(release)* switch to release-plz ([#1333](https://github.com/topgrade-rs/topgrade/pull/1333))
- *(pre-commit)* Make pre-commit.ci use conventional commits ([#1388](https://github.com/topgrade-rs/topgrade/pull/1388))
- *(pre-commit)* pre-commit autoupdate ([#1383](https://github.com/topgrade-rs/topgrade/pull/1383))
- *(deps)* bump actions/upload-artifact from 4.6.2 to 5.0.0 ([#1382](https://github.com/topgrade-rs/topgrade/pull/1382))
- *(deps)* bump github/codeql-action from 4.30.9 to 4.31.0 ([#1379](https://github.com/topgrade-rs/topgrade/pull/1379))
- *(deps)* bump actions/download-artifact from 5.0.0 to 6.0.0 ([#1380](https://github.com/topgrade-rs/topgrade/pull/1380))
- *(deps)* bump taiki-e/install-action from 2.62.33 to 2.62.38 ([#1381](https://github.com/topgrade-rs/topgrade/pull/1381))
- *(pre-commit)* Fix pre-commit-config.yaml ([#1378](https://github.com/topgrade-rs/topgrade/pull/1378))
- *(release)* Add .deb auto completion script ([#1353](https://github.com/topgrade-rs/topgrade/pull/1353))
- *(deps)* bump github/codeql-action from 4.30.8 to 4.30.9 ([#1369](https://github.com/topgrade-rs/topgrade/pull/1369))
- *(deps)* bump taiki-e/install-action from 2.62.28 to 2.62.33 ([#1368](https://github.com/topgrade-rs/topgrade/pull/1368))
- *(deps)* bump actions/dependency-review-action from 4.8.0 to 4.8.1 ([#1362](https://github.com/topgrade-rs/topgrade/pull/1362))
- *(deps)* bump softprops/action-gh-release from 2.3.4 to 2.4.1 ([#1364](https://github.com/topgrade-rs/topgrade/pull/1364))
- *(deps)* bump taiki-e/install-action from 2.62.21 to 2.62.28 ([#1363](https://github.com/topgrade-rs/topgrade/pull/1363))
- *(deps)* bump github/codeql-action from 3.30.6 to 4.30.8 ([#1365](https://github.com/topgrade-rs/topgrade/pull/1365))
- *(deps)* bump github/codeql-action from 3.30.5 to 3.30.6 ([#1355](https://github.com/topgrade-rs/topgrade/pull/1355))
- *(deps)* bump softprops/action-gh-release from 2.3.3 to 2.3.4 ([#1356](https://github.com/topgrade-rs/topgrade/pull/1356))
- *(deps)* bump taiki-e/install-action from 2.62.13 to 2.62.21 ([#1357](https://github.com/topgrade-rs/topgrade/pull/1357))
- *(deps)* bump ossf/scorecard-action from 2.4.2 to 2.4.3 ([#1358](https://github.com/topgrade-rs/topgrade/pull/1358))
- *(deps)* bump actions/dependency-review-action from 4.7.3 to 4.8.0 ([#1350](https://github.com/topgrade-rs/topgrade/pull/1350))
- *(deps)* bump github/codeql-action from 3.30.3 to 3.30.5 ([#1349](https://github.com/topgrade-rs/topgrade/pull/1349))
- *(deps)* bump taiki-e/install-action from 2.62.1 to 2.62.13 ([#1351](https://github.com/topgrade-rs/topgrade/pull/1351))
- *(deps)* bump actions/cache from 4.2.4 to 4.3.0 ([#1352](https://github.com/topgrade-rs/topgrade/pull/1352))
- Fix WSL distribution name cleanup ([#1348](https://github.com/topgrade-rs/topgrade/pull/1348))
- *(pyproject)* mark version as dynamic ([#1347](https://github.com/topgrade-rs/topgrade/pull/1347))
- *(deps)* replace winapi with windows
- *(sudo)* rename interactive to login_shell
- Fix "WSL already reported" panic ([#1344](https://github.com/topgrade-rs/topgrade/pull/1344))
- Move step logic out of Powershell struct ([#1345](https://github.com/topgrade-rs/topgrade/pull/1345))
- *(deps)* bump taiki-e/install-action from 2.61.5 to 2.62.1 ([#1335](https://github.com/topgrade-rs/topgrade/pull/1335))
- *(deps)* bump Swatinem/rust-cache from 2.8.0 to 2.8.1 ([#1336](https://github.com/topgrade-rs/topgrade/pull/1336))
- Fixes for #1188; custom_commands broken ([#1332](https://github.com/topgrade-rs/topgrade/pull/1332))
- use login shell when executing topgrade ([#1327](https://github.com/topgrade-rs/topgrade/pull/1327))
- *(deps)* bump taiki-e/install-action from 2.60.0 to 2.61.5 ([#1325](https://github.com/topgrade-rs/topgrade/pull/1325))
- *(deps)* bump github/codeql-action from 3.30.1 to 3.30.3 ([#1324](https://github.com/topgrade-rs/topgrade/pull/1324))
- *(pre-commit)* add typos with conservative excludes; no content changes ([#1317](https://github.com/topgrade-rs/topgrade/pull/1317))
- fix simple typos in code and comments (split var, whether, Extensions) ([#1318](https://github.com/topgrade-rs/topgrade/pull/1318))
- *(deps)* bump github/codeql-action from 3.29.11 to 3.30.1 ([#1301](https://github.com/topgrade-rs/topgrade/pull/1301))
- *(deps)* bump softprops/action-gh-release from 2.3.2 to 2.3.3 ([#1302](https://github.com/topgrade-rs/topgrade/pull/1302))
- *(deps)* bump taiki-e/install-action from 2.58.21 to 2.60.0 ([#1303](https://github.com/topgrade-rs/topgrade/pull/1303))
- *(deps)* bump actions/dependency-review-action from 4.7.2 to 4.7.3 ([#1304](https://github.com/topgrade-rs/topgrade/pull/1304))
- *(deps)* bump actions/attest-build-provenance from 2.4.0 to 3.0.0 ([#1305](https://github.com/topgrade-rs/topgrade/pull/1305))
- update tracing-subscriber to ~0.3.20 (ANSI escape injection fix, GHSA-xwfj-jgwm-7wp5) ([#1288](https://github.com/topgrade-rs/topgrade/pull/1288))
- *(deps)* bump github/codeql-action from 3.29.8 to 3.29.11 ([#1281](https://github.com/topgrade-rs/topgrade/pull/1281))
- *(deps)* bump actions/dependency-review-action from 4.7.1 to 4.7.2 ([#1282](https://github.com/topgrade-rs/topgrade/pull/1282))
- *(deps)* bump taiki-e/install-action from 2.58.9 to 2.58.21 ([#1283](https://github.com/topgrade-rs/topgrade/pull/1283))
- *(deps)* bump PyO3/maturin-action from 1.49.3 to 1.49.4 ([#1285](https://github.com/topgrade-rs/topgrade/pull/1285))
- *(deps)* bump actions/cache from 4.2.3 to 4.2.4 ([#1284](https://github.com/topgrade-rs/topgrade/pull/1284))
- Support "Insiders" versions of VSCode and VSCodium ([#1279](https://github.com/topgrade-rs/topgrade/pull/1279))
- Sudo preserve env list argument is `--preserve-env` ([#1276](https://github.com/topgrade-rs/topgrade/pull/1276))
- Clippy fixes from rust 1.91 nightly ([#1267](https://github.com/topgrade-rs/topgrade/pull/1267))
- *(deps)* bump actions/checkout from 4.2.2 to 5.0.0 ([#1264](https://github.com/topgrade-rs/topgrade/pull/1264))
- *(deps)* bump actions/download-artifact from 4.3.0 to 5.0.0 ([#1263](https://github.com/topgrade-rs/topgrade/pull/1263))
- *(deps)* bump taiki-e/install-action from 2.58.0 to 2.58.9 ([#1261](https://github.com/topgrade-rs/topgrade/pull/1261))
- *(deps)* bump ossf/scorecard-action from 2.4.0 to 2.4.2 ([#1262](https://github.com/topgrade-rs/topgrade/pull/1262))
- *(deps)* bump github/codeql-action from 3.29.5 to 3.29.8 ([#1265](https://github.com/topgrade-rs/topgrade/pull/1265))
- *(ci)* Dependabot, workflow security ([#1257](https://github.com/topgrade-rs/topgrade/pull/1257))
- replace once_cell crate with std equivalent ([#1260](https://github.com/topgrade-rs/topgrade/pull/1260))
- *(deps)* bump tokio from 1.38 to 1.47 ([#1256](https://github.com/topgrade-rs/topgrade/pull/1256))
- *(app.yml)* fix fr language #1248
- *(sudo)* add SudoKind::WinSudo
- *(sudo)* add SudoExecuteOpts builder functions and preserve_env enum
- *(yarn)* remove unnecessary Yarn::yarn field
- *(apt)* extract detect_apt() function
- route sudo usage through Sudo::execute*
- move RunType::execute to ExecutionContext
- *(powershell)* store powershell path directly
- *(powershell)* cleanup and simplify code
- Move step running into enum for dynamic ordering ([#1188](https://github.com/topgrade-rs/topgrade/pull/1188))
- Generate artifact attestations for release assets ([#1216](https://github.com/topgrade-rs/topgrade/pull/1216))
- windows update, use explicit reboot policy ([#1143](https://github.com/topgrade-rs/topgrade/pull/1143))
- add Discord invite link to README ([#1203](https://github.com/topgrade-rs/topgrade/pull/1203))
- Catch secondary uv self-update error ([#1201](https://github.com/topgrade-rs/topgrade/pull/1201))
- Handle another format change in asdf version ([#1194](https://github.com/topgrade-rs/topgrade/pull/1194))
- Preserve custom command order from config instead of sorting alphabetically ([#1182](https://github.com/topgrade-rs/topgrade/pull/1182))
- Add support for multiple binary names and idea having multiple binaries ([#1167](https://github.com/topgrade-rs/topgrade/pull/1167))
- fix the invalid action version ([#1185](https://github.com/topgrade-rs/topgrade/pull/1185))
- allow us to re-run AUR CI ([#1184](https://github.com/topgrade-rs/topgrade/pull/1184))
- Update Yazi upgrade step to use ya pkg. ([#1163](https://github.com/topgrade-rs/topgrade/pull/1163))
- use the new tag name and specify shell to bash ([#1183](https://github.com/topgrade-rs/topgrade/pull/1183))
- allow specifying tag when manually run 'create_release_assets.yml' ([#1180](https://github.com/topgrade-rs/topgrade/pull/1180))
- fix homebrew ci, remove duplicate trigger event ([#1179](https://github.com/topgrade-rs/topgrade/pull/1179))
- fix PyPI pipeline duplicate wheel name ([#1178](https://github.com/topgrade-rs/topgrade/pull/1178))
- add event workflow_dispatch to release pipelines ([#1177](https://github.com/topgrade-rs/topgrade/pull/1177))
- fix pipeline release to PyPI ([#1176](https://github.com/topgrade-rs/topgrade/pull/1176))
- Install rustfmt and clippy where necessary ([#1171](https://github.com/topgrade-rs/topgrade/pull/1171))

View File

@@ -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.

2442
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -6,41 +6,33 @@ keywords = ["upgrade", "update"]
license = "GPL-3.0"
repository = "https://github.com/topgrade-rs/topgrade"
rust-version = "1.84.1"
version = "16.0.4"
version = "16.2.1"
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"
readme = "README.md"
[[bin]]
name = "topgrade"
path = "src/main.rs"
##[lib]
##name = "topgrade_lib"
[dependencies]
home = "~0.5"
etcetera = "~0.8"
home = "=0.5.12"
etcetera = "=0.10.0"
serde = { version = "~1.0", features = ["derive"] }
toml = "0.8"
which_crate = { version = "~6.0", package = "which" }
toml = "=0.9.8"
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 +46,14 @@ 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"
# Temporary transitive dependency pins
ignore = "=0.4.23"
globset = "=0.4.16"
base64ct = "<1.8.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" }]
@@ -74,22 +74,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"

View File

@@ -11,7 +11,6 @@
<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
[![Packaging status](https://repology.org/badge/vertical-allrepos/topgrade.svg)](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)
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/)
- Fedora ([Copr](https://copr.fedorainfracloud.org/)): [
`dnf copr enable lilay/topgrade && dnf install topgrade`](https://copr.fedorainfracloud.org/coprs/lilay/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

View File

@@ -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.

View File

@@ -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,11 @@
# winget_use_sudo = true
[chezmoi]
# Exclude encrypted files from update
# (default: false)
# exclude_encrypted = false
[npm]
# Use sudo if the NPM directory isn't owned by the current user
# use_sudo = true
@@ -376,3 +393,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"]

View File

@@ -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"
@@ -942,13 +950,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 Managernpm"
zh_TW: "Node 套件管理員npm"
zh_TW: "Node Package Managernpm"
de: "Node Package Manager (npm)"
"Performant Node Package Manager":
en: "Performant Node Package Manager"
@@ -956,7 +972,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 Managerpnpm"
de: "Performanter Node Package Manager (pnpm)"
"Yarn Package Manager":
en: "Yarn Package Manager"
@@ -1012,7 +1028,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 +1062,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 +1116,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 +1126,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 +1168,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 +1176,7 @@ _version: 2
es: "Instale uno de `sudo`, `doas`, `pkexec`, `run0` o `please` para ejecutar estos pasos."
fr: "Installez lun 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 +1240,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 +1287,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 +1296,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 +1306,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 +1351,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 +1359,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 +1375,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"

10
renovate.json Normal file
View File

@@ -0,0 +1,10 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:best-practices",
":semanticCommits"
],
"lockFileMaintenance": {
"enabled": true
}
}

View File

@@ -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,

View File

@@ -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");
@@ -165,6 +165,13 @@ 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)]
@@ -346,6 +353,8 @@ pub struct Misc {
no_self_update: Option<bool>,
log_filters: Option<Vec<String>>,
show_distribution_summary: Option<bool>,
}
#[derive(Clone, Copy, Debug, Deserialize, ValueEnum)]
@@ -388,6 +397,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 +464,9 @@ 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)]
yarn: Option<Yarn>,
@@ -472,6 +502,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 +677,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)
}
@@ -708,9 +736,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,
@@ -933,16 +967,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 +993,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
}
@@ -1001,9 +1041,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.
@@ -1482,6 +1526,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 +1607,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 +1616,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 +1783,14 @@ 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 vscode_profile(&self) -> Option<&str> {
let vscode_cfg = self.config_file.vscode.as_ref()?;
let profile = vscode_cfg.profile.as_ref()?;
@@ -1734,6 +1801,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)]

View File

@@ -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"))
}
}

View File

@@ -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)),
}
}

View File

@@ -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::{debug, enabled, Level};
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()));
}
}

View File

@@ -1,2 +0,0 @@
//pub mod steps;
//pub mod utils;

View File

@@ -12,7 +12,6 @@ use clap::{crate_version, Parser};
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 +22,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::*, *};
@@ -124,7 +121,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 +160,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 +186,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 +195,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 +275,7 @@ fn run() -> Result<()> {
}
#[cfg(target_os = "linux")]
{
if config.show_distribution_summary() {
if let Ok(distribution) = &distribution {
distribution.show_summary();
}
@@ -335,13 +334,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>()

View File

@@ -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::{print_error, print_warning, should_retry, ShouldRetry};
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 => (),
}
}
}

View File

@@ -1,18 +1,19 @@
use std::env;
#[cfg(unix)]
use std::os::unix::process::CommandExt as _;
#[cfg(windows)]
use std::process::exit;
use std::process::Command;
use crate::step::Step;
use color_eyre::eyre::{bail, Result};
#[cfg(unix)]
use color_eyre::eyre::bail;
use color_eyre::eyre::Result;
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"));
}
}
}

View File

@@ -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,
@@ -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() {
@@ -767,6 +778,7 @@ pub(crate) fn default_steps() -> Vec<Step> {
Sdkman,
Rcm,
Maza,
Hyprpm,
Atuin,
]);
@@ -876,6 +888,7 @@ pub(crate) fn default_steps() -> Vec<Step> {
// JetBrains Space Desktop does not have a CLI
JetbrainsWebstorm,
Yazi,
Falconf,
Powershell,
CustomCommands,
Vagrant,

View File

@@ -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::{eyre, OptionExt};
use tracing::{debug, error, warn};
use wildmatch::WildMatch;
use crate::command::CommandExt;
use crate::error::{self, TopgradeError};
use crate::error::{self, 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,6 +150,41 @@ pub fn run_containers(ctx: &ExecutionContext) -> Result<()> {
debug!("Using container runtime '{}'", crt.display());
print_separator(t!("Containers"));
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 mut success = true;
let containers =
list_containers(&crt, ctx.config().containers_ignored_tags()).context("Failed to list Docker containers")?;

View File

@@ -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()
}

View File

@@ -5,6 +5,7 @@ use jetbrains_toolbox_updater::{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;
@@ -105,8 +106,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 +228,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 +263,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 +282,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 +321,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 +452,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()
@@ -601,11 +623,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 +889,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 +931,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 +998,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 +1132,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");
@@ -1555,9 +1580,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 +1640,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 +1663,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
@@ -1696,7 +1742,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 +1825,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()
}

View File

@@ -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));
}
}

View File

@@ -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,
@@ -161,6 +163,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 +200,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 +710,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 +1034,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 +1044,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 +1122,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 +1166,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);

View File

@@ -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;

View File

@@ -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)]

View File

@@ -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()?;

View 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"

View File

@@ -1,6 +1,6 @@
use color_eyre::eyre::eyre;
use color_eyre::eyre::Context;
use color_eyre::eyre::Result;
use color_eyre::eyre::{eyre, OptionExt};
use etcetera::BaseStrategy;
use home;
use ini::Ini;
@@ -10,13 +10,14 @@ use regex::Regex;
use rust_i18n::t;
use semver::Version;
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::command::CommandExt;
@@ -410,17 +411,80 @@ 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<()> {
@@ -452,54 +516,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 +571,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,6 +804,26 @@ pub fn run_mise(ctx: &ExecutionContext) -> Result<()> {
ctx.execute(&mise).args(["plugins", "update"]).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());
}
}
ctx.execute(&mise).arg("upgrade").status_checked()
}
@@ -927,6 +955,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")?;

View File

@@ -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> {

View File

@@ -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) {

View File

@@ -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()

10
translate.sh Executable file
View 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