Compare commits

...

23 Commits

Author SHA1 Message Date
renovate[bot]
ed59b3c261 fix(deps): update rust crate merge to ~0.2.0 2025-11-15 16:07:35 +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
28 changed files with 1429 additions and 1309 deletions

View File

@@ -14,52 +14,65 @@ Please make sure to
before filing a new one! before filing a new one!
Questions labeled with `Optional` can be skipped. Questions labeled with `Optional` can be skipped.
-->
<!--
If you're here to report about a "No asset found" error, please make sure that 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. 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 ## Erroneous Behavior
<!-- <!--
What actually happened? What actually happened?
--> -->
## Expected Behavior ## Expected Behavior
<!-- <!--
Describe the expected behavior Describe the expected behavior.
--> -->
## Steps to reproduce ## Steps to reproduce
<!-- <!--
A minimal example to reproduce the issue A minimal example to reproduce the issue.
--> -->
## Possible Cause (Optional) ## Possible Cause (Optional)
<!-- <!--
If you know the possible cause of the issue, please tell us. If you know the possible cause of the issue, please tell us.
--> -->
## Problem persists without calling from topgrade ## 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 - [ ] Yes
- [ ] No - [ ] No
## Did you run topgrade through `Remote Execution` ## Ran through `Remote Execution`
<!--
Did you run topgrade through `Remote Execution`?
-->
- [ ] Yes - [ ] Yes
- [ ] No - [ ] No
If yes, does the issue still occur when you run topgrade directly in your If yes, does the issue still occur when you run topgrade directly in your
remote host remote host?
- [ ] Yes - [ ] Yes
- [ ] No - [ ] No
## Configuration file (Optional) ## Configuration file (Optional)
<!-- <!--
Paste your configuration file inside the code block if you think this issue is Paste your configuration file inside the code block if you think this issue is
related to configuration. related to configuration.
@@ -70,6 +83,7 @@ related to configuration.
``` ```
## Additional Details ## Additional Details
- Operation System/Version - Operation System/Version
<!-- For example, Fedora Linux 38 --> <!-- For example, Fedora Linux 38 -->
@@ -82,6 +96,7 @@ related to configuration.
- Topgrade version (`topgrade -V`) - Topgrade version (`topgrade -V`)
## Verbose Output (`topgrade -v`) ## Verbose Output (`topgrade -v`)
<!-- <!--
Paste the verbose output into the pre-tags Paste the verbose output into the pre-tags
--> -->

View File

@@ -1,21 +1,15 @@
--- ---
name: Feature request name: General feature request
about: Can you please support...? about: Suggest a general feature, or feature within an already existing step
title: '' title: ''
labels: 'C-feature request' labels: C-feature request
assignees: '' assignees: ''
--- ---
## I want to suggest a new step ## Checklist
* Which tool is this about? Where is its repository? - [ ] I have searched the issue tracker for relevant or duplicate issues.
* 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 want to suggest some general feature ## I want to suggest some general feature
@@ -25,3 +19,5 @@ Topgrade should...
<!-- Assuming that someone else implements the feature, <!-- Assuming that someone else implements the feature,
please state if you know how to test it from a side branch of Topgrade. --> 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,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,7 +15,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v5.0.0 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
persist-credentials: false persist-credentials: false

View File

@@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v5.0.0 uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
persist-credentials: false persist-credentials: false

View File

@@ -24,7 +24,7 @@ jobs:
security-events: write security-events: write
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v5.0.0 uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
persist-credentials: false persist-credentials: false
@@ -32,6 +32,6 @@ jobs:
uses: microsoft/DevSkim-Action@4b5047945a44163b94642a1cecc0d93a3f428cc6 # v1.0.16 uses: microsoft/DevSkim-Action@4b5047945a44163b94642a1cecc0d93a3f428cc6 # v1.0.16
- name: Upload DevSkim scan results to GitHub Security tab - name: Upload DevSkim scan results to GitHub Security tab
uses: github/codeql-action/upload-sarif@v4.31.2 uses: github/codeql-action/upload-sarif@014f16e7ab1402f30e7c3329d33797e7948572db # v4.31.3
with: with:
sarif_file: devskim-results.sarif sarif_file: devskim-results.sarif

View File

