Skip to content

feat(install): add uninstall scripts - #302

Merged
CalvinAllen merged 2 commits into
mainfrom
feat/install/uninstall-scripts
Nov 7, 2026
Merged

CalvinAllen merged 2 commits into
mainfrom
feat/install/uninstall-scripts

Conversation

@CalvinAllen

Copy link
Copy Markdown
Contributor

Description

Adds uninstall.sh (macOS/Linux) and uninstall.ps1 (Windows), the counterparts of the install scripts. They remove everything dtvem puts on a machine, after listing it and asking for confirmation (--yes / -Yes skips the prompt):

curl -fsSL dtvem.io/uninstall.sh | bash
irm dtvem.io/uninstall.ps1 | iex

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Code refactoring (no functional changes)
  • Performance improvement
  • Test coverage improvement
  • CI/CD or build process change

Related Issues

None.

Changes Made

  • uninstall.sh / uninstall.ps1 remove:
    • every dtvem root: DTVEM_ROOT, $XDG_DATA_HOME/dtvem, ~/.local/share/dtvem, ~/.dtvem, including all installed runtime versions and their global packages
    • the PATH changes made by the installers and dtvem init:
      • Unix: only the # Added by dtvem / # Added by dtvem installer blocks in shell config files. Other lines that mention dtvem are listed, never edited. Config files that are symlinks stay symlinks, and CRLF files are handled.
      • Windows: User and System PATH entries. PATH is written back as REG_EXPAND_SZ (as writePathValue does), so %VAR% entries keep working. If the System PATH needs editing, there is one UAC prompt.
    • Windows only: what RubyInstaller registered when older dtvem versions installed Ruby with its .exe. That means Programs and Features entries, Start menu folders, PATH entries and the .rb/.rbw associations, all pointing at a deleted %TEMP%\dtvem-ruby-<version>.
  • Safety:
    • The scripts refuse /, a drive root and the home directory.
    • A custom DTVEM_ROOT not named dtvem/.dtvem only loses dtvem's own entries (shims, versions, config, cache, tmp, bin/dtvem*).
    • Another dtvem install that still exists is left alone.
    • Links and junctions inside the root (such as npm link targets) are removed without touching what they point to. On Windows this uses rd /s /q, because Remove-Item in PowerShell 5.1 can follow a junction, and .NET's Directory.Delete fails on read-only files.
  • script-lint.yml: lints the uninstall scripts with ShellCheck and PSScriptAnalyzer.
  • integration-test-uninstall.yml (new):
    • Installs dtvem as the installers and dtvem init do.
    • Adds the tricky cases: read-only files, links leading out of the root, paths over 260 characters, a stale PATH entry, and fake RubyInstaller leftovers.
    • Runs the uninstaller, checks nothing is left and nothing else was touched, then runs it again.
    • Covers Ubuntu, macOS with the stock /bin/bash 3.2, and Windows under both pwsh and Windows PowerShell 5.1.
  • release.yml: publishes uninstall.sh and uninstall.ps1 as release assets next to the install scripts.
  • README and CLAUDE.md: document the uninstallers.

Testing

Test Environment

  • OS: Windows 11 (Git Bash 5.2, PowerShell 7.6, Windows PowerShell 5.1)
  • Go Version: 1.25.4
  • dtvem Version: dev

