diff --git a/.editorconfig b/.editorconfig index 1840589a7..fe3ed6082 100644 --- a/.editorconfig +++ b/.editorconfig @@ -9,3 +9,6 @@ insert_final_newline = true [*.py] indent_size = 4 + +[*.fish] +indent_size = 4 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index c39b5619f..6727f4c3b 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -6,55 +6,26 @@ on: - master pull_request: -jobs: - shellcheck: - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v3 - - - name: Install asdf dependencies - uses: asdf-vm/actions/install@v1 - - - name: Run ShellCheck - run: scripts/shellcheck.bash - - shellfmt: - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v3 +env: + PYTHON_MIN_VERSION: "3.7.13" - - name: Install asdf dependencies - uses: asdf-vm/actions/install@v1 - - - name: List file to shfmt - run: shfmt -f . - - - name: Run shfmt - run: scripts/shfmt.bash - - checkstyle-py: +jobs: + asdf: runs-on: ubuntu-latest steps: - - name: Checkout code - uses: actions/checkout@v3 - - - name: Install Python - uses: actions/setup-python@v4 + - uses: actions/checkout@v3 + - uses: asdf-vm/actions/install@v2 + - uses: actions/setup-python@v4 with: - python-version: "3.7.13" - - - name: Run checkstyle.py - run: scripts/checkstyle.py + python-version: ${{ env.PYTHON_MIN_VERSION }} + - run: scripts/install_dependencies.bash + - run: scripts/lint.bash --check - actionlint: + actions: runs-on: ubuntu-latest steps: - - name: Checkout code - uses: actions/checkout@v3 - + - uses: actions/checkout@v3 - name: Check workflow files - uses: docker://rhysd/actionlint:1.6.23 + uses: docker://rhysd/actionlint:1.6.24 with: args: -color diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 68211ded9..e3fc771fd 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,6 +14,12 @@ jobs: with: release-type: simple bump-minor-pre-major: true # remove this to enable breaking changes causing 1.0.0 tag + changelog-types: | + [ + { "type": "feat", "section": "Features", "hidden": false }, + { "type": "fix", "section": "Patches", "hidden": false }, + { "type": "docs", "section": "Documentation", "hidden": false } + ] extra-files: | SECURITY.md docs/guide/getting-started.md diff --git a/.github/workflows/semantic-pr.yml b/.github/workflows/semantic-pr.yml index d5216b647..8b26fa428 100644 --- a/.github/workflows/semantic-pr.yml +++ b/.github/workflows/semantic-pr.yml @@ -11,7 +11,7 @@ jobs: semantic-pr: runs-on: ubuntu-latest steps: - - uses: amannn/action-semantic-pull-request@v5.1.0 + - uses: amannn/action-semantic-pull-request@v5.2.0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 9842726d7..ec5931d5e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -6,10 +6,6 @@ on: - master pull_request: -env: - ELVISH_VERSION: v0.19.2 - NUSHELL_VERSION: 0.77.0 - jobs: detect-changes: runs-on: ubuntu-latest @@ -20,11 +16,9 @@ jobs: documentation: ${{ steps.filter.outputs.documentation }} cli: ${{ steps.filter.outputs.cli }} steps: - - name: Checkout code - uses: actions/checkout@v3 + - uses: actions/checkout@v3 with: fetch-depth: 0 - - uses: dorny/paths-filter@v2 id: filter with: @@ -32,7 +26,6 @@ jobs: documentation: - '.github/workflows/**' - 'docs/**' - - '.tool-versions' cli: - '.github/workflows/**' - 'bin/**' @@ -40,10 +33,9 @@ jobs: - 'scripts/**' - 'test/**' - '.tool-versions' - - 'asdf.elv' - - 'asdf.fish' - - 'asdf.nu' - - 'asdf.sh' + - 'asdf.*' + - 'defaults' + - 'help.txt' ubuntu: needs: detect-changes @@ -52,42 +44,11 @@ jobs: if: ${{ needs.detect-changes.outputs.cli == 'true' }} runs-on: ubuntu-latest steps: - - name: Checkout code - uses: actions/checkout@v3 + - uses: actions/checkout@v3 with: fetch-depth: 0 - - - name: Install test dependencies - run: | - sudo add-apt-repository -y ppa:fish-shell/nightly-master - sudo apt-get update - sudo apt-get -y install fish curl parallel - - # Create $HOME/bin - mkdir -p "$HOME/bin" - - # Download elvish binary and add to path - curl https://dl.elv.sh/linux-amd64/elvish-${{ env.ELVISH_VERSION }}.tar.gz -o elvish-${{ env.ELVISH_VERSION }}.tar.gz - tar xzf elvish-${{ env.ELVISH_VERSION }}.tar.gz - rm elvish-${{ env.ELVISH_VERSION }}.tar.gz - mv elvish-${{ env.ELVISH_VERSION }} "$HOME/bin/elvish" - - # Download nushell binary and add to path - curl -L https://github.com/nushell/nushell/releases/download/${{ env.NUSHELL_VERSION }}/nu-${{ env.NUSHELL_VERSION }}-x86_64-unknown-linux-gnu.tar.gz -o nu-${{ env.NUSHELL_VERSION }}-x86_64-unknown-linux-gnu.tar.gz - tar xzf nu-${{ env.NUSHELL_VERSION }}-x86_64-unknown-linux-gnu.tar.gz - rm nu-${{ env.NUSHELL_VERSION }}-x86_64-unknown-linux-gnu.tar.gz - mv nu-${{ env.NUSHELL_VERSION }}-x86_64-unknown-linux-gnu/* "$HOME/bin" - - # Add $HOME/bin to path - echo "$HOME/bin" >>"$GITHUB_PATH" - - - name: Install bats - run: | - git clone --depth 1 --branch "v$(grep -Eo "^\\s*bats\\s*.*$" ".tool-versions" | cut -d ' ' -f2-)" https://github.com/bats-core/bats-core.git "$HOME/bats-core" - echo "$HOME/bats-core/bin" >>"$GITHUB_PATH" - - - name: Run tests - run: scripts/test.bash + - run: scripts/install_dependencies.bash + - run: scripts/test.bash env: GITHUB_API_TOKEN: ${{ github.token }} @@ -98,21 +59,11 @@ jobs: if: ${{ needs.detect-changes.outputs.cli == 'true' }} runs-on: macos-latest steps: - - name: Checkout code - uses: actions/checkout@v3 + - uses: actions/checkout@v3 with: fetch-depth: 0 - - - name: Install test dependencies - run: brew install coreutils parallel fish elvish nushell - - - name: Install bats - run: | - git clone --depth 1 --branch "v$(grep -Eo "^\\s*bats\\s*.*$" ".tool-versions" | cut -d ' ' -f2-)" https://github.com/bats-core/bats-core.git "$HOME/bats-core" - echo "$HOME/bats-core/bin" >>"$GITHUB_PATH" - - - name: Run tests - run: scripts/test.bash + - run: scripts/install_dependencies.bash + - run: scripts/test.bash env: GITHUB_API_TOKEN: ${{ github.token }} @@ -130,13 +81,11 @@ jobs: fetch-depth: 0 # only run steps past here if changes to docs/** directory - - name: Setup Node.js - uses: actions/setup-node@v3 + - uses: actions/setup-node@v3 with: node-version: "18" - - name: Cache dependencies - uses: actions/cache@v3 + - uses: actions/cache@v3 id: npm-cache with: path: | @@ -150,6 +99,6 @@ jobs: working-directory: docs/ run: npm install - - name: Check errors by biulding Documentation site + - name: Check errors by building Documentation site working-directory: docs/ run: npm run build diff --git a/CHANGELOG.md b/CHANGELOG.md index 96633bbf4..7de85e536 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,54 @@ # Changelog +## [0.12.0](https://github.com/asdf-vm/asdf/compare/v0.11.3...v0.12.0) (2023-06-09) + + +### ⚠ BREAKING CHANGES + +* Remove files containing only `asdf` wrapper functions ([#1525](https://github.com/asdf-vm/asdf/issues/1525)) +* align Fish entrypoint behaviour with other shells ([#1524](https://github.com/asdf-vm/asdf/issues/1524)) +* do not remove items from PATH in POSIX entrypoint ([#1521](https://github.com/asdf-vm/asdf/issues/1521)) +* rework POSIX entrypoint for greater shell support ([#1480](https://github.com/asdf-vm/asdf/issues/1480)) + +### Features + +* Support configurable `ASDF_CONCURRENCY` ([#1532](https://github.com/asdf-vm/asdf/issues/1532)) ([684f4f0](https://github.com/asdf-vm/asdf/commit/684f4f058f24cc418f77825a59a22bacd16a9bee)) +* Support PowerShell Core ([#1522](https://github.com/asdf-vm/asdf/issues/1522)) ([213aa22](https://github.com/asdf-vm/asdf/commit/213aa22378cf0ecf5b1924f1bfc4fee43338255a)) + + +### Documentation + +* Add Nushell installation instructions for all languages ([#1519](https://github.com/asdf-vm/asdf/issues/1519)) ([6a6c539](https://github.com/asdf-vm/asdf/commit/6a6c539f4a21fdb863fd938edd94ac3bdced349b)) +* fix `ASDF_${LANG}_VERSION` usage ([#1528](https://github.com/asdf-vm/asdf/issues/1528)) ([63f422b](https://github.com/asdf-vm/asdf/commit/63f422b4c7afcf53ef72002e39967eb9ca2da2a9)) +* fix Nushell-Homebrew setup instructions ([#1495](https://github.com/asdf-vm/asdf/issues/1495)) ([49e541a](https://github.com/asdf-vm/asdf/commit/49e541a29ff7a2f35917a4544a8b9adbc02bb1b4)) +* fix uninstall instructions for Fish Shell ([#1547](https://github.com/asdf-vm/asdf/issues/1547)) ([a1e858d](https://github.com/asdf-vm/asdf/commit/a1e858d2542691adabf9b066add86f16e759a90c)) +* Improve wording of env vars section ([#1514](https://github.com/asdf-vm/asdf/issues/1514)) ([ec3eb2d](https://github.com/asdf-vm/asdf/commit/ec3eb2d64f0531be86d10e1202a92f6b7820e294)) +* verbose plugin create command details ([#1445](https://github.com/asdf-vm/asdf/issues/1445)) ([8108ca6](https://github.com/asdf-vm/asdf/commit/8108ca6d7e5f34b9b9723f945a9c4b137f2e10ef)) + + +### Patches + +* `asdf info` show BASH_VERSION & all asdf envs ([#1513](https://github.com/asdf-vm/asdf/issues/1513)) ([a1b5eee](https://github.com/asdf-vm/asdf/commit/a1b5eeec1caf605c0e4c80748703b9e227b57aeb)) +* align Fish entrypoint behaviour with other shells ([#1524](https://github.com/asdf-vm/asdf/issues/1524)) ([8919f40](https://github.com/asdf-vm/asdf/commit/8919f4009ea233c32298911b28ceb879e2dbc675)) +* assign default values to all internal variables ([#1518](https://github.com/asdf-vm/asdf/issues/1518)) ([86477ee](https://github.com/asdf-vm/asdf/commit/86477ee8dea14ab63faf7132133304855a647fde)) +* Better handling with paths that include spaces ([#1485](https://github.com/asdf-vm/asdf/issues/1485)) ([bbcbddc](https://github.com/asdf-vm/asdf/commit/bbcbddcdd4ffa0f49c3772b66d87331420fa5727)) +* create install directory with `mkdir -p` ([#1563](https://github.com/asdf-vm/asdf/issues/1563)) ([d6185a2](https://github.com/asdf-vm/asdf/commit/d6185a21207e0ac45e69499883dad5e2b585c1b6)) +* do not remove items from PATH in POSIX entrypoint ([#1521](https://github.com/asdf-vm/asdf/issues/1521)) ([b6d0ca2](https://github.com/asdf-vm/asdf/commit/b6d0ca28d5fd2b63c7da67b127e6c2a0e01b2670)) +* enforce consistent shell redirection format ([#1533](https://github.com/asdf-vm/asdf/issues/1533)) ([1bc205e](https://github.com/asdf-vm/asdf/commit/1bc205e8aa61287c766c673acb8f0d4f9c6ee249)) +* improve readability of the non-set `nullglob` guard ([#1545](https://github.com/asdf-vm/asdf/issues/1545)) ([f273612](https://github.com/asdf-vm/asdf/commit/f273612155188f62cf8daf584d5581cd4214daf4)) +* Introduce `ASDF_FORCE_PREPEND` variable on POSIX entrypoint ([#1560](https://github.com/asdf-vm/asdf/issues/1560)) ([5b7d0fe](https://github.com/asdf-vm/asdf/commit/5b7d0fea0a10681d89dd7bf4010e0a39e6696841)) +* lint & style errors in `bin/asdf` ([#1516](https://github.com/asdf-vm/asdf/issues/1516)) ([13c0e2f](https://github.com/asdf-vm/asdf/commit/13c0e2fab0e9ad4dccf72b6f5586fb32458b8709)) +* Nushell plugin list --urls ([#1507](https://github.com/asdf-vm/asdf/issues/1507)) ([9363fb2](https://github.com/asdf-vm/asdf/commit/9363fb2f72e7fa08d3580b22d465af48a7d37031)) +* nushell plugin list all ([#1501](https://github.com/asdf-vm/asdf/issues/1501)) ([#1502](https://github.com/asdf-vm/asdf/issues/1502)) ([c5b8b3c](https://github.com/asdf-vm/asdf/commit/c5b8b3c128b48e1531f6d03d2083435f413a4738)) +* Remove files containing only `asdf` wrapper functions ([#1525](https://github.com/asdf-vm/asdf/issues/1525)) ([00fee78](https://github.com/asdf-vm/asdf/commit/00fee78423de0e399f5705bb483e599e39b707c9)) +* remove leading asterick in Fish completion ([#1543](https://github.com/asdf-vm/asdf/issues/1543)) ([198ced5](https://github.com/asdf-vm/asdf/commit/198ced50327b20b136cb6ec165610d37334a2962)) +* rename internal function `asdf_tool_versions_filename` ([#1544](https://github.com/asdf-vm/asdf/issues/1544)) ([b36ec73](https://github.com/asdf-vm/asdf/commit/b36ec7338654abc3773314147540dfa8297b48b8)) +* rename internal plugin repository functions ([#1537](https://github.com/asdf-vm/asdf/issues/1537)) ([5367f1f](https://github.com/asdf-vm/asdf/commit/5367f1f09079070c7b47551dc453c686991564a0)) +* rework POSIX entrypoint for greater shell support ([#1480](https://github.com/asdf-vm/asdf/issues/1480)) ([3379af8](https://github.com/asdf-vm/asdf/commit/3379af845ed2e281703bc0e9e4f388a7845edc2a)) +* support `asdf shim-versions` completions in fish & bash ([#1554](https://github.com/asdf-vm/asdf/issues/1554)) ([99623d7](https://github.com/asdf-vm/asdf/commit/99623d7eac0fe17e330a950c71b7ba378f656b2c)) +* Typo in POSIX entrypoint ([#1562](https://github.com/asdf-vm/asdf/issues/1562)) ([6b2ebf5](https://github.com/asdf-vm/asdf/commit/6b2ebf575ff98d3970b518de04238d30804a40d1)) +* warn if `.tool-versions` or asdfrc contains carriage returns ([#1561](https://github.com/asdf-vm/asdf/issues/1561)) ([097f773](https://github.com/asdf-vm/asdf/commit/097f7733d67aaf8d0dca1c793407babbdf6f8394)) + ## [0.11.3](https://github.com/asdf-vm/asdf/compare/v0.11.2...v0.11.3) (2023-03-16) diff --git a/SECURITY.md b/SECURITY.md index d5923ebed..4f2555eef 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -9,7 +9,7 @@ not covered under this security policy.** ``` -0.11.3 +0.12.0 ``` diff --git a/asdf.fish b/asdf.fish index 7cef8114a..afb161dc7 100644 --- a/asdf.fish +++ b/asdf.fish @@ -1,38 +1,36 @@ -if not set -q ASDF_DIR - set -x ASDF_DIR (dirname (status -f)) +if test -z $ASDF_DIR + set ASDF_DIR (realpath (dirname (status filename))) end +set --export ASDF_DIR $ASDF_DIR -# Add asdf to PATH -# fish_add_path was added in fish 3.2, so we need a fallback for older version -if type -q fish_add_path - if test -n "$ASDF_DATA_DIR" - fish_add_path --global --move "$ASDF_DATA_DIR/shims" "$ASDF_DIR/bin" - else - fish_add_path --global --move "$HOME/.asdf/shims" "$ASDF_DIR/bin" - end +set -l _asdf_bin "$ASDF_DIR/bin" +if test -z $ASDF_DATA_DIR + set _asdf_shims "$HOME/.asdf/shims" else - set -l asdf_user_shims ( - if test -n "$ASDF_DATA_DIR" - printf "%s\n" "$ASDF_DATA_DIR/shims" - else - printf "%s\n" "$HOME/.asdf/shims" - end - ) + set _asdf_shims "$ASDF_DATA_DIR/shims" +end + +# Do not use fish_add_path (added in Fish 3.2) because it +# potentially changes the order of items in fish_user_paths +if not contains $_asdf_bin $fish_user_paths + set --global --prepend fish_user_paths $_asdf_bin +end +if not contains $_asdf_shims $fish_user_paths + set --global --prepend fish_user_paths $_asdf_shims +end +set --erase _asdf_bin _asdf_shims - set -l asdf_bin_dirs $ASDF_DIR/bin $asdf_user_shims +# The asdf function is a wrapper so we can export variables +function asdf + set command $argv[1] + set -e argv[1] - for x in $asdf_bin_dirs - if test -d $x - for i in (seq 1 (count $PATH)) - if test $PATH[$i] = $x - set -e PATH[$i] - break - end - end + switch "$command" + case shell + # Source commands that need to export variables. + command asdf export-shell-version fish $argv | source # asdf_allow: source + case '*' + # Forward other commands to asdf script. + command asdf "$command" $argv end - set PATH $x $PATH - end end - -# Load the asdf wrapper function -. $ASDF_DIR/lib/asdf.fish diff --git a/asdf.nu b/asdf.nu index 092fd3bec..e4a9313ef 100644 --- a/asdf.nu +++ b/asdf.nu @@ -79,8 +79,10 @@ module asdf { ] { let params = [ - {name: 'urls', enabled: $urls, template: '\s+?(?Pgit@.+\.git)', flag: '--urls'} - {name: 'refs', enabled: $refs, template: '\s+?(?P\w+)\s+(?P\w+)', flag: '--refs'} + {name: 'urls', enabled: $urls, flag: '--urls', + template: '\s+?(?P(?:http[s]?|git).+\.git|/.+)'} + {name: 'refs', enabled: $refs, flag: '--refs', + template: '\s+?(?P\w+)\s+(?P\w+)'} ] let template = '(?P.+)' + ( @@ -91,15 +93,14 @@ module asdf { str trim ) - let parsed_urls_flag = ($params | where enabled and name == 'urls' | get --ignore-errors flag | default '' ) - let parsed_refs_flag = ($params | where enabled and name == 'refs' | get --ignore-errors flag | default '' ) + let flags = ($params | where enabled | get --ignore-errors flag | default '' ) - ^asdf plugin list $parsed_urls_flag $parsed_refs_flag | lines | parse -r $template | str trim + ^asdf plugin list $flags | lines | parse -r $template | str trim } # list all available plugins export def "asdf plugin list all" [] { - let template = '(?P.+)\s+?(?P[*]?)(?P(?:git|http).+\.git)' + let template = '(?P.+)\s+?(?P[*]?)(?P(?:git|http|https).+)' let is_installed = { |it| $it.installed == '*' } ^asdf plugin list all | diff --git a/asdf.ps1 b/asdf.ps1 new file mode 100644 index 000000000..a4b405ee2 --- /dev/null +++ b/asdf.ps1 @@ -0,0 +1,31 @@ +$Env:ASDF_DIR = $PSScriptRoot + +$_asdf_bin = "$Env:ASDF_DIR/bin" +if ($null -eq $ASDF_DATA_DIR -or $ASDF_DATA_DIR -eq '') { + $_asdf_shims = "${env:HOME}/.asdf/shims" +} +else { + $_asdf_shims = "$ASDF_DATA_DIR/shims" +} + +$env:PATH = "${_asdf_bin}:${_asdf_shims}:${env:PATH}" + +if ($env:PATH -cnotlike "*${_asdf_bin}*") { + $env:PATH = "_asdf_bin:${env:PATH}" +} +if ($env:PATH -cnotlike "*${_asdf_shims}*") { + $env:PATH = "_asdf_shims:${env:PATH}" +} + +Remove-Variable -Force _asdf_bin, _asdf_shims + +function asdf { + $asdf = $(Get-Command -CommandType Application asdf).Source + + if ($args.Count -gt 0 -and $args[0] -eq 'shell') { + Invoke-Expression $(& $asdf 'export-shell-version' pwsh $args[1..($args.Count + -1)]) + } + else { + & $asdf $args + } +} diff --git a/asdf.sh b/asdf.sh index 7a01f5e8c..1f8d2b56d 100644 --- a/asdf.sh +++ b/asdf.sh @@ -1,37 +1,144 @@ -# For Korn shells (ksh, mksh, etc.), capture $_ (the final parameter passed to -# the last command) straightaway, as it will contain the path to this script. -# For Bash, ${BASH_SOURCE[0]} will be used to obtain this script's path. -# For Zsh and others, $0 (the path to the shell or script) will be used. -_under="$_" -if [ -z "${ASDF_DIR:-}" ]; then - if [ -n "${BASH_SOURCE[0]}" ]; then - current_script_path="${BASH_SOURCE[0]}" - elif [[ "$_under" == *".sh" ]]; then - current_script_path="$_under" +# shellcheck shell=sh +# shellcheck disable=SC1007 + +# This file is the entrypoint for all POSIX-compatible shells. If `ASDF_DIR` is +# not already set, this script is able to calculate it, but only if the shell is +# either Bash, Zsh, and Ksh. For other shells, `ASDF_DIR` must be manually set. + +export ASDF_DIR="${ASDF_DIR:-}" + +if [ -z "$ASDF_DIR" ]; then + if [ -n "$BASH_VERSION" ]; then + # Use BASH_SOURCE[0] to obtain the relative path to this source'd file. Since it's + # a relative path, 'cd' to its dirname and use '$PWD' to obtain the fullpath. + # Use 'builtin cd' to ensure user-defined 'cd()' functions aren't called. + # Use variable '_asdf_old_dir' to avoid using subshells. + + _asdf_old_dir=$PWD + # shellcheck disable=SC3028,SC3054 + if ! CDPATH= builtin cd -- "${BASH_SOURCE[0]%/*}"; then + printf '%s\n' 'asdf: Error: Failed to cd' >&2 + unset -v _asdf_old_dir + return 1 + fi + ASDF_DIR=$PWD + if ! CDPATH= builtin cd -- "$_asdf_old_dir"; then + printf '%s\n' 'asdf: Error: Failed to cd' >&2 + unset -v _asdf_old_dir + return 1 + fi + unset -v _asdf_old_dir + elif [ -n "$ZSH_VERSION" ]; then + # Use '%x' to expand to path of current file. It must be prefixed + # with '(%):-', so it expands in non-prompt-string contexts. + + # shellcheck disable=SC2296 + ASDF_DIR=${(%):-%x} + ASDF_DIR=${ASDF_DIR%/*} + elif [ -n "$KSH_VERSION" ] && [ -z "$PATHSEP" ]; then + # Only the original KornShell (kornshell.com) has a '.sh.file' variable with the path + # of the current file. To prevent errors with other variations, such as the MirBSD + # Korn shell (mksh), test for 'PATHSEP' which is _not_ set on the original Korn Shell. + + # shellcheck disable=SC2296 + ASDF_DIR=${.sh.file} + ASDF_DIR=${ASDF_DIR%/*} + fi +fi + +if [ -z "$ASDF_DIR" ]; then + printf "%s\n" "asdf: Error: Source directory could not be calculated. Please set \$ASDF_DIR manually before sourcing this file." >&2 + return 1 +fi + +if [ ! -d "$ASDF_DIR" ]; then + printf "%s\n" "asdf: Error: Variable '\$ASDF_DIR' is not a directory: $ASDF_DIR" >&2 + return 1 +fi + +_asdf_bin="$ASDF_DIR/bin" +_asdf_shims="${ASDF_DATA_DIR:-$HOME/.asdf}/shims" + +_asdf_should_prepend=no +if [ -n "${ASDF_FORCE_PREPEND+x}" ]; then + _asdf_should_prepend=$ASDF_FORCE_PREPEND +else + # If ASDF_FORCE_PREPEND is not set, then prepend by default on macOS + # to workaround `path_helper`. + if [ -n "$BASH_VERSION" ] || [ -n "$ZSH_VERSION" ]; then + # shellcheck disable=SC3028 + case $OSTYPE in + darwin*) _asdf_should_prepend=yes ;; + esac else - current_script_path="$0" + if ! _asdf_output=$(uname); then + printf "%s\n" "asdf: Error: Failed to execute 'uname'" >&2 + return 1 + fi + if [ "$_asdf_output" = 'Darwin' ]; then + _asdf_should_prepend=yes + fi + unset -v _asdf_output fi +fi + +# If prepending is enabled, remove any existing instances of asdf from PATH so +# the prepending done after is always at the frontmost part of the PATH. +if [ "$_asdf_should_prepend" = 'yes' ]; then + if [ -n "$BASH_VERSION" ] || [ -n "$ZSH_VERSION" ]; then + # shellcheck disable=SC3060 + case ":$PATH:" in + *":${_asdf_bin}:"*) PATH=${PATH//$_asdf_bin:/} ;; + esac + # shellcheck disable=SC3060 + case ":$PATH:" in + *":${_asdf_shims}:"*) PATH=${PATH//$_asdf_shims:/} ;; + esac + else + _path=${PATH}: + _new_path= + while [ -n "$_path" ]; do + _part=${_path%%:*} + _path=${_path#*:} + + if [ "$_part" = "$_asdf_bin" ] || [ "$_part" = "$_asdf_shims" ]; then + continue + fi - ASDF_DIR="$(dirname "$current_script_path")" + _new_path="$_new_path${_new_path:+:}$_part" + done + PATH=$_new_path + unset -v _path _new_path _part + fi fi -export ASDF_DIR -# shellcheck disable=SC2016 -[ -d "$ASDF_DIR" ] || printf "%s\n" "$ASDF_DIR is not a directory" - -# Add asdf to PATH -# -# if in $PATH, remove, regardless of if it is in the right place (at the front) or not. -# replace all occurrences - ${parameter//pattern/string} -ASDF_BIN="${ASDF_DIR}/bin" -ASDF_USER_SHIMS="${ASDF_DATA_DIR:-$HOME/.asdf}/shims" -[[ ":$PATH:" == *":${ASDF_BIN}:"* ]] && PATH="${PATH//$ASDF_BIN:/}" -[[ ":$PATH:" == *":${ASDF_USER_SHIMS}:"* ]] && PATH="${PATH//$ASDF_USER_SHIMS:/}" -# add to front of $PATH -PATH="${ASDF_BIN}:$PATH" -PATH="${ASDF_USER_SHIMS}:$PATH" - -# shellcheck source=lib/asdf.sh -# Load the asdf wrapper function -. "${ASDF_DIR}/lib/asdf.sh" - -unset _under current_script_path ASDF_BIN ASDF_USER_SHIMS +unset -v _asdf_should_prepend + +case ":$PATH:" in + *":$_asdf_bin:"*) : ;; + *) PATH="$_asdf_bin:$PATH" ;; +esac +case ":$PATH:" in + *":$_asdf_shims:"*) : ;; + *) PATH="$_asdf_shims:$PATH" ;; +esac + +unset -v _asdf_bin _asdf_shims + +# The asdf function is a wrapper so we can export variables +asdf() { + case $1 in + "shell") + if ! shift; then + printf '%s\n' 'asdf: Error: Failed to shift' >&2 + return 1 + fi + + # Invoke command that needs to export variables. + eval "$(asdf export-shell-version sh "$@")" # asdf_allow: eval + ;; + *) + # Forward other commands to asdf script. + command asdf "$@" # asdf_allow: ' asdf ' + ;; + esac +} diff --git a/bin/asdf b/bin/asdf index 0693133da..452f24877 100755 --- a/bin/asdf +++ b/bin/asdf @@ -16,9 +16,9 @@ find_cmd() { done if [ -f "$cmd_dir/$cmd_name" ]; then - printf "%s %s\\n" "$cmd_dir/$cmd_name" "$((args_offset + 1))" + printf "%s %s\n" "$cmd_dir/$cmd_name" "$((args_offset + 1))" elif [ -f "$cmd_dir/command.bash" ]; then - printf "%s %s\\n" "$cmd_dir/command.bash" 1 + printf "%s %s\n" "$cmd_dir/command.bash" 1 fi } @@ -29,15 +29,15 @@ find_asdf_cmd() { 'exec' | 'current' | 'env' | 'global' | 'install' | 'latest' | 'local' | \ 'reshim' | 'uninstall' | 'update' | 'where' | 'which' | \ 'export-shell-version') - printf "%s %s\\n" "$asdf_cmd_dir/command-$1.bash" 2 + printf "%s %s\n" "$asdf_cmd_dir/command-$1.bash" 2 ;; '' | '--help' | '-h' | 'help') - printf "%s %s\\n" "$asdf_cmd_dir/command-help.bash" 2 + printf "%s %s\n" "$asdf_cmd_dir/command-help.bash" 2 ;; '--version' | 'version') - printf "%s %s\\n" "$asdf_cmd_dir/command-version.bash" 2 + printf "%s %s\n" "$asdf_cmd_dir/command-version.bash" 2 ;; *) @@ -49,10 +49,13 @@ find_asdf_cmd() { find_plugin_cmd() { local ASDF_CMD_FILE args_offset if [ -d "$(get_plugin_path "$1")/bin" ]; then - IFS=' ' read -r ASDF_CMD_FILE args_offset <<<"$(find_cmd "$(get_plugin_path "$1")/lib/commands" "${@:2}")" + local result= + result="$(find_cmd "$(get_plugin_path "$1")/lib/commands" "${@:2}")" + ASDF_CMD_FILE=${result% *} + args_offset=${result##* } if [ -n "$ASDF_CMD_FILE" ]; then args_offset=$((args_offset + 1)) # since the first argument is the plugin name - printf "%s %s\\n" "$ASDF_CMD_FILE" "$args_offset" + printf "%s %s\n" "$ASDF_CMD_FILE" "$args_offset" fi fi } @@ -60,25 +63,45 @@ find_plugin_cmd() { asdf_cmd() { local ASDF_CMD_FILE args_offset - if [ "shell" == "$1" ]; then + if [ "shell" = "$1" ]; then printf "Shell integration is not enabled. Please ensure you source asdf in your shell setup." >&2 exit 1 fi - IFS=' ' read -r ASDF_CMD_FILE args_offset <<<"$(find_asdf_cmd "$@")" + # Internal Variables + ASDF_DEFAULT_TOOL_VERSIONS_FILENAME=$(asdf_tool_versions_filename) + export ASDF_DEFAULT_TOOL_VERSIONS_FILENAME + + ASDF_CONFIG_FILE=$(asdf_config_file) + export ASDF_CONFIG_FILE + + ASDF_DATA_DIR=$(asdf_data_dir) + export ASDF_DATA_DIR + + ASDF_DIR=$(asdf_dir) + export ASDF_DIR + + local result= + result="$(find_asdf_cmd "$@")" + ASDF_CMD_FILE=${result% *} + args_offset=${result##* } if [ -z "$ASDF_CMD_FILE" ]; then - IFS=' ' read -r ASDF_CMD_FILE args_offset <<<"$(find_plugin_cmd "$@")" + result="$(find_plugin_cmd "$@")" + ASDF_CMD_FILE=${result% *} + args_offset=${result##* } fi if [ -x "$ASDF_CMD_FILE" ]; then exec "$ASDF_CMD_FILE" "${@:${args_offset}}" elif [ -f "$ASDF_CMD_FILE" ]; then set -- "${@:${args_offset}}" + # shellcheck source=/dev/null . "$ASDF_CMD_FILE" else local asdf_cmd_dir asdf_cmd_dir="$(asdf_dir)/lib/commands" - printf "%s\\n" "Unknown command: \`asdf ${*}\`" >&2 + printf "%s\n" "Unknown command: \`asdf ${*}\`" >&2 + # shellcheck source=lib/commands/command-help.bash . "$asdf_cmd_dir/command-help.bash" >&2 return 127 fi diff --git a/completions/asdf.bash b/completions/asdf.bash index b66c5e030..f828e9db5 100644 --- a/completions/asdf.bash +++ b/completions/asdf.bash @@ -1,3 +1,12 @@ +_asdf_list_shims() ( + # this function runs in a subshell so shopt is scoped + shopt -s nullglob # globs that don't match should disappear + shopt -u failglob # globs that don't match shouldn't fail + for shim in "${ASDF_DATA_DIR:-$HOME/.asdf}"/shims/*; do + basename "$shim" + done +) + _asdf() { local cur cur=${COMP_WORDS[COMP_CWORD]} @@ -71,13 +80,13 @@ _asdf() { # shellcheck disable=SC2207 COMPREPLY=($(compgen -W "--all" -- "$cur")) ;; - which) + which | shim-versions) # shellcheck disable=SC2207 - COMPREPLY=($(compgen -c -- "$cur")) + COMPREPLY=($(compgen -W "$(_asdf_list_shims)" -- "$cur")) ;; plugin-list | plugin-list-all | info) ;; *) - local cmds='current global help install latest list list-all local plugin-add plugin-list plugin-list-all plugin-remove plugin-update reshim shell uninstall update where which info' + local cmds='current global help install latest list list-all local plugin-add plugin-list plugin-list-all plugin-remove plugin-update reshim shim-versions shell uninstall update where which info' # shellcheck disable=SC2207 COMPREPLY=($(compgen -W "$cmds" -- "$cur")) ;; diff --git a/completions/asdf.fish b/completions/asdf.fish index 372ed5bc3..a309bf49d 100644 --- a/completions/asdf.fish +++ b/completions/asdf.fish @@ -31,107 +31,111 @@ function __fish_asdf_arg_at -a number end function __fish_asdf_list_versions -a plugin - asdf list $plugin 2> /dev/null | sed -e 's/^[[:space:]]*//' + asdf list $plugin 2>/dev/null | string trim | string trim --left --chars '*' end function __fish_asdf_list_all -a plugin - asdf list-all $plugin 2> /dev/null + asdf list-all $plugin 2>/dev/null end function __fish_asdf_plugin_list - asdf plugin-list 2> /dev/null + asdf plugin-list 2>/dev/null end function __fish_asdf_plugin_list_all - asdf plugin-list-all 2> /dev/null + asdf plugin-list-all 2>/dev/null end function __fish_asdf_list_shims - ls $asdf_data_dir/shims + path basename $asdf_data_dir/shims/* end # update -complete -f -c asdf -n '__fish_asdf_needs_command' -a update -d "Update asdf" -complete -f -c asdf -n '__fish_asdf_using_command update; and __fish_asdf_arg_number 2' -l "head" -d "Updates to master HEAD" +complete -f -c asdf -n __fish_asdf_needs_command -a update -d "Update asdf" +complete -f -c asdf -n '__fish_asdf_using_command update; and __fish_asdf_arg_number 2' -l head -d "Updates to master HEAD" # plugin-add completion -complete -f -c asdf -n '__fish_asdf_needs_command' -a plugin-add -d "Add git repo as plugin" +complete -f -c asdf -n __fish_asdf_needs_command -a plugin-add -d "Add git repo as plugin" complete -f -c asdf -n '__fish_asdf_using_command plugin-add; and __fish_asdf_arg_number 2' -a '(__fish_asdf_plugin_list_all | grep -v \'*\' | awk \'{ print $1 }\')' complete -f -c asdf -n '__fish_asdf_using_command plugin-add; and __fish_asdf_arg_number 3' -a '(__fish_asdf_plugin_list_all | grep (__fish_asdf_arg_at 3) | awk \'{ print $2 }\')' complete -f -c asdf -n '__fish_asdf_using_command plugin-add; and __fish_asdf_arg_number 4' # plugin-list completion -complete -f -c asdf -n '__fish_asdf_needs_command' -a plugin-list -d "List installed plugins" +complete -f -c asdf -n __fish_asdf_needs_command -a plugin-list -d "List installed plugins" # plugin-list-all completion -complete -f -c asdf -n '__fish_asdf_needs_command' -a plugin-list-all -d "List all existing plugins" +complete -f -c asdf -n __fish_asdf_needs_command -a plugin-list-all -d "List all existing plugins" # plugin-remove completion -complete -f -c asdf -n '__fish_asdf_needs_command' -a plugin-remove -d "Remove plugin and package versions" +complete -f -c asdf -n __fish_asdf_needs_command -a plugin-remove -d "Remove plugin and package versions" complete -f -c asdf -n '__fish_asdf_using_command plugin-remove; and __fish_asdf_arg_number 2' -a '(__fish_asdf_plugin_list)' # plugin-update completion -complete -f -c asdf -n '__fish_asdf_needs_command' -a plugin-update -d "Update plugin" +complete -f -c asdf -n __fish_asdf_needs_command -a plugin-update -d "Update plugin" complete -f -c asdf -n '__fish_asdf_using_command plugin-update; and __fish_asdf_arg_number 2' -a '(__fish_asdf_plugin_list)' complete -f -c asdf -n '__fish_asdf_using_command plugin-update; and __fish_asdf_arg_number 2' -a --all # install completion -complete -f -c asdf -n '__fish_asdf_needs_command' -a install -d "Install a specific version of a package" +complete -f -c asdf -n __fish_asdf_needs_command -a install -d "Install a specific version of a package" complete -f -c asdf -n '__fish_asdf_using_command install; and __fish_asdf_arg_number 2' -a '(__fish_asdf_plugin_list)' complete -f -c asdf -n '__fish_asdf_using_command install; and __fish_asdf_arg_number 3' -a '(__fish_asdf_list_all (__fish_asdf_arg_at 3))' # uninstall completion -complete -f -c asdf -n '__fish_asdf_needs_command' -a uninstall -d "Remove a specific version of a package" +complete -f -c asdf -n __fish_asdf_needs_command -a uninstall -d "Remove a specific version of a package" complete -f -c asdf -n '__fish_asdf_using_command uninstall; and __fish_asdf_arg_number 2' -a '(__fish_asdf_plugin_list)' complete -f -c asdf -n '__fish_asdf_using_command uninstall; and __fish_asdf_arg_number 3' -a '(__fish_asdf_list_versions (__fish_asdf_arg_at 3))' # current completion -complete -f -c asdf -n '__fish_asdf_needs_command' -a current -d "Display version set or being used for package" +complete -f -c asdf -n __fish_asdf_needs_command -a current -d "Display version set or being used for package" complete -f -c asdf -n '__fish_asdf_using_command current; and __fish_asdf_arg_number 2' -a '(__fish_asdf_plugin_list)' # where completion -complete -f -c asdf -n '__fish_asdf_needs_command' -a where -d "Display install path for an installed version" +complete -f -c asdf -n __fish_asdf_needs_command -a where -d "Display install path for an installed version" complete -f -c asdf -n '__fish_asdf_using_command where; and __fish_asdf_arg_number 2' -a '(__fish_asdf_plugin_list)' complete -f -c asdf -n '__fish_asdf_using_command where; and __fish_asdf_arg_number 3' -a '(__fish_asdf_list_versions (__fish_asdf_arg_at 3))' # which completion -complete -f -c asdf -n '__fish_asdf_needs_command' -a which -d "Display executable path for a command" +complete -f -c asdf -n __fish_asdf_needs_command -a which -d "Display executable path for a command" complete -f -c asdf -n '__fish_asdf_using_command which; and __fish_asdf_arg_number 2' -a '(__fish_asdf_list_shims)' # latest completion -complete -f -c asdf -n '__fish_asdf_needs_command' -a latest -d "Show latest stable version of a package" +complete -f -c asdf -n __fish_asdf_needs_command -a latest -d "Show latest stable version of a package" complete -f -c asdf -n '__fish_asdf_using_command latest; and __fish_asdf_arg_number 2' -a '(__fish_asdf_plugin_list)' complete -f -c asdf -n '__fish_asdf_using_command latest; and __fish_asdf_arg_number 2' -a --all # list completion -complete -f -c asdf -n '__fish_asdf_needs_command' -a list -d "List installed versions of a package" +complete -f -c asdf -n __fish_asdf_needs_command -a list -d "List installed versions of a package" complete -f -c asdf -n '__fish_asdf_using_command list; and __fish_asdf_arg_number 2' -a '(__fish_asdf_plugin_list)' # list-all completion -complete -f -c asdf -n '__fish_asdf_needs_command' -a list-all -d "List all versions of a package" +complete -f -c asdf -n __fish_asdf_needs_command -a list-all -d "List all versions of a package" complete -f -c asdf -n '__fish_asdf_using_command list-all; and __fish_asdf_arg_number 2' -a '(__fish_asdf_plugin_list)' # reshim completion -complete -f -c asdf -n '__fish_asdf_needs_command' -a reshim -d "Recreate shims for version of a package" +complete -f -c asdf -n __fish_asdf_needs_command -a reshim -d "Recreate shims for version of a package" complete -f -c asdf -n '__fish_asdf_using_command reshim; and __fish_asdf_arg_number 2' -a '(__fish_asdf_plugin_list)' complete -f -c asdf -n '__fish_asdf_using_command reshim; and __fish_asdf_arg_number 3' -a '(__fish_asdf_list_versions (__fish_asdf_arg_at 3))' +# shim-versions completion +complete -f -c asdf -n __fish_asdf_needs_command -a shim-versions -d "List the plugins and versions that provide a command" +complete -f -c asdf -n '__fish_asdf_using_command shim-versions; and __fish_asdf_arg_number 2' -a '(__fish_asdf_list_shims)' + # local completion -complete -f -c asdf -n '__fish_asdf_needs_command' -a local -d "Set local version for a plugin" +complete -f -c asdf -n __fish_asdf_needs_command -a local -d "Set local version for a plugin" complete -f -c asdf -n '__fish_asdf_using_command local; and __fish_asdf_arg_number 2' -a '(__fish_asdf_plugin_list)' complete -f -c asdf -n '__fish_asdf_using_command local; and test (count (commandline -opc)) -gt 2' -a '(__fish_asdf_list_versions (__fish_asdf_arg_at 3)) system' # global completion -complete -f -c asdf -n '__fish_asdf_needs_command' -a global -d "Set global version for a plugin" +complete -f -c asdf -n __fish_asdf_needs_command -a global -d "Set global version for a plugin" complete -f -c asdf -n '__fish_asdf_using_command global; and __fish_asdf_arg_number 2' -a '(__fish_asdf_plugin_list)' complete -f -c asdf -n '__fish_asdf_using_command global; and test (count (commandline -opc)) -gt 2' -a '(__fish_asdf_list_versions (__fish_asdf_arg_at 3)) system' # shell completion -complete -f -c asdf -n '__fish_asdf_needs_command' -a shell -d "Set version for a plugin in current shell session" +complete -f -c asdf -n __fish_asdf_needs_command -a shell -d "Set version for a plugin in current shell session" complete -f -c asdf -n '__fish_asdf_using_command shell; and __fish_asdf_arg_number 2' -a '(__fish_asdf_plugin_list)' complete -f -c asdf -n '__fish_asdf_using_command shell; and test (count (commandline -opc)) -gt 2' -a '(__fish_asdf_list_versions (__fish_asdf_arg_at 3)) system' # misc -complete -f -c asdf -n '__fish_asdf_needs_command' -l "help" -d "Displays help" -complete -f -c asdf -n '__fish_asdf_needs_command' -a "info" -d "Print OS, Shell and ASDF debug information" -complete -f -c asdf -n '__fish_asdf_needs_command' -l "version" -d "Displays asdf version" +complete -f -c asdf -n __fish_asdf_needs_command -l help -d "Displays help" +complete -f -c asdf -n __fish_asdf_needs_command -a info -d "Print OS, Shell and ASDF debug information" +complete -f -c asdf -n __fish_asdf_needs_command -l version -d "Displays asdf version" diff --git a/defaults b/defaults index 382278058..abd3428b7 100644 --- a/defaults +++ b/defaults @@ -5,3 +5,4 @@ use_release_candidates = no always_keep_download = no plugin_repository_last_check_duration = 60 disable_plugin_short_name_repository = no +concurrency = auto diff --git a/docs/contribute/core.md b/docs/contribute/core.md index 76201e2d7..046f195fd 100644 --- a/docs/contribute/core.md +++ b/docs/contribute/core.md @@ -40,14 +40,15 @@ If you want to try out your changes without making change to your installed `asd It is best to format, lint and test your code locally before you commit or push to the remote. Use the following scripts/commands: ```shell:no-line-numbers -# Shellcheck -./scripts/shellcheck.bash +# Lint +./scripts/lint.bash --check -# Format -./scripts/shfmt.bash +# Fix & Format +./scripts/lint.bash --fix # Test: all tests -bats test/ +./scripts/test.bash + # Test: for specific command bats test/list_commands.bash ``` diff --git a/docs/guide/getting-started.md b/docs/guide/getting-started.md index 5ec756976..1a75744e1 100644 --- a/docs/guide/getting-started.md +++ b/docs/guide/getting-started.md @@ -35,7 +35,7 @@ asdf primarily requires `git` & `curl`. Here is a _non-exhaustive_ list of comma ```shell:no-line-numbers -git clone https://github.com/asdf-vm/asdf.git ~/.asdf --branch v0.11.3 +git clone https://github.com/asdf-vm/asdf.git ~/.asdf --branch v0.12.0 ``` @@ -51,7 +51,9 @@ We highly recommend using the official `git` method. ## 3. Install asdf -There are many different combinations of Shells, OSs & Installation methods all of which affect the configuration here. Expand the selection below that best matches your system: +There are many different combinations of Shells, OSs & Installation methods all of which affect the configuration here. Expand the selection below that best matches your system. + +**macOS users, be sure to read the warning about `path_helper` at the end of this section.** ::: details Bash & Git @@ -261,6 +263,36 @@ Add the following to `~/.zshrc`: Completions are placed in a ZSH friendly location, but [ZSH must be configured to use the autocompletions](https://wiki.archlinux.org/index.php/zsh#Command_completion). ::: +::: details PowerShell Core & Git + +Add the following to `~/.config/powershell/profile.ps1`: + +```shell +. "$HOME/.asdf/asdf.ps1" +``` + +::: + +::: details PowerShell Core & Homebrew + +Add `asdf.sh` to your `~/.config/powershell/profile.ps1` with: + +```shell:no-line-numbers +echo -e "\n. \"$(brew --prefix asdf)/libexec/asdf.ps1\"" >> ~/.config/powershell/profile.ps1 +``` + +::: + +::: details PowerShell Core & Pacman + +Add the following to `~/.config/powershell/profile.ps1`: + +```shell +. /opt/asdf-vm/asdf.ps1 +``` + +::: + ::: details Nushell & Git Add `asdf.nu` to your `~/.config/nushell/config.nu` with: @@ -277,7 +309,7 @@ Completions are automatically configured Add `asdf.nu` to your `~/.config/nushell/config.nu` with: ```shell:no-line-numbers -"\nlet-env ASDF_NU_DIR = (brew --prefix asdf | into string | path join 'libexec')\n source " + (brew --prefix asdf | into string | path join 'libexec/asdf.nu') | save --append $nu.config-path +"\nlet-env ASDF_NU_DIR = (brew --prefix asdf | str trim | into string | path join 'libexec')\n source " + (brew --prefix asdf | into string | path join 'libexec/asdf.nu') | save --append $nu.config-path ``` Completions are automatically configured @@ -294,8 +326,45 @@ Add `asdf.nu` to your `~/.config/nushell/config.nu` with: Completions are automatically configured. ::: +::: details POSIX Shell & Git + +Add the following to `~/.profile`: + +```shell +export ASDF_DIR="$HOME/.asdf" +. "$HOME/.asdf/asdf.sh" +``` + +::: + +::: details POSIX Shell & Homebrew + +Add `asdf.sh` to your `~/.profile` with: + +```shell:no-line-numbers +echo -e "\nexport ASDF_DIR=\"$(brew --prefix asdf)/libexec/asdf.sh\"" >> ~/.profile +echo -e "\n. \"$(brew --prefix asdf)/libexec/asdf.sh\"" >> ~/.profile +``` + +::: + +::: details POSIX Shell & Pacman + +Add the following to `~/.profile`: + +```shell +export ASDF_DIR="/opt/asdf-vm" +. /opt/asdf-vm/asdf.sh +``` + +::: + `asdf` scripts need to be sourced **after** you have set your `$PATH` and **after** you have sourced your framework (oh-my-zsh etc). +::: warning +On macOS, starting a Bash or Zsh shell automatically calls a utility called `path_helper`. `path_helper` can rearrange items in `PATH` (and `MANPATH`), causing inconsistent behavior for tools that require specific ordering. To workaround this, `asdf` on macOS defaults to forcily adding its `PATH`-entries to the front (taking highest priority). This is controllable with the `ASDF_FORCE_PREPEND` variable.`. +::: + Restart your shell so that `PATH` changes take effect. Opening a new terminal tab will usually do it. ## Core Installation Complete! diff --git a/docs/manage/configuration.md b/docs/manage/configuration.md index 17e22f4d4..1ec7d83ed 100644 --- a/docs/manage/configuration.md +++ b/docs/manage/configuration.md @@ -50,9 +50,13 @@ To install a single tool defined in a `.tool-versions` file run `asdf install /asdf.sh"` + +## Full Configuration Example + +Following a simple asdf setup with: + +- a Bash Shell +- an installation location of `$HOME/.asdf` +- installed via Git +- NO environment variables set +- NO custom `.asdfrc` file + +would result in the following outcomes: + +| Configuration | Value | Calculated by | +| :------------------------------------ | :--------------- | :------------------------------------------------------------------------------------------------------------------------------------------------- | +| config file location | `$HOME/.asdfrc` | `ASDF_CONFIG_FILE` is empty, so use `$HOME/.asdfrc` | +| default tool versions filename | `.tool-versions` | `ASDF_DEFAULT_TOOL_VERSIONS_FILENAME` is empty, so use `.tool-versions` | +| asdf dir | `$HOME/.asdf` | `ASDF_DIR` is empty, so use parent dir of `bin/asdf` | +| asdf data dir | `$HOME/.asdf` | `ASDF_DATA_DIR` is empty so use `$HOME/.asdf` as `$HOME` exists. | +| concurrency | `auto` | `ASDF_CONCURRENCY` is empty, so rely on `concurrency` value from the [default configuration](https://github.com/asdf-vm/asdf/blob/master/defaults) | +| legacy_version_file | `no` | No custom `.asdfrc`, so use the [default configuration](https://github.com/asdf-vm/asdf/blob/master/defaults) | +| use_release_candidates | `no` | No custom `.asdfrc`, so use the [default configuration](https://github.com/asdf-vm/asdf/blob/master/defaults) | +| always_keep_download | `no` | No custom `.asdfrc`, so use the [default configuration](https://github.com/asdf-vm/asdf/blob/master/defaults) | +| plugin_repository_last_check_duration | `60` | No custom `.asdfrc`, so use the [default configuration](https://github.com/asdf-vm/asdf/blob/master/defaults) | +| disable_plugin_short_name_repository | `no` | No custom `.asdfrc`, so use the [default configuration](https://github.com/asdf-vm/asdf/blob/master/defaults) | ## Internal Configuration Users should not worry about this section as it describes configuration internal to `asdf` useful for Package Managers and integrators. -- `$ASDF_DIR/asdf_updates_disabled`: Updates via the `asdf update` command are disabled when this file is present (content irrelevant). This is used by Package Managers like Pacman or Homebrew to ensure the correct update method is used for the particular installation. +- `$ASDF_DIR/asdf_updates_disabled`: Updates via the `asdf update` command are disabled when this file is present (content irrelevant). This is used by package managers like Pacman or Homebrew to ensure the correct update method is used for the particular installation. diff --git a/docs/manage/core.md b/docs/manage/core.md index 65e61e920..970a62d90 100644 --- a/docs/manage/core.md +++ b/docs/manage/core.md @@ -221,7 +221,7 @@ rm -rf ~/.config/fish/completions/asdf.fish 2. Remove the `$HOME/.asdf` dir: ```shell:no-line-numbers -rm -rf "${ASDF_DATA_DIR:-$HOME/.asdf}" +rm -rf (string join : -- $ASDF_DATA_DIR $HOME/.asdf) ``` 3. Run this command to remove all `asdf` config files: @@ -271,7 +271,7 @@ pacman -Rs asdf-vm 3. Remove the `$HOME/.asdf` dir: ```shell:no-line-numbers -rm -rf "${ASDF_DATA_DIR:-$HOME/.asdf}" +rm -rf (string join : -- $ASDF_DATA_DIR $HOME/.asdf) ``` 4. Run this command to remove all `asdf` config files: diff --git a/docs/manage/versions.md b/docs/manage/versions.md index 3afc0c59c..99f8f83d2 100644 --- a/docs/manage/versions.md +++ b/docs/manage/versions.md @@ -80,7 +80,7 @@ asdf local latest[:] `global` writes the version to `$HOME/.tool-versions`. -`shell` set the version to an environment variable named `ASDF_${LANG}_VERSION`, for the current shell session only. +`shell` set the version to an environment variable named `ASDF_${TOOL}_VERSION`, for the current shell session only. `local` writes the version to `$PWD/.tool-versions`, creating it if needed. diff --git a/docs/plugins/create.md b/docs/plugins/create.md index cafc9f053..b87566fae 100644 --- a/docs/plugins/create.md +++ b/docs/plugins/create.md @@ -1,104 +1,525 @@ # Create a Plugin -## What's in a Plugin - -A plugin is a git repo, with a couple executable scripts, to support versioning another language or tool. These scripts are run when `list-all`, `install` or `uninstall` commands are run. You can set or unset env vars and do anything required to setup the environment for the tool. +A plugin is a Git repo with some executable scripts to support versioning a +language / tool. These scripts are run by asdf using specific commands to +support features such as `asdf list-all `, `asdf install ` +etc. + +## Quickstart + +There are two options to get started with creating your own plugin: + +1. use the + [asdf-vm/asdf-plugin-template](https://github.com/asdf-vm/asdf-plugin-template) + repository to + [generate](https://github.com/asdf-vm/asdf-plugin-template/generate) a plugin + repo (named `asdf-`) with default scripts implemented. Once + generated, clone the repo and run the `setup.bash` script to interactively + update the template. +2. start your own repo called `asdf-` and implement the required + scripts as listed in the documentation below. + +### Golden Rules for Plugin Scripts + +- scripts should **NOT** call other `asdf` commands +- keep your dependency list of Shell tools/commands small +- avoid non-portable tools or command flags. For example, `sort -V`. See our + asdf core + [list of banned commands](https://github.com/asdf-vm/asdf/blob/master/test/banned_commands.bats) + +## Scripts Overview + +The full list of scripts callable from asdf. + +| Script | Description | +| :---------------------------------------------------------------------------------------------------- |:-----------------------------------------------------------------| +| [bin/list-all](#bin-list-all) | List all installable versions | +| [bin/download](#bin-download) | Download source code or binary for the specified version | +| [bin/install](#bin-install) | Installs the specified version | +| [bin/latest-stable](#bin-latest-stable) | List the latest stable version of the specified tool | +| [bin/help.overview](#bin-help.overview) | Output a general description about the plugin & tool | +| [bin/help.deps](#bin-help.deps) | Output a list of dependencies per Operating System | +| [bin/help.config](#bin-help.config) | Output plugin or tool configuration information | +| [bin/help.links](#bin-help.links) | Output a list of links for the plugin or tool | +| [bin/list-bin-paths](#bin-list-bin-paths) | List relative paths to directories with binaries to create shims | +| [bin/exec-env](#bin-exec-env) | Prepare the environment for running the binaries | +| [bin/exec-path](#bin-exec-path) | Output the executable path for a version of a tool | +| [bin/uninstall](#bin-uninstall) | Uninstall a specific version of a tool | +| [bin/list-legacy-filenames](#bin-list-legacy-filenames) | Output filenames of legacy version files: `.ruby-version` | +| [bin/parse-legacy-file](#bin-parse-legacy-file) | Custom parser for legacy version files | +| [bin/post-plugin-add](#bin-post-plugin-add) | Hook to execute after a plugin has been added | +| [bin/post-plugin-update](#bin-post-plugin-update) | Hook to execute after a plugin has been updated | +| [bin/pre-plugin-remove](#bin-pre-plugin-remove) | Hook to execute before a plugin is removed | + +To see which commands invoke which scripts, see the detailed documentation for +each script. + +## Environment Variables Overview + +The full list of Environment Variables used throughout all scripts. + +| Environment Variables | Description | +| :----------------------- |:----------------------------------------------------------------------------------------| +| `ASDF_INSTALL_TYPE` | `version` or `ref` | +| `ASDF_INSTALL_VERSION` | full version number or Git Ref depending on `ASDF_INSTALL_TYPE` | +| `ASDF_INSTALL_PATH` | the path to where the tool _should_, or _has been_ installed | +| `ASDF_CONCURRENCY` | the number of cores to use when compiling the source code. Useful for setting `make -j` | +| `ASDF_DOWNLOAD_PATH` | the path to where the source code or binary was downloaded to by `bin/download` | +| `ASDF_PLUGIN_PATH` | the path the plugin was installed | +| `ASDF_PLUGIN_SOURCE_URL` | the source URL of the plugin | +| `ASDF_PLUGIN_PREV_REF` | prevous `git-ref` of the plugin repo | +| `ASDF_PLUGIN_POST_REF` | updated `git-ref` of the plugin repo | +| `ASDF_CMD_FILE` | resolves to the full path of the file being sourced | + +::: tip NOTE + +**Not all environment variables are available in all scripts.** Check the +documentation for each script below to see which env vars are available to it. + +::: ## Required Scripts -- `bin/list-all` - lists all installable versions -- `bin/download` - download source code or binary for the specified version -- `bin/install` - installs the specified version +### `bin/list-all` -## Environment Variables +**Description** -All scripts except `bin/list-all` will have access to the following env vars to act upon: +List all installable versions. -- `ASDF_INSTALL_TYPE` - `version` or `ref` -- `ASDF_INSTALL_VERSION` - if `ASDF_INSTALL_TYPE` is `version` then this will be the version number. Else it will be the git ref that is passed. Might point to a tag/commit/branch on the repo. -- `ASDF_INSTALL_PATH` - the dir where it _has been_ installed (or _should_ be installed in case of the `bin/install` script) +**Output Format** -These additional environment variables will be available to the `bin/install` script: +Must print a string with a **space-separated** list of versions. For example: -- `ASDF_CONCURRENCY` - the number of cores to use when compiling the source code. Useful for setting `make -j`. -- `ASDF_DOWNLOAD_PATH` - the path to where the source code or binary was downloaded by the `bin/download` script. +```text:no-line-numbers +1.0.1 1.0.2 1.3.0 1.4 +``` -These additional environment variables will be available to the `bin/download` script: +Newest version should be last. -- `ASDF_DOWNLOAD_PATH` - the path to where the source code or binary should be downloaded. +asdf core will print each version on its own line, potentially pushing some +versions offscreen. -#### bin/list-all +**Sorting** -Must print a string with a space-separated list of versions. Example output would be the following: +If versions are being pulled from releases page on a website it's recommended to +leave the versions in the provided order as they are often already in the +correct order. If they are in reverse order piping them through `tac` should +suffice. -```shell -1.0.1 1.0.2 1.3.0 1.4 +If sorting is unavoidable, `sort -V` is not portable, so we suggest either: + +- [using the Git sort capability](https://github.com/asdf-vm/asdf-plugin-template/blob/main/template/lib/utils.bash) + (requires Git >= `v2.18.0`) +- [writing a custom sort method](https://github.com/vic/asdf-idris/blob/master/bin/list-all#L6) + (requires `sed`, `sort` & `awk`) + +**Environment Variables available to script** + +No environment variables are provided to this script. + +**Commands that invoke this script** + +- `asdf list all [version]` +- `asdf list all nodejs`: lists all versions as returned by this script, one on + each line. +- `asdf list all nodejs 18`: lists all versions as returned by this script, one + on each line, with a filter matching any version beginning with `18` applied. + +**Call signature from asdf core** + +No parameters provided. + +```bash:no-line-numbers +"${plugin_path}/bin/list-all" +``` + +--- + +### `bin/download` + +**Description** + +Download the source code or binary for a specific version of a tool to a specified location. + +**Implementation Details** + +- The script must download the source or binary to the directory specified by `ASDF_DOWNLOAD_PATH`. +- Only the decompressed source code or binary should be placed in the `ASDF_DOWNLOAD_PATH` directory. +- On failure, no files should be placed in `ASDF_DOWNLOAD_PATH`. +- Success should exit with `0`. +- Failure should exit with a non-zero status. + +**Legacy Plugins** + +Though this script is marked as _required_ for all plugins, it is _optional_ for "legacy" plugins which predate its introduction. + +If this script is absent, asdf will assume that the `bin/install` script is present and will download **and** install the version. + +All plugins must include this script as support for legacy plugins will eventually be removed. + +**Environment Variables available to script** + +- `ASDF_INSTALL_TYPE`: `version` or `ref` +- `ASDF_INSTALL_VERSION`: + - Full version number if `ASDF_INSTALL_TYPE=version`. + - Git ref (tag/commit/branch) if `ASDF_INSTALL_TYPE=ref`. +- `ASDF_INSTALL_PATH`: The path to where the tool _has been_, or _should be_ installed. +- `ASDF_DOWNLOAD_PATH`: The path to where the source code or binary was downloaded to. + +**Commands that invoke this script** + +- `asdf install [version]` +- `asdf install latest[:version]` +- `asdf install nodejs 18.0.0`: downloads the source code or binary for Node.js + version `18.0.0` and places it in the `ASDF_DOWNLOAD_PATH` directory. Then runs the `bin/install` script. + +**Call signature from asdf core** + +No parameters provided. + +```bash:no-line-numbers +"${plugin_path}"/bin/download ``` -Note that the newest version should be listed last so it appears closer to the user's prompt. This is helpful since the `list-all` command prints each version on its own line. If there are many versions it's possible the early versions will be off screen. +--- + +### `bin/install` + +**Description** -If versions are being pulled from releases page on a website it's recommended to not sort the versions if at all possible. Often the versions are already in the correct order or, in reverse order, in which case something like `tac` should suffice. If you must sort versions manually you cannot rely on `sort -V` since it is not supported on OSX. An alternate sort function [like this is a better choice](https://github.com/vic/asdf-idris/blob/master/bin/list-all#L6). +Install a specific version of a tool to a specified location. -#### bin/download +**Implementation Details** -This script must download the source or binary, in the path contained in the `ASDF_DOWNLOAD_PATH` environment variable. If the downloaded source or binary is compressed, only the uncompressed source code or binary may be placed in the `ASDF_DOWNLOAD_PATH` directory. +- The script should install the specified version in the path `ASDF_INSTALL_PATH`. +- Shims will be created by default for any files in `$ASDF_INSTALL_PATH/bin`. This behaviour can be customised with the optional +[bin/list-bin-paths](#binlist-bin-paths) script. +- Success should exit with `0`. +- Failure should exit with a non-zero status. +- To avoid TOCTOU (Time-of-Check-to-Time-of-Use) issues, ensure the script only places files in `ASDF_INSTALL_PATH` once the build and installation of the tool is deemed a success. -The script must exit with a status of `0` when the download is successful. If the download fails the script must exit with any non-zero exit status. +**Legacy Plugins** -If possible the script should only place files in the `ASDF_DOWNLOAD_PATH`. If the download fails no files should be placed in the directory. +If the `bin/download` script is absent, this script should download **and** install the specified version. -If this script is not present asdf will assume that the `bin/install` script is present and will download and install the version. asdf only works without this script to support legacy plugins. All plugins must include this script, and eventually support for legacy plugins will be removed. +For compatibility with versions of the asdf core earlier than `0.7._` and newer than `0.8._`, check for the presence of the `ASDF_DOWNLOAD_PATH` environment +variable. If set, assume the `bin/download` script already downloaded the version, else download the source code in the `bin/install` script. -#### bin/install +**Environment Variables available to script** -This script should install the version, in the path mentioned in `ASDF_INSTALL_PATH`. By default, asdf will create shims for any files in `$ASDF_INSTALL_PATH/bin` (this can be customized with the optional [bin/list-bin-paths](#binlist-bin-paths) script). +- `ASDF_INSTALL_TYPE`: `version` or `ref` +- `ASDF_INSTALL_VERSION`: + - Full version number if `ASDF_INSTALL_TYPE=version`. + - Git ref (tag/commit/branch) if `ASDF_INSTALL_TYPE=ref`. +- `ASDF_INSTALL_PATH`: The path to where the tool _has been_, or _should be_ installed. +- `ASDF_CONCURRENCY`: The number of cores to use when compiling source code. Useful for setting flags like `make -j`. +- `ASDF_DOWNLOAD_PATH`: The path where the source code or binary was downloaded to. -The install script should exit with a status of `0` when the installation is successful. If the installation fails the script should exit with any non-zero exit status. +**Commands that invoke this script** -If possible the script should only place files in the `ASDF_INSTALL_PATH` directory once the build and installation of the tool is deemed successful by the install script. asdf [checks for the existence](https://github.com/asdf-vm/asdf/blob/242d132afbf710fe3c7ec23c68cec7bdd2c78ab5/lib/utils.sh#L44) of the `ASDF_INSTALL_PATH` directory in order to determine if that version of the tool is installed. If the `ASDF_INSTALL_PATH` directory is populated at the beginning of the installation process other asdf commands run in other terminals during the installation may consider that version of the tool installed, even when it is not fully installed. +- `asdf install` +- `asdf install ` +- `asdf install [version]` +- `asdf install latest[:version]` +- `asdf install nodejs 18.0.0`: installs Node.js version `18.0.0` in the + `ASDF_INSTALL_PATH` directory. -If you want your plugin to work with asdf version 0.7._ and earlier and version 0.8._ and newer check for the presence of the `ASDF_DOWNLOAD_PATH` environment variable. If it is not set download the source code in the bin/install callback. If it is set assume the `bin/download` script already downloaded it. +**Call signature from asdf core** + +No parameters provided. + +```bash:no-line-numbers +"${plugin_path}"/bin/install +``` ## Optional Scripts -#### bin/help scripts +### `bin/latest-stable` -This is not one callback script but rather a set of callback scripts that each print different documentation to STDOUT. The possible callback scripts are listed below. Note that `bin/help.overview` is a special case as it must be present for any help output to be displayed for the script. +**Description** -- `bin/help.overview` - This script should output a general description about the plugin and the tool being managed. No heading should be printed as asdf will print headings. Output may be free-form text but ideally only one short paragraph. This script must be present if you want asdf to provide help information for your plugin. All other help callback scripts are optional. -- `bin/help.deps` - This script should output the list of dependencies tailored to the operating system. One dependency per line. -- `bin/help.config` - This script should print any required or optional configuration that may be available for the plugin and tool. Any environment variables or other flags needed to install or compile the tool (for the users operating system when possible). Output can be free-form text. -- `bin/help.links` - This should be a list of links relevant to the plugin and tool (again, tailored to the current operating system when possible). One link per line. Lines may be in the format `: <link>` or just `<link>`. +Determine the latest stable version of a tool. If absent, the asdf core will `tail` the `bin/list-all` output which may be undesirable. -Each of these scripts should tailor their output to the current operating system. For example, when on Ubuntu the deps script could output the dependencies as apt-get packages that must be installed. The script should also tailor its output to the value of `ASDF_INSTALL_VERSION` and `ASDF_INSTALL_TYPE` when the variables are set. They are optional and will not always be set. +**Implementation Details** -The help callback script MUST NOT output any information that is already covered in the core asdf-vm documentation. General asdf usage information must not be present. +- The script should print the latest stable version of the tool to stdout. +- Non-stable or release candidate versions should be omitted. +- A filter query is provided as the first argument to the script. This should be used to filter the output by version number or tool provider. + - For instance, the output of `asdf list all ruby` from the [ruby plugin](https://github.com/asdf-vm/asdf-ruby) lists versions of Ruby from many providers: `jruby`, `rbx` & `truffleruby` amongst others. The user provided filter could be used by the plugin to filter the semver versions and/or provider. + ``` + > asdf latest ruby + 3.2.2 + > asdf latest ruby 2 + 2.7.8 + > asdf latest ruby truffleruby + truffleruby+graalvm-22.3.1 + ``` +- Success should exit with `0`. +- Failure should exit with a non-zero status. -#### bin/latest-stable +**Environment Variables available to script** -If this callback is implemented asdf will use it to determine the latest stable version of your tool instead of trying deduce it for you on its own. `asdf latest` deduces the latest version by looking at the last version printed by the `list-all` callback, after a few types of versions (like release candidate versions) are excluded from the output. This default behavior is undesirable when your plugin's `list-all` callback prints different variations of the same tool and the last version isn't the latest stable version of the variation you'd like to default to. For example with Ruby the latest stable version should be the regular implementation of Ruby (MRI), but truffleruby versions are printed last by the `list-all` callback. +- `ASDF_INSTALL_TYPE`: `version` or `ref` +- `ASDF_INSTALL_VERSION`: + - Full version number if `ASDF_INSTALL_TYPE=version`. + - Git ref (tag/commit/branch) if `ASDF_INSTALL_TYPE=ref`. +- `ASDF_INSTALL_PATH`: The path to where the tool _has been_, or _should be_ installed. -This callback is invoked with a single "filter" string as its only argument. This should be used for filter all latest stable versions. For example with Ruby, the user may choose to pass in `jruby` to select the latest stable version of `jruby`. +**Commands that invoke this script** -#### bin/list-bin-paths +- `asdf global <tool> latest`: set the global version of a tool to the latest stable version for that tool. +- `asdf local <name> latest`: set the local version of a tool to the latest stable version for that tool. +- `asdf install <tool> latest`: installs the latest version of a tool. +- `asdf latest <tool> [<version>]`: outputs the latest version of a tool based on the optional filter. +- `asdf latest --all`: outputs the latest version of all tools managed by asdf and whether they are installed. -List executables for the specified version of the tool. Must print a string with a space-separated list of dir paths that contain executables. The paths must be relative to the install path passed. Example output would be: +**Call signature from asdf core** -```shell +The script should accept a single argument, the filter query. + +```bash:no-line-numbers +"${plugin_path}"/bin/latest-stable "" +"${plugin_path}"/bin/latest-stable "$query" +``` + +--- + +### `bin/help.overview` + +**Description** + +Output a general description about the plugin and the tool being managed. + +**Implementation Details** + +- This script is required for any help output to be displayed for the plugin. +- No heading should be printed as asdf core will print headings. +- Output may be free-form text but ideally only one short paragraph. +- Must not output any information that is already covered in the core asdf-vm documentation. +- Should be tailored to the Operating System and version of the tool being installed (using optionally set Environment Variables `ASDF_INSTALL_VERSION` and `ASDF_INSTALL_TYPE`). +- Success should exit with `0`. +- Failure should exit with a non-zero status. + +**Environment Variables available to script** + +- `ASDF_INSTALL_TYPE`: `version` or `ref` +- `ASDF_INSTALL_VERSION`: + - Full version number if `ASDF_INSTALL_TYPE=version`. + - Git ref (tag/commit/branch) if `ASDF_INSTALL_TYPE=ref`. +- `ASDF_INSTALL_PATH`: The path to where the tool _has been_, or _should be_ installed. + +**Commands that invoke this script** + +- `asdf help <name> [<version>]`: Output documentation for plugin and tool + +**Call signature from asdf core** + +```bash:no-line-numbers +"${plugin_path}"/bin/help.overview +``` + +--- + +### `bin/help.deps` + +**Description** + +Output the list of dependencies tailored to the operating system. One dependency per line. + +```bash:no-line-numbers +git +curl +sed +``` + +**Implementation Details** + +- This script requires `bin/help.overview` for its output to be considered. +- Should be tailored to the Operating System and version of the tool being installed (using optionally set Environment Variables `ASDF_INSTALL_VERSION` and `ASDF_INSTALL_TYPE`). +- Success should exit with `0`. +- Failure should exit with a non-zero status. + +**Environment Variables available to script** + +- `ASDF_INSTALL_TYPE`: `version` or `ref` +- `ASDF_INSTALL_VERSION`: + - Full version number if `ASDF_INSTALL_TYPE=version`. + - Git ref (tag/commit/branch) if `ASDF_INSTALL_TYPE=ref`. +- `ASDF_INSTALL_PATH`: The path to where the tool _has been_, or _should be_ installed. + +**Commands that invoke this script** + +- `asdf help <name> [<version>]`: Output documentation for plugin and tool + +**Call signature from asdf core** + +```bash:no-line-numbers +"${plugin_path}"/bin/help.deps +``` + +--- + +### `bin/help.config` + +**Description** + +Output any required or optional configuration for the plugin and tool. For example, describe any environment variables or other flags needed to install or compile the tool. + +**Implementation Details** + +- This script requires `bin/help.overview` for its output to be considered. +- Output can be free-form text. +- Should be tailored to the Operating System and version of the tool being installed (using optionally set Environment Variables `ASDF_INSTALL_VERSION` and `ASDF_INSTALL_TYPE`). +- Success should exit with `0`. +- Failure should exit with a non-zero status. + +**Environment Variables available to script** + +- `ASDF_INSTALL_TYPE`: `version` or `ref` +- `ASDF_INSTALL_VERSION`: + - Full version number if `ASDF_INSTALL_TYPE=version`. + - Git ref (tag/commit/branch) if `ASDF_INSTALL_TYPE=ref`. +- `ASDF_INSTALL_PATH`: The path to where the tool _has been_, or _should be_ installed. + +**Commands that invoke this script** + +- `asdf help <name> [<version>]`: Output documentation for plugin and tool + +**Call signature from asdf core** + +```bash:no-line-numbers +"${plugin_path}"/bin/help.config +``` + +--- + +### `bin/help.links` + +**Description** + +Output a list of links relevant to the plugin and tool. One link per line. + +```bash:no-line-numbers +Git Repository: https://github.com/vlang/v +Documentation: https://vlang.io +``` + +**Implementation Details** + +- This script requires `bin/help.overview` for its output to be considered. +- One link per line. +- Format must be either: + - `<title>: <link>` + - or just `<link>` +- Should be tailored to the Operating System and version of the tool being installed (using optionally set Environment Variables `ASDF_INSTALL_VERSION` and `ASDF_INSTALL_TYPE`). +- Success should exit with `0`. +- Failure should exit with a non-zero status. + +**Environment Variables available to script** + +- `ASDF_INSTALL_TYPE`: `version` or `ref` +- `ASDF_INSTALL_VERSION`: + - Full version number if `ASDF_INSTALL_TYPE=version`. + - Git ref (tag/commit/branch) if `ASDF_INSTALL_TYPE=ref`. +- `ASDF_INSTALL_PATH`: The path to where the tool _has been_, or _should be_ installed. + +**Commands that invoke this script** + +- `asdf help <name> [<version>]`: Output documentation for plugin and tool + +**Call signature from asdf core** + +```bash:no-line-numbers +"${plugin_path}"/bin/help.links +``` + +--- + +### `bin/list-bin-paths` + +**Description** + +List directories containing executables for the specified version of the tool. + +**Implementation Details** + +- If this script is not present, asdf will look for binaries in the `"${ASDF_INSTALL_PATH}"/bin` directory & create shims for those. +- Output a space-separated list of paths containing executables. +- Paths must be relative to `ASDF_INSTALL_PATH`. Example output would be: + +```bash:no-line-numbers bin tools veggies ``` -This will instruct asdf to create shims for the files in `<install-path>/bin`, `<install-path>/tools` and `<install-path>/veggies` +This will instruct asdf to create shims for the files in: +- `"${ASDF_INSTALL_PATH}"/bin` +- `"${ASDF_INSTALL_PATH}"/tools` +- `"${ASDF_INSTALL_PATH}"/veggies` + +**Environment Variables available to script** + +- `ASDF_INSTALL_TYPE`: `version` or `ref` +- `ASDF_INSTALL_VERSION`: + - Full version number if `ASDF_INSTALL_TYPE=version`. + - Git ref (tag/commit/branch) if `ASDF_INSTALL_TYPE=ref`. +- `ASDF_INSTALL_PATH`: The path to where the tool _has been_, or _should be_ installed. + +**Commands that invoke this script** + +- `asdf install <tool> [version]`: initially create shims for binaries. +- `asdf reshim <tool> <version>`: recreate shims for binaries. + +**Call signature from asdf core** + +```bash:no-line-numbers +"${plugin_path}/bin/list-bin-paths" +``` + +--- + +### `bin/exec-env` + +**Description** + +Prepare the environment before executing the shims for the binaries for the tool. + +**Environment Variables available to script** + +- `ASDF_INSTALL_TYPE`: `version` or `ref` +- `ASDF_INSTALL_VERSION`: + - Full version number if `ASDF_INSTALL_TYPE=version`. + - Git ref (tag/commit/branch) if `ASDF_INSTALL_TYPE=ref`. +- `ASDF_INSTALL_PATH`: The path to where the tool _has been_, or _should be_ installed. + +**Commands that invoke this script** + +- `asdf which <command>`: Display the path to an executable +- `asdf exec <command> [args...]`: Executes the command shim for current version +- `asdf env <command> [util]`: Runs util (default: `env`) inside the environment used for command shim execution. + +**Call signature from asdf core** + +```bash:no-line-numbers +"${plugin_path}/bin/exec-env" +``` + +--- -If this script is not specified, asdf will look for the `bin` dir in an installation and create shims for those. +### `bin/exec-path` -#### bin/exec-env +Get the executable path for the specified version of the tool. Must print a +string with the relative executable path. This allows the plugin to +conditionally override the shim's specified executable path, otherwise return +the default path specified by the shim. -Setup the env to run the binaries in the package. +**Description** -#### bin/exec-path +Get the executable path for the specified version of the tool. -Get the executable path for the specified version of the tool. Must print a string with the relative executable path. This allows the plugin to conditionally override the shim's specified executable path, otherwise return the default path specified by the shim. +**Implementation Details** + +- Must print a string with the relative executable path. +- Conditionally override the shim's specified executable path, otherwise return the default path specified by the shim. ```shell Usage: @@ -111,71 +532,222 @@ Output: bin/foox ``` -#### bin/uninstall +**Environment Variables available to script** -Uninstalls a specific version of a tool. +- `ASDF_INSTALL_TYPE`: `version` or `ref` +- `ASDF_INSTALL_VERSION`: + - Full version number if `ASDF_INSTALL_TYPE=version`. + - Git ref (tag/commit/branch) if `ASDF_INSTALL_TYPE=ref`. +- `ASDF_INSTALL_PATH`: The path to where the tool _has been_, or _should be_ installed. -#### bin/list-legacy-filenames +**Commands that invoke this script** -Register additional setter files for this plugin. Must print a string with a space-separated list of filenames. +- `asdf which <command>`: Display the path to an executable +- `asdf exec <command> [args...]`: Executes the command shim for current version +- `asdf env <command> [util]`: Runs util (default: `env`) inside the environment used for command shim execution. -```shell -.ruby-version .rvmrc +**Call signature from asdf core** + +```bash:no-line-numbers +"${plugin_path}/bin/exec-path" "$install_path" "$cmd" "$relative_path" +``` + +--- + +### `bin/uninstall` + +**Description** + +Uninstall the provided version of a tool. + +**Output Format** + +Output should be sent to `stdout` or `stderr` as appropriate for the user. No output is read by subsequent execution in the core. + +**Environment Variables available to script** + +No environment variables are provided to this script. + +**Commands that invoke this script** + +- `asdf list all <name> <version>` +- `asdf uninstall nodejs 18.15.0`: Uninstalls the version `18.15.0` of nodejs, removing all shims including those installed global with `npm i -g` + +**Call signature from asdf core** + +No parameters provided. + +```bash:no-line-numbers +"${plugin_path}/bin/uninstall" +``` + +--- + +### `bin/list-legacy-filenames` + +**Description** + +List legacy configuration filenames for determining the specified version of the tool. + +**Implementation Details** + +- Output a space-separated list of filenames. + ```bash:no-line-numbers + .ruby-version .rvmrc + ``` +- Only applies for users who have enabled the `legacy_version_file` option in their `"${HOME}"/.asdfrc`. + +**Environment Variables available to script** + +- `ASDF_INSTALL_TYPE`: `version` or `ref` +- `ASDF_INSTALL_VERSION`: + - Full version number if `ASDF_INSTALL_TYPE=version`. + - Git ref (tag/commit/branch) if `ASDF_INSTALL_TYPE=ref`. +- `ASDF_INSTALL_PATH`: The path to where the tool _has been_, or _should be_ installed. + +**Commands that invoke this script** + +Any command which reads a tool version. + +**Call signature from asdf core** + +No parameters provided. + +```bash:no-line-numbers +"${plugin_path}/bin/list-legacy-filenames" ``` -Note: This will only apply for users who have enabled the `legacy_version_file` option in their `~/.asdfrc`. +--- + +### `bin/parse-legacy-file` + +**Description** + +Parse the legacy file found by asdf to determine the version of the tool. Useful to extract version numbers from files like JavaScript's `package.json` or Golangs `go.mod`. + +**Implementation Details** -#### bin/parse-legacy-file +- If not present, asdf will simply `cat` the legacy file to determine the version. +- Should be **deterministic** and always return the same exact version: + - when parsing the same legacy file. + - regardless of what is installed on the machine or whether the legacy version is valid or complete. Some legacy file formats may not be suitable. +- Output a single line with the version: + ```bash:no-line-numbers + 1.2.3 + ``` -This can be used to further parse the legacy file found by asdf. If `parse-legacy-file` isn't implemented, asdf will simply cat the file to determine the version. The script will be passed the file path as its first argument. Note that this script should be **deterministic** and always return the same exact version when parsing the same legacy file. The script should return the same version regardless of what is installed on the machine or whether the legacy version is valid or complete. Some legacy file formats may not be suitable. +**Environment Variables available to script** -#### bin/post-plugin-add +No environment variables specifically set before this script is called. -This can be used to run any post-installation actions after the plugin has been added to asdf. +**Commands that invoke this script** -The script has access to the path the plugin was installed (`${ASDF_PLUGIN_PATH}`) and the source URL (`${ASDF_PLUGIN_SOURCE_URL}`), if any was used. +Any command which reads a tool version. -See also the related hooks: +**Call signature from asdf core** + +The script should accept a single argument, the path to the legacy file for reading its contents. + +```bash:no-line-numbers +"${plugin_path}/bin/parse-legacy-file" "$file_path" +``` + +--- + +### `bin/post-plugin-add` + +**Description** + +Execute this callback script **after** the plugin has been _added_ to asdf with `asdf plugin add <tool>`. + +See also the related command hooks: - `pre_asdf_plugin_add` - `pre_asdf_plugin_add_${plugin_name}` - `post_asdf_plugin_add` - `post_asdf_plugin_add_${plugin_name}` -#### bin/post-plugin-update +**Environment Variables available to script** + +- `ASDF_PLUGIN_PATH`: path where the plugin was installed. +- `ASDF_PLUGIN_SOURCE_URL`: URL of the plugin source. Can be a local directory path. + +**Call signature from asdf core** + +No parameters provided. + +```bash:no-line-numbers +"${plugin_path}/bin/post-plugin-add" +``` + +--- + +### `bin/post-plugin-update` -This can be used to run any post-plugin-update actions after asdf has downloaded the updated plugin +**Description** -The script has access to the path the plugin was installed (`${ASDF_PLUGIN_PATH}`), previous git-ref (`${ASDF_PLUGIN_PREV_REF}`), and updated git-ref (`${ASDF_PLUGIN_POST_REF}`). +Execute this callback script **after** asdf has downloaded the _updated_ plugin with `asdf plugin update <tool> [<git-ref>]`. -See also the related hooks: +See also the related command hooks: - `pre_asdf_plugin_updated` - `pre_asdf_plugin_updated_${plugin_name}` - `post_asdf_plugin_updated` - `post_asdf_plugin_updated_${plugin_name}` -#### bin/pre-plugin-remove +**Environment Variables available to script** + +- `ASDF_PLUGIN_PATH`: path where the plugin was installed. +- `ASDF_PLUGIN_PREV_REF`: the plugin's previous git-ref +- `ASDF_PLUGIN_POST_REF`: the plugin's updated git-ref + +**Call signature from asdf core** + +No parameters provided. + +```bash:no-line-numbers +"${plugin_path}/bin/post-plugin-update" +``` -This can be used to run any pre-removal actions before the plugin will be removed from asdf. +--- -The script has access to the path the plugin was installed in (`${ASDF_PLUGIN_PATH}`). +### `bin/pre-plugin-remove` -See also the related hooks: +**Description** + +Execute this callback script **before** asdf has removed the plugin with `asdf plugin remove <tool>`. + +See also the related command hooks: - `pre_asdf_plugin_remove` - `pre_asdf_plugin_remove_${plugin_name}` - `post_asdf_plugin_remove` - `post_asdf_plugin_remove_${plugin_name}` -## Extension commands for asdf CLI. +**Environment Variables available to script** + +- `ASDF_PLUGIN_PATH`: path where the plugin was installed. + +**Call signature from asdf core** + +No parameters provided. + +```bash:no-line-numbers +"${plugin_path}/bin/pre-plugin-remove" +``` + +<!-- TODO: document command hooks --> +<!-- ## Command Hooks --> -It's possible for plugins to define new asdf commands by providing `lib/commands/command*.bash` scripts or executables that -will be callable using the asdf command line interface by using the plugin name as a subcommand. +## Extension Commands for asdf CLI <Badge type="danger" text="advanced" vertical="middle" /> + +It's possible for plugins to define new asdf commands by providing +`lib/commands/command*.bash` scripts or executables that will be callable using +the asdf command line interface by using the plugin name as a subcommand. For example, suppose a `foo` plugin has: -```shell +```shell:no-line-numbers foo/ lib/commands/ command.bash @@ -184,9 +756,9 @@ foo/ command-help.bash ``` -Users can now execute +Users can now execute: -```shell +```shell:no-line-numbers $ asdf foo # same as running `$ASDF_DATA_DIR/plugins/foo/lib/commands/command.bash` $ asdf foo bar # same as running `$ASDF_DATA_DIR/plugins/foo/lib/commands/command.bash bar` $ asdf foo help # same as running `$ASDF_DATA_DIR/plugins/foo/lib/commands/command-help.bash` @@ -195,69 +767,106 @@ $ asdf foo bat baz # same as running `$ASDF_DATA_DIR/plugins/foo/lib/commands/co ``` Plugin authors can use this feature to provide utilities related to their tools, -or even create plugins that are just new command extensions for asdf itself. +or even create plugins that are just new command extensions of asdf itself. -When invoked, if extension commands do not have their executable-bit set they will be -sourced as bash scripts. Also, the `$ASDF_CMD_FILE` resolves to the full path of the file being sourced. -If the executable bit is set, they are just executed and replace the asdf execution. +If the executable bit is set, the script is executed, replacing the asdf +execution. -A good example of this feature is for plugins like [`haxe`](https://github.com/asdf-community/asdf-haxe) -which provides the `asdf haxe neko-dylibs-link` to fix an issue where haxe executables expect to find -dynamic libraries relative to the executable directory. +If the executable bit is not set, asdf will source the scripts as Bash scripts. -If your plugin provides an asdf extension command, be sure to mention it in your plugin's README. +`$ASDF_CMD_FILE` resolves to the full path of the file being sourced. -## Custom shim templates +[`haxe`](https://github.com/asdf-community/asdf-haxe) is a great example of a +plugin which uses this feature. It provides the `asdf haxe neko-dylibs-link` to +fix an issue where Haxe executables expect to find dynamic libraries relative to +the executable directory. -**PLEASE use this feature only if absolutely required** +Be sure to list your asdf Extension Commands in your plugins README. -asdf allows custom shim templates. For an executable called `foo`, if there's a `shims/foo` file in the plugin, then asdf will copy that file instead of using its standard shim template. +## Custom Shim Templates <Badge type="danger" text="advanced" vertical="middle" /> -This must be used wisely. For now AFAIK, it's only being used in the Elixir plugin, because an executable is also read as an Elixir file apart from just being an executable. Which makes it not possible to use the standard bash shim. +::: warning -## Testing plugins +Please only use if **absolutely** required -`asdf` contains the `plugin-test` command to test your plugin. You can use it as follows +::: -```shell -asdf plugin test <plugin-name> <plugin-url> [--asdf-tool-version <version>] [--asdf-plugin-gitref <git-ref>] [test-command*] -``` +asdf allows custom shim templates. For an executable called `foo`, if there's a +`shims/foo` file in the plugin, then asdf will copy that file instead of using +its standard shim template. -Only the two first arguments are required. -If \__version_ is specified, the tool will be installed with that specific version. Defaults to whatever returns `asdf latest <plugin-name>`. -If _git-ref_ is specified, the plugin itself is checked out at that commit/branch/tag, useful for testing a pull-request on your plugin's CI. +**This must be used wisely.** -Rest arguments are considered the command to execute to ensure the installed tool works correctly. -Normally it would be something that takes `--version` or `--help`. -For example, to test the NodeJS plugin, we could run +As far as the asdf core team is aware, this feature is only in use in the +first-party [Elixir plugin](https://github.com/asdf-vm/asdf-elixir). This is +because an executable is also read as an Elixir file in addition to being an +executable. This makes it not possible to use the standard Bash shim. -```shell -asdf plugin test nodejs https://github.com/asdf-vm/asdf-nodejs.git node --version +## Testing + +`asdf` contains the `plugin-test` command to test your plugin: + +```shell:no-line-numbers +asdf plugin test <plugin_name> <plugin_url> [--asdf-tool-version <version>] [--asdf-plugin-gitref <git_ref>] [test_command...] ``` -We strongly recommend you test your plugin on a CI environment and make sure it works on both Linux and OSX. +- `<plugin_name>` & `<plugin_url>` are required +- If optional `[--asdf-tool-version <version>]` is specified, the tool will be + installed with that specific version. Defaults to `asdf latest <plugin-name>` +- If optional `[--asdf-plugin-gitref <git_ref>]` is specified, the plugin itself + is checked out at that commit/branch/tag. This is useful for testing a + pull-request on your plugin's CI. +- Optional parameter `[test_command...]` is the command to execute to validate + the installed tool works correctly. Typically `<tool> --version` or + `<tool> --help`. For example, to test the NodeJS plugin, we could run + ```shell:no-line-numbers + # asdf plugin test <plugin_name> <plugin_url> [test_command] + asdf plugin test nodejs https://github.com/asdf-vm/asdf-nodejs.git node --version + ``` + +::: tip Note + +We recommend testing in both Linux & macOS CI environments + +::: -#### Example GitHub Action +### GitHub Action -The [asdf-vm/actions](https://github.com/asdf-vm/actions) repo provides a GitHub Action for testing your plugins hosted on github. +The [asdf-vm/actions](https://github.com/asdf-vm/actions) repo provides a GitHub +Action for testing your plugins hosted on GitHub. A sample +`.github/workflows/test.yaml` Actions Workflow: ```yaml -steps: - - name: asdf_plugin_test - uses: asdf-vm/actions/plugin-test@v1 - with: - command: "my_tool --version" - env: - GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} # automatically provided +name: Test +on: + push: + branches: + - main + pull_request: + +jobs: + plugin_test: + name: asdf plugin test + strategy: + matrix: + os: + - ubuntu-latest + - macos-latest + runs-on: ${{ matrix.os }} + steps: + - name: asdf_plugin_test + uses: asdf-vm/actions/plugin-test@v2 + with: + command: "<MY_TOOL> --version" ``` -#### Example TravisCI config +### TravisCI Config -Here is a sample `.travis.yml` file, customize it to your needs +A sample `.travis.yml` file, customize it to your needs ```yaml language: c -script: asdf plugin test nodejs $TRAVIS_BUILD_DIR 'node --version' +script: asdf plugin test <MY_TOOL> $TRAVIS_BUILD_DIR '<MY_TOOL> --version' before_script: - git clone https://github.com/asdf-vm/asdf.git asdf - . asdf/asdf.sh @@ -266,25 +875,26 @@ os: - osx ``` -Note: -When using another CI, you will need to check what variable maps to the repo path. +::: tip NOTE -You also have the option to pass a relative path to `plugin-test`. +When using another CI you may need to pass a relative path to the plugin +location: -For example, if the test script is ran in the repo directory: `asdf plugin test nodejs . 'node --version'`. - -## GitHub API Rate Limiting - -If your plugin's `list-all` depends on accessing the GitHub API, make sure you provide an Authorization token when accessing it, otherwise your tests might fail due to rate limiting. +```shell:no-line-numbers +asdf plugin test <tool_name> <path> '<tool_command> --version' +``` -To do so, create a [new personal token](https://github.com/settings/tokens/new) with only `public_repo` access. +::: -Then on your travis.ci build settings add a _secure_ environment variable for it named something like `GITHUB_API_TOKEN`. And _DO NOT_ EVER publish your token in your code. +## API Rate Limiting -Finally, add something like the following to `bin/list-all` +If a command depends on accessing an external API, like `bin/list-all` or +`bin/latest-stable`, it may experience rate limiting during automated testing. +To mitigate this, ensure there is a code-path which provides an authentication +token via an environment variable. For example: ```shell -cmd="curl -s" +cmd="curl --silent" if [ -n "$GITHUB_API_TOKEN" ]; then cmd="$cmd -H 'Authorization: token $GITHUB_API_TOKEN'" fi @@ -292,10 +902,37 @@ fi cmd="$cmd $releases_path" ``` -## Submitting plugins to the official plugins repository +### `GITHUB_API_TOKEN` + +To utilise the `GITHUB_API_TOKEN`, create a +[new personal token](https://github.com/settings/tokens/new) with only +`public_repo` access. + +Then add this to your CI pipeline environment variables. + +::: warning + +NEVER publish your authentication tokens in your code repository + +::: + +## Plugin Shortname Index + +::: tip + +The recommended installation method for a plugin is via direct URL installation: + +```shell:no-line-numbers +# asdf plugin add <name> <git_url> + asdf plugin add nodejs https://github.com/asdf-vm/asdf-nodejs +``` -`asdf` can easily install plugins by specifying the plugin repository url, e.g. `plugin add my-plugin https://github.com/user/asdf-my-plugin.git`. +::: -To make it easier on your users, you can add your plugin to the official plugins repository to have your plugin listed and easily installable using a shorter command, e.g. `asdf plugin add my-plugin`. +If the `git_url` is not provided, asdf will use the +[Shortname Index repository](https://github.com/asdf-vm/asdf-plugins) to +determine the exact `git_url` to use. -Follow the instruction at the plugins repository: [asdf-vm/asdf-plugins](https://github.com/asdf-vm/asdf-plugins). +You can add your plugin to the +[Shortname Index](https://github.com/asdf-vm/asdf-plugins) by following the +instructions in that repo. diff --git a/docs/pt-br/guide/getting-started.md b/docs/pt-br/guide/getting-started.md index c57fb35c2..26b712493 100644 --- a/docs/pt-br/guide/getting-started.md +++ b/docs/pt-br/guide/getting-started.md @@ -39,7 +39,7 @@ asdf primarily requires `git` & `curl`. Here is a _non-exhaustive_ list of comma <!-- x-release-please-start-version --> ```shell:no-line-numbers -git clone https://github.com/asdf-vm/asdf.git ~/.asdf --branch v0.11.3 +git clone https://github.com/asdf-vm/asdf.git ~/.asdf --branch v0.12.0 ``` <!-- x-release-please-end --> @@ -262,6 +262,102 @@ Adicione a seguinte linha ao seu `~/.zshrc`: . /opt/asdf-vm/asdf.sh ``` +::: details PowerShell Core & Git + +Adicione a seguinte linha ao seu `~/.config/powershell/profile.ps1`: + +```shell +. "$HOME/.asdf/asdf.ps1" +``` + +::: + +::: details PowerShell Core & Homebrew + +Adicione `asdf.ps1` ao seu `~/.config/powershell/profile.ps1` através do comando: + +```shell:no-line-numbers +echo -e "\n. \"$(brew --prefix asdf)/libexec/asdf.ps1\"" >> ~/.config/powershell/profile.ps1 +``` + +::: + +::: details PowerShell Core & Pacman + +Adicione a seguinte linha ao seu `~/.config/powershell/profile.ps1`: + +```shell +. /opt/asdf-vm/asdf.ps1 +``` + +::: + +::: details Nushell & Git + +Adicione `asdf.nu` ao seu `~/.config/nushell/config.nu` através do comando: + +```shell +"\nlet-env ASDF_NU_DIR = ($env.HOME | path join '.asdf')\n source " + ($env.HOME | path join '.asdf/asdf.nu') | save --append $nu.config-path +``` + +Ao concluir atualizará automaticamente +::: + +::: details Nushell & Homebrew + +Adicione `asdf.nu` ao seu `~/.config/nushell/config.nu` através do comando: + +```shell:no-line-numbers +"\nlet-env ASDF_NU_DIR = (brew --prefix asdf | str trim | into string | path join 'libexec')\n source " + (brew --prefix asdf | into string | path join 'libexec/asdf.nu') | save --append $nu.config-path +``` + +Ao concluir atualizará automaticamente +::: + +::: details Nushell & Pacman + +Adicione `asdf.nu` ao seu `~/.config/nushell/config.nu` através do comando: + +```shell +"\nlet-env ASDF_NU_DIR = '/opt/asdf-vm/'\n source /opt/asdf-vm/asdf.nu" | save --append $nu.config-path +``` + +Ao concluir atualizará automaticamente +::: + +::: details POSIX Shell & Git + +Adicione a seguinte linha ao seu `~/.profile`: + +```shell +export ASDF_DIR="$HOME/.asdf" +. "$HOME/.asdf/asdf.sh" +``` + +::: + +::: details POSIX Shell & Homebrew + +Adicione `asdf.sh` ao `~/.profile` através do comando: + +```shell:no-line-numbers +echo -e "\nexport ASDF_DIR=\"$(brew --prefix asdf)/libexec/asdf.sh\"" >> ~/.profile +echo -e "\n. \"$(brew --prefix asdf)/libexec/asdf.sh\"" >> ~/.profile +``` + +::: + +::: details POSIX Shell & Pacman + +Adicione a seguinte linha ao seu `~/.profile`: + +```shell +export ASDF_DIR="/opt/asdf-vm" +. /opt/asdf-vm/asdf.sh +``` + +::: + O auto completar é colocado em um local familiar para o ZSH, [mas o ZSH deve ser configurado para conseguir utilizá-lo](https://wiki.archlinux.org/index.php/zsh#Command_completion). ::: diff --git a/docs/pt-br/manage/core.md b/docs/pt-br/manage/core.md index 0e39f36e7..a456e3530 100644 --- a/docs/pt-br/manage/core.md +++ b/docs/pt-br/manage/core.md @@ -223,7 +223,7 @@ rm -rf ~/.config/fish/completions/asdf.fish 2. Remova o diretório `$HOME/.asdf`: ```shell -rm -rf "${ASDF_DATA_DIR:-$HOME/.asdf}" +rm -rf (string join : -- $ASDF_DATA_DIR $HOME/.asdf) ``` 3. Execute o comando para remover todos os arquivos de configurações do `asdf`: @@ -273,7 +273,7 @@ pacman -Rs asdf-vm 3. Remova o diretório `$HOME/.asdf`: ```shell -rm -rf "${ASDF_DATA_DIR:-$HOME/.asdf}" +rm -rf (string join : -- $ASDF_DATA_DIR $HOME/.asdf) ``` 4. Execute o comando para remover todos os arquivos de configurações do `asdf`: diff --git a/docs/zh-hans/guide/getting-started.md b/docs/zh-hans/guide/getting-started.md index fb39a0b82..ea3f55f09 100644 --- a/docs/zh-hans/guide/getting-started.md +++ b/docs/zh-hans/guide/getting-started.md @@ -35,7 +35,7 @@ asdf primarily requires `git` & `curl`. Here is a _non-exhaustive_ list of comma <!-- x-release-please-start-version --> ```shell:no-line-numbers -git clone https://github.com/asdf-vm/asdf.git ~/.asdf --branch v0.11.3 +git clone https://github.com/asdf-vm/asdf.git ~/.asdf --branch v0.12.0 ``` <!-- x-release-please-end --> @@ -263,6 +263,102 @@ echo -e "\n. $(brew --prefix asdf)/libexec/asdf.sh" >> ${ZDOTDIR:-~}/.zshrc 补全功能会被放在一个对 ZSH 很友好的位置,但是 [ZSH 必须使用自动补全完成配置](https://wiki.archlinux.org/index.php/zsh#Command_completion)。 ::: +::: details PowerShell Core & Git + +在 `~/.config/powershell/profile.ps1` 文件中加入以下内容: + +```shell +. "$HOME/.asdf/asdf.ps1" +``` + +::: + +::: details PowerShell Core & Homebrew + +使用以下命令将 `asdf.ps1` 加入到 `~/.config/powershell/profile.ps1` 文件中: + +```shell:no-line-numbers +echo -e "\n. \"$(brew --prefix asdf)/libexec/asdf.ps1\"" >> ~/.config/powershell/profile.ps1 +``` + +::: + +::: details PowerShell Core & Pacman + +在 `~/.config/powershell/profile.ps1` 文件中加入以下内容: + +```shell +. /opt/asdf-vm/asdf.ps1 +``` + +::: + +::: details Nushell & Git + +使用以下命令将 `asdf.nu` 加入到 `~/.config/nushell/config.nu` 文件中: + +```shell +"\nlet-env ASDF_NU_DIR = ($env.HOME | path join '.asdf')\n source " + ($env.HOME | path join '.asdf/asdf.nu') | save --append $nu.config-path +``` + +补全功能将会自动配置。 +::: + +::: details Nushell & Homebrew + +使用以下命令将 `asdf.nu` 加入到 `~/.config/nushell/config.nu` 文件中: + +```shell:no-line-numbers +"\nlet-env ASDF_NU_DIR = (brew --prefix asdf | str trim | into string | path join 'libexec')\n source " + (brew --prefix asdf | into string | path join 'libexec/asdf.nu') | save --append $nu.config-path +``` + +补全功能将会自动配置。 +::: + +::: details Nushell & Pacman + +使用以下命令将 `asdf.nu` 加入到 `~/.config/nushell/config.nu` 文件中: + +```shell +"\nlet-env ASDF_NU_DIR = '/opt/asdf-vm/'\n source /opt/asdf-vm/asdf.nu" | save --append $nu.config-path +``` + +补全功能将会自动配置。 +::: + +::: details POSIX Shell & Git + +在 `~/.profile` 文件中加入以下内容: + +```shell +export ASDF_DIR="$HOME/.asdf" +. "$HOME/.asdf/asdf.sh" +``` + +::: + +::: details POSIX Shell & Homebrew + +使用以下命令将 `asdf.sh` 加入到 `~/.profile` 文件中: + +```shell:no-line-numbers +echo -e "\nexport ASDF_DIR=\"$(brew --prefix asdf)/libexec/asdf.sh\"" >> ~/.profile +echo -e "\n. \"$(brew --prefix asdf)/libexec/asdf.sh\"" >> ~/.profile +``` + +::: + +::: details POSIX Shell & Pacman + +在 `~/.profile` 文件中加入以下内容: + +```shell +export ASDF_DIR="/opt/asdf-vm" +. /opt/asdf-vm/asdf.sh +``` + +::: + `asdf` 脚本需要在设置好的 `$PATH` **之后**和已经生效的框架(比如 oh-my-zsh 等等)**之后**的位置生效。 通常打开一个新的终端标签页来重启你的 shell 让 `PATH` 更改即时生效。 diff --git a/docs/zh-hans/manage/core.md b/docs/zh-hans/manage/core.md index 82c5c2601..6eb0292ac 100644 --- a/docs/zh-hans/manage/core.md +++ b/docs/zh-hans/manage/core.md @@ -221,7 +221,7 @@ rm -rf ~/.config/fish/completions/asdf.fish 2. 移除 `$HOME/.asdf` 目录: ```shell:no-line-numbers -rm -rf "${ASDF_DATA_DIR:-$HOME/.asdf}" +rm -rf (string join : -- $ASDF_DATA_DIR $HOME/.asdf) ``` 3. 执行以下命令移除 `asdf` 所有配置文件: @@ -271,7 +271,7 @@ pacman -Rs asdf-vm 3. 移除 `$HOME/.asdf` 目录: ```shell:no-line-numbers -rm -rf "${ASDF_DATA_DIR:-$HOME/.asdf}" +rm -rf (string join : -- $ASDF_DATA_DIR $HOME/.asdf) ``` 4. 执行以下命令移除 `asdf` 所有配置文件: diff --git a/lib/asdf.fish b/lib/asdf.fish deleted file mode 100644 index d14ab3897..000000000 --- a/lib/asdf.fish +++ /dev/null @@ -1,15 +0,0 @@ - -# Add function wrapper so we can export variables -function asdf - set command $argv[1] - set -e argv[1] - - switch "$command" - case "shell" - # source commands that need to export variables - command asdf export-shell-version fish $argv | source # asdf_allow: source - case '*' - # forward other commands to asdf script - command asdf "$command" $argv - end -end diff --git a/lib/asdf.sh b/lib/asdf.sh deleted file mode 100644 index f43aae581..000000000 --- a/lib/asdf.sh +++ /dev/null @@ -1,20 +0,0 @@ -# The asdf function is a wrapper so we can export variables -asdf() { - local command - command="$1" - if [ "$#" -gt 0 ]; then - shift - fi - - case "$command" in - "shell") - # commands that need to export variables - eval "$(asdf export-shell-version sh "$@")" # asdf_allow: eval - ;; - *) - # forward other commands to asdf script - command asdf "$command" "$@" # asdf_allow: ' asdf ' - ;; - - esac -} diff --git a/lib/commands/command-export-shell-version.bash b/lib/commands/command-export-shell-version.bash index 13b779d1b..92ace8abe 100644 --- a/lib/commands/command-export-shell-version.bash +++ b/lib/commands/command-export-shell-version.bash @@ -31,6 +31,9 @@ shell_command() { # and pass to unset-env. printf "unset-env\n%s" "$version_env_var" ;; + pwsh) + printf '%s\n' "if (\$(Test-Path Env:$version_env_var) -eq 'True') { Remove-Item Env:$version_env_var }" + ;; *) printf "unset %s\n" "$version_env_var" ;; @@ -56,6 +59,9 @@ shell_command() { # and pass to set-env. printf "set-env\n%s\n%s" "$version_env_var" "$version" ;; + pwsh) + printf '%s\n' "\$Env:$version_env_var = '$version'" + ;; *) printf "export %s=\"%s\"\n" "$version_env_var" "$version" ;; diff --git a/lib/commands/command-info.bash b/lib/commands/command-info.bash index 9304ce687..d4e761cf4 100644 --- a/lib/commands/command-info.bash +++ b/lib/commands/command-info.bash @@ -4,9 +4,14 @@ info_command() { printf "%s:\n%s\n\n" "OS" "$(uname -a)" - printf "%s:\n%s\n\n" "SHELL" "$($SHELL --version)" + printf "%s:\n%s\n\n" "SHELL" "$("$SHELL" --version)" + printf "%s:\n%s\n\n" "BASH VERSION" "$BASH_VERSION" printf "%s:\n%s\n\n" "ASDF VERSION" "$(asdf_version)" - printf "%s:\n%s\n\n" "ASDF ENVIRONMENT VARIABLES" "$(env | grep -E "ASDF_DIR|ASDF_DATA_DIR|ASDF_CONFIG_FILE|ASDF_DEFAULT_TOOL_VERSIONS_FILENAME")" + printf '%s\n' 'ASDF INTERNAL VARIABLES:' + printf 'ASDF_DEFAULT_TOOL_VERSIONS_FILENAME=%s\n' "${ASDF_DEFAULT_TOOL_VERSIONS_FILENAME}" + printf 'ASDF_DATA_DIR=%s\n' "${ASDF_DATA_DIR}" + printf 'ASDF_DIR=%s\n' "${ASDF_DIR}" + printf 'ASDF_CONFIG_FILE=%s\n\n' "${ASDF_CONFIG_FILE}" printf "%s:\n%s\n\n" "ASDF INSTALLED PLUGINS" "$(plugin_list_command --urls --refs)" } diff --git a/lib/commands/command-plugin-list-all.bash b/lib/commands/command-plugin-list-all.bash index 33333e59a..232b69cd6 100644 --- a/lib/commands/command-plugin-list-all.bash +++ b/lib/commands/command-plugin-list-all.bash @@ -1,7 +1,7 @@ # -*- sh -*- plugin_list_all_command() { - initialize_or_update_repository + initialize_or_update_plugin_repository local plugins_index_path plugins_index_path="$(asdf_data_dir)/repository/plugins" diff --git a/lib/commands/command-plugin-remove.bash b/lib/commands/command-plugin-remove.bash index e4cee0532..7b959bdf7 100644 --- a/lib/commands/command-plugin-remove.bash +++ b/lib/commands/command-plugin-remove.bash @@ -21,7 +21,13 @@ plugin_remove_command() { rm -rf "$(asdf_data_dir)/installs/${plugin_name}" rm -rf "$(asdf_data_dir)/downloads/${plugin_name}" - grep -l "asdf-plugin: ${plugin_name}" "$(asdf_data_dir)"/shims/* 2>/dev/null | xargs rm -f + for f in "$(asdf_data_dir)"/shims/*; do + if [ -f "$f" ]; then # nullglob may not be set + if grep -q "asdf-plugin: ${plugin_name}" "$f"; then + rm -f "$f" + fi + fi + done asdf_run_hook "post_asdf_plugin_remove" "$plugin_name" asdf_run_hook "post_asdf_plugin_remove_${plugin_name}" diff --git a/lib/commands/reshim.bash b/lib/commands/reshim.bash index 8a703a4d2..82bed60fb 100644 --- a/lib/commands/reshim.bash +++ b/lib/commands/reshim.bash @@ -11,7 +11,7 @@ remove_shim_for_version() { local count_installed count_installed=$(list_installed_versions "$plugin_name" | wc -l) - if ! grep -x "# asdf-plugin: $plugin_name $version" "$shim_path" >/dev/null 2>&1; then + if ! grep -x "# asdf-plugin: $plugin_name $version" "$shim_path" &>/dev/null; then return 0 fi diff --git a/lib/functions/installs.bash b/lib/functions/installs.bash index 9f85c652c..721ae50c4 100644 --- a/lib/functions/installs.bash +++ b/lib/functions/installs.bash @@ -25,15 +25,27 @@ install_command() { } get_concurrency() { - if command -v nproc >/dev/null 2>&1; then - nproc - elif command -v sysctl >/dev/null 2>&1 && sysctl hw.ncpu >/dev/null 2>&1; then - sysctl -n hw.ncpu - elif [ -f /proc/cpuinfo ]; then - grep -c processor /proc/cpuinfo + local asdf_concurrency= + + if [ -n "$ASDF_CONCURRENCY" ]; then + asdf_concurrency="$ASDF_CONCURRENCY" else - printf "1\n" + asdf_concurrency=$(get_asdf_config_value 'concurrency') + fi + + if [ "$asdf_concurrency" = 'auto' ]; then + if command -v nproc &>/dev/null; then + asdf_concurrency=$(nproc) + elif command -v sysctl &>/dev/null && sysctl hw.ncpu &>/dev/null; then + asdf_concurrency=$(sysctl -n hw.ncpu) + elif [ -f /proc/cpuinfo ]; then + asdf_concurrency=$(grep -c processor /proc/cpuinfo) + else + asdf_concurrency="1" + fi fi + + printf "%s\n" "$asdf_concurrency" } install_one_local_tool() { @@ -195,7 +207,7 @@ install_tool_version() { export ASDF_INSTALL_PATH=$install_path # shellcheck disable=SC2030 export ASDF_DOWNLOAD_PATH=$download_path - mkdir "$download_path" + mkdir -p "$download_path" asdf_run_hook "pre_asdf_download_${plugin_name}" "$full_version" "${plugin_path}"/bin/download ) diff --git a/lib/functions/plugins.bash b/lib/functions/plugins.bash index 18d50af11..247a22dfc 100644 --- a/lib/functions/plugins.bash +++ b/lib/functions/plugins.bash @@ -28,15 +28,13 @@ plugin_list_command() { printf "%s" "$plugin_name" if [ -n "$show_repo" ]; then - printf "\t%s" "$(git --git-dir "$plugin_path/.git" remote get-url origin 2>/dev/null)" + printf "\t%s" "$(get_plugin_remote_url "$plugin_name")" fi if [ -n "$show_ref" ]; then - local branch - local gitref - branch=$(git --git-dir "$plugin_path/.git" rev-parse --abbrev-ref HEAD 2>/dev/null) - gitref=$(git --git-dir "$plugin_path/.git" rev-parse --short HEAD 2>/dev/null) - printf "\t%s\t%s" "$branch" "$gitref" + printf "\t%s\t%s" \ + "$(get_plugin_remote_branch "$plugin_name")" \ + "$(get_plugin_remote_gitref "$plugin_name")" fi printf "\n" @@ -65,7 +63,7 @@ plugin_add_command() { if [ -n "$2" ]; then local source_url=$2 else - initialize_or_update_repository + initialize_or_update_plugin_repository local source_url source_url=$(get_plugin_source_url "$plugin_name") fi diff --git a/lib/functions/versions.bash b/lib/functions/versions.bash index 28262f78e..e3e873ab7 100644 --- a/lib/functions/versions.bash +++ b/lib/functions/versions.bash @@ -17,7 +17,7 @@ version_command() { local file_name local file - file_name="$(version_file_name)" + file_name="$(asdf_tool_versions_filename)" if [ "$cmd" = "global" ]; then file="$HOME/$file_name" diff --git a/lib/utils.bash b/lib/utils.bash index 862c4449a..8eae18fcb 100644 --- a/lib/utils.bash +++ b/lib/utils.bash @@ -5,9 +5,6 @@ GREP_OPTIONS="--color=never" # shellcheck disable=SC2034 GREP_COLORS= -ASDF_DIR=${ASDF_DIR:-''} -ASDF_DATA_DIR=${ASDF_DATA_DIR:-''} - asdf_version() { local version git_rev version="v$(cat "$(asdf_dir)/version.txt")" @@ -19,21 +16,12 @@ asdf_version() { fi } -asdf_dir() { - if [ -z "$ASDF_DIR" ]; then - local current_script_path=${BASH_SOURCE[0]} - export ASDF_DIR - ASDF_DIR=$( - cd "$(dirname "$(dirname "$current_script_path")")" || exit - printf '%s\n' "$PWD" - ) - fi - - printf "%s\n" "$ASDF_DIR" +asdf_tool_versions_filename() { + printf '%s\n' "${ASDF_DEFAULT_TOOL_VERSIONS_FILENAME:-.tool-versions}" } -asdf_repository_url() { - printf "https://github.com/asdf-vm/asdf-plugins.git\n" +asdf_config_file() { + printf '%s\n' "${ASDF_CONFIG_FILE:-$HOME/.asdfrc}" } asdf_data_dir() { @@ -50,6 +38,22 @@ asdf_data_dir() { printf "%s\n" "$data_dir" } +asdf_dir() { + if [ -z "$ASDF_DIR" ]; then + local current_script_path=${BASH_SOURCE[0]} + printf '%s\n' "$( + cd -- "$(dirname "$(dirname "$current_script_path")")" || exit + printf '%s\n' "$PWD" + )" + else + printf '%s\n' "$ASDF_DIR" + fi +} + +asdf_plugin_repository_url() { + printf "https://github.com/asdf-vm/asdf-plugins.git\n" +} + get_install_path() { local plugin=$1 local install_type=$2 @@ -159,7 +163,7 @@ get_version_in_dir() { local asdf_version - file_name=$(version_file_name) + file_name=$(asdf_tool_versions_filename) asdf_version=$(parse_asdf_version_file "$search_path/$file_name" "$plugin_name") if [ -n "$asdf_version" ]; then @@ -178,10 +182,6 @@ get_version_in_dir() { done } -version_file_name() { - printf "%s" "${ASDF_DEFAULT_TOOL_VERSIONS_FILENAME:-.tool-versions}" -} - find_versions() { local plugin_name=$1 local search_path=$2 @@ -206,7 +206,7 @@ find_versions() { local legacy_filenames="" if [ "$legacy_config" = "yes" ] && [ -f "$legacy_list_filenames_script" ]; then - legacy_filenames=$($legacy_list_filenames_script) + legacy_filenames=$("$legacy_list_filenames_script") fi while [ "$search_path" != "/" ]; do @@ -373,6 +373,8 @@ get_asdf_config_value_from_file() { return 1 fi + util_validate_no_carriage_returns "$config_path" + local result result=$(grep -E "^\s*$key\s*=\s*" "$config_path" | head | sed -e 's/^[^=]*= *//' -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//') if [ -n "$result" ]; then @@ -385,7 +387,8 @@ get_asdf_config_value_from_file() { get_asdf_config_value() { local key=$1 - local config_path=${ASDF_CONFIG_FILE:-"$HOME/.asdfrc"} + local config_path= + config_path=$(asdf_config_file) local default_config_path=${ASDF_CONFIG_DEFAULT_FILE:-"$(asdf_dir)/defaults"} local local_config_path @@ -417,7 +420,7 @@ repository_needs_update() { [ "$sync_required" ] } -initialize_or_update_repository() { +initialize_or_update_plugin_repository() { local repository_url local repository_path @@ -427,7 +430,7 @@ initialize_or_update_repository() { exit 1 fi - repository_url=$(asdf_repository_url) + repository_url=$(asdf_plugin_repository_url) repository_path=$(asdf_data_dir)/repository if [ ! -d "$repository_path" ]; then @@ -435,7 +438,8 @@ initialize_or_update_repository() { git clone "$repository_url" "$repository_path" elif repository_needs_update; then printf "updating plugin repository..." - (cd "$repository_path" && git fetch && git reset --hard origin/master) + git -C "$repository_path" fetch + git -C "$repository_path" reset --hard origin/master fi mkdir -p "$(asdf_data_dir)/tmp" @@ -454,7 +458,7 @@ get_plugin_source_url() { } find_tool_versions() { - find_file_upwards "$(version_file_name)" + find_file_upwards "$(asdf_tool_versions_filename)" } find_file_upwards() { @@ -463,6 +467,8 @@ find_file_upwards() { search_path=$PWD while [ "$search_path" != "/" ]; do if [ -f "$search_path/$name" ]; then + util_validate_no_carriage_returns "$search_path/$name" + printf "%s\n" "${search_path}/$name" return 0 fi @@ -860,3 +866,33 @@ util_resolve_user_path() { util_resolve_user_path_reply="$path" fi } + +# @description Check if a file contains carriage returns. If it does, print a warning. +util_validate_no_carriage_returns() { + local file_path="$1" + + if grep -qr $'\r' "$file_path"; then + printf '%s\n' "asdf: Warning: File $file_path contains carriage returns. Please remove them." >&2 + fi +} + +get_plugin_remote_url() { + local plugin_name="$1" + local plugin_path + plugin_path="$(get_plugin_path "$plugin_name")" + git --git-dir "$plugin_path/.git" remote get-url origin 2>/dev/null +} + +get_plugin_remote_branch() { + local plugin_name="$1" + local plugin_path + plugin_path="$(get_plugin_path "$plugin_name")" + git --git-dir "$plugin_path/.git" rev-parse --abbrev-ref HEAD 2>/dev/null +} + +get_plugin_remote_gitref() { + local plugin_name="$1" + local plugin_path + plugin_path="$(get_plugin_path "$plugin_name")" + git --git-dir "$plugin_path/.git" rev-parse --short HEAD 2>/dev/null +} diff --git a/scripts/checkstyle.py b/scripts/checkstyle.py index 7c0271478..28294ea0b 100755 --- a/scripts/checkstyle.py +++ b/scripts/checkstyle.py @@ -8,17 +8,17 @@ # This file checks Bash and Shell scripts for violations not found with # shellcheck or existing methods. You can use it in several ways: # -# Lint all .bash, .sh, and .bats files and print out violations +# Lint all .bash, .sh, .bats files along with 'bin/asdf' and print out violations: # $ ./scripts/checkstyle.py # # The former, but also fix all violations. This must be ran until there -# are zero violations since any line can have more than one violation +# are zero violations since any line can have more than one violation: # $ ./scripts/checkstyle.py --fix # -# Lint a particular file +# Lint a particular file: # $ ./scripts/checkstyle.py ./lib/functions/installs.bash # -# Check to ensure all regexes are working as intended +# Check to ensure all regular expressions are working as intended: # $ ./scripts/checkstyle.py --internal-test-regex Rule = Dict[str, Any] @@ -35,7 +35,7 @@ class c: UNDERLINE = '\033[4m' LINK: Callable[[str, str], str] = lambda href, text: f'\033]8;;{href}\a{text}\033]8;;\a' -def utilGetStrs(line, m): +def utilGetStrs(line: Any, m: Any): return ( line[0:m.start('match')], line[m.start('match'):m.end('match')], @@ -44,22 +44,22 @@ def utilGetStrs(line, m): # Before: printf '%s\\n' '^w^' # After: printf '%s\n' '^w^' -def noDoubleBackslashFixer(line: str, m) -> str: +def noDoubleBackslashFixer(line: str, m: Any) -> str: prestr, midstr, poststr = utilGetStrs(line, m) return f'{prestr}{midstr[1:]}{poststr}' # Before: $(pwd) # After: $PWD -def noPwdCaptureFixer(line: str, m) -> str: - prestr, midstr, poststr = utilGetStrs(line, m) +def noPwdCaptureFixer(line: str, m: Any) -> str: + prestr, _, poststr = utilGetStrs(line, m) return f'{prestr}$PWD{poststr}' # Before: [ a == b ] # After: [ a = b ] -def noTestDoubleEqualsFixer(line: str, m) -> str: - prestr, midstr, poststr = utilGetStrs(line, m) +def noTestDoubleEqualsFixer(line: str, m: Any) -> str: + prestr, _, poststr = utilGetStrs(line, m) return f'{prestr}={poststr}' @@ -68,7 +68,7 @@ def noTestDoubleEqualsFixer(line: str, m) -> str: # --- # Before: function fn { ... # After fn() { ... -def noFunctionKeywordFixer(line: str, m) -> str: +def noFunctionKeywordFixer(line: str, m: Any) -> str: prestr, midstr, poststr = utilGetStrs(line, m) midstr = midstr.strip() @@ -80,17 +80,41 @@ def noFunctionKeywordFixer(line: str, m) -> str: return f'{prestr}{midstr}() {poststr}' -def lintfile(filepath: Path, rules: List[Rule], options: Dict[str, Any]): - content_arr = filepath.read_text().split('\n') +# Before: >/dev/null 2>&1 +# After: &>/dev/null +# --- +# Before: 2>/dev/null 1>&2 +# After: &>/dev/null +def noVerboseRedirectionFixer(line: str, m: Any) -> str: + prestr, _, poststr = utilGetStrs(line, m) + + return f'{prestr}&>/dev/null{poststr}' + +def lintfile(file: Path, rules: List[Rule], options: Dict[str, Any]): + content_arr = file.read_text().split('\n') for line_i, line in enumerate(content_arr): if 'checkstyle-ignore' in line: continue for rule in rules: + should_run = False + if 'sh' in rule['fileTypes']: + if file.name.endswith('.sh') or str(file.absolute()).endswith('bin/asdf'): + should_run = True + if 'bash' in rule['fileTypes']: + if file.name.endswith('.bash') or file.name.endswith('.bats'): + should_run = True + + if options['verbose']: + print(f'{str(file)}: {should_run}') + + if not should_run: + continue + m = re.search(rule['regex'], line) if m is not None and m.group('match') is not None: - dir = os.path.relpath(filepath.resolve(), Path.cwd()) + dir = os.path.relpath(file.resolve(), Path.cwd()) prestr = line[0:m.start('match')] midstr = line[m.start('match'):m.end('match')] poststr = line[m.end('match'):] @@ -106,7 +130,7 @@ def lintfile(filepath: Path, rules: List[Rule], options: Dict[str, Any]): rule['found'] += 1 if options['fix']: - filepath.write_text('\n'.join(content_arr)) + file.write_text('\n'.join(content_arr)) def main(): rules: List[Rule] = [ @@ -114,6 +138,7 @@ def main(): 'name': 'no-double-backslash', 'regex': '".*?(?P<match>\\\\\\\\[abeEfnrtv\'"?xuUc]).*?(?<!\\\\)"', 'reason': 'Backslashes are only required if followed by a $, `, ", \\, or <newline>', + 'fileTypes': ['bash', 'sh'], 'fixerFn': noDoubleBackslashFixer, 'testPositiveMatches': [ 'printf "%s\\\\n" "Hai"', @@ -123,12 +148,12 @@ def main(): 'printf "%s\\n" "Hai"', 'echo -n "Hello\\n"' ], - 'found': 0 }, { 'name': 'no-pwd-capture', 'regex': '(?P<match>\\$\\(pwd\\))', 'reason': '$PWD is essentially equivalent to $(pwd) without the overhead of a subshell', + 'fileTypes': ['bash', 'sh'], 'fixerFn': noPwdCaptureFixer, 'testPositiveMatches': [ '$(pwd)' @@ -136,12 +161,12 @@ def main(): 'testNegativeMatches': [ '$PWD' ], - 'found': 0 }, { 'name': 'no-test-double-equals', 'regex': '(?<!\\[)\\[ (?:[^]]|](?=}))*?(?P<match>==).*?]', 'reason': 'Disallow double equals in places where they are not necessary for consistency', + 'fileTypes': ['bash', 'sh'], 'fixerFn': noTestDoubleEqualsFixer, 'testPositiveMatches': [ '[ a == b ]', @@ -156,12 +181,12 @@ def main(): '[[ "${lines[0]}" == \'usage: \'* ]]', '[ "${lines[0]}" = blah ]', ], - 'found': 0 }, { 'name': 'no-function-keyword', 'regex': '^[ \\t]*(?P<match>function .*?(?:\\([ \\t]*\\))?[ \\t]*){', 'reason': 'Only allow functions declared like `fn_name() {{ :; }}` for consistency (see ' + c.LINK('https://www.shellcheck.net/wiki/SC2113', 'ShellCheck SC2113') + ')', + 'fileTypes': ['bash', 'sh'], 'fixerFn': noFunctionKeywordFixer, 'testPositiveMatches': [ 'function fn() { :; }', @@ -170,27 +195,43 @@ def main(): 'testNegativeMatches': [ 'fn() { :; }', ], - 'found': 0 + }, + { + 'name': 'no-verbose-redirection', + 'regex': '(?P<match>(>/dev/null 2>&1|2>/dev/null 1>&2))', + 'reason': 'Use `&>/dev/null` instead of `>/dev/null 2>&1` or `2>/dev/null 1>&2` for consistency', + 'fileTypes': ['bash'], + 'fixerFn': noVerboseRedirectionFixer, + 'testPositiveMatches': [ + 'echo woof >/dev/null 2>&1', + 'echo woof 2>/dev/null 1>&2', + ], + 'testNegativeMatches': [ + 'echo woof &>/dev/null', + 'echo woof >&/dev/null', + ], }, ] + [rule.update({ 'found': 0 }) for rule in rules] parser = argparse.ArgumentParser() parser.add_argument('files', metavar='FILES', nargs='*') parser.add_argument('--fix', action='store_true') + parser.add_argument('--verbose', action='store_true') parser.add_argument('--internal-test-regex', action='store_true') args = parser.parse_args() if args.internal_test_regex: for rule in rules: for positiveMatch in rule['testPositiveMatches']: - m = re.search(rule['regex'], positiveMatch) + m: Any = re.search(rule['regex'], positiveMatch) if m is None or m.group('match') is None: print(f'{c.MAGENTA}{rule["name"]}{c.RESET}: Failed {c.CYAN}positive{c.RESET} test:') print(f'=> {positiveMatch}') print() for negativeMatch in rule['testNegativeMatches']: - m = re.search(rule['regex'], negativeMatch) + m: Any = re.search(rule['regex'], negativeMatch) if m is not None and m.group('match') is not None: print(f'{c.MAGENTA}{rule["name"]}{c.RESET}: Failed {c.YELLOW}negative{c.RESET} test:') print(f'=> {negativeMatch}') @@ -199,7 +240,8 @@ def main(): return options = { - 'fix': args.fix + 'fix': args.fix, + 'verbose': args.verbose, } # parse files and print matched lints @@ -210,9 +252,11 @@ def main(): lintfile(p, rules, options) else: for file in Path.cwd().glob('**/*'): - if file.name.endswith('.bash') or file.name.endswith('.sh') or file.name.endswith('.bats'): - if file.is_file(): - lintfile(file, rules, options) + if '.git' in str(file.absolute()): + continue + + if file.is_file(): + lintfile(file, rules, options) # print final results print(f'{c.UNDERLINE}TOTAL ISSUES{c.RESET}') diff --git a/scripts/format.bash b/scripts/format.bash deleted file mode 100755 index 5be8205a8..000000000 --- a/scripts/format.bash +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -# check .sh files -# TODO(jthegedus): unlock this check later -# TODO shfmt --language-dialect posix --indent 2 --write \ -# TODO asdf.sh \ -# TODO lib/*.sh - -# check .bash files -shfmt --language-dialect bash --indent 2 --write \ - completions/*.bash \ - bin/asdf \ - bin/private/asdf-exec \ - lib/utils.bash \ - lib/commands/*.bash \ - lib/functions/*.bash \ - scripts/*.bash \ - test/test_helpers.bash \ - test/fixtures/dummy_broken_plugin/bin/* \ - test/fixtures/dummy_legacy_plugin/bin/* \ - test/fixtures/dummy_plugin/bin/* - -# check .bats files -shfmt --language-dialect bats --indent 2 --write \ - test/*.bats diff --git a/scripts/install_dependencies.bash b/scripts/install_dependencies.bash new file mode 100755 index 000000000..f8c6ecf50 --- /dev/null +++ b/scripts/install_dependencies.bash @@ -0,0 +1,77 @@ +#!/usr/bin/env bash + +set -euo pipefail +IFS=$'\n\t' + +### Used env vars set by default in GitHub Actions +# docs: https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables +# GITHUB_ACTIONS +# RUNNER_OS + +if [ -z "$GITHUB_ACTIONS" ]; then + printf "%s\n" "GITHUB_ACTIONS is not set. This script is only intended to be run in GitHub Actions. Exiting." + exit 1 +fi + +if [ -z "$RUNNER_OS" ]; then + printf "%s\n" "RUNNER_OS is not set. This script is only intended to be run in GitHub Actions. Exiting." + exit 1 +fi + +### Set environment variables for tracking versions +# Elvish +elvish_semver="v0.19.2" +# Fish +fish_semver="3.6.1" +fish_apt_semver="${fish_semver}-1~jammy" +# Nushell +nushell_semver="0.78.0" +# Powershell +powershell_semver="7.3.3" +powershell_apt_semver="${powershell_semver}-1.deb" + +### Install dependencies on Linux +if [ "$RUNNER_OS" = "Linux" ]; then + printf "%s\n" "Installing dependencies on Linux" + + curl -fsSLo- https://packages.microsoft.com/keys/microsoft.asc | sudo tee /etc/apt/trusted.gpg.d/microsoft.asc >/dev/null + sudo sh -c 'echo "deb [arch=amd64] https://packages.microsoft.com/repos/microsoft-debian-bullseye-prod bullseye main" > /etc/apt/sources.list.d/microsoft.list' + sudo add-apt-repository -y ppa:fish-shell/release-3 + sudo apt-get update + sudo apt-get -y install curl parallel \ + fish="${fish_apt_semver}" \ + powershell="${powershell_apt_semver}" + + # Create $HOME/bin + mkdir -p "$HOME/bin" + + # Download elvish binary and add to path + curl https://dl.elv.sh/linux-amd64/elvish-${elvish_semver}.tar.gz -o elvish-${elvish_semver}.tar.gz + tar xzf elvish-${elvish_semver}.tar.gz + rm elvish-${elvish_semver}.tar.gz + mv elvish-${elvish_semver} "$HOME/bin/elvish" + + # Download nushell binary and add to path + curl -L https://github.com/nushell/nushell/releases/download/${nushell_semver}/nu-${nushell_semver}-x86_64-unknown-linux-gnu.tar.gz -o nu-${nushell_semver}-x86_64-unknown-linux-gnu.tar.gz + tar xzf nu-${nushell_semver}-x86_64-unknown-linux-gnu.tar.gz + rm nu-${nushell_semver}-x86_64-unknown-linux-gnu.tar.gz + mv nu-${nushell_semver}-x86_64-unknown-linux-gnu/* "$HOME/bin" + + # Add $HOME/bin to path (add Elvish & Nushell to path) + echo "$HOME/bin" >>"$GITHUB_PATH" +fi + +### Install dependencies on macOS +if [ "$RUNNER_OS" = "macOS" ]; then + printf "%s\n" "Installing dependencies on macOS" + brew install coreutils parallel \ + elvish \ + fish \ + nushell \ + powershell +fi + +### Install bats-core +printf "%s\n" "Installing bats-core" +git clone --depth 1 --branch "v$(grep -Eo "^\\s*bats\\s*.*$" ".tool-versions" | cut -d ' ' -f2-)" https://github.com/bats-core/bats-core.git "$HOME/bats-core" +echo "$HOME/bats-core/bin" >>"$GITHUB_PATH" diff --git a/scripts/lint.bash b/scripts/lint.bash new file mode 100755 index 000000000..392d36b69 --- /dev/null +++ b/scripts/lint.bash @@ -0,0 +1,180 @@ +#!/usr/bin/env bash + +set -euo pipefail +IFS=$'\n\t' + +usage() { + printf "%s\n" "Lint script for the asdf codebase. Must be executed from the" + printf "%s\n\n" "repository root directory." + printf "%s\n\n" "Usage: scripts/lint.bash [options]" + printf "%s\n" "Options:" + printf "%s\n" " -c, --check Error if any issues are found" + printf "%s\n" " -f, --fix Automatically fix issues if possible" + printf "%s\n" " -h, --help Display this help message" +} + +run_shfmt_stylecheck() { + local shfmt_flag="" + if [ "$1" = "fix" ]; then + shfmt_flag="--write" + else + shfmt_flag="--diff" + fi + + printf "%s\n" "[INFO] Checking .bash with shfmt" + shfmt --language-dialect bash --indent 2 "${shfmt_flag}" \ + completions/*.bash \ + bin/asdf \ + bin/private/asdf-exec \ + lib/utils.bash \ + lib/commands/*.bash \ + lib/functions/*.bash \ + scripts/*.bash \ + test/test_helpers.bash \ + test/fixtures/dummy_broken_plugin/bin/* \ + test/fixtures/dummy_legacy_plugin/bin/* \ + test/fixtures/dummy_plugin/bin/* + + printf "%s\n" "[INFO] Checking .bats with shfmt" + shfmt --language-dialect bats --indent 2 "${shfmt_flag}" \ + test/*.bats +} + +run_shellcheck_linter() { + printf "%s\n" "[INFO] Checking .sh files with Shellcheck" + shellcheck --shell sh --external-sources \ + asdf.sh + + printf "%s\n" "[INFO] Checking .bash files with Shellcheck" + shellcheck --shell bash --external-sources \ + completions/*.bash \ + bin/asdf \ + bin/private/asdf-exec \ + lib/utils.bash \ + lib/commands/*.bash \ + lib/functions/*.bash \ + scripts/*.bash \ + test/test_helpers.bash \ + test/fixtures/dummy_broken_plugin/bin/* \ + test/fixtures/dummy_legacy_plugin/bin/* \ + test/fixtures/dummy_plugin/bin/* + + printf "%s\n" "[INFO] Checking .bats files with Shellcheck" + shellcheck --shell bats --external-source \ + test/*.bats +} + +run_custom_python_stylecheck() { + local github_actions=${GITHUB_ACTIONS:-} + local flag= + + if [ "$1" = "fix" ]; then + flag="--fix" + fi + + if [ -n "$github_actions" ] && ! command -v python3 &>/dev/null; then + # fail if CI and no python3 + printf "%s\n" "[ERROR] Detected execution in GitHub Actions but python3 was not found. This is required during CI linting." + exit 1 + fi + + if ! command -v python3 &>/dev/null; then + # skip if local and no python3 + printf "%s\n" "[WARNING] python3 not found. Skipping Custom Python Script." + else + printf "%s\n" "[INFO] Checking files with Custom Python Script." + "${0%/*}/checkstyle.py" "${flag}" + fi + +} + +# TODO: there is no elvish linter/formatter yet +# see https://github.com/elves/elvish/issues/1651 +#run_elvish_linter() { +# printf "%s\n" "[WARNING] elvish linter/formatter not found, skipping for now." +#} + +run_fish_linter() { + local github_actions=${GITHUB_ACTIONS:-} + local flag= + + if [ "$1" = "fix" ]; then + flag="--write" + else + flag="--check" + fi + + if [ -n "$github_actions" ] && ! command -v fish_indent &>/dev/null; then + # fail if CI and no fish_ident + printf "%s\n" "[ERROR] Detected execution in GitHub Actions but fish_indent was not found. This is required during CI linting." + exit 1 + fi + + if ! command -v fish_indent &>/dev/null; then + # skip if local and no fish_ident + printf "%s\n" "[WARNING] fish_indent not found. Skipping .fish files." + else + printf "%s\n" "[INFO] Checking .fish files with fish_indent" + fish_indent "${flag}" ./**/*.fish + fi +} + +# TODO: there is no nushell linter/formatter yet +#run_nushell_linter() { +# printf "%s\n" "[WARNING] nushell linter/formatter not found, skipping for now." +#} + +# TODO: select powershell linter/formatter & setup installation in CI +#run_powershell_linter() { +# printf "%s\n" "[WARNING] powershell linter/formatter not found, skipping for now." +#} + +repo_root=$(git rev-parse --show-toplevel) +if [ "$repo_root" != "$PWD" ]; then + printf "%s\n" "[ERROR] This scripts requires execution from the repository root directory." + printf "\t%s\t%s\n" "Repo root dir:" "$repo_root" + printf "\t%s\t%s\n\n" "Current dir:" "$PWD" + printf "%s\n" "See usage details with -h or --help." + exit 1 +fi + +if [ $# -eq 0 ]; then + printf "%s\n" "[ERROR] At least one option required." + printf "=%.0s" {1..60} + printf "\n" + usage + exit 1 +fi + +mode= +case "$1" in +-h | --help) + usage + exit 0 + ;; +-c | --check) + mode="check" + ;; +-f | --fix) + mode="fix" + ;; +*) + printf "%s\n" "[ERROR] Invalid flag: $1" + printf "=%.0s" {1..60} + printf "\n" + usage + exit 1 + ;; +esac + +printf "%s\"%s\"\n" "[INFO] Executing with mode: " "$mode" + +run_shfmt_stylecheck "$mode" +run_custom_python_stylecheck "$mode" +run_shellcheck_linter "$mode" +run_fish_linter "$mode" +#run_elvish_linter "$mode" +#run_nushell_linter "$mode" +#run_powershell_linter "$mode" + +printf "%s\n" "[INFO] Success!" diff --git a/scripts/shellcheck.bash b/scripts/shellcheck.bash deleted file mode 100755 index 223af27ae..000000000 --- a/scripts/shellcheck.bash +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -# check .sh files -# TODO(jthegedus): unlock this check later -# TODO shellcheck --shell sh --external-sources \ -# TODO asdf.sh \ -# TODO lib/*.sh - -# check .bash files -shellcheck --shell bash --external-sources \ - completions/*.bash \ - bin/asdf \ - bin/private/asdf-exec \ - lib/utils.bash \ - lib/commands/*.bash \ - lib/functions/*.bash \ - scripts/*.bash \ - test/test_helpers.bash \ - test/fixtures/dummy_broken_plugin/bin/* \ - test/fixtures/dummy_legacy_plugin/bin/* \ - test/fixtures/dummy_plugin/bin/* - -shellcheck --shell bats --external-source \ - test/*.bats diff --git a/scripts/shfmt.bash b/scripts/shfmt.bash deleted file mode 100755 index 7f256b865..000000000 --- a/scripts/shfmt.bash +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -# check .sh files -# TODO(jthegedus): unlock this check later -# TODO shfmt --language-dialect posix --indent 2 --diff \ -# TODO asdf.sh \ -# TODO lib/*.sh - -# check .bash files -shfmt --language-dialect bash --indent 2 --diff \ - completions/*.bash \ - bin/asdf \ - bin/private/asdf-exec \ - lib/utils.bash \ - lib/commands/*.bash \ - lib/functions/*.bash \ - scripts/*.bash \ - test/test_helpers.bash \ - test/fixtures/dummy_broken_plugin/bin/* \ - test/fixtures/dummy_legacy_plugin/bin/* \ - test/fixtures/dummy_plugin/bin/* - -# check .bats files -shfmt --language-dialect bats --indent 2 --diff \ - test/*.bats diff --git a/scripts/test.bash b/scripts/test.bash index 5e361fdd9..13148590f 100755 --- a/scripts/test.bash +++ b/scripts/test.bash @@ -1,6 +1,15 @@ #!/usr/bin/env bash set -euo pipefail +IFS=$'\n\t' + +repo_root=$(git rev-parse --show-toplevel) +if [ "$repo_root" != "$PWD" ]; then + printf "%s\n" "[ERROR] This scripts requires execution from the repository root directory." + printf "\t%s\t%s\n" "Repo root dir:" "$repo_root" + printf "\t%s\t%s\n\n" "Current dir:" "$PWD" + exit 1 +fi test_directory="test" bats_options=(--timing --print-output-on-failure) diff --git a/test/asdf_elvish.bats b/test/asdf_elvish.bats index d223c7e47..68b5a4918 100644 --- a/test/asdf_elvish.bats +++ b/test/asdf_elvish.bats @@ -8,12 +8,16 @@ setup() { export XDG_DATA_HOME= export XDG_DATA_DIRS= + if ! command -v elvish &>/dev/null && [ -z "$GITHUB_ACTIONS" ]; then + skip 'Elvish not installed' + fi + local ver_major= local ver_minor= local ver_patch= IFS='.' read -r ver_major ver_minor ver_patch <<<"$(elvish -version)" - if ((ver_major == 0 && ver_minor < 18)); then + if ((ver_major == 0 && ver_minor < 18)) && [ -z "$GITHUB_ACTIONS" ]; then skip "Elvish version is not at least 0.18. Found ${ver_major}.${ver_minor}.${ver_patch}" fi } diff --git a/test/asdf_fish.bats b/test/asdf_fish.bats index 883643335..8c15f7b42 100644 --- a/test/asdf_fish.bats +++ b/test/asdf_fish.bats @@ -6,7 +6,7 @@ load test_helpers setup() { cd "$(dirname "$BATS_TEST_DIRNAME")" - if ! command -v fish; then + if ! command -v fish &>/dev/null && [ -z "$GITHUB_ACTIONS" ]; then skip "Fish is not installed" fi } diff --git a/test/asdf_nu.bats b/test/asdf_nu.bats index 9319e7eae..526442449 100644 --- a/test/asdf_nu.bats +++ b/test/asdf_nu.bats @@ -6,16 +6,22 @@ load test_helpers setup() { cd "$(dirname "$BATS_TEST_DIRNAME")" - if ! command -v nu; then + if ! command -v nu &>/dev/null && [ -z "$GITHUB_ACTIONS" ]; then skip "Nu is not installed" fi + + setup_asdf_dir +} + +teardown() { + clean_asdf_dir } cleaned_path() { echo "$PATH" | tr ':' '\n' | grep -v "asdf" | tr '\n' ':' } -@test "exports ASDF_DIR" { +run_nushell() { run nu -c " hide-env -i asdf hide-env -i ASDF_DIR @@ -23,8 +29,11 @@ cleaned_path() { let-env ASDF_NU_DIR = '$PWD' source asdf.nu + $1" +} - echo \$env.ASDF_DIR" +@test "exports ASDF_DIR" { + run_nushell "echo \$env.ASDF_DIR" [ "$status" -eq 0 ] result=$(echo "$output" | grep "asdf") @@ -32,16 +41,7 @@ cleaned_path() { } @test "adds asdf dirs to PATH" { - run nu -c " - hide-env -i asdf - hide-env -i ASDF_DIR - let-env PATH = ( '$(cleaned_path)' | split row ':' ) - let-env ASDF_NU_DIR = '$PWD' - - source asdf.nu - - - \$env.PATH | to text" + run_nushell "\$env.PATH | to text" [ "$status" -eq 0 ] @@ -50,15 +50,8 @@ cleaned_path() { } @test "does not add paths to PATH more than once" { - run nu -c " - hide-env -i asdf - hide-env -i ASDF_DIR - let-env PATH = ( '$(cleaned_path)' | split row ':' ) - let-env ASDF_NU_DIR = '$PWD' - + run_nushell " source asdf.nu - source asdf.nu - echo \$env.PATH" [ "$status" -eq 0 ] @@ -83,30 +76,85 @@ cleaned_path() { } @test "defines the asdf or main function" { - run nu -c " - hide-env -i asdf - hide-env -i ASDF_DIR - let-env PATH = ( '$(cleaned_path)' | split row ':' ) - let-env ASDF_NU_DIR = '$PWD' - - source asdf.nu - which asdf | get path | to text" + run_nushell "which asdf | get path | to text" [ "$status" -eq 0 ] } @test "function calls asdf command" { - run nu -c " - hide-env -i asdf - hide-env -i ASDF_DIR - let-env PATH = ( '$(cleaned_path)' | split row ':' ) - let-env ASDF_NU_DIR = '$PWD' - - source asdf.nu - asdf info" + run_nushell "asdf info" [ "$status" -eq 0 ] result=$(echo "$output" | grep "ASDF INSTALLED PLUGINS:") [ "$result" != "" ] } + +@test "parses the output of asdf plugin list" { + setup_repo + install_dummy_plugin + run_nushell "asdf plugin list | to csv -n" + + [ "$status" -eq 0 ] + [ "$output" = "dummy" ] +} + +@test "parses the output of asdf plugin list --urls" { + setup_repo + install_mock_plugin_repo "dummy" + asdf plugin add "dummy" "${BASE_DIR}/repo-dummy" + + run_nushell "asdf plugin list --urls | to csv -n" + + [ "$status" -eq 0 ] + + local repo_url + repo_url=$(get_plugin_remote_url "dummy") + + [ "$output" = "dummy,$repo_url" ] +} + +@test "parses the output of asdf plugin list --refs" { + setup_repo + install_mock_plugin_repo "dummy" + asdf plugin add "dummy" "${BASE_DIR}/repo-dummy" + + run_nushell "asdf plugin list --refs | to csv -n" + + [ "$status" -eq 0 ] + + local branch gitref + branch=$(get_plugin_remote_branch "dummy") + gitref=$(get_plugin_remote_gitref "dummy") + + [ "$output" = "dummy,$branch,$gitref" ] +} + +@test "parses the output of asdf plugin list --urls --refs" { + setup_repo + install_mock_plugin_repo "dummy" + asdf plugin add "dummy" "${BASE_DIR}/repo-dummy" + + run_nushell "asdf plugin list --urls --refs | to csv -n" + + [ "$status" -eq 0 ] + + local repo_url branch gitref + repo_url=$(get_plugin_remote_url "dummy") + branch=$(get_plugin_remote_branch "dummy") + gitref=$(get_plugin_remote_gitref "dummy") + + [ "$output" = "dummy,$repo_url,$branch,$gitref" ] +} + +@test "parses the output of asdf plugin list all" { + setup_repo + install_dummy_plugin + run_nushell "asdf plugin list all | to csv -n" + + [ "$status" -eq 0 ] + [ "$output" = "\ +bar,false,http://example.com/bar +dummy,true,http://example.com/dummy +foo,false,http://example.com/foo" ] +} diff --git a/test/asdf_pwsh.bats b/test/asdf_pwsh.bats new file mode 100644 index 000000000..4f27d12f6 --- /dev/null +++ b/test/asdf_pwsh.bats @@ -0,0 +1,96 @@ +#!/usr/bin/env bats +# shellcheck disable=SC2164 + +load test_helpers + +setup() { + cd "$(dirname "$BATS_TEST_DIRNAME")" + + if ! command -v pwsh &>/dev/null && [ -z "$GITHUB_ACTIONS" ]; then + skip "Powershell Core is not installed" + fi +} + +cleaned_path() { + echo "$PATH" | tr ':' '\n' | grep -v "asdf" | tr '\n' ':' +} + +@test "exports ASDF_DIR" { + run pwsh -Command " + function asdf() {} # checkstyle-ignore + Remove-item Function:asdf + \$Env:ASDF_DIR = '' + \$Env:ASDF_DATA_DIR = '' + \$Env:PATH = \"$(cleaned_path)\" + + . ./asdf.ps1 + Write-Output \"\$env:ASDF_DIR\"" + + [ "$status" -eq 0 ] + [ "$output" != "" ] +} + +@test "adds asdf dirs to PATH" { + run pwsh -Command " + function asdf() {} # checkstyle-ignore + Remove-item Function:asdf + \$Env:ASDF_DIR = '' + \$Env:ASDF_DATA_DIR = '' + \$Env:PATH = \"$(cleaned_path)\" + + . ./asdf.ps1 + Write-Output \$Env:PATH" + + [ "$status" -eq 0 ] + result=$(echo "$output" | grep "asdf") + [ "$result" != "" ] +} + +@test "does not add paths to PATH more than once" { + run pwsh -Command " + function asdf() {} # checkstyle-ignore + Remove-item Function:asdf + \$Env:ASDF_DIR = '' + \$Env:ASDF_DATA_DIR = '' + \$Env:PATH = \"$(cleaned_path)\" + + . ./asdf.ps1 + . ./asdf.ps1 + Write-Output \$Env:PATH" + + [ "$status" -eq 0 ] + + result=$(echo "$output" | tr ' ' '\n' | grep "asdf" | sort | uniq -d) + [ "$result" = "" ] +} + +@test "defines the asdf function" { + run pwsh -Command " + function asdf() {} # checkstyle-ignore + Remove-item Function:asdf + \$Env:ASDF_DIR = '' + \$Env:ASDF_DATA_DIR = '' + \$Env:PATH = \"$(cleaned_path)\" + + ./ asdf.ps1 + \$(Get-Command -CommandType asdf).Name" + + [ "$status" -eq 0 ] + [[ "$output" =~ "asdf" ]] +} + +@test "function calls asdf command" { + run pwsh -Command " + function asdf() {} # checkstyle-ignore + Remove-item Function:asdf + \$Env:ASDF_DIR = '' + \$Env:ASDF_DATA_DIR = '' + \$Env:PATH = \"$(cleaned_path)\" + + . ./asdf.ps1 + asdf info" + + [ "$status" -eq 0 ] + result=$(echo "$output" | grep "ASDF INSTALLED PLUGINS:") + [ "$result" != "" ] +} diff --git a/test/banned_commands.bats b/test/banned_commands.bats index b947e1895..84f8bf2a9 100644 --- a/test/banned_commands.bats +++ b/test/banned_commands.bats @@ -16,8 +16,6 @@ banned_commands=( eval # realpath not available by default on OSX. realpath - # readlink on OSX behaves differently from readlink on other Unix systems - readlink # source isn't POSIX compliant. . behaves the same and is POSIX compliant # Except in fish, where . is deprecated, and will be removed in the future. source @@ -32,6 +30,8 @@ banned_commands_regex=( "grep.* -P" # Ban grep long commands as they do not work on alpine "grep[^|]+--\w{2,}" + # readlink -f on OSX behaves differently from readlink -f on other Unix systems + 'readlink.+-.*f.+["$]' # sort --sort-version isn't supported everywhere "sort.*-V" "sort.*--sort-versions" diff --git a/test/info_command.bats b/test/info_command.bats index f456df1e3..3a8fc05d6 100644 --- a/test/info_command.bats +++ b/test/info_command.bats @@ -25,8 +25,9 @@ teardown() { [ "$status" -eq 0 ] [[ $output == *$'OS:\n'* ]] [[ $output == *$'SHELL:\n'* ]] + [[ $output == *$'BASH VERSION:\n'* ]] [[ $output == *$'ASDF VERSION:\n'* ]] - [[ $output == *$'ASDF ENVIRONMENT VARIABLES:\n'* ]] + [[ $output == *$'ASDF INTERNAL VARIABLES:\n'* ]] [[ $output == *$'ASDF INSTALLED PLUGINS:\n'* ]] } diff --git a/test/install_command.bats b/test/install_command.bats index dd90aa629..bd945cbe9 100644 --- a/test/install_command.bats +++ b/test/install_command.bats @@ -52,6 +52,25 @@ teardown() { [ "$status" -eq 0 ] } +@test "install_command set ASDF_CONCURRENCY via env var" { + ASDF_CONCURRENCY=-1 run asdf install dummy 1.0.0 + [ "$status" -eq 0 ] + [ -f "$ASDF_DIR/installs/dummy/1.0.0/env" ] + run grep ASDF_CONCURRENCY=-1 "$ASDF_DIR/installs/dummy/1.0.0/env" + [ "$status" -eq 0 ] +} + +@test "install_command set ASDF_CONCURRENCY via asdfrc" { + cat >"$HOME/.asdfrc" <<-'EOM' + concurrency = -2 +EOM + run asdf install dummy 1.0.0 + [ "$status" -eq 0 ] + [ -f "$ASDF_DIR/installs/dummy/1.0.0/env" ] + run grep ASDF_CONCURRENCY=-2 "$ASDF_DIR/installs/dummy/1.0.0/env" + [ "$status" -eq 0 ] +} + @test "install_command without arguments should work in directory containing whitespace" { WHITESPACE_DIR="$PROJECT_DIR/whitespace\ dir" mkdir -p "$WHITESPACE_DIR" diff --git a/test/latest_command.bats b/test/latest_command.bats index 795327df9..9c0bd5af3 100644 --- a/test/latest_command.bats +++ b/test/latest_command.bats @@ -38,48 +38,36 @@ teardown() { #################################################### @test "[latest_command - dummy_legacy_plugin] shows latest stable version" { run asdf latest legacy-dummy - echo "status: $status" - echo "output: $output" [ "5.1.0" = "$output" ] [ "$status" -eq 0 ] } @test "[latest_command - dummy_legacy_plugin] shows latest stable version that matches the given string" { run asdf latest legacy-dummy 1 - echo "status: $status" - echo "output: $output" [ "1.1.0" = "$output" ] [ "$status" -eq 0 ] } @test "[latest_command - dummy_legacy_plugin] No stable version should return an error" { run asdf latest legacy-dummy 3 - echo "status: $status" - echo "output: $output" [ -z "$output" ] [ "$status" -eq 1 ] } @test "[latest_command - dummy_legacy_plugin] do not show latest unstable version that matches the given string" { run asdf latest legacy-dummy 4 - echo "status: $status" - echo "output: $output" [ "4.0.0" = "$output" ] [ "$status" -eq 0 ] } @test "[latest_command - dummy_legacy_plugin] do not show latest unstable version with capital characters that matches the given string" { run asdf latest legacy-dummy 5 - echo "status: $status" - echo "output: $output" [ "5.1.0" = "$output" ] [ "$status" -eq 0 ] } @test "[latest_command - dummy_legacy_plugin] an invalid version should return an error" { run asdf latest legacy-dummy 6 - echo "status: $status" - echo "output: $output" [ "No compatible versions available (legacy-dummy 6)" = "$output" ] [ "$status" -eq 1 ] } @@ -91,13 +79,12 @@ teardown() { run asdf install dummy 2.0.0 run asdf install legacy-dummy 4.0.0 run asdf latest --all - echo "output $output" - [ "$(echo -e "dummy\t2.0.0\tinstalled\nlegacy-dummy\t5.1.0\tmissing\n")" = "$output" ] + [ $'dummy\t2.0.0\tinstalled\nlegacy-dummy\t5.1.0\tmissing' = "$output" ] [ "$status" -eq 0 ] } @test "[latest_command - all plugins] not installed plugin should return missing" { run asdf latest --all - [ "$(echo -e "dummy\t2.0.0\tmissing\nlegacy-dummy\t5.1.0\tmissing\n")" = "$output" ] + [ $'dummy\t2.0.0\tmissing\nlegacy-dummy\t5.1.0\tmissing' = "$output" ] [ "$status" -eq 0 ] } diff --git a/test/list_command.bats b/test/list_command.bats index 102304659..f21f2cd1b 100644 --- a/test/list_command.bats +++ b/test/list_command.bats @@ -19,8 +19,8 @@ teardown() { run asdf install dummy 1.0.0 run asdf install dummy 1.1.0 run asdf list - [[ "$output" == *"$(echo -e "dummy\n 1.0.0\n 1.1.0")"* ]] - [[ "$output" == *"$(echo -e "dummy-broken\n No versions installed")"* ]] + [[ "$output" == *$'dummy\n 1.0.0\n 1.1.0'* ]] + [[ "$output" == *$'dummy-broken\n No versions installed'* ]] [ "$status" -eq 0 ] } @@ -31,8 +31,8 @@ teardown() { run asdf install dummy 1.1.0 run asdf list - [[ "$output" == *"$(echo -e "dummy\n 1.0.0\n *1.1.0")"* ]] - [[ "$output" == *"$(echo -e "dummy-broken\n No versions installed")"* ]] + [[ "$output" == *$'dummy\n 1.0.0\n *1.1.0'* ]] + [[ "$output" == *$'dummy-broken\n No versions installed'* ]] [ "$status" -eq 0 ] } @@ -43,10 +43,10 @@ teardown() { run asdf install dummy 1.0.0 run asdf install tummy 2.0.0 run asdf list - [[ "$output" == *"$(echo -e "dummy\n 1.0.0")"* ]] - [[ "$output" == *"$(echo -e "dummy-broken\n No versions installed")"* ]] - [[ "$output" == *"$(echo -e "mummy\n No versions installed")"* ]] - [[ "$output" == *"$(echo -e "tummy\n 2.0.0")"* ]] + [[ "$output" == *$'dummy\n 1.0.0'* ]] + [[ "$output" == *$'dummy-broken\n No versions installed'* ]] + [[ "$output" == *$'mummy\n No versions installed'* ]] + [[ "$output" == *$'tummy\n 2.0.0'* ]] [ "$status" -eq 0 ] } @@ -54,7 +54,7 @@ teardown() { run asdf install dummy 1.0.0 run asdf install dummy 1.1.0 run asdf list dummy - [ "$(echo -e " 1.0.0\n 1.1.0")" = "$output" ] + [ $' 1.0.0\n 1.1.0' = "$output" ] [ "$status" -eq 0 ] } @@ -63,7 +63,7 @@ teardown() { run asdf install dummy 1.1 run asdf install dummy 2.0 run asdf list dummy 1 - [ "$(echo -e " 1.0\n 1.1")" = "$output" ] + [ $' 1.0\n 1.1' = "$output" ] [ "$status" -eq 0 ] } @@ -77,13 +77,13 @@ teardown() { @test "list_all_command lists available versions" { run asdf list-all dummy - [ "$(echo -e "1.0.0\n1.1.0\n2.0.0")" = "$output" ] + [ $'1.0.0\n1.1.0\n2.0.0' = "$output" ] [ "$status" -eq 0 ] } @test "list_all_command with version filters available versions" { run asdf list-all dummy 1 - [ "$(echo -e "1.0.0\n1.1.0")" = "$output" ] + [ $'1.0.0\n1.1.0' = "$output" ] [ "$status" -eq 0 ] } diff --git a/test/plugin_test_command.bats b/test/plugin_test_command.bats index 1b486649d..c725450c9 100644 --- a/test/plugin_test_command.bats +++ b/test/plugin_test_command.bats @@ -25,14 +25,10 @@ teardown() { @test "plugin_test_command works with no options provided" { run asdf plugin-test dummy "${BASE_DIR}/repo-dummy" - echo "status = ${status}" - echo "output = ${output}" [ "$status" -eq 0 ] } @test "plugin_test_command works with all options provided" { run asdf plugin-test dummy "${BASE_DIR}/repo-dummy" --asdf-tool-version 1.0.0 --asdf-plugin-gitref master - echo "status = ${status}" - echo "output = ${output}" [ "$status" -eq 0 ] } diff --git a/test/reshim_command.bats b/test/reshim_command.bats index dd98c882b..4b1db7ef5 100644 --- a/test/reshim_command.bats +++ b/test/reshim_command.bats @@ -4,7 +4,7 @@ load test_helpers setup() { - ASDF_BATS_SPACE_IN_PATH=true setup_asdf_dir + setup_asdf_dir install_dummy_plugin PROJECT_DIR="$HOME/project" @@ -157,3 +157,19 @@ EOM run grep -v 'borked_path_due_to_homebrew_update' "$dummy_shim" [ "$status" -eq 0 ] } + +@test "reshim should allow local path versions" { + run asdf install dummy 1.0 + + mkdir -p "$ASDF_DIR/installs/dummy/path/bin/" + touch "$ASDF_DIR/installs/dummy/path/bin/dummy" + chmod +x "$ASDF_DIR/installs/dummy/path/bin/dummy" + + run asdf reshim dummy "path:$ASDF_DIR/installs/dummy/path" + + [ "$status" -eq 0 ] + run grep "asdf-plugin: dummy 1.0" "$ASDF_DIR/shims/dummy" + [ "$status" -eq 0 ] + run grep "asdf-plugin: dummy path:$ASDF_DIR/installs/dummy" "$ASDF_DIR/shims/dummy" + [ "$status" -eq 0 ] +} diff --git a/test/shim_exec.bats b/test/shim_exec.bats index ad8a89603..5c6d0673e 100644 --- a/test/shim_exec.bats +++ b/test/shim_exec.bats @@ -206,6 +206,8 @@ teardown() { [ "$output" = "System" ] } +# NOTE: The name of this test is linked to a condition in `test_helpers.bash. See +# the 'setup_asdf_dir' function for details. @test "shim exec should use path executable when specified version path:<path>" { run asdf install dummy 1.0 @@ -331,7 +333,6 @@ teardown() { chmod +x "$PROJECT_DIR/sys/dummy" run env "PATH=$PATH:$PROJECT_DIR/sys" "$ASDF_DIR/shims/dummy" - echo "$status $output" [ "$output" = "$ASDF_DIR/shims/dummy" ] } diff --git a/test/test_helpers.bash b/test/test_helpers.bash index 814742ce4..103da76cd 100644 --- a/test/test_helpers.bash +++ b/test/test_helpers.bash @@ -6,11 +6,12 @@ bats_require_minimum_version 1.7.0 . "$(dirname "$BATS_TEST_DIRNAME")"/lib/utils.bash setup_asdf_dir() { - if [ -n "${ASDF_BATS_SPACE_IN_PATH:-}" ]; then - BASE_DIR="$(mktemp -dt "asdf with spaces.XXXX")" + if [ "$BATS_TEST_NAME" = 'test_shim_exec_should_use_path_executable_when_specified_version_path-3a-3cpath-3e' ]; then + BASE_DIR="$(mktemp -dt "asdf_with_no_spaces.XXXX")" else - BASE_DIR="$(mktemp -dt asdf.XXXX)" + BASE_DIR="$(mktemp -dt "asdf with spaces.XXXX")" fi + HOME="$BASE_DIR/home" ASDF_DIR="$HOME/.asdf" mkdir -p "$ASDF_DIR/plugins" diff --git a/test/uninstall_command.bats b/test/uninstall_command.bats index a55134682..c5bef1b46 100644 --- a/test/uninstall_command.bats +++ b/test/uninstall_command.bats @@ -32,7 +32,7 @@ teardown() { run asdf install dummy 1.1.0 [ "$status" -eq 0 ] mkdir -p "$ASDF_DIR/plugins/dummy/bin" - echo "echo custom uninstall" >"$ASDF_DIR/plugins/dummy/bin/uninstall" + printf '%s\n' "echo custom uninstall" >"$ASDF_DIR/plugins/dummy/bin/uninstall" chmod 755 "$ASDF_DIR/plugins/dummy/bin/uninstall" run asdf uninstall dummy 1.1.0 [ "$output" = "custom uninstall" ] diff --git a/test/update_command.bats b/test/update_command.bats index c8333673f..9e377f16c 100644 --- a/test/update_command.bats +++ b/test/update_command.bats @@ -61,14 +61,14 @@ teardown() { touch "$ASDF_DIR/asdf_updates_disabled" run asdf update [ "$status" -eq 42 ] - [ "$(echo -e "Update command disabled. Please use the package manager that you used to install asdf to upgrade asdf.")" = "$output" ] + [ $'Update command disabled. Please use the package manager that you used to install asdf to upgrade asdf.' = "$output" ] } @test "asdf update is a noop for non-git repos" { rm -rf "$ASDF_DIR/.git/" run asdf update [ "$status" -eq 42 ] - [ "$(echo -e "Update command disabled. Please use the package manager that you used to install asdf to upgrade asdf.")" = "$output" ] + [ $'Update command disabled. Please use the package manager that you used to install asdf to upgrade asdf.' = "$output" ] } @test "asdf update fails with exit code 1" { diff --git a/test/utils.bats b/test/utils.bats index 288a2e228..39f29ebac 100644 --- a/test/utils.bats +++ b/test/utils.bats @@ -43,7 +43,6 @@ teardown() { run get_download_path foo version "1.0.0" [ "$status" -eq 0 ] download_path=${output#"$HOME/"} - echo "$download_path" [ "$download_path" = ".asdf/downloads/foo/1.0.0" ] } @@ -461,3 +460,16 @@ EOF [ "$status" -eq 0 ] [ "$output" = "$message" ] } + +@test "prints warning if .tool-versions file has carriage returns" { + ASDF_CONFIG_FILE="$BATS_TEST_TMPDIR/asdfrc" + cat >"$ASDF_CONFIG_FILE" <<<$'key2 = value2\r' + + [[ "$(get_asdf_config_value "key1" 2>&1)" = *"contains carriage returns"* ]] +} + +@test "prints if asdfrc config file has carriage returns" { + cat >".tool-versions" <<<$'nodejs 19.6.0\r' + + [[ "$(find_tool_versions 2>&1)" = *"contains carriage returns"* ]] +} diff --git a/version.txt b/version.txt index 1a96df19c..ac454c6a1 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -0.11.3 +0.12.0