@@ -23,7 +23,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v5.0.0 uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
persist-credentials: false persist-credentials: false
@@ -39,7 +39,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v5.0.0 uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
persist-credentials: false persist-credentials: false
@@ -135,7 +135,7 @@ jobs:
matrix_target: ${{ matrix.target }} matrix_target: ${{ matrix.target }}
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v5.0.0 uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
persist-credentials: false persist-credentials: false

View File

@@ -33,7 +33,7 @@ jobs:
env: env:
tag: ${{ github.event.client_payload.tag }} tag: ${{ github.event.client_payload.tag }}
steps: steps:
- uses: actions/checkout@v5.0.0 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
persist-credentials: false persist-credentials: false
@@ -137,7 +137,7 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Generate artifact attestations - name: Generate artifact attestations
uses: actions/attest-build-provenance@v3.0.0 uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # v3.0.0
with: with:
subject-path: assets/* subject-path: assets/*
@@ -169,7 +169,7 @@ jobs:
matrix_target: ${{ matrix.target }} matrix_target: ${{ matrix.target }}
tag: ${{ github.event.client_payload.tag }} tag: ${{ github.event.client_payload.tag }}
steps: steps:
- uses: actions/checkout@v5.0.0 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
persist-credentials: false persist-credentials: false
@@ -284,7 +284,7 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Generate artifact attestations - name: Generate artifact attestations
uses: actions/attest-build-provenance@v3.0.0 uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # v3.0.0
with: with:
subject-path: assets/* subject-path: assets/*

View File

@@ -17,9 +17,9 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: 'Checkout Repository' - name: 'Checkout Repository'
uses: actions/checkout@v5.0.0 uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
persist-credentials: false persist-credentials: false
- name: 'Dependency Review' - name: 'Dependency Review'
uses: actions/dependency-review-action@40c09b7dc99638e5ddb0bfd91c1673effc064d8a # v4.8.1 uses: actions/dependency-review-action@3c4e3dcb1aa7874d2c16be7d79418e9b7efd6261 # v4.8.2

View File

@@ -1,11 +1,12 @@
name: 'Lint PR' name: 'Lint PR'
on: on:
pull_request_target: pull_request_target: # zizmor: ignore[dangerous-triggers] this is the only way, and we're not running user code
types: types:
- opened - opened
- edited - edited
- reopened - reopened
- synchronize
jobs: jobs:
main: main:
@@ -14,6 +15,6 @@ jobs:
permissions: permissions:
pull-requests: read pull-requests: read
steps: steps:
- uses: amannn/action-semantic-pull-request@v6.1.1 - uses: amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50 # v6.1.1
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -17,7 +17,7 @@ jobs:
id-token: write # For trusted publishing id-token: write # For trusted publishing
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v5 uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
with: with:
fetch-depth: 0 fetch-depth: 0
persist-credentials: false persist-credentials: false
@@ -25,7 +25,7 @@ jobs:
uses: dtolnay/rust-toolchain@stable uses: dtolnay/rust-toolchain@stable
- name: Run release-plz - name: Run release-plz
id: release-plz id: release-plz
uses: release-plz/action@v0.5 uses: release-plz/action@d529f731ae3e89610ada96eda34e5c6ba3b12214 # v0.5
with: with:
command: release command: release
env: env:
@@ -53,14 +53,14 @@ jobs:
cancel-in-progress: false cancel-in-progress: false
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v5 uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
with: with:
fetch-depth: 0 fetch-depth: 0
persist-credentials: false persist-credentials: false
- name: Install Rust toolchain - name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable uses: dtolnay/rust-toolchain@stable
- name: Run release-plz - name: Run release-plz
uses: release-plz/action@v0.5 uses: release-plz/action@d529f731ae3e89610ada96eda34e5c6ba3b12214 # v0.5
with: with:
command: release-pr command: release-pr
env: env:

View File

@@ -18,4 +18,5 @@ jobs:
token: ${{secrets.HOMEBREW_ACCESS_TOKEN}} token: ${{secrets.HOMEBREW_ACCESS_TOKEN}}
formula: topgrade formula: topgrade
tag: ${{ github.event.client_payload.tag }} tag: ${{ github.event.client_payload.tag }}
org: topgrade-rs # 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,7 @@ jobs:
matrix: matrix:
target: [x86_64, x86, aarch64] target: [x86_64, x86, aarch64]
steps: steps:
- uses: actions/checkout@v5.0.0 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
persist-credentials: false persist-credentials: false
@@ -26,7 +26,7 @@ jobs:
args: --release --out dist args: --release --out dist
manylinux: auto manylinux: auto
- name: Upload wheels - name: Upload wheels
uses: actions/upload-artifact@v5.0.0 uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
with: with:
name: wheels-linux-${{ matrix.target }} name: wheels-linux-${{ matrix.target }}
path: dist path: dist
@@ -37,7 +37,7 @@ jobs:
matrix: matrix:
target: [x64, x86] target: [x64, x86]
steps: steps:
- uses: actions/checkout@v5.0.0 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
persist-credentials: false persist-credentials: false
@@ -47,7 +47,7 @@ jobs:
target: ${{ matrix.target }} target: ${{ matrix.target }}
args: --release --out dist args: --release --out dist
- name: Upload wheels - name: Upload wheels
uses: actions/upload-artifact@v5.0.0 uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
with: with:
name: wheels-windows-${{ matrix.target }} name: wheels-windows-${{ matrix.target }}
path: dist path: dist
@@ -58,7 +58,7 @@ jobs:
matrix: matrix:
target: [x86_64, aarch64] target: [x86_64, aarch64]
steps: steps:
- uses: actions/checkout@v5.0.0 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
persist-credentials: false persist-credentials: false
@@ -68,7 +68,7 @@ jobs:
target: ${{ matrix.target }} target: ${{ matrix.target }}
args: --release --out dist args: --release --out dist
- name: Upload wheels - name: Upload wheels
uses: actions/upload-artifact@v5.0.0 uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
with: with:
name: wheels-macos-${{ matrix.target }} name: wheels-macos-${{ matrix.target }}
path: dist path: dist
@@ -76,7 +76,7 @@ jobs:
sdist: sdist:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v5.0.0 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
persist-credentials: false persist-credentials: false
@@ -86,7 +86,7 @@ jobs:
command: sdist command: sdist
args: --out dist args: --out dist
- name: Upload sdist - name: Upload sdist
uses: actions/upload-artifact@v5.0.0 uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
with: with:
name: wheels-sdist name: wheels-sdist
path: dist path: dist
@@ -103,10 +103,10 @@ jobs:
# Used to generate artifact attestation # Used to generate artifact attestation
attestations: write attestations: write
steps: steps:
- uses: actions/download-artifact@v6.0.0 - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
- name: Generate artifact attestation - name: Generate artifact attestation
uses: actions/attest-build-provenance@v3.0.0 uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # v3.0.0
with: with:
subject-path: 'wheels-*/*' subject-path: 'wheels-*/*'

View File

@@ -36,7 +36,7 @@ jobs:
steps: steps:
- name: "Checkout code" - name: "Checkout code"
uses: actions/checkout@v5.0.0 uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
persist-credentials: false persist-credentials: false
@@ -63,7 +63,7 @@ jobs:
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
# format to the repository Actions tab. # format to the repository Actions tab.
- name: "Upload artifact" - name: "Upload artifact"
uses: actions/upload-artifact@v5.0.0 uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
with: with:
name: SARIF file name: SARIF file
path: results.sarif path: results.sarif
@@ -71,6 +71,6 @@ jobs:
# Upload the results to GitHub's code scanning dashboard. # Upload the results to GitHub's code scanning dashboard.
- name: "Upload to code-scanning" - name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@v4.31.2 uses: github/codeql-action/upload-sarif@014f16e7ab1402f30e7c3329d33797e7948572db # v4.31.3
with: with:
sarif_file: results.sarif sarif_file: results.sarif

View File

@@ -1,6 +1,6 @@
repos: repos:
- repo: https://github.com/gitleaks/gitleaks - repo: https://github.com/gitleaks/gitleaks
rev: v8.28.0 rev: v8.29.0
hooks: hooks:
- id: gitleaks - id: gitleaks
@@ -16,7 +16,7 @@ repos:
- id: trailing-whitespace - id: trailing-whitespace
- repo: https://github.com/crate-ci/typos - repo: https://github.com/crate-ci/typos
rev: v1.38.1 rev: v1.39.2
hooks: hooks:
- id: typos - id: typos

2275
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -14,32 +14,31 @@ edition = "2021"
readme = "README.md" readme = "README.md"
[dependencies] [dependencies]
home = "~0.5" home = "=0.5.11"
etcetera = "~0.8" etcetera = "=0.10.0"
serde = { version = "~1.0", features = ["derive"] } serde = { version = "~1.0", features = ["derive"] }
toml = "0.8" toml = "=0.9.8"
which_crate = { version = "~6.0", package = "which" } which_crate = { version = "~8.0", package = "which" }
shellexpand = "~3.1" shellexpand = "~3.1"
clap = { version = "~4.5", features = ["cargo", "derive"] } clap = { version = "~4.5", features = ["cargo", "derive"] }
clap_complete = "~4.5" clap_complete = "~4.5"
clap_mangen = "~0.2" clap_mangen = "~0.2"
walkdir = "~2.5" walkdir = "~2.5"
console = "~0.15" console = "~0.16"
chrono = "~0.4" chrono = "~0.4"
glob = "~0.3" glob = "~0.3"
strum = { version = "~0.26", features = ["derive"] } strum = { version = "~0.27", features = ["derive"] }
thiserror = "~1.0" thiserror = "~2.0"
tempfile = "~3.10" tempfile = "~3.23"
cfg-if = "~1.0" tokio = { version = "~1.48", features = ["process", "rt-multi-thread"] }
tokio = { version = "~1.47", features = ["process", "rt-multi-thread"] }
futures = "~0.3" futures = "~0.3"
regex = "~1.10" regex = "~1.12"
semver = "~1.0" semver = "~1.0"
shell-words = "~1.1" shell-words = "~1.1"
color-eyre = "~0.6" color-eyre = "~0.6"
tracing = { version = "~0.1", features = ["attributes", "log"] } tracing = { version = "~0.1", features = ["attributes", "log"] }
tracing-subscriber = { version = "~0.3.20", features = ["env-filter", "time"] } tracing-subscriber = { version = "~0.3.20", features = ["env-filter", "time"] }
merge = "~0.1" merge = "~0.2.0"
regex-split = "~0.1" regex-split = "~0.1"
notify-rust = "~4.11" notify-rust = "~4.11"
wildmatch = "2.3.0" wildmatch = "2.3.0"
@@ -48,6 +47,13 @@ sys-locale = "0.3.1"
jetbrains-toolbox-updater = "5.0.0" jetbrains-toolbox-updater = "5.0.0"
indexmap = { version = "2.9.0", features = ["serde"] } indexmap = { version = "2.9.0", features = ["serde"] }
serde_json = "1.0.145" 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] [package.metadata.generate-rpm]
assets = [{ source = "target/release/topgrade", dest = "/usr/bin/topgrade" }] assets = [{ source = "target/release/topgrade", dest = "/usr/bin/topgrade" }]
@@ -77,14 +83,14 @@ assets = [
] ]
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]
nix = { version = "~0.29", features = ["hostname", "signal", "user"] } nix = { version = "~0.30", features = ["hostname", "signal", "user"] }
rust-ini = "~0.21" 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] [target.'cfg(windows)'.dependencies]
is_elevated = "~0.1" is_elevated = "~0.1"
parselnk = "~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 = { version = "~0.62", features = ["Win32_System_Console"] }
windows-registry = "~0.6" windows-registry = "~0.6"

View File

@@ -286,6 +286,11 @@
# winget_use_sudo = true # winget_use_sudo = true
[chezmoi]
# Exclude encrypted files from update
# (default: false)
# exclude_encrypted = false
[npm] [npm]
# Use sudo if the NPM directory isn't owned by the current user # Use sudo if the NPM directory isn't owned by the current user
# use_sudo = true # use_sudo = true

View File

@@ -1306,14 +1306,6 @@ _version: 2
zh_CN: "Windows 更新" zh_CN: "Windows 更新"
zh_TW: "Windows 更新" zh_TW: "Windows 更新"
de: "Windows-Update" de: "Windows-Update"
"Checking if /etc/motd contains -current or -beta":
en: "Checking if /etc/motd contains -current or -beta"
lt: "Tikrinimas, jei /etc/motd yra -current arba -beta"
es: "Comprobación de si /etc/motd contiene -current o -beta"
fr: "Vérification si /etc/motd contient -current ou -beta"
zh_CN: "检查 /etc/motd 是否包含 -current 或 -beta"
zh_TW: "檢查 /etc/motd 是否包含 -current 或 -beta"
de: "Überprüfen, ob /etc/motd -current oder -beta enthält"
"Microsoft Store": "Microsoft Store":
en: "Microsoft Store" en: "Microsoft Store"
lt: "Microsoft parduotuvė" lt: "Microsoft parduotuvė"

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

@@ -165,6 +165,13 @@ pub struct Deno {
version: Option<String>, 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)] #[derive(Deserialize, Default, Debug, Merge)]
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]
#[allow(clippy::upper_case_acronyms)] #[allow(clippy::upper_case_acronyms)]
@@ -457,6 +464,9 @@ pub struct ConfigFile {
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)] #[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
npm: Option<NPM>, npm: Option<NPM>,
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
chezmoi: Option<Chezmoi>,
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)] #[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
yarn: Option<Yarn>, yarn: Option<Yarn>,
@@ -1597,8 +1607,7 @@ impl Config {
self.config_file.misc.as_ref().and_then(|misc| misc.sudo_command) 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 /// If `true`, `sudo -v` should be called to cache credentials at the start of the run
/// start of the session (and not in the middle).
pub fn pre_sudo(&self) -> bool { pub fn pre_sudo(&self) -> bool {
self.config_file self.config_file
.misc .misc
@@ -1774,6 +1783,14 @@ impl Config {
.unwrap_or(false) .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> { pub fn vscode_profile(&self) -> Option<&str> {
let vscode_cfg = self.config_file.vscode.as_ref()?; let vscode_cfg = self.config_file.vscode.as_ref()?;
let profile = vscode_cfg.profile.as_ref()?; let profile = vscode_cfg.profile.as_ref()?;

View File

@@ -12,7 +12,6 @@ use clap::{crate_version, Parser};
use color_eyre::eyre::Context; use color_eyre::eyre::Context;
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use console::Key; use console::Key;
use etcetera::base_strategy::BaseStrategy;
#[cfg(windows)] #[cfg(windows)]
use etcetera::base_strategy::Windows; use etcetera::base_strategy::Windows;
#[cfg(unix)] #[cfg(unix)]
@@ -187,17 +186,7 @@ fn run() -> Result<()> {
} }
} }
// Self-Update step, this will execute only if: step::Step::SelfUpdate.run(&mut runner, &ctx)?;
// 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))?;
}
}
#[cfg(windows)] #[cfg(windows)]
let _self_rename = if config.self_rename() { let _self_rename = if config.self_rename() {
@@ -206,20 +195,32 @@ fn run() -> Result<()> {
None 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 config.pre_sudo() {
if let Some(sudo) = ctx.sudo() { if let Some(sudo) = ctx.sudo() {
sudo.elevate(&ctx)?; 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() { 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; let mut failed = false;

View File

@@ -1,14 +1,15 @@
use color_eyre::eyre::Result; use color_eyre::eyre::{Result, WrapErr};
use rust_i18n::t; use rust_i18n::t;
use std::borrow::Cow; use std::borrow::Cow;
use std::fmt::Debug; use std::fmt::Debug;
use std::io;
use tracing::debug; use tracing::debug;
use crate::ctrlc; use crate::ctrlc;
use crate::error::{DryRun, MissingSudo, SkipStep}; use crate::error::{DryRun, MissingSudo, SkipStep};
use crate::execution_context::ExecutionContext; use crate::execution_context::ExecutionContext;
use crate::step::Step; 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 { pub enum StepResult {
Success, Success,
@@ -98,21 +99,28 @@ impl<'a> Runner<'a> {
let should_ask = interrupted || !(self.ctx.config().no_retry() || ignore_failure); let should_ask = interrupted || !(self.ctx.config().no_retry() || ignore_failure);
let should_retry = if should_ask { let should_retry = if should_ask {
print_error(&key, format!("{e:?}")); print_error(&key, format!("{e:?}"));
should_retry(interrupted, key.as_ref())? should_retry(key.as_ref())?
} else { } else {
false ShouldRetry::No
}; };
if !should_retry { match should_retry {
self.push_result( ShouldRetry::No | ShouldRetry::Quit => {
key, self.push_result(
if ignore_failure { key,
StepResult::Ignored if ignore_failure {
} else { StepResult::Ignored
StepResult::Failure } else {
}, StepResult::Failure
); },
break; );
if let ShouldRetry::Quit = should_retry {
return Err(io::Error::from(io::ErrorKind::Interrupted))
.context("Quit from user input");
}
break;
}
ShouldRetry::Yes => (),
} }
} }
} }

View File

@@ -544,6 +544,9 @@ impl Step {
runner.execute(*self, "SDKMAN!", || unix::run_sdkman(ctx))? runner.execute(*self, "SDKMAN!", || unix::run_sdkman(ctx))?
} }
SelfUpdate => { 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")] #[cfg(feature = "self-update")]
{ {
if std::env::var("TOPGRADE_NO_SELF_UPGRADE").is_err() && !ctx.config().no_self_update() { if std::env::var("TOPGRADE_NO_SELF_UPGRADE").is_err() && !ctx.config().no_self_update() {

View File

@@ -282,6 +282,17 @@ pub fn run_elan(ctx: &ExecutionContext) -> Result<()> {
print_separator("elan"); 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 disabled_error_msg = "self-update is disabled";
let executor_output = ctx.execute(&elan).args(["self", "update"]).output()?; let executor_output = ctx.execute(&elan).args(["self", "update"]).output()?;
match executor_output { match executor_output {
@@ -310,7 +321,12 @@ pub fn run_elan(ctx: &ExecutionContext) -> Result<()> {
ExecutorOutput::Dry => { /* nothing needed because in a dry run */ } 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<()> { pub fn run_juliaup(ctx: &ExecutionContext) -> Result<()> {
@@ -873,12 +889,11 @@ pub fn run_tldr(ctx: &ExecutionContext) -> Result<()> {
} }
pub fn run_tlmgr_update(ctx: &ExecutionContext) -> Result<()> { pub fn run_tlmgr_update(ctx: &ExecutionContext) -> Result<()> {
cfg_if::cfg_if! { if cfg!(any(target_os = "linux", target_os = "android")) && !ctx.config().enable_tlmgr_linux() {
if #[cfg(any(target_os = "linux", target_os = "android"))] { return Err(SkipStep(String::from(
if !ctx.config().enable_tlmgr_linux() { "tlmgr must be explicitly enabled in the configuration to run in Android/Linux",
return Err(SkipStep(String::from("tlmgr must be explicitly enabled in the configuration to run in Android/Linux")).into()); ))
} .into());
}
} }
let tlmgr = require("tlmgr")?; let tlmgr = require("tlmgr")?;
@@ -916,9 +931,17 @@ pub fn run_chezmoi_update(ctx: &ExecutionContext) -> Result<()> {
let chezmoi = require("chezmoi")?; let chezmoi = require("chezmoi")?;
HOME_DIR.join(".local/share/chezmoi").require()?; HOME_DIR.join(".local/share/chezmoi").require()?;
let mut cmd = ctx.execute(chezmoi);
print_separator("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<()> { pub fn run_myrepos_update(ctx: &ExecutionContext) -> Result<()> {
@@ -975,23 +998,19 @@ pub fn run_composer_update(ctx: &ExecutionContext) -> Result<()> {
print_separator(t!("Composer")); print_separator(t!("Composer"));
if ctx.config().composer_self_update() { if ctx.config().composer_self_update() {
cfg_if::cfg_if! { if cfg!(unix) {
if #[cfg(unix)] { // If self-update fails without sudo then there's probably an update
// If self-update fails without sudo then there's probably an update let has_update = match ctx.execute(&composer).arg("self-update").output()? {
let has_update = match ctx.execute(&composer).arg("self-update").output()? { ExecutorOutput::Wet(output) => !output.status.success(),
ExecutorOutput::Wet(output) => !output.status.success(), _ => false,
_ => false };
};
if has_update { if has_update {
let sudo = ctx.require_sudo()?; let sudo = ctx.require_sudo()?;
sudo.execute(ctx, &composer)? sudo.execute(ctx, &composer)?.arg("self-update").status_checked()?;
.arg("self-update")
.status_checked()?;
}
} else {
ctx.execute(&composer).arg("self-update").status_checked()?;
} }
} else {
ctx.execute(&composer).arg("self-update").status_checked()?;
} }
} }
@@ -1561,9 +1580,25 @@ pub fn run_zvm(ctx: &ExecutionContext) -> Result<()> {
pub fn run_bun(ctx: &ExecutionContext) -> Result<()> { pub fn run_bun(ctx: &ExecutionContext) -> Result<()> {
let bun = require("bun")?; 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<()> { pub fn run_zigup(ctx: &ExecutionContext) -> Result<()> {

View File

@@ -1,20 +1,17 @@
use crate::command::CommandExt; use crate::command::CommandExt;
use crate::execution_context::ExecutionContext; use crate::execution_context::ExecutionContext;
use crate::executor::RunType;
use crate::terminal::print_separator; use crate::terminal::print_separator;
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use rust_i18n::t; use rust_i18n::t;
use std::fs; 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 motd_content = fs::read_to_string("/etc/motd")?;
let is_current = ["-current", "-beta"].iter().any(|&s| motd_content.contains(s)); let is_current = ["-current", "-beta"].iter().any(|&s| motd_content.contains(s));
match ctx.config.run_type() {
RunType::Dry | RunType::Damp => { debug!("OpenBSD is -current/-beta: {is_current}");
println!("{}", t!("Checking if /etc/motd contains -current or -beta"));
}
RunType::Wet => {}
}
Ok(is_current) Ok(is_current)
} }
@@ -23,12 +20,7 @@ pub fn upgrade_openbsd(ctx: &ExecutionContext) -> Result<()> {
let sudo = ctx.require_sudo()?; let sudo = ctx.require_sudo()?;
let is_current = is_openbsd_current(ctx)?; let is_current = is_openbsd_current()?;
if ctx.config().dry_run() {
println!("{}", t!("Would upgrade the OpenBSD system"));
return Ok(());
}
if is_current { if is_current {
sudo.execute(ctx, "/usr/sbin/sysupgrade")?.arg("-sn").status_checked() sudo.execute(ctx, "/usr/sbin/sysupgrade")?.arg("-sn").status_checked()
@@ -42,7 +34,7 @@ pub fn upgrade_packages(ctx: &ExecutionContext) -> Result<()> {
let sudo = ctx.require_sudo()?; let sudo = ctx.require_sudo()?;
let is_current = is_openbsd_current(ctx)?; let is_current = is_openbsd_current()?;
if ctx.config().cleanup() { if ctx.config().cleanup() {
sudo.execute(ctx, "/usr/sbin/pkg_delete")?.arg("-ac").status_checked()?; sudo.execute(ctx, "/usr/sbin/pkg_delete")?.arg("-ac").status_checked()?;

View File

@@ -201,10 +201,11 @@ impl Terminal {
} }
} }
} }
#[allow(unused_variables)] #[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() { if self.width.is_none() {
return Ok(false); return Ok(ShouldRetry::No);
} }
if self.set_title { if self.set_title {
@@ -223,7 +224,7 @@ impl Terminal {
let answer = loop { let answer = loop {
match self.term.read_key() { 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')) => { Ok(Key::Char('s' | 'S')) => {
println!( println!(
"\n\n{}\n", "\n\n{}\n",
@@ -232,16 +233,16 @@ impl Terminal {
if let Err(err) = run_shell().context("Failed to run shell") { if let Err(err) = run_shell().context("Failed to run shell") {
self.term.write_fmt(format_args!("{err:?}\n{prompt_inner}")).ok(); self.term.write_fmt(format_args!("{err:?}\n{prompt_inner}")).ok();
} else { } 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) => { Err(e) => {
error!("Error reading from terminal: {}", e); error!("Error reading from terminal: {}", e);
break Ok(false); break Ok(ShouldRetry::No);
} }
Ok(Key::Char('q' | 'Q')) => { 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 { impl Default for Terminal {
fn default() -> Self { fn default() -> Self {
Self::new() Self::new()
} }
} }
pub fn should_retry(interrupted: bool, step_name: &str) -> eyre::Result<bool> { pub fn should_retry(step_name: &str) -> eyre::Result<ShouldRetry> {
TERMINAL.lock().unwrap().should_retry(interrupted, step_name) TERMINAL.lock().unwrap().should_retry(step_name)
} }
pub fn print_separator<P: AsRef<str>>(message: P) { pub fn print_separator<P: AsRef<str>>(message: P) {