Test Steps

  1. uninstall.sh: 15 checks with a fake HOME under Git Bash.
    • Asking without a terminal fails and removes nothing.
    • Default, XDG and custom roots are removed. DTVEM_ROOT=$HOME is refused.
    • Blocks for stale roots are removed; blocks for other existing roots are kept.
    • User-written lines are kept, and CRLF config files are handled.
  2. uninstall.ps1: 31 checks of its functions under both PowerShell 7.6 and 5.1.
    • Deleting a root works through junctions, read-only files and paths over 260 characters, and keeps junction targets.
    • A custom root keeps files dtvem didn't create.
    • Registry removals were run against a throwaway HKCU key.
    • Start menu shortcut detection works.
    • The generated elevated script parses, and quoting survives ' and ‘’ in paths.
    • An end-to-end run started from inside the root succeeds.
  3. Read-only dry run on a real machine (non-interactive, so it can't confirm). It listed ~/.dtvem and its PATH entries, plus the RubyInstaller leftovers from an old dtvem Ruby install: two Programs and Features entries, two Start menu folders, the .rb/.rbw association and two dead System PATH entries. Nothing was removed.
  4. ./rnr check passes.

Test Results

uninstall.sh tests:            OVERALL: PASS
uninstall.ps1 tests (7.6/5.1): OVERALL: PASS
./rnr check:                   0 issues, all packages ok

Checklist

  • My code follows the Google Go Style Guide (no Go changes)
  • I have run golangci-lint run and fixed all issues (includes gofmt, govet, and 40+ linters)
  • I have run go mod tidy to clean up dependencies (no dependency changes)
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes (go test ./...)
  • I have commented my code, particularly in hard-to-understand areas
  • I have updated the documentation (README.md, CONTRIBUTING.md, etc.) if needed
  • My changes generate no new warnings or errors
  • I have checked my code works on multiple platforms (Windows, macOS, Linux) if applicable (Windows locally; Linux and macOS through the new integration test)
  • I have added appropriate error handling
  • I have followed conventional commit format in my commit messages

Breaking Changes

N/A

Additional Notes

  • Not run locally: ShellCheck and PSScriptAnalyzer. This PR's script-lint.yml run is the first lint.
  • Not covered by CI: the UAC elevation path. The Windows runner is already an administrator, so the integration test covers the in-process System PATH path only.
  • When the dtvem.io/uninstall.* URLs work: after a release that includes the new assets, and after the dtvem.io worker redirects /uninstall.sh and /uninstall.ps1 (in progress in the dtvem.io repo). Until then, the raw GitHub main URLs in the scripts' headers work.
  • Why uninstall.ps1 is ASCII-only: Windows PowerShell 5.1 reads a script file without a BOM in the ANSI code page, where the UTF-8 bytes of → ✓ ✗ include quote characters. install.ps1 has this problem today when run with -File on 5.1 (irm | iex is unaffected). That's worth a separate fix.

By submitting this pull request, I confirm that my contribution is made under the terms of the project's MIT License and I agree to follow the Code of Conduct.

Add uninstall.sh (macOS/Linux) and uninstall.ps1 (Windows) to remove
everything dtvem puts on a machine:

- every dtvem root (DTVEM_ROOT, XDG_DATA_HOME/dtvem,
  ~/.local/share/dtvem, ~/.dtvem), with all installed runtime versions
- the PATH changes made by the installers and `dtvem init`: the marked
  blocks in shell config files on Unix, and the User and System PATH
  entries on Windows (REG_EXPAND_SZ and %VAR% entries preserved, one
  UAC prompt when the System PATH needs editing)
- on Windows, what RubyInstaller registered when older dtvem versions
  used its .exe to install Ruby: Programs and Features entries, Start
  menu folders, PATH entries and the .rb/.rbw file associations

Both scripts list what they will remove and ask first (--yes / -Yes
skips the prompt). They refuse /, a drive root and the home directory,
only remove dtvem's own entries from a custom DTVEM_ROOT, and never
follow links or junctions out of the root.

Lint both scripts in script-lint.yml, publish them as release assets,
and add an integration test that installs dtvem as the installers do,
uninstalls it, and checks nothing is left on Linux, macOS (bash 3.2),
and Windows (pwsh and Windows PowerShell 5.1).
The Windows uninstall test expected the System PATH to still be
REG_EXPAND_SZ afterwards, but GitHub's Windows runner images store it
as REG_SZ with no %VAR% entries, so the check failed although the
uninstaller kept the value's type.

Make the System PATH REG_EXPAND_SZ with a %SystemRoot% entry before
`dtvem init`, log the type it had, and check the uninstaller keeps both
the type and the unexpanded entry, as the User PATH check already does.
@CalvinAllen
CalvinAllen merged commit 52ae214 into main Nov 7, 2026
7 checks passed
@CalvinAllen
CalvinAllen deleted the feat/install/uninstall-scripts branch November 7, 2026 16:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant