diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml index 70e085ae..c9976d94 100644 --- a/.github/release-drafter.yml +++ b/.github/release-drafter.yml @@ -2,27 +2,27 @@ name-template: 'v$RESOLVED_VERSION' tag-template: 'v$RESOLVED_VERSION' template: | - ## Changelog $CHANGES ## Contributors + Thanks to everyone who shipped with us this release 🎉 $CONTRIBUTORS ## Installation To install wpm v$RESOLVED_VERSION **Linux and Mac** - ``` + ```sh curl -fsSL https://wpm.so/install | bash ``` **Windows** - ``` + ```powershell powershell -c "irm wpm.so/install.ps1|iex" ``` **Docker** - ``` + ```sh docker pull trywpm/cli ``` @@ -30,21 +30,25 @@ categories: - title: 'Core' labels: - 'core' + - title: 'Enhancements' + labels: + - 'enhancement' - title: 'Commands' labels: - 'command' - title: 'Bug Fixes' labels: - 'bugfix' - - title: 'Misc' + - title: 'Documentation' labels: - - 'misc' + - 'documentation' - title: 'Infrastructure' labels: - 'infrastructure' - title: 'Dependencies' labels: - 'dependencies' + collapse-after: 5 change-template: '- $TITLE (#$NUMBER)' version-resolver: major: diff --git a/cli/cobra.go b/cli/cobra.go index ef411cc1..2a4c98dd 100644 --- a/cli/cobra.go +++ b/cli/cobra.go @@ -7,6 +7,7 @@ import ( "strings" "go.wpm.so/cli/cli/command" + "go.wpm.so/cli/cli/command/completion" cliflags "go.wpm.so/cli/cli/flags" "github.com/fvbommel/sortorder" @@ -23,6 +24,12 @@ func setupCommonRootCommand(rootCmd *cobra.Command) (*cliflags.ClientOptions, *c opts := cliflags.NewClientOptions() opts.InstallFlags(rootCmd.Flags()) + _ = rootCmd.MarkFlagDirname("config") + _ = rootCmd.RegisterFlagCompletionFunc( + "log-level", + completion.FromList("debug", "info", "warn", "error", "fatal"), + ) + cobra.AddTemplateFunc("add", func(a, b int) int { return a + b }) cobra.AddTemplateFunc("hasAliases", hasAliases) cobra.AddTemplateFunc("hasSubCommands", hasSubCommands) diff --git a/cli/command/completion/functions.go b/cli/command/completion/functions.go new file mode 100644 index 00000000..9f38a175 --- /dev/null +++ b/cli/command/completion/functions.go @@ -0,0 +1,145 @@ +package completion + +import ( + "os" + "sort" + + "go.wpm.so/cli/pkg/pm/wpmjson" + "go.wpm.so/cli/pkg/pm/wpmjson/types" + "go.wpm.so/cli/pkg/pm/wpmlock" + + "github.com/spf13/cobra" +) + +// PackagesFromWpmJson offers completion for package names declared in +// wpm.json's dependencies and devDependencies. +func PackagesFromWpmJson() cobra.CompletionFunc { + return Unique(func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) { + cwd, err := os.Getwd() + if err != nil { + return nil, cobra.ShellCompDirectiveNoFileComp + } + + cfg, err := wpmjson.Read(cwd) + if err != nil || cfg == nil { + return nil, cobra.ShellCompDirectiveNoFileComp + } + + var names []string + if cfg.Dependencies != nil { + for name := range *cfg.Dependencies { + names = append(names, name) + } + } + if cfg.DevDependencies != nil { + for name := range *cfg.DevDependencies { + names = append(names, name) + } + } + sort.Strings(names) + return names, cobra.ShellCompDirectiveNoFileComp + }) +} + +// PackagesFromLockfile offers completion for package names recorded in +// wpm.lock. +func PackagesFromLockfile() cobra.CompletionFunc { + return func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) { + cwd, err := os.Getwd() + if err != nil { + return nil, cobra.ShellCompDirectiveNoFileComp + } + + lock, err := wpmlock.Read(cwd) + if err != nil || lock == nil { + return nil, cobra.ShellCompDirectiveNoFileComp + } + + names := make([]string, 0, len(lock.Packages)) + for name := range lock.Packages { + names = append(names, name) + } + sort.Strings(names) + return names, cobra.ShellCompDirectiveNoFileComp + } +} + +// PackageTypes offers completion for the closed set of valid package types. +func PackageTypes() cobra.CompletionFunc { + return FromList( + string(types.TypePlugin), + string(types.TypeTheme), + string(types.TypeMuPlugin), + ) +} + +// PackageVisibility offers completion for the closed set of valid visibility. +func PackageVisibility() cobra.CompletionFunc { + return FromList( + string(types.VisibilityPublic), + string(types.VisibilityPrivate), + ) +} + +// PublishTags suggests a non-exhaustive list of common dist-tags for +// `wpm publish --tag`. +func PublishTags() cobra.CompletionFunc { + return func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) { + return []string{"latest", "next", "beta", "alpha"}, cobra.ShellCompDirectiveNoFileComp + } +} + +// PackageLicenses suggests a non-exhaustive list of common SPDX licenses. +func PackageLicenses() cobra.CompletionFunc { + return func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) { + return []string{"GPL-2.0-or-later", "GPL-3.0-or-later"}, cobra.ShellCompDirectiveNoFileComp + } +} + +// FileNames opts a flag or positional argument back into the shell's default +// file completion. +func FileNames() cobra.CompletionFunc { + return func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) { + return nil, cobra.ShellCompDirectiveDefault + } +} + +// FromList offers completion for the given list of options. +func FromList(options ...string) cobra.CompletionFunc { + return Unique(cobra.FixedCompletions(options, cobra.ShellCompDirectiveNoFileComp)) +} + +// Unique wraps a completion func and removes completion results that are +// already consumed (i.e., appear in "args"). +// +// For example: +// +// # initial completion: args is empty, so all results are shown +// command +// one two three +// +// # "one" is already used so omitted +// command one +// two three +func Unique(fn cobra.CompletionFunc) cobra.CompletionFunc { + return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + all, dir := fn(cmd, args, toComplete) + if len(all) == 0 || len(args) == 0 { + return all, dir + } + + alreadyCompleted := make(map[string]struct{}, len(args)) + for _, a := range args { + alreadyCompleted[a] = struct{}{} + } + + out := make([]string, 0, len(all)) + for _, c := range all { + if _, ok := alreadyCompleted[c]; !ok { + out = append(out, c) + } + } + + return out, dir + } +} diff --git a/cli/command/init/init.go b/cli/command/init/init.go index da5a94a3..88583d3c 100644 --- a/cli/command/init/init.go +++ b/cli/command/init/init.go @@ -11,6 +11,7 @@ import ( "unicode" "go.wpm.so/cli/cli/command" + "go.wpm.so/cli/cli/command/completion" "go.wpm.so/cli/pkg/output" "go.wpm.so/cli/pkg/pm/wpmjson" "go.wpm.so/cli/pkg/pm/wpmjson/types" @@ -81,6 +82,9 @@ func NewInitCommand(wpmCli command.Cli) *cobra.Command { flags.StringVar(&opts.license, "license", "", "Package license") flags.StringVar(&opts.packageType, "type", "", "Package type (plugin, theme, mu-plugin)") + _ = cmd.RegisterFlagCompletionFunc("type", completion.PackageTypes()) + _ = cmd.RegisterFlagCompletionFunc("license", completion.PackageLicenses()) + return cmd } diff --git a/cli/command/publish/publish.go b/cli/command/publish/publish.go index 5bd970a2..40c6c1f2 100644 --- a/cli/command/publish/publish.go +++ b/cli/command/publish/publish.go @@ -13,6 +13,7 @@ import ( "go.wpm.so/cli/cli" "go.wpm.so/cli/cli/command" + "go.wpm.so/cli/cli/command/completion" "go.wpm.so/cli/cli/version" "go.wpm.so/cli/pkg/archive" "go.wpm.so/cli/pkg/output" @@ -56,6 +57,9 @@ func NewPublishCommand(wpmCli command.Cli) *cobra.Command { flags.StringVarP(&opts.access, "access", "a", "private", "Set the package access level to either public or private") flags.BoolVar(&opts.dryRun, "dry-run", false, "Perform a publish operation without actually publishing the package") + _ = cmd.RegisterFlagCompletionFunc("tag", completion.PublishTags()) + _ = cmd.RegisterFlagCompletionFunc("access", completion.PackageVisibility()) + return cmd } diff --git a/cli/command/uninstall/uninstall.go b/cli/command/uninstall/uninstall.go index 4d3d8d67..80c486b5 100644 --- a/cli/command/uninstall/uninstall.go +++ b/cli/command/uninstall/uninstall.go @@ -8,6 +8,7 @@ import ( "go.wpm.so/cli/cli" "go.wpm.so/cli/cli/command" + "go.wpm.so/cli/cli/command/completion" "go.wpm.so/cli/cli/command/install" "go.wpm.so/cli/cli/version" "go.wpm.so/cli/pkg/output" @@ -29,6 +30,7 @@ func NewUninstallCommand(wpmCli command.Cli) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { return runUninstall(cmd.Context(), wpmCli, args) }, + ValidArgsFunction: completion.PackagesFromWpmJson(), } return cmd diff --git a/cli/command/why/why.go b/cli/command/why/why.go index f216e60b..b74427e3 100644 --- a/cli/command/why/why.go +++ b/cli/command/why/why.go @@ -10,6 +10,7 @@ import ( "go.wpm.so/cli/cli" "go.wpm.so/cli/cli/command" + "go.wpm.so/cli/cli/command/completion" "go.wpm.so/cli/pkg/output" "go.wpm.so/cli/pkg/pm/wpmjson" "go.wpm.so/cli/pkg/pm/wpmlock" @@ -32,6 +33,7 @@ func NewWhyCommand(wpmCli command.Cli) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { return runWhy(wpmCli, args[0]) }, + ValidArgsFunction: completion.PackagesFromLockfile(), } return cmd } diff --git a/cmd/wpm/wpm.go b/cmd/wpm/wpm.go index 4c166e75..7fa9273e 100644 --- a/cmd/wpm/wpm.go +++ b/cmd/wpm/wpm.go @@ -157,11 +157,16 @@ func newWpmCommand(wpmCli *command.WpmCli) *cli.TopLevelCommand { Version: ver, DisableFlagsInUseLine: true, CompletionOptions: cobra.CompletionOptions{ - DisableDefaultCmd: false, HiddenDefaultCmd: true, - DisableDescriptions: true, + DisableDefaultCmd: false, + DisableDescriptions: os.Getenv("WPM_CLI_DISABLE_COMPLETION_DESCRIPTION") != "", }, } + + // Disable file-completion by default. Most commands and flags should not + // complete with filenames. + cmd.CompletionOptions.SetDefaultShellCompDirective(cobra.ShellCompDirectiveNoFileComp) + cmd.SetIn(wpmCli.In()) cmd.SetOut(wpmCli.Out()) cmd.SetErr(wpmCli.Err()) diff --git a/scripts/install/install.ps1 b/scripts/install/install.ps1 index 8d80e9c6..2b65e456 100644 --- a/scripts/install/install.ps1 +++ b/scripts/install/install.ps1 @@ -1,114 +1,176 @@ -# Usage: -# powershell -ExecutionPolicy ByPass -File install.ps1 -# powershell -ExecutionPolicy ByPass -File install.ps1 -Version v1.0.0 +#!/usr/bin/env pwsh param ( [string]$Version ) -$ErrorActionPreference = 'Stop' Set-StrictMode -Version Latest -[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$ProgressPreference = 'SilentlyContinue' +$ErrorActionPreference = 'Stop' -function Show-Error { param([string]$Msg); Write-Host "error: $Msg" -ForegroundColor Red } -function Show-Success { param([string]$Msg); Write-Host "$Msg" -ForegroundColor Green } -function Show-Info { param([string]$Msg); Write-Host "$Msg" -ForegroundColor Gray } -function Show-Bold { param([string]$Msg); Write-Host "$Msg" -ForegroundColor White } +[Net.ServicePointManager]::SecurityProtocol = ` + [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12 -$Arch = $env:PROCESSOR_ARCHITECTURE -$Target = "" +function Show-Info { param([string]$Msg); Write-Host $Msg -ForegroundColor Gray } +function Show-Bold { param([string]$Msg); Write-Host $Msg -ForegroundColor White } +function Show-Error { param([string]$Msg); Write-Host "error: $Msg" -ForegroundColor Red } +function Show-Success { param([string]$Msg); Write-Host $Msg -ForegroundColor Green } +function Show-Warning { param([string]$Msg); Write-Host "warning: $Msg" -ForegroundColor Yellow } -if ($Arch -eq "AMD64") { - $Target = "windows-amd64" -} elseif ($Arch -eq "ARM64") { - $Target = "windows-arm64" -} else { - Show-Error "Unsupported architecture: $Arch" - exit 1 +$arch = try { + (Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment' -Name PROCESSOR_ARCHITECTURE -ErrorAction Stop).PROCESSOR_ARCHITECTURE +} +catch { + if ($env:PROCESSOR_ARCHITEW6432) { $env:PROCESSOR_ARCHITEW6432 } else { $env:PROCESSOR_ARCHITECTURE } } -$GitHubOrg = "trywpm" -$Repo = "cli" -$ExeName = "wpm.exe" +switch ($arch) { + 'AMD64' { $target = 'windows-amd64' } + 'ARM64' { $target = 'windows-arm64' } + default { + Show-Error "Unsupported architecture: $arch" + exit 1 + } +} -$InstallDir = Join-Path $env:LOCALAPPDATA "wpm" -$ExePath = Join-Path $InstallDir $ExeName +$ExeName = 'wpm.exe' +$BinaryName = "wpm-$target.exe" -$BaseUrl = "https://github.com/$GitHubOrg/$Repo/releases" -$BinaryName = "wpm-$Target.exe" +$InstallDir = if ($env:WPM_INSTALL) { $env:WPM_INSTALL } else { Join-Path $HOME '.wpm' } +$BinDir = Join-Path $InstallDir 'bin' +$CompletionsDir = Join-Path $InstallDir 'completions' +$ExePath = Join-Path $BinDir $ExeName + +$BaseUrl = 'https://github.com/trywpm/cli/releases' +$Uri = if ([string]::IsNullOrEmpty($Version)) { + "$BaseUrl/latest/download/$BinaryName" +} +else { + "$BaseUrl/download/$Version/$BinaryName" +} -if ([string]::IsNullOrEmpty($Version)) { - $Uri = "$BaseUrl/latest/download/$BinaryName" -} else { - $Uri = "$BaseUrl/download/$Version/$BinaryName" +# remove any old wpm.exe left by the legacy installer. +$LegacyExePath = Join-Path $env:LOCALAPPDATA 'wpm\wpm.exe' +if (Test-Path $LegacyExePath) { + Show-Info "A previous installation of wpm was found in $env:LOCALAPPDATA\wpm." + Show-Info 'Removing it to avoid conflicts...' + Remove-Item -Path $LegacyExePath -Force -ErrorAction SilentlyContinue + Show-Success "Removed $LegacyExePath" } try { - if (Get-Process "wpm" -ErrorAction SilentlyContinue) { - Show-Error "wpm is currently running. Please close it and try again." + if (Get-Process -Name 'wpm' -ErrorAction SilentlyContinue) { + Show-Error 'wpm is currently running. Please close it and try again.' exit 1 } - if (-not (Test-Path $InstallDir)) { - New-Item -ItemType Directory -Force -Path $InstallDir | Out-Null + foreach ($dir in @($BinDir, $CompletionsDir)) { + if (-not (Test-Path -Path $dir)) { + New-Item -ItemType Directory -Force -Path $dir | Out-Null + } } - Show-Info "Installing wpm for $Target..." - Show-Info "Downloading from $Uri..." - $TempFile = Join-Path $env:TEMP $BinaryName - Invoke-WebRequest -Uri $Uri -OutFile $TempFile - - # Download Checksum - $ChecksumUri = "$Uri.sha256" $TempChecksum = "$TempFile.sha256" + Show-Info 'Downloading wpm...' try { - Invoke-WebRequest -Uri $ChecksumUri -OutFile $TempChecksum -ErrorAction Stop + Invoke-WebRequest -Uri $Uri -OutFile $TempFile -UseBasicParsing + } + catch { + Show-Error "Failed to download wpm from `"$Uri`": $($_.Exception.Message)" + exit 1 + } - Show-Info "Verifying checksum..." + # checksum verification. + $haveChecksum = $false + try { + Invoke-WebRequest -Uri "$Uri.sha256" -OutFile $TempChecksum -UseBasicParsing -ErrorAction Stop + $haveChecksum = $true + } + catch {} - $ExpectedHash = (Get-Content $TempChecksum).Split(" ")[0].Trim() - $ActualHash = (Get-FileHash -Path $TempFile -Algorithm SHA256).Hash + if ($haveChecksum) { + Show-Info 'Verifying checksum...' + $expected = ((Get-Content -Path $TempChecksum -Raw).Trim() -split '\s+')[0] + $actual = (Get-FileHash -Path $TempFile -Algorithm SHA256).Hash - if ($ExpectedHash -ne $ActualHash) { - throw "Checksum mismatch! Expected: $ExpectedHash, Actual: $ActualHash" + if ($expected -ne $actual) { + Remove-Item -Path $TempFile, $TempChecksum -ErrorAction SilentlyContinue + Show-Error "Checksum mismatch! expected $expected, got $actual" + exit 1 } - } catch { - Show-Error "Failed to download or verify checksum: $_" - Remove-Item $TempFile -ErrorAction SilentlyContinue - if (Test-Path $TempChecksum) { Remove-Item $TempChecksum -ErrorAction SilentlyContinue } - exit 1 + + Remove-Item -Path $TempChecksum -ErrorAction SilentlyContinue } Move-Item -Path $TempFile -Destination $ExePath -Force - if (Test-Path $TempChecksum) { Remove-Item $TempChecksum } + # shell completion setup. + $CompletionFile = Join-Path $CompletionsDir 'wpm.ps1' + try { + $completion = & $ExePath completion powershell 2>$null + if ($LASTEXITCODE -eq 0 -and $completion) { + $completion | Out-File -FilePath $CompletionFile -Encoding utf8 + } + } + catch {} - $UserPathArgs = "Path", "User" - $CurrentPath = [Environment]::GetEnvironmentVariable($UserPathArgs) + Show-Success "wpm installed to $ExePath" + + $UserPath = [Environment]::GetEnvironmentVariable('Path', 'User') + $PathEntries = if ($UserPath) { $UserPath -split ';' | ForEach-Object { $_.TrimEnd('\') } } else { @() } + $BinOnUserPath = $PathEntries -contains $BinDir - if (($CurrentPath -split ';') -notcontains $InstallDir) { - Show-Info "Adding $InstallDir to User PATH..." + if (-not $BinOnUserPath) { + $NewPath = if ([string]::IsNullOrEmpty($UserPath)) { $BinDir } else { "$UserPath;$BinDir" } + [Environment]::SetEnvironmentVariable('Path', $NewPath, 'User') - # Add to persistent Registry PATH - [Environment]::SetEnvironmentVariable("Path", "$CurrentPath;$InstallDir", "User") + $env:Path = "$env:Path;$BinDir" - $env:Path += ";$InstallDir" + Show-Info "Added `"$BinDir`" to your system `$PATH" } - Write-Host "" - Show-Success "wpm installed to $ExePath" + $ProfilePath = $PROFILE.CurrentUserCurrentHost + $ProfileDir = Split-Path -Path $ProfilePath -Parent + if (-not (Test-Path -Path $ProfileDir)) { + New-Item -ItemType Directory -Path $ProfileDir -Force | Out-Null + } - Write-Host "" - Show-Info "To get started, run:" - Write-Host "" - Show-Bold " wpm --help" + $marker = '# wpm completions' + $existing = if (Test-Path -Path $ProfilePath) { + Get-Content -Path $ProfilePath -Raw + } + else { '' } + + if (-not $existing -or ($existing -notmatch [regex]::Escape($marker))) { + if ($InstallDir.StartsWith("$HOME\") -or $InstallDir -eq $HOME) { + $InstallRef = '"$HOME' + $InstallDir.Substring($HOME.Length) + '"' + } + else { + $escaped = $InstallDir -replace "'", "''" + $InstallRef = "'$escaped'" + } + + $block = @" - Write-Host "" - Show-Info "Note: You may need to restart your terminal for the PATH changes to take effect." -} catch { +$marker +`$env:WPM_INSTALL = $InstallRef +if (Test-Path "`$env:WPM_INSTALL\completions\wpm.ps1") { . "`$env:WPM_INSTALL\completions\wpm.ps1" } +"@ + Add-Content -Path $ProfilePath -Value $block -Encoding utf8 + + Show-Info "Added completions to `"$ProfilePath`"" + } + + Write-Host '' + Show-Info 'To get started, run:' + Write-Host '' + Show-Bold " . `$PROFILE" + Show-Bold ' wpm --help' +} +catch { Show-Error $_.Exception.Message exit 1 } diff --git a/scripts/install/install.sh b/scripts/install/install.sh index 2ce56c3e..bb593a2b 100644 --- a/scripts/install/install.sh +++ b/scripts/install/install.sh @@ -48,59 +48,40 @@ tildify() { if [[ -f "/usr/local/bin/wpm" ]]; then info "A previous installation of wpm was found in /usr/local/bin." info "Removing it to avoid conflicts..." - sudo rm /usr/local/bin/wpm || error "Failed to remove /usr/local/bin/wpm" + sudo rm -f /usr/local/bin/wpm || error "Failed to remove /usr/local/bin/wpm" success "Removed /usr/local/bin/wpm" fi +if [[ -f "$HOME/.local/bin/wpm" ]]; then + info "A previous installation of wpm was found in $HOME/.local/bin." + info "Removing it to avoid conflicts..." + rm -f "$HOME/.local/bin/wpm" || error "Failed to remove $HOME/.local/bin/wpm" + success "Removed $HOME/.local/bin/wpm" +fi + if [[ $# -gt 1 ]]; then - error 'Usage: install.sh [version]' + error 'Usage: install.sh [version]' fi platform=$(uname -ms) case $platform in - *'MINGW'* | *'CYGWIN'* | *'MSYS'* | 'Windows_NT'*) - error "Please run \`powershell -c \"irm wpm.so/install.ps1|iex\"\` to install wpm on Windows systems." - ;; +*'MINGW'* | *'CYGWIN'* | *'MSYS'* | 'Windows_NT'*) + error 'Please run `powershell -c "irm wpm.so/install.ps1|iex"` to install wpm on Windows systems.' + ;; esac case $platform in - # --- macOS --- - 'Darwin x86_64') - target="darwin-amd64" - ;; - 'Darwin arm64') - target="darwin-arm64" - ;; - - # --- Linux --- - 'Linux x86_64') - target="linux-amd64" - ;; - 'Linux aarch64' | 'Linux arm64') - target="linux-arm64" - ;; - 'Linux armv7'*) - target="linux-arm-v7" - ;; - 'Linux armv6'*) - target="linux-arm-v6" - ;; - 'Linux ppc64le') - target="linux-ppc64le" - ;; - 'Linux riscv64') - target="linux-riscv64" - ;; - 'Linux s390x') - target="linux-s390x" - ;; - - # --- Unsupported --- - *) - error "Unsupported platform: $platform" - exit 1 - ;; +'Linux s390x') target="linux-s390x" ;; +'Linux x86_64') target="linux-amd64" ;; +'Darwin arm64') target="darwin-arm64" ;; +'Linux armv6'*) target="linux-arm-v6" ;; +'Linux armv7'*) target="linux-arm-v7" ;; +'Darwin x86_64') target="darwin-amd64" ;; +'Linux ppc64le') target="linux-ppc64le" ;; +'Linux riscv64') target="linux-riscv64" ;; +'Linux aarch64' | 'Linux arm64') target="linux-arm64" ;; +*) error "Unsupported platform: $platform" ;; esac if [[ $target = darwin-amd64 ]]; then @@ -116,7 +97,13 @@ GITHUB=${GITHUB-"https://github.com"} github_repo="$GITHUB/trywpm/cli" exe_name=wpm -bin_dir="$HOME/.local/bin" +install_env=WPM_INSTALL +bin_env=\$$install_env/bin +completions_env=\$$install_env/completions + +wpm_install_dir="${!install_env:-$HOME/.wpm}" +bin_dir="$wpm_install_dir/bin" +completions_dir="$wpm_install_dir/completions" exe="$bin_dir/$exe_name" if [[ $# = 0 ]]; then @@ -125,13 +112,12 @@ else wpm_uri=$github_repo/releases/download/$1/wpm-$target fi -mkdir -p "$bin_dir" +mkdir -p "$bin_dir" "$completions_dir" info "Downloading wpm..." curl --fail --location --progress-bar --output "/tmp/$exe_name" "$wpm_uri" || error "Failed to download wpm from \"$wpm_uri\"" - checksum_cmd="" if [[ $platform == 'Darwin'* ]] && command -v shasum >/dev/null; then checksum_cmd="shasum -a 256 -c" @@ -155,104 +141,153 @@ fi chmod +x "/tmp/$exe_name" || error 'Failed to make wpm executable' mv "/tmp/$exe_name" "$exe" || error 'Failed to move wpm to destination' +"$exe" completion zsh > "$completions_dir/_wpm" 2>/dev/null || : +"$exe" completion bash > "$completions_dir/wpm.bash" 2>/dev/null || : +"$exe" completion fish > "$completions_dir/wpm.fish" 2>/dev/null || : + success "wpm installed to ${Bold_Green}$(tildify "$exe")${Color_Off}" if [[ ":$PATH:" == *":$bin_dir:"* ]]; then - info "To get started, run:" - echo - info_bold " wpm --help" - exit 0 + echo "Run 'wpm --help' to get started" + exit fi +refresh_command='' + +tilde_bin_dir=$(tildify "$bin_dir") +quoted_install_dir=\"${wpm_install_dir//\"/\\\"}\" + +if [[ $quoted_install_dir = \"$HOME/* ]]; then + quoted_install_dir=${quoted_install_dir/$HOME\//\$HOME/} +fi + +echo + case $(basename "$SHELL") in fish) - commands=( - "set --export PATH $bin_dir \$PATH" - ) + commands=( + "set --export $install_env $quoted_install_dir" + "set --export PATH $bin_env \$PATH" + "set -gx fish_complete_path \"$completions_env\" \$fish_complete_path" + ) - fish_config=$HOME/.config/fish/config.fish - tilde_fish_config=$(tildify "$fish_config") - - if [[ -w $fish_config ]]; then - { - echo -e '\n# wpm' - for command in "${commands[@]}"; do echo "$command"; done - } >>"$fish_config" - - info "Added \"$(tildify "$bin_dir")\" to \$PATH in \"$tilde_fish_config\"" - info "To get started, run:" - info_bold " source $tilde_fish_config" - info_bold " wpm --help" - else - echo "Manually add the directory to $tilde_fish_config (or similar):" - for command in "${commands[@]}"; do info_bold " $command"; done - fi - ;; + fish_config=$HOME/.config/fish/config.fish + tilde_fish_config=$(tildify "$fish_config") + + if [[ -w $fish_config ]]; then + { + echo -e '\n# wpm' + + for command in "${commands[@]}"; do + echo "$command" + done + } >>"$fish_config" + + info "Added \"$tilde_bin_dir\" to \$PATH in \"$tilde_fish_config\"" + + refresh_command="source $tilde_fish_config" + else + echo "Manually add the directory to $tilde_fish_config (or similar):" + + for command in "${commands[@]}"; do + info_bold " $command" + done + fi + ;; zsh) - commands=( - "export PATH=\"$bin_dir:\$PATH\"" - ) + commands=( + "export $install_env=$quoted_install_dir" + "export PATH=\"$bin_env:\$PATH\"" + "command -v compdef >/dev/null || { autoload -Uz compinit && compinit; }" + "[ -s \"$completions_env/_wpm\" ] && source \"$completions_env/_wpm\"" + ) - zsh_config=$HOME/.zshrc - tilde_zsh_config=$(tildify "$zsh_config") - - if [[ -w $zsh_config ]]; then - { - echo -e '\n# wpm' - for command in "${commands[@]}"; do echo "$command"; done - } >>"$zsh_config" - - info "Added \"$(tildify "$bin_dir")\" to \$PATH in \"$tilde_zsh_config\"" - info "To get started, run:" - info_bold " exec \$SHELL" - info_bold " wpm --help" - else - echo "Manually add the directory to $tilde_zsh_config (or similar):" - for command in "${commands[@]}"; do info_bold " $command"; done - fi - ;; -bash) - commands=( - "export PATH=\"$bin_dir:\$PATH\"" - ) + zsh_config=$HOME/.zshrc + tilde_zsh_config=$(tildify "$zsh_config") - bash_configs=("$HOME/.bashrc" "$HOME/.bash_profile") - if [[ ${XDG_CONFIG_HOME:-} ]]; then - bash_configs+=( - "$XDG_CONFIG_HOME/.bash_profile" - "$XDG_CONFIG_HOME/.bashrc" - "$XDG_CONFIG_HOME/bash_profile" - "$XDG_CONFIG_HOME/bashrc" - ) - fi + if [[ -w $zsh_config ]]; then + { + echo -e '\n# wpm' - set_manually=true - for bash_config in "${bash_configs[@]}"; do - tilde_bash_config=$(tildify "$bash_config") + for command in "${commands[@]}"; do + echo "$command" + done + } >>"$zsh_config" - if [[ -w $bash_config ]]; then - { - echo -e '\n# wpm' - for command in "${commands[@]}"; do echo "$command"; done - } >>"$bash_config" + info "Added \"$tilde_bin_dir\" to \$PATH in \"$tilde_zsh_config\"" - info "Added \"$(tildify "$bin_dir")\" to \$PATH in \"$tilde_bash_config\"" - info "To get started, run:" - info_bold " source $tilde_bash_config" - info_bold " wpm --help" + refresh_command="exec $SHELL" + else + echo "Manually add the directory to $tilde_zsh_config (or similar):" - set_manually=false - break - fi + for command in "${commands[@]}"; do + info_bold " $command" done + fi + ;; +bash) + commands=( + "export $install_env=$quoted_install_dir" + "export PATH=\"$bin_env:\$PATH\"" + "[ -s \"$completions_env/wpm.bash\" ] && source \"$completions_env/wpm.bash\"" + ) + + bash_configs=( + "$HOME/.bash_profile" + "$HOME/.bashrc" + ) + + if [[ ${XDG_CONFIG_HOME:-} ]]; then + bash_configs+=( + "$XDG_CONFIG_HOME/.bash_profile" + "$XDG_CONFIG_HOME/.bashrc" + "$XDG_CONFIG_HOME/bash_profile" + "$XDG_CONFIG_HOME/bashrc" + ) + fi + + set_manually=true + for bash_config in "${bash_configs[@]}"; do + tilde_bash_config=$(tildify "$bash_config") + + if [[ -w $bash_config ]]; then + { + echo -e '\n# wpm' - if [[ $set_manually = true ]]; then - echo "Manually add the directory to $tilde_bash_config (or similar):" - for command in "${commands[@]}"; do info_bold " $command"; done + for command in "${commands[@]}"; do + echo "$command" + done + } >>"$bash_config" + + info "Added \"$tilde_bin_dir\" to \$PATH in \"$tilde_bash_config\"" + + refresh_command="source $bash_config" + set_manually=false + break fi - ;; + done + + if [[ $set_manually = true ]]; then + echo "Manually add the directory to $tilde_bash_config (or similar):" + + for command in "${commands[@]}"; do + info_bold " $command" + done + fi + ;; *) - echo "Manually add the directory to your shell configuration:" - info_bold " export PATH=\"$bin_dir:\$PATH\"" - ;; + echo 'Manually add the directory to ~/.bashrc (or similar):' + info_bold " export $install_env=$quoted_install_dir" + info_bold " export PATH=\"$bin_env:\$PATH\"" + ;; esac + +echo +info "To get started, run:" +echo + +if [[ $refresh_command ]]; then + info_bold " $refresh_command" +fi + +info_bold " wpm --help"